diff --git a/plugin/scripts/context-hook.js b/plugin/scripts/context-hook.js index 28e16077..51e76694 100755 --- a/plugin/scripts/context-hook.js +++ b/plugin/scripts/context-hook.js @@ -1,7 +1,7 @@ #!/usr/bin/env node -import Y from"path";import W from"better-sqlite3";import{join as E,dirname as M,basename as q}from"path";import{homedir as v}from"os";import{existsSync as Z,mkdirSync as X}from"fs";import{fileURLToPath as F}from"url";function P(){return typeof __dirname<"u"?__dirname:M(F(import.meta.url))}var G=P(),_=process.env.CLAUDE_MEM_DATA_DIR||E(v(),".claude-mem"),b=process.env.CLAUDE_CONFIG_DIR||E(v(),".claude"),se=E(_,"archives"),te=E(_,"logs"),re=E(_,"trash"),ne=E(_,"backups"),oe=E(_,"settings.json"),C=E(_,"claude-mem.db"),ie=E(b,"settings.json"),ae=E(b,"commands"),de=E(b,"CLAUDE.md");function y(p){X(p,{recursive:!0})}function x(){return E(G,"..","..")}var N=(o=>(o[o.DEBUG=0]="DEBUG",o[o.INFO=1]="INFO",o[o.WARN=2]="WARN",o[o.ERROR=3]="ERROR",o[o.SILENT=4]="SILENT",o))(N||{}),R=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=N[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,o){if(e0&&(u=` {${Object.entries(f).map(([w,$])=>`${w}=${$}`).join(", ")}}`)}let l=`[${d}] [${n}] [${c}] ${a}${t}${u}${m}`;e===3?console.error(l):console.log(l)}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`})}},D=new R;var S=class{db;constructor(){y(_),this.db=new W(C),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 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(` CREATE TABLE IF NOT EXISTS schema_versions ( id INTEGER PRIMARY KEY, version INTEGER UNIQUE NOT NULL, @@ -222,7 +222,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje SELECT files_read, files_modified FROM observations WHERE sdk_session_id = ? - `).all(e),r=new Set,o=new Set;for(let d of t){if(d.files_read)try{let n=JSON.parse(d.files_read);Array.isArray(n)&&n.forEach(c=>r.add(c))}catch{}if(d.files_modified)try{let n=JSON.parse(d.files_modified);Array.isArray(n)&&n.forEach(c=>o.add(c))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(o)}}getSessionById(e){return this.db.prepare(` + `).all(e),r=new Set,i=new Set;for(let d of t){if(d.files_read)try{let n=JSON.parse(d.files_read);Array.isArray(n)&&n.forEach(c=>r.add(c))}catch{}if(d.files_modified)try{let n=JSON.parse(d.files_modified);Array.isArray(n)&&n.forEach(c=>i.add(c))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(i)}}getSessionById(e){return this.db.prepare(` SELECT id, sdk_session_id, project, user_prompt FROM sdk_sessions WHERE id = ? @@ -249,17 +249,17 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje SELECT prompt_counter FROM sdk_sessions WHERE id = ? `).get(e)?.prompt_counter||1}getPromptCounter(e){return this.db.prepare(` SELECT prompt_counter FROM sdk_sessions WHERE id = ? - `).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,o=r.getTime(),n=this.db.prepare(` + `).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,i=r.getTime(),n=this.db.prepare(` INSERT OR IGNORE INTO sdk_sessions (claude_session_id, sdk_session_id, project, user_prompt, started_at, started_at_epoch, status) VALUES (?, ?, ?, ?, ?, ?, 'active') - `).run(e,e,s,t,r.toISOString(),o);return n.lastInsertRowid===0||n.changes===0?this.db.prepare(` + `).run(e,e,s,t,r.toISOString(),i);return n.lastInsertRowid===0||n.changes===0?this.db.prepare(` SELECT id FROM sdk_sessions WHERE claude_session_id = ? LIMIT 1 `).get(e).id:n.lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(` UPDATE sdk_sessions SET sdk_session_id = ? WHERE id = ? AND sdk_session_id IS NULL - `).run(s,e).changes===0?(D.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(` + `).run(s,e).changes===0?($.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(` UPDATE sdk_sessions SET worker_port = ? WHERE id = ? @@ -268,33 +268,33 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje FROM sdk_sessions WHERE id = ? LIMIT 1 - `).get(e)?.worker_port||null}saveUserPrompt(e,s,t){let r=new Date,o=r.getTime();return this.db.prepare(` + `).get(e)?.worker_port||null}saveUserPrompt(e,s,t){let r=new Date,i=r.getTime();return this.db.prepare(` INSERT INTO user_prompts (claude_session_id, prompt_number, prompt_text, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?) - `).run(e,s,t,r.toISOString(),o).lastInsertRowid}storeObservation(e,s,t,r){let o=new Date,d=o.getTime();this.db.prepare(` + `).run(e,s,t,r.toISOString(),i).lastInsertRowid}storeObservation(e,s,t,r){let i=new Date,d=i.getTime();this.db.prepare(` SELECT id FROM sdk_sessions WHERE sdk_session_id = ? `).get(e)||(this.db.prepare(` INSERT INTO sdk_sessions (claude_session_id, sdk_session_id, project, started_at, started_at_epoch, status) VALUES (?, ?, ?, ?, ?, 'active') - `).run(e,e,s,o.toISOString(),d),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`)),this.db.prepare(` + `).run(e,e,s,i.toISOString(),d),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`)),this.db.prepare(` INSERT INTO observations (sdk_session_id, project, type, title, subtitle, facts, narrative, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,o.toISOString(),d)}storeSummary(e,s,t,r){let o=new Date,d=o.getTime();this.db.prepare(` + `).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,i.toISOString(),d)}storeSummary(e,s,t,r){let i=new Date,d=i.getTime();this.db.prepare(` SELECT id FROM sdk_sessions WHERE sdk_session_id = ? `).get(e)||(this.db.prepare(` INSERT INTO sdk_sessions (claude_session_id, sdk_session_id, project, started_at, started_at_epoch, status) VALUES (?, ?, ?, ?, ?, 'active') - `).run(e,e,s,o.toISOString(),d),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`)),this.db.prepare(` + `).run(e,e,s,i.toISOString(),d),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`)),this.db.prepare(` INSERT INTO session_summaries (sdk_session_id, project, request, investigated, learned, completed, next_steps, notes, prompt_number, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,o.toISOString(),d)}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(` + `).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,i.toISOString(),d)}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(` UPDATE sdk_sessions SET status = 'completed', completed_at = ?, completed_at_epoch = ? WHERE id = ? @@ -306,25 +306,25 @@ ${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 I from"path";import{existsSync as O}from"fs";import{spawn as j}from"child_process";var H=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),B=`http://127.0.0.1:${H}/health`;async function k(){try{return(await fetch(B,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function U(){try{if(await k())return!0;console.error("[claude-mem] Worker not responding, starting...");let p=x(),e=I.join(p,"plugin","scripts","worker-service.cjs");if(!O(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=I.join(p,"ecosystem.config.cjs"),t=I.join(p,"node_modules",".bin","pm2");if(!O(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!O(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=j(t,["start",s],{detached:!0,stdio:"ignore",cwd:p});r.on("error",o=>{throw new Error(`Failed to spawn PM2: ${o.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let o=0;o<3;o++)if(await new Promise(d=>setTimeout(d,500)),await k())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 i={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 L(p,e=!1,s=!1){U();let t=p?.cwd??process.cwd(),r=t?Y.basename(t):"unknown-project",o=new S;try{let d=o.db.prepare(` + `).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(` 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 10 + LIMIT 30 ) ORDER BY created_at_epoch ASC `).all(r);if(d.length===0)return e?` -${i.bright}${i.cyan}\u{1F4DD} [${r}] recent context${i.reset} -${i.gray}${"\u2500".repeat(60)}${i.reset} +${o.bright}${o.cyan}\u{1F4DD} [${r}] recent context${o.reset} +${o.gray}${"\u2500".repeat(60)}${o.reset} -${i.dim}No previous summaries found for this project yet.${i.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(`${i.bright}${i.cyan}\u{1F4DD} [${r}] recent context${i.reset}`),n.push(`${i.gray}${"\u2500".repeat(60)}${i.reset}`)):(n.push(`# [${r}] recent context`),n.push(""));let c=!0;for(let a of d){c?e&&n.push(""):e?(n.push(`${i.gray}${"\u2500".repeat(60)}${i.reset}`),n.push("")):(n.push("---"),n.push("")),c=!1,a.request&&(e?(n.push(`${i.bright}${i.yellow}Request:${i.reset} ${a.request}`),n.push("")):(n.push(`**Request:** ${a.request}`),n.push(""))),a.learned&&(e?(n.push(`${i.bright}${i.blue}Learned:${i.reset} ${a.learned}`),n.push("")):(n.push(`**Learned:** ${a.learned}`),n.push(""))),a.completed&&(e?(n.push(`${i.bright}${i.green}Completed:${i.reset} ${a.completed}`),n.push("")):(n.push(`**Completed:** ${a.completed}`),n.push(""))),a.next_steps&&(e?(n.push(`${i.bright}${i.magenta}Next Steps:${i.reset} ${a.next_steps}`),n.push("")):(n.push(`**Next Steps:** ${a.next_steps}`),n.push("")));let m=o.db.prepare(` +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),u=new Set,l=new Set;for(let g of m){if(g.files_read)try{let T=JSON.parse(g.files_read);Array.isArray(T)&&T.forEach(f=>u.add(f))}catch{}if(g.files_modified)try{let T=JSON.parse(g.files_modified);Array.isArray(T)&&T.forEach(f=>l.add(f))}catch{}}u.size>0&&(e?n.push(`${i.dim}Files Read: ${Array.from(u).join(", ")}${i.reset}`):n.push(`**Files Read:** ${Array.from(u).join(", ")}`)),l.size>0&&(e?n.push(`${i.dim}Files Modified: ${Array.from(l).join(", ")}${i.reset}`):n.push(`**Files Modified:** ${Array.from(l).join(", ")}`));let h=new Date(a.created_at).toLocaleString();e?n.push(`${i.dim}Date: ${h}${i.reset}`):n.push(`**Date:** ${h}`),e||n.push("")}return e&&(n.push(""),n.push(`${i.gray}${"\u2500".repeat(60)}${i.reset}`)),n.join(` -`)}finally{o.close()}}import{stdin as A}from"process";try{let p=process.argv.includes("--index");if(A.isTTY){let e=L(void 0,!0,p);console.log(e),process.exit(0)}else{let e="";A.on("data",s=>e+=s),A.on("end",()=>{let s=e.trim()?JSON.parse(e):void 0,r={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:L(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)} + `).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)} diff --git a/src/hooks/context.ts b/src/hooks/context.ts index 6e155182..f7d596d6 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 10 + LIMIT 30 ) ORDER BY created_at_epoch ASC `).all(project) as Array<{ @@ -75,7 +75,16 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals let isFirstSummary = true; - for (const summary of summaries) { + for (let i = 0; i < summaries.length; i++) { + const summary = summaries[i]; + + // Determine verbosity tier based on position + // 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) + // Add separator between summaries (but not before the first one) if (!isFirstSummary) { if (useColors) { @@ -93,6 +102,24 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals isFirstSummary = false; + // TIER 3: Compact (just Learned + citation) + if (isTier3) { + if (summary.learned) { + if (useColors) { + output.push(`${colors.bright}${colors.blue}Learned:${colors.reset} ${summary.learned}`); + 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(''); + } + } + continue; // Skip the rest for Tier 3 + } + + // TIER 1 & 2: Show Request if (summary.request) { if (useColors) { output.push(`${colors.bright}${colors.yellow}Request:${colors.reset} ${summary.request}`); @@ -103,6 +130,7 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals } } + // TIER 1 & 2: Show Learned if (summary.learned) { if (useColors) { output.push(`${colors.bright}${colors.blue}Learned:${colors.reset} ${summary.learned}`); @@ -113,6 +141,7 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals } } + // TIER 1 & 2: Show Completed if (summary.completed) { if (useColors) { output.push(`${colors.bright}${colors.green}Completed:${colors.reset} ${summary.completed}`); @@ -123,6 +152,7 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals } } + // TIER 1 & 2: Show Next Steps if (summary.next_steps) { if (useColors) { output.push(`${colors.bright}${colors.magenta}Next Steps:${colors.reset} ${summary.next_steps}`); @@ -133,7 +163,7 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals } } - // Get files from observations directly + // Get files from observations (for both Tier 1 and Tier 2) const observations = db.db.prepare(` SELECT files_read, files_modified FROM observations @@ -170,7 +200,8 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals } } - if (filesReadSet.size > 0) { + // 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 { @@ -178,6 +209,7 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals } } + // TIER 1 & 2: Show Files Modified if (filesModifiedSet.size > 0) { if (useColors) { output.push(`${colors.dim}Files Modified: ${Array.from(filesModifiedSet).join(', ')}${colors.reset}`); @@ -186,6 +218,7 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals } } + // TIER 1 & 2: Show Date const dateTime = new Date(summary.created_at).toLocaleString(); if (useColors) { output.push(`${colors.dim}Date: ${dateTime}${colors.reset}`);