infernoflow 0.40.4 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog — infernoflow
2
2
 
3
+ ## 0.41.0 — 2026-05-03
4
+
5
+ ### Changed
6
+ - **Command surface culled from 51 visible to 12.** `--help` now shows only the 5-command memory core (log/ask/switch/recap/status), 3 setup commands (init/watch/doctor), and 3 subsystem dispatchers (contract/cloud/dev). All 53 legacy command names remain callable as top-level aliases — backward compatible. K.I.S.S. first-impression for new users; full discoverability via `infernoflow commands`.
7
+ - **New namespace dispatchers** — `infernoflow contract scan` routes to the same handler as `infernoflow scan`. Same for `infernoflow dev publish`, etc. Run `infernoflow contract` or `infernoflow dev` with no verb to see the verbs in that namespace. The existing `cloud` dispatcher (with init/push/pull/status/dashboard subcommands) is untouched.
8
+ - **`infernoflow commands` regrouped** to advertise the new namespace structure: Memory / Watch / Setup at top level, then Contract / Cloud / Dev grouped with their verbs.
9
+
10
+ ### Internal
11
+ - This is the first half of the move toward the AI Memory Protocol (AMP) — see docs/protocol/PROTOCOL.md. Phase A.2 (folder rename to `.ai-memory/`, AMP-compliant entry shape with ULIDs and `meta.subtype` for infernoflow extras) is up next.
12
+
13
+ ## 0.40.6 — 2026-05-02
14
+
15
+ ### Fixed
16
+ - **`infernoflow init` first-gotcha prompt is now bulletproof.** The original prompt accepted whatever you typed verbatim, so a confused user (Ron earlier today, after Ctrl+C'ing a stuck `--browser` login) could paste a multi-line shell command and end up with `node ../infernoflow-pkg/bin/infernoflow.mjs log "..."` saved as their first memory. The first interaction every new user has now:
17
+ - **Detects shell-command-shaped input** (starts with node/npm/npx/git/cd/python/etc., or contains `&&`/`||`/`>`/`|` operators, or contains a Windows drive path) and re-prompts with a hint.
18
+ - **Detects multi-line paste** and re-prompts asking for a single short sentence.
19
+ - **Trims accidental leading prompt characters** (`> `, `$ `, `# `) — common when copy/pasting from terminal output.
20
+ - **Treats input shorter than 3 chars as too short** (single keystrokes, accidental Enter).
21
+ - **Handles Ctrl+C cleanly** — exits the prompt without leaving inferno/ in a half-state.
22
+ - **Two-strike rule** — after two bad inputs we silently skip rather than block the install.
23
+ - Verified via 18-case unit test of the classifier covering all the failure modes.
24
+
25
+ ## 0.40.5 — 2026-05-02
26
+
27
+ ### Fixed
28
+ - **`infernoflow scaffold` ID consistency** — scaffold previously rejected `UserSearch` with "Invalid capability ID — use lowercase kebab-case", but every other contracts command (scan, adopt, freeze, contract.json itself) uses PascalCase IDs like `CreateItem`. Anyone seeing the existing capabilities and trying to scaffold a similar one was blocked. Now scaffold accepts kebab-case, snake_case, camelCase, PascalCase, or space-separated input and normalizes to the canonical PascalCase. Duplicate-detection matches either the canonical or the user-typed form.
29
+
3
30
  ## 0.40.4 — 2026-05-02
4
31
 
5
32
  ### Added
@@ -1,34 +1,41 @@
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 e={"\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]"},t=new RegExp(Object.keys(e).join("|"),"g");function c(m){const g=m.write.bind(m);m.write=function(i,...w){if(typeof i=="string")i=i.replace(t,l=>e[l]);else if(Buffer.isBuffer(i)){const l=i.toString("utf8").replace(t,C=>e[C]);i=Buffer.from(l,"utf8")}return g(i,...w)}}c(process.stdout),c(process.stderr)})();import{readFileSync as b}from"node:fs";import{dirname as v,join as f}from"node:path";import{fileURLToPath as k}from"node:url";import{bold as r,gray as o,cyan as n,red as h}from"../lib/ui/output.mjs";const $=v(k(import.meta.url));function S(a){for(const e of[f(a,"..","..","package.json"),f(a,"..","package.json")])try{return JSON.parse(b(e,"utf8"))}catch{}return{version:"0.0.0-source"}}const x=S($),d=x.version||"0.0.0",u={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",dashboard:"Launch local web dashboard on localhost:7337 \u2014 live contract health, capabilities, agents",login:"Sign in with GitHub \u2014 syncs session memory to the cloud on every log",logout:"Sign out and remove local credentials",whoami:"Show currently logged-in user",cloud:"Sync capability contracts via infernoflow cloud (init | push | pull | status | dashboard)",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)"},p={publish:async a=>(await import("../lib/commands/publish.mjs")).publishCommand(a),diff:async a=>(await import("../lib/commands/diff.mjs")).diffCommand(a),changelog:async a=>(await import("../lib/commands/changelog.mjs")).changelogCommand(a),setup:async a=>(await import("../lib/commands/setup.mjs")).setupCommand(a),init:async a=>(await import("../lib/commands/init.mjs")).initCommand(a),"install-cursor-hooks":async a=>(await import("../lib/commands/installCursorHooks.mjs")).installCursorHooksCommand(a),"install-vscode-copilot-hooks":async a=>(await import("../lib/commands/installVsCodeCopilotHooks.mjs")).installVsCodeCopilotHooksCommand(a),check:async a=>(await import("../lib/commands/check.mjs")).checkCommand(a),status:async a=>(await import("../lib/commands/status.mjs")).statusCommand(a),"pr-impact":async a=>(await import("../lib/commands/prImpact.mjs")).prImpactCommand(a),sync:async a=>(await import("../lib/commands/syncAuto.mjs")).syncCommand(a),run:async a=>(await import("../lib/commands/run.mjs")).runCommand(a),suggest:async a=>(await import("../lib/commands/suggest.mjs")).suggestCommand(a),implement:async a=>(await import("../lib/commands/implement.mjs")).implementCommand(a),context:async a=>(await import("../lib/commands/context.mjs")).contextCommand(a),"doc-gate":async a=>(await import("../lib/commands/docGate.mjs")).docGateCommand(a),"generate-skills":async a=>(await import("../lib/commands/generateSkills.mjs")).generateSkillsCommand(a),dashboard:async a=>(await import("../lib/commands/dashboard.mjs")).dashboardCommand(a),login:async a=>(await import("../lib/commands/login.mjs")).loginCommand(a),logout:async()=>(await import("../lib/commands/login.mjs")).logoutCommand(),whoami:async()=>(await import("../lib/commands/login.mjs")).whoamiCommand(),cloud:async a=>(await import("../lib/commands/cloud.mjs")).cloudCommand(a),watch:async a=>(await import("../lib/commands/watch.mjs")).watchCommand(a),ci:async a=>(await import("../lib/commands/ci.mjs")).ciCommand(a),notify:async a=>(await import("../lib/commands/notify.mjs")).notifyCommand(a),monorepo:async a=>(await import("../lib/commands/monorepo.mjs")).monorepoCommand(a),doctor:async a=>(await import("../lib/commands/doctor.mjs")).doctorCommand(a),coverage:async a=>(await import("../lib/commands/coverage.mjs")).coverageCommand(a),review:async a=>(await import("../lib/commands/review.mjs")).reviewCommand(a),scan:async a=>(await import("../lib/commands/scan.mjs")).scanCommand(a),graph:async a=>(await import("../lib/commands/graph.mjs")).graphCommand(a),stability:async a=>(await import("../lib/commands/stability.mjs")).stabilityCommand(a),freeze:async a=>(await import("../lib/commands/stability.mjs")).freezeCommand(a),thaw:async a=>(await import("../lib/commands/stability.mjs")).thawCommand(a),why:async a=>(await import("../lib/commands/why.mjs")).whyCommand(a),impact:async a=>(await import("../lib/commands/impact.mjs")).impactCommand(a),scaffold:async a=>(await import("../lib/commands/scaffold.mjs")).scaffoldCommand(a),explain:async a=>(await import("../lib/commands/explain.mjs")).explainCommand(a),test:async a=>(await import("../lib/commands/test.mjs")).testCommand(a),ai:async a=>(await import("../lib/commands/ai.mjs")).aiCommand(a),demo:async a=>(await import("../lib/commands/demo.mjs")).demoCommand(a),log:async a=>(await import("../lib/commands/log.mjs")).logCommand(a),theme:async a=>(await import("../lib/commands/theme.mjs")).themeCommand(a),switch:async a=>(await import("../lib/commands/switch.mjs")).switchCommand(a),upgrade:async a=>(await import("../lib/commands/upgrade.mjs")).upgradeCommand(a),stats:async a=>(await import("../lib/commands/stats.mjs")).statsCommand(a),ask:async a=>(await import("../lib/commands/ask.mjs")).askCommand(a),recap:async a=>(await import("../lib/commands/recap.mjs")).recapCommand(a),uninstall:async a=>(await import("../lib/commands/uninstall.mjs")).uninstallCommand(a),feedback:async a=>(await import("../lib/commands/feedback.mjs")).feedbackCommand(a),telemetry:async a=>(await import("../lib/telemetry.mjs")).telemetryCommand(a)};function L(){const a=Object.keys(u),e=Math.max(...a.map(t=>t.length),8)+1;return Object.entries(u).map(([t,c])=>` ${t.padEnd(e," ")}${c}`).join(`
3
- `)}const A={"Session Memory":["log","ask","switch","recap","stats","theme"],Context:["context","scan","suggest","check","status"],"Code Analysis":["graph","impact","why","coverage","stability","freeze","thaw"],Workflow:["run","sync","watch","implement","doc-gate"],Publishing:["publish","changelog","diff"],Cloud:["login","logout","whoami","cloud","notify","pr-impact"],Quality:["review"],Integration:["ai","ci","coverage","dashboard"],Setup:["init","setup","demo","doctor","generate-skills","upgrade","uninstall"],Advanced:["scaffold","explain","test","monorepo","feedback","telemetry"]};function I(){return Object.entries(A).map(([e,t])=>` ${r(e+":")}
4
- ${t.join(" ")}`).join(`
2
+ (function(){if(process.platform!=="win32"||process.env.WT_SESSION||process.env.ConEmuPID||process.env.TERM_PROGRAM==="vscode")return;const a={"\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(a).join("|"),"g");function i(c){const d=c.write.bind(c);c.write=function(s,...p){if(typeof s=="string")s=s.replace(n,f=>a[f]);else if(Buffer.isBuffer(s)){const f=s.toString("utf8").replace(n,k=>a[k]);s=Buffer.from(f,"utf8")}return d(s,...p)}}i(process.stdout),i(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 e,cyan as t,red as u}from"../lib/ui/output.mjs";const O=S(x(import.meta.url));function A(o){for(const a of[g(o,"..","..","package.json"),g(o,"..","package.json")])try{return JSON.parse($(a,"utf8"))}catch{}return{version:"0.0.0-source"}}const R=A(O),h=R.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",dashboard:"Launch local web dashboard on localhost:7337 \u2014 live contract health, capabilities, agents",login:"Sign in with GitHub \u2014 syncs session memory to the cloud on every log",logout:"Sign out and remove local credentials",whoami:"Show currently logged-in user",cloud:"Sync capability contracts via infernoflow cloud (init | push | pull | status | dashboard)",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)"},l={publish:async o=>(await import("../lib/commands/publish.mjs")).publishCommand(o),diff:async o=>(await import("../lib/commands/diff.mjs")).diffCommand(o),changelog:async o=>(await import("../lib/commands/changelog.mjs")).changelogCommand(o),setup:async o=>(await import("../lib/commands/setup.mjs")).setupCommand(o),init:async o=>(await import("../lib/commands/init.mjs")).initCommand(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),check:async o=>(await import("../lib/commands/check.mjs")).checkCommand(o),status:async o=>(await import("../lib/commands/status.mjs")).statusCommand(o),"pr-impact":async o=>(await import("../lib/commands/prImpact.mjs")).prImpactCommand(o),sync:async o=>(await import("../lib/commands/syncAuto.mjs")).syncCommand(o),run:async o=>(await import("../lib/commands/run.mjs")).runCommand(o),suggest:async o=>(await import("../lib/commands/suggest.mjs")).suggestCommand(o),implement:async o=>(await import("../lib/commands/implement.mjs")).implementCommand(o),context:async o=>(await import("../lib/commands/context.mjs")).contextCommand(o),"doc-gate":async o=>(await import("../lib/commands/docGate.mjs")).docGateCommand(o),"generate-skills":async o=>(await import("../lib/commands/generateSkills.mjs")).generateSkillsCommand(o),dashboard:async o=>(await import("../lib/commands/dashboard.mjs")).dashboardCommand(o),login:async o=>(await import("../lib/commands/login.mjs")).loginCommand(o),logout:async()=>(await import("../lib/commands/login.mjs")).logoutCommand(),whoami:async()=>(await import("../lib/commands/login.mjs")).whoamiCommand(),cloud:async o=>(await import("../lib/commands/cloud.mjs")).cloudCommand(o),watch:async o=>(await import("../lib/commands/watch.mjs")).watchCommand(o),ci:async o=>(await import("../lib/commands/ci.mjs")).ciCommand(o),notify:async o=>(await import("../lib/commands/notify.mjs")).notifyCommand(o),monorepo:async o=>(await import("../lib/commands/monorepo.mjs")).monorepoCommand(o),doctor:async o=>(await import("../lib/commands/doctor.mjs")).doctorCommand(o),coverage:async o=>(await import("../lib/commands/coverage.mjs")).coverageCommand(o),review:async o=>(await import("../lib/commands/review.mjs")).reviewCommand(o),scan:async o=>(await import("../lib/commands/scan.mjs")).scanCommand(o),graph:async o=>(await import("../lib/commands/graph.mjs")).graphCommand(o),stability:async o=>(await import("../lib/commands/stability.mjs")).stabilityCommand(o),freeze:async o=>(await import("../lib/commands/stability.mjs")).freezeCommand(o),thaw:async o=>(await import("../lib/commands/stability.mjs")).thawCommand(o),why:async o=>(await import("../lib/commands/why.mjs")).whyCommand(o),impact:async o=>(await import("../lib/commands/impact.mjs")).impactCommand(o),scaffold:async o=>(await import("../lib/commands/scaffold.mjs")).scaffoldCommand(o),explain:async o=>(await import("../lib/commands/explain.mjs")).explainCommand(o),test:async o=>(await import("../lib/commands/test.mjs")).testCommand(o),ai:async o=>(await import("../lib/commands/ai.mjs")).aiCommand(o),demo:async o=>(await import("../lib/commands/demo.mjs")).demoCommand(o),log:async o=>(await import("../lib/commands/log.mjs")).logCommand(o),theme:async o=>(await import("../lib/commands/theme.mjs")).themeCommand(o),switch:async o=>(await import("../lib/commands/switch.mjs")).switchCommand(o),upgrade:async o=>(await import("../lib/commands/upgrade.mjs")).upgradeCommand(o),stats:async o=>(await import("../lib/commands/stats.mjs")).statsCommand(o),ask:async o=>(await import("../lib/commands/ask.mjs")).askCommand(o),recap:async o=>(await import("../lib/commands/recap.mjs")).recapCommand(o),uninstall:async o=>(await import("../lib/commands/uninstall.mjs")).uninstallCommand(o),feedback:async o=>(await import("../lib/commands/feedback.mjs")).feedbackCommand(o),telemetry:async o=>(await import("../lib/telemetry.mjs")).telemetryCommand(o),contract:async o=>w("contract",b,o),dev:async o=>w("dev",v,o)};async function w(o,a,n){const i=n[1];if(!i||i==="--help"||i==="-h"){console.log(),console.log(` ${m("\u{1F525} infernoflow "+o)} ${e("\u2014 available verbs:")}`),console.log();const d=Math.max(...Object.keys(a).map(s=>s.length))+2;for(const s of Object.keys(a)){const p=y[a[s]]||"";console.log(` ${t(s.padEnd(d))} ${e(p)}`)}console.log(),console.log(` ${e("Run")} ${t(`infernoflow ${o} <verb> --help`)} ${e("for verb-specific options.")}`),console.log();return}const c=a[i];return(!c||!l[c])&&(console.error(u(`
3
+ Unknown ${o} verb: ${i}`)),console.error(e(` Run: infernoflow ${o} (see all verbs)
4
+ `)),process.exit(1)),l[c]([c,...n.slice(2)])}function U(){const o=Object.keys(y),a=Math.max(...o.map(n=>n.length),8)+1;return Object.entries(y).map(([n,i])=>` ${n.padEnd(a," ")}${i}`).join(`
5
+ `)}const b={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"},v={publish:"publish",changelog:"changelog",diff:"diff",dashboard:"dashboard",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"},I={"Memory (top-level)":["log","ask","switch","recap","status"],"Watch (top-level)":["watch"],"Setup (top-level)":["init","doctor"],"Contract (use: infernoflow contract <verb>)":Object.keys(b),"Cloud (use: infernoflow cloud <verb>)":["login","logout","whoami","cloud","notify"],"Dev (use: infernoflow dev <verb>)":Object.keys(v)};function j(){return Object.entries(I).map(([a,n])=>` ${m(a+":")}
6
+ ${n.join(" ")}`).join(`
5
7
 
6
- `)}const y=Object.keys(p).length,O=`
7
- ${r("\u{1F525} infernoflow")} ${o("v"+d)}
8
- ${o("Persistent memory for AI coding sessions")}
8
+ `)}const C=Object.keys(l).length,M=`
9
+ ${m("\u{1F525} infernoflow")} ${e("v"+h)}
10
+ ${e("Persistent memory for AI coding sessions")}
9
11
 
10
- ${r("Usage:")}
12
+ ${m("Usage:")}
11
13
  infernoflow [command] [options]
12
14
 
13
- ${r("Core Commands:")}
14
- ${n("log")} ${o('"..."')} Add to session memory ${o("(--type gotcha|decision|attempt|preference)")}
15
- ${n("ask")} ${o('"..."')} Search your memory by keyword ${o("(gotchas surface first)")}
16
- ${n("switch")} Generate handoff for next AI agent
17
- ${n("recap")} End-of-session health score + unlogged changes
18
- ${n("status")} Contract health at a glance
15
+ ${m("Memory")} ${e("\u2014 the 5-command core")}
16
+ ${t("log")} ${e('"..."')} Add to session memory ${e("(--type gotcha|decision|attempt)")}
17
+ ${t("ask")} ${e('"..."')} Search your memory by keyword ${e("(gotchas surface first)")}
18
+ ${t("switch")} Generate handoff for next AI agent
19
+ ${t("recap")} End-of-session health score + unlogged changes
20
+ ${t("status")} Quick health check
19
21
 
20
- ${r("Getting Started:")}
21
- ${n("setup")} One command to get fully operational
22
- ${n("demo")} Interactive walkthrough ${o("(5 minutes)")}
23
- ${n("doctor")} Diagnose your setup
22
+ ${m("Setup")}
23
+ ${t("init")} 60-second setup ${e("(memory mode by default)")}
24
+ ${t("watch")} Auto-capture mode ${e("(stuck-loops, dep changes, test removals)")}
25
+ ${t("doctor")} Diagnose your setup
24
26
 
25
- ${o("Run")} ${n("infernoflow commands")} ${o("to see all "+y+" commands.")}
26
- ${o("Run")} ${n("infernoflow <command> --help")} ${o("for command-specific options.")}
27
- `;import*as M from"node:fs";import*as R from"node:path";try{const a=R.join(process.cwd(),"inferno");if(M.existsSync(a)){const{observeCommandStart:e}=await import("../lib/learning/observe.mjs"),t=process.argv[2];t&&!t.startsWith("-")&&e(a,t)}}catch{}const[,,s,...j]=process.argv;(!s||s==="--help"||s==="-h")&&(console.log(O),process.exit(0)),(s==="--version"||s==="-v")&&(console.log(d),process.exit(0)),s==="commands"&&(console.log(`
28
- ${r("\u{1F525} infernoflow")} ${o("v"+d)} ${o("\u2014 all "+y+" commands")}
29
- `),console.log(I()),console.log(`
30
- ${o("Run")} ${n("infernoflow <command> --help")} ${o("for options.")}
31
- `),process.exit(0));const E=Object.keys(p);E.includes(s)||(console.error(h(`
32
- Unknown command: ${s}`)),console.error(o("Run: infernoflow commands (see all commands)")),console.error(o(`Run: infernoflow --help (quick start)
33
- `)),process.exit(1));const P=[s,...j];p[s](P).catch(a=>{console.error(h(`
34
- Error: `)+a.message),process.exit(1)});
27
+ ${m("Subsystems")} ${e("\u2014 grouped, run for verbs:")}
28
+ ${t("contract")} Capability contracts ${e("(scan, freeze, impact, scaffold, \u2026)")}
29
+ ${t("cloud")} Cloud sync + accounts ${e("(login, push, pull, status, \u2026)")}
30
+ ${t("dev")} Publishing, dashboards, AI providers ${e("(publish, ai, ci, \u2026)")}
31
+
32
+ ${e("Run")} ${t("infernoflow commands")} ${e("to see all "+C+" commands grouped.")}
33
+ ${e("Run")} ${t("infernoflow <command> --help")} ${e("for command-specific options.")}
34
+ `;import*as E from"node:fs";import*as D from"node:path";try{const o=D.join(process.cwd(),"inferno");if(E.existsSync(o)){const{observeCommandStart:a}=await import("../lib/learning/observe.mjs"),n=process.argv[2];n&&!n.startsWith("-")&&a(o,n)}}catch{}const[,,r,...P]=process.argv;(!r||r==="--help"||r==="-h")&&(console.log(M),process.exit(0)),(r==="--version"||r==="-v")&&(console.log(h),process.exit(0)),r==="commands"&&(console.log(`
35
+ ${m("\u{1F525} infernoflow")} ${e("v"+h)} ${e("\u2014 all "+C+" commands")}
36
+ `),console.log(j()),console.log(`
37
+ ${e("Run")} ${t("infernoflow <command> --help")} ${e("for options.")}
38
+ `),process.exit(0));const G=Object.keys(l);G.includes(r)||(console.error(u(`
39
+ Unknown command: ${r}`)),console.error(e("Run: infernoflow commands (see all commands)")),console.error(e(`Run: infernoflow --help (quick start)
40
+ `)),process.exit(1));const T=[r,...P];l[r](T).catch(o=>{console.error(u(`
41
+ Error: `)+o.message),process.exit(1)});
@@ -1,8 +1,8 @@
1
- import*as i from"node:fs";import*as t from"node:path";import*as L from"node:readline";import{fileURLToPath as se}from"node:url";import{header as re,ok as y,warn as _,done as z,nextSteps as ce,bold as H,cyan as f,yellow as J,gray as F}from"../ui/output.mjs";import{discoverProjectSignals as D,reviewCapabilitiesInteractive as le,writeAdoptionBaseline as ae,buildAdoptionReport as pe,summarizeCapabilities as fe,buildSignalsReport as de}from"./adopt.mjs";import{installCursorHooksArtifacts as ue}from"../cursorHooksInstall.mjs";import{installVsCodeCopilotHooksArtifacts as me}from"../vsCodeCopilotHooksInstall.mjs";const ye=t.dirname(se(import.meta.url));function ge(){return t.resolve(ye,"../../templates")}function R(e,o,s=""){return new Promise(n=>{const c=s?F(` (${s})`):"";e.question(` ${o}${c}: `,a=>{n(a.trim()||s)})})}function q(e,...o){for(const s of o){const n=e.indexOf(s);if(n!==-1&&e[n+1]&&!e[n+1].startsWith("-"))return e[n+1]}return null}function G(e,o,s,n=!1){return i.existsSync(o)&&!s?(n||_("Skipped (exists): "+t.relative(process.cwd(),o)),!1):(i.mkdirSync(t.dirname(o),{recursive:!0}),i.copyFileSync(e,o),n||y("Created: "+f(t.relative(process.cwd(),o))),!0)}function we(e,o,s){i.mkdirSync(o,{recursive:!0});for(const n of i.readdirSync(e,{withFileTypes:!0})){const c=t.join(e,n.name),a=t.join(o,n.name);n.isDirectory()?we(c,a,s):G(c,a,s)}}function je(e,o=!1){const s=t.join(e,"package.json");if(!i.existsSync(s))return;const n=JSON.parse(i.readFileSync(s,"utf8"));n.scripts=n.scripts||{};let c=!1;const a={"inferno:check":"infernoflow check","inferno:status":"infernoflow status","inferno:gate":"infernoflow doc-gate","inferno:impact":"infernoflow pr-impact --json","inferno:sync":"infernoflow sync --auto --json","inferno:run":'infernoflow run "sync check" --provider auto --json',"inferno:hooks":"node scripts/inferno-install-hooks.mjs"};for(const[h,A]of Object.entries(a))n.scripts[h]||(n.scripts[h]=A,c=!0);c&&(i.writeFileSync(s,JSON.stringify(n,null,2)+`
2
- `,"utf8"),o||y("Updated "+f("package.json")+" scripts"))}function U(e){const o=t.join(e,"package.json");if(i.existsSync(o))try{const s=JSON.parse(i.readFileSync(o,"utf8"));if(s.name)return s.name.replace(/[^a-z0-9_-]/gi,"_")}catch{}return t.basename(e)}function he(e,o,s){const n={policyId:o,policyVersion:1,capabilities:s,rules:{docsRequiredOnCapabilityChange:!0,requireScenarioForEachCapability:!0,requireChangelogOnCapabilityChange:!0}};i.writeFileSync(e,JSON.stringify(n,null,2)+`
3
- `)}function ke(e,o){const s={schemaVersion:1,capabilities:o.map(n=>({id:n,title:n.replace(/([A-Z])/g," $1").trim(),since:"0.1.0"}))};i.writeFileSync(e,JSON.stringify(s,null,2)+`
4
- `)}function Se(e,o){i.mkdirSync(e,{recursive:!0});const s={scenarioId:"happy_path",description:"Basic happy-path flow covering all capabilities",capabilitiesCovered:o,steps:o.map(n=>({action:n,expect:`${n} works as expected`}))};i.writeFileSync(t.join(e,"happy_path.json"),JSON.stringify(s,null,2)+`
5
- `)}function V(e,o){const s=`# Changelog \u2014 ${o}
1
+ import*as i from"node:fs";import*as s from"node:path";import*as J from"node:readline";import{fileURLToPath as se}from"node:url";import{header as re,ok as w,warn as E,done as q,nextSteps as ce,bold as L,cyan as d,yellow as $,gray as T}from"../ui/output.mjs";import{discoverProjectSignals as H,reviewCapabilitiesInteractive as le,writeAdoptionBaseline as ae,buildAdoptionReport as pe,summarizeCapabilities as fe,buildSignalsReport as de}from"./adopt.mjs";import{installCursorHooksArtifacts as ue}from"../cursorHooksInstall.mjs";import{installVsCodeCopilotHooksArtifacts as me}from"../vsCodeCopilotHooksInstall.mjs";const ye=s.dirname(se(import.meta.url));function ge(){return s.resolve(ye,"../../templates")}function R(o,e,n=""){return new Promise(t=>{const c=n?T(` (${n})`):"";o.question(` ${e}${c}: `,a=>{t(a.trim()||n)})})}function M(o,...e){for(const n of e){const t=o.indexOf(n);if(t!==-1&&o[t+1]&&!o[t+1].startsWith("-"))return o[t+1]}return null}function D(o,e,n,t=!1){return i.existsSync(e)&&!n?(t||E("Skipped (exists): "+s.relative(process.cwd(),e)),!1):(i.mkdirSync(s.dirname(e),{recursive:!0}),i.copyFileSync(o,e),t||w("Created: "+d(s.relative(process.cwd(),e))),!0)}function we(o,e,n){i.mkdirSync(e,{recursive:!0});for(const t of i.readdirSync(o,{withFileTypes:!0})){const c=s.join(o,t.name),a=s.join(e,t.name);t.isDirectory()?we(c,a,n):D(c,a,n)}}function he(o,e=!1){const n=s.join(o,"package.json");if(!i.existsSync(n))return;const t=JSON.parse(i.readFileSync(n,"utf8"));t.scripts=t.scripts||{};let c=!1;const a={"inferno:check":"infernoflow check","inferno:status":"infernoflow status","inferno:gate":"infernoflow doc-gate","inferno:impact":"infernoflow pr-impact --json","inferno:sync":"infernoflow sync --auto --json","inferno:run":'infernoflow run "sync check" --provider auto --json',"inferno:hooks":"node scripts/inferno-install-hooks.mjs"};for(const[y,k]of Object.entries(a))t.scripts[y]||(t.scripts[y]=k,c=!0);c&&(i.writeFileSync(n,JSON.stringify(t,null,2)+`
2
+ `,"utf8"),e||w("Updated "+d("package.json")+" scripts"))}function G(o){const e=s.join(o,"package.json");if(i.existsSync(e))try{const n=JSON.parse(i.readFileSync(e,"utf8"));if(n.name)return n.name.replace(/[^a-z0-9_-]/gi,"_")}catch{}return s.basename(o)}function ke(o,e,n){const t={policyId:e,policyVersion:1,capabilities:n,rules:{docsRequiredOnCapabilityChange:!0,requireScenarioForEachCapability:!0,requireChangelogOnCapabilityChange:!0}};i.writeFileSync(o,JSON.stringify(t,null,2)+`
3
+ `)}function je(o,e){const n={schemaVersion:1,capabilities:e.map(t=>({id:t,title:t.replace(/([A-Z])/g," $1").trim(),since:"0.1.0"}))};i.writeFileSync(o,JSON.stringify(n,null,2)+`
4
+ `)}function Se(o,e){i.mkdirSync(o,{recursive:!0});const n={scenarioId:"happy_path",description:"Basic happy-path flow covering all capabilities",capabilitiesCovered:e,steps:e.map(t=>({action:t,expect:`${t} works as expected`}))};i.writeFileSync(s.join(o,"happy_path.json"),JSON.stringify(n,null,2)+`
5
+ `)}function z(o,e){const n=`# Changelog \u2014 ${e}
6
6
 
7
7
  ## Unreleased
8
8
 
@@ -11,42 +11,42 @@ import*as i from"node:fs";import*as t from"node:path";import*as L from"node:read
11
11
  ## 0.1.0 \u2014 Initial release
12
12
 
13
13
  - Project initialized with infernoflow
14
- `;i.writeFileSync(e,s)}async function ve(e,o){const{bold:s,cyan:n,gray:c,green:a,yellow:h,red:A}=await import("../ui/output.mjs");console.log(`
15
- `+s("\u{1F525} infernoflow init --lite")),console.log(" "+"\u2500".repeat(50)+`
16
- `),console.log(c(" Lite mode: 3 files, no scripts, no workflows, no hooks.")),console.log(c(" Use `infernoflow upgrade` later to expand to the full setup.\n"));const m=t.join(e,"inferno");i.existsSync(m)&&!o&&(console.log(h(` \u26A0 inferno/ already exists. Use --force to overwrite.
17
- `)),process.exit(0)),i.mkdirSync(m,{recursive:!0});const w=U(e);let S="";if(!process.argv.includes("--yes")&&!process.argv.includes("-y")&&process.stdin.isTTY){const C=L.createInterface({input:process.stdin,output:process.stdout});S=await new Promise(j=>{C.question(c(" What does this project do? (one line, Enter to skip): "),O=>{C.close(),j(O.trim())})})}const v={policyId:w,policyVersion:1,lite:!0,capabilities:[],intent:S||void 0};i.writeFileSync(t.join(m,"contract.json"),JSON.stringify(v,null,2)+`
18
- `),i.writeFileSync(t.join(m,"capabilities.json"),JSON.stringify([],null,2)+`
19
- `),i.writeFileSync(t.join(m,"sessions.jsonl"),"","utf8"),i.writeFileSync(t.join(m,".lite"),"1","utf8"),console.log(a(" \u2714 Created inferno/contract.json")),console.log(a(" \u2714 Created inferno/capabilities.json")),console.log(a(" \u2714 Created inferno/sessions.jsonl")),console.log(),console.log(" "+s("Ready. Start using it:")),console.log(" "+n("infernoflow log")+c(` "what you're building" --type note`)),console.log(" "+n("infernoflow theme")+c(" \u2014 scan your fonts + colors")),console.log(" "+n("infernoflow context")+c(" \u2014 generate AI context to paste")),console.log(" "+n("infernoflow upgrade")+c(" \u2014 expand to full setup when you need it")),console.log()}async function Ce(e,o,s){const{bold:n,cyan:c,gray:a,green:h,yellow:A}=await import("../ui/output.mjs"),m=t.join(e,"inferno"),w=t.join(m,"sessions.jsonl"),S=t.join(m,"config.json");if(i.existsSync(m)&&!o){console.log(`
20
- `+n("\u{1F525} infernoflow")+a(` \u2014 already set up
21
- `)),console.log(" "+h("\u2714")+` inferno/ found
14
+ `;i.writeFileSync(o,n)}async function ve(o,e){const{bold:n,cyan:t,gray:c,green:a,yellow:y,red:k}=await import("../ui/output.mjs");console.log(`
15
+ `+n("\u{1F525} infernoflow init --lite")),console.log(" "+"\u2500".repeat(50)+`
16
+ `),console.log(c(" Lite mode: 3 files, no scripts, no workflows, no hooks.")),console.log(c(" Use `infernoflow upgrade` later to expand to the full setup.\n"));const p=s.join(o,"inferno");i.existsSync(p)&&!e&&(console.log(y(` \u26A0 inferno/ already exists. Use --force to overwrite.
17
+ `)),process.exit(0)),i.mkdirSync(p,{recursive:!0});const g=G(o);let j="";if(!process.argv.includes("--yes")&&!process.argv.includes("-y")&&process.stdin.isTTY){const C=J.createInterface({input:process.stdin,output:process.stdout});j=await new Promise(b=>{C.question(c(" What does this project do? (one line, Enter to skip): "),_=>{C.close(),b(_.trim())})})}const v={policyId:g,policyVersion:1,lite:!0,capabilities:[],intent:j||void 0};i.writeFileSync(s.join(p,"contract.json"),JSON.stringify(v,null,2)+`
18
+ `),i.writeFileSync(s.join(p,"capabilities.json"),JSON.stringify([],null,2)+`
19
+ `),i.writeFileSync(s.join(p,"sessions.jsonl"),"","utf8"),i.writeFileSync(s.join(p,".lite"),"1","utf8"),console.log(a(" \u2714 Created inferno/contract.json")),console.log(a(" \u2714 Created inferno/capabilities.json")),console.log(a(" \u2714 Created inferno/sessions.jsonl")),console.log(),console.log(" "+n("Ready. Start using it:")),console.log(" "+t("infernoflow log")+c(` "what you're building" --type note`)),console.log(" "+t("infernoflow theme")+c(" \u2014 scan your fonts + colors")),console.log(" "+t("infernoflow context")+c(" \u2014 generate AI context to paste")),console.log(" "+t("infernoflow upgrade")+c(" \u2014 expand to full setup when you need it")),console.log()}const be=/^(?:node|npm|npx|yarn|pnpm|bun|git|cd|mkdir|rm|ls|cat|echo|type|dir|copy|del|move|python|python3|pip|go|cargo|java|gradle|mvn|docker|kubectl|curl|wget|ssh|scp|chmod|chown|sudo|brew|apt|yum)\b/i,Ce=/\s(?:&&|\|\||>>|>|<<|<|\|)\s/,Oe=/(?:^|\s)[A-Za-z]:\\|\.\.[\\\/]|[\\\/]bin[\\\/]/;function xe(o){if(!o)return{kind:"empty"};const e=o.replace(/^\s*[>$#]\s+/,"").trim();return e?/[\r\n]/.test(e)?{kind:"multiline",value:e}:be.test(e)?{kind:"command",value:e}:Ce.test(e)?{kind:"command",value:e}:Oe.test(e)?{kind:"command",value:e}:e.length<3?{kind:"tooShort",value:e}:{kind:"ok",value:e}:{kind:"empty"}}async function Ie({yes:o}){if(o||!process.stdin.isTTY)return"";const{gray:e,yellow:n,cyan:t}=await import("../ui/output.mjs"),c=" "+e(`What should the next AI agent know about this project?
20
+ > `),a=y=>new Promise(k=>{const p=J.createInterface({input:process.stdin,output:process.stdout});let g=!1;p.on("SIGINT",()=>{g=!0,p.close(),k(null)}),p.on("close",()=>{g&&k(null)}),p.question(y,j=>{p.close(),k(j)})});for(let y=0;y<2;y++){const k=await a(y===0?c:" "+e("> "));if(k===null)return console.log(),"";const p=xe(k);if(p.kind==="ok")return p.value;if(p.kind==="empty")return"";if(p.kind==="command"){console.log(" "+n("\u26A0")+" That looks like a shell command, not a memory."),console.log(" "+e(" Try a short note like: ")+t('"API returns 202 not 200 on async upload"'));continue}if(p.kind==="multiline"){console.log(" "+n("\u26A0")+" Multi-line paste detected \u2014 log a single gotcha at a time."),console.log(" "+e(" Try one short sentence:"));continue}if(p.kind==="tooShort"){console.log(" "+n("\u26A0")+" Too short to be useful as a memory. Skip with Enter, or try again:");continue}}return""}async function Te(o,e,n){const{bold:t,cyan:c,gray:a,green:y,yellow:k}=await import("../ui/output.mjs"),p=s.join(o,"inferno"),g=s.join(p,"sessions.jsonl"),j=s.join(p,"config.json");if(i.existsSync(p)&&!e){console.log(`
21
+ `+t("\u{1F525} infernoflow")+a(` \u2014 already set up
22
+ `)),console.log(" "+y("\u2714")+` inferno/ found
22
23
  `),console.log(" Quick commands:"),console.log(" "+c('infernoflow log "..."')+a(" \u2014 remember something")),console.log(" "+c("infernoflow switch")+a(" \u2014 handoff to next AI")),console.log(" "+c("infernoflow recap")+a(` \u2014 session summary
23
24
  `)),console.log(a(` For contracts & CI gates: infernoflow init --mode full
24
- `));return}const v=U(e);console.log(`
25
- `+n("\u{1F525} infernoflow")+a(` \u2014 let's get you set up (30 seconds)
25
+ `));return}const v=G(o);console.log(`
26
+ `+t("\u{1F525} infernoflow")+a(` \u2014 let's get you set up (30 seconds)
26
27
  `)),console.log(" Detected: "+c(v)+`
27
- `),i.mkdirSync(m,{recursive:!0}),i.writeFileSync(S,JSON.stringify({project:v,version:"1",mode:"memory",created:new Date().toISOString()},null,2)+`
28
- `,"utf8"),i.existsSync(w)||i.writeFileSync(w,"","utf8");let C="";if(!s&&process.stdin.isTTY){const j=L.createInterface({input:process.stdin,output:process.stdout});C=await new Promise(O=>{j.question(" "+a(`What should the next AI agent know about this project?
29
- > `),N=>{j.close(),O(N.trim())})})}if(C){const j={ts:new Date().toISOString(),agent:"user",type:"gotcha",summary:C,source:"init"};i.appendFileSync(w,JSON.stringify(j)+`
28
+ `),i.mkdirSync(p,{recursive:!0}),i.writeFileSync(j,JSON.stringify({project:v,version:"1",mode:"memory",created:new Date().toISOString()},null,2)+`
29
+ `,"utf8"),i.existsSync(g)||i.writeFileSync(g,"","utf8");const C=await Ie({yes:n});if(C){const b={ts:new Date().toISOString(),agent:"user",type:"gotcha",summary:C,source:"init"};i.appendFileSync(g,JSON.stringify(b)+`
30
30
  `,"utf8"),console.log(`
31
- `+h("\u2714")+" First gotcha logged!")}console.log(`
32
- `+h("\u2714")+` You're set up. Quick commands:
31
+ `+y("\u2714")+" First gotcha logged!")}console.log(`
32
+ `+y("\u2714")+` You're set up. Quick commands:
33
33
  `),console.log(" "+c('infernoflow log "..."')+a(" \u2014 remember something")),console.log(" "+c("infernoflow switch")+a(" \u2014 generate handoff for next AI")),console.log(" "+c("infernoflow recap")+a(` \u2014 session summary
34
34
  `)),console.log(a(` Tip: infernoflow switch --copy puts the handoff on your clipboard.
35
35
  `)),console.log(a(` Want contracts & CI gates? Run: infernoflow init --mode full
36
- `))}async function Te(e){const o=process.cwd(),s=e.includes("--force")||e.includes("-f"),n=e.includes("--yes")||e.includes("-y"),c=e.includes("--adopt"),a=e.find(l=>l.startsWith("--mode="))?.split("=")[1]||(e.indexOf("--mode")!==-1?e[e.indexOf("--mode")+1]:null),h=a==="full"||a==="contract",A=c||e.includes("--template")||e.includes("--cursor-hooks")||e.includes("--vscode-copilot-hooks")||e.includes("--lite");if(!h&&!A){await Ce(o,s,n);return}if(e.includes("--lite")){await ve(o,s);return}const m=e.indexOf("--template"),w=m!==-1?e[m+1]:null;if(w){let l;try{l=await import("../templates/index.mjs")}catch{}const r=l?.getTemplate(w);if(!r){const p=l?l.listTemplates().map(b=>b.name).join(", "):"rest-api, nextjs, cli, graphql, monorepo";_(`Unknown template: ${w}. Available: ${p}`),process.exit(1)}const u=t.join(o,"inferno"),I=t.join(u,"scenarios");i.existsSync(u)||i.mkdirSync(u,{recursive:!0}),i.existsSync(I)||i.mkdirSync(I,{recursive:!0});const E=U(o),g=r.capabilities;i.writeFileSync(t.join(u,"contract.json"),JSON.stringify({policyId:E,policyVersion:1,capabilities:g.map(p=>p.id)},null,2)+`
37
- `),i.writeFileSync(t.join(u,"capabilities.json"),JSON.stringify({capabilities:g.map(p=>({id:p.id,description:p.description,since:new Date().toISOString().slice(0,10),source:`template:${w}`}))},null,2)+`
38
- `);for(const p of g)i.writeFileSync(t.join(I,`${p.id}.json`),JSON.stringify({id:`${p.id}-happy-path`,capability:p.id,description:`Happy path for ${p.description||p.id}`,steps:[{action:"invoke",target:p.id,input:{}},{action:"assert",field:"status",value:"success"}],capabilitiesCovered:[p.id]},null,2)+`
39
- `);V(t.join(u,"CHANGELOG.md"),E),i.writeFileSync(t.join(u,"CONTEXT.md"),`# ${E} \u2014 infernoflow context
36
+ `))}async function _e(o){const e=process.cwd(),n=o.includes("--force")||o.includes("-f"),t=o.includes("--yes")||o.includes("-y"),c=o.includes("--adopt"),a=o.find(l=>l.startsWith("--mode="))?.split("=")[1]||(o.indexOf("--mode")!==-1?o[o.indexOf("--mode")+1]:null),y=a==="full"||a==="contract",k=c||o.includes("--template")||o.includes("--cursor-hooks")||o.includes("--vscode-copilot-hooks")||o.includes("--lite");if(!y&&!k){await Te(e,n,t);return}if(o.includes("--lite")){await ve(e,n);return}const p=o.indexOf("--template"),g=p!==-1?o[p+1]:null;if(g){let l;try{l=await import("../templates/index.mjs")}catch{}const r=l?.getTemplate(g);if(!r){const f=l?l.listTemplates().map(O=>O.name).join(", "):"rest-api, nextjs, cli, graphql, monorepo";E(`Unknown template: ${g}. Available: ${f}`),process.exit(1)}const m=s.join(e,"inferno"),I=s.join(m,"scenarios");i.existsSync(m)||i.mkdirSync(m,{recursive:!0}),i.existsSync(I)||i.mkdirSync(I,{recursive:!0});const N=G(e),h=r.capabilities;i.writeFileSync(s.join(m,"contract.json"),JSON.stringify({policyId:N,policyVersion:1,capabilities:h.map(f=>f.id)},null,2)+`
37
+ `),i.writeFileSync(s.join(m,"capabilities.json"),JSON.stringify({capabilities:h.map(f=>({id:f.id,description:f.description,since:new Date().toISOString().slice(0,10),source:`template:${g}`}))},null,2)+`
38
+ `);for(const f of h)i.writeFileSync(s.join(I,`${f.id}.json`),JSON.stringify({id:`${f.id}-happy-path`,capability:f.id,description:`Happy path for ${f.description||f.id}`,steps:[{action:"invoke",target:f.id,input:{}},{action:"assert",field:"status",value:"success"}],capabilitiesCovered:[f.id]},null,2)+`
39
+ `);z(s.join(m,"CHANGELOG.md"),N),i.writeFileSync(s.join(m,"CONTEXT.md"),`# ${N} \u2014 infernoflow context
40
40
 
41
- > Template: ${w} \u2014 ${r.description}
41
+ > Template: ${g} \u2014 ${r.description}
42
42
 
43
43
  ## Hint
44
44
  ${r.contextHint}
45
45
 
46
- ## Capabilities (${g.length})
47
- ${g.map(p=>`- \`${p.id}\`: ${p.description}`).join(`
46
+ ## Capabilities (${h.length})
47
+ ${h.map(f=>`- \`${f.id}\`: ${f.description}`).join(`
48
48
  `)}
49
- `),r.scripts&&(info("Suggested package.json scripts for this template:"),Object.entries(r.scripts).forEach(([p,b])=>console.log(` ${H(p)}: ${F(b)}`)),console.log()),z(`Initialised from template ${H(f(w))} \u2014 ${H(String(g.length))} capabilities`),console.log(),info(`Run ${f("infernoflow vibe")} to start vibe coding mode`),console.log();return}const S=e.includes("--cursor-hooks"),v=e.includes("--vscode-copilot-hooks"),C=e.includes("--report-json"),j=e.includes("--report-json-only"),O=e.includes("--report-human-only"),N=q(e,"--lang"),W=q(e,"--framework"),Y=q(e,"--project-type"),d=j;j&&O&&(console.error("Error: --report-json-only and --report-human-only cannot be used together."),process.exit(1)),d||re("init");const k=t.join(o,"inferno"),M=t.join(o,".github","workflows");i.existsSync(k)&&!s&&(d&&(console.log(JSON.stringify({ok:!1,error:"inferno_exists",hint:"Use --force to overwrite"},null,2)),process.exit(1)),_("inferno/ already exists. Use --force to overwrite."),console.log(),process.exit(0));const T=U(o),K="CreateTask, ReadTasks, UpdateTask, ToggleComplete, DeleteTask";let P=T,x=K.split(",").map(l=>l.trim());if(c){let r=D(o,{language:N||void 0,framework:W||void 0,projectType:Y||void 0});if(!n&&!j){const g=L.createInterface({input:process.stdin,output:process.stdout}),p=r.developmentProfile||{},b=p.detected||{};console.log(F(` Review inferred development stack (press Enter to accept detected values)
50
- `));const ne=await R(g,"Language",p.language||b.language||"unknown"),ie=await R(g,"Framework",p.framework||b.framework||"unknown"),te=await R(g,"Project type",p.projectType||b.projectType||"unknown");g.close(),r=D(o,{language:ne,framework:ie,projectType:te})}const u=r.capabilities,I=fe(u);j?console.log(JSON.stringify({mode:"adopt",policyId:T,inferredCapabilities:I,components:r.components,displayFields:r.displayFields,externalLibraries:r.externalLibraries,uiLayout:r.uiLayout,styling:r.styling,developmentProfile:r.developmentProfile,apiCalls:r.apiCalls},null,2)):(console.log(),console.log(F(pe(u))),console.log(),console.log(F(de(r))),console.log(),C&&!O&&(console.log(JSON.stringify({mode:"adopt",policyId:T,inferredCapabilities:I,components:r.components,displayFields:r.displayFields,externalLibraries:r.externalLibraries,uiLayout:r.uiLayout,styling:r.styling,developmentProfile:r.developmentProfile,apiCalls:r.apiCalls},null,2)),console.log()));const E=await le(u,n);P=T,x=E.map(g=>g.id)}else if(!n){const l=L.createInterface({input:process.stdin,output:process.stdout});console.log(F(` Press Enter to accept defaults
51
- `)),P=await R(l,"Project / policy name",T),x=(await R(l,"Capabilities (comma-separated)",K)).split(",").map(u=>u.trim()).filter(Boolean),l.close(),console.log()}if(i.mkdirSync(k,{recursive:!0}),c){const l=x.map(u=>({id:u,title:u.replace(/([A-Z])/g," $1").trim()})),r=D(o,{language:N||void 0,framework:W||void 0,projectType:Y||void 0});ae(k,P,l,r),d||(y("Created: "+f("inferno/contract.json")),y("Created: "+f("inferno/capabilities.json")),y("Created: "+f("inferno/scenarios/adoption_baseline.json")),y("Created: "+f("inferno/adoption_profile.json")),y("Created: "+f("inferno/CHANGELOG.md")))}else he(t.join(k,"contract.json"),P,x),d||y("Created: "+f("inferno/contract.json")),ke(t.join(k,"capabilities.json"),x),d||y("Created: "+f("inferno/capabilities.json")),Se(t.join(k,"scenarios"),x),d||y("Created: "+f("inferno/scenarios/happy_path.json")),V(t.join(k,"CHANGELOG.md"),P),d||y("Created: "+f("inferno/CHANGELOG.md"));const $=ge(),B=t.join($,"scripts","inferno-doc-gate.mjs"),X=t.join(o,"scripts","inferno-doc-gate.mjs");G(B,X,s,d);const Q=t.join($,"scripts","inferno-install-hooks.mjs"),Z=t.join(o,"scripts","inferno-install-hooks.mjs");G(Q,Z,s,d);const ee=t.join($,"ci","github-inferno-check.yml"),oe=t.join(M,"infernoflow-check.yml");if(G(ee,oe,s,d),je(o,d),S&&ue({cwd:o,templatesRoot:$,force:s,silent:d,logOk:l=>{d||y(l)},logWarn:l=>{d||_(l)}}),v&&me({cwd:o,templatesRoot:$,force:s,silent:d,logOk:l=>{d||y(l)},logWarn:l=>{d||_(l)}}),c){const l=t.join(k,"context-state.json");let r={};try{r=JSON.parse(i.readFileSync(l,"utf8"))}catch{}const u=D(o,{language:N||void 0,framework:W||void 0,projectType:Y||void 0});r.stack=u.developmentProfile,i.writeFileSync(l,JSON.stringify(r,null,2)+`
52
- `,"utf8"),d||y("Created: "+f("inferno/context-state.json"))}if(!d){z("infernoflow initialized!");const l=t.join(k,"integrations.json");!(process.env.ANTHROPIC_API_KEY||process.env.OPENAI_API_KEY||process.env.GOOGLE_AI_API_KEY||process.env.OPENROUTER_API_KEY||process.env.GEMINI_API_KEY)&&!i.existsSync(l)&&(console.log(),console.log(` ${J("\u{1F4A1}")} ${H("Tip:")} connect an AI provider for explain, why, review, and changelog AI.`),console.log(` ${f("infernoflow ai setup")} \u2014 takes 60 seconds`)),ce([f("infernoflow status")+" \u2014 see your contract at a glance",f("infernoflow check")+" \u2014 validate everything",(c?"Review inferred baseline in ":"Edit ")+J("inferno/capabilities.json")+(c?" and refine IDs/titles":" to describe each capability in detail"),"Add more "+J("inferno/scenarios/*.json")+" files for edge cases","Add "+f("inferno:check")+" to your CI pipeline",...S?["Restart Cursor \u2014 hooks write assistant text to "+J("inferno/CONTEXT.draft.md"),"Promote when ready: "+f("npm run inferno:promote-draft -- --append-notes")]:[],...v?["Restart VS Code \u2014 Copilot hooks append prompts + assistant (from transcript) to "+J("inferno/CONTEXT.draft.md"),"Promote when ready: "+f("npm run inferno:promote-draft -- --append-notes")]:[],...!S&&!v?["Optional: "+f("infernoflow install-cursor-hooks")+" or "+f("infernoflow install-vscode-copilot-hooks")]:[]])}}export{Te as initCommand};
49
+ `),r.scripts&&(info("Suggested package.json scripts for this template:"),Object.entries(r.scripts).forEach(([f,O])=>console.log(` ${L(f)}: ${T(O)}`)),console.log()),q(`Initialised from template ${L(d(g))} \u2014 ${L(String(h.length))} capabilities`),console.log(),info(`Run ${d("infernoflow vibe")} to start vibe coding mode`),console.log();return}const j=o.includes("--cursor-hooks"),v=o.includes("--vscode-copilot-hooks"),C=o.includes("--report-json"),b=o.includes("--report-json-only"),_=o.includes("--report-human-only"),U=M(o,"--lang"),W=M(o,"--framework"),K=M(o,"--project-type"),u=b;b&&_&&(console.error("Error: --report-json-only and --report-human-only cannot be used together."),process.exit(1)),u||re("init");const S=s.join(e,"inferno"),V=s.join(e,".github","workflows");i.existsSync(S)&&!n&&(u&&(console.log(JSON.stringify({ok:!1,error:"inferno_exists",hint:"Use --force to overwrite"},null,2)),process.exit(1)),E("inferno/ already exists. Use --force to overwrite."),console.log(),process.exit(0));const A=G(e),Y="CreateTask, ReadTasks, UpdateTask, ToggleComplete, DeleteTask";let F=A,x=Y.split(",").map(l=>l.trim());if(c){let r=H(e,{language:U||void 0,framework:W||void 0,projectType:K||void 0});if(!t&&!b){const h=J.createInterface({input:process.stdin,output:process.stdout}),f=r.developmentProfile||{},O=f.detected||{};console.log(T(` Review inferred development stack (press Enter to accept detected values)
50
+ `));const ne=await R(h,"Language",f.language||O.language||"unknown"),te=await R(h,"Framework",f.framework||O.framework||"unknown"),ie=await R(h,"Project type",f.projectType||O.projectType||"unknown");h.close(),r=H(e,{language:ne,framework:te,projectType:ie})}const m=r.capabilities,I=fe(m);b?console.log(JSON.stringify({mode:"adopt",policyId:A,inferredCapabilities:I,components:r.components,displayFields:r.displayFields,externalLibraries:r.externalLibraries,uiLayout:r.uiLayout,styling:r.styling,developmentProfile:r.developmentProfile,apiCalls:r.apiCalls},null,2)):(console.log(),console.log(T(pe(m))),console.log(),console.log(T(de(r))),console.log(),C&&!_&&(console.log(JSON.stringify({mode:"adopt",policyId:A,inferredCapabilities:I,components:r.components,displayFields:r.displayFields,externalLibraries:r.externalLibraries,uiLayout:r.uiLayout,styling:r.styling,developmentProfile:r.developmentProfile,apiCalls:r.apiCalls},null,2)),console.log()));const N=await le(m,t);F=A,x=N.map(h=>h.id)}else if(!t){const l=J.createInterface({input:process.stdin,output:process.stdout});console.log(T(` Press Enter to accept defaults
51
+ `)),F=await R(l,"Project / policy name",A),x=(await R(l,"Capabilities (comma-separated)",Y)).split(",").map(m=>m.trim()).filter(Boolean),l.close(),console.log()}if(i.mkdirSync(S,{recursive:!0}),c){const l=x.map(m=>({id:m,title:m.replace(/([A-Z])/g," $1").trim()})),r=H(e,{language:U||void 0,framework:W||void 0,projectType:K||void 0});ae(S,F,l,r),u||(w("Created: "+d("inferno/contract.json")),w("Created: "+d("inferno/capabilities.json")),w("Created: "+d("inferno/scenarios/adoption_baseline.json")),w("Created: "+d("inferno/adoption_profile.json")),w("Created: "+d("inferno/CHANGELOG.md")))}else ke(s.join(S,"contract.json"),F,x),u||w("Created: "+d("inferno/contract.json")),je(s.join(S,"capabilities.json"),x),u||w("Created: "+d("inferno/capabilities.json")),Se(s.join(S,"scenarios"),x),u||w("Created: "+d("inferno/scenarios/happy_path.json")),z(s.join(S,"CHANGELOG.md"),F),u||w("Created: "+d("inferno/CHANGELOG.md"));const P=ge(),X=s.join(P,"scripts","inferno-doc-gate.mjs"),B=s.join(e,"scripts","inferno-doc-gate.mjs");D(X,B,n,u);const Z=s.join(P,"scripts","inferno-install-hooks.mjs"),Q=s.join(e,"scripts","inferno-install-hooks.mjs");D(Z,Q,n,u);const ee=s.join(P,"ci","github-inferno-check.yml"),oe=s.join(V,"infernoflow-check.yml");if(D(ee,oe,n,u),he(e,u),j&&ue({cwd:e,templatesRoot:P,force:n,silent:u,logOk:l=>{u||w(l)},logWarn:l=>{u||E(l)}}),v&&me({cwd:e,templatesRoot:P,force:n,silent:u,logOk:l=>{u||w(l)},logWarn:l=>{u||E(l)}}),c){const l=s.join(S,"context-state.json");let r={};try{r=JSON.parse(i.readFileSync(l,"utf8"))}catch{}const m=H(e,{language:U||void 0,framework:W||void 0,projectType:K||void 0});r.stack=m.developmentProfile,i.writeFileSync(l,JSON.stringify(r,null,2)+`
52
+ `,"utf8"),u||w("Created: "+d("inferno/context-state.json"))}if(!u){q("infernoflow initialized!");const l=s.join(S,"integrations.json");!(process.env.ANTHROPIC_API_KEY||process.env.OPENAI_API_KEY||process.env.GOOGLE_AI_API_KEY||process.env.OPENROUTER_API_KEY||process.env.GEMINI_API_KEY)&&!i.existsSync(l)&&(console.log(),console.log(` ${$("\u{1F4A1}")} ${L("Tip:")} connect an AI provider for explain, why, review, and changelog AI.`),console.log(` ${d("infernoflow ai setup")} \u2014 takes 60 seconds`)),ce([d("infernoflow status")+" \u2014 see your contract at a glance",d("infernoflow check")+" \u2014 validate everything",(c?"Review inferred baseline in ":"Edit ")+$("inferno/capabilities.json")+(c?" and refine IDs/titles":" to describe each capability in detail"),"Add more "+$("inferno/scenarios/*.json")+" files for edge cases","Add "+d("inferno:check")+" to your CI pipeline",...j?["Restart Cursor \u2014 hooks write assistant text to "+$("inferno/CONTEXT.draft.md"),"Promote when ready: "+d("npm run inferno:promote-draft -- --append-notes")]:[],...v?["Restart VS Code \u2014 Copilot hooks append prompts + assistant (from transcript) to "+$("inferno/CONTEXT.draft.md"),"Promote when ready: "+d("npm run inferno:promote-draft -- --append-notes")]:[],...!j&&!v?["Optional: "+d("infernoflow install-cursor-hooks")+" or "+d("infernoflow install-vscode-copilot-hooks")]:[]])}}export{_e as initCommand};
@@ -1,8 +1,8 @@
1
- import*as p from"node:fs";import*as a from"node:path";import{bold as U,cyan as _,gray as l,green as C,yellow as Z,red as b}from"../ui/output.mjs";function T(t){try{return JSON.parse(p.readFileSync(t,"utf8"))}catch{return null}}function J(t,e){p.writeFileSync(t,JSON.stringify(e,null,2)+`
2
- `)}function d(t){return t.split(/[-_]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("")}function P(t){const e=d(t);return e.charAt(0).toLowerCase()+e.slice(1)}function K(t){return t.split(/[-_]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}function Q(t){const e=t.split(/[-_]/);if(e.length===1)return P(t);const o=["auth","login","logout","register","refresh","validate","verify","process","refund","charge","send","fetch","create","update","delete","get","list","search","sync","import","export","scan","check","notify"],r=e[e.length-1],i=e[0];if(o.includes(i)){const n={auth:"authenticate",get:"get",list:"list",send:"send",check:"check",notify:"notify"}[i]||i,u=e.slice(1).map((g,A)=>A===0?g.charAt(0).toUpperCase()+g.slice(1):g).join("");return n+u.charAt(0).toUpperCase()+u.slice(1)}if(o.includes(r)){const s=e.slice(0,-1).map((n,u)=>u===0?n.charAt(0).toUpperCase()+n.slice(1):n).join("");return r+s}return P(t)}function X(t,e,o){if(t?.capabilities?.length){const s=t.capabilities.flatMap(n=>n.codeAnalysis?.sourceFiles||[]).map(n=>a.extname(n));if(s.filter(n=>n===".ts").length>s.filter(n=>n===".js").length)return"ts";if(s.includes(".py"))return"py";if(s.includes(".go"))return"go";if(s.some(n=>n===".js"||n===".mjs"))return"js"}const r=e?.language||e?.lang;return r?r.toLowerCase().replace("javascript","js").replace("typescript","ts"):p.existsSync(a.join(o,"tsconfig.json"))?"ts":p.existsSync(a.join(o,"pyproject.toml"))?"py":p.existsSync(a.join(o,"go.mod"))?"go":"js"}function Y(t,e){if(!t?.capabilities?.length)return null;const o=t.capabilities.flatMap(s=>s.codeAnalysis?.sourceFiles||[]);if(!o.length)return null;const r={};for(const s of o){const n=a.dirname(s).split("/")[0];r[n]=(r[n]||0)+1}const i=Object.entries(r).sort((s,n)=>n[1]-s[1])[0];return i?i[0]:null}function ee(t){if(!t?.capabilities?.length)return[];const e=t.capabilities.flatMap(o=>o.codeAnalysis?.services||[]);return[...new Set(e)]}function te(t,e,o,r,i){const s=d(t),n=`${s}Error`,u=L("ts",i);return`/**
1
+ import*as p from"node:fs";import*as a from"node:path";import{bold as U,cyan as T,gray as l,green as O,yellow as Q,red as b}from"../ui/output.mjs";function P(t){try{return JSON.parse(p.readFileSync(t,"utf8"))}catch{return null}}function z(t,e){p.writeFileSync(t,JSON.stringify(e,null,2)+`
2
+ `)}function d(t){return t.split(/[-_]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("")}function fe(t){return t.replace(/([A-Z]+)([A-Z][a-z])/g,"$1-$2").replace(/([a-z\d])([A-Z])/g,"$1-$2").toLowerCase().replace(/[_\s]+/g,"-").replace(/^-+|-+$/g,"")}function X(t){if(!t||typeof t!="string")return null;const e=t.trim();if(!/^[A-Za-z][A-Za-z0-9 _-]*$/.test(e))return null;const n=e.replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").replace(/([a-z\d])([A-Z])/g,"$1 $2").split(/[-_\s]+/).filter(Boolean);return n.length?n.map(r=>r.charAt(0).toUpperCase()+r.slice(1).toLowerCase()).join(""):null}function R(t){const e=d(t);return e.charAt(0).toLowerCase()+e.slice(1)}function Y(t){return t.split(/[-_]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}function ee(t){const e=t.split(/[-_]/);if(e.length===1)return R(t);const n=["auth","login","logout","register","refresh","validate","verify","process","refund","charge","send","fetch","create","update","delete","get","list","search","sync","import","export","scan","check","notify"],r=e[e.length-1],i=e[0];if(n.includes(i)){const o={auth:"authenticate",get:"get",list:"list",send:"send",check:"check",notify:"notify"}[i]||i,u=e.slice(1).map((g,v)=>v===0?g.charAt(0).toUpperCase()+g.slice(1):g).join("");return o+u.charAt(0).toUpperCase()+u.slice(1)}if(n.includes(r)){const s=e.slice(0,-1).map((o,u)=>u===0?o.charAt(0).toUpperCase()+o.slice(1):o).join("");return r+s}return R(t)}function te(t,e,n){if(t?.capabilities?.length){const s=t.capabilities.flatMap(o=>o.codeAnalysis?.sourceFiles||[]).map(o=>a.extname(o));if(s.filter(o=>o===".ts").length>s.filter(o=>o===".js").length)return"ts";if(s.includes(".py"))return"py";if(s.includes(".go"))return"go";if(s.some(o=>o===".js"||o===".mjs"))return"js"}const r=e?.language||e?.lang;return r?r.toLowerCase().replace("javascript","js").replace("typescript","ts"):p.existsSync(a.join(n,"tsconfig.json"))?"ts":p.existsSync(a.join(n,"pyproject.toml"))?"py":p.existsSync(a.join(n,"go.mod"))?"go":"js"}function ne(t,e){if(!t?.capabilities?.length)return null;const n=t.capabilities.flatMap(s=>s.codeAnalysis?.sourceFiles||[]);if(!n.length)return null;const r={};for(const s of n){const o=a.dirname(s).split("/")[0];r[o]=(r[o]||0)+1}const i=Object.entries(r).sort((s,o)=>o[1]-s[1])[0];return i?i[0]:null}function oe(t){if(!t?.capabilities?.length)return[];const e=t.capabilities.flatMap(n=>n.codeAnalysis?.services||[]);return[...new Set(e)]}function re(t,e,n,r,i){const s=d(t),o=`${s}Error`,u=J("ts",i);return`/**
3
3
  * ${e}
4
4
  *
5
- * ${o}
5
+ * ${n}
6
6
  *
7
7
  * @capability ${t}
8
8
  * @stability experimental
@@ -11,10 +11,10 @@ ${u}
11
11
 
12
12
  // \u2500\u2500 errors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
13
13
 
14
- export class ${n} extends Error {
14
+ export class ${o} extends Error {
15
15
  constructor(message: string, public readonly code?: string) {
16
16
  super(message);
17
- this.name = "${n}";
17
+ this.name = "${o}";
18
18
  }
19
19
  }
20
20
 
@@ -37,12 +37,12 @@ export interface ${s}Result {
37
37
  */
38
38
  export async function ${r}(input: ${s}Input): Promise<${s}Result> {
39
39
  // TODO: implement
40
- throw new ${n}("Not implemented yet");
40
+ throw new ${o}("Not implemented yet");
41
41
  }
42
- `}function ne(t,e,o,r,i){const n=`${d(t)}Error`,u=L("js",i);return`/**
42
+ `}function se(t,e,n,r,i){const o=`${d(t)}Error`,u=J("js",i);return`/**
43
43
  * ${e}
44
44
  *
45
- * ${o}
45
+ * ${n}
46
46
  *
47
47
  * @capability ${t}
48
48
  * @stability experimental
@@ -51,10 +51,10 @@ ${u}
51
51
 
52
52
  // \u2500\u2500 errors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
53
53
 
54
- export class ${n} extends Error {
54
+ export class ${o} extends Error {
55
55
  constructor(message, code) {
56
56
  super(message);
57
- this.name = "${n}";
57
+ this.name = "${o}";
58
58
  this.code = code;
59
59
  }
60
60
  }
@@ -70,12 +70,12 @@ export class ${n} extends Error {
70
70
  */
71
71
  export async function ${r}(input = {}) {
72
72
  // TODO: implement
73
- throw new ${n}("Not implemented yet");
73
+ throw new ${o}("Not implemented yet");
74
74
  }
75
- `}function oe(t,e,o,r){const i=d(t);return`"""
75
+ `}function ie(t,e,n,r){const i=d(t);return`"""
76
76
  ${e}
77
77
 
78
- ${o}
78
+ ${n}
79
79
 
80
80
  capability: ${t}
81
81
  stability: experimental
@@ -97,9 +97,9 @@ async def ${r.replace(/([A-Z])/g,"_$1").toLowerCase().replace(/^_/,"")}(input: d
97
97
  TODO: implement this function.
98
98
  """
99
99
  raise ${i}Error("Not implemented yet")
100
- `}function se(t,e,o,r){const i=t.split("-")[0];return`// Package ${i} implements ${e}.
100
+ `}function ce(t,e,n,r){const i=t.split("-")[0];return`// Package ${i} implements ${e}.
101
101
  //
102
- // ${o}
102
+ // ${n}
103
103
  //
104
104
  // capability: ${t}
105
105
  // stability: experimental
@@ -115,10 +115,10 @@ var Err${d(t)} = errors.New("${t}: operation failed")
115
115
  func ${d(r)}(input map[string]any) (map[string]any, error) {
116
116
  return nil, Err${d(t)}
117
117
  }
118
- `}function L(t,e){if(!e.length)return"";const o=[];if(t==="ts"||t==="js"){const r={stripe:"// import Stripe from 'stripe';",postgres:"// import { Pool } from 'pg';",mysql:"// import mysql from 'mysql2/promise';",redis:"// import { createClient } from 'redis';",s3:"// import { S3Client } from '@aws-sdk/client-s3';",sendgrid:"// import sgMail from '@sendgrid/mail';",twilio:"// import twilio from 'twilio';",openai:"// import OpenAI from 'openai';"};for(const i of e){const s=r[i.toLowerCase()];s&&o.push(s)}}return o.length?o.join(`
118
+ `}function J(t,e){if(!e.length)return"";const n=[];if(t==="ts"||t==="js"){const r={stripe:"// import Stripe from 'stripe';",postgres:"// import { Pool } from 'pg';",mysql:"// import mysql from 'mysql2/promise';",redis:"// import { createClient } from 'redis';",s3:"// import { S3Client } from '@aws-sdk/client-s3';",sendgrid:"// import sgMail from '@sendgrid/mail';",twilio:"// import twilio from 'twilio';",openai:"// import OpenAI from 'openai';"};for(const i of e){const s=r[i.toLowerCase()];s&&n.push(s)}}return n.length?n.join(`
119
119
  `)+`
120
- `:""}function re(t,e,o){return{scenarioId:`${t}-happy-path`,description:`Happy path for ${e}`,capabilitiesCovered:[t],createdAt:new Date().toISOString(),steps:[{step:1,action:`Call ${o} with valid input`,expected:"Returns success result"},{step:2,action:`Call ${o} with invalid input`,expected:"Throws appropriate error"}]}}function ie({id:t,filePath:e,scenarioPath:o,lang:r,fn:i,dryRun:s}){console.log(),console.log(U(` \u{1F30A} ${C(t)}`)),console.log(l(" stability: experimental \u2014 free to evolve")),console.log(),console.log(l(" Generated:")),console.log(` ${C("+")} ${_(e)} ${l(`(${r} source skeleton)`)}`),console.log(` ${C("+")} ${_("inferno/capabilities.json")} ${l("(capability registered)")}`),console.log(` ${C("+")} ${_(o)} ${l("(placeholder scenario)")}`),console.log(),s?console.log(Z(" [dry-run] \u2014 no files were written")):(console.log(l(" Next steps:")),console.log(l(` 1. Implement ${i}() in ${e}`)),console.log(l(" 2. Run: infernoflow scan \u2014 to extract call graph")),console.log(l(" 3. Run: infernoflow graph \u2014 to see dependencies")),console.log(l(" 4. Run: infernoflow check \u2014 to validate contract"))),console.log()}async function le(t){const e=(t||[]).slice(1),o=e.includes("--dry-run"),r=e.includes("--json"),i=e.indexOf("--lang"),s=i!==-1?e[i+1]:null,n=e.indexOf("--dir"),u=n!==-1?e[n+1]:null,g=e.indexOf("--description"),A=g!==-1?e[g+1]:null,M=new Set([i+1,n+1,g+1].filter(f=>f>0)),c=e.find((f,v)=>!f.startsWith("--")&&!M.has(v));c||(console.error(b("\u2717 Usage: infernoflow scaffold <capability-id> [--dir <src>] [--lang ts|js|py|go] [--dry-run] [--json]")),console.error(l(" Example: infernoflow scaffold payment-refund")),process.exit(1)),/^[a-z][a-z0-9-]*$/.test(c)||(console.error(b(`\u2717 Invalid capability ID: "${c}"`)),console.error(l(" Use lowercase kebab-case: payment-refund, user-auth, etc.")),process.exit(1));const h=process.cwd(),D=a.join(h,"inferno"),I=a.join(D,"capabilities.json");p.existsSync(I)||(console.error(b("\u2717 inferno/capabilities.json not found \u2014 run `infernoflow init` first.")),process.exit(1));let w=[];const S=T(I);S&&(w=Array.isArray(S)?S:S.capabilities||[]),w.some(f=>f.id===c)&&(console.error(b(`\u2717 Capability "${c}" already exists in capabilities.json`)),console.error(l(" Use a different ID, or run: infernoflow why "+c)),process.exit(1));const E=T(a.join(D,"scan.json")),q=T(a.join(D,"developer-profile.json")),x=s||X(E,q,h),z=u||Y(E,h)||"src",G={ts:".ts",js:".js",py:".py",go:".go"}[x]||".js",m=K(c),j=A||`TODO: describe ${m}`,y=Q(c),R=ee(E);let $;x==="ts"?$=te(c,m,j,y,R):x==="py"?$=oe(c,m,j,y):x==="go"?$=se(c,m,j,y):$=ne(c,m,j,y,R);const B=P(c)+G,O=a.join(z,B),N=a.join(h,O),k=a.join("inferno","scenarios",`${c}.json`),F=a.join(h,k),H=re(c,m,y),V={id:c,name:m,description:j,stability:"experimental",since:new Date().toISOString().slice(0,10)};if(r){console.log(JSON.stringify({capId:c,name:m,stability:"experimental",lang:x,filePath:O,scenarioPath:k,primaryFn:y,dryRun:o,code:$},null,2));return}if(console.log(l(`
121
- infernoflow scaffold \u2192 ${U(c)}`)),console.log(l(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),!o){const f=a.dirname(N);p.existsSync(f)||p.mkdirSync(f,{recursive:!0}),p.existsSync(N)&&(console.error(b(` \u2717 File already exists: ${O}`)),console.error(l(" Delete it first or choose a different --dir")),process.exit(1)),p.writeFileSync(N,$,"utf8"),w.push(V),J(I,w);const v=a.join(h,"inferno","scenarios");p.existsSync(v)||p.mkdirSync(v,{recursive:!0}),p.existsSync(F)||J(F,H)}const W=$.split(`
120
+ `:""}function le(t,e,n){return{scenarioId:`${t}-happy-path`,description:`Happy path for ${e}`,capabilitiesCovered:[t],createdAt:new Date().toISOString(),steps:[{step:1,action:`Call ${n} with valid input`,expected:"Returns success result"},{step:2,action:`Call ${n} with invalid input`,expected:"Throws appropriate error"}]}}function ae({id:t,filePath:e,scenarioPath:n,lang:r,fn:i,dryRun:s}){console.log(),console.log(U(` \u{1F30A} ${O(t)}`)),console.log(l(" stability: experimental \u2014 free to evolve")),console.log(),console.log(l(" Generated:")),console.log(` ${O("+")} ${T(e)} ${l(`(${r} source skeleton)`)}`),console.log(` ${O("+")} ${T("inferno/capabilities.json")} ${l("(capability registered)")}`),console.log(` ${O("+")} ${T(n)} ${l("(placeholder scenario)")}`),console.log(),s?console.log(Q(" [dry-run] \u2014 no files were written")):(console.log(l(" Next steps:")),console.log(l(` 1. Implement ${i}() in ${e}`)),console.log(l(" 2. Run: infernoflow scan \u2014 to extract call graph")),console.log(l(" 3. Run: infernoflow graph \u2014 to see dependencies")),console.log(l(" 4. Run: infernoflow check \u2014 to validate contract"))),console.log()}async function ue(t){const e=(t||[]).slice(1),n=e.includes("--dry-run"),r=e.includes("--json"),i=e.indexOf("--lang"),s=i!==-1?e[i+1]:null,o=e.indexOf("--dir"),u=o!==-1?e[o+1]:null,g=e.indexOf("--description"),v=g!==-1?e[g+1]:null,M=new Set([i+1,o+1,g+1].filter(f=>f>0));let c=e.find((f,A)=>!f.startsWith("--")&&!M.has(A));c||(console.error(b("\u2717 Usage: infernoflow scaffold <capability-id> [--dir <src>] [--lang ts|js|py|go] [--dry-run] [--json]")),console.error(l(" Example: infernoflow scaffold CreateItem (or payment-refund \u2014 both work)")),process.exit(1));const I=c,F=X(I);F||(console.error(b(`\u2717 Invalid capability ID: "${I}"`)),console.error(l(' Try: CreateItem, payment-refund, user_auth, or "Send Email".')),process.exit(1)),c=F;const h=process.cwd(),D=a.join(h,"inferno"),_=a.join(D,"capabilities.json");p.existsSync(_)||(console.error(b("\u2717 inferno/capabilities.json not found \u2014 run `infernoflow init` first.")),process.exit(1));let w=[];const C=P(_);C&&(w=Array.isArray(C)?C:C.capabilities||[]),w.some(f=>f.id===c||f.id===I)&&(console.error(b(`\u2717 Capability "${c}" already exists in capabilities.json`)),console.error(l(" Use a different ID, or run: infernoflow why "+c)),process.exit(1));const E=P(a.join(D,"scan.json")),q=P(a.join(D,"developer-profile.json")),x=s||te(E,q,h),B=u||ne(E,h)||"src",G={ts:".ts",js:".js",py:".py",go:".go"}[x]||".js",m=Y(c),j=v||`TODO: describe ${m}`,y=ee(c),Z=oe(E);let $;x==="ts"?$=re(c,m,j,y,Z):x==="py"?$=ie(c,m,j,y):x==="go"?$=ce(c,m,j,y):$=se(c,m,j,y,Z);const H=R(c)+G,S=a.join(B,H),N=a.join(h,S),k=a.join("inferno","scenarios",`${c}.json`),L=a.join(h,k),K=le(c,m,y),V={id:c,name:m,description:j,stability:"experimental",since:new Date().toISOString().slice(0,10)};if(r){console.log(JSON.stringify({capId:c,name:m,stability:"experimental",lang:x,filePath:S,scenarioPath:k,primaryFn:y,dryRun:n,code:$},null,2));return}if(console.log(l(`
121
+ infernoflow scaffold \u2192 ${U(c)}`)),console.log(l(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),!n){const f=a.dirname(N);p.existsSync(f)||p.mkdirSync(f,{recursive:!0}),p.existsSync(N)&&(console.error(b(` \u2717 File already exists: ${S}`)),console.error(l(" Delete it first or choose a different --dir")),process.exit(1)),p.writeFileSync(N,$,"utf8"),w.push(V),z(_,w);const A=a.join(h,"inferno","scenarios");p.existsSync(A)||p.mkdirSync(A,{recursive:!0}),p.existsSync(L)||z(L,K)}const W=$.split(`
122
122
  `).slice(0,12).map(f=>" "+f).join(`
123
123
  `);console.log(l(`
124
- Preview:`)),console.log(l(W)),console.log(l(" ...")),ie({id:c,filePath:O,scenarioPath:k,lang:x,fn:y,dryRun:o})}export{le as scaffoldCommand};
124
+ Preview:`)),console.log(l(W)),console.log(l(" ...")),ae({id:c,filePath:S,scenarioPath:k,lang:x,fn:y,dryRun:n})}export{ue as scaffoldCommand};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infernoflow",
3
- "version": "0.40.4",
3
+ "version": "0.41.0",
4
4
  "description": "Persistent memory for AI coding sessions \u2014 captures what agents can't infer from code alone. Works with Copilot, Cursor, Claude, and Windsurf.",
5
5
  "type": "module",
6
6
  "bin": {