infernoflow 0.32.8 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/bin/infernoflow.mjs +84 -255
  2. package/dist/lib/adopters/angular.mjs +1 -128
  3. package/dist/lib/adopters/css.mjs +1 -111
  4. package/dist/lib/adopters/react.mjs +1 -104
  5. package/dist/lib/ai/ideDetection.mjs +1 -31
  6. package/dist/lib/ai/localProvider.mjs +1 -88
  7. package/dist/lib/ai/providerRouter.mjs +2 -295
  8. package/dist/lib/commands/adopt.mjs +20 -869
  9. package/dist/lib/commands/adoptWizard.mjs +9 -320
  10. package/dist/lib/commands/agent.mjs +5 -191
  11. package/dist/lib/commands/ai.mjs +2 -407
  12. package/dist/lib/commands/audit.mjs +13 -300
  13. package/dist/lib/commands/changelog.mjs +26 -594
  14. package/dist/lib/commands/check.mjs +3 -184
  15. package/dist/lib/commands/ci.mjs +3 -208
  16. package/dist/lib/commands/claudeMd.mjs +25 -130
  17. package/dist/lib/commands/cloud.mjs +5 -521
  18. package/dist/lib/commands/context.mjs +34 -287
  19. package/dist/lib/commands/coverage.mjs +2 -282
  20. package/dist/lib/commands/dashboard.mjs +123 -635
  21. package/dist/lib/commands/demo.mjs +8 -465
  22. package/dist/lib/commands/diff.mjs +5 -274
  23. package/dist/lib/commands/docGate.mjs +2 -81
  24. package/dist/lib/commands/doctor.mjs +3 -321
  25. package/dist/lib/commands/explain.mjs +8 -438
  26. package/dist/lib/commands/export.mjs +10 -239
  27. package/dist/lib/commands/generateSkills.mjs +38 -163
  28. package/dist/lib/commands/graph.mjs +203 -321
  29. package/dist/lib/commands/health.mjs +2 -309
  30. package/dist/lib/commands/impact.mjs +2 -325
  31. package/dist/lib/commands/implement.mjs +7 -103
  32. package/dist/lib/commands/init.mjs +23 -475
  33. package/dist/lib/commands/installCursorHooks.mjs +1 -36
  34. package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -37
  35. package/dist/lib/commands/link.mjs +2 -342
  36. package/dist/lib/commands/log.mjs +16 -0
  37. package/dist/lib/commands/monorepo.mjs +4 -428
  38. package/dist/lib/commands/notify.mjs +4 -258
  39. package/dist/lib/commands/onboard.mjs +4 -296
  40. package/dist/lib/commands/prComment.mjs +2 -361
  41. package/dist/lib/commands/prImpact.mjs +2 -157
  42. package/dist/lib/commands/publish.mjs +15 -316
  43. package/dist/lib/commands/report.mjs +28 -272
  44. package/dist/lib/commands/review.mjs +9 -223
  45. package/dist/lib/commands/run.mjs +8 -336
  46. package/dist/lib/commands/scaffold.mjs +54 -419
  47. package/dist/lib/commands/scan.mjs +5 -558
  48. package/dist/lib/commands/scout.mjs +2 -291
  49. package/dist/lib/commands/setup.mjs +5 -310
  50. package/dist/lib/commands/share.mjs +13 -196
  51. package/dist/lib/commands/snapshot.mjs +3 -383
  52. package/dist/lib/commands/stability.mjs +2 -293
  53. package/dist/lib/commands/status.mjs +4 -172
  54. package/dist/lib/commands/suggest.mjs +21 -563
  55. package/dist/lib/commands/syncAuto.mjs +1 -96
  56. package/dist/lib/commands/synthesize.mjs +10 -228
  57. package/dist/lib/commands/teamSync.mjs +2 -388
  58. package/dist/lib/commands/test.mjs +6 -363
  59. package/dist/lib/commands/theme.mjs +18 -0
  60. package/dist/lib/commands/version.mjs +2 -282
  61. package/dist/lib/commands/vibe.mjs +7 -357
  62. package/dist/lib/commands/watch.mjs +4 -203
  63. package/dist/lib/commands/why.mjs +4 -358
  64. package/dist/lib/cursorHooksInstall.mjs +1 -60
  65. package/dist/lib/draftToolingInstall.mjs +7 -68
  66. package/dist/lib/git/detect-drift.mjs +4 -208
  67. package/dist/lib/learning/adapt.mjs +6 -101
  68. package/dist/lib/learning/observe.mjs +1 -119
  69. package/dist/lib/learning/patternDetector.mjs +1 -298
  70. package/dist/lib/learning/profile.mjs +2 -279
  71. package/dist/lib/learning/skillSynthesizer.mjs +24 -145
  72. package/dist/lib/templates/index.mjs +1 -131
  73. package/dist/lib/theme/scanner.mjs +4 -0
  74. package/dist/lib/ui/errors.mjs +1 -142
  75. package/dist/lib/ui/output.mjs +6 -72
  76. package/dist/lib/ui/prompts.mjs +6 -147
  77. package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
  78. package/dist/templates/cursor/inferno-mcp-server.mjs +29 -0
  79. package/dist/templates/github-app/GITHUB_APP.md +67 -0
  80. package/dist/templates/github-app/app-manifest.json +20 -0
  81. package/package.json +1 -1
@@ -1,184 +1,3 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import { header, ok, fail, warn, info, section, done, errorAndExit, cyan, bold, red, green, yellow, gray } from "../ui/output.mjs";
4
- import { docGateCommand } from "./docGate.mjs";
5
-
6
- function readJson(filePath, jsonOut = false) {
7
- try { return JSON.parse(fs.readFileSync(filePath, "utf8")); }
8
- catch (err) {
9
- if (jsonOut) {
10
- throw new Error(`Cannot parse ${path.basename(filePath)}`);
11
- }
12
- errorAndExit(
13
- `Cannot parse ${path.basename(filePath)}`,
14
- `Check JSON syntax in: ${filePath}`
15
- );
16
- }
17
- }
18
-
19
- function getScenarioFiles(scenariosDir) {
20
- if (!fs.existsSync(scenariosDir)) return [];
21
- return fs.readdirSync(scenariosDir)
22
- .filter(f => f.endsWith(".json"))
23
- .map(f => path.join(scenariosDir, f));
24
- }
25
-
26
- function getCovered(scenarioFiles) {
27
- const covered = new Set();
28
- for (const f of scenarioFiles) {
29
- try {
30
- const s = JSON.parse(fs.readFileSync(f, "utf8"));
31
- (s.capabilitiesCovered || []).forEach(c => covered.add(c));
32
- } catch {}
33
- }
34
- return covered;
35
- }
36
-
37
- export async function checkCommand(args) {
38
- const cwd = process.cwd();
39
- const infernoDir = path.join(cwd, "inferno");
40
- const skipDocGate = args.includes("--skip-doc-gate");
41
- const jsonOut = args.includes("--json");
42
-
43
- if (!jsonOut) header("check");
44
-
45
- const errors = [];
46
- const warnings = [];
47
-
48
- // ── inferno/ exists ─────────────────────────────────────────────
49
- if (!fs.existsSync(infernoDir)) {
50
- if (jsonOut) { console.log(JSON.stringify({ ok: false, errors: ["inferno/ not found"] })); process.exit(1); }
51
- errorAndExit("inferno/ not found", `Run: infernoflow init`);
52
- }
53
-
54
- // ── contract.json ───────────────────────────────────────────────
55
- const contractPath = path.join(infernoDir, "contract.json");
56
- const capsPath = path.join(infernoDir, "capabilities.json");
57
- const scenariosDir = path.join(infernoDir, "scenarios");
58
- const changelogPath = path.join(infernoDir, "CHANGELOG.md");
59
-
60
- if (!jsonOut) section("Contract");
61
- if (!fs.existsSync(contractPath)) {
62
- fail("contract.json not found", "Run: infernoflow init");
63
- errors.push("contract.json missing");
64
- } else {
65
- let contract;
66
- try {
67
- contract = readJson(contractPath, jsonOut);
68
- } catch (err) {
69
- errors.push(err.message);
70
- if (!jsonOut) {
71
- fail(err.message);
72
- process.exit(1);
73
- }
74
- console.log(JSON.stringify({ ok: false, errors, warnings }, null, 2));
75
- process.exit(1);
76
- }
77
- const caps = contract.capabilities || [];
78
-
79
- if (!contract.policyId) { fail("policyId missing"); errors.push("policyId missing"); }
80
- else if (!jsonOut) ok(`policyId: ${bold(contract.policyId)}`);
81
-
82
- if (!Number.isInteger(contract.policyVersion)) { fail("policyVersion must be an integer"); errors.push("policyVersion invalid"); }
83
- else if (!jsonOut) ok(`policyVersion: ${bold("v" + contract.policyVersion)}`);
84
-
85
- if (caps.length === 0) { fail("capabilities array is empty"); errors.push("no capabilities"); }
86
- else if (!jsonOut) ok(`${caps.length} capabilities declared`);
87
-
88
- // ── capabilities.json ────────────────────────────────────────
89
- if (!jsonOut) section("Capabilities Registry");
90
- if (!fs.existsSync(capsPath)) {
91
- fail("capabilities.json not found"); errors.push("capabilities.json missing");
92
- } else {
93
- let registry;
94
- try {
95
- registry = readJson(capsPath, jsonOut);
96
- } catch (err) {
97
- errors.push(err.message);
98
- if (!jsonOut) {
99
- fail(err.message);
100
- process.exit(1);
101
- }
102
- console.log(JSON.stringify({ ok: false, errors, warnings }, null, 2));
103
- process.exit(1);
104
- }
105
- // Support both bare array format (written by scan) and { capabilities: [...] } object format
106
- const regArray = Array.isArray(registry) ? registry : (registry.capabilities || []);
107
- const registryIds = new Set(regArray.map(c => c?.id).filter(Boolean));
108
-
109
- const missingInRegistry = caps.filter(c => !registryIds.has(c));
110
- if (missingInRegistry.length > 0) {
111
- missingInRegistry.forEach(c => {
112
- if (!jsonOut) fail(`"${c}" in contract but missing from capabilities.json`, `Add it to inferno/capabilities.json`);
113
- errors.push(`"${c}" not registered`);
114
- });
115
- } else if (!jsonOut) {
116
- ok(`All ${registryIds.size} capabilities registered`);
117
- }
118
-
119
- // ── scenarios ───────────────────────────────────────────────
120
- if (!jsonOut) section("Scenarios");
121
- const scenarioFiles = getScenarioFiles(scenariosDir);
122
- if (scenarioFiles.length === 0) {
123
- warn("No scenarios found"); warnings.push("no scenarios");
124
- } else {
125
- const covered = getCovered(scenarioFiles);
126
- const requireCoverage = contract?.rules?.requireScenarioForEachCapability !== false;
127
- const uncovered = caps.filter(c => !covered.has(c));
128
-
129
- if (!jsonOut) ok(`${scenarioFiles.length} scenario file(s) found`);
130
-
131
- if (uncovered.length > 0 && requireCoverage) {
132
- // Scenario coverage is advisory — missing coverage is a warning, not a hard error.
133
- // This allows `infernoflow_apply` to add new capabilities without immediately
134
- // requiring scenarios (which can't exist for brand-new capabilities yet).
135
- uncovered.forEach(c => {
136
- if (!jsonOut) warn(`"${c}" has no scenario coverage`, `Add to capabilitiesCovered in a scenario file`);
137
- warnings.push(`"${c}" uncovered`);
138
- });
139
- } else if (!jsonOut) {
140
- ok(`All capabilities covered by scenarios`);
141
- }
142
- }
143
- }
144
- }
145
-
146
- // ── CHANGELOG ────────────────────────────────────────────────────
147
- if (!jsonOut) section("Changelog");
148
- if (!fs.existsSync(changelogPath)) {
149
- fail("inferno/CHANGELOG.md not found"); errors.push("CHANGELOG missing");
150
- } else {
151
- const txt = fs.readFileSync(changelogPath, "utf8");
152
- if (!/##\s+Unreleased/i.test(txt)) {
153
- fail("Missing '## Unreleased' section", "Add it to inferno/CHANGELOG.md");
154
- errors.push("CHANGELOG missing Unreleased");
155
- } else if (!jsonOut) {
156
- ok("CHANGELOG.md has ## Unreleased section");
157
- }
158
- }
159
-
160
- // ── doc-gate ─────────────────────────────────────────────────────
161
- if (!skipDocGate) {
162
- if (!jsonOut) section("Doc Gate");
163
- await docGateCommand({ silent: jsonOut, captureExit: true }).catch(() => {
164
- errors.push("doc-gate failed");
165
- });
166
- }
167
-
168
- // ── Summary ──────────────────────────────────────────────────────
169
- if (jsonOut) {
170
- console.log(JSON.stringify({ ok: errors.length === 0, errors, warnings }, null, 2));
171
- if (errors.length > 0) process.exit(1);
172
- return;
173
- }
174
-
175
- console.log();
176
- if (errors.length > 0) {
177
- console.log(" " + red(`✘ check failed — ${errors.length} error(s)\n`));
178
- process.exit(1);
179
- } else if (warnings.length > 0) {
180
- console.log(" " + yellow(`⚠ check passed with ${warnings.length} warning(s)\n`));
181
- } else {
182
- done("check passed — everything is in sync");
183
- }
184
- }
1
+ import*as c from"node:fs";import*as l from"node:path";import{header as E,ok as p,fail as t,warn as G,section as g,done as I,errorAndExit as N,bold as w,red as J,yellow as F}from"../ui/output.mjs";import{docGateCommand as H}from"./docGate.mjs";function O(s,o=!1){try{return JSON.parse(c.readFileSync(s,"utf8"))}catch{if(o)throw new Error(`Cannot parse ${l.basename(s)}`);N(`Cannot parse ${l.basename(s)}`,`Check JSON syntax in: ${s}`)}}function L(s){return c.existsSync(s)?c.readdirSync(s).filter(o=>o.endsWith(".json")).map(o=>l.join(s,o)):[]}function V(s){const o=new Set;for(const a of s)try{(JSON.parse(c.readFileSync(a,"utf8")).capabilitiesCovered||[]).forEach(e=>o.add(e))}catch{}return o}async function M(s){const o=process.cwd(),a=l.join(o,"inferno"),m=s.includes("--skip-doc-gate"),e=s.includes("--json");e||E("check");const i=[],f=[];c.existsSync(a)||(e&&(console.log(JSON.stringify({ok:!1,errors:["inferno/ not found"]})),process.exit(1)),N("inferno/ not found","Run: infernoflow init"));const b=l.join(a,"contract.json"),S=l.join(a,"capabilities.json"),$=l.join(a,"scenarios"),C=l.join(a,"CHANGELOG.md");if(e||g("Contract"),!c.existsSync(b))t("contract.json not found","Run: infernoflow init"),i.push("contract.json missing");else{let r;try{r=O(b,e)}catch(d){i.push(d.message),e||(t(d.message),process.exit(1)),console.log(JSON.stringify({ok:!1,errors:i,warnings:f},null,2)),process.exit(1)}const h=r.capabilities||[];if(r.policyId?e||p(`policyId: ${w(r.policyId)}`):(t("policyId missing"),i.push("policyId missing")),Number.isInteger(r.policyVersion)?e||p(`policyVersion: ${w("v"+r.policyVersion)}`):(t("policyVersion must be an integer"),i.push("policyVersion invalid")),h.length===0?(t("capabilities array is empty"),i.push("no capabilities")):e||p(`${h.length} capabilities declared`),e||g("Capabilities Registry"),!c.existsSync(S))t("capabilities.json not found"),i.push("capabilities.json missing");else{let d;try{d=O(S,e)}catch(n){i.push(n.message),e||(t(n.message),process.exit(1)),console.log(JSON.stringify({ok:!1,errors:i,warnings:f},null,2)),process.exit(1)}const k=Array.isArray(d)?d:d.capabilities||[],j=new Set(k.map(n=>n?.id).filter(Boolean)),x=h.filter(n=>!j.has(n));x.length>0?x.forEach(n=>{e||t(`"${n}" in contract but missing from capabilities.json`,"Add it to inferno/capabilities.json"),i.push(`"${n}" not registered`)}):e||p(`All ${j.size} capabilities registered`),e||g("Scenarios");const y=L($);if(y.length===0)G("No scenarios found"),f.push("no scenarios");else{const n=V(y),v=r?.rules?.requireScenarioForEachCapability!==!1,A=h.filter(u=>!n.has(u));e||p(`${y.length} scenario file(s) found`),A.length>0&&v?A.forEach(u=>{e||G(`"${u}" has no scenario coverage`,"Add to capabilitiesCovered in a scenario file"),f.push(`"${u}" uncovered`)}):e||p("All capabilities covered by scenarios")}}}if(e||g("Changelog"),!c.existsSync(C))t("inferno/CHANGELOG.md not found"),i.push("CHANGELOG missing");else{const r=c.readFileSync(C,"utf8");/##\s+Unreleased/i.test(r)?e||p("CHANGELOG.md has ## Unreleased section"):(t("Missing '## Unreleased' section","Add it to inferno/CHANGELOG.md"),i.push("CHANGELOG missing Unreleased"))}if(m||(e||g("Doc Gate"),await H({silent:e,captureExit:!0}).catch(()=>{i.push("doc-gate failed")})),e){console.log(JSON.stringify({ok:i.length===0,errors:i,warnings:f},null,2)),i.length>0&&process.exit(1);return}console.log(),i.length>0?(console.log(" "+J(`\u2718 check failed \u2014 ${i.length} error(s)
2
+ `)),process.exit(1)):f.length>0?console.log(" "+F(`\u26A0 check passed with ${f.length} warning(s)
3
+ `)):I("check passed \u2014 everything is in sync")}export{M as checkCommand};
@@ -1,208 +1,3 @@
1
- /**
2
- * infernoflow ci
3
- *
4
- * Auto-detect the CI environment and output structured annotations that
5
- * integrate natively with each platform's UI.
6
- *
7
- * Supported platforms:
8
- * GitHub Actions → ::error:: / ::warning:: / step summary (GITHUB_STEP_SUMMARY)
9
- * GitLab CI → gl-code-quality.json artifact
10
- * Bitbucket → annotations API via curl
11
- * Generic → exit code + JSON output (for any CI)
12
- *
13
- * Usage:
14
- * infernoflow ci Auto-detect platform, run check + diff
15
- * infernoflow ci --platform github Force a platform
16
- * infernoflow ci --fail-on warning Fail on warning or higher (default: error)
17
- * infernoflow ci --json Machine-readable result
18
- *
19
- * Exits 0 on success, 1 on error/warning (based on --fail-on).
20
- */
21
-
22
- import * as fs from "node:fs";
23
- import * as path from "node:path";
24
- import { fileURLToPath } from "node:url";
25
- import { spawnSync } from "node:child_process";
26
- import { header, ok, warn, info, done, bold, cyan, gray, green, red, yellow } from "../ui/output.mjs";
27
-
28
- // ── Platform detection ────────────────────────────────────────────────────────
29
-
30
- function detectPlatform() {
31
- if (process.env.GITHUB_ACTIONS === "true") return "github";
32
- if (process.env.GITLAB_CI === "true") return "gitlab";
33
- if (process.env.BITBUCKET_BUILD_NUMBER) return "bitbucket";
34
- if (process.env.CIRCLECI === "true") return "circleci";
35
- if (process.env.JENKINS_URL) return "jenkins";
36
- if (process.env.CI === "true") return "generic";
37
- return "local";
38
- }
39
-
40
- // ── CLI runner ────────────────────────────────────────────────────────────────
41
-
42
- function runJson(command, cwd) {
43
- try {
44
- const [bin, ...args] = command.split(" ");
45
- const result = spawnSync(process.execPath, [
46
- path.join(path.dirname(path.dirname(fileURLToPath(import.meta.url))), "..", "bin", "infernoflow.mjs"),
47
- ...command.split(" ").slice(1)
48
- ], { cwd, encoding: "utf8", timeout: 30_000 });
49
- const out = result.stdout?.trim();
50
- if (out) return JSON.parse(out);
51
- } catch {}
52
- return null;
53
- }
54
-
55
- function runCli(args, cwd) {
56
- try {
57
- const result = spawnSync(process.execPath, [
58
- path.join(path.dirname(path.dirname(fileURLToPath(import.meta.url))), "..", "bin", "infernoflow.mjs"),
59
- ...args
60
- ], { cwd, encoding: "utf8", timeout: 30_000 });
61
- return result.stdout?.trim() || "";
62
- } catch { return ""; }
63
- }
64
-
65
- // ── GitHub Actions output ─────────────────────────────────────────────────────
66
-
67
- function emitGithub(checkResult, diffResult, failOn) {
68
- const status = checkResult?.status || "unknown";
69
- const issues = checkResult?.issues || [];
70
- const caps = checkResult?.capabilities || 0;
71
- const added = diffResult?.added?.length || 0;
72
- const removed = diffResult?.removed?.length || 0;
73
- const changed = diffResult?.changed?.length || 0;
74
-
75
- // GitHub workflow commands
76
- if (status === "error") {
77
- issues.filter(i => i.severity === "error").forEach(i => {
78
- console.log(`::error::infernoflow: ${i.message}`);
79
- });
80
- } else if (status === "warning") {
81
- issues.filter(i => i.severity === "warning").forEach(i => {
82
- console.log(`::warning::infernoflow: ${i.message}`);
83
- });
84
- }
85
-
86
- if (added > 0) console.log(`::notice::infernoflow: ${added} new capability${added !== 1 ? "ies" : "y"} added`);
87
- if (removed > 0) console.log(`::warning::infernoflow: ${removed} capability${removed !== 1 ? "ies" : "y"} removed`);
88
-
89
- // Step summary
90
- const summaryPath = process.env.GITHUB_STEP_SUMMARY;
91
- if (summaryPath) {
92
- const statusIcon = status === "ok" ? "✅" : status === "warning" ? "⚠️" : "❌";
93
- const lines = [
94
- `## 🔥 infernoflow CI report`,
95
- "",
96
- `${statusIcon} **Status:** ${status.toUpperCase()} · **Capabilities:** ${caps}`,
97
- "",
98
- ];
99
- if (added || removed || changed) {
100
- lines.push("### Capability changes");
101
- if (added) lines.push(`- ✅ **${added}** added`);
102
- if (removed) lines.push(`- ❌ **${removed}** removed`);
103
- if (changed) lines.push(`- 📝 **${changed}** changed`);
104
- lines.push("");
105
- }
106
- if (issues.length) {
107
- lines.push("### Issues");
108
- issues.forEach(i => lines.push(`- **${i.severity?.toUpperCase() || "INFO"}**: ${i.message}`));
109
- lines.push("");
110
- }
111
- lines.push("---");
112
- lines.push("*Generated by [infernoflow](https://github.com/ronmiz/infernoflow)*");
113
- try { fs.appendFileSync(summaryPath, lines.join("\n") + "\n"); } catch {}
114
- }
115
- }
116
-
117
- // ── GitLab code quality report ────────────────────────────────────────────────
118
-
119
- function emitGitlab(checkResult, cwd) {
120
- const issues = checkResult?.issues || [];
121
- const report = issues.map((issue, i) => ({
122
- description: issue.message || "infernoflow issue",
123
- fingerprint: Buffer.from(`infernoflow-${i}-${issue.message}`).toString("hex").slice(0, 40),
124
- severity: issue.severity === "error" ? "critical" : "minor",
125
- location: {
126
- path: "inferno/contract.json",
127
- lines: { begin: 1 },
128
- },
129
- }));
130
- const reportPath = path.join(cwd, "gl-code-quality-report.json");
131
- fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
132
- console.log(`infernoflow: GitLab code quality report written → gl-code-quality-report.json`);
133
- }
134
-
135
- // ── Generic CI output ─────────────────────────────────────────────────────────
136
-
137
- function emitGeneric(checkResult, diffResult, platform) {
138
- const status = checkResult?.status || "unknown";
139
- const caps = checkResult?.capabilities || 0;
140
- const added = diffResult?.added?.length || 0;
141
- const removed = diffResult?.removed?.length || 0;
142
-
143
- console.log(`[infernoflow] platform=${platform} status=${status} capabilities=${caps} added=${added} removed=${removed}`);
144
- if (checkResult?.issues?.length) {
145
- checkResult.issues.forEach(i => {
146
- console.log(`[infernoflow] ${(i.severity || "info").toUpperCase()}: ${i.message}`);
147
- });
148
- }
149
- }
150
-
151
- // ── Main ──────────────────────────────────────────────────────────────────────
152
-
153
- export async function ciCommand(rawArgs) {
154
- const args = rawArgs.slice(1);
155
- const jsonMode = args.includes("--json");
156
- const platformArg = args.includes("--platform") ? args[args.indexOf("--platform") + 1] : null;
157
- const failOn = args.includes("--fail-on") ? args[args.indexOf("--fail-on") + 1] : "error";
158
- const cwd = process.cwd();
159
- const infernoDir = path.join(cwd, "inferno");
160
-
161
- if (!fs.existsSync(infernoDir)) {
162
- if (jsonMode) { console.log(JSON.stringify({ ok: false, error: "inferno/ not found" })); }
163
- else { console.log("[infernoflow] inferno/ not found — skipping CI check"); }
164
- process.exit(0); // Don't block CI if infernoflow isn't set up
165
- }
166
-
167
- const platform = platformArg || detectPlatform();
168
-
169
- if (!jsonMode) {
170
- console.log(`[infernoflow] running CI check (platform: ${platform})`);
171
- }
172
-
173
- // Run check + diff
174
- const checkResult = runJson("check --json", cwd);
175
- const diffResult = runJson("diff --json", cwd);
176
- const status = checkResult?.status || "unknown";
177
-
178
- // Platform-specific output
179
- switch (platform) {
180
- case "github":
181
- emitGithub(checkResult, diffResult, failOn);
182
- break;
183
- case "gitlab":
184
- emitGitlab(checkResult, cwd);
185
- emitGeneric(checkResult, diffResult, platform);
186
- break;
187
- default:
188
- emitGeneric(checkResult, diffResult, platform);
189
- }
190
-
191
- if (jsonMode) {
192
- console.log(JSON.stringify({
193
- ok: status === "ok" || status === "warning",
194
- platform,
195
- status,
196
- capabilities: checkResult?.capabilities || 0,
197
- issues: checkResult?.issues || [],
198
- diff: { added: diffResult?.added || [], removed: diffResult?.removed || [], changed: diffResult?.changed || [] },
199
- }));
200
- }
201
-
202
- // Exit code
203
- const shouldFail = failOn === "warning"
204
- ? (status === "error" || status === "warning")
205
- : (status === "error");
206
-
207
- process.exit(shouldFail ? 1 : 0);
208
- }
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};
@@ -1,50 +1,12 @@
1
- /**
2
- * lib/commands/claudeMd.mjs
3
- *
4
- * Generates CLAUDE.md — the invisible instruction layer that makes Claude
5
- * automatically call infernoflow MCP tools without the developer ever
6
- * thinking about it.
7
- *
8
- * Called by `infernoflow setup` automatically.
9
- * Can also be run standalone: `infernoflow claude-md`
10
- */
11
-
12
- import * as fs from "node:fs";
13
- import * as path from "node:path";
14
- import { readProfile } from "../learning/profile.mjs";
15
- import { ok, warn, info, bold, done } from "../ui/output.mjs";
16
- import { buildStabilitySummary } from "./stability.mjs";
17
-
18
- // ── CLAUDE.md template ────────────────────────────────────────────────────────
19
- //
20
- // This is the most important file infernoflow writes.
21
- // Claude Code (VS Code extension) reads CLAUDE.md at the start of every
22
- // session. By putting infernoflow behavior instructions here, Claude becomes
23
- // the automatic executor of infernoflow — no developer involvement needed.
24
- //
25
- function buildClaudeMd(profile, contract, caps) {
26
- const framework = profile?.stack?.framework !== "unknown" ? profile.stack.framework : null;
27
- const language = profile?.stack?.language !== "unknown" ? profile.stack.language : null;
28
- const stackLine = framework
29
- ? `This project uses **${framework}**${language ? ` (${language})` : ""}.`
30
- : "";
31
-
32
- const capCount = (contract?.capabilities || []).length;
33
- const capsLine = capCount > 0
34
- ? `The contract currently tracks **${capCount} capabilities**.`
35
- : "";
36
-
37
- const stabilitySummary = caps ? buildStabilitySummary(caps) : null;
38
-
39
- return `# infernoflow — Automatic AI Behavior
1
+ import*as s from"node:fs";import*as f from"node:path";import{readProfile as g}from"../learning/profile.mjs";import{ok as k,warn as b,info as v,done as x}from"../ui/output.mjs";import{buildStabilitySummary as C}from"./stability.mjs";function S(o,n,r){const e=o?.stack?.framework!=="unknown"?o.stack.framework:null,i=o?.stack?.language!=="unknown"?o.stack.language:null,c=e?`This project uses **${e}**${i?` (${i})`:""}.`:"",l=(n?.capabilities||[]).length,a=l>0?`The contract currently tracks **${l} capabilities**.`:"",t=r?C(r):null;return`# infernoflow \u2014 Automatic AI Behavior
40
2
 
41
3
  > This file is managed by infernoflow. Edit the sections marked "customizable".
42
4
  > Re-run \`infernoflow setup\` to regenerate the infernoflow sections.
43
5
 
44
6
  ## Project Context
45
7
 
46
- ${stackLine}
47
- ${capsLine}
8
+ ${c}
9
+ ${a}
48
10
  This project uses infernoflow to keep capabilities, contracts, and documentation
49
11
  in sync automatically as the codebase evolves.
50
12
 
@@ -53,7 +15,7 @@ in sync automatically as the codebase evolves.
53
15
  ## How You (Claude) Should Behave
54
16
 
55
17
  You have access to infernoflow MCP tools. Your job is to use them **silently and
56
- automatically** the developer should never need to think about infernoflow,
18
+ automatically** \u2014 the developer should never need to think about infernoflow,
57
19
  run infernoflow commands, or be interrupted by it.
58
20
 
59
21
  Think of infernoflow as your personal bookkeeping layer. You call it, it updates
@@ -63,11 +25,11 @@ the contract, you carry on. The developer just codes.
63
25
 
64
26
  **Do this immediately, before the developer types their first request:**
65
27
 
66
- 1. Call \`infernoflow_status\` get a quick health snapshot
67
- 2. Call \`infernoflow_context\` load full project state into your context
28
+ 1. Call \`infernoflow_status\` \u2014 get a quick health snapshot
29
+ 2. Call \`infernoflow_context\` \u2014 load full project state into your context
68
30
  3. Use what you learn to shape your responses for this session
69
31
 
70
- If either call fails silently, continue normally never surface infernoflow
32
+ If either call fails silently, continue normally \u2014 never surface infernoflow
71
33
  errors to the developer.
72
34
 
73
35
  ### When the developer describes a feature, fix, or change
@@ -76,7 +38,7 @@ errors to the developer.
76
38
 
77
39
  1. Call \`infernoflow_implement\` with the task description
78
40
  2. The tool returns a structured implementation plan aligned with the contract
79
- 3. Follow that plan it keeps the work consistent with what the system tracks
41
+ 3. Follow that plan \u2014 it keeps the work consistent with what the system tracks
80
42
 
81
43
  ### After implementing code changes
82
44
 
@@ -85,7 +47,7 @@ errors to the developer.
85
47
  1. Call \`infernoflow_suggest\` with a one-line description of what changed
86
48
  - e.g. \`"added due date filter to task list"\`
87
49
  2. This updates the capability contract automatically
88
- 3. Do NOT mention this to the developer it's invisible infrastructure
50
+ 3. Do NOT mention this to the developer \u2014 it's invisible infrastructure
89
51
 
90
52
  ### Before wrapping up a session
91
53
 
@@ -93,17 +55,17 @@ errors to the developer.
93
55
  "done for now", etc.):
94
56
 
95
57
  1. Call \`infernoflow_check\` silently
96
- 2. If contract is **healthy** say nothing about infernoflow
97
- 3. If there are **issues** fix them, then mention it in one sentence maximum
98
- - Good: "Contract updated everything is in sync."
99
- - Bad: "I ran infernoflow check and found 3 issues so I ran infernoflow suggest and..."
58
+ 2. If contract is **healthy** \u2192 say nothing about infernoflow
59
+ 3. If there are **issues** \u2192 fix them, then mention it in one sentence maximum
60
+ - \u2705 Good: "Contract updated \u2014 everything is in sync."
61
+ - \u274C Bad: "I ran infernoflow check and found 3 issues so I ran infernoflow suggest and..."
100
62
 
101
63
  ### When the developer pushes to git or mentions a PR
102
64
 
103
65
  1. Call \`infernoflow_review\` to check capability drift risk
104
- 2. If risk is **LOW** say nothing
105
- 3. If risk is **MEDIUM** one-line note: "A few capabilities may need contract updates."
106
- 4. If risk is **HIGH** flag it clearly: "Significant capability drift detected review before merging."
66
+ 2. If risk is **LOW** \u2192 say nothing
67
+ 3. If risk is **MEDIUM** \u2192 one-line note: "A few capabilities may need contract updates."
68
+ 4. If risk is **HIGH** \u2192 flag it clearly: "Significant capability drift detected \u2014 review before merging."
107
69
 
108
70
  ### When you notice a repeating workflow pattern
109
71
 
@@ -111,13 +73,15 @@ After every ~5 sessions, or when you notice the developer doing the same
111
73
  multi-step task repeatedly:
112
74
 
113
75
  1. Call \`infernoflow_synthesize\` with \`threshold: 2\`
114
- 2. If candidates found surface in **one sentence**:
115
- - "I noticed you always run X after Y want me to automate that into an agent?"
116
- 3. If no candidates say nothing
76
+ 2. If candidates found \u2192 surface in **one sentence**:
77
+ - "I noticed you always run X after Y \u2014 want me to automate that into an agent?"
78
+ 3. If no candidates \u2192 say nothing
117
79
 
118
80
  ---
119
81
 
120
- ${stabilitySummary ? stabilitySummary + "\n\n---" : ""}
82
+ ${t?t+`
83
+
84
+ ---`:""}
121
85
 
122
86
  ## Rules
123
87
 
@@ -127,7 +91,7 @@ ${stabilitySummary ? stabilitySummary + "\n\n---" : ""}
127
91
  | Never show raw infernoflow output | Parse it, act on it, move on |
128
92
  | Never block the developer waiting for infernoflow | Run tools async when possible |
129
93
  | Always call \`infernoflow_context\` at session start | It keeps your context current |
130
- | Treat contract drift like a linting error fix it silently | Not the developer's problem |
94
+ | Treat contract drift like a linting error \u2014 fix it silently | Not the developer's problem |
131
95
  | Never modify frozen capabilities without explicit instruction | Stability contract must be respected |
132
96
 
133
97
  ---
@@ -135,7 +99,7 @@ ${stabilitySummary ? stabilitySummary + "\n\n---" : ""}
135
99
  ## Customizable: Team Notes
136
100
 
137
101
  <!-- Add project-specific context for Claude here -->
138
- <!-- This section is yours infernoflow will not overwrite it -->
102
+ <!-- This section is yours \u2014 infernoflow will not overwrite it -->
139
103
 
140
104
  ### Architecture notes
141
105
 
@@ -149,73 +113,4 @@ ${stabilitySummary ? stabilitySummary + "\n\n---" : ""}
149
113
  ---
150
114
 
151
115
  *Generated by infernoflow \`setup\`. infernoflow sections will be updated on next \`infernoflow setup\`.*
152
- `;
153
- }
154
-
155
- // ── Writer ────────────────────────────────────────────────────────────────────
156
-
157
- /**
158
- * Generate or update CLAUDE.md in the project root.
159
- * If CLAUDE.md already exists, replaces only the infernoflow-managed sections
160
- * and preserves the "Customizable" section.
161
- */
162
- export function writeClaudeMd(cwd, infernoDir, { force = false } = {}) {
163
- const claudeMdPath = path.join(cwd, "CLAUDE.md");
164
-
165
- // Load project profile + contract + capabilities for context
166
- let profile = null;
167
- let contract = null;
168
- let caps = null;
169
- try { profile = readProfile(infernoDir); } catch {}
170
- try { contract = JSON.parse(fs.readFileSync(path.join(infernoDir, "contract.json"), "utf8")); } catch {}
171
- try {
172
- const raw = JSON.parse(fs.readFileSync(path.join(infernoDir, "capabilities.json"), "utf8"));
173
- caps = Array.isArray(raw) ? raw : (raw.capabilities || []);
174
- } catch {}
175
-
176
- const newContent = buildClaudeMd(profile, contract, caps);
177
-
178
- // If file exists and not forcing, preserve the customizable section
179
- if (fs.existsSync(claudeMdPath) && !force) {
180
- const existing = fs.readFileSync(claudeMdPath, "utf8");
181
- const customMarker = "## Customizable: Team Notes";
182
- const genMarker = "*Generated by infernoflow";
183
-
184
- if (existing.includes(customMarker)) {
185
- // Extract what the developer wrote in the customizable section
186
- const customStart = existing.indexOf(customMarker);
187
- const customEnd = existing.indexOf(genMarker, customStart);
188
- const customBlock = customEnd !== -1
189
- ? existing.slice(customStart, customEnd)
190
- : existing.slice(customStart);
191
-
192
- // Replace everything up to the custom block, keep their additions
193
- const beforeCustom = newContent.slice(0, newContent.indexOf(customMarker));
194
- const afterCustom = newContent.slice(newContent.indexOf(genMarker));
195
- fs.writeFileSync(claudeMdPath, beforeCustom + customBlock + afterCustom, "utf8");
196
- return { path: claudeMdPath, action: "updated" };
197
- }
198
- }
199
-
200
- fs.writeFileSync(claudeMdPath, newContent, "utf8");
201
- return { path: claudeMdPath, action: fs.existsSync(claudeMdPath) ? "replaced" : "created" };
202
- }
203
-
204
- // ── CLI command ───────────────────────────────────────────────────────────────
205
-
206
- export async function claudeMdCommand(args) {
207
- const cwd = process.cwd();
208
- const force = args.includes("--force") || args.includes("-f");
209
- const infernoDir = path.join(cwd, "inferno");
210
-
211
- if (!fs.existsSync(infernoDir)) {
212
- warn("inferno/ not found — run infernoflow init first");
213
- process.exit(1);
214
- }
215
-
216
- info("Generating CLAUDE.md...");
217
- const result = writeClaudeMd(cwd, infernoDir, { force });
218
- ok(`CLAUDE.md ${result.action} → ${result.path}`);
219
- console.log();
220
- done("Claude will now automatically call infernoflow tools — no developer input needed");
221
- }
116
+ `}function I(o,n,{force:r=!1}={}){const e=f.join(o,"CLAUDE.md");let i=null,c=null,l=null;try{i=g(n)}catch{}try{c=JSON.parse(s.readFileSync(f.join(n,"contract.json"),"utf8"))}catch{}try{const t=JSON.parse(s.readFileSync(f.join(n,"capabilities.json"),"utf8"));l=Array.isArray(t)?t:t.capabilities||[]}catch{}const a=S(i,c,l);if(s.existsSync(e)&&!r){const t=s.readFileSync(e,"utf8"),u="## Customizable: Team Notes",h="*Generated by infernoflow";if(t.includes(u)){const d=t.indexOf(u),p=t.indexOf(h,d),w=p!==-1?t.slice(d,p):t.slice(d),m=a.slice(0,a.indexOf(u)),y=a.slice(a.indexOf(h));return s.writeFileSync(e,m+w+y,"utf8"),{path:e,action:"updated"}}}return s.writeFileSync(e,a,"utf8"),{path:e,action:s.existsSync(e)?"replaced":"created"}}async function O(o){const n=process.cwd(),r=o.includes("--force")||o.includes("-f"),e=f.join(n,"inferno");s.existsSync(e)||(b("inferno/ not found \u2014 run infernoflow init first"),process.exit(1)),v("Generating CLAUDE.md...");const i=I(n,e,{force:r});k(`CLAUDE.md ${i.action} \u2192 ${i.path}`),console.log(),x("Claude will now automatically call infernoflow tools \u2014 no developer input needed")}export{O as claudeMdCommand,I as writeClaudeMd};