Refactor silent debugging to happy path error handling

- Replaced instances of silentDebug with happy_path_error__with_fallback across multiple files to improve error logging and handling.
- Updated the utility function to provide clearer semantics for error handling when expected values are missing.
- Introduced a script to find potential silent failures in the codebase that may need to be addressed with the new error handling approach.
This commit is contained in:
Alex Newman
2025-12-09 15:09:44 -05:00
parent d957bff495
commit c3761a2204
25 changed files with 293 additions and 204 deletions

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env node #!/usr/bin/env node
import{stdin as A}from"process";import p from"path";import{existsSync as C}from"fs";import{homedir as I}from"os";import{spawnSync as U}from"child_process";import{readFileSync as x,existsSync as H}from"fs";import{join as F}from"path";import{homedir as j}from"os";var W=["bugfix","feature","refactor","discovery","decision","change"],b=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var D=W.join(","),R=b.join(",");var f=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_DATA_DIR:F(j(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:D,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:R,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return process.env[t]||this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!H(t))return this.getAllDefaults();let e=x(t,"utf-8"),r=JSON.parse(e).env||{},s={...this.DEFAULTS};for(let l of Object.keys(this.DEFAULTS))r[l]!==void 0&&(s[l]=r[l]);return s}};import{join as G}from"path";import{homedir as Y}from"os";import{existsSync as J,readFileSync as q}from"fs";import{appendFileSync as K}from"fs";import{homedir as V}from"os";import{join as X}from"path";var B=X(V(),".claude-mem","silent.log");function E(o,t,e=""){let n=new Date().toISOString(),g=((new Error().stack||"").split(` import{stdin as A}from"process";import p from"path";import{existsSync as C}from"fs";import{homedir as I}from"os";import{spawnSync as U}from"child_process";import{readFileSync as x,existsSync as H}from"fs";import{join as F}from"path";import{homedir as j}from"os";var W=["bugfix","feature","refactor","discovery","decision","change"],b=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var D=W.join(","),R=b.join(",");var g=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_DATA_DIR:F(j(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:D,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:R,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return process.env[t]||this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!H(t))return this.getAllDefaults();let e=x(t,"utf-8"),r=JSON.parse(e).env||{},s={...this.DEFAULTS};for(let l of Object.keys(this.DEFAULTS))r[l]!==void 0&&(s[l]=r[l]);return s}};import{join as G}from"path";import{homedir as Y}from"os";import{existsSync as J,readFileSync as q}from"fs";import{appendFileSync as K}from"fs";import{homedir as V}from"os";import{join as X}from"path";var B=X(V(),".claude-mem","silent.log");function E(o,t,e=""){let n=new Date().toISOString(),f=((new Error().stack||"").split(`
`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),u=g?`${g[1].split("/").pop()}:${g[2]}`:"unknown",a=`[${n}] [${u}] ${o}`;if(t!==void 0)try{a+=` ${JSON.stringify(t)}`}catch(c){a+=` [stringify error: ${c}]`}a+=` `)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),S=f?`${f[1].split("/").pop()}:${f[2]}`:"unknown",a=`[${n}] [HAPPY-PATH-ERROR] [${S}] ${o}`;if(t!==void 0)try{a+=` ${JSON.stringify(t)}`}catch(c){a+=` [stringify error: ${c}]`}a+=`
`;try{K(B,a)}catch(c){console.error("[silent-debug] Failed to write to log:",c)}return e}var S=G(Y(),".claude-mem","settings.json");function y(o,t){try{if(J(S)){let n=JSON.parse(q(S,"utf-8")).env?.[o];if(n!==void 0)return n}}catch(e){E("Failed to load settings file",{error:e,settingsPath:S,key:o})}return process.env[o]||t}var O=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(O||{}),m=class{level;useColor;constructor(){let t=y("CLAUDE_MEM_LOG_LEVEL","INFO").toUpperCase();this.level=O[t]??1,this.useColor=process.stdout.isTTY??!1}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.level===0?`${t.message} `;try{K(B,a)}catch(c){console.error("[silent-debug] Failed to write to log:",c)}return e}var u=G(Y(),".claude-mem","settings.json");function y(o,t){try{if(J(u)){let n=JSON.parse(q(u,"utf-8")).env?.[o];if(n!==void 0)return n}}catch(e){E("Failed to load settings file",{error:e,settingsPath:u,key:o})}return process.env[o]||t}var O=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(O||{}),m=class{level;useColor;constructor(){let t=y("CLAUDE_MEM_LOG_LEVEL","INFO").toUpperCase();this.level=O[t]??1,this.useColor=process.stdout.isTTY??!1}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.level===0?`${t.message}
${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let n=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&n.command){let r=n.command.length>50?n.command.substring(0,50)+"...":n.command;return`${t}(${r})`}if(t==="Read"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}if(t==="Edit"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}if(t==="Write"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}return t}catch{return t}}log(t,e,n,r,s){if(t<this.level)return;let l=new Date().toISOString().replace("T"," ").substring(0,23),g=O[t].padEnd(5),u=e.padEnd(6),a="";r?.correlationId?a=`[${r.correlationId}] `:r?.sessionId&&(a=`[session-${r.sessionId}] `);let c="";s!=null&&(this.level===0&&typeof s=="object"?c=` ${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let n=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&n.command){let r=n.command.length>50?n.command.substring(0,50)+"...":n.command;return`${t}(${r})`}if(t==="Read"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}if(t==="Edit"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}if(t==="Write"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}return t}catch{return t}}log(t,e,n,r,s){if(t<this.level)return;let l=new Date().toISOString().replace("T"," ").substring(0,23),f=O[t].padEnd(5),S=e.padEnd(6),a="";r?.correlationId?a=`[${r.correlationId}] `:r?.sessionId&&(a=`[session-${r.sessionId}] `);let c="";s!=null&&(this.level===0&&typeof s=="object"?c=`
`+JSON.stringify(s,null,2):c=" "+this.formatData(s));let L="";if(r){let{sessionId:et,sdkSessionId:rt,correlationId:nt,...h}=r;Object.keys(h).length>0&&(L=` {${Object.entries(h).map(([$,v])=>`${$}=${v}`).join(", ")}}`)}let M=`[${l}] [${g}] [${u}] ${a}${n}${L}${c}`;t===3?console.error(M):console.log(M)}debug(t,e,n,r){this.log(0,t,e,n,r)}info(t,e,n,r){this.log(1,t,e,n,r)}warn(t,e,n,r){this.log(2,t,e,n,r)}error(t,e,n,r){this.log(3,t,e,n,r)}dataIn(t,e,n,r){this.info(t,`\u2192 ${e}`,n,r)}dataOut(t,e,n,r){this.info(t,`\u2190 ${e}`,n,r)}success(t,e,n,r){this.info(t,`\u2713 ${e}`,n,r)}failure(t,e,n,r){this.error(t,`\u2717 ${e}`,n,r)}timing(t,e,n,r){this.info(t,`\u23F1 ${e}`,r,{duration:`${n}ms`})}},d=new m;var _={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function N(o){return process.platform==="win32"?Math.round(o*_.WINDOWS_MULTIPLIER):o}var i=p.join(I(),".claude","plugins","marketplaces","thedotmack"),z=N(_.HEALTH_CHECK),Q=_.WORKER_STARTUP_WAIT,Z=_.WORKER_STARTUP_RETRIES;function T(){let o=p.join(I(),".claude-mem","settings.json"),t=f.loadFromFile(o);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function P(){try{let o=T();return(await fetch(`http://127.0.0.1:${o}/health`,{signal:AbortSignal.timeout(z)})).ok}catch(o){return d.debug("SYSTEM","Worker health check failed",{error:o instanceof Error?o.message:String(o),errorType:o?.constructor?.name}),!1}}async function tt(){try{let o=p.join(i,"plugin","scripts","worker-service.cjs");if(!C(o))throw new Error(`Worker script not found at ${o}`);if(process.platform==="win32"){let t=U("powershell.exe",["-NoProfile","-NonInteractive","-Command",`Start-Process -FilePath 'node' -ArgumentList '${o}' -WorkingDirectory '${i}' -WindowStyle Hidden`],{cwd:i,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(t.status!==0)throw new Error(t.stderr||"PowerShell Start-Process failed")}else{let t=p.join(i,"ecosystem.config.cjs");if(!C(t))throw new Error(`Ecosystem config not found at ${t}`);let e=p.join(i,"node_modules",".bin","pm2"),n=C(e)?e:"pm2",r=U(n,["start",t],{cwd:i,stdio:"pipe",encoding:"utf-8"});if(r.status!==0)throw new Error(r.stderr||"PM2 start failed")}for(let t=0;t<Z;t++)if(await new Promise(e=>setTimeout(e,Q)),await P())return!0;return!1}catch(o){return d.error("SYSTEM","Failed to start worker",{platform:process.platform,workerScript:p.join(i,"plugin","scripts","worker-service.cjs"),error:o instanceof Error?o.message:String(o),marketplaceRoot:i}),!1}}async function k(){if(await P())return;if(!await tt()){let t=T();throw new Error(`Worker service failed to start on port ${t}. `+JSON.stringify(s,null,2):c=" "+this.formatData(s));let L="";if(r){let{sessionId:et,sdkSessionId:rt,correlationId:nt,...M}=r;Object.keys(M).length>0&&(L=` {${Object.entries(M).map(([$,v])=>`${$}=${v}`).join(", ")}}`)}let h=`[${l}] [${f}] [${S}] ${a}${n}${L}${c}`;t===3?console.error(h):console.log(h)}debug(t,e,n,r){this.log(0,t,e,n,r)}info(t,e,n,r){this.log(1,t,e,n,r)}warn(t,e,n,r){this.log(2,t,e,n,r)}error(t,e,n,r){this.log(3,t,e,n,r)}dataIn(t,e,n,r){this.info(t,`\u2192 ${e}`,n,r)}dataOut(t,e,n,r){this.info(t,`\u2190 ${e}`,n,r)}success(t,e,n,r){this.info(t,`\u2713 ${e}`,n,r)}failure(t,e,n,r){this.error(t,`\u2717 ${e}`,n,r)}timing(t,e,n,r){this.info(t,`\u23F1 ${e}`,r,{duration:`${n}ms`})}},d=new m;var _={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function N(o){return process.platform==="win32"?Math.round(o*_.WINDOWS_MULTIPLIER):o}var i=p.join(I(),".claude","plugins","marketplaces","thedotmack"),z=N(_.HEALTH_CHECK),Q=_.WORKER_STARTUP_WAIT,Z=_.WORKER_STARTUP_RETRIES;function T(){let o=p.join(I(),".claude-mem","settings.json"),t=g.loadFromFile(o);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function P(){try{let o=T();return(await fetch(`http://127.0.0.1:${o}/health`,{signal:AbortSignal.timeout(z)})).ok}catch(o){return d.debug("SYSTEM","Worker health check failed",{error:o instanceof Error?o.message:String(o),errorType:o?.constructor?.name}),!1}}async function tt(){try{let o=p.join(i,"plugin","scripts","worker-service.cjs");if(!C(o))throw new Error(`Worker script not found at ${o}`);if(process.platform==="win32"){let t=U("powershell.exe",["-NoProfile","-NonInteractive","-Command",`Start-Process -FilePath 'node' -ArgumentList '${o}' -WorkingDirectory '${i}' -WindowStyle Hidden`],{cwd:i,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(t.status!==0)throw new Error(t.stderr||"PowerShell Start-Process failed")}else{let t=p.join(i,"ecosystem.config.cjs");if(!C(t))throw new Error(`Ecosystem config not found at ${t}`);let e=p.join(i,"node_modules",".bin","pm2"),n=C(e)?e:"pm2",r=U(n,["start",t],{cwd:i,stdio:"pipe",encoding:"utf-8"});if(r.status!==0)throw new Error(r.stderr||"PM2 start failed")}for(let t=0;t<Z;t++)if(await new Promise(e=>setTimeout(e,Q)),await P())return!0;return!1}catch(o){return d.error("SYSTEM","Failed to start worker",{platform:process.platform,workerScript:p.join(i,"plugin","scripts","worker-service.cjs"),error:o instanceof Error?o.message:String(o),marketplaceRoot:i}),!1}}async function k(){if(await P())return;if(!await tt()){let t=T();throw new Error(`Worker service failed to start on port ${t}.
To start manually, run: To start manually, run:
cd ${i} cd ${i}

View File

@@ -1,5 +1,5 @@
"use strict";var Be=Object.create;var V=Object.defineProperty;var He=Object.getOwnPropertyDescriptor;var je=Object.getOwnPropertyNames;var Ge=Object.getPrototypeOf,Ye=Object.prototype.hasOwnProperty;var Ve=(a,e)=>{for(var s in e)V(a,s,{get:e[s],enumerable:!0})},Te=(a,e,s,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of je(e))!Ye.call(a,n)&&n!==s&&V(a,n,{get:()=>e[n],enumerable:!(t=He(e,n))||t.enumerable});return a};var ge=(a,e,s)=>(s=a!=null?Be(Ge(a)):{},Te(e||!a||!a.__esModule?V(s,"default",{value:a,enumerable:!0}):s,a)),Ke=a=>Te(V({},"__esModule",{value:!0}),a);var cs={};Ve(cs,{generateContext:()=>as});module.exports=Ke(cs);var U=ge(require("path"),1),ee=require("os"),P=require("fs");var Ce=ge(require("better-sqlite3"),1);var S=require("path"),oe=require("os"),Ne=require("fs");var Ae=require("url");var Oe=require("path"),Re=require("os"),K=require("fs");var Se=require("fs"),he=require("os"),be=require("path"),qe=(0,be.join)((0,he.homedir)(),".claude-mem","silent.log");function fe(a,e,s=""){let t=new Date().toISOString(),d=((new Error().stack||"").split(` "use strict";var Be=Object.create;var V=Object.defineProperty;var He=Object.getOwnPropertyDescriptor;var je=Object.getOwnPropertyNames;var Ge=Object.getPrototypeOf,Ye=Object.prototype.hasOwnProperty;var Ve=(a,e)=>{for(var s in e)V(a,s,{get:e[s],enumerable:!0})},Te=(a,e,s,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of je(e))!Ye.call(a,n)&&n!==s&&V(a,n,{get:()=>e[n],enumerable:!(t=He(e,n))||t.enumerable});return a};var ge=(a,e,s)=>(s=a!=null?Be(Ge(a)):{},Te(e||!a||!a.__esModule?V(s,"default",{value:a,enumerable:!0}):s,a)),Ke=a=>Te(V({},"__esModule",{value:!0}),a);var cs={};Ve(cs,{generateContext:()=>as});module.exports=Ke(cs);var U=ge(require("path"),1),ee=require("os"),P=require("fs");var Ce=ge(require("better-sqlite3"),1);var S=require("path"),oe=require("os"),Ne=require("fs");var Ae=require("url");var Oe=require("path"),Re=require("os"),K=require("fs");var Se=require("fs"),he=require("os"),be=require("path"),qe=(0,be.join)((0,he.homedir)(),".claude-mem","silent.log");function fe(a,e,s=""){let t=new Date().toISOString(),d=((new Error().stack||"").split(`
`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),_=d?`${d[1].split("/").pop()}:${d[2]}`:"unknown",m=`[${t}] [${_}] ${a}`;if(e!==void 0)try{m+=` ${JSON.stringify(e)}`}catch(l){m+=` [stringify error: ${l}]`}m+=` `)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),_=d?`${d[1].split("/").pop()}:${d[2]}`:"unknown",m=`[${t}] [HAPPY-PATH-ERROR] [${_}] ${a}`;if(e!==void 0)try{m+=` ${JSON.stringify(e)}`}catch(l){m+=` [stringify error: ${l}]`}m+=`
`;try{(0,Se.appendFileSync)(qe,m)}catch(l){console.error("[silent-debug] Failed to write to log:",l)}return s}var ne=(0,Oe.join)((0,Re.homedir)(),".claude-mem","settings.json");function q(a,e){try{if((0,K.existsSync)(ne)){let t=JSON.parse((0,K.readFileSync)(ne,"utf-8")).env?.[a];if(t!==void 0)return t}}catch(s){fe("Failed to load settings file",{error:s,settingsPath:ne,key:a})}return process.env[a]||e}var Qe={};function Je(){return typeof __dirname<"u"?__dirname:(0,S.dirname)((0,Ae.fileURLToPath)(Qe.url))}var Ss=Je(),C=q("CLAUDE_MEM_DATA_DIR",(0,S.join)((0,oe.homedir)(),".claude-mem")),ie=process.env.CLAUDE_CONFIG_DIR||(0,S.join)((0,oe.homedir)(),".claude"),hs=(0,S.join)(C,"archives"),bs=(0,S.join)(C,"logs"),fs=(0,S.join)(C,"trash"),Os=(0,S.join)(C,"backups"),Rs=(0,S.join)(C,"settings.json"),Ie=(0,S.join)(C,"claude-mem.db"),Ns=(0,S.join)(C,"vector-db"),As=(0,S.join)(ie,"settings.json"),Is=(0,S.join)(ie,"commands"),Ls=(0,S.join)(ie,"CLAUDE.md");function Le(a){(0,Ne.mkdirSync)(a,{recursive:!0})}var ae=(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))(ae||{}),ce=class{level;useColor;constructor(){let e=q("CLAUDE_MEM_LOG_LEVEL","INFO").toUpperCase();this.level=ae[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} `;try{(0,Se.appendFileSync)(qe,m)}catch(l){console.error("[silent-debug] Failed to write to log:",l)}return s}var ne=(0,Oe.join)((0,Re.homedir)(),".claude-mem","settings.json");function q(a,e){try{if((0,K.existsSync)(ne)){let t=JSON.parse((0,K.readFileSync)(ne,"utf-8")).env?.[a];if(t!==void 0)return t}}catch(s){fe("Failed to load settings file",{error:s,settingsPath:ne,key:a})}return process.env[a]||e}var Qe={};function Je(){return typeof __dirname<"u"?__dirname:(0,S.dirname)((0,Ae.fileURLToPath)(Qe.url))}var Ss=Je(),C=q("CLAUDE_MEM_DATA_DIR",(0,S.join)((0,oe.homedir)(),".claude-mem")),ie=process.env.CLAUDE_CONFIG_DIR||(0,S.join)((0,oe.homedir)(),".claude"),hs=(0,S.join)(C,"archives"),bs=(0,S.join)(C,"logs"),fs=(0,S.join)(C,"trash"),Os=(0,S.join)(C,"backups"),Rs=(0,S.join)(C,"settings.json"),Ie=(0,S.join)(C,"claude-mem.db"),Ns=(0,S.join)(C,"vector-db"),As=(0,S.join)(ie,"settings.json"),Is=(0,S.join)(ie,"commands"),Ls=(0,S.join)(ie,"CLAUDE.md");function Le(a){(0,Ne.mkdirSync)(a,{recursive:!0})}var ae=(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))(ae||{}),ce=class{level;useColor;constructor(){let e=q("CLAUDE_MEM_LOG_LEVEL","INFO").toUpperCase();this.level=ae[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 n=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${n})`}if(e==="Read"&&t.file_path){let n=t.file_path.split("/").pop()||t.file_path;return`${e}(${n})`}if(e==="Edit"&&t.file_path){let n=t.file_path.split("/").pop()||t.file_path;return`${e}(${n})`}if(e==="Write"&&t.file_path){let n=t.file_path.split("/").pop()||t.file_path;return`${e}(${n})`}return e}catch{return e}}log(e,s,t,n,i){if(e<this.level)return;let c=new Date().toISOString().replace("T"," ").substring(0,23),d=ae[e].padEnd(5),_=s.padEnd(6),m="";n?.correlationId?m=`[${n.correlationId}] `:n?.sessionId&&(m=`[session-${n.sessionId}] `);let l="";i!=null&&(this.level===0&&typeof i=="object"?l=` ${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 n=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${n})`}if(e==="Read"&&t.file_path){let n=t.file_path.split("/").pop()||t.file_path;return`${e}(${n})`}if(e==="Edit"&&t.file_path){let n=t.file_path.split("/").pop()||t.file_path;return`${e}(${n})`}if(e==="Write"&&t.file_path){let n=t.file_path.split("/").pop()||t.file_path;return`${e}(${n})`}return e}catch{return e}}log(e,s,t,n,i){if(e<this.level)return;let c=new Date().toISOString().replace("T"," ").substring(0,23),d=ae[e].padEnd(5),_=s.padEnd(6),m="";n?.correlationId?m=`[${n.correlationId}] `:n?.sessionId&&(m=`[session-${n.sessionId}] `);let l="";i!=null&&(this.level===0&&typeof i=="object"?l=`
`+JSON.stringify(i,null,2):l=" "+this.formatData(i));let b="";if(n){let{sessionId:f,sdkSessionId:y,correlationId:E,...r}=n;Object.keys(r).length>0&&(b=` {${Object.entries(r).map(([O,h])=>`${O}=${h}`).join(", ")}}`)}let v=`[${c}] [${d}] [${_}] ${m}${t}${b}${l}`;e===3?console.error(v):console.log(v)}debug(e,s,t,n){this.log(0,e,s,t,n)}info(e,s,t,n){this.log(1,e,s,t,n)}warn(e,s,t,n){this.log(2,e,s,t,n)}error(e,s,t,n){this.log(3,e,s,t,n)}dataIn(e,s,t,n){this.info(e,`\u2192 ${s}`,t,n)}dataOut(e,s,t,n){this.info(e,`\u2190 ${s}`,t,n)}success(e,s,t,n){this.info(e,`\u2713 ${s}`,t,n)}failure(e,s,t,n){this.error(e,`\u2717 ${s}`,t,n)}timing(e,s,t,n){this.info(e,`\u23F1 ${s}`,n,{duration:`${t}ms`})}},B=new ce;var J=class{db;constructor(){Le(C),this.db=new Ce.default(Ie),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(),this.ensureDiscoveryTokensColumn()}initializeSchema(){try{this.db.exec(` `+JSON.stringify(i,null,2):l=" "+this.formatData(i));let b="";if(n){let{sessionId:f,sdkSessionId:y,correlationId:E,...r}=n;Object.keys(r).length>0&&(b=` {${Object.entries(r).map(([O,h])=>`${O}=${h}`).join(", ")}}`)}let v=`[${c}] [${d}] [${_}] ${m}${t}${b}${l}`;e===3?console.error(v):console.log(v)}debug(e,s,t,n){this.log(0,e,s,t,n)}info(e,s,t,n){this.log(1,e,s,t,n)}warn(e,s,t,n){this.log(2,e,s,t,n)}error(e,s,t,n){this.log(3,e,s,t,n)}dataIn(e,s,t,n){this.info(e,`\u2192 ${s}`,t,n)}dataOut(e,s,t,n){this.info(e,`\u2190 ${s}`,t,n)}success(e,s,t,n){this.info(e,`\u2713 ${s}`,t,n)}failure(e,s,t,n){this.error(e,`\u2717 ${s}`,t,n)}timing(e,s,t,n){this.info(e,`\u23F1 ${s}`,n,{duration:`${t}ms`})}},B=new ce;var J=class{db;constructor(){Le(C),this.db=new Ce.default(Ie),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(),this.ensureDiscoveryTokensColumn()}initializeSchema(){try{this.db.exec(`

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env node #!/usr/bin/env node
import et from"path";import{stdin as d}from"process";import l from"path";import{existsSync as C}from"fs";import{homedir as I}from"os";import{spawnSync as U}from"child_process";import{readFileSync as b,existsSync as H}from"fs";import{join as F}from"path";import{homedir as j}from"os";var v=["bugfix","feature","refactor","discovery","decision","change"],W=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var h=v.join(","),D=W.join(",");var f=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_DATA_DIR:F(j(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:h,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:D,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return process.env[t]||this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!H(t))return this.getAllDefaults();let e=b(t,"utf-8"),n=JSON.parse(e).env||{},s={...this.DEFAULTS};for(let a of Object.keys(this.DEFAULTS))n[a]!==void 0&&(s[a]=n[a]);return s}};import{join as G}from"path";import{homedir as Y}from"os";import{existsSync as J,readFileSync as q}from"fs";import{appendFileSync as K}from"fs";import{homedir as V}from"os";import{join as X}from"path";var B=X(V(),".claude-mem","silent.log");function R(o,t,e=""){let r=new Date().toISOString(),E=((new Error().stack||"").split(` import et from"path";import{stdin as d}from"process";import l from"path";import{existsSync as C}from"fs";import{homedir as I}from"os";import{spawnSync as U}from"child_process";import{readFileSync as b,existsSync as H}from"fs";import{join as F}from"path";import{homedir as j}from"os";var v=["bugfix","feature","refactor","discovery","decision","change"],W=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var M=v.join(","),R=W.join(",");var f=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_DATA_DIR:F(j(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:M,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:R,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return process.env[t]||this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!H(t))return this.getAllDefaults();let e=b(t,"utf-8"),n=JSON.parse(e).env||{},s={...this.DEFAULTS};for(let c of Object.keys(this.DEFAULTS))n[c]!==void 0&&(s[c]=n[c]);return s}};import{join as G}from"path";import{homedir as Y}from"os";import{existsSync as J,readFileSync as q}from"fs";import{appendFileSync as K}from"fs";import{homedir as V}from"os";import{join as X}from"path";var B=X(V(),".claude-mem","silent.log");function D(o,t,e=""){let r=new Date().toISOString(),E=((new Error().stack||"").split(`
`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),u=E?`${E[1].split("/").pop()}:${E[2]}`:"unknown",c=`[${r}] [${u}] ${o}`;if(t!==void 0)try{c+=` ${JSON.stringify(t)}`}catch(_){c+=` [stringify error: ${_}]`}c+=` `)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),u=E?`${E[1].split("/").pop()}:${E[2]}`:"unknown",a=`[${r}] [HAPPY-PATH-ERROR] [${u}] ${o}`;if(t!==void 0)try{a+=` ${JSON.stringify(t)}`}catch(_){a+=` [stringify error: ${_}]`}a+=`
`;try{K(B,c)}catch(_){console.error("[silent-debug] Failed to write to log:",_)}return e}var g=G(Y(),".claude-mem","settings.json");function y(o,t){try{if(J(g)){let r=JSON.parse(q(g,"utf-8")).env?.[o];if(r!==void 0)return r}}catch(e){R("Failed to load settings file",{error:e,settingsPath:g,key:o})}return process.env[o]||t}var S=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(S||{}),O=class{level;useColor;constructor(){let t=y("CLAUDE_MEM_LOG_LEVEL","INFO").toUpperCase();this.level=S[t]??1,this.useColor=process.stdout.isTTY??!1}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.level===0?`${t.message} `;try{K(B,a)}catch(_){console.error("[silent-debug] Failed to write to log:",_)}return e}var g=G(Y(),".claude-mem","settings.json");function y(o,t){try{if(J(g)){let r=JSON.parse(q(g,"utf-8")).env?.[o];if(r!==void 0)return r}}catch(e){D("Failed to load settings file",{error:e,settingsPath:g,key:o})}return process.env[o]||t}var S=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(S||{}),O=class{level;useColor;constructor(){let t=y("CLAUDE_MEM_LOG_LEVEL","INFO").toUpperCase();this.level=S[t]??1,this.useColor=process.stdout.isTTY??!1}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.level===0?`${t.message}
${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let r=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&r.command){let n=r.command.length>50?r.command.substring(0,50)+"...":r.command;return`${t}(${n})`}if(t==="Read"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}if(t==="Edit"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}if(t==="Write"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}return t}catch{return t}}log(t,e,r,n,s){if(t<this.level)return;let a=new Date().toISOString().replace("T"," ").substring(0,23),E=S[t].padEnd(5),u=e.padEnd(6),c="";n?.correlationId?c=`[${n.correlationId}] `:n?.sessionId&&(c=`[session-${n.sessionId}] `);let _="";s!=null&&(this.level===0&&typeof s=="object"?_=` ${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let r=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&r.command){let n=r.command.length>50?r.command.substring(0,50)+"...":r.command;return`${t}(${n})`}if(t==="Read"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}if(t==="Edit"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}if(t==="Write"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}return t}catch{return t}}log(t,e,r,n,s){if(t<this.level)return;let c=new Date().toISOString().replace("T"," ").substring(0,23),E=S[t].padEnd(5),u=e.padEnd(6),a="";n?.correlationId?a=`[${n.correlationId}] `:n?.sessionId&&(a=`[session-${n.sessionId}] `);let _="";s!=null&&(this.level===0&&typeof s=="object"?_=`
`+JSON.stringify(s,null,2):_=" "+this.formatData(s));let A="";if(n){let{sessionId:nt,sdkSessionId:ot,correlationId:st,...M}=n;Object.keys(M).length>0&&(A=` {${Object.entries(M).map(([k,x])=>`${k}=${x}`).join(", ")}}`)}let L=`[${a}] [${E}] [${u}] ${c}${r}${A}${_}`;t===3?console.error(L):console.log(L)}debug(t,e,r,n){this.log(0,t,e,r,n)}info(t,e,r,n){this.log(1,t,e,r,n)}warn(t,e,r,n){this.log(2,t,e,r,n)}error(t,e,r,n){this.log(3,t,e,r,n)}dataIn(t,e,r,n){this.info(t,`\u2192 ${e}`,r,n)}dataOut(t,e,r,n){this.info(t,`\u2190 ${e}`,r,n)}success(t,e,r,n){this.info(t,`\u2713 ${e}`,r,n)}failure(t,e,r,n){this.error(t,`\u2717 ${e}`,r,n)}timing(t,e,r,n){this.info(t,`\u23F1 ${e}`,n,{duration:`${r}ms`})}},m=new O;var p={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function N(o){return process.platform==="win32"?Math.round(o*p.WINDOWS_MULTIPLIER):o}var i=l.join(I(),".claude","plugins","marketplaces","thedotmack"),z=N(p.HEALTH_CHECK),Q=p.WORKER_STARTUP_WAIT,Z=p.WORKER_STARTUP_RETRIES;function T(){let o=l.join(I(),".claude-mem","settings.json"),t=f.loadFromFile(o);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function w(){try{let o=T();return(await fetch(`http://127.0.0.1:${o}/health`,{signal:AbortSignal.timeout(z)})).ok}catch(o){return m.debug("SYSTEM","Worker health check failed",{error:o instanceof Error?o.message:String(o),errorType:o?.constructor?.name}),!1}}async function tt(){try{let o=l.join(i,"plugin","scripts","worker-service.cjs");if(!C(o))throw new Error(`Worker script not found at ${o}`);if(process.platform==="win32"){let t=U("powershell.exe",["-NoProfile","-NonInteractive","-Command",`Start-Process -FilePath 'node' -ArgumentList '${o}' -WorkingDirectory '${i}' -WindowStyle Hidden`],{cwd:i,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(t.status!==0)throw new Error(t.stderr||"PowerShell Start-Process failed")}else{let t=l.join(i,"ecosystem.config.cjs");if(!C(t))throw new Error(`Ecosystem config not found at ${t}`);let e=l.join(i,"node_modules",".bin","pm2"),r=C(e)?e:"pm2",n=U(r,["start",t],{cwd:i,stdio:"pipe",encoding:"utf-8"});if(n.status!==0)throw new Error(n.stderr||"PM2 start failed")}for(let t=0;t<Z;t++)if(await new Promise(e=>setTimeout(e,Q)),await w())return!0;return!1}catch(o){return m.error("SYSTEM","Failed to start worker",{platform:process.platform,workerScript:l.join(i,"plugin","scripts","worker-service.cjs"),error:o instanceof Error?o.message:String(o),marketplaceRoot:i}),!1}}async function P(){if(await w())return;if(!await tt()){let t=T();throw new Error(`Worker service failed to start on port ${t}. `+JSON.stringify(s,null,2):_=" "+this.formatData(s));let A="";if(n){let{sessionId:nt,sdkSessionId:ot,correlationId:st,...h}=n;Object.keys(h).length>0&&(A=` {${Object.entries(h).map(([k,x])=>`${k}=${x}`).join(", ")}}`)}let L=`[${c}] [${E}] [${u}] ${a}${r}${A}${_}`;t===3?console.error(L):console.log(L)}debug(t,e,r,n){this.log(0,t,e,r,n)}info(t,e,r,n){this.log(1,t,e,r,n)}warn(t,e,r,n){this.log(2,t,e,r,n)}error(t,e,r,n){this.log(3,t,e,r,n)}dataIn(t,e,r,n){this.info(t,`\u2192 ${e}`,r,n)}dataOut(t,e,r,n){this.info(t,`\u2190 ${e}`,r,n)}success(t,e,r,n){this.info(t,`\u2713 ${e}`,r,n)}failure(t,e,r,n){this.error(t,`\u2717 ${e}`,r,n)}timing(t,e,r,n){this.info(t,`\u23F1 ${e}`,n,{duration:`${r}ms`})}},m=new O;var p={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function N(o){return process.platform==="win32"?Math.round(o*p.WINDOWS_MULTIPLIER):o}var i=l.join(I(),".claude","plugins","marketplaces","thedotmack"),z=N(p.HEALTH_CHECK),Q=p.WORKER_STARTUP_WAIT,Z=p.WORKER_STARTUP_RETRIES;function T(){let o=l.join(I(),".claude-mem","settings.json"),t=f.loadFromFile(o);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function w(){try{let o=T();return(await fetch(`http://127.0.0.1:${o}/health`,{signal:AbortSignal.timeout(z)})).ok}catch(o){return m.debug("SYSTEM","Worker health check failed",{error:o instanceof Error?o.message:String(o),errorType:o?.constructor?.name}),!1}}async function tt(){try{let o=l.join(i,"plugin","scripts","worker-service.cjs");if(!C(o))throw new Error(`Worker script not found at ${o}`);if(process.platform==="win32"){let t=U("powershell.exe",["-NoProfile","-NonInteractive","-Command",`Start-Process -FilePath 'node' -ArgumentList '${o}' -WorkingDirectory '${i}' -WindowStyle Hidden`],{cwd:i,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(t.status!==0)throw new Error(t.stderr||"PowerShell Start-Process failed")}else{let t=l.join(i,"ecosystem.config.cjs");if(!C(t))throw new Error(`Ecosystem config not found at ${t}`);let e=l.join(i,"node_modules",".bin","pm2"),r=C(e)?e:"pm2",n=U(r,["start",t],{cwd:i,stdio:"pipe",encoding:"utf-8"});if(n.status!==0)throw new Error(n.stderr||"PM2 start failed")}for(let t=0;t<Z;t++)if(await new Promise(e=>setTimeout(e,Q)),await w())return!0;return!1}catch(o){return m.error("SYSTEM","Failed to start worker",{platform:process.platform,workerScript:l.join(i,"plugin","scripts","worker-service.cjs"),error:o instanceof Error?o.message:String(o),marketplaceRoot:i}),!1}}async function P(){if(await w())return;if(!await tt()){let t=T();throw new Error(`Worker service failed to start on port ${t}.
To start manually, run: To start manually, run:
cd ${i} cd ${i}

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +1,12 @@
#!/usr/bin/env node #!/usr/bin/env node
import ot from"path";import{stdin as b}from"process";function x(n,t,e){return n==="PreCompact"?t?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:e.reason||"Pre-compact operation failed",suppressOutput:!0}:n==="SessionStart"?t&&e.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:e.context}}:{continue:!0,suppressOutput:!0}:n==="UserPromptSubmit"||n==="PostToolUse"?{continue:!0,suppressOutput:!0}:n==="Stop"?{continue:!0,suppressOutput:!0}:{continue:t,suppressOutput:!0,...e.reason&&!t?{stopReason:e.reason}:{}}}function m(n,t,e={}){let r=x(n,t,e);return JSON.stringify(r)}import _ from"path";import{existsSync as A}from"fs";import{homedir as w}from"os";import{spawnSync as I}from"child_process";import{readFileSync as F,existsSync as j}from"fs";import{join as V}from"path";import{homedir as K}from"os";var H=["bugfix","feature","refactor","discovery","decision","change"],W=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var y=H.join(","),D=W.join(",");var S=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_DATA_DIR:V(K(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:y,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:D,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return process.env[t]||this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!j(t))return this.getAllDefaults();let e=F(t,"utf-8"),o=JSON.parse(e).env||{},s={...this.DEFAULTS};for(let c of Object.keys(this.DEFAULTS))o[c]!==void 0&&(s[c]=o[c]);return s}};import{join as J}from"path";import{homedir as q}from"os";import{existsSync as z,readFileSync as Q}from"fs";import{appendFileSync as X}from"fs";import{homedir as B}from"os";import{join as G}from"path";var Y=G(B(),".claude-mem","silent.log");function l(n,t,e=""){let r=new Date().toISOString(),p=((new Error().stack||"").split(` import ot from"path";import{stdin as b}from"process";function H(n,t,e){return n==="PreCompact"?t?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:e.reason||"Pre-compact operation failed",suppressOutput:!0}:n==="SessionStart"?t&&e.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:e.context}}:{continue:!0,suppressOutput:!0}:n==="UserPromptSubmit"||n==="PostToolUse"?{continue:!0,suppressOutput:!0}:n==="Stop"?{continue:!0,suppressOutput:!0}:{continue:t,suppressOutput:!0,...e.reason&&!t?{stopReason:e.reason}:{}}}function m(n,t,e={}){let r=H(n,t,e);return JSON.stringify(r)}import _ from"path";import{existsSync as A}from"fs";import{homedir as w}from"os";import{spawnSync as I}from"child_process";import{readFileSync as F,existsSync as j}from"fs";import{join as V}from"path";import{homedir as K}from"os";var v=["bugfix","feature","refactor","discovery","decision","change"],W=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var y=v.join(","),D=W.join(",");var S=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_DATA_DIR:V(K(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:y,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:D,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return process.env[t]||this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!j(t))return this.getAllDefaults();let e=F(t,"utf-8"),o=JSON.parse(e).env||{},s={...this.DEFAULTS};for(let p of Object.keys(this.DEFAULTS))o[p]!==void 0&&(s[p]=o[p]);return s}};import{join as J}from"path";import{homedir as q}from"os";import{existsSync as z,readFileSync as Q}from"fs";import{appendFileSync as X}from"fs";import{homedir as B}from"os";import{join as G}from"path";var Y=G(B(),".claude-mem","silent.log");function l(n,t,e=""){let r=new Date().toISOString(),c=((new Error().stack||"").split(`
`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),i=p?`${p[1].split("/").pop()}:${p[2]}`:"unknown",a=`[${r}] [${i}] ${n}`;if(t!==void 0)try{a+=` ${JSON.stringify(t)}`}catch(E){a+=` [stringify error: ${E}]`}a+=` `)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),i=c?`${c[1].split("/").pop()}:${c[2]}`:"unknown",a=`[${r}] [HAPPY-PATH-ERROR] [${i}] ${n}`;if(t!==void 0)try{a+=` ${JSON.stringify(t)}`}catch(E){a+=` [stringify error: ${E}]`}a+=`
`;try{X(Y,a)}catch(E){console.error("[silent-debug] Failed to write to log:",E)}return e}var O=J(q(),".claude-mem","settings.json");function N(n,t){try{if(z(O)){let r=JSON.parse(Q(O,"utf-8")).env?.[n];if(r!==void 0)return r}}catch(e){l("Failed to load settings file",{error:e,settingsPath:O,key:n})}return process.env[n]||t}var T=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(T||{}),d=class{level;useColor;constructor(){let t=N("CLAUDE_MEM_LOG_LEVEL","INFO").toUpperCase();this.level=T[t]??1,this.useColor=process.stdout.isTTY??!1}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.level===0?`${t.message} `;try{X(Y,a)}catch(E){console.error("[silent-debug] Failed to write to log:",E)}return e}var O=J(q(),".claude-mem","settings.json");function N(n,t){try{if(z(O)){let r=JSON.parse(Q(O,"utf-8")).env?.[n];if(r!==void 0)return r}}catch(e){l("Failed to load settings file",{error:e,settingsPath:O,key:n})}return process.env[n]||t}var T=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(T||{}),d=class{level;useColor;constructor(){let t=N("CLAUDE_MEM_LOG_LEVEL","INFO").toUpperCase();this.level=T[t]??1,this.useColor=process.stdout.isTTY??!1}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.level===0?`${t.message}
${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let r=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&r.command){let o=r.command.length>50?r.command.substring(0,50)+"...":r.command;return`${t}(${o})`}if(t==="Read"&&r.file_path){let o=r.file_path.split("/").pop()||r.file_path;return`${t}(${o})`}if(t==="Edit"&&r.file_path){let o=r.file_path.split("/").pop()||r.file_path;return`${t}(${o})`}if(t==="Write"&&r.file_path){let o=r.file_path.split("/").pop()||r.file_path;return`${t}(${o})`}return t}catch{return t}}log(t,e,r,o,s){if(t<this.level)return;let c=new Date().toISOString().replace("T"," ").substring(0,23),p=T[t].padEnd(5),i=e.padEnd(6),a="";o?.correlationId?a=`[${o.correlationId}] `:o?.sessionId&&(a=`[session-${o.sessionId}] `);let E="";s!=null&&(this.level===0&&typeof s=="object"?E=` ${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let r=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&r.command){let o=r.command.length>50?r.command.substring(0,50)+"...":r.command;return`${t}(${o})`}if(t==="Read"&&r.file_path){let o=r.file_path.split("/").pop()||r.file_path;return`${t}(${o})`}if(t==="Edit"&&r.file_path){let o=r.file_path.split("/").pop()||r.file_path;return`${t}(${o})`}if(t==="Write"&&r.file_path){let o=r.file_path.split("/").pop()||r.file_path;return`${t}(${o})`}return t}catch{return t}}log(t,e,r,o,s){if(t<this.level)return;let p=new Date().toISOString().replace("T"," ").substring(0,23),c=T[t].padEnd(5),i=e.padEnd(6),a="";o?.correlationId?a=`[${o.correlationId}] `:o?.sessionId&&(a=`[session-${o.sessionId}] `);let E="";s!=null&&(this.level===0&&typeof s=="object"?E=`
`+JSON.stringify(s,null,2):E=" "+this.formatData(s));let h="";if(o){let{sessionId:st,sdkSessionId:it,correlationId:at,...R}=o;Object.keys(R).length>0&&(h=` {${Object.entries(R).map(([$,v])=>`${$}=${v}`).join(", ")}}`)}let M=`[${c}] [${p}] [${i}] ${a}${r}${h}${E}`;t===3?console.error(M):console.log(M)}debug(t,e,r,o){this.log(0,t,e,r,o)}info(t,e,r,o){this.log(1,t,e,r,o)}warn(t,e,r,o){this.log(2,t,e,r,o)}error(t,e,r,o){this.log(3,t,e,r,o)}dataIn(t,e,r,o){this.info(t,`\u2192 ${e}`,r,o)}dataOut(t,e,r,o){this.info(t,`\u2190 ${e}`,r,o)}success(t,e,r,o){this.info(t,`\u2713 ${e}`,r,o)}failure(t,e,r,o){this.error(t,`\u2717 ${e}`,r,o)}timing(t,e,r,o){this.info(t,`\u23F1 ${e}`,o,{duration:`${r}ms`})}},C=new d;var f={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function U(n){return process.platform==="win32"?Math.round(n*f.WINDOWS_MULTIPLIER):n}var u=_.join(w(),".claude","plugins","marketplaces","thedotmack"),Z=U(f.HEALTH_CHECK),tt=f.WORKER_STARTUP_WAIT,et=f.WORKER_STARTUP_RETRIES;function g(){let n=_.join(w(),".claude-mem","settings.json"),t=S.loadFromFile(n);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function P(){try{let n=g();return(await fetch(`http://127.0.0.1:${n}/health`,{signal:AbortSignal.timeout(Z)})).ok}catch(n){return C.debug("SYSTEM","Worker health check failed",{error:n instanceof Error?n.message:String(n),errorType:n?.constructor?.name}),!1}}async function rt(){try{let n=_.join(u,"plugin","scripts","worker-service.cjs");if(!A(n))throw new Error(`Worker script not found at ${n}`);if(process.platform==="win32"){let t=I("powershell.exe",["-NoProfile","-NonInteractive","-Command",`Start-Process -FilePath 'node' -ArgumentList '${n}' -WorkingDirectory '${u}' -WindowStyle Hidden`],{cwd:u,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(t.status!==0)throw new Error(t.stderr||"PowerShell Start-Process failed")}else{let t=_.join(u,"ecosystem.config.cjs");if(!A(t))throw new Error(`Ecosystem config not found at ${t}`);let e=_.join(u,"node_modules",".bin","pm2"),r=A(e)?e:"pm2",o=I(r,["start",t],{cwd:u,stdio:"pipe",encoding:"utf-8"});if(o.status!==0)throw new Error(o.stderr||"PM2 start failed")}for(let t=0;t<et;t++)if(await new Promise(e=>setTimeout(e,tt)),await P())return!0;return!1}catch(n){return C.error("SYSTEM","Failed to start worker",{platform:process.platform,workerScript:_.join(u,"plugin","scripts","worker-service.cjs"),error:n instanceof Error?n.message:String(n),marketplaceRoot:u}),!1}}async function k(){if(await P())return;if(!await rt()){let t=g();throw new Error(`Worker service failed to start on port ${t}. `+JSON.stringify(s,null,2):E=" "+this.formatData(s));let L="";if(o){let{sessionId:st,sdkSessionId:it,correlationId:at,...M}=o;Object.keys(M).length>0&&(L=` {${Object.entries(M).map(([$,x])=>`${$}=${x}`).join(", ")}}`)}let R=`[${p}] [${c}] [${i}] ${a}${r}${L}${E}`;t===3?console.error(R):console.log(R)}debug(t,e,r,o){this.log(0,t,e,r,o)}info(t,e,r,o){this.log(1,t,e,r,o)}warn(t,e,r,o){this.log(2,t,e,r,o)}error(t,e,r,o){this.log(3,t,e,r,o)}dataIn(t,e,r,o){this.info(t,`\u2192 ${e}`,r,o)}dataOut(t,e,r,o){this.info(t,`\u2190 ${e}`,r,o)}success(t,e,r,o){this.info(t,`\u2713 ${e}`,r,o)}failure(t,e,r,o){this.error(t,`\u2717 ${e}`,r,o)}timing(t,e,r,o){this.info(t,`\u23F1 ${e}`,o,{duration:`${r}ms`})}},C=new d;var f={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function U(n){return process.platform==="win32"?Math.round(n*f.WINDOWS_MULTIPLIER):n}var u=_.join(w(),".claude","plugins","marketplaces","thedotmack"),Z=U(f.HEALTH_CHECK),tt=f.WORKER_STARTUP_WAIT,et=f.WORKER_STARTUP_RETRIES;function g(){let n=_.join(w(),".claude-mem","settings.json"),t=S.loadFromFile(n);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function P(){try{let n=g();return(await fetch(`http://127.0.0.1:${n}/health`,{signal:AbortSignal.timeout(Z)})).ok}catch(n){return C.debug("SYSTEM","Worker health check failed",{error:n instanceof Error?n.message:String(n),errorType:n?.constructor?.name}),!1}}async function rt(){try{let n=_.join(u,"plugin","scripts","worker-service.cjs");if(!A(n))throw new Error(`Worker script not found at ${n}`);if(process.platform==="win32"){let t=I("powershell.exe",["-NoProfile","-NonInteractive","-Command",`Start-Process -FilePath 'node' -ArgumentList '${n}' -WorkingDirectory '${u}' -WindowStyle Hidden`],{cwd:u,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(t.status!==0)throw new Error(t.stderr||"PowerShell Start-Process failed")}else{let t=_.join(u,"ecosystem.config.cjs");if(!A(t))throw new Error(`Ecosystem config not found at ${t}`);let e=_.join(u,"node_modules",".bin","pm2"),r=A(e)?e:"pm2",o=I(r,["start",t],{cwd:u,stdio:"pipe",encoding:"utf-8"});if(o.status!==0)throw new Error(o.stderr||"PM2 start failed")}for(let t=0;t<et;t++)if(await new Promise(e=>setTimeout(e,tt)),await P())return!0;return!1}catch(n){return C.error("SYSTEM","Failed to start worker",{platform:process.platform,workerScript:_.join(u,"plugin","scripts","worker-service.cjs"),error:n instanceof Error?n.message:String(n),marketplaceRoot:u}),!1}}async function k(){if(await P())return;if(!await rt()){let t=g();throw new Error(`Worker service failed to start on port ${t}.
To start manually, run: To start manually, run:
cd ${u} cd ${u}
npx pm2 start ecosystem.config.cjs npx pm2 start ecosystem.config.cjs
If already running, try: npx pm2 restart claude-mem-worker`)}}async function nt(n){if(await k(),!n)throw new Error("newHook requires input");let{session_id:t,cwd:e,prompt:r}=n;l("[new-hook] Input received",{session_id:t,cwd:e,cwd_type:typeof e,cwd_length:e?.length,has_cwd:!!e,prompt_length:r?.length});let o=ot.basename(e);l("[new-hook] Project extracted",{project:o,project_type:typeof o,project_length:o?.length,is_empty:o==="",cwd_was:e});let s=g(),c,p;try{let i=await fetch(`http://127.0.0.1:${s}/api/sessions/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,project:o,prompt:r}),signal:AbortSignal.timeout(5e3)});if(!i.ok){let E=await i.text();throw new Error(`Failed to initialize session: ${i.status} ${E}`)}let a=await i.json();if(c=a.sessionDbId,p=a.promptNumber,a.skipped&&a.reason==="private"){console.error(`[new-hook] Session ${c}, prompt #${p} (fully private - skipped)`),console.log(m("UserPromptSubmit",!0));return}console.error(`[new-hook] Session ${c}, prompt #${p}`)}catch(i){throw i.cause?.code==="ECONNREFUSED"||i.name==="TimeoutError"||i.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):i}console.log(m("UserPromptSubmit",!0))}var L="";b.on("data",n=>L+=n);b.on("end",async()=>{let n=L?JSON.parse(L):void 0;await nt(n)}); If already running, try: npx pm2 restart claude-mem-worker`)}}async function nt(n){if(await k(),!n)throw new Error("newHook requires input");let{session_id:t,cwd:e,prompt:r}=n;l("[new-hook] Input received",{session_id:t,cwd:e,cwd_type:typeof e,cwd_length:e?.length,has_cwd:!!e,prompt_length:r?.length});let o=ot.basename(e);l("[new-hook] Project extracted",{project:o,project_type:typeof o,project_length:o?.length,is_empty:o==="",cwd_was:e});let s=g(),p,c;try{let i=await fetch(`http://127.0.0.1:${s}/api/sessions/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,project:o,prompt:r}),signal:AbortSignal.timeout(5e3)});if(!i.ok){let E=await i.text();throw new Error(`Failed to initialize session: ${i.status} ${E}`)}let a=await i.json();if(p=a.sessionDbId,c=a.promptNumber,a.skipped&&a.reason==="private"){console.error(`[new-hook] Session ${p}, prompt #${c} (fully private - skipped)`),console.log(m("UserPromptSubmit",!0));return}console.error(`[new-hook] Session ${p}, prompt #${c}`)}catch(i){throw i.cause?.code==="ECONNREFUSED"||i.name==="TimeoutError"||i.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):i}console.log(m("UserPromptSubmit",!0))}var h="";b.on("data",n=>h+=n);b.on("end",async()=>{let n=h?JSON.parse(h):void 0;await nt(n)});

View File

@@ -1,12 +1,12 @@
#!/usr/bin/env node #!/usr/bin/env node
import{stdin as b}from"process";function H(o,t,e){return o==="PreCompact"?t?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:e.reason||"Pre-compact operation failed",suppressOutput:!0}:o==="SessionStart"?t&&e.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:e.context}}:{continue:!0,suppressOutput:!0}:o==="UserPromptSubmit"||o==="PostToolUse"?{continue:!0,suppressOutput:!0}:o==="Stop"?{continue:!0,suppressOutput:!0}:{continue:t,suppressOutput:!0,...e.reason&&!t?{stopReason:e.reason}:{}}}function T(o,t,e={}){let r=H(o,t,e);return JSON.stringify(r)}import{join as j}from"path";import{homedir as V}from"os";import{existsSync as X,readFileSync as B}from"fs";import{appendFileSync as x}from"fs";import{homedir as W}from"os";import{join as F}from"path";var K=F(W(),".claude-mem","silent.log");function h(o,t,e=""){let r=new Date().toISOString(),p=((new Error().stack||"").split(` import{stdin as b}from"process";function v(o,t,e){return o==="PreCompact"?t?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:e.reason||"Pre-compact operation failed",suppressOutput:!0}:o==="SessionStart"?t&&e.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:e.context}}:{continue:!0,suppressOutput:!0}:o==="UserPromptSubmit"||o==="PostToolUse"?{continue:!0,suppressOutput:!0}:o==="Stop"?{continue:!0,suppressOutput:!0}:{continue:t,suppressOutput:!0,...e.reason&&!t?{stopReason:e.reason}:{}}}function g(o,t,e={}){let r=v(o,t,e);return JSON.stringify(r)}import{join as j}from"path";import{homedir as V}from"os";import{existsSync as X,readFileSync as B}from"fs";import{appendFileSync as x}from"fs";import{homedir as W}from"os";import{join as F}from"path";var K=F(W(),".claude-mem","silent.log");function S(o,t,e=""){let r=new Date().toISOString(),u=((new Error().stack||"").split(`
`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),i=p?`${p[1].split("/").pop()}:${p[2]}`:"unknown",a=`[${r}] [${i}] ${o}`;if(t!==void 0)try{a+=` ${JSON.stringify(t)}`}catch(_){a+=` [stringify error: ${_}]`}a+=` `)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),i=u?`${u[1].split("/").pop()}:${u[2]}`:"unknown",a=`[${r}] [HAPPY-PATH-ERROR] [${i}] ${o}`;if(t!==void 0)try{a+=` ${JSON.stringify(t)}`}catch(_){a+=` [stringify error: ${_}]`}a+=`
`;try{x(K,a)}catch(_){console.error("[silent-debug] Failed to write to log:",_)}return e}var g=j(V(),".claude-mem","settings.json");function y(o,t){try{if(X(g)){let r=JSON.parse(B(g,"utf-8")).env?.[o];if(r!==void 0)return r}}catch(e){h("Failed to load settings file",{error:e,settingsPath:g,key:o})}return process.env[o]||t}var m=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(m||{}),d=class{level;useColor;constructor(){let t=y("CLAUDE_MEM_LOG_LEVEL","INFO").toUpperCase();this.level=m[t]??1,this.useColor=process.stdout.isTTY??!1}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.level===0?`${t.message} `;try{x(K,a)}catch(_){console.error("[silent-debug] Failed to write to log:",_)}return e}var m=j(V(),".claude-mem","settings.json");function y(o,t){try{if(X(m)){let r=JSON.parse(B(m,"utf-8")).env?.[o];if(r!==void 0)return r}}catch(e){S("Failed to load settings file",{error:e,settingsPath:m,key:o})}return process.env[o]||t}var d=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(d||{}),C=class{level;useColor;constructor(){let t=y("CLAUDE_MEM_LOG_LEVEL","INFO").toUpperCase();this.level=d[t]??1,this.useColor=process.stdout.isTTY??!1}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.level===0?`${t.message}
${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let r=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&r.command){let n=r.command.length>50?r.command.substring(0,50)+"...":r.command;return`${t}(${n})`}if(t==="Read"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}if(t==="Edit"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}if(t==="Write"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}return t}catch{return t}}log(t,e,r,n,s){if(t<this.level)return;let c=new Date().toISOString().replace("T"," ").substring(0,23),p=m[t].padEnd(5),i=e.padEnd(6),a="";n?.correlationId?a=`[${n.correlationId}] `:n?.sessionId&&(a=`[session-${n.sessionId}] `);let _="";s!=null&&(this.level===0&&typeof s=="object"?_=` ${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let r=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&r.command){let n=r.command.length>50?r.command.substring(0,50)+"...":r.command;return`${t}(${n})`}if(t==="Read"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}if(t==="Edit"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}if(t==="Write"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}return t}catch{return t}}log(t,e,r,n,s){if(t<this.level)return;let c=new Date().toISOString().replace("T"," ").substring(0,23),u=d[t].padEnd(5),i=e.padEnd(6),a="";n?.correlationId?a=`[${n.correlationId}] `:n?.sessionId&&(a=`[session-${n.sessionId}] `);let _="";s!=null&&(this.level===0&&typeof s=="object"?_=`
`+JSON.stringify(s,null,2):_=" "+this.formatData(s));let L="";if(n){let{sessionId:st,sdkSessionId:it,correlationId:at,...R}=n;Object.keys(R).length>0&&(L=` {${Object.entries(R).map(([$,v])=>`${$}=${v}`).join(", ")}}`)}let M=`[${c}] [${p}] [${i}] ${a}${r}${L}${_}`;t===3?console.error(M):console.log(M)}debug(t,e,r,n){this.log(0,t,e,r,n)}info(t,e,r,n){this.log(1,t,e,r,n)}warn(t,e,r,n){this.log(2,t,e,r,n)}error(t,e,r,n){this.log(3,t,e,r,n)}dataIn(t,e,r,n){this.info(t,`\u2192 ${e}`,r,n)}dataOut(t,e,r,n){this.info(t,`\u2190 ${e}`,r,n)}success(t,e,r,n){this.info(t,`\u2713 ${e}`,r,n)}failure(t,e,r,n){this.error(t,`\u2717 ${e}`,r,n)}timing(t,e,r,n){this.info(t,`\u23F1 ${e}`,n,{duration:`${r}ms`})}},u=new d;import f from"path";import{existsSync as C}from"fs";import{homedir as P}from"os";import{spawnSync as I}from"child_process";import{readFileSync as J,existsSync as q}from"fs";import{join as Q}from"path";import{homedir as z}from"os";var G=["bugfix","feature","refactor","discovery","decision","change"],Y=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var D=G.join(","),U=Y.join(",");var S=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_DATA_DIR:Q(z(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:D,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:U,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return process.env[t]||this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!q(t))return this.getAllDefaults();let e=J(t,"utf-8"),n=JSON.parse(e).env||{},s={...this.DEFAULTS};for(let c of Object.keys(this.DEFAULTS))n[c]!==void 0&&(s[c]=n[c]);return s}};var l={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function N(o){return process.platform==="win32"?Math.round(o*l.WINDOWS_MULTIPLIER):o}var E=f.join(P(),".claude","plugins","marketplaces","thedotmack"),Z=N(l.HEALTH_CHECK),tt=l.WORKER_STARTUP_WAIT,et=l.WORKER_STARTUP_RETRIES;function O(){let o=f.join(P(),".claude-mem","settings.json"),t=S.loadFromFile(o);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function w(){try{let o=O();return(await fetch(`http://127.0.0.1:${o}/health`,{signal:AbortSignal.timeout(Z)})).ok}catch(o){return u.debug("SYSTEM","Worker health check failed",{error:o instanceof Error?o.message:String(o),errorType:o?.constructor?.name}),!1}}async function rt(){try{let o=f.join(E,"plugin","scripts","worker-service.cjs");if(!C(o))throw new Error(`Worker script not found at ${o}`);if(process.platform==="win32"){let t=I("powershell.exe",["-NoProfile","-NonInteractive","-Command",`Start-Process -FilePath 'node' -ArgumentList '${o}' -WorkingDirectory '${E}' -WindowStyle Hidden`],{cwd:E,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(t.status!==0)throw new Error(t.stderr||"PowerShell Start-Process failed")}else{let t=f.join(E,"ecosystem.config.cjs");if(!C(t))throw new Error(`Ecosystem config not found at ${t}`);let e=f.join(E,"node_modules",".bin","pm2"),r=C(e)?e:"pm2",n=I(r,["start",t],{cwd:E,stdio:"pipe",encoding:"utf-8"});if(n.status!==0)throw new Error(n.stderr||"PM2 start failed")}for(let t=0;t<et;t++)if(await new Promise(e=>setTimeout(e,tt)),await w())return!0;return!1}catch(o){return u.error("SYSTEM","Failed to start worker",{platform:process.platform,workerScript:f.join(E,"plugin","scripts","worker-service.cjs"),error:o instanceof Error?o.message:String(o),marketplaceRoot:E}),!1}}async function k(){if(await w())return;if(!await rt()){let t=O();throw new Error(`Worker service failed to start on port ${t}. `+JSON.stringify(s,null,2):_=" "+this.formatData(s));let h="";if(n){let{sessionId:st,sdkSessionId:it,correlationId:at,...R}=n;Object.keys(R).length>0&&(h=` {${Object.entries(R).map(([H,$])=>`${H}=${$}`).join(", ")}}`)}let M=`[${c}] [${u}] [${i}] ${a}${r}${h}${_}`;t===3?console.error(M):console.log(M)}debug(t,e,r,n){this.log(0,t,e,r,n)}info(t,e,r,n){this.log(1,t,e,r,n)}warn(t,e,r,n){this.log(2,t,e,r,n)}error(t,e,r,n){this.log(3,t,e,r,n)}dataIn(t,e,r,n){this.info(t,`\u2192 ${e}`,r,n)}dataOut(t,e,r,n){this.info(t,`\u2190 ${e}`,r,n)}success(t,e,r,n){this.info(t,`\u2713 ${e}`,r,n)}failure(t,e,r,n){this.error(t,`\u2717 ${e}`,r,n)}timing(t,e,r,n){this.info(t,`\u23F1 ${e}`,n,{duration:`${r}ms`})}},p=new C;import f from"path";import{existsSync as A}from"fs";import{homedir as P}from"os";import{spawnSync as I}from"child_process";import{readFileSync as J,existsSync as q}from"fs";import{join as Q}from"path";import{homedir as z}from"os";var G=["bugfix","feature","refactor","discovery","decision","change"],Y=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var D=G.join(","),U=Y.join(",");var T=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_DATA_DIR:Q(z(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:D,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:U,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return process.env[t]||this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!q(t))return this.getAllDefaults();let e=J(t,"utf-8"),n=JSON.parse(e).env||{},s={...this.DEFAULTS};for(let c of Object.keys(this.DEFAULTS))n[c]!==void 0&&(s[c]=n[c]);return s}};var l={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function N(o){return process.platform==="win32"?Math.round(o*l.WINDOWS_MULTIPLIER):o}var E=f.join(P(),".claude","plugins","marketplaces","thedotmack"),Z=N(l.HEALTH_CHECK),tt=l.WORKER_STARTUP_WAIT,et=l.WORKER_STARTUP_RETRIES;function O(){let o=f.join(P(),".claude-mem","settings.json"),t=T.loadFromFile(o);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function w(){try{let o=O();return(await fetch(`http://127.0.0.1:${o}/health`,{signal:AbortSignal.timeout(Z)})).ok}catch(o){return p.debug("SYSTEM","Worker health check failed",{error:o instanceof Error?o.message:String(o),errorType:o?.constructor?.name}),!1}}async function rt(){try{let o=f.join(E,"plugin","scripts","worker-service.cjs");if(!A(o))throw new Error(`Worker script not found at ${o}`);if(process.platform==="win32"){let t=I("powershell.exe",["-NoProfile","-NonInteractive","-Command",`Start-Process -FilePath 'node' -ArgumentList '${o}' -WorkingDirectory '${E}' -WindowStyle Hidden`],{cwd:E,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(t.status!==0)throw new Error(t.stderr||"PowerShell Start-Process failed")}else{let t=f.join(E,"ecosystem.config.cjs");if(!A(t))throw new Error(`Ecosystem config not found at ${t}`);let e=f.join(E,"node_modules",".bin","pm2"),r=A(e)?e:"pm2",n=I(r,["start",t],{cwd:E,stdio:"pipe",encoding:"utf-8"});if(n.status!==0)throw new Error(n.stderr||"PM2 start failed")}for(let t=0;t<et;t++)if(await new Promise(e=>setTimeout(e,tt)),await w())return!0;return!1}catch(o){return p.error("SYSTEM","Failed to start worker",{platform:process.platform,workerScript:f.join(E,"plugin","scripts","worker-service.cjs"),error:o instanceof Error?o.message:String(o),marketplaceRoot:E}),!1}}async function k(){if(await w())return;if(!await rt()){let t=O();throw new Error(`Worker service failed to start on port ${t}.
To start manually, run: To start manually, run:
cd ${E} cd ${E}
npx pm2 start ecosystem.config.cjs npx pm2 start ecosystem.config.cjs
If already running, try: npx pm2 restart claude-mem-worker`)}}var ot=new Set(["ListMcpResourcesTool","SlashCommand","Skill","TodoWrite","AskUserQuestion"]);async function nt(o){if(await k(),!o)throw new Error("saveHook requires input");let{session_id:t,cwd:e,tool_name:r,tool_input:n,tool_response:s}=o;if(ot.has(r)){console.log(T("PostToolUse",!0));return}let c=O(),p=u.formatTool(r,n);u.dataIn("HOOK",`PostToolUse: ${p}`,{workerPort:c});try{let i=await fetch(`http://127.0.0.1:${c}/api/sessions/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,tool_name:r,tool_input:n,tool_response:s,cwd:e||""}),signal:AbortSignal.timeout(l.DEFAULT)});if(!i.ok){let a=await i.text();throw u.failure("HOOK","Failed to send observation",{status:i.status},a),new Error(`Failed to send observation to worker: ${i.status} ${a}`)}u.debug("HOOK","Observation sent successfully",{toolName:r})}catch(i){throw i.cause?.code==="ECONNREFUSED"||i.name==="TimeoutError"||i.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):i}console.log(T("PostToolUse",!0))}var A="";b.on("data",o=>A+=o);b.on("end",async()=>{let o=A?JSON.parse(A):void 0;await nt(o)}); If already running, try: npx pm2 restart claude-mem-worker`)}}var ot=new Set(["ListMcpResourcesTool","SlashCommand","Skill","TodoWrite","AskUserQuestion"]);async function nt(o){if(await k(),!o)throw new Error("saveHook requires input");let{session_id:t,cwd:e,tool_name:r,tool_input:n,tool_response:s}=o;if(ot.has(r)){console.log(g("PostToolUse",!0));return}let c=O(),u=p.formatTool(r,n);p.dataIn("HOOK",`PostToolUse: ${u}`,{workerPort:c});try{let i=await fetch(`http://127.0.0.1:${c}/api/sessions/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,tool_name:r,tool_input:n,tool_response:s,cwd:S("Missing cwd in PostToolUse hook input",{session_id:t,tool_name:r},e||"")}),signal:AbortSignal.timeout(l.DEFAULT)});if(!i.ok){let a=await i.text();throw p.failure("HOOK","Failed to send observation",{status:i.status},a),new Error(`Failed to send observation to worker: ${i.status} ${a}`)}p.debug("HOOK","Observation sent successfully",{toolName:r})}catch(i){throw i.cause?.code==="ECONNREFUSED"||i.name==="TimeoutError"||i.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):i}console.log(g("PostToolUse",!0))}var L="";b.on("data",o=>L+=o);b.on("end",async()=>{let o=L?JSON.parse(L):void 0;await nt(o)});

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env node #!/usr/bin/env node
import{stdin as x}from"process";import{readFileSync as H,existsSync as $}from"fs";function W(r,t,e){return r==="PreCompact"?t?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:e.reason||"Pre-compact operation failed",suppressOutput:!0}:r==="SessionStart"?t&&e.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:e.context}}:{continue:!0,suppressOutput:!0}:r==="UserPromptSubmit"||r==="PostToolUse"?{continue:!0,suppressOutput:!0}:r==="Stop"?{continue:!0,suppressOutput:!0}:{continue:t,suppressOutput:!0,...e.reason&&!t?{stopReason:e.reason}:{}}}function M(r,t,e={}){let n=W(r,t,e);return JSON.stringify(n)}import{join as X}from"path";import{homedir as B}from"os";import{existsSync as G,readFileSync as Y}from"fs";import{appendFileSync as F}from"fs";import{homedir as K}from"os";import{join as j}from"path";var V=j(K(),".claude-mem","silent.log");function h(r,t,e=""){let n=new Date().toISOString(),a=((new Error().stack||"").split(` import{stdin as x}from"process";import{readFileSync as H,existsSync as $}from"fs";function W(r,t,e){return r==="PreCompact"?t?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:e.reason||"Pre-compact operation failed",suppressOutput:!0}:r==="SessionStart"?t&&e.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:e.context}}:{continue:!0,suppressOutput:!0}:r==="UserPromptSubmit"||r==="PostToolUse"?{continue:!0,suppressOutput:!0}:r==="Stop"?{continue:!0,suppressOutput:!0}:{continue:t,suppressOutput:!0,...e.reason&&!t?{stopReason:e.reason}:{}}}function M(r,t,e={}){let n=W(r,t,e);return JSON.stringify(n)}import{join as X}from"path";import{homedir as B}from"os";import{existsSync as G,readFileSync as Y}from"fs";import{appendFileSync as F}from"fs";import{homedir as K}from"os";import{join as j}from"path";var V=j(K(),".claude-mem","silent.log");function g(r,t,e=""){let n=new Date().toISOString(),a=((new Error().stack||"").split(`
`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),E=a?`${a[1].split("/").pop()}:${a[2]}`:"unknown",u=`[${n}] [${E}] ${r}`;if(t!==void 0)try{u+=` ${JSON.stringify(t)}`}catch(l){u+=` [stringify error: ${l}]`}u+=` `)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),E=a?`${a[1].split("/").pop()}:${a[2]}`:"unknown",u=`[${n}] [HAPPY-PATH-ERROR] [${E}] ${r}`;if(t!==void 0)try{u+=` ${JSON.stringify(t)}`}catch(_){u+=` [stringify error: ${_}]`}u+=`
`;try{F(V,u)}catch(l){console.error("[silent-debug] Failed to write to log:",l)}return e}var O=X(B(),".claude-mem","settings.json");function R(r,t){try{if(G(O)){let n=JSON.parse(Y(O,"utf-8")).env?.[r];if(n!==void 0)return n}}catch(e){h("Failed to load settings file",{error:e,settingsPath:O,key:r})}return process.env[r]||t}var m=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(m||{}),T=class{level;useColor;constructor(){let t=R("CLAUDE_MEM_LOG_LEVEL","INFO").toUpperCase();this.level=m[t]??1,this.useColor=process.stdout.isTTY??!1}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.level===0?`${t.message} `;try{F(V,u)}catch(_){console.error("[silent-debug] Failed to write to log:",_)}return e}var m=X(B(),".claude-mem","settings.json");function R(r,t){try{if(G(m)){let n=JSON.parse(Y(m,"utf-8")).env?.[r];if(n!==void 0)return n}}catch(e){g("Failed to load settings file",{error:e,settingsPath:m,key:r})}return process.env[r]||t}var T=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(T||{}),d=class{level;useColor;constructor(){let t=R("CLAUDE_MEM_LOG_LEVEL","INFO").toUpperCase();this.level=T[t]??1,this.useColor=process.stdout.isTTY??!1}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.level===0?`${t.message}
${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let n=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&n.command){let o=n.command.length>50?n.command.substring(0,50)+"...":n.command;return`${t}(${o})`}if(t==="Read"&&n.file_path){let o=n.file_path.split("/").pop()||n.file_path;return`${t}(${o})`}if(t==="Edit"&&n.file_path){let o=n.file_path.split("/").pop()||n.file_path;return`${t}(${o})`}if(t==="Write"&&n.file_path){let o=n.file_path.split("/").pop()||n.file_path;return`${t}(${o})`}return t}catch{return t}}log(t,e,n,o,s){if(t<this.level)return;let i=new Date().toISOString().replace("T"," ").substring(0,23),a=m[t].padEnd(5),E=e.padEnd(6),u="";o?.correlationId?u=`[${o.correlationId}] `:o?.sessionId&&(u=`[session-${o.sessionId}] `);let l="";s!=null&&(this.level===0&&typeof s=="object"?l=` ${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let n=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&n.command){let o=n.command.length>50?n.command.substring(0,50)+"...":n.command;return`${t}(${o})`}if(t==="Read"&&n.file_path){let o=n.file_path.split("/").pop()||n.file_path;return`${t}(${o})`}if(t==="Edit"&&n.file_path){let o=n.file_path.split("/").pop()||n.file_path;return`${t}(${o})`}if(t==="Write"&&n.file_path){let o=n.file_path.split("/").pop()||n.file_path;return`${t}(${o})`}return t}catch{return t}}log(t,e,n,o,s){if(t<this.level)return;let i=new Date().toISOString().replace("T"," ").substring(0,23),a=T[t].padEnd(5),E=e.padEnd(6),u="";o?.correlationId?u=`[${o.correlationId}] `:o?.sessionId&&(u=`[session-${o.sessionId}] `);let _="";s!=null&&(this.level===0&&typeof s=="object"?_=`
`+JSON.stringify(s,null,2):l=" "+this.formatData(s));let y="";if(o){let{sessionId:ct,sdkSessionId:pt,correlationId:ut,...L}=o;Object.keys(L).length>0&&(y=` {${Object.entries(L).map(([b,v])=>`${b}=${v}`).join(", ")}}`)}let A=`[${i}] [${a}] [${E}] ${u}${n}${y}${l}`;t===3?console.error(A):console.log(A)}debug(t,e,n,o){this.log(0,t,e,n,o)}info(t,e,n,o){this.log(1,t,e,n,o)}warn(t,e,n,o){this.log(2,t,e,n,o)}error(t,e,n,o){this.log(3,t,e,n,o)}dataIn(t,e,n,o){this.info(t,`\u2192 ${e}`,n,o)}dataOut(t,e,n,o){this.info(t,`\u2190 ${e}`,n,o)}success(t,e,n,o){this.info(t,`\u2713 ${e}`,n,o)}failure(t,e,n,o){this.error(t,`\u2717 ${e}`,n,o)}timing(t,e,n,o){this.info(t,`\u23F1 ${e}`,o,{duration:`${n}ms`})}},c=new T;import _ from"path";import{existsSync as d}from"fs";import{homedir as P}from"os";import{spawnSync as I}from"child_process";import{readFileSync as z,existsSync as Q}from"fs";import{join as Z}from"path";import{homedir as tt}from"os";var J=["bugfix","feature","refactor","discovery","decision","change"],q=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var D=J.join(","),N=q.join(",");var g=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_DATA_DIR:Z(tt(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:D,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:N,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return process.env[t]||this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!Q(t))return this.getAllDefaults();let e=z(t,"utf-8"),o=JSON.parse(e).env||{},s={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))o[i]!==void 0&&(s[i]=o[i]);return s}};var f={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function U(r){return process.platform==="win32"?Math.round(r*f.WINDOWS_MULTIPLIER):r}var p=_.join(P(),".claude","plugins","marketplaces","thedotmack"),et=U(f.HEALTH_CHECK),rt=f.WORKER_STARTUP_WAIT,nt=f.WORKER_STARTUP_RETRIES;function S(){let r=_.join(P(),".claude-mem","settings.json"),t=g.loadFromFile(r);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function k(){try{let r=S();return(await fetch(`http://127.0.0.1:${r}/health`,{signal:AbortSignal.timeout(et)})).ok}catch(r){return c.debug("SYSTEM","Worker health check failed",{error:r instanceof Error?r.message:String(r),errorType:r?.constructor?.name}),!1}}async function ot(){try{let r=_.join(p,"plugin","scripts","worker-service.cjs");if(!d(r))throw new Error(`Worker script not found at ${r}`);if(process.platform==="win32"){let t=I("powershell.exe",["-NoProfile","-NonInteractive","-Command",`Start-Process -FilePath 'node' -ArgumentList '${r}' -WorkingDirectory '${p}' -WindowStyle Hidden`],{cwd:p,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(t.status!==0)throw new Error(t.stderr||"PowerShell Start-Process failed")}else{let t=_.join(p,"ecosystem.config.cjs");if(!d(t))throw new Error(`Ecosystem config not found at ${t}`);let e=_.join(p,"node_modules",".bin","pm2"),n=d(e)?e:"pm2",o=I(n,["start",t],{cwd:p,stdio:"pipe",encoding:"utf-8"});if(o.status!==0)throw new Error(o.stderr||"PM2 start failed")}for(let t=0;t<nt;t++)if(await new Promise(e=>setTimeout(e,rt)),await k())return!0;return!1}catch(r){return c.error("SYSTEM","Failed to start worker",{platform:process.platform,workerScript:_.join(p,"plugin","scripts","worker-service.cjs"),error:r instanceof Error?r.message:String(r),marketplaceRoot:p}),!1}}async function w(){if(await k())return;if(!await ot()){let t=S();throw new Error(`Worker service failed to start on port ${t}. `+JSON.stringify(s,null,2):_=" "+this.formatData(s));let A="";if(o){let{sessionId:ct,sdkSessionId:pt,correlationId:ut,...L}=o;Object.keys(L).length>0&&(A=` {${Object.entries(L).map(([b,v])=>`${b}=${v}`).join(", ")}}`)}let h=`[${i}] [${a}] [${E}] ${u}${n}${A}${_}`;t===3?console.error(h):console.log(h)}debug(t,e,n,o){this.log(0,t,e,n,o)}info(t,e,n,o){this.log(1,t,e,n,o)}warn(t,e,n,o){this.log(2,t,e,n,o)}error(t,e,n,o){this.log(3,t,e,n,o)}dataIn(t,e,n,o){this.info(t,`\u2192 ${e}`,n,o)}dataOut(t,e,n,o){this.info(t,`\u2190 ${e}`,n,o)}success(t,e,n,o){this.info(t,`\u2713 ${e}`,n,o)}failure(t,e,n,o){this.error(t,`\u2717 ${e}`,n,o)}timing(t,e,n,o){this.info(t,`\u23F1 ${e}`,o,{duration:`${n}ms`})}},c=new d;import f from"path";import{existsSync as y}from"fs";import{homedir as P}from"os";import{spawnSync as I}from"child_process";import{readFileSync as z,existsSync as Q}from"fs";import{join as Z}from"path";import{homedir as tt}from"os";var J=["bugfix","feature","refactor","discovery","decision","change"],q=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var D=J.join(","),N=q.join(",");var S=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_DATA_DIR:Z(tt(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:D,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:N,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return process.env[t]||this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!Q(t))return this.getAllDefaults();let e=z(t,"utf-8"),o=JSON.parse(e).env||{},s={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))o[i]!==void 0&&(s[i]=o[i]);return s}};var l={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function U(r){return process.platform==="win32"?Math.round(r*l.WINDOWS_MULTIPLIER):r}var p=f.join(P(),".claude","plugins","marketplaces","thedotmack"),et=U(l.HEALTH_CHECK),rt=l.WORKER_STARTUP_WAIT,nt=l.WORKER_STARTUP_RETRIES;function O(){let r=f.join(P(),".claude-mem","settings.json"),t=S.loadFromFile(r);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function k(){try{let r=O();return(await fetch(`http://127.0.0.1:${r}/health`,{signal:AbortSignal.timeout(et)})).ok}catch(r){return c.debug("SYSTEM","Worker health check failed",{error:r instanceof Error?r.message:String(r),errorType:r?.constructor?.name}),!1}}async function ot(){try{let r=f.join(p,"plugin","scripts","worker-service.cjs");if(!y(r))throw new Error(`Worker script not found at ${r}`);if(process.platform==="win32"){let t=I("powershell.exe",["-NoProfile","-NonInteractive","-Command",`Start-Process -FilePath 'node' -ArgumentList '${r}' -WorkingDirectory '${p}' -WindowStyle Hidden`],{cwd:p,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(t.status!==0)throw new Error(t.stderr||"PowerShell Start-Process failed")}else{let t=f.join(p,"ecosystem.config.cjs");if(!y(t))throw new Error(`Ecosystem config not found at ${t}`);let e=f.join(p,"node_modules",".bin","pm2"),n=y(e)?e:"pm2",o=I(n,["start",t],{cwd:p,stdio:"pipe",encoding:"utf-8"});if(o.status!==0)throw new Error(o.stderr||"PM2 start failed")}for(let t=0;t<nt;t++)if(await new Promise(e=>setTimeout(e,rt)),await k())return!0;return!1}catch(r){return c.error("SYSTEM","Failed to start worker",{platform:process.platform,workerScript:f.join(p,"plugin","scripts","worker-service.cjs"),error:r instanceof Error?r.message:String(r),marketplaceRoot:p}),!1}}async function w(){if(await k())return;if(!await ot()){let t=O();throw new Error(`Worker service failed to start on port ${t}.
To start manually, run: To start manually, run:
cd ${p} cd ${p}
@@ -15,4 +15,4 @@ If already running, try: npx pm2 restart claude-mem-worker`)}}function st(r){if(
`);for(let n=e.length-1;n>=0;n--)try{let o=JSON.parse(e[n]);if(o.type==="assistant"&&o.message?.content){let s="",i=o.message.content;return typeof i=="string"?s=i:Array.isArray(i)&&(s=i.filter(E=>E.type==="text").map(E=>E.text).join(` `);for(let n=e.length-1;n>=0;n--)try{let o=JSON.parse(e[n]);if(o.type==="assistant"&&o.message?.content){let s="",i=o.message.content;return typeof i=="string"?s=i:Array.isArray(i)&&(s=i.filter(E=>E.type==="text").map(E=>E.text).join(`
`)),s=s.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g,""),s=s.replace(/\n{3,}/g,` `)),s=s.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g,""),s=s.replace(/\n{3,}/g,`
`).trim(),s}}catch{continue}}catch(t){c.error("HOOK","Failed to read transcript",{transcriptPath:r},t)}return""}async function at(r){if(await w(),!r)throw new Error("summaryHook requires input");let{session_id:t}=r,e=S(),n=st(r.transcript_path||""),o=it(r.transcript_path||"");c.dataIn("HOOK","Stop: Requesting summary",{workerPort:e,hasLastUserMessage:!!n,hasLastAssistantMessage:!!o});try{let s=await fetch(`http://127.0.0.1:${e}/api/sessions/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,last_user_message:n,last_assistant_message:o}),signal:AbortSignal.timeout(f.DEFAULT)});if(!s.ok){let i=await s.text();throw c.failure("HOOK","Failed to generate summary",{status:s.status},i),new Error(`Failed to request summary from worker: ${s.status} ${i}`)}c.debug("HOOK","Summary request sent successfully")}catch(s){throw s.cause?.code==="ECONNREFUSED"||s.name==="TimeoutError"||s.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):s}finally{fetch(`http://127.0.0.1:${e}/api/processing`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({isProcessing:!1})}).catch(()=>{})}console.log(M("Stop",!0))}var C="";x.on("data",r=>C+=r);x.on("end",async()=>{let r=C?JSON.parse(C):void 0;await at(r)}); `).trim(),s}}catch{continue}}catch(t){c.error("HOOK","Failed to read transcript",{transcriptPath:r},t)}return""}async function at(r){if(await w(),!r)throw new Error("summaryHook requires input");let{session_id:t}=r,e=O(),n=g("Missing transcript_path in Stop hook input",{session_id:t},r.transcript_path||""),o=st(n),s=it(n);c.dataIn("HOOK","Stop: Requesting summary",{workerPort:e,hasLastUserMessage:!!o,hasLastAssistantMessage:!!s});try{let i=await fetch(`http://127.0.0.1:${e}/api/sessions/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,last_user_message:o,last_assistant_message:s}),signal:AbortSignal.timeout(l.DEFAULT)});if(!i.ok){let a=await i.text();throw c.failure("HOOK","Failed to generate summary",{status:i.status},a),new Error(`Failed to request summary from worker: ${i.status} ${a}`)}c.debug("HOOK","Summary request sent successfully")}catch(i){throw i.cause?.code==="ECONNREFUSED"||i.name==="TimeoutError"||i.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):i}finally{fetch(`http://127.0.0.1:${e}/api/processing`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({isProcessing:!1})}).catch(()=>{})}console.log(M("Stop",!0))}var C="";x.on("data",r=>C+=r);x.on("end",async()=>{let r=C?JSON.parse(C):void 0;await at(r)});

View File

@@ -1,15 +1,15 @@
#!/usr/bin/env node #!/usr/bin/env node
import{basename as rt}from"path";import p from"path";import{existsSync as h}from"fs";import{homedir as w}from"os";import{spawnSync as I}from"child_process";import{readFileSync as H,existsSync as F}from"fs";import{join as j}from"path";import{homedir as V}from"os";var b=["bugfix","feature","refactor","discovery","decision","change"],x=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var D=b.join(","),y=x.join(",");var S=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_DATA_DIR:j(V(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:D,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:y,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return process.env[t]||this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!F(t))return this.getAllDefaults();let e=H(t,"utf-8"),r=JSON.parse(e).env||{},s={...this.DEFAULTS};for(let E of Object.keys(this.DEFAULTS))r[E]!==void 0&&(s[E]=r[E]);return s}};import{join as Y}from"path";import{homedir as J}from"os";import{existsSync as q,readFileSync as Z}from"fs";import{appendFileSync as K}from"fs";import{homedir as X}from"os";import{join as B}from"path";var G=B(X(),".claude-mem","silent.log");function R(o,t,e=""){let n=new Date().toISOString(),_=((new Error().stack||"").split(` import{basename as rt}from"path";import p from"path";import{existsSync as h}from"fs";import{homedir as w}from"os";import{spawnSync as I}from"child_process";import{readFileSync as H,existsSync as F}from"fs";import{join as j}from"path";import{homedir as V}from"os";var b=["bugfix","feature","refactor","discovery","decision","change"],x=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var D=b.join(","),y=x.join(",");var S=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_DATA_DIR:j(V(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:D,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:y,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return process.env[t]||this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!F(t))return this.getAllDefaults();let e=H(t,"utf-8"),r=JSON.parse(e).env||{},s={...this.DEFAULTS};for(let E of Object.keys(this.DEFAULTS))r[E]!==void 0&&(s[E]=r[E]);return s}};import{join as Y}from"path";import{homedir as J}from"os";import{existsSync as q,readFileSync as Z}from"fs";import{appendFileSync as K}from"fs";import{homedir as X}from"os";import{join as B}from"path";var G=B(X(),".claude-mem","silent.log");function R(o,t,e=""){let n=new Date().toISOString(),_=((new Error().stack||"").split(`
`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),l=_?`${_[1].split("/").pop()}:${_[2]}`:"unknown",i=`[${n}] [${l}] ${o}`;if(t!==void 0)try{i+=` ${JSON.stringify(t)}`}catch(a){i+=` [stringify error: ${a}]`}i+=` `)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),l=_?`${_[1].split("/").pop()}:${_[2]}`:"unknown",i=`[${n}] [HAPPY-PATH-ERROR] [${l}] ${o}`;if(t!==void 0)try{i+=` ${JSON.stringify(t)}`}catch(a){i+=` [stringify error: ${a}]`}i+=`
`;try{K(G,i)}catch(a){console.error("[silent-debug] Failed to write to log:",a)}return e}var O=Y(J(),".claude-mem","settings.json");function U(o,t){try{if(q(O)){let n=JSON.parse(Z(O,"utf-8")).env?.[o];if(n!==void 0)return n}}catch(e){R("Failed to load settings file",{error:e,settingsPath:O,key:o})}return process.env[o]||t}var C=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(C||{}),d=class{level;useColor;constructor(){let t=U("CLAUDE_MEM_LOG_LEVEL","INFO").toUpperCase();this.level=C[t]??1,this.useColor=process.stdout.isTTY??!1}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.level===0?`${t.message} `;try{K(G,i)}catch(a){console.error("[silent-debug] Failed to write to log:",a)}return e}var O=Y(J(),".claude-mem","settings.json");function U(o,t){try{if(q(O)){let n=JSON.parse(Z(O,"utf-8")).env?.[o];if(n!==void 0)return n}}catch(e){R("Failed to load settings file",{error:e,settingsPath:O,key:o})}return process.env[o]||t}var C=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(C||{}),d=class{level;useColor;constructor(){let t=U("CLAUDE_MEM_LOG_LEVEL","INFO").toUpperCase();this.level=C[t]??1,this.useColor=process.stdout.isTTY??!1}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.level===0?`${t.message}
${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let n=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&n.command){let r=n.command.length>50?n.command.substring(0,50)+"...":n.command;return`${t}(${r})`}if(t==="Read"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}if(t==="Edit"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}if(t==="Write"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}return t}catch{return t}}log(t,e,n,r,s){if(t<this.level)return;let E=new Date().toISOString().replace("T"," ").substring(0,23),_=C[t].padEnd(5),l=e.padEnd(6),i="";r?.correlationId?i=`[${r.correlationId}] `:r?.sessionId&&(i=`[session-${r.sessionId}] `);let a="";s!=null&&(this.level===0&&typeof s=="object"?a=` ${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let n=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&n.command){let r=n.command.length>50?n.command.substring(0,50)+"...":n.command;return`${t}(${r})`}if(t==="Read"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}if(t==="Edit"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}if(t==="Write"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}return t}catch{return t}}log(t,e,n,r,s){if(t<this.level)return;let E=new Date().toISOString().replace("T"," ").substring(0,23),_=C[t].padEnd(5),l=e.padEnd(6),i="";r?.correlationId?i=`[${r.correlationId}] `:r?.sessionId&&(i=`[session-${r.sessionId}] `);let a="";s!=null&&(this.level===0&&typeof s=="object"?a=`
`+JSON.stringify(s,null,2):a=" "+this.formatData(s));let u="";if(r){let{sessionId:L,sdkSessionId:$,correlationId:M,...f}=r;Object.keys(f).length>0&&(u=` {${Object.entries(f).map(([k,W])=>`${k}=${W}`).join(", ")}}`)}let T=`[${E}] [${_}] [${l}] ${i}${n}${u}${a}`;t===3?console.error(T):console.log(T)}debug(t,e,n,r){this.log(0,t,e,n,r)}info(t,e,n,r){this.log(1,t,e,n,r)}warn(t,e,n,r){this.log(2,t,e,n,r)}error(t,e,n,r){this.log(3,t,e,n,r)}dataIn(t,e,n,r){this.info(t,`\u2192 ${e}`,n,r)}dataOut(t,e,n,r){this.info(t,`\u2190 ${e}`,n,r)}success(t,e,n,r){this.info(t,`\u2713 ${e}`,n,r)}failure(t,e,n,r){this.error(t,`\u2717 ${e}`,n,r)}timing(t,e,n,r){this.info(t,`\u23F1 ${e}`,r,{duration:`${n}ms`})}},A=new d;var g={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function N(o){return process.platform==="win32"?Math.round(o*g.WINDOWS_MULTIPLIER):o}var c=p.join(w(),".claude","plugins","marketplaces","thedotmack"),z=N(g.HEALTH_CHECK),Q=g.WORKER_STARTUP_WAIT,tt=g.WORKER_STARTUP_RETRIES;function m(){let o=p.join(w(),".claude-mem","settings.json"),t=S.loadFromFile(o);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function P(){try{let o=m();return(await fetch(`http://127.0.0.1:${o}/health`,{signal:AbortSignal.timeout(z)})).ok}catch(o){return A.debug("SYSTEM","Worker health check failed",{error:o instanceof Error?o.message:String(o),errorType:o?.constructor?.name}),!1}}async function et(){try{let o=p.join(c,"plugin","scripts","worker-service.cjs");if(!h(o))throw new Error(`Worker script not found at ${o}`);if(process.platform==="win32"){let t=I("powershell.exe",["-NoProfile","-NonInteractive","-Command",`Start-Process -FilePath 'node' -ArgumentList '${o}' -WorkingDirectory '${c}' -WindowStyle Hidden`],{cwd:c,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(t.status!==0)throw new Error(t.stderr||"PowerShell Start-Process failed")}else{let t=p.join(c,"ecosystem.config.cjs");if(!h(t))throw new Error(`Ecosystem config not found at ${t}`);let e=p.join(c,"node_modules",".bin","pm2"),n=h(e)?e:"pm2",r=I(n,["start",t],{cwd:c,stdio:"pipe",encoding:"utf-8"});if(r.status!==0)throw new Error(r.stderr||"PM2 start failed")}for(let t=0;t<tt;t++)if(await new Promise(e=>setTimeout(e,Q)),await P())return!0;return!1}catch(o){return A.error("SYSTEM","Failed to start worker",{platform:process.platform,workerScript:p.join(c,"plugin","scripts","worker-service.cjs"),error:o instanceof Error?o.message:String(o),marketplaceRoot:c}),!1}}async function v(){if(await P())return;if(!await et()){let t=m();throw new Error(`Worker service failed to start on port ${t}. `+JSON.stringify(s,null,2):a=" "+this.formatData(s));let u="";if(r){let{sessionId:L,sdkSessionId:v,correlationId:M,...g}=r;Object.keys(g).length>0&&(u=` {${Object.entries(g).map(([$,W])=>`${$}=${W}`).join(", ")}}`)}let T=`[${E}] [${_}] [${l}] ${i}${n}${u}${a}`;t===3?console.error(T):console.log(T)}debug(t,e,n,r){this.log(0,t,e,n,r)}info(t,e,n,r){this.log(1,t,e,n,r)}warn(t,e,n,r){this.log(2,t,e,n,r)}error(t,e,n,r){this.log(3,t,e,n,r)}dataIn(t,e,n,r){this.info(t,`\u2192 ${e}`,n,r)}dataOut(t,e,n,r){this.info(t,`\u2190 ${e}`,n,r)}success(t,e,n,r){this.info(t,`\u2713 ${e}`,n,r)}failure(t,e,n,r){this.error(t,`\u2717 ${e}`,n,r)}timing(t,e,n,r){this.info(t,`\u23F1 ${e}`,r,{duration:`${n}ms`})}},A=new d;var f={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function N(o){return process.platform==="win32"?Math.round(o*f.WINDOWS_MULTIPLIER):o}var c=p.join(w(),".claude","plugins","marketplaces","thedotmack"),z=N(f.HEALTH_CHECK),Q=f.WORKER_STARTUP_WAIT,tt=f.WORKER_STARTUP_RETRIES;function m(){let o=p.join(w(),".claude-mem","settings.json"),t=S.loadFromFile(o);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function P(){try{let o=m();return(await fetch(`http://127.0.0.1:${o}/health`,{signal:AbortSignal.timeout(z)})).ok}catch(o){return A.debug("SYSTEM","Worker health check failed",{error:o instanceof Error?o.message:String(o),errorType:o?.constructor?.name}),!1}}async function et(){try{let o=p.join(c,"plugin","scripts","worker-service.cjs");if(!h(o))throw new Error(`Worker script not found at ${o}`);if(process.platform==="win32"){let t=I("powershell.exe",["-NoProfile","-NonInteractive","-Command",`Start-Process -FilePath 'node' -ArgumentList '${o}' -WorkingDirectory '${c}' -WindowStyle Hidden`],{cwd:c,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(t.status!==0)throw new Error(t.stderr||"PowerShell Start-Process failed")}else{let t=p.join(c,"ecosystem.config.cjs");if(!h(t))throw new Error(`Ecosystem config not found at ${t}`);let e=p.join(c,"node_modules",".bin","pm2"),n=h(e)?e:"pm2",r=I(n,["start",t],{cwd:c,stdio:"pipe",encoding:"utf-8"});if(r.status!==0)throw new Error(r.stderr||"PM2 start failed")}for(let t=0;t<tt;t++)if(await new Promise(e=>setTimeout(e,Q)),await P())return!0;return!1}catch(o){return A.error("SYSTEM","Failed to start worker",{platform:process.platform,workerScript:p.join(c,"plugin","scripts","worker-service.cjs"),error:o instanceof Error?o.message:String(o),marketplaceRoot:c}),!1}}async function k(){if(await P())return;if(!await et()){let t=m();throw new Error(`Worker service failed to start on port ${t}.
To start manually, run: To start manually, run:
cd ${c} cd ${c}
npx pm2 start ecosystem.config.cjs npx pm2 start ecosystem.config.cjs
If already running, try: npx pm2 restart claude-mem-worker`)}}try{await v();let o=m(),t=rt(process.cwd()),e=await fetch(`http://127.0.0.1:${o}/api/context/inject?project=${encodeURIComponent(t)}&colors=true`,{method:"GET",signal:AbortSignal.timeout(5e3)});if(!e.ok)throw new Error(`Worker error ${e.status}`);let n=await e.text(),r=new Date,s=new Date("2025-12-06T00:00:00Z"),E=new Date("2025-12-05T05:00:00Z"),_="";r<E&&(_=` If already running, try: npx pm2 restart claude-mem-worker`)}}try{await k();let o=m(),t=rt(process.cwd()),e=await fetch(`http://127.0.0.1:${o}/api/context/inject?project=${encodeURIComponent(t)}&colors=true`,{method:"GET",signal:AbortSignal.timeout(5e3)});if(!e.ok)throw new Error(`Worker error ${e.status}`);let n=await e.text(),r=new Date,s=new Date("2025-12-06T00:00:00Z"),E=new Date("2025-12-05T05:00:00Z"),_="";r<E&&(_=`
\u{1F680} \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 \u{1F680} \u{1F680} \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 \u{1F680}
@@ -19,7 +19,7 @@ If already running, try: npx pm2 restart claude-mem-worker`)}}try{await v();let
\u2B50 Your upvote means the world - thank you! \u2B50 Your upvote means the world - thank you!
\u{1F680} \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 \u{1F680} \u{1F680} \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 \u{1F680}
`);let l="";if(r<s){let a=r.getUTCHours()*60+r.getUTCMinutes(),u=Math.floor((a-300+1440)%1440/60),T=r.getUTCDate(),L=r.getUTCMonth(),M=r.getUTCFullYear()===2025&&L===11&&T>=1&&T<=5,f=u>=17&&u<19;M&&f?l=` `);let l="";if(r<s){let a=r.getUTCHours()*60+r.getUTCMinutes(),u=Math.floor((a-300+1440)%1440/60),T=r.getUTCDate(),L=r.getUTCMonth(),M=r.getUTCFullYear()===2025&&L===11&&T>=1&&T<=5,g=u>=17&&u<19;M&&g?l=`
\u{1F534} LIVE NOW: AMA w/ Dev (@thedotmack) until 7pm EST \u{1F534} LIVE NOW: AMA w/ Dev (@thedotmack) until 7pm EST
`:l=` `:l=`
\u2013 LIVE AMA w/ Dev (@thedotmack) Dec 1st\u20135th, 5pm to 7pm EST \u2013 LIVE AMA w/ Dev (@thedotmack) Dec 1st\u20135th, 5pm to 7pm EST

File diff suppressed because one or more lines are too long

38
scripts/find-silent-failures.sh Executable file
View File

@@ -0,0 +1,38 @@
#!/bin/bash
# Find Silent Failure Patterns
#
# This script searches for defensive OR patterns (|| '' || null || undefined)
# that should potentially use happy_path_error__with_fallback instead.
#
# Usage: ./scripts/find-silent-failures.sh
echo "=================================================="
echo "Searching for defensive OR patterns in src/"
echo "These MAY be silent failures that should log errors"
echo "=================================================="
echo ""
echo "🔍 Searching for: || ''"
echo "---"
grep -rn "|| ''" src/ --include="*.ts" --color=always || echo " (none found)"
echo ""
echo "🔍 Searching for: || \"\""
echo "---"
grep -rn '|| ""' src/ --include="*.ts" --color=always || echo " (none found)"
echo ""
echo "🔍 Searching for: || null"
echo "---"
grep -rn "|| null" src/ --include="*.ts" --color=always || echo " (none found)"
echo ""
echo "🔍 Searching for: || undefined"
echo "---"
grep -rn "|| undefined" src/ --include="*.ts" --color=always || echo " (none found)"
echo ""
echo "=================================================="
echo "Review each match and determine if it should use:"
echo " happy_path_error__with_fallback('description', data, fallback)"
echo "=================================================="

View File

@@ -8,7 +8,7 @@
import { stdin } from 'process'; import { stdin } from 'process';
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js'; import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
import { silentDebug } from '../utils/silent-debug.js'; import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
import { HOOK_TIMEOUTS } from '../shared/hook-constants.js'; import { HOOK_TIMEOUTS } from '../shared/hook-constants.js';
export interface SessionEndInput { export interface SessionEndInput {
@@ -26,7 +26,7 @@ async function cleanupHook(input?: SessionEndInput): Promise<void> {
// Ensure worker is running before any other logic // Ensure worker is running before any other logic
await ensureWorkerRunning(); await ensureWorkerRunning();
silentDebug('[cleanup-hook] Hook fired', { happy_path_error__with_fallback('[cleanup-hook] Hook fired', {
session_id: input?.session_id, session_id: input?.session_id,
cwd: input?.cwd, cwd: input?.cwd,
reason: input?.reason reason: input?.reason
@@ -64,14 +64,14 @@ async function cleanupHook(input?: SessionEndInput): Promise<void> {
if (response.ok) { if (response.ok) {
const result = await response.json(); const result = await response.json();
silentDebug('[cleanup-hook] Session cleanup completed', result); happy_path_error__with_fallback('[cleanup-hook] Session cleanup completed', result);
} else { } else {
// Non-fatal - session might not exist // Non-fatal - session might not exist
silentDebug('[cleanup-hook] Session not found or already cleaned up'); happy_path_error__with_fallback('[cleanup-hook] Session not found or already cleaned up');
} }
} catch (error: any) { } catch (error: any) {
// Worker might not be running - that's okay // Worker might not be running - that's okay
silentDebug('[cleanup-hook] Worker not reachable (non-critical)', { happy_path_error__with_fallback('[cleanup-hook] Worker not reachable (non-critical)', {
error: error.message error: error.message
}); });
} }

View File

@@ -37,7 +37,7 @@ import path from 'path';
import { stdin } from 'process'; import { stdin } from 'process';
import { createHookResponse } from './hook-response.js'; import { createHookResponse } from './hook-response.js';
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js'; import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
import { silentDebug } from '../utils/silent-debug.js'; import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
export interface UserPromptSubmitInput { export interface UserPromptSubmitInput {
session_id: string; session_id: string;
@@ -61,7 +61,7 @@ async function newHook(input?: UserPromptSubmitInput): Promise<void> {
const { session_id, cwd, prompt } = input; const { session_id, cwd, prompt } = input;
// Debug: Log what we received // Debug: Log what we received
silentDebug('[new-hook] Input received', { happy_path_error__with_fallback('[new-hook] Input received', {
session_id, session_id,
cwd, cwd,
cwd_type: typeof cwd, cwd_type: typeof cwd,
@@ -72,7 +72,7 @@ async function newHook(input?: UserPromptSubmitInput): Promise<void> {
const project = path.basename(cwd); const project = path.basename(cwd);
silentDebug('[new-hook] Project extracted', { happy_path_error__with_fallback('[new-hook] Project extracted', {
project, project,
project_type: typeof project, project_type: typeof project,
project_length: project?.length, project_length: project?.length,

View File

@@ -11,6 +11,7 @@ import { createHookResponse } from './hook-response.js';
import { logger } from '../utils/logger.js'; import { logger } from '../utils/logger.js';
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js'; import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
import { HOOK_TIMEOUTS } from '../shared/hook-constants.js'; import { HOOK_TIMEOUTS } from '../shared/hook-constants.js';
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
export interface PostToolUseInput { export interface PostToolUseInput {
session_id: string; session_id: string;
@@ -66,7 +67,11 @@ async function saveHook(input?: PostToolUseInput): Promise<void> {
tool_name, tool_name,
tool_input, tool_input,
tool_response, tool_response,
cwd: cwd || '' cwd: happy_path_error__with_fallback(
'Missing cwd in PostToolUse hook input',
{ session_id, tool_name },
cwd || ''
)
}), }),
signal: AbortSignal.timeout(HOOK_TIMEOUTS.DEFAULT) signal: AbortSignal.timeout(HOOK_TIMEOUTS.DEFAULT)
}); });

View File

@@ -15,6 +15,7 @@ import { createHookResponse } from './hook-response.js';
import { logger } from '../utils/logger.js'; import { logger } from '../utils/logger.js';
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js'; import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
import { HOOK_TIMEOUTS } from '../shared/hook-constants.js'; import { HOOK_TIMEOUTS } from '../shared/hook-constants.js';
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
export interface StopInput { export interface StopInput {
session_id: string; session_id: string;
@@ -143,8 +144,13 @@ async function summaryHook(input?: StopInput): Promise<void> {
const port = getWorkerPort(); const port = getWorkerPort();
// Extract last user AND assistant messages from transcript // Extract last user AND assistant messages from transcript
const lastUserMessage = extractLastUserMessage(input.transcript_path || ''); const transcriptPath = happy_path_error__with_fallback(
const lastAssistantMessage = extractLastAssistantMessage(input.transcript_path || ''); 'Missing transcript_path in Stop hook input',
{ session_id },
input.transcript_path || ''
);
const lastUserMessage = extractLastUserMessage(transcriptPath);
const lastAssistantMessage = extractLastAssistantMessage(transcriptPath);
logger.dataIn('HOOK', 'Stop: Requesting summary', { logger.dataIn('HOOK', 'Stop: Requesting summary', {
workerPort: port, workerPort: port,

View File

@@ -3,6 +3,8 @@
* Generates prompts for the Claude Agent SDK memory worker * Generates prompts for the Claude Agent SDK memory worker
*/ */
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
export interface Observation { export interface Observation {
id: number; id: number;
tool_name: string; tool_name: string;
@@ -175,7 +177,11 @@ export function buildObservationPrompt(obs: Observation): string {
* Build prompt to generate progress summary * Build prompt to generate progress summary
*/ */
export function buildSummaryPrompt(session: SDKSession): string { export function buildSummaryPrompt(session: SDKSession): string {
const lastAssistantMessage = session.last_assistant_message || ''; const lastAssistantMessage = happy_path_error__with_fallback(
'Missing last_assistant_message in session for summary prompt',
session,
session.last_assistant_message || ''
);
return `PROGRESS SUMMARY CHECKPOINT return `PROGRESS SUMMARY CHECKPOINT
=========================== ===========================

View File

@@ -14,7 +14,7 @@ import {
} from '@modelcontextprotocol/sdk/types.js'; } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod'; import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema'; import { zodToJsonSchema } from 'zod-to-json-schema';
import { silentDebug } from '../utils/silent-debug.js'; import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
import { getWorkerPort } from '../shared/worker-utils.js'; import { getWorkerPort } from '../shared/worker-utils.js';
/** /**
@@ -50,7 +50,7 @@ async function callWorkerAPI(
endpoint: string, endpoint: string,
params: Record<string, any> params: Record<string, any>
): Promise<{ content: Array<{ type: 'text'; text: string }>; isError?: boolean }> { ): Promise<{ content: Array<{ type: 'text'; text: string }>; isError?: boolean }> {
silentDebug('[mcp-server] → Worker API', { endpoint, params }); happy_path_error__with_fallback('[mcp-server] → Worker API', { endpoint, params });
try { try {
const searchParams = new URLSearchParams(); const searchParams = new URLSearchParams();
@@ -72,12 +72,12 @@ async function callWorkerAPI(
const data = await response.json() as { content: Array<{ type: 'text'; text: string }>; isError?: boolean }; const data = await response.json() as { content: Array<{ type: 'text'; text: string }>; isError?: boolean };
silentDebug('[mcp-server] ← Worker API success', { endpoint }); happy_path_error__with_fallback('[mcp-server] ← Worker API success', { endpoint });
// Worker returns { content: [...] } format directly // Worker returns { content: [...] } format directly
return data; return data;
} catch (error: any) { } catch (error: any) {
silentDebug('[mcp-server] ← Worker API error', { endpoint, error: error.message }); happy_path_error__with_fallback('[mcp-server] ← Worker API error', { endpoint, error: error.message });
return { return {
content: [{ content: [{
type: 'text' as const, type: 'text' as const,
@@ -412,7 +412,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
// Cleanup function // Cleanup function
async function cleanup() { async function cleanup() {
silentDebug('[mcp-server] Shutting down...'); happy_path_error__with_fallback('[mcp-server] Shutting down...');
process.exit(0); process.exit(0);
} }
@@ -425,22 +425,22 @@ async function main() {
// Start the MCP server // Start the MCP server
const transport = new StdioServerTransport(); const transport = new StdioServerTransport();
await server.connect(transport); await server.connect(transport);
silentDebug('[mcp-server] Claude-mem search server started'); happy_path_error__with_fallback('[mcp-server] Claude-mem search server started');
// Check Worker availability in background // Check Worker availability in background
setTimeout(async () => { setTimeout(async () => {
const workerAvailable = await verifyWorkerConnection(); const workerAvailable = await verifyWorkerConnection();
if (!workerAvailable) { if (!workerAvailable) {
silentDebug('[mcp-server] WARNING: Worker not available at', WORKER_BASE_URL); happy_path_error__with_fallback('[mcp-server] WARNING: Worker not available at', WORKER_BASE_URL);
silentDebug('[mcp-server] Tools will fail until Worker is started'); happy_path_error__with_fallback('[mcp-server] Tools will fail until Worker is started');
silentDebug('[mcp-server] Start Worker with: npm run worker:restart'); happy_path_error__with_fallback('[mcp-server] Start Worker with: npm run worker:restart');
} else { } else {
silentDebug('[mcp-server] Worker available at', WORKER_BASE_URL); happy_path_error__with_fallback('[mcp-server] Worker available at', WORKER_BASE_URL);
} }
}, 0); }, 0);
} }
main().catch((error) => { main().catch((error) => {
silentDebug('[mcp-server] Fatal error:', error); happy_path_error__with_fallback('[mcp-server] Fatal error:', error);
process.exit(1); process.exit(1);
}); });

View File

@@ -15,6 +15,7 @@ import { SessionStore } from '../sqlite/SessionStore.js';
import { logger } from '../../utils/logger.js'; import { logger } from '../../utils/logger.js';
import { SettingsDefaultsManager } from '../worker/settings/SettingsDefaultsManager.js'; import { SettingsDefaultsManager } from '../worker/settings/SettingsDefaultsManager.js';
import { USER_SETTINGS_PATH } from '../../shared/paths.js'; import { USER_SETTINGS_PATH } from '../../shared/paths.js';
import { happy_path_error__with_fallback } from '../../utils/silent-debug.js';
import path from 'path'; import path from 'path';
import os from 'os'; import os from 'os';
@@ -766,7 +767,11 @@ export class ChromaSync {
arguments: arguments_obj arguments: arguments_obj
}); });
const resultText = result.content[0]?.text || ''; const resultText = happy_path_error__with_fallback(
'Missing text in MCP chroma_query_documents result',
{ project: this.project, query_text: query },
result.content[0]?.text || ''
);
// Parse JSON response // Parse JSON response
let parsed: any; let parsed: any;

View File

@@ -14,7 +14,7 @@ import path from 'path';
import { DatabaseManager } from './DatabaseManager.js'; import { DatabaseManager } from './DatabaseManager.js';
import { SessionManager } from './SessionManager.js'; import { SessionManager } from './SessionManager.js';
import { logger } from '../../utils/logger.js'; import { logger } from '../../utils/logger.js';
import { silentDebug } from '../../utils/silent-debug.js'; import { happy_path_error__with_fallback } from '../../utils/silent-debug.js';
import { parseObservations, parseSummary } from '../../sdk/parser.js'; import { parseObservations, parseSummary } from '../../sdk/parser.js';
import { buildInitPrompt, buildObservationPrompt, buildSummaryPrompt, buildContinuationPrompt } from '../../sdk/prompts.js'; import { buildInitPrompt, buildObservationPrompt, buildSummaryPrompt, buildContinuationPrompt } from '../../sdk/prompts.js';
import { SettingsDefaultsManager } from './settings/SettingsDefaultsManager.js'; import { SettingsDefaultsManager } from './settings/SettingsDefaultsManager.js';
@@ -233,8 +233,16 @@ export class SDKAgent {
sdk_session_id: session.sdkSessionId, sdk_session_id: session.sdkSessionId,
project: session.project, project: session.project,
user_prompt: session.userPrompt, user_prompt: session.userPrompt,
last_user_message: message.last_user_message || '', last_user_message: happy_path_error__with_fallback(
last_assistant_message: message.last_assistant_message || '' 'Missing last_user_message for summary in SDKAgent',
{ sessionDbId: session.sessionDbId, sdkSessionId: session.sdkSessionId },
message.last_user_message || ''
),
last_assistant_message: happy_path_error__with_fallback(
'Missing last_assistant_message for summary in SDKAgent',
{ sessionDbId: session.sessionDbId, sdkSessionId: session.sdkSessionId },
message.last_assistant_message || ''
)
}) })
}, },
session_id: session.claudeSessionId, session_id: session.claudeSessionId,
@@ -268,16 +276,16 @@ export class SDKAgent {
sessionId: session.sessionDbId, sessionId: session.sessionDbId,
obsId, obsId,
type: obs.type, type: obs.type,
title: obs.title || silentDebug('obs.title is null', { obsId, type: obs.type }, '(untitled)'), title: obs.title || happy_path_error__with_fallback('obs.title is null', { obsId, type: obs.type }, '(untitled)'),
filesRead: obs.files_read?.length ?? (silentDebug('obs.files_read is null/undefined', { obsId }), 0), filesRead: obs.files_read?.length ?? (happy_path_error__with_fallback('obs.files_read is null/undefined', { obsId }), 0),
filesModified: obs.files_modified?.length ?? (silentDebug('obs.files_modified is null/undefined', { obsId }), 0), filesModified: obs.files_modified?.length ?? (happy_path_error__with_fallback('obs.files_modified is null/undefined', { obsId }), 0),
concepts: obs.concepts?.length ?? (silentDebug('obs.concepts is null/undefined', { obsId }), 0) concepts: obs.concepts?.length ?? (happy_path_error__with_fallback('obs.concepts is null/undefined', { obsId }), 0)
}); });
// Sync to Chroma with error logging // Sync to Chroma with error logging
const chromaStart = Date.now(); const chromaStart = Date.now();
const obsType = obs.type; const obsType = obs.type;
const obsTitle = obs.title || silentDebug('obs.title is null for Chroma sync', { obsId, type: obs.type }, '(untitled)'); const obsTitle = obs.title || happy_path_error__with_fallback('obs.title is null for Chroma sync', { obsId, type: obs.type }, '(untitled)');
this.dbManager.getChromaSync().syncObservation( this.dbManager.getChromaSync().syncObservation(
obsId, obsId,
session.claudeSessionId, session.claudeSessionId,
@@ -345,14 +353,14 @@ export class SDKAgent {
logger.info('SDK', 'Summary saved', { logger.info('SDK', 'Summary saved', {
sessionId: session.sessionDbId, sessionId: session.sessionDbId,
summaryId, summaryId,
request: summary.request || silentDebug('summary.request is null', { summaryId }, '(no request)'), request: summary.request || happy_path_error__with_fallback('summary.request is null', { summaryId }, '(no request)'),
hasCompleted: !!summary.completed, hasCompleted: !!summary.completed,
hasNextSteps: !!summary.next_steps hasNextSteps: !!summary.next_steps
}); });
// Sync to Chroma with error logging // Sync to Chroma with error logging
const chromaStart = Date.now(); const chromaStart = Date.now();
const summaryRequest = summary.request || silentDebug('summary.request is null for Chroma sync', { summaryId }, '(no request)'); const summaryRequest = summary.request || happy_path_error__with_fallback('summary.request is null for Chroma sync', { summaryId }, '(no request)');
this.dbManager.getChromaSync().syncSummary( this.dbManager.getChromaSync().syncSummary(
summaryId, summaryId,
session.claudeSessionId, session.claudeSessionId,

View File

@@ -13,7 +13,7 @@ import { ChromaSync } from '../sync/ChromaSync.js';
import { FormattingService } from './FormattingService.js'; import { FormattingService } from './FormattingService.js';
import { TimelineService, TimelineItem } from './TimelineService.js'; import { TimelineService, TimelineItem } from './TimelineService.js';
import { ObservationSearchResult, SessionSummarySearchResult, UserPromptSearchResult } from '../sqlite/types.js'; import { ObservationSearchResult, SessionSummarySearchResult, UserPromptSearchResult } from '../sqlite/types.js';
import { silentDebug } from '../../utils/silent-debug.js'; import { happy_path_error__with_fallback } from '../../utils/silent-debug.js';
const COLLECTION_NAME = 'cm__claude-mem'; const COLLECTION_NAME = 'cm__claude-mem';
@@ -97,7 +97,7 @@ export class SearchManager {
// PATH 1: FILTER-ONLY (no query text) - Skip Chroma/FTS5, use direct SQLite filtering // PATH 1: FILTER-ONLY (no query text) - Skip Chroma/FTS5, use direct SQLite filtering
// This path enables date filtering which Chroma cannot do (requires direct SQLite access) // This path enables date filtering which Chroma cannot do (requires direct SQLite access)
if (!query) { if (!query) {
silentDebug(`[mcp-server] Filter-only query (no query text), using direct SQLite filtering (enables date filters)`); happy_path_error__with_fallback(`[mcp-server] Filter-only query (no query text), using direct SQLite filtering (enables date filters)`);
const obsOptions = { ...options, type: obs_type, concepts, files }; const obsOptions = { ...options, type: obs_type, concepts, files };
if (searchObservations) { if (searchObservations) {
observations = this.sessionSearch.searchObservations(undefined, obsOptions); observations = this.sessionSearch.searchObservations(undefined, obsOptions);
@@ -113,7 +113,7 @@ export class SearchManager {
else if (this.chromaSync) { else if (this.chromaSync) {
let chromaSucceeded = false; let chromaSucceeded = false;
try { try {
silentDebug(`[mcp-server] Using ChromaDB semantic search (type filter: ${type || 'all'})`); happy_path_error__with_fallback(`[mcp-server] Using ChromaDB semantic search (type filter: ${type || 'all'})`);
// Build Chroma where filter for doc_type // Build Chroma where filter for doc_type
let whereFilter: Record<string, any> | undefined; let whereFilter: Record<string, any> | undefined;
@@ -128,7 +128,7 @@ export class SearchManager {
// Step 1: Chroma semantic search with optional type filter // Step 1: Chroma semantic search with optional type filter
const chromaResults = await this.queryChroma(query, 100, whereFilter); const chromaResults = await this.queryChroma(query, 100, whereFilter);
chromaSucceeded = true; // Chroma didn't throw error chromaSucceeded = true; // Chroma didn't throw error
silentDebug(`[mcp-server] ChromaDB returned ${chromaResults.ids.length} semantic matches`); happy_path_error__with_fallback(`[mcp-server] ChromaDB returned ${chromaResults.ids.length} semantic matches`);
if (chromaResults.ids.length > 0) { if (chromaResults.ids.length > 0) {
// Step 2: Filter by recency (90 days) // Step 2: Filter by recency (90 days)
@@ -139,7 +139,7 @@ export class SearchManager {
isRecent: meta && meta.created_at_epoch > ninetyDaysAgo isRecent: meta && meta.created_at_epoch > ninetyDaysAgo
})).filter(item => item.isRecent); })).filter(item => item.isRecent);
silentDebug(`[mcp-server] ${recentMetadata.length} results within 90-day window`); happy_path_error__with_fallback(`[mcp-server] ${recentMetadata.length} results within 90-day window`);
// Step 3: Categorize IDs by document type // Step 3: Categorize IDs by document type
const obsIds: number[] = []; const obsIds: number[] = [];
@@ -157,7 +157,7 @@ export class SearchManager {
} }
} }
silentDebug(`[mcp-server] Categorized: ${obsIds.length} obs, ${sessionIds.length} sessions, ${promptIds.length} prompts`); happy_path_error__with_fallback(`[mcp-server] Categorized: ${obsIds.length} obs, ${sessionIds.length} sessions, ${promptIds.length} prompts`);
// Step 4: Hydrate from SQLite with additional filters // Step 4: Hydrate from SQLite with additional filters
if (obsIds.length > 0) { if (obsIds.length > 0) {
@@ -172,14 +172,14 @@ export class SearchManager {
prompts = this.sessionStore.getUserPromptsByIds(promptIds, { orderBy: 'date_desc', limit: options.limit }); prompts = this.sessionStore.getUserPromptsByIds(promptIds, { orderBy: 'date_desc', limit: options.limit });
} }
silentDebug(`[mcp-server] Hydrated ${observations.length} obs, ${sessions.length} sessions, ${prompts.length} prompts from SQLite`); happy_path_error__with_fallback(`[mcp-server] Hydrated ${observations.length} obs, ${sessions.length} sessions, ${prompts.length} prompts from SQLite`);
} else { } else {
// Chroma returned 0 results - this is the correct answer, don't fall back to FTS5 // Chroma returned 0 results - this is the correct answer, don't fall back to FTS5
silentDebug(`[mcp-server] ChromaDB found no matches (this is final - NOT falling back to FTS5)`); happy_path_error__with_fallback(`[mcp-server] ChromaDB found no matches (this is final - NOT falling back to FTS5)`);
} }
} catch (chromaError: any) { } catch (chromaError: any) {
silentDebug('[mcp-server] ChromaDB failed - returning empty results (FTS5 fallback removed):', chromaError.message); happy_path_error__with_fallback('[mcp-server] ChromaDB failed - returning empty results (FTS5 fallback removed):', chromaError.message);
silentDebug('[mcp-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/'); happy_path_error__with_fallback('[mcp-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/');
// Return empty results - no fallback // Return empty results - no fallback
observations = []; observations = [];
sessions = []; sessions = [];
@@ -188,8 +188,8 @@ export class SearchManager {
} }
// ChromaDB not initialized - return empty results (no fallback) // ChromaDB not initialized - return empty results (no fallback)
else { else {
silentDebug(`[mcp-server] ChromaDB not initialized - returning empty results (FTS5 fallback removed)`); happy_path_error__with_fallback(`[mcp-server] ChromaDB not initialized - returning empty results (FTS5 fallback removed)`);
silentDebug(`[mcp-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/`); happy_path_error__with_fallback(`[mcp-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/`);
observations = []; observations = [];
sessions = []; sessions = [];
prompts = []; prompts = [];
@@ -312,9 +312,9 @@ export class SearchManager {
if (this.chromaSync) { if (this.chromaSync) {
try { try {
silentDebug('[mcp-server] Using hybrid semantic search for timeline query'); happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for timeline query');
const chromaResults = await this.queryChroma(query, 100); const chromaResults = await this.queryChroma(query, 100);
silentDebug(`[mcp-server] Chroma returned ${chromaResults?.ids?.length ?? 0} semantic matches`); happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults?.ids?.length ?? 0} semantic matches`);
if (chromaResults?.ids && chromaResults.ids.length > 0) { if (chromaResults?.ids && chromaResults.ids.length > 0) {
const ninetyDaysAgo = Date.now() - (90 * 24 * 60 * 60 * 1000); const ninetyDaysAgo = Date.now() - (90 * 24 * 60 * 60 * 1000);
@@ -328,7 +328,7 @@ export class SearchManager {
} }
} }
} catch (chromaError: any) { } catch (chromaError: any) {
silentDebug('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message); happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
} }
} }
@@ -345,7 +345,7 @@ export class SearchManager {
const topResult = results[0]; const topResult = results[0];
anchorId = topResult.id; anchorId = topResult.id;
anchorEpoch = topResult.created_at_epoch; anchorEpoch = topResult.created_at_epoch;
silentDebug(`[mcp-server] Query mode: Using observation #${topResult.id} as timeline anchor`); happy_path_error__with_fallback(`[mcp-server] Query mode: Using observation #${topResult.id} as timeline anchor`);
timelineData = this.sessionStore.getTimelineAroundObservation(topResult.id, topResult.created_at_epoch, depth_before, depth_after, project); timelineData = this.sessionStore.getTimelineAroundObservation(topResult.id, topResult.created_at_epoch, depth_before, depth_after, project);
} }
// MODE 2: Anchor-based timeline // MODE 2: Anchor-based timeline
@@ -621,7 +621,7 @@ export class SearchManager {
try { try {
if (query) { if (query) {
// Semantic search filtered to decision type // Semantic search filtered to decision type
silentDebug('[mcp-server] Using Chroma semantic search with type=decision filter'); happy_path_error__with_fallback('[mcp-server] Using Chroma semantic search with type=decision filter');
const chromaResults = await this.queryChroma(query, Math.min((filters.limit || 20) * 2, 100), { type: 'decision' }); const chromaResults = await this.queryChroma(query, Math.min((filters.limit || 20) * 2, 100), { type: 'decision' });
const obsIds = chromaResults.ids; const obsIds = chromaResults.ids;
@@ -632,7 +632,7 @@ export class SearchManager {
} }
} else { } else {
// No query: get all decisions, rank by "decision" keyword // No query: get all decisions, rank by "decision" keyword
silentDebug('[mcp-server] Using metadata-first + semantic ranking for decisions'); happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for decisions');
const metadataResults = this.sessionSearch.findByType('decision', filters); const metadataResults = this.sessionSearch.findByType('decision', filters);
if (metadataResults.length > 0) { if (metadataResults.length > 0) {
@@ -653,7 +653,7 @@ export class SearchManager {
} }
} }
} catch (chromaError: any) { } catch (chromaError: any) {
silentDebug('[mcp-server] Chroma search failed, using SQLite fallback:', chromaError.message); happy_path_error__with_fallback('[mcp-server] Chroma search failed, using SQLite fallback:', chromaError.message);
} }
} }
@@ -709,7 +709,7 @@ export class SearchManager {
// Search for change-type observations and change-related concepts // Search for change-type observations and change-related concepts
if (this.chromaSync) { if (this.chromaSync) {
try { try {
silentDebug('[mcp-server] Using hybrid search for change-related observations'); happy_path_error__with_fallback('[mcp-server] Using hybrid search for change-related observations');
// Get all observations with type="change" or concepts containing change // Get all observations with type="change" or concepts containing change
const typeResults = this.sessionSearch.findByType('change', filters); const typeResults = this.sessionSearch.findByType('change', filters);
@@ -737,7 +737,7 @@ export class SearchManager {
} }
} }
} catch (chromaError: any) { } catch (chromaError: any) {
silentDebug('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message); happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
} }
} }
@@ -807,7 +807,7 @@ export class SearchManager {
// Search for how-it-works concept observations // Search for how-it-works concept observations
if (this.chromaSync) { if (this.chromaSync) {
try { try {
silentDebug('[mcp-server] Using metadata-first + semantic ranking for how-it-works'); happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for how-it-works');
const metadataResults = this.sessionSearch.findByConcept('how-it-works', filters); const metadataResults = this.sessionSearch.findByConcept('how-it-works', filters);
if (metadataResults.length > 0) { if (metadataResults.length > 0) {
@@ -827,7 +827,7 @@ export class SearchManager {
} }
} }
} catch (chromaError: any) { } catch (chromaError: any) {
silentDebug('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message); happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
} }
} }
@@ -883,11 +883,11 @@ export class SearchManager {
// Vector-first search via ChromaDB // Vector-first search via ChromaDB
if (this.chromaSync) { if (this.chromaSync) {
try { try {
silentDebug('[mcp-server] Using hybrid semantic search (Chroma + SQLite)'); happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search (Chroma + SQLite)');
// Step 1: Chroma semantic search (top 100) // Step 1: Chroma semantic search (top 100)
const chromaResults = await this.queryChroma(query, 100); const chromaResults = await this.queryChroma(query, 100);
silentDebug(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`); happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
if (chromaResults.ids.length > 0) { if (chromaResults.ids.length > 0) {
// Step 2: Filter by recency (90 days) // Step 2: Filter by recency (90 days)
@@ -897,17 +897,17 @@ export class SearchManager {
return meta && meta.created_at_epoch > ninetyDaysAgo; return meta && meta.created_at_epoch > ninetyDaysAgo;
}); });
silentDebug(`[mcp-server] ${recentIds.length} results within 90-day window`); happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
// Step 3: Hydrate from SQLite in temporal order // Step 3: Hydrate from SQLite in temporal order
if (recentIds.length > 0) { if (recentIds.length > 0) {
const limit = options.limit || 20; const limit = options.limit || 20;
results = this.sessionStore.getObservationsByIds(recentIds, { orderBy: 'date_desc', limit }); results = this.sessionStore.getObservationsByIds(recentIds, { orderBy: 'date_desc', limit });
silentDebug(`[mcp-server] Hydrated ${results.length} observations from SQLite`); happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} observations from SQLite`);
} }
} }
} catch (chromaError: any) { } catch (chromaError: any) {
silentDebug('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message); happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
} }
} }
@@ -960,11 +960,11 @@ export class SearchManager {
// Vector-first search via ChromaDB // Vector-first search via ChromaDB
if (this.chromaSync) { if (this.chromaSync) {
try { try {
silentDebug('[mcp-server] Using hybrid semantic search for sessions'); happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for sessions');
// Step 1: Chroma semantic search (top 100) // Step 1: Chroma semantic search (top 100)
const chromaResults = await this.queryChroma(query, 100, { doc_type: 'session_summary' }); const chromaResults = await this.queryChroma(query, 100, { doc_type: 'session_summary' });
silentDebug(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`); happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
if (chromaResults.ids.length > 0) { if (chromaResults.ids.length > 0) {
// Step 2: Filter by recency (90 days) // Step 2: Filter by recency (90 days)
@@ -974,17 +974,17 @@ export class SearchManager {
return meta && meta.created_at_epoch > ninetyDaysAgo; return meta && meta.created_at_epoch > ninetyDaysAgo;
}); });
silentDebug(`[mcp-server] ${recentIds.length} results within 90-day window`); happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
// Step 3: Hydrate from SQLite in temporal order // Step 3: Hydrate from SQLite in temporal order
if (recentIds.length > 0) { if (recentIds.length > 0) {
const limit = options.limit || 20; const limit = options.limit || 20;
results = this.sessionStore.getSessionSummariesByIds(recentIds, { orderBy: 'date_desc', limit }); results = this.sessionStore.getSessionSummariesByIds(recentIds, { orderBy: 'date_desc', limit });
silentDebug(`[mcp-server] Hydrated ${results.length} sessions from SQLite`); happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} sessions from SQLite`);
} }
} }
} catch (chromaError: any) { } catch (chromaError: any) {
silentDebug('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message); happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
} }
} }
@@ -1037,11 +1037,11 @@ export class SearchManager {
// Vector-first search via ChromaDB // Vector-first search via ChromaDB
if (this.chromaSync) { if (this.chromaSync) {
try { try {
silentDebug('[mcp-server] Using hybrid semantic search for user prompts'); happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for user prompts');
// Step 1: Chroma semantic search (top 100) // Step 1: Chroma semantic search (top 100)
const chromaResults = await this.queryChroma(query, 100, { doc_type: 'user_prompt' }); const chromaResults = await this.queryChroma(query, 100, { doc_type: 'user_prompt' });
silentDebug(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`); happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
if (chromaResults.ids.length > 0) { if (chromaResults.ids.length > 0) {
// Step 2: Filter by recency (90 days) // Step 2: Filter by recency (90 days)
@@ -1051,17 +1051,17 @@ export class SearchManager {
return meta && meta.created_at_epoch > ninetyDaysAgo; return meta && meta.created_at_epoch > ninetyDaysAgo;
}); });
silentDebug(`[mcp-server] ${recentIds.length} results within 90-day window`); happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
// Step 3: Hydrate from SQLite in temporal order // Step 3: Hydrate from SQLite in temporal order
if (recentIds.length > 0) { if (recentIds.length > 0) {
const limit = options.limit || 20; const limit = options.limit || 20;
results = this.sessionStore.getUserPromptsByIds(recentIds, { orderBy: 'date_desc', limit }); results = this.sessionStore.getUserPromptsByIds(recentIds, { orderBy: 'date_desc', limit });
silentDebug(`[mcp-server] Hydrated ${results.length} user prompts from SQLite`); happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} user prompts from SQLite`);
} }
} }
} catch (chromaError: any) { } catch (chromaError: any) {
silentDebug('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message); happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
} }
} }
@@ -1114,11 +1114,11 @@ export class SearchManager {
// Metadata-first, semantic-enhanced search // Metadata-first, semantic-enhanced search
if (this.chromaSync) { if (this.chromaSync) {
try { try {
silentDebug('[mcp-server] Using metadata-first + semantic ranking for concept search'); happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for concept search');
// Step 1: SQLite metadata filter (get all IDs with this concept) // Step 1: SQLite metadata filter (get all IDs with this concept)
const metadataResults = this.sessionSearch.findByConcept(concept, filters); const metadataResults = this.sessionSearch.findByConcept(concept, filters);
silentDebug(`[mcp-server] Found ${metadataResults.length} observations with concept "${concept}"`); happy_path_error__with_fallback(`[mcp-server] Found ${metadataResults.length} observations with concept "${concept}"`);
if (metadataResults.length > 0) { if (metadataResults.length > 0) {
// Step 2: Chroma semantic ranking (rank by relevance to concept) // Step 2: Chroma semantic ranking (rank by relevance to concept)
@@ -1133,7 +1133,7 @@ export class SearchManager {
} }
} }
silentDebug(`[mcp-server] Chroma ranked ${rankedIds.length} results by semantic relevance`); happy_path_error__with_fallback(`[mcp-server] Chroma ranked ${rankedIds.length} results by semantic relevance`);
// Step 3: Hydrate in semantic rank order // Step 3: Hydrate in semantic rank order
if (rankedIds.length > 0) { if (rankedIds.length > 0) {
@@ -1143,14 +1143,14 @@ export class SearchManager {
} }
} }
} catch (chromaError: any) { } catch (chromaError: any) {
silentDebug('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message); happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
// Fall through to SQLite fallback // Fall through to SQLite fallback
} }
} }
// Fall back to SQLite-only if Chroma unavailable or failed // Fall back to SQLite-only if Chroma unavailable or failed
if (results.length === 0) { if (results.length === 0) {
silentDebug('[mcp-server] Using SQLite-only concept search'); happy_path_error__with_fallback('[mcp-server] Using SQLite-only concept search');
results = this.sessionSearch.findByConcept(concept, filters); results = this.sessionSearch.findByConcept(concept, filters);
} }
@@ -1204,11 +1204,11 @@ export class SearchManager {
// Metadata-first, semantic-enhanced search for observations // Metadata-first, semantic-enhanced search for observations
if (this.chromaSync) { if (this.chromaSync) {
try { try {
silentDebug('[mcp-server] Using metadata-first + semantic ranking for file search'); happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for file search');
// Step 1: SQLite metadata filter (get all results with this file) // Step 1: SQLite metadata filter (get all results with this file)
const metadataResults = this.sessionSearch.findByFile(filePath, filters); const metadataResults = this.sessionSearch.findByFile(filePath, filters);
silentDebug(`[mcp-server] Found ${metadataResults.observations.length} observations, ${metadataResults.sessions.length} sessions for file "${filePath}"`); happy_path_error__with_fallback(`[mcp-server] Found ${metadataResults.observations.length} observations, ${metadataResults.sessions.length} sessions for file "${filePath}"`);
// Sessions: Keep as-is (already summarized, no semantic ranking needed) // Sessions: Keep as-is (already summarized, no semantic ranking needed)
sessions = metadataResults.sessions; sessions = metadataResults.sessions;
@@ -1227,7 +1227,7 @@ export class SearchManager {
} }
} }
silentDebug(`[mcp-server] Chroma ranked ${rankedIds.length} observations by semantic relevance`); happy_path_error__with_fallback(`[mcp-server] Chroma ranked ${rankedIds.length} observations by semantic relevance`);
// Step 3: Hydrate in semantic rank order // Step 3: Hydrate in semantic rank order
if (rankedIds.length > 0) { if (rankedIds.length > 0) {
@@ -1237,14 +1237,14 @@ export class SearchManager {
} }
} }
} catch (chromaError: any) { } catch (chromaError: any) {
silentDebug('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message); happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
// Fall through to SQLite fallback // Fall through to SQLite fallback
} }
} }
// Fall back to SQLite-only if Chroma unavailable or failed // Fall back to SQLite-only if Chroma unavailable or failed
if (observations.length === 0 && sessions.length === 0) { if (observations.length === 0 && sessions.length === 0) {
silentDebug('[mcp-server] Using SQLite-only file search'); happy_path_error__with_fallback('[mcp-server] Using SQLite-only file search');
const results = this.sessionSearch.findByFile(filePath, filters); const results = this.sessionSearch.findByFile(filePath, filters);
observations = results.observations; observations = results.observations;
sessions = results.sessions; sessions = results.sessions;
@@ -1323,11 +1323,11 @@ export class SearchManager {
// Metadata-first, semantic-enhanced search // Metadata-first, semantic-enhanced search
if (this.chromaSync) { if (this.chromaSync) {
try { try {
silentDebug('[mcp-server] Using metadata-first + semantic ranking for type search'); happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for type search');
// Step 1: SQLite metadata filter (get all IDs with this type) // Step 1: SQLite metadata filter (get all IDs with this type)
const metadataResults = this.sessionSearch.findByType(type, filters); const metadataResults = this.sessionSearch.findByType(type, filters);
silentDebug(`[mcp-server] Found ${metadataResults.length} observations with type "${typeStr}"`); happy_path_error__with_fallback(`[mcp-server] Found ${metadataResults.length} observations with type "${typeStr}"`);
if (metadataResults.length > 0) { if (metadataResults.length > 0) {
// Step 2: Chroma semantic ranking (rank by relevance to type) // Step 2: Chroma semantic ranking (rank by relevance to type)
@@ -1342,7 +1342,7 @@ export class SearchManager {
} }
} }
silentDebug(`[mcp-server] Chroma ranked ${rankedIds.length} results by semantic relevance`); happy_path_error__with_fallback(`[mcp-server] Chroma ranked ${rankedIds.length} results by semantic relevance`);
// Step 3: Hydrate in semantic rank order // Step 3: Hydrate in semantic rank order
if (rankedIds.length > 0) { if (rankedIds.length > 0) {
@@ -1352,14 +1352,14 @@ export class SearchManager {
} }
} }
} catch (chromaError: any) { } catch (chromaError: any) {
silentDebug('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message); happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
// Fall through to SQLite fallback // Fall through to SQLite fallback
} }
} }
// Fall back to SQLite-only if Chroma unavailable or failed // Fall back to SQLite-only if Chroma unavailable or failed
if (results.length === 0) { if (results.length === 0) {
silentDebug('[mcp-server] Using SQLite-only type search'); happy_path_error__with_fallback('[mcp-server] Using SQLite-only type search');
results = this.sessionSearch.findByType(type, filters); results = this.sessionSearch.findByType(type, filters);
} }
@@ -1815,9 +1815,9 @@ export class SearchManager {
// Use hybrid search if available // Use hybrid search if available
if (this.chromaSync) { if (this.chromaSync) {
try { try {
silentDebug('[mcp-server] Using hybrid semantic search for timeline query'); happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for timeline query');
const chromaResults = await this.queryChroma(query, 100); const chromaResults = await this.queryChroma(query, 100);
silentDebug(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`); happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
if (chromaResults.ids.length > 0) { if (chromaResults.ids.length > 0) {
// Filter by recency (90 days) // Filter by recency (90 days)
@@ -1827,15 +1827,15 @@ export class SearchManager {
return meta && meta.created_at_epoch > ninetyDaysAgo; return meta && meta.created_at_epoch > ninetyDaysAgo;
}); });
silentDebug(`[mcp-server] ${recentIds.length} results within 90-day window`); happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
if (recentIds.length > 0) { if (recentIds.length > 0) {
results = this.sessionStore.getObservationsByIds(recentIds, { orderBy: 'date_desc', limit: mode === 'auto' ? 1 : limit }); results = this.sessionStore.getObservationsByIds(recentIds, { orderBy: 'date_desc', limit: mode === 'auto' ? 1 : limit });
silentDebug(`[mcp-server] Hydrated ${results.length} observations from SQLite`); happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} observations from SQLite`);
} }
} }
} catch (chromaError: any) { } catch (chromaError: any) {
silentDebug('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message); happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
} }
} }
@@ -1886,7 +1886,7 @@ export class SearchManager {
} else { } else {
// Auto mode: Use top result as timeline anchor // Auto mode: Use top result as timeline anchor
const topResult = results[0]; const topResult = results[0];
silentDebug(`[mcp-server] Auto mode: Using observation #${topResult.id} as timeline anchor`); happy_path_error__with_fallback(`[mcp-server] Auto mode: Using observation #${topResult.id} as timeline anchor`);
// Get timeline around this observation // Get timeline around this observation
const timelineData = this.sessionStore.getTimelineAroundObservation( const timelineData = this.sessionStore.getTimelineAroundObservation(

View File

@@ -11,7 +11,7 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { DatabaseManager } from './DatabaseManager.js'; import { DatabaseManager } from './DatabaseManager.js';
import { logger } from '../../utils/logger.js'; import { logger } from '../../utils/logger.js';
import { silentDebug } from '../../utils/silent-debug.js'; import { happy_path_error__with_fallback } from '../../utils/silent-debug.js';
import type { ActiveSession, PendingMessage, ObservationData } from '../worker-types.js'; import type { ActiveSession, PendingMessage, ObservationData } from '../worker-types.js';
export class SessionManager { export class SessionManager {
@@ -43,7 +43,7 @@ export class SessionManager {
// in the database but the in-memory session still has the stale empty value // in the database but the in-memory session still has the stale empty value
const dbSession = this.dbManager.getSessionById(sessionDbId); const dbSession = this.dbManager.getSessionById(sessionDbId);
if (dbSession.project && dbSession.project !== session.project) { if (dbSession.project && dbSession.project !== session.project) {
silentDebug('[SessionManager] Updating project from database', { happy_path_error__with_fallback('[SessionManager] Updating project from database', {
sessionDbId, sessionDbId,
oldProject: session.project, oldProject: session.project,
newProject: dbSession.project newProject: dbSession.project
@@ -53,7 +53,7 @@ export class SessionManager {
// Update userPrompt for continuation prompts // Update userPrompt for continuation prompts
if (currentUserPrompt) { if (currentUserPrompt) {
silentDebug('[SessionManager] Updating userPrompt for continuation', { happy_path_error__with_fallback('[SessionManager] Updating userPrompt for continuation', {
sessionDbId, sessionDbId,
promptNumber, promptNumber,
oldPrompt: session.userPrompt.substring(0, 80), oldPrompt: session.userPrompt.substring(0, 80),
@@ -62,7 +62,7 @@ export class SessionManager {
session.userPrompt = currentUserPrompt; session.userPrompt = currentUserPrompt;
session.lastPromptNumber = promptNumber || session.lastPromptNumber; session.lastPromptNumber = promptNumber || session.lastPromptNumber;
} else { } else {
silentDebug('[SessionManager] No currentUserPrompt provided for existing session', { happy_path_error__with_fallback('[SessionManager] No currentUserPrompt provided for existing session', {
sessionDbId, sessionDbId,
promptNumber, promptNumber,
usingCachedPrompt: session.userPrompt.substring(0, 80) usingCachedPrompt: session.userPrompt.substring(0, 80)
@@ -78,13 +78,13 @@ export class SessionManager {
const userPrompt = currentUserPrompt || dbSession.user_prompt; const userPrompt = currentUserPrompt || dbSession.user_prompt;
if (!currentUserPrompt) { if (!currentUserPrompt) {
silentDebug('[SessionManager] No currentUserPrompt provided for new session, using database', { happy_path_error__with_fallback('[SessionManager] No currentUserPrompt provided for new session, using database', {
sessionDbId, sessionDbId,
promptNumber, promptNumber,
dbPrompt: dbSession.user_prompt.substring(0, 80) dbPrompt: dbSession.user_prompt.substring(0, 80)
}); });
} else { } else {
silentDebug('[SessionManager] Initializing session with fresh userPrompt', { happy_path_error__with_fallback('[SessionManager] Initializing session with fresh userPrompt', {
sessionDbId, sessionDbId,
promptNumber, promptNumber,
userPrompt: currentUserPrompt.substring(0, 80) userPrompt: currentUserPrompt.substring(0, 80)

View File

@@ -9,6 +9,7 @@ import express, { Request, Response } from 'express';
import { getWorkerPort } from '../../../../shared/worker-utils.js'; import { getWorkerPort } from '../../../../shared/worker-utils.js';
import { logger } from '../../../../utils/logger.js'; import { logger } from '../../../../utils/logger.js';
import { stripMemoryTagsFromJson, stripMemoryTagsFromPrompt } from '../../../../utils/tag-stripping.js'; import { stripMemoryTagsFromJson, stripMemoryTagsFromPrompt } from '../../../../utils/tag-stripping.js';
import { happy_path_error__with_fallback } from '../../../../utils/silent-debug.js';
import { SessionManager } from '../../SessionManager.js'; import { SessionManager } from '../../SessionManager.js';
import { DatabaseManager } from '../../DatabaseManager.js'; import { DatabaseManager } from '../../DatabaseManager.js';
import { SDKAgent } from '../../SDKAgent.js'; import { SDKAgent } from '../../SDKAgent.js';
@@ -307,7 +308,11 @@ export class SessionRoutes extends BaseRouteHandler {
tool_input: cleanedToolInput, tool_input: cleanedToolInput,
tool_response: cleanedToolResponse, tool_response: cleanedToolResponse,
prompt_number: promptNumber, prompt_number: promptNumber,
cwd: cwd || '' cwd: happy_path_error__with_fallback(
'Missing cwd when queueing observation in SessionRoutes',
{ sessionDbId, tool_name },
cwd || ''
)
}); });
// Ensure SDK agent is running // Ensure SDK agent is running
@@ -353,7 +358,15 @@ export class SessionRoutes extends BaseRouteHandler {
} }
// Queue summarize // Queue summarize
this.sessionManager.queueSummarize(sessionDbId, last_user_message || '', last_assistant_message); this.sessionManager.queueSummarize(
sessionDbId,
happy_path_error__with_fallback(
'Missing last_user_message when queueing summary in SessionRoutes',
{ sessionDbId },
last_user_message || ''
),
last_assistant_message
);
// Ensure SDK agent is running // Ensure SDK agent is running
this.ensureGeneratorRunning(sessionDbId, 'summarize'); this.ensureGeneratorRunning(sessionDbId, 'summarize');

View File

@@ -1,7 +1,7 @@
import { join } from 'path'; import { join } from 'path';
import { homedir } from 'os'; import { homedir } from 'os';
import { existsSync, readFileSync } from 'fs'; import { existsSync, readFileSync } from 'fs';
import { silentDebug } from '../utils/silent-debug.js'; import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
const SETTINGS_PATH = join(homedir(), '.claude-mem', 'settings.json'); const SETTINGS_PATH = join(homedir(), '.claude-mem', 'settings.json');
@@ -25,7 +25,7 @@ export function loadEarlySetting(key: keyof EarlySettings, defaultValue: string)
if (fileValue !== undefined) return fileValue; if (fileValue !== undefined) return fileValue;
} }
} catch (error) { } catch (error) {
silentDebug('Failed to load settings file', { error, settingsPath: SETTINGS_PATH, key }); happy_path_error__with_fallback('Failed to load settings file', { error, settingsPath: SETTINGS_PATH, key });
} }
return process.env[key] || defaultValue; return process.env[key] || defaultValue;

View File

@@ -1,25 +1,27 @@
/** /**
* Silent Debug Logger * Happy Path Error With Fallback
*
* Semantic meaning: "When the happy path fails, this is an error, but we have a fallback."
* *
* NOTE: This utility is to be used like Frank's Red Hot, we put that shit on everything. * NOTE: This utility is to be used like Frank's Red Hot, we put that shit on everything.
* *
* USE THIS INSTEAD OF SILENT FAILURES! * USE THIS INSTEAD OF SILENT FAILURES!
* Stop doing this: `const value = something || '';` * Stop doing this: `const value = something || '';`
* Start doing this: `const value = something || silentDebug('something was undefined');` * Start doing this: `const value = something || happy_path_error__with_fallback('something was undefined');`
* *
* Logs to ~/.claude-mem/silent.log and returns a fallback value. * Logs to ~/.claude-mem/silent.log and returns a fallback value.
* Check logs with `npm run logs:silent` * Check logs with `npm run logs:silent`
* *
* Usage: * Usage:
* import { silentDebug } from '../utils/silent-debug.js'; * import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
* *
* const title = obs.title || silentDebug('obs.title missing', { obs }); * const title = obs.title || happy_path_error__with_fallback('obs.title missing', { obs });
* const name = user.name || silentDebug('user.name missing', { user }, 'Anonymous'); * const name = user.name || happy_path_error__with_fallback('user.name missing', { user }, 'Anonymous');
* *
* try { * try {
* doSomething(); * doSomething();
* } catch (error) { * } catch (error) {
* silentDebug('doSomething failed', { error }); * happy_path_error__with_fallback('doSomething failed', { error });
* } * }
*/ */
@@ -30,13 +32,13 @@ import { join } from 'path';
const LOG_FILE = join(homedir(), '.claude-mem', 'silent.log'); const LOG_FILE = join(homedir(), '.claude-mem', 'silent.log');
/** /**
* Write a debug message to silent.log and return fallback value * Write an error message to silent.log and return fallback value
* @param message - The message to log * @param message - Error message describing what went wrong
* @param data - Optional data to include (will be JSON stringified) * @param data - Optional data to include (will be JSON stringified)
* @param fallback - Value to return (defaults to empty string) * @param fallback - Value to return (defaults to empty string)
* @returns The fallback value (for use in || fallbacks) * @returns The fallback value (for use in || fallbacks)
*/ */
export function silentDebug(message: string, data?: any, fallback: string = ''): string { export function happy_path_error__with_fallback(message: string, data?: any, fallback: string = ''): string {
const timestamp = new Date().toISOString(); const timestamp = new Date().toISOString();
// Capture stack trace to get caller location // Capture stack trace to get caller location
@@ -51,7 +53,7 @@ export function silentDebug(message: string, data?: any, fallback: string = ''):
? `${callerMatch[1].split('/').pop()}:${callerMatch[2]}` ? `${callerMatch[1].split('/').pop()}:${callerMatch[2]}`
: 'unknown'; : 'unknown';
let logLine = `[${timestamp}] [${location}] ${message}`; let logLine = `[${timestamp}] [HAPPY-PATH-ERROR] [${location}] ${message}`;
if (data !== undefined) { if (data !== undefined) {
try { try {
@@ -84,3 +86,9 @@ export function clearSilentLog(): void {
// Ignore errors // Ignore errors
} }
} }
/**
* @deprecated Use happy_path_error__with_fallback instead
* Backward compatibility alias for silentDebug
*/
export const silentDebug = happy_path_error__with_fallback;

View File

@@ -11,7 +11,7 @@
* This keeps the worker service simple and follows one-way data stream. * This keeps the worker service simple and follows one-way data stream.
*/ */
import { silentDebug } from './silent-debug.js'; import { happy_path_error__with_fallback } from './silent-debug.js';
/** /**
* Maximum number of tags allowed in a single content block * Maximum number of tags allowed in a single content block
@@ -41,14 +41,14 @@ function countTags(content: string): number {
*/ */
export function stripMemoryTagsFromJson(content: string): string { export function stripMemoryTagsFromJson(content: string): string {
if (typeof content !== 'string') { if (typeof content !== 'string') {
silentDebug('[tag-stripping] received non-string for JSON context:', { type: typeof content }); happy_path_error__with_fallback('[tag-stripping] received non-string for JSON context:', { type: typeof content });
return '{}'; // Safe default for JSON context return '{}'; // Safe default for JSON context
} }
// ReDoS protection: limit tag count before regex processing // ReDoS protection: limit tag count before regex processing
const tagCount = countTags(content); const tagCount = countTags(content);
if (tagCount > MAX_TAG_COUNT) { if (tagCount > MAX_TAG_COUNT) {
silentDebug('[tag-stripping] tag count exceeds limit, truncating:', { happy_path_error__with_fallback('[tag-stripping] tag count exceeds limit, truncating:', {
tagCount, tagCount,
maxAllowed: MAX_TAG_COUNT, maxAllowed: MAX_TAG_COUNT,
contentLength: content.length contentLength: content.length
@@ -73,14 +73,14 @@ export function stripMemoryTagsFromJson(content: string): string {
*/ */
export function stripMemoryTagsFromPrompt(content: string): string { export function stripMemoryTagsFromPrompt(content: string): string {
if (typeof content !== 'string') { if (typeof content !== 'string') {
silentDebug('[tag-stripping] received non-string for prompt context:', { type: typeof content }); happy_path_error__with_fallback('[tag-stripping] received non-string for prompt context:', { type: typeof content });
return ''; // Safe default for prompt content return ''; // Safe default for prompt content
} }
// ReDoS protection: limit tag count before regex processing // ReDoS protection: limit tag count before regex processing
const tagCount = countTags(content); const tagCount = countTags(content);
if (tagCount > MAX_TAG_COUNT) { if (tagCount > MAX_TAG_COUNT) {
silentDebug('[tag-stripping] tag count exceeds limit, truncating:', { happy_path_error__with_fallback('[tag-stripping] tag count exceeds limit, truncating:', {
tagCount, tagCount,
maxAllowed: MAX_TAG_COUNT, maxAllowed: MAX_TAG_COUNT,
contentLength: content.length contentLength: content.length