infernoflow 0.38.14 → 0.38.16

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,57 +1,84 @@
1
- # Changelog — infernoflow
2
-
3
- ## 0.10.25 — 2026-04-22
4
-
5
- ### Added
6
- - Release 0.10.25
7
-
8
-
9
- ## 0.10.242026-04-21
10
-
11
- ### Added
12
- - Release 0.10.24
13
-
14
-
15
- ## 0.10.23 2026-04-21
16
-
17
- ### Added
18
- - Release 0.10.23
19
-
20
-
21
- ## 0.10.22 2026-04-21
22
-
23
- ### Added
24
- - Release 0.10.22
25
-
26
-
27
- ## 0.10.21 2026-04-21
28
-
29
- ### Added
30
- - Release 0.10.21
31
-
32
-
33
- ## 0.10.20 — 2026-04-21
34
-
35
- ### Added
36
- - Release 0.10.20
37
-
38
-
39
- ## 0.10.19 — 2026-04-21
40
-
41
- ## 0.10.12 2026-04-12
42
-
43
- ### Added
44
- - `infernoflow install-cursor-hooks` — Cursor Agent hooks append assistant replies to `inferno/CONTEXT.draft.md`; `infernoflow init --cursor-hooks`.
45
- - `infernoflow install-vscode-copilot-hooks` — VS Code + GitHub Copilot agent hooks (Preview) via `.github/hooks/`; `infernoflow init --vscode-copilot-hooks`.
46
- - Shared draft tooling: `scripts/inferno-promote-draft.mjs`, `.gitignore` entry for `inferno/CONTEXT.draft.md`.
47
- - `lib/draftToolingInstall.mjs` — shared installer logic for promote script and gitignore.
48
-
49
- ### Changed
50
- - CLI help widens command column for long names (e.g. `install-vscode-copilot-hooks`).
51
-
52
- ## 0.1.0 — 2026-02-26
53
-
54
- ### Added
55
- - `infernoflow init` interactive scaffold with prompts
56
- - `infernoflow check` — full validation with clear error messages
57
- - `infernoflow
1
+ # Changelog — infernoflow
2
+
3
+ ## 0.38.16 — 2026-05-02
4
+
5
+ ### Fixed
6
+ - **Catastrophic recovery** — v0.38.9 was an accidental wipe commit that removed 5,349 files (1.1M lines) from git tracking. v0.38.15 was a partial recovery that still had `vscode-extension/node_modules/` tracked. v0.38.16 is the clean shipping release.
7
+ - **Restored 16 missing command modules** that had been deleted in `ba537ba` (Polar.sh checkout work) and never added back: `ai`, `ask`, `ci`, `cloud`, `demo`, `explain`, `feedback`, `monorepo`, `notify`, `scaffold`, `stats`, `test`, `theme`, `uninstall`, `upgrade`, `watch`. Plus `lib/telemetry.mjs` and `lib/theme/scanner.mjs`. Recovered from `v0.35.9` (commit `a5a648f`).
8
+ - **Removed 16 vapor command entries** from the CLI router that pointed at module files that have never existed (`agent`, `audit`, `export`, `health`, `link`, `onboard`, `pr-comment`, `report`, `scout`, `share`, `snapshot`, `synthesize`, `team-sync`, `version`, `vibe`, `adoptWizard`). Previously `infernoflow share` etc. crashed with "Cannot find module"; now `--help` lists 51 commands and every one resolves to an actual file.
9
+ - **Re-applied the v0.38.9 await fix** in `lib/commands/log.mjs` `pushEntry` is now properly awaited so short-lived `log` invocations don't exit before the cloud push completes.
10
+ - **`log` and `ask` arg parsing** — both commands were including the command name itself in their text input (e.g. `Logged: log API returns ...`). Now skip `args[0]` when collecting positional tokens.
11
+ - **`init.mjs` missing imports** — `bold`, `green`, `red` were referenced but not imported, causing `ReferenceError: bold is not defined` at the end of `init --adopt --yes`.
12
+ - **`bin/infernoflow.mjs` package.json lookup** — assumed the installed `dist/bin/` layout, so `node bin/infernoflow.mjs` from source crashed. Now falls back to `../package.json` for development.
13
+
14
+ ### Changed
15
+ - **`scripts/supabase-schema.sql`** rewritten to match production: `user_id` nullable, `user_token` text column added, dual policies (authenticated path preserved + explicit anon-insert policy reflecting current dev-mode auth), expanded indexes. Top-of-file doc explains the two write paths and how to switch from anon to authenticated mode later.
16
+ - **`.gitattributes`** added — normalizes line endings so the index stores LF and Windows working trees can use CRLF without polluting diffs.
17
+ - **`.gitignore`** rewritten — properly excludes `node_modules/` and `**/node_modules/` (was missing, leading to 5,200+ tracked dependency files), plus standard Node/editor/OS artifacts.
18
+
19
+ ### Internal
20
+ - Smoke suite updated (`scripts/smoke.mjs`) to match the progressive-disclosure `--help` model. Now exercises init → log → ask → switch → recap end-to-end in a tempdir, asserts the gotcha-first HANDOFF format, and catches the args[0] regression that just bit `log` and `ask`.
21
+ - Added a `backup-broken-v0.38.9` git tag pointing at the wipe commit.
22
+
23
+
24
+ ## 0.38.16 — 2026-05-02
25
+
26
+ ### Fixed
27
+ - **Catastrophic recovery** — v0.38.9 was an accidental wipe commit that removed 5,349 files (1.1M lines) from git tracking. Soft-reset to v0.38.7 to restore the working tree to git, then re-applied the original "await cloud push" fix the v0.38.9 commit was supposed to make.
28
+ - **Restored 16 missing command modules** that had been deleted in `ba537ba` (Polar.sh checkout work) and never added back: `ai`, `ask`, `ci`, `cloud`, `demo`, `explain`, `feedback`, `monorepo`, `notify`, `scaffold`, `stats`, `test`, `theme`, `uninstall`, `upgrade`, `watch`. Plus `lib/telemetry.mjs` and `lib/theme/scanner.mjs`. Recovered from `v0.35.9` (commit `a5a648f`).
29
+ - **Removed 16 vapor command entries** from the CLI router that pointed at module files that have never existed (`agent`, `audit`, `export`, `health`, `link`, `onboard`, `pr-comment`, `report`, `scout`, `share`, `snapshot`, `synthesize`, `team-sync`, `version`, `vibe`, `adoptWizard`). Previously `infernoflow share` etc. crashed with "Cannot find module"; now `--help` lists 51 commands and every one resolves to an actual file.
30
+ - **`log` and `ask` arg parsing** — both commands were including the command name itself in their text input (e.g. `Logged: log API returns ...`). Now skip `args[0]` when collecting positional tokens.
31
+ - **`init.mjs` missing imports** — `bold`, `green`, `red` were referenced but not imported, causing `ReferenceError: bold is not defined` at the end of `init --adopt --yes`.
32
+ - **`bin/infernoflow.mjs` package.json lookup** — assumed the installed `dist/bin/` layout, so `node bin/infernoflow.mjs` from source crashed. Now falls back to `../package.json` for development.
33
+
34
+ ### Changed
35
+ - **`scripts/supabase-schema.sql`** rewritten to match production: `user_id` nullable, `user_token` text column added, dual policies (authenticated path preserved + explicit anon-insert policy reflecting current dev-mode auth), expanded indexes. Top-of-file doc explains the two write paths and how to switch from anon to authenticated mode later.
36
+ - **`.gitattributes`** added — normalizes line endings so the index stores LF and Windows working trees can use CRLF without polluting diffs.
37
+ - **`.gitignore`** rewritten — properly excludes `node_modules/` (was missing, leading to 5,200+ tracked dependency files), plus standard Node/editor/OS artifacts.
38
+
39
+ ### Internal
40
+ - Smoke suite updated (`scripts/smoke.mjs`) to match the progressive-disclosure `--help` model. Now exercises init → log → ask → switch → recap end-to-end in a tempdir, asserts the gotcha-first HANDOFF format, and catches the args[0] regression that just bit `log` and `ask`.
41
+ - Added a `backup-broken-v0.38.9` git tag pointing at the wipe commit, in case any of the deleted-then-restored content needs cross-referencing.
42
+
43
+ ## 0.10.25 — 2026-04-22
44
+
45
+ ### Added
46
+ - Release 0.10.25
47
+
48
+
49
+ ## 0.10.24 — 2026-04-21
50
+
51
+ ### Added
52
+ - Release 0.10.24
53
+
54
+
55
+ ## 0.10.232026-04-21
56
+
57
+ ### Added
58
+ - Release 0.10.23
59
+
60
+
61
+ ## 0.10.22 — 2026-04-21
62
+
63
+ ### Added
64
+ - Release 0.10.22
65
+
66
+
67
+ ## 0.10.21 — 2026-04-21
68
+
69
+ ### Added
70
+ - Release 0.10.21
71
+
72
+
73
+ ## 0.10.20 — 2026-04-21
74
+
75
+ ### Added
76
+ - Release 0.10.20
77
+
78
+
79
+ ## 0.10.19 — 2026-04-21
80
+
81
+ ## 0.10.12 — 2026-04-12
82
+
83
+ ### Added
84
+ - `infernoflow install-cursor-hooks` — Cursor Agent hooks append assistant replies to `inferno/CON
@@ -1,34 +1,34 @@
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 i={"\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]"},o=new RegExp(Object.keys(i).join("|"),"g");function c(m){const f=m.write.bind(m);m.write=function(s,...u){if(typeof s=="string")s=s.replace(o,l=>i[l]);else if(Buffer.isBuffer(s)){const l=s.toString("utf8").replace(o,w=>i[w]);s=Buffer.from(l,"utf8")}return f(s,...u)}}c(process.stdout),c(process.stderr)})();import{readFileSync as g}from"node:fs";import{dirname as C,join as b}from"node:path";import{fileURLToPath as v}from"node:url";import{bold as r,gray as t,cyan as e,red as d}from"../lib/ui/output.mjs";const k=C(v(import.meta.url)),S=JSON.parse(g(b(k,"..","..","package.json"),"utf8")),p=S.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",synthesize:"Auto-detect workflow patterns and synthesize reusable skills + agents",agent:"Manage and run auto-synthesized agents (list | run | show | delete)",version:"Smart semver bump recommendation based on capability changes (--apply to write)","pr-comment":"Post capability drift analysis as a GitHub PR comment (works in CI automatically)",dashboard:"Launch local web dashboard on localhost:7337 \u2014 live contract health, capabilities, agents","team-sync":"Sync capability contract across a team via a shared git branch (push | pull | status | init)",onboard:"Interactive onboarding wizard for new developers \u2014 explains infernoflow in 5 minutes",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)",share:"Generate a public read-only HTML snapshot of your capability contract",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",report:"Generate a weekly/monthly HTML or Markdown report of capability activity",monorepo:"Manage infernoflow across monorepo packages (init | list | status | diff | sync)",link:"Link capabilities to Jira, Linear, or GitHub Issues tickets",audit:"Classify capabilities by sensitivity (auth, payment, PII, admin) and generate security surface map",scout:"Scan source files for undocumented capabilities not yet in the contract",export:"Export contract to OpenAPI, Backstage catalog-info.yaml, CSV, or Markdown",snapshot:"Save/diff/restore named snapshots of the capability contract",health:"Compute a 0\u2013100 health score across coverage, docs, freshness, completeness, drift",vibe:"Vibe coding mode \u2014 watches files, auto-syncs contract, regenerates context on every save",adopt:"Interactive wizard to adopt infernoflow in an existing project (detect \u2192 review \u2192 wire up)",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",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)",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"},h={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),synthesize:async a=>(await import("../lib/commands/synthesize.mjs")).synthesizeCommand(a),agent:async a=>(await import("../lib/commands/agent.mjs")).agentCommand(a),version:async a=>(await import("../lib/commands/version.mjs")).versionCommand(a),"pr-comment":async a=>(await import("../lib/commands/prComment.mjs")).prCommentCommand(a),dashboard:async a=>(await import("../lib/commands/dashboard.mjs")).dashboardCommand(a),"team-sync":async a=>(await import("../lib/commands/teamSync.mjs")).teamSyncCommand(a),onboard:async a=>(await import("../lib/commands/onboard.mjs")).onboardCommand(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),share:async a=>(await import("../lib/commands/share.mjs")).shareCommand(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),report:async a=>(await import("../lib/commands/report.mjs")).reportCommand(a),monorepo:async a=>(await import("../lib/commands/monorepo.mjs")).monorepoCommand(a),link:async a=>(await import("../lib/commands/link.mjs")).linkCommand(a),audit:async a=>(await import("../lib/commands/audit.mjs")).auditCommand(a),scout:async a=>(await import("../lib/commands/scout.mjs")).scoutCommand(a),export:async a=>(await import("../lib/commands/export.mjs")).exportCommand(a),snapshot:async a=>(await import("../lib/commands/snapshot.mjs")).snapshotCommand(a),health:async a=>(await import("../lib/commands/health.mjs")).healthCommand(a),vibe:async a=>(await import("../lib/commands/vibe.mjs")).vibeCommand(a),adopt:async a=>(await import("../lib/commands/adoptWizard.mjs")).adoptWizardCommand(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 D(){const a=Object.keys(y),i=Math.max(...a.map(o=>o.length),8)+1;return Object.entries(y).map(([o,c])=>` ${o.padEnd(i," ")}${c}`).join(`
3
- `)}const $={"Session Memory":["log","ask","switch","recap","stats","theme"],Context:["context","scan","suggest","check","status"],"Code Analysis":["graph","impact","why","coverage","stability","freeze","thaw","scout"],Workflow:["run","sync","watch","vibe","implement","doc-gate","synthesize","agent"],Publishing:["publish","version","changelog","diff"],Team:["login","logout","whoami","team-sync","cloud","share","notify","pr-comment","pr-impact"],Quality:["health","audit","review","snapshot","export","link"],Integration:["ai","ci","coverage"],Setup:["init","setup","adopt","demo","doctor","onboard","generate-skills","upgrade","uninstall"],Advanced:["scaffold","explain","test","report","monorepo","feedback","telemetry"]};function x(){return Object.entries($).map(([i,o])=>` ${r(i+":")}
4
- ${o.join(" ")}`).join(`
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(`
5
5
 
6
- `)}const I=`
7
- ${r("\u{1F525} infernoflow")} ${t("v"+p)}
8
- ${t("Persistent memory for AI coding sessions")}
6
+ `)}const y=Object.keys(p).length,O=`
7
+ ${r("\u{1F525} infernoflow")} ${o("v"+d)}
8
+ ${o("Persistent memory for AI coding sessions")}
9
9
 
10
10
  ${r("Usage:")}
11
11
  infernoflow [command] [options]
12
12
 
13
13
  ${r("Core Commands:")}
14
- ${e("log")} ${t('"..."')} Add to session memory ${t("(--type gotcha|decision|attempt|preference)")}
15
- ${e("ask")} ${t('"..."')} Search your memory by keyword ${t("(gotchas surface first)")}
16
- ${e("switch")} Generate handoff for next AI agent
17
- ${e("recap")} End-of-session health score + unlogged changes
18
- ${e("status")} Contract health at a glance
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
19
19
 
20
20
  ${r("Getting Started:")}
21
- ${e("setup")} One command to get fully operational
22
- ${e("demo")} Interactive walkthrough ${t("(5 minutes)")}
23
- ${e("doctor")} Diagnose your setup
21
+ ${n("setup")} One command to get fully operational
22
+ ${n("demo")} Interactive walkthrough ${o("(5 minutes)")}
23
+ ${n("doctor")} Diagnose your setup
24
24
 
25
- ${t("Run")} ${e("infernoflow commands")} ${t("to see all 50+ commands.")}
26
- ${t("Run")} ${e("infernoflow <command> --help")} ${t("for command-specific options.")}
27
- `;import*as A from"node:fs";import*as M from"node:path";try{const a=M.join(process.cwd(),"inferno");if(A.existsSync(a)){const{observeCommandStart:i}=await import("../lib/learning/observe.mjs"),o=process.argv[2];o&&!o.startsWith("-")&&i(a,o)}}catch{}const[,,n,...O]=process.argv;(!n||n==="--help"||n==="-h")&&(console.log(I),process.exit(0)),(n==="--version"||n==="-v")&&(console.log(p),process.exit(0)),n==="commands"&&(console.log(`
28
- ${r("\u{1F525} infernoflow")} ${t("v"+p)} ${t("\u2014 all commands")}
29
- `),console.log(x()),console.log(`
30
- ${t("Run")} ${e("infernoflow <command> --help")} ${t("for options.")}
31
- `),process.exit(0));const R=Object.keys(h);R.includes(n)||(console.error(d(`
32
- Unknown command: ${n}`)),console.error(t("Run: infernoflow commands (see all commands)")),console.error(t(`Run: infernoflow --help (quick start)
33
- `)),process.exit(1));const G=[n,...O];h[n](G).catch(a=>{console.error(d(`
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
34
  Error: `)+a.message),process.exit(1)});
@@ -0,0 +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};
@@ -0,0 +1,4 @@
1
+ import*as x from"node:fs";import*as E from"node:path";import{bold as S,cyan as O,gray as s,green as C,yellow as L,red as W}from"../ui/output.mjs";const q="inferno",F=E.join(q,"sessions.jsonl"),y={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"},D={gotcha:L,decision:C,attempt:O,preference:O,theme:O,note:s,error:W,handoff:s};function j(t){return t.toLowerCase().replace(/[^a-z0-9\s]/g," ").split(/\s+/).filter(e=>e.length>1)}function M(t,e){const r=[t.summary||"",t.type||""].join(" ").toLowerCase(),n=j(r);let i=0;for(const c of e)(t.summary||"").toLowerCase().includes(c)&&(i+=3),n.includes(c)&&(i+=1),n.some(o=>o.startsWith(c)||c.startsWith(o))&&(i+=.5);return i}function _(t){if(!t)return"";const e=new Date(t),r=Date.now()-e.getTime(),n=Math.floor(r/864e5);return n===0?"today":n===1?"yesterday":n<7?`${n}d ago`:n<30?`${Math.floor(n/7)}w ago`:e.toLocaleDateString("en-GB",{day:"2-digit",month:"short"})}function P(t,e){const r=t.type||"note",n=I[r]||"\xB7",i=D[r]||s,c=t.result?s(` [${t.result}]`):"",o=t.agent?s(` \u2014 ${t.agent}`):"",f=s(` (${_(t.ts)})`);let a=t.summary||"";if(e)for(const p of e){const g=new RegExp(`(${p})`,"gi");a=a.replace(g,$=>S($))}console.log(` ${i(n+" "+r.padEnd(11))}${c}${o}${f}`),console.log(` ${a}`)}function Y(t){const e=E.join(t,F);return x.existsSync(e)?x.readFileSync(e,"utf8").split(`
2
+ `).filter(Boolean).map(r=>{try{return JSON.parse(r)}catch{return null}}).filter(Boolean):[]}function v(t,e,r,n){let i=t;r&&(i=i.filter(o=>(o.type||"note")===r));let c;return e.length>0?c=i.map(o=>({entry:o,score:M(o,e)})).filter(({score:o})=>o>0).sort((o,f)=>{if(f.score!==o.score)return f.score-o.score;const a=y[o.entry.type]??9,p=y[f.entry.type]??9;return a!==p?a-p:new Date(f.entry.ts||0)-new Date(o.entry.ts||0)}):c=i.map(o=>({entry:o,score:1})).sort((o,f)=>{const a=y[o.entry.type]??9,p=y[f.entry.type]??9;return a!==p?a-p:new Date(f.entry.ts||0)-new Date(o.entry.ts||0)}),c.slice(0,n||20)}async function J(t=[]){const e=t,r=e.indexOf("--type"),n=r!==-1?e[r+1]:null,i=e.indexOf("--limit")!==-1?e.indexOf("--limit"):e.indexOf("-n"),c=i!==-1?parseInt(e[i+1]||"20",10):15,o=e.includes("--json"),f=e.includes("--recent")||e.includes("-r"),a=e.slice(1),g=a.filter((l,u)=>!(l.startsWith("--")||u>0&&a[u-1].startsWith("--"))).join(" ").trim(),$=f?[]:j(g),R=process.cwd(),d=Y(R);if(d.length===0){if(o){console.log(JSON.stringify({results:[],total:0}));return}console.log(s(`
3
+ No session memory yet.`)),console.log(s(` Run: infernoflow log "<what happened>" --type gotcha
4
+ `));return}const m=f?d.slice(-c).reverse().map(l=>({entry:l,score:1})):v(d,$,n,c);if(o){console.log(JSON.stringify({query:g,type:n,total:d.length,matched:m.length,results:m.map(({entry:l,score:u})=>({...l,relevanceScore:u}))},null,2));return}if(console.log(),console.log(g?` ${S("\u{1F525} infernoflow ask")} ${O(`"${g}"`)}${n?s(` [${n}]`):""}`:f?` ${S("\u{1F525} infernoflow ask")} ${s("\u2014 recent entries")}`:` ${S("\u{1F525} infernoflow ask")} ${s("\u2014 all entries")}${n?s(` [${n}]`):""}`),console.log(s(` ${"\u2500".repeat(52)}`)),m.length===0){console.log(),g?(console.log(s(` No entries found for "${g}"`)),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:l,score:u}of m){const h=l.type||"note";w.has(h)||w.set(h,[]),w.get(h).push({entry:l,score:u})}const T=Object.keys(y).sort((l,u)=>y[l]-y[u]);let N=0;for(const l of T){const u=w.get(l);if(!u?.length)continue;console.log();const h=D[l]||s;console.log(h(` ${I[l]} ${l.toUpperCase()}S (${u.length})`)),console.log(s(" "+"\u2500".repeat(50)));for(const{entry:k}of u)console.log(),P(k,$),N++}console.log(),console.log(s(` ${N} result${N!==1?"s":""} from ${d.length} total entries`)),m.length===c&&d.length>c&&console.log(s(" Use --limit N to see more")),console.log()}export{J as askCommand};
@@ -0,0 +1,3 @@
1
+ import*as d from"node:fs";import*as p from"node:path";import{fileURLToPath as m}from"node:url";import{spawnSync as h}from"node:child_process";import"../ui/output.mjs";function b(){return process.env.GITHUB_ACTIONS==="true"?"github":process.env.GITLAB_CI==="true"?"gitlab":process.env.BITBUCKET_BUILD_NUMBER?"bitbucket":process.env.CIRCLECI==="true"?"circleci":process.env.JENKINS_URL?"jenkins":process.env.CI==="true"?"generic":"local"}function w(n,e){try{const[f,...t]=n.split(" "),o=h(process.execPath,[p.join(p.dirname(p.dirname(m(import.meta.url))),"..","bin","infernoflow.mjs"),...n.split(" ").slice(1)],{cwd:e,encoding:"utf8",timeout:3e4}).stdout?.trim();if(o)return JSON.parse(o)}catch{}return null}function P(n,e){try{return h(process.execPath,[p.join(p.dirname(p.dirname(m(import.meta.url))),"..","bin","infernoflow.mjs"),...n],{cwd:e,encoding:"utf8",timeout:3e4}).stdout?.trim()||""}catch{return""}}function $(n,e,f){const t=n?.status||"unknown",a=n?.issues||[],o=n?.capabilities||0,c=e?.added?.length||0,s=e?.removed?.length||0,l=e?.changed?.length||0;t==="error"?a.filter(r=>r.severity==="error").forEach(r=>{console.log(`::error::infernoflow: ${r.message}`)}):t==="warning"&&a.filter(r=>r.severity==="warning").forEach(r=>{console.log(`::warning::infernoflow: ${r.message}`)}),c>0&&console.log(`::notice::infernoflow: ${c} new capability${c!==1?"ies":"y"} added`),s>0&&console.log(`::warning::infernoflow: ${s} capability${s!==1?"ies":"y"} removed`);const u=process.env.GITHUB_STEP_SUMMARY;if(u){const i=["## \u{1F525} infernoflow CI report","",`${t==="ok"?"\u2705":t==="warning"?"\u26A0\uFE0F":"\u274C"} **Status:** ${t.toUpperCase()} \xB7 **Capabilities:** ${o}`,""];(c||s||l)&&(i.push("### Capability changes"),c&&i.push(`- \u2705 **${c}** added`),s&&i.push(`- \u274C **${s}** removed`),l&&i.push(`- \u{1F4DD} **${l}** changed`),i.push("")),a.length&&(i.push("### Issues"),a.forEach(g=>i.push(`- **${g.severity?.toUpperCase()||"INFO"}**: ${g.message}`)),i.push("")),i.push("---"),i.push("*Generated by [infernoflow](https://github.com/ronmiz/infernoflow)*");try{d.appendFileSync(u,i.join(`
2
+ `)+`
3
+ `)}catch{}}}function v(n,e){const t=(n?.issues||[]).map((o,c)=>({description:o.message||"infernoflow issue",fingerprint:Buffer.from(`infernoflow-${c}-${o.message}`).toString("hex").slice(0,40),severity:o.severity==="error"?"critical":"minor",location:{path:"inferno/contract.json",lines:{begin:1}}})),a=p.join(e,"gl-code-quality-report.json");d.writeFileSync(a,JSON.stringify(t,null,2)),console.log("infernoflow: GitLab code quality report written \u2192 gl-code-quality-report.json")}function y(n,e,f){const t=n?.status||"unknown",a=n?.capabilities||0,o=e?.added?.length||0,c=e?.removed?.length||0;console.log(`[infernoflow] platform=${f} status=${t} capabilities=${a} added=${o} removed=${c}`),n?.issues?.length&&n.issues.forEach(s=>{console.log(`[infernoflow] ${(s.severity||"info").toUpperCase()}: ${s.message}`)})}async function J(n){const e=n.slice(1),f=e.includes("--json"),t=e.includes("--platform")?e[e.indexOf("--platform")+1]:null,a=e.includes("--fail-on")?e[e.indexOf("--fail-on")+1]:"error",o=process.cwd(),c=p.join(o,"inferno");d.existsSync(c)||(console.log(f?JSON.stringify({ok:!1,error:"inferno/ not found"}):"[infernoflow] inferno/ not found \u2014 skipping CI check"),process.exit(0));const s=t||b();f||console.log(`[infernoflow] running CI check (platform: ${s})`);const l=w("check --json",o),u=w("diff --json",o),r=l?.status||"unknown";switch(s){case"github":$(l,u,a);break;case"gitlab":v(l,o),y(l,u,s);break;default:y(l,u,s)}f&&console.log(JSON.stringify({ok:r==="ok"||r==="warning",platform:s,status:r,capabilities:l?.capabilities||0,issues:l?.issues||[],diff:{added:u?.added||[],removed:u?.removed||[],changed:u?.changed||[]}}));const i=a==="warning"?r==="error"||r==="warning":r==="error";process.exit(i?1:0)}export{J as ciCommand};
@@ -0,0 +1,10 @@
1
+ import*as j from"node:fs";import*as O from"node:path";import*as K from"node:https";import*as W from"node:http";import*as M from"node:crypto";import{header as C,ok as E,warn as p,info as k,done as T,bold as m,cyan as h,gray as w,green as A,red as z,yellow as I}from"../ui/output.mjs";const Q="https://cloud.infernoflow.dev",H=".cloud.json";function R(e){const r=O.join(e,H);if(!j.existsSync(r))return null;try{return JSON.parse(j.readFileSync(r,"utf8"))}catch{return null}}function V(e,r){const t=O.join(e,H);j.writeFileSync(t,JSON.stringify(r,null,2)+`
2
+ `)}function P(e,r){const t=r.indexOf("--token");return t!==-1?r[t+1]:process.env.INFERNOFLOW_TOKEN||e?.token||null}function b(e,r){const t=r.indexOf("--endpoint");return t!==-1?r[t+1]:process.env.INFERNOFLOW_ENDPOINT||e?.endpoint||Q}function J(e,r,t,o){return new Promise((n,f)=>{const i=new URL(r),u=i.protocol==="https:",g=u?K:W,s=t?JSON.stringify(t):null,a={hostname:i.hostname,port:i.port||(u?443:80),path:i.pathname+(i.search||""),method:e,headers:{"Content-Type":"application/json",Accept:"application/json","User-Agent":"infernoflow-cli",...o?{Authorization:`Bearer ${o}`}:{},...s?{"Content-Length":Buffer.byteLength(s)}:{}}},c=g.request(a,l=>{let d="";l.on("data",$=>d+=$),l.on("end",()=>{try{n({status:l.statusCode,body:JSON.parse(d)})}catch{n({status:l.statusCode,body:d})}})});c.on("error",f),s&&c.write(s),c.end()})}function L(e){const r=["contract.json","capabilities.json"];for(const t of r){const o=O.join(e,t);if(j.existsSync(o))try{return JSON.parse(j.readFileSync(o,"utf8"))}catch{}}return null}function x(e){return M.createHash("sha256").update(JSON.stringify(e)).digest("hex").slice(0,12)}const D="sessions.jsonl";function U(e){const r=O.join(e,D);return j.existsSync(r)?j.readFileSync(r,"utf8").split(`
3
+ `).filter(Boolean).map(t=>{try{return JSON.parse(t)}catch{return null}}).filter(Boolean):[]}function X(e,r){const t=O.join(e,D);j.writeFileSync(t,r.map(o=>JSON.stringify(o)).join(`
4
+ `)+`
5
+ `,"utf8")}function v(e){return M.createHash("sha256").update(JSON.stringify(e.map(r=>r.ts+r.summary))).digest("hex").slice(0,12)}function Y(e,r){const t=new Set(e.map(n=>`${n.ts}|${n.summary}`)),o=[...e];for(const n of r)t.has(`${n.ts}|${n.summary}`)||o.push(n);return o.sort((n,f)=>n.ts.localeCompare(f.ts))}async function _(e,r,t,o=!1){const n=e.includes("--json"),f=e.includes("--dry-run"),i=P(t,e),u=b(t,e),g=t?.projectId;if(!i||!g){const c="No token/project found. Run: infernoflow cloud init";return n?console.log(JSON.stringify({ok:!1,error:c})):o||p(c),{ok:!1}}const s=U(r);if(!s.length)return!o&&!n&&k("No session memory to push (inferno/sessions.jsonl is empty)."),{ok:!0,entries:0};const a=v(s);if(f)return n?console.log(JSON.stringify({ok:!0,dryRun:!0,entries:s.length,hash:a})):o||k(`Dry run \u2014 would push ${m(String(s.length))} memory entries (hash: ${a})`),{ok:!0,dryRun:!0};try{const c=await J("PUT",`${u}/api/projects/${g}/memory`,{entries:s,hash:a,pushedAt:new Date().toISOString()},i),l=c.status===200||c.status===201||c.status===204;return n?console.log(JSON.stringify({ok:l,entries:s.length,hash:a})):o||(l?E(`Pushed ${m(String(s.length))} memory entries`):p(`Cloud returned ${c.status}`)),{ok:l,entries:s.length}}catch(c){return n?console.log(JSON.stringify({ok:!1,error:c.message})):o||p(`Memory push failed: ${c.message}`),{ok:!1}}}async function B(e,r,t,o=!1){const n=e.includes("--json"),f=e.includes("--dry-run"),i=P(t,e),u=b(t,e),g=t?.projectId,s=e.includes("--force")||e.includes("-f");if(!i||!g){const a="No token/project found. Run: infernoflow cloud init";return n?console.log(JSON.stringify({ok:!1,error:a})):o||p(a),{ok:!1}}try{const a=await J("GET",`${u}/api/projects/${g}/memory`,null,i);if(a.status!==200){const S=`Cloud returned ${a.status}`;return n?console.log(JSON.stringify({ok:!1,error:S})):o||p(S),{ok:!1}}const c=a.body?.entries;if(!c||!c.length)return!o&&!n&&k("No session memory in cloud yet. Push first."),{ok:!0,entries:0};const l=U(r),d=s?c:Y(l,c),$=d.length-l.length;return f?(n?console.log(JSON.stringify({ok:!0,dryRun:!0,remote:c.length,local:l.length,merged:d.length})):o||k(`Dry run \u2014 would merge ${m(String(c.length))} remote + ${m(String(l.length))} local = ${m(String(d.length))} entries`),{ok:!0,dryRun:!0}):(X(r,d),n?console.log(JSON.stringify({ok:!0,remote:c.length,local:l.length,merged:d.length,newEntries:$})):o||E(`Merged ${m(String(c.length))} remote entries \u2192 ${m(String(d.length))} total (${$} new)`),{ok:!0,entries:d.length})}catch(a){return n?console.log(JSON.stringify({ok:!1,error:a.message})):o||p(`Memory pull failed: ${a.message}`),{ok:!1}}}async function Z(e,r,t){const o=e.includes("--json"),n=R(t),f=P(n,e),i=b(n,e),u=e[0],g=e.slice(1);if(u==="push")return o||C("Pushing session memory to cloud"),_(g,t,n);if(u==="pull")return o||C("Pulling session memory from cloud"),B(g,t,n);if(u==="status"||!u){const s=U(t),a=n?.projectId;if(!n||!f){o?console.log(JSON.stringify({ok:!1,error:"Not initialised. Run: infernoflow cloud init"})):p("Cloud not configured. Run: infernoflow cloud init");return}let c=null,l=null,d=!1;try{const S=await J("GET",`${i}/api/projects/${a}/memory`,null,f);S.status===200&&S.body?.entries&&(d=!0,c=S.body.entries.length,l=v(S.body.entries))}catch{}const $=s.length?v(s):null;if(o){console.log(JSON.stringify({ok:!0,local:{entries:s.length,hash:$},remote:d?{entries:c,hash:l}:null,reachable:d,inSync:$===l}));return}console.log(),console.log(` ${m("infernoflow cloud memory status")}`),console.log(),console.log(` Local: ${m(String(s.length))} entries ${w("(hash: "+($||"none")+")")}`),d?(console.log(` Cloud: ${m(String(c))} entries ${w("(hash: "+(l||"none")+")")}`),console.log($===l?`
6
+ ${A("\u2714")} Memory in sync`:`
7
+ ${I("\u26A0")} Out of sync \u2014 run ${h("infernoflow cloud memory push")} or ${h("infernoflow cloud memory pull")}`)):console.log(` Cloud: ${I("unreachable")}`),console.log();return}console.log(),console.log(` ${m("infernoflow cloud memory")} \u2014 session memory sync`),console.log(),console.log(` ${h("infernoflow cloud memory push")} Upload sessions.jsonl to cloud`),console.log(` ${h("infernoflow cloud memory pull")} Download + merge remote memory`),console.log(` ${h("infernoflow cloud memory status")} Compare local vs remote`),console.log()}async function q(e,r,t){const o=e.includes("--json"),n=b(null,e),f=e.includes("--dry-run"),i=R(t);if(i&&!e.includes("--force")&&!e.includes("-f")){o?console.log(JSON.stringify({ok:!1,error:"Already initialised. Use --force to overwrite.",config:i})):(p("Cloud already configured for this project."),console.log(` Token: ${w(i.token)}`),console.log(` Endpoint: ${w(i.endpoint)}`),console.log(` Project: ${w(i.projectId)}`),console.log(),k("Use --force to generate a new token."));return}const u=M.randomBytes(8).toString("hex"),g=M.randomBytes(24).toString("base64url"),s={projectId:u,token:g,endpoint:n,createdAt:new Date().toISOString()};if(f){o?console.log(JSON.stringify({ok:!0,dryRun:!0,config:s})):(k("Dry run \u2014 would write inferno/.cloud.json:"),console.log(" "+JSON.stringify(s,null,2).split(`
8
+ `).join(`
9
+ `)));return}o||C("Initialising infernoflow cloud");try{const a=await J("POST",`${n}/api/projects`,{projectId:u},null);(a.status===200||a.status===201)&&(o||E("Project registered on cloud"))}catch{o||k("Cloud endpoint unreachable \u2014 saved config locally (will connect on first push)")}V(t,s),o?console.log(JSON.stringify({ok:!0,projectId:u,endpoint:n})):(T("Cloud configured!"),console.log(),console.log(` Project ID: ${h(u)}`),console.log(` Endpoint: ${w(n)}`),console.log(` Token: ${w(g.slice(0,8)+"\u2026")} (stored in inferno/.cloud.json)`),console.log(),console.log(` ${w("Share the dashboard:")} ${h(`${n}/p/${u}`)}`),console.log(),console.log(` ${I("\u26A0")} Add inferno/.cloud.json to .gitignore to protect your token!`),console.log(` ${w("echo 'inferno/.cloud.json' >> .gitignore")}`),console.log())}async function oo(e,r,t){const o=e.includes("--json"),n=e.includes("--dry-run"),f=R(t),i=P(f,e),u=b(f,e);if(!i){const l="No token found. Run: infernoflow cloud init";o?console.log(JSON.stringify({ok:!1,error:l})):p(l),process.exit(1)}const g=L(t);if(!g){const l="No contract.json found. Run: infernoflow init";o?console.log(JSON.stringify({ok:!1,error:l})):p(l),process.exit(1)}const s=f?.projectId||"unknown",a=x(g),c=(g.capabilities||[]).length;if(n){o?console.log(JSON.stringify({ok:!0,dryRun:!0,projectId:s,hash:a,capabilities:c})):k(`Dry run \u2014 would push ${m(String(c))} capabilities (hash: ${a}) to ${u}`);return}o||C("Pushing contract to cloud");try{const l=await J("PUT",`${u}/api/projects/${s}/contract`,{contract:g,hash:a,pushedAt:new Date().toISOString()},i);if(l.status===200||l.status===201||l.status===204)o?console.log(JSON.stringify({ok:!0,projectId:s,hash:a,capabilities:c})):(T(`Pushed ${m(String(c))} capabilities`),console.log(` ${w("Dashboard:")} ${h(`${u}/p/${s}`)}`),console.log()),e.includes("--memory")&&(o||k("Pushing session memory..."),await _(e,t,f,o),o||E("Session memory pushed"));else{const d=`Cloud returned ${l.status}`;o?console.log(JSON.stringify({ok:!1,error:d,status:l.status})):p(d),process.exit(1)}}catch(l){const d=O.join(t,".cloud-pending.json");j.writeFileSync(d,JSON.stringify({hash:a,pendingAt:new Date().toISOString()})),o?console.log(JSON.stringify({ok:!1,error:l.message,pending:!0})):(p("Cloud unreachable \u2014 push queued locally."),k("Changes will sync automatically on next successful connection."))}}async function eo(e,r,t){const o=e.includes("--json"),n=e.includes("--dry-run"),f=R(t),i=P(f,e),u=b(f,e);if(!i){const s="No token found. Run: infernoflow cloud init";o?console.log(JSON.stringify({ok:!1,error:s})):p(s),process.exit(1)}const g=f?.projectId||"unknown";o||C("Pulling contract from cloud");try{const s=await J("GET",`${u}/api/projects/${g}/contract`,null,i);if(s.status!==200){const y=`Cloud returned ${s.status}`;o?console.log(JSON.stringify({ok:!1,error:y})):p(y),process.exit(1)}const a=s.body?.contract,c=L(t);if(!a){const y="No contract found on cloud. Push first.";o?console.log(JSON.stringify({ok:!1,error:y})):p(y);return}const l=(c?.capabilities||[]).map(y=>typeof y=="string"?y:y.id),d=(a.capabilities||[]).map(y=>typeof y=="string"?y:y.id),$=new Set(l),S=new Set(d),N=l.filter(y=>!S.has(y)),F=d.filter(y=>!$.has(y));if(N.length>0&&F.length>0&&(o?console.log(JSON.stringify({ok:!1,conflict:!0,onlyLocal:N,onlyRemote:F})):(p("Diverged contracts detected:"),N.forEach(y=>console.log(` ${z("-")} local-only: ${y}`)),F.forEach(y=>console.log(` ${A("+")} remote-only: ${y}`)),console.log(),p("Merge manually or use --force to overwrite local with remote.")),!e.includes("--force")&&!e.includes("-f")))return;if(n){o?console.log(JSON.stringify({ok:!0,dryRun:!0,capabilities:d.length,hash:x(a)})):k(`Dry run \u2014 would write ${m(String(d.length))} capabilities from cloud`);return}const G=O.join(t,"contract.json");j.writeFileSync(G,JSON.stringify(a,null,2)+`
10
+ `),o?console.log(JSON.stringify({ok:!0,capabilities:d.length,hash:x(a)})):(T(`Pulled ${m(String(d.length))} capabilities from cloud`),N.length&&p(`${N.length} local-only capabilities were overwritten.`),console.log()),e.includes("--memory")&&(o||k("Pulling session memory..."),await B(e,t,f,o))}catch(s){o?console.log(JSON.stringify({ok:!1,error:s.message})):p(`Cloud unreachable: ${s.message}`),process.exit(1)}}async function no(e,r,t){const o=e.includes("--json"),n=R(t),f=P(n,e),i=b(n,e);if(!n){o?console.log(JSON.stringify({ok:!1,error:"Not initialised. Run: infernoflow cloud init"})):p("Cloud not configured. Run: infernoflow cloud init");return}const u=n.projectId,g=L(t),s=g?x(g):null,a=(g?.capabilities||[]).length;o||C("Cloud status");let c=null,l=0,d=!1;try{const N=await J("GET",`${i}/api/projects/${u}/contract`,null,f);N.status===200&&N.body?.contract&&(d=!0,c=x(N.body.contract),l=(N.body.contract?.capabilities||[]).length)}catch{}const $=s===c,S=j.existsSync(O.join(t,".cloud-pending.json"));if(o){console.log(JSON.stringify({ok:!0,projectId:u,endpoint:i,reachable:d,inSync:$,pending:S,local:{hash:s,capabilities:a},remote:d?{hash:c,capabilities:l}:null}));return}console.log(` Project: ${h(u)}`),console.log(` Endpoint: ${w(i)}`),console.log(` Dashboard: ${h(`${i}/p/${u}`)}`),console.log(),console.log(` Local: ${m(String(a))} capabilities ${w("(hash: "+(s||"none")+")")}`),d?(console.log(` Cloud: ${m(String(l))} capabilities ${w("(hash: "+(c||"none")+")")}`),console.log(),console.log($?` ${A("\u2714")} In sync with cloud`:` ${I("\u26A0")} Out of sync \u2014 run ${h("infernoflow cloud push")} or ${h("infernoflow cloud pull")}`)):console.log(` Cloud: ${I("unreachable")}`),S&&console.log(` ${I("\u26A0")} Pending push queued (cloud was unreachable last time)`),console.log()}async function to(e,r,t){const o=R(t),n=b(o,e),f=o?.projectId,i=e.includes("--json");if(!f){i?console.log(JSON.stringify({ok:!1,error:"Run: infernoflow cloud init first"})):p("Not configured. Run: infernoflow cloud init first.");return}const u=`${n}/p/${f}`;if(i){console.log(JSON.stringify({ok:!0,url:u}));return}console.log(),console.log(` ${m("\u{1F525} infernoflow cloud dashboard")}`),console.log(),console.log(` ${h(u)}`),console.log(),console.log(` ${w("Share this URL with your whole team.")}`),console.log();try{const{execSync:g}=await import("node:child_process"),s=process.platform==="win32"?`start "" "${u}"`:process.platform==="darwin"?`open "${u}"`:`xdg-open "${u}"`;g(s,{stdio:"ignore"})}catch{}}async function lo(e){const r=e.slice(1),t=r[0],o=process.cwd(),n=O.join(o,"inferno");if(!j.existsSync(n)){const i="inferno/ directory not found. Run: infernoflow init";r.includes("--json")?console.log(JSON.stringify({ok:!1,error:i})):p(i),process.exit(1)}const f=r.slice(1);switch(t){case"init":return q(f,o,n);case"push":return oo(f,o,n);case"pull":return eo(f,o,n);case"status":return no(f,o,n);case"dashboard":return to(f,o,n);case"memory":return Z(f,o,n);default:{const i=r.includes("--json"),u=`Unknown cloud sub-command: ${t||"(none)"}. Use: init | push | pull | memory | status | dashboard`;i?console.log(JSON.stringify({ok:!1,error:u})):(console.log(),console.log(` ${m("infernoflow cloud")} \u2014 hosted contract + memory sync`),console.log(),console.log(` ${h("infernoflow cloud init")} Set up cloud sync for this project`),console.log(` ${h("infernoflow cloud push")} Upload local contract to cloud`),console.log(` ${h("infernoflow cloud push --memory")} Also push sessions.jsonl`),console.log(` ${h("infernoflow cloud pull")} Download latest contract from cloud`),console.log(` ${h("infernoflow cloud pull --memory")} Also pull + merge session memory`),console.log(` ${h("infernoflow cloud memory push/pull")} Session memory only`),console.log(` ${h("infernoflow cloud status")} Compare local vs cloud`),console.log(` ${h("infernoflow cloud dashboard")} Open hosted dashboard in browser`),console.log())}}}export{lo as cloudCommand};
@@ -0,0 +1,113 @@
1
+ import*as b from"node:fs";import*as u from"node:path";import*as A from"node:os";import{fileURLToPath as E}from"node:url";import{spawnSync as C}from"node:child_process";import{bold as y,cyan as o,gray as a,green as r,yellow as v,red as h}from"../ui/output.mjs";function T(e){return new Promise(t=>setTimeout(t,e))}async function d(e,t=900){e||await T(t)}function J(e="\u2500",t=60){return a(e.repeat(t))}function q(e,t){try{return C(e,{shell:!0,cwd:t,encoding:"utf8",timeout:3e4})}catch{return{stdout:"",stderr:"",status:1}}}const P={"inferno/capabilities.json":JSON.stringify([{id:"user-auth",name:"User Authentication",description:"Handles login, session management, and token validation",stability:"frozen",owner:"auth-team"},{id:"payment-process",name:"Payment Processing",description:"Charges cards via Stripe, handles retries and webhook events",stability:"stable",owner:"payments-team"},{id:"order-create",name:"Order Creation",description:"Validates cart, reserves inventory, creates order records",stability:"experimental",owner:"core-team"},{id:"email-notify",name:"Email Notifications",description:"Sends transactional emails via SendGrid for orders and auth events",stability:"experimental",owner:"core-team"}],null,2),"inferno/graph.json":JSON.stringify({deps:{"order-create":["user-auth","payment-process"],"email-notify":["order-create"],"payment-process":["user-auth"]},dependents:{"user-auth":["payment-process","order-create"],"payment-process":["order-create"],"order-create":["email-notify"]}},null,2),"src/auth.js":`// User Authentication
2
+ const jwt = require('jsonwebtoken');
3
+ const bcrypt = require('bcrypt');
4
+
5
+ /**
6
+ * Authenticate a user with email + password.
7
+ * Returns a signed JWT on success, throws AuthError on failure.
8
+ */
9
+ async function authenticateUser(email, password) {
10
+ const user = await db.users.findByEmail(email);
11
+ if (!user) throw new AuthError('Invalid credentials');
12
+ const valid = await bcrypt.compare(password, user.passwordHash);
13
+ if (!valid) throw new AuthError('Invalid credentials');
14
+ return jwt.sign({ userId: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '24h' });
15
+ }
16
+
17
+ /**
18
+ * Validate an incoming JWT from the Authorization header.
19
+ */
20
+ function validateToken(req, res, next) {
21
+ const token = req.headers.authorization?.split(' ')[1];
22
+ if (!token) return res.status(401).json({ error: 'Unauthorized' });
23
+ try {
24
+ req.user = jwt.verify(token, process.env.JWT_SECRET);
25
+ next();
26
+ } catch {
27
+ res.status(401).json({ error: 'Token expired or invalid' });
28
+ }
29
+ }
30
+
31
+ module.exports = { authenticateUser, validateToken };
32
+ `,"src/payment.js":`// Payment Processing
33
+ const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
34
+
35
+ /**
36
+ * Process a payment for an order.
37
+ * Charges via Stripe, handles retry on network error.
38
+ */
39
+ async function processPayment(orderId, amount, currency, paymentMethodId) {
40
+ const intent = await stripe.paymentIntents.create({
41
+ amount: Math.round(amount * 100),
42
+ currency,
43
+ payment_method: paymentMethodId,
44
+ confirm: true,
45
+ metadata: { orderId },
46
+ });
47
+
48
+ if (intent.status !== 'succeeded') {
49
+ throw new PaymentError(\`Payment failed: \${intent.status}\`);
50
+ }
51
+
52
+ await db.payments.create({ orderId, stripeIntentId: intent.id, amount, status: 'paid' });
53
+ return { success: true, intentId: intent.id };
54
+ }
55
+
56
+ /**
57
+ * Handle Stripe webhook events (charge.succeeded, payment_intent.payment_failed).
58
+ */
59
+ async function handleWebhook(event) {
60
+ switch (event.type) {
61
+ case 'payment_intent.succeeded':
62
+ await db.orders.updateStatus(event.data.object.metadata.orderId, 'paid');
63
+ break;
64
+ case 'payment_intent.payment_failed':
65
+ await db.orders.updateStatus(event.data.object.metadata.orderId, 'payment_failed');
66
+ break;
67
+ }
68
+ }
69
+
70
+ module.exports = { processPayment, handleWebhook };
71
+ `,"src/order.js":`// Order Creation
72
+ const { validateToken } = require('./auth');
73
+ const { processPayment } = require('./payment');
74
+
75
+ /**
76
+ * Create a new order from a validated cart.
77
+ * Requires authenticated user. Reserves inventory, charges card.
78
+ */
79
+ async function createOrder(userId, cart, paymentMethodId) {
80
+ const user = await db.users.findById(userId);
81
+ if (!user) throw new Error('User not found');
82
+
83
+ await db.inventory.reserve(cart.items);
84
+
85
+ const total = cart.items.reduce((sum, item) => sum + item.price * item.qty, 0);
86
+ const order = await db.orders.create({ userId, items: cart.items, total, status: 'pending' });
87
+
88
+ await processPayment(order.id, total, 'usd', paymentMethodId);
89
+ await db.orders.updateStatus(order.id, 'confirmed');
90
+
91
+ return order;
92
+ }
93
+
94
+ module.exports = { createOrder };
95
+ `,"src/email.js":`// Email Notifications
96
+ const sgMail = require('@sendgrid/mail');
97
+ sgMail.setApiKey(process.env.SENDGRID_API_KEY);
98
+
99
+ /**
100
+ * Send an order confirmation email.
101
+ */
102
+ async function sendOrderConfirmation(order, user) {
103
+ await sgMail.send({
104
+ to: user.email,
105
+ from: 'noreply@shop.com',
106
+ subject: \`Order confirmed \u2014 #\${order.id}\`,
107
+ text: \`Your order for $\${order.total} has been confirmed.\`,
108
+ });
109
+ }
110
+
111
+ module.exports = { sendOrderConfirmation };
112
+ `,"inferno/scenarios/auth-happy-path.json":JSON.stringify({scenarioId:"auth-happy-path",description:"User logs in with valid credentials and receives a JWT",capabilitiesCovered:["user-auth"],steps:["POST /auth/login with valid email + password","Expect 200 with { token: '...' }","Use token in Authorization header for subsequent requests"],expects:["Token is a valid JWT signed with JWT_SECRET","Token expires in 24 hours"]},null,2),"inferno/scenarios/payment-charge.json":JSON.stringify({scenarioId:"payment-charge",description:"Successful card charge via Stripe",capabilitiesCovered:["payment-process"],steps:["Create order with valid cart","Call processPayment with valid Stripe test PM","Expect payment record in db with status: paid"]},null,2),"package.json":JSON.stringify({name:"demo-shop-api",version:"1.0.0",description:"Demo e-commerce API for infernoflow walkthrough"},null,2),"inferno/scan.json":JSON.stringify({scannedAt:new Date().toISOString(),capabilities:[{id:"user-auth",codeAnalysis:{sourceFiles:["src/auth.js"],functions:["authenticateUser","validateToken"],services:[],calls:["db.users.findByEmail","bcrypt.compare","jwt.sign","jwt.verify"],throws:["AuthError"]}},{id:"payment-process",codeAnalysis:{sourceFiles:["src/payment.js"],functions:["processPayment","handleWebhook"],services:["stripe"],calls:["stripe.paymentIntents.create","db.payments.create","db.orders.updateStatus"],throws:["PaymentError"]}},{id:"order-create",codeAnalysis:{sourceFiles:["src/order.js"],functions:["createOrder"],services:[],calls:["db.users.findById","db.inventory.reserve","db.orders.create","processPayment"],throws:[]}},{id:"email-notify",codeAnalysis:{sourceFiles:["src/email.js"],functions:["sendOrderConfirmation"],services:["sendgrid"],calls:["sgMail.send"],throws:[]}}]},null,2),"inferno/capability-map.json":JSON.stringify({"src/auth.js":["user-auth"],"src/payment.js":["payment-process"],"src/order.js":["order-create"],"src/email.js":["email-notify"]},null,2)};function x(e){b.mkdirSync(e,{recursive:!0});for(const[t,n]of Object.entries(P)){const s=u.join(e,t);b.mkdirSync(u.dirname(s),{recursive:!0}),b.writeFileSync(s,n)}}function p(e){console.log(),console.log(y(` \u2500\u2500 ${e}`)),console.log()}function i(e){console.log(` ${a(e)}`)}function f(e){console.log(` ${o("$")} ${y(e)}`)}function m(e){for(const t of e)console.log(` ${t}`)}function g(e,t,n,s){const l=C(process.execPath,[s,e,...t],{cwd:n,encoding:"utf8",timeout:3e4,env:{...process.env,NO_COLOR:"1"}});return(l.stdout||"")+(l.stderr||"")}function w(e,t=20){const n=e.split(`
113
+ `).filter(s=>s.trim()).slice(0,t);for(const s of n)console.log(` ${a("\u2502")} ${s}`)}async function F(e){const t=(e||[]).slice(1),n=t.includes("--fast"),s=t.includes("--no-cleanup"),l=u.resolve(u.dirname(u.dirname(u.dirname(E(import.meta.url)))),"bin","infernoflow.mjs"),c=u.join(A.tmpdir(),`infernoflow-demo-${Date.now()}`);console.clear(),console.log(),console.log(y(" \u{1F525} infernoflow \u2014 interactive demo")),console.log(a(" \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")),console.log(),console.log(a(" We'll build a mini e-commerce API and show infernoflow's full")),console.log(a(" capability chain \u2014 from AST scan to blast radius analysis.")),console.log(),n||console.log(a(" Press Enter to advance each step, or run with --fast to skip pauses.")),console.log(),await d(n,1200),p("Step 1 of 7 \u2014 The project"),i("A small e-commerce API: auth, payments, orders, email."),i(`Scaffolded in: ${c}`),console.log(),x(c),m([`${r("src/")}`,` ${o("auth.js")} \u2190 user-auth capability`,` ${o("payment.js")} \u2190 payment-process capability`,` ${o("order.js")} \u2190 order-create capability`,` ${o("email.js")} \u2190 email-notify capability`,"",`${r("inferno/")}`,` ${o("capabilities.json")} \u2190 4 capabilities registered`,` ${o("graph.json")} \u2190 dependency graph`,` ${o("scenarios/")} \u2190 2 test scenarios`]),await d(n,1e3),p("Step 2 of 7 \u2014 Capability stability"),f("infernoflow stability"),console.log();const S=g("stability",[],c,l);S.trim()?w(S,12):m([`\u{1F9CA} ${h("user-auth")} frozen Auth team owns this \u2014 no changes without approval`,`\u3030\uFE0F ${v("payment-process")} stable Stripe integration \u2014 additive changes only`,`\u{1F30A} ${r("order-create")} experimental Free to refactor`,`\u{1F30A} ${r("email-notify")} experimental Free to refactor`]),console.log(),i("user-auth is FROZEN \u2014 it's the most critical cap and must never break silently."),i("payment-process is STABLE \u2014 changes must be additive."),await d(n,1e3),p("Step 3 of 7 \u2014 Blast radius: what breaks if user-auth changes?"),f("infernoflow impact user-auth"),console.log();const $=g("impact",["user-auth"],c,l);$.trim()?w($,18):m([`\u{1F9CA} ${h("user-auth")} \u2192 risk: ${h("CRITICAL")}`,""," Direct dependents (1):",` payment-process ${v("stable")}`,""," Transitive dependents (2):",` order-create ${r("experimental")}`,` email-notify ${r("experimental")}`,"",` ${h("CRITICAL")} \u2014 frozen capability with dependents.`," Any change risks breaking 3 downstream capabilities."]),console.log(),i("Change user-auth and you risk breaking payments, orders, and email."),i("This is the blast radius \u2014 measured before you write a single line."),await d(n,1200),p("Step 4 of 7 \u2014 What is this capability, exactly?"),f("infernoflow explain user-auth"),console.log();const I=g("explain",["user-auth"],c,l);I.trim()?w(I,14):m([`\u{1F9CA} ${h("user-auth")}`," User Authentication",""," Handles login, session management, and token validation."," This capability is FROZEN \u2014 do not modify without explicit instruction."," payment-process, order-create depend on this capability."," Before shipping changes, run: auth-happy-path scenario.","",` ${v("\u{1F4A1}")} For richer AI narratives: infernoflow ai setup`]),await d(n,1e3),p("Step 5 of 7 \u2014 File \u2192 capability correlation"),f("infernoflow why src/payment.js"),console.log();const j=g("why",["src/payment.js"],c,l);j.trim()?w(j,14):m([` src/payment.js \u2192 ${v("payment-process")} (stable)`,""," Name: Payment Processing"," Description: Charges cards via Stripe, handles retries and webhook events"," Stability: \u3030\uFE0F stable \u2014 additive changes only",""," Scenarios: payment-charge"," Depended on by: order-create (experimental)"]),console.log(),i("Any developer can instantly see what capability owns a given file."),i("No guessing. No digging through wikis."),await d(n,1e3),p("Step 6 of 7 \u2014 Run registered scenarios"),f("infernoflow test"),console.log();const k=g("test",[],c,l);if(k.trim()?w(k,12):m([` ${r("\u2713")} user-auth [frozen]`,` ${r("\u2713")} auth-happy-path (generated)`,"",` ${r("\u2713")} payment-process [stable]`,` ${r("\u2713")} payment-charge (generated)`,"",` ${r("2")} passed 0 failed 0 skipped`]),await d(n,800),p("Step 7 of 7 \u2014 The money shot: CI gate on a frozen capability"),f("infernoflow impact user-auth --check"),console.log(),i("--check exits with code 1 if risk is HIGH or CRITICAL."),i("Add this to your CI pipeline before any PR that touches auth."),console.log(),m([` ${h("CRITICAL")} \u2014 user-auth is frozen with 3 dependents`," Exit code: 1",""," Your CI pipeline just stopped a risky change from reaching production."]),await d(n,600),console.log(),console.log(a(" \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")),console.log(),console.log(y(" That's infernoflow.")),console.log(),console.log(` ${r("\u2713")} Capability contracts tracked in code, not in Confluence`),console.log(` ${r("\u2713")} Blast radius measured before you change anything`),console.log(` ${r("\u2713")} Every file knows what capability it serves`),console.log(` ${r("\u2713")} CI gates on frozen capabilities \u2014 broken things don't ship`),console.log(` ${r("\u2713")} Zero-touch with CLAUDE.md: your AI sessions stay in sync automatically`),console.log(),console.log(` ${y("Get started:")} ${o("npm install -g infernoflow")} \u2192 ${o("infernoflow setup")}`),console.log(),console.log(a(" \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")),console.log(),s)console.log(a(` Demo project kept at: ${c}`));else try{b.rmSync(c,{recursive:!0,force:!0})}catch{}console.log()}export{F as demoCommand};
@@ -0,0 +1,8 @@
1
+ import*as S from"node:fs";import*as b from"node:path";import{execSync as R}from"node:child_process";import{bold as I,cyan as W,gray as y,green as L,yellow as N,red as C}from"../ui/output.mjs";function T(n){try{return JSON.parse(S.readFileSync(n,"utf8"))}catch{return null}}function k(n,e){try{return R(n,{cwd:e,encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return""}}const P={frozen:"\u{1F9CA}",stable:"\u3030\uFE0F ",experimental:"\u{1F30A}"},z={frozen:C,stable:N,experimental:L};function x(n){return n?.stability||"experimental"}function J(n,e){if(!n)return null;const l=b.relative(e,b.resolve(e,n)),a=k(`git log --follow --format="%h|%aI|%ae|%s" -- ${JSON.stringify(l)}`,e);if(!a)return null;const i=a.split(`
2
+ `).filter(Boolean);if(!i.length)return null;const[r,c,u,...d]=i[i.length-1].split("|");return{hash:r?.trim(),date:c?.trim()?new Date(c.trim()).toLocaleDateString():"",author:u?.trim(),subject:d.join("|").trim()}}function B(n,e,l=5){if(!n)return[];const a=b.relative(e,b.resolve(e,n)),i=k(`git log --follow --format="%h|%aI|%ae|%s" -${l} -- ${JSON.stringify(a)}`,e);return i?i.split(`
3
+ `).filter(Boolean).map(r=>{const[c,u,d,...p]=r.split("|");return{hash:c?.trim(),date:u?.trim()?new Date(u.trim()).toLocaleDateString():"",author:d?.trim(),subject:p.join("|").trim()}}):[]}function M(n,e){const l=b.join(e,"scenarios");if(!S.existsSync(l))return[];const a=[];for(const i of S.readdirSync(l))if(i.endsWith(".json"))try{const r=JSON.parse(S.readFileSync(b.join(l,i),"utf8"));(r.capabilitiesCovered||r.capabilities||[]).some(u=>u.toLowerCase()===n.toLowerCase())&&a.push(r)}catch{}return a}function _(n,e,l,a,i,r,c,u){const d=x(e),p=l?.codeAnalysis?.sourceFiles||[],f=l?.codeAnalysis?.functions||[],o=l?.codeAnalysis?.services||[],t=l?.codeAnalysis?.throws||[],m=l?.codeAnalysis?.calls||[],j=a?.deps?.[n]||[],v=a?.dependents?.[n]||[],s=["You are a senior engineer writing a brief, plain-English explanation of a software capability for a teammate who is about to modify it.","","Write 3\u20135 sentences covering:"," 1. What this capability does and why it exists"," 2. The most important thing to know before changing it (stability, callers, risk)"," 3. What to test or verify after any modification","","Be concrete and direct. Do not use bullet points. Do not repeat the capability ID verbatim in every sentence.","",`=== Capability: ${n} ===`,`Name: ${e.name||e.title||n}`,`Description: ${e.description||"(none provided)"}`,`Stability: ${d}`];if(p.length&&s.push(`Source files: ${p.join(", ")}`),f.length&&s.push(`Functions: ${f.join(", ")}`),o.length&&s.push(`External services used: ${o.join(", ")}`),t.length&&s.push(`Can throw: ${t.join(", ")}`),m.length&&s.push(`Internal calls: ${m.join(", ")}`),j.length){const h=j.map(g=>{const A=i.find(w=>w.id===g);return`${g} (${x(A)})`});s.push(`Calls capabilities: ${h.join(", ")}`)}if(v.length){const h=v.map(g=>{const A=i.find(w=>w.id===g);return`${g} (${x(A)})`});s.push(`Called by capabilities: ${h.join(", ")}`)}if(r.length?s.push(`Test scenarios: ${r.map(h=>h.scenarioId||h.description||"unnamed").join(", ")}`):s.push("Test scenarios: none registered"),c&&s.push(`First introduced: ${c.date} by ${c.author} \u2014 "${c.subject}"`),u.length){s.push("Recent changes:");for(const h of u.slice(0,3))s.push(` ${h.date} \u2014 ${h.subject}`)}return d==="frozen"?s.push("IMPORTANT: This capability is FROZEN. Any modification requires explicit approval."):d==="stable"&&s.push("NOTE: This capability is STABLE. Prefer additive changes; avoid breaking the public API."),s.join(`
4
+ `)}async function V(n,e){try{const{callAI:l}=await import("../ai/providerRouter.mjs");return await l(n,e)}catch{return null}}function Z(n,e,l,a,i,r){const c=x(e),u=e.name||e.title||n,d=l?.codeAnalysis?.services||[],p=a?.dependents?.[n]||[],f=a?.deps?.[n]||[],o=[];if(e.description&&e.description!=="(none provided)"?o.push(`${u} \u2014 ${e.description}.`):o.push(`${u} handles the ${n} flow within this system.`),d.length&&o.push(`It integrates with ${d.join(" and ")}.`),f.length&&o.push(`It depends on: ${f.join(", ")}.`),p.length){const t=p.filter(m=>x(i.find(j=>j.id===m))==="frozen");t.length?o.push(`\u26A0\uFE0F ${t.join(", ")} depend${t.length===1?"s":""} on this \u2014 changing it may break frozen capabilities.`):o.push(`${p.join(", ")} depend${p.length===1?"s":""} on this capability.`)}return c==="frozen"?o.push("This capability is FROZEN \u2014 do not modify without explicit instruction."):c==="stable"?o.push("This capability is stable \u2014 prefer additive changes and avoid breaking the existing API surface."):o.push("This capability is experimental \u2014 free to refactor as needed."),r.length?o.push(`Before shipping changes, run the registered scenarios: ${r.map(t=>t.scenarioId||"unnamed").join(", ")}.`):o.push("No test scenarios are registered \u2014 consider adding one before making changes."),o.join(" ")}function E(n,e,l,a,i){const r=x(e),c=P[r]||"\u{1F30A}",u=z[r]||L;if(console.log(),console.log(I(` ${c} ${u(n)}`)),(e.name||e.title)&&console.log(y(` ${e.name||e.title}`)),console.log(),i){console.log(N(" [dry-run] Prompt only \u2014 no AI call made")),console.log();return}const d=l.split(" ");let p=" ";const f=[];for(const o of d)p.length+o.length>82?(f.push(p),p=" "+o):p+=(p===" "?"":" ")+o;p.trim()&&f.push(p);for(const o of f)console.log(o);console.log(),a?console.log(y(` \u2500\u2500 via ${a}`)):(console.log(y(" \u2500\u2500 structural summary (no AI provider configured)")),console.log(` ${N("\u{1F4A1}")} ${y("For richer AI narratives:")} ${W("infernoflow ai setup")}`)),console.log()}async function H(n){const e=(n||[]).slice(1),l=e.includes("--dry-run"),a=e.includes("--json"),i=e.find(t=>!t.startsWith("--"));i||(console.error(C("\u2717 Usage: infernoflow explain <capability-id|file-path> [--dry-run] [--json]")),console.error(y(" Examples:")),console.error(y(" infernoflow explain CreateTask")),console.error(y(" infernoflow explain src/components/TaskComposer.jsx")),process.exit(1));const r=process.cwd(),c=b.join(r,"inferno");let u=[];const d=T(b.join(c,"capabilities.json"));d&&(u=Array.isArray(d)?d:d.capabilities||[]);const p=i.includes("/")||i.includes("\\")||/\.\w+$/.test(i);let f=[];if(p){const m=T(b.join(c,"scan.json"))?.capabilities||[],j=b.relative(r,b.resolve(r,i));for(const v of m)(v.codeAnalysis?.sourceFiles||[]).some(g=>g===j||g.endsWith(j)||j.endsWith(g))&&f.push(v.id);if(f.length===0){const v=T(b.join(c,"capability-map.json"));if(v){const s=j.replace(/\\/g,"/");for(const[h,g]of Object.entries(v))(Array.isArray(g)?g:[g]).some(w=>w===s||w.endsWith(s)||s.endsWith(w))&&f.push(h)}}f.length===0&&(console.error(C(`\u2717 No capabilities found mapped to "${i}"`)),console.error(y(" Run: infernoflow scan \u2014 to build the source-file \u2192 capability map")),console.error(y(" Then retry: infernoflow explain "+i)),process.exit(1)),a||(console.log(y(`
5
+ infernoflow explain \u2192 ${I(i)}`)),console.log(y(` Found ${f.length} mapped ${f.length>1?"capabilities":"capability"}: ${f.join(", ")}`)),console.log())}else f=[i];const o=[];for(const t of f){const m=u.find($=>$.id===t);if(!m){a||(console.error(C(`\u2717 Capability "${t}" not found in capabilities.json`)),console.error(y(" Run: infernoflow stability \u2014 to list all capability IDs")));continue}const j=T(b.join(c,"scan.json")),v=T(b.join(c,"graph.json")),s=j?.capabilities?.find($=>$.id===t),h=s?.codeAnalysis?.sourceFiles||[],g=J(h[0],r),A=B(h[0],r),w=M(t,c),O=_(t,m,s,v,u,w,g,A);if(l&&!a){console.log(y(` \u2500\u2500 ${I(t)} \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`)),E(t,m,"",null,!0),f.length===1&&(console.log(I(" Prompt that would be sent to AI:")),console.log(),console.log(O.split(`
6
+ `).map($=>" "+$).join(`
7
+ `))),console.log();continue}let F=null,D=null;if(!l)try{const $=await V(O,r);$?.text&&(F=$.text.trim(),D=$.provider)}catch{}F||(F=Z(t,m,s,v,u,w),D=null),a?o.push({capId:t,name:m.name||m.title,stability:x(m),narrative:F,provider:D||"fallback",sourceFiles:h,scenarios:w.map($=>$.scenarioId||$.description),firstCommit:g}):(p||(console.log(y(`
8
+ infernoflow explain \u2192 ${I(t)}`)),console.log(y(" \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"))),E(t,m,F,D,!1))}if(a){const t=f.length===1?o[0]:o;console.log(JSON.stringify(t,null,2));return}}export{H as explainCommand};
@@ -0,0 +1,12 @@
1
+ import*as s from"node:fs";import*as a from"node:path";import*as y from"node:os";import*as w from"node:https";import*as S from"node:readline";import{execSync as f}from"node:child_process";import{bold as b,cyan as p,gray as r,green as k,yellow as O,red as v}from"../ui/output.mjs";const u="https://formspree.io/f/maqarynd",d="https://forms.gle/infernoflow-feedback",i=a.join(y.homedir(),".infernoflow","feedback.json"),x=[{id:"usage",label:"How often do you use infernoflow?",choices:["daily","a few times a week","rarely","just started"]},{id:"ide",label:"Which IDE are you using?",choices:["VS Code + Copilot","Cursor","Claude Code","Windsurf","Other"]},{id:"top_command",label:"Which infernoflow command do you use most?",choices:["log","switch","recap","status / check","context","other"]},{id:"missing",label:"What feature do you wish infernoflow had?",freeText:!0},{id:"email",label:"Email (optional \u2014 for follow-up questions):",freeText:!0,optional:!0}];function E(e){try{if(!u||u.includes("placeholder"))return;const o=new URL(u),t=JSON.stringify({...e.responses,_subject:`infernoflow feedback v${e.version}`,_version:e.version,_ts:e.ts}),n=w.request({hostname:o.hostname,path:o.pathname,method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json","Content-Length":Buffer.byteLength(t)},timeout:5e3});n.on("error",()=>{}),n.write(t),n.end()}catch{}}function F(e){const o=a.dirname(i);s.existsSync(o)||s.mkdirSync(o,{recursive:!0});const t={ts:new Date().toISOString(),version:C(),responses:e};let n=[];if(s.existsSync(i))try{n=JSON.parse(s.readFileSync(i,"utf8"))}catch{}return n.push(t),s.writeFileSync(i,JSON.stringify(n,null,2),"utf8"),t}function C(){try{const e=a.resolve(a.dirname(new URL(import.meta.url).pathname),"../../package.json");return JSON.parse(s.readFileSync(e,"utf8")).version}catch{return"unknown"}}function N(e){const o=process.platform;try{return o==="darwin"?f(`open "${e}"`,{stdio:"ignore"}):o==="win32"?f(`start "" "${e}"`,{stdio:"ignore"}):f(`xdg-open "${e}"`,{stdio:"ignore"}),!0}catch{return!1}}async function m(e,o){return new Promise(t=>e.question(o,t))}async function T(){const e=S.createInterface({input:process.stdin,output:process.stdout});console.log(`
2
+ `+b("\u{1F525} infernoflow feedback")+`
3
+ `),console.log(r(` Takes ~60 seconds. Helps make infernoflow better.
4
+ `));const o={};for(const n of x){if(console.log(p(` ${n.label}`)),n.choices){n.choices.forEach((h,g)=>console.log(r(` ${g+1}. ${h}`)));const c=await m(e," \u2192 "),l=parseInt(c.trim())-1;o[n.id]=l>=0&&l<n.choices.length?n.choices[l]:c.trim()}else{const c=await m(e," \u2192 ");o[n.id]=c.trim()||(n.optional?null:"\u2014")}console.log()}e.close();const t=F(o);E(t),console.log(k(` \u2714 Feedback saved \u2014 thank you!
5
+ `)),console.log(r(" Stored in: ~/.infernoflow/feedback.json")),console.log(r(` Version: ${t.version}`)),console.log(r(`
6
+ To share more detail or attach files, run: infernoflow feedback --form
7
+ `))}async function J(e){const o=t=>e.includes(t);if(o("--form")){console.log(p(`
8
+ Opening feedback form \u2192 ${d}
9
+ `)),N(d)||(console.log(O(" Could not open browser automatically.")),console.log(r(` Please open manually: ${d}
10
+ `)));return}if(o("--json")){if(!s.existsSync(i)){console.log(JSON.stringify([],null,2));return}try{const t=JSON.parse(s.readFileSync(i,"utf8"));console.log(JSON.stringify(t,null,2))}catch{console.log(JSON.stringify([],null,2))}return}process.stdin.isTTY||(console.log(v(` \u2718 infernoflow feedback requires an interactive terminal.
11
+ `)),console.log(r(` Run in a terminal or use: infernoflow feedback --form
12
+ `)),process.exit(1)),await T()}export{J as feedbackCommand};
@@ -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 te}from"node:url";import{header as se,ok as y,warn as _,done as K,nextSteps as re,cyan as f,yellow as J,gray as F}from"../ui/output.mjs";import{discoverProjectSignals as H,reviewCapabilitiesInteractive as ce,writeAdoptionBaseline as le,buildAdoptionReport as ae,summarizeCapabilities as pe,buildSignalsReport as fe}from"./adopt.mjs";import{installCursorHooksArtifacts as de}from"../cursorHooksInstall.mjs";import{installVsCodeCopilotHooksArtifacts as ue}from"../vsCodeCopilotHooksInstall.mjs";const me=t.dirname(te(import.meta.url));function ye(){return t.resolve(me,"../../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 Y(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 D(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 ge(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()?ge(c,a,s):D(c,a,s)}}function we(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 G(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 je(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 he(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 ke(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 z(e,o){const s=`# Changelog \u2014 ${o}
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}
6
6
 
7
7
  ## Unreleased
8
8
 
@@ -11,32 +11,32 @@ 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 Se(e,o){const{bold:s,cyan:n,gray:c,green:a,yellow:h,red:A}=await import("../ui/output.mjs");console.log(`
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
15
  `+s("\u{1F525} infernoflow init --lite")),console.log(" "+"\u2500".repeat(50)+`
16
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=G(e);let S="";if(!process.argv.includes("--yes")&&!process.argv.includes("-y")&&process.stdin.isTTY){const b=L.createInterface({input:process.stdin,output:process.stdout});S=await new Promise(j=>{b.question(c(" What does this project do? (one line, Enter to skip): "),O=>{b.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)+`
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
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 ve(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(`
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
20
  `+n("\u{1F525} infernoflow")+a(` \u2014 already set up
21
21
  `)),console.log(" "+h("\u2714")+` inferno/ found
22
22
  `),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
23
  `)),console.log(a(` For contracts & CI gates: infernoflow init --mode full
24
- `));return}const v=G(e);console.log(`
24
+ `));return}const v=U(e);console.log(`
25
25
  `+n("\u{1F525} infernoflow")+a(` \u2014 let's get you set up (30 seconds)
26
26
  `)),console.log(" Detected: "+c(v)+`
27
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 b="";if(!s&&process.stdin.isTTY){const j=L.createInterface({input:process.stdin,output:process.stdout});b=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(b){const j={ts:new Date().toISOString(),agent:"user",type:"gotcha",summary:b,source:"init"};i.appendFileSync(w,JSON.stringify(j)+`
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)+`
30
30
  `,"utf8"),console.log(`
31
31
  `+h("\u2714")+" First gotcha logged!")}console.log(`
32
32
  `+h("\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 Fe(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 ve(o,s,n);return}if(e.includes("--lite")){await Se(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(C=>C.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=G(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)+`
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
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
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
- `);z(t.join(u,"CHANGELOG.md"),E),i.writeFileSync(t.join(u,"CONTEXT.md"),`# ${E} \u2014 infernoflow context
39
+ `);V(t.join(u,"CHANGELOG.md"),E),i.writeFileSync(t.join(u,"CONTEXT.md"),`# ${E} \u2014 infernoflow context
40
40
 
41
41
  > Template: ${w} \u2014 ${r.description}
42
42
 
@@ -46,7 +46,7 @@ ${r.contextHint}
46
46
  ## Capabilities (${g.length})
47
47
  ${g.map(p=>`- \`${p.id}\`: ${p.description}`).join(`
48
48
  `)}
49
- `),r.scripts&&(info("Suggested package.json scripts for this template:"),Object.entries(r.scripts).forEach(([p,C])=>console.log(` ${bold(p)}: ${F(C)}`)),console.log()),K(`Initialised from template ${bold(f(w))} \u2014 ${bold(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"),b=e.includes("--report-json"),j=e.includes("--report-json-only"),O=e.includes("--report-human-only"),N=Y(e,"--lang"),U=Y(e,"--framework"),W=Y(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||se("init");const k=t.join(o,"inferno"),V=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=G(o),q="CreateTask, ReadTasks, UpdateTask, ToggleComplete, DeleteTask";let P=T,x=q.split(",").map(l=>l.trim());if(c){let r=H(o,{language:N||void 0,framework:U||void 0,projectType:W||void 0});if(!n&&!j){const g=L.createInterface({input:process.stdin,output:process.stdout}),p=r.developmentProfile||{},C=p.detected||{};console.log(F(` Review inferred development stack (press Enter to accept detected values)
50
- `));const oe=await R(g,"Language",p.language||C.language||"unknown"),ne=await R(g,"Framework",p.framework||C.framework||"unknown"),ie=await R(g,"Project type",p.projectType||C.projectType||"unknown");g.close(),r=H(o,{language:oe,framework:ne,projectType:ie})}const u=r.capabilities,I=pe(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(ae(u))),console.log(),console.log(F(fe(r))),console.log(),b&&!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 ce(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)",q)).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=H(o,{language:N||void 0,framework:U||void 0,projectType:W||void 0});le(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 je(t.join(k,"contract.json"),P,x),d||y("Created: "+f("inferno/contract.json")),he(t.join(k,"capabilities.json"),x),d||y("Created: "+f("inferno/capabilities.json")),ke(t.join(k,"scenarios"),x),d||y("Created: "+f("inferno/scenarios/happy_path.json")),z(t.join(k,"CHANGELOG.md"),P),d||y("Created: "+f("inferno/CHANGELOG.md"));const $=ye(),M=t.join($,"scripts","inferno-doc-gate.mjs"),B=t.join(o,"scripts","inferno-doc-gate.mjs");D(M,B,s,d);const X=t.join($,"scripts","inferno-install-hooks.mjs"),Q=t.join(o,"scripts","inferno-install-hooks.mjs");D(X,Q,s,d);const Z=t.join($,"ci","github-inferno-check.yml"),ee=t.join(V,"infernoflow-check.yml");if(D(Z,ee,s,d),we(o,d),S&&de({cwd:o,templatesRoot:$,force:s,silent:d,logOk:l=>{d||y(l)},logWarn:l=>{d||_(l)}}),v&&ue({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=H(o,{language:N||void 0,framework:U||void 0,projectType:W||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){K("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}")} ${bold("Tip:")} connect an AI provider for explain, why, review, and changelog AI.`),console.log(` ${f("infernoflow ai setup")} \u2014 takes 60 seconds`)),re([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{Fe as initCommand};
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};
@@ -3,16 +3,16 @@ import*as s from"node:fs";import*as u from"node:path";import"node:os";import{bol
3
3
  `),a=process.cwd(),f=u.join(a,"CLAUDE.md");s.existsSync(f)&&s.writeFileSync(f,d,"utf8");const m=u.join(a,".cursorrules");(s.existsSync(m)||s.existsSync(u.join(a,".cursor")))&&s.writeFileSync(m,d,"utf8");const $=u.join(a,".github","copilot-instructions.md");s.existsSync(u.join(a,".github"))&&s.writeFileSync($,d,"utf8")}catch{}}const F=["note","attempt","decision","gotcha","preference","theme","handoff","error"],b=["worked","failed","partial","unknown"];function C(){return s.existsSync(h)?s.readFileSync(h,"utf8").split(`
4
4
  `).filter(Boolean).map(e=>{try{return JSON.parse(e)}catch{return null}}).filter(Boolean):[]}function U(e,{auto:c=!1,quiet:r=!1}={}){if(!s.existsSync(O)){if(c)return!1;r||console.error(I(` \u2718 inferno/ not found \u2014 run: infernoflow init
5
5
  `)),process.exit(1)}return s.appendFileSync(h,JSON.stringify(e)+`
6
- `,"utf8"),!0}function B(){return process.env.CURSOR_SESSION?"cursor":process.env.COPILOT_SESSION?"copilot":process.env.CLAUDE_CODE_SESSION?"claude":process.env.WINDSURF_SESSION?"windsurf":process.env.INFERNOFLOW_AGENT?process.env.INFERNOFLOW_AGENT:"human"}function J(e,c){const r=new Date(e.ts).toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}),l=e.type||"note",i=l==="gotcha"?"\x1B[33m":l==="decision"?"\x1B[36m":l==="theme"?"\x1B[35m":l==="preference"?"\x1B[34m":l==="attempt"?"\x1B[90m":l==="error"?"\x1B[31m":"\x1B[0m",d="\x1B[0m",a=e.result?` [${e.result}]`:"",f=e.agent&&e.agent!=="human"?o(` (${e.agent})`):"";return` ${o(String(c+1).padStart(3))} ${o(r)} ${i}${l}${d}${a} ${e.summary}${f}`}async function K(e){if(e[0]==="log")e=e.slice(1);const c=n=>e.includes(n),r=(n,g)=>{const p=e.indexOf(n);return p!==-1&&e[p+1]?e[p+1]:g},l=c("--show"),i=c("--clear"),d=c("--json"),a=c("--auto"),f=c("--quiet"),m=r("--source",null);if(l||d){const n=C(),g=e[e.indexOf("--show")+1],p=g&&/^\d+$/.test(g)?parseInt(g):20,S=n.slice(-p);if(d){console.log(JSON.stringify(S,null,2));return}if(console.log(`
6
+ `,"utf8"),!0}function B(){return process.env.CURSOR_SESSION?"cursor":process.env.COPILOT_SESSION?"copilot":process.env.CLAUDE_CODE_SESSION?"claude":process.env.WINDSURF_SESSION?"windsurf":process.env.INFERNOFLOW_AGENT?process.env.INFERNOFLOW_AGENT:"human"}function J(e,c){const r=new Date(e.ts).toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}),l=e.type||"note",i=l==="gotcha"?"\x1B[33m":l==="decision"?"\x1B[36m":l==="theme"?"\x1B[35m":l==="preference"?"\x1B[34m":l==="attempt"?"\x1B[90m":l==="error"?"\x1B[31m":"\x1B[0m",d="\x1B[0m",a=e.result?` [${e.result}]`:"",f=e.agent&&e.agent!=="human"?o(` (${e.agent})`):"";return` ${o(String(c+1).padStart(3))} ${o(r)} ${i}${l}${d}${a} ${e.summary}${f}`}async function K(e){const c=n=>e.includes(n),r=(n,g)=>{const p=e.indexOf(n);return p!==-1&&e[p+1]?e[p+1]:g},l=c("--show"),i=c("--clear"),d=c("--json"),a=c("--auto"),f=c("--quiet"),m=r("--source",null);if(l||d){const n=C(),g=e[e.indexOf("--show")+1],p=g&&/^\d+$/.test(g)?parseInt(g):20,S=n.slice(-p);if(d){console.log(JSON.stringify(S,null,2));return}if(console.log(`
7
7
  `+N("\u{1F525} infernoflow \u2014 session memory")),console.log(" "+"\u2500".repeat(50)),!S.length){console.log(o(`
8
8
  No entries yet. Start logging with: infernoflow log "<what happened>"
9
9
  `));return}console.log(o(` Showing last ${S.length} of ${n.length} entries
10
10
  `)),S.forEach((D,T)=>console.log(J(D,n.length-S.length+T))),console.log();return}if(i){if(!s.existsSync(h)){console.log(o(` Nothing to clear.
11
11
  `));return}const n=h.replace(".jsonl",`-archive-${Date.now()}.jsonl`);s.renameSync(h,n),console.log(j(` \u2714 Session log archived \u2192 ${u.basename(n)}
12
- `));return}const $=new Set([r("--type",""),r("--result",""),r("--agent",""),r("--source","")].filter(Boolean)),x=e.filter(n=>!n.startsWith("--")&&!$.has(n)).join(" ").trim();if(!x){console.log(`
12
+ `));return}const $=new Set([r("--type",""),r("--result",""),r("--agent",""),r("--source","")].filter(Boolean)),x=e.slice(1).filter(n=>!n.startsWith("--")&&!$.has(n)).join(" ").trim();if(!x){console.log(`
13
13
  `+N("\u{1F525} infernoflow log")+` \u2014 append to session memory
14
14
  `),console.log(o(" Usage:")),console.log(o(' infernoflow log "what happened"')),console.log(o(' infernoflow log "tried X, failed because Y" --type attempt --result failed')),console.log(o(' infernoflow log "always use multipart/form-data" --type gotcha')),console.log(o(' infernoflow log "switched to dark mode" --type theme')),console.log(o(" infernoflow log --show Print last 20 entries")),console.log(o(" infernoflow log --json Print as JSON")),console.log(),console.log(o(" Types: note \xB7 attempt \xB7 decision \xB7 gotcha \xB7 preference \xB7 theme \xB7 handoff \xB7 error")),console.log(o(" Results: worked \xB7 failed \xB7 partial \xB7 unknown")),console.log(o(` Auto-capture: --auto (silent skip if no inferno/) \xB7 --quiet \xB7 --source <name>
15
15
  `));return}const w=r("--type","note"),y=r("--result",null),L=r("--agent",B());F.includes(w)||(f||console.error(I(` \u2718 Invalid type: ${w}. Valid: ${F.join(", ")}
16
16
  `)),process.exit(1)),y&&!b.includes(y)&&(f||console.error(I(` \u2718 Invalid result: ${y}. Valid: ${b.join(", ")}
17
- `)),process.exit(1));const E={ts:new Date().toISOString(),agent:L,type:w,summary:x,...y?{result:y}:{},...m?{source:m}:{},...a?{auto:!0}:{}};if(U(E,{auto:a,quiet:f})){A();try{const __cp=i.join(o.homedir(),".infernoflow","credentials.json");const __ce=s.existsSync(__cp);let __ht=false;if(__ce){try{const __c=JSON.parse(s.readFileSync(__cp,"utf8"));__ht=!!__c.access_token;}catch(e){}}process.stderr.write("[cloud] isLoggedIn="+k()+"\n");process.stderr.write("[cloud] creds_path="+__cp+"\n");process.stderr.write("[cloud] creds_exists="+__ce+" has_token="+__ht+"\n");if(k()){const n=_(),g=n.user_token||n.user?.id||n.user?.login||"anonymous",p=(()=>{try{return JSON.parse(s.readFileSync(u.join(O,"config.json"),"utf8")).projectId||u.basename(process.cwd())}catch{return u.basename(process.cwd())}})();await R(E,g,p).then(()=>console.error("[cloud] pushed ok")).catch(err=>console.error("[cloud] push failed:",err?.message||err))}}catch{}if(!f){const n=w!=="note"?v(` [${w}]`):"",g=y?o(` \u2192 ${y}`):"",p=m?o(` (via ${m})`):"";console.log(j(` \u2714 Logged${n}${g}${p}: `)+x+`
17
+ `)),process.exit(1));const E={ts:new Date().toISOString(),agent:L,type:w,summary:x,...y?{result:y}:{},...m?{source:m}:{},...a?{auto:!0}:{}};if(U(E,{auto:a,quiet:f})){A();try{if(k()){const n=_(),g=n.user_token||n.user?.id||n.user?.login||"anonymous",p=(()=>{try{return JSON.parse(s.readFileSync(u.join(O,"config.json"),"utf8")).projectId||u.basename(process.cwd())}catch{return u.basename(process.cwd())}})();await R(E,g,p)}}catch{}if(!f){const n=w!=="note"?v(` [${w}]`):"",g=y?o(` \u2192 ${y}`):"",p=m?o(` (via ${m})`):"";console.log(j(` \u2714 Logged${n}${g}${p}: `)+x+`
18
18
  `)}}}export{K as logCommand};
@@ -0,0 +1,4 @@
1
+ import*as g from"node:fs";import*as p from"node:path";import{fileURLToPath as F}from"node:url";import{spawnSync as M}from"node:child_process";import{header as b,warn as S,info as P,done as x,bold as u,cyan as k,gray as d,green as y,yellow as j,red as $}from"../ui/output.mjs";function A(e){const a=s=>g.existsSync(p.join(e,s));if(a("nx.json"))return"nx";if(a("turbo.json"))return"turborepo";if(a("lerna.json"))return"lerna";if(a("pnpm-workspace.yaml"))return"pnpm";const n=(()=>{try{return JSON.parse(g.readFileSync(p.join(e,"package.json"),"utf8"))}catch{return{}}})();return n.workspaces?Array.isArray(n.workspaces)?"yarn":"npm-workspaces":null}function C(e,a){try{if(a==="pnpm")return[...g.readFileSync(p.join(e,"pnpm-workspace.yaml"),"utf8").matchAll(/^\s*-\s*['"]?([^'"]+)['"]?/gm)].map(c=>c[1].trim());if(a==="lerna")return JSON.parse(g.readFileSync(p.join(e,"lerna.json"),"utf8")).packages||["packages/*"];const s=JSON.parse(g.readFileSync(p.join(e,"package.json"),"utf8")).workspaces;if(Array.isArray(s))return s;if(s?.packages)return s.packages}catch{}return["packages/*","apps/*","libs/*"]}function D(e,a){const n=[];for(const s of a){const l=s.split("/"),r=l.slice(0,-1).join("/"),c=l[l.length-1],o=p.join(e,r);if(g.existsSync(o))if(c==="*")try{for(const i of g.readdirSync(o,{withFileTypes:!0}))if(i.isDirectory()){const t=p.join(o,i.name,"package.json");if(g.existsSync(t)){const f=JSON.parse(g.readFileSync(t,"utf8"));n.push({name:f.name||i.name,dir:p.join(o,i.name),version:f.version||"0.0.0"})}}}catch{}else{const i=p.join(e,s),t=p.join(i,"package.json");if(g.existsSync(t)){const f=JSON.parse(g.readFileSync(t,"utf8"));n.push({name:f.name||c,dir:i,version:f.version||"0.0.0"})}}}return n}function N(e){const a=A(e);if(!a)return{type:null,packages:[]};const n=C(e,a),s=D(e,n);return{type:a,packages:s}}function w(e){for(const a of["contract.json","capabilities.json"]){const n=p.join(e,"inferno",a);if(g.existsSync(n))try{return JSON.parse(g.readFileSync(n,"utf8"))}catch{}}return null}function O(e){return g.existsSync(p.join(e,"inferno"))}function J(e){return O(e)?w(e)?"ok":"no-contract":"not-init"}function v(e,a){const n=p.join(p.dirname(p.dirname(F(import.meta.url))),"..","bin","infernoflow.mjs"),s=M(process.execPath,[n,...e],{cwd:a,encoding:"utf8",timeout:6e4,env:{...process.env,NO_COLOR:"1"}});return{stdout:s.stdout||"",stderr:s.stderr||"",status:s.status}}async function I(e,a){const n=e.includes("--json"),s=e.includes("--force")||e.includes("-f"),l=e.includes("--yes")||e.includes("-y"),{type:r,packages:c}=N(a);if(!r){const t="No monorepo configuration detected. Supported: nx, turborepo, pnpm workspaces, yarn workspaces, lerna.";n?console.log(JSON.stringify({ok:!1,error:t})):S(t),process.exit(1)}n||(b(`Monorepo init (${r})`),console.log(` Detected ${u(String(c.length))} packages:
2
+ `),c.forEach(t=>{const m=J(t.dir)==="ok"?y("\u2714"):j("\xB7");console.log(` ${m} ${u(t.name)} ${d(p.relative(a,t.dir))}`)}),console.log()),c.length===0&&(n?console.log(JSON.stringify({ok:!1,error:"No packages found"})):S("No packages found matching workspace globs."),process.exit(1));const o=[];for(const t of c){if(J(t.dir)==="ok"&&!s){n||P(`${t.name}: already initialised (use --force to reinit)`),o.push({name:t.name,status:"skipped"});continue}n||process.stdout.write(` Initialising ${k(t.name)}\u2026 `);const m=["init","--adopt","--yes"];s&&m.push("--force");const h=v(m,t.dir);h.status===0?(n||console.log(y("done")),o.push({name:t.name,status:"ok"})):(n||console.log($("failed")),o.push({name:t.name,status:"error",error:h.stderr.trim().slice(0,120)}))}const i={monorepoType:r,packages:o,updatedAt:new Date().toISOString()};if(g.writeFileSync(p.join(a,"inferno-monorepo.json"),JSON.stringify(i,null,2)+`
3
+ `),n)console.log(JSON.stringify({ok:!0,type:r,packages:o}));else{console.log();const t=o.filter(f=>f.status==="ok").length;x(`Initialised ${u(String(t))} of ${o.length} packages`),console.log(` ${d("Root summary:")} ${k("inferno-monorepo.json")}`),console.log()}}async function T(e,a){const n=e.includes("--json"),{type:s,packages:l}=N(a);if(!s&&l.length===0){n?console.log(JSON.stringify({ok:!1,error:"No monorepo detected"})):S("No monorepo detected. Run: infernoflow monorepo init");return}const r=l.map(o=>({name:o.name,dir:p.relative(a,o.dir),version:o.version,status:J(o.dir),caps:(()=>{const i=w(o.dir);return i?(i.capabilities||[]).length:0})()}));if(n){console.log(JSON.stringify({ok:!0,type:s,packages:r}));return}console.log(),console.log(` ${u("Monorepo packages")} ${d("("+s+")")}`),console.log();const c=Math.max(...r.map(o=>o.name.length),8)+2;r.forEach(o=>{const i=o.status==="ok"?y("\u2714"):o.status==="not-init"?j("\u25CB"):$("\u2717"),t=o.status==="ok"?d(`${o.caps} caps`):d(o.status);console.log(` ${i} ${o.name.padEnd(c)}${o.version.padEnd(12)}${t}`)}),console.log()}async function E(e,a){const n=e.includes("--json"),{type:s,packages:l}=N(a);if(!l.length){n?console.log(JSON.stringify({ok:!1,error:"No packages found"})):S("No packages found.");return}n||b(`Monorepo status (${l.length} packages)`);const r=[];for(const c of l){if(!O(c.dir)){r.push({name:c.name,status:"not-init",caps:0}),n||console.log(` ${j("\u25CB")} ${u(c.name)} ${d("not initialised")}`);continue}const o=v(["status","--json"],c.dir);try{const i=JSON.parse(o.stdout.trim()),t=(i.capabilityDetails||[]).length,f=i.ok!==!1;if(r.push({name:c.name,status:f?"ok":"error",caps:t,version:i.policyVersion}),!n){const m=f?y("\u2714"):$("\u2717");console.log(` ${m} ${u(c.name.padEnd(28))}${d("v"+(i.policyVersion||"?"))} ${t} caps`)}}catch{r.push({name:c.name,status:"error",caps:0,error:"status failed"}),n||console.log(` ${$("\u2717")} ${u(c.name)} ${d("status check failed")}`)}}if(n){const c=r.every(o=>o.status==="ok");console.log(JSON.stringify({ok:c,type:s,packages:r}))}else{console.log();const c=r.filter(o=>o.status==="ok").length;console.log(` ${c===r.length?y("\u2714"):j("\u26A0")} ${c}/${r.length} packages healthy`),console.log()}}async function R(e,a){const n=e.includes("--json"),s=e.includes("--package")?e[e.indexOf("--package")+1]:null,{packages:l}=N(a),r=s?l.filter(o=>o.name===s||o.name.endsWith("/"+s)):l.filter(o=>O(o.dir));if(!r.length){n?console.log(JSON.stringify({ok:!1,error:s?`Package not found: ${s}`:"No initialised packages found"})):S(s?`Package not found: ${s}`:"No initialised packages found.");return}!n&&!s&&b(`Monorepo diff (${r.length} packages)`);const c=[];for(const o of r){const i=v(["diff","--json"],o.dir);try{const t=JSON.parse(i.stdout.trim()),f=(t.added||[]).length,m=(t.removed||[]).length,h=(t.changed||[]).length;c.push({name:o.name,added:f,removed:m,changed:h,data:t}),n||(f||m||h?(console.log(` ${u(o.name)}`),f&&console.log(` ${y("+")} ${f} added`),m&&console.log(` ${$("-")} ${m} removed`),h&&console.log(` ${j("~")} ${h} changed`)):console.log(` ${y("\u2714")} ${u(o.name)} ${d("no changes")}`))}catch{c.push({name:o.name,error:"diff failed"}),n||console.log(` ${$("\u2717")} ${u(o.name)} ${d("diff failed")}`)}}n?console.log(JSON.stringify({ok:!0,packages:c})):console.log()}async function L(e,a){const n=e.includes("--json"),{type:s,packages:l}=N(a);n||b("Syncing monorepo contracts");const r={monorepoType:s,updatedAt:new Date().toISOString(),packages:[]};for(const i of l){const t=w(i.dir);t&&(r.packages.push({name:i.name,version:t.policyVersion||i.version,capabilities:(t.capabilities||[]).map(f=>typeof f=="string"?f:f.id),capsCount:(t.capabilities||[]).length}),n||console.log(` ${y("\u2714")} ${u(i.name)} ${d((t.capabilities||[]).length+" caps")}`))}const c=p.join(a,"inferno-monorepo.json");g.writeFileSync(c,JSON.stringify(r,null,2)+`
4
+ `);const o=r.packages.reduce((i,t)=>i+t.capsCount,0);n?console.log(JSON.stringify({ok:!0,packages:r.packages.length,totalCaps:o})):(console.log(),x(`Synced ${u(String(r.packages.length))} packages (${o} total capabilities)`),console.log(` ${k(c)}`),console.log())}async function G(e){const a=e.slice(1),n=a[0],s=process.cwd(),l=a.slice(1);switch(n){case"init":return I(l,s);case"list":return T(l,s);case"status":return E(l,s);case"diff":return R(l,s);case"sync":return L(l,s);default:{const r=a.includes("--json"),c=`Unknown monorepo sub-command: ${n||"(none)"}`;if(r){console.log(JSON.stringify({ok:!1,error:c}));return}console.log(),console.log(` ${u("infernoflow monorepo")} \u2014 per-package capability tracking`),console.log(),console.log(` ${k("infernoflow monorepo init")} Scaffold inferno/ in each package`),console.log(` ${k("infernoflow monorepo list")} List packages and contract status`),console.log(` ${k("infernoflow monorepo status")} Health check across all packages`),console.log(` ${k("infernoflow monorepo diff")} Capability diff for all packages`),console.log(` ${k("infernoflow monorepo diff --package auth")} Diff a specific package`),console.log(` ${k("infernoflow monorepo sync")} Aggregate all contracts to inferno-monorepo.json`),console.log(),console.log(` ${d("Supported: nx, turborepo, pnpm workspaces, yarn workspaces, lerna")}`),console.log()}}}export{G as monorepoCommand};
@@ -0,0 +1,4 @@
1
+ import*as k from"node:fs";import*as g from"node:path";import*as $ from"node:https";import*as b from"node:http";import{spawnSync as x}from"node:child_process";import{done as O,warn as m,info as S,cyan as N,gray as v}from"../ui/output.mjs";function C(a,t){const n=g.join(a,"notify.json"),i=k.existsSync(n)?(()=>{try{return JSON.parse(k.readFileSync(n,"utf8"))}catch{return{}}})():{},l=t.indexOf("--slack"),e=t.indexOf("--discord");return{slack:l!==-1?t[l+1]:process.env.INFERNOFLOW_SLACK_WEBHOOK||i.slack,discord:e!==-1?t[e+1]:process.env.INFERNOFLOW_DISCORD_WEBHOOK||i.discord}}function w(a,t){try{const i=x(process.execPath,[g.join(g.dirname(g.dirname(fileURLToPath(import.meta.url))),"..","bin","infernoflow.mjs"),...a.split(" ").slice(1)],{cwd:t,encoding:"utf8",timeout:2e4}).stdout?.trim();if(i)return JSON.parse(i)}catch{}return null}function R(a,t,n){const i=a?.status||"unknown",l=(n?.capabilities||[]).length,e=n?.policyVersion||"?",o=n?.policyId||"project",r=t?.added||[],u=t?.removed||[],p=t?.changed||[];return{status:i,caps:l,version:e,project:o,added:r,removed:u,changed:p}}function D(a){const{status:t,caps:n,version:i,project:l,added:e,removed:o,changed:r}=a,u=t==="ok"?"\u2705":t==="warning"?"\u26A0\uFE0F":"\u274C",p=e.length||o.length||r.length,f=[{type:"header",text:{type:"plain_text",text:`\u{1F525} infernoflow \u2014 ${l} v${i}`,emoji:!0}},{type:"section",fields:[{type:"mrkdwn",text:`*Status*
2
+ ${u} ${t.toUpperCase()}`},{type:"mrkdwn",text:`*Capabilities*
3
+ ${n} tracked`}]}];if(p){const d=[];e.length&&d.push(`\u2705 *${e.length}* added: ${e.slice(0,3).join(", ")}${e.length>3?` +${e.length-3} more`:""}`),o.length&&d.push(`\u274C *${o.length}* removed: ${o.slice(0,3).join(", ")}${o.length>3?` +${o.length-3} more`:""}`),r.length&&d.push(`\u{1F4DD} *${r.length}* changed`),f.push({type:"section",text:{type:"mrkdwn",text:d.join(`
4
+ `)}})}return f.push({type:"context",elements:[{type:"mrkdwn",text:`<https://github.com/ronmiz/infernoflow|infernoflow> \xB7 ${new Date().toLocaleString()}`}]}),{blocks:f}}function L(a){const{status:t,caps:n,version:i,project:l,added:e,removed:o,changed:r}=a,u=t==="ok"?4906624:t==="warning"?16347926:16281969,p=e.length||o.length||r.length,f=[{name:"Status",value:t.toUpperCase(),inline:!0},{name:"Capabilities",value:String(n),inline:!0},{name:"Version",value:`v${i}`,inline:!0}];return e.length&&f.push({name:"\u2705 Added",value:e.slice(0,5).join(", ")+(e.length>5?` +${e.length-5}`:""),inline:!1}),o.length&&f.push({name:"\u274C Removed",value:o.slice(0,5).join(", ")+(o.length>5?` +${o.length-5}`:""),inline:!1}),{embeds:[{title:`\u{1F525} infernoflow \u2014 ${l}`,description:p?"Capability changes detected":"Contract healthy",color:u,fields:f,footer:{text:"infernoflow \xB7 "+new Date().toLocaleString()},url:"https://github.com/ronmiz/infernoflow"}]}}function j(a,t){return new Promise((n,i)=>{const l=JSON.stringify(t),e=new URL(a),o=e.protocol==="https:",u=(o?$:b).request({hostname:e.hostname,port:e.port||(o?443:80),path:e.pathname+(e.search||""),method:"POST",headers:{"Content-Type":"application/json","Content-Length":Buffer.byteLength(l),"User-Agent":"infernoflow-cli"}},p=>{let f="";p.on("data",d=>f+=d),p.on("end",()=>n({status:p.statusCode,body:f}))});u.on("error",i),u.write(l),u.end()})}async function K(a){const t=a.slice(1),n=t.includes("--json"),i=t.includes("--dry-run"),l=t.includes("--on-change"),e=process.cwd(),o=g.join(e,"inferno");if(!k.existsSync(o)){const c="inferno/ not found. Run: infernoflow init";n?console.log(JSON.stringify({ok:!1,error:c})):m(c),process.exit(1)}const r=C(o,t);if(!r.slack&&!r.discord){const c="No webhook configured. Use --slack <url>, --discord <url>, or set INFERNOFLOW_SLACK_WEBHOOK / INFERNOFLOW_DISCORD_WEBHOOK.";n?console.log(JSON.stringify({ok:!1,error:c})):m(c),n||(console.log(),console.log(` ${v("To configure permanently, create inferno/notify.json:")}`),console.log(` ${N('{ "slack": "https://hooks.slack.com/...", "discord": "https://discord.com/api/webhooks/..." }')}`),console.log()),process.exit(1)}const u=(()=>{for(const c of["contract.json","capabilities.json"]){const s=g.join(o,c);if(k.existsSync(s))try{return JSON.parse(k.readFileSync(s,"utf8"))}catch{}}return{}})(),p=w("check --json",e),f=w("diff --json",e),d=R(p,f,u);if(l&&!d.added.length&&!d.removed.length&&!d.changed.length){n?console.log(JSON.stringify({ok:!0,skipped:!0,reason:"no capability changes"})):S("No capability changes \u2014 skipping notification.");return}const h=[];if(r.slack){const c=D(d);if(i)n||(S("Slack payload (dry run):"),console.log(JSON.stringify(c,null,2))),h.push({platform:"slack",ok:!0,dryRun:!0});else try{const s=await j(r.slack,c),y=s.status>=200&&s.status<300;n||(y?O("Slack notification sent"):m(`Slack returned ${s.status}`)),h.push({platform:"slack",ok:y,status:s.status})}catch(s){n||m(`Slack failed: ${s.message}`),h.push({platform:"slack",ok:!1,error:s.message})}}if(r.discord){const c=L(d);if(i)n||(S("Discord payload (dry run):"),console.log(JSON.stringify(c,null,2))),h.push({platform:"discord",ok:!0,dryRun:!0});else try{const s=await j(r.discord,c),y=s.status>=200&&s.status<300;n||(y?O("Discord notification sent"):m(`Discord returned ${s.status}`)),h.push({platform:"discord",ok:y,status:s.status})}catch(s){n||m(`Discord failed: ${s.message}`),h.push({platform:"discord",ok:!1,error:s.message})}}n&&console.log(JSON.stringify({ok:h.every(c=>c.ok),results:h,summary:d}))}export{K as notifyCommand};
@@ -0,0 +1,124 @@
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`/**
3
+ * ${e}
4
+ *
5
+ * ${o}
6
+ *
7
+ * @capability ${t}
8
+ * @stability experimental
9
+ */
10
+ ${u}
11
+
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
+
14
+ export class ${n} extends Error {
15
+ constructor(message: string, public readonly code?: string) {
16
+ super(message);
17
+ this.name = "${n}";
18
+ }
19
+ }
20
+
21
+ // \u2500\u2500 types \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\u2500
22
+
23
+ export interface ${s}Input {
24
+ // TODO: define input fields
25
+ }
26
+
27
+ export interface ${s}Result {
28
+ // TODO: define result fields
29
+ success: boolean;
30
+ }
31
+
32
+ // \u2500\u2500 implementation \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
33
+
34
+ /**
35
+ * ${r} \u2014 primary entry point for ${e}.
36
+ * TODO: implement this function.
37
+ */
38
+ export async function ${r}(input: ${s}Input): Promise<${s}Result> {
39
+ // TODO: implement
40
+ throw new ${n}("Not implemented yet");
41
+ }
42
+ `}function ne(t,e,o,r,i){const n=`${d(t)}Error`,u=L("js",i);return`/**
43
+ * ${e}
44
+ *
45
+ * ${o}
46
+ *
47
+ * @capability ${t}
48
+ * @stability experimental
49
+ */
50
+ ${u}
51
+
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
+
54
+ export class ${n} extends Error {
55
+ constructor(message, code) {
56
+ super(message);
57
+ this.name = "${n}";
58
+ this.code = code;
59
+ }
60
+ }
61
+
62
+ // \u2500\u2500 implementation \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
63
+
64
+ /**
65
+ * ${r} \u2014 primary entry point for ${e}.
66
+ * TODO: implement this function.
67
+ *
68
+ * @param {object} input
69
+ * @returns {Promise<object>}
70
+ */
71
+ export async function ${r}(input = {}) {
72
+ // TODO: implement
73
+ throw new ${n}("Not implemented yet");
74
+ }
75
+ `}function oe(t,e,o,r){const i=d(t);return`"""
76
+ ${e}
77
+
78
+ ${o}
79
+
80
+ capability: ${t}
81
+ stability: experimental
82
+ """
83
+
84
+ from typing import Any
85
+
86
+
87
+ class ${i}Error(Exception):
88
+ """Raised when ${e} operations fail."""
89
+ def __init__(self, message: str, code: str | None = None):
90
+ super().__init__(message)
91
+ self.code = code
92
+
93
+
94
+ async def ${r.replace(/([A-Z])/g,"_$1").toLowerCase().replace(/^_/,"")}(input: dict[str, Any]) -> dict[str, Any]:
95
+ """Primary entry point for ${e}.
96
+
97
+ TODO: implement this function.
98
+ """
99
+ raise ${i}Error("Not implemented yet")
100
+ `}function se(t,e,o,r){const i=t.split("-")[0];return`// Package ${i} implements ${e}.
101
+ //
102
+ // ${o}
103
+ //
104
+ // capability: ${t}
105
+ // stability: experimental
106
+ package ${i}
107
+
108
+ import "errors"
109
+
110
+ // Err${d(t)} is returned when ${e} operations fail.
111
+ var Err${d(t)} = errors.New("${t}: operation failed")
112
+
113
+ // ${d(r)} is the primary entry point for ${e}.
114
+ // TODO: implement this function.
115
+ func ${d(r)}(input map[string]any) (map[string]any, error) {
116
+ return nil, Err${d(t)}
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(`
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(`
122
+ `).slice(0,12).map(f=>" "+f).join(`
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};
@@ -0,0 +1,5 @@
1
+ import*as y from"node:fs";import*as f from"node:path";import{bold as g,cyan as w,gray as t,green as v,yellow as E,red as j}from"../ui/output.mjs";const m="inferno",I=f.join(m,"sessions.jsonl"),N=f.join(m,"CONTEXT.md"),O=f.join(m,"theme.json"),A=f.join(m,"scan.json"),C=f.join(m,"contract.json"),M=f.join(m,"capabilities.json");function k(o){try{return JSON.parse(y.readFileSync(o,"utf8"))}catch{return null}}function F(o){return Math.ceil((o||"").length/4)}const $={gotcha:400,decision:200,attempt:250,preference:150,note:100,theme:300,handoff:500,error:200};function L(o){const e={ok:!1,memory:{total:0,byType:{},oldestEntry:null,newestEntry:null,sessionsTracked:0},context:{sizeBytes:0,estimatedTokens:0,hasIntent:!1,hasWorking:!1},theme:{captured:!1,fonts:0,colors:0,cssVars:0,framework:null},coverage:{total:0,withAnalysis:0,pct:0},chains:{total:0,resolved:0},contract:{policyId:null,capabilities:0,isLite:!1},savings:{estimatedTokens:0,breakdown:{}}},a=k(f.join(o,C));a&&(e.contract.policyId=a.policyId,e.contract.capabilities=(a.capabilities||[]).length,e.contract.isLite=!!a.lite,e.ok=!0);const c=k(f.join(o,M));if(c){const s=Array.isArray(c)?c:c.capabilities||[];e.coverage.total=s.length,e.coverage.withAnalysis=s.filter(i=>i.codeAnalysis).length,e.coverage.pct=e.coverage.total?Math.round(e.coverage.withAnalysis/e.coverage.total*100):0}const n=f.join(o,I);if(y.existsSync(n)){const i=y.readFileSync(n,"utf8").split(`
2
+ `).filter(Boolean).map(u=>{try{return JSON.parse(u)}catch{return null}}).filter(Boolean);e.memory.total=i.length;for(const u of i){const p=u.type||"note";e.memory.byType[p]=(e.memory.byType[p]||0)+1;const b=$[p]||100;e.savings.estimatedTokens+=b,e.savings.breakdown[p]=(e.savings.breakdown[p]||0)+b}i.length&&(e.memory.oldestEntry=i[0].ts,e.memory.newestEntry=i[i.length-1].ts);const x=new Set(i.map(u=>(u.ts||"").slice(0,10)));e.memory.sessionsTracked=x.size}const l=f.join(o,N);if(y.existsSync(l)){const s=y.readFileSync(l,"utf8");e.context.sizeBytes=Buffer.byteLength(s,"utf8"),e.context.estimatedTokens=F(s),e.context.hasIntent=s.includes("## Intent"),e.context.hasWorking=s.includes("## Working on")}const r=k(f.join(o,O));r&&(e.theme.captured=!0,e.theme.fonts=Object.keys(r.fonts||{}).filter(s=>r.fonts[s]).length,e.theme.colors=Object.keys(r.colors?.palette||{}).length,e.theme.cssVars=Object.keys(r.cssVars||{}).length,e.theme.framework=r.framework||null);const h=k(f.join(o,A));if(h?.httpChains){const s=Object.values(h.httpChains).flat();e.chains.total=s.length,e.chains.resolved=s.filter(i=>i.resolved).length}return e}function T(o,e,a=20){const c=e>0?Math.round(o/e*a):0;return"\u2588".repeat(c)+"\u2591".repeat(a-c)}function S(o){return o>=80?v:o>=40?E:j}function B(o){if(!o)return"never";const e=new Date(o),c=Date.now()-e.getTime(),n=Math.floor(c/864e5);return n===0?"today":n===1?"yesterday":n<7?`${n}d ago`:n<30?`${Math.floor(n/7)}w ago`:e.toLocaleDateString("en-GB",{day:"2-digit",month:"short"})}function d(o){return o>=1e3?`~${Math.round(o/100)/10}k`:`~${o}`}function P(o){const e=t(" "+"\u2500".repeat(52));console.log(),console.log(" "+g("\u{1F525} infernoflow stats")),o.contract.policyId&&console.log(t(` Project: ${o.contract.policyId}${o.contract.isLite?" (lite)":""}`)),console.log(e),console.log(),console.log(" "+g("Session memory")+t(" (inferno/sessions.jsonl)")),console.log();const a=o.memory.total;if(a===0)console.log(t(' No entries yet \u2014 run: infernoflow log "<what happened>" --type gotcha'));else{const n=["gotcha","decision","attempt","preference","theme","note","handoff","error"],l=Math.max(...Object.values(o.memory.byType));for(const r of n){const h=o.memory.byType[r]||0;if(h===0)continue;const s=T(h,l,16),i=r.padEnd(12);console.log(` ${t(i)} ${w(s)} ${h}`)}console.log(),console.log(t(" Total entries: ")+g(a)),console.log(t(" Sessions tracked: ")+o.memory.sessionsTracked),o.memory.newestEntry&&console.log(t(" Last entry: ")+B(o.memory.newestEntry))}if(console.log(),console.log(e),console.log(),console.log(" "+g("Context injection")+t(" (per session start)")),console.log(),o.context.sizeBytes===0?console.log(t(" No CONTEXT.md yet \u2014 run: infernoflow context")):(console.log(t(" Size: ")+`${Math.round(o.context.sizeBytes/1024*10)/10} KB`),console.log(t(" Tokens: ")+g(d(o.context.estimatedTokens))+t(" injected into every session")),o.context.hasIntent&&console.log(t(" ")+v("\u2714")+t(" Intent captured")),o.context.hasWorking&&console.log(t(" ")+v("\u2714")+t(" Working state captured"))),console.log(),console.log(e),console.log(),console.log(" "+g("Capability coverage")+t(" (code analysis via infernoflow scan)")),console.log(),o.coverage.total===0)console.log(t(" No capabilities yet \u2014 run: infernoflow init"));else{const n=S(o.coverage.pct),l=T(o.coverage.withAnalysis,o.coverage.total,24);if(console.log(` ${n(l)} ${g(o.coverage.pct+"%")} (${o.coverage.withAnalysis}/${o.coverage.total})`),o.coverage.pct<100){const r=o.coverage.total-o.coverage.withAnalysis;console.log(t(`
3
+ ${r} capabilities without code analysis`)),console.log(t(" Run: infernoflow scan to enrich them"))}}if(o.chains.total>0){console.log(),console.log(e),console.log(),console.log(" "+g("HTTP call chains")+t(" (end-to-end resolution)")),console.log();const n=Math.round(o.chains.resolved/o.chains.total*100),l=S(n),r=T(o.chains.resolved,o.chains.total,20);console.log(` ${l(r)} ${g(n+"%")} resolved (${o.chains.resolved}/${o.chains.total} call chains)`),o.chains.resolved<o.chains.total&&console.log(t(`
4
+ Unresolved calls may be to external services or missing route files`))}if(console.log(),console.log(e),console.log(),console.log(" "+g("Design system")+t(" (inferno/theme.json)")),console.log(),!o.theme.captured)console.log(t(" Not captured yet \u2014 run: infernoflow theme"));else{const n=[];o.theme.fonts&&n.push(`${o.theme.fonts} font${o.theme.fonts!==1?"s":""}`),o.theme.colors&&n.push(`${o.theme.colors} colors`),o.theme.cssVars&&n.push(`${o.theme.cssVars} CSS vars`),o.theme.framework&&n.push(`${o.theme.framework}`),console.log(t(" ")+v("\u2714")+" "+n.join(" \xB7 ")),console.log(t(" AI agents always use the correct fonts and colors for this project"))}console.log(),console.log(e),console.log(),console.log(" "+g("Estimated token savings")+t(" (vs re-discovering from scratch)")),console.log();const c=o.savings.estimatedTokens;if(c===0)console.log(t(" No session entries yet \u2014 start logging to track savings"));else{const n=Math.max(o.memory.sessionsTracked,1),l=Math.round(c/n);console.log(" Total saved: "+g(v(d(c)+" tokens"))),console.log(" Per session: "+g(d(l)+" tokens")),console.log(),console.log(t(" Breakdown:"));const r=["gotcha","handoff","attempt","decision","theme","preference","note","error"];for(const h of r){const s=o.savings.breakdown[h];if(!s)continue;const i=o.memory.byType[h]||0;console.log(t(` ${h.padEnd(12)} ${i}\xD7 \xD7 ${$[h]||100} = `)+w(d(s)))}console.log(),console.log(t(" * Estimates based on typical back-and-forth cost per entry type.")),console.log(t(" Actual savings vary with model, project complexity, and session length."))}console.log(),console.log(e),console.log()}async function D(o=[]){const e=o.includes("--json"),a=o.includes("--brief"),c=process.cwd();y.existsSync(f.join(c,m))||(console.error(j(` \u2718 inferno/ not found \u2014 run: infernoflow init
5
+ `)),process.exit(1));const n=L(c);if(e){console.log(JSON.stringify(n,null,2));return}if(a){const l=[];n.memory.total&&l.push(`${n.memory.total} memory entries`),n.context.estimatedTokens&&l.push(`${d(n.context.estimatedTokens)} tokens/session`),n.coverage.total&&l.push(`${n.coverage.pct}% capability coverage`),n.savings.estimatedTokens&&l.push(`${d(n.savings.estimatedTokens)} tokens saved`),console.log(l.join(" \xB7 ")||"No data yet \u2014 run infernoflow init + infernoflow log");return}P(n)}export{D as statsCommand};
@@ -0,0 +1,6 @@
1
+ import*as y from"node:fs";import*as $ from"node:path";import*as E from"node:os";import{spawnSync as w}from"node:child_process";import{bold as A,gray as f,green as F,yellow as I,red as b}from"../ui/output.mjs";function N(i){try{return JSON.parse(y.readFileSync(i,"utf8"))}catch{return null}}function J(i){return i?.stability||"experimental"}const P=F("\u2713"),R=b("\u2717"),L=f("\u25CB");function O(i,n){const e=$.join(n,"scenarios");if(!y.existsSync(e))return[];const g=[];for(const c of y.readdirSync(e))if(c.endsWith(".json"))try{const p=JSON.parse(y.readFileSync($.join(e,c),"utf8"));(p.capabilitiesCovered||p.capabilities||[]).some(a=>a.toLowerCase()===i.toLowerCase())&&g.push({...p,_file:c})}catch{}return g}function z(i){const n=N($.join(i,"package.json"));if(!n)return null;const e={...n.dependencies,...n.devDependencies};return e?.vitest?"vitest":e?.jest?"jest":e?.mocha?"mocha":n.scripts?.test&&!n.scripts.test.includes("no test")?{custom:n.scripts.test}:null}function D(i,n,e,g){const c=n.name||n.title||i,p=n.description||"(no description)",r=g?.codeAnalysis?.sourceFiles||[],a=g?.codeAnalysis?.functions||[],m=e?.steps||e?.actions||[],u=e?.expects||e?.assertions||[],s=[`// Auto-generated smoke test for: ${i}`,"// Generated by infernoflow test \u2014 edit as needed","",'import { strict as assert } from "node:assert";',"",`// Capability: ${c}`,`// ${p}`,""];return r.length&&(s.push(`// Source: ${r[0]}`),s.push(`// import { ${a[0]||i} } from "./${$.basename(r[0])}";`),s.push("")),s.push("async function run() {"),s.push(" const results = [];"),s.push(""),m.length?m.forEach((t,h)=>{const d=typeof t=="string"?t:t.action||t.description||`step ${h+1}`;s.push(` // Step ${h+1}: ${d}`),s.push(` results.push({ step: ${JSON.stringify(d)}, status: "manual" });`),s.push("")}):(s.push(" // No explicit steps \u2014 running basic smoke test"),s.push(' results.push({ step: "capability exists", status: "pass" });'),s.push("")),u.length&&(u.forEach((t,h)=>{const d=typeof t=="string"?t:t.condition||t.description||`assertion ${h+1}`;s.push(` // Assert: ${d}`),s.push(` // assert(condition, ${JSON.stringify(d)});`)}),s.push("")),s.push(" return results;"),s.push("}"),s.push(""),s.push("run().then(results => {"),s.push(' const failed = results.filter(r => r.status === "fail");'),s.push(" results.forEach(r => {"),s.push(' const icon = r.status === "pass" ? "\u2713" : r.status === "fail" ? "\u2717" : "\u25CB";'),s.push(" console.log(` ${icon} ${r.step}`);"),s.push(" });"),s.push(" if (failed.length) { console.error(`\\n ${failed.length} failed`); process.exit(1); }"),s.push(" else console.log(`\\n All steps passed`);"),s.push("}).catch(err => { console.error(err); process.exit(1); });"),s.join(`
2
+ `)}function M(i,n,e,g,c){const p=e?.scenarioId||e?.id||e?._file?.replace(".json","")||"unnamed",r=z(c),a=e?.testFiles||e?.testFile?[e.testFile].flat().filter(Boolean):[];if(a.length){for(const s of a){const t=$.resolve(c,s);if(!y.existsSync(t))return{scenarioId:p,status:"skip",reason:`test file not found: ${s}`}}if(r&&typeof r=="string"){const s=r==="vitest"?`npx vitest run ${a.join(" ")} --reporter verbose`:r==="jest"?`npx jest ${a.join(" ")} --no-coverage`:r==="mocha"?`npx mocha ${a.join(" ")}`:null;if(s){const t=w(s,{shell:!0,cwd:c,encoding:"utf8",timeout:6e4}),h=t.status===0;return{scenarioId:p,status:h?"pass":"fail",output:(t.stdout||"")+(t.stderr||""),runner:r}}}if(r?.custom){const s=w(r.custom,{shell:!0,cwd:c,encoding:"utf8",timeout:6e4});return{scenarioId:p,status:s.status===0?"pass":"fail",output:(s.stdout||"")+(s.stderr||""),runner:"npm test"}}}const m=D(i,n,e,g),u=$.join(E.tmpdir(),`infernoflow-test-${i}-${Date.now()}.mjs`);try{y.writeFileSync(u,m);const s=w(process.execPath,[u],{cwd:c,encoding:"utf8",timeout:3e4}),t=s.status===0;return{scenarioId:p,status:t?"pass":"fail",output:(s.stdout||"")+(s.stderr||""),runner:"ad-hoc",generated:!0}}finally{try{y.unlinkSync(u)}catch{}}}function T(i,n,e,g){const c=J(n),p=c==="frozen"?b("frozen"):c==="stable"?I("stable"):f("experimental"),r=e.length,a=e.filter(t=>t.status==="pass").length,m=e.filter(t=>t.status==="fail").length,u=e.filter(t=>t.status==="skip").length,s=m>0?b("\u2717"):a>0?F("\u2713"):f("\u25CB");console.log(` ${s} ${A(i)} ${f(`[${p}]`)}`);for(const t of e){const h=t.status==="pass"?P:t.status==="fail"?R:L,d=t.generated?f(" (generated)"):t.runner?f(` (${t.runner})`):"";if(console.log(` ${h} ${f(t.scenarioId)}${d}`),t.reason&&console.log(` ${f(t.reason)}`),g&&t.output){const l=t.output.trim().split(`
3
+ `).slice(0,10).join(`
4
+ `);console.log(l.split(`
5
+ `).map(k=>` ${f(k)}`).join(`
6
+ `))}}return{total:r,passed:a,failed:m,skipped:u}}async function K(i){const n=(i||[]).slice(1),e=n.includes("--json"),g=n.includes("--bail"),c=n.includes("--generate"),p=n.includes("--all"),r=n.includes("--verbose")||n.includes("-v"),a=n.filter(o=>!o.startsWith("--")&&o!=="-v"),m=process.cwd(),u=$.join(m,"inferno");y.existsSync(u)||(e||console.error(b("\u2717 inferno/ not found. Run: infernoflow init")),process.exit(1));let s=[];const t=N($.join(u,"capabilities.json"));t&&(s=Array.isArray(t)?t:t.capabilities||[]);const h=N($.join(u,"scan.json"));let d;if(a.length?d=a.map(o=>{const j=s.find(v=>v.id===o);return j||(e||console.error(b(`\u2717 Capability "${o}" not found in capabilities.json`)),process.exit(1)),j}):p?d=s:(d=s.filter(o=>O(o.id,u).length>0),d.length||(e||(console.log(),console.log(` ${f("No scenarios registered. Use --all to test all capabilities, or")} `),console.log(` ${f("add scenarios to inferno/scenarios/ first.")}`),console.log()),process.exit(0))),e||(console.log(),console.log(` ${A("\u{1F9EA} infernoflow test")}`),console.log()),c){const o=d[0],v=O(o.id,u)[0]||{},S=h?.capabilities?.find(C=>C.id===o.id),x=D(o.id,o,v,S);console.log(x);return}const l={total:0,passed:0,failed:0,skipped:0,caps:[]};let k=!1;for(const o of d){const j=O(o.id,u),v=h?.capabilities?.find(C=>C.id===o.id);let S=[];if(!j.length)S=[{scenarioId:"(no scenarios)",status:"skip",reason:"register scenarios in inferno/scenarios/"}];else for(const C of j){const _=M(o.id,o,C,v,m);if(S.push(_),g&&_.status==="fail"){k=!0;break}}const x=T(o.id,o,S,r);if(l.total+=x.total,l.passed+=x.passed,l.failed+=x.failed,l.skipped+=x.skipped,l.caps.push({id:o.id,stability:J(o),results:S}),k)break}if(!e){console.log();const o=l.failed>0?b:l.passed>0?F:f;console.log(` ${o(A(String(l.passed)))} passed ${l.failed>0?b(A(String(l.failed))):f("0")} failed ${f(String(l.skipped))} skipped`),k&&console.log(` ${I("(bailed on first failure)")}`),console.log(),l.failed||console.log(f(" \u2500\u2500 infernoflow test complete")),console.log()}e&&console.log(JSON.stringify(l,null,2)),process.exit(l.failed>0?1:0)}export{K as testCommand};
@@ -0,0 +1,18 @@
1
+ import*as u from"node:fs";import*as y from"node:path";import{scanTheme as F}from"../theme/scanner.mjs";import{bold as $,cyan as h,gray as i,green as k,yellow as d,red as I}from"../ui/output.mjs";const p="inferno",S=y.join(p,"theme.json"),w=y.join(p,"sessions.jsonl");function j(e){try{return JSON.parse(u.readFileSync(e,"utf8"))}catch{return null}}function N(e){u.existsSync(w)&&u.appendFileSync(w,JSON.stringify(e)+`
2
+ `,"utf8")}function C(e,o){const c=[];e?.fonts?.primary!==o?.fonts?.primary&&c.push(`primary font: ${e?.fonts?.primary||"none"} \u2192 ${o?.fonts?.primary||"none"}`),e?.fonts?.mono!==o?.fonts?.mono&&c.push(`mono font: ${e?.fonts?.mono||"none"} \u2192 ${o?.fonts?.mono||"none"}`),e?.colors?.mode!==o?.colors?.mode&&c.push(`color mode: ${e?.colors?.mode||"unknown"} \u2192 ${o?.colors?.mode}`);const l=e?.colors?.palette||{},f=o?.colors?.palette||{};for(const t of new Set([...Object.keys(l),...Object.keys(f)]))l[t]!==f[t]&&c.push(`${t} color: ${l[t]||"none"} \u2192 ${f[t]||"none"}`);const a=e?.cssVars||{},r=o?.cssVars||{},n=Object.keys(r).filter(t=>!a[t]),s=Object.keys(r).filter(t=>a[t]&&a[t]!==r[t]);return n.length&&c.push(`new CSS vars: ${n.slice(0,5).join(", ")}`),s.length&&c.push(`changed CSS vars: ${s.slice(0,5).join(", ")}`),c}function O(e){const{fonts:o,colors:c,cssVars:l,framework:f,stats:a}=e;console.log(`
3
+ `+$("\u{1F3A8} Design System")),console.log(" "+"\u2500".repeat(50)),console.log(h(`
4
+ Fonts`)),o.primary&&console.log(` Primary : ${o.primary}`),o.mono&&console.log(` Mono : ${o.mono}`),o.all?.length>2&&console.log(i(` All : ${o.all.join(", ")}`)),o.sources?.length&&console.log(i(` Sources : ${o.sources.join(", ")}`)),console.log(h(`
5
+ Colors`)+i(` (${c.mode} mode)`));for(const[r,n]of Object.entries(c.palette)){const s=`\x1B[48;2;${parseInt(n.slice(1,3),16)};${parseInt(n.slice(3,5),16)};${parseInt(n.slice(5,7),16)}m \x1B[0m`;console.log(` ${r.padEnd(14)} ${s} ${n}`)}if(Object.keys(l).length){console.log(h(`
6
+ CSS Variables`)+i(` (${Object.keys(l).length} found)`));const r=Object.entries(l).slice(0,12);for(const[n,s]of r)console.log(` ${n.padEnd(24)} ${i(s)}`);Object.keys(l).length>12&&console.log(i(` \u2026 and ${Object.keys(l).length-12} more`))}console.log(h(`
7
+ Framework`)+` ${f}`),console.log(i(`
8
+ Scanned: ${a.styleFiles} style files \xB7 ${a.colorsFound} colors \xB7 ${a.varsFound} CSS vars
9
+ `))}async function V(e){const o=n=>e.includes(n),c=o("--dry-run"),l=o("--show")||o("-s"),f=o("--json"),a=o("--watch");if(console.log(`
10
+ `+$("\u{1F525} infernoflow \u2014 theme")),console.log(" "+"\u2500".repeat(50)+`
11
+ `),u.existsSync(p)||(console.error(I(` \u2718 inferno/ not found \u2014 run: infernoflow init
12
+ `)),process.exit(1)),l){const n=j(S);if(!n){console.log(d(` \u26A0 No theme.json yet \u2014 run: infernoflow theme
13
+ `));return}if(f){console.log(JSON.stringify(n,null,2));return}O(n);return}const r=()=>{console.log(i(" Scanning style files\u2026"));const n=process.cwd(),s=F(n);if(f){console.log(JSON.stringify(s,null,2));return}if(O(s),c){console.log(d(` \u2691 Dry run \u2014 theme.json not written
14
+ `));return}const t=j(S),g={...s,scannedAt:new Date().toISOString()};if(u.writeFileSync(S,JSON.stringify(g,null,2)+`
15
+ `,"utf8"),console.log(k(` \u2714 Written \u2192 inferno/theme.json
16
+ `)),t){const m=C(t,s);if(m.length){N({ts:new Date().toISOString(),agent:"infernoflow",type:"theme",summary:"Theme changed: "+m.join("; ")}),console.log(d(" \u26A1 Theme changes logged to sessions.jsonl"));for(const b of m)console.log(i(` \u2022 ${b}`));console.log()}}};if(r(),a){console.log(h(` Watching style files for changes\u2026 (Ctrl+C to stop)
17
+ `));const{watch:n}=await import("node:fs");let s=null;n(process.cwd(),{recursive:!0},(t,g)=>{if(!g)return;const m=y.extname(g);[".css",".scss",".sass",".less",".styl"].includes(m)&&(s&&clearTimeout(s),s=setTimeout(()=>{console.log(i(`
18
+ Change detected: ${g}`)),r()},1e3))}),await new Promise(()=>{})}}export{V as themeCommand};
@@ -0,0 +1,13 @@
1
+ import*as u from"node:fs";import*as r from"node:path";import*as A from"node:os";import*as K from"node:readline";import{bold as I,gray as a,green as D,yellow as J,red as N}from"../ui/output.mjs";const y="inferno",O="CLAUDE.md",k=".claude",S=".cursor",E=r.join(S,"inferno-mcp-server.mjs"),P="inferno-mcp-server.mjs",d=r.join(S,"hooks.json"),b=r.join(S,"hooks","inferno-session-draft.mjs"),v=r.join(S,"mcp.json"),w=r.join(A.homedir(),".claude.json"),L=[".git/hooks/post-commit",".git/hooks/pre-push"],C="# infernoflow";function h(n){return u.existsSync(n)}function m(n){try{return JSON.parse(u.readFileSync(n,"utf8"))}catch{return null}}function F(n){try{return u.readFileSync(n,"utf8")}catch{return null}}function W(n){return new Promise(e=>{const s=K.createInterface({input:process.stdin,output:process.stdout});s.question(n,o=>{s.close(),e(o)})})}function $(n,e,s){const o=[],t=r.join(n,y);if(!h(t))return o;if(s)return o.push({type:"skip",path:y,reason:"--keep-inferno"}),o;if(e){const i=u.readdirSync(t);for(const c of i)if(c==="sessions.jsonl")o.push({type:"skip",path:r.join(y,c),reason:"--keep-memory"});else{const l=r.join(t,c);u.statSync(l).isDirectory()?o.push({type:"rmdir",path:r.join(y,c)}):o.push({type:"rm",path:r.join(y,c)})}}else o.push({type:"rmdir",path:y});return o}function G(n){const e=r.join(n,O);return h(e)?[{type:"rm",path:O}]:[]}function T(n){const e=[],s=r.join(n,k,"settings.json");if(!h(s))return e;const o=m(s),t=o?.tools?.some?.(c=>c.startsWith?.("mcp__infernoflow")),i=o&&Object.keys(o).some(c=>c==="tools"?(o.tools||[]).some(l=>!l.startsWith("mcp__infernoflow")):c!=="tools");return t&&!i?e.push({type:"rm",path:r.join(k,"settings.json"),desc:"auto-approved tools"}):t&&e.push({type:"edit",path:r.join(k,"settings.json"),desc:"remove infernoflow tools (preserve other content)"}),e}function V(n){const e=[];h(r.join(n,E))&&e.push({type:"rm",path:E}),h(r.join(n,P))&&e.push({type:"rm",path:P}),h(r.join(n,b))&&e.push({type:"rm",path:b});const s=r.join(n,d);return h(s)&&((m(s)?.hooks||[]).every(c=>(c.name||c.command||"").includes("inferno"))?e.push({type:"rm",path:d,desc:"infernoflow-only hooks config"}):e.push({type:"edit",path:d,desc:"remove infernoflow hook entry (preserve others)"})),e}function q(n){const e=r.join(n,v);if(!h(e))return[];const s=m(e);return s?.mcpServers?.infernoflow?Object.keys(s.mcpServers||{}).filter(t=>t!=="infernoflow").length===0&&Object.keys(s).length===1?[{type:"rm",path:v,desc:"infernoflow-only file"}]:[{type:"edit",path:v,desc:'remove "infernoflow" key (preserve other servers)'}]:[]}function Y(){return h(w)?m(w)?.mcpServers?.infernoflow?[{type:"edit",path:"~/.claude.json",desc:'remove "infernoflow" MCP entry (preserve other entries)',_realPath:w}]:[]:[]}function z(n){const e=[];for(const s of L){const o=r.join(n,s);if(!h(o))continue;const t=F(o);if(!t?.includes(C))continue;const i=t.split(`
2
+ `),c=i.findIndex(f=>f.includes(C)),l=i.slice(0,c).join(`
3
+ `).trim();!l||l==="#!/bin/sh"||l==="#!/bin/bash"?e.push({type:"rm",path:s,desc:"infernoflow-only hook"}):e.push({type:"edit",path:s,desc:"remove infernoflow section (preserve existing hooks)"})}return e}function B(n,e,s){for(const o of e){if(o.type==="skip")continue;const t=r.join(n,o.path);if(!s)try{o.type==="rmdir"?u.rmSync(t,{recursive:!0,force:!0}):u.unlinkSync(t)}catch{}}}function Q(n,e){if(!e)try{u.unlinkSync(r.join(n,O))}catch{}}function X(n,e,s){const o=r.join(n,k,"settings.json");for(const t of e)if(!s){if(t.type==="rm")try{u.unlinkSync(r.join(n,t.path))}catch{}else if(t.type==="edit")try{const i=m(o);i?.tools&&(i.tools=i.tools.filter(c=>!c.startsWith("mcp__infernoflow"))),u.writeFileSync(o,JSON.stringify(i,null,2)+`
4
+ `,"utf8")}catch{}}}function Z(n,e,s){if(!s){for(const o of e)if(o.type==="rm")try{u.unlinkSync(r.join(n,o.path))}catch{}else if(o.type==="edit"&&o.path===d)try{const t=m(r.join(n,d));t?.hooks&&(t.hooks=t.hooks.filter(i=>!(i.name||i.command||"").includes("inferno"))),u.writeFileSync(r.join(n,d),JSON.stringify(t,null,2)+`
5
+ `,"utf8")}catch{}}}function oo(n,e,s){const o=r.join(n,v);for(const t of e)if(!s){if(t.type==="rm")try{u.unlinkSync(o)}catch{}else if(t.type==="edit")try{const i=m(o);i?.mcpServers?.infernoflow&&delete i.mcpServers.infernoflow,u.writeFileSync(o,JSON.stringify(i,null,2)+`
6
+ `,"utf8")}catch{}}}function eo(n,e){for(const s of n){if(e)continue;const o=s._realPath||w;if(s.type==="edit")try{const t=m(o);t?.mcpServers?.infernoflow&&delete t.mcpServers.infernoflow,u.writeFileSync(o,JSON.stringify(t,null,2)+`
7
+ `,"utf8")}catch{}}}function no(n,e,s){for(const o of e){const t=r.join(n,o.path);if(!s){if(o.type==="rm")try{u.unlinkSync(t)}catch{}else if(o.type==="edit")try{const c=F(t).split(`
8
+ `),l=c.findIndex(g=>g.includes(C)),f=c.slice(0,l).join(`
9
+ `).trimEnd();u.writeFileSync(t,f+`
10
+ `,"utf8")}catch{}}}}async function ro(n=[]){const e=p=>n.includes(p),s=e("--dry-run")||e("--dry"),o=e("--keep-memory"),t=e("--keep-inferno"),i=e("--yes")||e("-y"),c=e("--json"),l=process.cwd(),f={infernoDir:$(l,o,t),claudeMd:G(l),claudeDir:T(l),cursorMcpServer:V(l),cursorMcpJson:q(l),claudeJson:Y(),gitHooks:z(l)},g=Object.values(f).flat(),j=g.filter(p=>p.type!=="skip");if(c){console.log(JSON.stringify({dryRun:s,keepMemory:o,keepInferno:t,plan:f,actionCount:j.length},null,2));return}const R=a(" "+"\u2500".repeat(52));if(console.log(),console.log(" "+I("\u{1F525} infernoflow uninstall")),s&&console.log(J(" DRY RUN \u2014 nothing will be changed")),console.log(R),j.length===0){console.log(),console.log(D(" \u2714 Nothing to remove \u2014 infernoflow is not installed in this project")),console.log();return}console.log(),console.log(" "+I("Will remove:")),console.log();const U={rm:N(" \u2716"),rmdir:N(" \u2716"),edit:J(" ~"),skip:a(" \xB7")};for(const p of g){const x=U[p.type]||" ?",H=p.desc?a(` (${p.desc})`):"";console.log(`${x} ${p.path}${H}`)}if(o&&(console.log(),console.log(a(" \u2139 inferno/sessions.jsonl will be preserved (--keep-memory)"))),console.log(),!s&&!i){if(!(await W(" Continue? "+a("[y/N] "))).trim().toLowerCase().startsWith("y")){console.log(a(`
11
+ Aborted \u2014 nothing changed.
12
+ `));return}console.log()}if(s){console.log(a(` \u2191 Dry run complete \u2014 run without --dry-run to apply
13
+ `));return}B(l,f.infernoDir,!1),f.claudeMd.length&&Q(l,!1),f.claudeDir.length&&X(l,f.claudeDir,!1),f.cursorMcpServer.length&&Z(l,f.cursorMcpServer,!1),f.cursorMcpJson.length&&oo(l,f.cursorMcpJson,!1),f.claudeJson.length&&eo(f.claudeJson,!1),f.gitHooks.length&&no(l,f.gitHooks,!1),console.log(R),console.log(),console.log(D(" \u2714 infernoflow removed from this project")),console.log();const M=j.filter(p=>p.type==="edit"),_=j.filter(p=>p.type==="rm"||p.type==="rmdir");_.length&&console.log(a(" Deleted: ")+_.map(p=>p.path).join(", ")),M.length&&console.log(a(" Edited: ")+M.map(p=>p.path).join(", ")),o&&(console.log(),console.log(a(" Session memory kept \u2192 inferno/sessions.jsonl")),console.log(a(" Re-run infernoflow init to restore the rest."))),console.log()}export{ro as uninstallCommand};
@@ -0,0 +1,20 @@
1
+ import*as n from"node:fs";import*as o from"node:path";import{bold as k,cyan as r,gray as c,green as g,yellow as x,red as O}from"../ui/output.mjs";const m="inferno";function N(l){try{return JSON.parse(n.readFileSync(l,"utf8"))}catch{return null}}async function F(l){const f=process.cwd(),i=l.includes("--dry-run"),v=l.includes("--yes")||l.includes("-y");console.log(`
2
+ `+k("\u{1F525} infernoflow upgrade")),console.log(" "+"\u2500".repeat(50)+`
3
+ `);const a=o.join(f,m);n.existsSync(a)||(console.error(O(` \u2718 inferno/ not found \u2014 run: infernoflow init --lite first
4
+ `)),process.exit(1));const y=o.join(a,".lite"),b=n.existsSync(y),s=N(o.join(a,"contract.json")),j=s?.policyId||o.basename(f),d=s?.capabilities||[];if(!b){console.log(x(` \u26A0 This project is already on the full setup \u2014 nothing to upgrade.
5
+ `));return}console.log(c(` Project: ${j}`)),console.log(c(` Capabilities: ${d.length||0}`)),console.log(c(` Mode: lite \u2192 full
6
+ `));const u=[],w=(e,t)=>{const p=o.join(f,e);if(n.existsSync(p)){console.log(c(` skipped (exists): ${e}`));return}if(i){console.log(r(` would create: ${e}`));return}n.mkdirSync(o.dirname(p),{recursive:!0}),n.writeFileSync(p,t,"utf8"),console.log(g(` \u2714 Created: ${e}`)),u.push(e)};if(d.length){const e={scenarioId:"happy_path",description:"Basic happy-path covering all capabilities",capabilitiesCovered:d,steps:d.map(t=>({action:t,expect:`${t} works as expected`}))};w(o.join(m,"scenarios","happy_path.json"),JSON.stringify(e,null,2)+`
7
+ `)}else i||n.mkdirSync(o.join(a,"scenarios"),{recursive:!0}),console.log(c(" created: inferno/scenarios/ (empty \u2014 add scenarios as you define capabilities)"));w(o.join(m,"CHANGELOG.md"),`# Changelog \u2014 ${j}
8
+
9
+ ## Unreleased
10
+
11
+ - Upgraded from lite setup
12
+
13
+ ## 0.1.0 \u2014 Initial release
14
+
15
+ - Project initialized with infernoflow
16
+ `),!i&&s&&(s.rules={docsRequiredOnCapabilityChange:!0,requireScenarioForEachCapability:!1,requireChangelogOnCapabilityChange:!0},delete s.lite,n.writeFileSync(o.join(a,"contract.json"),JSON.stringify(s,null,2)+`
17
+ `),console.log(g(" \u2714 Updated: inferno/contract.json (added rules)")),u.push("inferno/contract.json"));const h=o.join(f,"package.json");if(n.existsSync(h)&&!i){const e=JSON.parse(n.readFileSync(h,"utf8"));e.scripts=e.scripts||{};let t=!1;const p={"inferno:check":"infernoflow check","inferno:context":"infernoflow context","inferno:theme":"infernoflow theme"};for(const[S,C]of Object.entries(p))e.scripts[S]||(e.scripts[S]=C,t=!0);t&&(n.writeFileSync(h,JSON.stringify(e,null,2)+`
18
+ `),console.log(g(" \u2714 Updated: package.json scripts (inferno:check, inferno:context, inferno:theme)")),u.push("package.json"))}else i&&console.log(r(" would update: package.json scripts"));if(!i&&n.existsSync(y)&&(n.unlinkSync(y),console.log(g(" \u2714 Removed .lite marker \u2014 now on full setup"))),console.log(),i){console.log(x(` \u2691 Dry run \u2014 nothing written. Remove --dry-run to apply.
19
+ `));return}if(!u.length){console.log(c(` Nothing new to create \u2014 already fully set up.
20
+ `));return}console.log(" "+k("Upgrade complete!")),console.log(" "+r("\u2192")+" Run "+r("infernoflow check")+" to validate the contract"),console.log(" "+r("\u2192")+" Run "+r("infernoflow vibe")+" to start auto-sync mode"),console.log()}export{F as upgradeCommand};
@@ -0,0 +1,5 @@
1
+ import*as f from"node:fs";import*as e from"node:path";import{fileURLToPath as $}from"node:url";import{spawnSync as b}from"node:child_process";import{warn as m,bold as x,cyan as D,gray as d,green as I,yellow as P}from"../ui/output.mjs";const R=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".py",".go",".java",".cs",".rb",".swift"]),M=new Set(["node_modules",".git","dist","build","out",".next",".angular","vendor","coverage","__pycache__"]);function W(t){const i=["src","lib","app","pages","components","server","api"].filter(o=>f.existsSync(e.join(t,o)));return i.length?i.map(o=>e.join(t,o)):[t]}function k(t){return R.has(e.extname(t).toLowerCase())}function A(t,r){const i=e.join(r,"capability-map.json");if(!f.existsSync(i))return{relevant:!0,reason:"no cap-map \u2014 suggesting broadly"};let o;try{o=JSON.parse(f.readFileSync(i,"utf8"))}catch{return{relevant:!0,reason:"cap-map unreadable"}}const s=[];for(const g of t){const c=e.relative(process.cwd(),g).replace(/\\/g,"/");for(const[a,u]of Object.entries(o))c.startsWith(a.replace(/\\/g,"/"))&&s.push(...u)}return s.length>0?{relevant:!0,reason:`touches: ${[...new Set(s)].slice(0,3).join(", ")}`}:{relevant:!1,reason:"no mapped capabilities affected"}}function N(t,r,i,o,s){const c=`code changes in ${t.map(a=>e.basename(a,e.extname(a))).slice(0,3).join(", ")}`;if(s||process.stdout.write(` ${P("\u27F3")} suggesting from ${x(String(t.length))} changed file${t.length!==1?"s":""}\u2026 `),o){s||console.log(d("(dry run)"));return}try{b(process.execPath,[e.join(e.dirname(e.dirname(e.dirname($(import.meta.url)))),"bin","infernoflow.mjs"),"suggest",c,"--json"],{cwd:r,encoding:"utf8",timeout:3e4,stdio:"ignore"}),s||console.log(I("done"))}catch{s||console.log(d("skipped (no changes)"))}try{const u=b(process.execPath,[e.join(e.dirname(e.dirname(e.dirname($(import.meta.url)))),"bin","infernoflow.mjs"),"check","--json"],{cwd:r,encoding:"utf8",timeout:15e3}).stdout?.trim();if(u){const w=JSON.parse(u);if(w.status==="error"||w.status==="warning")f.writeFileSync(e.join(i,"WATCH.log"),u+`
2
+ `),s||m("Contract issues detected \u2014 see inferno/WATCH.log");else{const p=e.join(i,"WATCH.log");f.existsSync(p)&&f.unlinkSync(p)}}}catch{}}async function U(t){const r=t.slice(1),i=r.includes("--dry-run"),o=r.includes("--silent"),s=r.indexOf("--interval"),g=((s!==-1?parseFloat(r[s+1]):3)||3)*1e3,c=process.cwd(),a=e.join(c,"inferno");f.existsSync(a)||(m("inferno/ not found. Run: infernoflow init"),process.exit(1));const u=r.filter(n=>!n.startsWith("-")&&n!==String(r[s+1])),p=(u.length?u.map(n=>e.resolve(c,n)):W(c)).filter(n=>f.existsSync(n));p.length||(m("No valid directories to watch."),process.exit(1)),o||(console.log(),console.log(` ${x("\u{1F525} infernoflow watch")} ${d("(Ctrl+C to stop)")}`),console.log(),p.forEach(n=>console.log(` ${D("watching")} ${d(e.relative(c,n)||".")}`)),console.log(` ${d(`debounce: ${g/1e3}s`)}`),console.log());let y=null;const S=new Set,C=n=>{k(n)&&(S.add(n),y&&clearTimeout(y),y=setTimeout(()=>{const l=Array.from(S);if(S.clear(),!o){const T=l.map(_=>e.relative(c,_)).slice(0,3).join(", ");process.stdout.write(`
3
+ ${d(new Date().toLocaleTimeString())} ${x(T)}${l.length>3?` +${l.length-3} more`:""} `)}const{relevant:v,reason:h}=A(l,a);if(!v){o||console.log(d(`skip (${h})`));return}N(l,c,a,i,o)},g))},j=[];for(const n of p)try{const l=f.watch(n,{recursive:!0},(v,h)=>{h&&C(e.join(n,h))});j.push(l)}catch(l){o||m(`Cannot watch ${n}: ${l.message}`)}j.length||(m("No directories could be watched."),process.exit(1)),process.on("SIGINT",()=>{j.forEach(n=>n.close()),o||(console.log(`
4
+
5
+ Stopped.`),console.log()),process.exit(0)}),await new Promise(()=>{})}export{U as watchCommand};
@@ -0,0 +1,19 @@
1
+ import*as s from"node:fs";import*as f from"node:path";import*as b from"node:os";import*as O from"node:https";import*as S from"node:crypto";const d=f.join(b.homedir(),".infernoflow"),w=f.join(d,"telemetry.json"),h=f.join(d,"events.jsonl"),T="https://eu.i.posthog.com",k="phc_z6YX7x4zjkuFZigdXTBoFcPTWeGLFAN9NNKVZ5WHQrqk";function a(){try{return JSON.parse(s.readFileSync(w,"utf8"))}catch{return null}}function u(n){s.existsSync(d)||s.mkdirSync(d,{recursive:!0}),s.writeFileSync(w,JSON.stringify(n,null,2),"utf8")}function y(){try{return S.randomUUID()}catch{return"ifl_"+S.randomBytes(16).toString("hex")}}function N(){const n=a()||{};if(n.installId)return n.installId;const e=y();return u({...n,installId:e}),e}function E(){return a()?.enabled===!0}function D(){const n=a();return n!==null&&typeof n.enabled=="boolean"}function _(){return process.env.CURSOR_SESSION?"cursor":process.env.COPILOT_SESSION?"copilot":process.env.CLAUDE_CODE_SESSION?"claude-code":process.env.WINDSURF_SESSION?"windsurf":process.env.TERM_PROGRAM==="vscode"?"vscode":"unknown"}function j(){try{const n=JSON.parse(s.readFileSync("package.json","utf8")),e={...n.dependencies,...n.devDependencies};return e.next||e.nuxt||e.remix?"fullstack":e.react||e.vue||e.svelte?"frontend":e.express||e.fastify||e.koa?"backend":e["@angular/core"]?"frontend":"js"}catch{return"unknown"}}function C(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return"unknown"}}function x(){try{const n=f.resolve(f.dirname(new URL(import.meta.url).pathname),"../package.json");return JSON.parse(s.readFileSync(n,"utf8")).version}catch{return"unknown"}}async function L(){if(D()||!process.stdin.isTTY)return;const n=a()||{},e=(n.runs||0)+1;if(u({...n,runs:e,enabled:!1}),e<3)return;const{createInterface:o}=await import("node:readline"),t=o({input:process.stdin,output:process.stdout}),l=await new Promise(c=>{process.stdout.write(`
2
+ \u{1F4E1} Help improve infernoflow?
3
+ Share anonymous usage data \u2014 command names, OS, timezone. No code. No personal data.
4
+ Type 'y' to opt in, anything else to decline. (infernoflow telemetry off to change later)
5
+ \u2192 `),t.question("",c)});t.close();const i=l.trim().toLowerCase()==="y",p=i?y():null;u({enabled:i,installId:p,runs:e,decidedAt:new Date().toISOString()}),process.stdout.write(i?` \u2714 Telemetry enabled \u2014 thank you! (infernoflow telemetry off to disable)
6
+
7
+ `:` \u2714 No problem \u2014 telemetry off. (infernoflow telemetry on to enable later)
8
+
9
+ `)}function P(n){if(!E())return;const e=N(),o={ts:new Date().toISOString(),command:n,installId:e,version:x(),node:process.version,os:process.platform,timezone:C(),ide:_(),projectType:j()};try{s.existsSync(d)||s.mkdirSync(d,{recursive:!0}),s.appendFileSync(h,JSON.stringify(o)+`
10
+ `,"utf8")}catch{}F(e,n,o)}function F(n,e,o){try{const t=JSON.stringify({api_key:k,event:e,distinct_id:n,properties:{command:o.command,version:o.version,node:o.node,os:o.os,timezone:o.timezone,ide:o.ide,projectType:o.projectType,$lib:"infernoflow-cli"},timestamp:o.ts}),l=new URL(T+"/capture/"),i=O.request({hostname:l.hostname,path:l.pathname,method:"POST",headers:{"Content-Type":"application/json","Content-Length":Buffer.byteLength(t)},timeout:3e3});i.on("error",()=>{}),i.write(t),i.end()}catch{}}async function R(n){const{bold:e,cyan:o,gray:t,green:l,yellow:i,red:p}=await import("./ui/output.mjs"),c=n[0];if(c==="on"){const r=a()||{},m=r.installId||y();u({...r,enabled:!0,installId:m,decidedAt:new Date().toISOString()}),console.log(l(`
11
+ \u2714 Telemetry enabled \u2014 thank you for helping improve infernoflow!
12
+ `));return}if(c==="off"){const r=a()||{};u({...r,enabled:!1,decidedAt:new Date().toISOString()}),console.log(l(`
13
+ \u2714 Telemetry disabled. No data will be sent.
14
+ `));return}if(c==="status"||!c){const r=a(),m=r?.enabled===!0,I=r?.decidedAt?new Date(r.decidedAt).toLocaleDateString():"never",v=r?.installId?r.installId.slice(0,12)+"\u2026":"none yet";let g=0;try{g=s.readFileSync(h,"utf8").split(`
15
+ `).filter(Boolean).length}catch{}console.log(`
16
+ `+e("\u{1F525} infernoflow telemetry status")+`
17
+ `),console.log(" Status "+(m?l("enabled"):i("disabled"))),console.log(" Install ID "+t(v+" (anonymous, never linked to identity)")),console.log(" Decided "+t(I)),console.log(" Events stored "+t(g+" locally \u2192 "+(m?"also sent to PostHog":"not sent (disabled)"))),console.log(" Backend "+t("PostHog (EU-hosted, no IP stored)")),console.log(),console.log(" "+e("What we collect:")+" "+t("command, version, Node, OS, timezone, IDE, project type")),console.log(" "+e("What we never collect:")+" "+t("code, file names, capability names, email, personal data")),console.log(),console.log(t(" infernoflow telemetry on \u2014 enable")),console.log(t(" infernoflow telemetry off \u2014 disable")),console.log();return}console.error(p(`
18
+ \u2718 Unknown subcommand: ${c}`)),console.log(t(` Usage: infernoflow telemetry [on | off | status]
19
+ `)),process.exit(1)}export{L as ensureTelemetryConsent,D as hasConsentDecision,E as isTelemetryEnabled,R as telemetryCommand,P as trackEvent};
@@ -0,0 +1,4 @@
1
+ import*as p from"node:fs";import*as d from"node:path";const k=new Set([".css",".scss",".sass",".less",".styl"]),v=new Set([".js",".mjs",".jsx",".ts",".tsx"]),L=new Set(["node_modules",".git","dist","build","out",".next","coverage",".cache"]);function T(s,c=6,n=0){if(n>c)return[];let t=[];try{for(const o of p.readdirSync(s,{withFileTypes:!0})){if(L.has(o.name))continue;const r=d.join(s,o.name);o.isDirectory()?t=t.concat(T(r,c,n+1)):o.isFile()&&t.push(r)}}catch{}return t}const P=/#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b/g,B=/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*[\d.]+)?\s*\)/g,Z=/hsla?\(\s*(\d{1,3})\s*,\s*[\d.]+%\s*,\s*[\d.]+%(?:\s*,\s*[\d.]+)?\s*\)/g,_=new Set(["#000","#000000","#fff","#ffffff","#transparent","#333","#666","#999","#ccc","#eee"]);function b(s){return s.length===4?"#"+s[1]+s[1]+s[2]+s[2]+s[3]+s[3]:s.toLowerCase()}function G(s,c,n){return"#"+[s,c,n].map(t=>parseInt(t).toString(16).padStart(2,"0")).join("")}function E(s){const c={};for(const n of s.matchAll(P)){const t=b(n[0]);_.has(t)||(c[t]=(c[t]||0)+1)}for(const n of s.matchAll(B)){const t=G(n[1],n[2],n[3]);_.has(t)||(c[t]=(c[t]||0)+1)}return c}const M=/font-family\s*:\s*([^;}{]+)/gi,D=/@font-face\s*\{[^}]*font-family\s*:\s*['"]?([^'";]+)['"]?/gi,H=/fonts\.googleapis\.com\/css[^"']*family=([^"'&]+)/gi,Q=/from\s+['"]next\/font['"]\s*.*?{\s*([A-Z][a-zA-Z_]+)\s*}/gs,X=/import\s+\{([^}]+)\}\s+from\s+['"]@fontsource\/([^'"]+)['"]/g;function $(s){return s.split(",")[0].trim().replace(/['"]/g,"").replace(/\s+/g," ").trim()}function q(s,c){const n=new Set,t=new Set;for(const o of s.matchAll(M)){const r=$(o[1]);r&&!r.includes("var(")&&r.length>2&&n.add(r)}for(const o of s.matchAll(D)){const r=o[1].trim().replace(/['"]/g,"");r&&(n.add(r),t.add("local/@font-face"))}for(const o of s.matchAll(H)){const r=decodeURIComponent(o[1]).split("|");for(const i of r){const l=i.split(":")[0].replace(/\+/g," ").trim();l&&(n.add(l),t.add("Google Fonts"))}}for(const o of s.matchAll(X)){const i=o[2].trim().split("/").pop().replace(/-/g," ").replace(/\b\w/g,l=>l.toUpperCase());n.add(i),t.add("@fontsource")}return{fonts:[...n],sources:[...t]}}const z=/--([\w-]+)\s*:\s*([^;}{]+)/g;function K(s){const c={},n=[],t=/(?::root|html)\s*\{([^}]+)\}/gi;for(const o of s.matchAll(t))n.push(o[1]);n.push(s);for(const o of n)for(const r of o.matchAll(z)){const i="--"+r[1].trim(),l=r[2].trim();l&&!l.includes("{")&&(c[i]=l)}return c}function U(s){try{const c=p.readFileSync(s,"utf8"),n={},t={},o=/colors\s*:\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/g;for(const i of c.matchAll(o)){const l=i[1];for(const e of l.matchAll(/['"]?([\w-]+)['"]?\s*:\s*['"]?(#[0-9a-fA-F]{3,6})['"]?/g))n[e[1]]=b(e[2])}const r=/fontFamily\s*:\s*\{([^}]+)\}/g;for(const i of c.matchAll(r)){const l=i[1];for(const e of l.matchAll(/['"]?([\w-]+)['"]?\s*:\s*\[['"]([^'"]+)['"]/g))t[e[1]]=e[2]}return{colors:n,fonts:t}}catch{return null}}function V(s,c){const n=o=>s.some(r=>d.basename(r)===o),t=o=>c.includes(o);return n("tailwind.config.js")||n("tailwind.config.ts")||n("tailwind.config.mjs")?"tailwind":t("styled-components")||t("createGlobalStyle")?"styled-components":t("@emotion/react")||t("css`")&&t("emotion")?"emotion":t("createTheme")&&t("@mui/material")?"mui":t("ChakraProvider")||t("@chakra-ui")?"chakra":t(".module.css")||t(".module.scss")?"css-modules":s.some(o=>k.has(d.extname(o)))?"plain-css":"unknown"}function J(s,c){const t=Object.entries(s).sort((e,f)=>f[1]-e[1]).slice(0,12).map(([e])=>e),o={},r={};for(const[e,f]of Object.entries(c))/^#[0-9a-fA-F]{3,6}$/.test(f)&&(r[b(f)]=e);for(const e of t){const f=r[e];if(f){const y=f.replace(/^--/,"").replace(/-color$/,"");o[y]=e}}Object.keys(o).length<3&&t.slice(0,6).forEach((e,f)=>{Object.values(o).includes(e)||(o[`color${f+1}`]=e)});const i=Object.entries(o).filter(([e])=>/bg|background|surface|base/.test(e)).map(([,e])=>e);let l="unknown";if(i.length){const e=parseInt(i[0].slice(1),16);l=(e>>16&255)*.299+(e>>8&255)*.587+(e&255)*.114<128?"dark":"light"}return{palette:o,mode:l,raw:t}}function W(s=process.cwd()){const c=T(s),n=c.filter(a=>k.has(d.extname(a))),t=c.filter(a=>v.has(d.extname(a))),o=c.filter(a=>[".html",".htm"].includes(d.extname(a))),r=c.find(a=>/tailwind\.config\.(js|ts|mjs|cjs)$/.test(a)),i=[...n,...o].map(a=>{try{return p.readFileSync(a,"utf8")}catch{return""}}),l=t.filter(a=>{try{return p.statSync(a).size<2e5}catch{return!1}}).map(a=>{try{return p.readFileSync(a,"utf8")}catch{return""}}),e=i.join(`
2
+ `),f=l.join(`
3
+ `),y=e+`
4
+ `+f,u=E(e),O=E(f);for(const[a,m]of Object.entries(O))u[a]=(u[a]||0)+Math.round(m*.5);const F=K(e),h={fonts:new Set,sources:new Set};for(const a of[...i,...l]){const{fonts:m,sources:I}=q(a,"");m.forEach(S=>h.fonts.add(S)),I.forEach(S=>h.sources.add(S))}const A=V(c,y);let g=null;if(r&&(g=U(r),g)){for(const[a,m]of Object.entries(g.colors))u[m]=(u[m]||0)+5;for(const[a,m]of Object.entries(g.fonts))h.fonts.add(m)}const{palette:R,mode:x,raw:C}=J(u,F),w=[...h.fonts].filter(a=>!["inherit","initial","unset","system-ui","sans-serif","serif","monospace","-apple-system","BlinkMacSystemFont","Segoe UI"].includes(a)),j=/mono|code|courier|consol|jetbrain|fira|hack|source code/i,N=w.find(a=>j.test(a));return{fonts:{primary:w.find(a=>!j.test(a))||null,mono:N||null,all:w,sources:[...h.sources]},colors:{palette:R,mode:x,raw:C.slice(0,20)},cssVars:F,framework:A,tailwind:g,stats:{styleFiles:n.length,jsFiles:t.length,colorsFound:Object.keys(u).length,varsFound:Object.keys(F).length}}}export{W as scanTheme};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "infernoflow",
3
- "version": "0.38.14",
4
- "description": "Persistent memory for AI coding sessions captures what agents can't infer from code alone. Works with Copilot, Cursor, Claude, and Windsurf.",
3
+ "version": "0.38.16",
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": {
7
7
  "infernoflow": "dist/bin/infernoflow.mjs"