diff --git a/plugin/scripts/context-hook.js b/plugin/scripts/context-hook.js index 687d4edc..925639a6 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 H from"better-sqlite3";import{join as c,dirname as X,basename as V}from"path";import{homedir as v}from"os";import{existsSync as z,mkdirSync as F}from"fs";import{fileURLToPath as P}from"url";function G(){return typeof __dirname<"u"?__dirname:X(P(import.meta.url))}var W=G(),E=process.env.CLAUDE_MEM_DATA_DIR||c(v(),".claude-mem"),b=process.env.CLAUDE_CONFIG_DIR||c(v(),".claude"),ee=c(E,"archives"),se=c(E,"logs"),te=c(E,"trash"),re=c(E,"backups"),ne=c(E,"settings.json"),C=c(E,"claude-mem.db"),oe=c(b,"settings.json"),ie=c(b,"commands"),ae=c(b,"CLAUDE.md");function k(d){F(d,{recursive:!0})}function D(){return c(W,"..","..")}var N=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(N||{}),h=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,n){if(e0&&(T=` {${Object.entries(_).map(([w,M])=>`${w}=${M}`).join(", ")}}`)}let u=`[${i}] [${o}] [${p}] ${a}${t}${T}${m}`;e===3?console.error(u):console.log(u)}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`})}},x=new h;var f=class{db;constructor(){k(E),this.db=new H(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 K from"path";import j from"better-sqlite3";import{join as _,dirname as X,basename as q}from"path";import{homedir as C}from"os";import{existsSync as Z,mkdirSync as F}from"fs";import{fileURLToPath as P}from"url";function G(){return typeof __dirname<"u"?__dirname:X(P(import.meta.url))}var W=G(),m=process.env.CLAUDE_MEM_DATA_DIR||_(C(),".claude-mem"),R=process.env.CLAUDE_CONFIG_DIR||_(C(),".claude"),se=_(m,"archives"),te=_(m,"logs"),re=_(m,"trash"),ne=_(m,"backups"),ie=_(m,"settings.json"),x=_(m,"claude-mem.db"),oe=_(R,"settings.json"),ae=_(R,"commands"),de=_(R,"CLAUDE.md");function D(p){F(p,{recursive:!0})}function k(){return _(W,"..","..")}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&&(b=` {${Object.entries(u).map(([g,M])=>`${g}=${M}`).join(", ")}}`)}let S=`[${d}] [${n}] [${c}] ${E}${t}${b}${a}`;e===3?console.error(S):console.log(S)}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`})}},U=new O;var h=class{db;constructor(){D(m),this.db=new j(x),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, @@ -63,7 +63,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje CREATE INDEX IF NOT EXISTS idx_session_summaries_sdk_session ON session_summaries(sdk_session_id); CREATE INDEX IF NOT EXISTS idx_session_summaries_project ON session_summaries(project); CREATE INDEX IF NOT EXISTS idx_session_summaries_created ON session_summaries(created_at_epoch DESC); - `),this.db.prepare("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)").run(4,new Date().toISOString()),console.error("[SessionStore] Migration004 applied successfully"))}catch(e){throw console.error("[SessionStore] Schema initialization error:",e.message),e}}ensureWorkerPortColumn(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(5))return;this.db.pragma("table_info(sdk_sessions)").some(r=>r.name==="worker_port")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER"),console.error("[SessionStore] Added worker_port column to sdk_sessions table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(5,new Date().toISOString())}catch(e){console.error("[SessionStore] Migration error:",e.message)}}ensurePromptTrackingColumns(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(6))return;this.db.pragma("table_info(sdk_sessions)").some(p=>p.name==="prompt_counter")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0"),console.error("[SessionStore] Added prompt_counter column to sdk_sessions table")),this.db.pragma("table_info(observations)").some(p=>p.name==="prompt_number")||(this.db.exec("ALTER TABLE observations ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to observations table")),this.db.pragma("table_info(session_summaries)").some(p=>p.name==="prompt_number")||(this.db.exec("ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to session_summaries table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(6,new Date().toISOString())}catch(e){console.error("[SessionStore] Prompt tracking migration error:",e.message)}}removeSessionSummariesUniqueConstraint(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(7))return;if(!this.db.pragma("index_list(session_summaries)").some(r=>r.unique===1)){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(7,new Date().toISOString());return}console.error("[SessionStore] Removing UNIQUE constraint from session_summaries.sdk_session_id..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(` + `),this.db.prepare("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)").run(4,new Date().toISOString()),console.error("[SessionStore] Migration004 applied successfully"))}catch(e){throw console.error("[SessionStore] Schema initialization error:",e.message),e}}ensureWorkerPortColumn(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(5))return;this.db.pragma("table_info(sdk_sessions)").some(r=>r.name==="worker_port")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER"),console.error("[SessionStore] Added worker_port column to sdk_sessions table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(5,new Date().toISOString())}catch(e){console.error("[SessionStore] Migration error:",e.message)}}ensurePromptTrackingColumns(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(6))return;this.db.pragma("table_info(sdk_sessions)").some(c=>c.name==="prompt_counter")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0"),console.error("[SessionStore] Added prompt_counter column to sdk_sessions table")),this.db.pragma("table_info(observations)").some(c=>c.name==="prompt_number")||(this.db.exec("ALTER TABLE observations ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to observations table")),this.db.pragma("table_info(session_summaries)").some(c=>c.name==="prompt_number")||(this.db.exec("ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to session_summaries table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(6,new Date().toISOString())}catch(e){console.error("[SessionStore] Prompt tracking migration error:",e.message)}}removeSessionSummariesUniqueConstraint(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(7))return;if(!this.db.pragma("index_list(session_summaries)").some(r=>r.unique===1)){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(7,new Date().toISOString());return}console.error("[SessionStore] Removing UNIQUE constraint from session_summaries.sdk_session_id..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(` CREATE TABLE session_summaries_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, sdk_session_id TEXT 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,n=new Set;for(let i of t){if(i.files_read)try{let o=JSON.parse(i.files_read);Array.isArray(o)&&o.forEach(p=>r.add(p))}catch{}if(i.files_modified)try{let o=JSON.parse(i.files_modified);Array.isArray(o)&&o.forEach(p=>n.add(p))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(n)}}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,n=r.getTime(),o=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(),n);return o.lastInsertRowid===0||o.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:o.lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(` + `).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?(x.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?(U.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,n=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(),n).lastInsertRowid}storeObservation(e,s,t,r){let n=new Date,i=n.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,n.toISOString(),i),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,n.toISOString(),i)}storeSummary(e,s,t,r){let n=new Date,i=n.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,n.toISOString(),i),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,n.toISOString(),i)}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,17 +306,22 @@ ${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 R from"path";import{existsSync as I}from"fs";import{spawn as $}from"child_process";var B=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),j=`http://127.0.0.1:${B}/health`;async function y(){try{return(await fetch(j,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function U(){try{if(await y())return!0;console.error("[claude-mem] Worker not responding, starting...");let d=D(),e=R.join(d,"plugin","scripts","worker-service.cjs");if(!I(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=R.join(d,"ecosystem.config.cjs"),t=R.join(d,"node_modules",".bin","pm2");if(!I(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!I(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=$(t,["start",s],{detached:!0,stdio:"ignore",cwd:d});r.on("error",n=>{throw new Error(`Failed to spawn PM2: ${n.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let n=0;n<3;n++)if(await new Promise(i=>setTimeout(i,500)),await y())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(d){return console.error(`[claude-mem] Failed to start worker: ${d.message}`),!1}}function O(d,e=!1,s=!1){U();let t=d?.cwd??process.cwd(),r=t?Y.basename(t):"unknown-project",n=new f;try{let i=n.db.prepare(` + `).run(e.toISOString(),s).changes}close(){this.db.close()}};import L from"path";import{existsSync as A}from"fs";import{spawn as H}from"child_process";var B=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),Y=`http://127.0.0.1:${B}/health`;async function w(){try{return(await fetch(Y,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function $(){try{if(await w())return!0;console.error("[claude-mem] Worker not responding, starting...");let p=k(),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=H(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 w())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){$();let t=p?.cwd??process.cwd(),r=t?K.basename(t):"unknown-project",i=new h;try{let d=i.db.prepare(` SELECT sdk_session_id, request, learned, completed, next_steps, created_at FROM session_summaries WHERE project = ? ORDER BY created_at_epoch DESC LIMIT 3 - `).all(r);if(i.length===0)return`# [${r}] recent context + `).all(r);if(d.length===0)return e?` +${o.bright}${o.cyan}\u{1F4DD} [${r}] recent context${o.reset} +${o.gray}${"\u2500".repeat(60)}${o.reset} -No previous summaries found for this project yet.`;let o=[];o.push(`# [${r}] recent context`),o.push("");let p=null;for(let a of i){p!==null&&a.sdk_session_id!==p&&(o.push(""),o.push("--- New Session ---"),o.push("")),a.request&&o.push(`**Request:** ${a.request}`),a.learned&&o.push(`**Learned:** ${a.learned}`),a.completed&&o.push(`**Completed:** ${a.completed}`),a.next_steps&&o.push(`**Next Steps:** ${a.next_steps}`);let T=n.db.prepare(` +${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=null,E=!0;for(let a of d){c!==null&&a.sdk_session_id!==c?e?(n.push(""),n.push(`${o.dim}${"\u2500".repeat(23)} New Session ${"\u2500".repeat(24)}${o.reset}`),n.push("")):(n.push(""),n.push("--- New Session ---"),n.push("")):E?e&&n.push(""):e?(n.push(`${o.gray}${"\u2500".repeat(60)}${o.reset}`),n.push("")):(n.push("---"),n.push("")),E=!1,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 S=i.db.prepare(` SELECT files_read, files_modified FROM observations WHERE sdk_session_id = ? - `).all(a.sdk_session_id),u=new Set,S=new Set;for(let l of T){if(l.files_read)try{let _=JSON.parse(l.files_read);Array.isArray(_)&&_.forEach(g=>u.add(g))}catch{}if(l.files_modified)try{let _=JSON.parse(l.files_modified);Array.isArray(_)&&_.forEach(g=>S.add(g))}catch{}}u.size>0&&o.push(`**Files Read:** ${Array.from(u).join(", ")}`),S.size>0&&o.push(`**Files Modified:** ${Array.from(S).join(", ")}`);let A=new Date(a.created_at).toLocaleString();o.push(`**Date:** ${A}`),o.push(""),p=a.sdk_session_id}return o.join(` -`)}finally{n.close()}}import{stdin as L}from"process";try{let d=process.argv.includes("--index");if(L.isTTY){let e=O(void 0,!0,d);console.log(e),process.exit(0)}else{let e="";L.on("data",s=>e+=s),L.on("end",()=>{let s=e.trim()?JSON.parse(e):void 0,r={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:O(s,!1,d)}};console.log(JSON.stringify(r)),process.exit(0)})}}catch(d){console.error(`[claude-mem context-hook error: ${d.message}]`),process.exit(0)} + `).all(a.sdk_session_id),T=new Set,f=new Set;for(let u of S){if(u.files_read)try{let l=JSON.parse(u.files_read);Array.isArray(l)&&l.forEach(g=>T.add(g))}catch{}if(u.files_modified)try{let l=JSON.parse(u.files_modified);Array.isArray(l)&&l.forEach(g=>f.add(g))}catch{}}T.size>0&&(e?n.push(`${o.dim}Files Read: ${Array.from(T).join(", ")}${o.reset}`):n.push(`**Files Read:** ${Array.from(T).join(", ")}`)),f.size>0&&(e?n.push(`${o.dim}Files Modified: ${Array.from(f).join(", ")}${o.reset}`):n.push(`**Files Modified:** ${Array.from(f).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(""),c=a.sdk_session_id}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 802307b0..36009e27 100644 --- a/src/hooks/context.ts +++ b/src/hooks/context.ts @@ -26,7 +26,7 @@ const colors = { /** * Context Hook - SessionStart - * Shows recent summaries for the project + * Shows user what happened in recent sessions */ export function contextHook(input?: SessionStartInput, useColors: boolean = false, useIndexView: boolean = false): string { ensureWorkerRunning(); @@ -36,7 +36,7 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals const db = new SessionStore(); try { - // Query session_summaries directly - no need for sdk_sessions table + // Query session_summaries directly const summaries = db.db.prepare(` SELECT sdk_session_id, request, learned, completed, next_steps, created_at FROM session_summaries @@ -53,30 +53,96 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals }>; if (summaries.length === 0) { + if (useColors) { + return `\n${colors.bright}${colors.cyan}📝 [${project}] recent context${colors.reset}\n${colors.gray}${'─'.repeat(60)}${colors.reset}\n\n${colors.dim}No previous summaries found for this project yet.${colors.reset}\n`; + } return `# [${project}] recent context\n\nNo previous summaries found for this project yet.`; } const output: string[] = []; - output.push(`# [${project}] recent context`); - output.push(''); + + if (useColors) { + output.push(''); + output.push(`${colors.bright}${colors.cyan}📝 [${project}] recent context${colors.reset}`); + output.push(`${colors.gray}${'─'.repeat(60)}${colors.reset}`); + } else { + output.push(`# [${project}] recent context`); + output.push(''); + } let previousSessionId: string | null = null; + let isFirstSummary = true; for (const summary of summaries) { const isNewSession = previousSessionId !== null && summary.sdk_session_id !== previousSessionId; if (isNewSession) { - output.push(''); - output.push('--- New Session ---'); - output.push(''); + if (useColors) { + output.push(''); + output.push(`${colors.dim}${'─'.repeat(23)} New Session ${'─'.repeat(24)}${colors.reset}`); + output.push(''); + } else { + output.push(''); + output.push('--- New Session ---'); + output.push(''); + } + } else if (!isFirstSummary) { + if (useColors) { + output.push(`${colors.gray}${'─'.repeat(60)}${colors.reset}`); + output.push(''); + } else { + output.push('---'); + output.push(''); + } + } else { + if (useColors) { + output.push(''); + } } - if (summary.request) output.push(`**Request:** ${summary.request}`); - if (summary.learned) output.push(`**Learned:** ${summary.learned}`); - if (summary.completed) output.push(`**Completed:** ${summary.completed}`); - if (summary.next_steps) output.push(`**Next Steps:** ${summary.next_steps}`); + isFirstSummary = false; - // Get files from observations by sdk_session_id + if (summary.request) { + if (useColors) { + output.push(`${colors.bright}${colors.yellow}Request:${colors.reset} ${summary.request}`); + output.push(''); + } else { + output.push(`**Request:** ${summary.request}`); + output.push(''); + } + } + + if (summary.learned) { + if (useColors) { + output.push(`${colors.bright}${colors.blue}Learned:${colors.reset} ${summary.learned}`); + output.push(''); + } else { + output.push(`**Learned:** ${summary.learned}`); + output.push(''); + } + } + + if (summary.completed) { + if (useColors) { + output.push(`${colors.bright}${colors.green}Completed:${colors.reset} ${summary.completed}`); + output.push(''); + } else { + output.push(`**Completed:** ${summary.completed}`); + output.push(''); + } + } + + if (summary.next_steps) { + if (useColors) { + output.push(`${colors.bright}${colors.magenta}Next Steps:${colors.reset} ${summary.next_steps}`); + output.push(''); + } else { + output.push(`**Next Steps:** ${summary.next_steps}`); + output.push(''); + } + } + + // Get files from observations directly const observations = db.db.prepare(` SELECT files_read, files_modified FROM observations @@ -93,33 +159,63 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals if (obs.files_read) { try { const files = JSON.parse(obs.files_read); - if (Array.isArray(files)) files.forEach(f => filesReadSet.add(f)); - } catch {} + 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 {} + if (Array.isArray(files)) { + files.forEach(f => filesModifiedSet.add(f)); + } + } catch { + // Skip invalid JSON + } } } if (filesReadSet.size > 0) { - output.push(`**Files Read:** ${Array.from(filesReadSet).join(', ')}`); + if (useColors) { + output.push(`${colors.dim}Files Read: ${Array.from(filesReadSet).join(', ')}${colors.reset}`); + } else { + output.push(`**Files Read:** ${Array.from(filesReadSet).join(', ')}`); + } } + if (filesModifiedSet.size > 0) { - output.push(`**Files Modified:** ${Array.from(filesModifiedSet).join(', ')}`); + if (useColors) { + output.push(`${colors.dim}Files Modified: ${Array.from(filesModifiedSet).join(', ')}${colors.reset}`); + } else { + output.push(`**Files Modified:** ${Array.from(filesModifiedSet).join(', ')}`); + } } const dateTime = new Date(summary.created_at).toLocaleString(); - output.push(`**Date:** ${dateTime}`); - output.push(''); + if (useColors) { + output.push(`${colors.dim}Date: ${dateTime}${colors.reset}`); + } else { + output.push(`**Date:** ${dateTime}`); + } + + if (!useColors) { + output.push(''); + } previousSessionId = summary.sdk_session_id; } + if (useColors) { + output.push(''); + output.push(`${colors.gray}${'─'.repeat(60)}${colors.reset}`); + } + return output.join('\n'); } finally { db.close(); } -} \ No newline at end of file +}