infernoflow 0.37.1 โ†’ 0.37.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/bin/infernoflow.mjs +29 -277
  3. package/dist/lib/adopters/angular.mjs +1 -128
  4. package/dist/lib/adopters/css.mjs +1 -111
  5. package/dist/lib/adopters/react.mjs +1 -104
  6. package/dist/lib/ai/ideDetection.mjs +1 -31
  7. package/dist/lib/ai/localProvider.mjs +1 -88
  8. package/dist/lib/ai/providerRouter.mjs +2 -295
  9. package/dist/lib/commands/adopt.mjs +20 -869
  10. package/dist/lib/commands/adoptWizard.mjs +9 -320
  11. package/dist/lib/commands/agent.mjs +5 -191
  12. package/dist/lib/commands/ai.mjs +2 -407
  13. package/dist/lib/commands/ask.mjs +4 -299
  14. package/dist/lib/commands/audit.mjs +13 -300
  15. package/dist/lib/commands/changelog.mjs +26 -594
  16. package/dist/lib/commands/check.mjs +3 -184
  17. package/dist/lib/commands/ci.mjs +3 -208
  18. package/dist/lib/commands/claudeMd.mjs +30 -135
  19. package/dist/lib/commands/cloud.mjs +10 -773
  20. package/dist/lib/commands/context.mjs +34 -346
  21. package/dist/lib/commands/coverage.mjs +2 -282
  22. package/dist/lib/commands/dashboard.mjs +123 -635
  23. package/dist/lib/commands/demo.mjs +8 -465
  24. package/dist/lib/commands/diff.mjs +5 -274
  25. package/dist/lib/commands/docGate.mjs +2 -81
  26. package/dist/lib/commands/doctor.mjs +3 -321
  27. package/dist/lib/commands/explain.mjs +8 -438
  28. package/dist/lib/commands/export.mjs +10 -239
  29. package/dist/lib/commands/feedback.mjs +12 -216
  30. package/dist/lib/commands/generateSkills.mjs +38 -163
  31. package/dist/lib/commands/graph.mjs +11 -378
  32. package/dist/lib/commands/health.mjs +2 -309
  33. package/dist/lib/commands/impact.mjs +2 -325
  34. package/dist/lib/commands/implement.mjs +7 -103
  35. package/dist/lib/commands/init.mjs +45 -631
  36. package/dist/lib/commands/installCursorHooks.mjs +1 -36
  37. package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -37
  38. package/dist/lib/commands/link.mjs +2 -342
  39. package/dist/lib/commands/log.mjs +18 -248
  40. package/dist/lib/commands/monorepo.mjs +4 -428
  41. package/dist/lib/commands/notify.mjs +4 -258
  42. package/dist/lib/commands/onboard.mjs +4 -296
  43. package/dist/lib/commands/prComment.mjs +2 -361
  44. package/dist/lib/commands/prImpact.mjs +2 -157
  45. package/dist/lib/commands/publish.mjs +15 -316
  46. package/dist/lib/commands/recap.mjs +6 -380
  47. package/dist/lib/commands/report.mjs +28 -272
  48. package/dist/lib/commands/review.mjs +9 -223
  49. package/dist/lib/commands/run.mjs +8 -336
  50. package/dist/lib/commands/scaffold.mjs +54 -419
  51. package/dist/lib/commands/scan.mjs +11 -1118
  52. package/dist/lib/commands/scout.mjs +2 -291
  53. package/dist/lib/commands/setup.mjs +5 -310
  54. package/dist/lib/commands/share.mjs +13 -196
  55. package/dist/lib/commands/snapshot.mjs +3 -383
  56. package/dist/lib/commands/stability.mjs +2 -293
  57. package/dist/lib/commands/stats.mjs +5 -402
  58. package/dist/lib/commands/status.mjs +4 -172
  59. package/dist/lib/commands/suggest.mjs +21 -563
  60. package/dist/lib/commands/switch.mjs +13 -520
  61. package/dist/lib/commands/syncAuto.mjs +1 -96
  62. package/dist/lib/commands/synthesize.mjs +10 -228
  63. package/dist/lib/commands/teamSync.mjs +2 -388
  64. package/dist/lib/commands/test.mjs +6 -363
  65. package/dist/lib/commands/theme.mjs +18 -195
  66. package/dist/lib/commands/uninstall.mjs +13 -406
  67. package/dist/lib/commands/upgrade.mjs +20 -153
  68. package/dist/lib/commands/version.mjs +2 -282
  69. package/dist/lib/commands/vibe.mjs +7 -357
  70. package/dist/lib/commands/watch.mjs +4 -203
  71. package/dist/lib/commands/why.mjs +4 -358
  72. package/dist/lib/cursorHooksInstall.mjs +1 -60
  73. package/dist/lib/draftToolingInstall.mjs +7 -68
  74. package/dist/lib/git/detect-drift.mjs +4 -208
  75. package/dist/lib/learning/adapt.mjs +6 -101
  76. package/dist/lib/learning/observe.mjs +1 -119
  77. package/dist/lib/learning/patternDetector.mjs +1 -298
  78. package/dist/lib/learning/profile.mjs +2 -279
  79. package/dist/lib/learning/skillSynthesizer.mjs +24 -145
  80. package/dist/lib/telemetry.mjs +19 -269
  81. package/dist/lib/templates/index.mjs +1 -131
  82. package/dist/lib/theme/scanner.mjs +4 -343
  83. package/dist/lib/ui/errors.mjs +1 -142
  84. package/dist/lib/ui/output.mjs +6 -95
  85. package/dist/lib/ui/prompts.mjs +6 -147
  86. package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
  87. package/package.json +2 -4
  88. package/scripts/postinstall.js +2 -2
@@ -1,280 +1,9 @@
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>
1
+ import*as E from"node:fs";import*as x from"node:path";import{bold as u,cyan as z,gray as c,green as A,yellow as $,red as k}from"../ui/output.mjs";function S(t){try{return JSON.parse(E.readFileSync(t,"utf8"))}catch{return null}}function B(t){return t?.stability||"experimental"}const w={frozen:"\u{1F9CA}",stable:"\u3030\uFE0F ",experimental:"\u{1F30A}"},C={frozen:k,stable:$,experimental:A};function T(t,i){const l={},a={},d={},n=new Map;for(const e of t){const r=i.find(o=>o.id===e.id)||{};l[e.id]={id:e.id,name:e.name||r.name||r.title||e.id,stability:r.stability||"experimental",functions:e.codeAnalysis?.functions||[],calls:e.codeAnalysis?.calls||[],services:e.codeAnalysis?.services||[],dbCalls:e.codeAnalysis?.dbCalls||[],httpCalls:e.codeAnalysis?.httpCalls||[]},a[e.id]=new Set,d[e.id]=new Set;for(const o of e.codeAnalysis?.functions||[]){const s=o.replace(/\(\)$/,"");n.set(s,e.id),n.set(s.toLowerCase(),e.id)}}for(const[e,r]of Object.entries(l))for(const o of r.calls){const s=o.replace(/\(\)$/,""),g=n.get(s)||n.get(s.toLowerCase());g&&g!==e&&(a[e].add(g),d[g].add(e))}const f={},p={};for(const e of Object.keys(l))f[e]=[...a[e]],p[e]=[...d[e]];return{nodes:l,edges:f,reverse:p}}function O(t){const{nodes:i,edges:l,reverse:a}=t,d=Object.keys(i).sort();console.log(),console.log(u(" Capability Dependency Graph")),console.log(c(" \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\u2500")),console.log();let n=!1;for(const p of d){const e=i[p],r=l[p]||[],o=a[p]||[],s=w[e.stability]||"\u{1F30A}",g=C[e.stability]||A;if(!(r.length===0&&o.length===0)){if(n=!0,console.log(` ${s} ${u(g(p))}`),r.length>0){console.log(c(" calls \u2192"));for(const h of r){const y=i[h],j=w[y?.stability]||"\u{1F30A}";console.log(c(` ${j} ${h}`))}}if(o.length>0){console.log(c(" called by \u2190"));for(const h of o){const y=w[i[h]?.stability]||"\u{1F30A}";console.log(c(` ${y} ${h}`))}}console.log()}}n||(console.log(c(" No inter-capability dependencies detected.")),console.log(c(" Run `infernoflow scan` first to populate call data.")),console.log());const f=Object.values(t.edges).reduce((p,e)=>p+e.length,0);console.log(c(" \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\u2500")),console.log(c(` ${d.length} capabilities \xB7 ${f} dependency edge(s)`)),console.log()}function W(t,i){const{nodes:l,edges:a,reverse:d}=i,n=l[t];n||(console.error(k(`\u2717 Capability "${t}" not found in graph.`)),process.exit(1));const f=w[n.stability]||"\u{1F30A}",p=C[n.stability]||A;console.log(),console.log(u(` ${f} ${p(t)}`)+c(` (${n.stability})`)),n.services?.length&&console.log(c(" external: ")+z(n.services.join(", "))),console.log();const e=a[t]||[],r=d[t]||[];if(e.length>0){console.log(u(" Calls (downstream dependencies):"));for(const o of e){const s=l[o],g=C[s?.stability]||A,h=w[s?.stability]||"\u{1F30A}";console.log(` ${h} ${g(o)}`+c(s?.services?.length?` [${s.services.join(", ")}]`:""))}console.log()}else console.log(c(" No downstream dependencies.")),console.log();if(r.length>0){console.log(u(" Called by (upstream dependents):"));for(const o of r){const s=l[o],g=C[s?.stability]||A,h=w[s?.stability]||"\u{1F30A}";console.log(` ${h} ${g(o)}`)}console.log()}else console.log(c(" No capabilities call this one.")),console.log();if((n.stability==="frozen"||n.stability==="stable")&&r.length>0){const o=n.stability==="frozen"?k:$;console.log(o(` \u26A0 This capability is ${n.stability}. Changing it may break:`));for(const s of r)console.log(o(` \u2022 ${s}`));console.log()}}function H(t,i){const l=[];if(!t||!i)return l;for(const[a,d]of Object.entries(i.nodes)){if(d.stability==="experimental")continue;const n=new Set(t.reverse?.[a]||[]),p=[...new Set(i.reverse[a]||[])].filter(e=>!n.has(e));if(p.length>0&&l.push({type:"new-dependents",capId:a,stability:d.stability,detail:`${p.join(", ")} now depend on this`}),d.stability==="frozen"){const e=new Set(t.edges?.[a]||[]),r=new Set(i.edges[a]||[]),o=[...r].filter(g=>!e.has(g)),s=[...e].filter(g=>!r.has(g));(o.length>0||s.length>0)&&l.push({type:"frozen-internals-changed",capId:a,stability:d.stability,detail:[o.length?`added calls: ${o.join(", ")}`:"",s.length?`removed calls: ${s.join(", ")}`:""].filter(Boolean).join("; ")})}}return l}function M(t){const i=t.nodes||{},l=t.deps||{},a=Object.keys(i),d=[];for(const[f,p]of Object.entries(l))for(const e of p)d.push({from:f,to:e});return`<!DOCTYPE html>
273
2
  <html lang="en">
274
3
  <head>
275
4
  <meta charset="UTF-8">
276
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
277
- <title>infernoflow โ€” Capability Graph</title>
6
+ <title>infernoflow \u2014 Capability Graph</title>
278
7
  <style>
279
8
  *{box-sizing:border-box;margin:0;padding:0}
280
9
  body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0f1117;color:#e2e8f0;height:100vh;display:flex;flex-direction:column}
@@ -302,8 +31,8 @@ export function buildGraphHtml(graphData) {
302
31
  </head>
303
32
  <body>
304
33
  <header>
305
- <span style="font-size:18px">๐Ÿ”ฅ</span>
306
- <h1>infernoflow โ€” Capability Graph</h1>
34
+ <span style="font-size:18px">\u{1F525}</span>
35
+ <h1>infernoflow \u2014 Capability Graph</h1>
307
36
  <span id="project-name"></span>
308
37
  <span id="stats"></span>
309
38
  </header>
@@ -322,7 +51,7 @@ export function buildGraphHtml(graphData) {
322
51
  </div>
323
52
  </div>
324
53
  <script>
325
- const DATA = ${dataJson};
54
+ const DATA = ${JSON.stringify({nodes:a,edges:d})};
326
55
  const NW=120, NH=34;
327
56
 
328
57
  // Separate connected vs isolated nodes
@@ -451,15 +180,15 @@ function render() {
451
180
  svg.appendChild(g);
452
181
  });
453
182
 
454
- document.getElementById("stats").textContent = \`\${DATA.nodes.length} capabilities ยท \${DATA.edges.length} edges\`;
183
+ document.getElementById("stats").textContent = \`\${DATA.nodes.length} capabilities \xB7 \${DATA.edges.length} edges\`;
455
184
  }
456
185
 
457
186
  function showTip(id, evt) {
458
187
  const calls = DATA.edges.filter(e=>e.from===id).map(e=>e.to);
459
188
  const callers = DATA.edges.filter(e=>e.to===id).map(e=>e.from);
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>\`;
189
+ let html = \`<strong>\u{1F30A} \${id}</strong>\`;
190
+ if (calls.length) html += \`<div style="margin-top:6px">calls \u2192<br>\${calls.map(c=>\`<span class="tag calls">\${c}</span>\`).join('')}</div>\`;
191
+ if (callers.length) html += \`<div style="margin-top:6px">\u2190 called by<br>\${callers.map(c=>\`<span class="tag callers">\${c}</span>\`).join('')}</div>\`;
463
192
  if (!calls.length && !callers.length) html += \`<div style="margin-top:6px;color:#475569;font-size:11px">No inter-capability dependencies</div>\`;
464
193
  const tip=document.getElementById("tip");
465
194
  tip.innerHTML=html; tip.style.opacity="1";
@@ -487,101 +216,5 @@ render();
487
216
  window.addEventListener("resize", render);
488
217
  </script>
489
218
  </body>
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
- }
219
+ </html>`}async function F(t){const i=(t||[]).slice(1),l=i.includes("--json"),a=i.includes("--check"),d=i.includes("--html"),n=i.indexOf("--cap"),f=n!==-1?i[n+1]:null,p=process.cwd(),e=x.join(p,"inferno"),r=x.join(e,"scan.json"),o=x.join(e,"graph.json"),s=x.join(e,"capabilities.json"),g=S(r);g||(console.error(k("\u2717 inferno/scan.json not found \u2014 run `infernoflow scan` first.")),process.exit(1));let h=[];const y=S(s);y&&(h=Array.isArray(y)?y:y.capabilities||[]);const j=g.capabilities||[],m=T(j,h),L=S(o),D=H(L,m),v={builtAt:new Date().toISOString(),capabilities:Object.keys(m.nodes).length,edges:Object.values(m.edges).reduce((b,N)=>b+N.length,0),nodes:m.nodes,deps:m.edges,dependents:m.reverse};if(l||E.writeFileSync(o,JSON.stringify(v,null,2)),d){const b=x.join(e,"graph.html");E.writeFileSync(b,M(v)),console.log(c(`
220
+ infernoflow graph \u2192 HTML`)),console.log(c(" \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\u2500\u2500\u2500")),console.log(` ${u("graph.html")} written \u2192 ${z("inferno/graph.html")}`),console.log(c(` ${v.capabilities} capabilities \xB7 ${v.edges} dependency edge(s)`)),console.log(),console.log(c(" Open in browser: ")+z("inferno/graph.html")),console.log();return}if(l){console.log(JSON.stringify(v,null,2));return}if(f?W(f,m):O(m),D.length>0){console.log($(" \u26A0 Dependency changes detected:"));for(const b of D){const N=b.stability==="frozen"?k("\u{1F9CA}"):$("\u3030\uFE0F ");console.log(` ${N} ${u(b.capId)} \u2014 ${b.detail}`)}console.log(),a&&process.exit(1)}l||console.log(c(" Graph saved \u2192 inferno/graph.json"))}function X(t){return S(x.join(t,"graph.json"))}export{M as buildGraphHtml,F as graphCommand,X as loadGraph};