mirror of
https://github.com/thedotmack/claude-mem
synced 2026-04-25 17:15:04 +02:00
- 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.
19 lines
9.5 KiB
JavaScript
Executable File
19 lines
9.5 KiB
JavaScript
Executable File
#!/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 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}] [HAPPY-PATH-ERROR] [${E}] ${r}`;if(t!==void 0)try{u+=` ${JSON.stringify(t)}`}catch(_){u+=` [stringify error: ${_}]`}u+=`
|
|
`;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=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):_=" "+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:
|
|
cd ${p}
|
|
npx pm2 start ecosystem.config.cjs
|
|
|
|
If already running, try: npx pm2 restart claude-mem-worker`)}}function st(r){if(!r||!$(r))return"";try{let t=H(r,"utf-8").trim();if(!t)return"";let e=t.split(`
|
|
`);for(let n=e.length-1;n>=0;n--)try{let o=JSON.parse(e[n]);if(o.type==="user"&&o.message?.content){let s=o.message.content;if(typeof s=="string")return s;if(Array.isArray(s))return s.filter(a=>a.type==="text").map(a=>a.text).join(`
|
|
`)}}catch{continue}}catch(t){c.error("HOOK","Failed to read transcript",{transcriptPath:r},t)}return""}function it(r){if(!r||!$(r))return"";try{let t=H(r,"utf-8").trim();if(!t)return"";let e=t.split(`
|
|
`);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,`
|
|
|
|
`).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)});
|