infernoflow 0.44.12 → 0.44.14

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 infernoflow
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -89,13 +89,14 @@ In practice you barely run any of these — the MCP-aware AI does it for you. Th
89
89
 
90
90
  ---
91
91
 
92
- ## Works with GitHub Copilot Chat (via VS Code LMT — not MCP)
92
+ ## Works with GitHub Copilot Chat — via LMT + MCP
93
93
 
94
- **GitHub Copilot Chat doesn't support MCP.** Every other AI memory tool assumes MCP is the transport, so none of them work with Copilot Chat.
94
+ Copilot Chat has supported MCP servers since VS Code 1.102 (GA July 2025), so any MCP-based memory tool can plug in. infernoflow ships **two transports side by side** so Copilot picks up the same six `amp_*` tools whichever path is available:
95
95
 
96
- The infernoflow **VS Code extension** registers `amp_write` and `amp_read` as native **VS Code Language Model Tools** — Copilot's supported extension surface. So Copilot can log a gotcha or recall a past decision **on its own**, mid-conversation, the moment it notices something worth remembering. Nothing to wire up: install the extension and Copilot's tool picker shows `🔥 amp_write`. Call by hand with `#amp_write` / `#amp_read` in the chat box.
96
+ - **VS Code Language Model Tools (LMT).** The infernoflow VS Code extension registers `amp_write` and `amp_read` as native LMT tools. No per-project config, no MCP client setup — install the extension and Copilot's tool picker shows `🔥 amp_write`. Call by hand with `#amp_write` / `#amp_read` in the chat box.
97
+ - **MCP server.** `infernoflow init` wires the MCP server into `.vscode/mcp.json` too, so Copilot's MCP client can call the same tools if you prefer that route (or want them available to agent mode).
97
98
 
98
- Cursor and Claude Code get the same capability through the MCP server the CLI installs. **Same protocol, same disk file, three transports** — MCP for Cursor/Claude, LMT for Copilot, native Node in the extension.
99
+ Cursor, Claude Code, and any MCP-capable client use the same MCP server. **One product, one disk file, multiple transports** — pick whichever fits your setup; both write to the same `.ai-memory/`.
99
100
 
100
101
  ---
101
102
 
@@ -123,8 +124,9 @@ The injected memory block is paid for on every AI turn (and twice when a tool lo
123
124
  Or write the same values via CLI flags:
124
125
 
125
126
  ```bash
126
- infernoflow setup --max-memory 3 --max-commits 5 --max-entry-chars 200 --no-protocol
127
- infernoflow refresh --max-memory 3 # same; persists into amp.json
127
+ infernoflow setup --max-memory 3 --max-commits 5 --max-entry-chars 200 --protocol-style compact
128
+ infernoflow refresh --targets CLAUDE.md,.cursorrules # only these get the block; the rest are stripped
129
+ infernoflow refresh --targets auto # canonical file for the IDE you're in (kills Copilot's double-load)
128
130
  infernoflow prune --apply --max-age-days 14 # one-off cleanup
129
131
  ```
130
132
 
@@ -166,9 +168,9 @@ infernoflow prune --apply --max-age-days 14 # one-off cleanup
166
168
  |---|---|---|
167
169
  | Claude Code | `CLAUDE.md` | MCP (`amp_write`) |
168
170
  | Cursor | `.cursorrules` | MCP (`amp_write`) + `beforeSubmitPrompt` hook |
169
- | GitHub Copilot Chat (VS Code) | `.github/copilot-instructions.md` | **VS Code LMT** (`amp_write`) — extension only, no MCP |
170
- | GitHub Copilot (JetBrains) | `.github/copilot-instructions.md` | rule files only (read-only surface) |
171
- | Windsurf | `.windsurfrules` | MCP (planned) |
171
+ | GitHub Copilot Chat (VS Code) | `.github/copilot-instructions.md` | **VS Code LMT** (from the extension) + MCP (from `init`) — both wired |
172
+ | GitHub Copilot (JetBrains) | `.github/copilot-instructions.md` | MCP (`amp_write`, manual wiring) — JetBrains Copilot supports MCP (agent mode GA); `init` auto-wiring planned |
173
+ | Windsurf | `.windsurfrules` | MCP (`amp_write`, manual wiring) — Windsurf/Cascade supports MCP; `init` auto-wiring planned |
172
174
 
173
175
  The MCP server is wired by `infernoflow setup` / `init` into each tool's config file. No per-tool setup.
174
176
 
@@ -219,7 +221,7 @@ The companion extension is the visual surface over your memory:
219
221
  - **Live sidebar** — ranked-by-relevance gotchas / decisions / attempts for whatever file you're editing.
220
222
  - **Gotchas as Problems** — logged with a `file:line`? They appear as yellow squigglies in the editor and rows in the **Problems panel**, right next to your TypeScript errors. Both *you* and *Copilot* see the warning before making the same mistake again.
221
223
  - **Status bar health score** — always visible: `🔥 B 65 · ⚠3 · ✓2 · ❌1 · 📋 Switch`. Click `Switch` to copy the handoff.
222
- - **Copilot Chat integration** — `#amp_write` / `#amp_read` in the chat box; Copilot picks them up automatically via VS Code LMT (see above).
224
+ - **Copilot Chat integration** — `#amp_write` / `#amp_read` in the chat box; Copilot picks them up via VS Code Language Model Tools registered by the extension (works even without an MCP client configured).
223
225
  - **Keyboard-first logging** — `Ctrl+Alt+G` (gotcha) / `Ctrl+Alt+D` (decision) / `Ctrl+Alt+A` (ask) / `Ctrl+Alt+S` (switch) / `Ctrl+Alt+R` (recap). Right-click in the editor to log a gotcha for the current line.
224
226
 
225
227
  ```
@@ -268,13 +270,20 @@ Full policy: [SECURITY.md](./SECURITY.md). Vulnerability reports: `hello@inferno
268
270
 
269
271
  ---
270
272
 
273
+ ## Community
274
+
275
+ - 💬 [**GitHub Discussions**](https://github.com/ronmiz/infernoflow/discussions) — Q&A, ideas, show and tell. First stop for questions.
276
+ - 🐛 [**Issues**](https://github.com/ronmiz/infernoflow/issues) — bugs only. Feature requests go in Discussions → Ideas.
277
+ - 🔒 [**Security Advisories**](https://github.com/ronmiz/infernoflow/security/advisories/new) — private disclosure for vulnerabilities.
278
+
279
+ ---
280
+
271
281
  ## License
272
282
 
273
- MIT
283
+ MIT — see [`LICENSE`](./LICENSE).
274
284
 
275
285
  ## Links
276
286
 
277
- - [GitHub](https://github.com/ronmiz/infernoflow) · [npm](https://www.npmjs.com/package/infernoflow) · [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=infernoflow.infernoflow) · [Issues](https://github.com/ronmiz/infernoflow/issues) · [infernoflow.dev](https://www.infernoflow.dev)
287
+ - [GitHub](https://github.com/ronmiz/infernoflow) · [Discussions](https://github.com/ronmiz/infernoflow/discussions) · [npm](https://www.npmjs.com/package/infernoflow) · [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=infernoflow.infernoflow) · [infernoflow.dev](https://www.infernoflow.dev)
278
288
  - [AMP protocol spec](docs/protocol/PROTOCOL.md) — vendor-neutral memory format
279
- - [Dogfood: what infernoflow caught while building infernotest_01](docs/dogfood-infernotest_01.md)
280
- - [Why your AI coding assistant has amnesia](docs/BLOG_POST.md) — the case for the protocol
289
+ - [Dogfood: what infernoflow caught
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- (function(){if(process.platform!=="win32"||process.env.WT_SESSION||process.env.ConEmuPID||process.env.TERM_PROGRAM==="vscode")return;const n={"\u2500":"-","\u2501":"-","\u2550":"=","\u2502":"|","\u2503":"|","\u2551":"|","\u250C":"+","\u2510":"+","\u2514":"+","\u2518":"+","\u251C":"+","\u2524":"+","\u252C":"+","\u2534":"+","\u253C":"+","\xB7":"*","\u2192":"->","\u2190":"<-","\u2714":"[OK]","\u2713":"[OK]","\u2718":"[X]","\u2717":"[X]","\u26A0":"[!]",\u2139:"[i]"},r=new RegExp(Object.keys(n).join("|"),"g"),l=/[\u{1F000}-\u{1FAFF}\u{2600}-\u{27BF}\u{2B00}-\u{2BFF}\u{200D}]\u{FE0F}? ?/gu,f=c=>c.replace(r,p=>n[p]).replace(l,"");function u(c){const p=c.write.bind(c);c.write=function(a,...k){return typeof a=="string"?a=f(a):Buffer.isBuffer(a)&&(a=Buffer.from(f(a.toString("utf8")),"utf8")),p(a,...k)}}u(process.stdout),u(process.stderr)})();import{readFileSync as C}from"node:fs";import{dirname as $,join as y}from"node:path";import{fileURLToPath as v}from"node:url";import{bold as i,gray as e,cyan as t,red as g}from"../lib/ui/output.mjs";const b=$(v(import.meta.url));function A(o){for(const n of[y(o,"..","..","package.json"),y(o,"..","package.json")])try{return JSON.parse(C(n,"utf8"))}catch{}return{version:"0.0.0-source"}}const I=A(b),m=I.version||"0.0.0",h={log:"Append to session memory (decisions, gotchas, failed attempts)",ask:"Query memory by keyword (gotchas surface first)",switch:"Generate a handoff doc for the next AI agent / session",recap:"End-of-session summary + health score + unlogged-change surfacing",status:"Quick health check \u2014 entries, gotchas, decisions, last activity",refresh:"Rebuild CLAUDE.md / .cursorrules / copilot-instructions.md from memory",forget:"Delete a memory entry by id or unique prefix (--last for the newest)",prune:"Archive stale notes/attempts (gotchas/decisions never touched) \u2014 see --apply",bookmark:"Drop / list / recall named session resume points (bookmarks with context)",init:"Scaffold .ai-memory/ and wire the current IDE in one command",setup:"Re-run wiring (idempotent) \u2014 detects IDE, installs MCP + hooks",doctor:"Diagnose your setup \u2014 Node, git, contract, AI provider, MCP, hooks",context:"Generate AI-ready context for new sessions","install-cursor-hooks":"Install Cursor hooks (afterAgentResponse + stop)","install-vscode-copilot-hooks":"Install VS Code + Copilot agent hooks (Preview)","generate-skills":"Generate Cursor rules + skill files from your developer profile",ai:"Manage AI providers \u2014 setup, status, test, clear",telemetry:"Opt-in anonymous telemetry (on | off | status)",uninstall:"Remove infernoflow from a project (--dry-run to preview)",check:"Validate contract, capabilities, scenarios, changelog",sync:"Cross-machine sync for personal memory \u2014 status/set/clear/migrate",amp:"AI Memory Protocol \u2014 status, migrate, validate (run: infernoflow amp)"},d={log:async o=>(await import("../lib/commands/log.mjs")).logCommand(o),ask:async o=>(await import("../lib/commands/ask.mjs")).askCommand(o),switch:async o=>(await import("../lib/commands/switch.mjs")).switchCommand(o),recap:async o=>(await import("../lib/commands/recap.mjs")).recapCommand(o),status:async o=>(await import("../lib/commands/status.mjs")).statusCommand(o),refresh:async o=>(await import("../lib/commands/refresh.mjs")).refreshCommand(o),forget:async o=>(await import("../lib/commands/forget.mjs")).forgetCommand(o),prune:async o=>(await import("../lib/commands/prune.mjs")).pruneCommand(o),bookmark:async o=>(await import("../lib/commands/bookmark.mjs")).bookmarkCommand(o),init:async o=>(await import("../lib/commands/init.mjs")).initCommand(o),setup:async o=>(await import("../lib/commands/setup.mjs")).setupCommand(o),doctor:async o=>(await import("../lib/commands/doctor.mjs")).doctorCommand(o),context:async o=>(await import("../lib/commands/context.mjs")).contextCommand(o),"install-cursor-hooks":async o=>(await import("../lib/commands/installCursorHooks.mjs")).installCursorHooksCommand(o),"install-vscode-copilot-hooks":async o=>(await import("../lib/commands/installVsCodeCopilotHooks.mjs")).installVsCodeCopilotHooksCommand(o),"generate-skills":async o=>(await import("../lib/commands/generateSkills.mjs")).generateSkillsCommand(o),ai:async o=>(await import("../lib/commands/ai.mjs")).aiCommand(o),telemetry:async o=>(await import("../lib/telemetry.mjs")).telemetryCommand(o),uninstall:async o=>(await import("../lib/commands/uninstall.mjs")).uninstallCommand(o),check:async o=>(await import("../lib/commands/check.mjs")).checkCommand(o),sync:async o=>(await import("../lib/commands/sync.mjs")).syncCommand(o),amp:async o=>(await import("../lib/commands/amp.mjs")).ampCommand(o)};function G(){const o=Object.keys(h),n=Math.max(...o.map(r=>r.length),8)+1;return Object.entries(h).map(([r,l])=>` ${r.padEnd(n," ")}${l}`).join(`
3
- `)}const M={"Memory (the 5-command core)":["log","ask","switch","recap","status","refresh","forget","prune","bookmark"],Setup:["init","setup","doctor","context"],"IDE wiring":["install-cursor-hooks","install-vscode-copilot-hooks","generate-skills"],Configuration:["ai","telemetry","sync","uninstall"],Contract:["check"],"AMP (use: infernoflow amp <verb>)":["status","migrate","validate","version"]};function O(){return Object.entries(M).map(([o,n])=>` ${i(o+":")}
2
+ (function(){if(process.platform!=="win32"||process.env.WT_SESSION||process.env.ConEmuPID||process.env.TERM_PROGRAM==="vscode")return;const n={"\u2500":"-","\u2501":"-","\u2550":"=","\u2502":"|","\u2503":"|","\u2551":"|","\u250C":"+","\u2510":"+","\u2514":"+","\u2518":"+","\u251C":"+","\u2524":"+","\u252C":"+","\u2534":"+","\u253C":"+","\xB7":"*","\u2192":"->","\u2190":"<-","\u2714":"[OK]","\u2713":"[OK]","\u2718":"[X]","\u2717":"[X]","\u26A0":"[!]",\u2139:"[i]"},r=new RegExp(Object.keys(n).join("|"),"g"),l=/[\u{1F000}-\u{1FAFF}\u{2600}-\u{27BF}\u{2B00}-\u{2BFF}\u{200D}]\u{FE0F}? ?/gu,f=c=>c.replace(r,p=>n[p]).replace(l,"");function u(c){const p=c.write.bind(c);c.write=function(a,...k){return typeof a=="string"?a=f(a):Buffer.isBuffer(a)&&(a=Buffer.from(f(a.toString("utf8")),"utf8")),p(a,...k)}}u(process.stdout),u(process.stderr)})();import{readFileSync as $}from"node:fs";import{dirname as C,join as y}from"node:path";import{fileURLToPath as v}from"node:url";import{bold as i,gray as e,cyan as t,red as g}from"../lib/ui/output.mjs";const b=C(v(import.meta.url));function A(o){for(const n of[y(o,"..","..","package.json"),y(o,"..","package.json")])try{return JSON.parse($(n,"utf8"))}catch{}return{version:"0.0.0-source"}}const I=A(b),m=I.version||"0.0.0",h={log:"Append to session memory (decisions, gotchas, failed attempts)",ask:"Query memory by keyword (gotchas surface first)",switch:"Generate a handoff doc for the next AI agent / session",recap:"End-of-session summary + health score + unlogged-change surfacing",status:"Quick health check \u2014 entries, gotchas, decisions, last activity",refresh:"Rebuild CLAUDE.md / .cursorrules / copilot-instructions.md from memory",forget:"Delete a memory entry by id or unique prefix (--last for the newest)",prune:"Archive stale notes/attempts (gotchas/decisions never touched) \u2014 see --apply",bookmark:"Drop / list / recall named session resume points (bookmarks with context)",init:"Scaffold .ai-memory/ and wire the current IDE in one command",setup:"Re-run wiring (idempotent) \u2014 detects IDE, installs MCP + hooks",doctor:"Diagnose your setup \u2014 Node, git, contract, AI provider, MCP, hooks",context:"Generate AI-ready context for new sessions","install-cursor-hooks":"Install Cursor hooks (afterAgentResponse + stop)","install-vscode-copilot-hooks":"Install VS Code + Copilot agent hooks (Preview)","generate-skills":"Generate Cursor rules + skill files from your developer profile",ai:"Manage AI providers \u2014 setup, status, test, clear",telemetry:"Opt-in anonymous telemetry (on | off | status)",uninstall:"Remove infernoflow from a project (--dry-run to preview)",check:"Validate contract, capabilities, scenarios, changelog",sync:"Cross-machine sync for personal memory \u2014 status/set/clear/migrate",amp:"AI Memory Protocol \u2014 status, migrate, validate (run: infernoflow amp)"},d={log:async o=>(await import("../lib/commands/log.mjs")).logCommand(o),ask:async o=>(await import("../lib/commands/ask.mjs")).askCommand(o),switch:async o=>(await import("../lib/commands/switch.mjs")).switchCommand(o),recap:async o=>(await import("../lib/commands/recap.mjs")).recapCommand(o),status:async o=>(await import("../lib/commands/status.mjs")).statusCommand(o),refresh:async o=>(await import("../lib/commands/refresh.mjs")).refreshCommand(o),forget:async o=>(await import("../lib/commands/forget.mjs")).forgetCommand(o),prune:async o=>(await import("../lib/commands/prune.mjs")).pruneCommand(o),bookmark:async o=>(await import("../lib/commands/bookmark.mjs")).bookmarkCommand(o),init:async o=>(await import("../lib/commands/init.mjs")).initCommand(o),setup:async o=>(await import("../lib/commands/setup.mjs")).setupCommand(o),doctor:async o=>(await import("../lib/commands/doctor.mjs")).doctorCommand(o),context:async o=>(await import("../lib/commands/context.mjs")).contextCommand(o),"install-cursor-hooks":async o=>(await import("../lib/commands/installCursorHooks.mjs")).installCursorHooksCommand(o),"install-vscode-copilot-hooks":async o=>(await import("../lib/commands/installVsCodeCopilotHooks.mjs")).installVsCodeCopilotHooksCommand(o),"generate-skills":async o=>(await import("../lib/commands/generateSkills.mjs")).generateSkillsCommand(o),ai:async o=>(await import("../lib/commands/ai.mjs")).aiCommand(o),telemetry:async o=>(await import("../lib/telemetry.mjs")).telemetryCommand(o),uninstall:async o=>(await import("../lib/commands/uninstall.mjs")).uninstallCommand(o),check:async o=>(await import("../lib/commands/check.mjs")).checkCommand(o),sync:async o=>(await import("../lib/commands/sync.mjs")).syncCommand(o),amp:async o=>(await import("../lib/commands/amp.mjs")).ampCommand(o)};function G(){const o=Object.keys(h),n=Math.max(...o.map(r=>r.length),8)+1;return Object.entries(h).map(([r,l])=>` ${r.padEnd(n," ")}${l}`).join(`
3
+ `)}const M={Memory:["log","ask","switch","recap","status","refresh","forget","prune","bookmark"],Setup:["init","setup","doctor","context"],"IDE wiring":["install-cursor-hooks","install-vscode-copilot-hooks","generate-skills"],Configuration:["ai","telemetry","sync","uninstall"],Contract:["check"],"AMP (use: infernoflow amp <verb>)":["status","migrate","validate","version"]};function O(){return Object.entries(M).map(([o,n])=>` ${i(o+":")}
4
4
  ${n.join(" ")}`).join(`
5
5
 
6
6
  `)}const w=Object.keys(d).length,E=`
@@ -10,12 +10,14 @@
10
10
  ${i("Usage:")}
11
11
  infernoflow [command] [options]
12
12
 
13
- ${i("Memory")} ${e("\u2014 the 5-command core")}
13
+ ${i("Memory")} ${e("\u2014 capture, recall & curate")}
14
14
  ${t("log")} ${e('"..."')} Add to session memory ${e("(--type gotcha|decision|attempt)")}
15
15
  ${t("ask")} ${e('"..."')} Search your memory by keyword ${e("(gotchas surface first)")}
16
16
  ${t("switch")} Generate handoff for next AI agent
17
17
  ${t("recap")} End-of-session health score + unlogged changes
18
18
  ${t("status")} Quick health check
19
+ ${t("forget")} Delete a bad entry ${e("(--last for the newest, or by id/prefix)")}
20
+ ${t("prune")} Archive stale notes/attempts ${e("(dry-run; --apply to commit)")}
19
21
 
20
22
  ${i("Setup")}
21
23
  ${t("init")} 60-second setup ${e("(memory mode by default)")}
@@ -1,20 +1,20 @@
1
- import*as s from"node:fs";import*as J from"node:os";import*as r from"node:path";import{findProjectRoot as O}from"../projectRoot.mjs";import{getBranchInfo as M}from"../git/branch.mjs";const T="1.0",_=new Set(["gotcha","decision","attempt","note","detection","pattern"]),P=new Set(["copilot","cursor","claude","windsurf","other"]);function L(e){let t=r.basename(r.resolve(e));try{const o=r.join(e,".ai-memory","amp.json");if(s.existsSync(o)){const n=JSON.parse(s.readFileSync(o,"utf8"));n&&typeof n.project=="string"&&n.project.trim()&&(t=n.project.trim())}}catch{}return t.toLowerCase().replace(/[^a-z0-9_.\-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,64)||"unnamed-project"}function W(e,t){let o=process.env.INFERNOFLOW_GLOBAL_DIR;if(!o)try{const l=JSON.parse(s.readFileSync(r.join(t,"amp.json"),"utf8"));l&&typeof l.globalDir=="string"&&l.globalDir.trim()&&(o=l.globalDir.trim())}catch{}if(!o)return r.join(t,"global.jsonl");let n=o;n.startsWith("~")&&(n=r.join(J.homedir(),n.slice(1).replace(/^[\/\\]/,""))),r.isAbsolute(n)||(n=r.resolve(e,n));const c=L(e);return r.join(n,c,"global.jsonl")}function m(e,t={}){const o=t.literal?r.resolve(e):O(e),n=r.join(o,".ai-memory"),c=r.join(o,"inferno"),l=t.forWrite||s.existsSync(n)||!s.existsSync(c),a=l?n:c,i=l,u=r.join(a,"branches"),f=M(o),h=r.join(u,`${f.currentSlug}.jsonl`),g=f.defaultSlug?r.join(u,`${f.defaultSlug}.jsonl`):null,S=Y(o,a);return{root:a,projectRoot:o,isAmp:i,sessions:r.join(a,"sessions.jsonl"),config:r.join(a,i?"amp.json":"config.json"),handoff:r.join(a,i?"handoff.md":"HANDOFF.md"),globalFile:S,branchesDir:u,currentBranchFile:h,defaultBranchFile:g,branch:f}}function Y(e,t){try{return W(e,t)}catch{return r.join(t,"global.jsonl")}}function x(e){const t=O(e),o=r.join(t,".ai-memory");return s.existsSync(o)||s.mkdirSync(o,{recursive:!0}),o}const B="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function E(){let e=Date.now(),t="";for(let n=0;n<10;n++)t=B[e%32]+t,e=Math.floor(e/32);let o="";for(let n=0;n<16;n++)o+=B[Math.floor(Math.random()*32)];return t+o}function N(e){const t={...e.meta||{}};let o=e.type||"note";_.has(o)||(t.subtype=o,o="note"),e.result&&(t.result=e.result),e.detailRef&&(t.detailRef=e.detailRef);let n;const c=e.agent;c&&P.has(c)?n=c:c&&(t.agent=c);const l=typeof e.ts=="number"?e.ts:e.ts?Date.parse(e.ts):Date.now(),a=e.confidence!=null?e.confidence:e.auto?.7:void 0,i={type:o,msg:e.summary||e.msg||"",ts:l,id:e.id||`amp_${E()}`};return e.file&&(i.file=e.file),e.line&&(i.line=e.line),e.function&&(i.function=e.function),e.tags&&e.tags.length&&(i.tags=e.tags),e.source&&(i.source=e.source),n&&(i.tool=n),e.session&&(i.session=e.session),a!=null&&(i.confidence=a),Object.keys(t).length&&(i.meta=t),i}function G(e){if(e.summary&&!e.msg)return e;const t=e.meta||{},o=t.subtype||e.type||"note",n={ts:e.ts,type:o,summary:e.msg||""};e.id&&(n.id=e.id),e.file&&(n.file=e.file),e.line&&(n.line=e.line),e.function&&(n.function=e.function),e.tags&&(n.tags=e.tags),e.source&&(n.source=e.source),e.tool&&(n.agent=e.tool),t.agent&&(n.agent=t.agent),t.result&&(n.result=t.result),t.detailRef&&(n.detailRef=t.detailRef),e.confidence!=null&&(n.confidence=e.confidence,e.confidence<1&&(n.auto=!0));const{subtype:c,agent:l,result:a,detailRef:i,...u}=t;return Object.keys(u).length&&(n.meta=u),n}function U(e){if(!e||!s.existsSync(e))return[];try{return s.readFileSync(e,"utf8").split(`
2
- `).filter(Boolean).map(t=>{try{return JSON.parse(t)}catch{return null}}).filter(Boolean).map(G)}catch{return[]}}function ne(e){const t=m(e),o=new Set,n=[],c=[t.sessions,t.globalFile,t.defaultBranchFile,t.currentBranchFile],l=[...new Set(c.filter(Boolean))];for(const a of l)for(const i of U(a)){const u=i.id||`${i.ts}|${i.summary}`;o.has(u)||(o.add(u),n.push(i))}return n.sort((a,i)=>{const u=typeof a.ts=="number"?a.ts:Date.parse(a.ts||0),f=typeof i.ts=="number"?i.ts:Date.parse(i.ts||0);return u-f})}function H(e,t){return t.target==="global"?e.globalFile:t.target==="legacy"?e.sessions:t.target==="branch"?e.currentBranchFile:t.type==="preference"?e.globalFile:e.currentBranchFile}function oe(e,t){x(e);const o=m(e,{forWrite:!0}),n=t.id||`amp_${E()}`,c={...t,id:n};if(typeof t.detail=="string"&&t.detail.trim()){const u=`details/${n}.md`,f=r.join(o.root,"details",`${n}.md`);try{s.mkdirSync(r.dirname(f),{recursive:!0}),s.writeFileSync(f,t.detail,"utf8"),c.detailRef=u}catch{}}delete c.detail;const l=H(o,c),a=N(c),i=JSON.stringify(a)+`
3
- `;if(s.mkdirSync(r.dirname(l),{recursive:!0}),s.appendFileSync(l,i,"utf8"),l!==o.sessions)try{s.mkdirSync(r.dirname(o.sessions),{recursive:!0}),s.appendFileSync(o.sessions,i,"utf8")}catch{}return a}function ie(e,t){const o=m(e);return r.join(o.root,"details",`${t}.md`)}function se(e,t){if(!t)return null;const o=t.detailRef||t.meta&&t.meta.detailRef;if(!o||typeof o!="string")return null;const n=m(e),c=r.isAbsolute(o)?o:r.join(n.root,o);try{return s.readFileSync(c,"utf8")}catch{return null}}function re(e,t){if(!t)return{removed:0,files:[]};const o=m(e),n=[o.sessions,o.globalFile,o.currentBranchFile,o.defaultBranchFile].filter(Boolean);try{if(s.existsSync(o.branchesDir))for(const i of s.readdirSync(o.branchesDir))i.endsWith(".jsonl")&&n.push(r.join(o.branchesDir,i))}catch{}const c=[...new Set(n)];let l=0;const a=[];for(const i of c){if(!s.existsSync(i))continue;let u;try{u=s.readFileSync(i,"utf8").split(`
4
- `)}catch{continue}const f=[];let h=0;for(const g of u){if(!g.trim())continue;let S;try{S=JSON.parse(g).id}catch{f.push(g);continue}if(S===t){h++,l++;continue}f.push(g)}h>0&&(s.writeFileSync(i,f.length?f.join(`
1
+ import*as i from"node:fs";import*as C from"node:os";import*as r from"node:path";import{findProjectRoot as B}from"../projectRoot.mjs";import{getBranchInfo as J}from"../git/branch.mjs";const E="1.0",M=new Set(["gotcha","decision","attempt","note","detection","pattern"]),_=new Set(["copilot","cursor","claude","windsurf","other"]);function L(e){let t=r.basename(r.resolve(e));try{const o=r.join(e,".ai-memory","amp.json");if(i.existsSync(o)){const n=JSON.parse(i.readFileSync(o,"utf8"));n&&typeof n.project=="string"&&n.project.trim()&&(t=n.project.trim())}}catch{}return t.toLowerCase().replace(/[^a-z0-9_.\-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,64)||"unnamed-project"}function P(e,t){let o=process.env.INFERNOFLOW_GLOBAL_DIR;if(!o)try{const l=JSON.parse(i.readFileSync(r.join(t,"amp.json"),"utf8"));l&&typeof l.globalDir=="string"&&l.globalDir.trim()&&(o=l.globalDir.trim())}catch{}if(!o)return r.join(t,"global.jsonl");let n=o;n.startsWith("~")&&(n=r.join(C.homedir(),n.slice(1).replace(/^[\/\\]/,""))),r.isAbsolute(n)||(n=r.resolve(e,n));const c=L(e);return r.join(n,c,"global.jsonl")}function S(e,t={}){const o=t.literal?r.resolve(e):B(e),n=r.join(o,".ai-memory"),c=r.join(o,"inferno"),l=t.forWrite||i.existsSync(n)||!i.existsSync(c),a=l?n:c,s=l,u=r.join(a,"branches"),f=J(o),d=r.join(u,`${f.currentSlug}.jsonl`),p=f.defaultSlug?r.join(u,`${f.defaultSlug}.jsonl`):null,m=W(o,a);return{root:a,projectRoot:o,isAmp:s,sessions:r.join(a,"sessions.jsonl"),config:r.join(a,s?"amp.json":"config.json"),handoff:r.join(a,s?"handoff.md":"HANDOFF.md"),globalFile:m,branchesDir:u,currentBranchFile:d,defaultBranchFile:p,branch:f}}function W(e,t){try{return P(e,t)}catch{return r.join(t,"global.jsonl")}}function v(e){const t=B(e),o=r.join(t,".ai-memory");return i.existsSync(o)||i.mkdirSync(o,{recursive:!0}),o}const N="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function $(){let e=Date.now(),t="";for(let n=0;n<10;n++)t=N[e%32]+t,e=Math.floor(e/32);let o="";for(let n=0;n<16;n++)o+=N[Math.floor(Math.random()*32)];return t+o}function k(e){const t={...e.meta||{}};let o=e.type||"note";M.has(o)||(t.subtype=o,o="note"),e.result&&(t.result=e.result);let n;const c=e.agent;c&&_.has(c)?n=c:c&&(t.agent=c);const l=typeof e.ts=="number"?e.ts:e.ts?Date.parse(e.ts):Date.now(),a=e.confidence!=null?e.confidence:e.auto?.7:void 0,s={type:o,msg:e.summary||e.msg||"",ts:l,id:e.id||`amp_${$()}`};return e.file&&(s.file=e.file),e.line&&(s.line=e.line),e.function&&(s.function=e.function),e.tags&&e.tags.length&&(s.tags=e.tags),e.source&&(s.source=e.source),n&&(s.tool=n),e.session&&(s.session=e.session),a!=null&&(s.confidence=a),Object.keys(t).length&&(s.meta=t),s}function Y(e){if(e.summary&&!e.msg)return e;const t=e.meta||{},o=t.subtype||e.type||"note",n={ts:e.ts,type:o,summary:e.msg||""};e.id&&(n.id=e.id),e.file&&(n.file=e.file),e.line&&(n.line=e.line),e.function&&(n.function=e.function),e.tags&&(n.tags=e.tags),e.source&&(n.source=e.source),e.tool&&(n.agent=e.tool),t.agent&&(n.agent=t.agent),t.result&&(n.result=t.result),e.confidence!=null&&(n.confidence=e.confidence,e.confidence<1&&(n.auto=!0));const{subtype:c,agent:l,result:a,...s}=t;return Object.keys(s).length&&(n.meta=s),n}function G(e){if(!e||!i.existsSync(e))return[];try{return i.readFileSync(e,"utf8").split(`
2
+ `).filter(Boolean).map(t=>{try{return JSON.parse(t)}catch{return null}}).filter(Boolean).map(Y)}catch{return[]}}function te(e){const t=S(e),o=new Set,n=[],c=[t.sessions,t.globalFile,t.defaultBranchFile,t.currentBranchFile],l=[...new Set(c.filter(Boolean))];for(const a of l)for(const s of G(a)){const u=s.id||`${s.ts}|${s.summary}`;o.has(u)||(o.add(u),n.push(s))}return n.sort((a,s)=>{const u=typeof a.ts=="number"?a.ts:Date.parse(a.ts||0),f=typeof s.ts=="number"?s.ts:Date.parse(s.ts||0);return u-f})}function U(e,t){return t.target==="global"?e.globalFile:t.target==="legacy"?e.sessions:t.target==="branch"?e.currentBranchFile:t.type==="preference"?e.globalFile:e.currentBranchFile}function ne(e,t){v(e);const o=S(e,{forWrite:!0}),n=U(o,t),c=k(t),l=JSON.stringify(c)+`
3
+ `;if(i.mkdirSync(r.dirname(n),{recursive:!0}),i.appendFileSync(n,l,"utf8"),n!==o.sessions)try{i.mkdirSync(r.dirname(o.sessions),{recursive:!0}),i.appendFileSync(o.sessions,l,"utf8")}catch{}return c}function oe(e,t){if(!t)return{removed:0,files:[]};const o=S(e),n=[o.sessions,o.globalFile,o.currentBranchFile,o.defaultBranchFile].filter(Boolean);try{if(i.existsSync(o.branchesDir))for(const s of i.readdirSync(o.branchesDir))s.endsWith(".jsonl")&&n.push(r.join(o.branchesDir,s))}catch{}const c=[...new Set(n)];let l=0;const a=[];for(const s of c){if(!i.existsSync(s))continue;let u;try{u=i.readFileSync(s,"utf8").split(`
4
+ `)}catch{continue}const f=[];let d=0;for(const p of u){if(!p.trim())continue;let m;try{m=JSON.parse(p).id}catch{f.push(p);continue}if(m===t){d++,l++;continue}f.push(p)}d>0&&(i.writeFileSync(s,f.length?f.join(`
5
5
  `)+`
6
- `:"","utf8"),a.push(i))}try{const i=r.join(o.root,"details",`${t}.md`);s.existsSync(i)&&(s.unlinkSync(i),a.push(i))}catch{}return{removed:l,files:a}}const V=30,z=["note","attempt","detection"],w=new Set(["gotcha","decision","pattern"]);function q(e){const t=e&&e.config&&e.config.rotation&&typeof e.config.rotation=="object"?e.config.rotation:{};return{archiveAfterDays:Number.isInteger(t.archiveAfterDays)&&t.archiveAfterDays>0?t.archiveAfterDays:V,archivableTypes:Array.isArray(t.archivableTypes)&&t.archivableTypes.length?t.archivableTypes:z,auto:t.auto===!0}}function K(e){const t=[e.sessions,e.currentBranchFile,e.defaultBranchFile].filter(Boolean);try{if(s.existsSync(e.branchesDir))for(const o of s.readdirSync(e.branchesDir))o.endsWith(".jsonl")&&t.push(r.join(e.branchesDir,o))}catch{}return[...new Set(t)]}function Q(e,t){const o=new Date(t),n=o.getUTCFullYear(),c=String(o.getUTCMonth()+1).padStart(2,"0");return r.join(e,"archive",`sessions-${n}-${c}.jsonl`)}function ce(e,t={}){const o=m(e,{forWrite:!t.dryRun}),n=q(X(e)),c=Number.isFinite(t.maxAgeDays)?t.maxAgeDays:n.archiveAfterDays,l=t.archive!==!1,a=new Set((Array.isArray(t.types)&&t.types.length?t.types:n.archivableTypes).filter(p=>!w.has(p))),i=Date.now()-c*24*60*60*1e3,u=K(o),f={scanned:0,archived:0,kept:0,files:[],byType:{},dryRun:!!t.dryRun},h=new Map,g=new Set,S=new Set,R=new Set;for(const p of u){if(!s.existsSync(p))continue;let b;try{b=s.readFileSync(p,"utf8").split(`
7
- `)}catch{continue}const F=[];let v=0;for(const j of b){if(!j.trim())continue;let y;try{y=JSON.parse(j)}catch{F.push(j);continue}const d=y&&y.id,D=y&&typeof y.type=="string"?y.type:"note",A=typeof y.ts=="number"?y.ts:Date.parse(y.ts||0),I=Number.isFinite(A)&&A<i,$=Array.isArray(y.tags)&&y.tags.includes("bookmark"),C=a.has(D)&&!w.has(D)&&!$;if(d&&!S.has(d)&&(S.add(d),f.scanned++),d||f.scanned++,I&&C){if(v++,(!d||!g.has(d))&&(d&&g.add(d),f.archived++,f.byType[D]=(f.byType[D]||0)+1,l)){const k=Q(o.root,A);h.has(k)||h.set(k,[]),h.get(k).push(j)}}else d&&!R.has(d)&&(R.add(d),f.kept++),d||f.kept++,F.push(j)}v>0&&!t.dryRun?(s.writeFileSync(p,F.length?F.join(`
6
+ `:"","utf8"),a.push(s))}return{removed:l,files:a}}const H=30,V=["note","attempt","detection"],w=new Set(["gotcha","decision","pattern"]);function z(e){const t=e&&e.config&&e.config.rotation&&typeof e.config.rotation=="object"?e.config.rotation:{};return{archiveAfterDays:Number.isInteger(t.archiveAfterDays)&&t.archiveAfterDays>0?t.archiveAfterDays:H,archivableTypes:Array.isArray(t.archivableTypes)&&t.archivableTypes.length?t.archivableTypes:V,auto:t.auto===!0}}function q(e){const t=[e.sessions,e.currentBranchFile,e.defaultBranchFile].filter(Boolean);try{if(i.existsSync(e.branchesDir))for(const o of i.readdirSync(e.branchesDir))o.endsWith(".jsonl")&&t.push(r.join(e.branchesDir,o))}catch{}return[...new Set(t)]}function K(e,t){const o=new Date(t),n=o.getUTCFullYear(),c=String(o.getUTCMonth()+1).padStart(2,"0");return r.join(e,"archive",`sessions-${n}-${c}.jsonl`)}function se(e,t={}){const o=S(e,{forWrite:!t.dryRun}),n=z(Q(e)),c=Number.isFinite(t.maxAgeDays)?t.maxAgeDays:n.archiveAfterDays,l=t.archive!==!1,a=new Set((Array.isArray(t.types)&&t.types.length?t.types:n.archivableTypes).filter(h=>!w.has(h))),s=Date.now()-c*24*60*60*1e3,u=q(o),f={scanned:0,archived:0,kept:0,files:[],byType:{},dryRun:!!t.dryRun},d=new Map,p=new Set,m=new Set,T=new Set;for(const h of u){if(!i.existsSync(h))continue;let b;try{b=i.readFileSync(h,"utf8").split(`
7
+ `)}catch{continue}const F=[];let x=0;for(const j of b){if(!j.trim())continue;let g;try{g=JSON.parse(j)}catch{F.push(j);continue}const y=g&&g.id,D=g&&typeof g.type=="string"?g.type:"note",A=typeof g.ts=="number"?g.ts:Date.parse(g.ts||0),I=Number.isFinite(A)&&A<s,R=a.has(D)&&!w.has(D);if(y&&!m.has(y)&&(m.add(y),f.scanned++),y||f.scanned++,I&&R){if(x++,(!y||!p.has(y))&&(y&&p.add(y),f.archived++,f.byType[D]=(f.byType[D]||0)+1,l)){const O=K(o.root,A);d.has(O)||d.set(O,[]),d.get(O).push(j)}}else y&&!T.has(y)&&(T.add(y),f.kept++),y||f.kept++,F.push(j)}x>0&&!t.dryRun?(i.writeFileSync(h,F.length?F.join(`
8
8
  `)+`
9
- `:"","utf8"),f.files.push(p)):v>0&&f.files.push(p)}if(!t.dryRun&&l&&h.size>0)for(const[p,b]of h)try{s.mkdirSync(r.dirname(p),{recursive:!0}),s.appendFileSync(p,b.join(`
9
+ `:"","utf8"),f.files.push(h)):x>0&&f.files.push(h)}if(!t.dryRun&&l&&d.size>0)for(const[h,b]of d)try{i.mkdirSync(r.dirname(h),{recursive:!0}),i.appendFileSync(h,b.join(`
10
10
  `)+`
11
- `,"utf8")}catch{}return f}function X(e){const{config:t}=m(e);try{return JSON.parse(s.readFileSync(t,"utf8"))}catch{return null}}function ae(e,t={}){x(e);const{config:o}=m(e,{forWrite:!0});if(s.existsSync(o))return!1;const n={amp:T,project:t.project||r.basename(e),stack:t.stack||{},config:{autoCapture:!0,maxEntries:1e3,rotationStrategy:"archive",inject:["all"],injection:{maxEntries:4,maxCommits:5,maxEntryChars:200,protocolStyle:"compact"},rotation:{archiveAfterDays:30,archivableTypes:["note","attempt","detection"],auto:!1},...t.config||{}}};return s.writeFileSync(o,JSON.stringify(n,null,2)+`
12
- `,"utf8"),!0}function fe(e,t={}){x(e);const{config:o}=m(e,{forWrite:!0});let n={};try{n=JSON.parse(s.readFileSync(o,"utf8"))}catch{n={}}(!n||typeof n!="object")&&(n={}),n.amp||(n.amp=T),n.project||(n.project=r.basename(e)),(!n.config||typeof n.config!="object")&&(n.config={});const c=n.config.injection&&typeof n.config.injection=="object"?n.config.injection:{};return n.config.injection={...c,...t},s.writeFileSync(o,JSON.stringify(n,null,2)+`
13
- `,"utf8"),n.config.injection}function le(e){const t=r.join(e,"inferno"),o=r.join(t,"sessions.jsonl");if(!s.existsSync(o))return{migrated:0,reason:"no legacy sessions.jsonl"};const n=r.join(e,".ai-memory"),c=r.join(n,"sessions.jsonl");if(s.existsSync(c))return{migrated:0,reason:".ai-memory/sessions.jsonl already exists"};x(e);const l=s.readFileSync(o,"utf8").split(`
14
- `).filter(Boolean);let a=0;for(const i of l)try{const u=JSON.parse(i),f=N(u);s.appendFileSync(c,JSON.stringify(f)+`
15
- `,"utf8"),a++}catch{}return s.writeFileSync(r.join(n,"MIGRATED.md"),`# Migrated from inferno/
11
+ `,"utf8")}catch{}return f}function Q(e){const{config:t}=S(e);try{return JSON.parse(i.readFileSync(t,"utf8"))}catch{return null}}function ie(e,t={}){v(e);const{config:o}=S(e,{forWrite:!0});if(i.existsSync(o))return!1;const n={amp:E,project:t.project||r.basename(e),stack:t.stack||{},config:{autoCapture:!0,maxEntries:1e3,rotationStrategy:"archive",inject:["all"],injection:{maxEntries:4,maxCommits:5,maxEntryChars:200},rotation:{archiveAfterDays:30,archivableTypes:["note","attempt","detection"],auto:!1},...t.config||{}}};return i.writeFileSync(o,JSON.stringify(n,null,2)+`
12
+ `,"utf8"),!0}function re(e,t={}){v(e);const{config:o}=S(e,{forWrite:!0});let n={};try{n=JSON.parse(i.readFileSync(o,"utf8"))}catch{n={}}(!n||typeof n!="object")&&(n={}),n.amp||(n.amp=E),n.project||(n.project=r.basename(e)),(!n.config||typeof n.config!="object")&&(n.config={});const c=n.config.injection&&typeof n.config.injection=="object"?n.config.injection:{};return n.config.injection={...c,...t},i.writeFileSync(o,JSON.stringify(n,null,2)+`
13
+ `,"utf8"),n.config.injection}function ce(e){const t=r.join(e,"inferno"),o=r.join(t,"sessions.jsonl");if(!i.existsSync(o))return{migrated:0,reason:"no legacy sessions.jsonl"};const n=r.join(e,".ai-memory"),c=r.join(n,"sessions.jsonl");if(i.existsSync(c))return{migrated:0,reason:".ai-memory/sessions.jsonl already exists"};v(e);const l=i.readFileSync(o,"utf8").split(`
14
+ `).filter(Boolean);let a=0;for(const s of l)try{const u=JSON.parse(s),f=k(u);i.appendFileSync(c,JSON.stringify(f)+`
15
+ `,"utf8"),a++}catch{}return i.writeFileSync(r.join(n,"MIGRATED.md"),`# Migrated from inferno/
16
16
 
17
17
  Copied ${a} entries from inferno/sessions.jsonl on ${new Date().toISOString()}.
18
18
 
19
19
  The original inferno/sessions.jsonl is untouched. You can delete it once you're confident the new layout works.
20
- `,"utf8"),{migrated:a,reason:"ok"}}export{T as AMP_VERSION,m as ampPaths,oe as appendEntry,re as deleteEntry,ie as detailPath,x as ensureAmpDir,G as fromAmp,E as generateULID,le as migrateLegacy,L as projectSlug,ce as pruneEntries,X as readConfig,se as readDetail,ne as readEntries,W as resolveGlobalFile,q as resolveRotationSettings,N as toAmp,fe as updateInjectionConfig,ae as writeDefaultConfig};
20
+ `,"utf8"),{migrated:a,reason:"ok"}}export{E as AMP_VERSION,S as ampPaths,ne as appendEntry,oe as deleteEntry,v as ensureAmpDir,Y as fromAmp,$ as generateULID,ce as migrateLegacy,L as projectSlug,se as pruneEntries,Q as readConfig,te as readEntries,P as resolveGlobalFile,z as resolveRotationSettings,k as toAmp,re as updateInjectionConfig,ie as writeDefaultConfig};
@@ -1,3 +1,3 @@
1
- import"node:fs";import"node:path";import{bold as O,cyan as T,gray as s,green as R,yellow as I,red as W}from"../ui/output.mjs";import{readEntries as j}from"../amp/io.mjs";const d={gotcha:0,decision:1,attempt:2,preference:3,theme:4,note:5,error:5,handoff:6},S={gotcha:"\u26A0",decision:"\u2713",attempt:"\u21BA",preference:"\u2666",theme:"\u{1F3A8}",note:"\xB7",error:"\u2717",handoff:"\u2192"},D={gotcha:I,decision:R,attempt:T,preference:T,theme:T,note:s,error:W,handoff:s};function E(o){return o.toLowerCase().replace(/[^a-z0-9\s]/g," ").split(/\s+/).filter(t=>t.length>1)}function q(o,t){const i=[o.summary||"",o.type||""].join(" ").toLowerCase(),n=E(i);let l=0;for(const r of t)(o.summary||"").toLowerCase().includes(r)&&(l+=3),n.includes(r)&&(l+=1),n.some(e=>e.startsWith(r)||r.startsWith(e))&&(l+=.5);return l}function L(o){if(!o)return"";const t=new Date(o),i=Date.now()-t.getTime(),n=Math.floor(i/864e5);return n===0?"today":n===1?"yesterday":n<7?`${n}d ago`:n<30?`${Math.floor(n/7)}w ago`:t.toLocaleDateString("en-GB",{day:"2-digit",month:"short"})}function M(o,t){const i=o.type||"note",n=S[i]||"\xB7",l=D[i]||s,r=o.result?s(` [${o.result}]`):"",e=o.agent?s(` \u2014 ${o.agent}`):"",f=s(` (${L(o.ts)})`);let a=o.summary||"";if(t)for(const u of t){const p=new RegExp(`(${u})`,"gi");a=a.replace(p,$=>O($))}console.log(` ${l(n+" "+i.padEnd(11))}${r}${e}${f}`),console.log(` ${a}`)}function P(o){return j(o)}function Y(o,t,i,n){let l=o;i&&(l=l.filter(e=>(e.type||"note")===i));let r;return t.length>0?r=l.map(e=>({entry:e,score:q(e,t)})).filter(({score:e})=>e>0).sort((e,f)=>{if(f.score!==e.score)return f.score-e.score;const a=d[e.entry.type]??9,u=d[f.entry.type]??9;return a!==u?a-u:new Date(f.entry.ts||0).getTime()-new Date(e.entry.ts||0).getTime()}):r=l.map(e=>({entry:e,score:1})).sort((e,f)=>{const a=d[e.entry.type]??9,u=d[f.entry.type]??9;return a!==u?a-u:new Date(f.entry.ts||0).getTime()-new Date(e.entry.ts||0).getTime()}),r.slice(0,n||20)}async function U(o=[]){const t=o,i=t.indexOf("--type"),n=i!==-1?t[i+1]:null,l=t.indexOf("--limit")!==-1?t.indexOf("--limit"):t.indexOf("-n"),r=l!==-1?parseInt(t[l+1]||"20",10):15,e=t.includes("--json"),f=t.includes("--recent")||t.includes("-r"),a=t.slice(1),p=a.filter((c,g)=>!(c.startsWith("--")||g>0&&a[g-1].startsWith("--"))).join(" ").trim(),$=f?[]:E(p),k=process.cwd(),m=P(k);if(m.length===0){if(e){console.log(JSON.stringify({results:[],total:0}));return}console.log(s(`
2
- No session memory yet.`)),console.log(s(` Run: infernoflow log "<what happened>" --type gotcha
3
- `));return}const y=f?m.slice(-r).reverse().map(c=>({entry:c,score:1})):Y(m,$,n,r);if(e){console.log(JSON.stringify({query:p,type:n,total:m.length,matched:y.length,results:y.map(({entry:c,score:g})=>({...c,relevanceScore:g}))},null,2));return}if(console.log(),console.log(p?` ${O("\u{1F525} infernoflow ask")} ${T(`"${p}"`)}${n?s(` [${n}]`):""}`:f?` ${O("\u{1F525} infernoflow ask")} ${s("\u2014 recent entries")}`:` ${O("\u{1F525} infernoflow ask")} ${s("\u2014 all entries")}${n?s(` [${n}]`):""}`),console.log(s(` ${"\u2500".repeat(52)}`)),y.length===0){console.log(),p?(console.log(s(` No entries found for "${p}"`)),n&&console.log(s(` Try removing --type ${n} to widen the search`))):console.log(s(" No entries found.")),console.log();return}const w=new Map;for(const{entry:c,score:g}of y){const h=c.type||"note";w.has(h)||w.set(h,[]),w.get(h).push({entry:c,score:g})}const C=Object.keys(d).sort((c,g)=>d[c]-d[g]);let x=0;for(const c of C){const g=w.get(c);if(!g?.length)continue;console.log();const h=D[c]||s;console.log(h(` ${S[c]} ${c.toUpperCase()}S (${g.length})`)),console.log(s(" "+"\u2500".repeat(50)));for(const{entry:N}of g)console.log(),M(N,$),x++}console.log(),console.log(s(` ${x} result${x!==1?"s":""} from ${m.length} total entries`)),y.length===r&&m.length>r&&console.log(s(" Use --limit N to see more")),console.log()}export{U as askCommand};
1
+ import"node:fs";import"node:path";import{bold as T,cyan as O,gray as r,green as R,yellow as I,red as L}from"../ui/output.mjs";import{readEntries as M}from"../amp/io.mjs";const d={gotcha:0,decision:1,attempt:2,preference:3,theme:4,note:5,error:5,handoff:6},S={gotcha:"\u26A0",decision:"\u2713",attempt:"\u21BA",preference:"\u2666",theme:"\u{1F3A8}",note:"\xB7",error:"\u2717",handoff:"\u2192"},N={gotcha:I,decision:R,attempt:O,preference:O,theme:O,note:r,error:L,handoff:r};function E(o){return o.toLowerCase().replace(/[^a-z0-9\s]/g," ").split(/\s+/).filter(e=>e.length>1)}function W(o,e){const i=[o.summary||"",o.type||""].join(" ").toLowerCase(),n=E(i);let s=0;for(const c of e)(o.summary||"").toLowerCase().includes(c)&&(s+=3),n.includes(c)&&(s+=1),n.some(t=>t.startsWith(c)||c.startsWith(t))&&(s+=.5);return s}function j(o){if(o==null||o==="")return"";const e=new Date(o);if(isNaN(e.getTime()))return"";const i=s=>new Date(s.getFullYear(),s.getMonth(),s.getDate()).getTime(),n=Math.round((i(new Date)-i(e))/864e5);return n<=0?`today ${e.toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit"})}`:n===1?"yesterday":n<7?`${n}d ago`:n<30?`${Math.floor(n/7)}w ago`:e.toLocaleDateString("en-GB",{day:"2-digit",month:"short"})}function q(o,e){const i=o.type||"note",n=S[i]||"\xB7",s=N[i]||r,c=o.result?r(` [${o.result}]`):"",t=o.agent?r(` \u2014 ${o.agent}`):"",a=r(` (${j(o.ts)})`);let f=o.summary||"";if(e)for(const u of e){const p=new RegExp(`(${u})`,"gi");f=f.replace(p,$=>T($))}console.log(` ${s(n+" "+i.padEnd(11))}${c}${t}${a}`),console.log(` ${f}`)}function Y(o){return M(o)}function P(o,e,i,n){let s=o;i&&(s=s.filter(t=>(t.type||"note")===i));let c;return e.length>0?c=s.map(t=>({entry:t,score:W(t,e)})).filter(({score:t})=>t>0).sort((t,a)=>{if(a.score!==t.score)return a.score-t.score;const f=d[t.entry.type]??9,u=d[a.entry.type]??9;return f!==u?f-u:new Date(a.entry.ts||0).getTime()-new Date(t.entry.ts||0).getTime()}):c=s.map(t=>({entry:t,score:1})).sort((t,a)=>{const f=d[t.entry.type]??9,u=d[a.entry.type]??9;return f!==u?f-u:new Date(a.entry.ts||0).getTime()-new Date(t.entry.ts||0).getTime()}),c.slice(0,n||20)}async function G(o=[]){const e=o,i=e.indexOf("--type"),n=i!==-1?e[i+1]:null,s=e.indexOf("--limit")!==-1?e.indexOf("--limit"):e.indexOf("-n"),c=s!==-1?parseInt(e[s+1]||"20",10):15,t=e.includes("--json"),a=e.includes("--recent")||e.includes("-r"),f=e.slice(1),p=f.filter((l,g)=>!(l.startsWith("--")||g>0&&f[g-1].startsWith("--"))).join(" ").trim(),$=a?[]:E(p),k=process.cwd(),m=Y(k);if(m.length===0){if(t){console.log(JSON.stringify({results:[],total:0}));return}console.log(r(`
2
+ No session memory yet.`)),console.log(r(` Run: infernoflow log "<what happened>" --type gotcha
3
+ `));return}const y=a?m.slice(-c).reverse().map(l=>({entry:l,score:1})):P(m,$,n,c);if(t){console.log(JSON.stringify({query:p,type:n,total:m.length,matched:y.length,results:y.map(({entry:l,score:g})=>({...l,relevanceScore:g}))},null,2));return}if(console.log(),console.log(p?` ${T("\u{1F525} infernoflow ask")} ${O(`"${p}"`)}${n?r(` [${n}]`):""}`:a?` ${T("\u{1F525} infernoflow ask")} ${r("\u2014 recent entries")}`:` ${T("\u{1F525} infernoflow ask")} ${r("\u2014 all entries")}${n?r(` [${n}]`):""}`),console.log(r(` ${"\u2500".repeat(52)}`)),y.length===0){console.log(),p?(console.log(r(` No entries found for "${p}"`)),n&&console.log(r(` Try removing --type ${n} to widen the search`))):console.log(r(" No entries found.")),console.log();return}const w=new Map;for(const{entry:l,score:g}of y){const h=l.type||"note";w.has(h)||w.set(h,[]),w.get(h).push({entry:l,score:g})}const x=Object.keys(d).sort((l,g)=>d[l]-d[g]);let D=0;for(const l of x){const g=w.get(l);if(!g?.length)continue;console.log();const h=N[l]||r;console.log(h(` ${S[l]} ${l.toUpperCase()}S (${g.length})`)),console.log(r(" "+"\u2500".repeat(50)));for(const{entry:C}of g)console.log(),q(C,$),D++}console.log(),console.log(r(` ${D} result${D!==1?"s":""} from ${m.length} total entries`)),y.length===c&&m.length>c&&console.log(r(" Use --limit N to see more")),console.log()}export{G as askCommand};
@@ -1,3 +1,3 @@
1
- import*as s from"node:fs";import*as c from"node:path";import*as y from"node:os";import*as P from"node:http";import{execSync as A,spawnSync as v}from"node:child_process";import{fileURLToPath as O}from"node:url";import{bold as C,cyan as b,gray as S,green as w,yellow as k,red as $}from"../ui/output.mjs";import{detectAvailableProviders as I}from"../ai/providerRouter.mjs";import{readEntries as _,readConfig as M}from"../amp/io.mjs";import{resolveInjectionSettings as E}from"../ruleFiles.mjs";function f(e,n){try{const o=n();return{label:e,...o}}catch(o){return{label:e,status:"error",message:o.message,fix:null}}}function a(e,n){return{status:"pass",message:e,detail:n||null,fix:null}}function l(e,n){return{status:"warn",message:e,detail:null,fix:n||null}}function h(e,n){return{status:"fail",message:e,detail:null,fix:n||null}}function T(){const e=process.version,n=parseInt(e.slice(1).split(".")[0],10);return n>=20?a(`Node.js ${e}`,"Node 20+ recommended"):n>=18?a(`Node.js ${e}`):h(`Node.js ${e} \u2014 infernoflow requires Node 18+`,"Install Node 20 from nodejs.org")}function D(){try{const e=v("infernoflow",["--version"],{encoding:"utf8",timeout:5e3,shell:process.platform==="win32"});return e.status===0?a(`infernoflow v${e.stdout.trim()} installed`):a("infernoflow CLI on PATH (version probe failed but doctor itself ran)")}catch{return a("infernoflow CLI on PATH (version probe threw but doctor itself ran)")}}function F(e){try{return A("git rev-parse --git-dir",{cwd:e,stdio:"ignore"}),a("Git repository detected")}catch{return h("Not a git repository","git init && git add . && git commit -m 'init'")}}function G(e){const n=c.join(e,".ai-memory"),o=c.join(e,"inferno");return s.existsSync(n)&&s.existsSync(o)?a(".ai-memory/ + inferno/ both present"):s.existsSync(n)?a(".ai-memory/ directory exists (memory mode)"):s.existsSync(o)?a("inferno/ directory exists"):h("No memory directory found (.ai-memory/ or inferno/)","infernoflow init")}function L(e){if(x(e)){let o=0;try{o=_(e).length}catch{}return a(o===0?"Memory mode \u2014 sessions.jsonl will be created on first log":`Memory mode \u2014 ${o} session entr${o===1?"y":"ies"}`)}const n=c.join(e,"inferno");for(const o of["contract.json","capabilities.json"]){const t=c.join(n,o);if(s.existsSync(t))try{const i=(JSON.parse(s.readFileSync(t,"utf8")).capabilities||[]).length;return a(`${o} valid \u2014 ${i} capabilities`)}catch{return h(`${o} contains invalid JSON`,`Fix the JSON syntax in inferno/${o}`)}}return h("No contract.json/capabilities.json (and not in memory mode)","infernoflow init or infernoflow init --mode full")}function x(e){const n=c.join(e,".ai-memory"),o=c.join(e,"inferno","contract.json");if(s.existsSync(n)&&!s.existsSync(o))return!0;try{return JSON.parse(s.readFileSync(c.join(e,"inferno","config.json"),"utf8")).mode==="memory"}catch{return!1}}function R(e){if(x(e))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const n=c.join(e,"inferno","scenarios");if(!s.existsSync(n))return l("No scenarios/ directory","infernoflow init");const o=s.readdirSync(n).filter(t=>t.endsWith(".json"));return o.length?a(`${o.length} scenario file${o.length!==1?"s":""} found`):l("scenarios/ is empty","Add scenario files or run infernoflow suggest")}function J(e){if(x(e))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const n=c.join(e,"inferno","CHANGELOG.md");return s.existsSync(n)?a("inferno/CHANGELOG.md exists"):l("No inferno/CHANGELOG.md","infernoflow init")}function H(e){if(x(e))return{status:"info",message:"n/a in memory mode (CLAUDE.md is auto-maintained)",detail:null,fix:null};const n=c.join(e,"inferno","CONTEXT.md");if(!s.existsSync(n))return l("No CONTEXT.md generated","infernoflow context");const o=(Date.now()-s.statSync(n).mtimeMs)/(1e3*60*60*24);return o>7?l(`CONTEXT.md is ${Math.round(o)} days old \u2014 may be stale`,"infernoflow context"):a(`CONTEXT.md present (${Math.round(o)}d old)`)}function W(e){const n=c.join(e,".git","hooks"),o=c.join(n,"post-commit"),t=c.join(n,"pre-push"),r=s.existsSync(o)&&s.readFileSync(o,"utf8").includes("infernoflow"),i=s.existsSync(t)&&s.readFileSync(t,"utf8").includes("infernoflow");return r&&i?a("Git hooks installed (post-commit + pre-push)"):l(r||i?"Partial git hooks installed":"Git hooks not installed","infernoflow setup --yes")}function X(e){const n=[c.join(e,".cursor","mcp.json"),c.join(e,".mcp.json"),c.join(y.homedir(),".cursor","mcp.json"),c.join(y.homedir(),"Library","Application Support","Claude","claude_desktop_config.json"),c.join(y.homedir(),"AppData","Roaming","Claude","claude_desktop_config.json")];for(const o of n)if(s.existsSync(o))try{const t=JSON.parse(s.readFileSync(o,"utf8")),r=t.mcpServers||t.mcp_servers||{};if(Object.keys(r).some(i=>i.toLowerCase().includes("inferno")))return a(`MCP server configured in ${c.basename(o)}`)}catch{}return l("MCP server not configured","infernoflow setup --yes (adds to Cursor/Claude config)")}function K(e,n){const o=c.join(e,".ai-memory",".mcp-runtime.json");if(!s.existsSync(o))return a("MCP runtime stamp not present yet \u2014 start your AI tool to write one");let t;try{t=JSON.parse(s.readFileSync(o,"utf8"))}catch{return l(".mcp-runtime.json present but unreadable","Delete .ai-memory/.mcp-runtime.json and restart your AI tool")}return!t||typeof t.version!="string"?l(".mcp-runtime.json malformed","Delete it; the next MCP boot will rewrite it"):t.version===n?a(`MCP runtime v${t.version} matches CLI`):n.startsWith("0.0.0")||t.version.startsWith("0.0.0")?a(`MCP runtime v${t.version} (dev/source) \u2014 skipping version-skew check`):l(`MCP server is running v${t.version} but CLI is v${n} \u2014 restart your AI tool to load the new code.`,"Quit and reopen Cursor / Claude Code / VS Code (the long-running MCP process keeps the old code in memory until restart)")}function U(e){const n=E(M(e));if(!n.includeProtocol)return a("Memory-protocol injection disabled in config (injection.includeProtocol=false)");const o=n.targets,t=[];for(const r of o)try{const i=s.readFileSync(c.join(e,String(r)),"utf8");i.includes("amp_write")&&/Memory protocol/i.test(i)&&t.push(r)}catch{}return t.length?a(`Memory protocol in ${t.length}/${o.length} configured rule file(s) \u2014 AI knows when to auto-capture`):l("Memory protocol missing from rule files \u2014 the AI won't auto-capture gotchas","infernoflow refresh (writes the amp_write capture protocol into your rule files)")}function V(e){const n=I(e),o=Object.entries(n).filter(([,t])=>t).map(([t])=>t);return o.length?a(`AI provider${o.length!==1?"s":""}: ${o.join(", ")}`):l("No AI provider configured",`Set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_AI_API_KEY, or OPENROUTER_API_KEY
1
+ import*as c from"node:fs";import*as s from"node:path";import*as y from"node:os";import*as A from"node:http";import{execSync as O,spawnSync as v}from"node:child_process";import{fileURLToPath as P}from"node:url";import{bold as C,cyan as b,gray as k,green as w,yellow as S,red as $}from"../ui/output.mjs";import{detectAvailableProviders as I}from"../ai/providerRouter.mjs";import{readEntries as _,readConfig as M}from"../amp/io.mjs";import{resolveInjectionSettings as E}from"../ruleFiles.mjs";function l(e,n){try{const o=n();return{label:e,...o}}catch(o){return{label:e,status:"error",message:o.message,fix:null}}}function a(e,n){return{status:"pass",message:e,detail:n||null,fix:null}}function f(e,n){return{status:"warn",message:e,detail:null,fix:n||null}}function h(e,n){return{status:"fail",message:e,detail:null,fix:n||null}}function T(){const e=process.version,n=parseInt(e.slice(1).split(".")[0],10);return n>=20?a(`Node.js ${e}`,"Node 20+ recommended"):n>=18?a(`Node.js ${e}`):h(`Node.js ${e} \u2014 infernoflow requires Node 18+`,"Install Node 20 from nodejs.org")}function D(){try{const e=v("infernoflow",["--version"],{encoding:"utf8",timeout:5e3,shell:process.platform==="win32"});return e.status===0?a(`infernoflow v${e.stdout.trim()} installed`):a("infernoflow CLI on PATH (version probe failed but doctor itself ran)")}catch{return a("infernoflow CLI on PATH (version probe threw but doctor itself ran)")}}function G(e){try{return O("git rev-parse --git-dir",{cwd:e,stdio:"ignore"}),a("Git repository detected")}catch{return h("Not a git repository","git init && git add . && git commit -m 'init'")}}function F(e){const n=s.join(e,".ai-memory"),o=s.join(e,"inferno");return c.existsSync(n)&&c.existsSync(o)?a(".ai-memory/ + inferno/ both present"):c.existsSync(n)?a(".ai-memory/ directory exists (memory mode)"):c.existsSync(o)?a("inferno/ directory exists"):h("No memory directory found (.ai-memory/ or inferno/)","infernoflow init")}function L(e){if(j(e)){let o=0;try{o=_(e).length}catch{}return a(o===0?"Memory mode \u2014 sessions.jsonl will be created on first log":`Memory mode \u2014 ${o} session entr${o===1?"y":"ies"}`)}const n=s.join(e,"inferno");for(const o of["contract.json","capabilities.json"]){const t=s.join(n,o);if(c.existsSync(t))try{const i=(JSON.parse(c.readFileSync(t,"utf8")).capabilities||[]).length;return a(`${o} valid \u2014 ${i} capabilities`)}catch{return h(`${o} contains invalid JSON`,`Fix the JSON syntax in inferno/${o}`)}}return h("No contract.json/capabilities.json (and not in memory mode)","infernoflow init or infernoflow init --mode full")}function j(e){const n=s.join(e,".ai-memory"),o=s.join(e,"inferno","contract.json");if(c.existsSync(n)&&!c.existsSync(o))return!0;try{return JSON.parse(c.readFileSync(s.join(e,"inferno","config.json"),"utf8")).mode==="memory"}catch{return!1}}function R(e){if(j(e))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const n=s.join(e,"inferno","scenarios");if(!c.existsSync(n))return f("No scenarios/ directory","infernoflow init");const o=c.readdirSync(n).filter(t=>t.endsWith(".json"));return o.length?a(`${o.length} scenario file${o.length!==1?"s":""} found`):f("scenarios/ is empty","Add scenario files or run infernoflow suggest")}function J(e){if(j(e))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const n=s.join(e,"inferno","CHANGELOG.md");return c.existsSync(n)?a("inferno/CHANGELOG.md exists"):f("No inferno/CHANGELOG.md","infernoflow init")}function H(e){if(j(e))return{status:"info",message:"n/a in memory mode (CLAUDE.md is auto-maintained)",detail:null,fix:null};const n=s.join(e,"inferno","CONTEXT.md");if(!c.existsSync(n))return f("No CONTEXT.md generated","infernoflow context");const o=(Date.now()-c.statSync(n).mtimeMs)/(1e3*60*60*24);return o>7?f(`CONTEXT.md is ${Math.round(o)} days old \u2014 may be stale`,"infernoflow context"):a(`CONTEXT.md present (${Math.round(o)}d old)`)}function W(e){const n=s.join(e,".git","hooks"),o=s.join(n,"post-commit");return c.existsSync(o)&&c.readFileSync(o,"utf8").includes("infernoflow")?a("Git hooks installed (post-commit)"):f("Git hooks not installed","infernoflow setup --yes")}function X(e){const n=[s.join(e,".cursor","mcp.json"),s.join(e,".mcp.json"),s.join(e,".vscode","mcp.json"),s.join(y.homedir(),".claude.json"),s.join(y.homedir(),".cursor","mcp.json"),s.join(y.homedir(),"Library","Application Support","Claude","claude_desktop_config.json"),s.join(y.homedir(),"AppData","Roaming","Claude","claude_desktop_config.json")];for(const o of n)if(c.existsSync(o))try{const t=JSON.parse(c.readFileSync(o,"utf8")),r=t.mcpServers||t.mcp_servers||t.servers||{};if(Object.keys(r).some(i=>i.toLowerCase().includes("inferno")))return a(`MCP server configured in ${s.basename(o)}`)}catch{}return f("MCP server not configured","infernoflow setup --yes (adds to Cursor/Claude/Copilot config)")}function K(e,n){const o=s.join(e,".ai-memory",".mcp-runtime.json");if(!c.existsSync(o))return a("MCP runtime stamp not present yet \u2014 start your AI tool to write one");let t;try{t=JSON.parse(c.readFileSync(o,"utf8"))}catch{return f(".mcp-runtime.json present but unreadable","Delete .ai-memory/.mcp-runtime.json and restart your AI tool")}return!t||typeof t.version!="string"?f(".mcp-runtime.json malformed","Delete it; the next MCP boot will rewrite it"):t.version===n?a(`MCP runtime v${t.version} matches CLI`):n.startsWith("0.0.0")||t.version.startsWith("0.0.0")?a(`MCP runtime v${t.version} (dev/source) \u2014 skipping version-skew check`):f(`MCP server is running v${t.version} but CLI is v${n} \u2014 restart your AI tool to load the new code.`,"Quit and reopen Cursor / Claude Code / VS Code (the long-running MCP process keeps the old code in memory until restart)")}function U(e){const n=E(M(e));if(!n.includeProtocol)return a("Memory-protocol injection disabled in config (injection.includeProtocol=false)");const o=n.targets,t=[];for(const r of o)try{const i=c.readFileSync(s.join(e,String(r)),"utf8");i.includes("amp_write")&&/Memory protocol/i.test(i)&&t.push(r)}catch{}return t.length?a(`Memory protocol in ${t.length}/${o.length} configured rule file(s) \u2014 AI knows when to auto-capture`):f("Memory protocol missing from rule files \u2014 the AI won't auto-capture gotchas","infernoflow refresh (writes the amp_write capture protocol into your rule files)")}function V(e){const n=I(e),o=Object.entries(n).filter(([,t])=>t).map(([t])=>t);return o.length?a(`AI provider${o.length!==1?"s":""}: ${o.join(", ")}`):f("No AI provider configured",`Set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_AI_API_KEY, or OPENROUTER_API_KEY
2
2
  Or install Ollama (ollama.com) for free local AI
3
- Or use VS Code with GitHub Copilot (zero config)`)}async function Y(){return new Promise(e=>{const n=P.get({hostname:"localhost",port:11434,path:"/api/tags",timeout:1500},o=>{e(a("Ollama running on localhost:11434"))});n.on("error",()=>e({status:"info",message:"Ollama not running (optional)",fix:"ollama serve",detail:null})),n.on("timeout",()=>{n.destroy(),e({status:"info",message:"Ollama not running (optional)",fix:null,detail:null})})})}function z(){const e=c.join(y.homedir(),".infernoflow","credentials.json");if(!s.existsSync(e))return{status:"info",message:"Not logged in to cloud (optional)",fix:"infernoflow login",detail:null};try{const n=JSON.parse(s.readFileSync(e,"utf8")),o=n.user?.login||n.user?.name||n.user?.email||"unknown";if(n.mode==="supabase"&&n.access_token){if(n.expires_at){const t=new Date(n.expires_at).getTime();if(Date.now()>t)return l(`JWT expired for ${o} \u2014 refresh on next log will retry`,"infernoflow login")}return a(`Authenticated as ${o} (Supabase JWT \u2014 auth.uid() writes)`)}return n.mode==="device-flow"&&n.github_access_token?{status:"info",message:`Identity-only as ${o} (device flow \u2014 anon-mode writes)`,fix:"infernoflow login (without --device-flow, for full auth)",detail:null}:n.access_token?l(`Legacy login for ${o} \u2014 re-run for authenticated cloud writes`,"infernoflow logout && infernoflow login"):{status:"info",message:"Credentials file present but no recognised token",fix:"infernoflow logout && infernoflow login",detail:null}}catch{return l("Credentials file unreadable","infernoflow logout && infernoflow login")}}function q(){try{const e=O(import.meta.url),n=c.resolve(c.dirname(e),"..","..","bin","infernoflow.mjs");if(!s.existsSync(n))return{status:"info",message:"bin/infernoflow.mjs not found from doctor location",fix:null,detail:null};const t=[...s.readFileSync(n,"utf8").matchAll(/import\("\.\.\/lib\/(commands\/[^"]+|telemetry\.mjs)"\)/g)],r=[],i=c.resolve(c.dirname(n),"..");for(const d of t){const m=d[1],p=c.join(i,"lib",m);s.existsSync(p)||r.push(m)}return r.length?h(`${r.length} routed command(s) missing module files: ${r.slice(0,3).join(", ")}${r.length>3?"\u2026":""}`,"Restore the missing files or remove their entries from bin/infernoflow.mjs"):a(`All ${t.length} routed commands resolve to real files`)}catch(e){return{status:"info",message:`Router integrity check skipped: ${e.message}`,fix:null,detail:null}}}function B(e){const n=c.join(e,"package.json");if(!s.existsSync(n))return a("No package.json to audit");let o;try{o=JSON.parse(s.readFileSync(n,"utf8"))}catch{return{status:"info",message:"package.json unreadable; skipping audit",detail:null,fix:null}}const t=o.scripts||{},r=new Set(["log","ask","switch","recap","status","init","doctor","graph","watch","amp","contract","dev","demo","setup","log-decision","log-attempt","context","stats","test"]),i=[];for(const[m,p]of Object.entries(t)){const j=/\binfernoflow\s+([a-z][a-z0-9-]*)/.exec(String(p));if(!j)continue;const u=j[1];r.has(u)||i.push({scriptName:m,verb:u})}if(i.length===0)return a("npm scripts use current command surface");const d=i.map(m=>`${m.scriptName} \u2192 infernoflow ${m.verb}`).join(", ");return l(`package.json references ${i.length} deprecated command(s): ${d}`,"Edit package.json scripts to use the current surface (run `infernoflow --help` to list verbs)")}function Q(e){const n=c.join(e,".gitignore");if(!s.existsSync(n))return{status:"info",message:".gitignore not found",fix:null,detail:null};const o=s.readFileSync(n,"utf8");return/^(?:\*\*\/)?node_modules\/?$/m.test(o)?a(".gitignore excludes node_modules"):l(".gitignore does not exclude node_modules","Add 'node_modules/' (and '**/node_modules/') to .gitignore")}function Z(e,n){const o=e.filter(r=>r.status==="warn"&&r.fix),t=[];for(const r of o){const i=r.fix;if(i.startsWith("infernoflow ")){const d=i.slice(12).split(" ");v("infernoflow",d,{cwd:n,encoding:"utf8",timeout:3e4}).status===0&&t.push(r.label)}}return t}function ee(e){return e==="pass"?w("\u2714"):e==="warn"?k("\u26A0"):e==="fail"?$("\u2717"):S("\xB7")}function ne(e,n){const o={pass:0,warn:0,fail:0,info:0,error:0};for(const i of e)o[i.status]=(o[i.status]||0)+1;console.log(),console.log(` ${C("\u{1F525} infernoflow doctor")}`),console.log();const t=Math.max(...e.map(i=>i.label.length))+2;for(const i of e)console.log(` ${ee(i.status)} ${C(i.label.padEnd(t))} ${i.message}`),i.detail&&console.log(` ${" ".repeat(t)} ${S(i.detail)}`),i.fix&&(i.status==="warn"||i.status==="fail")&&console.log(` ${" ".repeat(t)} ${b("fix:")} ${S(i.fix)}`);console.log();const r=o.fail>0?$("issues found"):o.warn>0?k("warnings"):w("all good");console.log(` ${r} \u2014 ${w(String(o.pass))} pass \xB7 ${k(String(o.warn))} warn \xB7 ${$(String(o.fail))} fail (${n}ms)`),console.log(),(o.warn>0||o.fail>0)&&(console.log(` Run ${b("infernoflow doctor --fix")} to auto-fix warnings`),console.log())}async function oe(e){const n=e.slice(1),o=n.includes("--json"),t=n.includes("--fix"),r=process.cwd(),i=Date.now();let d="0.0.0-unknown";try{const{fileURLToPath:u}=await import("node:url"),g=c.dirname(u(import.meta.url)),N=c.join(g,"..","..","package.json");d=JSON.parse(s.readFileSync(N,"utf8")).version||d}catch{}const m=[f("Node.js version",()=>T()),f("infernoflow CLI",()=>D()),f("Git repository",()=>F(r)),f("inferno/ directory",()=>G(r)),f("Contract / mode",()=>L(r)),f("Scenarios",()=>R(r)),f("Changelog",()=>J(r)),f("CONTEXT.md",()=>H(r)),f("Git hooks",()=>W(r)),f("Auto-capture protocol",()=>U(r)),f("MCP server",()=>X(r)),f("MCP runtime version",()=>K(r,d)),f("AI providers",()=>V(r)),f("Cloud sync",()=>z()),f(".gitignore",()=>Q(r)),f("Router integrity",()=>q()),f("npm scripts",()=>B(r)),await Y().then(u=>({label:"Ollama (local AI)",...u}))],p=Date.now()-i;if(t){const u=Z(m,r);if(u.length)return o||(console.log(),u.forEach(g=>console.log(` ${w("\u2714")} Fixed: ${g}`)),console.log()),oe(["doctor","--json"])}if(o){const u={pass:0,warn:0,fail:0,info:0};m.forEach(g=>u[g.status]=(u[g.status]||0)+1),console.log(JSON.stringify({ok:u.fail===0,counts:u,results:m,elapsed:p}));return}ne(m,p),m.some(u=>u.status==="fail")&&process.exit(1)}export{oe as doctorCommand};
3
+ Or use VS Code with GitHub Copilot (zero config)`)}async function Y(){return new Promise(e=>{const n=A.get({hostname:"localhost",port:11434,path:"/api/tags",timeout:1500},o=>{e(a("Ollama running on localhost:11434"))});n.on("error",()=>e({status:"info",message:"Ollama not running (optional)",fix:"ollama serve",detail:null})),n.on("timeout",()=>{n.destroy(),e({status:"info",message:"Ollama not running (optional)",fix:null,detail:null})})})}function z(){const e=s.join(y.homedir(),".infernoflow","credentials.json");if(!c.existsSync(e))return{status:"info",message:"Not logged in to cloud (optional)",fix:"infernoflow login",detail:null};try{const n=JSON.parse(c.readFileSync(e,"utf8")),o=n.user?.login||n.user?.name||n.user?.email||"unknown";if(n.mode==="supabase"&&n.access_token){if(n.expires_at){const t=new Date(n.expires_at).getTime();if(Date.now()>t)return f(`JWT expired for ${o} \u2014 refresh on next log will retry`,"infernoflow login")}return a(`Authenticated as ${o} (Supabase JWT \u2014 auth.uid() writes)`)}return n.mode==="device-flow"&&n.github_access_token?{status:"info",message:`Identity-only as ${o} (device flow \u2014 anon-mode writes)`,fix:"infernoflow login (without --device-flow, for full auth)",detail:null}:n.access_token?f(`Legacy login for ${o} \u2014 re-run for authenticated cloud writes`,"infernoflow logout && infernoflow login"):{status:"info",message:"Credentials file present but no recognised token",fix:"infernoflow logout && infernoflow login",detail:null}}catch{return f("Credentials file unreadable","infernoflow logout && infernoflow login")}}function q(){try{const e=P(import.meta.url),n=s.resolve(s.dirname(e),"..","..","bin","infernoflow.mjs");if(!c.existsSync(n))return{status:"info",message:"bin/infernoflow.mjs not found from doctor location",fix:null,detail:null};const t=[...c.readFileSync(n,"utf8").matchAll(/import\("\.\.\/lib\/(commands\/[^"]+|telemetry\.mjs)"\)/g)],r=[],i=s.resolve(s.dirname(n),"..");for(const d of t){const m=d[1],p=s.join(i,"lib",m);c.existsSync(p)||r.push(m)}return r.length?h(`${r.length} routed command(s) missing module files: ${r.slice(0,3).join(", ")}${r.length>3?"\u2026":""}`,"Restore the missing files or remove their entries from bin/infernoflow.mjs"):a(`All ${t.length} routed commands resolve to real files`)}catch(e){return{status:"info",message:`Router integrity check skipped: ${e.message}`,fix:null,detail:null}}}function B(e){const n=s.join(e,"package.json");if(!c.existsSync(n))return a("No package.json to audit");let o;try{o=JSON.parse(c.readFileSync(n,"utf8"))}catch{return{status:"info",message:"package.json unreadable; skipping audit",detail:null,fix:null}}const t=o.scripts||{},r=new Set(["log","ask","switch","recap","status","refresh","forget","prune","bookmark","init","setup","doctor","context","install-cursor-hooks","install-vscode-copilot-hooks","generate-skills","ai","telemetry","uninstall","check","sync","amp","commands"]),i=[];for(const[m,p]of Object.entries(t)){const x=/\binfernoflow\s+([a-z][a-z0-9-]*)/.exec(String(p));if(!x)continue;const u=x[1];r.has(u)||i.push({scriptName:m,verb:u})}if(i.length===0)return a("npm scripts use current command surface");const d=i.map(m=>`${m.scriptName} \u2192 infernoflow ${m.verb}`).join(", ");return f(`package.json references ${i.length} deprecated command(s): ${d}`,"Edit package.json scripts to use the current surface (run `infernoflow --help` to list verbs)")}function Q(e){const n=s.join(e,".gitignore");if(!c.existsSync(n))return{status:"info",message:".gitignore not found",fix:null,detail:null};const o=c.readFileSync(n,"utf8");return/^(?:\*\*\/)?node_modules\/?$/m.test(o)?a(".gitignore excludes node_modules"):f(".gitignore does not exclude node_modules","Add 'node_modules/' (and '**/node_modules/') to .gitignore")}function Z(e,n){const o=e.filter(r=>r.status==="warn"&&r.fix),t=[];for(const r of o){const i=r.fix;if(i.startsWith("infernoflow ")){const d=i.slice(12).split(" ");v("infernoflow",d,{cwd:n,encoding:"utf8",timeout:3e4}).status===0&&t.push(r.label)}}return t}function ee(e){return e==="pass"?w("\u2714"):e==="warn"?S("\u26A0"):e==="fail"?$("\u2717"):k("\xB7")}function oe(e,n){const o={pass:0,warn:0,fail:0,info:0,error:0};for(const i of e)o[i.status]=(o[i.status]||0)+1;console.log(),console.log(` ${C("\u{1F525} infernoflow doctor")}`),console.log();const t=Math.max(...e.map(i=>i.label.length))+2;for(const i of e)console.log(` ${ee(i.status)} ${C(i.label.padEnd(t))} ${i.message}`),i.detail&&console.log(` ${" ".repeat(t)} ${k(i.detail)}`),i.fix&&(i.status==="warn"||i.status==="fail")&&console.log(` ${" ".repeat(t)} ${b("fix:")} ${k(i.fix)}`);console.log();const r=o.fail>0?$("issues found"):o.warn>0?S("warnings"):w("all good");console.log(` ${r} \u2014 ${w(String(o.pass))} pass \xB7 ${S(String(o.warn))} warn \xB7 ${$(String(o.fail))} fail (${n}ms)`),console.log(),(o.warn>0||o.fail>0)&&(console.log(` Run ${b("infernoflow doctor --fix")} to auto-fix warnings`),console.log())}async function ne(e){const n=e.slice(1),o=n.includes("--json"),t=n.includes("--fix"),r=process.cwd(),i=Date.now();let d="0.0.0-unknown";try{const{fileURLToPath:u}=await import("node:url"),g=s.dirname(u(import.meta.url)),N=s.join(g,"..","..","package.json");d=JSON.parse(c.readFileSync(N,"utf8")).version||d}catch{}const m=[l("Node.js version",()=>T()),l("infernoflow CLI",()=>D()),l("Git repository",()=>G(r)),l("inferno/ directory",()=>F(r)),l("Contract / mode",()=>L(r)),l("Scenarios",()=>R(r)),l("Changelog",()=>J(r)),l("CONTEXT.md",()=>H(r)),l("Git hooks",()=>W(r)),l("Auto-capture protocol",()=>U(r)),l("MCP server",()=>X(r)),l("MCP runtime version",()=>K(r,d)),l("AI providers",()=>V(r)),l("Cloud sync",()=>z()),l(".gitignore",()=>Q(r)),l("Router integrity",()=>q()),l("npm scripts",()=>B(r)),await Y().then(u=>({label:"Ollama (local AI)",...u}))],p=Date.now()-i;if(t){const u=Z(m,r);if(u.length)return o||(console.log(),u.forEach(g=>console.log(` ${w("\u2714")} Fixed: ${g}`)),console.log()),ne(o?["doctor","--json"]:["doctor"])}if(o){const u={pass:0,warn:0,fail:0,info:0};m.forEach(g=>u[g.status]=(u[g.status]||0)+1),console.log(JSON.stringify({ok:u.fail===0,counts:u,results:m,elapsed:p}));return}oe(m,p),m.some(u=>u.status==="fail")&&process.exit(1)}export{ne as doctorCommand};
@@ -1,15 +1,15 @@
1
- import*as c from"node:fs";import*as O from"node:path";import"node:os";import{bold as v,cyan as P,gray as t,green as A,red as S}from"../ui/output.mjs";import{ampPaths as U,appendEntry as V,readEntries as q,readConfig as W,resolveRotationSettings as B,pruneEntries as G}from"../amp/io.mjs";import{refreshRuleFilesFromMemory as L}from"../ruleFiles.mjs";function J(){return U(process.cwd())}const j=["gotcha","decision","attempt","note","detection","pattern","preference","theme","handoff","error"],D=["worked","failed","partial","unknown"];function Y(){return q(process.cwd())}function M(n,{auto:l=!1,quiet:e=!1}={}){const s=process.cwd(),p=O.join(s,".ai-memory"),a=O.join(s,"inferno");if(l&&!c.existsSync(p)&&!c.existsSync(a))return!1;!c.existsSync(p)&&!c.existsSync(a)&&(e||console.error(S(` \u2718 no .ai-memory/ or inferno/ \u2014 run: infernoflow init
2
- `)),process.exit(1)),V(s,n);try{B(W(s)).auto&&G(s)}catch{}try{L(s)}catch{}return!0}function X(){return process.env.CURSOR_SESSION?"cursor":process.env.COPILOT_SESSION?"copilot":process.env.CLAUDE_CODE_SESSION?"claude":process.env.WINDSURF_SESSION?"windsurf":process.env.INFERNOFLOW_AGENT?process.env.INFERNOFLOW_AGENT:"human"}function z(n,l){const e=new Date(n.ts).toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}),s=n.type||"note",p=s==="gotcha"?"\x1B[33m":s==="decision"?"\x1B[36m":s==="theme"?"\x1B[35m":s==="preference"?"\x1B[34m":s==="attempt"?"\x1B[90m":s==="error"?"\x1B[31m":"\x1B[0m",a="\x1B[0m",m=n.result?` [${n.result}]`:"",f=n.agent&&n.agent!=="human"?t(` (${n.agent})`):"";return` ${t(String(l+1).padStart(3))} ${t(e)} ${p}${s}${a}${m} ${n.summary}${f}`}async function te(n){const l=o=>n.includes(o),e=(o,r)=>{const g=n.indexOf(o);return g!==-1&&n[g+1]?n[g+1]:r},s=l("--show"),p=l("--clear"),a=l("--json"),m=l("--auto"),f=l("--quiet"),y=e("--source",null);if(s||a){const o=Y(),r=n[n.indexOf("--show")+1],g=r&&/^\d+$/.test(r)?parseInt(r):20,d=o.slice(-g);if(a){console.log(JSON.stringify(d,null,2));return}if(console.log(`
3
- `+v("\u{1F525} infernoflow \u2014 session memory")),console.log(" "+"\u2500".repeat(50)),!d.length){console.log(t(`
1
+ import*as c from"node:fs";import*as E from"node:path";import"node:os";import{bold as j,cyan as I,gray as o,green as b,yellow as P,red as S,orange as U}from"../ui/output.mjs";import{ampPaths as V,appendEntry as W,readEntries as q,readConfig as G,resolveRotationSettings as J,pruneEntries as Y}from"../amp/io.mjs";import{refreshRuleFilesFromMemory as R}from"../ruleFiles.mjs";function M(){return V(process.cwd())}const _=["gotcha","decision","attempt","note","detection","pattern","preference","theme","handoff","error"],T=["worked","failed","partial","unknown"];function X(){return q(process.cwd())}function z(t,{auto:l=!1,quiet:n=!1}={}){const r=process.cwd(),h=E.join(r,".ai-memory"),g=E.join(r,"inferno");if(l&&!c.existsSync(h)&&!c.existsSync(g))return!1;!c.existsSync(h)&&!c.existsSync(g)&&(n||console.error(S(` \u2718 no .ai-memory/ or inferno/ \u2014 run: infernoflow init
2
+ `)),process.exit(1)),W(r,t);try{J(G(r)).auto&&Y(r)}catch{}try{R(r)}catch{}return!0}function H(){return process.env.CURSOR_SESSION?"cursor":process.env.COPILOT_SESSION?"copilot":process.env.CLAUDE_CODE_SESSION?"claude":process.env.WINDSURF_SESSION?"windsurf":process.env.INFERNOFLOW_AGENT?process.env.INFERNOFLOW_AGENT:"human"}function K(t,l){const n=new Date(t.ts).toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}),r=t.type||"note",h=r==="gotcha"?P:r==="decision"?I:r==="theme"?U:r==="preference"?I:r==="attempt"?o:r==="error"?S:d=>d,g=t.result?` [${t.result}]`:"",m=t.agent&&t.agent!=="human"?o(` (${t.agent})`):"";return` ${o(String(l+1).padStart(3))} ${o(n)} ${h(r)}${g} ${t.summary}${m}`}async function re(t){const l=e=>t.includes(e),n=(e,a)=>{const i=t.indexOf(e);return i!==-1&&t[i+1]?t[i+1]:a};l("--no-color")&&(process.env.NO_COLOR="1");const r=l("--show"),h=l("--clear"),g=l("--json"),m=l("--auto"),d=l("--quiet"),w=n("--source",null);if(r||g){const e=X(),a=t[t.indexOf("--show")+1],i=a&&/^\d+$/.test(a)?parseInt(a):20,s=e.slice(-i);if(g){console.log(JSON.stringify(s,null,2));return}if(console.log(`
3
+ `+j("\u{1F525} infernoflow \u2014 session memory")),console.log(" "+"\u2500".repeat(50)),!s.length){console.log(o(`
4
4
  No entries yet. Start logging with: infernoflow log "<what happened>"
5
- `));return}console.log(t(` Showing last ${d.length} of ${o.length} entries
6
- `)),d.forEach((T,C)=>console.log(z(T,o.length-d.length+C))),console.log();return}if(p){const{sessions:o}=J();if(!c.existsSync(o)){console.log(t(` Nothing to clear.
7
- `));return}const r=o.replace(".jsonl",`-archive-${Date.now()}.jsonl`);c.renameSync(o,r),console.log(A(` \u2714 Session log archived \u2192 ${O.basename(r)}
8
- `));return}const R=new Set([e("--type",""),e("--result",""),e("--agent",""),e("--source",""),e("--file",""),e("--line",""),e("--tags",""),e("--detail",""),e("--detail-file","")].filter(Boolean)),$=n.slice(1).filter(o=>!o.startsWith("--")&&!R.has(o)).join(" ").trim();if(!$){console.log(`
9
- `+v("\u{1F525} infernoflow log")+` \u2014 append to session memory
10
- `),console.log(t(" Usage:")),console.log(t(' infernoflow log "what happened"')),console.log(t(' infernoflow log "tried X, failed because Y" --type attempt --result failed')),console.log(t(' infernoflow log "always use multipart/form-data" --type gotcha')),console.log(t(' infernoflow log "switched to dark mode" --type theme')),console.log(t(" infernoflow log --show Print last 20 entries")),console.log(t(" infernoflow log --json Print as JSON")),console.log(),console.log(t(" Types: note \xB7 attempt \xB7 decision \xB7 gotcha \xB7 preference \xB7 theme \xB7 handoff \xB7 error")),console.log(t(" Results: worked \xB7 failed \xB7 partial \xB7 unknown")),console.log(t(` Auto-capture: --auto (silent skip if no inferno/) \xB7 --quiet \xB7 --source <name>
11
- `));return}const h=e("--type","note"),u=e("--result",null),_=e("--agent",X()),b=e("--file",null),x=e("--line",null),F=e("--tags",null);j.includes(h)||(f||console.error(S(` \u2718 Invalid type: ${h}. Valid: ${j.join(", ")}
12
- `)),process.exit(1)),u&&!D.includes(u)&&(f||console.error(S(` \u2718 Invalid result: ${u}. Valid: ${D.join(", ")}
13
- `)),process.exit(1));const I=x&&/^\d+$/.test(x)?parseInt(x,10):null,E=F?F.split(",").map(o=>o.trim()).filter(Boolean):null,N=e("--detail",null),w=e("--detail-file",null);let i=null;if(w)try{i=w==="-"?c.readFileSync(0,"utf8"):c.readFileSync(w,"utf8")}catch(o){f||console.error(S(` \u2718 Could not read --detail-file ${w}: ${o.message}
14
- `)),process.exit(1)}else N&&(i=N);const k={ts:new Date().toISOString(),agent:_,type:h,summary:$,...u?{result:u}:{},...y?{source:y}:{},...b?{file:b}:{},...I?{line:I}:{},...E&&E.length?{tags:E}:{},...i&&i.trim()?{detail:i}:{},...m?{auto:!0}:{}};if(M(k,{auto:m,quiet:f})){if(n.includes("--refresh-rules"))try{L(process.cwd())}catch{}if(!f){const o=h!=="note"?P(` [${h}]`):"",r=u?t(` \u2192 ${u}`):"",g=y?t(` (via ${y})`):"",d=i&&i.trim()?t(` (+${i.length}-char detail)`):"";console.log(A(` \u2714 Logged${o}${r}${g}: `)+$+d+`
15
- `)}}}export{te as logCommand};
5
+ `));return}console.log(o(` Showing last ${s.length} of ${e.length} entries
6
+ `)),s.forEach((u,O)=>console.log(K(u,e.length-s.length+O))),console.log();return}if(h){const e=M(),a=Date.now(),i=new Set([e.sessions,e.currentBranchFile,e.defaultBranchFile].filter(Boolean));try{if(e.branchesDir&&c.existsSync(e.branchesDir))for(const u of c.readdirSync(e.branchesDir))u.endsWith(".jsonl")&&i.add(E.join(e.branchesDir,u))}catch{}const s=[];for(const u of i){if(!c.existsSync(u))continue;const O=u.replace(/\.jsonl$/,`-archive-${a}.jsonl`);try{c.renameSync(u,O),s.push(E.basename(O))}catch{}}if(!s.length){console.log(o(` Nothing to clear.
7
+ `));return}console.log(b(` \u2714 Session log archived (${s.length} file${s.length===1?"":"s"}) \u2014 entries cleared from active memory.
8
+ `));return}const k=new Set([n("--type",""),n("--result",""),n("--agent",""),n("--source",""),n("--file",""),n("--line",""),n("--tags",""),n("--detail",""),n("--detail-file","")].filter(Boolean)),v=t.slice(1).filter(e=>!e.startsWith("--")&&!k.has(e)).join(" ").trim();if(!v){console.log(`
9
+ `+j("\u{1F525} infernoflow log")+` \u2014 append to session memory
10
+ `),console.log(o(" Usage:")),console.log(o(' infernoflow log "what happened"')),console.log(o(' infernoflow log "tried X, failed because Y" --type attempt --result failed')),console.log(o(' infernoflow log "always use multipart/form-data" --type gotcha')),console.log(o(' infernoflow log "switched to dark mode" --type theme')),console.log(o(" infernoflow log --show Print last 20 entries")),console.log(o(" infernoflow log --json Print as JSON")),console.log(),console.log(o(" Types: note \xB7 attempt \xB7 decision \xB7 gotcha \xB7 preference \xB7 theme \xB7 handoff \xB7 error")),console.log(o(" Results: worked \xB7 failed \xB7 partial \xB7 unknown")),console.log(o(` Auto-capture: --auto (silent skip if no inferno/) \xB7 --quiet \xB7 --source <name>
11
+ `));return}const y=n("--type","note"),p=n("--result",null),C=n("--agent",H()),A=n("--file",null),F=n("--line",null),D=n("--tags",null);_.includes(y)||(d||console.error(S(` \u2718 Invalid type: ${y}. Valid: ${_.join(", ")}
12
+ `)),process.exit(1)),p&&!T.includes(p)&&(d||console.error(S(` \u2718 Invalid result: ${p}. Valid: ${T.join(", ")}
13
+ `)),process.exit(1));const L=F&&/^\d+$/.test(F)?parseInt(F,10):null,N=D?D.split(",").map(e=>e.trim()).filter(Boolean):null,x=n("--detail",null),$=n("--detail-file",null);let f=null;if($)try{f=$==="-"?c.readFileSync(0,"utf8"):c.readFileSync($,"utf8")}catch(e){d||console.error(S(` \u2718 Could not read --detail-file ${$}: ${e.message}
14
+ `)),process.exit(1)}else x&&(f=x);const B={ts:new Date().toISOString(),agent:C,type:y,summary:v,...p?{result:p}:{},...w?{source:w}:{},...A?{file:A}:{},...L?{line:L}:{},...N&&N.length?{tags:N}:{},...f&&f.trim()?{detail:f}:{},...m?{auto:!0}:{}};if(z(B,{auto:m,quiet:d})){if(t.includes("--refresh-rules"))try{R(process.cwd())}catch{}if(!d){const e=y!=="note"?I(` [${y}]`):"",a=p?o(` \u2192 ${p}`):"",i=w?o(` (via ${w})`):"",s=f&&f.trim()?o(` (+${f.length}-char detail)`):"";console.log(b(` \u2714 Logged${e}${a}${i}: `)+v+s+`
15
+ `)}}}export{re as logCommand};
@@ -1,6 +1,35 @@
1
- import*as s from"node:fs";import*as a from"node:path";import*as P from"node:os";import{fileURLToPath as J}from"node:url";import{execSync as O}from"node:child_process";import{detectIdeContext as F}from"../ai/ideDetection.mjs";import{header as N,ok as y,warn as R,info as w,done as T,cyan as p,yellow as v,bold as g,green as u,gray as I}from"../ui/output.mjs";import"../cursorHooksInstall.mjs";import"../vsCodeCopilotHooksInstall.mjs";import{updateInjectionConfig as V}from"../amp/io.mjs";import{injectionPatchFromArgs as L}from"./refresh.mjs";const A=a.dirname(J(import.meta.url));function j(){return a.resolve(A,"../../templates")}function D(n){try{return O(`npx infernoflow ${n}`,{encoding:"utf8",cwd:process.cwd(),timeout:6e4,stdio:["inherit","pipe","pipe"]})}catch(t){return t.stdout||t.stderr||t.message}}const W=["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(n){const t=a.join(P.homedir(),".claude.json");let c={};if(s.existsSync(t))try{c=JSON.parse(s.readFileSync(t,"utf8"))}catch{c={}}c.mcpServers||(c.mcpServers={});const o=c.mcpServers.infernoflow;if(o&&o.args&&o.args[0]===n)return{updated:!1};c.mcpServers.infernoflow={command:"node",args:[n]};const r=JSON.stringify(c,null,2).replace(/\u0000+/g,"");return s.writeFileSync(t,r,"utf8"),{updated:!0,path:t}}function H(n,t){const c=a.join(n,".vscode"),o=a.join(c,"mcp.json");let r={};if(s.existsSync(o))try{r=JSON.parse(s.readFileSync(o,"utf8"))}catch{r={}}r.servers||(r.servers={});const i=r.servers.infernoflow;return i&&i.args&&i.args[0]===t?{updated:!1}:(r.servers.infernoflow={type:"stdio",command:"node",args:[t]},s.mkdirSync(c,{recursive:!0}),s.writeFileSync(o,JSON.stringify(r,null,2)+`
2
- `,"utf8"),{updated:!0,path:o})}function U(n,t){const c=a.join(n,".claude"),o=a.join(c,"settings.json");let r={};if(s.existsSync(o))try{r=JSON.parse(s.readFileSync(o,"utf8"))}catch{r={}}const i=new Set(r.allowedTools||[]);for(const f of W)i.add(`mcp__infernoflow__${f}`);const m={...r,allowedTools:[...i]};return s.mkdirSync(c,{recursive:!0}),s.writeFileSync(o,JSON.stringify(m,null,2),"utf8"),o}function q(n,{silent:t=!1}={}){const c=j(),o=t?()=>{}:e=>y(e),r=t?()=>{}:e=>R(e),i={mcpServer:!1,claudeJson:!1,claudeSettings:!1},m=a.join(c,"cursor","inferno-mcp-server.mjs"),f=a.join(n,".cursor","inferno-mcp-server.mjs");try{s.existsSync(f)||(s.mkdirSync(a.dirname(f),{recursive:!0}),s.copyFileSync(m,f),i.mcpServer=!0,o("Copied MCP server \u2192 "+p(".cursor/inferno-mcp-server.mjs")))}catch(e){r("MCP server copy skipped: "+e.message)}try{b(f).updated&&(i.claudeJson=!0,o("Registered MCP server in "+p("~/.claude.json")))}catch(e){r("~/.claude.json update skipped: "+e.message)}try{H(n,f).updated&&(i.vscodeMcp=!0,o("Registered MCP server in "+p(".vscode/mcp.json")+I(" (Copilot Chat)")))}catch(e){r(".vscode/mcp.json update skipped: "+e.message)}try{const e=a.join(n,".cursor","mcp.json");let l={};if(s.existsSync(e))try{l=JSON.parse(s.readFileSync(e,"utf8"))}catch{l={}}l.mcpServers||(l.mcpServers={});const d=l.mcpServers.infernoflow;(!d||!d.args||d.args[0]!==f)&&(l.mcpServers.infernoflow={command:"node",args:[f],env:{}},s.mkdirSync(a.dirname(e),{recursive:!0}),s.writeFileSync(e,JSON.stringify(l,null,2)+`
3
- `,"utf8"),i.cursorMcp=!0,o("Registered MCP server in "+p(".cursor/mcp.json")))}catch(e){r(".cursor/mcp.json update skipped: "+e.message)}try{U(n,!1),i.claudeSettings=!0,o("Pre-approved infernoflow tools in "+p(".claude/settings.json"))}catch(e){r(".claude/settings.json skipped: "+e.message)}return i}async function oe(n){const t=process.cwd(),c=n.includes("--force")||n.includes("-f"),o=n.includes("--yes")||n.includes("-y"),r=j();N("infernoflow setup");const{ideDetected:i}=F("auto");w(`IDE detected: ${g(i==="cursor"?"Cursor":i==="vscode"?"VS Code":i==="windsurf"?"Windsurf":"unknown")}`);const f=a.join(t,".ai-memory");s.existsSync(f)?y(".ai-memory/ already exists \u2014 skipping init"):(console.log(`
4
- ${v(".ai-memory/")} not found \u2014 running init ...
5
- `),D(o?"init --yes":"init"));const e=L(n);if(Object.keys(e).length)try{V(t,e),y("Injection config updated \u2192 "+JSON.stringify(e))}catch{}console.log(),w("Wiring up MCP servers for Cursor / VS Code Copilot / Claude Code ...");const l=q(t,{silent:!1});console.log(),T("infernoflow ready"),console.log(`
6
- ${g("What was set up:")}`),console.log(` ${u("\u2714")} MCP server installed \u2192 ${p(".cursor/inferno-mcp-server.mjs")}`),l.cursorMcp&&console.log(` ${u("\u2714")} Cursor MCP config \u2192 ${p(".cursor/mcp.json")}`),l.vscodeMcp&&console.log(` ${u("\u2714")} VS Code Copilot MCP config \u2192 ${p(".vscode/mcp.json")}`),l.claudeJson&&console.log(` ${u("\u2714")} Claude Code MCP config \u2192 ${p("~/.claude.json")}`),l.claudeSettings&&console.log(` ${u("\u2714")} Auto-approved tools \u2192 ${p(".claude/settings.json")}`);try{const{detectStaleMcpRuntime:d}=await import("../mcpRuntime.mjs"),{readFileSync:C}=await import("node:fs"),{dirname:h,join:_}=await import("node:path"),{fileURLToPath:x}=await import("node:url"),M=h(x(import.meta.url)),$=_(M,"..","..","package.json"),k=JSON.parse(C($,"utf8")).version,S=d(t,k);S&&(console.log(),console.log(` ${v("\u26A0")} ${g("Restart required:")} ${S.message}`))}catch{}console.log(),console.log(` ${g("Next step:")} Restart your AI tool. Test by asking:`),console.log(` ${p('"call the amp_write tool with a test note"')}`),console.log()}export{W as MCP_TOOLS,q as autoSetupMcp,oe as setupCommand,b as updateClaudeJson,H as updateVscodeMcpJson,U as writeClaudeSettings};
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*$/,"")+`
2
+
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
7
+ // infernoflow UserPromptSubmit hook (Claude Code / Cursor).
8
+ // Logs a best-effort 'attempt' entry when the user's prompt shows frustration,
9
+ // so the highest-value capture signal doesn't depend on the model remembering.
10
+ // Never blocks the prompt.
11
+ import { readFileSync } from "node:fs";
12
+ import { spawnSync } from "node:child_process";
13
+
14
+ let raw = "";
15
+ try { raw = readFileSync(0, "utf8"); } catch {}
16
+ let prompt = "";
17
+ try { const j = JSON.parse(raw); prompt = j.prompt || j.user_prompt || j.userPrompt || ""; }
18
+ catch { prompt = raw; }
19
+
20
+ const MARKERS = [/!!+/, /not working/i, /still (broken|failing|not)/i, /does ?n'?t work/i, /\\bbroken\\b/i, /\\bretry(ing)?\\b/i, /same error/i, /no change/i];
21
+ if (prompt && MARKERS.some((re) => re.test(prompt))) {
22
+ const msg = "User frustration: " + prompt.replace(/\\s+/g, " ").trim().slice(0, 120);
23
+ try {
24
+ spawnSync("infernoflow", ["log", msg, "--type", "attempt", "--result", "failed", "--auto", "--quiet", "--source", "hook"], {
25
+ stdio: "ignore", timeout: 5000, shell: process.platform === "win32",
26
+ });
27
+ } catch {}
28
+ }
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};