infernoflow 0.44.16 → 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.
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import*as r from"node:fs";import*as n from"node:path";import{fileURLToPath as m}from"node:url";const f=n.dirname(m(import.meta.url));function l(){return n.resolve(f,"../templates")}const c=new Set;function p(e){return r.existsSync(n.join(e,".ai-memory"))||r.existsSync(n.join(e,"inferno"))}function a(e,i){if(!r.existsSync(e))return 0;let t=0;for(const s of r.readdirSync(e,{withFileTypes:!0})){const u=n.join(e,s.name),o=n.join(i,s.name);if(s.isDirectory()){t+=a(u,o);continue}r.existsSync(o)||(r.mkdirSync(n.dirname(o),{recursive:!0}),r.copyFileSync(u,o),t++)}return t}function d(e){try{if(!e||c.has(e)||(c.add(e),!p(e)))return 0;const i=l();let t=0;return t+=a(n.join(i,"skills"),n.join(e,".claude","skills")),t+=a(n.join(i,"agents"),n.join(e,".claude","agents")),t}catch{return 0}}export{d as ensureClaudeAssets};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import*as
|
|
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
|
-
`+
|
|
5
|
-
`;
|
|
6
|
-
`);
|
|
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
|
|
31
|
-
`,"utf8"),{updated:!0,path:
|
|
32
|
-
`,"utf8"),
|
|
33
|
-
${
|
|
34
|
-
`),
|
|
35
|
-
${
|
|
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};
|
package/dist/lib/ruleFiles.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import*as u from"node:fs";import*as y from"node:path";import{execSync as j}from"node:child_process";import{readEntries as v,readConfig as x}from"./amp/io.mjs";const g="<!-- infernoflow:start -->",m="<!-- infernoflow:end -->",w=[".cursorrules","CLAUDE.md",y.join(".github","copilot-instructions.md")],k=4,C=5,S=200;function A(e){const o=e&&e.config||{},t=o.injection&&typeof o.injection=="object"?o.injection:{},n=Array.isArray(o.inject)&&!o.inject.includes("all")?o.inject:null,r=["full","compact","off"].includes(t.protocolStyle)?t.protocolStyle:"compact",s=t.includeProtocol===!1?"off":r;return{maxEntries:Number.isInteger(t.maxEntries)&&t.maxEntries>=0?t.maxEntries:k,maxCommits:Number.isInteger(t.maxCommits)&&t.maxCommits>=0?t.maxCommits:C,maxEntryChars:Number.isInteger(t.maxEntryChars)&&t.maxEntryChars>0?t.maxEntryChars:S,targets:Array.isArray(t.targets)&&t.targets.length?t.targets:n||w,protocolStyle:s,includeProtocol:s!=="off"}}function
|
|
1
|
+
import*as u from"node:fs";import*as y from"node:path";import{execSync as j}from"node:child_process";import{readEntries as v,readConfig as x}from"./amp/io.mjs";import{ensureClaudeAssets as T}from"./claudeAssets.mjs";const g="<!-- infernoflow:start -->",m="<!-- infernoflow:end -->",w=[".cursorrules","CLAUDE.md",y.join(".github","copilot-instructions.md")],k=4,C=5,S=200;function A(e){const o=e&&e.config||{},t=o.injection&&typeof o.injection=="object"?o.injection:{},n=Array.isArray(o.inject)&&!o.inject.includes("all")?o.inject:null,r=["full","compact","off"].includes(t.protocolStyle)?t.protocolStyle:"compact",s=t.includeProtocol===!1?"off":r;return{maxEntries:Number.isInteger(t.maxEntries)&&t.maxEntries>=0?t.maxEntries:k,maxCommits:Number.isInteger(t.maxCommits)&&t.maxCommits>=0?t.maxCommits:C,maxEntryChars:Number.isInteger(t.maxEntryChars)&&t.maxEntryChars>0?t.maxEntryChars:S,targets:Array.isArray(t.targets)&&t.targets.length?t.targets:n||w,protocolStyle:s,includeProtocol:s!=="off"}}function F(e){if(!u.existsSync(e))return{stripped:!1};const o=u.readFileSync(e,"utf8"),t=o.indexOf(g),n=o.indexOf(m);if(t===-1||n===-1||n<=t)return{stripped:!1};const r=o.slice(0,t).replace(/\s+$/,""),s=o.slice(n+m.length).replace(/^\s+/,""),i=r&&s?r+`
|
|
2
2
|
|
|
3
3
|
`+s:r||s;return u.writeFileSync(e,i?i.replace(/\s*$/,"")+`
|
|
4
|
-
`:"","utf8"),{stripped:!0}}function E(e="compact"){return e==="off"?"":e==="full"?
|
|
5
|
-
`)}function
|
|
6
|
-
`)}function
|
|
7
|
-
`)}function
|
|
4
|
+
`:"","utf8"),{stripped:!0}}function E(e="compact"){return e==="off"?"":e==="full"?D():M()}function M(){return["### Memory protocol","",'Use the `amp_write` and `amp_bookmark` MCP tools **proactively** (without being asked): log a one-sentence entry when the user hits frustration (`!!` / "not working" / "retry" \u2192 `attempt`), a `decision` is made, or you learn a non-obvious `gotcha`; drop a bookmark on "bookmark this". Don\'t log what\'s re-derivable from the code. The full trigger list and field shapes are in the tool descriptions \u2014 if the `amp_*` tools aren\'t visible, load them first (Claude Code: `ToolSearch` with query `infernoflow`).'].join(`
|
|
5
|
+
`)}function D(){const e=[];return e.push("### Memory protocol \u2014 capture as you go"),e.push(""),e.push("You have the `amp_write` MCP tool. **Use it proactively** (without the user asking) whenever you detect any of these moments. Capture the lesson in one sentence so the next AI session inherits it."),e.push(""),e.push("**If `amp_write` is not in your visible tools**, your client uses deferred MCP loading \u2014 call your tool-discovery mechanism (Claude Code: `ToolSearch` with query `infernoflow`) to load the `amp_*` tools BEFORE proceeding. Don't silently skip the protocol because the tool isn't visible yet."),e.push(""),e.push("| When you see\u2026 | Call `amp_write` with\u2026 |"),e.push("|---|---|"),e.push("| User types `!!`, `!!!`, `not working`, `still broken`, `no change`, `same error`, `retry` / `retrying`, repeated frustration | `{type: 'attempt', msg: '<one-line: what was tried and how it failed>', file: '<active file>'}` |"),e.push("| You generate a plan, numbered steps, or `Plan:` / `Approach:` block | `{type: 'note', msg: 'Plan: <one-line summary>', file: '<focus file>'}` |"),e.push("| User picks X over Y, decides an architectural approach, or you both agree on a direction | `{type: 'decision', msg: '<X> chosen over <Y> because <reason>', file: '<file>'}` |"),e.push("| Discovered non-obvious behavior, API quirk, or `aha \u2014 turns out\u2026` moment | `{type: 'gotcha', msg: '<one-line>', file: '<file>', line: <line if known>}` |"),e.push("| User says `switching` / `back to X`, or ~30 min passed without logging | `{type: 'note', msg: 'Session snapshot \u2014 worked on: <X>. Open threads: <Y>.'}` |"),e.push(""),e.push("**Do NOT log:** typos, syntax errors, obvious bugs visible in the code, anything the next AI can infer by re-reading the file."),e.push("**Do log:** anything that taught you something non-derivable from code."),e.push("**Be concise:** entries should be one sentence. Future-you will skim them."),e.push("**Bookmark resume points:** when the user says `bookmark this` / `mark this point`, or the context window is filling up while work is mid-flight, call `amp_bookmark` with a short `label` (omit `note` to auto-capture the session transcript) \u2014 so they can jump back to that exact point in the next session."),e.push("**When you use a \u{1F525} memory entry in your reply, briefly cite it** \u2014 e.g. `\u{1F525} (from infernoflow memory) gotcha at src/api.js:42: ...` \u2014 so the user can see which guidance came from project memory vs. your own inference."),e.join(`
|
|
6
|
+
`)}function R(e="compact"){const o=E(e);return[g,"<!-- Auto-managed by infernoflow. Don't edit between these markers. -->","## Project memory (infernoflow)","",...o?[o,""]:[],'_No entries yet. They\'ll appear here as you and your AI tools log them \u2014 run `infernoflow log "..."` or call `amp_write` from any MCP-aware AI._',m].join(`
|
|
7
|
+
`)}function N(e){const o=e.indexOf("<!-- AMP:START -->"),t=e.indexOf("<!-- AMP:END -->");if(o===-1||t===-1||t<=o)return e;const n=e.slice(0,o).replace(/\s+$/,""),r=e.slice(t+16).replace(/^\s+/,"");return(n?n+(r?`
|
|
8
8
|
|
|
9
9
|
`:""):"")+r}function I(e,o){const t=y.dirname(e);if(u.existsSync(t)||u.mkdirSync(t,{recursive:!0}),!u.existsSync(e))return u.writeFileSync(e,o+`
|
|
10
|
-
`,"utf8"),{created:!0,updated:!1};let n=u.readFileSync(e,"utf8");n=
|
|
10
|
+
`,"utf8"),{created:!0,updated:!1};let n=u.readFileSync(e,"utf8");n=N(n);const r=n.indexOf(g),s=n.indexOf(m);if(r===-1||s===-1){const d=o+`
|
|
11
11
|
|
|
12
|
-
`+n;return u.writeFileSync(e,d,"utf8"),{created:!1,updated:!0}}const i=n.slice(0,r),p=n.slice(s+m.length),l=i+o+p;return l===n?{created:!1,updated:!1}:(u.writeFileSync(e,l,"utf8"),{created:!1,updated:!0})}function
|
|
13
|
-
`).filter(Boolean).map(n=>{const[r,s,i]=n.split(" ");return{hash:(r||"").slice(0,7),date:s||"",subject:i||""}})}catch{return[]}}function
|
|
14
|
-
`)}function
|
|
12
|
+
`+n;return u.writeFileSync(e,d,"utf8"),{created:!1,updated:!0}}const i=n.slice(0,r),p=n.slice(s+m.length),l=i+o+p;return l===n?{created:!1,updated:!1}:(u.writeFileSync(e,l,"utf8"),{created:!1,updated:!0})}function Y(e){const{protocolStyle:o}=A(x(e)),t=R(o),n=[];for(const r of w){const s=y.join(e,r);try{const i=I(s,t);n.push({rel:r,...i})}catch(i){n.push({rel:r,error:i.message})}}return n}function P(e){return v(e)}function O(e,o=10){try{return j(`git log --pretty=format:"%h%x09%ad%x09%s" --date=short -n ${o}`,{cwd:e,encoding:"utf8",stdio:["ignore","pipe","ignore"]}).split(`
|
|
13
|
+
`).filter(Boolean).map(n=>{const[r,s,i]=n.split(" ");return{hash:(r||"").slice(0,7),date:s||"",subject:i||""}})}catch{return[]}}function $(e,o={}){const{maxEntries:t=k,maxCommits:n=C,maxEntryChars:r=S,protocolStyle:s="compact",includeProtocol:i=!0}=o,p=P(e),l=O(e,n);p.sort((a,f)=>{const h=typeof a.ts=="number"?a.ts:Date.parse(a.ts||0);return(typeof f.ts=="number"?f.ts:Date.parse(f.ts||0))-h});const d=p.slice(0,t),_={gotcha:"\u26A0",decision:"\u2713",attempt:"\u2717",note:"\xB7",detection:"\u25CB",pattern:"\u25C7"},c=[];if(c.push(g),c.push("<!-- Auto-managed by infernoflow. Don't edit between these markers. -->"),c.push("## Project memory (infernoflow)"),c.push(""),i&&s!=="off"&&(c.push(E(s)),c.push("")),l.length>0){c.push("### Recent commits");for(const a of l)c.push(`- \`${a.hash}\` _${a.date}_ ${a.subject}`);c.push("")}if(d.length>0){c.push("### Recent memory");for(const a of d){const f=a.file?` (\`${a.file}${a.line?":"+a.line:""}\`)`:"",h=(a.msg||a.summary||"").replace(/\n/g," "),b=r&&h.length>r?h.slice(0,r).trimEnd()+"\u2026":h;c.push(`- \u{1F525} ${_[a.type]||"\xB7"} **${a.type||"note"}**${f}: ${b}`)}c.push("")}return p.length===0&&l.length===0&&c.push('_No entries yet. They\'ll appear here as you and your AI tools log them \u2014 run `infernoflow log "..."` or call `amp_write` from any MCP-aware AI._'),c.push(m),c.join(`
|
|
14
|
+
`)}function X(e){T(e);const o=A(x(e)),t=$(e,o),n=i=>String(i).replace(/\\/g,"/"),r=new Set(o.targets.map(n)),s=[];for(const i of w){const p=y.join(e,i);try{if(r.has(n(i))){const l=I(p,t);s.push({rel:i,...l})}else F(p).stripped&&s.push({rel:i,stripped:!0})}catch(l){s.push({rel:i,error:l.message})}}return s}export{X as refreshRuleFilesFromMemory,A as resolveInjectionSettings,Y as writeInitRuleFiles};
|
|
@@ -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(
|
|
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(
|
|
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:
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
471
|
-
const legacyPath = path.join(
|
|
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.
|
|
4
|
-
"description": "Persistent memory for AI coding sessions
|
|
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"
|