@kage-core/kage-graph-mcp 1.1.12 → 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.
- package/README.md +71 -12
- package/dist/cli.js +102 -0
- package/dist/daemon.js +11 -3
- package/dist/graph-registry.js +167 -0
- package/dist/index.js +73 -0
- package/dist/kernel.js +672 -25
- package/dist/registry/index.js +1 -1
- package/package.json +2 -2
- package/viewer/app.js +124 -8
- package/viewer/index.html +3 -3
- package/viewer/styles.css +1 -0
package/dist/registry/index.js
CHANGED
|
@@ -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.
|
|
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": [
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"ai-agents",
|
|
34
34
|
"code-graph"
|
|
35
35
|
],
|
|
36
|
-
"license": "
|
|
36
|
+
"license": "GPL-3.0-only",
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@modelcontextprotocol/sdk": "^1.10.2",
|
|
39
39
|
"typescript": "^5.0.0"
|
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) {
|
|
351
|
-
|
|
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
|
-
|
|
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=
|
|
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>
|
|
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=
|
|
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); }
|