infernoflow 0.44.17 → 0.44.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
- import*as c from"node:fs";import*as a from"node:path";import*as S from"node:os";import{fileURLToPath as D}from"node:url";import{execSync as F}from"node:child_process";import{detectIdeContext as J}from"../ai/ideDetection.mjs";import{header as A,ok as k,warn as O,info as w,done as N,cyan as u,yellow as j,bold as y,green as f,gray as C}from"../ui/output.mjs";import"../cursorHooksInstall.mjs";import"../vsCodeCopilotHooksInstall.mjs";import{updateInjectionConfig as H}from"../amp/io.mjs";import{injectionPatchFromArgs as I}from"./refresh.mjs";const T=a.dirname(D(import.meta.url));function v(){return a.resolve(T,"../../templates")}function U(r){try{return F(`npx infernoflow ${r}`,{encoding:"utf8",cwd:process.cwd(),timeout:6e4,stdio:["inherit","pipe","pipe"]})}catch(n){return n.stdout||n.stderr||n.message}}function m(r){if(!c.existsSync(r))return{data:{},existed:!1,corrupt:!1,backup:null};let n;try{n=c.readFileSync(r,"utf8")}catch{return{data:{},existed:!0,corrupt:!1,backup:null}}try{const i=JSON.parse(n);return!i||typeof i!="object"||Array.isArray(i)?{data:{},existed:!0,corrupt:!1,backup:null}:{data:i,existed:!0,corrupt:!1,backup:null}}catch{let i=null;try{i=`${r}.corrupt-${Date.now()}.bak`,c.writeFileSync(i,n,"utf8")}catch{i=null}return{data:{},existed:!0,corrupt:!0,backup:i}}}function L(){const r=S.homedir();return process.platform==="win32"?a.join(r,"AppData","Roaming","Claude","claude_desktop_config.json"):process.platform==="darwin"?a.join(r,"Library","Application Support","Claude","claude_desktop_config.json"):a.join(r,".config","Claude","claude_desktop_config.json")}function V(r){const n=L(),i=a.dirname(n);if(!c.existsSync(i)&&!c.existsSync(n))return{updated:!1,skipped:"not-installed"};const{data:t,backup:o}=m(n);t.mcpServers||(t.mcpServers={});const s=t.mcpServers.infernoflow;if(s&&Array.isArray(s.args)&&s.args[0]===r)return{updated:!1,backup:o};t.mcpServers.infernoflow={command:"node",args:[r]};const p=JSON.stringify(t,null,2);return c.mkdirSync(i,{recursive:!0}),c.writeFileSync(n,p,"utf8"),{updated:!0,path:n,backup:o}}function E(r){const n=a.join(r,".git");if(!c.existsSync(n))return{installed:!1,skipped:"not-a-git-repo"};const i=a.join(n,"hooks"),t=a.join(i,"post-commit"),o='infernoflow log "commit: $(git log -1 --pretty=%s)" --type note --source git-hook --auto --quiet >/dev/null 2>&1 || true';try{if(c.mkdirSync(i,{recursive:!0}),c.existsSync(t)){const s=c.readFileSync(t,"utf8");if(s.includes("infernoflow"))return{installed:!1,already:!0};const p=s.replace(/\s*$/,"")+`
1
+ import*as a from"node:fs";import*as u from"node:path";import*as v from"node:os";import{fileURLToPath as $}from"node:url";import{execSync as N}from"node:child_process";import{detectIdeContext as I}from"../ai/ideDetection.mjs";import{header as J,ok as h,warn as M,info as w,done as D,cyan as d,yellow as C,bold as k,green as m,gray as j}from"../ui/output.mjs";import"../cursorHooksInstall.mjs";import"../vsCodeCopilotHooksInstall.mjs";import{updateInjectionConfig as E}from"../amp/io.mjs";import{findProjectRoot as T}from"../projectRoot.mjs";import{injectionPatchFromArgs as A}from"./refresh.mjs";const L=u.dirname($(import.meta.url));function _(){return u.resolve(L,"../../templates")}function W(t){try{return N(`npx infernoflow ${t}`,{encoding:"utf8",cwd:process.cwd(),timeout:6e4,stdio:["inherit","pipe","pipe"]})}catch(i){return i.stdout||i.stderr||i.message}}function y(t){if(!a.existsSync(t))return{data:{},existed:!1,corrupt:!1,backup:null};let i;try{i=a.readFileSync(t,"utf8")}catch{return{data:{},existed:!0,corrupt:!1,backup:null}}try{const s=JSON.parse(i);return!s||typeof s!="object"||Array.isArray(s)?{data:{},existed:!0,corrupt:!1,backup:null}:{data:s,existed:!0,corrupt:!1,backup:null}}catch{let s=null;try{s=`${t}.corrupt-${Date.now()}.bak`,a.writeFileSync(s,i,"utf8")}catch{s=null}return{data:{},existed:!0,corrupt:!0,backup:s}}}function H(){const t=v.homedir();return process.platform==="win32"?u.join(t,"AppData","Roaming","Claude","claude_desktop_config.json"):process.platform==="darwin"?u.join(t,"Library","Application Support","Claude","claude_desktop_config.json"):u.join(t,".config","Claude","claude_desktop_config.json")}function U(t,i){const s=H(),n=u.dirname(s);if(!a.existsSync(n)&&!a.existsSync(s))return{updated:!1,skipped:"not-installed"};const{data:r,backup:e}=y(s);r.mcpServers||(r.mcpServers={});const l=r.mcpServers.infernoflow;if(l&&Array.isArray(l.args)&&l.args[0]===t&&l.env&&l.env.INFERNOFLOW_PROJECT_DIR===i)return{updated:!1,backup:e};const c={...l&&l.env?l.env:{}};i&&(c.INFERNOFLOW_PROJECT_DIR=i),r.mcpServers.infernoflow={command:"node",args:[t],env:c};const p=JSON.stringify(r,null,2);return a.mkdirSync(n,{recursive:!0}),a.writeFileSync(s,p,"utf8"),{updated:!0,path:s,backup:e}}function V(t){const i=u.join(t,".git");if(!a.existsSync(i))return{installed:!1,skipped:"not-a-git-repo"};const s=u.join(i,"hooks"),n=u.join(s,"post-commit"),r='infernoflow log "commit: $(git log -1 --pretty=%s)" --type note --source git-hook --auto --quiet >/dev/null 2>&1 || true';try{if(a.mkdirSync(s,{recursive:!0}),a.existsSync(n)){const e=a.readFileSync(n,"utf8");if(e.includes("infernoflow"))return{installed:!1,already:!0};const l=e.replace(/\s*$/,"")+`
2
2
 
3
3
  # infernoflow auto-capture
4
- `+o+`
5
- `;c.writeFileSync(t,p,"utf8")}else{const s=["#!/bin/sh","# infernoflow: auto-capture the commit subject into session memory.","# Best-effort and non-blocking \u2014 never fails a commit.",o,""].join(`
6
- `);c.writeFileSync(t,s,"utf8")}try{c.chmodSync(t,493)}catch{}return{installed:!0,path:t}}catch(s){return{installed:!1,error:s.message}}}const W=`#!/usr/bin/env node
4
+ `+r+`
5
+ `;a.writeFileSync(n,l,"utf8")}else{const e=["#!/bin/sh","# infernoflow: auto-capture the commit subject into session memory.","# Best-effort and non-blocking \u2014 never fails a commit.",r,""].join(`
6
+ `);a.writeFileSync(n,e,"utf8")}try{a.chmodSync(n,493)}catch{}return{installed:!0,path:n}}catch(e){return{installed:!1,error:e.message}}}const q=`#!/usr/bin/env node
7
7
  // infernoflow UserPromptSubmit hook (Claude Code / Cursor).
8
8
  // Logs a best-effort 'attempt' entry when the user's prompt shows frustration,
9
9
  // so the highest-value capture signal doesn't depend on the model remembering.
@@ -27,9 +27,9 @@ if (prompt && MARKERS.some((re) => re.test(prompt))) {
27
27
  } catch {}
28
28
  }
29
29
  process.exit(0);
30
- `;function q(r){const n=a.join(r,".claude","hooks"),i=a.join(n,"log-frustration.mjs");try{c.mkdirSync(n,{recursive:!0}),c.writeFileSync(i,W,"utf8");try{c.chmodSync(i,493)}catch{}}catch(l){return{installed:!1,error:l.message}}const t=a.join(r,".claude","settings.json"),{data:o}=m(t);(!o.hooks||typeof o.hooks!="object")&&(o.hooks={}),Array.isArray(o.hooks.UserPromptSubmit)||(o.hooks.UserPromptSubmit=[]);const s="node .claude/hooks/log-frustration.mjs",p=o.hooks.UserPromptSubmit.some(l=>Array.isArray(l?.hooks)&&l.hooks.some(e=>typeof e?.command=="string"&&e.command.includes("log-frustration.mjs")));p||o.hooks.UserPromptSubmit.push({hooks:[{type:"command",command:s}]});try{c.mkdirSync(a.dirname(t),{recursive:!0}),c.writeFileSync(t,JSON.stringify(o,null,2),"utf8")}catch(l){return{installed:!0,registered:!1,error:l.message}}return{installed:!0,registered:!p}}const K=["infernoflow_status","infernoflow_run","infernoflow_apply","infernoflow_check","infernoflow_context","infernoflow_implement","infernoflow_git_drift","infernoflow_scan_ui","infernoflow_review","amp_read","amp_write","amp_search","amp_handoff","amp_health"];function G(r){const n=a.join(S.homedir(),".claude.json"),{data:i,backup:t}=m(n);i.mcpServers||(i.mcpServers={});const o=i.mcpServers.infernoflow;if(o&&o.args&&o.args[0]===r)return{updated:!1,backup:t};i.mcpServers.infernoflow={command:"node",args:[r]};const s=JSON.stringify(i,null,2).replace(/\u0000+/g,"");return c.writeFileSync(n,s,"utf8"),{updated:!0,path:n,backup:t}}function B(r,n){const i=a.join(r,".vscode"),t=a.join(i,"mcp.json"),{data:o,backup:s}=m(t);o.servers||(o.servers={});const p=o.servers.infernoflow;return p&&p.args&&p.args[0]===n?{updated:!1,backup:s}:(o.servers.infernoflow={type:"stdio",command:"node",args:[n]},c.mkdirSync(i,{recursive:!0}),c.writeFileSync(t,JSON.stringify(o,null,2)+`
31
- `,"utf8"),{updated:!0,path:t,backup:s})}function z(r,n){const i=a.join(r,".claude"),t=a.join(i,"settings.json"),{data:o}=m(t),s=new Set(o.allowedTools||[]);for(const l of K)s.add(`mcp__infernoflow__${l}`);const p={...o,allowedTools:[...s]};return c.mkdirSync(i,{recursive:!0}),c.writeFileSync(t,JSON.stringify(p,null,2),"utf8"),t}function Q(r,{silent:n=!1}={}){const i=v(),t=n?()=>{}:e=>k(e),o=n?()=>{}:e=>O(e),s={mcpServer:!1,claudeJson:!1,claudeSettings:!1,claudeDesktop:!1,gitHooks:!1,captureHook:!1,backups:[]},p=a.join(i,"cursor","inferno-mcp-server.mjs"),l=a.join(r,".cursor","inferno-mcp-server.mjs");try{c.existsSync(l)||(c.mkdirSync(a.dirname(l),{recursive:!0}),c.copyFileSync(p,l),s.mcpServer=!0,t("Copied MCP server \u2192 "+u(".cursor/inferno-mcp-server.mjs")))}catch(e){o("MCP server copy skipped: "+e.message)}try{const e=G(l);e.updated&&(s.claudeJson=!0,t("Registered MCP server in "+u("~/.claude.json"))),e.backup&&(s.backups.push(e.backup),o("~/.claude.json was unreadable \u2014 backed up to "+e.backup+" before rewrite"))}catch(e){o("~/.claude.json update skipped: "+e.message)}try{B(r,l).updated&&(s.vscodeMcp=!0,t("Registered MCP server in "+u(".vscode/mcp.json")+C(" (Copilot Chat)")))}catch(e){o(".vscode/mcp.json update skipped: "+e.message)}try{const e=a.join(r,".cursor","mcp.json"),{data:d}=m(e);d.mcpServers||(d.mcpServers={});const g=d.mcpServers.infernoflow;(!g||!g.args||g.args[0]!==l)&&(d.mcpServers.infernoflow={command:"node",args:[l],env:{}},c.mkdirSync(a.dirname(e),{recursive:!0}),c.writeFileSync(e,JSON.stringify(d,null,2)+`
32
- `,"utf8"),s.cursorMcp=!0,t("Registered MCP server in "+u(".cursor/mcp.json")))}catch(e){o(".cursor/mcp.json update skipped: "+e.message)}try{z(r,!1),s.claudeSettings=!0,t("Pre-approved infernoflow tools in "+u(".claude/settings.json"))}catch(e){o(".claude/settings.json skipped: "+e.message)}try{const e=V(l);e.updated&&(s.claudeDesktop=!0,t("Registered MCP server in "+u("claude_desktop_config.json")+C(" (Claude Desktop app)"))),e.backup&&(s.backups.push(e.backup),o("Claude Desktop config was unreadable \u2014 backed up to "+e.backup))}catch(e){o("Claude Desktop config skipped: "+e.message)}try{E(r).installed&&(s.gitHooks=!0,t("Installed git post-commit hook \u2192 "+u(".git/hooks/post-commit")))}catch(e){o("git hook install skipped: "+e.message)}try{q(r).installed&&(s.captureHook=!0,t("Installed capture hook \u2192 "+u(".claude/hooks/log-frustration.mjs")))}catch(e){o("capture hook install skipped: "+e.message)}return s}async function ce(r){const n=process.cwd(),i=r.includes("--force")||r.includes("-f"),t=r.includes("--yes")||r.includes("-y"),o=v();A("infernoflow setup");const{ideDetected:s}=J("auto");w(`IDE detected: ${y(s==="cursor"?"Cursor":s==="vscode"?"VS Code":s==="windsurf"?"Windsurf":"unknown")}`);const l=a.join(n,".ai-memory");c.existsSync(l)?k(".ai-memory/ already exists \u2014 skipping init"):(console.log(`
33
- ${j(".ai-memory/")} not found \u2014 running init ...
34
- `),U(t?"init --yes":"init"));const e=I(r);if(Object.keys(e).length)try{H(n,e),k("Injection config updated \u2192 "+JSON.stringify(e))}catch{}console.log(),w("Wiring up MCP servers for Cursor / VS Code Copilot / Claude Code ...");const d=Q(n,{silent:!1});console.log(),N("infernoflow ready"),console.log(`
35
- ${y("What was set up:")}`),console.log(` ${f("\u2714")} MCP server installed \u2192 ${u(".cursor/inferno-mcp-server.mjs")}`),d.cursorMcp&&console.log(` ${f("\u2714")} Cursor MCP config \u2192 ${u(".cursor/mcp.json")}`),d.vscodeMcp&&console.log(` ${f("\u2714")} VS Code Copilot MCP config \u2192 ${u(".vscode/mcp.json")}`),d.claudeJson&&console.log(` ${f("\u2714")} Claude Code MCP config \u2192 ${u("~/.claude.json")}`),d.claudeSettings&&console.log(` ${f("\u2714")} Auto-approved tools \u2192 ${u(".claude/settings.json")}`),d.claudeDesktop&&console.log(` ${f("\u2714")} Claude Desktop MCP config \u2192 ${u("claude_desktop_config.json")}`),d.gitHooks&&console.log(` ${f("\u2714")} Git post-commit hook \u2192 ${u(".git/hooks/post-commit")}`),d.captureHook&&console.log(` ${f("\u2714")} Capture hook (Claude Code/Cursor) \u2192 ${u(".claude/hooks/log-frustration.mjs")}`);try{const{detectStaleMcpRuntime:g}=await import("../mcpRuntime.mjs"),{readFileSync:b}=await import("node:fs"),{dirname:_,join:x}=await import("node:path"),{fileURLToPath:$}=await import("node:url"),P=_($(import.meta.url)),M=x(P,"..","..","package.json"),R=JSON.parse(b(M,"utf8")).version,h=g(n,R);h&&(console.log(),console.log(` ${j("\u26A0")} ${y("Restart required:")} ${h.message}`))}catch{}console.log(),console.log(` ${y("Next step:")} Restart your AI tool. Test by asking:`),console.log(` ${u('"call the amp_write tool with a test note"')}`),console.log()}export{K as MCP_TOOLS,Q as autoSetupMcp,L as claudeDesktopConfigPath,q as installClaudeCodeCaptureHook,E as installGitHooks,ce as setupCommand,V as updateClaudeDesktopConfig,G as updateClaudeJson,B as updateVscodeMcpJson,z as writeClaudeSettings};
30
+ `;function K(t){const i=u.join(t,".claude","hooks"),s=u.join(i,"log-frustration.mjs");try{a.mkdirSync(i,{recursive:!0}),a.writeFileSync(s,q,"utf8");try{a.chmodSync(s,493)}catch{}}catch(c){return{installed:!1,error:c.message}}const n=u.join(t,".claude","settings.json"),{data:r}=y(n);(!r.hooks||typeof r.hooks!="object")&&(r.hooks={}),Array.isArray(r.hooks.UserPromptSubmit)||(r.hooks.UserPromptSubmit=[]);const e="node .claude/hooks/log-frustration.mjs",l=r.hooks.UserPromptSubmit.some(c=>Array.isArray(c?.hooks)&&c.hooks.some(p=>typeof p?.command=="string"&&p.command.includes("log-frustration.mjs")));l||r.hooks.UserPromptSubmit.push({hooks:[{type:"command",command:e}]});try{a.mkdirSync(u.dirname(n),{recursive:!0}),a.writeFileSync(n,JSON.stringify(r,null,2),"utf8")}catch(c){return{installed:!0,registered:!1,error:c.message}}return{installed:!0,registered:!l}}const G=["infernoflow_status","infernoflow_run","infernoflow_apply","infernoflow_check","infernoflow_context","infernoflow_implement","infernoflow_git_drift","infernoflow_scan_ui","infernoflow_review","amp_read","amp_write","amp_search","amp_handoff","amp_health"];function B(t,i){const s=u.join(v.homedir(),".claude.json"),{data:n,backup:r}=y(s);n.mcpServers||(n.mcpServers={});const e=n.mcpServers.infernoflow;if(e&&e.args&&e.args[0]===t&&e.env&&e.env.INFERNOFLOW_PROJECT_DIR===i)return{updated:!1,backup:r};const l={...e&&e.env?e.env:{}};i&&(l.INFERNOFLOW_PROJECT_DIR=i),n.mcpServers.infernoflow={command:"node",args:[t],env:l};const c=JSON.stringify(n,null,2).replace(/\u0000+/g,"");return a.writeFileSync(s,c,"utf8"),{updated:!0,path:s,backup:r}}function z(t,i,s){const n=u.join(t,".vscode"),r=u.join(n,"mcp.json"),{data:e,backup:l}=y(r);e.servers||(e.servers={});const c=e.servers.infernoflow;if(c&&c.args&&c.args[0]===i&&c.env&&c.env.INFERNOFLOW_PROJECT_DIR===s)return{updated:!1,backup:l};const p={...c&&c.env?c.env:{}};return s&&(p.INFERNOFLOW_PROJECT_DIR=s),e.servers.infernoflow={type:"stdio",command:"node",args:[i],env:p},a.mkdirSync(n,{recursive:!0}),a.writeFileSync(r,JSON.stringify(e,null,2)+`
31
+ `,"utf8"),{updated:!0,path:r,backup:l}}function Q(t,i){const s=u.join(t,".claude"),n=u.join(s,"settings.json"),{data:r}=y(n),e=new Set(r.allowedTools||[]);for(const c of G)e.add(`mcp__infernoflow__${c}`);const l={...r,allowedTools:[...e]};return a.mkdirSync(s,{recursive:!0}),a.writeFileSync(n,JSON.stringify(l,null,2),"utf8"),n}function X(t,{silent:i=!1}={}){const s=_(),n=i?()=>{}:o=>h(o),r=i?()=>{}:o=>M(o),e={mcpServer:!1,claudeJson:!1,claudeSettings:!1,claudeDesktop:!1,gitHooks:!1,captureHook:!1,backups:[]},l=u.join(s,"cursor","inferno-mcp-server.mjs"),c=u.join(t,".cursor","inferno-mcp-server.mjs"),p=(()=>{try{return T(t)}catch{return t}})();try{a.existsSync(c)||(a.mkdirSync(u.dirname(c),{recursive:!0}),a.copyFileSync(l,c),e.mcpServer=!0,n("Copied MCP server \u2192 "+d(".cursor/inferno-mcp-server.mjs")))}catch(o){r("MCP server copy skipped: "+o.message)}try{const o=B(c,p);o.updated&&(e.claudeJson=!0,n("Registered MCP server in "+d("~/.claude.json"))),o.backup&&(e.backups.push(o.backup),r("~/.claude.json was unreadable \u2014 backed up to "+o.backup+" before rewrite"))}catch(o){r("~/.claude.json update skipped: "+o.message)}try{z(t,c,p).updated&&(e.vscodeMcp=!0,n("Registered MCP server in "+d(".vscode/mcp.json")+j(" (Copilot Chat)")))}catch(o){r(".vscode/mcp.json update skipped: "+o.message)}try{const o=u.join(t,".cursor","mcp.json"),{data:g}=y(o);g.mcpServers||(g.mcpServers={});const f=g.mcpServers.infernoflow;(!f||!f.args||f.args[0]!==c||!f.env||f.env.INFERNOFLOW_PROJECT_DIR!==p)&&(g.mcpServers.infernoflow={command:"node",args:[c],env:{...f&&f.env?f.env:{},INFERNOFLOW_PROJECT_DIR:p}},a.mkdirSync(u.dirname(o),{recursive:!0}),a.writeFileSync(o,JSON.stringify(g,null,2)+`
32
+ `,"utf8"),e.cursorMcp=!0,n("Registered MCP server in "+d(".cursor/mcp.json")))}catch(o){r(".cursor/mcp.json update skipped: "+o.message)}try{Q(t,!1),e.claudeSettings=!0,n("Pre-approved infernoflow tools in "+d(".claude/settings.json"))}catch(o){r(".claude/settings.json skipped: "+o.message)}try{const o=U(c,p);o.updated&&(e.claudeDesktop=!0,n("Registered MCP server in "+d("claude_desktop_config.json")+j(" (Claude Desktop app)"))),o.backup&&(e.backups.push(o.backup),r("Claude Desktop config was unreadable \u2014 backed up to "+o.backup))}catch(o){r("Claude Desktop config skipped: "+o.message)}try{V(t).installed&&(e.gitHooks=!0,n("Installed git post-commit hook \u2192 "+d(".git/hooks/post-commit")))}catch(o){r("git hook install skipped: "+o.message)}try{K(t).installed&&(e.captureHook=!0,n("Installed capture hook \u2192 "+d(".claude/hooks/log-frustration.mjs")))}catch(o){r("capture hook install skipped: "+o.message)}return e}async function le(t){const i=process.cwd(),s=t.includes("--force")||t.includes("-f"),n=t.includes("--yes")||t.includes("-y"),r=_();J("infernoflow setup");const{ideDetected:e}=I("auto");w(`IDE detected: ${k(e==="cursor"?"Cursor":e==="vscode"?"VS Code":e==="windsurf"?"Windsurf":"unknown")}`);const c=u.join(i,".ai-memory");a.existsSync(c)?h(".ai-memory/ already exists \u2014 skipping init"):(console.log(`
33
+ ${C(".ai-memory/")} not found \u2014 running init ...
34
+ `),W(n?"init --yes":"init"));const p=A(t);if(Object.keys(p).length)try{E(i,p),h("Injection config updated \u2192 "+JSON.stringify(p))}catch{}console.log(),w("Wiring up MCP servers for Cursor / VS Code Copilot / Claude Code ...");const o=X(i,{silent:!1});console.log(),D("infernoflow ready"),console.log(`
35
+ ${k("What was set up:")}`),console.log(` ${m("\u2714")} MCP server installed \u2192 ${d(".cursor/inferno-mcp-server.mjs")}`),o.cursorMcp&&console.log(` ${m("\u2714")} Cursor MCP config \u2192 ${d(".cursor/mcp.json")}`),o.vscodeMcp&&console.log(` ${m("\u2714")} VS Code Copilot MCP config \u2192 ${d(".vscode/mcp.json")}`),o.claudeJson&&console.log(` ${m("\u2714")} Claude Code MCP config \u2192 ${d("~/.claude.json")}`),o.claudeSettings&&console.log(` ${m("\u2714")} Auto-approved tools \u2192 ${d(".claude/settings.json")}`),o.claudeDesktop&&console.log(` ${m("\u2714")} Claude Desktop MCP config \u2192 ${d("claude_desktop_config.json")}`),o.gitHooks&&console.log(` ${m("\u2714")} Git post-commit hook \u2192 ${d(".git/hooks/post-commit")}`),o.captureHook&&console.log(` ${m("\u2714")} Capture hook (Claude Code/Cursor) \u2192 ${d(".claude/hooks/log-frustration.mjs")}`);try{const{detectStaleMcpRuntime:g}=await import("../mcpRuntime.mjs"),{readFileSync:f}=await import("node:fs"),{dirname:b,join:R}=await import("node:path"),{fileURLToPath:O}=await import("node:url"),P=b(O(import.meta.url)),x=R(P,"..","..","package.json"),F=JSON.parse(f(x,"utf8")).version,S=g(i,F);S&&(console.log(),console.log(` ${C("\u26A0")} ${k("Restart required:")} ${S.message}`))}catch{}console.log(),console.log(` ${k("Next step:")} Restart your AI tool. Test by asking:`),console.log(` ${d('"call the amp_write tool with a test note"')}`),console.log()}export{G as MCP_TOOLS,X as autoSetupMcp,H as claudeDesktopConfigPath,K as installClaudeCodeCaptureHook,V as installGitHooks,le as setupCommand,U as updateClaudeDesktopConfig,B as updateClaudeJson,z as updateVscodeMcpJson,Q as writeClaudeSettings};
@@ -114,6 +114,7 @@ const INFERNOFLOW_BIN = resolveInfernoflowBin();
114
114
  let ampIo = null;
115
115
  let refreshRuleFiles = null;
116
116
  let harvestSnapshot = null;
117
+ let findProjectRoot = null;
117
118
  if (INFERNOFLOW_ROOT) {
118
119
  try {
119
120
  for (const c of [
@@ -134,9 +135,37 @@ if (INFERNOFLOW_ROOT) {
134
135
  ]) {
135
136
  if (fs.existsSync(c)) { harvestSnapshot = (await import(pathToFileURL(c).href)).harvestSnapshot; break; }
136
137
  }
138
+ for (const c of [
139
+ path.join(INFERNOFLOW_ROOT, "lib", "projectRoot.mjs"),
140
+ path.join(INFERNOFLOW_ROOT, "dist", "lib", "projectRoot.mjs"),
141
+ ]) {
142
+ if (fs.existsSync(c)) { findProjectRoot = (await import(pathToFileURL(c).href)).findProjectRoot; break; }
143
+ }
137
144
  } catch { /* swallow — fallback path handles it */ }
138
145
  }
139
146
 
147
+ // ── Project directory resolution ───────────────────────────────────────────
148
+ // NEVER trust process.cwd() to locate .ai-memory. IDEs and the desktop bridge
149
+ // routinely launch this server from the wrong place — a monorepo parent, or
150
+ // C:\WINDOWS\system32 on Windows — so every amp_* tool looked for .ai-memory
151
+ // relative to that wrong cwd and failed with "not initialized". Resolve the
152
+ // real project root once, in priority order:
153
+ // 1. INFERNOFLOW_PROJECT_DIR — explicit override (init can bake this into the MCP config)
154
+ // 2. WORKSPACE_FOLDER_PATHS[0] — the folder the IDE actually opened (Cursor / VS Code set this)
155
+ // 3. findProjectRoot(cwd) — walk up to .ai-memory / .git / a manifest (subfolder case)
156
+ // 4. process.cwd() — last resort
157
+ function resolveProjectDir() {
158
+ const hint = process.env.INFERNOFLOW_PROJECT_DIR
159
+ || (process.env.WORKSPACE_FOLDER_PATHS || "").split(path.delimiter).filter(Boolean)[0];
160
+ const start = (hint && fs.existsSync(hint)) ? hint : process.cwd();
161
+ if (typeof findProjectRoot === "function") {
162
+ try { return findProjectRoot(start); } catch { /* fall through to start */ }
163
+ }
164
+ return start;
165
+ }
166
+ const PROJECT_DIR = resolveProjectDir();
167
+ process.stderr.write(`[infernoflow MCP] project dir: ${PROJECT_DIR}\n`);
168
+
140
169
  // ── Clean-tree policy: regenerate rule files ONCE at boot ──────────────────
141
170
  // Historically, every amp_write rewrote CLAUDE.md / .cursorrules. That
142
171
  // dirtied tracked files dozens of times per session and blocked git
@@ -145,7 +174,7 @@ if (INFERNOFLOW_ROOT) {
145
174
  // the session. amp_read serves runtime queries; rule-file content is
146
175
  // for cold-start injection only.
147
176
  if (refreshRuleFiles) {
148
- try { refreshRuleFiles(process.cwd()); } catch { /* non-fatal */ }
177
+ try { refreshRuleFiles(PROJECT_DIR); } catch { /* non-fatal */ }
149
178
  }
150
179
 
151
180
  // ── Boot stamp: record which MCP version is running ──────────────────────
@@ -163,7 +192,7 @@ try {
163
192
  try { runtimeVersion = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")).version || runtimeVersion; }
164
193
  catch {}
165
194
  }
166
- const memDir = path.join(process.cwd(), ".ai-memory");
195
+ const memDir = path.join(PROJECT_DIR, ".ai-memory");
167
196
  if (fs.existsSync(memDir)) {
168
197
  fs.writeFileSync(path.join(memDir, ".mcp-runtime.json"), JSON.stringify({
169
198
  version: runtimeVersion,
@@ -200,7 +229,7 @@ function runCmd(args, env = {}) {
200
229
  : `"${INFERNOFLOW_BIN}" ${args}`;
201
230
  return execSync(cmd, {
202
231
  encoding: "utf8",
203
- cwd: process.cwd(),
232
+ cwd: PROJECT_DIR,
204
233
  timeout: 30000,
205
234
  env: { ...process.env, ...env },
206
235
  });
@@ -246,7 +275,7 @@ const TOOLS = [
246
275
 
247
276
  // ── git drift detection (inline — no external imports in this template file) ─
248
277
  function detectGitDrift(sinceCommits) {
249
- const cwd = process.cwd();
278
+ const cwd = PROJECT_DIR;
250
279
  const infernoDir = path.join(cwd, "inferno");
251
280
 
252
281
  const runGit = (cmd) => {
@@ -402,7 +431,7 @@ function handleTool(id, name, input) {
402
431
  if (input.tags && input.tags.length) entry.tags = input.tags;
403
432
  if (input.detail && String(input.detail).trim()) entry.detail = String(input.detail);
404
433
  try {
405
- const written = ampIo.appendEntry(process.cwd(), entry);
434
+ const written = ampIo.appendEntry(PROJECT_DIR, entry);
406
435
  // NOTE: rule-file refresh deliberately NOT called here — clean-tree
407
436
  // policy regenerates them once at MCP boot only. Doing it on every
408
437
  // write dirties tracked files and blocks `git checkout`. Within a
@@ -446,10 +475,10 @@ function handleTool(id, name, input) {
446
475
  if (input.note && String(input.note).trim()) {
447
476
  entry.detail = String(input.note);
448
477
  } else if (harvestSnapshot) {
449
- try { const snap = harvestSnapshot(process.cwd()); if (snap) entry.detail = snap; } catch { /* best-effort */ }
478
+ try { const snap = harvestSnapshot(PROJECT_DIR); if (snap) entry.detail = snap; } catch { /* best-effort */ }
450
479
  }
451
480
  try {
452
- const written = ampIo.appendEntry(process.cwd(), entry);
481
+ const written = ampIo.appendEntry(PROJECT_DIR, entry);
453
482
  text = `🔖 Bookmark saved: ${written.msg} (${written.id})` +
454
483
  (written.meta && written.meta.detailRef ? `\n context: ${written.meta.detailRef}` : "");
455
484
  } catch (err) {
@@ -467,8 +496,8 @@ function handleTool(id, name, input) {
467
496
  return sendError(id, -32000, `infernoflow switch failed: ${switchResult.message}\n${switchResult.stderr || switchResult.stdout || ""}`.trim());
468
497
  }
469
498
  try {
470
- const ampPath = path.join(process.cwd(), ".ai-memory", "handoff.md");
471
- const legacyPath = path.join(process.cwd(), "inferno", "HANDOFF.md");
499
+ const ampPath = path.join(PROJECT_DIR, ".ai-memory", "handoff.md");
500
+ const legacyPath = path.join(PROJECT_DIR, "inferno", "HANDOFF.md");
472
501
  const target = fs.existsSync(ampPath) ? ampPath : legacyPath;
473
502
  text = fs.readFileSync(target, "utf8");
474
503
  if (input.format === "json") {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "infernoflow",
3
- "version": "0.44.17",
4
- "description": "Persistent memory for AI coding sessions \u2014 captures what agents can't infer from code alone. Works with Copilot, Cursor, Claude, and Windsurf.",
3
+ "version": "0.44.18",
4
+ "description": "Persistent memory for AI coding sessions — captures what agents can't infer from code alone. Works with Copilot, Cursor, Claude, and Windsurf.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "infernoflow": "dist/bin/infernoflow.mjs"