infernoflow 0.32.8 → 0.32.9
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/dist/bin/infernoflow.mjs +84 -255
- 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/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 +25 -130
- package/dist/lib/commands/cloud.mjs +5 -521
- package/dist/lib/commands/context.mjs +31 -287
- 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/generateSkills.mjs +38 -163
- package/dist/lib/commands/graph.mjs +203 -321
- 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 +23 -475
- 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/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/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 +5 -558
- 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/status.mjs +4 -172
- package/dist/lib/commands/suggest.mjs +21 -563
- 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/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/templates/index.mjs +1 -131
- package/dist/lib/ui/errors.mjs +1 -142
- package/dist/lib/ui/output.mjs +6 -72
- package/dist/lib/ui/prompts.mjs +6 -147
- package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
- package/dist/templates/cursor/inferno-mcp-server.mjs +29 -0
- package/dist/templates/github-app/GITHUB_APP.md +67 -0
- package/dist/templates/github-app/app-manifest.json +20 -0
- package/package.json +1 -1
|
@@ -1,293 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* The solid/liquid layer — mark capabilities as frozen (don't touch),
|
|
5
|
-
* stable (be careful), or experimental (feel free to reshape).
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* infernoflow stability List all caps with stability level
|
|
9
|
-
* infernoflow freeze <cap-id> Mark a capability as frozen
|
|
10
|
-
* infernoflow freeze <cap-id> --stable Mark as stable (default middle tier)
|
|
11
|
-
* infernoflow thaw <cap-id> Reset to experimental
|
|
12
|
-
* infernoflow stability --json Machine-readable output
|
|
13
|
-
*
|
|
14
|
-
* Levels:
|
|
15
|
-
* experimental New or actively changing — AI may freely refactor
|
|
16
|
-
* stable Settled API — AI should be careful, prefer additive changes
|
|
17
|
-
* frozen Core contract — AI must never modify without explicit instruction
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import * as fs from "node:fs";
|
|
21
|
-
import * as path from "node:path";
|
|
22
|
-
import { bold, cyan, gray, green, yellow, red } from "../ui/output.mjs";
|
|
23
|
-
|
|
24
|
-
// ── constants ─────────────────────────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
export const LEVELS = ["experimental", "stable", "frozen"];
|
|
27
|
-
|
|
28
|
-
const LEVEL_ICON = {
|
|
29
|
-
experimental: "🌊", // liquid — flows freely
|
|
30
|
-
stable: "〰️", // semi-fluid — treat with care
|
|
31
|
-
frozen: "🧊", // solid — do not touch
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const LEVEL_COLOR = {
|
|
35
|
-
experimental: green,
|
|
36
|
-
stable: yellow,
|
|
37
|
-
frozen: red,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
41
|
-
|
|
42
|
-
function loadCaps(capsPath) {
|
|
43
|
-
try {
|
|
44
|
-
const data = JSON.parse(fs.readFileSync(capsPath, "utf8"));
|
|
45
|
-
return Array.isArray(data) ? data : (data.capabilities || []);
|
|
46
|
-
} catch (e) {
|
|
47
|
-
console.error(red("✗ Failed to read capabilities.json: " + e.message));
|
|
48
|
-
process.exit(1);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function saveCaps(capsPath, caps) {
|
|
53
|
-
fs.writeFileSync(capsPath, JSON.stringify(caps, null, 2));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function getLevel(cap) {
|
|
57
|
-
return cap.stability || "experimental";
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function bar(level) {
|
|
61
|
-
const idx = LEVELS.indexOf(level);
|
|
62
|
-
const color = LEVEL_COLOR[level] || gray;
|
|
63
|
-
const filled = "█".repeat(idx + 1);
|
|
64
|
-
const empty = "░".repeat(LEVELS.length - idx - 1);
|
|
65
|
-
return color(filled) + gray(empty);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// ── sub-commands ──────────────────────────────────────────────────────────────
|
|
69
|
-
|
|
70
|
-
function cmdList(caps, jsonMode) {
|
|
71
|
-
if (jsonMode) {
|
|
72
|
-
const out = caps.map(c => ({ id: c.id, name: c.name || c.title, stability: getLevel(c) }));
|
|
73
|
-
console.log(JSON.stringify(out, null, 2));
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const byLevel = { frozen: [], stable: [], experimental: [] };
|
|
78
|
-
for (const cap of caps) byLevel[getLevel(cap)].push(cap);
|
|
79
|
-
|
|
80
|
-
console.log();
|
|
81
|
-
console.log(bold(" Capability Stability"));
|
|
82
|
-
console.log(gray(" ───────────────────────────────────────────────────────────"));
|
|
83
|
-
console.log(
|
|
84
|
-
gray(" ") + bold(cyan("Capability".padEnd(32))) +
|
|
85
|
-
bold(cyan("Level".padEnd(16))) + bold(cyan("Solid/Liquid"))
|
|
86
|
-
);
|
|
87
|
-
console.log(gray(" ───────────────────────────────────────────────────────────"));
|
|
88
|
-
|
|
89
|
-
for (const cap of caps) {
|
|
90
|
-
const level = getLevel(cap);
|
|
91
|
-
const icon = LEVEL_ICON[level];
|
|
92
|
-
const color = LEVEL_COLOR[level] || gray;
|
|
93
|
-
console.log(
|
|
94
|
-
` ${icon} ${cap.id.padEnd(30)} ${color(level.padEnd(14))} ${bar(level)}`
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
console.log(gray(" ───────────────────────────────────────────────────────────"));
|
|
99
|
-
console.log();
|
|
100
|
-
|
|
101
|
-
const counts = {
|
|
102
|
-
frozen: byLevel.frozen.length,
|
|
103
|
-
stable: byLevel.stable.length,
|
|
104
|
-
experimental: byLevel.experimental.length,
|
|
105
|
-
};
|
|
106
|
-
console.log(
|
|
107
|
-
` ${red("🧊 Frozen:")} ${counts.frozen} ` +
|
|
108
|
-
`${yellow("〰️ Stable:")} ${counts.stable} ` +
|
|
109
|
-
`${green("🌊 Experimental:")} ${counts.experimental}`
|
|
110
|
-
);
|
|
111
|
-
console.log();
|
|
112
|
-
console.log(gray(" Tip: infernoflow freeze <cap-id> — infernoflow thaw <cap-id>"));
|
|
113
|
-
console.log();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function cmdFreeze(caps, capsPath, capId, level) {
|
|
117
|
-
if (!LEVELS.includes(level)) {
|
|
118
|
-
console.error(red(`✗ Invalid level "${level}". Must be: ${LEVELS.join(", ")}`));
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const idx = caps.findIndex(c => c.id === capId);
|
|
123
|
-
if (idx === -1) {
|
|
124
|
-
console.error(red(`✗ Capability "${capId}" not found in capabilities.json`));
|
|
125
|
-
process.exit(1);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const prev = getLevel(caps[idx]);
|
|
129
|
-
caps[idx] = { ...caps[idx], stability: level, stabilitySetAt: new Date().toISOString() };
|
|
130
|
-
saveCaps(capsPath, caps);
|
|
131
|
-
|
|
132
|
-
const icon = LEVEL_ICON[level];
|
|
133
|
-
const color = LEVEL_COLOR[level];
|
|
134
|
-
console.log();
|
|
135
|
-
console.log(` ${icon} ${bold(capId)} ${gray(prev)} → ${color(level)}`);
|
|
136
|
-
if (level === "frozen") {
|
|
137
|
-
console.log(gray(" AI assistants will be instructed not to modify this capability."));
|
|
138
|
-
console.log(gray(" Run `infernoflow setup` to update CLAUDE.md with this change."));
|
|
139
|
-
}
|
|
140
|
-
console.log();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function cmdThaw(caps, capsPath, capId) {
|
|
144
|
-
const idx = caps.findIndex(c => c.id === capId);
|
|
145
|
-
if (idx === -1) {
|
|
146
|
-
console.error(red(`✗ Capability "${capId}" not found in capabilities.json`));
|
|
147
|
-
process.exit(1);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const prev = getLevel(caps[idx]);
|
|
151
|
-
caps[idx] = { ...caps[idx], stability: "experimental", stabilitySetAt: new Date().toISOString() };
|
|
152
|
-
saveCaps(capsPath, caps);
|
|
153
|
-
|
|
154
|
-
console.log();
|
|
155
|
-
console.log(` 🌊 ${bold(capId)} ${gray(prev)} → ${green("experimental")}`);
|
|
156
|
-
console.log(gray(" This capability is now liquid — free to evolve."));
|
|
157
|
-
console.log();
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// ── scan drift check (frozen caps whose files changed) ────────────────────────
|
|
161
|
-
|
|
162
|
-
export function checkFrozenDrift(infernoDir, cwd) {
|
|
163
|
-
const capsPath = path.join(infernoDir, "capabilities.json");
|
|
164
|
-
const scanPath = path.join(infernoDir, "scan.json");
|
|
165
|
-
if (!fs.existsSync(capsPath) || !fs.existsSync(scanPath)) return [];
|
|
166
|
-
|
|
167
|
-
const caps = loadCaps(capsPath);
|
|
168
|
-
const scan = JSON.parse(fs.readFileSync(scanPath, "utf8"));
|
|
169
|
-
const scannedAt = new Date(scan.scannedAt);
|
|
170
|
-
|
|
171
|
-
const warnings = [];
|
|
172
|
-
for (const cap of caps) {
|
|
173
|
-
if (getLevel(cap) !== "frozen") continue;
|
|
174
|
-
const scanEntry = scan.capabilities?.find(c => c.id === cap.id);
|
|
175
|
-
if (!scanEntry?.codeAnalysis?.sourceFiles) continue;
|
|
176
|
-
|
|
177
|
-
for (const relFile of scanEntry.codeAnalysis.sourceFiles) {
|
|
178
|
-
const absFile = path.join(cwd, relFile);
|
|
179
|
-
try {
|
|
180
|
-
const stat = fs.statSync(absFile);
|
|
181
|
-
if (stat.mtimeMs > scannedAt.getTime()) {
|
|
182
|
-
warnings.push({ capId: cap.id, file: relFile });
|
|
183
|
-
}
|
|
184
|
-
} catch {}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return warnings;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// ── stability summary for CLAUDE.md ──────────────────────────────────────────
|
|
191
|
-
|
|
192
|
-
export function buildStabilitySummary(caps) {
|
|
193
|
-
const frozen = caps.filter(c => getLevel(c) === "frozen").map(c => c.id);
|
|
194
|
-
const stable = caps.filter(c => getLevel(c) === "stable").map(c => c.id);
|
|
195
|
-
const experimental = caps.filter(c => getLevel(c) === "experimental").map(c => c.id);
|
|
196
|
-
|
|
197
|
-
if (frozen.length === 0 && stable.length === 0) return null;
|
|
198
|
-
|
|
199
|
-
const lines = ["### Capability Stability (Solid/Liquid Layer)", ""];
|
|
200
|
-
|
|
201
|
-
if (frozen.length > 0) {
|
|
202
|
-
lines.push("**🧊 Frozen — NEVER modify without explicit instruction:**");
|
|
203
|
-
for (const id of frozen) lines.push(`- \`${id}\``);
|
|
204
|
-
lines.push("");
|
|
205
|
-
}
|
|
206
|
-
if (stable.length > 0) {
|
|
207
|
-
lines.push("**〰️ Stable — prefer additive changes, avoid breaking API:**");
|
|
208
|
-
for (const id of stable) lines.push(`- \`${id}\``);
|
|
209
|
-
lines.push("");
|
|
210
|
-
}
|
|
211
|
-
if (experimental.length > 0) {
|
|
212
|
-
lines.push(`**🌊 Experimental — free to refactor:** ${experimental.map(id => `\`${id}\``).join(", ")}`);
|
|
213
|
-
lines.push("");
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
lines.push("> Run `infernoflow stability` to see the full liquid/solid map.");
|
|
217
|
-
return lines.join("\n");
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// ── entry point ───────────────────────────────────────────────────────────────
|
|
221
|
-
|
|
222
|
-
export async function stabilityCommand(rawArgs) {
|
|
223
|
-
const args = (rawArgs || []).slice(1); // skip command name
|
|
224
|
-
const jsonMode = args.includes("--json");
|
|
225
|
-
const cwd = process.cwd();
|
|
226
|
-
const infernoDir = path.join(cwd, "inferno");
|
|
227
|
-
const capsPath = path.join(infernoDir, "capabilities.json");
|
|
228
|
-
|
|
229
|
-
if (!fs.existsSync(capsPath)) {
|
|
230
|
-
console.error(red("✗ inferno/capabilities.json not found — run `infernoflow init` first."));
|
|
231
|
-
process.exit(1);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const caps = loadCaps(capsPath);
|
|
235
|
-
cmdList(caps, jsonMode);
|
|
236
|
-
|
|
237
|
-
// Also check for frozen drift if scan.json exists
|
|
238
|
-
const driftWarnings = checkFrozenDrift(infernoDir, cwd);
|
|
239
|
-
if (driftWarnings.length > 0) {
|
|
240
|
-
console.log(red(" ⚠ Frozen capability drift detected!"));
|
|
241
|
-
for (const w of driftWarnings) {
|
|
242
|
-
console.log(red(` ${w.capId}: ${w.file} was modified since last scan`));
|
|
243
|
-
}
|
|
244
|
-
console.log(gray(" Run `infernoflow scan` to update the baseline."));
|
|
245
|
-
console.log();
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export async function freezeCommand(rawArgs) {
|
|
250
|
-
const args = (rawArgs || []).slice(1); // skip command name
|
|
251
|
-
const capId = args.find(a => !a.startsWith("--"));
|
|
252
|
-
const isStable = args.includes("--stable");
|
|
253
|
-
const level = isStable ? "stable" : "frozen";
|
|
254
|
-
|
|
255
|
-
if (!capId) {
|
|
256
|
-
console.error(red("✗ Usage: infernoflow freeze <capability-id> [--stable]"));
|
|
257
|
-
process.exit(1);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const cwd = process.cwd();
|
|
261
|
-
const infernoDir = path.join(cwd, "inferno");
|
|
262
|
-
const capsPath = path.join(infernoDir, "capabilities.json");
|
|
263
|
-
|
|
264
|
-
if (!fs.existsSync(capsPath)) {
|
|
265
|
-
console.error(red("✗ inferno/capabilities.json not found."));
|
|
266
|
-
process.exit(1);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const caps = loadCaps(capsPath);
|
|
270
|
-
cmdFreeze(caps, capsPath, capId, level);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
export async function thawCommand(rawArgs) {
|
|
274
|
-
const args = (rawArgs || []).slice(1); // skip command name
|
|
275
|
-
const capId = args.find(a => !a.startsWith("--"));
|
|
276
|
-
|
|
277
|
-
if (!capId) {
|
|
278
|
-
console.error(red("✗ Usage: infernoflow thaw <capability-id>"));
|
|
279
|
-
process.exit(1);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const cwd = process.cwd();
|
|
283
|
-
const infernoDir = path.join(cwd, "inferno");
|
|
284
|
-
const capsPath = path.join(infernoDir, "capabilities.json");
|
|
285
|
-
|
|
286
|
-
if (!fs.existsSync(capsPath)) {
|
|
287
|
-
console.error(red("✗ inferno/capabilities.json not found."));
|
|
288
|
-
process.exit(1);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const caps = loadCaps(capsPath);
|
|
292
|
-
cmdThaw(caps, capsPath, capId);
|
|
293
|
-
}
|
|
1
|
+
import*as p from"node:fs";import*as d from"node:path";import{bold as b,cyan as x,gray as l,green as w,yellow as $,red as r}from"../ui/output.mjs";const m=["experimental","stable","frozen"],j={experimental:"\u{1F30A}",stable:"\u3030\uFE0F",frozen:"\u{1F9CA}"},S={experimental:w,stable:$,frozen:r};function h(n){try{const s=JSON.parse(p.readFileSync(n,"utf8"));return Array.isArray(s)?s:s.capabilities||[]}catch(s){console.error(r("\u2717 Failed to read capabilities.json: "+s.message)),process.exit(1)}}function z(n,s){p.writeFileSync(n,JSON.stringify(s,null,2))}function u(n){return n.stability||"experimental"}function L(n){const s=m.indexOf(n),i=S[n]||l,t="\u2588".repeat(s+1),e="\u2591".repeat(m.length-s-1);return i(t)+l(e)}function C(n,s){if(s){const e=n.map(o=>({id:o.id,name:o.name||o.title,stability:u(o)}));console.log(JSON.stringify(e,null,2));return}const i={frozen:[],stable:[],experimental:[]};for(const e of n)i[u(e)].push(e);console.log(),console.log(b(" Capability Stability")),console.log(l(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(l(" ")+b(x("Capability".padEnd(32)))+b(x("Level".padEnd(16)))+b(x("Solid/Liquid"))),console.log(l(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));for(const e of n){const o=u(e),a=j[o],c=S[o]||l;console.log(` ${a} ${e.id.padEnd(30)} ${c(o.padEnd(14))} ${L(o)}`)}console.log(l(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log();const t={frozen:i.frozen.length,stable:i.stable.length,experimental:i.experimental.length};console.log(` ${r("\u{1F9CA} Frozen:")} ${t.frozen} ${$("\u3030\uFE0F Stable:")} ${t.stable} ${w("\u{1F30A} Experimental:")} ${t.experimental}`),console.log(),console.log(l(" Tip: infernoflow freeze <cap-id> \u2014 infernoflow thaw <cap-id>")),console.log()}function F(n,s,i,t){m.includes(t)||(console.error(r(`\u2717 Invalid level "${t}". Must be: ${m.join(", ")}`)),process.exit(1));const e=n.findIndex(f=>f.id===i);e===-1&&(console.error(r(`\u2717 Capability "${i}" not found in capabilities.json`)),process.exit(1));const o=u(n[e]);n[e]={...n[e],stability:t,stabilitySetAt:new Date().toISOString()},z(s,n);const a=j[t],c=S[t];console.log(),console.log(` ${a} ${b(i)} ${l(o)} \u2192 ${c(t)}`),t==="frozen"&&(console.log(l(" AI assistants will be instructed not to modify this capability.")),console.log(l(" Run `infernoflow setup` to update CLAUDE.md with this change."))),console.log()}function A(n,s,i){const t=n.findIndex(o=>o.id===i);t===-1&&(console.error(r(`\u2717 Capability "${i}" not found in capabilities.json`)),process.exit(1));const e=u(n[t]);n[t]={...n[t],stability:"experimental",stabilitySetAt:new Date().toISOString()},z(s,n),console.log(),console.log(` \u{1F30A} ${b(i)} ${l(e)} \u2192 ${w("experimental")}`),console.log(l(" This capability is now liquid \u2014 free to evolve.")),console.log()}function O(n,s){const i=d.join(n,"capabilities.json"),t=d.join(n,"scan.json");if(!p.existsSync(i)||!p.existsSync(t))return[];const e=h(i),o=JSON.parse(p.readFileSync(t,"utf8")),a=new Date(o.scannedAt),c=[];for(const f of e){if(u(f)!=="frozen")continue;const g=o.capabilities?.find(y=>y.id===f.id);if(g?.codeAnalysis?.sourceFiles)for(const y of g.codeAnalysis.sourceFiles){const E=d.join(s,y);try{p.statSync(E).mtimeMs>a.getTime()&&c.push({capId:f.id,file:y})}catch{}}}return c}function D(n){const s=n.filter(o=>u(o)==="frozen").map(o=>o.id),i=n.filter(o=>u(o)==="stable").map(o=>o.id),t=n.filter(o=>u(o)==="experimental").map(o=>o.id);if(s.length===0&&i.length===0)return null;const e=["### Capability Stability (Solid/Liquid Layer)",""];if(s.length>0){e.push("**\u{1F9CA} Frozen \u2014 NEVER modify without explicit instruction:**");for(const o of s)e.push(`- \`${o}\``);e.push("")}if(i.length>0){e.push("**\u3030\uFE0F Stable \u2014 prefer additive changes, avoid breaking API:**");for(const o of i)e.push(`- \`${o}\``);e.push("")}return t.length>0&&(e.push(`**\u{1F30A} Experimental \u2014 free to refactor:** ${t.map(o=>`\`${o}\``).join(", ")}`),e.push("")),e.push("> Run `infernoflow stability` to see the full liquid/solid map."),e.join(`
|
|
2
|
+
`)}async function N(n){const i=(n||[]).slice(1).includes("--json"),t=process.cwd(),e=d.join(t,"inferno"),o=d.join(e,"capabilities.json");p.existsSync(o)||(console.error(r("\u2717 inferno/capabilities.json not found \u2014 run `infernoflow init` first.")),process.exit(1));const a=h(o);C(a,i);const c=O(e,t);if(c.length>0){console.log(r(" \u26A0 Frozen capability drift detected!"));for(const f of c)console.log(r(` ${f.capId}: ${f.file} was modified since last scan`));console.log(l(" Run `infernoflow scan` to update the baseline.")),console.log()}}async function P(n){const s=(n||[]).slice(1),i=s.find(g=>!g.startsWith("--")),e=s.includes("--stable")?"stable":"frozen";i||(console.error(r("\u2717 Usage: infernoflow freeze <capability-id> [--stable]")),process.exit(1));const o=process.cwd(),a=d.join(o,"inferno"),c=d.join(a,"capabilities.json");p.existsSync(c)||(console.error(r("\u2717 inferno/capabilities.json not found.")),process.exit(1));const f=h(c);F(f,c,i,e)}async function R(n){const i=(n||[]).slice(1).find(c=>!c.startsWith("--"));i||(console.error(r("\u2717 Usage: infernoflow thaw <capability-id>")),process.exit(1));const t=process.cwd(),e=d.join(t,"inferno"),o=d.join(e,"capabilities.json");p.existsSync(o)||(console.error(r("\u2717 inferno/capabilities.json not found.")),process.exit(1));const a=h(o);A(a,o,i)}export{m as LEVELS,D as buildStabilitySummary,O as checkFrozenDrift,P as freezeCommand,N as stabilityCommand,R as thawCommand};
|
|
@@ -1,172 +1,4 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
function timeAgo(ms) {
|
|
6
|
-
const s = Math.floor((Date.now() - ms) / 1000);
|
|
7
|
-
if (s < 60) return "just now";
|
|
8
|
-
if (s < 3600) return `${Math.floor(s / 60)}m ago`;
|
|
9
|
-
if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
|
|
10
|
-
return `${Math.floor(s / 86400)}d ago`;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function getCoverage(scenariosDir, caps) {
|
|
14
|
-
const covered = new Set();
|
|
15
|
-
if (fs.existsSync(scenariosDir)) {
|
|
16
|
-
for (const f of fs.readdirSync(scenariosDir).filter(f => f.endsWith(".json"))) {
|
|
17
|
-
try {
|
|
18
|
-
const s = JSON.parse(fs.readFileSync(path.join(scenariosDir, f), "utf8"));
|
|
19
|
-
(s.capabilitiesCovered || []).forEach(c => covered.add(c));
|
|
20
|
-
} catch {}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return { covered: caps.filter(c => covered.has(c)), uncovered: caps.filter(c => !covered.has(c)) };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function statusCommand(args = []) {
|
|
27
|
-
const asJson = args.includes("--json");
|
|
28
|
-
const cwd = process.cwd();
|
|
29
|
-
const infernoDir = path.join(cwd, "inferno");
|
|
30
|
-
if (!asJson) {
|
|
31
|
-
header("status");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (!fs.existsSync(infernoDir)) {
|
|
35
|
-
if (asJson) {
|
|
36
|
-
console.log(JSON.stringify({ ok: false, error: "inferno_not_found", hint: "Run: infernoflow init" }, null, 2));
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
fail("inferno/ not found", `Run: infernoflow init`);
|
|
40
|
-
console.log();
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const contractPath = path.join(infernoDir, "contract.json");
|
|
45
|
-
if (!fs.existsSync(contractPath)) {
|
|
46
|
-
if (asJson) {
|
|
47
|
-
console.log(JSON.stringify({ ok: false, error: "contract_not_found" }, null, 2));
|
|
48
|
-
process.exit(1);
|
|
49
|
-
}
|
|
50
|
-
fail("contract.json not found");
|
|
51
|
-
console.log();
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const contract = JSON.parse(fs.readFileSync(contractPath, "utf8"));
|
|
56
|
-
const caps = contract.capabilities || [];
|
|
57
|
-
const stat = fs.statSync(contractPath);
|
|
58
|
-
const scenariosDir = path.join(infernoDir, "scenarios");
|
|
59
|
-
const changelogPath = path.join(infernoDir, "CHANGELOG.md");
|
|
60
|
-
const capsPath = path.join(infernoDir, "capabilities.json");
|
|
61
|
-
const { covered, uncovered } = getCoverage(scenariosDir, caps);
|
|
62
|
-
|
|
63
|
-
const hasChangelog = fs.existsSync(changelogPath) && /##\s+Unreleased/i.test(fs.readFileSync(changelogPath, "utf8"));
|
|
64
|
-
const driftReasons = [];
|
|
65
|
-
if (uncovered.length > 0) driftReasons.push(`${uncovered.length} capabilities without scenario coverage`);
|
|
66
|
-
if (!hasChangelog) driftReasons.push("CHANGELOG missing ## Unreleased section");
|
|
67
|
-
const allGood = driftReasons.length === 0;
|
|
68
|
-
|
|
69
|
-
if (asJson) {
|
|
70
|
-
const payload = {
|
|
71
|
-
ok: allGood,
|
|
72
|
-
driftReasons,
|
|
73
|
-
project: {
|
|
74
|
-
policyId: contract.policyId || null,
|
|
75
|
-
policyVersion: contract.policyVersion || null,
|
|
76
|
-
lastChange: timeAgo(stat.mtimeMs),
|
|
77
|
-
},
|
|
78
|
-
capabilities: {
|
|
79
|
-
total: caps.length,
|
|
80
|
-
uncovered,
|
|
81
|
-
},
|
|
82
|
-
changelog: {
|
|
83
|
-
hasUnreleased: hasChangelog,
|
|
84
|
-
},
|
|
85
|
-
};
|
|
86
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
87
|
-
process.exit(allGood ? 0 : 1);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (!allGood) {
|
|
91
|
-
section("Drift");
|
|
92
|
-
driftReasons.forEach((reason) => console.log(` ${yellow("⚠")} ${reason}`));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// ── Project ─────────────────────────────────────────────────────
|
|
96
|
-
section("Project");
|
|
97
|
-
console.log(` ${gray("policy")} ${bold(contract.policyId || "—")}`);
|
|
98
|
-
console.log(` ${gray("version")} ${bold("v" + (contract.policyVersion || "?"))}`);
|
|
99
|
-
console.log(` ${gray("last change")} ${gray(timeAgo(stat.mtimeMs))}`);
|
|
100
|
-
|
|
101
|
-
// ── Capabilities ─────────────────────────────────────────────────
|
|
102
|
-
section(`Capabilities ${gray("(" + caps.length + ")")}`);
|
|
103
|
-
|
|
104
|
-
let capsRegistry = {};
|
|
105
|
-
if (fs.existsSync(capsPath)) {
|
|
106
|
-
try {
|
|
107
|
-
const reg = JSON.parse(fs.readFileSync(capsPath, "utf8"));
|
|
108
|
-
(reg.capabilities || []).forEach(c => { capsRegistry[c.id] = c; });
|
|
109
|
-
} catch {}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
caps.forEach(cap => {
|
|
113
|
-
const reg = capsRegistry[cap];
|
|
114
|
-
const hasCoverage = covered.includes(cap);
|
|
115
|
-
const icon = hasCoverage ? green("✔") : red("✘");
|
|
116
|
-
const title = reg?.title ? gray(` — ${reg.title}`) : "";
|
|
117
|
-
const since = reg?.since ? gray(` [${reg.since}]`) : "";
|
|
118
|
-
console.log(` ${icon} ${white(cap)}${title}${since}`);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
if (uncovered.length > 0) {
|
|
122
|
-
console.log(`\n ${yellow("⚠")} ${uncovered.length} capability(ies) lack scenario coverage`);
|
|
123
|
-
} else {
|
|
124
|
-
console.log(`\n ${green("✔")} All capabilities have scenario coverage`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// ── Scenarios ─────────────────────────────────────────────────────
|
|
128
|
-
section("Scenarios");
|
|
129
|
-
if (fs.existsSync(scenariosDir)) {
|
|
130
|
-
const files = fs.readdirSync(scenariosDir).filter(f => f.endsWith(".json"));
|
|
131
|
-
if (files.length === 0) {
|
|
132
|
-
warn("No scenario files — add .json files to inferno/scenarios/");
|
|
133
|
-
} else {
|
|
134
|
-
files.forEach(f => {
|
|
135
|
-
try {
|
|
136
|
-
const s = JSON.parse(fs.readFileSync(path.join(scenariosDir, f), "utf8"));
|
|
137
|
-
const steps = s.steps?.length || 0;
|
|
138
|
-
const capCount = (s.capabilitiesCovered || []).length;
|
|
139
|
-
console.log(` ${green("✔")} ${cyan(f)} ${gray(`— ${steps} steps, ${capCount} caps covered`)}`);
|
|
140
|
-
} catch {
|
|
141
|
-
console.log(` ${red("✘")} ${cyan(f)} ${gray("— invalid JSON")}`);
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
} else {
|
|
146
|
-
warn("scenarios/ directory not found");
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// ── Changelog ─────────────────────────────────────────────────────
|
|
150
|
-
section("Changelog");
|
|
151
|
-
if (fs.existsSync(changelogPath)) {
|
|
152
|
-
const txt = fs.readFileSync(changelogPath, "utf8");
|
|
153
|
-
if (/##\s+Unreleased/i.test(txt)) {
|
|
154
|
-
ok("Has ## Unreleased section");
|
|
155
|
-
} else {
|
|
156
|
-
fail("Missing ## Unreleased section");
|
|
157
|
-
}
|
|
158
|
-
const sections = txt.split("\n").filter(l => /^##\s/.test(l)).slice(0, 3);
|
|
159
|
-
sections.forEach(l => console.log(` ${gray(l)}`));
|
|
160
|
-
} else {
|
|
161
|
-
fail("inferno/CHANGELOG.md not found");
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// ── Health ────────────────────────────────────────────────────────
|
|
165
|
-
console.log();
|
|
166
|
-
if (allGood) {
|
|
167
|
-
console.log(` ${green("●")} ${bold(green("ready"))} ${gray("— run infernoflow check for full validation")}`);
|
|
168
|
-
} else {
|
|
169
|
-
console.log(` ${yellow("●")} ${bold(yellow("needs attention"))} ${gray("— run infernoflow check for details")}`);
|
|
170
|
-
}
|
|
171
|
-
console.log();
|
|
172
|
-
}
|
|
1
|
+
import*as o from"node:fs";import*as a from"node:path";import{header as A,ok as H,fail as j,warn as J,section as p,bold as m,cyan as k,yellow as x,gray as t,green as u,red as G,white as P}from"../ui/output.mjs";function M(c){const e=Math.floor((Date.now()-c)/1e3);return e<60?"just now":e<3600?`${Math.floor(e/60)}m ago`:e<86400?`${Math.floor(e/3600)}h ago`:`${Math.floor(e/86400)}d ago`}function R(c,e){const g=new Set;if(o.existsSync(c))for(const i of o.readdirSync(c).filter(f=>f.endsWith(".json")))try{(JSON.parse(o.readFileSync(a.join(c,i),"utf8")).capabilitiesCovered||[]).forEach(l=>g.add(l))}catch{}return{covered:e.filter(i=>g.has(i)),uncovered:e.filter(i=>!g.has(i))}}async function I(c=[]){const e=c.includes("--json"),g=process.cwd(),i=a.join(g,"inferno");e||A("status"),o.existsSync(i)||(e&&(console.log(JSON.stringify({ok:!1,error:"inferno_not_found",hint:"Run: infernoflow init"},null,2)),process.exit(1)),j("inferno/ not found","Run: infernoflow init"),console.log(),process.exit(1));const f=a.join(i,"contract.json");o.existsSync(f)||(e&&(console.log(JSON.stringify({ok:!1,error:"contract_not_found"},null,2)),process.exit(1)),j("contract.json not found"),console.log(),process.exit(1));const l=JSON.parse(o.readFileSync(f,"utf8")),y=l.capabilities||[],N=o.statSync(f),$=a.join(i,"scenarios"),S=a.join(i,"CHANGELOG.md"),b=a.join(i,"capabilities.json"),{covered:F,uncovered:d}=R($,y),O=o.existsSync(S)&&/##\s+Unreleased/i.test(o.readFileSync(S,"utf8")),h=[];d.length>0&&h.push(`${d.length} capabilities without scenario coverage`),O||h.push("CHANGELOG missing ## Unreleased section");const v=h.length===0;if(e){const n={ok:v,driftReasons:h,project:{policyId:l.policyId||null,policyVersion:l.policyVersion||null,lastChange:M(N.mtimeMs)},capabilities:{total:y.length,uncovered:d},changelog:{hasUnreleased:O}};console.log(JSON.stringify(n,null,2)),process.exit(v?0:1)}v||(p("Drift"),h.forEach(n=>console.log(` ${x("\u26A0")} ${n}`))),p("Project"),console.log(` ${t("policy")} ${m(l.policyId||"\u2014")}`),console.log(` ${t("version")} ${m("v"+(l.policyVersion||"?"))}`),console.log(` ${t("last change")} ${t(M(N.mtimeMs))}`),p(`Capabilities ${t("("+y.length+")")}`);let E={};if(o.existsSync(b))try{(JSON.parse(o.readFileSync(b,"utf8")).capabilities||[]).forEach(s=>{E[s.id]=s})}catch{}if(y.forEach(n=>{const s=E[n],C=F.includes(n)?u("\u2714"):G("\u2718"),w=s?.title?t(` \u2014 ${s.title}`):"",U=s?.since?t(` [${s.since}]`):"";console.log(` ${C} ${P(n)}${w}${U}`)}),d.length>0?console.log(`
|
|
2
|
+
${x("\u26A0")} ${d.length} capability(ies) lack scenario coverage`):console.log(`
|
|
3
|
+
${u("\u2714")} All capabilities have scenario coverage`),p("Scenarios"),o.existsSync($)){const n=o.readdirSync($).filter(s=>s.endsWith(".json"));n.length===0?J("No scenario files \u2014 add .json files to inferno/scenarios/"):n.forEach(s=>{try{const r=JSON.parse(o.readFileSync(a.join($,s),"utf8")),C=r.steps?.length||0,w=(r.capabilitiesCovered||[]).length;console.log(` ${u("\u2714")} ${k(s)} ${t(`\u2014 ${C} steps, ${w} caps covered`)}`)}catch{console.log(` ${G("\u2718")} ${k(s)} ${t("\u2014 invalid JSON")}`)}})}else J("scenarios/ directory not found");if(p("Changelog"),o.existsSync(S)){const n=o.readFileSync(S,"utf8");/##\s+Unreleased/i.test(n)?H("Has ## Unreleased section"):j("Missing ## Unreleased section"),n.split(`
|
|
4
|
+
`).filter(r=>/^##\s/.test(r)).slice(0,3).forEach(r=>console.log(` ${t(r)}`))}else j("inferno/CHANGELOG.md not found");console.log(),console.log(v?` ${u("\u25CF")} ${m(u("ready"))} ${t("\u2014 run infernoflow check for full validation")}`:` ${x("\u25CF")} ${m(x("needs attention"))} ${t("\u2014 run infernoflow check for details")}`),console.log()}export{I as statusCommand};
|