@kage-core/kage-graph-mcp 1.1.13 → 1.1.14

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.
@@ -57,7 +57,7 @@ function verifySignedManifest(manifest) {
57
57
  if (manifest.schema_version !== exports.REGISTRY_MANIFEST_SCHEMA_VERSION) {
58
58
  errors.push("manifest schema_version must be 1");
59
59
  }
60
- if (!["public_candidate_bundle", "org_registry"].includes(String(manifest.kind))) {
60
+ if (!["public_candidate_bundle", "org_registry", "graph_registry"].includes(String(manifest.kind))) {
61
61
  errors.push("manifest kind is not supported");
62
62
  }
63
63
  if (!isNonEmptyString(manifest.name)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kage-core/kage-graph-mcp",
3
- "version": "1.1.13",
3
+ "version": "1.1.14",
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
@@ -14,6 +14,7 @@
14
14
  visibleEdgeIds: new Set(),
15
15
  selected: null,
16
16
  metrics: null,
17
+ inbox: null,
17
18
  pendingPackets: [],
18
19
  reviewText: "",
19
20
  viewBox: { x: 0, y: 0, width: 1000, height: 660 },
@@ -66,6 +67,7 @@
66
67
  code: "#6ad7ff",
67
68
  amber: "#ffd166",
68
69
  danger: "#ff6b6b",
70
+ bridge: "#d8ff5f",
69
71
  dependency: "#62776b",
70
72
  body: "rgba(4,12,8,0.88)",
71
73
  bodyCode: "rgba(5,16,18,0.88)",
@@ -109,6 +111,8 @@
109
111
  proofList: document.getElementById("proofList")
110
112
  };
111
113
 
114
+ var MEMORY_CODE_RELATIONS = new Set(["explains_symbol", "informs_symbol", "fixes_symbol", "applies_to_route", "verified_by_test", "affects_path"]);
115
+
112
116
  els.graphFile.addEventListener("change", handleFile);
113
117
  els.searchInput.addEventListener("input", scheduleRender);
114
118
  els.viewMode.addEventListener("change", render);
@@ -223,13 +227,16 @@
223
227
  .map(function (value) { return value.trim(); })
224
228
  .filter(Boolean);
225
229
  var metricsPath = params.get("metrics");
230
+ var inboxPath = params.get("inbox");
226
231
  var reviewPath = params.get("review");
227
232
  var pendingPath = params.get("pending");
228
233
  var inferredRoot = inferMemoryRoot(graphPaths[0] || "");
234
+ if (!inboxPath && inferredRoot) inboxPath = inferredRoot + "/inbox.json";
229
235
  if (!reviewPath && inferredRoot) reviewPath = inferredRoot + "/review/memory-review.md";
230
236
  if (!pendingPath && inferredRoot) pendingPath = inferredRoot + "/pending";
231
237
  var jobs = [];
232
238
  if (metricsPath) jobs.push(fetchJson(metricsPath).then(function (metrics) { state.metrics = metrics; }));
239
+ if (inboxPath) jobs.push(fetchJson(inboxPath).then(function (inbox) { state.inbox = inbox; }).catch(function () { state.inbox = null; }));
233
240
  if (reviewPath) jobs.push(fetchText(reviewPath).then(function (text) { state.reviewText = text; }).catch(function () { state.reviewText = ""; }));
234
241
  if (pendingPath) jobs.push(loadPending(pendingPath).then(function (packets) { state.pendingPackets = packets; }));
235
242
  if (!graphPaths.length && !jobs.length) {
@@ -263,10 +270,12 @@
263
270
  Promise.all([
264
271
  fetchJson("./data/kage/graph.json"),
265
272
  fetchJson("./data/kage/code_graph/graph.json"),
266
- fetchJson("./data/kage/metrics.json").catch(function () { return null; })
273
+ fetchJson("./data/kage/metrics.json").catch(function () { return null; }),
274
+ fetchJson("./data/kage/inbox.json").catch(function () { return null; })
267
275
  ]).then(function (items) {
268
276
  var merged = mergeNormalizedGraphs([normalizeGraph(items[0]), normalizeGraph(items[1])]);
269
277
  state.metrics = items[2];
278
+ state.inbox = items[3];
270
279
  loadNormalizedGraph(merged, "Kage repo graph");
271
280
  setAutoLoad("Kage repo graph loaded", true);
272
281
  }).catch(function () {
@@ -346,14 +355,64 @@
346
355
  var entities = new Map();
347
356
  var edges = new Map();
348
357
  var episodes = new Map();
358
+ var aliasToCodeId = new Map();
349
359
  graphs.forEach(function (graph) {
350
- graph.entities.forEach(function (entity) { entities.set(entity.id, entity); });
351
- graph.edges.forEach(function (edge) { edges.set(edge.id, edge); });
360
+ graph.entities.forEach(function (entity) {
361
+ if (entity.graph_kind !== "code") return;
362
+ aliasToCodeId.set(entity.id, entity.id);
363
+ (entity.aliases || []).forEach(function (alias) { aliasToCodeId.set(alias, entity.id); });
364
+ });
365
+ });
366
+ var remappedIds = new Map();
367
+ graphs.forEach(function (graph) {
368
+ graph.entities.forEach(function (entity) {
369
+ var id = canonicalEntityId(entity, aliasToCodeId);
370
+ remappedIds.set(entity.id, id);
371
+ entities.set(id, mergeViewerEntity(entities.get(id), Object.assign({}, entity, {
372
+ id: id,
373
+ aliases: unique([entity.id].concat(entity.aliases || []))
374
+ })));
375
+ });
376
+ });
377
+ graphs.forEach(function (graph) {
378
+ graph.edges.forEach(function (edge) {
379
+ var from = remappedIds.get(edge.from) || edge.from;
380
+ var to = remappedIds.get(edge.to) || edge.to;
381
+ var memoryCodeLink = Boolean(edge.memory_code_link || isMemoryCodeRelation(edge.relation));
382
+ var id = edge.id + ":" + from + ":" + to;
383
+ edges.set(id, Object.assign({}, edge, {
384
+ id: id,
385
+ from: from,
386
+ to: to,
387
+ memory_code_link: memoryCodeLink
388
+ }));
389
+ });
352
390
  graph.episodes.forEach(function (episode) { episodes.set(episode.id, episode); });
353
391
  });
354
392
  return { entities: Array.from(entities.values()), edges: Array.from(edges.values()), episodes: Array.from(episodes.values()) };
355
393
  }
356
394
 
395
+ function canonicalEntityId(entity, aliasToCodeId) {
396
+ if (!entity) return "";
397
+ if (entity.graph_kind === "memory" && ["symbol", "test", "route", "file", "path"].indexOf(entity.type) !== -1) {
398
+ var alias = [entity.id, entity.name].concat(entity.aliases || []).find(function (value) { return aliasToCodeId.has(value); });
399
+ if (alias) return aliasToCodeId.get(alias);
400
+ }
401
+ return aliasToCodeId.get(entity.id) || entity.id;
402
+ }
403
+
404
+ function mergeViewerEntity(existing, next) {
405
+ if (!existing) return next;
406
+ var graphKinds = unique([existing.graph_kind, next.graph_kind].concat(existing.graph_kinds || []).concat(next.graph_kinds || []));
407
+ var preferred = next.graph_kind === "code" ? next : existing;
408
+ return Object.assign({}, existing, preferred, {
409
+ aliases: unique((existing.aliases || []).concat(next.aliases || [])),
410
+ evidence: unique((existing.evidence || []).concat(next.evidence || [])),
411
+ graph_kind: graphKinds.indexOf("code") !== -1 ? "code" : existing.graph_kind,
412
+ graph_kinds: graphKinds
413
+ });
414
+ }
415
+
357
416
  function normalizeGraph(graph) {
358
417
  if (Array.isArray(graph.entities) && Array.isArray(graph.edges)) {
359
418
  return {
@@ -402,7 +461,7 @@
402
461
  graph_kind: "code",
403
462
  name: file.path,
404
463
  summary: file.kind + " file, " + file.language + ", " + file.line_count + " lines",
405
- aliases: [file.hash],
464
+ aliases: [file.hash, file.path],
406
465
  evidence: []
407
466
  });
408
467
  });
@@ -466,6 +525,10 @@
466
525
  return match ? match.id : null;
467
526
  }
468
527
 
528
+ function isMemoryCodeRelation(relation) {
529
+ return MEMORY_CODE_RELATIONS.has(String(relation || ""));
530
+ }
531
+
469
532
  function populateFilters() {
470
533
  replaceOptions(els.typeFilter, "All types", unique(state.entities.map(function (entity) {
471
534
  return entity.type || "unknown";
@@ -473,6 +536,9 @@
473
536
  replaceOptions(els.relationFilter, "All relations", unique(state.edges.map(function (edge) {
474
537
  return edge.relation || "related";
475
538
  })));
539
+ if (state.edges.some(function (edge) { return edge.memory_code_link; })) {
540
+ els.relationFilter.appendChild(new Option("Memory-Code links", "__memory_code__"));
541
+ }
476
542
  }
477
543
 
478
544
  function replaceOptions(select, label, values) {
@@ -540,7 +606,7 @@
540
606
  var fromMatched = matchedEntityIds.has(edge.from);
541
607
  var toMatched = matchedEntityIds.has(edge.to);
542
608
  var edgeMatchesSearch = matchesSearchQuery(edge, query);
543
- var passesRelation = !relation || edge.relation === relation;
609
+ var passesRelation = !relation || (relation === "__memory_code__" ? edge.memory_code_link : edge.relation === relation);
544
610
  if (passesRelation && (edgeMatchesSearch || fromMatched || toMatched)) {
545
611
  matchedEdgeIds.add(edge.id);
546
612
  if (!type) {
@@ -1093,6 +1159,7 @@
1093
1159
  }
1094
1160
 
1095
1161
  function edgeThemeColor(edge, fromEntity, toEntity) {
1162
+ if (edge.memory_code_link || isMemoryCodeRelation(edge.relation)) return graphPalette.bridge;
1096
1163
  if (edge.relation && /test|covers/i.test(edge.relation)) return graphPalette.amber;
1097
1164
  if (edge.relation && /invalid|risk|missing|bug/i.test(edge.relation)) return graphPalette.danger;
1098
1165
  if (isDependencyEntity(fromEntity) || isDependencyEntity(toEntity)) return graphPalette.dependency;
@@ -1121,7 +1188,7 @@
1121
1188
  var connected = selectedEdgeId === edge.id || connectedIds.edges.has(edge.id);
1122
1189
  var line = svgEl("path", {
1123
1190
  d: edgePath(from, to),
1124
- class: classNames("edge-line", "review-" + reviewStatus(edge).replace(/\s+/g, "-"), connected && "connected", selectedEdgeId === edge.id && "selected")
1191
+ class: classNames("edge-line", edge.memory_code_link && "memory-code-link", "review-" + reviewStatus(edge).replace(/\s+/g, "-"), connected && "connected", selectedEdgeId === edge.id && "selected")
1125
1192
  });
1126
1193
  var hit = svgEl("path", {
1127
1194
  d: edgePath(from, to),
@@ -1385,14 +1452,54 @@
1385
1452
  function renderReviewQueue() {
1386
1453
  if (!els.reviewList) return;
1387
1454
  var packets = state.pendingPackets || [];
1388
- els.reviewCount.textContent = String(packets.length);
1455
+ var inbox = state.inbox;
1456
+ var inboxItems = inbox && Array.isArray(inbox.items) ? inbox.items : [];
1457
+ els.reviewCount.textContent = String(packets.length + inboxItems.length);
1389
1458
  els.reviewList.textContent = "";
1390
- if (!packets.length && !state.reviewText) {
1459
+ if (!packets.length && !inboxItems.length && !state.reviewText) {
1391
1460
  els.reviewList.className = "review-list details-empty";
1392
1461
  els.reviewList.textContent = "No pending packets loaded. Launch with `kage viewer --project <repo>` to load review context automatically.";
1393
1462
  return;
1394
1463
  }
1395
1464
  els.reviewList.className = "review-list";
1465
+ if (inbox) {
1466
+ var summary = document.createElement("div");
1467
+ summary.className = "review-item";
1468
+ var counts = inbox.counts || {};
1469
+ summary.innerHTML = [
1470
+ "<div class=\"review-title\"></div>",
1471
+ "<div class=\"review-meta\"></div>",
1472
+ "<div class=\"review-summary\"></div>",
1473
+ "<div class=\"review-risks\"></div>"
1474
+ ].join("");
1475
+ summary.querySelector(".review-title").textContent = "Memory inbox";
1476
+ summary.querySelector(".review-meta").textContent = [
1477
+ "pending " + (counts.pending || 0),
1478
+ "stale " + (counts.stale || 0),
1479
+ "duplicates " + (counts.duplicates || 0),
1480
+ "missing context " + (counts.missing_context || 0)
1481
+ ].join(" | ");
1482
+ summary.querySelector(".review-summary").textContent = Array.isArray(inbox.recommendations) && inbox.recommendations.length
1483
+ ? inbox.recommendations.slice(0, 2).join(" ")
1484
+ : "No inbox recommendations.";
1485
+ summary.querySelector(".review-risks").textContent = inbox.ok ? "ready for handoff" : "requires review";
1486
+ els.reviewList.appendChild(summary);
1487
+ }
1488
+ inboxItems.slice(0, 20).forEach(function (entry) {
1489
+ var item = document.createElement("div");
1490
+ item.className = "review-item";
1491
+ item.innerHTML = [
1492
+ "<div class=\"review-title\"></div>",
1493
+ "<div class=\"review-meta\"></div>",
1494
+ "<div class=\"review-summary\"></div>",
1495
+ "<div class=\"review-risks\"></div>"
1496
+ ].join("");
1497
+ item.querySelector(".review-title").textContent = entry.title || entry.summary || entry.kind;
1498
+ item.querySelector(".review-meta").textContent = [entry.kind, entry.severity, entry.type, entry.status].filter(Boolean).join(" | ");
1499
+ item.querySelector(".review-summary").textContent = entry.action || entry.summary || "";
1500
+ item.querySelector(".review-risks").textContent = Array.isArray(entry.reasons) && entry.reasons.length ? "reasons: " + entry.reasons.slice(0, 3).join(", ") : "reasons: none";
1501
+ els.reviewList.appendChild(item);
1502
+ });
1396
1503
  packets.forEach(function (packet) {
1397
1504
  var item = document.createElement("div");
1398
1505
  item.className = "review-item";
@@ -1982,6 +2089,15 @@
1982
2089
  return element;
1983
2090
  }
1984
2091
 
2092
+ if (typeof globalThis !== "undefined") {
2093
+ globalThis.__KAGE_VIEWER_TEST__ = {
2094
+ normalizeGraph: normalizeGraph,
2095
+ normalizeCodeGraph: normalizeCodeGraph,
2096
+ mergeNormalizedGraphs: mergeNormalizedGraphs,
2097
+ isMemoryCodeRelation: isMemoryCodeRelation
2098
+ };
2099
+ }
2100
+
1985
2101
  function showError(message) {
1986
2102
  els.graphSummary.textContent = message;
1987
2103
  }
package/viewer/index.html CHANGED
@@ -4,7 +4,7 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>Kage Memory Terminal</title>
7
- <link rel="stylesheet" href="./styles.css?v=5">
7
+ <link rel="stylesheet" href="./styles.css?v=6">
8
8
  </head>
9
9
  <body>
10
10
  <header class="app-header">
@@ -125,7 +125,7 @@
125
125
 
126
126
  <section class="review-panel" aria-label="Review queue">
127
127
  <div class="panel-heading">
128
- <h2>Review Queue</h2>
128
+ <h2>Memory Inbox</h2>
129
129
  <span id="reviewCount">0</span>
130
130
  </div>
131
131
  <div id="reviewList" class="review-list"></div>
@@ -156,6 +156,6 @@
156
156
  </section>
157
157
  </main>
158
158
 
159
- <script src="./app.js?v=5"></script>
159
+ <script src="./app.js?v=6"></script>
160
160
  </body>
161
161
  </html>
package/viewer/styles.css CHANGED
@@ -527,6 +527,7 @@ input:focus, select:focus, button:focus, .file-picker:focus-within {
527
527
  .item-meta { color: var(--terminal-dim); font-size: 11px; overflow-wrap: anywhere; }
528
528
 
529
529
  .edge-line { stroke: var(--edge); stroke-width: 1.35; marker-end: url(#arrow); opacity: 0.50; }
530
+ .edge-line.memory-code-link { stroke: #d8ff5f; stroke-width: 2.2; opacity: 0.88; }
530
531
  .edge-line.filtered { opacity: 0.05; }
531
532
  .edge-line.selected, .edge-line.connected { stroke: var(--terminal-strong); stroke-width: 2.8; opacity: 1; }
532
533
  .edge-line.review-low-confidence { stroke-dasharray: 5 4; stroke: var(--warn); }