infernoflow 0.43.11 → 0.44.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.
Files changed (49) hide show
  1. package/dist/bin/infernoflow.mjs +30 -33
  2. package/dist/lib/amp/io.mjs +8 -8
  3. package/dist/lib/cleanTree.mjs +12 -0
  4. package/dist/lib/commands/ai.mjs +2 -2
  5. package/dist/lib/commands/amp.mjs +4 -4
  6. package/dist/lib/commands/ask.mjs +2 -2
  7. package/dist/lib/commands/context.mjs +18 -18
  8. package/dist/lib/commands/doctor.mjs +2 -3
  9. package/dist/lib/commands/init.mjs +31 -32
  10. package/dist/lib/commands/log.mjs +13 -19
  11. package/dist/lib/commands/recap.mjs +3 -3
  12. package/dist/lib/commands/refresh.mjs +5 -0
  13. package/dist/lib/commands/status.mjs +6 -7
  14. package/dist/lib/commands/switch.mjs +5 -5
  15. package/dist/lib/commands/sync.mjs +41 -0
  16. package/dist/lib/git/branch.mjs +2 -0
  17. package/dist/lib/projectRoot.mjs +1 -0
  18. package/dist/lib/ruleFiles.mjs +9 -5
  19. package/dist/lib/upgradeCheck.mjs +1 -1
  20. package/dist/templates/cursor/inferno-mcp-server.mjs +170 -325
  21. package/package.json +13 -5
  22. package/dist/lib/commands/changelog.mjs +0 -21
  23. package/dist/lib/commands/ci.mjs +0 -3
  24. package/dist/lib/commands/claudeMd.mjs +0 -116
  25. package/dist/lib/commands/coverage.mjs +0 -2
  26. package/dist/lib/commands/demo.mjs +0 -113
  27. package/dist/lib/commands/diff.mjs +0 -5
  28. package/dist/lib/commands/explain.mjs +0 -8
  29. package/dist/lib/commands/feedback.mjs +0 -12
  30. package/dist/lib/commands/graph.mjs +0 -76
  31. package/dist/lib/commands/impact.mjs +0 -2
  32. package/dist/lib/commands/implement.mjs +0 -7
  33. package/dist/lib/commands/monorepo.mjs +0 -4
  34. package/dist/lib/commands/notify.mjs +0 -4
  35. package/dist/lib/commands/prImpact.mjs +0 -2
  36. package/dist/lib/commands/publish.mjs +0 -21
  37. package/dist/lib/commands/review.mjs +0 -24
  38. package/dist/lib/commands/run.mjs +0 -10
  39. package/dist/lib/commands/scaffold.mjs +0 -124
  40. package/dist/lib/commands/scan.mjs +0 -42
  41. package/dist/lib/commands/stability.mjs +0 -2
  42. package/dist/lib/commands/stats.mjs +0 -4
  43. package/dist/lib/commands/suggest.mjs +0 -62
  44. package/dist/lib/commands/syncAuto.mjs +0 -1
  45. package/dist/lib/commands/test.mjs +0 -6
  46. package/dist/lib/commands/theme.mjs +0 -18
  47. package/dist/lib/commands/upgrade.mjs +0 -20
  48. package/dist/lib/commands/watch.mjs +0 -7
  49. package/dist/lib/commands/why.mjs +0 -4
@@ -1,41 +1,38 @@
1
1
  #!/usr/bin/env node
2
- (function(){if(process.platform!=="win32"||process.env.WT_SESSION||process.env.ConEmuPID||process.env.TERM_PROGRAM==="vscode")return;const 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]"},n=new RegExp(Object.keys(t).join("|"),"g");function r(c){const d=c.write.bind(c);c.write=function(s,...f){if(typeof s=="string")s=s.replace(n,u=>t[u]);else if(Buffer.isBuffer(s)){const u=s.toString("utf8").replace(n,k=>t[k]);s=Buffer.from(u,"utf8")}return d(s,...f)}}r(process.stdout),r(process.stderr)})();import{readFileSync as $}from"node:fs";import{dirname as S,join as g}from"node:path";import{fileURLToPath as x}from"node:url";import{bold as m,gray as o,cyan as a,red as h}from"../lib/ui/output.mjs";const A=S(x(import.meta.url));function O(e){for(const t of[g(e,"..","..","package.json"),g(e,"..","package.json")])try{return JSON.parse($(t,"utf8"))}catch{}return{version:"0.0.0-source"}}const I=O(A),p=I.version||"0.0.0",y={publish:"Bump version, update changelog, build, npm publish, git commit + push in one shot",diff:"Show what capabilities changed since the last git tag (or any ref)",changelog:"Draft a changelog entry from commits since the last tag",setup:"One command to get fully operational \u2014 detects IDE, inits, installs hooks + MCP",init:"Scaffold inferno/ in your project (or adopt existing project)","install-cursor-hooks":"Install Cursor hooks: draft agent replies to inferno/CONTEXT.draft.md","install-vscode-copilot-hooks":"Install VS Code + Copilot agent hooks (Preview): draft to inferno/CONTEXT.draft.md",check:"Validate contract, capabilities, scenarios, changelog",status:"Show contract health at a glance","pr-impact":"Summarize PR impact on capabilities and docs",sync:"Run deterministic inferno sync flow",run:"One-command detect/propose/apply/validate flow","doc-gate":"Fail if code changed but docs were not updated",suggest:"Generate AI prompt + apply capability updates",implement:"Generate code-agent implementation prompt(s)",context:"Generate AI-ready context for new sessions","generate-skills":"Generate personalised Cursor rules + skill files from your developer profile",watch:"Watch source files and run suggest automatically on save",ci:"CI-native check: GitHub Actions annotations, GitLab code quality, exit codes",notify:"Post capability drift summary to Slack or Discord",monorepo:"Manage infernoflow across monorepo packages (init | list | status | diff | sync)",doctor:"Diagnose your infernoflow setup \u2014 checks Node, git, contract, AI providers, MCP, hooks",coverage:"Map test files to capabilities \u2014 show which caps have test coverage and which don't",review:"AI-powered capability impact review for staged or recent git changes",scan:"Deep AST scan \u2014 route discovery, entry point detection, HTTP URL extraction, capability suggestions",graph:"Build capability dependency graph \u2014 shows which caps call which, detects breaking changes",stability:"Show solid/liquid stability level for every capability (frozen/stable/experimental)",freeze:"Mark a capability as frozen (solid) \u2014 AI will not modify it without explicit instruction",thaw:"Reset a capability to experimental (liquid) \u2014 free to evolve",why:"Given a file or function name \u2014 show which capability it serves, scenarios, stability, and git history",impact:"Blast radius analysis \u2014 see every cap, scenario, and risk level affected before you change anything",scaffold:"Generate a new capability \u2014 source skeleton, contract registration, and placeholder scenario in one command",explain:"AI narrative about a capability \u2014 what it does, why it exists, what's risky, and what to test",test:"Run registered scenarios for a capability \u2014 auto-generates a smoke harness if no test runner is configured",ai:"Manage AI providers \u2014 setup, status, test connection (subcommands: setup | status | test | clear)",demo:"Interactive walkthrough \u2014 scaffolds a sample project and runs the full capability chain end-to-end",feedback:"60-second CLI survey about how you use infernoflow (--form to open web form)",telemetry:"Manage anonymous usage telemetry (on | off | status) \u2014 opt-in, command names only",log:"Append to session memory (decisions, gotchas, failed attempts, theme changes) \u2014 what AI can't infer from code",theme:"Scan fonts, colors, and CSS variables \u2014 write inferno/theme.json so AI always matches the design system",switch:"Generate a handoff summary when switching AI agents \u2014 paste into the next session so nothing is lost",upgrade:"Upgrade a lite infernoflow setup to the full structure (scenarios, changelog, scripts)",stats:"Value dashboard \u2014 session memory, tokens injected per session, coverage %, estimated savings",ask:"Query session memory \u2014 search gotchas, decisions, and failed attempts by keyword or type",recap:"End-of-session summary \u2014 what was captured, what git changes weren't logged, session health score",uninstall:"Remove infernoflow from a project \u2014 inferno/, CLAUDE.md, MCP server, git hooks (--dry-run to preview)",contract:"Capability contracts \u2014 scan, freeze, impact, graph, scaffold, etc. (run: infernoflow contract)",dev:"Maintenance & integration \u2014 publish, changelog, dashboard, ai, ci, sync, etc. (run: infernoflow dev)",amp:"AI Memory Protocol \u2014 status, migrate from legacy, validate (run: infernoflow amp)"},l={publish:async e=>(await import("../lib/commands/publish.mjs")).publishCommand(e),diff:async e=>(await import("../lib/commands/diff.mjs")).diffCommand(e),changelog:async e=>(await import("../lib/commands/changelog.mjs")).changelogCommand(e),setup:async e=>(await import("../lib/commands/setup.mjs")).setupCommand(e),init:async e=>(await import("../lib/commands/init.mjs")).initCommand(e),"install-cursor-hooks":async e=>(await import("../lib/commands/installCursorHooks.mjs")).installCursorHooksCommand(e),"install-vscode-copilot-hooks":async e=>(await import("../lib/commands/installVsCodeCopilotHooks.mjs")).installVsCodeCopilotHooksCommand(e),check:async e=>(await import("../lib/commands/check.mjs")).checkCommand(e),status:async e=>(await import("../lib/commands/status.mjs")).statusCommand(e),"pr-impact":async e=>(await import("../lib/commands/prImpact.mjs")).prImpactCommand(e),sync:async e=>(await import("../lib/commands/syncAuto.mjs")).syncCommand(e),run:async e=>(await import("../lib/commands/run.mjs")).runCommand(e),suggest:async e=>(await import("../lib/commands/suggest.mjs")).suggestCommand(e),implement:async e=>(await import("../lib/commands/implement.mjs")).implementCommand(e),context:async e=>(await import("../lib/commands/context.mjs")).contextCommand(e),"doc-gate":async e=>(await import("../lib/commands/docGate.mjs")).docGateCommand(e),"generate-skills":async e=>(await import("../lib/commands/generateSkills.mjs")).generateSkillsCommand(e),watch:async e=>(await import("../lib/commands/watch.mjs")).watchCommand(e),ci:async e=>(await import("../lib/commands/ci.mjs")).ciCommand(e),notify:async e=>(await import("../lib/commands/notify.mjs")).notifyCommand(e),monorepo:async e=>(await import("../lib/commands/monorepo.mjs")).monorepoCommand(e),doctor:async e=>(await import("../lib/commands/doctor.mjs")).doctorCommand(e),coverage:async e=>(await import("../lib/commands/coverage.mjs")).coverageCommand(e),review:async e=>(await import("../lib/commands/review.mjs")).reviewCommand(e),scan:async e=>(await import("../lib/commands/scan.mjs")).scanCommand(e),graph:async e=>(await import("../lib/commands/graph.mjs")).graphCommand(e),stability:async e=>(await import("../lib/commands/stability.mjs")).stabilityCommand(e),freeze:async e=>(await import("../lib/commands/stability.mjs")).freezeCommand(e),thaw:async e=>(await import("../lib/commands/stability.mjs")).thawCommand(e),why:async e=>(await import("../lib/commands/why.mjs")).whyCommand(e),impact:async e=>(await import("../lib/commands/impact.mjs")).impactCommand(e),scaffold:async e=>(await import("../lib/commands/scaffold.mjs")).scaffoldCommand(e),explain:async e=>(await import("../lib/commands/explain.mjs")).explainCommand(e),test:async e=>(await import("../lib/commands/test.mjs")).testCommand(e),ai:async e=>(await import("../lib/commands/ai.mjs")).aiCommand(e),demo:async e=>(await import("../lib/commands/demo.mjs")).demoCommand(e),log:async e=>(await import("../lib/commands/log.mjs")).logCommand(e),theme:async e=>(await import("../lib/commands/theme.mjs")).themeCommand(e),switch:async e=>(await import("../lib/commands/switch.mjs")).switchCommand(e),upgrade:async e=>(await import("../lib/commands/upgrade.mjs")).upgradeCommand(e),stats:async e=>(await import("../lib/commands/stats.mjs")).statsCommand(e),ask:async e=>(await import("../lib/commands/ask.mjs")).askCommand(e),recap:async e=>(await import("../lib/commands/recap.mjs")).recapCommand(e),uninstall:async e=>(await import("../lib/commands/uninstall.mjs")).uninstallCommand(e),feedback:async e=>(await import("../lib/commands/feedback.mjs")).feedbackCommand(e),telemetry:async e=>(await import("../lib/telemetry.mjs")).telemetryCommand(e),contract:async e=>w("contract",v,e),dev:async e=>w("dev",b,e),amp:async e=>(await import("../lib/commands/amp.mjs")).ampCommand(e)};async function w(e,t,n){const r=n[1];if(!r||r==="--help"||r==="-h"){console.log(),console.log(` ${m("\u{1F525} infernoflow "+e)} ${o("\u2014 available verbs:")}`),console.log();const d=Math.max(...Object.keys(t).map(s=>s.length))+2;for(const s of Object.keys(t)){const f=y[t[s]]||"";console.log(` ${a(s.padEnd(d))} ${o(f)}`)}console.log(),console.log(` ${o("Run")} ${a(`infernoflow ${e} <verb> --help`)} ${o("for verb-specific options.")}`),console.log();return}const c=t[r];return(!c||!l[c])&&(console.error(h(`
3
- Unknown ${e} verb: ${r}`)),console.error(o(` Run: infernoflow ${e} (see all verbs)
4
- `)),process.exit(1)),l[c]([c,...n.slice(2)])}function B(){const e=Object.keys(y),t=Math.max(...e.map(n=>n.length),8)+1;return Object.entries(y).map(([n,r])=>` ${n.padEnd(t," ")}${r}`).join(`
5
- `)}const v={scan:"scan",check:"check",status:"status",freeze:"freeze",thaw:"thaw",why:"why",impact:"impact",graph:"graph",stability:"stability",scaffold:"scaffold",explain:"explain",test:"test",coverage:"coverage",suggest:"suggest",run:"run",implement:"implement","doc-gate":"doc-gate","pr-impact":"pr-impact",review:"review",demo:"demo",upgrade:"upgrade",context:"context",sync:"sync"},b={publish:"publish",changelog:"changelog",diff:"diff",monorepo:"monorepo",ci:"ci",ai:"ai",theme:"theme",stats:"stats",feedback:"feedback",telemetry:"telemetry",uninstall:"uninstall","generate-skills":"generate-skills","install-cursor-hooks":"install-cursor-hooks","install-vscode-copilot-hooks":"install-vscode-copilot-hooks",setup:"setup"},M={"Memory (top-level)":["log","ask","switch","recap","status"],"Watch (top-level)":["watch"],"Setup (top-level)":["init","doctor"],"AMP (use: infernoflow amp <verb>)":["status","migrate","validate","version"],"Contract (use: infernoflow contract <verb>)":Object.keys(v),"Dev (use: infernoflow dev <verb>)":Object.keys(b)};function R(){return Object.entries(M).map(([t,n])=>` ${m(t+":")}
6
- ${n.join(" ")}`).join(`
2
+ (function(){if(process.platform!=="win32"||process.env.WT_SESSION||process.env.ConEmuPID||process.env.TERM_PROGRAM==="vscode")return;const s={"\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(s).join("|"),"g");function c(l){const h=l.write.bind(l);l.write=function(a,...w){if(typeof a=="string")a=a.replace(r,p=>s[p]);else if(Buffer.isBuffer(a)){const p=a.toString("utf8").replace(r,$=>s[$]);a=Buffer.from(p,"utf8")}return h(a,...w)}}c(process.stdout),c(process.stderr)})();import{readFileSync as C}from"node:fs";import{dirname as k,join as f}from"node:path";import{fileURLToPath as v}from"node:url";import{bold as i,gray as e,cyan as t,red as u}from"../lib/ui/output.mjs";const A=k(v(import.meta.url));function I(o){for(const s of[f(o,"..","..","package.json"),f(o,"..","package.json")])try{return JSON.parse(C(s,"utf8"))}catch{}return{version:"0.0.0-source"}}const M=I(A),m=M.version||"0.0.0",y={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",init:"Scaffold .ai-memory/ and wire the current IDE in one command",setup:"Re-run wiring (idempotent) \u2014 detects IDE, installs MCP + hooks",doctor:"Diagnose your setup \u2014 Node, git, contract, AI provider, MCP, hooks",context:"Generate AI-ready context for new sessions","install-cursor-hooks":"Install Cursor hooks (afterAgentResponse + stop)","install-vscode-copilot-hooks":"Install VS Code + Copilot agent hooks (Preview)","generate-skills":"Generate Cursor rules + skill files from your developer profile",ai:"Manage AI providers \u2014 setup, status, test, clear",telemetry:"Opt-in anonymous telemetry (on | off | status)",uninstall:"Remove infernoflow from a project (--dry-run to preview)",check:"Validate contract, capabilities, scenarios, changelog",sync:"Cross-machine sync for personal memory \u2014 status/set/clear/migrate",amp:"AI Memory Protocol \u2014 status, migrate, validate (run: infernoflow amp)"},d={log:async o=>(await import("../lib/commands/log.mjs")).logCommand(o),ask:async o=>(await import("../lib/commands/ask.mjs")).askCommand(o),switch:async o=>(await import("../lib/commands/switch.mjs")).switchCommand(o),recap:async o=>(await import("../lib/commands/recap.mjs")).recapCommand(o),status:async o=>(await import("../lib/commands/status.mjs")).statusCommand(o),refresh:async o=>(await import("../lib/commands/refresh.mjs")).refreshCommand(o),init:async o=>(await import("../lib/commands/init.mjs")).initCommand(o),setup:async o=>(await import("../lib/commands/setup.mjs")).setupCommand(o),doctor:async o=>(await import("../lib/commands/doctor.mjs")).doctorCommand(o),context:async o=>(await import("../lib/commands/context.mjs")).contextCommand(o),"install-cursor-hooks":async o=>(await import("../lib/commands/installCursorHooks.mjs")).installCursorHooksCommand(o),"install-vscode-copilot-hooks":async o=>(await import("../lib/commands/installVsCodeCopilotHooks.mjs")).installVsCodeCopilotHooksCommand(o),"generate-skills":async o=>(await import("../lib/commands/generateSkills.mjs")).generateSkillsCommand(o),ai:async o=>(await import("../lib/commands/ai.mjs")).aiCommand(o),telemetry:async o=>(await import("../lib/telemetry.mjs")).telemetryCommand(o),uninstall:async o=>(await import("../lib/commands/uninstall.mjs")).uninstallCommand(o),check:async o=>(await import("../lib/commands/check.mjs")).checkCommand(o),sync:async o=>(await import("../lib/commands/sync.mjs")).syncCommand(o),amp:async o=>(await import("../lib/commands/amp.mjs")).ampCommand(o)};function U(){const o=Object.keys(y),s=Math.max(...o.map(r=>r.length),8)+1;return Object.entries(y).map(([r,c])=>` ${r.padEnd(s," ")}${c}`).join(`
3
+ `)}const O={"Memory (the 5-command core)":["log","ask","switch","recap","status","refresh"],Setup:["init","setup","doctor","context"],"IDE wiring":["install-cursor-hooks","install-vscode-copilot-hooks","generate-skills"],Configuration:["ai","telemetry","sync","uninstall"],Contract:["check"],"AMP (use: infernoflow amp <verb>)":["status","migrate","validate","version"]};function S(){return Object.entries(O).map(([o,s])=>` ${i(o+":")}
4
+ ${s.join(" ")}`).join(`
7
5
 
8
- `)}const C=Object.keys(l).length,j=`
9
- ${m("\u{1F525} infernoflow")} ${o("v"+p)}
10
- ${o("Persistent memory for AI coding sessions")}
6
+ `)}const g=Object.keys(d).length,R=`
7
+ ${i("\u{1F525} infernoflow")} ${e("v"+m)}
8
+ ${e("Persistent memory for AI coding sessions")}
11
9
 
12
- ${m("Usage:")}
10
+ ${i("Usage:")}
13
11
  infernoflow [command] [options]
14
12
 
15
- ${m("Memory")} ${o("\u2014 the 5-command core")}
16
- ${a("log")} ${o('"..."')} Add to session memory ${o("(--type gotcha|decision|attempt)")}
17
- ${a("ask")} ${o('"..."')} Search your memory by keyword ${o("(gotchas surface first)")}
18
- ${a("switch")} Generate handoff for next AI agent
19
- ${a("recap")} End-of-session health score + unlogged changes
20
- ${a("status")} Quick health check
13
+ ${i("Memory")} ${e("\u2014 the 5-command core")}
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
21
19
 
22
- ${m("Setup")}
23
- ${a("init")} 60-second setup ${o("(memory mode by default)")}
24
- ${a("watch")} Auto-capture mode ${o("(stuck-loops, dep changes, test removals)")}
25
- ${a("doctor")} Diagnose your setup
20
+ ${i("Setup")}
21
+ ${t("init")} 60-second setup ${e("(memory mode by default)")}
22
+ ${t("setup")} Re-run IDE wiring ${e("(idempotent \u2014 MCP + hooks)")}
23
+ ${t("doctor")} Diagnose your setup
24
+ ${t("context")} Generate AI-ready context for new sessions
26
25
 
27
- ${m("Subsystems")} ${o("\u2014 grouped, run for verbs:")}
28
- ${a("amp")} AI Memory Protocol ${o("(status, migrate, validate)")}
29
- ${a("contract")} Capability contracts ${o("(scan, freeze, impact, scaffold, \u2026)")}
30
- ${a("dev")} Publishing, AI providers, hooks ${o("(publish, ai, ci, \u2026)")}
26
+ ${i("Subsystems")} ${e("\u2014 grouped, run for verbs:")}
27
+ ${t("amp")} AI Memory Protocol ${e("(status, migrate, validate)")}
31
28
 
32
- ${o("Run")} ${a("infernoflow commands")} ${o("to see all "+C+" commands grouped.")}
33
- ${o("Run")} ${a("infernoflow <command> --help")} ${o("for command-specific options.")}
34
- `;import*as E from"node:fs";import*as P from"node:path";try{const e=P.join(process.cwd(),"inferno");if(E.existsSync(e)){const{observeCommandStart:t}=await import("../lib/learning/observe.mjs"),n=process.argv[2];n&&!n.startsWith("-")&&t(e,n)}}catch{}const[,,i,...D]=process.argv;(!i||i==="--help"||i==="-h")&&(console.log(j),process.exit(0)),(i==="--version"||i==="-v")&&(console.log(p),process.exit(0)),i==="commands"&&(console.log(`
35
- ${m("\u{1F525} infernoflow")} ${o("v"+p)} ${o("\u2014 all "+C+" commands")}
36
- `),console.log(R()),console.log(`
37
- ${o("Run")} ${a("infernoflow <command> --help")} ${o("for options.")}
38
- `),process.exit(0));const N=Object.keys(l);N.includes(i)||(console.error(h(`
39
- Unknown command: ${i}`)),console.error(o("Run: infernoflow commands (see all commands)")),console.error(o(`Run: infernoflow --help (quick start)
40
- `)),process.exit(1));const T=[i,...D];try{const{runUpgradeBackfillIfNeeded:e}=await import("../lib/upgradeCheck.mjs");await e(p,i)}catch{}l[i](T).catch(e=>{console.error(h(`
41
- Error: `)+e.message),process.exit(1)});
29
+ ${e("Run")} ${t("infernoflow commands")} ${e("to see all "+g+" commands grouped.")}
30
+ ${e("Run")} ${t("infernoflow <command> --help")} ${e("for command-specific options.")}
31
+ `;import*as b from"node:fs";import*as E from"node:path";try{const o=E.join(process.cwd(),"inferno");if(b.existsSync(o)){const{observeCommandStart:s}=await import("../lib/learning/observe.mjs"),r=process.argv[2];r&&!r.startsWith("-")&&s(o,r)}}catch{}const[,,n,...x]=process.argv;(!n||n==="--help"||n==="-h")&&(console.log(R),process.exit(0)),(n==="--version"||n==="-v")&&(console.log(m),process.exit(0)),n==="commands"&&(console.log(`
32
+ ${i("\u{1F525} infernoflow")} ${e("v"+m)} ${e("\u2014 all "+g+" commands")}
33
+ `),console.log(S()),console.log(`
34
+ ${e("Run")} ${t("infernoflow <command> --help")} ${e("for options.")}
35
+ `),process.exit(0));const P=Object.keys(d);P.includes(n)||(console.error(u(`
36
+ Unknown command: ${n}`)),console.error(e("Run: infernoflow commands (see all commands)")),console.error(e(`Run: infernoflow --help (quick start)
37
+ `)),process.exit(1));const j=[n,...x];try{const{runUpgradeBackfillIfNeeded:o}=await import("../lib/upgradeCheck.mjs");await o(m,n)}catch{}d[n](j).catch(o=>{console.error(u(`
38
+ Error: `)+o.message),process.exit(1)});
@@ -1,11 +1,11 @@
1
- import*as i from"node:fs";import*as c from"node:path";const p="1.0",A={start:"<!-- AMP:START -->",end:"<!-- AMP:END -->"},S=new Set(["gotcha","decision","attempt","note","detection","pattern"]),j=new Set(["copilot","cursor","claude","windsurf","other"]);function a(e,t={}){const o=c.join(e,".ai-memory"),n=c.join(e,"inferno"),r=t.forWrite||i.existsSync(o)||!i.existsSync(n),u=r?o:n,f=r;return{root:u,isAmp:f,sessions:c.join(u,"sessions.jsonl"),config:c.join(u,f?"amp.json":"config.json"),handoff:c.join(u,f?"handoff.md":"HANDOFF.md")}}function l(e){const t=c.join(e,".ai-memory");return i.existsSync(t)||i.mkdirSync(t,{recursive:!0}),t}const y="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function h(){let e=Date.now(),t="";for(let n=0;n<10;n++)t=y[e%32]+t,e=Math.floor(e/32);let o="";for(let n=0;n<16;n++)o+=y[Math.floor(Math.random()*32)];return t+o}function g(e){const t={...e.meta||{}};let o=e.type||"note";S.has(o)||(t.subtype=o,o="note"),e.result&&(t.result=e.result);let n;const r=e.agent;r&&j.has(r)?n=r:r&&(t.agent=r);const u=typeof e.ts=="number"?e.ts:e.ts?Date.parse(e.ts):Date.now(),f=e.confidence!=null?e.confidence:e.auto?.7:void 0,s={type:o,msg:e.summary||e.msg||"",ts:u,id:e.id||`amp_${h()}`};return e.file&&(s.file=e.file),e.line&&(s.line=e.line),e.function&&(s.function=e.function),e.tags&&e.tags.length&&(s.tags=e.tags),e.source&&(s.source=e.source),n&&(s.tool=n),e.session&&(s.session=e.session),f!=null&&(s.confidence=f),Object.keys(t).length&&(s.meta=t),s}function x(e){if(e.summary&&!e.msg)return e;const t=e.meta||{},o=t.subtype||e.type||"note",n={ts:e.ts,type:o,summary:e.msg||""};e.id&&(n.id=e.id),e.file&&(n.file=e.file),e.line&&(n.line=e.line),e.function&&(n.function=e.function),e.tags&&(n.tags=e.tags),e.source&&(n.source=e.source),e.tool&&(n.agent=e.tool),t.agent&&(n.agent=t.agent),t.result&&(n.result=t.result),e.confidence!=null&&(n.confidence=e.confidence,e.confidence<1&&(n.auto=!0));const{subtype:r,agent:u,result:f,...s}=t;return Object.keys(s).length&&(n.meta=s),n}function D(e){const{sessions:t}=a(e);return i.existsSync(t)?i.readFileSync(t,"utf8").split(`
2
- `).filter(Boolean).map(o=>{try{return JSON.parse(o)}catch{return null}}).filter(Boolean).map(x):[]}function M(e,t){l(e);const{sessions:o}=a(e,{forWrite:!0}),n=g(t);return i.appendFileSync(o,JSON.stringify(n)+`
3
- `,"utf8"),n}function O(e){const{config:t}=a(e);try{return JSON.parse(i.readFileSync(t,"utf8"))}catch{return null}}function N(e,t={}){l(e);const{config:o}=a(e,{forWrite:!0});if(i.existsSync(o))return!1;const n={amp:p,project:t.project||c.basename(e),stack:t.stack||{},config:{autoCapture:!0,maxEntries:1e3,rotationStrategy:"archive",inject:["all"],...t.config||{}}};return i.writeFileSync(o,JSON.stringify(n,null,2)+`
4
- `,"utf8"),!0}function E(e){const t=c.join(e,"inferno"),o=c.join(t,"sessions.jsonl");if(!i.existsSync(o))return{migrated:0,reason:"no legacy sessions.jsonl"};const n=c.join(e,".ai-memory"),r=c.join(n,"sessions.jsonl");if(i.existsSync(r))return{migrated:0,reason:".ai-memory/sessions.jsonl already exists"};l(e);const u=i.readFileSync(o,"utf8").split(`
5
- `).filter(Boolean);let f=0;for(const s of u)try{const d=JSON.parse(s),m=g(d);i.appendFileSync(r,JSON.stringify(m)+`
6
- `,"utf8"),f++}catch{}return i.writeFileSync(c.join(n,"MIGRATED.md"),`# Migrated from inferno/
1
+ import*as i from"node:fs";import*as F from"node:os";import*as r from"node:path";import{findProjectRoot as m}from"../projectRoot.mjs";import{getBranchInfo as b}from"../git/branch.mjs";const x="1.0",D=new Set(["gotcha","decision","attempt","note","detection","pattern"]),O=new Set(["copilot","cursor","claude","windsurf","other"]);function B(e){let t=r.basename(r.resolve(e));try{const o=r.join(e,".ai-memory","amp.json");if(i.existsSync(o)){const n=JSON.parse(i.readFileSync(o,"utf8"));n&&typeof n.project=="string"&&n.project.trim()&&(t=n.project.trim())}}catch{}return t.toLowerCase().replace(/[^a-z0-9_.\-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,64)||"unnamed-project"}function N(e,t){let o=process.env.INFERNOFLOW_GLOBAL_DIR;if(!o)try{const u=JSON.parse(i.readFileSync(r.join(t,"amp.json"),"utf8"));u&&typeof u.globalDir=="string"&&u.globalDir.trim()&&(o=u.globalDir.trim())}catch{}if(!o)return r.join(t,"global.jsonl");let n=o;n.startsWith("~")&&(n=r.join(F.homedir(),n.slice(1).replace(/^[\/\\]/,""))),r.isAbsolute(n)||(n=r.resolve(e,n));const f=B(e);return r.join(n,f,"global.jsonl")}function g(e,t={}){const o=t.literal?r.resolve(e):m(e),n=r.join(o,".ai-memory"),f=r.join(o,"inferno"),u=t.forWrite||i.existsSync(n)||!i.existsSync(f),c=u?n:f,s=u,l=r.join(c,"branches"),a=b(o),S=r.join(l,`${a.currentSlug}.jsonl`),j=a.defaultSlug?r.join(l,`${a.defaultSlug}.jsonl`):null,h=A(o,c);return{root:c,projectRoot:o,isAmp:s,sessions:r.join(c,"sessions.jsonl"),config:r.join(c,s?"amp.json":"config.json"),handoff:r.join(c,s?"handoff.md":"HANDOFF.md"),globalFile:h,branchesDir:l,currentBranchFile:S,defaultBranchFile:j,branch:a}}function A(e,t){try{return N(e,t)}catch{return r.join(t,"global.jsonl")}}function y(e){const t=m(e),o=r.join(t,".ai-memory");return i.existsSync(o)||i.mkdirSync(o,{recursive:!0}),o}const d="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function k(){let e=Date.now(),t="";for(let n=0;n<10;n++)t=d[e%32]+t,e=Math.floor(e/32);let o="";for(let n=0;n<16;n++)o+=d[Math.floor(Math.random()*32)];return t+o}function p(e){const t={...e.meta||{}};let o=e.type||"note";D.has(o)||(t.subtype=o,o="note"),e.result&&(t.result=e.result);let n;const f=e.agent;f&&O.has(f)?n=f:f&&(t.agent=f);const u=typeof e.ts=="number"?e.ts:e.ts?Date.parse(e.ts):Date.now(),c=e.confidence!=null?e.confidence:e.auto?.7:void 0,s={type:o,msg:e.summary||e.msg||"",ts:u,id:e.id||`amp_${k()}`};return e.file&&(s.file=e.file),e.line&&(s.line=e.line),e.function&&(s.function=e.function),e.tags&&e.tags.length&&(s.tags=e.tags),e.source&&(s.source=e.source),n&&(s.tool=n),e.session&&(s.session=e.session),c!=null&&(s.confidence=c),Object.keys(t).length&&(s.meta=t),s}function J(e){if(e.summary&&!e.msg)return e;const t=e.meta||{},o=t.subtype||e.type||"note",n={ts:e.ts,type:o,summary:e.msg||""};e.id&&(n.id=e.id),e.file&&(n.file=e.file),e.line&&(n.line=e.line),e.function&&(n.function=e.function),e.tags&&(n.tags=e.tags),e.source&&(n.source=e.source),e.tool&&(n.agent=e.tool),t.agent&&(n.agent=t.agent),t.result&&(n.result=t.result),e.confidence!=null&&(n.confidence=e.confidence,e.confidence<1&&(n.auto=!0));const{subtype:f,agent:u,result:c,...s}=t;return Object.keys(s).length&&(n.meta=s),n}function M(e){if(!e||!i.existsSync(e))return[];try{return i.readFileSync(e,"utf8").split(`
2
+ `).filter(Boolean).map(t=>{try{return JSON.parse(t)}catch{return null}}).filter(Boolean).map(J)}catch{return[]}}function P(e){const t=g(e),o=new Set,n=[],f=[t.sessions,t.globalFile,t.defaultBranchFile,t.currentBranchFile],u=[...new Set(f.filter(Boolean))];for(const c of u)for(const s of M(c)){const l=s.id||`${s.ts}|${s.summary}`;o.has(l)||(o.add(l),n.push(s))}return n.sort((c,s)=>{const l=typeof c.ts=="number"?c.ts:Date.parse(c.ts||0),a=typeof s.ts=="number"?s.ts:Date.parse(s.ts||0);return l-a})}function w(e,t){return t.target==="global"?e.globalFile:t.target==="legacy"?e.sessions:t.target==="branch"?e.currentBranchFile:t.type==="preference"?e.globalFile:e.currentBranchFile}function $(e,t){y(e);const o=g(e,{forWrite:!0}),n=w(o,t),f=p(t),u=JSON.stringify(f)+`
3
+ `;if(i.mkdirSync(r.dirname(n),{recursive:!0}),i.appendFileSync(n,u,"utf8"),n!==o.sessions)try{i.mkdirSync(r.dirname(o.sessions),{recursive:!0}),i.appendFileSync(o.sessions,u,"utf8")}catch{}return f}function C(e){const{config:t}=g(e);try{return JSON.parse(i.readFileSync(t,"utf8"))}catch{return null}}function L(e,t={}){y(e);const{config:o}=g(e,{forWrite:!0});if(i.existsSync(o))return!1;const n={amp:x,project:t.project||r.basename(e),stack:t.stack||{},config:{autoCapture:!0,maxEntries:1e3,rotationStrategy:"archive",inject:["all"],...t.config||{}}};return i.writeFileSync(o,JSON.stringify(n,null,2)+`
4
+ `,"utf8"),!0}function _(e){const t=r.join(e,"inferno"),o=r.join(t,"sessions.jsonl");if(!i.existsSync(o))return{migrated:0,reason:"no legacy sessions.jsonl"};const n=r.join(e,".ai-memory"),f=r.join(n,"sessions.jsonl");if(i.existsSync(f))return{migrated:0,reason:".ai-memory/sessions.jsonl already exists"};y(e);const u=i.readFileSync(o,"utf8").split(`
5
+ `).filter(Boolean);let c=0;for(const s of u)try{const l=JSON.parse(s),a=p(l);i.appendFileSync(f,JSON.stringify(a)+`
6
+ `,"utf8"),c++}catch{}return i.writeFileSync(r.join(n,"MIGRATED.md"),`# Migrated from inferno/
7
7
 
8
- Copied ${f} entries from inferno/sessions.jsonl on ${new Date().toISOString()}.
8
+ Copied ${c} entries from inferno/sessions.jsonl on ${new Date().toISOString()}.
9
9
 
10
10
  The original inferno/sessions.jsonl is untouched. You can delete it once you're confident the new layout works.
11
- `,"utf8"),{migrated:f,reason:"ok"}}export{A as AMP_MARKERS,p as AMP_VERSION,a as ampPaths,M as appendEntry,l as ensureAmpDir,x as fromAmp,h as generateULID,E as migrateLegacy,O as readConfig,D as readEntries,g as toAmp,N as writeDefaultConfig};
11
+ `,"utf8"),{migrated:c,reason:"ok"}}export{x as AMP_VERSION,g as ampPaths,$ as appendEntry,y as ensureAmpDir,J as fromAmp,k as generateULID,_ as migrateLegacy,B as projectSlug,C as readConfig,P as readEntries,N as resolveGlobalFile,p as toAmp,L as writeDefaultConfig};
@@ -0,0 +1,12 @@
1
+ import*as m from"node:fs";import*as p 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","# 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
+ `),I=["",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
+ `),T="# --- infernoflow (developer-local AI memory; do not commit) ---",y="# --- /infernoflow ---";function A(n){const r=n.indexOf(T),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=p.join(n,r);let e="";try{e=m.readFileSync(o,"utf8")}catch{}e=A(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+$/,""),u=e.slice(a+s.length).replace(/^\s+/,"");t=(f?f+`
5
+
6
+ `:"")+l+`
7
+ `+(u?`
8
+ `+u:"")}else e?t=e.replace(/\s+$/,"")+`
9
+
10
+ `+l+`
11
+ `:t=l+`
12
+ `;return t===e?"unchanged":(m.mkdirSync(p.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",I)}}export{s as GITIGNORE_END,d as GITIGNORE_START,E as applyCleanTreePolicy,h as ensureManagedBlock};
@@ -1,2 +1,2 @@
1
- import*as g from"node:fs";import*as k from"node:path";import*as x from"node:https";import*as C from"node:http";import*as R from"node:readline";import{bold as d,cyan as v,gray as i,green as r,yellow as y,red as h}from"../ui/output.mjs";function O(n){return k.join(n,"inferno")}function A(n){const o=k.join(O(n),"integrations.json");if(!g.existsSync(o))return{};try{return JSON.parse(g.readFileSync(o,"utf8"))}catch{return{}}}function E(n,o){const t=O(n);g.existsSync(t)||g.mkdirSync(t,{recursive:!0}),g.writeFileSync(k.join(t,"integrations.json"),JSON.stringify(o,null,2)+`
2
- `)}const m=[{id:"anthropic",name:"Anthropic (Claude)",envKey:"ANTHROPIC_API_KEY",models:["claude-sonnet-4-6","claude-opus-4-6","claude-haiku-4-5-20251001"],default:"claude-sonnet-4-6",keyHint:"sk-ant-api03-\u2026",docsUrl:"https://console.anthropic.com/settings/keys"},{id:"openai",name:"OpenAI (GPT)",envKey:"OPENAI_API_KEY",models:["gpt-4o","gpt-4o-mini","gpt-4-turbo"],default:"gpt-4o",keyHint:"sk-\u2026",docsUrl:"https://platform.openai.com/api-keys"},{id:"gemini",name:"Google Gemini",envKey:"GOOGLE_AI_API_KEY",models:["gemini-2.0-flash","gemini-1.5-pro","gemini-1.5-flash"],default:"gemini-2.0-flash",keyHint:"AIza\u2026",docsUrl:"https://aistudio.google.com/app/apikey"},{id:"openrouter",name:"OpenRouter",envKey:"OPENROUTER_API_KEY",models:["anthropic/claude-sonnet-4-6","openai/gpt-4o","meta-llama/llama-3.1-8b-instruct:free"],default:"anthropic/claude-sonnet-4-6",keyHint:"sk-or-\u2026",docsUrl:"https://openrouter.ai/keys"},{id:"ollama",name:"Ollama (local)",envKey:null,models:["llama3.2","mistral","codellama","phi3"],default:"llama3.2",keyHint:null,docsUrl:"https://ollama.com"}];function _(n){return new Promise(o=>{const t=new URL(n),e=(t.protocol==="https:"?x:C).request({hostname:t.hostname,port:t.port||(t.protocol==="https:"?443:80),path:t.pathname+(t.search||""),method:"GET",timeout:5e3},s=>{let a="";s.on("data",c=>a+=c),s.on("end",()=>{try{o({status:s.statusCode,body:JSON.parse(a)})}catch{o({status:s.statusCode,body:a})}})});e.on("error",()=>o(null)),e.on("timeout",()=>{e.destroy(),o(null)}),e.end()})}async function b(n,o){const l={anthropic:process.env.ANTHROPIC_API_KEY,openai:process.env.OPENAI_API_KEY,gemini:process.env.GOOGLE_AI_API_KEY||process.env.GEMINI_API_KEY,openrouter:process.env.OPENROUTER_API_KEY}[n],e=o[n]?.apiKey,s=l||e,a=l?"env":e?"integrations.json":null,c=o[n]?.model||m.find(u=>u.id===n)?.default;if(n==="ollama"){const u=await _("http://localhost:11434/api/tags").catch(()=>null);if(u?.status===200){const p=u.body?.models?.map(f=>f.name)||[];return{configured:!0,source:"local",model:o.ollama?.model||"llama3.2",available:!0,models:p}}return{configured:!1,source:null,model:null,available:!1}}return{configured:!!s,source:a,model:c,available:null,masked:s?s.slice(0,8)+"\u2026":null}}async function N(n,o,t){try{const{callAI:l}=await import("../ai/providerRouter.mjs"),e=`Reply with exactly: "infernoflow AI test OK \u2014 ${n}"`;return await l(e,t,n)}catch{return null}}function $(n,o){return new Promise(t=>n.question(o,t))}async function U(n){const o=A(n);console.log(),console.log(` ${d("infernoflow ai")} ${i("\u2014 provider status")}`),console.log();let t=!1;for(const l of m){const e=await b(l.id,o);e.configured&&(t=!0);const s=e.configured?r("\u2713"):i("\u25CB"),a=d(l.name.padEnd(22)),c=e.configured?`${r("configured")} ${i(e.source)} ${i("model: "+e.model)}${e.masked?" "+i(e.masked):""}`:i("not configured");console.log(` ${s} ${a} ${c}`)}console.log(),t?console.log(` ${i("Run")} ${v("infernoflow ai test")} ${i("to verify the active provider.")}`):(console.log(` ${y("No AI providers configured.")} Run: ${v("infernoflow ai setup")}`),console.log(` ${i("Without a provider, explain/why/review use structural fallbacks.")}`)),console.log()}async function G(n){const o=A(n),t={anthropic:process.env.ANTHROPIC_API_KEY,openai:process.env.OPENAI_API_KEY,gemini:process.env.GOOGLE_AI_API_KEY||process.env.GEMINI_API_KEY,openrouter:process.env.OPENROUTER_API_KEY};console.log(),console.log(` ${d("\u{1F525} infernoflow ai setup")}`),console.log(` ${i("Connect an AI provider for explain, why, review, and changelog.")}`),console.log(),m.forEach((e,s)=>{const a=t[e.id],c=o[e.id]?.apiKey,u=a?r(" \u2713 key detected in environment"):c?r(" \u2713 key already saved"):"",p=d(String(s+1)),f=e.id==="ollama"?i(" (local, no key needed)"):"";console.log(` ${p}) ${d(e.name.padEnd(22))}${f}${u}`)}),console.log();const l=R.createInterface({input:process.stdin,output:process.stdout});try{const e=await $(l," Select provider [1]: "),s=(parseInt(e.trim())||1)-1;if(s<0||s>=m.length){console.log(h(` Invalid choice. Enter a number 1\u2013${m.length}.`));return}const a=m[s],c=a.id;if(console.log(),console.log(` ${d(a.name)}`),c==="ollama"){const p=await $(l," Ollama host [http://localhost:11434]: "),f=await $(l,` Model [${a.default}]: `);o.ollama={host:p.trim()||"http://localhost:11434",model:f.trim()||a.default},E(n,o),console.log(),process.stdout.write(` ${r("\u2713")} Saved. Testing connection\u2026 `),(await _(`${o.ollama.host}/api/tags`).catch(()=>null))?.status===200?console.log(r("OK")):(console.log(y("not reachable")),console.log(` ${y("\u26A0")} Start Ollama first: ${v("ollama serve")}`))}else{const p=t[c],f=o[c]?.apiKey,I=p||f;if(I){const w=p?"environment variable":"saved config";if(console.log(` ${r("\u2713")} API key detected from ${w}: ${i(I.slice(0,12)+"\u2026")}`),(await $(l," Use this key? [Y/n]: ")).trim().toLowerCase()==="n"){console.log(),a.docsUrl&&console.log(` ${i("Get a key at:")} ${v(a.docsUrl)}`);const K=await $(l," Paste new API key: ");if(!K.trim()){console.log(h(" No key provided. Exiting."));return}o[c]={apiKey:K.trim(),model:o[c]?.model||a.default}}else o[c]={apiKey:I,model:o[c]?.model||a.default}}else{console.log(` ${i("Get your API key at:")} ${v(a.docsUrl)}`),console.log(` ${i("Tip: paste the key below \u2014 it starts with")} ${i(a.keyHint)}`),console.log();const w=await $(l," Paste API key: ");if(!w.trim()){console.log(h(" No key provided. Exiting."));return}o[c]={apiKey:w.trim(),model:a.default}}const P=o[c].model;console.log(),console.log(` ${i("Available models:")} ${a.models.join(" ")}`);const S=await $(l,` Model [${P}]: `);o[c].model=S.trim()||P,E(n,o),console.log(),process.stdout.write(` ${r("\u2713")} Saved. Testing connection\u2026 `),(await N(c,o,n))?.text?console.log(r("OK")+i(` (${o[c].model})`)):(console.log(y("no response")),console.log(` ${y("\u26A0")} Connection failed \u2014 double-check your API key.`))}console.log(),console.log(` ${r("\u2713")} ${d(a.name)} is ready.`),console.log(` ${i("AI-powered commands:")} explain why review changelog`),console.log();const u=k.join(n,".gitignore");g.existsSync(u)&&(g.readFileSync(u,"utf8").includes("integrations.json")||(console.log(` ${y("\u26A0")} Add ${v("inferno/integrations.json")} to your .gitignore to avoid committing your API key.`),console.log()))}finally{l.close()}}async function Y(n,o){const t=A(o),l=n.find(s=>!s.startsWith("--"))||null;console.log(),console.log(` ${d("infernoflow ai test")}`),console.log();const e=l?m.filter(s=>s.id===l):m;for(const s of e){if(!(await b(s.id,t)).configured){console.log(` ${i("\u25CB")} ${d(s.name.padEnd(22))} ${i("not configured \u2014 skipping")}`);continue}process.stdout.write(` ${y("\u2026")} ${d(s.name.padEnd(22))} testing\u2026 `);const c=await N(s.id,t,o);c?.text?(console.log(r("OK")+i(` (${c.model||s.id})`)),console.log(` ${i(c.text.trim().slice(0,80))}`)):(console.log(h("FAIL")),console.log(` ${h("No response \u2014 check API key or model name")}`))}console.log()}async function T(n,o){const t=A(o),l=n.find(e=>!e.startsWith("--"));if(l||(console.error(h("\u2717 Usage: infernoflow ai clear <provider>")),console.error(i(" Example: infernoflow ai clear openai")),process.exit(1)),!t[l]){console.log(i(` No config found for "${l}"`));return}delete t[l],E(o,t),console.log(r(` \u2713 Cleared config for ${l}`))}async function L(n){const o=(n||[]).slice(1),t=o.find(s=>!s.startsWith("--"))||"status",l=o.filter(s=>s!==t),e=process.cwd();switch(t){case"setup":return G(e);case"status":return U(e);case"test":return Y(l,e);case"clear":return T(l,e);default:console.error(h(`\u2717 Unknown subcommand: "${t}"`)),console.error(i(" Usage: infernoflow ai <setup|status|test|clear>")),process.exit(1)}}export{L as aiCommand};
1
+ import*as g from"node:fs";import*as A from"node:path";import*as x from"node:https";import*as C from"node:http";import*as R from"node:readline";import{bold as d,cyan as v,gray as i,green as r,yellow as y,red as h}from"../ui/output.mjs";function O(n){return A.join(n,"inferno")}function k(n){const o=A.join(O(n),"integrations.json");if(!g.existsSync(o))return{};try{return JSON.parse(g.readFileSync(o,"utf8"))}catch{return{}}}function E(n,o){const t=O(n);g.existsSync(t)||g.mkdirSync(t,{recursive:!0}),g.writeFileSync(A.join(t,"integrations.json"),JSON.stringify(o,null,2)+`
2
+ `)}const m=[{id:"anthropic",name:"Anthropic (Claude)",envKey:"ANTHROPIC_API_KEY",models:["claude-sonnet-4-6","claude-opus-4-6","claude-haiku-4-5-20251001"],default:"claude-sonnet-4-6",keyHint:"sk-ant-api03-\u2026",docsUrl:"https://console.anthropic.com/settings/keys"},{id:"openai",name:"OpenAI (GPT)",envKey:"OPENAI_API_KEY",models:["gpt-4o","gpt-4o-mini","gpt-4-turbo"],default:"gpt-4o",keyHint:"sk-\u2026",docsUrl:"https://platform.openai.com/api-keys"},{id:"gemini",name:"Google Gemini",envKey:"GOOGLE_AI_API_KEY",models:["gemini-2.0-flash","gemini-1.5-pro","gemini-1.5-flash"],default:"gemini-2.0-flash",keyHint:"AIza\u2026",docsUrl:"https://aistudio.google.com/app/apikey"},{id:"openrouter",name:"OpenRouter",envKey:"OPENROUTER_API_KEY",models:["anthropic/claude-sonnet-4-6","openai/gpt-4o","meta-llama/llama-3.1-8b-instruct:free"],default:"anthropic/claude-sonnet-4-6",keyHint:"sk-or-\u2026",docsUrl:"https://openrouter.ai/keys"},{id:"ollama",name:"Ollama (local)",envKey:null,models:["llama3.2","mistral","codellama","phi3"],default:"llama3.2",keyHint:null,docsUrl:"https://ollama.com"}];function _(n){return new Promise(o=>{const t=new URL(n),e=(t.protocol==="https:"?x:C).request({hostname:t.hostname,port:t.port||(t.protocol==="https:"?443:80),path:t.pathname+(t.search||""),method:"GET",timeout:5e3},s=>{let a="";s.on("data",c=>a+=c),s.on("end",()=>{try{o({status:s.statusCode,body:JSON.parse(a)})}catch{o({status:s.statusCode,body:a})}})});e.on("error",()=>o(null)),e.on("timeout",()=>{e.destroy(),o(null)}),e.end()})}async function b(n,o){const l={anthropic:process.env.ANTHROPIC_API_KEY,openai:process.env.OPENAI_API_KEY,gemini:process.env.GOOGLE_AI_API_KEY||process.env.GEMINI_API_KEY,openrouter:process.env.OPENROUTER_API_KEY}[n],e=o[n]?.apiKey,s=l||e,a=l?"env":e?"integrations.json":null,c=o[n]?.model||m.find(u=>u.id===n)?.default;if(n==="ollama"){const u=await _("http://localhost:11434/api/tags").catch(()=>null);if(u?.status===200){const p=u.body?.models?.map(f=>f.name)||[];return{configured:!0,source:"local",model:o.ollama?.model||"llama3.2",available:!0,models:p}}return{configured:!1,source:null,model:null,available:!1}}return{configured:!!s,source:a,model:c,available:null,masked:s?s.slice(0,8)+"\u2026":null}}async function N(n,o,t){try{const l=await import("../ai/providerRouter.mjs");if(typeof l.callAI!="function")return null;const e=`Reply with exactly: "infernoflow AI test OK \u2014 ${n}"`;return await l.callAI(e,t,n)}catch{return null}}function $(n,o){return new Promise(t=>n.question(o,t))}async function U(n){const o=k(n);console.log(),console.log(` ${d("infernoflow ai")} ${i("\u2014 provider status")}`),console.log();let t=!1;for(const l of m){const e=await b(l.id,o);e.configured&&(t=!0);const s=e.configured?r("\u2713"):i("\u25CB"),a=d(l.name.padEnd(22)),c=e.configured?`${r("configured")} ${i(e.source)} ${i("model: "+e.model)}${e.masked?" "+i(e.masked):""}`:i("not configured");console.log(` ${s} ${a} ${c}`)}console.log(),t?console.log(` ${i("Run")} ${v("infernoflow ai test")} ${i("to verify the active provider.")}`):(console.log(` ${y("No AI providers configured.")} Run: ${v("infernoflow ai setup")}`),console.log(` ${i("Without a provider, explain/why/review use structural fallbacks.")}`)),console.log()}async function G(n){const o=k(n),t={anthropic:process.env.ANTHROPIC_API_KEY,openai:process.env.OPENAI_API_KEY,gemini:process.env.GOOGLE_AI_API_KEY||process.env.GEMINI_API_KEY,openrouter:process.env.OPENROUTER_API_KEY};console.log(),console.log(` ${d("\u{1F525} infernoflow ai setup")}`),console.log(` ${i("Connect an AI provider for explain, why, review, and changelog.")}`),console.log(),m.forEach((e,s)=>{const a=t[e.id],c=o[e.id]?.apiKey,u=a?r(" \u2713 key detected in environment"):c?r(" \u2713 key already saved"):"",p=d(String(s+1)),f=e.id==="ollama"?i(" (local, no key needed)"):"";console.log(` ${p}) ${d(e.name.padEnd(22))}${f}${u}`)}),console.log();const l=R.createInterface({input:process.stdin,output:process.stdout});try{const e=await $(l," Select provider [1]: "),s=(parseInt(e.trim())||1)-1;if(s<0||s>=m.length){console.log(h(` Invalid choice. Enter a number 1\u2013${m.length}.`));return}const a=m[s],c=a.id;if(console.log(),console.log(` ${d(a.name)}`),c==="ollama"){const p=await $(l," Ollama host [http://localhost:11434]: "),f=await $(l,` Model [${a.default}]: `);o.ollama={host:p.trim()||"http://localhost:11434",model:f.trim()||a.default},E(n,o),console.log(),process.stdout.write(` ${r("\u2713")} Saved. Testing connection\u2026 `),(await _(`${o.ollama.host}/api/tags`).catch(()=>null))?.status===200?console.log(r("OK")):(console.log(y("not reachable")),console.log(` ${y("\u26A0")} Start Ollama first: ${v("ollama serve")}`))}else{const p=t[c],f=o[c]?.apiKey,I=p||f;if(I){const w=p?"environment variable":"saved config";if(console.log(` ${r("\u2713")} API key detected from ${w}: ${i(I.slice(0,12)+"\u2026")}`),(await $(l," Use this key? [Y/n]: ")).trim().toLowerCase()==="n"){console.log(),a.docsUrl&&console.log(` ${i("Get a key at:")} ${v(a.docsUrl)}`);const K=await $(l," Paste new API key: ");if(!K.trim()){console.log(h(" No key provided. Exiting."));return}o[c]={apiKey:K.trim(),model:o[c]?.model||a.default}}else o[c]={apiKey:I,model:o[c]?.model||a.default}}else{console.log(` ${i("Get your API key at:")} ${v(a.docsUrl)}`),console.log(` ${i("Tip: paste the key below \u2014 it starts with")} ${i(a.keyHint)}`),console.log();const w=await $(l," Paste API key: ");if(!w.trim()){console.log(h(" No key provided. Exiting."));return}o[c]={apiKey:w.trim(),model:a.default}}const P=o[c].model;console.log(),console.log(` ${i("Available models:")} ${a.models.join(" ")}`);const S=await $(l,` Model [${P}]: `);o[c].model=S.trim()||P,E(n,o),console.log(),process.stdout.write(` ${r("\u2713")} Saved. Testing connection\u2026 `),(await N(c,o,n))?.text?console.log(r("OK")+i(` (${o[c].model})`)):(console.log(y("no response")),console.log(` ${y("\u26A0")} Connection failed \u2014 double-check your API key.`))}console.log(),console.log(` ${r("\u2713")} ${d(a.name)} is ready.`),console.log(` ${i("AI-powered commands:")} explain why review changelog`),console.log();const u=A.join(n,".gitignore");g.existsSync(u)&&(g.readFileSync(u,"utf8").includes("integrations.json")||(console.log(` ${y("\u26A0")} Add ${v("inferno/integrations.json")} to your .gitignore to avoid committing your API key.`),console.log()))}finally{l.close()}}async function Y(n,o){const t=k(o),l=n.find(s=>!s.startsWith("--"))||null;console.log(),console.log(` ${d("infernoflow ai test")}`),console.log();const e=l?m.filter(s=>s.id===l):m;for(const s of e){if(!(await b(s.id,t)).configured){console.log(` ${i("\u25CB")} ${d(s.name.padEnd(22))} ${i("not configured \u2014 skipping")}`);continue}process.stdout.write(` ${y("\u2026")} ${d(s.name.padEnd(22))} testing\u2026 `);const c=await N(s.id,t,o);c?.text?(console.log(r("OK")+i(` (${c.model||s.id})`)),console.log(` ${i(c.text.trim().slice(0,80))}`)):(console.log(h("FAIL")),console.log(` ${h("No response \u2014 check API key or model name")}`))}console.log()}async function T(n,o){const t=k(o),l=n.find(e=>!e.startsWith("--"));if(l||(console.error(h("\u2717 Usage: infernoflow ai clear <provider>")),console.error(i(" Example: infernoflow ai clear openai")),process.exit(1)),!t[l]){console.log(i(` No config found for "${l}"`));return}delete t[l],E(o,t),console.log(r(` \u2713 Cleared config for ${l}`))}async function L(n){const o=(n||[]).slice(1),t=o.find(s=>!s.startsWith("--"))||"status",l=o.filter(s=>s!==t),e=process.cwd();switch(t){case"setup":return G(e);case"status":return U(e);case"test":return Y(l,e);case"clear":return T(l,e);default:console.error(h(`\u2717 Unknown subcommand: "${t}"`)),console.error(i(" Usage: infernoflow ai <setup|status|test|clear>")),process.exit(1)}}export{L as aiCommand};
@@ -1,4 +1,4 @@
1
- import*as m from"node:fs";import*as u from"node:path";import{bold as l,cyan as d,gray as s,green as h,yellow as S,red as v}from"../ui/output.mjs";import{AMP_VERSION as y,ampPaths as M,readEntries as A,migrateLegacy as b}from"../amp/io.mjs";const j=new Set(["gotcha","decision","attempt","note","detection","pattern"]),x=new Set(["copilot","cursor","claude","windsurf","other"]);function k(o,n){const e=[];return(!o||typeof o!="object")&&e.push("not an object"),o.type?j.has(o.type)||e.push(`type "${o.type}" not in AMP enum`):e.push("missing required field: type"),o.msg?typeof o.msg!="string"?e.push("msg must be a string"):o.msg.length>500&&e.push(`msg too long (${o.msg.length} > 500)`):e.push("missing required field: msg"),o.ts==null?e.push("missing required field: ts"):(typeof o.ts!="number"||!Number.isFinite(o.ts))&&e.push("ts must be a Unix-ms integer"),o.id&&!/^amp_[0-9A-Z]{26}$/.test(o.id)&&e.push(`id "${o.id}" not in amp_ULID format`),o.tool&&!x.has(o.tool)&&e.push(`tool "${o.tool}" not in AMP enum`),o.confidence!=null&&(o.confidence<0||o.confidence>1)&&e.push("confidence out of range [0,1]"),e}function E(o){const{sessions:n}=M(o);if(!m.existsSync(n))return{sessions:n,entries:[],lines:[]};const e=m.readFileSync(n,"utf8").split(`
2
- `).filter(Boolean),a=e.map((c,r)=>{try{return{ok:!0,line:r+1,value:JSON.parse(c)}}catch(t){return{ok:!1,line:r+1,value:null,error:t.message}}});return{sessions:n,entries:a,lines:e}}function P(o){const{sessions:n,isAmp:e,root:a}=M(o),c=u.join(o,".ai-memory"),r=u.join(o,"inferno"),t=m.existsSync(c),g=m.existsSync(r)&&m.existsSync(u.join(r,"sessions.jsonl"));if(console.log(),console.log(` ${l("\u{1F525} infernoflow amp")} ${s("\u2014 AI Memory Protocol status")}`),console.log(),console.log(` Spec version ${l(y)}`),console.log(` Conformance level ${l("AMP Full")} ${s("(read + write + handoff + injection)")}`),console.log(),!t&&!g){console.log(` ${S("\u26A0")} ${s("Project not initialised \u2014 run:")} ${d("infernoflow init")}`),console.log();return}if(console.log(` ${l("Layout:")}`),console.log(` .ai-memory/ ${t?h("\u2714 present")+(e?s(" (active)"):""):s("\u2014")}`),console.log(` inferno/ ${g?S("\u26A0 legacy")+(e?"":s(" (active)")):s("\u2014")}`),console.log(),m.existsSync(n)){const f=A(o);if(console.log(` ${l("Memory:")}`),console.log(` file ${s(u.relative(o,n))}`),console.log(` entries ${l(String(f.length))}`),f.length){const i=new Map;for(const $ of f)i.set($.type,(i.get($.type)||0)+1);const p=[...i.entries()].map(([$,w])=>`${$}:${w}`).join(" ");console.log(` breakdown ${s(p)}`)}}g&&!t&&(console.log(),console.log(` ${s("Tip:")} ${d("infernoflow amp migrate")} ${s("copies legacy memory into the AMP layout.")}`)),console.log()}function N(o){console.log(),console.log(` ${l("\u{1F525} infernoflow amp migrate")}`),console.log();const n=b(o);n.migrated>0?(console.log(` ${h("\u2714")} Migrated ${l(String(n.migrated))} entr${n.migrated===1?"y":"ies"} \u2192 ${d(".ai-memory/sessions.jsonl")}`),console.log(` ${s("The original inferno/sessions.jsonl is untouched. See .ai-memory/MIGRATED.md for details.")}`)):console.log(` ${s("Nothing migrated \u2014 ")}${n.reason}${s(".")}`),console.log()}function O(o){console.log(),console.log(` ${l("\u{1F525} infernoflow amp validate")}`),console.log();const{sessions:n,entries:e}=E(o);if(!e.length){console.log(` ${s("No entries to validate at")} ${d(u.relative(o,n))}`),console.log();return}let a=0,c=0,r=0;const t=[];for(const i of e){if(!i.ok){c++,t.push({line:i.line,error:`parse error: ${i.error}`});continue}const p=k(i.value,i.line);p.length?(r++,t.push({line:i.line,error:p.join("; ")})):a++}for(const i of t.slice(0,10))console.log(` ${v("\u2718")} line ${i.line} ${s(i.error)}`);t.length>10&&console.log(` ${s(`\u2026 ${t.length-10} more`)}`),t.length&&console.log();const g=e.length,f=c===0&&r===0;console.log(f?` ${h("\u2714")} ${l(`${a}/${g}`)} entries conform to AMP v${y}.`:` ${v("\u2718")} ${l(`${a}/${g}`)} entries OK \xB7 ${c} parse \xB7 ${r} schema`),console.log(),f||process.exit(1)}function T(){console.log(y)}async function I(o){const n=o[1],e=process.cwd();if(!n||n==="status")return P(e);if(n==="migrate")return N(e);if(n==="validate")return O(e);if(n==="version")return T();if(n==="--help"||n==="-h")return P(e);console.error(v(`
3
- Unknown amp verb: ${n}`)),console.error(s(` Try: infernoflow amp (status | migrate | validate | version)
4
- `)),process.exit(1)}export{I as ampCommand};
1
+ import*as m from"node:fs";import*as p from"node:path";import{bold as r,cyan as d,gray as s,green as h,yellow as S,red as y}from"../ui/output.mjs";import{AMP_VERSION as v,ampPaths as b,readEntries as P,migrateLegacy as w}from"../amp/io.mjs";const A=new Set(["gotcha","decision","attempt","note","detection","pattern"]),x=new Set(["copilot","cursor","claude","windsurf","other"]);function E(o,e){const n=[];return(!o||typeof o!="object")&&n.push("not an object"),o.type?A.has(o.type)||n.push(`type "${o.type}" not in AMP enum`):n.push("missing required field: type"),o.msg?typeof o.msg!="string"?n.push("msg must be a string"):o.msg.length>500&&n.push(`msg too long (${o.msg.length} > 500)`):n.push("missing required field: msg"),o.ts==null?n.push("missing required field: ts"):(typeof o.ts!="number"||!Number.isFinite(o.ts))&&n.push("ts must be a Unix-ms integer"),o.id&&!/^amp_[0-9A-Z]{26}$/.test(o.id)&&n.push(`id "${o.id}" not in amp_ULID format`),o.tool&&!x.has(o.tool)&&n.push(`tool "${o.tool}" not in AMP enum`),o.confidence!=null&&(o.confidence<0||o.confidence>1)&&n.push("confidence out of range [0,1]"),n}function k(o){const e=b(o),n=[e.sessions,e.globalFile,e.currentBranchFile,e.defaultBranchFile],c=[...new Set(n.filter(Boolean))];try{if(m.existsSync(e.branchesDir))for(const t of m.readdirSync(e.branchesDir)){if(!t.endsWith(".jsonl"))continue;const l=p.join(e.branchesDir,t);c.includes(l)||c.push(l)}}catch{}const a=[];for(const t of c){if(!m.existsSync(t))continue;let l;try{l=m.readFileSync(t,"utf8").split(`
2
+ `).filter(Boolean)}catch{continue}l.forEach((g,f)=>{try{a.push({ok:!0,file:t,line:f+1,value:JSON.parse(g)})}catch(i){a.push({ok:!1,file:t,line:f+1,value:null,error:i.message})}})}return{sessions:e.sessions,entries:a,lines:[],files:c}}function j(o){const{sessions:e,isAmp:n,root:c}=b(o),a=p.join(o,".ai-memory"),t=p.join(o,"inferno"),l=m.existsSync(a),g=m.existsSync(t)&&m.existsSync(p.join(t,"sessions.jsonl"));if(console.log(),console.log(` ${r("\u{1F525} infernoflow amp")} ${s("\u2014 AI Memory Protocol status")}`),console.log(),console.log(` Spec version ${r(v)}`),console.log(` Conformance level ${r("AMP Full")} ${s("(read + write + handoff + injection)")}`),console.log(),!l&&!g){console.log(` ${S("\u26A0")} ${s("Project not initialised \u2014 run:")} ${d("infernoflow init")}`),console.log();return}if(console.log(` ${r("Layout:")}`),console.log(` .ai-memory/ ${l?h("\u2714 present")+(n?s(" (active)"):""):s("\u2014")}`),console.log(` inferno/ ${g?S("\u26A0 legacy")+(n?"":s(" (active)")):s("\u2014")}`),console.log(),m.existsSync(e)){const f=P(o);if(console.log(` ${r("Memory:")}`),console.log(` file ${s(p.relative(o,e))}`),console.log(` entries ${r(String(f.length))}`),f.length){const i=new Map;for(const u of f)i.set(u.type,(i.get(u.type)||0)+1);const $=[...i.entries()].map(([u,M])=>`${u}:${M}`).join(" ");console.log(` breakdown ${s($)}`)}}g&&!l&&(console.log(),console.log(` ${s("Tip:")} ${d("infernoflow amp migrate")} ${s("copies legacy memory into the AMP layout.")}`)),console.log()}function D(o){console.log(),console.log(` ${r("\u{1F525} infernoflow amp migrate")}`),console.log();const e=w(o);e.migrated>0?(console.log(` ${h("\u2714")} Migrated ${r(String(e.migrated))} entr${e.migrated===1?"y":"ies"} \u2192 ${d(".ai-memory/sessions.jsonl")}`),console.log(` ${s("The original inferno/sessions.jsonl is untouched. See .ai-memory/MIGRATED.md for details.")}`)):console.log(` ${s("Nothing migrated \u2014 ")}${e.reason}${s(".")}`),console.log()}function F(o){console.log(),console.log(` ${r("\u{1F525} infernoflow amp validate")}`),console.log();const{sessions:e,entries:n}=k(o);if(!n.length){console.log(` ${s("No entries to validate at")} ${d(p.relative(o,e))}`),console.log();return}let c=0,a=0,t=0;const l=[];for(const i of n){const $=p.relative(o,i.file);if(!i.ok){a++,l.push({file:$,line:i.line,error:`parse error: ${i.error}`});continue}const u=E(i.value,i.line);u.length?(t++,l.push({file:$,line:i.line,error:u.join("; ")})):c++}for(const i of l.slice(0,10))console.log(` ${y("\u2718")} ${i.file}:${i.line} ${s(i.error)}`);l.length>10&&console.log(` ${s(`\u2026 ${l.length-10} more`)}`),l.length&&console.log();const g=n.length,f=a===0&&t===0;console.log(f?` ${h("\u2714")} ${r(`${c}/${g}`)} entries conform to AMP v${v}.`:` ${y("\u2718")} ${r(`${c}/${g}`)} entries OK \xB7 ${a} parse \xB7 ${t} schema`),console.log(),f||process.exit(1)}function N(){console.log(v)}async function L(o){const e=o[1],n=process.cwd();if(!e||e==="status")return j(n);if(e==="migrate")return D(n);if(e==="validate")return F(n);if(e==="version")return N();if(e==="--help"||e==="-h")return j(n);console.error(y(`
3
+ Unknown amp verb: ${e}`)),console.error(s(` Try: infernoflow amp (status | migrate | validate | version)
4
+ `)),process.exit(1)}export{L as ampCommand};
@@ -1,3 +1,3 @@
1
- import"node:fs";import*as j from"node:path";import{bold as O,cyan as S,gray as s,green as k,yellow as C,red as L}from"../ui/output.mjs";import{readEntries as W}from"../amp/io.mjs";const q="inferno",b=j.join(q,"sessions.jsonl"),d={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:C,decision:k,attempt:S,preference:S,theme:S,note:s,error:L,handoff:s};function x(t){return t.toLowerCase().replace(/[^a-z0-9\s]/g," ").split(/\s+/).filter(o=>o.length>1)}function M(t,o){const i=[t.summary||"",t.type||""].join(" ").toLowerCase(),n=x(i);let l=0;for(const r of o)(t.summary||"").toLowerCase().includes(r)&&(l+=3),n.includes(r)&&(l+=1),n.some(e=>e.startsWith(r)||r.startsWith(e))&&(l+=.5);return l}function _(t){if(!t)return"";const o=new Date(t),i=Date.now()-o.getTime(),n=Math.floor(i/864e5);return n===0?"today":n===1?"yesterday":n<7?`${n}d ago`:n<30?`${Math.floor(n/7)}w ago`:o.toLocaleDateString("en-GB",{day:"2-digit",month:"short"})}function F(t,o){const i=t.type||"note",n=I[i]||"\xB7",l=N[i]||s,r=t.result?s(` [${t.result}]`):"",e=t.agent?s(` \u2014 ${t.agent}`):"",f=s(` (${_(t.ts)})`);let a=t.summary||"";if(o)for(const u of o){const p=new RegExp(`(${u})`,"gi");a=a.replace(p,$=>O($))}console.log(` ${l(n+" "+i.padEnd(11))}${r}${e}${f}`),console.log(` ${a}`)}function P(t){return W(t)}function Y(t,o,i,n){let l=t;i&&(l=l.filter(e=>(e.type||"note")===i));let r;return o.length>0?r=l.map(e=>({entry:e,score:M(e,o)})).filter(({score:e})=>e>0).sort((e,f)=>{if(f.score!==e.score)return f.score-e.score;const a=d[e.entry.type]??9,u=d[f.entry.type]??9;return a!==u?a-u:new Date(f.entry.ts||0)-new Date(e.entry.ts||0)}):r=l.map(e=>({entry:e,score:1})).sort((e,f)=>{const a=d[e.entry.type]??9,u=d[f.entry.type]??9;return a!==u?a-u:new Date(f.entry.ts||0)-new Date(e.entry.ts||0)}),r.slice(0,n||20)}async function z(t=[]){const o=t,i=o.indexOf("--type"),n=i!==-1?o[i+1]:null,l=o.indexOf("--limit")!==-1?o.indexOf("--limit"):o.indexOf("-n"),r=l!==-1?parseInt(o[l+1]||"20",10):15,e=o.includes("--json"),f=o.includes("--recent")||o.includes("-r"),a=o.slice(1),p=a.filter((c,g)=>!(c.startsWith("--")||g>0&&a[g-1].startsWith("--"))).join(" ").trim(),$=f?[]:x(p),D=process.cwd(),y=P(D);if(y.length===0){if(e){console.log(JSON.stringify({results:[],total:0}));return}console.log(s(`
1
+ import"node:fs";import"node:path";import{bold as O,cyan as T,gray as s,green as R,yellow as I,red as W}from"../ui/output.mjs";import{readEntries as j}from"../amp/io.mjs";const d={gotcha:0,decision:1,attempt:2,preference:3,theme:4,note:5,error:5,handoff:6},S={gotcha:"\u26A0",decision:"\u2713",attempt:"\u21BA",preference:"\u2666",theme:"\u{1F3A8}",note:"\xB7",error:"\u2717",handoff:"\u2192"},D={gotcha:I,decision:R,attempt:T,preference:T,theme:T,note:s,error:W,handoff:s};function E(o){return o.toLowerCase().replace(/[^a-z0-9\s]/g," ").split(/\s+/).filter(t=>t.length>1)}function q(o,t){const i=[o.summary||"",o.type||""].join(" ").toLowerCase(),n=E(i);let l=0;for(const r of t)(o.summary||"").toLowerCase().includes(r)&&(l+=3),n.includes(r)&&(l+=1),n.some(e=>e.startsWith(r)||r.startsWith(e))&&(l+=.5);return l}function L(o){if(!o)return"";const t=new Date(o),i=Date.now()-t.getTime(),n=Math.floor(i/864e5);return n===0?"today":n===1?"yesterday":n<7?`${n}d ago`:n<30?`${Math.floor(n/7)}w ago`:t.toLocaleDateString("en-GB",{day:"2-digit",month:"short"})}function M(o,t){const i=o.type||"note",n=S[i]||"\xB7",l=D[i]||s,r=o.result?s(` [${o.result}]`):"",e=o.agent?s(` \u2014 ${o.agent}`):"",f=s(` (${L(o.ts)})`);let a=o.summary||"";if(t)for(const u of t){const p=new RegExp(`(${u})`,"gi");a=a.replace(p,$=>O($))}console.log(` ${l(n+" "+i.padEnd(11))}${r}${e}${f}`),console.log(` ${a}`)}function P(o){return j(o)}function Y(o,t,i,n){let l=o;i&&(l=l.filter(e=>(e.type||"note")===i));let r;return t.length>0?r=l.map(e=>({entry:e,score:q(e,t)})).filter(({score:e})=>e>0).sort((e,f)=>{if(f.score!==e.score)return f.score-e.score;const a=d[e.entry.type]??9,u=d[f.entry.type]??9;return a!==u?a-u:new Date(f.entry.ts||0).getTime()-new Date(e.entry.ts||0).getTime()}):r=l.map(e=>({entry:e,score:1})).sort((e,f)=>{const a=d[e.entry.type]??9,u=d[f.entry.type]??9;return a!==u?a-u:new Date(f.entry.ts||0).getTime()-new Date(e.entry.ts||0).getTime()}),r.slice(0,n||20)}async function U(o=[]){const t=o,i=t.indexOf("--type"),n=i!==-1?t[i+1]:null,l=t.indexOf("--limit")!==-1?t.indexOf("--limit"):t.indexOf("-n"),r=l!==-1?parseInt(t[l+1]||"20",10):15,e=t.includes("--json"),f=t.includes("--recent")||t.includes("-r"),a=t.slice(1),p=a.filter((c,g)=>!(c.startsWith("--")||g>0&&a[g-1].startsWith("--"))).join(" ").trim(),$=f?[]:E(p),k=process.cwd(),m=P(k);if(m.length===0){if(e){console.log(JSON.stringify({results:[],total:0}));return}console.log(s(`
2
2
  No session memory yet.`)),console.log(s(` Run: infernoflow log "<what happened>" --type gotcha
3
- `));return}const m=f?y.slice(-r).reverse().map(c=>({entry:c,score:1})):Y(y,$,n,r);if(e){console.log(JSON.stringify({query:p,type:n,total:y.length,matched:m.length,results:m.map(({entry:c,score:g})=>({...c,relevanceScore:g}))},null,2));return}if(console.log(),console.log(p?` ${O("\u{1F525} infernoflow ask")} ${S(`"${p}"`)}${n?s(` [${n}]`):""}`:f?` ${O("\u{1F525} infernoflow ask")} ${s("\u2014 recent entries")}`:` ${O("\u{1F525} infernoflow ask")} ${s("\u2014 all entries")}${n?s(` [${n}]`):""}`),console.log(s(` ${"\u2500".repeat(52)}`)),m.length===0){console.log(),p?(console.log(s(` No entries found for "${p}"`)),n&&console.log(s(` Try removing --type ${n} to widen the search`))):console.log(s(" No entries found.")),console.log();return}const w=new Map;for(const{entry:c,score:g}of m){const h=c.type||"note";w.has(h)||w.set(h,[]),w.get(h).push({entry:c,score:g})}const R=Object.keys(d).sort((c,g)=>d[c]-d[g]);let E=0;for(const c of R){const g=w.get(c);if(!g?.length)continue;console.log();const h=N[c]||s;console.log(h(` ${I[c]} ${c.toUpperCase()}S (${g.length})`)),console.log(s(" "+"\u2500".repeat(50)));for(const{entry:T}of g)console.log(),F(T,$),E++}console.log(),console.log(s(` ${E} result${E!==1?"s":""} from ${y.length} total entries`)),m.length===r&&y.length>r&&console.log(s(" Use --limit N to see more")),console.log()}export{z as askCommand};
3
+ `));return}const y=f?m.slice(-r).reverse().map(c=>({entry:c,score:1})):Y(m,$,n,r);if(e){console.log(JSON.stringify({query:p,type:n,total:m.length,matched:y.length,results:y.map(({entry:c,score:g})=>({...c,relevanceScore:g}))},null,2));return}if(console.log(),console.log(p?` ${O("\u{1F525} infernoflow ask")} ${T(`"${p}"`)}${n?s(` [${n}]`):""}`:f?` ${O("\u{1F525} infernoflow ask")} ${s("\u2014 recent entries")}`:` ${O("\u{1F525} infernoflow ask")} ${s("\u2014 all entries")}${n?s(` [${n}]`):""}`),console.log(s(` ${"\u2500".repeat(52)}`)),y.length===0){console.log(),p?(console.log(s(` No entries found for "${p}"`)),n&&console.log(s(` Try removing --type ${n} to widen the search`))):console.log(s(" No entries found.")),console.log();return}const w=new Map;for(const{entry:c,score:g}of y){const h=c.type||"note";w.has(h)||w.set(h,[]),w.get(h).push({entry:c,score:g})}const C=Object.keys(d).sort((c,g)=>d[c]-d[g]);let x=0;for(const c of C){const g=w.get(c);if(!g?.length)continue;console.log();const h=D[c]||s;console.log(h(` ${S[c]} ${c.toUpperCase()}S (${g.length})`)),console.log(s(" "+"\u2500".repeat(50)));for(const{entry:N}of g)console.log(),M(N,$),x++}console.log(),console.log(s(` ${x} result${x!==1?"s":""} from ${m.length} total entries`)),y.length===r&&m.length>r&&console.log(s(" Use --limit N to see more")),console.log()}export{U as askCommand};
@@ -1,31 +1,31 @@
1
- import g from"node:fs";import b from"node:path";import{execSync as h}from"node:child_process";import{bold as I,gray as d,cyan as l,red as q,green as a,yellow as F}from"../ui/output.mjs";import{buildCursorImplementPrompt as wt,buildGenericImplementPrompt as yt}from"../ui/prompts.mjs";import{detectDrift as kt}from"../git/detect-drift.mjs";function Ct(e){try{const n=process.platform;if(n==="win32")h("clip",{input:e});else if(n==="darwin")h("pbcopy",{input:e});else try{h("xclip -selection clipboard",{input:e})}catch{h("xsel --clipboard --input",{input:e})}return!0}catch{return!1}}const w="inferno",P=b.join(w,"CONTEXT.md"),z=b.join(w,"context-state.json");function Q(e){try{return JSON.parse(g.readFileSync(e,"utf8"))}catch{return null}}function W(e){try{return g.readFileSync(e,"utf8")}catch{return null}}function Y(){const e=W(z);if(!e)return{};try{return JSON.parse(e)}catch{return{}}}function Z(e){g.writeFileSync(z,JSON.stringify(e,null,2),"utf8")}function E(e){return e?new Date(e).toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"}):"unknown"}function St(e,n){if(!e)return[];const i=[];let c=null;for(const r of e.split(`
2
- `))if(r.startsWith("## ")){if(c&&i.length<n&&i.push(c),i.length>=n)break;c={title:r.replace("## ","").trim(),items:[]}}else c&&r.startsWith("- ")&&c.items.push(r.replace("- ","").trim());return c&&i.length<n&&i.push(c),i.filter(r=>r.items.length>0)}async function bt(e){const n=o=>e.includes(o),i=o=>{const u=e.indexOf(o);return u!==-1&&e[u+1]?e[u+1]:null},c=i("--intent")||i("-i"),r=i("--working")||i("-w"),$=i("--decision")||i("-d"),tt=n("--show")||n("-s"),G=n("--copy")||n("-c"),et=n("--cursor"),nt=n("--copilot"),ot=n("--reset"),it=n("--watch"),N=n("--auto-commit")||n("--auto-push"),v=n("--auto-push"),R=parseInt(i("--interval")||"30",10)*1e3;console.log(`
1
+ import p from"node:fs";import b from"node:path";import{execSync as h}from"node:child_process";import{bold as I,gray as d,cyan as l,red as H,green as a,yellow as F}from"../ui/output.mjs";import{buildCursorImplementPrompt as wt,buildGenericImplementPrompt as yt}from"../ui/prompts.mjs";import{detectDrift as Ct}from"../git/detect-drift.mjs";function kt(e){try{const n=process.platform;if(n==="win32")h("clip",{input:e});else if(n==="darwin")h("pbcopy",{input:e});else try{h("xclip -selection clipboard",{input:e})}catch{h("xsel --clipboard --input",{input:e})}return!0}catch{return!1}}const w="inferno",O=b.join(w,"CONTEXT.md"),K=b.join(w,"context-state.json");function M(e){try{return JSON.parse(p.readFileSync(e,"utf8"))}catch{return null}}function W(e){try{return p.readFileSync(e,"utf8")}catch{return null}}function q(){const e=W(K);if(!e)return{};try{return JSON.parse(e)}catch{return{}}}function z(e){p.writeFileSync(K,JSON.stringify(e,null,2),"utf8")}function E(e){return e?new Date(e).toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"}):"unknown"}function St(e,n){if(!e)return[];const i=[];let c=null;for(const r of e.split(`
2
+ `))if(r.startsWith("## ")){if(c&&i.length<n&&i.push(c),i.length>=n)break;c={title:r.replace("## ","").trim(),items:[]}}else c&&r.startsWith("- ")&&c.items.push(r.replace("- ","").trim());return c&&i.length<n&&i.push(c),i.filter(r=>r.items.length>0)}async function bt(e){const n=o=>e.includes(o),i=o=>{const u=e.indexOf(o);return u!==-1&&e[u+1]?e[u+1]:null},c=i("--intent")||i("-i"),r=i("--working")||i("-w"),x=i("--decision")||i("-d"),Q=n("--show")||n("-s"),G=n("--copy")||n("-c"),Y=n("--cursor"),Z=n("--copilot"),tt=n("--reset"),et=n("--watch"),P=n("--auto-commit")||n("--auto-push"),N=n("--auto-push"),R=parseInt(i("--interval")||"30",10)*1e3;console.log(`
3
3
  `+I("\uFFFD\uFFFD\uFFFD infernoflow \u2014 context")),console.log(" "+"\u2500".repeat(50)+`
4
- `),g.existsSync(w)||(console.error(q(" \u2718 inferno/ not found")),console.error(d(` \u2192 Run: infernoflow init
5
- `)),process.exit(1));const f=Q(b.join(w,"contract.json")),D=Q(b.join(w,"capabilities.json")),st=W(b.join(w,"CHANGELOG.md"));(!f||!D)&&(console.error(q(` \u2718 Missing contract.json or capabilities.json
6
- `)),process.exit(1));let t=Y();ot&&(t={},console.log(F(` \u26A0 State reset
7
- `))),c&&(t.intent=c,t.intentUpdated=new Date().toISOString(),console.log(a(' \u2714 Intent saved: "'+c+'"'))),r&&(t.working=r,t.workingUpdated=new Date().toISOString(),console.log(a(' \u2714 Working on: "'+r+'"'))),$&&(t.decisions||(t.decisions=[]),t.decisions.push({text:$,date:new Date().toISOString()}),console.log(a(' \u2714 Decision recorded: "'+$+'"'))),(c||r||$)&&Z(t);const j=D.capabilities||[],A=j.length===(f.capabilities||[]).length,U=St(st,3),T=String(f.policyVersion).replace(/^v/i,""),ct=new Date().toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"}),B=A?"\u2713 validated":"\u26A0 out of sync",L=t.intent||"describe the exact task to implement",J={task:L,contract:f,caps:D,scenarios:[],state:t},rt=wt(J),lt=yt(J),at=j.map(o=>"- **"+o.id+"** \u2014 "+o.title).join(`
8
- `),dt=U.length>0?U.map(o=>"### "+o.title+`
4
+ `),p.existsSync(w)||(console.error(H(" \u2718 inferno/ not found")),console.error(d(` \u2192 Run: infernoflow init
5
+ `)),process.exit(1));const f=M(b.join(w,"contract.json")),v=M(b.join(w,"capabilities.json")),nt=W(b.join(w,"CHANGELOG.md"));(!f||!v)&&(console.error(H(` \u2718 Missing contract.json or capabilities.json
6
+ `)),process.exit(1));let t=q();tt&&(t={},console.log(F(` \u26A0 State reset
7
+ `))),c&&(t.intent=c,t.intentUpdated=new Date().toISOString(),console.log(a(' \u2714 Intent saved: "'+c+'"'))),r&&(t.working=r,t.workingUpdated=new Date().toISOString(),console.log(a(' \u2714 Working on: "'+r+'"'))),x&&(t.decisions||(t.decisions=[]),t.decisions.push({text:x,date:new Date().toISOString()}),console.log(a(' \u2714 Decision recorded: "'+x+'"'))),(c||r||x)&&z(t);const $=v.capabilities||[],A=$.length===(f.capabilities||[]).length,U=St(nt,3),D=String(f.policyVersion).replace(/^v/i,""),ot=new Date().toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"}),B=A?"\u2713 validated":"\u26A0 out of sync",T=t.intent||"describe the exact task to implement",J={task:T,contract:f,caps:v,scenarios:[],state:t},it=wt(J),st=yt(J),ct=$.map(o=>"- **"+o.id+"** \u2014 "+o.title).join(`
8
+ `),rt=U.length>0?U.map(o=>"### "+o.title+`
9
9
  `+o.items.map(u=>" - "+u).join(`
10
10
  `)).join(`
11
11
 
12
- `):"_No recent changes_",pt=t.intent?t.intent+" _("+E(t.intentUpdated)+")_":'_Not set \u2014 run: infernoflow context --intent "..."_',gt=t.working?t.working+" _("+E(t.workingUpdated)+")_":'_Not set \u2014 run: infernoflow context --working "..."_',ut=t.decisions&&t.decisions.length>0?t.decisions.slice(-5).map(o=>"- "+o.text+" _("+E(o.date)+")_").join(`
13
- `):"_No decisions recorded_",x=["# Project Context \u2014 "+f.policyId+" v"+T,"> Generated by infernoflow | "+ct+" | "+B,"","---","","## What this system does","",at,"","---","","## Recent changes","",dt,"","---","","## Current state","","- **Capabilities:** "+j.length,"- **Version:** v"+T,"- **Sync:** "+B,"","---","","## What I am working on right now","",gt,"","---","","## Intent \u2014 what I want to build next","",pt,"","---","","## Decisions & notes","",ut,"","---","","## Implementation Prompt Seed","","Use this to start coding immediately with an agent:","","```bash",`infernoflow implement "${L}" --mode both`,"```","","### Cursor Agent Prompt","","```text",rt,"```","","### Generic Agent Prompt","","```text",lt,"```","","---","_Paste this block at the start of any new AI session._"].join(`
14
- `);if(tt||(g.writeFileSync(P,x,"utf8"),console.log(a(`
15
- \u2714 Context written \u2192 `+P))),G){const o=Ct(x);console.log(o?a(" \u2714 Copied to clipboard \u2014 paste with Ctrl+V"):F(" \u26A0 Clipboard copy failed \u2014 open inferno/CONTEXT.md manually"))}if(et&&(g.writeFileSync(".cursorrules",x,"utf8"),console.log(a(" \u2714 Written to .cursorrules \u2014 Cursor loads this automatically"))),nt&&(g.existsSync(".github")||g.mkdirSync(".github"),g.writeFileSync(".github/copilot-instructions.md",x,"utf8"),console.log(a(" \u2714 Written to .github/copilot-instructions.md \u2014 Copilot loads this automatically"))),console.log(`
16
- `+I("Context Summary")),console.log(" "+"\u2500".repeat(50)),console.log(" Project "+f.policyId+" \u2014 v"+T),console.log(" Capabilities "+j.length+" registered"),console.log(" Sync "+(A?a("\u2713 in sync"):F("\u26A0 check needed"))),console.log(" Working on "+(t.working?l(t.working):d("not set"))),console.log(" Intent "+(t.intent?l(t.intent):d("not set"))),console.log(" Decisions "+(t.decisions?t.decisions.length:0)+` recorded
17
- `),console.log(" "+I("Implementation Prompt")),console.log(" "+l("\u2192")+" Run "+l(`infernoflow implement "${L}" --mode both`)+`
12
+ `):"_No recent changes_",lt=t.intent?t.intent+" _("+E(t.intentUpdated)+")_":'_Not set \u2014 run: infernoflow context --intent "..."_',at=t.working?t.working+" _("+E(t.workingUpdated)+")_":'_Not set \u2014 run: infernoflow context --working "..."_',dt=t.decisions&&t.decisions.length>0?t.decisions.slice(-5).map(o=>"- "+o.text+" _("+E(o.date)+")_").join(`
13
+ `):"_No decisions recorded_",j=["# Project Context \u2014 "+f.policyId+" v"+D,"> Generated by infernoflow | "+ot+" | "+B,"","---","","## What this system does","",ct,"","---","","## Recent changes","",rt,"","---","","## Current state","","- **Capabilities:** "+$.length,"- **Version:** v"+D,"- **Sync:** "+B,"","---","","## What I am working on right now","",at,"","---","","## Intent \u2014 what I want to build next","",lt,"","---","","## Decisions & notes","",dt,"","---","","## Implementation Prompt Seed","","Use this to start coding immediately with an agent:","","```bash",`infernoflow implement "${T}" --mode both`,"```","","### Cursor Agent Prompt","","```text",it,"```","","### Generic Agent Prompt","","```text",st,"```","","---","_Paste this block at the start of any new AI session._"].join(`
14
+ `);if(Q||(p.writeFileSync(O,j,"utf8"),console.log(a(`
15
+ \u2714 Context written \u2192 `+O))),G){const o=kt(j);console.log(o?a(" \u2714 Copied to clipboard \u2014 paste with Ctrl+V"):F(" \u26A0 Clipboard copy failed \u2014 open inferno/CONTEXT.md manually"))}if(Y&&(p.writeFileSync(".cursorrules",j,"utf8"),console.log(a(" \u2714 Written to .cursorrules \u2014 Cursor loads this automatically"))),Z&&(p.existsSync(".github")||p.mkdirSync(".github"),p.writeFileSync(".github/copilot-instructions.md",j,"utf8"),console.log(a(" \u2714 Written to .github/copilot-instructions.md \u2014 Copilot loads this automatically"))),console.log(`
16
+ `+I("Context Summary")),console.log(" "+"\u2500".repeat(50)),console.log(" Project "+f.policyId+" \u2014 v"+D),console.log(" Capabilities "+$.length+" registered"),console.log(" Sync "+(A?a("\u2713 in sync"):F("\u26A0 check needed"))),console.log(" Working on "+(t.working?l(t.working):d("not set"))),console.log(" Intent "+(t.intent?l(t.intent):d("not set"))),console.log(" Decisions "+(t.decisions?t.decisions.length:0)+` recorded
17
+ `),console.log(" "+I("Implementation Prompt")),console.log(" "+l("\u2192")+" Run "+l(`infernoflow implement "${T}" --mode both`)+`
18
18
  `),G?(console.log(" "+I("Ready to use:")),console.log(" "+l("\u2192")+" Paste into Claude / Cursor / Copilot with "+l("Ctrl+V")+`
19
19
  `)):(console.log(" "+I("Ready to use:")),console.log(" "+l("1.")+" Open "+l("inferno/CONTEXT.md")),console.log(" "+l("2.")+" Copy everything"),console.log(" "+l("3.")+" Paste at the start of your next AI session"),console.log(" "+d(" tip: use --copy to skip steps 1-2 automatically")+`
20
- `)),it){let _=function(p){try{return h(p,{cwd:process.cwd(),encoding:"utf8",stdio:["ignore","pipe","pipe"]}),!0}catch{return!1}},X=function(p){try{return h(`git status --porcelain "${p}"`,{cwd:process.cwd(),encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim()===""}catch{return!0}},H=function(p,s,O){const k=`chore: update context [${s.length>0?s.slice(0,3).join(", "):`${O} files`}]`;return _(`git add "${p}"`)?X(p)?{ok:!1,reason:"nothing to commit"}:_(`git commit -m "${k}"`)?{ok:!0,msg:k}:{ok:!1,reason:"git commit failed (lock?)"}:{ok:!1,reason:"git add failed"}},K=function(){return _("git push")};var It=_,Ft=X,$t=H,jt=K;const o=v?"auto-push":N?"auto-commit":"watch";console.log(" "+l("\u{1F441} Watch mode active")+d(` \u2014 polling every ${R/1e3}s`+(v?" \xB7 will commit + push on change":N?" \xB7 will commit on change":""))),console.log(" "+d(`Press Ctrl+C to stop
21
- `));let u="",V=null;const M=async()=>{try{const p=process.cwd(),s=kt(p,{sinceCommits:1}),O=s.changedFiles.sort().join("|");if(O===u||(u=O,s.changedFiles.length===0))return;const y=s.affectedCapabilities.map(S=>S.id),k=y.length>0?`Working on: ${y.join(", ")} (${s.changedFiles.length} files changed)`:`${s.changedFiles.length} files changed \u2014 no capability match yet`,C=Y();if(C.working!==k){C.working=k,C.workingUpdated=new Date().toISOString(),Z(C),await bt(e.filter(m=>m!=="--watch"&&m!=="--auto-commit"&&m!=="--auto-push"));const S=W(P),ft=new Date().toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",second:"2-digit"});if(process.stderr.write(`
20
+ `)),et){let L=function(g){try{return h(g,{cwd:process.cwd(),encoding:"utf8",stdio:["ignore","pipe","pipe"]}),!0}catch{return!1}},gt=function(g){try{return h(`git status --porcelain "${g}"`,{cwd:process.cwd(),encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim()===""}catch{return!0}},pt=function(g,s,_){const C=`chore: update context [${s.length>0?s.slice(0,3).join(", "):`${_} files`}]`;return L(`git add "${g}"`)?gt(g)?{ok:!1,reason:"nothing to commit"}:L(`git commit -m "${C}"`)?{ok:!0,msg:C}:{ok:!1,reason:"git commit failed (lock?)"}:{ok:!1,reason:"git add failed"}},ut=function(){return L("git push")};const o=N?"auto-push":P?"auto-commit":"watch";console.log(" "+l("\u{1F441} Watch mode active")+d(` \u2014 polling every ${R/1e3}s`+(N?" \xB7 will commit + push on change":P?" \xB7 will commit on change":""))),console.log(" "+d(`Press Ctrl+C to stop
21
+ `));let u="",V=null;const X=async()=>{try{const g=process.cwd(),s=Ct(g,{sinceCommits:1}),_=s.changedFiles.sort().join("|");if(_===u||(u=_,s.changedFiles.length===0))return;const y=s.affectedCapabilities.map(S=>S.id),C=y.length>0?`Working on: ${y.join(", ")} (${s.changedFiles.length} files changed)`:`${s.changedFiles.length} files changed \u2014 no capability match yet`,k=q();if(k.working!==C){k.working=C,k.workingUpdated=new Date().toISOString(),z(k),await bt(e.filter(m=>m!=="--watch"&&m!=="--auto-commit"&&m!=="--auto-push"));const S=W(O),ft=new Date().toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",second:"2-digit"});if(process.stderr.write(`
22
22
  ${a("\u2714")} [${ft}] Context updated \u2014 ${y.length} capabilities affected
23
23
  ${d(s.changedFiles.slice(0,3).join(", ")+(s.changedFiles.length>3?` +${s.changedFiles.length-3} more`:""))}
24
- `),N&&S!==V){V=S;const m=H(P,y,s.changedFiles.length);if(m.ok){if(process.stderr.write(` ${a("\u2714")} Committed: ${d(m.msg)}
25
- `),v){const ht=K();process.stderr.write(ht?` ${a("\u2714")} Pushed to origin
24
+ `),P&&S!==V){V=S;const m=pt(O,y,s.changedFiles.length);if(m.ok){if(process.stderr.write(` ${a("\u2714")} Committed: ${d(m.msg)}
25
+ `),N){const ht=ut();process.stderr.write(ht?` ${a("\u2714")} Pushed to origin
26
26
  `:` ${F("\u26A0")} Push failed \u2014 will retry next change
27
27
  `)}}else process.stderr.write(` ${F("\u26A0")} Commit skipped: ${d(m.reason)}
28
- `)}}}catch{}};await M();const mt=setInterval(M,R);process.on("SIGINT",()=>{clearInterval(mt),process.stderr.write(`
28
+ `)}}}catch{}};await X();const mt=setInterval(X,R);process.on("SIGINT",()=>{clearInterval(mt),process.stderr.write(`
29
29
  `+d(`Watch stopped.
30
30
 
31
31
  `)),process.exit(0)}),await new Promise(()=>{})}}export{bt as contextCommand};
@@ -1,4 +1,3 @@
1
- import*as s from"node:fs";import*as l from"node:path";import*as y from"node:os";import*as v from"node:http";import{execSync as C,spawnSync as $}from"node:child_process";import{fileURLToPath as O}from"node:url";import{bold as b,cyan as N,gray as x,green as w,yellow as j,red as S}from"../ui/output.mjs";import{detectAvailableProviders as A}from"../ai/providerRouter.mjs";function a(n,e){try{const o=e();return{label:n,...o}}catch(o){return{label:n,status:"error",message:o.message,fix:null}}}function c(n,e){return{status:"pass",message:n,detail:e||null,fix:null}}function u(n,e){return{status:"warn",message:n,detail:null,fix:e||null}}function p(n,e){return{status:"fail",message:n,detail:null,fix:e||null}}function _(){const n=process.version,e=parseInt(n.slice(1).split(".")[0],10);return e>=20?c(`Node.js ${n}`,"Node 20+ recommended"):e>=18?c(`Node.js ${n}`):p(`Node.js ${n} \u2014 infernoflow requires Node 18+`,"Install Node 20 from nodejs.org")}function P(){try{const n=$("infernoflow",["--version"],{encoding:"utf8",timeout:5e3,shell:process.platform==="win32"});return n.status===0?c(`infernoflow v${n.stdout.trim()} installed`):c("infernoflow CLI on PATH (version probe failed but doctor itself ran)")}catch{return c("infernoflow CLI on PATH (version probe threw but doctor itself ran)")}}function E(n){try{return C("git rev-parse --git-dir",{cwd:n,stdio:"ignore"}),c("Git repository detected")}catch{return p("Not a git repository","git init && git add . && git commit -m 'init'")}}function I(n){const e=l.join(n,"inferno");return s.existsSync(e)?c("inferno/ directory exists"):p("inferno/ not found","infernoflow init")}function T(n){const e=l.join(n,"inferno");if((()=>{try{return JSON.parse(s.readFileSync(l.join(e,"config.json"),"utf8"))}catch{return{}}})().mode==="memory"){const i=l.join(e,"sessions.jsonl");if(!s.existsSync(i))return c("Memory mode \u2014 sessions.jsonl will be created on first log");let t=0;try{t=s.readFileSync(i,"utf8").split(`
2
- `).filter(Boolean).length}catch{}return c(`Memory mode \u2014 ${t} session entr${t===1?"y":"ies"}`)}for(const i of["contract.json","capabilities.json"]){const t=l.join(e,i);if(s.existsSync(t))try{const d=(JSON.parse(s.readFileSync(t,"utf8")).capabilities||[]).length;return c(`${i} valid \u2014 ${d} capabilities`)}catch{return p(`${i} contains invalid JSON`,`Fix the JSON syntax in inferno/${i}`)}}return p("No contract.json/capabilities.json (and not in memory mode)","infernoflow init or infernoflow init --mode full")}function k(n){try{return JSON.parse(s.readFileSync(l.join(n,"inferno","config.json"),"utf8")).mode==="memory"}catch{return!1}}function G(n){if(k(n))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const e=l.join(n,"inferno","scenarios");if(!s.existsSync(e))return u("No scenarios/ directory","infernoflow init");const o=s.readdirSync(e).filter(i=>i.endsWith(".json"));return o.length?c(`${o.length} scenario file${o.length!==1?"s":""} found`):u("scenarios/ is empty","Add scenario files or run infernoflow suggest")}function F(n){if(k(n))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const e=l.join(n,"inferno","CHANGELOG.md");return s.existsSync(e)?c("inferno/CHANGELOG.md exists"):u("No inferno/CHANGELOG.md","infernoflow init")}function M(n){if(k(n))return{status:"info",message:"n/a in memory mode (CLAUDE.md is auto-maintained)",detail:null,fix:null};const e=l.join(n,"inferno","CONTEXT.md");if(!s.existsSync(e))return u("No CONTEXT.md generated","infernoflow context");const o=(Date.now()-s.statSync(e).mtimeMs)/(1e3*60*60*24);return o>7?u(`CONTEXT.md is ${Math.round(o)} days old \u2014 may be stale`,"infernoflow context"):c(`CONTEXT.md present (${Math.round(o)}d old)`)}function D(n){const e=l.join(n,".git","hooks"),o=l.join(e,"post-commit"),i=l.join(e,"pre-push"),t=s.existsSync(o)&&s.readFileSync(o,"utf8").includes("infernoflow"),r=s.existsSync(i)&&s.readFileSync(i,"utf8").includes("infernoflow");return t&&r?c("Git hooks installed (post-commit + pre-push)"):u(t||r?"Partial git hooks installed":"Git hooks not installed","infernoflow setup --yes")}function L(n){const e=[l.join(n,".cursor","mcp.json"),l.join(n,".mcp.json"),l.join(y.homedir(),".cursor","mcp.json"),l.join(y.homedir(),"Library","Application Support","Claude","claude_desktop_config.json"),l.join(y.homedir(),"AppData","Roaming","Claude","claude_desktop_config.json")];for(const o of e)if(s.existsSync(o))try{const i=JSON.parse(s.readFileSync(o,"utf8")),t=i.mcpServers||i.mcp_servers||{};if(Object.keys(t).some(r=>r.toLowerCase().includes("inferno")))return c(`MCP server configured in ${l.basename(o)}`)}catch{}return u("MCP server not configured","infernoflow setup --yes (adds to Cursor/Claude config)")}function R(n){const e=A(n),o=Object.entries(e).filter(([,i])=>i).map(([i])=>i);return o.length?c(`AI provider${o.length!==1?"s":""}: ${o.join(", ")}`):u("No AI provider configured",`Set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_AI_API_KEY, or OPENROUTER_API_KEY
1
+ import*as s from"node:fs";import*as a from"node:path";import*as y from"node:os";import*as v from"node:http";import{execSync as C,spawnSync as $}from"node:child_process";import{fileURLToPath as O}from"node:url";import{bold as b,cyan as N,gray as x,green as w,yellow as j,red as S}from"../ui/output.mjs";import{detectAvailableProviders as A}from"../ai/providerRouter.mjs";import{readEntries as _}from"../amp/io.mjs";function c(n,e){try{const o=e();return{label:n,...o}}catch(o){return{label:n,status:"error",message:o.message,fix:null}}}function l(n,e){return{status:"pass",message:n,detail:e||null,fix:null}}function u(n,e){return{status:"warn",message:n,detail:null,fix:e||null}}function p(n,e){return{status:"fail",message:n,detail:null,fix:e||null}}function P(){const n=process.version,e=parseInt(n.slice(1).split(".")[0],10);return e>=20?l(`Node.js ${n}`,"Node 20+ recommended"):e>=18?l(`Node.js ${n}`):p(`Node.js ${n} \u2014 infernoflow requires Node 18+`,"Install Node 20 from nodejs.org")}function E(){try{const n=$("infernoflow",["--version"],{encoding:"utf8",timeout:5e3,shell:process.platform==="win32"});return n.status===0?l(`infernoflow v${n.stdout.trim()} installed`):l("infernoflow CLI on PATH (version probe failed but doctor itself ran)")}catch{return l("infernoflow CLI on PATH (version probe threw but doctor itself ran)")}}function I(n){try{return C("git rev-parse --git-dir",{cwd:n,stdio:"ignore"}),l("Git repository detected")}catch{return p("Not a git repository","git init && git add . && git commit -m 'init'")}}function T(n){const e=a.join(n,"inferno");return s.existsSync(e)?l("inferno/ directory exists"):p("inferno/ not found","infernoflow init")}function G(n){const e=a.join(n,"inferno");if((()=>{try{return JSON.parse(s.readFileSync(a.join(e,"config.json"),"utf8"))}catch{return{}}})().mode==="memory"){let t=0;try{t=_(n).length}catch{}return l(t===0?"Memory mode \u2014 sessions.jsonl will be created on first log":`Memory mode \u2014 ${t} session entr${t===1?"y":"ies"}`)}for(const t of["contract.json","capabilities.json"]){const i=a.join(e,t);if(s.existsSync(i))try{const d=(JSON.parse(s.readFileSync(i,"utf8")).capabilities||[]).length;return l(`${t} valid \u2014 ${d} capabilities`)}catch{return p(`${t} contains invalid JSON`,`Fix the JSON syntax in inferno/${t}`)}}return p("No contract.json/capabilities.json (and not in memory mode)","infernoflow init or infernoflow init --mode full")}function k(n){try{return JSON.parse(s.readFileSync(a.join(n,"inferno","config.json"),"utf8")).mode==="memory"}catch{return!1}}function F(n){if(k(n))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const e=a.join(n,"inferno","scenarios");if(!s.existsSync(e))return u("No scenarios/ directory","infernoflow init");const o=s.readdirSync(e).filter(t=>t.endsWith(".json"));return o.length?l(`${o.length} scenario file${o.length!==1?"s":""} found`):u("scenarios/ is empty","Add scenario files or run infernoflow suggest")}function M(n){if(k(n))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const e=a.join(n,"inferno","CHANGELOG.md");return s.existsSync(e)?l("inferno/CHANGELOG.md exists"):u("No inferno/CHANGELOG.md","infernoflow init")}function R(n){if(k(n))return{status:"info",message:"n/a in memory mode (CLAUDE.md is auto-maintained)",detail:null,fix:null};const e=a.join(n,"inferno","CONTEXT.md");if(!s.existsSync(e))return u("No CONTEXT.md generated","infernoflow context");const o=(Date.now()-s.statSync(e).mtimeMs)/(1e3*60*60*24);return o>7?u(`CONTEXT.md is ${Math.round(o)} days old \u2014 may be stale`,"infernoflow context"):l(`CONTEXT.md present (${Math.round(o)}d old)`)}function D(n){const e=a.join(n,".git","hooks"),o=a.join(e,"post-commit"),t=a.join(e,"pre-push"),i=s.existsSync(o)&&s.readFileSync(o,"utf8").includes("infernoflow"),r=s.existsSync(t)&&s.readFileSync(t,"utf8").includes("infernoflow");return i&&r?l("Git hooks installed (post-commit + pre-push)"):u(i||r?"Partial git hooks installed":"Git hooks not installed","infernoflow setup --yes")}function L(n){const e=[a.join(n,".cursor","mcp.json"),a.join(n,".mcp.json"),a.join(y.homedir(),".cursor","mcp.json"),a.join(y.homedir(),"Library","Application Support","Claude","claude_desktop_config.json"),a.join(y.homedir(),"AppData","Roaming","Claude","claude_desktop_config.json")];for(const o of e)if(s.existsSync(o))try{const t=JSON.parse(s.readFileSync(o,"utf8")),i=t.mcpServers||t.mcp_servers||{};if(Object.keys(i).some(r=>r.toLowerCase().includes("inferno")))return l(`MCP server configured in ${a.basename(o)}`)}catch{}return u("MCP server not configured","infernoflow setup --yes (adds to Cursor/Claude config)")}function J(n){const e=A(n),o=Object.entries(e).filter(([,t])=>t).map(([t])=>t);return o.length?l(`AI provider${o.length!==1?"s":""}: ${o.join(", ")}`):u("No AI provider configured",`Set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_AI_API_KEY, or OPENROUTER_API_KEY
3
2
  Or install Ollama (ollama.com) for free local AI
4
- Or use VS Code with GitHub Copilot (zero config)`)}async function J(){return new Promise(n=>{const e=v.get({hostname:"localhost",port:11434,path:"/api/tags",timeout:1500},o=>{n(c("Ollama running on localhost:11434"))});e.on("error",()=>n({status:"info",message:"Ollama not running (optional)",fix:"ollama serve",detail:null})),e.on("timeout",()=>{e.destroy(),n({status:"info",message:"Ollama not running (optional)",fix:null,detail:null})})})}function H(){const n=l.join(y.homedir(),".infernoflow","credentials.json");if(!s.existsSync(n))return{status:"info",message:"Not logged in to cloud (optional)",fix:"infernoflow login",detail:null};try{const e=JSON.parse(s.readFileSync(n,"utf8")),o=e.user?.login||e.user?.name||e.user?.email||"unknown";if(e.mode==="supabase"&&e.access_token){if(e.expires_at){const i=new Date(e.expires_at).getTime();if(Date.now()>i)return u(`JWT expired for ${o} \u2014 refresh on next log will retry`,"infernoflow login")}return c(`Authenticated as ${o} (Supabase JWT \u2014 auth.uid() writes)`)}return e.mode==="device-flow"&&e.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}:e.access_token?u(`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 u("Credentials file unreadable","infernoflow logout && infernoflow login")}}function X(){try{const n=O(import.meta.url),e=l.resolve(l.dirname(n),"..","..","bin","infernoflow.mjs");if(!s.existsSync(e))return{status:"info",message:"bin/infernoflow.mjs not found from doctor location",fix:null,detail:null};const i=[...s.readFileSync(e,"utf8").matchAll(/import\("\.\.\/lib\/(commands\/[^"]+|telemetry\.mjs)"\)/g)],t=[],r=l.resolve(l.dirname(e),"..");for(const d of i){const m=d[1],h=l.join(r,"lib",m);s.existsSync(h)||t.push(m)}return t.length?p(`${t.length} routed command(s) missing module files: ${t.slice(0,3).join(", ")}${t.length>3?"\u2026":""}`,"Restore the missing files or remove their entries from bin/infernoflow.mjs"):c(`All ${i.length} routed commands resolve to real files`)}catch(n){return{status:"info",message:`Router integrity check skipped: ${n.message}`,fix:null,detail:null}}}function K(n){const e=l.join(n,"package.json");if(!s.existsSync(e))return c("No package.json to audit");let o;try{o=JSON.parse(s.readFileSync(e,"utf8"))}catch{return{status:"info",message:"package.json unreadable; skipping audit",detail:null,fix:null}}const i=o.scripts||{},t=new Set(["log","ask","switch","recap","status","init","doctor","graph","watch","amp","contract","dev","demo","setup","log-decision","log-attempt","context","stats","test"]),r=[];for(const[m,h]of Object.entries(i)){const f=/\binfernoflow\s+([a-z][a-z0-9-]*)/.exec(String(h));if(!f)continue;const g=f[1];t.has(g)||r.push({scriptName:m,verb:g})}if(r.length===0)return c("npm scripts use current command surface");const d=r.map(m=>`${m.scriptName} \u2192 infernoflow ${m.verb}`).join(", ");return u(`package.json references ${r.length} deprecated command(s): ${d}`,"Edit package.json scripts to use the current surface (run `infernoflow --help` to list verbs)")}function V(n){const e=l.join(n,".gitignore");if(!s.existsSync(e))return{status:"info",message:".gitignore not found",fix:null,detail:null};const o=s.readFileSync(e,"utf8");return/^(?:\*\*\/)?node_modules\/?$/m.test(o)?c(".gitignore excludes node_modules"):u(".gitignore does not exclude node_modules","Add 'node_modules/' (and '**/node_modules/') to .gitignore")}function W(n,e){const o=n.filter(t=>t.status==="warn"&&t.fix),i=[];for(const t of o){const r=t.fix;if(r.startsWith("infernoflow ")){const d=r.slice(12).split(" ");$("infernoflow",d,{cwd:e,encoding:"utf8",timeout:3e4}).status===0&&i.push(t.label)}}return i}function Y(n){return n==="pass"?w("\u2714"):n==="warn"?j("\u26A0"):n==="fail"?S("\u2717"):x("\xB7")}function z(n,e){const o={pass:0,warn:0,fail:0,info:0,error:0};for(const r of n)o[r.status]=(o[r.status]||0)+1;console.log(),console.log(` ${b("\u{1F525} infernoflow doctor")}`),console.log();const i=Math.max(...n.map(r=>r.label.length))+2;for(const r of n)console.log(` ${Y(r.status)} ${b(r.label.padEnd(i))} ${r.message}`),r.detail&&console.log(` ${" ".repeat(i)} ${x(r.detail)}`),r.fix&&(r.status==="warn"||r.status==="fail")&&console.log(` ${" ".repeat(i)} ${N("fix:")} ${x(r.fix)}`);console.log();const t=o.fail>0?S("issues found"):o.warn>0?j("warnings"):w("all good");console.log(` ${t} \u2014 ${w(String(o.pass))} pass \xB7 ${j(String(o.warn))} warn \xB7 ${S(String(o.fail))} fail (${e}ms)`),console.log(),(o.warn>0||o.fail>0)&&(console.log(` Run ${N("infernoflow doctor --fix")} to auto-fix warnings`),console.log())}async function U(n){const e=n.slice(1),o=e.includes("--json"),i=e.includes("--fix"),t=process.cwd(),r=Date.now(),d=[a("Node.js version",()=>_()),a("infernoflow CLI",()=>P()),a("Git repository",()=>E(t)),a("inferno/ directory",()=>I(t)),a("Contract / mode",()=>T(t)),a("Scenarios",()=>G(t)),a("Changelog",()=>F(t)),a("CONTEXT.md",()=>M(t)),a("Git hooks",()=>D(t)),a("MCP server",()=>L(t)),a("AI providers",()=>R(t)),a("Cloud sync",()=>H()),a(".gitignore",()=>V(t)),a("Router integrity",()=>X()),a("npm scripts",()=>K(t)),await J().then(f=>({label:"Ollama (local AI)",...f}))],m=Date.now()-r;if(i){const f=W(d,t);if(f.length)return o||(console.log(),f.forEach(g=>console.log(` ${w("\u2714")} Fixed: ${g}`)),console.log()),U(["doctor","--json"])}if(o){const f={pass:0,warn:0,fail:0,info:0};d.forEach(g=>f[g.status]=(f[g.status]||0)+1),console.log(JSON.stringify({ok:f.fail===0,counts:f,results:d,elapsed:m}));return}z(d,m),d.some(f=>f.status==="fail")&&process.exit(1)}export{U as doctorCommand};
3
+ Or use VS Code with GitHub Copilot (zero config)`)}async function H(){return new Promise(n=>{const e=v.get({hostname:"localhost",port:11434,path:"/api/tags",timeout:1500},o=>{n(l("Ollama running on localhost:11434"))});e.on("error",()=>n({status:"info",message:"Ollama not running (optional)",fix:"ollama serve",detail:null})),e.on("timeout",()=>{e.destroy(),n({status:"info",message:"Ollama not running (optional)",fix:null,detail:null})})})}function X(){const n=a.join(y.homedir(),".infernoflow","credentials.json");if(!s.existsSync(n))return{status:"info",message:"Not logged in to cloud (optional)",fix:"infernoflow login",detail:null};try{const e=JSON.parse(s.readFileSync(n,"utf8")),o=e.user?.login||e.user?.name||e.user?.email||"unknown";if(e.mode==="supabase"&&e.access_token){if(e.expires_at){const t=new Date(e.expires_at).getTime();if(Date.now()>t)return u(`JWT expired for ${o} \u2014 refresh on next log will retry`,"infernoflow login")}return l(`Authenticated as ${o} (Supabase JWT \u2014 auth.uid() writes)`)}return e.mode==="device-flow"&&e.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}:e.access_token?u(`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 u("Credentials file unreadable","infernoflow logout && infernoflow login")}}function K(){try{const n=O(import.meta.url),e=a.resolve(a.dirname(n),"..","..","bin","infernoflow.mjs");if(!s.existsSync(e))return{status:"info",message:"bin/infernoflow.mjs not found from doctor location",fix:null,detail:null};const t=[...s.readFileSync(e,"utf8").matchAll(/import\("\.\.\/lib\/(commands\/[^"]+|telemetry\.mjs)"\)/g)],i=[],r=a.resolve(a.dirname(e),"..");for(const d of t){const m=d[1],h=a.join(r,"lib",m);s.existsSync(h)||i.push(m)}return i.length?p(`${i.length} routed command(s) missing module files: ${i.slice(0,3).join(", ")}${i.length>3?"\u2026":""}`,"Restore the missing files or remove their entries from bin/infernoflow.mjs"):l(`All ${t.length} routed commands resolve to real files`)}catch(n){return{status:"info",message:`Router integrity check skipped: ${n.message}`,fix:null,detail:null}}}function V(n){const e=a.join(n,"package.json");if(!s.existsSync(e))return l("No package.json to audit");let o;try{o=JSON.parse(s.readFileSync(e,"utf8"))}catch{return{status:"info",message:"package.json unreadable; skipping audit",detail:null,fix:null}}const t=o.scripts||{},i=new Set(["log","ask","switch","recap","status","init","doctor","graph","watch","amp","contract","dev","demo","setup","log-decision","log-attempt","context","stats","test"]),r=[];for(const[m,h]of Object.entries(t)){const f=/\binfernoflow\s+([a-z][a-z0-9-]*)/.exec(String(h));if(!f)continue;const g=f[1];i.has(g)||r.push({scriptName:m,verb:g})}if(r.length===0)return l("npm scripts use current command surface");const d=r.map(m=>`${m.scriptName} \u2192 infernoflow ${m.verb}`).join(", ");return u(`package.json references ${r.length} deprecated command(s): ${d}`,"Edit package.json scripts to use the current surface (run `infernoflow --help` to list verbs)")}function W(n){const e=a.join(n,".gitignore");if(!s.existsSync(e))return{status:"info",message:".gitignore not found",fix:null,detail:null};const o=s.readFileSync(e,"utf8");return/^(?:\*\*\/)?node_modules\/?$/m.test(o)?l(".gitignore excludes node_modules"):u(".gitignore does not exclude node_modules","Add 'node_modules/' (and '**/node_modules/') to .gitignore")}function Y(n,e){const o=n.filter(i=>i.status==="warn"&&i.fix),t=[];for(const i of o){const r=i.fix;if(r.startsWith("infernoflow ")){const d=r.slice(12).split(" ");$("infernoflow",d,{cwd:e,encoding:"utf8",timeout:3e4}).status===0&&t.push(i.label)}}return t}function z(n){return n==="pass"?w("\u2714"):n==="warn"?j("\u26A0"):n==="fail"?S("\u2717"):x("\xB7")}function U(n,e){const o={pass:0,warn:0,fail:0,info:0,error:0};for(const r of n)o[r.status]=(o[r.status]||0)+1;console.log(),console.log(` ${b("\u{1F525} infernoflow doctor")}`),console.log();const t=Math.max(...n.map(r=>r.label.length))+2;for(const r of n)console.log(` ${z(r.status)} ${b(r.label.padEnd(t))} ${r.message}`),r.detail&&console.log(` ${" ".repeat(t)} ${x(r.detail)}`),r.fix&&(r.status==="warn"||r.status==="fail")&&console.log(` ${" ".repeat(t)} ${N("fix:")} ${x(r.fix)}`);console.log();const i=o.fail>0?S("issues found"):o.warn>0?j("warnings"):w("all good");console.log(` ${i} \u2014 ${w(String(o.pass))} pass \xB7 ${j(String(o.warn))} warn \xB7 ${S(String(o.fail))} fail (${e}ms)`),console.log(),(o.warn>0||o.fail>0)&&(console.log(` Run ${N("infernoflow doctor --fix")} to auto-fix warnings`),console.log())}async function q(n){const e=n.slice(1),o=e.includes("--json"),t=e.includes("--fix"),i=process.cwd(),r=Date.now(),d=[c("Node.js version",()=>P()),c("infernoflow CLI",()=>E()),c("Git repository",()=>I(i)),c("inferno/ directory",()=>T(i)),c("Contract / mode",()=>G(i)),c("Scenarios",()=>F(i)),c("Changelog",()=>M(i)),c("CONTEXT.md",()=>R(i)),c("Git hooks",()=>D(i)),c("MCP server",()=>L(i)),c("AI providers",()=>J(i)),c("Cloud sync",()=>X()),c(".gitignore",()=>W(i)),c("Router integrity",()=>K()),c("npm scripts",()=>V(i)),await H().then(f=>({label:"Ollama (local AI)",...f}))],m=Date.now()-r;if(t){const f=Y(d,i);if(f.length)return o||(console.log(),f.forEach(g=>console.log(` ${w("\u2714")} Fixed: ${g}`)),console.log()),q(["doctor","--json"])}if(o){const f={pass:0,warn:0,fail:0,info:0};d.forEach(g=>f[g.status]=(f[g.status]||0)+1),console.log(JSON.stringify({ok:f.fail===0,counts:f,results:d,elapsed:m}));return}U(d,m),d.some(f=>f.status==="fail")&&process.exit(1)}export{q as doctorCommand};