From 7b7a53ee19c71edf8e8970111426215a6378c53f Mon Sep 17 00:00:00 2001 From: Alex Newman Date: Tue, 21 Oct 2025 23:07:23 -0400 Subject: [PATCH] feat(context-hook): simplify to 3-tier system with 10 sessions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New structure (10 sessions total): - Tier 3 (oldest 6): Request + Date only - Tier 2 (middle 3): Request + Learned + Completed + Date - Tier 1 (most recent): Full verbosity (all fields) This provides cleaner, more focused context with less redundancy. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- plugin/scripts/context-hook.js | 22 +++---- src/hooks/context.ts | 114 +++++++++++++++++---------------- 2 files changed, 70 insertions(+), 66 deletions(-) diff --git a/plugin/scripts/context-hook.js b/plugin/scripts/context-hook.js index 51e76694..595daa1e 100755 --- a/plugin/scripts/context-hook.js +++ b/plugin/scripts/context-hook.js @@ -1,7 +1,7 @@ #!/usr/bin/env node -import V from"path";import H from"better-sqlite3";import{join as _,dirname as F,basename as J}from"path";import{homedir as x}from"os";import{existsSync as ee,mkdirSync as P}from"fs";import{fileURLToPath as G}from"url";function W(){return typeof __dirname<"u"?__dirname:F(G(import.meta.url))}var j=W(),m=process.env.CLAUDE_MEM_DATA_DIR||_(x(),".claude-mem"),R=process.env.CLAUDE_CONFIG_DIR||_(x(),".claude"),te=_(m,"archives"),re=_(m,"logs"),ne=_(m,"trash"),ie=_(m,"backups"),oe=_(m,"settings.json"),k=_(m,"claude-mem.db"),ae=_(R,"settings.json"),de=_(R,"commands"),pe=_(R,"CLAUDE.md");function U(p){P(p,{recursive:!0})}function w(){return _(j,"..","..")}var I=(i=>(i[i.DEBUG=0]="DEBUG",i[i.INFO=1]="INFO",i[i.WARN=2]="WARN",i[i.ERROR=3]="ERROR",i[i.SILENT=4]="SILENT",i))(I||{}),O=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=I[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message} -${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,i){if(e0&&(g=` {${Object.entries(u).map(([T,l])=>`${T}=${l}`).join(", ")}}`)}let h=`[${d}] [${n}] [${c}] ${E}${t}${g}${a}`;e===3?console.error(h):console.log(h)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},$=new O;var S=class{db;constructor(){U(m),this.db=new H(k),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable()}initializeSchema(){try{this.db.exec(` +import V from"path";import B from"better-sqlite3";import{join as E,dirname as P,basename as Q}from"path";import{homedir as x}from"os";import{existsSync as se,mkdirSync as G}from"fs";import{fileURLToPath as W}from"url";function j(){return typeof __dirname<"u"?__dirname:P(W(import.meta.url))}var H=j(),m=process.env.CLAUDE_MEM_DATA_DIR||E(x(),".claude-mem"),I=process.env.CLAUDE_CONFIG_DIR||E(x(),".claude"),re=E(m,"archives"),ne=E(m,"logs"),ie=E(m,"trash"),oe=E(m,"backups"),ae=E(m,"settings.json"),k=E(m,"claude-mem.db"),de=E(I,"settings.json"),pe=E(I,"commands"),ce=E(I,"CLAUDE.md");function U(p){G(p,{recursive:!0})}function w(){return E(H,"..","..")}var O=(i=>(i[i.DEBUG=0]="DEBUG",i[i.INFO=1]="INFO",i[i.WARN=2]="WARN",i[i.ERROR=3]="ERROR",i[i.SILENT=4]="SILENT",i))(O||{}),L=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=O[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message} +${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,i){if(e0&&(u=` {${Object.entries(l).map(([T,f])=>`${T}=${f}`).join(", ")}}`)}let g=`[${d}] [${n}] [${c}] ${_}${t}${u}${a}`;e===3?console.error(g):console.log(g)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},$=new L;var b=class{db;constructor(){U(m),this.db=new B(k),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable()}initializeSchema(){try{this.db.exec(` CREATE TABLE IF NOT EXISTS schema_versions ( id INTEGER PRIMARY KEY, version INTEGER UNIQUE NOT NULL, @@ -306,13 +306,13 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje UPDATE sdk_sessions SET status = 'failed', completed_at = ?, completed_at_epoch = ? WHERE status = 'active' - `).run(e.toISOString(),s).changes}close(){this.db.close()}};import L from"path";import{existsSync as A}from"fs";import{spawn as B}from"child_process";var Y=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),K=`http://127.0.0.1:${Y}/health`;async function M(){try{return(await fetch(K,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function X(){try{if(await M())return!0;console.error("[claude-mem] Worker not responding, starting...");let p=w(),e=L.join(p,"plugin","scripts","worker-service.cjs");if(!A(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=L.join(p,"ecosystem.config.cjs"),t=L.join(p,"node_modules",".bin","pm2");if(!A(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!A(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=B(t,["start",s],{detached:!0,stdio:"ignore",cwd:p});r.on("error",i=>{throw new Error(`Failed to spawn PM2: ${i.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let i=0;i<3;i++)if(await new Promise(d=>setTimeout(d,500)),await M())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(p){return console.error(`[claude-mem] Failed to start worker: ${p.message}`),!1}}var o={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",gray:"\x1B[90m"};function v(p,e=!1,s=!1){X();let t=p?.cwd??process.cwd(),r=t?V.basename(t):"unknown-project",i=new S;try{let d=i.db.prepare(` + `).run(e.toISOString(),s).changes}close(){this.db.close()}};import A from"path";import{existsSync as v}from"fs";import{spawn as Y}from"child_process";var q=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),K=`http://127.0.0.1:${q}/health`;async function M(){try{return(await fetch(K,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function X(){try{if(await M())return!0;console.error("[claude-mem] Worker not responding, starting...");let p=w(),e=A.join(p,"plugin","scripts","worker-service.cjs");if(!v(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=A.join(p,"ecosystem.config.cjs"),t=A.join(p,"node_modules",".bin","pm2");if(!v(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!v(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=Y(t,["start",s],{detached:!0,stdio:"ignore",cwd:p});r.on("error",i=>{throw new Error(`Failed to spawn PM2: ${i.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let i=0;i<3;i++)if(await new Promise(d=>setTimeout(d,500)),await M())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(p){return console.error(`[claude-mem] Failed to start worker: ${p.message}`),!1}}var o={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",gray:"\x1B[90m"};function y(p,e=!1,s=!1){X();let t=p?.cwd??process.cwd(),r=t?V.basename(t):"unknown-project",i=new b;try{let d=i.db.prepare(` SELECT * FROM ( SELECT sdk_session_id, request, learned, completed, next_steps, created_at, created_at_epoch FROM session_summaries WHERE project = ? ORDER BY created_at_epoch DESC - LIMIT 30 + LIMIT 10 ) ORDER BY created_at_epoch ASC `).all(r);if(d.length===0)return e?` @@ -322,9 +322,9 @@ ${o.gray}${"\u2500".repeat(60)}${o.reset} ${o.dim}No previous summaries found for this project yet.${o.reset} `:`# [${r}] recent context -No previous summaries found for this project yet.`;let n=[];e?(n.push(""),n.push(`${o.bright}${o.cyan}\u{1F4DD} [${r}] recent context${o.reset}`),n.push(`${o.gray}${"\u2500".repeat(60)}${o.reset}`)):(n.push(`# [${r}] recent context`),n.push(""));let c=!0;for(let E=0;E4;if(c?e&&n.push(""):e?(n.push(`${o.gray}${"\u2500".repeat(60)}${o.reset}`),n.push("")):(n.push("---"),n.push("")),c=!1,C){a.learned&&(e?(n.push(`${o.bright}${o.blue}Learned:${o.reset} ${a.learned}`),n.push(""),n.push(`${o.dim}[Details: claude-mem://session/${a.sdk_session_id}]${o.reset}`)):(n.push(`**Learned:** ${a.learned}`),n.push(""),n.push(`[Details: claude-mem://session/${a.sdk_session_id}]`),n.push("")));continue}a.request&&(e?(n.push(`${o.bright}${o.yellow}Request:${o.reset} ${a.request}`),n.push("")):(n.push(`**Request:** ${a.request}`),n.push(""))),a.learned&&(e?(n.push(`${o.bright}${o.blue}Learned:${o.reset} ${a.learned}`),n.push("")):(n.push(`**Learned:** ${a.learned}`),n.push(""))),a.completed&&(e?(n.push(`${o.bright}${o.green}Completed:${o.reset} ${a.completed}`),n.push("")):(n.push(`**Completed:** ${a.completed}`),n.push(""))),a.next_steps&&(e?(n.push(`${o.bright}${o.magenta}Next Steps:${o.reset} ${a.next_steps}`),n.push("")):(n.push(`**Next Steps:** ${a.next_steps}`),n.push("")));let D=i.db.prepare(` - SELECT files_read, files_modified - FROM observations - WHERE sdk_session_id = ? - `).all(a.sdk_session_id),f=new Set,u=new Set;for(let T of D){if(T.files_read)try{let l=JSON.parse(T.files_read);Array.isArray(l)&&l.forEach(N=>f.add(N))}catch{}if(T.files_modified)try{let l=JSON.parse(T.files_modified);Array.isArray(l)&&l.forEach(N=>u.add(N))}catch{}}h&&f.size>0&&(e?n.push(`${o.dim}Files Read: ${Array.from(f).join(", ")}${o.reset}`):n.push(`**Files Read:** ${Array.from(f).join(", ")}`)),u.size>0&&(e?n.push(`${o.dim}Files Modified: ${Array.from(u).join(", ")}${o.reset}`):n.push(`**Files Modified:** ${Array.from(u).join(", ")}`));let b=new Date(a.created_at).toLocaleString();e?n.push(`${o.dim}Date: ${b}${o.reset}`):n.push(`**Date:** ${b}`),e||n.push("")}return e&&(n.push(""),n.push(`${o.gray}${"\u2500".repeat(60)}${o.reset}`)),n.join(` -`)}finally{i.close()}}import{stdin as y}from"process";try{let p=process.argv.includes("--index");if(y.isTTY){let e=v(void 0,!0,p);console.log(e),process.exit(0)}else{let e="";y.on("data",s=>e+=s),y.on("end",()=>{let s=e.trim()?JSON.parse(e):void 0,r={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:v(s,!1,p)}};console.log(JSON.stringify(r)),process.exit(0)})}}catch(p){console.error(`[claude-mem context-hook error: ${p.message}]`),process.exit(0)} +No previous summaries found for this project yet.`;let n=[];e?(n.push(""),n.push(`${o.bright}${o.cyan}\u{1F4DD} [${r}] recent context${o.reset}`),n.push(`${o.gray}${"\u2500".repeat(60)}${o.reset}`)):(n.push(`# [${r}] recent context`),n.push(""));let c=!0;for(let _=0;_=1&&u<=3,D=u>3;if(c?e&&n.push(""):e?(n.push(`${o.gray}${"\u2500".repeat(60)}${o.reset}`),n.push("")):(n.push("---"),n.push("")),c=!1,D){a.request&&(e?(n.push(`${o.bright}${o.yellow}Request:${o.reset} ${a.request}`),n.push("")):(n.push(`**Request:** ${a.request}`),n.push("")));let l=new Date(a.created_at).toLocaleString();e?n.push(`${o.dim}Date: ${l}${o.reset}`):(n.push(`**Date:** ${l}`),n.push(""));continue}if(a.request&&(e?(n.push(`${o.bright}${o.yellow}Request:${o.reset} ${a.request}`),n.push("")):(n.push(`**Request:** ${a.request}`),n.push(""))),a.learned&&(e?(n.push(`${o.bright}${o.blue}Learned:${o.reset} ${a.learned}`),n.push("")):(n.push(`**Learned:** ${a.learned}`),n.push(""))),a.completed&&(e?(n.push(`${o.bright}${o.green}Completed:${o.reset} ${a.completed}`),n.push("")):(n.push(`**Completed:** ${a.completed}`),n.push(""))),g&&a.next_steps&&(e?(n.push(`${o.bright}${o.magenta}Next Steps:${o.reset} ${a.next_steps}`),n.push("")):(n.push(`**Next Steps:** ${a.next_steps}`),n.push(""))),g){let l=i.db.prepare(` + SELECT files_read, files_modified + FROM observations + WHERE sdk_session_id = ? + `).all(a.sdk_session_id),h=new Set,T=new Set;for(let f of l){if(f.files_read)try{let S=JSON.parse(f.files_read);Array.isArray(S)&&S.forEach(R=>h.add(R))}catch{}if(f.files_modified)try{let S=JSON.parse(f.files_modified);Array.isArray(S)&&S.forEach(R=>T.add(R))}catch{}}h.size>0&&(e?n.push(`${o.dim}Files Read: ${Array.from(h).join(", ")}${o.reset}`):n.push(`**Files Read:** ${Array.from(h).join(", ")}`)),T.size>0&&(e?n.push(`${o.dim}Files Modified: ${Array.from(T).join(", ")}${o.reset}`):n.push(`**Files Modified:** ${Array.from(T).join(", ")}`))}let N=new Date(a.created_at).toLocaleString();e?n.push(`${o.dim}Date: ${N}${o.reset}`):n.push(`**Date:** ${N}`),e||n.push("")}return e&&(n.push(""),n.push(`${o.gray}${"\u2500".repeat(60)}${o.reset}`)),n.join(` +`)}finally{i.close()}}import{stdin as C}from"process";try{let p=process.argv.includes("--index");if(C.isTTY){let e=y(void 0,!0,p);console.log(e),process.exit(0)}else{let e="";C.on("data",s=>e+=s),C.on("end",()=>{let s=e.trim()?JSON.parse(e):void 0,r={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:y(s,!1,p)}};console.log(JSON.stringify(r)),process.exit(0)})}}catch(p){console.error(`[claude-mem context-hook error: ${p.message}]`),process.exit(0)} diff --git a/src/hooks/context.ts b/src/hooks/context.ts index f7d596d6..1f35b683 100644 --- a/src/hooks/context.ts +++ b/src/hooks/context.ts @@ -43,7 +43,7 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals FROM session_summaries WHERE project = ? ORDER BY created_at_epoch DESC - LIMIT 30 + LIMIT 10 ) ORDER BY created_at_epoch ASC `).all(project) as Array<{ @@ -82,8 +82,8 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals // Most recent summary is at the end (highest index) since we display chronologically const positionFromEnd = summaries.length - 1 - i; const isTier1 = positionFromEnd === 0; // Most recent (full verbosity) - const isTier3 = positionFromEnd > 4; // Older (compact verbosity) - // Tier 2 (positions 1-4): Medium verbosity - implicit (not Tier1, not Tier3) + const isTier2 = positionFromEnd >= 1 && positionFromEnd <= 3; // Middle 3 (request + what was done) + const isTier3 = positionFromEnd > 3; // Oldest 6 (request only) // Add separator between summaries (but not before the first one) if (!isFirstSummary) { @@ -102,20 +102,24 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals isFirstSummary = false; - // TIER 3: Compact (just Learned + citation) + // TIER 3: Minimal (just Request + Date) if (isTier3) { - if (summary.learned) { + if (summary.request) { if (useColors) { - output.push(`${colors.bright}${colors.blue}Learned:${colors.reset} ${summary.learned}`); + output.push(`${colors.bright}${colors.yellow}Request:${colors.reset} ${summary.request}`); output.push(''); - output.push(`${colors.dim}[Details: claude-mem://session/${summary.sdk_session_id}]${colors.reset}`); } else { - output.push(`**Learned:** ${summary.learned}`); - output.push(''); - output.push(`[Details: claude-mem://session/${summary.sdk_session_id}]`); + output.push(`**Request:** ${summary.request}`); output.push(''); } } + const dateTime = new Date(summary.created_at).toLocaleString(); + if (useColors) { + output.push(`${colors.dim}Date: ${dateTime}${colors.reset}`); + } else { + output.push(`**Date:** ${dateTime}`); + output.push(''); + } continue; // Skip the rest for Tier 3 } @@ -152,8 +156,8 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals } } - // TIER 1 & 2: Show Next Steps - if (summary.next_steps) { + // TIER 1 ONLY: Show Next Steps + if (isTier1 && summary.next_steps) { if (useColors) { output.push(`${colors.bright}${colors.magenta}Next Steps:${colors.reset} ${summary.next_steps}`); output.push(''); @@ -163,58 +167,58 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals } } - // Get files from observations (for both Tier 1 and Tier 2) - const observations = db.db.prepare(` - SELECT files_read, files_modified - FROM observations - WHERE sdk_session_id = ? - `).all(summary.sdk_session_id) as Array<{ - files_read: string | null; - files_modified: string | null; - }>; + // TIER 1 ONLY: Get and show files + if (isTier1) { + const observations = db.db.prepare(` + SELECT files_read, files_modified + FROM observations + WHERE sdk_session_id = ? + `).all(summary.sdk_session_id) as Array<{ + files_read: string | null; + files_modified: string | null; + }>; - const filesReadSet = new Set(); - const filesModifiedSet = new Set(); + const filesReadSet = new Set(); + const filesModifiedSet = new Set(); - for (const obs of observations) { - if (obs.files_read) { - try { - const files = JSON.parse(obs.files_read); - if (Array.isArray(files)) { - files.forEach(f => filesReadSet.add(f)); + for (const obs of observations) { + if (obs.files_read) { + try { + const files = JSON.parse(obs.files_read); + if (Array.isArray(files)) { + files.forEach(f => filesReadSet.add(f)); + } + } catch { + // Skip invalid JSON + } + } + + if (obs.files_modified) { + try { + const files = JSON.parse(obs.files_modified); + if (Array.isArray(files)) { + files.forEach(f => filesModifiedSet.add(f)); + } + } catch { + // Skip invalid JSON } - } catch { - // Skip invalid JSON } } - if (obs.files_modified) { - try { - const files = JSON.parse(obs.files_modified); - if (Array.isArray(files)) { - files.forEach(f => filesModifiedSet.add(f)); - } - } catch { - // Skip invalid JSON + if (filesReadSet.size > 0) { + if (useColors) { + output.push(`${colors.dim}Files Read: ${Array.from(filesReadSet).join(', ')}${colors.reset}`); + } else { + output.push(`**Files Read:** ${Array.from(filesReadSet).join(', ')}`); } } - } - // TIER 1 ONLY: Show Files Read - if (isTier1 && filesReadSet.size > 0) { - if (useColors) { - output.push(`${colors.dim}Files Read: ${Array.from(filesReadSet).join(', ')}${colors.reset}`); - } else { - output.push(`**Files Read:** ${Array.from(filesReadSet).join(', ')}`); - } - } - - // TIER 1 & 2: Show Files Modified - if (filesModifiedSet.size > 0) { - if (useColors) { - output.push(`${colors.dim}Files Modified: ${Array.from(filesModifiedSet).join(', ')}${colors.reset}`); - } else { - output.push(`**Files Modified:** ${Array.from(filesModifiedSet).join(', ')}`); + if (filesModifiedSet.size > 0) { + if (useColors) { + output.push(`${colors.dim}Files Modified: ${Array.from(filesModifiedSet).join(', ')}${colors.reset}`); + } else { + output.push(`**Files Modified:** ${Array.from(filesModifiedSet).join(', ')}`); + } } }