diff --git a/docs/i18n/CLAUDE.md b/docs/i18n/CLAUDE.md index 6f7bcb59..adfdcb11 100644 --- a/docs/i18n/CLAUDE.md +++ b/docs/i18n/CLAUDE.md @@ -3,36 +3,5 @@ -### Dec 12, 2025 - -**README.ar.md** -| ID | Time | T | Title | Read | -|----|------|---|-------|------| -| #24246 | 2:43 AM | 🟣 | Comprehensive Translation System Added with 22 Language READMEs | ~386 | - -**README.zh.md** -| ID | Time | T | Title | Read | -|----|------|---|-------|------| -| #24241 | 2:35 AM | ✅ | Internationalized README Files Moved to Dedicated i18n Directory | ~284 | - -### Dec 22, 2025 - -**README.ja.md** -| ID | Time | T | Title | Read | -|----|------|---|-------|------| -| #31948 | 8:08 PM | ✅ | Batch Updated All Translation READMEs with Language Navigation | ~400 | - -**README.zh.md** -| ID | Time | T | Title | Read | -|----|------|---|-------|------| -| #31947 | 8:07 PM | ✅ | Added Language Navigation Menu to Chinese Translation README | ~412 | -| #31945 | 8:06 PM | 🟣 | Multi-language navigation added to internationalized README files | ~386 | -| #31942 | 8:01 PM | 🔵 | Internationalization Documentation Coverage | ~315 | - -### Dec 28, 2025 - -**README.*.md** -| ID | Time | T | Title | Read | -|----|------|---|-------|------| -| #33540 | 10:55 PM | 🔵 | Grep search found mem-search references in internationalized documentation | ~577 | +*No recent activity* \ No newline at end of file diff --git a/docs/reports/2026-01-10--anti-pattern-czar-generalization-analysis.md b/docs/reports/2026-01-10--anti-pattern-czar-generalization-analysis.md new file mode 100644 index 00000000..91a232c1 --- /dev/null +++ b/docs/reports/2026-01-10--anti-pattern-czar-generalization-analysis.md @@ -0,0 +1,215 @@ +# Anti-Pattern Czar Generalization Analysis + +*Generated: January 10, 2026* + +This report analyzes whether the `/anti-pattern-czar` command and its underlying detector script can be generalized for use in any TypeScript codebase or other programming languages. + +--- + +## Executive Summary + +The anti-pattern detection system in claude-mem consists of two components: +1. **`/anti-pattern-czar`** - An interactive workflow command for detecting and fixing error handling anti-patterns +2. **`detect-error-handling-antipatterns.ts`** - The underlying static analysis script + +**Verdict:** The core detection patterns are highly generalizable to any TypeScript/JavaScript codebase. However, the current implementation has claude-mem-specific hardcoding that would need to be extracted into configuration for broader use. + +--- + +## Current Implementation Analysis + +### Detection Methodology + +The script uses **purely regex-based detection** (no AST parsing) with two phases: + +1. **Line-by-Line Pattern Matching** - Scans for known anti-patterns: + - `ERROR_STRING_MATCHING` - Fragile `error.message.includes('keyword')` checks + - `PARTIAL_ERROR_LOGGING` - Logging `error.message` instead of full error object + - `ERROR_MESSAGE_GUESSING` - Multiple `.includes()` chains for error classification + - `PROMISE_EMPTY_CATCH` - `.catch(() => {})` handlers + - `PROMISE_CATCH_NO_LOGGING` - Promise catches without logging + +2. **Try-Catch Block Analysis** - Brace-depth tracking to identify: + - `EMPTY_CATCH` - Catch blocks with no meaningful code + - `NO_LOGGING_IN_CATCH` - Catch blocks without logging/throwing + - `LARGE_TRY_BLOCK` - More than 10 significant lines (uncertain error source) + - `GENERIC_CATCH` - No `instanceof` or error type discrimination + - `CATCH_AND_CONTINUE_CRITICAL_PATH` - Logging but not failing in critical code + +### Claude-Mem Specific Elements + +| Element | Location | Generalization Required | +|---------|----------|-------------------------| +| `CRITICAL_PATHS` array | Lines 24-30 | Extract to config file | +| Script path in command | anti-pattern-czar.md | Make path configurable | +| Severity thresholds | Line 10 limit | Make configurable | +| Directory to scan | `src/` hardcoded | Accept as parameter | +| Exclusions | `node_modules`, `dist` | Make configurable | + +--- + +## Comparison with Industry Tools + +### ESLint Rules Coverage + +| Anti-Pattern | ESLint Equivalent | Coverage Gap | +|--------------|-------------------|--------------| +| Empty catch blocks | `no-empty` | Fully covered | +| Catch-and-rethrow | `no-useless-catch` | Fully covered | +| Floating promises | `@typescript-eslint/no-floating-promises` | Fully covered | +| Partial error logging | None | **Gap** | +| Error string matching | None | **Gap** | +| Error message guessing | None | **Gap** | +| Large try blocks | `sonarjs/cognitive-complexity` | Partial | +| Critical path continuation | None | **Gap** | + +### Unique Value Proposition + +The claude-mem detector catches patterns that **no standard ESLint rule addresses**: + +1. **Partial Error Logging** - Logging `error.message` loses stack traces +2. **Error String Matching** - Fragile `if (error.message.includes('timeout'))` patterns +3. **Error Message Guessing** - Chained `.includes()` for error classification +4. **Critical Path Continuation** - Logging but continuing in code that should fail + +These patterns represent **real debugging nightmares** that caused hours of investigation in claude-mem's development. + +--- + +## Generalization Recommendations + +### Tier 1: Quick Generalization (Configuration) + +Extract hardcoded values to a config file: + +```json +{ + "sourceDir": "src/", + "criticalPaths": ["**/services/*.ts", "**/core/*.ts"], + "excludeDirs": ["node_modules", "dist", "test"], + "largeBlockThreshold": 10, + "overrideComment": "// [ANTI-PATTERN IGNORED]:" +} +``` + +**Effort:** 2-4 hours + +### Tier 2: ESLint Plugin (Broader Adoption) + +Convert patterns to ESLint custom rules for standard toolchain integration: + +```javascript +// eslint-plugin-error-hygiene +module.exports = { + rules: { + 'no-partial-error-logging': { /* ... */ }, + 'no-error-string-matching': { /* ... */ }, + 'no-error-message-guessing': { /* ... */ }, + 'critical-path-must-fail': { /* ... */ } + } +} +``` + +**Advantages:** +- Integrates with existing toolchains +- IDE integration via ESLint plugins +- Auto-fix support possible +- Community-standard distribution + +**Effort:** 1-2 weeks + +### Tier 3: Multi-Language Support + +The regex patterns could be adapted for: +- **Go** - `defer` with empty recover, error checking patterns +- **Python** - `except:` without logging, bare `except Exception:` +- **Rust** - `.unwrap()` in production paths, `_` pattern for `Result` + +**Effort:** 1 week per language + +--- + +## Architecture for General Use + +``` +error-pattern-detector/ +├── config/ +│ ├── default.json # Sensible defaults +│ └── schema.json # Config validation +├── patterns/ +│ ├── typescript/ # TS-specific patterns +│ │ ├── empty-catch.ts +│ │ ├── partial-logging.ts +│ │ └── critical-path.ts +│ └── shared/ # Cross-language patterns +│ ├── large-try-block.ts +│ └── swallowed-errors.ts +├── reporters/ +│ ├── console.ts # CLI output +│ ├── json.ts # Machine-readable +│ ├── sarif.ts # GitHub/IDE integration +│ └── markdown.ts # Report generation +├── cli.ts # Entry point +└── index.ts # Programmatic API +``` + +--- + +## PR #666 Review Context + +The PR review raised a valid concern: the `/anti-pattern-czar` command references a script (`scripts/anti-pattern-test/detect-error-handling-antipatterns.ts`) that only exists in the claude-mem development repository. + +**Options:** + +1. **Keep as development tool** - Don't distribute with plugin (recommended by reviewer) +2. **Bundle the detector** - Include the script in the plugin distribution +3. **Extract to standalone package** - Publish as `@claude-mem/error-pattern-detector` and depend on it + +Option 3 enables both plugin distribution and community adoption. + +--- + +## Conclusions + +### What's Generalizable + +| Component | Generalizability | Notes | +|-----------|------------------|-------| +| Regex detection patterns | High | Universal to TS/JS | +| Brace-depth tracking | High | Works for any curly-brace language | +| Override comment syntax | High | Adoptable by any project | +| Report formatting | High | Standard markdown output | +| 4-step workflow | High | Applicable to any codebase | + +### What's Claude-Mem Specific + +| Component | Specificity | Extraction Effort | +|-----------|-------------|-------------------| +| Critical path file list | High | Configuration file | +| Script location | High | Path parameter | +| Severity philosophy | Medium | Documentation | +| Exit codes | Low | Already standard | + +### Recommendation + +**Invest in Tier 2 (ESLint Plugin)** - The patterns detected are genuinely unique and valuable. Standard ESLint rules miss these debugging nightmares. An ESLint plugin would: + +1. Enable adoption in any TS/JS project +2. Integrate with existing CI/CD pipelines +3. Provide IDE feedback in real-time +4. Allow community contributions to pattern library +5. Create a marketable open-source project + +The name `eslint-plugin-error-hygiene` captures the philosophy: maintaining clean error handling practices to prevent silent failures. + +--- + +## Next Steps + +1. **Short-term:** Extract configuration to enable use in other projects +2. **Medium-term:** Create ESLint plugin with AST-based detection (more robust than regex) +3. **Long-term:** Multi-language support, SARIF output for security tool integration + +--- + +*Report generated by analyzing PR #666 review comments, the anti-pattern-czar.md command, and detect-error-handling-antipatterns.ts implementation.* diff --git a/plugin/package.json b/plugin/package.json index 3ff5afd8..87d01d32 100644 --- a/plugin/package.json +++ b/plugin/package.json @@ -1,6 +1,6 @@ { "name": "claude-mem-plugin", - "version": "9.0.3", + "version": "9.0.4", "private": true, "description": "Runtime dependencies for claude-mem bundled hooks", "type": "module", diff --git a/plugin/scripts/worker-service.cjs b/plugin/scripts/worker-service.cjs index db249c45..7a78bfe8 100755 --- a/plugin/scripts/worker-service.cjs +++ b/plugin/scripts/worker-service.cjs @@ -749,7 +749,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs. `}var mG=yl.default.platform==="win32"?["APPDATA","HOMEDRIVE","HOMEPATH","LOCALAPPDATA","PATH","PROCESSOR_ARCHITECTURE","SYSTEMDRIVE","SYSTEMROOT","TEMP","USERNAME","USERPROFILE","PROGRAMFILES"]:["HOME","LOGNAME","PATH","SHELL","TERM","USER"];function hG(){let t={};for(let e of mG){let r=yl.default.env[e];r!==void 0&&(r.startsWith("()")||(t[e]=r))}return t}var Es=class{constructor(e){this._readBuffer=new am,this._stderrStream=null,this._serverParams=e,(e.stderr==="pipe"||e.stderr==="overlapped")&&(this._stderrStream=new DR.PassThrough)}async start(){if(this._process)throw new Error("StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.");return new Promise((e,r)=>{this._process=(0,MR.default)(this._serverParams.command,this._serverParams.args??[],{env:{...hG(),...this._serverParams.env},stdio:["pipe","pipe",this._serverParams.stderr??"inherit"],shell:!1,windowsHide:yl.default.platform==="win32"&&gG(),cwd:this._serverParams.cwd}),this._process.on("error",n=>{r(n),this.onerror?.(n)}),this._process.on("spawn",()=>{e()}),this._process.on("close",n=>{this._process=void 0,this.onclose?.()}),this._process.stdin?.on("error",n=>{this.onerror?.(n)}),this._process.stdout?.on("data",n=>{this._readBuffer.append(n),this.processReadBuffer()}),this._process.stdout?.on("error",n=>{this.onerror?.(n)}),this._stderrStream&&this._process.stderr&&this._process.stderr.pipe(this._stderrStream)})}get stderr(){return this._stderrStream?this._stderrStream:this._process?.stderr??null}get pid(){return this._process?.pid??null}processReadBuffer(){for(;;)try{let e=this._readBuffer.readMessage();if(e===null)break;this.onmessage?.(e)}catch(e){this.onerror?.(e)}}async close(){if(this._process){let e=this._process;this._process=void 0;let r=new Promise(n=>{e.once("close",()=>{n()})});try{e.stdin?.end()}catch{}if(await Promise.race([r,new Promise(n=>setTimeout(n,2e3).unref())]),e.exitCode===null){try{e.kill("SIGTERM")}catch{}await Promise.race([r,new Promise(n=>setTimeout(n,2e3).unref())])}if(e.exitCode===null)try{e.kill("SIGKILL")}catch{}}this._readBuffer.clear()}send(e){return new Promise(r=>{if(!this._process?.stdin)throw new Error("Not connected");let n=AR(e);this._process.stdin.write(n)?r():this._process.stdin.once("drain",r)})}};function gG(){return"type"in yl.default}Zr();$e();var W0=ut(require("path"),1),XR=require("os"),Hn=require("fs"),ks=require("child_process"),YR=require("util");$e();xl();var cm=(0,YR.promisify)(ks.exec),QR=W0.default.join((0,XR.homedir)(),".claude-mem"),ro=W0.default.join(QR,"worker.pid");function K0(t){(0,Hn.mkdirSync)(QR,{recursive:!0}),(0,Hn.writeFileSync)(ro,JSON.stringify(t,null,2))}function eC(){if(!(0,Hn.existsSync)(ro))return null;try{return JSON.parse((0,Hn.readFileSync)(ro,"utf-8"))}catch(t){return k.warn("SYSTEM","Failed to parse PID file",{path:ro},t),null}}function Ui(){if((0,Hn.existsSync)(ro))try{(0,Hn.unlinkSync)(ro)}catch(t){k.warn("SYSTEM","Failed to remove PID file",{path:ro},t)}}function no(t){return process.platform==="win32"?Math.round(t*2):t}async function tC(t){if(process.platform!=="win32")return[];if(!Number.isInteger(t)||t<=0)return k.warn("SYSTEM","Invalid parent PID for child process enumeration",{parentPid:t}),[];try{let e=`powershell -NoProfile -NonInteractive -Command "Get-Process | Where-Object { \\$_.ParentProcessId -eq ${t} } | Select-Object -ExpandProperty Id"`,{stdout:r}=await cm(e,{timeout:ma.POWERSHELL_COMMAND});return r.split(` `).map(n=>n.trim()).filter(n=>n.length>0&&/^\d+$/.test(n)).map(n=>parseInt(n,10)).filter(n=>n>0)}catch(e){return k.error("SYSTEM","Failed to enumerate child processes",{parentPid:t},e),[]}}async function rC(t){if(!Number.isInteger(t)||t<=0){k.warn("SYSTEM","Invalid PID for force kill",{pid:t});return}try{process.platform==="win32"?await cm(`taskkill /PID ${t} /T /F`,{timeout:ma.POWERSHELL_COMMAND}):process.kill(t,"SIGKILL"),k.info("SYSTEM","Killed process",{pid:t})}catch(e){k.debug("SYSTEM","Process already exited during force kill",{pid:t},e)}}async function nC(t,e){let r=Date.now();for(;Date.now()-r{try{return process.kill(i,0),!0}catch{return!1}});if(n.length===0){k.info("SYSTEM","All child processes exited");return}k.debug("SYSTEM","Waiting for processes to exit",{stillAlive:n}),await new Promise(i=>setTimeout(i,100))}k.warn("SYSTEM","Timeout waiting for child processes to exit")}async function iC(){let t=process.platform==="win32",e=[];try{if(t){let r=`powershell -NoProfile -NonInteractive -Command "Get-CimInstance Win32_Process | Where-Object { \\$_.Name -like '*python*' -and \\$_.CommandLine -like '*chroma-mcp*' } | Select-Object -ExpandProperty ProcessId"`,{stdout:n}=await cm(r,{timeout:ma.POWERSHELL_COMMAND});if(!n.trim()){k.debug("SYSTEM","No orphaned chroma-mcp processes found (Windows)");return}let i=n.split(` `).map(a=>a.trim()).filter(a=>a.length>0&&/^\d+$/.test(a));for(let a of i){let o=parseInt(a,10);!isNaN(o)&&Number.isInteger(o)&&o>0&&e.push(o)}}else{let{stdout:r}=await cm('ps aux | grep "chroma-mcp" | grep -v grep || true');if(!r.trim()){k.debug("SYSTEM","No orphaned chroma-mcp processes found (Unix)");return}let n=r.trim().split(` -`);for(let i of n){let a=i.trim().split(/\s+/);if(a.length>1){let o=parseInt(a[1],10);!isNaN(o)&&Number.isInteger(o)&&o>0&&e.push(o)}}}}catch(r){k.error("SYSTEM","Failed to enumerate orphaned processes",{},r);return}if(e.length!==0){if(k.info("SYSTEM","Cleaning up orphaned chroma-mcp processes",{platform:t?"Windows":"Unix",count:e.length,pids:e}),t)for(let r of e){if(!Number.isInteger(r)||r<=0){k.warn("SYSTEM","Skipping invalid PID",{pid:r});continue}try{(0,ks.execSync)(`taskkill /PID ${r} /T /F`,{timeout:ma.POWERSHELL_COMMAND,stdio:"ignore"})}catch(n){k.debug("SYSTEM","Failed to kill process, may have already exited",{pid:r},n)}}else for(let r of e)try{process.kill(r,"SIGKILL")}catch(n){k.debug("SYSTEM","Process already exited",{pid:r},n)}k.info("SYSTEM","Orphaned processes cleaned up",{count:e.length})}}function J0(t,e,r={}){let n=(0,ks.spawn)(process.execPath,[t,"--daemon"],{detached:!0,stdio:"ignore",windowsHide:!0,env:{...process.env,CLAUDE_MEM_WORKER_PORT:String(e),...r}});if(n.pid!==void 0)return n.unref(),n.pid}function aC(t,e){return async r=>{if(e.value){k.warn("SYSTEM",`Received ${r} but shutdown already in progress`);return}e.value=!0,k.info("SYSTEM",`Received ${r}, shutting down...`);try{await t(),process.exit(0)}catch(n){k.error("SYSTEM","Error during shutdown",{},n),process.exit(0)}}}var X0=ut(require("path"),1),oC=require("os"),sC=require("fs");$e();async function um(t){try{return(await fetch(`http://127.0.0.1:${t}/api/health`)).ok}catch{return!1}}async function El(t,e=3e4){let r=Date.now();for(;Date.now()-rsetTimeout(n,500))}return!1}async function lm(t,e=1e4){let r=Date.now();for(;Date.now()-rsetTimeout(n,500))}return!1}async function dm(t){try{let e=await fetch(`http://127.0.0.1:${t}/api/admin/shutdown`,{method:"POST"});return e.ok?!0:(k.warn("SYSTEM","Shutdown request returned error",{port:t,status:e.status}),!1)}catch(e){return e instanceof Error&&e.message?.includes("ECONNREFUSED")?(k.debug("SYSTEM","Worker already stopped",{port:t},e),!1):(k.error("SYSTEM","Shutdown request failed unexpectedly",{port:t},e),!1)}}function SG(){let t=X0.default.join((0,oC.homedir)(),".claude","plugins","marketplaces","thedotmack"),e=X0.default.join(t,"package.json");return JSON.parse((0,sC.readFileSync)(e,"utf-8")).version}async function wG(t){try{let e=await fetch(`http://127.0.0.1:${t}/api/version`);return e.ok?(await e.json()).version:null}catch{return k.debug("SYSTEM","Could not fetch worker version",{port:t}),null}}async function cC(t){let e=SG(),r=await wG(t);return r?{matches:e===r,pluginVersion:e,workerVersion:r}:{matches:!0,pluginVersion:e,workerVersion:r}}$e();async function uC(t){k.info("SYSTEM","Shutdown initiated"),Ui();let e=await tC(process.pid);if(k.info("SYSTEM","Found child processes",{count:e.length,pids:e}),t.server&&(await $G(t.server),k.info("SYSTEM","HTTP server closed")),await t.sessionManager.shutdownAll(),t.mcpClient&&(await t.mcpClient.close(),k.info("SYSTEM","MCP client closed")),t.dbManager&&await t.dbManager.close(),e.length>0){k.info("SYSTEM","Force killing remaining children");for(let r of e)await rC(r);await nC(e,5e3)}k.info("SYSTEM","Worker shutdown complete")}async function $G(t){t.closeAllConnections(),process.platform==="win32"&&await new Promise(e=>setTimeout(e,500)),await new Promise((e,r)=>{t.close(n=>n?r(n):e())}),process.platform==="win32"&&(await new Promise(e=>setTimeout(e,500)),k.info("SYSTEM","Waited for Windows port cleanup"))}var I4=ut(_h(),1),a$=ut(require("fs"),1),o$=ut(require("path"),1);$e();var r$=ut(_h(),1),w4=ut(f4(),1),$4=ut(require("path"),1);ln();$e();function n$(t){let e=[];e.push(r$.default.json({limit:"50mb"})),e.push((0,w4.default)()),e.push((i,a,o)=>{let c=[".html",".js",".css",".svg",".png",".jpg",".jpeg",".webp",".woff",".woff2",".ttf",".eot"].some(g=>i.path.endsWith(g)),u=i.path==="/api/logs";if(i.path.startsWith("/health")||i.path==="/"||c||u)return o();let l=Date.now(),d=`${i.method}-${Date.now()}`,p=t(i.method,i.path,i.body);k.info("HTTP",`\u2192 ${i.method} ${i.path}`,{requestId:d},p);let f=a.send.bind(a);a.send=function(g){let _=Date.now()-l;return k.info("HTTP",`\u2190 ${a.statusCode} ${i.path}`,{requestId:d,duration:`${_}ms`}),f(g)},o()});let r=Kr(),n=$4.default.join(r,"plugin","ui");return e.push(r$.default.static(n)),e}function bh(t,e,r){let n=t.ip||t.connection.remoteAddress||"";if(!(n==="127.0.0.1"||n==="::1"||n==="::ffff:127.0.0.1"||n==="localhost")){k.warn("SECURITY","Admin endpoint access denied - not localhost",{endpoint:t.path,clientIp:n,method:t.method}),e.status(403).json({error:"Forbidden",message:"Admin endpoints are only accessible from localhost"});return}r()}function i$(t,e,r){if(!r||Object.keys(r).length===0||e.includes("/init"))return"";if(e.includes("/observations")){let n=r.tool_name||"?",i=r.tool_input;return`tool=${k.formatTool(n,i)}`}return e.includes("/summarize")?"requesting summary":""}$e();var Qs=class extends Error{constructor(r,n=500,i,a){super(r);this.statusCode=n;this.code=i;this.details=a;this.name="AppError"}};function E4(t,e,r,n){let i={error:t,message:e};return r&&(i.code=r),n&&(i.details=n),i}var k4=(t,e,r,n)=>{let i=t instanceof Qs?t.statusCode:500;k.error("HTTP",`Error handling ${e.method} ${e.path}`,{statusCode:i,error:t.message,code:t instanceof Qs?t.code:void 0},t);let a=E4(t.name||"Error",t.message,t instanceof Qs?t.code:void 0,t instanceof Qs?t.details:void 0);r.status(i).json(a)};function T4(t,e){e.status(404).json(E4("NotFound",`Cannot ${t.method} ${t.path}`))}var zne="9.0.3",xh=class{app;server=null;options;startTime=Date.now();constructor(e){this.options=e,this.app=(0,I4.default)(),this.setupMiddleware(),this.setupCoreRoutes()}getHttpServer(){return this.server}async listen(e,r){return new Promise((n,i)=>{this.server=this.app.listen(e,r,()=>{k.info("SYSTEM","HTTP server started",{host:r,port:e,pid:process.pid}),n()}),this.server.on("error",i)})}async close(){this.server&&(this.server.closeAllConnections(),process.platform==="win32"&&await new Promise(e=>setTimeout(e,500)),await new Promise((e,r)=>{this.server.close(n=>n?r(n):e())}),process.platform==="win32"&&await new Promise(e=>setTimeout(e,500)),this.server=null,k.info("SYSTEM","HTTP server closed"))}registerRoutes(e){e.setupRoutes(this.app)}finalizeRoutes(){this.app.use(T4),this.app.use(k4)}setupMiddleware(){n$(i$).forEach(r=>this.app.use(r))}setupCoreRoutes(){let e="TEST-008-wrapper-ipc";this.app.get("/api/health",(r,n)=>{n.status(200).json({status:"ok",build:e,managed:process.env.CLAUDE_MEM_MANAGED==="true",hasIpc:typeof process.send=="function",platform:process.platform,pid:process.pid,initialized:this.options.getInitializationComplete(),mcpReady:this.options.getMcpReady()})}),this.app.get("/api/readiness",(r,n)=>{this.options.getInitializationComplete()?n.status(200).json({status:"ready",mcpReady:this.options.getMcpReady()}):n.status(503).json({status:"initializing",message:"Worker is still initializing, please retry"})}),this.app.get("/api/version",(r,n)=>{n.status(200).json({version:zne})}),this.app.get("/api/instructions",async(r,n)=>{let i=r.query.topic||"all",a=r.query.operation;try{let o;if(a){let s=o$.default.join(__dirname,"../skills/mem-search/operations",`${a}.md`);o=await a$.promises.readFile(s,"utf-8")}else{let s=o$.default.join(__dirname,"../skills/mem-search/SKILL.md"),c=await a$.promises.readFile(s,"utf-8");o=this.extractInstructionSection(c,i)}n.json({content:[{type:"text",text:o}]})}catch{n.status(404).json({error:"Instruction not found"})}}),this.app.post("/api/admin/restart",bh,async(r,n)=>{n.json({status:"restarting"}),process.platform==="win32"&&process.env.CLAUDE_MEM_MANAGED==="true"&&process.send?(k.info("SYSTEM","Sending restart request to wrapper"),process.send({type:"restart"})):setTimeout(async()=>{await this.options.onRestart()},100)}),this.app.post("/api/admin/shutdown",bh,async(r,n)=>{n.json({status:"shutting_down"}),process.platform==="win32"&&process.env.CLAUDE_MEM_MANAGED==="true"&&process.send?(k.info("SYSTEM","Sending shutdown request to wrapper"),process.send({type:"shutdown"})):setTimeout(async()=>{await this.options.onShutdown()},100)})}extractInstructionSection(e,r){let n={workflow:this.extractBetween(e,"## The Workflow","## Search Parameters"),search_params:this.extractBetween(e,"## Search Parameters","## Examples"),examples:this.extractBetween(e,"## Examples","## Why This Workflow"),all:e};return n[r]||n.all}extractBetween(e,r,n){let i=e.indexOf(r),a=e.indexOf(n);return i===-1?e:a===-1?e.substring(i):e.substring(i,a).trim()}};var lt=ut(require("path"),1),ec=require("os"),Vt=require("fs"),R4=require("child_process"),C4=require("util");$e();Zr();var In=require("fs"),ud=require("path");$e();function O4(t){try{return(0,In.existsSync)(t)?JSON.parse((0,In.readFileSync)(t,"utf-8")):{}}catch(e){return k.error("CONFIG","Failed to read Cursor registry, using empty registry",{file:t,error:e instanceof Error?e.message:String(e)}),{}}}function P4(t,e){let r=(0,ud.join)(t,"..");(0,In.mkdirSync)(r,{recursive:!0}),(0,In.writeFileSync)(t,JSON.stringify(e,null,2))}function s$(t,e){let r=(0,ud.join)(t,".cursor","rules"),n=(0,ud.join)(r,"claude-mem-context.mdc"),i=`${n}.tmp`;(0,In.mkdirSync)(r,{recursive:!0});let a=`--- +`);for(let i of n){let a=i.trim().split(/\s+/);if(a.length>1){let o=parseInt(a[1],10);!isNaN(o)&&Number.isInteger(o)&&o>0&&e.push(o)}}}}catch(r){k.error("SYSTEM","Failed to enumerate orphaned processes",{},r);return}if(e.length!==0){if(k.info("SYSTEM","Cleaning up orphaned chroma-mcp processes",{platform:t?"Windows":"Unix",count:e.length,pids:e}),t)for(let r of e){if(!Number.isInteger(r)||r<=0){k.warn("SYSTEM","Skipping invalid PID",{pid:r});continue}try{(0,ks.execSync)(`taskkill /PID ${r} /T /F`,{timeout:ma.POWERSHELL_COMMAND,stdio:"ignore"})}catch(n){k.debug("SYSTEM","Failed to kill process, may have already exited",{pid:r},n)}}else for(let r of e)try{process.kill(r,"SIGKILL")}catch(n){k.debug("SYSTEM","Process already exited",{pid:r},n)}k.info("SYSTEM","Orphaned processes cleaned up",{count:e.length})}}function J0(t,e,r={}){let n=(0,ks.spawn)(process.execPath,[t,"--daemon"],{detached:!0,stdio:"ignore",windowsHide:!0,env:{...process.env,CLAUDE_MEM_WORKER_PORT:String(e),...r}});if(n.pid!==void 0)return n.unref(),n.pid}function aC(t,e){return async r=>{if(e.value){k.warn("SYSTEM",`Received ${r} but shutdown already in progress`);return}e.value=!0,k.info("SYSTEM",`Received ${r}, shutting down...`);try{await t(),process.exit(0)}catch(n){k.error("SYSTEM","Error during shutdown",{},n),process.exit(0)}}}var X0=ut(require("path"),1),oC=require("os"),sC=require("fs");$e();async function um(t){try{return(await fetch(`http://127.0.0.1:${t}/api/health`)).ok}catch{return!1}}async function El(t,e=3e4){let r=Date.now();for(;Date.now()-rsetTimeout(n,500))}return!1}async function lm(t,e=1e4){let r=Date.now();for(;Date.now()-rsetTimeout(n,500))}return!1}async function dm(t){try{let e=await fetch(`http://127.0.0.1:${t}/api/admin/shutdown`,{method:"POST"});return e.ok?!0:(k.warn("SYSTEM","Shutdown request returned error",{port:t,status:e.status}),!1)}catch(e){return e instanceof Error&&e.message?.includes("ECONNREFUSED")?(k.debug("SYSTEM","Worker already stopped",{port:t},e),!1):(k.error("SYSTEM","Shutdown request failed unexpectedly",{port:t},e),!1)}}function SG(){let t=X0.default.join((0,oC.homedir)(),".claude","plugins","marketplaces","thedotmack"),e=X0.default.join(t,"package.json");return JSON.parse((0,sC.readFileSync)(e,"utf-8")).version}async function wG(t){try{let e=await fetch(`http://127.0.0.1:${t}/api/version`);return e.ok?(await e.json()).version:null}catch{return k.debug("SYSTEM","Could not fetch worker version",{port:t}),null}}async function cC(t){let e=SG(),r=await wG(t);return r?{matches:e===r,pluginVersion:e,workerVersion:r}:{matches:!0,pluginVersion:e,workerVersion:r}}$e();async function uC(t){k.info("SYSTEM","Shutdown initiated"),Ui();let e=await tC(process.pid);if(k.info("SYSTEM","Found child processes",{count:e.length,pids:e}),t.server&&(await $G(t.server),k.info("SYSTEM","HTTP server closed")),await t.sessionManager.shutdownAll(),t.mcpClient&&(await t.mcpClient.close(),k.info("SYSTEM","MCP client closed")),t.dbManager&&await t.dbManager.close(),e.length>0){k.info("SYSTEM","Force killing remaining children");for(let r of e)await rC(r);await nC(e,5e3)}k.info("SYSTEM","Worker shutdown complete")}async function $G(t){t.closeAllConnections(),process.platform==="win32"&&await new Promise(e=>setTimeout(e,500)),await new Promise((e,r)=>{t.close(n=>n?r(n):e())}),process.platform==="win32"&&(await new Promise(e=>setTimeout(e,500)),k.info("SYSTEM","Waited for Windows port cleanup"))}var I4=ut(_h(),1),a$=ut(require("fs"),1),o$=ut(require("path"),1);$e();var r$=ut(_h(),1),w4=ut(f4(),1),$4=ut(require("path"),1);ln();$e();function n$(t){let e=[];e.push(r$.default.json({limit:"50mb"})),e.push((0,w4.default)()),e.push((i,a,o)=>{let c=[".html",".js",".css",".svg",".png",".jpg",".jpeg",".webp",".woff",".woff2",".ttf",".eot"].some(g=>i.path.endsWith(g)),u=i.path==="/api/logs";if(i.path.startsWith("/health")||i.path==="/"||c||u)return o();let l=Date.now(),d=`${i.method}-${Date.now()}`,p=t(i.method,i.path,i.body);k.info("HTTP",`\u2192 ${i.method} ${i.path}`,{requestId:d},p);let f=a.send.bind(a);a.send=function(g){let _=Date.now()-l;return k.info("HTTP",`\u2190 ${a.statusCode} ${i.path}`,{requestId:d,duration:`${_}ms`}),f(g)},o()});let r=Kr(),n=$4.default.join(r,"plugin","ui");return e.push(r$.default.static(n)),e}function bh(t,e,r){let n=t.ip||t.connection.remoteAddress||"";if(!(n==="127.0.0.1"||n==="::1"||n==="::ffff:127.0.0.1"||n==="localhost")){k.warn("SECURITY","Admin endpoint access denied - not localhost",{endpoint:t.path,clientIp:n,method:t.method}),e.status(403).json({error:"Forbidden",message:"Admin endpoints are only accessible from localhost"});return}r()}function i$(t,e,r){if(!r||Object.keys(r).length===0||e.includes("/init"))return"";if(e.includes("/observations")){let n=r.tool_name||"?",i=r.tool_input;return`tool=${k.formatTool(n,i)}`}return e.includes("/summarize")?"requesting summary":""}$e();var Qs=class extends Error{constructor(r,n=500,i,a){super(r);this.statusCode=n;this.code=i;this.details=a;this.name="AppError"}};function E4(t,e,r,n){let i={error:t,message:e};return r&&(i.code=r),n&&(i.details=n),i}var k4=(t,e,r,n)=>{let i=t instanceof Qs?t.statusCode:500;k.error("HTTP",`Error handling ${e.method} ${e.path}`,{statusCode:i,error:t.message,code:t instanceof Qs?t.code:void 0},t);let a=E4(t.name||"Error",t.message,t instanceof Qs?t.code:void 0,t instanceof Qs?t.details:void 0);r.status(i).json(a)};function T4(t,e){e.status(404).json(E4("NotFound",`Cannot ${t.method} ${t.path}`))}var zne="9.0.4",xh=class{app;server=null;options;startTime=Date.now();constructor(e){this.options=e,this.app=(0,I4.default)(),this.setupMiddleware(),this.setupCoreRoutes()}getHttpServer(){return this.server}async listen(e,r){return new Promise((n,i)=>{this.server=this.app.listen(e,r,()=>{k.info("SYSTEM","HTTP server started",{host:r,port:e,pid:process.pid}),n()}),this.server.on("error",i)})}async close(){this.server&&(this.server.closeAllConnections(),process.platform==="win32"&&await new Promise(e=>setTimeout(e,500)),await new Promise((e,r)=>{this.server.close(n=>n?r(n):e())}),process.platform==="win32"&&await new Promise(e=>setTimeout(e,500)),this.server=null,k.info("SYSTEM","HTTP server closed"))}registerRoutes(e){e.setupRoutes(this.app)}finalizeRoutes(){this.app.use(T4),this.app.use(k4)}setupMiddleware(){n$(i$).forEach(r=>this.app.use(r))}setupCoreRoutes(){let e="TEST-008-wrapper-ipc";this.app.get("/api/health",(r,n)=>{n.status(200).json({status:"ok",build:e,managed:process.env.CLAUDE_MEM_MANAGED==="true",hasIpc:typeof process.send=="function",platform:process.platform,pid:process.pid,initialized:this.options.getInitializationComplete(),mcpReady:this.options.getMcpReady()})}),this.app.get("/api/readiness",(r,n)=>{this.options.getInitializationComplete()?n.status(200).json({status:"ready",mcpReady:this.options.getMcpReady()}):n.status(503).json({status:"initializing",message:"Worker is still initializing, please retry"})}),this.app.get("/api/version",(r,n)=>{n.status(200).json({version:zne})}),this.app.get("/api/instructions",async(r,n)=>{let i=r.query.topic||"all",a=r.query.operation;try{let o;if(a){let s=o$.default.join(__dirname,"../skills/mem-search/operations",`${a}.md`);o=await a$.promises.readFile(s,"utf-8")}else{let s=o$.default.join(__dirname,"../skills/mem-search/SKILL.md"),c=await a$.promises.readFile(s,"utf-8");o=this.extractInstructionSection(c,i)}n.json({content:[{type:"text",text:o}]})}catch{n.status(404).json({error:"Instruction not found"})}}),this.app.post("/api/admin/restart",bh,async(r,n)=>{n.json({status:"restarting"}),process.platform==="win32"&&process.env.CLAUDE_MEM_MANAGED==="true"&&process.send?(k.info("SYSTEM","Sending restart request to wrapper"),process.send({type:"restart"})):setTimeout(async()=>{await this.options.onRestart()},100)}),this.app.post("/api/admin/shutdown",bh,async(r,n)=>{n.json({status:"shutting_down"}),process.platform==="win32"&&process.env.CLAUDE_MEM_MANAGED==="true"&&process.send?(k.info("SYSTEM","Sending shutdown request to wrapper"),process.send({type:"shutdown"})):setTimeout(async()=>{await this.options.onShutdown()},100)})}extractInstructionSection(e,r){let n={workflow:this.extractBetween(e,"## The Workflow","## Search Parameters"),search_params:this.extractBetween(e,"## Search Parameters","## Examples"),examples:this.extractBetween(e,"## Examples","## Why This Workflow"),all:e};return n[r]||n.all}extractBetween(e,r,n){let i=e.indexOf(r),a=e.indexOf(n);return i===-1?e:a===-1?e.substring(i):e.substring(i,a).trim()}};var lt=ut(require("path"),1),ec=require("os"),Vt=require("fs"),R4=require("child_process"),C4=require("util");$e();Zr();var In=require("fs"),ud=require("path");$e();function O4(t){try{return(0,In.existsSync)(t)?JSON.parse((0,In.readFileSync)(t,"utf-8")):{}}catch(e){return k.error("CONFIG","Failed to read Cursor registry, using empty registry",{file:t,error:e instanceof Error?e.message:String(e)}),{}}}function P4(t,e){let r=(0,ud.join)(t,"..");(0,In.mkdirSync)(r,{recursive:!0}),(0,In.writeFileSync)(t,JSON.stringify(e,null,2))}function s$(t,e){let r=(0,ud.join)(t,".cursor","rules"),n=(0,ud.join)(r,"claude-mem-context.mdc"),i=`${n}.tmp`;(0,In.mkdirSync)(r,{recursive:!0});let a=`--- alwaysApply: true description: "Claude-mem context from past sessions (auto-updated)" ---