infernoflow 0.32.7 → 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 -320
- 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,337 +1,220 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>infernoflow \u2014 Capability Graph</title>
|
|
7
|
+
<style>
|
|
8
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
9
|
+
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0f1117;color:#e2e8f0;height:100vh;display:flex;flex-direction:column}
|
|
10
|
+
header{padding:14px 20px;border-bottom:1px solid #1e2535;display:flex;align-items:center;gap:12px;background:#0b0e18}
|
|
11
|
+
header h1{font-size:14px;font-weight:700;color:#f1f5f9;letter-spacing:.02em}
|
|
12
|
+
header span{font-size:12px;color:#64748b}
|
|
13
|
+
#stats{margin-left:auto;font-size:11px;color:#475569}
|
|
14
|
+
#canvas{flex:1;position:relative;overflow:hidden}
|
|
15
|
+
svg{width:100%;height:100%}
|
|
16
|
+
.node{cursor:pointer}
|
|
17
|
+
.node rect{rx:8;ry:8;transition:filter .15s}
|
|
18
|
+
.node rect:hover{filter:brightness(1.2)}
|
|
19
|
+
.node text{font-size:11px;font-weight:600;pointer-events:none;text-anchor:middle;dominant-baseline:middle}
|
|
20
|
+
.edge{fill:none;stroke:#334155;stroke-width:1.5;transition:stroke .15s,stroke-width .15s}
|
|
21
|
+
.edge.active{stroke:#f97316;stroke-width:2.5}
|
|
22
|
+
.edge.dim{stroke:#1e2535;stroke-width:1}
|
|
23
|
+
.tooltip{position:absolute;background:#1e2535;border:1px solid #334155;border-radius:10px;padding:12px 16px;font-size:12px;pointer-events:none;opacity:0;transition:opacity .15s;box-shadow:0 8px 24px rgba(0,0,0,.5);max-width:220px;color:#e2e8f0;z-index:10}
|
|
24
|
+
.tooltip strong{display:block;margin-bottom:6px;font-size:13px;color:#f1f5f9}
|
|
25
|
+
.tag{display:inline-block;background:#0f1117;border:1px solid #334155;border-radius:4px;padding:2px 7px;margin:2px 2px 2px 0;font-size:10px;color:#94a3b8}
|
|
26
|
+
.tag.calls{border-color:#6366f1;color:#a5b4fc}
|
|
27
|
+
.tag.callers{border-color:#10b981;color:#6ee7b7}
|
|
28
|
+
#legend{position:absolute;bottom:16px;left:20px;display:flex;gap:14px;font-size:11px;color:#64748b}
|
|
29
|
+
.ldot{width:10px;height:10px;border-radius:3px;display:inline-block;margin-right:5px;vertical-align:middle}
|
|
30
|
+
</style>
|
|
31
|
+
</head>
|
|
32
|
+
<body>
|
|
33
|
+
<header>
|
|
34
|
+
<span style="font-size:18px">\u{1F525}</span>
|
|
35
|
+
<h1>infernoflow \u2014 Capability Graph</h1>
|
|
36
|
+
<span id="project-name"></span>
|
|
37
|
+
<span id="stats"></span>
|
|
38
|
+
</header>
|
|
39
|
+
<div id="canvas">
|
|
40
|
+
<svg id="svg">
|
|
41
|
+
<defs>
|
|
42
|
+
<marker id="arr" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0,0 L0,6 L8,3 z" fill="#334155"/></marker>
|
|
43
|
+
<marker id="arr-hi" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0,0 L0,6 L8,3 z" fill="#f97316"/></marker>
|
|
44
|
+
</defs>
|
|
45
|
+
</svg>
|
|
46
|
+
<div class="tooltip" id="tip"></div>
|
|
47
|
+
<div id="legend">
|
|
48
|
+
<span><span class="ldot" style="background:#6366f180;border:2px solid #6366f1"></span>Has dependencies</span>
|
|
49
|
+
<span><span class="ldot" style="background:#10b98180;border:2px solid #10b981"></span>View layer</span>
|
|
50
|
+
<span><span class="ldot" style="background:#33415580;border:2px solid #475569"></span>Independent</span>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
<script>
|
|
54
|
+
const DATA = ${JSON.stringify({nodes:a,edges:d})};
|
|
55
|
+
const NW=120, NH=34;
|
|
56
|
+
|
|
57
|
+
// Separate connected vs isolated nodes
|
|
58
|
+
const connectedSet = new Set([...DATA.edges.map(e=>e.from),...DATA.edges.map(e=>e.to)]);
|
|
59
|
+
const connected = DATA.nodes.filter(id=>connectedSet.has(id));
|
|
60
|
+
const isolated = DATA.nodes.filter(id=>!connectedSet.has(id));
|
|
61
|
+
|
|
62
|
+
// Layout connected nodes using basic force-inspired placement
|
|
63
|
+
const W = ()=>document.getElementById("canvas").offsetWidth || 900;
|
|
64
|
+
const H = ()=>document.getElementById("canvas").offsetHeight || 600;
|
|
65
|
+
|
|
66
|
+
function layout() {
|
|
67
|
+
const w=W(), h=H();
|
|
68
|
+
const pos = {};
|
|
69
|
+
// Find sources (no incoming edges)
|
|
70
|
+
const hasIncoming = new Set(DATA.edges.map(e=>e.to));
|
|
71
|
+
const sources = connected.filter(id=>!hasIncoming.has(id));
|
|
72
|
+
const layers = [];
|
|
73
|
+
const assigned = new Set();
|
|
74
|
+
|
|
75
|
+
// BFS layering
|
|
76
|
+
let layer = sources.filter(id=>connected.includes(id));
|
|
77
|
+
while (layer.length > 0) {
|
|
78
|
+
layers.push(layer);
|
|
79
|
+
layer.forEach(id=>assigned.add(id));
|
|
80
|
+
const next = [];
|
|
81
|
+
for (const id of layer) {
|
|
82
|
+
for (const {from,to} of DATA.edges) {
|
|
83
|
+
if (from===id && !assigned.has(to)) { next.push(to); assigned.add(to); }
|
|
82
84
|
}
|
|
83
85
|
}
|
|
86
|
+
layer = [...new Set(next)];
|
|
84
87
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
// Any connected nodes not yet assigned
|
|
89
|
+
connected.filter(id=>!assigned.has(id)).forEach(id=>layers[layers.length-1||0]?.push(id)||layers.push([id]));
|
|
90
|
+
|
|
91
|
+
const layerH = Math.min(140, (h-180) / Math.max(layers.length,1));
|
|
92
|
+
layers.forEach((layer,li) => {
|
|
93
|
+
const y = 60 + li*layerH;
|
|
94
|
+
const totalW = layer.length*(NW+24)-24;
|
|
95
|
+
const startX = (w-totalW)/2;
|
|
96
|
+
layer.forEach((id,i)=>{
|
|
97
|
+
pos[id] = { x: startX+i*(NW+24), y };
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Isolated nodes in a grid at bottom
|
|
102
|
+
const cols = Math.max(1, Math.floor((w-40)/(NW+8)));
|
|
103
|
+
isolated.forEach((id,i)=>{
|
|
104
|
+
const col=i%cols, row=Math.floor(i/cols);
|
|
105
|
+
pos[id]={ x:20+col*(NW+8), y:h-120+row*40 };
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return pos;
|
|
95
109
|
}
|
|
96
110
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
111
|
+
function render() {
|
|
112
|
+
const svg = document.getElementById("svg");
|
|
113
|
+
svg.innerHTML = \`<defs>
|
|
114
|
+
<marker id="arr" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0,0 L0,6 L8,3 z" fill="#334155"/></marker>
|
|
115
|
+
<marker id="arr-hi" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0,0 L0,6 L8,3 z" fill="#f97316"/></marker>
|
|
116
|
+
</defs>\`;
|
|
102
117
|
|
|
103
|
-
|
|
104
|
-
console.log(bold(" Capability Dependency Graph"));
|
|
105
|
-
console.log(gray(" ────────────────────────────────────────────────────────────"));
|
|
106
|
-
console.log();
|
|
118
|
+
const pos = layout();
|
|
107
119
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const callers = reverse[id] || [];
|
|
113
|
-
const icon = LEVEL_ICON[node.stability] || "🌊";
|
|
114
|
-
const color = LEVEL_COLOR[node.stability] || green;
|
|
115
|
-
|
|
116
|
-
if (deps.length === 0 && callers.length === 0) continue;
|
|
117
|
-
hasDeps = true;
|
|
118
|
-
|
|
119
|
-
console.log(` ${icon} ${bold(color(id))}`);
|
|
120
|
-
|
|
121
|
-
if (deps.length > 0) {
|
|
122
|
-
console.log(gray(" calls →"));
|
|
123
|
-
for (const dep of deps) {
|
|
124
|
-
const depNode = nodes[dep];
|
|
125
|
-
const depIcon = LEVEL_ICON[depNode?.stability] || "🌊";
|
|
126
|
-
console.log(gray(` ${depIcon} ${dep}`));
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
if (callers.length > 0) {
|
|
130
|
-
console.log(gray(" called by ←"));
|
|
131
|
-
for (const caller of callers) {
|
|
132
|
-
const callerIcon = LEVEL_ICON[nodes[caller]?.stability] || "🌊";
|
|
133
|
-
console.log(gray(` ${callerIcon} ${caller}`));
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
console.log();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (!hasDeps) {
|
|
140
|
-
console.log(gray(" No inter-capability dependencies detected."));
|
|
141
|
-
console.log(gray(" Run `infernoflow scan` first to populate call data."));
|
|
142
|
-
console.log();
|
|
120
|
+
function nodeColor(id) {
|
|
121
|
+
if (!connectedSet.has(id)) return {fill:"#33415540",stroke:"#475569",text:"#94a3b8"};
|
|
122
|
+
if (id.startsWith("View")) return {fill:"#10b98120",stroke:"#10b981",text:"#6ee7b7"};
|
|
123
|
+
return {fill:"#6366f120",stroke:"#6366f1",text:"#a5b4fc"};
|
|
143
124
|
}
|
|
144
125
|
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
126
|
+
// Draw edges first
|
|
127
|
+
DATA.edges.forEach(({from,to})=>{
|
|
128
|
+
const f=pos[from], t=pos[to];
|
|
129
|
+
if (!f||!t) return;
|
|
130
|
+
const fx=f.x+NW/2, fy=f.y+NH/2, tx=t.x+NW/2, ty=t.y+NH/2;
|
|
131
|
+
const dx=tx-fx, dy=ty-fy, len=Math.sqrt(dx*dx+dy*dy)||1;
|
|
132
|
+
const sx=fx+(dx/len)*(NW/2+4), sy=fy+(dy/len)*(NH/2+4);
|
|
133
|
+
const ex=tx-(dx/len)*(NW/2+8), ey=ty-(dy/len)*(NH/2+8);
|
|
134
|
+
const mx=(sx+ex)/2-dy*0.18, my=(sy+ey)/2+dx*0.18;
|
|
135
|
+
const p=document.createElementNS("http://www.w3.org/2000/svg","path");
|
|
136
|
+
p.setAttribute("d",\`M\${sx},\${sy} Q\${mx},\${my} \${ex},\${ey}\`);
|
|
137
|
+
p.setAttribute("class","edge");
|
|
138
|
+
p.setAttribute("marker-end","url(#arr)");
|
|
139
|
+
p.dataset.from=from; p.dataset.to=to;
|
|
140
|
+
svg.appendChild(p);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Draw nodes
|
|
144
|
+
DATA.nodes.forEach(id=>{
|
|
145
|
+
const p=pos[id]; if(!p) return;
|
|
146
|
+
const c=nodeColor(id);
|
|
147
|
+
const isSmall=!connectedSet.has(id);
|
|
148
|
+
const w=isSmall?NW:NW, h=isSmall?26:NH;
|
|
149
|
+
|
|
150
|
+
const g=document.createElementNS("http://www.w3.org/2000/svg","g");
|
|
151
|
+
g.setAttribute("class","node");
|
|
152
|
+
g.setAttribute("transform",\`translate(\${p.x},\${p.y})\`);
|
|
153
|
+
|
|
154
|
+
const rect=document.createElementNS("http://www.w3.org/2000/svg","rect");
|
|
155
|
+
rect.setAttribute("width",w); rect.setAttribute("height",h);
|
|
156
|
+
rect.setAttribute("rx",isSmall?5:8);
|
|
157
|
+
rect.setAttribute("fill",c.fill);
|
|
158
|
+
rect.setAttribute("stroke",c.stroke);
|
|
159
|
+
rect.setAttribute("stroke-width",isSmall?"1.5":"2");
|
|
160
|
+
|
|
161
|
+
const txt=document.createElementNS("http://www.w3.org/2000/svg","text");
|
|
162
|
+
txt.setAttribute("x",w/2); txt.setAttribute("y",h/2);
|
|
163
|
+
txt.setAttribute("fill",c.text);
|
|
164
|
+
txt.setAttribute("font-size",isSmall?"9":"11");
|
|
165
|
+
txt.setAttribute("font-weight","600");
|
|
166
|
+
txt.setAttribute("text-anchor","middle");
|
|
167
|
+
txt.setAttribute("dominant-baseline","middle");
|
|
168
|
+
// Shorten long names for display
|
|
169
|
+
let label=id;
|
|
170
|
+
if(label.length>15) {
|
|
171
|
+
label=label.replace(/([A-Z])/g,(m,c,o)=>o>0?' '+m:m);
|
|
172
|
+
if(label.length>18) label=label.replace('Complete Pending On','CPO ').replace('Advance Repeat','AdvRpt ');
|
|
173
|
+
if(label.length>18) label=id.replace(/([A-Z][a-z]+)/g,w=>w.slice(0,3)).slice(0,16);
|
|
178
174
|
}
|
|
179
|
-
|
|
180
|
-
} else {
|
|
181
|
-
console.log(gray(" No downstream dependencies."));
|
|
182
|
-
console.log();
|
|
183
|
-
}
|
|
175
|
+
txt.textContent=label;
|
|
184
176
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const cIcon = LEVEL_ICON[c?.stability] || "🌊";
|
|
191
|
-
console.log(` ${cIcon} ${cColor(caller)}`);
|
|
192
|
-
}
|
|
193
|
-
console.log();
|
|
194
|
-
} else {
|
|
195
|
-
console.log(gray(" No capabilities call this one."));
|
|
196
|
-
console.log();
|
|
197
|
-
}
|
|
177
|
+
g.appendChild(rect); g.appendChild(txt);
|
|
178
|
+
g.addEventListener("mouseenter",e=>showTip(id,e));
|
|
179
|
+
g.addEventListener("mouseleave",hideTip);
|
|
180
|
+
svg.appendChild(g);
|
|
181
|
+
});
|
|
198
182
|
|
|
199
|
-
|
|
200
|
-
if ((node.stability === "frozen" || node.stability === "stable") && callers.length > 0) {
|
|
201
|
-
const color2 = node.stability === "frozen" ? red : yellow;
|
|
202
|
-
console.log(color2(` ⚠ This capability is ${node.stability}. Changing it may break:`));
|
|
203
|
-
for (const caller of callers) console.log(color2(` • ${caller}`));
|
|
204
|
-
console.log();
|
|
205
|
-
}
|
|
183
|
+
document.getElementById("stats").textContent = \`\${DATA.nodes.length} capabilities \xB7 \${DATA.edges.length} edges\`;
|
|
206
184
|
}
|
|
207
185
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
type: "new-dependents",
|
|
229
|
-
capId,
|
|
230
|
-
stability: node.stability,
|
|
231
|
-
detail: `${addedCallers.join(", ")} now depend on this`,
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (node.stability === "frozen") {
|
|
236
|
-
const prevDeps = new Set(prevGraph.edges?.[capId] || []);
|
|
237
|
-
const newDeps = new Set(newGraph.edges[capId] || []);
|
|
238
|
-
const addedDeps = [...newDeps].filter(d => !prevDeps.has(d));
|
|
239
|
-
const removedDeps = [...prevDeps].filter(d => !newDeps.has(d));
|
|
240
|
-
|
|
241
|
-
if (addedDeps.length > 0 || removedDeps.length > 0) {
|
|
242
|
-
warnings.push({
|
|
243
|
-
type: "frozen-internals-changed",
|
|
244
|
-
capId,
|
|
245
|
-
stability: node.stability,
|
|
246
|
-
detail: [
|
|
247
|
-
addedDeps.length ? `added calls: ${addedDeps.join(", ")}` : "",
|
|
248
|
-
removedDeps.length ? `removed calls: ${removedDeps.join(", ")}` : "",
|
|
249
|
-
].filter(Boolean).join("; "),
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return warnings;
|
|
186
|
+
function showTip(id, evt) {
|
|
187
|
+
const calls = DATA.edges.filter(e=>e.from===id).map(e=>e.to);
|
|
188
|
+
const callers = DATA.edges.filter(e=>e.to===id).map(e=>e.from);
|
|
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>\`;
|
|
192
|
+
if (!calls.length && !callers.length) html += \`<div style="margin-top:6px;color:#475569;font-size:11px">No inter-capability dependencies</div>\`;
|
|
193
|
+
const tip=document.getElementById("tip");
|
|
194
|
+
tip.innerHTML=html; tip.style.opacity="1";
|
|
195
|
+
const canvas=document.getElementById("canvas").getBoundingClientRect();
|
|
196
|
+
let tx=evt.clientX-canvas.left+16, ty=evt.clientY-canvas.top-60;
|
|
197
|
+
if(tx+220>canvas.width) tx=evt.clientX-canvas.left-236;
|
|
198
|
+
tip.style.left=Math.max(0,tx)+"px"; tip.style.top=Math.max(0,ty)+"px";
|
|
199
|
+
|
|
200
|
+
// Highlight edges
|
|
201
|
+
document.querySelectorAll(".edge").forEach(p=>{
|
|
202
|
+
const active=p.dataset.from===id||p.dataset.to===id;
|
|
203
|
+
p.setAttribute("class",active?"edge active":"edge dim");
|
|
204
|
+
p.setAttribute("marker-end",active?"url(#arr-hi)":"url(#arr)");
|
|
205
|
+
});
|
|
256
206
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const checkMode = args.includes("--check");
|
|
264
|
-
const capIdx = args.indexOf("--cap");
|
|
265
|
-
const capFilter = capIdx !== -1 ? args[capIdx + 1] : null;
|
|
266
|
-
|
|
267
|
-
const cwd = process.cwd();
|
|
268
|
-
const infernoDir = path.join(cwd, "inferno");
|
|
269
|
-
const scanPath = path.join(infernoDir, "scan.json");
|
|
270
|
-
const graphPath = path.join(infernoDir, "graph.json");
|
|
271
|
-
const capsPath = path.join(infernoDir, "capabilities.json");
|
|
272
|
-
|
|
273
|
-
// Load scan data
|
|
274
|
-
const scan = loadJson(scanPath);
|
|
275
|
-
if (!scan) {
|
|
276
|
-
console.error(red("✗ inferno/scan.json not found — run `infernoflow scan` first."));
|
|
277
|
-
process.exit(1);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Load capabilities (for stability info)
|
|
281
|
-
let allCaps = [];
|
|
282
|
-
const rawCaps = loadJson(capsPath);
|
|
283
|
-
if (rawCaps) allCaps = Array.isArray(rawCaps) ? rawCaps : (rawCaps.capabilities || []);
|
|
284
|
-
|
|
285
|
-
// Build graph
|
|
286
|
-
const scanCaps = scan.capabilities || [];
|
|
287
|
-
const graph = buildGraph(scanCaps, allCaps);
|
|
288
|
-
|
|
289
|
-
// Check for breaking changes vs saved graph
|
|
290
|
-
const prevGraph = loadJson(graphPath);
|
|
291
|
-
const breakingWarnings = checkMode || true ? checkBreakingChanges(prevGraph, graph) : [];
|
|
292
|
-
|
|
293
|
-
// Save graph.json
|
|
294
|
-
const graphData = {
|
|
295
|
-
builtAt: new Date().toISOString(),
|
|
296
|
-
capabilities: Object.keys(graph.nodes).length,
|
|
297
|
-
edges: Object.values(graph.edges).reduce((n, arr) => n + arr.length, 0),
|
|
298
|
-
nodes: graph.nodes,
|
|
299
|
-
deps: graph.edges,
|
|
300
|
-
dependents: graph.reverse,
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
if (!jsonMode) {
|
|
304
|
-
fs.writeFileSync(graphPath, JSON.stringify(graphData, null, 2));
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Output
|
|
308
|
-
if (jsonMode) {
|
|
309
|
-
console.log(JSON.stringify(graphData, null, 2));
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (capFilter) {
|
|
314
|
-
printCapGraph(capFilter, graph);
|
|
315
|
-
} else {
|
|
316
|
-
printFullGraph(graph);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Breaking change warnings
|
|
320
|
-
if (breakingWarnings.length > 0) {
|
|
321
|
-
console.log(yellow(" ⚠ Dependency changes detected:"));
|
|
322
|
-
for (const w of breakingWarnings) {
|
|
323
|
-
const icon = w.stability === "frozen" ? red("🧊") : yellow("〰️ ");
|
|
324
|
-
console.log(` ${icon} ${bold(w.capId)} — ${w.detail}`);
|
|
325
|
-
}
|
|
326
|
-
console.log();
|
|
327
|
-
if (checkMode) process.exit(1);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (!jsonMode) console.log(gray(` Graph saved → inferno/graph.json`));
|
|
207
|
+
function hideTip(){
|
|
208
|
+
document.getElementById("tip").style.opacity="0";
|
|
209
|
+
document.querySelectorAll(".edge").forEach(p=>{
|
|
210
|
+
p.setAttribute("class","edge");
|
|
211
|
+
p.setAttribute("marker-end","url(#arr)");
|
|
212
|
+
});
|
|
331
213
|
}
|
|
332
214
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
215
|
+
render();
|
|
216
|
+
window.addEventListener("resize", render);
|
|
217
|
+
</script>
|
|
218
|
+
</body>
|
|
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};
|