infernoflow 0.37.0 → 0.37.3

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 (88) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/dist/bin/infernoflow.mjs +29 -277
  3. package/dist/lib/adopters/angular.mjs +1 -128
  4. package/dist/lib/adopters/css.mjs +1 -111
  5. package/dist/lib/adopters/react.mjs +1 -104
  6. package/dist/lib/ai/ideDetection.mjs +1 -31
  7. package/dist/lib/ai/localProvider.mjs +1 -88
  8. package/dist/lib/ai/providerRouter.mjs +2 -295
  9. package/dist/lib/commands/adopt.mjs +20 -869
  10. package/dist/lib/commands/adoptWizard.mjs +9 -320
  11. package/dist/lib/commands/agent.mjs +5 -191
  12. package/dist/lib/commands/ai.mjs +2 -407
  13. package/dist/lib/commands/ask.mjs +4 -299
  14. package/dist/lib/commands/audit.mjs +13 -300
  15. package/dist/lib/commands/changelog.mjs +26 -594
  16. package/dist/lib/commands/check.mjs +3 -184
  17. package/dist/lib/commands/ci.mjs +3 -208
  18. package/dist/lib/commands/claudeMd.mjs +30 -135
  19. package/dist/lib/commands/cloud.mjs +10 -773
  20. package/dist/lib/commands/context.mjs +34 -346
  21. package/dist/lib/commands/coverage.mjs +2 -282
  22. package/dist/lib/commands/dashboard.mjs +123 -635
  23. package/dist/lib/commands/demo.mjs +8 -465
  24. package/dist/lib/commands/diff.mjs +5 -274
  25. package/dist/lib/commands/docGate.mjs +2 -81
  26. package/dist/lib/commands/doctor.mjs +3 -321
  27. package/dist/lib/commands/explain.mjs +8 -438
  28. package/dist/lib/commands/export.mjs +10 -239
  29. package/dist/lib/commands/feedback.mjs +12 -216
  30. package/dist/lib/commands/generateSkills.mjs +38 -163
  31. package/dist/lib/commands/graph.mjs +11 -378
  32. package/dist/lib/commands/health.mjs +2 -309
  33. package/dist/lib/commands/impact.mjs +2 -325
  34. package/dist/lib/commands/implement.mjs +7 -103
  35. package/dist/lib/commands/init.mjs +45 -631
  36. package/dist/lib/commands/installCursorHooks.mjs +1 -36
  37. package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -37
  38. package/dist/lib/commands/link.mjs +2 -342
  39. package/dist/lib/commands/log.mjs +18 -248
  40. package/dist/lib/commands/monorepo.mjs +4 -428
  41. package/dist/lib/commands/notify.mjs +4 -258
  42. package/dist/lib/commands/onboard.mjs +4 -296
  43. package/dist/lib/commands/prComment.mjs +2 -361
  44. package/dist/lib/commands/prImpact.mjs +2 -157
  45. package/dist/lib/commands/publish.mjs +15 -316
  46. package/dist/lib/commands/recap.mjs +6 -380
  47. package/dist/lib/commands/report.mjs +28 -272
  48. package/dist/lib/commands/review.mjs +9 -223
  49. package/dist/lib/commands/run.mjs +8 -336
  50. package/dist/lib/commands/scaffold.mjs +54 -419
  51. package/dist/lib/commands/scan.mjs +11 -1118
  52. package/dist/lib/commands/scout.mjs +2 -291
  53. package/dist/lib/commands/setup.mjs +5 -310
  54. package/dist/lib/commands/share.mjs +13 -196
  55. package/dist/lib/commands/snapshot.mjs +3 -383
  56. package/dist/lib/commands/stability.mjs +2 -293
  57. package/dist/lib/commands/stats.mjs +5 -402
  58. package/dist/lib/commands/status.mjs +4 -172
  59. package/dist/lib/commands/suggest.mjs +21 -563
  60. package/dist/lib/commands/switch.mjs +13 -517
  61. package/dist/lib/commands/syncAuto.mjs +1 -96
  62. package/dist/lib/commands/synthesize.mjs +10 -228
  63. package/dist/lib/commands/teamSync.mjs +2 -388
  64. package/dist/lib/commands/test.mjs +6 -363
  65. package/dist/lib/commands/theme.mjs +18 -195
  66. package/dist/lib/commands/uninstall.mjs +13 -406
  67. package/dist/lib/commands/upgrade.mjs +20 -153
  68. package/dist/lib/commands/version.mjs +2 -282
  69. package/dist/lib/commands/vibe.mjs +7 -357
  70. package/dist/lib/commands/watch.mjs +4 -203
  71. package/dist/lib/commands/why.mjs +4 -358
  72. package/dist/lib/cursorHooksInstall.mjs +1 -60
  73. package/dist/lib/draftToolingInstall.mjs +7 -68
  74. package/dist/lib/git/detect-drift.mjs +4 -208
  75. package/dist/lib/learning/adapt.mjs +6 -101
  76. package/dist/lib/learning/observe.mjs +1 -119
  77. package/dist/lib/learning/patternDetector.mjs +1 -298
  78. package/dist/lib/learning/profile.mjs +2 -279
  79. package/dist/lib/learning/skillSynthesizer.mjs +24 -145
  80. package/dist/lib/telemetry.mjs +19 -269
  81. package/dist/lib/templates/index.mjs +1 -131
  82. package/dist/lib/theme/scanner.mjs +4 -343
  83. package/dist/lib/ui/errors.mjs +1 -142
  84. package/dist/lib/ui/output.mjs +6 -95
  85. package/dist/lib/ui/prompts.mjs +6 -147
  86. package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
  87. package/package.json +2 -4
  88. package/scripts/postinstall.js +2 -2
@@ -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 C}from"../ui/output.mjs";import{buildStabilitySummary as x}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?x(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,24 +25,24 @@ 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
74
36
 
75
37
  **Before writing any code:**
76
38
 
77
- 1. Call \`infernoflow_ask\` with the topic e.g. \`"auth"\`, \`"file upload"\`, \`"stripe"\`
39
+ 1. Call \`infernoflow_ask\` with the topic \u2014 e.g. \`"auth"\`, \`"file upload"\`, \`"stripe"\`
78
40
  - This searches session memory for relevant gotchas, decisions, and failed attempts
79
- - If gotchas are found mention them in one sentence before proceeding
80
- - If failed attempts are found do not repeat those approaches
41
+ - If gotchas are found \u2192 mention them in one sentence before proceeding
42
+ - If failed attempts are found \u2192 do not repeat those approaches
81
43
  2. Call \`infernoflow_implement\` with the task description
82
44
  3. The tool returns a structured implementation plan aligned with the contract
83
- 4. Follow that plan it keeps the work consistent with what the system tracks
45
+ 4. Follow that plan \u2014 it keeps the work consistent with what the system tracks
84
46
 
85
47
  ### After implementing code changes
86
48
 
@@ -89,27 +51,27 @@ errors to the developer.
89
51
  1. Call \`infernoflow_suggest\` with a one-line description of what changed
90
52
  - e.g. \`"added due date filter to task list"\`
91
53
  2. This updates the capability contract automatically
92
- 3. Do NOT mention this to the developer it's invisible infrastructure
54
+ 3. Do NOT mention this to the developer \u2014 it's invisible infrastructure
93
55
 
94
56
  ### Before wrapping up a session
95
57
 
96
58
  **When the developer signals they are done** ("thanks", "looks good", "ship it",
97
59
  "done for now", etc.):
98
60
 
99
- 1. Call \`infernoflow_recap\` get this session's captured entries + unlogged git changes
100
- 2. Call \`infernoflow_check\` silently validate contract health
101
- 3. If recap shows unlogged topics ask the developer ONE question:
61
+ 1. Call \`infernoflow_recap\` \u2014 get this session's captured entries + unlogged git changes
62
+ 2. Call \`infernoflow_check\` silently \u2014 validate contract health
63
+ 3. If recap shows unlogged topics \u2192 ask the developer ONE question:
102
64
  - e.g. "Anything worth noting about the auth changes before you go?"
103
- 4. If contract has **issues** fix them silently, mention in one sentence maximum
104
- - Good: "Contract updated everything is in sync."
105
- - Bad: "I ran infernoflow check and found 3 issues..."
65
+ 4. If contract has **issues** \u2192 fix them silently, mention in one sentence maximum
66
+ - \u2705 Good: "Contract updated \u2014 everything is in sync."
67
+ - \u274C Bad: "I ran infernoflow check and found 3 issues..."
106
68
 
107
69
  ### When the developer pushes to git or mentions a PR
108
70
 
109
71
  1. Call \`infernoflow_review\` to check capability drift risk
110
- 2. If risk is **LOW** say nothing
111
- 3. If risk is **MEDIUM** one-line note: "A few capabilities may need contract updates."
112
- 4. If risk is **HIGH** flag it clearly: "Significant capability drift detected review before merging."
72
+ 2. If risk is **LOW** \u2192 say nothing
73
+ 3. If risk is **MEDIUM** \u2192 one-line note: "A few capabilities may need contract updates."
74
+ 4. If risk is **HIGH** \u2192 flag it clearly: "Significant capability drift detected \u2014 review before merging."
113
75
 
114
76
  ### When you notice a repeating workflow pattern
115
77
 
@@ -117,13 +79,15 @@ After every ~5 sessions, or when you notice the developer doing the same
117
79
  multi-step task repeatedly:
118
80
 
119
81
  1. Call \`infernoflow_synthesize\` with \`threshold: 2\`
120
- 2. If candidates found surface in **one sentence**:
121
- - "I noticed you always run X after Y want me to automate that into an agent?"
122
- 3. If no candidates say nothing
82
+ 2. If candidates found \u2192 surface in **one sentence**:
83
+ - "I noticed you always run X after Y \u2014 want me to automate that into an agent?"
84
+ 3. If no candidates \u2192 say nothing
123
85
 
124
86
  ---
125
87
 
126
- ${stabilitySummary ? stabilitySummary + "\n\n---" : ""}
88
+ ${t?t+`
89
+
90
+ ---`:""}
127
91
 
128
92
  ## Rules
129
93
 
@@ -133,7 +97,7 @@ ${stabilitySummary ? stabilitySummary + "\n\n---" : ""}
133
97
  | Never show raw infernoflow output | Parse it, act on it, move on |
134
98
  | Never block the developer waiting for infernoflow | Run tools async when possible |
135
99
  | Always call \`infernoflow_context\` at session start | It keeps your context current |
136
- | Treat contract drift like a linting error fix it silently | Not the developer's problem |
100
+ | Treat contract drift like a linting error \u2014 fix it silently | Not the developer's problem |
137
101
  | Never modify frozen capabilities without explicit instruction | Stability contract must be respected |
138
102
 
139
103
  ---
@@ -141,7 +105,7 @@ ${stabilitySummary ? stabilitySummary + "\n\n---" : ""}
141
105
  ## Customizable: Team Notes
142
106
 
143
107
  <!-- Add project-specific context for Claude here -->
144
- <!-- This section is yours infernoflow will not overwrite it -->
108
+ <!-- This section is yours \u2014 infernoflow will not overwrite it -->
145
109
 
146
110
  ### Architecture notes
147
111
 
@@ -155,73 +119,4 @@ ${stabilitySummary ? stabilitySummary + "\n\n---" : ""}
155
119
  ---
156
120
 
157
121
  *Generated by infernoflow \`setup\`. infernoflow sections will be updated on next \`infernoflow setup\`.*
158
- `;
159
- }
160
-
161
- // ── Writer ────────────────────────────────────────────────────────────────────
162
-
163
- /**
164
- * Generate or update CLAUDE.md in the project root.
165
- * If CLAUDE.md already exists, replaces only the infernoflow-managed sections
166
- * and preserves the "Customizable" section.
167
- */
168
- export function writeClaudeMd(cwd, infernoDir, { force = false } = {}) {
169
- const claudeMdPath = path.join(cwd, "CLAUDE.md");
170
-
171
- // Load project profile + contract + capabilities for context
172
- let profile = null;
173
- let contract = null;
174
- let caps = null;
175
- try { profile = readProfile(infernoDir); } catch {}
176
- try { contract = JSON.parse(fs.readFileSync(path.join(infernoDir, "contract.json"), "utf8")); } catch {}
177
- try {
178
- const raw = JSON.parse(fs.readFileSync(path.join(infernoDir, "capabilities.json"), "utf8"));
179
- caps = Array.isArray(raw) ? raw : (raw.capabilities || []);
180
- } catch {}
181
-
182
- const newContent = buildClaudeMd(profile, contract, caps);
183
-
184
- // If file exists and not forcing, preserve the customizable section
185
- if (fs.existsSync(claudeMdPath) && !force) {
186
- const existing = fs.readFileSync(claudeMdPath, "utf8");
187
- const customMarker = "## Customizable: Team Notes";
188
- const genMarker = "*Generated by infernoflow";
189
-
190
- if (existing.includes(customMarker)) {
191
- // Extract what the developer wrote in the customizable section
192
- const customStart = existing.indexOf(customMarker);
193
- const customEnd = existing.indexOf(genMarker, customStart);
194
- const customBlock = customEnd !== -1
195
- ? existing.slice(customStart, customEnd)
196
- : existing.slice(customStart);
197
-
198
- // Replace everything up to the custom block, keep their additions
199
- const beforeCustom = newContent.slice(0, newContent.indexOf(customMarker));
200
- const afterCustom = newContent.slice(newContent.indexOf(genMarker));
201
- fs.writeFileSync(claudeMdPath, beforeCustom + customBlock + afterCustom, "utf8");
202
- return { path: claudeMdPath, action: "updated" };
203
- }
204
- }
205
-
206
- fs.writeFileSync(claudeMdPath, newContent, "utf8");
207
- return { path: claudeMdPath, action: fs.existsSync(claudeMdPath) ? "replaced" : "created" };
208
- }
209
-
210
- // ── CLI command ───────────────────────────────────────────────────────────────
211
-
212
- export async function claudeMdCommand(args) {
213
- const cwd = process.cwd();
214
- const force = args.includes("--force") || args.includes("-f");
215
- const infernoDir = path.join(cwd, "inferno");
216
-
217
- if (!fs.existsSync(infernoDir)) {
218
- warn("inferno/ not found — run infernoflow init first");
219
- process.exit(1);
220
- }
221
-
222
- info("Generating CLAUDE.md...");
223
- const result = writeClaudeMd(cwd, infernoDir, { force });
224
- ok(`CLAUDE.md ${result.action} → ${result.path}`);
225
- console.log();
226
- done("Claude will now automatically call infernoflow tools — no developer input needed");
227
- }
122
+ `}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(),C("Claude will now automatically call infernoflow tools \u2014 no developer input needed")}export{O as claudeMdCommand,I as writeClaudeMd};