infernoflow 0.38.15 → 0.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,57 +1,101 @@
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.202026-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.39.0 — 2026-05-02
4
+
5
+ ### Added
6
+ - **Memory-mode-aware `status`** — no longer prints "✘ contract.json not found" as if something is broken. In memory mode (the default since v0.37.0) it shows entries/gotchas/decisions/attempts/last-entry plus a next-step prompt. JSON mode equivalent.
7
+ - **`doctor` router-integrity check** — scans `bin/infernoflow.mjs` for every imported command module and verifies the file exists. Catches the "vapor commands" class of regression where the CLI advertises commands whose implementation was deleted.
8
+ - **`doctor` `.gitignore` sanity check** — flags missing `node_modules/` exclusion (the kind of thing that lets 5,200+ dependency files leak into git).
9
+ - **`doctor` correct cloud-credential detection** reads `~/.infernoflow/credentials.json` (the real path), shows logged-in user, and warns when token has expired.
10
+ - **Honest cloud documentation** — `lib/cloud/supabase.mjs` header, `login` success message, and `scripts/supabase-schema.sql` now accurately describe the anonymous-token write model rather than implying authenticated RLS that isn't enforced.
11
+
12
+ ### Changed
13
+ - **README repositioned** to lead with session memory (the actual product per `package.json` description and the strategic plan), with capability contracts as a secondary track. Added 5-command core table, badges, MCP-tools list expanded from 4 to the actual 9 tools registered by the server, cloud sync section with auth-model disclosure.
14
+ - **`doctor` memory-mode awareness** — scenarios/changelog/CONTEXT.md checks now short-circuit to "n/a in memory mode" instead of warning, since none of those exist by design in memory-mode projects.
15
+
16
+ ### Fixed
17
+ - **`doctor` crashed on launch** with `Error: The requested module '../ai/providerRouter.mjs' does not provide an export named 'detectAvailableProviders'`. Added the missing function (env-var-based provider detection).
18
+ - **`uninstall` crashed** with `hooks.every is not a function` when `.cursor/hooks.json` used the newer object-keyed-by-event format instead of a flat array. Now normalises both shapes.
19
+
20
+ ## 0.38.16 — 2026-05-02
21
+
22
+ ### Fixed
23
+ - **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.
24
+ - **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`).
25
+ - **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.
26
+ - **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.
27
+ - **`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.
28
+ - **`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`.
29
+ - **`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.
30
+
31
+ ### Changed
32
+ - **`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.
33
+ - **`.gitattributes`** added normalizes line endings so the index stores LF and Windows working trees can use CRLF without polluting diffs.
34
+ - **`.gitignore`** rewritten — properly excludes `node_modules/` and `**/node_modules/` (was missing, leading to 5,200+ tracked dependency files), plus standard Node/editor/OS artifacts.
35
+
36
+ ### Internal
37
+ - 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`.
38
+ - Added a `backup-broken-v0.38.9` git tag pointing at the wipe commit.
39
+
40
+
41
+ ## 0.38.16 — 2026-05-02
42
+
43
+ ### Fixed
44
+ - **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.
45
+ - **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`).
46
+ - **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.
47
+ - **`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.
48
+ - **`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`.
49
+ - **`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.
50
+
51
+ ### Changed
52
+ - **`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.
53
+ - **`.gitattributes`** added — normalizes line endings so the index stores LF and Windows working trees can use CRLF without polluting diffs.
54
+ - **`.gitignore`** rewritten — properly excludes `node_modules/` (was missing, leading to 5,200+ tracked dependency files), plus standard Node/editor/OS artifacts.
55
+
56
+ ### Internal
57
+ - 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`.
58
+ - 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.
59
+
60
+ ## 0.10.25 — 2026-04-22
61
+
62
+ ### Added
63
+ - Release 0.10.25
64
+
65
+
66
+ ## 0.10.24 — 2026-04-21
67
+
68
+ ### Added
69
+ - Release 0.10.24
70
+
71
+
72
+ ## 0.10.23 — 2026-04-21
73
+
74
+ ### Added
75
+ - Release 0.10.23
76
+
77
+
78
+ ## 0.10.22 — 2026-04-21
79
+
80
+ ### Added
81
+ - Release 0.10.22
82
+
83
+
84
+ ## 0.10.21 — 2026-04-21
85
+
86
+ ### Added
87
+ - Release 0.10.21
88
+
89
+
90
+ ## 0.10.20 — 2026-04-21
91
+
92
+ ### Added
93
+ - Release 0.10.20
94
+
95
+
96
+ ## 0.10.19 — 2026-04-21
97
+
98
+ ## 0.10.12 — 2026-04-12
99
+
100
+ ### Added
101
+ - `infernoflow install-cursor-hooks` — Cursor Agent hooks append assistant replies to `inferno/CON
package/README.md CHANGED
@@ -1,97 +1,142 @@
1
- # 🔥 infernoflow
2
- > The forge for liquid code — keep capabilities, contracts, and docs in sync with your codebase.
3
-
4
- ## What it does
5
- infernoflow ensures that when your code changes, your **capability contracts** and **documentation** stay in sync. It prevents semantic drift — where code evolves but no one knows what the system can actually do.
6
-
7
- ## Install
8
- ```bash
9
- npm install -g infernoflow
10
- # or:
11
- npx infernoflow init
12
- ```
13
-
14
- ## Quick Start
15
- ```bash
16
- npx infernoflow init
17
- npx infernoflow install-cursor-hooks # installs MCP server + .cursor/mcp.json
18
- # Restart Cursor → Settings → MCP → infernoflow: 4 tools enabled
19
- infernoflow status
20
- infernoflow suggest "added email notifications"
21
- infernoflow check
22
- ```
23
-
24
- ## Cursor MCP Integration (recommended)
25
-
26
- After running `install-cursor-hooks`, infernoflow registers as an MCP server inside Cursor. No copy/paste — Cursor calls infernoflow tools directly in chat.
27
-
28
- ### Setup
29
- ```bash
30
- infernoflow install-cursor-hooks
31
- # Restart Cursor
32
- # Settings → MCP → infernoflow: 4 tools enabled
33
- ```
34
-
35
- ### MCP tools
36
-
37
- | Tool | What it does |
38
- |---|---|
39
- | `infernoflow_run` | Generates a task prompt from your contract |
40
- | `infernoflow_apply` | Applies the JSON response updates contract + CHANGELOG |
41
- | `infernoflow_check` | Validates contract sync |
42
- | `infernoflow_status` | Shows contract health |
43
-
44
- ### Workflow in Cursor chat
45
- ```
46
- You: Use infernoflow_run with task "add search functionality"
47
- Cursor: [calls infernoflow_run → returns prompt]
48
- Cursor: [generates JSON]
49
- Cursor: [calls infernoflow_apply]
50
- contract.json, capabilities.json, CHANGELOG.md updated + validated
51
- ```
52
-
53
- ### Terminal fallback (without MCP)
54
- ```bash
55
- infernoflow run "add search functionality"
56
- # writes inferno/agent-prompt.md and waits
57
- # paste prompt into Cursor/Claude → save JSON to inferno/agent-response.json
58
- # infernoflow picks it up and applies automatically
59
- ```
60
-
61
- ## Commands
62
-
63
- | Command | Description |
64
- |---|---|
65
- | `infernoflow init` | Scaffold inferno/ in your project |
66
- | `infernoflow install-cursor-hooks` | MCP server + hooks + .cursor/mcp.json |
67
- | `infernoflow install-vscode-copilot-hooks` | VS Code + Copilot hooks (Preview) |
68
- | `infernoflow status` | Contract health at a glance |
69
- | `infernoflow check` | Full validation |
70
- | `infernoflow suggest` | AI-powered contract update |
71
- | `infernoflow run` | One-command flow with rollback |
72
- | `infernoflow implement` | Generate coding agent prompts |
73
- | `infernoflow context` | Build AI session context |
74
- | `infernoflow doc-gate` | Fail if docs not updated |
75
- | `infernoflow pr-impact` | Analyze PR capability drift |
76
-
77
- ## CI Integration
78
- ```yaml
79
- - name: infernoflow check
80
- run: npx infernoflow check --json
81
- - name: infernoflow doc-gate
82
- run: npx infernoflow doc-gate --json
83
- ```
84
-
85
- ## Troubleshooting
86
-
87
- - **MCP not showing in Cursor** — restart Cursor completely after install-cursor-hooks
88
- - `ide_agent_bridge_not_configured` — use MCP tools in Cursor chat instead
89
- - **infernoflow not found** use `npx infernoflow` or install globally
90
- - **PowerShell scripts disabled** — run `Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass`
91
-
92
- ## Why infernoflow?
93
-
94
- AI-assisted development moves fast. Code changes daily. But what does the system *actually do*? infernoflow keeps the answer current — automatically.
95
-
96
- ## License
97
- MIT
1
+ # 🔥 infernoflow
2
+
3
+ > Persistent memory for AI coding sessions. Captures what agents can't infer from code: gotchas, decisions, dead ends. Replays it into your next AI chat so you stop re-derived context every time.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/infernoflow.svg?color=orange)](https://www.npmjs.com/package/infernoflow)
6
+ [![npm downloads](https://img.shields.io/npm/dw/infernoflow.svg?color=orange)](https://www.npmjs.com/package/infernoflow)
7
+
8
+ ## The 60-second pitch
9
+
10
+ Every new Copilot/Cursor/Claude session starts cold. The agent re-reads your code, ignores constraints that aren't expressed there, and often re-makes the same wrong move someone else made yesterday. infernoflow is a small CLI that captures *those things* — the API quirks, the failed approaches, the architectural decisions — and replays them into the next AI session as a clean handoff. One command. No paste, no copy, no manual setup.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install -g infernoflow
16
+ # or zero-install:
17
+ npx infernoflow init
18
+ ```
19
+
20
+ Zero npm dependencies. Works on Node ≥ 18. Windows, macOS, Linux.
21
+
22
+ ## Quick start (90 seconds)
23
+
24
+ ```bash
25
+ cd your-project
26
+ infernoflow init # 30-second setup, asks for your first gotcha
27
+ infernoflow log "API returns 202 not 200" --type gotcha
28
+ infernoflow log "use polling not websocket for progress" --type decision
29
+ infernoflow ask "API" # search your memory
30
+ infernoflow switch --copy # generate handoff, copy to clipboard
31
+ # paste into your next Cursor/Copilot/Claude chat — the agent picks up everything
32
+ ```
33
+
34
+ ## The 5-command core
35
+
36
+ These five cover 90% of usage:
37
+
38
+ | Command | What it does |
39
+ |---|---|
40
+ | `infernoflow log "..."` | Remember a gotcha, decision, attempt, or note. `--type gotcha\|decision\|attempt\|preference` |
41
+ | `infernoflow ask "..."` | Search your memory by keyword. Gotchas surface first. |
42
+ | `infernoflow switch` | Generate a handoff for your next AI session. `--copy` puts it on the clipboard. |
43
+ | `infernoflow recap` | End-of-session summary with health score and unlogged-change detection. |
44
+ | `infernoflow status` | Quick session-memory health check. |
45
+
46
+ Run `infernoflow commands` for the full grouped list (51 commands across Session Memory, Code Analysis, Workflow, Cloud, Setup, Advanced).
47
+
48
+ ## Auto-context for AI agents
49
+
50
+ When you run `infernoflow log`, infernoflow silently keeps these files up to date so any AI agent reading them gets your latest gotchas/decisions automatically:
51
+
52
+ - `CLAUDE.md` — picked up by Claude Code
53
+ - `.cursorrules` picked up by Cursor
54
+ - `.github/copilot-instructions.md` — picked up by GitHub Copilot
55
+
56
+ You don't have to paste anything. Set up once, every future session is better.
57
+
58
+ ## Cursor / VS Code MCP integration
59
+
60
+ ```bash
61
+ infernoflow install-cursor-hooks
62
+ # Restart Cursor → Settings → MCP → infernoflow: 4 tools enabled
63
+
64
+ # or for VS Code + Copilot (Preview):
65
+ infernoflow install-vscode-copilot-hooks
66
+ ```
67
+
68
+ After install-cursor-hooks, your AI agent can call infernoflow directly in chat:
69
+
70
+ | MCP tool | What it does |
71
+ |---|---|
72
+ | `infernoflow_run` | Generate a task prompt from your contract |
73
+ | `infernoflow_apply` | Apply the JSON response — updates contract + CHANGELOG |
74
+ | `infernoflow_check` | Validate contract sync |
75
+ | `infernoflow_status` | Show contract health |
76
+ | `infernoflow_context` | Generate AI-ready context for a new session |
77
+ | `infernoflow_implement` | Step-by-step code prompt for a specific task |
78
+ | `infernoflow_review` | Pre-merge capability drift check on the current branch |
79
+ | `infernoflow_git_drift` | Detect capabilities affected by recent commits |
80
+ | `infernoflow_scan_ui` | Detect UI / design-token changes vs contract |
81
+
82
+ ## Cloud sync (optional)
83
+
84
+ ```bash
85
+ infernoflow login # GitHub Device Flow — no PKCE, no callback server
86
+ infernoflow whoami
87
+ ```
88
+
89
+ Once logged in, every `infernoflow log` quietly mirrors the entry to a Supabase project so your memory survives across machines. Push is fire-and-forget; local always succeeds even if cloud is down.
90
+
91
+ > **Auth model (v0.38.x):** the cloud currently uses anonymous-key writes with a per-user `user_token` column. Anyone with the public anon key can write rows — fine for solo dev, not yet a security boundary. The schema is forward-compatible with authenticated mode; see `scripts/supabase-schema.sql` for the migration path.
92
+
93
+ ## Capability contracts (advanced)
94
+
95
+ The "memory" track above (Tier 1) is what most users want. infernoflow also ships a heavier "contracts" track for teams that want machine-checked guarantees about what their codebase *does*:
96
+
97
+ ```bash
98
+ infernoflow init --mode full # set up contract.json, capabilities, scenarios
99
+ infernoflow scan # AST-walk to discover capabilities
100
+ infernoflow freeze CreateItem # mark a capability as protected — AI won't modify it
101
+ infernoflow impact CreateItem # blast radius before changes
102
+ infernoflow check # CI gate
103
+ ```
104
+
105
+ Most users don't need this. If you do, run `infernoflow demo` for an interactive walkthrough.
106
+
107
+ ## CI integration
108
+
109
+ ```yaml
110
+ - name: infernoflow check
111
+ run: npx infernoflow check --json
112
+ - name: infernoflow doc-gate
113
+ run: npx infernoflow doc-gate --json
114
+ ```
115
+
116
+ Or use the GitHub Action:
117
+
118
+ ```yaml
119
+ - uses: ronmiz/infernoflow-action@v1
120
+ ```
121
+
122
+ ## Troubleshooting
123
+
124
+ - **MCP not showing in Cursor** — fully quit and relaunch Cursor after `install-cursor-hooks`.
125
+ - **`infernoflow not found`** — use `npx infernoflow` or `npm install -g infernoflow`.
126
+ - **PowerShell script execution blocked** — `Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass`.
127
+ - **`infernoflow doctor`** — runs a full diagnostic if anything looks wrong.
128
+ - **Box-drawing chars look broken in PowerShell** — should auto-fall-back to ASCII; if not, you're on a non-WT_SESSION shell, please open an issue.
129
+
130
+ ## Why infernoflow?
131
+
132
+ Code changes daily. But what does the system *actually do*? What did someone try last week that didn't work? What invariants are load-bearing? infernoflow keeps the answer current — and feeds it to your AI agent so it stops re-deriving from scratch.
133
+
134
+ ## License
135
+
136
+ MIT
137
+
138
+ ## Links
139
+
140
+ - [GitHub](https://github.com/ronmiz/infernoflow)
141
+ - [npm](https://www.npmjs.com/package/infernoflow)
142
+ - [Issues](https://github.com/ronmiz/infernoflow/issues)
@@ -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)});
@@ -1 +1 @@
1
- import{detectIdeContext as r}from"./ideDetection.mjs";async function l(d="auto",i="auto"){const a=String(d||"auto").toLowerCase(),e=r(i),t=[...e.reasonCodes];return a==="local"?(t.push("LOCAL_PROVIDER_SELECTED"),{providerRequested:a,providerResolved:"local",ideDetected:e.ideDetected,agentAvailable:e.agentAvailable,reasonCodes:t}):a==="prompt"?(t.push("PROMPT_PROVIDER_SELECTED"),{providerRequested:a,providerResolved:"prompt",ideDetected:e.ideDetected,agentAvailable:e.agentAvailable,reasonCodes:t}):a==="agent"?e.agentAvailable?(t.push("IDE_AGENT_SELECTED"),{providerRequested:a,providerResolved:"agent",ideDetected:e.ideDetected,agentAvailable:e.agentAvailable,reasonCodes:t}):(t.push("EXPLICIT_AGENT_REQUIRED"),{providerRequested:a,providerResolved:"none",ideDetected:e.ideDetected,agentAvailable:e.agentAvailable,reasonCodes:t,error:"agent_unavailable"}):e.agentAvailable?(t.push("IDE_AGENT_SELECTED"),{providerRequested:"auto",providerResolved:"agent",ideDetected:e.ideDetected,agentAvailable:e.agentAvailable,reasonCodes:t}):(t.push("FALLBACK_PROMPT_MODE"),{providerRequested:"auto",providerResolved:"prompt",ideDetected:e.ideDetected,agentAvailable:e.agentAvailable,reasonCodes:t})}export{l as resolveProvider};
1
+ import{detectIdeContext as i}from"./ideDetection.mjs";async function d(o="auto",r="auto"){const a=String(o||"auto").toLowerCase(),e=i(r),t=[...e.reasonCodes];return a==="local"?(t.push("LOCAL_PROVIDER_SELECTED"),{providerRequested:a,providerResolved:"local",ideDetected:e.ideDetected,agentAvailable:e.agentAvailable,reasonCodes:t}):a==="prompt"?(t.push("PROMPT_PROVIDER_SELECTED"),{providerRequested:a,providerResolved:"prompt",ideDetected:e.ideDetected,agentAvailable:e.agentAvailable,reasonCodes:t}):a==="agent"?e.agentAvailable?(t.push("IDE_AGENT_SELECTED"),{providerRequested:a,providerResolved:"agent",ideDetected:e.ideDetected,agentAvailable:e.agentAvailable,reasonCodes:t}):(t.push("EXPLICIT_AGENT_REQUIRED"),{providerRequested:a,providerResolved:"none",ideDetected:e.ideDetected,agentAvailable:e.agentAvailable,reasonCodes:t,error:"agent_unavailable"}):e.agentAvailable?(t.push("IDE_AGENT_SELECTED"),{providerRequested:"auto",providerResolved:"agent",ideDetected:e.ideDetected,agentAvailable:e.agentAvailable,reasonCodes:t}):(t.push("FALLBACK_PROMPT_MODE"),{providerRequested:"auto",providerResolved:"prompt",ideDetected:e.ideDetected,agentAvailable:e.agentAvailable,reasonCodes:t})}function l(o){return{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}}export{l as detectAvailableProviders,d as resolveProvider};
@@ -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};