infernoflow 0.33.0 โ 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +208 -120
- package/dist/bin/infernoflow.mjs +271 -85
- package/dist/lib/adopters/angular.mjs +128 -1
- package/dist/lib/adopters/css.mjs +111 -1
- package/dist/lib/adopters/react.mjs +104 -1
- package/dist/lib/ai/ideDetection.mjs +31 -1
- package/dist/lib/ai/localProvider.mjs +88 -1
- package/dist/lib/ai/providerRouter.mjs +295 -2
- package/dist/lib/commands/adopt.mjs +869 -20
- package/dist/lib/commands/adoptWizard.mjs +320 -9
- package/dist/lib/commands/agent.mjs +191 -5
- package/dist/lib/commands/ai.mjs +407 -2
- package/dist/lib/commands/ask.mjs +299 -0
- package/dist/lib/commands/audit.mjs +300 -13
- package/dist/lib/commands/changelog.mjs +594 -26
- package/dist/lib/commands/check.mjs +184 -3
- package/dist/lib/commands/ci.mjs +208 -3
- package/dist/lib/commands/claudeMd.mjs +139 -28
- package/dist/lib/commands/cloud.mjs +521 -5
- package/dist/lib/commands/context.mjs +346 -34
- package/dist/lib/commands/coverage.mjs +282 -2
- package/dist/lib/commands/dashboard.mjs +635 -123
- package/dist/lib/commands/demo.mjs +465 -8
- package/dist/lib/commands/diff.mjs +274 -5
- package/dist/lib/commands/docGate.mjs +81 -2
- package/dist/lib/commands/doctor.mjs +321 -3
- package/dist/lib/commands/explain.mjs +438 -8
- package/dist/lib/commands/export.mjs +239 -10
- package/dist/lib/commands/generateSkills.mjs +163 -38
- package/dist/lib/commands/graph.mjs +378 -11
- package/dist/lib/commands/health.mjs +309 -2
- package/dist/lib/commands/impact.mjs +325 -2
- package/dist/lib/commands/implement.mjs +103 -7
- package/dist/lib/commands/init.mjs +545 -23
- package/dist/lib/commands/installCursorHooks.mjs +36 -1
- package/dist/lib/commands/installVsCodeCopilotHooks.mjs +37 -1
- package/dist/lib/commands/link.mjs +342 -2
- package/dist/lib/commands/log.mjs +164 -16
- package/dist/lib/commands/monorepo.mjs +428 -4
- package/dist/lib/commands/notify.mjs +258 -4
- package/dist/lib/commands/onboard.mjs +296 -4
- package/dist/lib/commands/prComment.mjs +361 -2
- package/dist/lib/commands/prImpact.mjs +157 -2
- package/dist/lib/commands/publish.mjs +316 -15
- package/dist/lib/commands/recap.mjs +359 -0
- package/dist/lib/commands/report.mjs +272 -28
- package/dist/lib/commands/review.mjs +223 -9
- package/dist/lib/commands/run.mjs +336 -8
- package/dist/lib/commands/scaffold.mjs +419 -54
- package/dist/lib/commands/scan.mjs +1118 -5
- package/dist/lib/commands/scout.mjs +291 -2
- package/dist/lib/commands/setup.mjs +310 -5
- package/dist/lib/commands/share.mjs +196 -13
- package/dist/lib/commands/snapshot.mjs +383 -3
- package/dist/lib/commands/stability.mjs +293 -2
- package/dist/lib/commands/stats.mjs +402 -0
- package/dist/lib/commands/status.mjs +172 -4
- package/dist/lib/commands/suggest.mjs +563 -21
- package/dist/lib/commands/switch.mjs +310 -0
- package/dist/lib/commands/syncAuto.mjs +96 -1
- package/dist/lib/commands/synthesize.mjs +228 -10
- package/dist/lib/commands/teamSync.mjs +388 -2
- package/dist/lib/commands/test.mjs +363 -6
- package/dist/lib/commands/theme.mjs +195 -18
- package/dist/lib/commands/upgrade.mjs +153 -0
- package/dist/lib/commands/version.mjs +282 -2
- package/dist/lib/commands/vibe.mjs +357 -7
- package/dist/lib/commands/watch.mjs +203 -4
- package/dist/lib/commands/why.mjs +358 -4
- package/dist/lib/cursorHooksInstall.mjs +60 -1
- package/dist/lib/draftToolingInstall.mjs +68 -7
- package/dist/lib/git/detect-drift.mjs +208 -4
- package/dist/lib/learning/adapt.mjs +101 -6
- package/dist/lib/learning/observe.mjs +119 -1
- package/dist/lib/learning/patternDetector.mjs +298 -1
- package/dist/lib/learning/profile.mjs +279 -2
- package/dist/lib/learning/skillSynthesizer.mjs +145 -24
- package/dist/lib/templates/index.mjs +131 -1
- package/dist/lib/theme/scanner.mjs +343 -4
- package/dist/lib/ui/errors.mjs +142 -1
- package/dist/lib/ui/output.mjs +72 -6
- package/dist/lib/ui/prompts.mjs +147 -6
- package/dist/lib/vsCodeCopilotHooksInstall.mjs +42 -1
- package/package.json +1 -1
|
@@ -1,9 +1,280 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* infernoflow graph
|
|
3
|
+
*
|
|
4
|
+
* Builds a capability dependency graph from scan.json.
|
|
5
|
+
* Shows which capabilities call which โ so changing one reveals its downstream impact.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* infernoflow graph Print full dependency tree
|
|
9
|
+
* infernoflow graph --cap auth-login Show deps for one capability (up + down)
|
|
10
|
+
* infernoflow graph --json Machine-readable graph.json to stdout
|
|
11
|
+
* infernoflow graph --check Warn if frozen/stable caps have new dependents
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import * as fs from "node:fs";
|
|
15
|
+
import * as path from "node:path";
|
|
16
|
+
import { bold, cyan, gray, green, yellow, red } from "../ui/output.mjs";
|
|
17
|
+
|
|
18
|
+
// โโ helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
19
|
+
|
|
20
|
+
function loadJson(p) {
|
|
21
|
+
try { return JSON.parse(fs.readFileSync(p, "utf8")); }
|
|
22
|
+
catch { return null; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getLevel(cap) {
|
|
26
|
+
return cap?.stability || "experimental";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const LEVEL_ICON = { frozen: "๐ง", stable: "ใฐ๏ธ ", experimental: "๐" };
|
|
30
|
+
const LEVEL_COLOR = { frozen: red, stable: yellow, experimental: green };
|
|
31
|
+
|
|
32
|
+
// โโ graph builder โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Build edges: capA โ capB if any function in capA calls a function in capB.
|
|
36
|
+
*
|
|
37
|
+
* Strategy:
|
|
38
|
+
* 1. Build a function-name โ capId index from scan data
|
|
39
|
+
* 2. For each cap, check its calls[] against the index
|
|
40
|
+
* 3. If a call matches a function in another cap โ edge
|
|
41
|
+
*/
|
|
42
|
+
function buildGraph(scanCaps, allCaps) {
|
|
43
|
+
// capId โ { id, name, stability, functions[], calls[], services[], dbCalls[], httpCalls[] }
|
|
44
|
+
const nodes = {};
|
|
45
|
+
// capId โ Set<capId> (edges: this cap calls that cap)
|
|
46
|
+
const edges = {};
|
|
47
|
+
// capId โ Set<capId> (reverse: this cap is called by those caps)
|
|
48
|
+
const reverse = {};
|
|
49
|
+
|
|
50
|
+
// Build function โ capId index
|
|
51
|
+
// Use Map (not plain object) to avoid collisions with inherited properties like toString, constructor, etc.
|
|
52
|
+
const funcIndex = new Map(); // functionName โ capId
|
|
53
|
+
for (const entry of scanCaps) {
|
|
54
|
+
const capFull = allCaps.find(c => c.id === entry.id) || {};
|
|
55
|
+
nodes[entry.id] = {
|
|
56
|
+
id: entry.id,
|
|
57
|
+
name: entry.name || capFull.name || capFull.title || entry.id,
|
|
58
|
+
stability: capFull.stability || "experimental",
|
|
59
|
+
functions: entry.codeAnalysis?.functions || [],
|
|
60
|
+
calls: entry.codeAnalysis?.calls || [],
|
|
61
|
+
services: entry.codeAnalysis?.services || [],
|
|
62
|
+
dbCalls: entry.codeAnalysis?.dbCalls || [],
|
|
63
|
+
httpCalls: entry.codeAnalysis?.httpCalls || [],
|
|
64
|
+
};
|
|
65
|
+
edges[entry.id] = new Set();
|
|
66
|
+
reverse[entry.id] = new Set();
|
|
67
|
+
|
|
68
|
+
for (const fn of (entry.codeAnalysis?.functions || [])) {
|
|
69
|
+
const bare = fn.replace(/\(\)$/, "");
|
|
70
|
+
funcIndex.set(bare, entry.id);
|
|
71
|
+
funcIndex.set(bare.toLowerCase(), entry.id);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Build edges from calls[]
|
|
76
|
+
for (const [capId, node] of Object.entries(nodes)) {
|
|
77
|
+
for (const call of node.calls) {
|
|
78
|
+
const bare = call.replace(/\(\)$/, "");
|
|
79
|
+
const target = funcIndex.get(bare) || funcIndex.get(bare.toLowerCase());
|
|
80
|
+
if (target && target !== capId) {
|
|
81
|
+
edges[capId].add(target);
|
|
82
|
+
reverse[target].add(capId);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Serialise Sets to arrays
|
|
88
|
+
const serialisedEdges = {};
|
|
89
|
+
const serialisedReverse = {};
|
|
90
|
+
for (const id of Object.keys(nodes)) {
|
|
91
|
+
serialisedEdges[id] = [...edges[id]];
|
|
92
|
+
serialisedReverse[id] = [...reverse[id]];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { nodes, edges: serialisedEdges, reverse: serialisedReverse };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// โโ terminal reporters โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
99
|
+
|
|
100
|
+
function printFullGraph(graph) {
|
|
101
|
+
const { nodes, edges, reverse } = graph;
|
|
102
|
+
const ids = Object.keys(nodes).sort();
|
|
103
|
+
|
|
104
|
+
console.log();
|
|
105
|
+
console.log(bold(" Capability Dependency Graph"));
|
|
106
|
+
console.log(gray(" โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"));
|
|
107
|
+
console.log();
|
|
108
|
+
|
|
109
|
+
let hasDeps = false;
|
|
110
|
+
for (const id of ids) {
|
|
111
|
+
const node = nodes[id];
|
|
112
|
+
const deps = edges[id] || [];
|
|
113
|
+
const callers = reverse[id] || [];
|
|
114
|
+
const icon = LEVEL_ICON[node.stability] || "๐";
|
|
115
|
+
const color = LEVEL_COLOR[node.stability] || green;
|
|
116
|
+
|
|
117
|
+
if (deps.length === 0 && callers.length === 0) continue;
|
|
118
|
+
hasDeps = true;
|
|
119
|
+
|
|
120
|
+
console.log(` ${icon} ${bold(color(id))}`);
|
|
121
|
+
|
|
122
|
+
if (deps.length > 0) {
|
|
123
|
+
console.log(gray(" calls โ"));
|
|
124
|
+
for (const dep of deps) {
|
|
125
|
+
const depNode = nodes[dep];
|
|
126
|
+
const depIcon = LEVEL_ICON[depNode?.stability] || "๐";
|
|
127
|
+
console.log(gray(` ${depIcon} ${dep}`));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (callers.length > 0) {
|
|
131
|
+
console.log(gray(" called by โ"));
|
|
132
|
+
for (const caller of callers) {
|
|
133
|
+
const callerIcon = LEVEL_ICON[nodes[caller]?.stability] || "๐";
|
|
134
|
+
console.log(gray(` ${callerIcon} ${caller}`));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
console.log();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!hasDeps) {
|
|
141
|
+
console.log(gray(" No inter-capability dependencies detected."));
|
|
142
|
+
console.log(gray(" Run `infernoflow scan` first to populate call data."));
|
|
143
|
+
console.log();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Summary stats
|
|
147
|
+
const totalEdges = Object.values(graph.edges).reduce((n, arr) => n + arr.length, 0);
|
|
148
|
+
console.log(gray(` โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ`));
|
|
149
|
+
console.log(gray(` ${ids.length} capabilities ยท ${totalEdges} dependency edge(s)`));
|
|
150
|
+
console.log();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function printCapGraph(capId, graph) {
|
|
154
|
+
const { nodes, edges, reverse } = graph;
|
|
155
|
+
const node = nodes[capId];
|
|
156
|
+
if (!node) {
|
|
157
|
+
console.error(red(`โ Capability "${capId}" not found in graph.`));
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const icon = LEVEL_ICON[node.stability] || "๐";
|
|
162
|
+
const color = LEVEL_COLOR[node.stability] || green;
|
|
163
|
+
|
|
164
|
+
console.log();
|
|
165
|
+
console.log(bold(` ${icon} ${color(capId)}`) + gray(` (${node.stability})`));
|
|
166
|
+
if (node.services?.length) console.log(gray(` external: `) + cyan(node.services.join(", ")));
|
|
167
|
+
console.log();
|
|
168
|
+
|
|
169
|
+
const deps = edges[capId] || [];
|
|
170
|
+
const callers = reverse[capId] || [];
|
|
171
|
+
|
|
172
|
+
if (deps.length > 0) {
|
|
173
|
+
console.log(bold(" Calls (downstream dependencies):"));
|
|
174
|
+
for (const dep of deps) {
|
|
175
|
+
const d = nodes[dep];
|
|
176
|
+
const dColor = LEVEL_COLOR[d?.stability] || green;
|
|
177
|
+
const dIcon = LEVEL_ICON[d?.stability] || "๐";
|
|
178
|
+
console.log(` ${dIcon} ${dColor(dep)}` + gray(d?.services?.length ? ` [${d.services.join(", ")}]` : ""));
|
|
179
|
+
}
|
|
180
|
+
console.log();
|
|
181
|
+
} else {
|
|
182
|
+
console.log(gray(" No downstream dependencies."));
|
|
183
|
+
console.log();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (callers.length > 0) {
|
|
187
|
+
console.log(bold(" Called by (upstream dependents):"));
|
|
188
|
+
for (const caller of callers) {
|
|
189
|
+
const c = nodes[caller];
|
|
190
|
+
const cColor = LEVEL_COLOR[c?.stability] || green;
|
|
191
|
+
const cIcon = LEVEL_ICON[c?.stability] || "๐";
|
|
192
|
+
console.log(` ${cIcon} ${cColor(caller)}`);
|
|
193
|
+
}
|
|
194
|
+
console.log();
|
|
195
|
+
} else {
|
|
196
|
+
console.log(gray(" No capabilities call this one."));
|
|
197
|
+
console.log();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Impact warning for frozen/stable
|
|
201
|
+
if ((node.stability === "frozen" || node.stability === "stable") && callers.length > 0) {
|
|
202
|
+
const color2 = node.stability === "frozen" ? red : yellow;
|
|
203
|
+
console.log(color2(` โ This capability is ${node.stability}. Changing it may break:`));
|
|
204
|
+
for (const caller of callers) console.log(color2(` โข ${caller}`));
|
|
205
|
+
console.log();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// โโ breaking change checker โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Compare previous graph.json with new graph to detect:
|
|
213
|
+
* - frozen/stable caps that have gained new callers (more dependents = higher risk)
|
|
214
|
+
* - frozen caps that have new outgoing deps (their internals changed)
|
|
215
|
+
*/
|
|
216
|
+
function checkBreakingChanges(prevGraph, newGraph) {
|
|
217
|
+
const warnings = [];
|
|
218
|
+
if (!prevGraph || !newGraph) return warnings;
|
|
219
|
+
|
|
220
|
+
for (const [capId, node] of Object.entries(newGraph.nodes)) {
|
|
221
|
+
if (node.stability === "experimental") continue;
|
|
222
|
+
|
|
223
|
+
const prevCallers = new Set(prevGraph.reverse?.[capId] || []);
|
|
224
|
+
const newCallers = new Set(newGraph.reverse[capId] || []);
|
|
225
|
+
const addedCallers = [...newCallers].filter(c => !prevCallers.has(c));
|
|
226
|
+
|
|
227
|
+
if (addedCallers.length > 0) {
|
|
228
|
+
warnings.push({
|
|
229
|
+
type: "new-dependents",
|
|
230
|
+
capId,
|
|
231
|
+
stability: node.stability,
|
|
232
|
+
detail: `${addedCallers.join(", ")} now depend on this`,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (node.stability === "frozen") {
|
|
237
|
+
const prevDeps = new Set(prevGraph.edges?.[capId] || []);
|
|
238
|
+
const newDeps = new Set(newGraph.edges[capId] || []);
|
|
239
|
+
const addedDeps = [...newDeps].filter(d => !prevDeps.has(d));
|
|
240
|
+
const removedDeps = [...prevDeps].filter(d => !newDeps.has(d));
|
|
241
|
+
|
|
242
|
+
if (addedDeps.length > 0 || removedDeps.length > 0) {
|
|
243
|
+
warnings.push({
|
|
244
|
+
type: "frozen-internals-changed",
|
|
245
|
+
capId,
|
|
246
|
+
stability: node.stability,
|
|
247
|
+
detail: [
|
|
248
|
+
addedDeps.length ? `added calls: ${addedDeps.join(", ")}` : "",
|
|
249
|
+
removedDeps.length ? `removed calls: ${removedDeps.join(", ")}` : "",
|
|
250
|
+
].filter(Boolean).join("; "),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return warnings;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// โโ HTML graph generator โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
260
|
+
|
|
261
|
+
export function buildGraphHtml(graphData) {
|
|
262
|
+
const nodes = graphData.nodes || {};
|
|
263
|
+
const edges = graphData.deps || {};
|
|
264
|
+
const allIds = Object.keys(nodes);
|
|
265
|
+
const edgeList = [];
|
|
266
|
+
for (const [from, targets] of Object.entries(edges)) {
|
|
267
|
+
for (const to of targets) edgeList.push({ from, to });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const dataJson = JSON.stringify({ nodes: allIds, edges: edgeList });
|
|
271
|
+
|
|
272
|
+
return `<!DOCTYPE html>
|
|
2
273
|
<html lang="en">
|
|
3
274
|
<head>
|
|
4
275
|
<meta charset="UTF-8">
|
|
5
276
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
-
<title>infernoflow
|
|
277
|
+
<title>infernoflow โ Capability Graph</title>
|
|
7
278
|
<style>
|
|
8
279
|
*{box-sizing:border-box;margin:0;padding:0}
|
|
9
280
|
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0f1117;color:#e2e8f0;height:100vh;display:flex;flex-direction:column}
|
|
@@ -31,8 +302,8 @@ import*as E from"node:fs";import*as x from"node:path";import{bold as u,cyan as z
|
|
|
31
302
|
</head>
|
|
32
303
|
<body>
|
|
33
304
|
<header>
|
|
34
|
-
<span style="font-size:18px"
|
|
35
|
-
<h1>infernoflow
|
|
305
|
+
<span style="font-size:18px">๐ฅ</span>
|
|
306
|
+
<h1>infernoflow โ Capability Graph</h1>
|
|
36
307
|
<span id="project-name"></span>
|
|
37
308
|
<span id="stats"></span>
|
|
38
309
|
</header>
|
|
@@ -51,7 +322,7 @@ import*as E from"node:fs";import*as x from"node:path";import{bold as u,cyan as z
|
|
|
51
322
|
</div>
|
|
52
323
|
</div>
|
|
53
324
|
<script>
|
|
54
|
-
const DATA = ${
|
|
325
|
+
const DATA = ${dataJson};
|
|
55
326
|
const NW=120, NH=34;
|
|
56
327
|
|
|
57
328
|
// Separate connected vs isolated nodes
|
|
@@ -180,15 +451,15 @@ function render() {
|
|
|
180
451
|
svg.appendChild(g);
|
|
181
452
|
});
|
|
182
453
|
|
|
183
|
-
document.getElementById("stats").textContent = \`\${DATA.nodes.length} capabilities
|
|
454
|
+
document.getElementById("stats").textContent = \`\${DATA.nodes.length} capabilities ยท \${DATA.edges.length} edges\`;
|
|
184
455
|
}
|
|
185
456
|
|
|
186
457
|
function showTip(id, evt) {
|
|
187
458
|
const calls = DATA.edges.filter(e=>e.from===id).map(e=>e.to);
|
|
188
459
|
const callers = DATA.edges.filter(e=>e.to===id).map(e=>e.from);
|
|
189
|
-
let html = \`<strong
|
|
190
|
-
if (calls.length) html += \`<div style="margin-top:6px">calls
|
|
191
|
-
if (callers.length) html += \`<div style="margin-top:6px"
|
|
460
|
+
let html = \`<strong>๐ \${id}</strong>\`;
|
|
461
|
+
if (calls.length) html += \`<div style="margin-top:6px">calls โ<br>\${calls.map(c=>\`<span class="tag calls">\${c}</span>\`).join('')}</div>\`;
|
|
462
|
+
if (callers.length) html += \`<div style="margin-top:6px">โ called by<br>\${callers.map(c=>\`<span class="tag callers">\${c}</span>\`).join('')}</div>\`;
|
|
192
463
|
if (!calls.length && !callers.length) html += \`<div style="margin-top:6px;color:#475569;font-size:11px">No inter-capability dependencies</div>\`;
|
|
193
464
|
const tip=document.getElementById("tip");
|
|
194
465
|
tip.innerHTML=html; tip.style.opacity="1";
|
|
@@ -216,5 +487,101 @@ render();
|
|
|
216
487
|
window.addEventListener("resize", render);
|
|
217
488
|
</script>
|
|
218
489
|
</body>
|
|
219
|
-
</html
|
|
220
|
-
|
|
490
|
+
</html>`;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// โโ entry point โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
494
|
+
|
|
495
|
+
export async function graphCommand(rawArgs) {
|
|
496
|
+
const args = (rawArgs || []).slice(1); // skip command name
|
|
497
|
+
const jsonMode = args.includes("--json");
|
|
498
|
+
const checkMode = args.includes("--check");
|
|
499
|
+
const htmlMode = args.includes("--html");
|
|
500
|
+
const capIdx = args.indexOf("--cap");
|
|
501
|
+
const capFilter = capIdx !== -1 ? args[capIdx + 1] : null;
|
|
502
|
+
|
|
503
|
+
const cwd = process.cwd();
|
|
504
|
+
const infernoDir = path.join(cwd, "inferno");
|
|
505
|
+
const scanPath = path.join(infernoDir, "scan.json");
|
|
506
|
+
const graphPath = path.join(infernoDir, "graph.json");
|
|
507
|
+
const capsPath = path.join(infernoDir, "capabilities.json");
|
|
508
|
+
|
|
509
|
+
// Load scan data
|
|
510
|
+
const scan = loadJson(scanPath);
|
|
511
|
+
if (!scan) {
|
|
512
|
+
console.error(red("โ inferno/scan.json not found โ run `infernoflow scan` first."));
|
|
513
|
+
process.exit(1);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Load capabilities (for stability info)
|
|
517
|
+
let allCaps = [];
|
|
518
|
+
const rawCaps = loadJson(capsPath);
|
|
519
|
+
if (rawCaps) allCaps = Array.isArray(rawCaps) ? rawCaps : (rawCaps.capabilities || []);
|
|
520
|
+
|
|
521
|
+
// Build graph
|
|
522
|
+
const scanCaps = scan.capabilities || [];
|
|
523
|
+
const graph = buildGraph(scanCaps, allCaps);
|
|
524
|
+
|
|
525
|
+
// Check for breaking changes vs saved graph
|
|
526
|
+
const prevGraph = loadJson(graphPath);
|
|
527
|
+
const breakingWarnings = checkMode || true ? checkBreakingChanges(prevGraph, graph) : [];
|
|
528
|
+
|
|
529
|
+
// Save graph.json
|
|
530
|
+
const graphData = {
|
|
531
|
+
builtAt: new Date().toISOString(),
|
|
532
|
+
capabilities: Object.keys(graph.nodes).length,
|
|
533
|
+
edges: Object.values(graph.edges).reduce((n, arr) => n + arr.length, 0),
|
|
534
|
+
nodes: graph.nodes,
|
|
535
|
+
deps: graph.edges,
|
|
536
|
+
dependents: graph.reverse,
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
if (!jsonMode) {
|
|
540
|
+
fs.writeFileSync(graphPath, JSON.stringify(graphData, null, 2));
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// HTML output
|
|
544
|
+
if (htmlMode) {
|
|
545
|
+
const htmlPath = path.join(infernoDir, "graph.html");
|
|
546
|
+
fs.writeFileSync(htmlPath, buildGraphHtml(graphData));
|
|
547
|
+
console.log(gray(`\n infernoflow graph โ HTML`));
|
|
548
|
+
console.log(gray(" โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"));
|
|
549
|
+
console.log(` ${bold("graph.html")} written โ ${cyan("inferno/graph.html")}`);
|
|
550
|
+
console.log(gray(` ${graphData.capabilities} capabilities ยท ${graphData.edges} dependency edge(s)`));
|
|
551
|
+
console.log();
|
|
552
|
+
console.log(gray(` Open in browser: `) + cyan(`inferno/graph.html`));
|
|
553
|
+
console.log();
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Output
|
|
558
|
+
if (jsonMode) {
|
|
559
|
+
console.log(JSON.stringify(graphData, null, 2));
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (capFilter) {
|
|
564
|
+
printCapGraph(capFilter, graph);
|
|
565
|
+
} else {
|
|
566
|
+
printFullGraph(graph);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Breaking change warnings
|
|
570
|
+
if (breakingWarnings.length > 0) {
|
|
571
|
+
console.log(yellow(" โ Dependency changes detected:"));
|
|
572
|
+
for (const w of breakingWarnings) {
|
|
573
|
+
const icon = w.stability === "frozen" ? red("๐ง") : yellow("ใฐ๏ธ ");
|
|
574
|
+
console.log(` ${icon} ${bold(w.capId)} โ ${w.detail}`);
|
|
575
|
+
}
|
|
576
|
+
console.log();
|
|
577
|
+
if (checkMode) process.exit(1);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (!jsonMode) console.log(gray(` Graph saved โ inferno/graph.json`));
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// โโ exported utility for other commands โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
584
|
+
|
|
585
|
+
export function loadGraph(infernoDir) {
|
|
586
|
+
return loadJson(path.join(infernoDir, "graph.json"));
|
|
587
|
+
}
|