infernoflow 0.37.1 → 0.37.4
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 +71 -0
- package/dist/bin/infernoflow.mjs +29 -277
- package/dist/lib/adopters/angular.mjs +1 -128
- package/dist/lib/adopters/css.mjs +1 -111
- package/dist/lib/adopters/react.mjs +1 -104
- package/dist/lib/ai/ideDetection.mjs +1 -31
- package/dist/lib/ai/localProvider.mjs +1 -88
- package/dist/lib/ai/providerRouter.mjs +2 -295
- package/dist/lib/commands/adopt.mjs +20 -869
- package/dist/lib/commands/adoptWizard.mjs +9 -320
- package/dist/lib/commands/agent.mjs +5 -191
- package/dist/lib/commands/ai.mjs +2 -407
- package/dist/lib/commands/ask.mjs +4 -299
- package/dist/lib/commands/audit.mjs +13 -300
- package/dist/lib/commands/changelog.mjs +26 -594
- package/dist/lib/commands/check.mjs +3 -184
- package/dist/lib/commands/ci.mjs +3 -208
- package/dist/lib/commands/claudeMd.mjs +30 -135
- package/dist/lib/commands/cloud.mjs +10 -773
- package/dist/lib/commands/context.mjs +34 -346
- package/dist/lib/commands/coverage.mjs +2 -282
- package/dist/lib/commands/dashboard.mjs +123 -635
- package/dist/lib/commands/demo.mjs +8 -465
- package/dist/lib/commands/diff.mjs +5 -274
- package/dist/lib/commands/docGate.mjs +2 -81
- package/dist/lib/commands/doctor.mjs +3 -321
- package/dist/lib/commands/explain.mjs +8 -438
- package/dist/lib/commands/export.mjs +10 -239
- package/dist/lib/commands/feedback.mjs +12 -216
- package/dist/lib/commands/generateSkills.mjs +38 -163
- package/dist/lib/commands/graph.mjs +11 -378
- package/dist/lib/commands/health.mjs +2 -309
- package/dist/lib/commands/impact.mjs +2 -325
- package/dist/lib/commands/implement.mjs +7 -103
- package/dist/lib/commands/init.mjs +45 -631
- package/dist/lib/commands/installCursorHooks.mjs +1 -36
- package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -37
- package/dist/lib/commands/link.mjs +2 -342
- package/dist/lib/commands/log.mjs +18 -248
- package/dist/lib/commands/monorepo.mjs +4 -428
- package/dist/lib/commands/notify.mjs +4 -258
- package/dist/lib/commands/onboard.mjs +4 -296
- package/dist/lib/commands/prComment.mjs +2 -361
- package/dist/lib/commands/prImpact.mjs +2 -157
- package/dist/lib/commands/publish.mjs +15 -316
- package/dist/lib/commands/recap.mjs +6 -380
- package/dist/lib/commands/report.mjs +28 -272
- package/dist/lib/commands/review.mjs +9 -223
- package/dist/lib/commands/run.mjs +8 -336
- package/dist/lib/commands/scaffold.mjs +54 -419
- package/dist/lib/commands/scan.mjs +11 -1118
- package/dist/lib/commands/scout.mjs +2 -291
- package/dist/lib/commands/setup.mjs +5 -310
- package/dist/lib/commands/share.mjs +13 -196
- package/dist/lib/commands/snapshot.mjs +3 -383
- package/dist/lib/commands/stability.mjs +2 -293
- package/dist/lib/commands/stats.mjs +5 -402
- package/dist/lib/commands/status.mjs +4 -172
- package/dist/lib/commands/suggest.mjs +21 -563
- package/dist/lib/commands/switch.mjs +13 -520
- package/dist/lib/commands/syncAuto.mjs +1 -96
- package/dist/lib/commands/synthesize.mjs +10 -228
- package/dist/lib/commands/teamSync.mjs +2 -388
- package/dist/lib/commands/test.mjs +6 -363
- package/dist/lib/commands/theme.mjs +18 -195
- package/dist/lib/commands/uninstall.mjs +13 -406
- package/dist/lib/commands/upgrade.mjs +20 -153
- package/dist/lib/commands/version.mjs +2 -282
- package/dist/lib/commands/vibe.mjs +7 -357
- package/dist/lib/commands/watch.mjs +4 -203
- package/dist/lib/commands/why.mjs +4 -358
- package/dist/lib/cursorHooksInstall.mjs +1 -60
- package/dist/lib/draftToolingInstall.mjs +7 -68
- package/dist/lib/git/detect-drift.mjs +4 -208
- package/dist/lib/learning/adapt.mjs +6 -101
- package/dist/lib/learning/observe.mjs +1 -119
- package/dist/lib/learning/patternDetector.mjs +1 -298
- package/dist/lib/learning/profile.mjs +2 -279
- package/dist/lib/learning/skillSynthesizer.mjs +24 -145
- package/dist/lib/telemetry.mjs +19 -269
- package/dist/lib/templates/index.mjs +1 -131
- package/dist/lib/theme/scanner.mjs +4 -343
- package/dist/lib/ui/errors.mjs +1 -142
- package/dist/lib/ui/output.mjs +6 -95
- package/dist/lib/ui/prompts.mjs +6 -147
- package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
- package/package.json +2 -4
- package/scripts/postinstall.js +2 -2
|
@@ -1,184 +1,3 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
|
|
3
|
-
|
|
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};
|
package/dist/lib/commands/ci.mjs
CHANGED
|
@@ -1,208 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
${
|
|
47
|
-
${
|
|
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**
|
|
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\`
|
|
67
|
-
2. Call \`infernoflow_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
|
|
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
|
|
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
|
|
80
|
-
- If failed attempts are found
|
|
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
|
|
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
|
|
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\`
|
|
100
|
-
2. Call \`infernoflow_check\` silently
|
|
101
|
-
3. If recap shows unlogged topics
|
|
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**
|
|
104
|
-
-
|
|
105
|
-
-
|
|
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**
|
|
111
|
-
3. If risk is **MEDIUM**
|
|
112
|
-
4. If risk is **HIGH**
|
|
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
|
|
121
|
-
- "I noticed you always run X after Y
|
|
122
|
-
3. If no candidates
|
|
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
|
-
${
|
|
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
|
|
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
|
|
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};
|