diff --git a/claude-mem/.claude-plugin/plugin.json b/claude-mem/.claude-plugin/plugin.json index 8f763679..ed7ff5da 100644 --- a/claude-mem/.claude-plugin/plugin.json +++ b/claude-mem/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "claude-mem", - "version": "3.9.16", + "version": "3.9.17", "description": "Persistent memory system for Claude Code - seamlessly preserve context across sessions", "author": { "name": "Alex Newman" diff --git a/claude-mem/.mcp.json b/claude-mem/.mcp.json index e0c7e57f..422931fc 100644 --- a/claude-mem/.mcp.json +++ b/claude-mem/.mcp.json @@ -2,7 +2,7 @@ "mcpServers": { "claude-mem": { "command": "uvx", - "args": ["chroma-mcp", "--client-type", "persistent", "--data-dir", "~/.claude-mem/chroma"] + "args": ["chroma-mcp", "--client-type", "persistent", "--data-dir", "${CLAUDE_PLUGIN_ROOT}/claude-mem-chroma"] } } } \ No newline at end of file diff --git a/package.json b/package.json index 7336b96a..319abe3c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claude-mem", - "version": "3.9.16", + "version": "3.9.17", "description": "Memory compression system for Claude Code - persist context across sessions", "keywords": [ "claude", diff --git a/scripts/hooks/context-hook.js b/scripts/hooks/context-hook.js index 65744b0b..2d262d94 100755 --- a/scripts/hooks/context-hook.js +++ b/scripts/hooks/context-hook.js @@ -1,6 +1,6 @@ #!/usr/bin/env bun // @bun -import{Database as x}from"bun:sqlite";import{join as K,dirname as E,basename as T}from"path";import{homedir as G}from"os";import{existsSync as w,mkdirSync as O}from"fs";var B=process.env.CLAUDE_MEM_DATA_DIR||K(G(),".claude-mem"),M=process.env.CLAUDE_CONFIG_DIR||K(G(),".claude"),k=K(B,"archives"),S=K(B,"logs"),l=K(B,"trash"),h=K(B,"backups"),R=K(B,"chroma"),A=K(B,"settings.json"),L=K(B,"claude-mem.db"),j=K(M,"settings.json"),_=K(M,"commands"),I=K(M,"CLAUDE.md");function N(z){O(z,{recursive:!0})}class q{db;constructor(){N(B),this.db=new x(L,{create:!0,readwrite:!0}),this.db.run("PRAGMA journal_mode = WAL"),this.db.run("PRAGMA synchronous = NORMAL"),this.db.run("PRAGMA foreign_keys = ON")}getRecentSummaries(z,X=10){return this.db.query(` +import E from"path";import{Database as x}from"bun:sqlite";import{join as K,dirname as H,basename as P}from"path";import{homedir as L}from"os";import{existsSync as T,mkdirSync as U}from"fs";var B=process.env.CLAUDE_MEM_DATA_DIR||K(L(),".claude-mem"),J=process.env.CLAUDE_CONFIG_DIR||K(L(),".claude"),l=K(B,"archives"),A=K(B,"logs"),R=K(B,"trash"),j=K(B,"backups"),_=K(B,"chroma"),k=K(B,"settings.json"),N=K(B,"claude-mem.db"),I=K(J,"settings.json"),h=K(J,"commands"),y=K(J,"CLAUDE.md");function O(Q){U(Q,{recursive:!0})}class M{db;constructor(){O(B),this.db=new x(N,{create:!0,readwrite:!0}),this.db.run("PRAGMA journal_mode = WAL"),this.db.run("PRAGMA synchronous = NORMAL"),this.db.run("PRAGMA foreign_keys = ON")}getRecentSummaries(Q,Y=10){return this.db.query(` SELECT request, investigated, learned, completed, next_steps, files_read, files_edited, notes, created_at @@ -8,37 +8,37 @@ import{Database as x}from"bun:sqlite";import{join as K,dirname as E,basename as WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ? - `).all(z,X)}findActiveSDKSession(z){return this.db.query(` + `).all(Q,Y)}findActiveSDKSession(Q){return this.db.query(` SELECT id, sdk_session_id, project FROM sdk_sessions WHERE claude_session_id = ? AND status = 'active' LIMIT 1 - `).get(z)||null}createSDKSession(z,X,Y){let $=new Date,Q=$.getTime();return this.db.query(` + `).get(Q)||null}createSDKSession(Q,Y,X){let z=new Date,W=z.getTime();return this.db.query(` INSERT INTO sdk_sessions (claude_session_id, project, user_prompt, started_at, started_at_epoch, status) VALUES (?, ?, ?, ?, ?, 'active') - `).run(z,X,Y,$.toISOString(),Q),this.db.query("SELECT last_insert_rowid() as id").get().id}updateSDKSessionId(z,X){this.db.query(` + `).run(Q,Y,X,z.toISOString(),W),this.db.query("SELECT last_insert_rowid() as id").get().id}updateSDKSessionId(Q,Y){this.db.query(` UPDATE sdk_sessions SET sdk_session_id = ? WHERE id = ? - `).run(X,z)}storeObservation(z,X,Y,$){let Q=new Date,V=Q.getTime();this.db.query(` + `).run(Y,Q)}storeObservation(Q,Y,X,z){let W=new Date,Z=W.getTime();this.db.query(` INSERT INTO observations (sdk_session_id, project, text, type, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?) - `).run(z,X,$,Y,Q.toISOString(),V)}storeSummary(z,X,Y){let $=new Date,Q=$.getTime();this.db.query(` + `).run(Q,Y,z,X,W.toISOString(),Z)}storeSummary(Q,Y,X){let z=new Date,W=z.getTime();this.db.query(` INSERT INTO session_summaries (sdk_session_id, project, request, investigated, learned, completed, next_steps, files_read, files_edited, notes, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run(z,X,Y.request||null,Y.investigated||null,Y.learned||null,Y.completed||null,Y.next_steps||null,Y.files_read||null,Y.files_edited||null,Y.notes||null,$.toISOString(),Q)}markSessionCompleted(z){let X=new Date,Y=X.getTime();this.db.query(` + `).run(Q,Y,X.request||null,X.investigated||null,X.learned||null,X.completed||null,X.next_steps||null,X.files_read||null,X.files_edited||null,X.notes||null,z.toISOString(),W)}markSessionCompleted(Q){let Y=new Date,X=Y.getTime();this.db.query(` UPDATE sdk_sessions SET status = 'completed', completed_at = ?, completed_at_epoch = ? WHERE id = ? - `).run(X.toISOString(),Y,z)}markSessionFailed(z){let X=new Date,Y=X.getTime();this.db.query(` + `).run(Y.toISOString(),X,Q)}markSessionFailed(Q){let Y=new Date,X=Y.getTime();this.db.query(` UPDATE sdk_sessions SET status = 'failed', completed_at = ?, completed_at_epoch = ? WHERE id = ? - `).run(X.toISOString(),Y,z)}close(){this.db.close()}}import v from"path";function U(z){try{if(console.error("[claude-mem context] Hook fired with input:",JSON.stringify({session_id:z?.session_id,transcript_path:z?.transcript_path,hook_event_name:z?.hook_event_name,source:z?.source,has_input:!!z})),!z)console.error("[claude-mem context] No input provided - exiting (standalone mode)"),console.log("No input provided - this script is designed to run as a Claude Code SessionStart hook"),process.exit(0);let X=z.cwd?v.basename(z.cwd):v.basename(v.dirname(z.transcript_path));console.error("[claude-mem context] Extracted project name:",X,"from",z.cwd?"cwd":"transcript_path"),console.error("[claude-mem context] Querying database for recent summaries...");let Y=new q,$=Y.getRecentSummaries(X,5);if(Y.close(),console.error("[claude-mem context] Database query complete - found",$.length,"summaries"),$.length>0)console.error("[claude-mem context] Summary previews:"),$.forEach((Z,W)=>{let F=Z.request?.substring(0,100)||Z.completed?.substring(0,100)||"(no content)";console.error(` [${W+1}]`,F+(F.length>=100?"...":""))});if($.length===0)console.error("[claude-mem context] No summaries found - outputting empty context message"),console.log(`# Recent Session Context + `).run(Y.toISOString(),X,Q)}close(){this.db.close()}}function b(Q,Y,X){if(Q==="PreCompact"){if(Y)return{continue:!0,suppressOutput:!0};return{continue:!1,stopReason:X.reason||"Pre-compact operation failed",suppressOutput:!0}}if(Q==="SessionStart"){if(Y&&X.context)return{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:X.context}};return{continue:!0,suppressOutput:!0}}if(Q==="UserPromptSubmit"||Q==="PostToolUse")return{continue:!0,suppressOutput:!0};if(Q==="Stop")return{continue:!0,suppressOutput:!0};return{continue:Y,suppressOutput:!0,...X.reason&&!Y?{stopReason:X.reason}:{}}}function q(Q,Y,X={}){let z=b(Q,Y,X);return JSON.stringify(z)}function F(Q){let Y=Q?.cwd??process.cwd(),X=Y?E.basename(Y):"unknown-project",z=new M;try{let W=z.getRecentSummaries(X,5);if(W.length===0){let $=q("SessionStart",!0,{context:`# Recent Session Context -No previous sessions found for this project yet.`),process.exit(0);console.error("[claude-mem context] Building markdown context from summaries...");let Q=[];Q.push("# Recent Session Context"),Q.push("");let V=$.length===1?"session":"sessions";Q.push(`Showing last ${$.length} ${V} for **${X}**:`),Q.push("");for(let Z of $){if(Q.push("---"),Q.push(""),Z.request)Q.push(`**Request:** ${Z.request}`);if(Z.completed)Q.push(`**Completed:** ${Z.completed}`);if(Z.learned)Q.push(`**Learned:** ${Z.learned}`);if(Z.next_steps)Q.push(`**Next Steps:** ${Z.next_steps}`);if(Z.files_read)try{let W=JSON.parse(Z.files_read);if(Array.isArray(W)&&W.length>0)Q.push(`**Files Read:** ${W.join(", ")}`)}catch{if(Z.files_read.trim())Q.push(`**Files Read:** ${Z.files_read}`)}if(Z.files_edited)try{let W=JSON.parse(Z.files_edited);if(Array.isArray(W)&&W.length>0)Q.push(`**Files Edited:** ${W.join(", ")}`)}catch{if(Z.files_edited.trim())Q.push(`**Files Edited:** ${Z.files_edited}`)}Q.push(`**Date:** ${Z.created_at.split("T")[0]}`),Q.push("")}let J=Q.join(` -`);console.error("[claude-mem context] Markdown built successfully"),console.error("[claude-mem context] Output length:",J.length,"characters,",Q.length,"lines"),console.error("[claude-mem context] Output preview (first 200 chars):",J.substring(0,200)+"..."),console.error("[claude-mem context] Outputting context to stdout for Claude Code injection"),console.log(J),console.error("[claude-mem context] Context hook completed successfully"),process.exit(0)}catch(X){console.error("[claude-mem context] ERROR occurred during context hook execution"),console.error("[claude-mem context] Error message:",X.message),console.error("[claude-mem context] Error stack:",X.stack),console.error("[claude-mem context] Exiting gracefully to avoid blocking Claude Code"),process.exit(0)}}var H=await Bun.stdin.text();try{let z=H.trim()?JSON.parse(H):void 0;U(z)}catch(z){console.error(`[claude-mem context-hook error: ${z.message}]`),process.exit(0)} +No previous sessions found for this project yet.`});console.log($);return}let Z=[];Z.push("# Recent Session Context"),Z.push("");let v=W.length===1?"session":"sessions";Z.push(`Showing last ${W.length} ${v} for **${X}**:`),Z.push("");for(let $ of W){if(Z.push("---"),Z.push(""),$.request)Z.push(`**Request:** ${$.request}`);if($.completed)Z.push(`**Completed:** ${$.completed}`);if($.learned)Z.push(`**Learned:** ${$.learned}`);if($.next_steps)Z.push(`**Next Steps:** ${$.next_steps}`);if($.files_read)try{let V=JSON.parse($.files_read);if(Array.isArray(V)&&V.length>0)Z.push(`**Files Read:** ${V.join(", ")}`)}catch{if($.files_read.trim())Z.push(`**Files Read:** ${$.files_read}`)}if($.files_edited)try{let V=JSON.parse($.files_edited);if(Array.isArray(V)&&V.length>0)Z.push(`**Files Edited:** ${V.join(", ")}`)}catch{if($.files_edited.trim())Z.push(`**Files Edited:** ${$.files_edited}`)}Z.push(`**Date:** ${$.created_at.split("T")[0]}`),Z.push("")}let G=q("SessionStart",!0,{context:Z.join(` +`)});console.log(G)}finally{z.close()}}try{if(process.stdin.isTTY)F();else{let Q=await Bun.stdin.text(),Y=Q.trim()?JSON.parse(Q):void 0;F(Y)}}catch(Q){console.error(`[claude-mem context-hook error: ${Q.message}]`),process.exit(0)} diff --git a/scripts/hooks/new-hook.js b/scripts/hooks/new-hook.js index 7ac06ba3..c9f46461 100755 --- a/scripts/hooks/new-hook.js +++ b/scripts/hooks/new-hook.js @@ -1,6 +1,6 @@ #!/usr/bin/env bun // @bun -import{Database as f}from"bun:sqlite";import{join as X,dirname as g,basename as C}from"path";import{homedir as q}from"os";import{existsSync as S,mkdirSync as U}from"fs";var Z=process.env.CLAUDE_MEM_DATA_DIR||X(q(),".claude-mem"),B=process.env.CLAUDE_CONFIG_DIR||X(q(),".claude"),l=X(Z,"archives"),y=X(Z,"logs"),h=X(Z,"trash"),j=X(Z,"backups"),A=X(Z,"chroma"),R=X(Z,"settings.json"),F=X(Z,"claude-mem.db"),_=X(B,"settings.json"),I=X(B,"commands"),c=X(B,"CLAUDE.md");function G(z){U(z,{recursive:!0})}class V{db;constructor(){G(Z),this.db=new f(F,{create:!0,readwrite:!0}),this.db.run("PRAGMA journal_mode = WAL"),this.db.run("PRAGMA synchronous = NORMAL"),this.db.run("PRAGMA foreign_keys = ON")}getRecentSummaries(z,Q=10){return this.db.query(` +import{spawn as b}from"child_process";import L from"path";import{Database as E}from"bun:sqlite";import{join as Z,dirname as w,basename as S}from"path";import{homedir as F}from"os";import{existsSync as j,mkdirSync as f}from"fs";var $=process.env.CLAUDE_MEM_DATA_DIR||Z(F(),".claude-mem"),B=process.env.CLAUDE_CONFIG_DIR||Z(F(),".claude"),R=Z($,"archives"),y=Z($,"logs"),_=Z($,"trash"),k=Z($,"backups"),I=Z($,"chroma"),h=Z($,"settings.json"),G=Z($,"claude-mem.db"),d=Z(B,"settings.json"),D=Z(B,"commands"),m=Z(B,"CLAUDE.md");function x(Q){f(Q,{recursive:!0})}class V{db;constructor(){x($),this.db=new E(G,{create:!0,readwrite:!0}),this.db.run("PRAGMA journal_mode = WAL"),this.db.run("PRAGMA synchronous = NORMAL"),this.db.run("PRAGMA foreign_keys = ON")}getRecentSummaries(Q,X=10){return this.db.query(` SELECT request, investigated, learned, completed, next_steps, files_read, files_edited, notes, created_at @@ -8,35 +8,34 @@ import{Database as f}from"bun:sqlite";import{join as X,dirname as g,basename as WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ? - `).all(z,Q)}findActiveSDKSession(z){return this.db.query(` + `).all(Q,X)}findActiveSDKSession(Q){return this.db.query(` SELECT id, sdk_session_id, project FROM sdk_sessions WHERE claude_session_id = ? AND status = 'active' LIMIT 1 - `).get(z)||null}createSDKSession(z,Q,W){let Y=new Date,$=Y.getTime();return this.db.query(` + `).get(Q)||null}createSDKSession(Q,X,W){let Y=new Date,z=Y.getTime();return this.db.query(` INSERT INTO sdk_sessions (claude_session_id, project, user_prompt, started_at, started_at_epoch, status) VALUES (?, ?, ?, ?, ?, 'active') - `).run(z,Q,W,Y.toISOString(),$),this.db.query("SELECT last_insert_rowid() as id").get().id}updateSDKSessionId(z,Q){this.db.query(` + `).run(Q,X,W,Y.toISOString(),z),this.db.query("SELECT last_insert_rowid() as id").get().id}updateSDKSessionId(Q,X){this.db.query(` UPDATE sdk_sessions SET sdk_session_id = ? WHERE id = ? - `).run(Q,z)}storeObservation(z,Q,W,Y){let $=new Date,K=$.getTime();this.db.query(` + `).run(X,Q)}storeObservation(Q,X,W,Y){let z=new Date,K=z.getTime();this.db.query(` INSERT INTO observations (sdk_session_id, project, text, type, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?) - `).run(z,Q,Y,W,$.toISOString(),K)}storeSummary(z,Q,W){let Y=new Date,$=Y.getTime();this.db.query(` + `).run(Q,X,Y,W,z.toISOString(),K)}storeSummary(Q,X,W){let Y=new Date,z=Y.getTime();this.db.query(` INSERT INTO session_summaries (sdk_session_id, project, request, investigated, learned, completed, next_steps, files_read, files_edited, notes, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run(z,Q,W.request||null,W.investigated||null,W.learned||null,W.completed||null,W.next_steps||null,W.files_read||null,W.files_edited||null,W.notes||null,Y.toISOString(),$)}markSessionCompleted(z){let Q=new Date,W=Q.getTime();this.db.query(` + `).run(Q,X,W.request||null,W.investigated||null,W.learned||null,W.completed||null,W.next_steps||null,W.files_read||null,W.files_edited||null,W.notes||null,Y.toISOString(),z)}markSessionCompleted(Q){let X=new Date,W=X.getTime();this.db.query(` UPDATE sdk_sessions SET status = 'completed', completed_at = ?, completed_at_epoch = ? WHERE id = ? - `).run(Q.toISOString(),W,z)}markSessionFailed(z){let Q=new Date,W=Q.getTime();this.db.query(` + `).run(X.toISOString(),W,Q)}markSessionFailed(Q){let X=new Date,W=X.getTime();this.db.query(` UPDATE sdk_sessions SET status = 'failed', completed_at = ?, completed_at_epoch = ? WHERE id = ? - `).run(Q.toISOString(),W,z)}close(){this.db.close()}}import x from"path";import{spawn as H}from"child_process";function L(z){try{if(!z)console.log("No input provided - this script is designed to run as a Claude Code UserPromptSubmit hook"),console.log(` -Expected input format:`),console.log(JSON.stringify({session_id:"string",cwd:"string",prompt:"string"},null,2)),process.exit(0);let{session_id:Q,cwd:W,prompt:Y}=z,$=x.basename(W),K=new V;if(K.findActiveSDKSession(Q))K.close(),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0);let J=K.createSDKSession(Q,$,Y);K.close();let M=process.env.CLAUDE_PLUGIN_ROOT;if(!M)throw new Error("CLAUDE_PLUGIN_ROOT not set - claude-mem must be installed as a Claude Code plugin");let N=x.join(M,"scripts","hooks","worker.js");H("bun",[N,J.toString()],{detached:!0,stdio:"ignore"}).unref(),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}catch(Q){console.error(`[claude-mem new error: ${Q.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}}var O=await Bun.stdin.text();try{let z=O.trim()?JSON.parse(O):void 0;L(z)}catch(z){console.error(`[claude-mem new-hook error: ${z.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)} + `).run(X.toISOString(),W,Q)}close(){this.db.close()}}function H(Q,X,W){if(Q==="PreCompact"){if(X)return{continue:!0,suppressOutput:!0};return{continue:!1,stopReason:W.reason||"Pre-compact operation failed",suppressOutput:!0}}if(Q==="SessionStart"){if(X&&W.context)return{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:W.context}};return{continue:!0,suppressOutput:!0}}if(Q==="UserPromptSubmit"||Q==="PostToolUse")return{continue:!0,suppressOutput:!0};if(Q==="Stop")return{continue:!0,suppressOutput:!0};return{continue:X,suppressOutput:!0,...W.reason&&!X?{stopReason:W.reason}:{}}}function v(Q,X,W={}){let Y=H(Q,X,W);return JSON.stringify(Y)}function O(Q){if(!Q)throw new Error("newHook requires input");let{session_id:X,cwd:W,prompt:Y}=Q,z=L.basename(W),K=new V;try{if(K.findActiveSDKSession(X)){console.log(v("UserPromptSubmit",!0));return}let M=K.createSDKSession(X,z,Y),q=process.env.CLAUDE_PLUGIN_ROOT;if(!q)throw new Error("CLAUDE_PLUGIN_ROOT not set");let U=L.join(q,"scripts","hooks","worker.js");b("bun",[U,M.toString()],{detached:!0,stdio:"ignore"}).unref(),console.log(v("UserPromptSubmit",!0))}finally{K.close()}}var N=await Bun.stdin.text();try{let Q=N.trim()?JSON.parse(N):void 0;O(Q)}catch(Q){console.error(`[claude-mem new-hook error: ${Q.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)} diff --git a/scripts/hooks/save-hook.js b/scripts/hooks/save-hook.js index 6e222add..12d18c2c 100755 --- a/scripts/hooks/save-hook.js +++ b/scripts/hooks/save-hook.js @@ -1,6 +1,6 @@ #!/usr/bin/env bun // @bun -import b from"net";import{Database as O}from"bun:sqlite";import{join as Y,dirname as w,basename as C}from"path";import{homedir as F}from"os";import{existsSync as S,mkdirSync as H}from"fs";var $=process.env.CLAUDE_MEM_DATA_DIR||Y(F(),".claude-mem"),J=process.env.CLAUDE_CONFIG_DIR||Y(F(),".claude"),h=Y($,"archives"),l=Y($,"logs"),R=Y($,"trash"),j=Y($,"backups"),A=Y($,"chroma"),I=Y($,"settings.json"),G=Y($,"claude-mem.db"),_=Y(J,"settings.json"),p=Y(J,"commands"),d=Y(J,"CLAUDE.md");function v(z){return Y($,`worker-${z}.sock`)}function x(z){H(z,{recursive:!0})}class M{db;constructor(){x($),this.db=new O(G,{create:!0,readwrite:!0}),this.db.run("PRAGMA journal_mode = WAL"),this.db.run("PRAGMA synchronous = NORMAL"),this.db.run("PRAGMA foreign_keys = ON")}getRecentSummaries(z,X=10){return this.db.query(` +import w from"net";import{Database as H}from"bun:sqlite";import{join as $,dirname as T,basename as l}from"path";import{homedir as x}from"os";import{existsSync as y,mkdirSync as E}from"fs";var z=process.env.CLAUDE_MEM_DATA_DIR||$(x(),".claude-mem"),M=process.env.CLAUDE_CONFIG_DIR||$(x(),".claude"),I=$(z,"archives"),k=$(z,"logs"),_=$(z,"trash"),h=$(z,"backups"),D=$(z,"chroma"),d=$(z,"settings.json"),N=$(z,"claude-mem.db"),m=$(M,"settings.json"),u=$(M,"commands"),c=$(M,"CLAUDE.md");function L(Q){return $(z,`worker-${Q}.sock`)}function U(Q){E(Q,{recursive:!0})}class q{db;constructor(){U(z),this.db=new H(N,{create:!0,readwrite:!0}),this.db.run("PRAGMA journal_mode = WAL"),this.db.run("PRAGMA synchronous = NORMAL"),this.db.run("PRAGMA foreign_keys = ON")}getRecentSummaries(Q,Y=10){return this.db.query(` SELECT request, investigated, learned, completed, next_steps, files_read, files_edited, notes, created_at @@ -8,36 +8,35 @@ import b from"net";import{Database as O}from"bun:sqlite";import{join as Y,dirnam WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ? - `).all(z,X)}findActiveSDKSession(z){return this.db.query(` + `).all(Q,Y)}findActiveSDKSession(Q){return this.db.query(` SELECT id, sdk_session_id, project FROM sdk_sessions WHERE claude_session_id = ? AND status = 'active' LIMIT 1 - `).get(z)||null}createSDKSession(z,X,Q){let Z=new Date,W=Z.getTime();return this.db.query(` + `).get(Q)||null}createSDKSession(Q,Y,X){let Z=new Date,W=Z.getTime();return this.db.query(` INSERT INTO sdk_sessions (claude_session_id, project, user_prompt, started_at, started_at_epoch, status) VALUES (?, ?, ?, ?, ?, 'active') - `).run(z,X,Q,Z.toISOString(),W),this.db.query("SELECT last_insert_rowid() as id").get().id}updateSDKSessionId(z,X){this.db.query(` + `).run(Q,Y,X,Z.toISOString(),W),this.db.query("SELECT last_insert_rowid() as id").get().id}updateSDKSessionId(Q,Y){this.db.query(` UPDATE sdk_sessions SET sdk_session_id = ? WHERE id = ? - `).run(X,z)}storeObservation(z,X,Q,Z){let W=new Date,B=W.getTime();this.db.query(` + `).run(Y,Q)}storeObservation(Q,Y,X,Z){let W=new Date,B=W.getTime();this.db.query(` INSERT INTO observations (sdk_session_id, project, text, type, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?) - `).run(z,X,Z,Q,W.toISOString(),B)}storeSummary(z,X,Q){let Z=new Date,W=Z.getTime();this.db.query(` + `).run(Q,Y,Z,X,W.toISOString(),B)}storeSummary(Q,Y,X){let Z=new Date,W=Z.getTime();this.db.query(` INSERT INTO session_summaries (sdk_session_id, project, request, investigated, learned, completed, next_steps, files_read, files_edited, notes, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run(z,X,Q.request||null,Q.investigated||null,Q.learned||null,Q.completed||null,Q.next_steps||null,Q.files_read||null,Q.files_edited||null,Q.notes||null,Z.toISOString(),W)}markSessionCompleted(z){let X=new Date,Q=X.getTime();this.db.query(` + `).run(Q,Y,X.request||null,X.investigated||null,X.learned||null,X.completed||null,X.next_steps||null,X.files_read||null,X.files_edited||null,X.notes||null,Z.toISOString(),W)}markSessionCompleted(Q){let Y=new Date,X=Y.getTime();this.db.query(` UPDATE sdk_sessions SET status = 'completed', completed_at = ?, completed_at_epoch = ? WHERE id = ? - `).run(X.toISOString(),Q,z)}markSessionFailed(z){let X=new Date,Q=X.getTime();this.db.query(` + `).run(Y.toISOString(),X,Q)}markSessionFailed(Q){let Y=new Date,X=Y.getTime();this.db.query(` UPDATE sdk_sessions SET status = 'failed', completed_at = ?, completed_at_epoch = ? WHERE id = ? - `).run(X.toISOString(),Q,z)}close(){this.db.close()}}var E=new Set(["TodoWrite","ListMcpResourcesTool"]);function N(z){try{if(!z)console.log("No input provided - this script is designed to run as a Claude Code PostToolUse hook"),console.log(` -Expected input format:`),console.log(JSON.stringify({session_id:"string",cwd:"string",tool_name:"string",tool_input:{},tool_output:{}},null,2)),process.exit(0);let{session_id:X,tool_name:Q,tool_input:Z,tool_output:W}=z;if(E.has(Q))console.log('{"continue": true, "suppressOutput": true}'),process.exit(0);let B=new M,K=B.findActiveSDKSession(X);if(B.close(),!K)console.log('{"continue": true, "suppressOutput": true}'),process.exit(0);let q=v(K.id),U={type:"observation",tool_name:Q,tool_input:JSON.stringify(Z),tool_output:JSON.stringify(W)},V=b.connect(q,()=>{V.write(JSON.stringify(U)+` -`),V.end()});V.on("error",(f)=>{console.error(`[claude-mem save] Socket error: ${f.message}`)}),V.on("close",()=>{console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)})}catch(X){console.error(`[claude-mem save error: ${X.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}}var L=await Bun.stdin.text();try{let z=L.trim()?JSON.parse(L):void 0;N(z)}catch(z){console.error(`[claude-mem save-hook error: ${z.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)} + `).run(Y.toISOString(),X,Q)}close(){this.db.close()}}function g(Q,Y,X){if(Q==="PreCompact"){if(Y)return{continue:!0,suppressOutput:!0};return{continue:!1,stopReason:X.reason||"Pre-compact operation failed",suppressOutput:!0}}if(Q==="SessionStart"){if(Y&&X.context)return{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:X.context}};return{continue:!0,suppressOutput:!0}}if(Q==="UserPromptSubmit"||Q==="PostToolUse")return{continue:!0,suppressOutput:!0};if(Q==="Stop")return{continue:!0,suppressOutput:!0};return{continue:Y,suppressOutput:!0,...X.reason&&!Y?{stopReason:X.reason}:{}}}function J(Q,Y,X={}){let Z=g(Q,Y,X);return JSON.stringify(Z)}var C=new Set(["TodoWrite","ListMcpResourcesTool"]);function f(Q){if(!Q)throw new Error("saveHook requires input");let{session_id:Y,tool_name:X,tool_input:Z,tool_output:W}=Q;if(C.has(X)){console.log(J("PostToolUse",!0));return}let B=new q,K=B.findActiveSDKSession(Y);if(B.close(),!K){console.log(J("PostToolUse",!0));return}let F=L(K.id),b={type:"observation",tool_name:X,tool_input:JSON.stringify(Z),tool_output:JSON.stringify(W)},V=w.connect(F,()=>{V.write(JSON.stringify(b)+` +`),V.end()}),G=!1,v=()=>{if(G)return;G=!0,console.log(J("PostToolUse",!0))};V.on("close",v),V.on("error",v)}var O=await Bun.stdin.text();try{let Q=O.trim()?JSON.parse(O):void 0;f(Q)}catch(Q){console.error(`[claude-mem save-hook error: ${Q.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)} diff --git a/scripts/hooks/summary-hook.js b/scripts/hooks/summary-hook.js index c44b4e7f..8194fef2 100755 --- a/scripts/hooks/summary-hook.js +++ b/scripts/hooks/summary-hook.js @@ -1,6 +1,6 @@ #!/usr/bin/env bun // @bun -import U from"net";import{Database as O}from"bun:sqlite";import{join as Z,dirname as b,basename as E}from"path";import{homedir as M}from"os";import{existsSync as C,mkdirSync as N}from"fs";var K=process.env.CLAUDE_MEM_DATA_DIR||Z(M(),".claude-mem"),v=process.env.CLAUDE_CONFIG_DIR||Z(M(),".claude"),P=Z(K,"archives"),l=Z(K,"logs"),S=Z(K,"trash"),k=Z(K,"backups"),y=Z(K,"chroma"),h=Z(K,"settings.json"),q=Z(K,"claude-mem.db"),R=Z(v,"settings.json"),j=Z(v,"commands"),A=Z(v,"CLAUDE.md");function F(z){return Z(K,`worker-${z}.sock`)}function G(z){N(z,{recursive:!0})}class J{db;constructor(){G(K),this.db=new O(q,{create:!0,readwrite:!0}),this.db.run("PRAGMA journal_mode = WAL"),this.db.run("PRAGMA synchronous = NORMAL"),this.db.run("PRAGMA foreign_keys = ON")}getRecentSummaries(z,Q=10){return this.db.query(` +import E from"net";import{Database as f}from"bun:sqlite";import{join as $,dirname as w,basename as P}from"path";import{homedir as F}from"os";import{existsSync as T,mkdirSync as U}from"fs";var z=process.env.CLAUDE_MEM_DATA_DIR||$(F(),".claude-mem"),v=process.env.CLAUDE_CONFIG_DIR||$(F(),".claude"),A=$(z,"archives"),j=$(z,"logs"),R=$(z,"trash"),_=$(z,"backups"),y=$(z,"chroma"),I=$(z,"settings.json"),G=$(z,"claude-mem.db"),k=$(v,"settings.json"),h=$(v,"commands"),D=$(v,"CLAUDE.md");function x(Q){return $(z,`worker-${Q}.sock`)}function L(Q){U(Q,{recursive:!0})}class J{db;constructor(){L(z),this.db=new f(G,{create:!0,readwrite:!0}),this.db.run("PRAGMA journal_mode = WAL"),this.db.run("PRAGMA synchronous = NORMAL"),this.db.run("PRAGMA foreign_keys = ON")}getRecentSummaries(Q,Y=10){return this.db.query(` SELECT request, investigated, learned, completed, next_steps, files_read, files_edited, notes, created_at @@ -8,36 +8,35 @@ import U from"net";import{Database as O}from"bun:sqlite";import{join as Z,dirnam WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ? - `).all(z,Q)}findActiveSDKSession(z){return this.db.query(` + `).all(Q,Y)}findActiveSDKSession(Q){return this.db.query(` SELECT id, sdk_session_id, project FROM sdk_sessions WHERE claude_session_id = ? AND status = 'active' LIMIT 1 - `).get(z)||null}createSDKSession(z,Q,X){let Y=new Date,$=Y.getTime();return this.db.query(` + `).get(Q)||null}createSDKSession(Q,Y,X){let Z=new Date,K=Z.getTime();return this.db.query(` INSERT INTO sdk_sessions (claude_session_id, project, user_prompt, started_at, started_at_epoch, status) VALUES (?, ?, ?, ?, ?, 'active') - `).run(z,Q,X,Y.toISOString(),$),this.db.query("SELECT last_insert_rowid() as id").get().id}updateSDKSessionId(z,Q){this.db.query(` + `).run(Q,Y,X,Z.toISOString(),K),this.db.query("SELECT last_insert_rowid() as id").get().id}updateSDKSessionId(Q,Y){this.db.query(` UPDATE sdk_sessions SET sdk_session_id = ? WHERE id = ? - `).run(Q,z)}storeObservation(z,Q,X,Y){let $=new Date,W=$.getTime();this.db.query(` + `).run(Y,Q)}storeObservation(Q,Y,X,Z){let K=new Date,B=K.getTime();this.db.query(` INSERT INTO observations (sdk_session_id, project, text, type, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?) - `).run(z,Q,Y,X,$.toISOString(),W)}storeSummary(z,Q,X){let Y=new Date,$=Y.getTime();this.db.query(` + `).run(Q,Y,Z,X,K.toISOString(),B)}storeSummary(Q,Y,X){let Z=new Date,K=Z.getTime();this.db.query(` INSERT INTO session_summaries (sdk_session_id, project, request, investigated, learned, completed, next_steps, files_read, files_edited, notes, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run(z,Q,X.request||null,X.investigated||null,X.learned||null,X.completed||null,X.next_steps||null,X.files_read||null,X.files_edited||null,X.notes||null,Y.toISOString(),$)}markSessionCompleted(z){let Q=new Date,X=Q.getTime();this.db.query(` + `).run(Q,Y,X.request||null,X.investigated||null,X.learned||null,X.completed||null,X.next_steps||null,X.files_read||null,X.files_edited||null,X.notes||null,Z.toISOString(),K)}markSessionCompleted(Q){let Y=new Date,X=Y.getTime();this.db.query(` UPDATE sdk_sessions SET status = 'completed', completed_at = ?, completed_at_epoch = ? WHERE id = ? - `).run(Q.toISOString(),X,z)}markSessionFailed(z){let Q=new Date,X=Q.getTime();this.db.query(` + `).run(Y.toISOString(),X,Q)}markSessionFailed(Q){let Y=new Date,X=Y.getTime();this.db.query(` UPDATE sdk_sessions SET status = 'failed', completed_at = ?, completed_at_epoch = ? WHERE id = ? - `).run(Q.toISOString(),X,z)}close(){this.db.close()}}function x(z){try{if(console.error("[claude-mem summary] Hook fired",{input:z?{session_id:z.session_id,cwd:z.cwd}:null}),!z)console.log("No input provided - this script is designed to run as a Claude Code Stop hook"),console.log(` -Expected input format:`),console.log(JSON.stringify({session_id:"string",cwd:"string"},null,2)),process.exit(0);let{session_id:Q}=z;console.error("[claude-mem summary] Searching for active SDK session",{session_id:Q});let X=new J,Y=X.findActiveSDKSession(Q);if(X.close(),!Y)console.error("[claude-mem summary] No active SDK session found",{session_id:Q}),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0);console.error("[claude-mem summary] Active SDK session found",{session_id:Y.id,collection_name:Y.collection_name,worker_pid:Y.worker_pid});let $=F(Y.id),W={type:"finalize"};console.error("[claude-mem summary] Attempting to send FINALIZE message to worker socket",{socketPath:$,message:W});let B=U.connect($,()=>{console.error("[claude-mem summary] Socket connection established, sending message"),B.write(JSON.stringify(W)+` -`),B.end()});B.on("error",(V)=>{console.error("[claude-mem summary] Socket error occurred",{error:V.message,code:V.code,socketPath:$})}),B.on("close",()=>{console.error("[claude-mem summary] Socket connection closed successfully"),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)})}catch(Q){console.error("[claude-mem summary] Unexpected error in hook",{error:Q.message,stack:Q.stack,name:Q.name}),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}}var L=await Bun.stdin.text();try{let z=L.trim()?JSON.parse(L):void 0;x(z)}catch(z){console.error(`[claude-mem summary-hook error: ${z.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)} + `).run(Y.toISOString(),X,Q)}close(){this.db.close()}}function b(Q,Y,X){if(Q==="PreCompact"){if(Y)return{continue:!0,suppressOutput:!0};return{continue:!1,stopReason:X.reason||"Pre-compact operation failed",suppressOutput:!0}}if(Q==="SessionStart"){if(Y&&X.context)return{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:X.context}};return{continue:!0,suppressOutput:!0}}if(Q==="UserPromptSubmit"||Q==="PostToolUse")return{continue:!0,suppressOutput:!0};if(Q==="Stop")return{continue:!0,suppressOutput:!0};return{continue:Y,suppressOutput:!0,...X.reason&&!Y?{stopReason:X.reason}:{}}}function M(Q,Y,X={}){let Z=b(Q,Y,X);return JSON.stringify(Z)}function N(Q){if(!Q)throw new Error("summaryHook requires input");let{session_id:Y}=Q,X=new J,Z=X.findActiveSDKSession(Y);if(X.close(),!Z){console.log(M("Stop",!0));return}let K=x(Z.id),B={type:"finalize"},W=E.connect(K,()=>{W.write(JSON.stringify(B)+` +`),W.end()}),V=!1,q=()=>{if(V)return;V=!0,console.log(M("Stop",!0))};W.on("close",q),W.on("error",q)}var O=await Bun.stdin.text();try{let Q=O.trim()?JSON.parse(O):void 0;N(Q)}catch(Q){console.error(`[claude-mem summary-hook error: ${Q.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)} diff --git a/src/bin/hooks/context-hook.ts b/src/bin/hooks/context-hook.ts index a04d8217..97a85623 100644 --- a/src/bin/hooks/context-hook.ts +++ b/src/bin/hooks/context-hook.ts @@ -7,12 +7,14 @@ import { contextHook } from '../../hooks/context.js'; -// Read input from stdin -const input = await Bun.stdin.text(); - try { - const parsed = input.trim() ? JSON.parse(input) : undefined; - contextHook(parsed); + if (process.stdin.isTTY) { + contextHook(); + } else { + const input = await Bun.stdin.text(); + const parsed = input.trim() ? JSON.parse(input) : undefined; + contextHook(parsed); + } } catch (error: any) { console.error(`[claude-mem context-hook error: ${error.message}]`); process.exit(0); diff --git a/src/hooks/context.ts b/src/hooks/context.ts index 5561067e..3b39a379 100644 --- a/src/hooks/context.ts +++ b/src/hooks/context.ts @@ -1,12 +1,13 @@ -import { HooksDatabase } from '../services/sqlite/HooksDatabase.js'; import path from 'path'; +import { HooksDatabase } from '../services/sqlite/HooksDatabase.js'; +import { createHookResponse } from './hook-response.js'; export interface SessionStartInput { - session_id: string; - transcript_path: string; - cwd: string; - hook_event_name: string; - source: "startup" | "resume" | "clear" | "compact"; + session_id?: string; + transcript_path?: string; + cwd?: string; + hook_event_name?: string; + source?: "startup" | "resume" | "clear" | "compact"; [key: string]: any; } @@ -15,18 +16,19 @@ export interface SessionStartInput { * Shows user what happened in recent sessions */ export function contextHook(input?: SessionStartInput): void { - if (!input) { - throw new Error('contextHook requires input'); - } + const cwd = input?.cwd ?? process.cwd(); + const project = cwd ? path.basename(cwd) : 'unknown-project'; - const project = input.cwd ? path.basename(input.cwd) : path.basename(path.dirname(input.transcript_path)); const db = new HooksDatabase(); try { const summaries = db.getRecentSummaries(project, 5); if (summaries.length === 0) { - console.log('# Recent Session Context\n\nNo previous sessions found for this project yet.'); + const response = createHookResponse('SessionStart', true, { + context: '# Recent Session Context\n\nNo previous sessions found for this project yet.' + }); + console.log(response); return; } @@ -87,8 +89,11 @@ export function contextHook(input?: SessionStartInput): void { output.push(''); } - console.log(output.join('\n')); + const response = createHookResponse('SessionStart', true, { + context: output.join('\n') + }); + console.log(response); } finally { db.close(); } -} +} \ No newline at end of file diff --git a/src/hooks/hook-response.ts b/src/hooks/hook-response.ts new file mode 100644 index 00000000..a1a0b838 --- /dev/null +++ b/src/hooks/hook-response.ts @@ -0,0 +1,87 @@ +export type HookType = 'PreCompact' | 'SessionStart' | 'UserPromptSubmit' | 'PostToolUse' | 'Stop' | string; + +export interface HookResponseOptions { + reason?: string; + context?: string; +} + +export interface HookResponse { + continue?: boolean; + suppressOutput?: boolean; + stopReason?: string; + hookSpecificOutput?: { + hookEventName: 'SessionStart'; + additionalContext: string; + }; +} + +function buildHookResponse( + hookType: HookType, + success: boolean, + options: HookResponseOptions +): HookResponse { + if (hookType === 'PreCompact') { + if (success) { + return { + continue: true, + suppressOutput: true + }; + } + + return { + continue: false, + stopReason: options.reason || 'Pre-compact operation failed', + suppressOutput: true + }; + } + + if (hookType === 'SessionStart') { + if (success && options.context) { + return { + continue: true, + suppressOutput: true, + hookSpecificOutput: { + hookEventName: 'SessionStart', + additionalContext: options.context + } + }; + } + + return { + continue: true, + suppressOutput: true + }; + } + + if (hookType === 'UserPromptSubmit' || hookType === 'PostToolUse') { + return { + continue: true, + suppressOutput: true + }; + } + + if (hookType === 'Stop') { + return { + continue: true, + suppressOutput: true + }; + } + + return { + continue: success, + suppressOutput: true, + ...(options.reason && !success ? { stopReason: options.reason } : {}) + }; +} + +/** + * Creates a standardized hook response using the HookTemplates system. + */ +export function createHookResponse( + hookType: HookType, + success: boolean, + options: HookResponseOptions = {} +): string { + const response = buildHookResponse(hookType, success, options); + return JSON.stringify(response); +} diff --git a/src/hooks/new.ts b/src/hooks/new.ts index fa7a70c4..5550ef37 100644 --- a/src/hooks/new.ts +++ b/src/hooks/new.ts @@ -1,6 +1,7 @@ -import { HooksDatabase } from '../services/sqlite/HooksDatabase.js'; -import path from 'path'; import { spawn } from 'child_process'; +import path from 'path'; +import { HooksDatabase } from '../services/sqlite/HooksDatabase.js'; +import { createHookResponse } from './hook-response.js'; export interface UserPromptSubmitInput { session_id: string; @@ -26,7 +27,7 @@ export function newHook(input?: UserPromptSubmitInput): void { const existing = db.findActiveSDKSession(session_id); if (existing) { - console.log('{"continue": true, "suppressOutput": true}'); + console.log(createHookResponse('UserPromptSubmit', true)); return; } @@ -46,7 +47,7 @@ export function newHook(input?: UserPromptSubmitInput): void { child.unref(); - console.log('{"continue": true, "suppressOutput": true}'); + console.log(createHookResponse('UserPromptSubmit', true)); } finally { db.close(); } diff --git a/src/hooks/save.ts b/src/hooks/save.ts index 44c8d125..fefccbd8 100644 --- a/src/hooks/save.ts +++ b/src/hooks/save.ts @@ -1,6 +1,7 @@ import net from 'net'; import { HooksDatabase } from '../services/sqlite/HooksDatabase.js'; import { getWorkerSocketPath } from '../shared/paths.js'; +import { createHookResponse } from './hook-response.js'; export interface PostToolUseInput { session_id: string; @@ -29,7 +30,7 @@ export function saveHook(input?: PostToolUseInput): void { const { session_id, tool_name, tool_input, tool_output } = input; if (SKIP_TOOLS.has(tool_name)) { - console.log('{"continue": true, "suppressOutput": true}'); + console.log(createHookResponse('PostToolUse', true)); return; } @@ -38,7 +39,7 @@ export function saveHook(input?: PostToolUseInput): void { db.close(); if (!session) { - console.log('{"continue": true, "suppressOutput": true}'); + console.log(createHookResponse('PostToolUse', true)); return; } @@ -55,7 +56,15 @@ export function saveHook(input?: PostToolUseInput): void { client.end(); }); - client.on('close', () => { - console.log('{"continue": true, "suppressOutput": true}'); - }); + let responded = false; + const respond = () => { + if (responded) { + return; + } + responded = true; + console.log(createHookResponse('PostToolUse', true)); + }; + + client.on('close', respond); + client.on('error', respond); } diff --git a/src/hooks/summary.ts b/src/hooks/summary.ts index 298b8728..d7019a5d 100644 --- a/src/hooks/summary.ts +++ b/src/hooks/summary.ts @@ -1,6 +1,7 @@ import net from 'net'; import { HooksDatabase } from '../services/sqlite/HooksDatabase.js'; import { getWorkerSocketPath } from '../shared/paths.js'; +import { createHookResponse } from './hook-response.js'; export interface StopInput { session_id: string; @@ -23,7 +24,7 @@ export function summaryHook(input?: StopInput): void { db.close(); if (!session) { - console.log('{"continue": true, "suppressOutput": true}'); + console.log(createHookResponse('Stop', true)); return; } @@ -37,7 +38,15 @@ export function summaryHook(input?: StopInput): void { client.end(); }); - client.on('close', () => { - console.log('{"continue": true, "suppressOutput": true}'); - }); + let responded = false; + const respond = () => { + if (responded) { + return; + } + responded = true; + console.log(createHookResponse('Stop', true)); + }; + + client.on('close', respond); + client.on('error', respond); } diff --git a/~/.claude-mem/chroma/chroma.sqlite3 b/~/.claude-mem/chroma/chroma.sqlite3 deleted file mode 100644 index d23ea3a9..00000000 Binary files a/~/.claude-mem/chroma/chroma.sqlite3 and /dev/null differ