@kage-core/kage-graph-mcp 1.1.7 → 1.1.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/README.md CHANGED
@@ -137,6 +137,11 @@ meaningful file changes. Refresh rebuilds indexes, code graph, memory graph,
137
137
  metrics, and stale-memory metadata. Memory is marked stale when status or
138
138
  feedback says it is stale, its TTL expires, or grounded paths disappear.
139
139
 
140
+ Use `kage gc --project <repo> --dry-run` to preview stale packet cleanup.
141
+ `kage gc --project <repo>` marks stale repo packets deprecated, rebuilds
142
+ indexes/graphs/metrics, and keeps helpful-voted memories unless `--force` is
143
+ used.
144
+
140
145
  Use `kage pr summarize --project <repo>` / `kage_pr_summarize` before handoff to
141
146
  write branch review metadata and repo-local change memory from the git diff.
142
147
  Use `kage pr check --project <repo>` / `kage_pr_check` before merge to verify
package/dist/cli.js CHANGED
@@ -24,6 +24,7 @@ Usage:
24
24
  kage daemon doctor --project <dir> [--json]
25
25
  kage viewer --project <dir> [--port 3113]
26
26
  kage refresh --project <dir> [--json]
27
+ kage gc --project <dir> [--dry-run] [--force] [--json]
27
28
  kage pr summarize --project <dir> [--json]
28
29
  kage pr check --project <dir> [--json]
29
30
  kage upgrade [--dry-run]
@@ -302,6 +303,32 @@ async function main() {
302
303
  await (0, daemon_js_1.startViewer)(projectArg(args), { port: numberArg(args, "--port", 3113) });
303
304
  return;
304
305
  }
306
+ if (command === "gc") {
307
+ const project = projectArg(args);
308
+ const dryRun = args.includes("--dry-run");
309
+ const force = args.includes("--force");
310
+ const result = (0, kernel_js_1.gcProject)(project, { dryRun, force });
311
+ if (args.includes("--json")) {
312
+ console.log(JSON.stringify(result, null, 2));
313
+ return;
314
+ }
315
+ const label = dryRun ? " [dry-run]" : "";
316
+ console.log(`Kage GC${label} — scanned ${result.total_scanned} packets`);
317
+ if (result.deprecated.length) {
318
+ console.log(`\nDeprecated (${result.deprecated.length}):`);
319
+ for (const p of result.deprecated)
320
+ console.log(` ✗ ${p.title} — ${p.reason}`);
321
+ }
322
+ if (result.deleted.length) {
323
+ console.log(`\nDeleted (${result.deleted.length}):`);
324
+ for (const p of result.deleted)
325
+ console.log(` 🗑 ${p.title}`);
326
+ }
327
+ if (!result.deprecated.length && !result.deleted.length) {
328
+ console.log("No stale packets found — memory is clean.");
329
+ }
330
+ return;
331
+ }
305
332
  if (command === "refresh") {
306
333
  const result = (0, kernel_js_1.refreshProject)(projectArg(args));
307
334
  if (args.includes("--json")) {
package/dist/kernel.js CHANGED
@@ -67,6 +67,7 @@ exports.buildKnowledgeGraph = buildKnowledgeGraph;
67
67
  exports.buildIndexes = buildIndexes;
68
68
  exports.indexProject = indexProject;
69
69
  exports.refreshProject = refreshProject;
70
+ exports.gcProject = gcProject;
70
71
  exports.installAgentPolicy = installAgentPolicy;
71
72
  exports.recall = recall;
72
73
  exports.queryCodeGraph = queryCodeGraph;
@@ -2516,6 +2517,57 @@ function refreshProject(projectDir) {
2516
2517
  next_actions: nextActions,
2517
2518
  };
2518
2519
  }
2520
+ function gcProject(projectDir, options = {}) {
2521
+ ensureMemoryDirs(projectDir);
2522
+ const packetEntries = loadPacketEntriesFromDir(packetsDir(projectDir));
2523
+ const deprecated = [];
2524
+ const deleted = [];
2525
+ const skipped = [];
2526
+ for (const { path, packet } of packetEntries) {
2527
+ if (packet.status === "deprecated") {
2528
+ skipped.push({ id: packet.id, title: packet.title, reason: "already deprecated" });
2529
+ continue;
2530
+ }
2531
+ const reasons = staleMemoryReasons(projectDir, packet);
2532
+ if (!reasons.length) {
2533
+ skipped.push({ id: packet.id, title: packet.title, reason: "healthy" });
2534
+ continue;
2535
+ }
2536
+ const quality = packet.quality;
2537
+ const hasHelpfulVotes = Number(quality?.votes_up ?? 0) > 0;
2538
+ if (hasHelpfulVotes && !options.force) {
2539
+ skipped.push({ id: packet.id, title: packet.title, reason: `stale but has helpful votes (use --force to override)` });
2540
+ continue;
2541
+ }
2542
+ // Mark as deprecated (or hard-delete if --force)
2543
+ if (options.force && !hasHelpfulVotes) {
2544
+ if (!options.dryRun) {
2545
+ (0, node_fs_1.unlinkSync)(path);
2546
+ }
2547
+ deleted.push({ id: packet.id, title: packet.title });
2548
+ }
2549
+ else {
2550
+ if (!options.dryRun) {
2551
+ const updated = { ...packet, status: "deprecated", updated_at: nowIso() };
2552
+ writeJson(path, updated);
2553
+ }
2554
+ deprecated.push({ id: packet.id, title: packet.title, reason: reasons[0] });
2555
+ }
2556
+ }
2557
+ if (!options.dryRun && (deprecated.length || deleted.length)) {
2558
+ buildIndexes(projectDir);
2559
+ buildKnowledgeGraph(projectDir);
2560
+ writeJson((0, node_path_1.join)(memoryRoot(projectDir), "metrics.json"), kageMetrics(projectDir));
2561
+ }
2562
+ return {
2563
+ ok: true,
2564
+ project_dir: projectDir,
2565
+ deprecated,
2566
+ deleted,
2567
+ skipped,
2568
+ total_scanned: packetEntries.length,
2569
+ };
2570
+ }
2519
2571
  function installAgentPolicy(projectDir) {
2520
2572
  const agentsPath = (0, node_path_1.join)(projectDir, "AGENTS.md");
2521
2573
  const claudePath = (0, node_path_1.join)(projectDir, "CLAUDE.md");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kage-core/kage-graph-mcp",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
4
4
  "description": "Local-first repo memory, code graph, and recall MCP server for coding agents",
5
5
  "main": "dist/index.js",
6
6
  "files": [
package/viewer/app.js CHANGED
@@ -7,6 +7,8 @@
7
7
  edges: [],
8
8
  episodesById: new Map(),
9
9
  entityById: new Map(),
10
+ edgeById: new Map(),
11
+ degreeById: new Map(),
10
12
  positions: new Map(),
11
13
  visibleEntityIds: new Set(),
12
14
  visibleEdgeIds: new Set(),
@@ -27,8 +29,12 @@
27
29
  panning: null,
28
30
  hoverNode: null,
29
31
  raf: null,
32
+ drawRaf: null,
30
33
  running: false,
31
- lastSignature: ""
34
+ lastSignature: "",
35
+ tick: 0,
36
+ idleFrames: 0,
37
+ adjacency: new Map()
32
38
  },
33
39
  pan: null
34
40
  };
@@ -104,7 +110,7 @@
104
110
  };
105
111
 
106
112
  els.graphFile.addEventListener("change", handleFile);
107
- els.searchInput.addEventListener("input", render);
113
+ els.searchInput.addEventListener("input", scheduleRender);
108
114
  els.viewMode.addEventListener("change", render);
109
115
  els.typeFilter.addEventListener("change", render);
110
116
  els.relationFilter.addEventListener("change", render);
@@ -183,6 +189,10 @@
183
189
  state.entityById = new Map(entities.map(function (entity) {
184
190
  return [entity.id, entity];
185
191
  }));
192
+ state.edgeById = new Map(edges.map(function (edge) {
193
+ return [edge.id, edge];
194
+ }));
195
+ state.degreeById = buildDegreeMap(edges);
186
196
  state.episodesById = new Map(episodes.map(function (episode) {
187
197
  return [episode.id, episode];
188
198
  }));
@@ -195,6 +205,15 @@
195
205
  render();
196
206
  }
197
207
 
208
+ function buildDegreeMap(edges) {
209
+ var degrees = new Map();
210
+ edges.forEach(function (edge) {
211
+ degrees.set(edge.from, (degrees.get(edge.from) || 0) + 1);
212
+ degrees.set(edge.to, (degrees.get(edge.to) || 0) + 1);
213
+ });
214
+ return degrees;
215
+ }
216
+
198
217
  function loadFromUrlParams() {
199
218
  var params = new URLSearchParams(window.location.search);
200
219
  var graphPaths = []
@@ -471,6 +490,7 @@
471
490
  if (!state.graph) return;
472
491
 
473
492
  var query = parseSearchQuery(els.searchInput.value);
493
+ state.renderQuery = query;
474
494
  var mode = els.viewMode.value;
475
495
  var type = els.typeFilter.value;
476
496
  var relation = els.relationFilter.value;
@@ -509,6 +529,7 @@
509
529
  type: type,
510
530
  relation: relation,
511
531
  scope: els.scopeFilter.value,
532
+ mode: mode,
512
533
  maxNodes: Number(els.maxNodes.value || 90),
513
534
  showDependencies: els.showDependencies.checked
514
535
  });
@@ -528,6 +549,14 @@
528
549
  renderProof();
529
550
  }
530
551
 
552
+ function scheduleRender() {
553
+ if (state.renderRaf) return;
554
+ state.renderRaf = window.requestAnimationFrame(function () {
555
+ state.renderRaf = null;
556
+ render();
557
+ });
558
+ }
559
+
531
560
  function refineVisibleGraph(entityIds, edgeIds, options) {
532
561
  var entities = new Set(entityIds);
533
562
  var edges = new Set(edgeIds);
@@ -552,7 +581,9 @@
552
581
  return entityImportance(state.entityById.get(b)) - entityImportance(state.entityById.get(a)) ||
553
582
  displayName(state.entityById.get(a)).localeCompare(displayName(state.entityById.get(b)));
554
583
  });
555
- entities = new Set(ranked.slice(0, options.maxNodes));
584
+ entities = options.mode === "combined"
585
+ ? balancedSignalEntities(ranked, options.maxNodes)
586
+ : new Set(ranked.slice(0, options.maxNodes));
556
587
  if (state.selected && state.selected.kind === "entity") entities.add(state.selected.id);
557
588
  edges = edgesWithVisibleEndpoints(edges, entities);
558
589
  }
@@ -568,6 +599,33 @@
568
599
  return { entities: entities, edges: edgesWithVisibleEndpoints(edges, entities) };
569
600
  }
570
601
 
602
+ function balancedSignalEntities(rankedIds, maxNodes) {
603
+ var result = new Set();
604
+ var memoryIds = rankedIds.filter(function (id) {
605
+ var entity = state.entityById.get(id);
606
+ return entity && entity.graph_kind === "memory" && ["memory", "repo", "memory_type", "command"].indexOf(entity.type) !== -1;
607
+ });
608
+ var codeIds = rankedIds.filter(function (id) {
609
+ var entity = state.entityById.get(id);
610
+ return entity && entity.graph_kind === "code";
611
+ });
612
+ var otherMemoryIds = rankedIds.filter(function (id) {
613
+ var entity = state.entityById.get(id);
614
+ return entity && entity.graph_kind === "memory" && !memoryIds.includes(id);
615
+ });
616
+
617
+ var memoryBudget = clamp(Math.round(maxNodes * 0.34), 18, Math.min(44, maxNodes));
618
+ memoryIds.slice(0, memoryBudget).forEach(function (id) { result.add(id); });
619
+ otherMemoryIds.slice(0, Math.max(0, Math.round(memoryBudget * 0.35))).forEach(function (id) { result.add(id); });
620
+ codeIds.forEach(function (id) {
621
+ if (result.size < maxNodes) result.add(id);
622
+ });
623
+ rankedIds.forEach(function (id) {
624
+ if (result.size < maxNodes) result.add(id);
625
+ });
626
+ return result;
627
+ }
628
+
571
629
  function focusSelection(entityIds, edgeIds, selection) {
572
630
  var entities = new Set();
573
631
  var edges = new Set();
@@ -599,7 +657,7 @@
599
657
 
600
658
  function edgesWithVisibleEndpoints(edgeIds, entityIds) {
601
659
  return new Set(Array.from(edgeIds).filter(function (id) {
602
- var edge = state.edges.find(function (candidate) { return candidate.id === id; });
660
+ var edge = state.edgeById.get(id);
603
661
  return edge && entityIds.has(edge.from) && entityIds.has(edge.to);
604
662
  }));
605
663
  }
@@ -656,6 +714,22 @@
656
714
  state.sim.edges = state.edges.filter(function (edge) {
657
715
  return state.visibleEdgeIds.has(edge.id) && state.sim.nodeById.has(edge.from) && state.sim.nodeById.has(edge.to);
658
716
  });
717
+ state.sim.adjacency = buildAdjacency(state.sim.edges);
718
+ if (forceReset) {
719
+ state.sim.tick = 0;
720
+ state.sim.idleFrames = 0;
721
+ }
722
+ }
723
+
724
+ function buildAdjacency(edges) {
725
+ var adjacency = new Map();
726
+ edges.forEach(function (edge) {
727
+ if (!adjacency.has(edge.from)) adjacency.set(edge.from, new Set());
728
+ if (!adjacency.has(edge.to)) adjacency.set(edge.to, new Set());
729
+ adjacency.get(edge.from).add(edge.to);
730
+ adjacency.get(edge.to).add(edge.from);
731
+ });
732
+ return adjacency;
659
733
  }
660
734
 
661
735
  function seededPosition(index, total) {
@@ -673,16 +747,28 @@
673
747
  state.sim.raf = window.requestAnimationFrame(simulationTick);
674
748
  }
675
749
 
750
+ function stopSimulation() {
751
+ state.sim.running = false;
752
+ if (state.sim.raf) window.cancelAnimationFrame(state.sim.raf);
753
+ state.sim.raf = null;
754
+ }
755
+
676
756
  function simulationTick() {
677
757
  if (!state.sim.running) return;
678
- stepSimulation();
758
+ var maxVelocity = stepSimulation();
679
759
  drawCanvasGraph();
760
+ state.sim.tick += 1;
761
+ state.sim.idleFrames = maxVelocity < 0.035 ? state.sim.idleFrames + 1 : 0;
762
+ if (!state.sim.dragNode && (state.sim.idleFrames > 24 || state.sim.tick > 220)) {
763
+ stopSimulation();
764
+ return;
765
+ }
680
766
  state.sim.raf = window.requestAnimationFrame(simulationTick);
681
767
  }
682
768
 
683
769
  function stepSimulation() {
684
770
  var nodes = state.sim.nodes;
685
- if (!nodes.length) return;
771
+ if (!nodes.length) return 0;
686
772
  var nodeMap = state.sim.nodeById;
687
773
  var count = nodes.length;
688
774
  var repulsion = count > 120 ? 3600 : count > 70 ? 2500 : 1700;
@@ -736,11 +822,16 @@
736
822
  }
737
823
  });
738
824
 
825
+ var maxVelocity = 0;
739
826
  nodes.forEach(function (node) {
740
827
  if (state.sim.dragNode === node) return;
741
- node.x += clamp(node.vx, -8, 8);
742
- node.y += clamp(node.vy, -8, 8);
828
+ var vx = clamp(node.vx, -8, 8);
829
+ var vy = clamp(node.vy, -8, 8);
830
+ node.x += vx;
831
+ node.y += vy;
832
+ maxVelocity = Math.max(maxVelocity, Math.abs(vx), Math.abs(vy));
743
833
  });
834
+ return maxVelocity;
744
835
  }
745
836
 
746
837
  function drawCanvasGraph() {
@@ -803,7 +894,7 @@
803
894
  function drawCanvasEdges(ctx) {
804
895
  var nodeMap = state.sim.nodeById;
805
896
  var focusId = focusedCanvasNodeId();
806
- var query = parseSearchQuery(els.searchInput.value);
897
+ var query = state.renderQuery || parseSearchQuery(els.searchInput.value);
807
898
  var dense = state.sim.nodes.length > 55;
808
899
  state.sim.edges.forEach(function (edge) {
809
900
  var from = nodeMap.get(edge.from);
@@ -832,15 +923,14 @@
832
923
 
833
924
  function drawCanvasNodes(ctx) {
834
925
  var focusId = focusedCanvasNodeId();
835
- var query = parseSearchQuery(els.searchInput.value);
926
+ var query = state.renderQuery || parseSearchQuery(els.searchInput.value);
836
927
  var dense = state.sim.nodes.length > 55;
928
+ var focusNeighbors = focusId ? state.sim.adjacency.get(focusId) : null;
837
929
  state.sim.nodes.forEach(function (node) {
838
930
  var entity = node.entity;
839
931
  var selected = state.selected && state.selected.kind === "entity" && state.selected.id === node.id;
840
932
  var hovered = state.sim.hoverNode && state.sim.hoverNode.id === node.id;
841
- var connected = focusId && (node.id === focusId || state.sim.edges.some(function (edge) {
842
- return (edge.from === focusId && edge.to === node.id) || (edge.to === focusId && edge.from === node.id);
843
- }));
933
+ var connected = focusId && (node.id === focusId || (focusNeighbors && focusNeighbors.has(node.id)));
844
934
  var matches = matchesSearchQuery(entity, query);
845
935
  var alpha = !matches ? 0.12 : focusId && !connected ? 0.20 : 1;
846
936
  var color = nodeThemeColor(entity);
@@ -1376,9 +1466,7 @@
1376
1466
  }
1377
1467
 
1378
1468
  function degreeOf(id) {
1379
- return state.edges.reduce(function (sum, edge) {
1380
- return sum + (edge.from === id || edge.to === id ? 1 : 0);
1381
- }, 0);
1469
+ return state.degreeById.get(id) || 0;
1382
1470
  }
1383
1471
 
1384
1472
  function entityImportance(entity) {
@@ -1442,6 +1530,9 @@
1442
1530
  state.sim.dragNode.y = world.y;
1443
1531
  state.sim.dragNode.vx = 0;
1444
1532
  state.sim.dragNode.vy = 0;
1533
+ state.sim.tick = 0;
1534
+ state.sim.idleFrames = 0;
1535
+ startSimulation();
1445
1536
  } else if (state.sim.panning) {
1446
1537
  var dx = event.clientX - state.sim.panning.x;
1447
1538
  var dy = event.clientY - state.sim.panning.y;
@@ -1451,7 +1542,7 @@
1451
1542
  }
1452
1543
  state.sim.hoverNode = findCanvasNode(world.x, world.y);
1453
1544
  updateCanvasTooltip(event);
1454
- drawCanvasGraph();
1545
+ scheduleCanvasDraw();
1455
1546
  }
1456
1547
 
1457
1548
  function endCanvasPointer() {
@@ -1471,7 +1562,7 @@
1471
1562
  state.sim.dragNode = null;
1472
1563
  state.sim.panning = null;
1473
1564
  if (els.tooltip) els.tooltip.classList.remove("visible");
1474
- drawCanvasGraph();
1565
+ scheduleCanvasDraw();
1475
1566
  }
1476
1567
 
1477
1568
  function handleCanvasWheel(event) {
@@ -1482,7 +1573,15 @@
1482
1573
  state.sim.zoom = clamp(state.sim.zoom * factor, 0.14, 4.5);
1483
1574
  state.sim.panX = event.clientX - rect.left - before.x * state.sim.zoom;
1484
1575
  state.sim.panY = event.clientY - rect.top - before.y * state.sim.zoom;
1485
- drawCanvasGraph();
1576
+ scheduleCanvasDraw();
1577
+ }
1578
+
1579
+ function scheduleCanvasDraw() {
1580
+ if (state.sim.drawRaf) return;
1581
+ state.sim.drawRaf = window.requestAnimationFrame(function () {
1582
+ state.sim.drawRaf = null;
1583
+ drawCanvasGraph();
1584
+ });
1486
1585
  }
1487
1586
 
1488
1587
  function handleCanvasDoubleClick(event) {