@plumpslabs/kuma 2.3.19 → 2.3.22
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 +18 -0
- package/dist/index.js +10221 -332
- package/package.json +2 -2
- package/packages/ide/studio/dist/index.js +37 -5
- package/packages/ide/studio/public/index.html +284 -63
- package/dist/agentDetector-YOWQVJFR.js +0 -186
- package/dist/chunk-3BRBJZ7P.js +0 -1055
- package/dist/chunk-3OHYYXYN.js +0 -71
- package/dist/chunk-ABKE45T4.js +0 -1264
- package/dist/chunk-E2KFPEBT.js +0 -183
- package/dist/chunk-FKRSI5U5.js +0 -282
- package/dist/chunk-GFLSAXAH.js +0 -155
- package/dist/chunk-L7F67KUP.js +0 -172
- package/dist/chunk-LVKOGXLC.js +0 -658
- package/dist/chunk-PRUTTZBS.js +0 -1113
- package/dist/contextDigest-QB5XHPXE.js +0 -177
- package/dist/domainRules-QLPAQASB.js +0 -20
- package/dist/init-PL4XL662.js +0 -15
- package/dist/kumaAstValidator-CNM7FHYA.js +0 -150
- package/dist/kumaCheckpoint-J2LDQMEO.js +0 -207
- package/dist/kumaCodeScanner-J6B2EHGI.js +0 -563
- package/dist/kumaContractEngine-KX27T4N7.js +0 -305
- package/dist/kumaDb-4XZ5S2LH.js +0 -65
- package/dist/kumaDriftDetector-TOORILSZ.js +0 -237
- package/dist/kumaGotchas-XRGFFBTA.js +0 -151
- package/dist/kumaGraph-UMXZNGYF.js +0 -44
- package/dist/kumaMemory-FBJMV77G.js +0 -16
- package/dist/kumaMiner-XJETL7TL.js +0 -176
- package/dist/kumaPolicyEngine-2QDJDLM7.js +0 -311
- package/dist/kumaProgressiveContext-MWEDRXOH.js +0 -231
- package/dist/kumaSearch-PV4QTKE7.js +0 -321
- package/dist/kumaTrajectory-7NOAVNG2.js +0 -460
- package/dist/kumaVerifier-6YEGC77M.js +0 -265
- package/dist/kumaVisualize-264OEBGJ.js +0 -264
- package/dist/pathValidator-V4DC6U6Z.js +0 -22
- package/dist/safetyAudit-O45SPNTS.js +0 -12
- package/dist/safetyScore-TMMRD2MV.js +0 -333
- package/dist/sessionMemory-YPKVIOMV.js +0 -6
- package/dist/skillGenerator-PWEJKZNX.js +0 -304
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumpslabs/kuma",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.22",
|
|
4
4
|
"description": "Safety-first context & orchestration engine for AI coding agents. MCP server with mandatory research pipeline, knowledge graph, impact analysis, decision memory, and safety guard — works with any MCP client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"homepage": "https://github.com/plumpslabs/kuma#readme",
|
|
64
64
|
"license": "MIT",
|
|
65
65
|
"scripts": {
|
|
66
|
-
"build": "tsup src/index.ts --format esm --clean --out-dir dist",
|
|
66
|
+
"build": "tsup src/index.ts --format esm --no-splitting --clean --out-dir dist",
|
|
67
67
|
"build:studio": "cd packages/ide/studio && tsup src/index.ts --format esm --clean --out-dir dist",
|
|
68
68
|
"build:all": "pnpm run build && pnpm run build:studio",
|
|
69
69
|
"dev": "tsup src/index.ts --format esm --watch --out-dir dist",
|
|
@@ -38,7 +38,17 @@ function query(sql) {
|
|
|
38
38
|
function queryJson(sql) {
|
|
39
39
|
const raw = query(sql);
|
|
40
40
|
if (!raw) return [];
|
|
41
|
-
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(raw);
|
|
43
|
+
} catch {
|
|
44
|
+
try {
|
|
45
|
+
const sanitized = raw.replace(/[\r\n]+/g, " ");
|
|
46
|
+
return JSON.parse(sanitized);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.warn("[Studio DB] queryJson parse failed:", e);
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
42
52
|
}
|
|
43
53
|
function getDashboardData() {
|
|
44
54
|
return {
|
|
@@ -53,31 +63,38 @@ function getDashboardData() {
|
|
|
53
63
|
)
|
|
54
64
|
`),
|
|
55
65
|
nodes: queryJson(
|
|
56
|
-
`SELECT json_group_array(json_object('id',id,'name',name,'type',type,'file_path',file_path)) FROM nodes ORDER BY
|
|
66
|
+
`SELECT json_group_array(json_object('id',id,'name',name,'type',type,'file_path',file_path)) FROM nodes ORDER BY updated_at DESC`
|
|
57
67
|
),
|
|
58
68
|
edges: queryJson(
|
|
59
69
|
`SELECT json_group_array(json_object('source',source_id,'target',target_id,'relation',type)) FROM edges`
|
|
60
70
|
),
|
|
61
71
|
gotchas: queryJson(
|
|
62
|
-
`SELECT json_group_array(json_object('id',id,'file_path',file_path,'description',description,'severity',severity,'workaround',workaround)) FROM known_gotchas ORDER BY severity DESC`
|
|
72
|
+
`SELECT json_group_array(json_object('id',id,'file_path',file_path,'description',REPLACE(REPLACE(description,char(10),' '),char(13),''),'severity',severity,'workaround',REPLACE(REPLACE(COALESCE(workaround,''),char(10),' '),char(13),''))) FROM known_gotchas ORDER BY severity DESC`
|
|
63
73
|
),
|
|
64
74
|
trajectories: queryJson(
|
|
65
|
-
`SELECT json_group_array(json_object('id',id,'goal',goal,'total_duration_ms',total_duration_ms,'success_rate',success_rate,'created_at',created_at)) FROM trajectories ORDER BY created_at DESC LIMIT 20`
|
|
75
|
+
`SELECT json_group_array(json_object('id',id,'goal',REPLACE(REPLACE(goal,char(10),' '),char(13),''),'total_duration_ms',total_duration_ms,'success_rate',success_rate,'created_at',created_at)) FROM trajectories ORDER BY created_at DESC LIMIT 20`
|
|
66
76
|
),
|
|
67
77
|
health: queryJson(
|
|
68
|
-
`SELECT json_group_array(json_object('score',score,'summary',summary,'risk_level',risk_level,'created_at',created_at)) FROM health_snapshots ORDER BY created_at DESC LIMIT 10`
|
|
78
|
+
`SELECT json_group_array(json_object('score',score,'summary',REPLACE(REPLACE(COALESCE(summary,''),char(10),' '),char(13),''),'risk_level',risk_level,'created_at',created_at)) FROM health_snapshots ORDER BY created_at DESC LIMIT 10`
|
|
69
79
|
)
|
|
70
80
|
};
|
|
71
81
|
}
|
|
72
82
|
|
|
73
83
|
// src/api.ts
|
|
84
|
+
import fs from "fs";
|
|
74
85
|
var api = new Hono();
|
|
75
86
|
api.use("/*", cors());
|
|
76
87
|
api.get("/status", (c) => {
|
|
77
88
|
const dbPath = findKumaDb();
|
|
89
|
+
let dbSize = "0 KB";
|
|
90
|
+
if (dbPath && fs.existsSync(dbPath)) {
|
|
91
|
+
const bytes = fs.statSync(dbPath).size;
|
|
92
|
+
dbSize = bytes > 1024 * 1024 ? (bytes / (1024 * 1024)).toFixed(2) + " MB" : (bytes / 1024).toFixed(1) + " KB";
|
|
93
|
+
}
|
|
78
94
|
return c.json({
|
|
79
95
|
ok: !!dbPath,
|
|
80
96
|
dbPath,
|
|
97
|
+
dbSize,
|
|
81
98
|
project: dbPath ? dbPath.replace("/.kuma/kuma.db", "").split("/").pop() : null
|
|
82
99
|
});
|
|
83
100
|
});
|
|
@@ -89,6 +106,21 @@ api.get("/dashboard", (c) => {
|
|
|
89
106
|
return c.json({ error: e.message }, 500);
|
|
90
107
|
}
|
|
91
108
|
});
|
|
109
|
+
api.post("/reset-db", (c) => {
|
|
110
|
+
try {
|
|
111
|
+
const dbPath = findKumaDb();
|
|
112
|
+
if (dbPath && fs.existsSync(dbPath)) {
|
|
113
|
+
fs.unlinkSync(dbPath);
|
|
114
|
+
}
|
|
115
|
+
return c.json({ ok: true, message: "Kuma DB reset successfully" });
|
|
116
|
+
} catch (e) {
|
|
117
|
+
return c.json({ error: e.message }, 500);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
api.post("/stop-server", (c) => {
|
|
121
|
+
setTimeout(() => process.exit(0), 500);
|
|
122
|
+
return c.json({ ok: true, message: "Kuma Studio server shutting down" });
|
|
123
|
+
});
|
|
92
124
|
var api_default = api;
|
|
93
125
|
|
|
94
126
|
// src/index.ts
|
|
@@ -71,19 +71,20 @@ header .status .dot.off { background: var(--red); }
|
|
|
71
71
|
.health-item .meta { font-size: 11px; color: var(--muted); }
|
|
72
72
|
.empty { text-align: center; color: var(--muted); padding: 40px 20px; font-size: 14px; }
|
|
73
73
|
#legend {
|
|
74
|
-
position: absolute; bottom: 16px; left: 16px; background: rgba(22,33,62,0.
|
|
74
|
+
position: absolute; bottom: 16px; left: 16px; background: rgba(22,33,62,0.95);
|
|
75
75
|
border: 1px solid var(--border); border-radius: 8px; padding: 10px 14px;
|
|
76
76
|
font-size: 11px; z-index: 10; max-height: 200px; overflow-y: auto;
|
|
77
|
-
backdrop-filter: blur(8px);
|
|
77
|
+
backdrop-filter: blur(8px); color: #f8fafc;
|
|
78
78
|
}
|
|
79
|
-
#legend .title { font-weight: 600; margin-bottom: 6px; color:
|
|
80
|
-
.legend-item { display: flex; align-items: center; gap: 6px; margin-bottom: 3px; }
|
|
79
|
+
#legend .title { font-weight: 600; margin-bottom: 6px; color: #94a3b8; }
|
|
80
|
+
.legend-item { display: flex; align-items: center; gap: 6px; margin-bottom: 3px; color: #f1f5f9; }
|
|
81
81
|
.legend-dot { width: 10px; height: 10px; border-radius: 50%; }
|
|
82
82
|
#graph-tooltip {
|
|
83
|
-
position: absolute; display: none; background: rgba(
|
|
83
|
+
position: absolute; display: none; background: rgba(15, 23, 42, 0.96);
|
|
84
84
|
border: 1px solid var(--border); border-radius: 8px; padding: 10px 14px;
|
|
85
|
-
font-size: 12px; z-index: 20; max-width:
|
|
86
|
-
|
|
85
|
+
font-size: 12px; z-index: 20; max-width: 420px; word-break: break-all;
|
|
86
|
+
overflow-wrap: anywhere; white-space: normal; color: #f8fafc;
|
|
87
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.6); backdrop-filter: blur(12px);
|
|
87
88
|
pointer-events: none;
|
|
88
89
|
}
|
|
89
90
|
#search-box {
|
|
@@ -108,6 +109,12 @@ header .status .dot.off { background: var(--red); }
|
|
|
108
109
|
transition: background 0.2s;
|
|
109
110
|
}
|
|
110
111
|
#graph-controls button:hover { background: var(--accent); border-color: var(--accent); }
|
|
112
|
+
#graph-controls button.active {
|
|
113
|
+
background: var(--accent) !important;
|
|
114
|
+
border-color: var(--accent) !important;
|
|
115
|
+
color: #ffffff !important;
|
|
116
|
+
box-shadow: 0 0 10px rgba(124, 58, 237, 0.4);
|
|
117
|
+
}
|
|
111
118
|
#loading {
|
|
112
119
|
position: fixed; inset: 0; background: var(--bg); display: flex;
|
|
113
120
|
align-items: center; justify-content: center; z-index: 100;
|
|
@@ -125,7 +132,11 @@ header .status .dot.off { background: var(--red); }
|
|
|
125
132
|
<header>
|
|
126
133
|
<h1>♢ Kuma Studio</h1>
|
|
127
134
|
<div class="status"><span class="dot" id="status-dot"></span><span id="status-text">connecting...</span></div>
|
|
128
|
-
<
|
|
135
|
+
<div style="margin-left:auto;display:flex;gap:8px;-webkit-app-region:no-drag">
|
|
136
|
+
<button onclick="refetch()" id="refetch-btn" style="background:rgba(124,58,237,0.15);border:1px solid rgba(124,58,237,0.3);border-radius:6px;color:var(--accent);padding:5px 12px;font-size:11px;cursor:pointer;transition:all 0.2s">↻ Refetch</button>
|
|
137
|
+
<button onclick="resetKumaDb()" style="background:rgba(239,68,68,0.15);border:1px solid rgba(239,68,68,0.3);border-radius:6px;color:var(--red);padding:5px 12px;font-size:11px;cursor:pointer;transition:all 0.2s">🗑 Reset DB</button>
|
|
138
|
+
<button onclick="stopKumaStudio()" style="background:rgba(148,163,184,0.15);border:1px solid rgba(148,163,184,0.3);border-radius:6px;color:#94a3b8;padding:5px 12px;font-size:11px;cursor:pointer;transition:all 0.2s">■ Stop Server</button>
|
|
139
|
+
</div>
|
|
129
140
|
</header>
|
|
130
141
|
<div class="stats-bar" id="stats-bar"></div>
|
|
131
142
|
<div id="main">
|
|
@@ -133,9 +144,11 @@ header .status .dot.off { background: var(--red); }
|
|
|
133
144
|
<div id="graph-container"></div>
|
|
134
145
|
<div id="search-box"><input id="node-search" type="text" placeholder="Search nodes..." /></div>
|
|
135
146
|
<div id="graph-controls">
|
|
136
|
-
<button onclick="
|
|
137
|
-
<button onclick="if(network)network.
|
|
138
|
-
<button onclick="
|
|
147
|
+
<button onclick="resetGraphFocus()" title="Reset node opacity and search">Reset Focus</button>
|
|
148
|
+
<button onclick="if(network)network.fit()" title="Fit graph to viewport">Fit</button>
|
|
149
|
+
<button onclick="exportGraphPNG()" title="Export graph as PNG image">Export PNG</button>
|
|
150
|
+
<button id="btn-unfreeze-physics" class="active" onclick="restartPhysics()" title="Enable physics simulation (nodes will move freely)">Unfreeze Physics</button>
|
|
151
|
+
<button id="btn-freeze-physics" onclick="stopPhysics()" title="Freeze physics simulation (lock nodes in place)">Freeze Physics</button>
|
|
139
152
|
</div>
|
|
140
153
|
<div id="graph-tooltip"></div>
|
|
141
154
|
<div id="legend"></div>
|
|
@@ -152,7 +165,74 @@ header .status .dot.off { background: var(--red); }
|
|
|
152
165
|
|
|
153
166
|
<script>
|
|
154
167
|
const API = "/api";
|
|
155
|
-
let network = null, nodesData = null, allNodes = null, allEdges = null;
|
|
168
|
+
let network = null, nodesData = null, allNodes = null, allEdges = null, currentDbSize = "0 KB";
|
|
169
|
+
|
|
170
|
+
async function resetKumaDb() {
|
|
171
|
+
var confirmation = prompt("⚠️ CRITICAL ACTION: TYPE 'DELETE' TO CONFIRM RESETTING KUMA DB\nThis will permanently wipe all recorded knowledge nodes, edges, gotchas, and trajectories in .kuma/kuma.db:");
|
|
172
|
+
if (confirmation !== "DELETE") {
|
|
173
|
+
if (confirmation !== null) alert("Reset cancelled. Confirmation text did not match 'DELETE'.");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
var res = await fetch(API + "/reset-db", { method: "POST" }).then(function(r){return r.json()});
|
|
178
|
+
if (res.ok) {
|
|
179
|
+
alert("Kuma DB reset successfully.");
|
|
180
|
+
refetch();
|
|
181
|
+
}
|
|
182
|
+
} catch (e) {
|
|
183
|
+
alert("Reset failed: " + e.message);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function stopKumaStudio() {
|
|
188
|
+
if (!confirm("Stop Kuma Studio background server? You can restart it anytime with 'agy kuma studio'.")) return;
|
|
189
|
+
try {
|
|
190
|
+
await fetch(API + "/stop-server", { method: "POST" });
|
|
191
|
+
document.body.innerHTML = '<div style="display:flex;height:100vh;align-items:center;justify-content:center;color:#94a3b8;font-size:16px;">Kuma Studio Server Stopped. You can close this tab.</div>';
|
|
192
|
+
} catch (e) {
|
|
193
|
+
alert("Stop failed: " + e.message);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function resetGraphFocus() {
|
|
198
|
+
if (!network) return;
|
|
199
|
+
network.body.data.nodes.forEach(function(n) {
|
|
200
|
+
network.body.data.nodes.update({
|
|
201
|
+
id: n.id,
|
|
202
|
+
opacity: 1.0,
|
|
203
|
+
font: { color: '#ffffff', bold: true }
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
document.getElementById("graph-tooltip").style.display = 'none';
|
|
207
|
+
var searchInput = document.getElementById("node-search");
|
|
208
|
+
if (searchInput) searchInput.value = "";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function exportGraphPNG() {
|
|
212
|
+
if (!network) return;
|
|
213
|
+
var canvas = document.querySelector("#graph-container canvas");
|
|
214
|
+
if (!canvas) return;
|
|
215
|
+
var link = document.createElement("a");
|
|
216
|
+
link.download = "kuma-knowledge-graph.png";
|
|
217
|
+
link.href = canvas.toDataURL("image/png");
|
|
218
|
+
link.click();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function restartPhysics() {
|
|
222
|
+
if (!network) return;
|
|
223
|
+
network.setOptions({ physics: { enabled: true } });
|
|
224
|
+
network.startSimulation();
|
|
225
|
+
document.getElementById('btn-unfreeze-physics').classList.add('active');
|
|
226
|
+
document.getElementById('btn-freeze-physics').classList.remove('active');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function stopPhysics() {
|
|
230
|
+
if (!network) return;
|
|
231
|
+
network.stopSimulation();
|
|
232
|
+
network.setOptions({ physics: { enabled: false } });
|
|
233
|
+
document.getElementById('btn-freeze-physics').classList.add('active');
|
|
234
|
+
document.getElementById('btn-unfreeze-physics').classList.remove('active');
|
|
235
|
+
}
|
|
156
236
|
|
|
157
237
|
async function init() {
|
|
158
238
|
var status = await fetch(API+"/status").then(function(r){return r.json()});
|
|
@@ -161,6 +241,7 @@ async function init() {
|
|
|
161
241
|
if (status.ok) {
|
|
162
242
|
dot.className = "dot on";
|
|
163
243
|
txt.textContent = status.project || "connected";
|
|
244
|
+
currentDbSize = status.dbSize || "0 KB";
|
|
164
245
|
} else {
|
|
165
246
|
dot.className = "dot off";
|
|
166
247
|
txt.textContent = "no kuma.db found";
|
|
@@ -191,6 +272,7 @@ function renderStats(data) {
|
|
|
191
272
|
'<div class="stat"><span class="num">'+(s.edge_count||0)+'</span><span class="lbl">edges</span></div>'+
|
|
192
273
|
'<div class="stat"><span class="num">'+(s.gotcha_count||0)+'</span><span class="lbl">gotchas</span></div>'+
|
|
193
274
|
'<div class="stat"><span class="num">'+(s.trajectory_count||0)+'</span><span class="lbl">trajectories</span></div>'+
|
|
275
|
+
'<div class="stat"><span class="num" style="color:var(--accent)">'+currentDbSize+'</span><span class="lbl">kuma.db</span></div>'+
|
|
194
276
|
'<div class="stat"><span class="num" style="color:'+hcColor+'">'+(hc||0)+'</span><span class="lbl">health</span></div>';
|
|
195
277
|
}
|
|
196
278
|
|
|
@@ -203,16 +285,21 @@ function truncateLabel(name, maxLen) {
|
|
|
203
285
|
|
|
204
286
|
/** Calculate node size based on label length and type */
|
|
205
287
|
function calcNodeSize(name, type) {
|
|
206
|
-
var
|
|
288
|
+
var isGotcha = name && name.toLowerCase().indexOf("gotcha:") === 0;
|
|
289
|
+
if (isGotcha) return 14;
|
|
290
|
+
if (type === "variable") return 14;
|
|
291
|
+
var base = 18;
|
|
292
|
+
if (type === "feature_domain") base = 30;
|
|
293
|
+
if (type === "workflow") base = 24;
|
|
294
|
+
if (type === "cross_service_link") base = 20;
|
|
207
295
|
if (type === "function") base = 16;
|
|
208
|
-
if (type === "class") base =
|
|
209
|
-
if (type === "module") base =
|
|
210
|
-
if (type === "component") base =
|
|
211
|
-
if (type === "route") base =
|
|
212
|
-
if (type === "test") base =
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return Math.min(28, Math.max(base, base + len * 0.4));
|
|
296
|
+
if (type === "class") base = 20;
|
|
297
|
+
if (type === "module") base = 22;
|
|
298
|
+
if (type === "component") base = 20;
|
|
299
|
+
if (type === "route") base = 16;
|
|
300
|
+
if (type === "test") base = 18;
|
|
301
|
+
var len = name ? Math.min(name.length, 25) : 4;
|
|
302
|
+
return Math.min(34, Math.max(base, base + len * 0.4));
|
|
216
303
|
}
|
|
217
304
|
|
|
218
305
|
|
|
@@ -225,10 +312,32 @@ function renderGraph(data) {
|
|
|
225
312
|
}
|
|
226
313
|
|
|
227
314
|
var types = [...new Set(allNodes.map(function(n){return n.type||'unknown'}))];
|
|
228
|
-
var colors = {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
315
|
+
var colors = {
|
|
316
|
+
feature_domain: '#dc2626',
|
|
317
|
+
workflow: '#2563eb',
|
|
318
|
+
cross_service_link: '#059669',
|
|
319
|
+
function: '#7c3aed',
|
|
320
|
+
class: '#0891b2',
|
|
321
|
+
module: '#16a34a',
|
|
322
|
+
component: '#ea580c',
|
|
323
|
+
file: '#d97706',
|
|
324
|
+
route: '#db2777',
|
|
325
|
+
test: '#0d9488',
|
|
326
|
+
variable: '#6366f1'
|
|
327
|
+
};
|
|
328
|
+
var shapes = {
|
|
329
|
+
feature_domain: 'star',
|
|
330
|
+
workflow: 'ellipse',
|
|
331
|
+
cross_service_link: 'box',
|
|
332
|
+
function: 'box',
|
|
333
|
+
class: 'hexagon',
|
|
334
|
+
module: 'ellipse',
|
|
335
|
+
component: 'diamond',
|
|
336
|
+
file: 'box',
|
|
337
|
+
route: 'triangleDown',
|
|
338
|
+
test: 'star',
|
|
339
|
+
variable: 'box'
|
|
340
|
+
};
|
|
232
341
|
|
|
233
342
|
// Limit nodes for performance if > 200
|
|
234
343
|
var maxNodes = 300;
|
|
@@ -239,16 +348,27 @@ function renderGraph(data) {
|
|
|
239
348
|
}
|
|
240
349
|
|
|
241
350
|
var visNodes = new vis.DataSet(displayNodes.map(function(n){
|
|
242
|
-
var
|
|
243
|
-
var
|
|
244
|
-
var
|
|
351
|
+
var isGotcha = n.name && n.name.toLowerCase().indexOf("gotcha:") === 0;
|
|
352
|
+
var isVariable = n.type === 'variable' && !isGotcha;
|
|
353
|
+
var shape = isGotcha ? 'triangle' : (shapes[n.type] || 'dot');
|
|
354
|
+
var maxLen = isGotcha ? 20 : (shape === 'ellipse' ? 18 : (isVariable ? 16 : 32));
|
|
355
|
+
var label = truncateLabel(n.name, maxLen);
|
|
356
|
+
var size = isGotcha ? 12 : (isVariable ? 10 : calcNodeSize(n.name, n.type));
|
|
357
|
+
var color = isGotcha ? '#f97316' : (colors[n.type] || '#64748b');
|
|
245
358
|
return {
|
|
246
359
|
id: n.id,
|
|
247
360
|
label: label,
|
|
248
|
-
|
|
249
|
-
color: {
|
|
250
|
-
|
|
251
|
-
|
|
361
|
+
|
|
362
|
+
color: {
|
|
363
|
+
background: color,
|
|
364
|
+
border: color,
|
|
365
|
+
highlight: { background: color, border: '#38bdf8' },
|
|
366
|
+
hover: { background: color, border: '#a855f7' }
|
|
367
|
+
},
|
|
368
|
+
shape: shape,
|
|
369
|
+
font: isVariable
|
|
370
|
+
? { color: '#ffffff', size: 10, face: 'sans-serif', multi: false, bold: true }
|
|
371
|
+
: { color: '#ffffff', size: Math.min(13, Math.max(10, size * 0.55)), face: 'sans-serif', multi: false, bold: true },
|
|
252
372
|
borderWidth: 0,
|
|
253
373
|
size: size,
|
|
254
374
|
mass: 1 + (size / 40),
|
|
@@ -256,21 +376,63 @@ function renderGraph(data) {
|
|
|
256
376
|
}));
|
|
257
377
|
|
|
258
378
|
var nodeIds = new Set(displayNodes.map(function(n){return n.id}));
|
|
259
|
-
var
|
|
379
|
+
var edgeList = allEdges.slice();
|
|
380
|
+
|
|
381
|
+
// Auto-connect floating file nodes to matching cross_service_link flow nodes
|
|
382
|
+
var nameToLinkIds = {};
|
|
383
|
+
displayNodes.forEach(function(n) {
|
|
384
|
+
if (n.type === 'cross_service_link') {
|
|
385
|
+
var cleanName = n.name.toLowerCase().trim();
|
|
386
|
+
if (!nameToLinkIds[cleanName]) nameToLinkIds[cleanName] = [];
|
|
387
|
+
nameToLinkIds[cleanName].push(n.id);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
displayNodes.forEach(function(n) {
|
|
392
|
+
if (n.type === 'file' || n.type === 'component' || n.type === 'function') {
|
|
393
|
+
var fn = n.name.toLowerCase().trim();
|
|
394
|
+
Object.keys(nameToLinkIds).forEach(function(linkName) {
|
|
395
|
+
if (fn.includes(linkName) || linkName.includes(fn)) {
|
|
396
|
+
var targetLinkIds = nameToLinkIds[linkName];
|
|
397
|
+
targetLinkIds.forEach(function(linkId) {
|
|
398
|
+
edgeList.push({
|
|
399
|
+
source: n.id,
|
|
400
|
+
target: linkId,
|
|
401
|
+
relation: 'contains'
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
var displayEdges = edgeList.filter(function(e){
|
|
260
410
|
return nodeIds.has(e.source) && nodeIds.has(e.target);
|
|
261
411
|
}).slice(0, 500);
|
|
262
412
|
|
|
263
|
-
var
|
|
413
|
+
var edgeDefaults = {};
|
|
414
|
+
var visEdges = new vis.DataSet(displayEdges.map(function(e, idx){
|
|
415
|
+
var edgeColor = '#64748b';
|
|
416
|
+
var width = 1.5;
|
|
417
|
+
var dashes = false;
|
|
418
|
+
if (e.relation === 'flows_through') { edgeColor = '#3b82f6'; width = 2.5; }
|
|
419
|
+
if (e.relation === 'triggers') { edgeColor = '#ef4444'; width = 2; dashes = true; }
|
|
420
|
+
if (e.relation === 'syncs_with') { edgeColor = '#10b981'; width = 2; }
|
|
421
|
+
if (e.relation === 'contains') { edgeColor = '#64748b'; width = 1.5; dashes = true; }
|
|
422
|
+
|
|
423
|
+
edgeDefaults['e'+idx] = { color: edgeColor, width: width };
|
|
264
424
|
return {
|
|
425
|
+
id: 'e'+idx,
|
|
265
426
|
from: e.source, to: e.target,
|
|
266
427
|
label: e.relation || '',
|
|
267
|
-
arrows: { to: { enabled: true, scaleFactor: 0.
|
|
268
|
-
color: { color:
|
|
269
|
-
font: { size: 10, color: '#
|
|
428
|
+
arrows: { to: { enabled: true, scaleFactor: 0.7, type: 'arrow' } },
|
|
429
|
+
color: { color: edgeColor, highlight: '#a855f7', hover: '#38bdf8', opacity: 0.85 },
|
|
430
|
+
font: { size: 10, color: '#94a3b8', strokeWidth: 0, background: 'transparent' },
|
|
431
|
+
dashes: dashes,
|
|
270
432
|
smooth: { type: 'continuous', roundness: 0.3 },
|
|
271
|
-
width:
|
|
272
|
-
hoverWidth:
|
|
273
|
-
selectionWidth:
|
|
433
|
+
width: width,
|
|
434
|
+
hoverWidth: 3,
|
|
435
|
+
selectionWidth: 3,
|
|
274
436
|
};
|
|
275
437
|
}));
|
|
276
438
|
|
|
@@ -303,9 +465,10 @@ function renderGraph(data) {
|
|
|
303
465
|
},
|
|
304
466
|
nodes: {
|
|
305
467
|
borderWidth: 0,
|
|
306
|
-
font: { color: '#
|
|
307
|
-
margin: { top:
|
|
308
|
-
|
|
468
|
+
font: { color: '#ffffff', size: 13, face: 'sans-serif', bold: 'true' },
|
|
469
|
+
margin: { top: 10, bottom: 10, left: 16, right: 16 },
|
|
470
|
+
shapeProperties: { borderRadius: 8 },
|
|
471
|
+
shadow: { enabled: true, color: 'rgba(0,0,0,0.4)', size: 8, x: 0, y: 3 },
|
|
309
472
|
},
|
|
310
473
|
edges: {
|
|
311
474
|
smooth: { type: 'continuous', roundness: 0.3 },
|
|
@@ -315,32 +478,72 @@ function renderGraph(data) {
|
|
|
315
478
|
configure: { enabled: false },
|
|
316
479
|
});
|
|
317
480
|
|
|
318
|
-
// Click node →
|
|
481
|
+
// Click node → highlight connected neighborhood (tooltip via hover only)
|
|
319
482
|
network.on('click', function(params) {
|
|
320
|
-
var tip = document.getElementById("graph-tooltip");
|
|
321
483
|
if (params.nodes.length) {
|
|
322
|
-
var
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
484
|
+
var selectedId = params.nodes[0];
|
|
485
|
+
var connectedNodeIds = new Set(network.getConnectedNodes(selectedId));
|
|
486
|
+
connectedNodeIds.add(selectedId);
|
|
487
|
+
|
|
488
|
+
network.body.data.nodes.forEach(function(n) {
|
|
489
|
+
var isConnected = connectedNodeIds.has(n.id);
|
|
490
|
+
network.body.data.nodes.update({
|
|
491
|
+
id: n.id,
|
|
492
|
+
opacity: isConnected ? 1.0 : 0.12,
|
|
493
|
+
font: {
|
|
494
|
+
color: isConnected ? '#ffffff' : 'rgba(255,255,255,0.07)',
|
|
495
|
+
bold: isConnected
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
network.body.data.edges.forEach(function(e) {
|
|
501
|
+
var def = edgeDefaults[e.id];
|
|
502
|
+
var isConnected = connectedNodeIds.has(e.from) && connectedNodeIds.has(e.to);
|
|
503
|
+
network.body.data.edges.update({
|
|
504
|
+
id: e.id,
|
|
505
|
+
color: {
|
|
506
|
+
color: isConnected ? (def ? def.color : '#64748b') : 'rgba(100,116,139,0.05)',
|
|
507
|
+
highlight: '#a855f7',
|
|
508
|
+
hover: '#38bdf8',
|
|
509
|
+
opacity: isConnected ? 0.85 : 0.05
|
|
510
|
+
},
|
|
511
|
+
width: isConnected ? (def ? def.width : 1.5) : 0.3,
|
|
512
|
+
font: {
|
|
513
|
+
color: isConnected ? '#94a3b8' : 'rgba(148,163,184,0.1)',
|
|
514
|
+
size: 10,
|
|
515
|
+
strokeWidth: 0,
|
|
516
|
+
background: 'transparent'
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
});
|
|
338
520
|
} else {
|
|
339
|
-
|
|
521
|
+
document.getElementById("graph-tooltip").style.display = 'none';
|
|
522
|
+
network.body.data.nodes.forEach(function(n) {
|
|
523
|
+
network.body.data.nodes.update({
|
|
524
|
+
id: n.id,
|
|
525
|
+
opacity: 1.0,
|
|
526
|
+
font: { color: '#ffffff', bold: true }
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
network.body.data.edges.forEach(function(e) {
|
|
530
|
+
var def = edgeDefaults[e.id];
|
|
531
|
+
network.body.data.edges.update({
|
|
532
|
+
id: e.id,
|
|
533
|
+
color: {
|
|
534
|
+
color: def ? def.color : '#64748b',
|
|
535
|
+
highlight: '#a855f7',
|
|
536
|
+
hover: '#38bdf8',
|
|
537
|
+
opacity: 0.85
|
|
538
|
+
},
|
|
539
|
+
width: def ? def.width : 1.5,
|
|
540
|
+
font: { size: 10, color: '#94a3b8', strokeWidth: 0, background: 'transparent' }
|
|
541
|
+
});
|
|
542
|
+
});
|
|
340
543
|
}
|
|
341
544
|
});
|
|
342
545
|
|
|
343
|
-
// Hover highlight
|
|
546
|
+
// Hover node → show tooltip + border highlight
|
|
344
547
|
network.on('hoverNode', function(params) {
|
|
345
548
|
var nodeId = params.node;
|
|
346
549
|
network.body.data.nodes.updateOnly({
|
|
@@ -348,6 +551,23 @@ function renderGraph(data) {
|
|
|
348
551
|
borderWidth: 2,
|
|
349
552
|
borderWidthSelected: 2,
|
|
350
553
|
});
|
|
554
|
+
|
|
555
|
+
var n = allNodes.find(function(x){return x.id===nodeId});
|
|
556
|
+
if (n) {
|
|
557
|
+
var tip = document.getElementById("graph-tooltip");
|
|
558
|
+
tip.style.display = 'block';
|
|
559
|
+
tip.innerHTML = '<div style="word-break:break-all;overflow-wrap:anywhere;max-width:390px;white-space:normal;line-height:1.4"><b style="color:#f8fafc">' + n.name + '</b> <span style="color:#94a3b8">' + n.type + '</span>' +
|
|
560
|
+
(n.file_path ? '<br><span style="color:#38bdf8;font-size:11px;word-break:break-all;overflow-wrap:anywhere">' + n.file_path + '</span>' : '') + '</div>';
|
|
561
|
+
var pos = network.getPositions([n.id]);
|
|
562
|
+
if (pos && pos[n.id]) {
|
|
563
|
+
var canvasPos = network.canvasToDOM(pos[n.id]);
|
|
564
|
+
var panelRect = container.getBoundingClientRect();
|
|
565
|
+
var tipX = Math.min(canvasPos.x + 12, panelRect.width - 340);
|
|
566
|
+
var tipY = Math.max(canvasPos.y - 10, 0);
|
|
567
|
+
tip.style.left = Math.max(tipX, 8) + 'px';
|
|
568
|
+
tip.style.top = tipY + 'px';
|
|
569
|
+
}
|
|
570
|
+
}
|
|
351
571
|
});
|
|
352
572
|
network.on('blurNode', function(params) {
|
|
353
573
|
network.body.data.nodes.updateOnly({
|
|
@@ -355,6 +575,7 @@ function renderGraph(data) {
|
|
|
355
575
|
borderWidth: 0,
|
|
356
576
|
borderWidthSelected: 0,
|
|
357
577
|
});
|
|
578
|
+
document.getElementById("graph-tooltip").style.display = 'none';
|
|
358
579
|
});
|
|
359
580
|
|
|
360
581
|
renderLegend(types, colors);
|