@plumpslabs/kuma 2.3.19 → 2.3.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumpslabs/kuma",
3
- "version": "2.3.19",
3
+ "version": "2.3.20",
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",
@@ -133,7 +133,9 @@ header .status .dot.off { background: var(--red); }
133
133
  <div id="graph-container"></div>
134
134
  <div id="search-box"><input id="node-search" type="text" placeholder="Search nodes..." /></div>
135
135
  <div id="graph-controls">
136
+ <button onclick="resetGraphFocus()">Reset Focus</button>
136
137
  <button onclick="if(network)network.fit()">Fit</button>
138
+ <button onclick="exportGraphPNG()">Export PNG</button>
137
139
  <button onclick="if(network)network.startSimulation()">Restart</button>
138
140
  <button onclick="if(network)network.stopSimulation()">Stop</button>
139
141
  </div>
@@ -154,6 +156,26 @@ header .status .dot.off { background: var(--red); }
154
156
  const API = "/api";
155
157
  let network = null, nodesData = null, allNodes = null, allEdges = null;
156
158
 
159
+ function resetGraphFocus() {
160
+ if (!network) return;
161
+ network.body.data.nodes.forEach(function(n) {
162
+ network.body.data.nodes.update({ id: n.id, opacity: 1.0 });
163
+ });
164
+ document.getElementById("graph-tooltip").style.display = 'none';
165
+ var searchInput = document.getElementById("node-search");
166
+ if (searchInput) searchInput.value = "";
167
+ }
168
+
169
+ function exportGraphPNG() {
170
+ if (!network) return;
171
+ var canvas = document.querySelector("#graph-container canvas");
172
+ if (!canvas) return;
173
+ var link = document.createElement("a");
174
+ link.download = "kuma-knowledge-graph.png";
175
+ link.href = canvas.toDataURL("image/png");
176
+ link.click();
177
+ }
178
+
157
179
  async function init() {
158
180
  var status = await fetch(API+"/status").then(function(r){return r.json()});
159
181
  var dot = document.getElementById("status-dot");
@@ -204,6 +226,9 @@ function truncateLabel(name, maxLen) {
204
226
  /** Calculate node size based on label length and type */
205
227
  function calcNodeSize(name, type) {
206
228
  var base = 14;
229
+ if (type === "feature_domain") base = 24;
230
+ if (type === "workflow") base = 18;
231
+ if (type === "cross_service_link") base = 16;
207
232
  if (type === "function") base = 16;
208
233
  if (type === "class") base = 18;
209
234
  if (type === "module") base = 12;
@@ -212,7 +237,7 @@ function calcNodeSize(name, type) {
212
237
  if (type === "test") base = 13;
213
238
  // Gentle scaling for long labels
214
239
  var len = name ? Math.min(name.length, 20) : 4;
215
- return Math.min(28, Math.max(base, base + len * 0.4));
240
+ return Math.min(32, Math.max(base, base + len * 0.4));
216
241
  }
217
242
 
218
243
 
@@ -225,9 +250,11 @@ function renderGraph(data) {
225
250
  }
226
251
 
227
252
  var types = [...new Set(allNodes.map(function(n){return n.type||'unknown'}))];
228
- var colors = {function:'#7c3aed',class:'#06b6d4',module:'#22c55e',
253
+ var colors = {feature_domain:'#ef4444',workflow:'#3b82f6',cross_service_link:'#10b981',
254
+ function:'#7c3aed',class:'#06b6d4',module:'#22c55e',
229
255
  component:'#f97316',file:'#eab308',route:'#ec4899',test:'#14b8a6'};
230
- var shapes = {function:'box',class:'hexagon',module:'ellipse',
256
+ var shapes = {feature_domain:'star',workflow:'ellipse',cross_service_link:'box',
257
+ function:'box',class:'hexagon',module:'ellipse',
231
258
  component:'diamond',file:'square',route:'triangle',test:'star'};
232
259
 
233
260
  // Limit nodes for performance if > 200
@@ -261,16 +288,25 @@ function renderGraph(data) {
261
288
  }).slice(0, 500);
262
289
 
263
290
  var visEdges = new vis.DataSet(displayEdges.map(function(e){
291
+ var edgeColor = '#64748b';
292
+ var width = 1.5;
293
+ var dashes = false;
294
+ if (e.relation === 'flows_through') { edgeColor = '#3b82f6'; width = 2.5; }
295
+ if (e.relation === 'triggers') { edgeColor = '#ef4444'; width = 2; dashes = true; }
296
+ if (e.relation === 'syncs_with') { edgeColor = '#10b981'; width = 2; }
297
+ if (e.relation === 'contains') { edgeColor = '#475569'; width = 1; }
298
+
264
299
  return {
265
300
  from: e.source, to: e.target,
266
301
  label: e.relation || '',
267
- arrows: { to: { enabled: true, scaleFactor: 0.6, type: 'arrow' } },
268
- color: { color: '#4a4a6a', highlight: '#7c3aed', hover: '#7c3aed', opacity: 0.5 },
269
- font: { size: 10, color: '#666', strokeWidth: 0, background: 'transparent' },
302
+ arrows: { to: { enabled: true, scaleFactor: 0.7, type: 'arrow' } },
303
+ color: { color: edgeColor, highlight: '#a855f7', hover: '#38bdf8', opacity: 0.85 },
304
+ font: { size: 10, color: '#94a3b8', strokeWidth: 0, background: 'transparent' },
305
+ dashes: dashes,
270
306
  smooth: { type: 'continuous', roundness: 0.3 },
271
- width: 1.5,
272
- hoverWidth: 2.5,
273
- selectionWidth: 2.5,
307
+ width: width,
308
+ hoverWidth: 3,
309
+ selectionWidth: 3,
274
310
  };
275
311
  }));
276
312
 
@@ -315,11 +351,22 @@ function renderGraph(data) {
315
351
  configure: { enabled: false },
316
352
  });
317
353
 
318
- // Click node → show tooltip with file path
354
+ // Click node → show tooltip with file path + highlight connected neighborhood
319
355
  network.on('click', function(params) {
320
356
  var tip = document.getElementById("graph-tooltip");
321
357
  if (params.nodes.length) {
322
- var n = allNodes.find(function(x){return x.id===params.nodes[0]});
358
+ var selectedId = params.nodes[0];
359
+ var connectedNodeIds = new Set(network.getConnectedNodes(selectedId));
360
+ connectedNodeIds.add(selectedId);
361
+
362
+ network.body.data.nodes.forEach(function(n) {
363
+ network.body.data.nodes.update({
364
+ id: n.id,
365
+ opacity: connectedNodeIds.has(n.id) ? 1.0 : 0.15
366
+ });
367
+ });
368
+
369
+ var n = allNodes.find(function(x){return x.id===selectedId});
323
370
  if (n) {
324
371
  tip.style.display = 'block';
325
372
  tip.innerHTML = '<b>' + n.name + '</b> <span style="color:var(--muted)">' + n.type + '</span>' +
@@ -337,6 +384,9 @@ function renderGraph(data) {
337
384
  }
338
385
  } else {
339
386
  tip.style.display = 'none';
387
+ network.body.data.nodes.forEach(function(n) {
388
+ network.body.data.nodes.update({ id: n.id, opacity: 1.0 });
389
+ });
340
390
  }
341
391
  });
342
392