infernoflow 0.45.0 → 0.46.0

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/README.md CHANGED
@@ -85,7 +85,8 @@ These cover 95% of usage:
85
85
  | Command | What it does |
86
86
  |---|---|
87
87
  | `infernoflow log "..."` | Remember a gotcha / decision / attempt / note. `--type gotcha\|decision\|attempt\|preference` |
88
- | `infernoflow ask "..."` | Search your memory by keyword — gotchas surface first |
88
+ | `infernoflow resume` | **🆕 0.46** "Where were we?" in one call — last resume point, open dead ends, recent decisions, uncommitted work. `--file <path>` ranks by file |
89
+ | `infernoflow ask "..."` | Search your memory by keyword — gotchas surface first. `--file <path>` ranks entries about that file first |
89
90
  | `infernoflow switch` | Generate a handoff for the next session. `--copy` puts it on your clipboard |
90
91
  | `infernoflow recap` | End-of-session summary with health score + unlogged-change detection |
91
92
  | `infernoflow status` | Quick health check — entries, gotchas, decisions, last activity |
@@ -93,6 +94,11 @@ These cover 95% of usage:
93
94
  | `infernoflow refresh` | Manually rebuild `CLAUDE.md` / `.cursorrules` / `copilot-instructions.md` from memory |
94
95
  | `infernoflow forget <id\|prefix>` | Delete a memory entry without hand-editing JSONL. `--last` for the newest |
95
96
  | `infernoflow prune` | Archive stale `note` / `attempt` entries older than 30 days. Gotchas/decisions/bookmarks never auto-pruned. Default dry-run; `--apply` to act |
97
+ | `infernoflow resolve <id>` | **🆕 0.46** Mark an entry fixed/outdated — stays searchable, no longer injected. Entries whose file changed since they were written show **"may be stale"** |
98
+ | `infernoflow curate` | **🆕 0.46** Remove noise: old commit notes, duplicates, raw frustration prompts. Dry-run; `--apply` |
99
+ | `infernoflow move <id…> --to <dir>` | **🆕 0.46** Move misfiled entries to another project's memory. Dry-run; `--apply` |
100
+
101
+ Any command takes `--project <dir>` to work on another project's memory (multi-folder workspaces, hooks, agents).
96
102
 
97
103
  In practice you barely run any of these — the MCP-aware AI does it for you. The CLI is for grep-style introspection.
98
104
 
@@ -193,8 +199,9 @@ When the MCP server is wired, your AI agent can call these directly in chat:
193
199
 
194
200
  | Tool | What it does |
195
201
  |---|---|
196
- | `amp_write` | Log an entry (`type`, `msg`, optional `file` / `line` / `tags` / `detail`) |
197
- | `amp_read` | Read entries with optional filters |
202
+ | `amp_write` | Log an entry (`type`, `msg`, optional `file` / `line` / `tags` / `detail`). In a multi-folder workspace an entry about another open folder's file goes to that folder's memory |
203
+ | `amp_resume` | **🆕 0.46** "Where were we?" — last resume point, open dead ends, recent decisions, uncommitted changes |
204
+ | `amp_read` | Read entries with optional filters (`type`, `query`, `file` — entries about that file rank first) |
198
205
  | `amp_search` | Keyword search across entries |
199
206
  | `amp_bookmark` | **🆕** Drop a named resume point — auto-captures the current session transcript when no `note` is given |
200
207
  | `amp_handoff` | Generate the handoff document for the next AI session |
@@ -1,40 +1,44 @@
1
1
  #!/usr/bin/env node
2
- (function(){if(process.platform!=="win32"||process.argv[2]==="mcp"||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)",mcp:"Run the MCP server (used by AI tool configs \u2014 not run by hand)"},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),mcp:async o=>(await import("../lib/commands/mcp.mjs")).mcpCommand(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","mcp"],"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
- ${n.join(" ")}`).join(`
2
+ (function(){if(process.platform!=="win32"||process.argv[2]==="mcp"||process.env.WT_SESSION||process.env.ConEmuPID||process.env.TERM_PROGRAM==="vscode")return;const t={"\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(t).join("|"),"g"),f=/[\u{1F000}-\u{1FAFF}\u{2600}-\u{27BF}\u{2B00}-\u{2BFF}\u{200D}]\u{FE0F}? ?/gu,w=c=>c.replace(r,y=>t[y]).replace(f,"");function k(c){const y=c.write.bind(c);c.write=function(a,...b){return typeof a=="string"?a=w(a):Buffer.isBuffer(a)&&(a=Buffer.from(w(a.toString("utf8")),"utf8")),y(a,...b)}}k(process.stdout),k(process.stderr)})();import{readFileSync as x}from"node:fs";import{dirname as A,join as C}from"node:path";import{fileURLToPath as I}from"node:url";import{bold as i,gray as e,cyan as s,red as p}from"../lib/ui/output.mjs";const M=A(I(import.meta.url));function S(o){for(const t of[C(o,"..","..","package.json"),C(o,"..","package.json")])try{return JSON.parse(x(t,"utf8"))}catch{}return{version:"0.0.0-source"}}const j=S(M),l=j.version||"0.0.0",v={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)",mcp:"Run the MCP server (used by AI tool configs \u2014 not run by hand)",resume:"Where were we? Latest resume point, open dead ends, recent decisions, uncommitted work",resolve:"Mark an entry resolved (kept searchable, no longer injected) \u2014 resolve <id> [--note]",move:"Move misfiled entries to another project \u2014 move <id\u2026>|--query q --to <dir> [--apply]",curate:"Remove noise: commit notes, duplicates, old raw frustration prompts [--apply]",transcript:"Print this project's current Claude Code session transcript (plain text)",hook:"Entry point for AI-tool hooks (session-start / session-end) \u2014 not run by hand"},h={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),mcp:async o=>(await import("../lib/commands/mcp.mjs")).mcpCommand(o),hook:async o=>(await import("../lib/commands/hook.mjs")).hookCommand(o),resume:async o=>(await import("../lib/commands/resume.mjs")).resumeCommand(o),resolve:async o=>(await import("../lib/commands/resolve.mjs")).resolveCommand(o),move:async o=>(await import("../lib/commands/move.mjs")).moveCommand(o),curate:async o=>(await import("../lib/commands/curate.mjs")).curateCommand(o),transcript:async o=>(await import("../lib/commands/transcript.mjs")).transcriptCommand(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 B(){const o=Object.keys(v),t=Math.max(...o.map(r=>r.length),8)+1;return Object.entries(v).map(([r,f])=>` ${r.padEnd(t," ")}${f}`).join(`
3
+ `)}const O={Memory:["log","ask","resume","switch","recap","status","bookmark","resolve","refresh","forget","prune","curate","move","transcript"],Setup:["init","setup","doctor","context","mcp","hook"],"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 R(){return Object.entries(O).map(([o,t])=>` ${i(o+":")}
4
+ ${t.join(" ")}`).join(`
5
5
 
6
- `)}const w=Object.keys(d).length,R=`
7
- ${i("\u{1F525} infernoflow")} ${e("v"+m)}
6
+ `)}const $=Object.keys(h).length,E=`
7
+ ${i("\u{1F525} infernoflow")} ${e("v"+l)}
8
8
  ${e("Persistent memory for AI coding sessions")}
9
9
 
10
10
  ${i("Usage:")}
11
11
  infernoflow [command] [options]
12
12
 
13
13
  ${i("Memory")} ${e("\u2014 capture, recall & curate")}
14
- ${t("log")} ${e('"..."')} Add to session memory ${e("(--type gotcha|decision|attempt)")}
15
- ${t("ask")} ${e('"..."')} Search your memory by keyword ${e("(gotchas surface first)")}
16
- ${t("switch")} Generate handoff for next AI agent
17
- ${t("recap")} End-of-session health score + unlogged changes
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)")}
14
+ ${s("log")} ${e('"..."')} Add to session memory ${e("(--type gotcha|decision|attempt)")}
15
+ ${s("ask")} ${e('"..."')} Search your memory by keyword ${e("(gotchas surface first)")}
16
+ ${s("switch")} Generate handoff for next AI agent
17
+ ${s("recap")} End-of-session health score + unlogged changes
18
+ ${s("status")} Quick health check
19
+ ${s("forget")} Delete a bad entry ${e("(--last for the newest, or by id/prefix)")}
20
+ ${s("prune")} Archive stale notes/attempts ${e("(dry-run; --apply to commit)")}
21
21
 
22
22
  ${i("Setup")}
23
- ${t("init")} 60-second setup ${e("(memory mode by default)")}
24
- ${t("setup")} Re-run IDE wiring ${e("(idempotent \u2014 MCP + hooks)")}
25
- ${t("doctor")} Diagnose your setup
26
- ${t("context")} Generate AI-ready context for new sessions
23
+ ${s("init")} 60-second setup ${e("(memory mode by default)")}
24
+ ${s("setup")} Re-run IDE wiring ${e("(idempotent \u2014 MCP + hooks)")}
25
+ ${s("doctor")} Diagnose your setup
26
+ ${s("context")} Generate AI-ready context for new sessions
27
27
 
28
28
  ${i("Subsystems")} ${e("\u2014 grouped, run for verbs:")}
29
- ${t("amp")} AI Memory Protocol ${e("(status, migrate, validate)")}
29
+ ${s("amp")} AI Memory Protocol ${e("(status, migrate, validate)")}
30
30
 
31
- ${e("Run")} ${t("infernoflow commands")} ${e("to see all "+w+" commands grouped.")}
32
- ${e("Run")} ${t("infernoflow <command> --help")} ${e("for command-specific options.")}
33
- `;import*as E from"node:fs";import*as S from"node:path";try{const o=S.join(process.cwd(),"inferno");if(E.existsSync(o)){const{observeCommandStart:n}=await import("../lib/learning/observe.mjs"),r=process.argv[2];r&&!r.startsWith("-")&&n(o,r)}}catch{}const[,,s,...x]=process.argv;(!s||s==="--help"||s==="-h")&&(console.log(R),process.exit(0)),(s==="--version"||s==="-v")&&(console.log(m),process.exit(0)),s==="commands"&&(console.log(`
34
- ${i("\u{1F525} infernoflow")} ${e("v"+m)} ${e("\u2014 all "+w+" commands")}
35
- `),console.log(O()),console.log(`
36
- ${e("Run")} ${t("infernoflow <command> --help")} ${e("for options.")}
37
- `),process.exit(0));const D=Object.keys(d);D.includes(s)||(console.error(g(`
38
- Unknown command: ${s}`)),console.error(e("Run: infernoflow commands (see all commands)")),console.error(e(`Run: infernoflow --help (quick start)
39
- `)),process.exit(1));const P=[s,...x];try{const{runUpgradeBackfillIfNeeded:o}=await import("../lib/upgradeCheck.mjs");await o(m,s)}catch{}d[s](P).catch(o=>{console.error(g(`
31
+ ${e("Run")} ${s("infernoflow commands")} ${e("to see all "+$+" commands grouped.")}
32
+ ${e("Run")} ${s("infernoflow <command> --help")} ${e("for command-specific options.")}
33
+ `;import*as m from"node:fs";import*as d from"node:path";try{const o=d.join(process.cwd(),"inferno");if(m.existsSync(o)){const{observeCommandStart:t}=await import("../lib/learning/observe.mjs"),r=process.argv[2];r&&!r.startsWith("-")&&t(o,r)}}catch{}const[,,n,...u]=process.argv;(!n||n==="--help"||n==="-h")&&(console.log(E),process.exit(0)),(n==="--version"||n==="-v")&&(console.log(l),process.exit(0)),n==="commands"&&(console.log(`
34
+ ${i("\u{1F525} infernoflow")} ${e("v"+l)} ${e("\u2014 all "+$+" commands")}
35
+ `),console.log(R()),console.log(`
36
+ ${e("Run")} ${s("infernoflow <command> --help")} ${e("for options.")}
37
+ `),process.exit(0));const D=Object.keys(h);D.includes(n)||(console.error(p(`
38
+ Unknown command: ${n}`)),console.error(e("Run: infernoflow commands (see all commands)")),console.error(e(`Run: infernoflow --help (quick start)
39
+ `)),process.exit(1));const g=u.indexOf("--project");if(g!==-1){const o=u[g+1],t=o?d.resolve(o):null;(!t||!m.existsSync(t)||!m.statSync(t).isDirectory())&&(console.error(p(`
40
+ \u2718 --project: not a folder: ${o||"(missing)"}
41
+ `)),process.exit(1)),!["init","setup"].includes(n)&&!m.existsSync(d.join(t,".ai-memory"))&&!m.existsSync(d.join(t,"inferno"))&&(console.error(p(`
42
+ \u2718 --project: ${t} has no .ai-memory/ \u2014 run infernoflow init there
43
+ `)),process.exit(1)),u.splice(g,2),process.chdir(t)}const P=[n,...u];try{const{runUpgradeBackfillIfNeeded:o}=await import("../lib/upgradeCheck.mjs");await o(l,n)}catch{}h[n](P).catch(o=>{console.error(p(`
40
44
  Error: `)+o.message),process.exit(1)});
@@ -1,27 +1,30 @@
1
- import*as s from"node:fs";import{redactSecrets as Y}from"../security/redact.mjs";import*as G from"node:os";import*as a from"node:path";import{findProjectRoot as N}from"../projectRoot.mjs";import{getBranchInfo as U}from"../git/branch.mjs";const B="1.0",z=new Set(["gotcha","decision","attempt","note","detection","pattern"]),H=new Set(["copilot","cursor","claude","windsurf","other"]);function V(t){let e=a.basename(a.resolve(t));try{const o=a.join(t,".ai-memory","amp.json");if(s.existsSync(o)){const n=JSON.parse(s.readFileSync(o,"utf8"));n&&typeof n.project=="string"&&n.project.trim()&&(e=n.project.trim())}}catch{}return e.toLowerCase().replace(/[^a-z0-9_.\-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,64)||"unnamed-project"}function K(t,e){let o=process.env.INFERNOFLOW_GLOBAL_DIR;if(!o)try{const f=JSON.parse(s.readFileSync(a.join(e,"amp.json"),"utf8"));f&&typeof f.globalDir=="string"&&f.globalDir.trim()&&(o=f.globalDir.trim())}catch{}if(!o)return a.join(e,"global.jsonl");let n=o;n.startsWith("~")&&(n=a.join(G.homedir(),n.slice(1).replace(/^[\/\\]/,""))),a.isAbsolute(n)||(n=a.resolve(t,n));const r=V(t);return a.join(n,r,"global.jsonl")}function S(t,e={}){const o=e.literal?a.resolve(t):N(t),n=a.join(o,".ai-memory"),r=a.join(o,"inferno"),f=e.forWrite||s.existsSync(n)||!s.existsSync(r),c=f?n:r,i=f,u=a.join(c,"branches"),l=U(o),d=a.join(u,`${l.currentSlug}.jsonl`),y=l.defaultSlug?a.join(u,`${l.defaultSlug}.jsonl`):null,m=Z(o,c);return{root:c,projectRoot:o,isAmp:i,sessions:a.join(c,"sessions.jsonl"),config:a.join(c,i?"amp.json":"config.json"),handoff:a.join(c,i?"handoff.md":"HANDOFF.md"),globalFile:m,branchesDir:u,currentBranchFile:d,defaultBranchFile:y,branch:l}}function dt(t){try{const e=S(t),o=e.branch&&e.branch.current;return`store: ${e.root}${o&&!e.branch.isSynthetic?` (branch ${o})`:""}`}catch{return"store: (unresolved)"}}function Z(t,e){try{return K(t,e)}catch{return a.join(e,"global.jsonl")}}function A(t){const e=N(t),o=a.join(e,".ai-memory");return s.existsSync(o)||s.mkdirSync(o,{recursive:!0}),o}const E="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function w(){let t=Date.now(),e="";for(let n=0;n<10;n++)e=E[t%32]+e,t=Math.floor(t/32);let o="";for(let n=0;n<16;n++)o+=E[Math.floor(Math.random()*32)];return e+o}function J(t){const e={...t.meta||{}};let o=t.type||"note";z.has(o)||(e.subtype=o,o="note"),t.result&&(e.result=t.result),t.detailRef&&(e.detailRef=t.detailRef);let n;const r=t.agent;r&&H.has(r)?n=r:r&&(e.agent=r);const f=typeof t.ts=="number"?t.ts:t.ts?Date.parse(t.ts):Date.now(),c=t.confidence!=null?t.confidence:t.auto?.7:void 0,i={type:o,msg:t.summary||t.msg||"",ts:f,id:t.id||`amp_${w()}`};return t.file&&(i.file=t.file),t.line&&(i.line=t.line),t.function&&(i.function=t.function),t.tags&&t.tags.length&&(i.tags=t.tags),t.source&&(i.source=t.source),n&&(i.tool=n),t.session&&(i.session=t.session),c!=null&&(i.confidence=c),Object.keys(e).length&&(i.meta=e),i}function q(t){if(t.summary&&!t.msg)return t;const e=t.meta||{},o=e.subtype||t.type||"note",n={ts:t.ts,type:o,summary:t.msg||""};t.id&&(n.id=t.id),t.file&&(n.file=t.file),t.line&&(n.line=t.line),t.function&&(n.function=t.function),t.tags&&(n.tags=t.tags),t.source&&(n.source=t.source),t.tool&&(n.agent=t.tool),e.agent&&(n.agent=e.agent),e.result&&(n.result=e.result),e.detailRef&&(n.detailRef=e.detailRef),t.confidence!=null&&(n.confidence=t.confidence,t.confidence<1&&(n.auto=!0));const{subtype:r,agent:f,result:c,detailRef:i,...u}=e;return Object.keys(u).length&&(n.meta=u),n}function Q(t){if(!t||!s.existsSync(t))return[];try{return s.readFileSync(t,"utf8").split(`
2
- `).filter(Boolean).map(e=>{try{return JSON.parse(e)}catch{return null}}).filter(Boolean).map(q)}catch{return[]}}function yt(t){const e=S(t),o=new Set,n=[],r=[e.sessions,e.globalFile,e.defaultBranchFile,e.currentBranchFile],f=[...new Set(r.filter(Boolean))];for(const c of f)for(const i of Q(c)){const u=i.id||`${i.ts}|${i.summary}`;o.has(u)||(o.add(u),n.push(i))}return n.sort((c,i)=>{const u=typeof c.ts=="number"?c.ts:Date.parse(c.ts||0),l=typeof i.ts=="number"?i.ts:Date.parse(i.ts||0);return u-l})}function X(t,e){return e.target==="global"?t.globalFile:e.target==="legacy"?t.sessions:e.target==="branch"?t.currentBranchFile:e.type==="preference"?t.globalFile:t.currentBranchFile}const L=new Set;function F(t){return a.join(t,"details.jsonl")}const $="details.local.jsonl";function R(t){return a.join(t,$)}function tt(t,e,o,n=!1){const r=n?R(t):F(t);s.mkdirSync(a.dirname(r),{recursive:!0}),s.appendFileSync(r,JSON.stringify({id:e,body:o})+`
3
- `,"utf8")}function _(t,e,o=!1){if(!e)return null;let n;try{n=s.readFileSync(o?R(t):F(t),"utf8")}catch{return null}let r=null;for(const f of n.split(`
4
- `))if(f.trim())try{const c=JSON.parse(f);c.id===e&&typeof c.body=="string"&&(r=c.body)}catch{}return r}function I(t,e,o=!1){const n=o?R(t):F(t);let r;try{r=s.readFileSync(n,"utf8")}catch{return!1}const f=[];let c=!1;for(const i of r.split(`
5
- `))if(i.trim()){try{if(JSON.parse(i).id===e){c=!0;continue}}catch{}f.push(i)}return c&&s.writeFileSync(n,f.length?f.join(`
1
+ import{execFileSync as Y}from"node:child_process";import{readPersonalConfig as H,expandUserPath as z}from"../personalConfig.mjs";import*as c from"node:fs";import{redactSecrets as V}from"../security/redact.mjs";import"node:os";import*as a from"node:path";import{findProjectRoot as B}from"../projectRoot.mjs";import{getBranchInfo as K}from"../git/branch.mjs";const E="1.0",Z=new Set(["gotcha","decision","attempt","note","detection","pattern"]),q=new Set(["copilot","cursor","claude","windsurf","other"]);function Q(t){let e=a.basename(a.resolve(t));try{const r=a.join(t,".ai-memory","amp.json");if(c.existsSync(r)){const n=JSON.parse(c.readFileSync(r,"utf8"));n&&typeof n.project=="string"&&n.project.trim()&&(e=n.project.trim())}}catch{}return e.toLowerCase().replace(/[^a-z0-9_.\-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,64)||"unnamed-project"}function X(t,e){let r=process.env.INFERNOFLOW_GLOBAL_DIR;if(!r){const f=H();typeof f.globalDir=="string"&&f.globalDir.trim()&&(r=f.globalDir.trim())}if(!r)return a.join(e,"global.jsonl");const n=z(r),o=Q(t);return a.join(n,o,"global.jsonl")}function g(t,e={}){const r=e.literal?a.resolve(t):B(t),n=a.join(r,".ai-memory"),o=a.join(r,"inferno"),f=e.forWrite||c.existsSync(n)||!c.existsSync(o),s=f?n:o,i=f,u=a.join(s,"branches"),l=K(r),d=a.join(u,`${l.currentSlug}.jsonl`),y=l.defaultSlug?a.join(u,`${l.defaultSlug}.jsonl`):null,S=tt(r,s);return{root:s,projectRoot:r,isAmp:i,sessions:a.join(s,"sessions.jsonl"),config:a.join(s,i?"amp.json":"config.json"),handoff:a.join(s,i?"handoff.md":"HANDOFF.md"),globalFile:S,branchesDir:u,currentBranchFile:d,defaultBranchFile:y,branch:l}}function Ft(t){try{const e=g(t),r=e.branch&&e.branch.current;return`store: ${e.root}${r&&!e.branch.isSynthetic?` (branch ${r})`:""}`}catch{return"store: (unresolved)"}}function tt(t,e){try{return X(t,e)}catch{return a.join(e,"global.jsonl")}}function A(t){const e=B(t),r=a.join(e,".ai-memory");return c.existsSync(r)||c.mkdirSync(r,{recursive:!0}),r}const _="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function J(){let t=Date.now(),e="";for(let n=0;n<10;n++)e=_[t%32]+e,t=Math.floor(t/32);let r="";for(let n=0;n<16;n++)r+=_[Math.floor(Math.random()*32)];return e+r}function L(t){const e={...t.meta||{}};let r=t.type||"note";Z.has(r)||(e.subtype=r,r="note"),t.result&&(e.result=t.result),t.detailRef&&(e.detailRef=t.detailRef);let n;const o=t.agent;o&&q.has(o)?n=o:o&&(e.agent=o);const f=typeof t.ts=="number"?t.ts:t.ts?Date.parse(t.ts):Date.now(),s=t.confidence!=null?t.confidence:t.auto?.7:void 0,i={type:r,msg:t.summary||t.msg||"",ts:f,id:t.id||`amp_${J()}`};return t.file&&(i.file=t.file),t.line&&(i.line=t.line),t.function&&(i.function=t.function),t.tags&&t.tags.length&&(i.tags=t.tags),t.source&&(i.source=t.source),n&&(i.tool=n),t.session&&(i.session=t.session),s!=null&&(i.confidence=s),Object.keys(e).length&&(i.meta=e),i}function et(t){if(t.summary&&!t.msg)return t;const e=t.meta||{},r=e.subtype||t.type||"note",n={ts:t.ts,type:r,summary:t.msg||""};t.id&&(n.id=t.id),t.file&&(n.file=t.file),t.line&&(n.line=t.line),t.function&&(n.function=t.function),t.tags&&(n.tags=t.tags),t.source&&(n.source=t.source),t.tool&&(n.agent=t.tool),e.agent&&(n.agent=e.agent),e.result&&(n.result=e.result),e.detailRef&&(n.detailRef=e.detailRef),t.confidence!=null&&(n.confidence=t.confidence,t.confidence<1&&(n.auto=!0));const{subtype:o,agent:f,result:s,detailRef:i,...u}=e;return Object.keys(u).length&&(n.meta=u),n}function nt(t){if(!t||!c.existsSync(t))return[];try{return c.readFileSync(t,"utf8").split(`
2
+ `).filter(Boolean).map(e=>{try{return JSON.parse(e)}catch{return null}}).filter(Boolean).map(et)}catch{return[]}}function xt(t){const e=g(t),r=new Set,n=[],o=[e.sessions,e.globalFile,e.defaultBranchFile,e.currentBranchFile],f=[...new Set(o.filter(Boolean))];for(const s of f)for(const i of nt(s)){const u=i.id||`${i.ts}|${i.summary}`;r.has(u)||(r.add(u),n.push(i))}return n.sort((s,i)=>{const u=typeof s.ts=="number"?s.ts:Date.parse(s.ts||0),l=typeof i.ts=="number"?i.ts:Date.parse(i.ts||0);return u-l})}function rt(t,e){return e.target==="global"?t.globalFile:e.target==="legacy"?t.sessions:e.target==="branch"?t.currentBranchFile:e.type==="preference"?t.globalFile:t.currentBranchFile}const $=new Set;function x(t){return a.join(t,"details.jsonl")}const C="details.local.jsonl";function N(t){return a.join(t,C)}function ot(t,e,r,n=!1){const o=n?N(t):x(t);c.mkdirSync(a.dirname(o),{recursive:!0}),c.appendFileSync(o,JSON.stringify({id:e,body:r})+`
3
+ `,"utf8")}function I(t,e,r=!1){if(!e)return null;let n;try{n=c.readFileSync(r?N(t):x(t),"utf8")}catch{return null}let o=null;for(const f of n.split(`
4
+ `))if(f.trim())try{const s=JSON.parse(f);s.id===e&&typeof s.body=="string"&&(o=s.body)}catch{}return o}function P(t,e,r=!1){const n=r?N(t):x(t);let o;try{o=c.readFileSync(n,"utf8")}catch{return!1}const f=[];let s=!1;for(const i of o.split(`
5
+ `))if(i.trim()){try{if(JSON.parse(i).id===e){s=!0;continue}}catch{}f.push(i)}return s&&c.writeFileSync(n,f.length?f.join(`
6
6
  `)+`
7
- `:"","utf8"),c}function et(t){const e=a.join(t,"details");if(!s.existsSync(e))return 0;let o;try{o=s.readdirSync(e).filter(c=>c.endsWith(".md"))}catch{return 0}if(!o.length)return 0;const n=new Set;try{const c=s.readFileSync(F(t),"utf8");for(const i of c.split(`
8
- `))if(i.trim())try{n.add(JSON.parse(i).id)}catch{}}catch{}let r="",f=0;for(const c of o){const i=c.replace(/\.md$/,"");if(n.has(i))continue;let u;try{u=s.readFileSync(a.join(e,c),"utf8")}catch{continue}r+=JSON.stringify({id:i,body:u})+`
9
- `,f++}if(r)try{s.appendFileSync(F(t),r,"utf8")}catch{return 0}for(const c of o)try{s.unlinkSync(a.join(e,c))}catch{}try{s.rmdirSync(e)}catch{}return f}function ht(t,e){A(t);const o=S(t,{forWrite:!0});if(!L.has(o.root)){L.add(o.root);try{et(o.root)}catch{}}const n=e.id||`amp_${w()}`,r={...e,id:n},f=new Set,c=m=>{if(typeof m!="string")return m;const j=Y(m);return j.kinds.forEach(h=>f.add(h)),j.text};r.msg!==void 0&&(r.msg=c(r.msg)),r.summary!==void 0&&(r.summary=c(r.summary)),Array.isArray(r.tags)&&(r.tags=r.tags.map(c)),typeof r.result=="string"&&(r.result=c(r.result));const i=r.detailLocal===!0;delete r.detailLocal;const u=typeof e.detail=="string"?c(e.detail):e.detail;if(f.size&&(r.meta={...r.meta||{},redacted:[...f]}),typeof u=="string"&&u.trim())try{tt(o.root,n,u,i),r.detailRef=i?$:"details.jsonl"}catch{}delete r.detail;const l=X(o,r),d=J(r),y=JSON.stringify(d)+`
10
- `;if(s.mkdirSync(a.dirname(l),{recursive:!0}),s.appendFileSync(l,y,"utf8"),l!==o.sessions)try{s.mkdirSync(a.dirname(o.sessions),{recursive:!0}),s.appendFileSync(o.sessions,y,"utf8")}catch{}return d}function pt(t,e){const o=S(t);return a.join(o.root,"details",`${e}.md`)}function mt(t,e){if(!e)return null;const o=e.detailRef||e.meta&&e.meta.detailRef;if(!o)return null;const n=S(t),r=e.id||e.meta&&e.meta.id,f=_(n.root,r,!0);if(f!=null)return f;const c=_(n.root,r);if(c!=null)return c;if(typeof o=="string"&&/^details[\\/][A-Za-z0-9_.-]+\.md$/.test(o)&&!o.includes("..")){const i=a.join(n.root,"details"),u=a.join(n.root,o);try{const l=a.join(s.realpathSync(n.root),"details");if(s.lstatSync(i).isSymbolicLink()||s.realpathSync(i)!==l)return null;const d=s.realpathSync(u);return a.dirname(d)!==l?null:s.readFileSync(d,"utf8")}catch{return null}}return null}function gt(t,e){if(!e)return{removed:0,files:[]};const o=S(t),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(a.join(o.branchesDir,i))}catch{}const r=[...new Set(n)];let f=0;const c=[];for(const i of r){if(!s.existsSync(i))continue;let u;try{u=s.readFileSync(i,"utf8").split(`
11
- `)}catch{continue}const l=[];let d=0;for(const y of u){if(!y.trim())continue;let m;try{m=JSON.parse(y).id}catch{l.push(y);continue}if(m===e){d++,f++;continue}l.push(y)}d>0&&(s.writeFileSync(i,l.length?l.join(`
7
+ `:"","utf8"),s}function it(t){const e=a.join(t,"details");if(!c.existsSync(e))return 0;let r;try{r=c.readdirSync(e).filter(s=>s.endsWith(".md"))}catch{return 0}if(!r.length)return 0;const n=new Set;try{const s=c.readFileSync(x(t),"utf8");for(const i of s.split(`
8
+ `))if(i.trim())try{n.add(JSON.parse(i).id)}catch{}}catch{}let o="",f=0;for(const s of r){const i=s.replace(/\.md$/,"");if(n.has(i))continue;let u;try{u=c.readFileSync(a.join(e,s),"utf8")}catch{continue}o+=JSON.stringify({id:i,body:u})+`
9
+ `,f++}if(o)try{c.appendFileSync(x(t),o,"utf8")}catch{return 0}for(const s of r)try{c.unlinkSync(a.join(e,s))}catch{}try{c.rmdirSync(e)}catch{}return f}const O=new Map;function T(t,e){const r=t+"\0"+e.join("\0");if(O.has(r))return O.get(r);let n=null;try{n=Y("git",e,{cwd:t,encoding:"utf8",stdio:["ignore","pipe","ignore"],windowsHide:!0,timeout:1e4}).trim()||null}catch{n=null}return O.set(r,n),n}function Dt(){O.clear()}function st(t,e,r){const n=t.projectRoot,o={},f=process.env.INFERNOFLOW_AUTHOR||T(n,["config","user.name"])||(T(n,["config","user.email"])||"").split("@")[0]||null;f&&(o.author=f.slice(0,80)),o.repo=a.basename(n),t.branch&&t.branch.current&&!t.branch.isSynthetic&&(o.branch=t.branch.current);const s=a.relative(n,a.resolve(r));if(s&&!s.startsWith("..")&&!a.isAbsolute(s)&&(o.cwd=s.split(a.sep).join("/")),e.file){const i=T(n,["rev-parse","HEAD"]);i&&/^[0-9a-f]{40}$/.test(i)&&(o.commit=i)}return o}function vt(t,e){A(t);const r=g(t,{forWrite:!0});if(!$.has(r.root)){$.add(r.root);try{it(r.root)}catch{}}const n=e.id||`amp_${J()}`,o={...e,id:n},f=new Set,s=j=>{if(typeof j!="string")return j;const h=V(j);return h.kinds.forEach(b=>f.add(b)),h.text};o.msg!==void 0&&(o.msg=s(o.msg)),o.summary!==void 0&&(o.summary=s(o.summary)),Array.isArray(o.tags)&&(o.tags=o.tags.map(s)),typeof o.result=="string"&&(o.result=s(o.result));const i=o.detailLocal===!0;delete o.detailLocal;const u=typeof e.detail=="string"?s(e.detail):e.detail;f.size&&(o.meta={...o.meta||{},redacted:[...f]});try{o.meta={...st(r,o,t),...o.meta||{}}}catch{}const l=o.localOnly===!0;if(delete o.localOnly,typeof u=="string"&&u.trim())try{ot(r.root,n,u,i),o.detailRef=i?C:"details.jsonl"}catch{}delete o.detail;const d=l?r.sessions:rt(r,o),y=L(o),S=JSON.stringify(y)+`
10
+ `;if(c.mkdirSync(a.dirname(d),{recursive:!0}),c.appendFileSync(d,S,"utf8"),d!==r.sessions)try{c.mkdirSync(a.dirname(r.sessions),{recursive:!0}),c.appendFileSync(r.sessions,S,"utf8")}catch{}return y}function At(t,e){const r=g(t);return a.join(r.root,"details",`${e}.md`)}function Ot(t,e){if(!e)return null;const r=e.detailRef||e.meta&&e.meta.detailRef;if(!r)return null;const n=g(t),o=e.id||e.meta&&e.meta.id,f=I(n.root,o,!0);if(f!=null)return f;const s=I(n.root,o);if(s!=null)return s;if(typeof r=="string"&&/^details[\\/][A-Za-z0-9_.-]+\.md$/.test(r)&&!r.includes("..")){const i=a.join(n.root,"details"),u=a.join(n.root,r);try{const l=a.join(c.realpathSync(n.root),"details");if(c.lstatSync(i).isSymbolicLink()||c.realpathSync(i)!==l)return null;const d=c.realpathSync(u);return a.dirname(d)!==l?null:c.readFileSync(d,"utf8")}catch{return null}}return null}function ct(t){const e=g(t),r=[e.sessions,e.globalFile,e.currentBranchFile,e.defaultBranchFile].filter(Boolean);try{if(c.existsSync(e.branchesDir))for(const n of c.readdirSync(e.branchesDir))n.endsWith(".jsonl")&&r.push(a.join(e.branchesDir,n))}catch{}return[...new Set(r)]}function Rt(t,e,r){if(!e||typeof r!="function")return{updated:0,files:[]};let n=0;const o=[];for(const f of ct(t)){if(!c.existsSync(f))continue;let s;try{s=c.readFileSync(f,"utf8").split(`
11
+ `)}catch{continue}let i=!1;const u=s.map(l=>{if(!l.trim())return l;let d;try{d=JSON.parse(l)}catch{return l}if(d.id!==e)return l;const y=r({...d,meta:{...d.meta||{}}})||d;return i=!0,n++,JSON.stringify(y)});i&&(c.writeFileSync(f,u.filter((l,d)=>l.trim()||d<u.length-1).join(`
12
+ `).replace(/\n*$/,`
13
+ `),"utf8"),o.push(f))}return{updated:n,files:o}}function kt(t,e){if(!e)return{removed:0,files:[]};const r=g(t),n=[r.sessions,r.globalFile,r.currentBranchFile,r.defaultBranchFile].filter(Boolean);try{if(c.existsSync(r.branchesDir))for(const i of c.readdirSync(r.branchesDir))i.endsWith(".jsonl")&&n.push(a.join(r.branchesDir,i))}catch{}const o=[...new Set(n)];let f=0;const s=[];for(const i of o){if(!c.existsSync(i))continue;let u;try{u=c.readFileSync(i,"utf8").split(`
14
+ `)}catch{continue}const l=[];let d=0;for(const y of u){if(!y.trim())continue;let S;try{S=JSON.parse(y).id}catch{l.push(y);continue}if(S===e){d++,f++;continue}l.push(y)}d>0&&(c.writeFileSync(i,l.length?l.join(`
12
15
  `)+`
13
- `:"","utf8"),c.push(i))}try{I(o.root,e)}catch{}try{I(o.root,e,!0)}catch{}try{const i=a.join(o.root,"details",`${e}.md`);s.existsSync(i)&&(s.unlinkSync(i),c.push(i))}catch{}return{removed:f,files:c}}const nt=30,ot=["note","attempt","detection"],C=new Set(["gotcha","decision","pattern"]);function rt(t){const e=t&&t.config&&t.config.rotation&&typeof t.config.rotation=="object"?t.config.rotation:{};return{archiveAfterDays:Number.isInteger(e.archiveAfterDays)&&e.archiveAfterDays>0?e.archiveAfterDays:nt,archivableTypes:Array.isArray(e.archivableTypes)&&e.archivableTypes.length?e.archivableTypes:ot,auto:e.auto===!0}}function it(t){const e=[t.sessions,t.currentBranchFile,t.defaultBranchFile].filter(Boolean);try{if(s.existsSync(t.branchesDir))for(const o of s.readdirSync(t.branchesDir))o.endsWith(".jsonl")&&e.push(a.join(t.branchesDir,o))}catch{}return[...new Set(e)]}function st(t,e){const o=new Date(e),n=o.getUTCFullYear(),r=String(o.getUTCMonth()+1).padStart(2,"0");return a.join(t,"archive",`sessions-${n}-${r}.jsonl`)}function St(t,e={}){const o=S(t,{forWrite:!e.dryRun}),n=rt(ct(t)),r=Number.isFinite(e.maxAgeDays)?e.maxAgeDays:n.archiveAfterDays,f=e.archive!==!1,c=new Set((Array.isArray(e.types)&&e.types.length?e.types:n.archivableTypes).filter(h=>!C.has(h))),i=Date.now()-r*24*60*60*1e3,u=it(o),l={scanned:0,archived:0,kept:0,files:[],byType:{},dryRun:!!e.dryRun},d=new Map,y=new Set,m=new Set,j=new Set;for(const h of u){if(!s.existsSync(h))continue;let D;try{D=s.readFileSync(h,"utf8").split(`
14
- `)}catch{continue}const x=[];let O=0;for(const b of D){if(!b.trim())continue;let g;try{g=JSON.parse(b)}catch{x.push(b);continue}const p=g&&g.id,v=g&&typeof g.type=="string"?g.type:"note",k=typeof g.ts=="number"?g.ts:Date.parse(g.ts||0),P=Number.isFinite(k)&&k<i,M=Array.isArray(g.tags)&&g.tags.includes("bookmark"),W=c.has(v)&&!C.has(v)&&!M;if(p&&!m.has(p)&&(m.add(p),l.scanned++),p||l.scanned++,P&&W){if(O++,(!p||!y.has(p))&&(p&&y.add(p),l.archived++,l.byType[v]=(l.byType[v]||0)+1,f)){const T=st(o.root,k);d.has(T)||d.set(T,[]),d.get(T).push(b)}}else p&&!j.has(p)&&(j.add(p),l.kept++),p||l.kept++,x.push(b)}O>0&&!e.dryRun?(s.writeFileSync(h,x.length?x.join(`
16
+ `:"","utf8"),s.push(i))}try{P(r.root,e)}catch{}try{P(r.root,e,!0)}catch{}try{const i=a.join(r.root,"details",`${e}.md`);c.existsSync(i)&&(c.unlinkSync(i),s.push(i))}catch{}return{removed:f,files:s}}const at=30,ft=["note","attempt","detection"],M=new Set(["gotcha","decision","pattern"]);function lt(t){const e=t&&t.config&&t.config.rotation&&typeof t.config.rotation=="object"?t.config.rotation:{};return{archiveAfterDays:Number.isInteger(e.archiveAfterDays)&&e.archiveAfterDays>0?e.archiveAfterDays:at,archivableTypes:Array.isArray(e.archivableTypes)&&e.archivableTypes.length?e.archivableTypes:ft,auto:e.auto===!0}}function ut(t){const e=[t.sessions,t.currentBranchFile,t.defaultBranchFile].filter(Boolean);try{if(c.existsSync(t.branchesDir))for(const r of c.readdirSync(t.branchesDir))r.endsWith(".jsonl")&&e.push(a.join(t.branchesDir,r))}catch{}return[...new Set(e)]}function dt(t,e){const r=new Date(e),n=r.getUTCFullYear(),o=String(r.getUTCMonth()+1).padStart(2,"0");return a.join(t,"archive",`sessions-${n}-${o}.jsonl`)}function wt(t,e={}){const r=g(t,{forWrite:!e.dryRun}),n=lt(yt(t)),o=Number.isFinite(e.maxAgeDays)?e.maxAgeDays:n.archiveAfterDays,f=e.archive!==!1,s=new Set((Array.isArray(e.types)&&e.types.length?e.types:n.archivableTypes).filter(h=>!M.has(h))),i=Date.now()-o*24*60*60*1e3,u=ut(r),l={scanned:0,archived:0,kept:0,files:[],byType:{},dryRun:!!e.dryRun},d=new Map,y=new Set,S=new Set,j=new Set;for(const h of u){if(!c.existsSync(h))continue;let b;try{b=c.readFileSync(h,"utf8").split(`
17
+ `)}catch{continue}const D=[];let R=0;for(const F of b){if(!F.trim())continue;let m;try{m=JSON.parse(F)}catch{D.push(F);continue}const p=m&&m.id,v=m&&typeof m.type=="string"?m.type:"note",k=typeof m.ts=="number"?m.ts:Date.parse(m.ts||0),W=Number.isFinite(k)&&k<i,G=Array.isArray(m.tags)&&m.tags.includes("bookmark"),U=s.has(v)&&!M.has(v)&&!G;if(p&&!S.has(p)&&(S.add(p),l.scanned++),p||l.scanned++,W&&U){if(R++,(!p||!y.has(p))&&(p&&y.add(p),l.archived++,l.byType[v]=(l.byType[v]||0)+1,f)){const w=dt(r.root,k);d.has(w)||d.set(w,[]),d.get(w).push(F)}}else p&&!j.has(p)&&(j.add(p),l.kept++),p||l.kept++,D.push(F)}R>0&&!e.dryRun?(c.writeFileSync(h,D.length?D.join(`
15
18
  `)+`
16
- `:"","utf8"),l.files.push(h)):O>0&&l.files.push(h)}if(!e.dryRun&&f&&d.size>0)for(const[h,D]of d)try{s.mkdirSync(a.dirname(h),{recursive:!0}),s.appendFileSync(h,D.join(`
19
+ `:"","utf8"),l.files.push(h)):R>0&&l.files.push(h)}if(!e.dryRun&&f&&d.size>0)for(const[h,b]of d)try{c.mkdirSync(a.dirname(h),{recursive:!0}),c.appendFileSync(h,b.join(`
17
20
  `)+`
18
- `,"utf8")}catch{}return l}function ct(t){const{config:e}=S(t);try{return JSON.parse(s.readFileSync(e,"utf8"))}catch{return null}}function jt(t,e={}){A(t);const{config:o}=S(t,{forWrite:!0});if(s.existsSync(o))return!1;const n={amp:B,project:e.project||a.basename(t),stack:e.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},...e.config||{}}};return s.writeFileSync(o,JSON.stringify(n,null,2)+`
19
- `,"utf8"),!0}function bt(t,e={}){A(t);const{config:o}=S(t,{forWrite:!0});let n={};try{n=JSON.parse(s.readFileSync(o,"utf8"))}catch{n={}}(!n||typeof n!="object")&&(n={}),n.amp||(n.amp=B),n.project||(n.project=a.basename(t)),(!n.config||typeof n.config!="object")&&(n.config={});const r=n.config.injection&&typeof n.config.injection=="object"?n.config.injection:{};return n.config.injection={...r,...e},s.writeFileSync(o,JSON.stringify(n,null,2)+`
20
- `,"utf8"),n.config.injection}function Ft(t){const e=a.join(t,"inferno"),o=a.join(e,"sessions.jsonl");if(!s.existsSync(o))return{migrated:0,reason:"no legacy sessions.jsonl"};const n=a.join(t,".ai-memory"),r=a.join(n,"sessions.jsonl");if(s.existsSync(r))return{migrated:0,reason:".ai-memory/sessions.jsonl already exists"};A(t);const f=s.readFileSync(o,"utf8").split(`
21
- `).filter(Boolean);let c=0;for(const i of f)try{const u=JSON.parse(i),l=J(u);s.appendFileSync(r,JSON.stringify(l)+`
22
- `,"utf8"),c++}catch{}return s.writeFileSync(a.join(n,"MIGRATED.md"),`# Migrated from inferno/
21
+ `,"utf8")}catch{}return l}function yt(t){const{config:e}=g(t);try{return JSON.parse(c.readFileSync(e,"utf8"))}catch{return null}}function Nt(t,e={}){A(t);const{config:r}=g(t,{forWrite:!0});if(c.existsSync(r))return!1;const n={amp:E,project:e.project||a.basename(t),stack:e.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},...e.config||{}}};return c.writeFileSync(r,JSON.stringify(n,null,2)+`
22
+ `,"utf8"),!0}function Tt(t,e={}){A(t);const{config:r}=g(t,{forWrite:!0});let n={};try{n=JSON.parse(c.readFileSync(r,"utf8"))}catch{n={}}(!n||typeof n!="object")&&(n={}),n.amp||(n.amp=E),n.project||(n.project=a.basename(t)),(!n.config||typeof n.config!="object")&&(n.config={});const o=n.config.injection&&typeof n.config.injection=="object"?n.config.injection:{};return n.config.injection={...o,...e},c.writeFileSync(r,JSON.stringify(n,null,2)+`
23
+ `,"utf8"),n.config.injection}function Bt(t){const e=a.join(t,"inferno"),r=a.join(e,"sessions.jsonl");if(!c.existsSync(r))return{migrated:0,reason:"no legacy sessions.jsonl"};const n=a.join(t,".ai-memory"),o=a.join(n,"sessions.jsonl");if(c.existsSync(o))return{migrated:0,reason:".ai-memory/sessions.jsonl already exists"};A(t);const f=c.readFileSync(r,"utf8").split(`
24
+ `).filter(Boolean);let s=0;for(const i of f)try{const u=JSON.parse(i),l=L(u);c.appendFileSync(o,JSON.stringify(l)+`
25
+ `,"utf8"),s++}catch{}return c.writeFileSync(a.join(n,"MIGRATED.md"),`# Migrated from inferno/
23
26
 
24
- Copied ${c} entries from inferno/sessions.jsonl on ${new Date().toISOString()}.
27
+ Copied ${s} entries from inferno/sessions.jsonl on ${new Date().toISOString()}.
25
28
 
26
29
  The original inferno/sessions.jsonl is untouched. You can delete it once you're confident the new layout works.
27
- `,"utf8"),{migrated:c,reason:"ok"}}export{B as AMP_VERSION,$ as LOCAL_DETAIL_STORE,S as ampPaths,ht as appendEntry,gt as deleteEntry,dt as describeStore,pt as detailPath,A as ensureAmpDir,q as fromAmp,w as generateULID,Ft as migrateLegacy,V as projectSlug,St as pruneEntries,ct as readConfig,mt as readDetail,yt as readEntries,K as resolveGlobalFile,rt as resolveRotationSettings,J as toAmp,bt as updateInjectionConfig,jt as writeDefaultConfig};
30
+ `,"utf8"),{migrated:s,reason:"ok"}}export{E as AMP_VERSION,C as LOCAL_DETAIL_STORE,Dt as _resetGitStampCache,g as ampPaths,vt as appendEntry,kt as deleteEntry,Ft as describeStore,At as detailPath,A as ensureAmpDir,et as fromAmp,J as generateULID,ct as memoryFiles,Bt as migrateLegacy,Q as projectSlug,wt as pruneEntries,yt as readConfig,Ot as readDetail,xt as readEntries,X as resolveGlobalFile,lt as resolveRotationSettings,L as toAmp,Rt as updateEntry,Tt as updateInjectionConfig,Nt as writeDefaultConfig};
@@ -1 +1,3 @@
1
- import*as r from"node:fs";import*as n from"node:path";import{fileURLToPath as m}from"node:url";const f=n.dirname(m(import.meta.url));function l(){return n.resolve(f,"../templates")}const c=new Set;function p(e){return r.existsSync(n.join(e,".ai-memory"))||r.existsSync(n.join(e,"inferno"))}function a(e,i){if(!r.existsSync(e))return 0;let t=0;for(const s of r.readdirSync(e,{withFileTypes:!0})){const u=n.join(e,s.name),o=n.join(i,s.name);if(s.isDirectory()){t+=a(u,o);continue}r.existsSync(o)||(r.mkdirSync(n.dirname(o),{recursive:!0}),r.copyFileSync(u,o),t++)}return t}function d(e){try{if(!e||c.has(e)||(c.add(e),!p(e)))return 0;const i=l();let t=0;return t+=a(n.join(i,"skills"),n.join(e,".claude","skills")),t+=a(n.join(i,"agents"),n.join(e,".claude","agents")),t}catch{return 0}}export{d as ensureClaudeAssets};
1
+ import*as n from"node:fs";import*as r from"node:path";import*as h from"node:crypto";import{fileURLToPath as y}from"node:url";const g=r.dirname(y(import.meta.url));function S(){return r.resolve(g,"../templates")}const o=e=>h.createHash("sha256").update(String(e).replace(/\r\n/g,`
2
+ `)).digest("hex"),b=new Set(["59ca1d6150462664c9677a112bc8cf57412b048788b8af3aef826acfe9a90fde","bb30ab374eace187685923ab2efb476703a9e0f13e0c99d6961104b04baa5953"]),w=[{src:["skills","infernoflow-memory","SKILL.md"],dst:["skills","infernoflow-memory","SKILL.md"],generated:!1},{src:["agents","memory-keeper.md"],dst:["agents","memory-keeper.md"],generated:!1},{src:["hooks","infernoflow-agent-guard.mjs"],dst:["hooks","infernoflow-agent-guard.mjs"],generated:!0}],u=new Set;function j(e){return n.existsSync(r.join(e,".ai-memory"))||n.existsSync(r.join(e,"inferno"))}function v(e){const t={created:[],updated:[],pending:[]};if(!e||!j(e))return t;const m=S();for(const i of w){const p=r.join(m,...i.src),a=r.join(e,".claude",...i.dst),d=[".claude",...i.dst].join("/");let s;try{s=n.readFileSync(p,"utf8")}catch{continue}let c=null;try{c=n.readFileSync(a,"utf8")}catch{}try{if(c==null)n.mkdirSync(r.dirname(a),{recursive:!0}),n.writeFileSync(a,s,"utf8"),t.created.push(d);else if(o(c)!==o(s))if(i.generated||b.has(o(c)))n.writeFileSync(a,s,"utf8"),t.updated.push(d);else{const f=a+".new";let l=null;try{l=n.readFileSync(f,"utf8")}catch{}(l==null||o(l)!==o(s))&&(n.writeFileSync(f,s,"utf8"),t.pending.push(d))}}catch{}}return t}function F(e){try{if(!e||u.has(e))return 0;u.add(e);const t=v(e);return t.pending.length&&process.stderr.write(` infernoflow: a newer version of ${t.pending.join(", ")} is available \u2014 you edited yours, so it was saved as <name>.new; merge when convenient.
3
+ `),t.created.length+t.updated.length}catch{return 0}}export{F as ensureClaudeAssets,v as syncClaudeAssets};
@@ -1,12 +1,12 @@
1
- import*as m from"node:fs";import*as u from"node:path";const d="# >>> infernoflow:start",s="# <<< infernoflow:end",g=["",d,"# Personal memory (per-developer, per-machine). Sync via cloud folder","# or `infernoflow sync`, not git.",".ai-memory/global.jsonl",".ai-memory/sessions.jsonl","# Regenerated artifacts \u2014 never commit these.",".ai-memory/handoff.md",".ai-memory/CONTEXT.draft.md",".ai-memory/HANDOFF.md",".ai-memory/.last-cli-version","# Automatic bookmark transcript snapshots \u2014 local only, never committed.",".ai-memory/details.local.jsonl","# AI provider settings \u2014 older versions stored API keys here.","inferno/integrations.json","# MCP runtime stamp (machine-specific).",".ai-memory/.mcp-runtime.json","# Build/publish hygiene \u2014 don't ship memory in published .NET / monorepo bundles.","**/publish/.ai-memory/","**/publish/inferno/","**/dist/.ai-memory/","**/dist/inferno/",s,""].join(`
1
+ import*as l from"node:fs";import*as u from"node:path";const d="# >>> infernoflow:start",s="# <<< infernoflow:end",g=["",d,"# Personal memory (per-developer, per-machine). Sync via cloud folder","# or `infernoflow sync`, not git.",".ai-memory/global.jsonl",".ai-memory/sessions.jsonl","# Regenerated artifacts \u2014 never commit these.",".ai-memory/handoff.md",".ai-memory/CONTEXT.draft.md",".ai-memory/HANDOFF.md",".ai-memory/.last-cli-version","# Automatic bookmark transcript snapshots \u2014 local only, never committed.",".ai-memory/details.local.jsonl","# AI provider settings \u2014 older versions stored API keys here.","inferno/integrations.json","# Hook state (machine-specific).",".ai-memory/.hook-state.json",".ai-memory/.trigger-state.json","# MCP runtime stamp (machine-specific).",".ai-memory/.mcp-runtime.json","# Build/publish hygiene \u2014 don't ship memory in published .NET / monorepo bundles.","**/publish/.ai-memory/","**/publish/inferno/","**/dist/.ai-memory/","**/dist/inferno/",s,""].join(`
2
2
  `),A=["",d,"# Branch-local memory: append-only JSONL files. Auto-merge concurrent","# additions from different machines/branches as union of lines so","# `home \u2192 work \u2192 home` syncs don't produce conflicts.",".ai-memory/branches/*.jsonl merge=union",s,""].join(`
3
3
  `),I="# --- infernoflow (developer-local AI memory; do not commit) ---",y="# --- /infernoflow ---";function T(n){const r=n.indexOf(I),i=n.indexOf(y);if(r===-1||i===-1||i<=r)return n;const o=n.slice(0,r).replace(/\s+$/,""),e=n.slice(i+y.length).replace(/^\s+/,"");return(o?o+`
4
- `:"")+(e||"")}function h(n,r,i){const o=u.join(n,r);let e="";try{e=m.readFileSync(o,"utf8")}catch{}e=T(e);const c=e.indexOf(d),a=e.indexOf(s),l=i.trim();let t;if(c!==-1&&a!==-1&&a>c){const f=e.slice(0,c).replace(/\s+$/,""),p=e.slice(a+s.length).replace(/^\s+/,"");t=(f?f+`
4
+ `:"")+(e||"")}function h(n,r,i){const o=u.join(n,r);let e="";try{e=l.readFileSync(o,"utf8")}catch{}e=T(e);const a=e.indexOf(d),c=e.indexOf(s),m=i.trim();let t;if(a!==-1&&c!==-1&&c>a){const f=e.slice(0,a).replace(/\s+$/,""),p=e.slice(c+s.length).replace(/^\s+/,"");t=(f?f+`
5
5
 
6
- `:"")+l+`
6
+ `:"")+m+`
7
7
  `+(p?`
8
8
  `+p:"")}else e?t=e.replace(/\s+$/,"")+`
9
9
 
10
- `+l+`
11
- `:t=l+`
12
- `;return t===e?"unchanged":(m.mkdirSync(u.dirname(o),{recursive:!0}),m.writeFileSync(o,t,"utf8"),e?"updated":"created")}function E(n){return{gitignore:h(n,".gitignore",g),gitattributes:h(n,".gitattributes",A)}}export{s as GITIGNORE_END,d as GITIGNORE_START,E as applyCleanTreePolicy,h as ensureManagedBlock};
10
+ `+m+`
11
+ `:t=m+`
12
+ `;return t===e?"unchanged":(l.mkdirSync(u.dirname(o),{recursive:!0}),l.writeFileSync(o,t,"utf8"),e?"updated":"created")}function E(n){return{gitignore:h(n,".gitignore",g),gitattributes:h(n,".gitattributes",A)}}export{s as GITIGNORE_END,d as GITIGNORE_START,E as applyCleanTreePolicy,h as ensureManagedBlock};
@@ -1,3 +1,3 @@
1
- import{describeStore as R}from"../amp/io.mjs";import"node:fs";import"node:path";import{bold as T,cyan as O,gray as r,green as I,yellow as L,red as M}from"../ui/output.mjs";import{readEntries as W}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:L,decision:I,attempt:O,preference:O,theme:O,note:r,error:M,handoff:r};function E(o){return o.toLowerCase().replace(/[^a-z0-9\s]/g," ").split(/\s+/).filter(e=>e.length>1)}function j(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 q(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 Y(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(` (${q(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 P(o){return W(o)}function v(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:j(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 J(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=P(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})):v(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(` ${R(process.cwd())}`)),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(),Y(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{J as askCommand};
1
+ import{describeStore as L}from"../amp/io.mjs";import"node:fs";import"node:path";import{bold as O,cyan as T,gray as s,green as M,yellow as j,red as q}from"../ui/output.mjs";import{readEntries as Y}from"../amp/io.mjs";const p={gotcha:0,decision:1,attempt:2,preference:3,theme:4,note:5,error:5,handoff:6},I={gotcha:"\u26A0",decision:"\u2713",attempt:"\u21BA",preference:"\u2666",theme:"\u{1F3A8}",note:"\xB7",error:"\u2717",handoff:"\u2192"},N={gotcha:j,decision:M,attempt:T,preference:T,theme:T,note:s,error:q,handoff:s};function E(n){return n.toLowerCase().replace(/[^a-z0-9\s]/g," ").split(/\s+/).filter(t=>t.length>1)}function P(n,t){const l=[n.summary||"",n.type||""].join(" ").toLowerCase(),o=E(l);let r=0;for(const c of t)(n.summary||"").toLowerCase().includes(c)&&(r+=3),o.includes(c)&&(r+=1),o.some(a=>a.startsWith(c)||c.startsWith(a))&&(r+=.5);return r}function v(n){if(n==null||n==="")return"";const t=new Date(n);if(isNaN(t.getTime()))return"";const l=r=>new Date(r.getFullYear(),r.getMonth(),r.getDate()).getTime(),o=Math.round((l(new Date)-l(t))/864e5);return o<=0?`today ${t.toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit"})}`:o===1?"yesterday":o<7?`${o}d ago`:o<30?`${Math.floor(o/7)}w ago`:t.toLocaleDateString("en-GB",{day:"2-digit",month:"short"})}function F(n,t){const l=n.type||"note",o=I[l]||"\xB7",r=N[l]||s,c=n.result?s(` [${n.result}]`):"",a=n.agent?s(` \u2014 ${n.agent}`):"",e=s(` (${v(n.ts)})`);let i=n.summary||"";if(t)for(const d of t){const u=new RegExp(`(${d})`,"gi");i=i.replace(u,D=>O(D))}console.log(` ${r(o+" "+l.padEnd(11))}${c}${a}${e}`),console.log(` ${i}`)}function _(n){return Y(n)}function W(n,t){if(!t||!n.file)return 0;const l=e=>String(e).replace(/\\/g,"/").replace(/^\.\//,""),o=l(n.file),r=l(t);if(o===r||o.endsWith("/"+r)||r.endsWith("/"+o))return 2;const c=o.includes("/")?o.slice(0,o.lastIndexOf("/")):"",a=r.includes("/")?r.slice(0,r.lastIndexOf("/")):"";return c&&c===a?1:0}function b(n,t,l,o,r=null){let c=n;l&&(c=c.filter(e=>(e.type||"note")===l));let a;return t.length>0?a=c.map(e=>({entry:e,score:P(e,t)})).filter(({score:e})=>e>0).map(e=>({...e,score:e.score+3*W(e.entry,r)})).sort((e,i)=>{if(i.score!==e.score)return i.score-e.score;const d=p[e.entry.type]??9,u=p[i.entry.type]??9;return d!==u?d-u:new Date(i.entry.ts||0).getTime()-new Date(e.entry.ts||0).getTime()}):a=c.map(e=>({entry:e,score:1+W(e,r)})).sort((e,i)=>{if(i.score!==e.score)return i.score-e.score;const d=p[e.entry.type]??9,u=p[i.entry.type]??9;return d!==u?d-u:new Date(i.entry.ts||0).getTime()-new Date(e.entry.ts||0).getTime()}),a.slice(0,o||20)}async function z(n=[]){const t=n,l=t.indexOf("--type"),o=l!==-1?t[l+1]:null,r=t.indexOf("--limit")!==-1?t.indexOf("--limit"):t.indexOf("-n"),c=r!==-1?parseInt(t[r+1]||"20",10):15,a=t.includes("--json"),e=t.includes("--recent")||t.includes("-r"),i=t.indexOf("--file"),d=i!==-1?t[i+1]:null,u=t.slice(1),m=u.filter((f,g)=>!(f.startsWith("--")||g>0&&u[g-1].startsWith("--"))).join(" ").trim(),x=e?[]:E(m),k=process.cwd(),y=_(k);if(y.length===0){if(a){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 h=e?y.slice(-c).reverse().map(f=>({entry:f,score:1})):b(y,x,o,c,d);if(a){console.log(JSON.stringify({query:m,type:o,total:y.length,matched:h.length,results:h.map(({entry:f,score:g})=>({...f,relevanceScore:g}))},null,2));return}if(console.log(),console.log(m?` ${O("\u{1F525} infernoflow ask")} ${T(`"${m}"`)}${o?s(` [${o}]`):""}`:e?` ${O("\u{1F525} infernoflow ask")} ${s("\u2014 recent entries")}`:` ${O("\u{1F525} infernoflow ask")} ${s("\u2014 all entries")}${o?s(` [${o}]`):""}`),console.log(s(` ${L(process.cwd())}`)),console.log(s(` ${"\u2500".repeat(52)}`)),h.length===0){console.log(),m?(console.log(s(` No entries found for "${m}"`)),o&&console.log(s(` Try removing --type ${o} to widen the search`))):console.log(s(" No entries found.")),console.log();return}const w=new Map;for(const{entry:f,score:g}of h){const $=f.type||"note";w.has($)||w.set($,[]),w.get($).push({entry:f,score:g})}const C=Object.keys(p).sort((f,g)=>p[f]-p[g]);let S=0;for(const f of C){const g=w.get(f);if(!g?.length)continue;console.log();const $=N[f]||s;console.log($(` ${I[f]} ${f.toUpperCase()}S (${g.length})`)),console.log(s(" "+"\u2500".repeat(50)));for(const{entry:R}of g)console.log(),F(R,x),S++}console.log(),console.log(s(` ${S} result${S!==1?"s":""} from ${y.length} total entries`)),h.length===c&&y.length>c&&console.log(s(" Use --limit N to see more")),console.log()}export{z as askCommand};
@@ -0,0 +1,10 @@
1
+ import*as d from"node:fs";import*as g from"node:path";import{readEntries as h,deleteEntry as w,toAmp as S,projectSlug as $}from"../amp/io.mjs";import{isNoise as j}from"../memoryView.mjs";import{findProjectRoot as k}from"../projectRoot.mjs";import{infernoflowHome as v}from"../personalConfig.mjs";import{bold as N,gray as l,green as b,yellow as A}from"../ui/output.mjs";const m=r=>(typeof r.ts=="number"?r.ts:Date.parse(r.ts||0))||0;function D(r){const i=h(r).sort((o,s)=>m(o)-m(s)),e={commitNotes:[],duplicates:[],oldFrustration:[]},n=new Map,c=Date.now()-14*864e5;for(const o of i){if(Array.isArray(o.tags)&&o.tags.includes("bookmark"))continue;if(j(o)){e.commitNotes.push(o);continue}if(o.source==="hook"&&/^User frustration:/.test(o.summary||"")&&m(o)<c){e.oldFrustration.push(o);continue}const s=`${o.type}\0${o.file||""}\0${String(o.summary||"").trim().toLowerCase()}`;n.has(s)?e.duplicates.push(o):n.set(s,o)}return e}async function M(r=[]){const i=process.cwd(),e=r.includes("--apply"),n=D(i),c=[["commit notes (duplicate git history)",n.commitNotes],["duplicates",n.duplicates],["raw frustration prompts > 14 days",n.oldFrustration]],o=c.reduce((t,[,a])=>t+a.length,0);console.log(`
2
+ `+N("\u{1F525} infernoflow curate"));for(const[t,a]of c){console.log(` ${String(a.length).padStart(4)} ${t}`);for(const y of a.slice(0,3))console.log(l(" "+String(y.summary||"").slice(0,90)))}if(!o){console.log(l(`
3
+ Nothing to curate.
4
+ `));return}if(!e){console.log(`
5
+ `+A("Dry run.")+l(` Add --apply to remove them (archived first).
6
+ `));return}const s=k(i),f=g.join(v(),"backups");d.mkdirSync(f,{recursive:!0});const p=g.join(f,`${$(s)}-curated-${new Date().toISOString().replace(/[:.]/g,"-")}.jsonl`),u=c.flatMap(([,t])=>t);d.writeFileSync(p,u.map(t=>JSON.stringify(S(t))).join(`
7
+ `)+`
8
+ `,"utf8");for(const t of u)w(i,t.id);console.log(`
9
+ `+b(`\u2714 removed ${u.length}`)+l(` \u2014 archived to ${p}
10
+ `))}export{D as curateCandidates,M as curateCommand};
@@ -1,4 +1,4 @@
1
- import{claudeJsonPath as A,claudeDesktopConfigPath as I,isInfernoflowEntry as M}from"../mcpRegistration.mjs";import{HOOK_VERSION_MARKER as _}from"../securityRefresh.mjs";import{redactSecrets as E}from"../security/redact.mjs";import*as c from"node:fs";import*as s from"node:path";import*as y from"node:os";import*as F from"node:http";import{execSync as $,spawnSync as C}from"node:child_process";import{fileURLToPath as b}from"node:url";import{bold as N,cyan as P,gray as S,green as j,yellow as x,red as v}from"../ui/output.mjs";import{detectAvailableProviders as R}from"../ai/providerRouter.mjs";import{readEntries as T,readConfig as D}from"../amp/io.mjs";import{resolveInjectionSettings as G}from"../ruleFiles.mjs";function f(e,o){try{const n=o();return{label:e,...n}}catch(n){return{label:e,status:"error",message:n.message,fix:null}}}function a(e,o){return{status:"pass",message:e,detail:o||null,fix:null}}function l(e,o){return{status:"warn",message:e,detail:null,fix:o||null}}function h(e,o){return{status:"fail",message:e,detail:null,fix:o||null}}function L(){const e=process.version,o=parseInt(e.slice(1).split(".")[0],10);return o>=20?a(`Node.js ${e}`,"Node 20+ recommended"):o>=18?a(`Node.js ${e}`):h(`Node.js ${e} \u2014 infernoflow requires Node 18+`,"Install Node 20 from nodejs.org")}function J(){try{const e=C("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 H(e){try{return $("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 K(e){const o=s.join(e,".ai-memory"),n=s.join(e,"inferno");return c.existsSync(o)&&c.existsSync(n)?a(".ai-memory/ + inferno/ both present"):c.existsSync(o)?a(".ai-memory/ directory exists (memory mode)"):c.existsSync(n)?a("inferno/ directory exists"):h("No memory directory found (.ai-memory/ or inferno/)","infernoflow init")}function U(e){if(w(e)){let n=0;try{n=T(e).length}catch{}return a(n===0?"Memory mode \u2014 sessions.jsonl will be created on first log":`Memory mode \u2014 ${n} session entr${n===1?"y":"ies"}`)}const o=s.join(e,"inferno");for(const n of["contract.json","capabilities.json"]){const t=s.join(o,n);if(c.existsSync(t))try{const i=(JSON.parse(c.readFileSync(t,"utf8")).capabilities||[]).length;return a(`${n} valid \u2014 ${i} capabilities`)}catch{return h(`${n} contains invalid JSON`,`Fix the JSON syntax in inferno/${n}`)}}return h("No contract.json/capabilities.json (and not in memory mode)","infernoflow init or infernoflow init --mode full")}function w(e){const o=s.join(e,".ai-memory"),n=s.join(e,"inferno","contract.json");if(c.existsSync(o)&&!c.existsSync(n))return!0;try{return JSON.parse(c.readFileSync(s.join(e,"inferno","config.json"),"utf8")).mode==="memory"}catch{return!1}}function W(e){if(w(e))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const o=s.join(e,"inferno","scenarios");if(!c.existsSync(o))return l("No scenarios/ directory","infernoflow init");const n=c.readdirSync(o).filter(t=>t.endsWith(".json"));return n.length?a(`${n.length} scenario file${n.length!==1?"s":""} found`):l("scenarios/ is empty","Add scenario files or run infernoflow suggest")}function V(e){if(w(e))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const o=s.join(e,"inferno","CHANGELOG.md");return c.existsSync(o)?a("inferno/CHANGELOG.md exists"):l("No inferno/CHANGELOG.md","infernoflow init")}function X(e){if(w(e))return{status:"info",message:"n/a in memory mode (CLAUDE.md is auto-maintained)",detail:null,fix:null};const o=s.join(e,"inferno","CONTEXT.md");if(!c.existsSync(o))return l("No CONTEXT.md generated","infernoflow context");const n=(Date.now()-c.statSync(o).mtimeMs)/(1e3*60*60*24);return n>7?l(`CONTEXT.md is ${Math.round(n)} days old \u2014 may be stale`,"infernoflow context"):a(`CONTEXT.md present (${Math.round(n)}d old)`)}function Y(e){const o=s.join(e,".git","hooks"),n=s.join(o,"post-commit");return c.existsSync(n)&&c.readFileSync(n,"utf8").includes("infernoflow")?a("Git hooks installed (post-commit)"):l("Git hooks not installed","infernoflow setup --yes")}function z(e){const o=[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 n of o)if(c.existsSync(n))try{const t=JSON.parse(c.readFileSync(n,"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(n)}`)}catch{}return l("MCP server not configured","infernoflow setup --yes (adds to Cursor/Claude/Copilot config)")}function q(){const e=[];for(const[o,n]of[["~/.claude.json",A()],["claude_desktop_config.json",I()]])try{const t=JSON.parse(c.readFileSync(n,"utf8"))?.mcpServers?.infernoflow;t&&M(t)&&e.push(`${o} \u2192 ${(t.env||{}).INFERNOFLOW_PROJECT_DIR||"?"}`)}catch{}return e.length?l(`User-level MCP entry pins every project to one repo: ${e.join("; ")}`,"infernoflow setup --yes (registers per project and removes the pinned entry)"):a("No user-level infernoflow MCP entry pinning all projects")}function B(e){const o=[],n=(()=>{try{return c.readFileSync(b(new URL("../../templates/cursor/inferno-mcp-server.mjs",import.meta.url)),"utf8")}catch{return null}})(),t=r=>r.replace(/\r\n/g,`
2
- `);for(const r of[s.join(".cursor","inferno-mcp-server.mjs"),"inferno-mcp-server.mjs"])try{const i=c.readFileSync(s.join(e,r),"utf8");/serverInfo: \{ name: "infernoflow"|\[infernoflow MCP\]/.test(i)&&n&&t(i)!==t(n)&&o.push(r)}catch{}for(const r of[s.join(".claude","hooks","log-frustration.mjs"),s.join(".cursor","hooks","inferno-session-draft.mjs")])try{c.readFileSync(s.join(e,r),"utf8").includes(_)||o.push(r)}catch{}return o.length?l(`Outdated (vulnerable) copies: ${o.map(r=>r.split(s.sep).join("/")).join(", ")}`,"infernoflow setup --yes"):a("Generated MCP server / hook copies are current")}function Q(e){const o=s.join(e,".ai-memory");if(!c.existsSync(o))return a("No memory store \u2014 nothing to scan");const n=[],t=r=>{for(const i of c.readdirSync(r,{withFileTypes:!0})){const d=s.join(r,i.name);if(i.isDirectory()){t(d);continue}if(/\.(jsonl|json|md)$/.test(i.name)&&!(i.name==="details.local.jsonl"||i.name==="global.jsonl"))try{const u=E(c.readFileSync(d,"utf8")).kinds;u.length&&n.push(`${s.relative(e,d).split(s.sep).join("/")} (${u.join(", ")})`)}catch{}}};try{t(o)}catch{}return n.length?l(`Possible secrets in memory files: ${n.join("; ")}`,"Remove them (infernoflow forget <id>); if already pushed, rotate the secret \u2014 it is in git history"):a("No secrets found in memory files")}function Z(e){const o=s.join(e,"inferno","integrations.json");if(!c.existsSync(o))return a("No API keys stored in the project");let n=!1;try{n=Object.values(JSON.parse(c.readFileSync(o,"utf8"))||{}).some(r=>r&&typeof r=="object"&&r.apiKey)}catch{}let t=!1;try{$("git ls-files --error-unmatch -- inferno/integrations.json",{cwd:e,stdio:"ignore"}),t=!0}catch{}return n&&t?l("inferno/integrations.json holds an API key AND is tracked by git","Rotate the key with the provider, then run: infernoflow ai status (moves keys out of the project)"):n?l("inferno/integrations.json holds an API key","infernoflow ai status (moves it to ~/.infernoflow/ai-credentials.json)"):t?l("inferno/integrations.json is tracked by git","git rm --cached inferno/integrations.json"):a("No API keys stored in the project")}function ee(e,o){const n=s.join(e,".ai-memory",".mcp-runtime.json");if(!c.existsSync(n))return a("MCP runtime stamp not present yet \u2014 start your AI tool to write one");let t;try{t=JSON.parse(c.readFileSync(n,"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===o?a(`MCP runtime v${t.version} matches CLI`):o.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${o} \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 ne(e){const o=G(D(e));if(!o.includeProtocol)return a("Memory-protocol injection disabled in config (injection.includeProtocol=false)");const n=o.targets,t=[];for(const r of n)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}/${n.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 oe(e){const o=R(e),n=Object.entries(o).filter(([,t])=>t).map(([t])=>t);return n.length?a(`AI provider${n.length!==1?"s":""}: ${n.join(", ")}`):l("No AI provider configured",`Set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_AI_API_KEY, or OPENROUTER_API_KEY
1
+ import{claudeJsonPath as A,claudeDesktopConfigPath as I,isInfernoflowEntry as M}from"../mcpRegistration.mjs";import{HOOK_VERSION_MARKER as _}from"../securityRefresh.mjs";import{redactSecrets as E}from"../security/redact.mjs";import*as c from"node:fs";import*as s from"node:path";import*as y from"node:os";import*as D from"node:http";import{execSync as $,spawnSync as b}from"node:child_process";import{fileURLToPath as C}from"node:url";import{bold as N,cyan as P,gray as S,green as j,yellow as x,red as v}from"../ui/output.mjs";import{detectAvailableProviders as F}from"../ai/providerRouter.mjs";import{readEntries as R,readConfig as T}from"../amp/io.mjs";import{resolveInjectionSettings as G}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 L(){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 J(){try{const e=b("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 H(e){try{return $("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 K(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 U(e){if(w(e)){let o=0;try{o=R(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 w(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 W(e){if(w(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 l("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`):l("scenarios/ is empty","Add scenario files or run infernoflow suggest")}function V(e){if(w(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"):l("No inferno/CHANGELOG.md","infernoflow init")}function X(e){if(w(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 l("No CONTEXT.md generated","infernoflow context");const o=(Date.now()-c.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 Y(e){const n=s.join(e,".git","hooks","post-commit");return c.existsSync(n)&&c.readFileSync(n,"utf8").includes('infernoflow log "commit:')?l("Old commit-logging git hook still installed","infernoflow setup --yes"):a("No commit-logging git hook (git keeps commit history)")}function z(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 l("MCP server not configured","infernoflow setup --yes (adds to Cursor/Claude/Copilot config)")}function q(){const e=[];for(const[n,o]of[["~/.claude.json",A()],["claude_desktop_config.json",I()]])try{const t=JSON.parse(c.readFileSync(o,"utf8"))?.mcpServers?.infernoflow;t&&M(t)&&e.push(`${n} \u2192 ${(t.env||{}).INFERNOFLOW_PROJECT_DIR||"?"}`)}catch{}return e.length?l(`User-level MCP entry pins every project to one repo: ${e.join("; ")}`,"infernoflow setup --yes (registers per project and removes the pinned entry)"):a("No user-level infernoflow MCP entry pinning all projects")}function B(e){const n=[],o=(()=>{try{return c.readFileSync(C(new URL("../../templates/cursor/inferno-mcp-server.mjs",import.meta.url)),"utf8")}catch{return null}})(),t=r=>r.replace(/\r\n/g,`
2
+ `);for(const r of[s.join(".cursor","inferno-mcp-server.mjs"),"inferno-mcp-server.mjs"])try{const i=c.readFileSync(s.join(e,r),"utf8");/serverInfo: \{ name: "infernoflow"|\[infernoflow MCP\]/.test(i)&&o&&t(i)!==t(o)&&n.push(r)}catch{}for(const r of[s.join(".claude","hooks","log-frustration.mjs"),s.join(".cursor","hooks","inferno-session-draft.mjs")])try{c.readFileSync(s.join(e,r),"utf8").includes(_)||n.push(r)}catch{}return n.length?l(`Outdated (vulnerable) copies: ${n.map(r=>r.split(s.sep).join("/")).join(", ")}`,"infernoflow setup --yes"):a("Generated MCP server / hook copies are current")}function Q(e){const n=s.join(e,".ai-memory");if(!c.existsSync(n))return a("No memory store \u2014 nothing to scan");const o=[],t=r=>{for(const i of c.readdirSync(r,{withFileTypes:!0})){const d=s.join(r,i.name);if(i.isDirectory()){t(d);continue}if(/\.(jsonl|json|md)$/.test(i.name)&&!(i.name==="details.local.jsonl"||i.name==="global.jsonl"))try{const u=E(c.readFileSync(d,"utf8")).kinds;u.length&&o.push(`${s.relative(e,d).split(s.sep).join("/")} (${u.join(", ")})`)}catch{}}};try{t(n)}catch{}return o.length?l(`Possible secrets in memory files: ${o.join("; ")}`,"Remove them (infernoflow forget <id>); if already pushed, rotate the secret \u2014 it is in git history"):a("No secrets found in memory files")}function Z(e){try{const n=JSON.parse(c.readFileSync(s.join(e,".ai-memory","amp.json"),"utf8"));if(n&&n.globalDir)return l(`.ai-memory/amp.json sets globalDir (${n.globalDir}) \u2014 ignored: a committed file can't decide where personal memory goes`,`infernoflow sync set ${n.globalDir} then remove globalDir from amp.json`)}catch{}return a("Personal memory location is set only in your personal config")}function ee(e){const n=s.join(e,"inferno","integrations.json");if(!c.existsSync(n))return a("No API keys stored in the project");let o=!1;try{o=Object.values(JSON.parse(c.readFileSync(n,"utf8"))||{}).some(r=>r&&typeof r=="object"&&r.apiKey)}catch{}let t=!1;try{$("git ls-files --error-unmatch -- inferno/integrations.json",{cwd:e,stdio:"ignore"}),t=!0}catch{}return o&&t?l("inferno/integrations.json holds an API key AND is tracked by git","Rotate the key with the provider, then run: infernoflow ai status (moves keys out of the project)"):o?l("inferno/integrations.json holds an API key","infernoflow ai status (moves it to ~/.infernoflow/ai-credentials.json)"):t?l("inferno/integrations.json is tracked by git","git rm --cached inferno/integrations.json"):a("No API keys stored in the project")}function ne(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 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 oe(e){const n=G(T(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`):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 te(e){const n=F(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
3
3
  Or install Ollama (ollama.com) for free local AI
4
- Or use VS Code with GitHub Copilot (zero config)`)}async function te(){return new Promise(e=>{const o=F.get({hostname:"localhost",port:11434,path:"/api/tags",timeout:1500},n=>{e(a("Ollama running on localhost:11434"))});o.on("error",()=>e({status:"info",message:"Ollama not running (optional)",fix:"ollama serve",detail:null})),o.on("timeout",()=>{o.destroy(),e({status:"info",message:"Ollama not running (optional)",fix:null,detail:null})})})}function re(){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 o=JSON.parse(c.readFileSync(e,"utf8")),n=o.user?.login||o.user?.name||o.user?.email||"unknown";if(o.mode==="supabase"&&o.access_token){if(o.expires_at){const t=new Date(o.expires_at).getTime();if(Date.now()>t)return l(`JWT expired for ${n} \u2014 refresh on next log will retry`,"infernoflow login")}return a(`Authenticated as ${n} (Supabase JWT \u2014 auth.uid() writes)`)}return o.mode==="device-flow"&&o.github_access_token?{status:"info",message:`Identity-only as ${n} (device flow \u2014 anon-mode writes)`,fix:"infernoflow login (without --device-flow, for full auth)",detail:null}:o.access_token?l(`Legacy login for ${n} \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 ie(){try{const e=b(import.meta.url),o=s.resolve(s.dirname(e),"..","..","bin","infernoflow.mjs");if(!c.existsSync(o))return{status:"info",message:"bin/infernoflow.mjs not found from doctor location",fix:null,detail:null};const t=[...c.readFileSync(o,"utf8").matchAll(/import\("\.\.\/lib\/(commands\/[^"]+|telemetry\.mjs)"\)/g)],r=[],i=s.resolve(s.dirname(o),"..");for(const d of t){const u=d[1],p=s.join(i,"lib",u);c.existsSync(p)||r.push(u)}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 se(e){const o=s.join(e,"package.json");if(!c.existsSync(o))return a("No package.json to audit");let n;try{n=JSON.parse(c.readFileSync(o,"utf8"))}catch{return{status:"info",message:"package.json unreadable; skipping audit",detail:null,fix:null}}const t=n.scripts||{},r=new Set(["log","ask","switch","recap","status","refresh","forget","prune","bookmark","init","setup","doctor","context","mcp","install-cursor-hooks","install-vscode-copilot-hooks","generate-skills","ai","telemetry","uninstall","check","sync","amp","commands"]),i=[];for(const[u,p]of Object.entries(t)){const k=/\binfernoflow\s+([a-z][a-z0-9-]*)/.exec(String(p));if(!k)continue;const m=k[1];r.has(m)||i.push({scriptName:u,verb:m})}if(i.length===0)return a("npm scripts use current command surface");const d=i.map(u=>`${u.scriptName} \u2192 infernoflow ${u.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 ce(e){const o=s.join(e,".gitignore");if(!c.existsSync(o))return{status:"info",message:".gitignore not found",fix:null,detail:null};const n=c.readFileSync(o,"utf8");return/^(?:\*\*\/)?node_modules\/?$/m.test(n)?a(".gitignore excludes node_modules"):l(".gitignore does not exclude node_modules","Add 'node_modules/' (and '**/node_modules/') to .gitignore")}function ae(e,o){const n=e.filter(r=>r.status==="warn"&&r.fix),t=[];for(const r of n){const i=r.fix;if(i.startsWith("infernoflow ")){const d=i.slice(12).split(" ");C("infernoflow",d,{cwd:o,encoding:"utf8",timeout:3e4}).status===0&&t.push(r.label)}}return t}function le(e){return e==="pass"?j("\u2714"):e==="warn"?x("\u26A0"):e==="fail"?v("\u2717"):S("\xB7")}function fe(e,o){const n={pass:0,warn:0,fail:0,info:0,error:0};for(const i of e)n[i.status]=(n[i.status]||0)+1;console.log(),console.log(` ${N("\u{1F525} infernoflow doctor")}`),console.log();const t=Math.max(...e.map(i=>i.label.length))+2;for(const i of e)console.log(` ${le(i.status)} ${N(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)} ${P("fix:")} ${S(i.fix)}`);console.log();const r=n.fail>0?v("issues found"):n.warn>0?x("warnings"):j("all good");console.log(` ${r} \u2014 ${j(String(n.pass))} pass \xB7 ${x(String(n.warn))} warn \xB7 ${v(String(n.fail))} fail (${o}ms)`),console.log(),(n.warn>0||n.fail>0)&&(console.log(` Run ${P("infernoflow doctor --fix")} to auto-fix warnings`),console.log())}async function ue(e){const o=e.slice(1),n=o.includes("--json"),t=o.includes("--fix"),r=process.cwd(),i=Date.now();let d="0.0.0-unknown";try{const{fileURLToPath:m}=await import("node:url"),g=s.dirname(m(import.meta.url)),O=s.join(g,"..","..","package.json");d=JSON.parse(c.readFileSync(O,"utf8")).version||d}catch{}const u=[f("Node.js version",()=>L()),f("infernoflow CLI",()=>J()),f("Git repository",()=>H(r)),f("inferno/ directory",()=>K(r)),f("Contract / mode",()=>U(r)),f("Scenarios",()=>W(r)),f("Changelog",()=>V(r)),f("CONTEXT.md",()=>X(r)),f("Git hooks",()=>Y(r)),f("Auto-capture protocol",()=>ne(r)),f("MCP server",()=>z(r)),f("MCP runtime version",()=>ee(r,d)),f("MCP registration",()=>q()),f("Generated copies",()=>B(r)),f("Secrets in memory",()=>Q(r)),f("API keys",()=>Z(r)),f("AI providers",()=>oe(r)),f("Cloud sync",()=>re()),f(".gitignore",()=>ce(r)),f("Router integrity",()=>ie()),f("npm scripts",()=>se(r)),await te().then(m=>({label:"Ollama (local AI)",...m}))],p=Date.now()-i;if(t){const m=ae(u,r);if(m.length)return n||(console.log(),m.forEach(g=>console.log(` ${j("\u2714")} Fixed: ${g}`)),console.log()),ue(n?["doctor","--json"]:["doctor"])}if(n){const m={pass:0,warn:0,fail:0,info:0};u.forEach(g=>m[g.status]=(m[g.status]||0)+1),console.log(JSON.stringify({ok:m.fail===0,counts:m,results:u,elapsed:p}));return}fe(u,p),u.some(m=>m.status==="fail")&&process.exit(1)}export{ue as doctorCommand};
4
+ Or use VS Code with GitHub Copilot (zero config)`)}async function re(){return new Promise(e=>{const n=D.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 ie(){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 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 se(){try{const e=C(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 u=d[1],p=s.join(i,"lib",u);c.existsSync(p)||r.push(u)}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 ce(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","mcp","hook","resume","resolve","move","curate","transcript","install-cursor-hooks","install-vscode-copilot-hooks","generate-skills","ai","telemetry","uninstall","check","sync","amp","commands"]),i=[];for(const[u,p]of Object.entries(t)){const k=/\binfernoflow\s+([a-z][a-z0-9-]*)/.exec(String(p));if(!k)continue;const m=k[1];r.has(m)||i.push({scriptName:u,verb:m})}if(i.length===0)return a("npm scripts use current command surface");const d=i.map(u=>`${u.scriptName} \u2192 infernoflow ${u.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 ae(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"):l(".gitignore does not exclude node_modules","Add 'node_modules/' (and '**/node_modules/') to .gitignore")}function le(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(" ");b("infernoflow",d,{cwd:n,encoding:"utf8",timeout:3e4}).status===0&&t.push(r.label)}}return t}function fe(e){return e==="pass"?j("\u2714"):e==="warn"?x("\u26A0"):e==="fail"?v("\u2717"):S("\xB7")}function ue(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(` ${N("\u{1F525} infernoflow doctor")}`),console.log();const t=Math.max(...e.map(i=>i.label.length))+2;for(const i of e)console.log(` ${fe(i.status)} ${N(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)} ${P("fix:")} ${S(i.fix)}`);console.log();const r=o.fail>0?v("issues found"):o.warn>0?x("warnings"):j("all good");console.log(` ${r} \u2014 ${j(String(o.pass))} pass \xB7 ${x(String(o.warn))} warn \xB7 ${v(String(o.fail))} fail (${n}ms)`),console.log(),(o.warn>0||o.fail>0)&&(console.log(` Run ${P("infernoflow doctor --fix")} to auto-fix warnings`),console.log())}async function me(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:m}=await import("node:url"),g=s.dirname(m(import.meta.url)),O=s.join(g,"..","..","package.json");d=JSON.parse(c.readFileSync(O,"utf8")).version||d}catch{}const u=[f("Node.js version",()=>L()),f("infernoflow CLI",()=>J()),f("Git repository",()=>H(r)),f("inferno/ directory",()=>K(r)),f("Contract / mode",()=>U(r)),f("Scenarios",()=>W(r)),f("Changelog",()=>V(r)),f("CONTEXT.md",()=>X(r)),f("Git hooks",()=>Y(r)),f("Auto-capture protocol",()=>oe(r)),f("MCP server",()=>z(r)),f("MCP runtime version",()=>ne(r,d)),f("MCP registration",()=>q()),f("Generated copies",()=>B(r)),f("Secrets in memory",()=>Q(r)),f("API keys",()=>ee(r)),f("Personal memory path",()=>Z(r)),f("AI providers",()=>te(r)),f("Cloud sync",()=>ie()),f(".gitignore",()=>ae(r)),f("Router integrity",()=>se()),f("npm scripts",()=>ce(r)),await re().then(m=>({label:"Ollama (local AI)",...m}))],p=Date.now()-i;if(t){const m=le(u,r);if(m.length)return o||(console.log(),m.forEach(g=>console.log(` ${j("\u2714")} Fixed: ${g}`)),console.log()),me(o?["doctor","--json"]:["doctor"])}if(o){const m={pass:0,warn:0,fail:0,info:0};u.forEach(g=>m[g.status]=(m[g.status]||0)+1),console.log(JSON.stringify({ok:m.fail===0,counts:m,results:u,elapsed:p}));return}ue(u,p),u.some(m=>m.status==="fail")&&process.exit(1)}export{me as doctorCommand};
@@ -0,0 +1,12 @@
1
+ import*as c from"node:fs";import*as f from"node:path";import{execFileSync as S}from"node:child_process";import{appendEntry as w,readEntries as x,deleteEntry as A,ampPaths as O}from"../amp/io.mjs";import{buildSessionContext as _,openAttempts as E,visibleEntries as j}from"../memoryView.mjs";import{findProjectRoot as D}from"../projectRoot.mjs";const p="auto-handoff",$=5;function m(){try{if(process.stdin.isTTY)return{};const t=c.readFileSync(0,"utf8");return t.trim()?JSON.parse(t):{}}catch{return{}}}function y(){const t=process.env.CLAUDE_PROJECT_DIR||process.cwd();try{return D(t)}catch{return t}}function g(t){if(!t||typeof t!="string")return null;let e;try{const o=c.statSync(t),r=c.openSync(t,"r"),s=Math.min(o.size,2e6),i=Buffer.alloc(s);c.readSync(r,i,0,s,o.size-s),c.closeSync(r),e=i.toString("utf8")}catch{return null}const a=e.split(`
2
+ `).reverse();for(const o of a){let r;try{r=JSON.parse(o)}catch{continue}if(r.type!=="user"||!r.message)continue;const s=r.message.content,i=typeof s=="string"?s:Array.isArray(s)?s.filter(u=>u&&u.type==="text").map(u=>u.text).join(" "):"",l=String(i||"").replace(/\s+/g," ").trim();if(l&&!l.startsWith("<"))return l.slice(0,400)}return null}function b(t){try{return S("git",["status","--porcelain"],{cwd:t,encoding:"utf8",stdio:["ignore","pipe","ignore"],windowsHide:!0,timeout:5e3}).split(`
3
+ `).map(e=>e.trimEnd()).filter(Boolean)}catch{return[]}}function T(){const t=y();if(c.existsSync(f.join(t,".ai-memory"))){m();try{process.stdout.write(_(t)+`
4
+ `)}catch{}}}function k(t=[]){const e=y();if(!c.existsSync(f.join(e,".ai-memory")))return;const a=n=>{const d=t.indexOf(n);return d!==-1?t[d+1]:void 0},o=a("--transcript")||a("--reason")?{transcript_path:a("--transcript"),reason:a("--reason")}:m(),r=g(o.transcript_path),s=b(e);if(!r&&!s.length)return;const i=E(j(e)).slice(-5),l=new Date().toISOString().slice(0,16).replace("T"," "),u=[`# Session ended \u2014 ${l}${o.reason?` (${o.reason})`:""}`,"",r?`**Last request:** ${r}`:null,s.length?`
5
+ **Uncommitted changes (${s.length}):**
6
+ ${s.slice(0,25).map(n=>"- `"+n+"`").join(`
7
+ `)}`:null,i.length?`
8
+ **Open dead ends:**
9
+ ${i.map(n=>"- "+(n.summary||n.msg)).join(`
10
+ `)}`:null].filter(Boolean).join(`
11
+ `);w(e,{ts:new Date().toISOString(),type:"note",summary:`Session ended \u2014 ${l}`,tags:["bookmark",p],agent:"hook",source:"session-end",detail:u,detailLocal:!0,localOnly:!0});const h=x(e).filter(n=>Array.isArray(n.tags)&&n.tags.includes(p)).sort((n,d)=>(Number(d.ts)||Date.parse(d.ts)||0)-(Number(n.ts)||Date.parse(n.ts)||0));for(const n of h.slice($))try{A(e,n.id)}catch{}}async function q(t){const e=(t||[])[1];try{if(e==="session-start")return T();if(e==="session-end")return k(t)}catch{}["session-start","session-end"].includes(e)||(process.stderr.write(`usage: infernoflow hook session-start|session-end (called by AI-tool hooks)
12
+ `),process.exitCode=1)}export{O as _ampPaths,g as _lastUserRequest,q as hookCommand};
@@ -1,16 +1,16 @@
1
- import{redactSecrets as I}from"../security/redact.mjs";import{describeStore as V}from"../amp/io.mjs";import*as f from"node:fs";import*as E from"node:path";import"node:os";import{bold as j,cyan as x,gray as o,green as k,yellow as R,red as S,orange as W}from"../ui/output.mjs";import{ampPaths as q,appendEntry as G,readEntries as J,readConfig as Y,resolveRotationSettings as M,pruneEntries as X}from"../amp/io.mjs";import{refreshRuleFilesFromMemory as _}from"../ruleFiles.mjs";function z(){return q(process.cwd())}const T=["gotcha","decision","attempt","note","detection","pattern","preference","theme","handoff","error"],C=["worked","failed","partial","unknown"];function H(){return J(process.cwd())}function K(t,{auto:c=!1,quiet:n=!1}={}){const r=process.cwd(),h=E.join(r,".ai-memory"),g=E.join(r,"inferno");if(c&&!f.existsSync(h)&&!f.existsSync(g))return!1;!f.existsSync(h)&&!f.existsSync(g)&&(n||console.error(S(` \u2718 no .ai-memory/ or inferno/ \u2014 run: infernoflow init
2
- `)),process.exit(1)),G(r,t);try{M(Y(r)).auto&&X(r)}catch{}try{_(r)}catch{}return!0}function Q(){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(t,c){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"?R:r==="decision"?x:r==="theme"?W:r==="preference"?x: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(c+1).padStart(3))} ${o(n)} ${h(r)}${g} ${t.summary}${m}`}async function ce(t){const c=e=>t.includes(e),n=(e,u)=>{const a=t.indexOf(e);return a!==-1&&t[a+1]?t[a+1]:u};c("--no-color")&&(process.env.NO_COLOR="1");const r=c("--show"),h=c("--clear"),g=c("--json"),m=c("--auto"),d=c("--quiet"),w=n("--source",null);if(r||g){const e=H(),u=t[t.indexOf("--show")+1],a=u&&/^\d+$/.test(u)?parseInt(u):20,i=e.slice(-a);if(g){console.log(JSON.stringify(i,null,2));return}if(console.log(`
3
- `+j("\u{1F525} infernoflow \u2014 session memory")),console.log(" "+o(V(process.cwd()))),console.log(" "+"\u2500".repeat(50)),!i.length){console.log(o(`
1
+ import{CLI_TYPES as G,RESULTS as T,AGENT_TYPES as V,LEGACY_CLI_TYPES as W}from"../schema.mjs";import{redactSecrets as I}from"../security/redact.mjs";import{describeStore as q}from"../amp/io.mjs";import*as f from"node:fs";import*as O from"node:path";import"node:os";import{bold as _,cyan as N,gray as o,green as b,yellow as R,red as S,orange as J}from"../ui/output.mjs";import{ampPaths as M,appendEntry as X,readEntries as z,readConfig as H,resolveRotationSettings as K,pruneEntries as Q}from"../amp/io.mjs";import{refreshRuleFilesFromMemory as C}from"../ruleFiles.mjs";function Z(){return M(process.cwd())}const P=G,k=T;function ee(){return z(process.cwd())}function ne(t,{auto:c=!1,quiet:n=!1}={}){const s=process.cwd(),m=O.join(s,".ai-memory"),g=O.join(s,"inferno");if(c&&!f.existsSync(m)&&!f.existsSync(g))return!1;!f.existsSync(m)&&!f.existsSync(g)&&(n||console.error(S(` \u2718 no .ai-memory/ or inferno/ \u2014 run: infernoflow init
2
+ `)),process.exit(1)),X(s,t);try{K(H(s)).auto&&Q(s)}catch{}try{C(s)}catch{}return!0}function oe(){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 te(t,c){const n=new Date(t.ts).toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}),s=t.type||"note",m=s==="gotcha"?R:s==="decision"?N:s==="theme"?J:s==="preference"?N:s==="attempt"?o:s==="error"?S:d=>d,g=t.result?` [${t.result}]`:"",h=t.agent&&t.agent!=="human"?o(` (${t.agent})`):"";return` ${o(String(c+1).padStart(3))} ${o(n)} ${m(s)}${g} ${t.summary}${h}`}async function de(t){const c=e=>t.includes(e),n=(e,u)=>{const a=t.indexOf(e);return a!==-1&&t[a+1]?t[a+1]:u};c("--no-color")&&(process.env.NO_COLOR="1");const s=c("--show"),m=c("--clear"),g=c("--json"),h=c("--auto"),d=c("--quiet"),w=n("--source",null);if(s||g){const e=ee(),u=t[t.indexOf("--show")+1],a=u&&/^\d+$/.test(u)?parseInt(u):20,l=e.slice(-a);if(g){console.log(JSON.stringify(l,null,2));return}if(console.log(`
3
+ `+_("\u{1F525} infernoflow \u2014 session memory")),console.log(" "+o(q(process.cwd()))),console.log(" "+"\u2500".repeat(50)),!l.length){console.log(o(`
4
4
  No entries yet. Start logging with: infernoflow log "<what happened>"
5
- `));return}console.log(o(` Showing last ${i.length} of ${e.length} entries
6
- `)),i.forEach((l,O)=>console.log(Z(l,e.length-i.length+O))),console.log();return}if(h){const e=z(),u=Date.now(),a=new Set([e.sessions,e.currentBranchFile,e.defaultBranchFile].filter(Boolean));try{if(e.branchesDir&&f.existsSync(e.branchesDir))for(const l of f.readdirSync(e.branchesDir))l.endsWith(".jsonl")&&a.add(E.join(e.branchesDir,l))}catch{}const i=[];for(const l of a){if(!f.existsSync(l))continue;const O=l.replace(/\.jsonl$/,`-archive-${u}.jsonl`);try{f.renameSync(l,O),i.push(E.basename(O))}catch{}}if(!i.length){console.log(o(` Nothing to clear.
7
- `));return}console.log(k(` \u2714 Session log archived (${i.length} file${i.length===1?"":"s"}) \u2014 entries cleared from active memory.
5
+ `));return}console.log(o(` Showing last ${l.length} of ${e.length} entries
6
+ `)),l.forEach((i,E)=>console.log(te(i,e.length-l.length+E))),console.log();return}if(m){const e=Z(),u=Date.now(),a=new Set([e.sessions,e.currentBranchFile,e.defaultBranchFile].filter(Boolean));try{if(e.branchesDir&&f.existsSync(e.branchesDir))for(const i of f.readdirSync(e.branchesDir))i.endsWith(".jsonl")&&a.add(O.join(e.branchesDir,i))}catch{}const l=[];for(const i of a){if(!f.existsSync(i))continue;const E=i.replace(/\.jsonl$/,`-archive-${u}.jsonl`);try{f.renameSync(i,E),l.push(O.basename(E))}catch{}}if(!l.length){console.log(o(` Nothing to clear.
7
+ `));return}console.log(b(` \u2714 Session log archived (${l.length} file${l.length===1?"":"s"}) \u2014 entries cleared from active memory.
8
8
  `));return}const B=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("--")&&!B.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),P=n("--agent",Q()),A=n("--file",null),F=n("--line",null),D=n("--tags",null);T.includes(y)||(d||console.error(S(` \u2718 Invalid type: ${y}. Valid: ${T.join(", ")}
12
- `)),process.exit(1)),p&&!C.includes(p)&&(d||console.error(S(` \u2718 Invalid result: ${p}. Valid: ${C.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,b=n("--detail",null),$=n("--detail-file",null);let s=null;if($)try{s=$==="-"?f.readFileSync(0,"utf8"):f.readFileSync($,"utf8")}catch(e){d||console.error(S(` \u2718 Could not read --detail-file ${$}: ${e.message}
14
- `)),process.exit(1)}else b&&(s=b);const U={ts:new Date().toISOString(),agent:P,type:y,summary:v,...p?{result:p}:{},...w?{source:w}:{},...A?{file:A}:{},...L?{line:L}:{},...N&&N.length?{tags:N}:{},...s&&s.trim()?{detail:s}:{},...m?{auto:!0}:{}};if(K(U,{auto:m,quiet:d})){if(t.includes("--refresh-rules"))try{_(process.cwd())}catch{}if(!d){const e=y!=="note"?x(` [${y}]`):"",u=p?o(` \u2192 ${p}`):"",a=w?o(` (via ${w})`):"",i=s&&s.trim()?o(` (+${s.length}-char detail)`):"",l=I(v);console.log(k(` \u2714 Logged${e}${u}${a}: `)+l.text+i+`
15
- `),(l.kinds.length||s&&I(s).kinds.length)&&console.log(R(` \u26A0 secrets were redacted before saving (${[...new Set([...l.kinds,...s?I(s).kinds:[]])].join(", ")})`)+`
16
- `)}}}export{ce as logCommand};
9
+ `+_("\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: "+V.join(" \xB7 ")+" (legacy: "+W.join(" \xB7 ")+")")),console.log(o(" Results: "+T.join(" \xB7 "))),console.log(o(` Auto-capture: --auto (silent skip if no .ai-memory/) \xB7 --quiet \xB7 --source <name>
11
+ `));return}const y=n("--type","note"),p=n("--result",null),U=n("--agent",oe()),j=n("--file",null),L=n("--line",null),A=n("--tags",null);P.includes(y)||(d||console.error(S(` \u2718 Invalid type: ${y}. Valid: ${P.join(", ")}
12
+ `)),process.exit(1)),p&&!k.includes(p)&&(d||console.error(S(` \u2718 Invalid result: ${p}. Valid: ${k.join(", ")}
13
+ `)),process.exit(1));const x=L&&/^\d+$/.test(L)?parseInt(L,10):null,F=A?A.split(",").map(e=>e.trim()).filter(Boolean):null,D=n("--detail",null),$=n("--detail-file",null);let r=null;if($)try{r=$==="-"?f.readFileSync(0,"utf8"):f.readFileSync($,"utf8")}catch(e){d||console.error(S(` \u2718 Could not read --detail-file ${$}: ${e.message}
14
+ `)),process.exit(1)}else D&&(r=D);const Y={ts:new Date().toISOString(),agent:U,type:y,summary:v,...p?{result:p}:{},...w?{source:w}:{},...j?{file:j}:{},...x?{line:x}:{},...F&&F.length?{tags:F}:{},...r&&r.trim()?{detail:r}:{},...h?{auto:!0}:{}};if(ne(Y,{auto:h,quiet:d})){if(t.includes("--refresh-rules"))try{C(process.cwd())}catch{}if(!d){const e=y!=="note"?N(` [${y}]`):"",u=p?o(` \u2192 ${p}`):"",a=w?o(` (via ${w})`):"",l=r&&r.trim()?o(` (+${r.length}-char detail)`):"",i=I(v);console.log(b(` \u2714 Logged${e}${u}${a}: `)+i.text+l+`
15
+ `),(i.kinds.length||r&&I(r).kinds.length)&&console.log(R(` \u26A0 secrets were redacted before saving (${[...new Set([...i.kinds,...r?I(r).kinds:[]])].join(", ")})`)+`
16
+ `)}}}export{de as logCommand};