Refactor contextHook to improve session summary handling and timeline rendering

- Updated logic to retrieve recent summaries and observations, focusing on the last 4 summaries for better context.
- Simplified the extraction of unique session IDs from the recent summaries.
- Enhanced the timeline rendering to include both observations and summaries, grouped by day and file.
- Removed redundant queries for recent summaries and observations, streamlining the data retrieval process.
- Improved output formatting for better readability, including color-coded sections and clearer headers.
- Added detailed display of the most recent session's completed status and next steps.
This commit is contained in:
Alex Newman
2025-10-25 01:23:47 -04:00
parent 28d9c43f85
commit 50d504715d
2 changed files with 158 additions and 180 deletions
+28 -40
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env node
import X from"path";import ae from"better-sqlite3";import{join as _,dirname as te,basename as Te}from"path";import{homedir as H}from"os";import{existsSync as Se,mkdirSync as re}from"fs";import{fileURLToPath as ne}from"url";function ie(){return typeof __dirname<"u"?__dirname:te(ne(import.meta.url))}var oe=ie(),L=process.env.CLAUDE_MEM_DATA_DIR||_(H(),".claude-mem"),C=process.env.CLAUDE_CONFIG_DIR||_(H(),".claude"),Re=_(L,"archives"),Ne=_(L,"logs"),Ie=_(L,"trash"),Oe=_(L,"backups"),Le=_(L,"settings.json"),B=_(L,"claude-mem.db"),Ae=_(C,"settings.json"),ve=_(C,"commands"),ye=_(C,"CLAUDE.md");function j(a){re(a,{recursive:!0})}function Y(){return _(oe,"..","..")}var x=(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))(x||{}),$=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=x[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(e<this.level)return;let d=new Date().toISOString().replace("T"," ").substring(0,23),u=x[e].padEnd(5),p=s.padEnd(6),c="";r?.correlationId?c=`[${r.correlationId}] `:r?.sessionId&&(c=`[session-${r.sessionId}] `);let R="";o!=null&&(this.level===0&&typeof o=="object"?R=`
`+JSON.stringify(o,null,2):R=" "+this.formatData(o));let n="";if(r){let{sessionId:E,sdkSessionId:h,correlationId:N,...g}=r;Object.keys(g).length>0&&(n=` {${Object.entries(g).map(([O,f])=>`${O}=${f}`).join(", ")}}`)}let b=`[${d}] [${u}] [${p}] ${c}${t}${n}${R}`;e===3?console.error(b):console.log(b)}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`})}},q=new $;var D=class{db;constructor(){j(L),this.db=new ae(B),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 G from"path";import ae from"better-sqlite3";import{join as T,dirname as te,basename as he}from"path";import{homedir as Y}from"os";import{existsSync as Ne,mkdirSync as re}from"fs";import{fileURLToPath as ne}from"url";function ie(){return typeof __dirname<"u"?__dirname:te(ne(import.meta.url))}var oe=ie(),I=process.env.CLAUDE_MEM_DATA_DIR||T(Y(),".claude-mem"),U=process.env.CLAUDE_CONFIG_DIR||T(Y(),".claude"),Ie=T(I,"archives"),Oe=T(I,"logs"),Le=T(I,"trash"),Ae=T(I,"backups"),ve=T(I,"settings.json"),K=T(I,"claude-mem.db"),ye=T(U,"settings.json"),De=T(U,"commands"),ke=T(U,"CLAUDE.md");function V(o){re(o,{recursive:!0})}function q(){return T(oe,"..","..")}var $=(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))($||{}),M=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=$[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(e<this.level)return;let c=new Date().toISOString().replace("T"," ").substring(0,23),p=$[e].padEnd(5),u=s.padEnd(6),O="";r?.correlationId?O=`[${r.correlationId}] `:r?.sessionId&&(O=`[session-${r.sessionId}] `);let b="";n!=null&&(this.level===0&&typeof n=="object"?b=`
`+JSON.stringify(n,null,2):b=" "+this.formatData(n));let i="";if(r){let{sessionId:g,sdkSessionId:C,correlationId:L,...D}=r;Object.keys(D).length>0&&(i=` {${Object.entries(D).map(([d,l])=>`${d}=${l}`).join(", ")}}`)}let N=`[${c}] [${p}] [${u}] ${O}${t}${i}${b}`;e===3?console.error(N):console.log(N)}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`})}},J=new M;var k=class{db;constructor(){V(I),this.db=new ae(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,
@@ -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(u=>u.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(u=>u.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(u=>u.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,o=new Set;for(let d of t){if(d.files_read)try{let u=JSON.parse(d.files_read);Array.isArray(u)&&u.forEach(p=>r.add(p))}catch{}if(d.files_modified)try{let u=JSON.parse(d.files_modified);Array.isArray(u)&&u.forEach(p=>o.add(p))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(o)}}getSessionById(e){return this.db.prepare(`
`).all(e),r=new Set,n=new Set;for(let c of t){if(c.files_read)try{let p=JSON.parse(c.files_read);Array.isArray(p)&&p.forEach(u=>r.add(u))}catch{}if(c.files_modified)try{let p=JSON.parse(c.files_modified);Array.isArray(p)&&p.forEach(u=>n.add(u))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(n)}}getSessionById(e){return this.db.prepare(`
SELECT id, claude_session_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(),u=this.db.prepare(`
`).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,n=r.getTime(),p=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 u.lastInsertRowid===0||u.changes===0?this.db.prepare(`
`).run(e,e,s,t,r.toISOString(),n);return p.lastInsertRowid===0||p.changes===0?this.db.prepare(`
SELECT id FROM sdk_sessions WHERE claude_session_id = ? LIMIT 1
`).get(e).id:u.lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(`
`).get(e).id:p.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?(q.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?(J.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,n=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(),n).lastInsertRowid}storeObservation(e,s,t,r){let n=new Date,c=n.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,n.toISOString(),c),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,n.toISOString(),c)}storeSummary(e,s,t,r){let n=new Date,c=n.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,n.toISOString(),c),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,n.toISOString(),c)}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,13 +306,7 @@ ${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 w from"path";import{existsSync as U}from"fs";import{spawn as de}from"child_process";var ce=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),pe=`http://127.0.0.1:${ce}/health`;async function K(){try{return(await fetch(pe,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function V(){try{if(await K())return!0;console.error("[claude-mem] Worker not responding, starting...");let a=Y(),e=w.join(a,"plugin","scripts","worker-service.cjs");if(!U(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=w.join(a,"ecosystem.config.cjs"),t=w.join(a,"node_modules",".bin","pm2");if(!U(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!U(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=de(t,["start",s],{detached:!0,stdio:"ignore",cwd:a});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(a){return console.error(`[claude-mem] Failed to start worker: ${a.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",red:"\x1B[31m"};function M(a){if(!a)return[];try{let e=JSON.parse(a);return Array.isArray(e)?e:[]}catch{return[]}}function J(a){return new Date(a).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0})}function Q(a){return new Date(a).toLocaleString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})}function z(a){return new Date(a).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric"})}function ue(a){return a?Math.ceil(a.length/4):0}function me(a,e){try{return X.isAbsolute(a)?X.relative(e,a):a}catch{return a}}function le(a,e,s=3){return a.db.prepare(`
SELECT sdk_session_id
FROM sdk_sessions
WHERE project = ? AND sdk_session_id IS NOT NULL
ORDER BY started_at_epoch DESC
LIMIT ?
`).all(e,s).map(r=>r.sdk_session_id)}function _e(a,e){if(e.length===0)return[];let s=e.map(()=>"?").join(",");return a.db.prepare(`
`).run(e.toISOString(),s).changes}close(){this.db.close()}};import X from"path";import{existsSync as F}from"fs";import{spawn as de}from"child_process";var ce=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),pe=`http://127.0.0.1:${ce}/health`;async function Q(){try{return(await fetch(pe,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function z(){try{if(await Q())return!0;console.error("[claude-mem] Worker not responding, starting...");let o=q(),e=X.join(o,"plugin","scripts","worker-service.cjs");if(!F(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=X.join(o,"ecosystem.config.cjs"),t=X.join(o,"node_modules",".bin","pm2");if(!F(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!F(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=de(t,["start",s],{detached:!0,stdio:"ignore",cwd:o});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(c=>setTimeout(c,500)),await Q())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(o){return console.error(`[claude-mem] Failed to start worker: ${o.message}`),!1}}var a={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",red:"\x1B[31m"};function P(o){if(!o)return[];try{let e=JSON.parse(o);return Array.isArray(e)?e:[]}catch{return[]}}function ue(o){return new Date(o).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0})}function me(o){return new Date(o).toLocaleString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})}function le(o){return new Date(o).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric"})}function _e(o){return o?Math.ceil(o.length/4):0}function Ee(o,e){try{return G.isAbsolute(o)?G.relative(e,o):o}catch{return o}}function Te(o,e){if(e.length===0)return[];let s=e.map(()=>"?").join(",");return o.db.prepare(`
SELECT
id, sdk_session_id, type, title, subtitle, narrative,
facts, concepts, files_read, files_modified,
@@ -320,24 +314,18 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
FROM observations
WHERE sdk_session_id IN (${s})
ORDER BY created_at_epoch DESC
`).all(...e)}function F(a,e=!1,s=!1){V();let t=a?.cwd??process.cwd(),r=t?X.basename(t):"unknown-project",o=new D;try{let d=le(o,r,3);if(d.length===0)return e?`
${i.bright}${i.cyan}\u{1F4DD} [${r}] recent context${i.reset}
${i.gray}${"\u2500".repeat(60)}${i.reset}
`).all(...e)}function W(o,e=!1,s=!1){z();let t=o?.cwd??process.cwd(),r=t?G.basename(t):"unknown-project",n=new k;try{let c=n.db.prepare(`
SELECT id, sdk_session_id, request, completed, next_steps, created_at, created_at_epoch
FROM session_summaries
WHERE project = ?
ORDER BY created_at_epoch DESC
LIMIT 4
`).all(r);if(c.length===0)return e?`
${a.bright}${a.cyan}\u{1F4DD} [${r}] recent context${a.reset}
${a.gray}${"\u2500".repeat(60)}${a.reset}
${i.dim}No previous sessions found for this project yet.${i.reset}
${a.dim}No previous sessions found for this project yet.${a.reset}
`:`# [${r}] recent context
No previous sessions found for this project yet.`;let p=_e(o,d).filter(b=>{let E=M(b.concepts);return E.includes("what-changed")||E.includes("how-it-works")||E.includes("problem-solution")||E.includes("gotcha")||E.includes("discovery")||E.includes("why-it-exists")||E.includes("decision")||E.includes("trade-off")}),c=o.db.prepare(`
SELECT request, completed, next_steps, created_at
FROM session_summaries
WHERE project = ?
ORDER BY created_at_epoch DESC
LIMIT 1
`).get(r),R=o.db.prepare(`
SELECT id, request, created_at, created_at_epoch
FROM session_summaries
WHERE project = ?
ORDER BY created_at_epoch DESC
LIMIT 3
`).all(r),n=[];if(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("")):(n.push(`# [${r}] recent context`),n.push("")),p.length>0){e?(n.push(`${i.bright}${i.blue}\u{1F4CB} RECENT ACTIVITY TIMELINE${i.reset}`),n.push("")):(n.push("## Recent Activity Timeline"),n.push("")),e?(n.push(`${i.dim}Legend: \u{1F3AF} session-request | \u{1F534} gotcha | \u{1F7E1} problem-solution | \u{1F535} how-it-works | \u{1F7E2} what-changed | \u{1F7E3} discovery | \u{1F7E0} why-it-exists | \u{1F7E4} decision | \u2696\uFE0F trade-off${i.reset}`),n.push("")):(n.push("**Legend:** \u{1F3AF} session-request | \u{1F534} gotcha | \u{1F7E1} problem-solution | \u{1F535} how-it-works | \u{1F7E2} what-changed | \u{1F7E3} discovery | \u{1F7E0} why-it-exists | \u{1F7E4} decision | \u2696\uFE0F trade-off"),n.push(""));let b=new Map;for(let h of p){let N=z(h.created_at),g=M(h.files_modified),I=g.length>0?me(g[0],t):"General";b.has(N)||b.set(N,new Map);let O=b.get(N);O.has(I)||O.set(I,[]),O.get(I).push(h)}let E=Array.from(b.entries()).sort((h,N)=>{let g=new Date(h[0]).getTime(),I=new Date(N[0]).getTime();return g-I});for(let[h,N]of E){e?(n.push(`${i.bright}${i.cyan}${h}${i.reset}`),n.push("")):(n.push(`### ${h}`),n.push(""));let g=R.filter(f=>z(f.created_at)===h);if(g.length>0){e?n.push(`${i.dim}Session Requests${i.reset}`):n.push("**Session Requests**"),e||(n.push("| ID | Time | Title | Link |"),n.push("|----|------|-------|------|"));let f=R[0]?.id;for(let S of g.slice().reverse()){let A=Q(S.created_at),v=S.request||"Session started",l=S.id===f?"":`claude-mem://session-summary/${S.id}`;if(e){let m=l?`${i.dim}[${l}]${i.reset}`:"";n.push(` ${i.dim}#S${S.id}${i.reset} ${i.dim}${A}${i.reset} \u{1F3AF} ${v} ${m}`)}else{let m=l?`[\u2192](${l})`:"-";n.push(`| #S${S.id} | ${A} | \u{1F3AF} ${v} | ${m} |`)}}n.push("")}let I=Array.from(N.entries()).sort((f,S)=>{let A=Math.min(...f[1].map(T=>T.created_at_epoch)),v=Math.min(...S[1].map(T=>T.created_at_epoch));return A-v}),O=0;for(let[f,S]of I){if(O>=10)break;e?n.push(`${i.dim}${f}${i.reset}`):n.push(`**${f}**`),e||(n.push("| ID | Time | T | Title | Tokens |"),n.push("|----|------|---|-------|--------|"));let A="",v=S.slice(0,5).reverse();for(let T of v){let l=M(T.concepts),m="\u2022";l.includes("gotcha")?m="\u{1F534}":l.includes("decision")?m="\u{1F7E4}":l.includes("trade-off")?m="\u2696\uFE0F":l.includes("problem-solution")?m="\u{1F7E1}":l.includes("discovery")?m="\u{1F7E3}":l.includes("why-it-exists")?m="\u{1F7E0}":l.includes("how-it-works")?m="\u{1F535}":l.includes("what-changed")&&(m="\u{1F7E2}");let y=Q(T.created_at),G=T.title||"Untitled",k=ue(T.narrative),W=y!==A,Z=W?y:"";if(A=y,e){let ee=W?`${i.dim}${y}${i.reset}`:" ".repeat(y.length),se=k>0?`${i.dim}(~${k}t)${i.reset}`:"";n.push(` ${i.dim}#${T.id}${i.reset} ${ee} ${m} ${G} ${se}`)}else n.push(`| #${T.id} | ${Z||"\u2033"} | ${m} | ${G} | ~${k} |`)}n.push(""),O++}}e?n.push(`${i.dim}Use claude-mem MCP search to access records with the given ID${i.reset}`):n.push("*Use claude-mem MCP search to access records with the given ID*"),n.push("")}return c&&(e?(n.push(`${i.bright}${i.cyan}\u{1F4CB} RECENT SESSION SUMMARY${i.reset} ${i.dim}(${J(c.created_at)})${i.reset}`),n.push("")):(n.push(`## Recent Session Summary *(${J(c.created_at)})*`),n.push("")),c.request&&(e?n.push(`${i.yellow}Request:${i.reset} ${c.request}`):n.push(`**Request**: ${c.request}`),n.push("")),c.completed&&(e?n.push(`${i.green}Completed:${i.reset} ${c.completed}`):n.push(`**Completed**: ${c.completed}`),n.push("")),c.next_steps&&(e?n.push(`${i.magenta}Next Steps:${i.reset} ${c.next_steps}`):n.push(`**Next Steps**: ${c.next_steps}`),n.push(""))),e&&(n.push(`${i.gray}${"\u2500".repeat(60)}${i.reset}`),n.push("")),n.join(`
`)}finally{o.close()}}import{stdin as P}from"process";try{let a=process.argv.includes("--index");if(P.isTTY){let e=F(void 0,!0,a);console.log(e),process.exit(0)}else{let e="";P.on("data",s=>e+=s),P.on("end",()=>{let s=e.trim()?JSON.parse(e):void 0,r={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:F(s,!1,a)}};console.log(JSON.stringify(r)),process.exit(0)})}}catch(a){console.error(`[claude-mem context-hook error: ${a.message}]`),process.exit(0)}
No previous sessions found for this project yet.`;let p=c.slice(0,3),u=[...new Set(p.map(N=>N.sdk_session_id))],b=Te(n,u).filter(N=>{let g=P(N.concepts);return g.includes("what-changed")||g.includes("how-it-works")||g.includes("problem-solution")||g.includes("gotcha")||g.includes("discovery")||g.includes("why-it-exists")||g.includes("decision")||g.includes("trade-off")}),i=[];if(e?(i.push(""),i.push(`${a.bright}${a.cyan}\u{1F4DD} [${r}] recent context${a.reset}`),i.push(`${a.gray}${"\u2500".repeat(60)}${a.reset}`),i.push("")):(i.push(`# [${r}] recent context`),i.push("")),b.length>0){e?(i.push(`${a.dim}Legend: \u{1F3AF} session-request | \u{1F534} gotcha | \u{1F7E1} problem-solution | \u{1F535} how-it-works | \u{1F7E2} what-changed | \u{1F7E3} discovery | \u{1F7E0} why-it-exists | \u{1F7E4} decision | \u2696\uFE0F trade-off${a.reset}`),i.push("")):(i.push("**Legend:** \u{1F3AF} session-request | \u{1F534} gotcha | \u{1F7E1} problem-solution | \u{1F535} how-it-works | \u{1F7E2} what-changed | \u{1F7E3} discovery | \u{1F7E0} why-it-exists | \u{1F7E4} decision | \u2696\uFE0F trade-off"),i.push(""));let N=c[0]?.id,g=p.map((d,l)=>{let m=l===0?null:c[l+1];return{...d,displayEpoch:m?m.created_at_epoch:d.created_at_epoch,displayTime:m?m.created_at:d.created_at,isMostRecent:d.id===N}}),C=[...b.map(d=>({type:"observation",data:d})),...g.map(d=>({type:"summary",data:d}))];C.sort((d,l)=>{let m=d.type==="observation"?d.data.created_at_epoch:d.data.displayEpoch,R=l.type==="observation"?l.data.created_at_epoch:l.data.displayEpoch;return m-R});let L=new Map;for(let d of C){let l=d.type==="observation"?d.data.created_at:d.data.displayTime,m=le(l);L.has(m)||L.set(m,[]),L.get(m).push(d)}let D=Array.from(L.entries()).sort((d,l)=>{let m=new Date(d[0]).getTime(),R=new Date(l[0]).getTime();return m-R});for(let[d,l]of D){e?(i.push(`${a.bright}${a.cyan}${d}${a.reset}`),i.push("")):(i.push(`### ${d}`),i.push(""));let m=null,R="",A=!1;for(let x of l)if(x.type==="summary"){A&&(i.push(""),A=!1,m=null,R="");let _=x.data,v=`${_.request||"Session started"} (${ue(_.displayTime)})`,S=_.isMostRecent?"":`claude-mem://session-summary/${_.id}`;if(e){let E=S?`${a.dim}[${S}]${a.reset}`:"";i.push(`\u{1F3AF} ${a.yellow}#S${_.id}${a.reset} ${v} ${E}`)}else{let E=S?` [\u2192](${S})`:"";i.push(`**\u{1F3AF} #S${_.id}** ${v}${E}`)}i.push("")}else{let _=x.data,v=P(_.files_modified),S=v.length>0?Ee(v[0],t):"General";S!==m&&(A&&i.push(""),e?i.push(`${a.dim}${S}${a.reset}`):i.push(`**${S}**`),e||(i.push("| ID | Time | T | Title | Tokens |"),i.push("|----|------|---|-------|--------|")),m=S,A=!0,R="");let E=P(_.concepts),f="\u2022";E.includes("gotcha")?f="\u{1F534}":E.includes("decision")?f="\u{1F7E4}":E.includes("trade-off")?f="\u2696\uFE0F":E.includes("problem-solution")?f="\u{1F7E1}":E.includes("discovery")?f="\u{1F7E3}":E.includes("why-it-exists")?f="\u{1F7E0}":E.includes("how-it-works")?f="\u{1F535}":E.includes("what-changed")&&(f="\u{1F7E2}");let y=me(_.created_at),B=_.title||"Untitled",w=_e(_.narrative),j=y!==R,Z=j?y:"";if(R=y,e){let ee=j?`${a.dim}${y}${a.reset}`:" ".repeat(y.length),se=w>0?`${a.dim}(~${w}t)${a.reset}`:"";i.push(` ${a.dim}#${_.id}${a.reset} ${ee} ${f} ${B} ${se}`)}else i.push(`| #${_.id} | ${Z||"\u2033"} | ${f} | ${B} | ~${w} |`)}A&&i.push("")}let h=c[0];h&&(h.completed||h.next_steps)&&(h.completed&&(e?i.push(`${a.green}Completed:${a.reset} ${h.completed}`):i.push(`**Completed**: ${h.completed}`),i.push("")),h.next_steps&&(e?i.push(`${a.magenta}Next Steps:${a.reset} ${h.next_steps}`):i.push(`**Next Steps**: ${h.next_steps}`),i.push(""))),e?i.push(`${a.dim}Use claude-mem MCP search to access records with the given ID${a.reset}`):i.push("*Use claude-mem MCP search to access records with the given ID*"),i.push("")}return e&&(i.push(`${a.gray}${"\u2500".repeat(60)}${a.reset}`),i.push("")),i.join(`
`)}finally{n.close()}}import{stdin as H}from"process";try{let o=process.argv.includes("--index");if(H.isTTY){let e=W(void 0,!0,o);console.log(e),process.exit(0)}else{let e="";H.on("data",s=>e+=s),H.on("end",()=>{let s=e.trim()?JSON.parse(e):void 0,r={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:W(s,!1,o)}};console.log(JSON.stringify(r)),process.exit(0)})}}catch(o){console.error(`[claude-mem context-hook error: ${o.message}]`),process.exit(0)}