#!/usr/bin/env node import y from"better-sqlite3";import{join as c,dirname as X,basename as j}from"path";import{homedir as O}from"os";import{existsSync as K,mkdirSync as C}from"fs";var p=process.env.CLAUDE_MEM_DATA_DIR||c(O(),".claude-mem"),g=process.env.CLAUDE_CONFIG_DIR||c(O(),".claude"),q=c(p,"archives"),J=c(p,"logs"),Y=c(p,"trash"),V=c(p,"backups"),Q=c(p,"settings.json"),L=c(p,"claude-mem.db"),z=c(g,"settings.json"),Z=c(g,"commands"),ee=c(g,"CLAUDE.md");function k(n){C(n,{recursive:!0})}var T=(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))(T||{}),b=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=T[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&&(S=` {${Object.entries(R).map(([N,v])=>`${N}=${v}`).join(", ")}}`)}let h=`[${i}] [${d}] [${_}] ${a}${t}${S}${m}`;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`})}},u=new b;var E=class{db;constructor(){k(p),this.db=new y(L),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}ensureWorkerPortColumn(){try{this.db.pragma("table_info(sdk_sessions)").some(t=>t.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"))}catch(e){console.error("[SessionStore] Migration error:",e.message)}}ensurePromptTrackingColumns(){try{this.db.pragma("table_info(sdk_sessions)").some(a=>a.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(a=>a.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(a=>a.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"));let _=this.db.pragma("index_list(session_summaries)").some(a=>a.unique===1)}catch(e){console.error("[SessionStore] Prompt tracking migration error:",e.message)}}removeSessionSummariesUniqueConstraint(){try{if(!this.db.pragma("index_list(session_summaries)").some(t=>t.unique===1))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, project TEXT NOT NULL, request TEXT, investigated TEXT, learned TEXT, completed TEXT, next_steps TEXT, files_read TEXT, files_edited TEXT, notes TEXT, prompt_number INTEGER, created_at TEXT NOT NULL, created_at_epoch INTEGER NOT NULL, FOREIGN KEY(sdk_session_id) REFERENCES sdk_sessions(sdk_session_id) ON DELETE CASCADE ) `),this.db.exec(` INSERT INTO session_summaries_new SELECT id, sdk_session_id, project, request, investigated, learned, completed, next_steps, files_read, files_edited, notes, prompt_number, created_at, created_at_epoch FROM session_summaries `),this.db.exec("DROP TABLE session_summaries"),this.db.exec("ALTER TABLE session_summaries_new RENAME TO session_summaries"),this.db.exec(` CREATE INDEX idx_session_summaries_sdk_session ON session_summaries(sdk_session_id); CREATE INDEX idx_session_summaries_project ON session_summaries(project); CREATE INDEX idx_session_summaries_created ON session_summaries(created_at_epoch DESC); `),this.db.exec("COMMIT"),console.error("[SessionStore] Successfully removed UNIQUE constraint from session_summaries.sdk_session_id")}catch(t){throw this.db.exec("ROLLBACK"),t}}catch(e){console.error("[SessionStore] Migration error (remove UNIQUE constraint):",e.message)}}addObservationHierarchicalFields(){try{if(this.db.pragma("table_info(observations)").some(t=>t.name==="title"))return;console.error("[SessionStore] Adding hierarchical fields to observations table..."),this.db.exec(` ALTER TABLE observations ADD COLUMN title TEXT; ALTER TABLE observations ADD COLUMN subtitle TEXT; ALTER TABLE observations ADD COLUMN facts TEXT; ALTER TABLE observations ADD COLUMN narrative TEXT; ALTER TABLE observations ADD COLUMN concepts TEXT; ALTER TABLE observations ADD COLUMN files_read TEXT; ALTER TABLE observations ADD COLUMN files_modified TEXT; `),console.error("[SessionStore] Successfully added hierarchical fields to observations table")}catch(e){console.error("[SessionStore] Migration error (add hierarchical fields):",e.message)}}makeObservationsTextNullable(){try{let s=this.db.pragma("table_info(observations)").find(t=>t.name==="text");if(!s||s.notnull===0)return;console.error("[SessionStore] Making observations.text nullable..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(` CREATE TABLE observations_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, sdk_session_id TEXT NOT NULL, project TEXT NOT NULL, text TEXT, type TEXT NOT NULL CHECK(type IN ('decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change')), title TEXT, subtitle TEXT, facts TEXT, narrative TEXT, concepts TEXT, files_read TEXT, files_modified TEXT, prompt_number INTEGER, created_at TEXT NOT NULL, created_at_epoch INTEGER NOT NULL, FOREIGN KEY(sdk_session_id) REFERENCES sdk_sessions(sdk_session_id) ON DELETE CASCADE ) `),this.db.exec(` INSERT INTO observations_new SELECT id, sdk_session_id, project, text, type, title, subtitle, facts, narrative, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch FROM observations `),this.db.exec("DROP TABLE observations"),this.db.exec("ALTER TABLE observations_new RENAME TO observations"),this.db.exec(` CREATE INDEX idx_observations_sdk_session ON observations(sdk_session_id); CREATE INDEX idx_observations_project ON observations(project); CREATE INDEX idx_observations_type ON observations(type); CREATE INDEX idx_observations_created ON observations(created_at_epoch DESC); `),this.db.exec("COMMIT"),console.error("[SessionStore] Successfully made observations.text nullable")}catch(t){throw this.db.exec("ROLLBACK"),t}}catch(e){console.error("[SessionStore] Migration error (make text nullable):",e.message)}}getRecentSummaries(e,s=10){return this.db.prepare(` SELECT request, investigated, learned, completed, next_steps, files_read, files_edited, notes, prompt_number, created_at FROM session_summaries WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ? `).all(e,s)}getRecentObservations(e,s=20){return this.db.prepare(` SELECT type, text, prompt_number, created_at FROM observations WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ? `).all(e,s)}getRecentSessionsWithStatus(e,s=3){return this.db.prepare(` SELECT * FROM ( SELECT s.sdk_session_id, s.status, s.started_at, s.started_at_epoch, s.user_prompt, CASE WHEN sum.sdk_session_id IS NOT NULL THEN 1 ELSE 0 END as has_summary FROM sdk_sessions s LEFT JOIN session_summaries sum ON s.sdk_session_id = sum.sdk_session_id WHERE s.project = ? AND s.sdk_session_id IS NOT NULL GROUP BY s.sdk_session_id ORDER BY s.started_at_epoch DESC LIMIT ? ) ORDER BY started_at_epoch ASC `).all(e,s)}getObservationsForSession(e){return this.db.prepare(` SELECT title, subtitle, type, prompt_number FROM observations WHERE sdk_session_id = ? ORDER BY created_at_epoch ASC `).all(e)}getSummaryForSession(e){return this.db.prepare(` SELECT request, investigated, learned, completed, next_steps, files_read, files_edited, notes, prompt_number, created_at FROM session_summaries WHERE sdk_session_id = ? ORDER BY created_at_epoch DESC LIMIT 1 `).get(e)||null}getSessionById(e){return this.db.prepare(` SELECT id, sdk_session_id, project, user_prompt FROM sdk_sessions WHERE id = ? LIMIT 1 `).get(e)||null}findActiveSDKSession(e){return this.db.prepare(` SELECT id, sdk_session_id, project, worker_port FROM sdk_sessions WHERE claude_session_id = ? AND status = 'active' LIMIT 1 `).get(e)||null}findAnySDKSession(e){return this.db.prepare(` SELECT id FROM sdk_sessions WHERE claude_session_id = ? LIMIT 1 `).get(e)||null}reactivateSession(e,s){this.db.prepare(` UPDATE sdk_sessions SET status = 'active', user_prompt = ?, worker_port = NULL WHERE id = ? `).run(s,e)}incrementPromptCounter(e){return this.db.prepare(` UPDATE sdk_sessions SET prompt_counter = COALESCE(prompt_counter, 0) + 1 WHERE id = ? `).run(e),this.db.prepare(` 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();return this.db.prepare(` INSERT INTO sdk_sessions (claude_session_id, project, user_prompt, started_at, started_at_epoch, status) VALUES (?, ?, ?, ?, ?, 'active') `).run(e,s,t,r.toISOString(),o).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?(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 = ? `).run(s,e)}getWorkerPort(e){return this.db.prepare(` SELECT worker_port FROM sdk_sessions WHERE id = ? LIMIT 1 `).get(e)?.worker_port||null}storeObservation(e,s,t,r){let o=new Date,i=o.getTime();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(),i)}storeSummary(e,s,t,r){let o=new Date,i=o.getTime();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(),i)}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 = ? `).run(s.toISOString(),t,e)}markSessionFailed(e){let s=new Date,t=s.getTime();this.db.prepare(` UPDATE sdk_sessions SET status = 'failed', completed_at = ?, completed_at_epoch = ? WHERE id = ? `).run(s.toISOString(),t,e)}cleanupOrphanedSessions(){let e=new Date,s=e.getTime();return this.db.prepare(` UPDATE sdk_sessions SET status = 'failed', completed_at = ?, completed_at_epoch = ? WHERE status = 'active' `).run(e.toISOString(),s).changes}close(){this.db.close()}};function D(n,e,s){return n==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:n==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:n==="UserPromptSubmit"||n==="PostToolUse"?{continue:!0,suppressOutput:!0}:n==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function l(n,e,s={}){let t=D(n,e,s);return JSON.stringify(t)}var x=new Set(["ListMcpResourcesTool"]);async function A(n){if(!n)throw new Error("saveHook requires input");let{session_id:e,tool_name:s,tool_input:t,tool_output:r}=n;if(x.has(s)){console.log(l("PostToolUse",!0));return}let o=new E,i=o.findActiveSDKSession(e);if(!i){o.close(),console.log(l("PostToolUse",!0));return}if(!i.worker_port){o.close(),u.error("HOOK","No worker port for session",{sessionId:i.id}),console.log(l("PostToolUse",!0));return}let d=o.getPromptCounter(i.id);o.close();let _=u.formatTool(s,t);try{u.dataIn("HOOK",`PostToolUse: ${_}`,{sessionId:i.id,workerPort:i.worker_port});let a=await fetch(`http://127.0.0.1:${i.worker_port}/sessions/${i.id}/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tool_name:s,tool_input:t!==void 0?JSON.stringify(t):"{}",tool_output:r!==void 0?JSON.stringify(r):"{}",prompt_number:d}),signal:AbortSignal.timeout(2e3)});if(a.ok)u.debug("HOOK","Observation sent successfully",{sessionId:i.id,toolName:s});else{let m=await a.text();u.failure("HOOK","Failed to send observation",{sessionId:i.id,status:a.status},m)}}catch(a){u.failure("HOOK","Error sending observation",{sessionId:i.id},a)}finally{console.log(l("PostToolUse",!0))}}import{stdin as I}from"process";var f="";I.on("data",n=>f+=n);I.on("end",async()=>{try{let n=f.trim()?JSON.parse(f):void 0;await A(n),process.exit(0)}catch(n){console.error(`[claude-mem save-hook error: ${n.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}});