@kage-core/kage-graph-mcp 1.1.36 → 1.1.38
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 +101 -2
- package/dist/cli.js +451 -3
- package/dist/daemon.js +344 -7
- package/dist/index.js +382 -3
- package/dist/kernel.js +3790 -76
- package/package.json +1 -1
- package/viewer/app.js +1448 -86
- package/viewer/data.html +8 -15
- package/viewer/graph.html +8 -15
- package/viewer/index.html +19 -15
- package/viewer/intel.html +8 -15
- package/viewer/memory.html +79 -15
- package/viewer/owners.html +8 -15
- package/viewer/review.html +20 -16
- package/viewer/styles.css +751 -124
package/viewer/app.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
visibleEntityIds: new Set(),
|
|
14
14
|
visibleEdgeIds: new Set(),
|
|
15
15
|
selected: null,
|
|
16
|
+
revealSelection: false,
|
|
16
17
|
viewerPage: "overview",
|
|
17
18
|
viewerSection: "overview",
|
|
18
19
|
viewerAction: null,
|
|
@@ -29,11 +30,24 @@
|
|
|
29
30
|
quality: null,
|
|
30
31
|
benchmark: null,
|
|
31
32
|
contributors: null,
|
|
33
|
+
profile: null,
|
|
34
|
+
capabilities: null,
|
|
35
|
+
slots: null,
|
|
32
36
|
decisions: null,
|
|
33
37
|
risk: null,
|
|
34
38
|
moduleHealth: null,
|
|
35
39
|
graphInsights: null,
|
|
36
|
-
|
|
40
|
+
xray: null,
|
|
41
|
+
workspace: null,
|
|
42
|
+
sessions: null,
|
|
43
|
+
replay: null,
|
|
44
|
+
memoryAccess: null,
|
|
45
|
+
memoryAudit: null,
|
|
46
|
+
handoff: null,
|
|
47
|
+
lifecycle: null,
|
|
48
|
+
timeline: null,
|
|
49
|
+
lineage: null,
|
|
50
|
+
setup: null
|
|
37
51
|
},
|
|
38
52
|
pendingPackets: [],
|
|
39
53
|
reviewText: "",
|
|
@@ -169,10 +183,31 @@
|
|
|
169
183
|
edgeCount: document.getElementById("edgeCount"),
|
|
170
184
|
reviewCount: document.getElementById("reviewCount"),
|
|
171
185
|
dashboardStats: document.getElementById("dashboardStats"),
|
|
186
|
+
repoXray: document.getElementById("repoXray"),
|
|
187
|
+
repoXrayStatus: document.getElementById("repoXrayStatus"),
|
|
188
|
+
repoXrayScript: document.getElementById("repoXrayScript"),
|
|
189
|
+
repoXrayLayers: document.getElementById("repoXrayLayers"),
|
|
172
190
|
dashboardCharts: document.getElementById("dashboardCharts"),
|
|
173
191
|
memoryStatus: document.getElementById("memoryStatus"),
|
|
174
192
|
memoryStats: document.getElementById("memoryStats"),
|
|
175
193
|
memoryOverview: document.getElementById("memoryOverview"),
|
|
194
|
+
lifecycleStatus: document.getElementById("lifecycleStatus"),
|
|
195
|
+
lifecycleSummary: document.getElementById("lifecycleSummary"),
|
|
196
|
+
lifecycleList: document.getElementById("lifecycleList"),
|
|
197
|
+
memoryReviewStatus: document.getElementById("memoryReviewStatus"),
|
|
198
|
+
memoryReviewActions: document.getElementById("memoryReviewActions"),
|
|
199
|
+
memoryTimelineStatus: document.getElementById("memoryTimelineStatus"),
|
|
200
|
+
memoryTimelineSummary: document.getElementById("memoryTimelineSummary"),
|
|
201
|
+
memoryTimelineList: document.getElementById("memoryTimelineList"),
|
|
202
|
+
memoryAuditStatus: document.getElementById("memoryAuditStatus"),
|
|
203
|
+
memoryAuditSummary: document.getElementById("memoryAuditSummary"),
|
|
204
|
+
memoryAuditList: document.getElementById("memoryAuditList"),
|
|
205
|
+
memoryLineageStatus: document.getElementById("memoryLineageStatus"),
|
|
206
|
+
memoryLineageSummary: document.getElementById("memoryLineageSummary"),
|
|
207
|
+
memoryLineageList: document.getElementById("memoryLineageList"),
|
|
208
|
+
sessionCaptureStatus: document.getElementById("sessionCaptureStatus"),
|
|
209
|
+
sessionCaptureSummary: document.getElementById("sessionCaptureSummary"),
|
|
210
|
+
sessionCaptureList: document.getElementById("sessionCaptureList"),
|
|
176
211
|
memorySearch: document.getElementById("memorySearch"),
|
|
177
212
|
memoryFilter: document.getElementById("memoryFilter"),
|
|
178
213
|
memoryList: document.getElementById("memoryList"),
|
|
@@ -180,6 +215,9 @@
|
|
|
180
215
|
ownersSummary: document.getElementById("ownersSummary"),
|
|
181
216
|
ownersList: document.getElementById("ownersList"),
|
|
182
217
|
reviewOverview: document.getElementById("reviewOverview"),
|
|
218
|
+
handoffStatus: document.getElementById("handoffStatus"),
|
|
219
|
+
handoffSummary: document.getElementById("handoffSummary"),
|
|
220
|
+
handoffList: document.getElementById("handoffList"),
|
|
183
221
|
reviewList: document.getElementById("reviewList"),
|
|
184
222
|
proofOverview: document.getElementById("proofOverview"),
|
|
185
223
|
proofStatus: document.getElementById("proofStatus"),
|
|
@@ -209,9 +247,9 @@
|
|
|
209
247
|
summary: "Find repo lore by file, feature, bug, command, or decision. Pick a packet to see linked code."
|
|
210
248
|
},
|
|
211
249
|
intel: {
|
|
212
|
-
eyebrow: "kage://
|
|
213
|
-
title: "
|
|
214
|
-
summary: "
|
|
250
|
+
eyebrow: "kage://before-edit",
|
|
251
|
+
title: "Before You Edit",
|
|
252
|
+
summary: "What can go wrong, why it matters, and the first safety step before an agent changes code."
|
|
215
253
|
},
|
|
216
254
|
review: {
|
|
217
255
|
eyebrow: "kage://review",
|
|
@@ -256,10 +294,12 @@
|
|
|
256
294
|
els.pathFromInput.addEventListener("keydown", function (event) { if (event.key === "Enter") findDependencyPath(); });
|
|
257
295
|
els.pathToInput.addEventListener("keydown", function (event) { if (event.key === "Enter") findDependencyPath(); });
|
|
258
296
|
els.viewMode.addEventListener("change", function () { clearGraphActionFilter(); render(); });
|
|
259
|
-
els.renderMode
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
297
|
+
if (els.renderMode) {
|
|
298
|
+
els.renderMode.addEventListener("change", function () {
|
|
299
|
+
state.lastVisibleSignature = "";
|
|
300
|
+
render();
|
|
301
|
+
});
|
|
302
|
+
}
|
|
263
303
|
els.typeFilter.addEventListener("change", function () { clearGraphActionFilter(); render(); });
|
|
264
304
|
els.relationFilter.addEventListener("change", function () { clearGraphActionFilter(); render(); });
|
|
265
305
|
els.scopeFilter.addEventListener("change", render);
|
|
@@ -298,7 +338,7 @@
|
|
|
298
338
|
function resetGraphView() {
|
|
299
339
|
els.searchInput.value = "";
|
|
300
340
|
els.viewMode.value = "combined";
|
|
301
|
-
els.renderMode.value = "2d";
|
|
341
|
+
if (els.renderMode) els.renderMode.value = "2d";
|
|
302
342
|
els.typeFilter.value = "";
|
|
303
343
|
els.relationFilter.value = "";
|
|
304
344
|
els.scopeFilter.value = "signal";
|
|
@@ -466,10 +506,12 @@
|
|
|
466
506
|
|
|
467
507
|
function selectEntity(id, openInspector) {
|
|
468
508
|
state.selected = { kind: "entity", id: id };
|
|
509
|
+
state.revealSelection = Boolean(openInspector);
|
|
469
510
|
}
|
|
470
511
|
|
|
471
512
|
function selectEdge(id, openInspector) {
|
|
472
513
|
state.selected = { kind: "edge", id: id };
|
|
514
|
+
state.revealSelection = Boolean(openInspector);
|
|
473
515
|
}
|
|
474
516
|
|
|
475
517
|
function handleFile(event) {
|
|
@@ -562,11 +604,24 @@
|
|
|
562
604
|
var qualityPath = params.get("quality");
|
|
563
605
|
var benchmarkPath = params.get("benchmark");
|
|
564
606
|
var contributorsPath = params.get("contributors");
|
|
607
|
+
var profilePath = params.get("profile") || params.get("projectProfile") || params.get("project-profile");
|
|
608
|
+
var capabilitiesPath = params.get("capabilities") || params.get("capabilityAudit") || params.get("capability-audit");
|
|
609
|
+
var slotsPath = params.get("slots") || params.get("contextSlots") || params.get("context-slots");
|
|
565
610
|
var decisionsPath = params.get("decisions");
|
|
566
611
|
var riskPath = params.get("risk");
|
|
567
612
|
var moduleHealthPath = params.get("moduleHealth") || params.get("module-health");
|
|
568
613
|
var graphInsightsPath = params.get("graphInsights") || params.get("graph-insights");
|
|
614
|
+
var xrayPath = params.get("xray") || params.get("repoXray") || params.get("repo-xray");
|
|
569
615
|
var workspacePath = params.get("workspace");
|
|
616
|
+
var sessionsPath = params.get("sessions");
|
|
617
|
+
var replayPath = params.get("replay") || params.get("sessionReplay") || params.get("session-replay");
|
|
618
|
+
var memoryAccessPath = params.get("memoryAccess") || params.get("memory-access");
|
|
619
|
+
var memoryAuditPath = params.get("memoryAudit") || params.get("memory-audit") || params.get("auditLog") || params.get("audit-log");
|
|
620
|
+
var handoffPath = params.get("handoff") || params.get("memoryHandoff") || params.get("memory-handoff");
|
|
621
|
+
var lifecyclePath = params.get("lifecycle") || params.get("memoryLifecycle") || params.get("memory-lifecycle");
|
|
622
|
+
var timelinePath = params.get("timeline") || params.get("memoryTimeline") || params.get("memory-timeline");
|
|
623
|
+
var lineagePath = params.get("lineage") || params.get("memoryLineage") || params.get("memory-lineage");
|
|
624
|
+
var setupPath = params.get("setup") || params.get("setupDoctor") || params.get("setup-doctor");
|
|
570
625
|
var inferredRoot = inferMemoryRoot(graphPaths[0] || "");
|
|
571
626
|
if (!inboxPath && inferredRoot) inboxPath = inferredRoot + "/inbox.json";
|
|
572
627
|
if (!reviewPath && inferredRoot) reviewPath = inferredRoot + "/review/memory-review.md";
|
|
@@ -575,11 +630,24 @@
|
|
|
575
630
|
if (!qualityPath) qualityPath = inferredRoot + "/reports/quality.json";
|
|
576
631
|
if (!benchmarkPath) benchmarkPath = inferredRoot + "/reports/benchmark.json";
|
|
577
632
|
if (!contributorsPath) contributorsPath = inferredRoot + "/reports/contributors.json";
|
|
633
|
+
if (!profilePath) profilePath = inferredRoot + "/reports/profile.json";
|
|
634
|
+
if (!capabilitiesPath) capabilitiesPath = inferredRoot + "/reports/capabilities.json";
|
|
635
|
+
if (!slotsPath) slotsPath = inferredRoot + "/reports/context-slots.json";
|
|
578
636
|
if (!decisionsPath) decisionsPath = inferredRoot + "/reports/decisions.json";
|
|
579
637
|
if (!riskPath) riskPath = inferredRoot + "/reports/risk.json";
|
|
580
638
|
if (!moduleHealthPath) moduleHealthPath = inferredRoot + "/reports/module-health.json";
|
|
581
639
|
if (!graphInsightsPath) graphInsightsPath = inferredRoot + "/reports/graph-insights.json";
|
|
640
|
+
if (!xrayPath) xrayPath = inferredRoot + "/reports/xray.json";
|
|
582
641
|
if (!workspacePath) workspacePath = inferredRoot + "/reports/workspace.json";
|
|
642
|
+
if (!sessionsPath) sessionsPath = inferredRoot + "/reports/sessions.json";
|
|
643
|
+
if (!replayPath) replayPath = inferredRoot + "/reports/replay.json";
|
|
644
|
+
if (!memoryAccessPath) memoryAccessPath = inferredRoot + "/reports/memory-access.json";
|
|
645
|
+
if (!memoryAuditPath) memoryAuditPath = inferredRoot + "/reports/memory-audit.json";
|
|
646
|
+
if (!handoffPath) handoffPath = inferredRoot + "/reports/handoff.json";
|
|
647
|
+
if (!lifecyclePath) lifecyclePath = inferredRoot + "/reports/lifecycle.json";
|
|
648
|
+
if (!timelinePath) timelinePath = inferredRoot + "/reports/timeline.json";
|
|
649
|
+
if (!lineagePath) lineagePath = inferredRoot + "/reports/lineage.json";
|
|
650
|
+
if (!setupPath) setupPath = inferredRoot + "/reports/setup.json";
|
|
583
651
|
}
|
|
584
652
|
var jobs = [];
|
|
585
653
|
if (metricsPath) jobs.push(fetchJson(metricsPath).then(function (metrics) { state.metrics = metrics; }));
|
|
@@ -589,11 +657,24 @@
|
|
|
589
657
|
if (qualityPath) jobs.push(fetchJson(qualityPath).then(function (report) { state.reports.quality = report; }).catch(function () { state.reports.quality = null; }));
|
|
590
658
|
if (benchmarkPath) jobs.push(fetchJson(benchmarkPath).then(function (report) { state.reports.benchmark = report; }).catch(function () { state.reports.benchmark = null; }));
|
|
591
659
|
if (contributorsPath) jobs.push(fetchJson(contributorsPath).then(function (report) { state.reports.contributors = report; }).catch(function () { state.reports.contributors = null; }));
|
|
660
|
+
if (profilePath) jobs.push(fetchJson(profilePath).then(function (report) { state.reports.profile = report; }).catch(function () { state.reports.profile = null; }));
|
|
661
|
+
if (capabilitiesPath) jobs.push(fetchJson(capabilitiesPath).then(function (report) { state.reports.capabilities = report; }).catch(function () { state.reports.capabilities = null; }));
|
|
662
|
+
if (slotsPath) jobs.push(fetchJson(slotsPath).then(function (report) { state.reports.slots = report; }).catch(function () { state.reports.slots = null; }));
|
|
592
663
|
if (decisionsPath) jobs.push(fetchJson(decisionsPath).then(function (report) { state.reports.decisions = report; }).catch(function () { state.reports.decisions = null; }));
|
|
593
664
|
if (riskPath) jobs.push(fetchJson(riskPath).then(function (report) { state.reports.risk = report; }).catch(function () { state.reports.risk = null; }));
|
|
594
665
|
if (moduleHealthPath) jobs.push(fetchJson(moduleHealthPath).then(function (report) { state.reports.moduleHealth = report; }).catch(function () { state.reports.moduleHealth = null; }));
|
|
595
666
|
if (graphInsightsPath) jobs.push(fetchJson(graphInsightsPath).then(function (report) { state.reports.graphInsights = report; }).catch(function () { state.reports.graphInsights = null; }));
|
|
667
|
+
if (xrayPath) jobs.push(fetchJson(xrayPath).then(function (report) { state.reports.xray = report; }).catch(function () { state.reports.xray = null; }));
|
|
596
668
|
if (workspacePath) jobs.push(fetchJson(workspacePath).then(function (report) { state.reports.workspace = report; }).catch(function () { state.reports.workspace = null; }));
|
|
669
|
+
if (sessionsPath) jobs.push(fetchJson(sessionsPath).then(function (report) { state.reports.sessions = report; }).catch(function () { state.reports.sessions = null; }));
|
|
670
|
+
if (replayPath) jobs.push(fetchJson(replayPath).then(function (report) { state.reports.replay = report; }).catch(function () { state.reports.replay = null; }));
|
|
671
|
+
if (memoryAccessPath) jobs.push(fetchJson(memoryAccessPath).then(function (report) { state.reports.memoryAccess = report; }).catch(function () { state.reports.memoryAccess = null; }));
|
|
672
|
+
if (memoryAuditPath) jobs.push(fetchJson(memoryAuditPath).then(function (report) { state.reports.memoryAudit = report; }).catch(function () { state.reports.memoryAudit = null; }));
|
|
673
|
+
if (handoffPath) jobs.push(fetchJson(handoffPath).then(function (report) { state.reports.handoff = report; }).catch(function () { state.reports.handoff = null; }));
|
|
674
|
+
if (lifecyclePath) jobs.push(fetchJson(lifecyclePath).then(function (report) { state.reports.lifecycle = report; }).catch(function () { state.reports.lifecycle = null; }));
|
|
675
|
+
if (timelinePath) jobs.push(fetchJson(timelinePath).then(function (report) { state.reports.timeline = report; }).catch(function () { state.reports.timeline = null; }));
|
|
676
|
+
if (lineagePath) jobs.push(fetchJson(lineagePath).then(function (report) { state.reports.lineage = report; }).catch(function () { state.reports.lineage = null; }));
|
|
677
|
+
if (setupPath) jobs.push(fetchJson(setupPath).then(function (report) { state.reports.setup = report; }).catch(function () { state.reports.setup = null; }));
|
|
597
678
|
if (!graphPaths.length && !jobs.length) {
|
|
598
679
|
loadHostedDefault();
|
|
599
680
|
return;
|
|
@@ -626,20 +707,46 @@
|
|
|
626
707
|
fetchJson("./data/kage/inbox.json").catch(function () { return null; }),
|
|
627
708
|
fetchJson("./data/kage/reports/risk.json").catch(function () { return null; }),
|
|
628
709
|
fetchJson("./data/kage/reports/contributors.json").catch(function () { return null; }),
|
|
710
|
+
fetchJson("./data/kage/reports/profile.json").catch(function () { return null; }),
|
|
711
|
+
fetchJson("./data/kage/reports/capabilities.json").catch(function () { return null; }),
|
|
712
|
+
fetchJson("./data/kage/reports/context-slots.json").catch(function () { return null; }),
|
|
629
713
|
fetchJson("./data/kage/reports/decisions.json").catch(function () { return null; }),
|
|
630
714
|
fetchJson("./data/kage/reports/module-health.json").catch(function () { return null; }),
|
|
631
715
|
fetchJson("./data/kage/reports/graph-insights.json").catch(function () { return null; }),
|
|
632
|
-
fetchJson("./data/kage/reports/
|
|
716
|
+
fetchJson("./data/kage/reports/xray.json").catch(function () { return null; }),
|
|
717
|
+
fetchJson("./data/kage/reports/workspace.json").catch(function () { return null; }),
|
|
718
|
+
fetchJson("./data/kage/reports/sessions.json").catch(function () { return null; }),
|
|
719
|
+
fetchJson("./data/kage/reports/replay.json").catch(function () { return null; }),
|
|
720
|
+
fetchJson("./data/kage/reports/memory-access.json").catch(function () { return null; }),
|
|
721
|
+
fetchJson("./data/kage/reports/memory-audit.json").catch(function () { return null; }),
|
|
722
|
+
fetchJson("./data/kage/reports/handoff.json").catch(function () { return null; }),
|
|
723
|
+
fetchJson("./data/kage/reports/lifecycle.json").catch(function () { return null; }),
|
|
724
|
+
fetchJson("./data/kage/reports/timeline.json").catch(function () { return null; }),
|
|
725
|
+
fetchJson("./data/kage/reports/lineage.json").catch(function () { return null; }),
|
|
726
|
+
fetchJson("./data/kage/reports/setup.json").catch(function () { return null; })
|
|
633
727
|
]).then(function (items) {
|
|
634
728
|
var merged = mergeNormalizedGraphs([normalizeGraph(items[0]), normalizeGraph(items[1])]);
|
|
635
729
|
state.metrics = items[2];
|
|
636
730
|
state.inbox = items[3];
|
|
637
731
|
state.reports.risk = items[4];
|
|
638
732
|
state.reports.contributors = items[5];
|
|
639
|
-
state.reports.
|
|
640
|
-
state.reports.
|
|
641
|
-
state.reports.
|
|
642
|
-
state.reports.
|
|
733
|
+
state.reports.profile = items[6];
|
|
734
|
+
state.reports.capabilities = items[7];
|
|
735
|
+
state.reports.slots = items[8];
|
|
736
|
+
state.reports.decisions = items[9];
|
|
737
|
+
state.reports.moduleHealth = items[10];
|
|
738
|
+
state.reports.graphInsights = items[11];
|
|
739
|
+
state.reports.xray = items[12];
|
|
740
|
+
state.reports.workspace = items[13];
|
|
741
|
+
state.reports.sessions = items[14];
|
|
742
|
+
state.reports.replay = items[15];
|
|
743
|
+
state.reports.memoryAccess = items[16];
|
|
744
|
+
state.reports.memoryAudit = items[17];
|
|
745
|
+
state.reports.handoff = items[18];
|
|
746
|
+
state.reports.lifecycle = items[19];
|
|
747
|
+
state.reports.timeline = items[20];
|
|
748
|
+
state.reports.lineage = items[21];
|
|
749
|
+
state.reports.setup = items[22];
|
|
643
750
|
loadNormalizedGraph(merged, "Kage repo graph");
|
|
644
751
|
setAutoLoad("Kage repo graph loaded", true);
|
|
645
752
|
}).catch(function () {
|
|
@@ -1383,6 +1490,7 @@
|
|
|
1383
1490
|
|
|
1384
1491
|
renderActiveGraph(graphChanged);
|
|
1385
1492
|
renderPagePanels();
|
|
1493
|
+
revealSelectionIfRequested();
|
|
1386
1494
|
}
|
|
1387
1495
|
|
|
1388
1496
|
function scheduleRender() {
|
|
@@ -2438,6 +2546,18 @@
|
|
|
2438
2546
|
document.body.classList.toggle("has-code-selection", Boolean(entity && entity.graph_kind === "code"));
|
|
2439
2547
|
}
|
|
2440
2548
|
|
|
2549
|
+
function revealSelectionIfRequested() {
|
|
2550
|
+
if (!state.revealSelection) return;
|
|
2551
|
+
state.revealSelection = false;
|
|
2552
|
+
window.requestAnimationFrame(function () {
|
|
2553
|
+
var panel = document.querySelector(".details-panel");
|
|
2554
|
+
if (!panel || window.getComputedStyle(panel).display === "none") return;
|
|
2555
|
+
panel.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
2556
|
+
var selectedRow = document.querySelector("[aria-selected=\"true\"], .node.selected");
|
|
2557
|
+
if (selectedRow && selectedRow.scrollIntoView) selectedRow.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2441
2561
|
function prefillPathFromSelection(silent) {
|
|
2442
2562
|
if (!state.selected || state.selected.kind !== "entity") {
|
|
2443
2563
|
if (!silent) setPathStatus("Select a code node first. Path tracing is for files, symbols, routes, tests, and scripts.", "warn");
|
|
@@ -2877,22 +2997,22 @@
|
|
|
2877
2997
|
var reports = state.reports || {};
|
|
2878
2998
|
var reportCount = Object.keys(reports).filter(function (key) { return reports[key]; }).length;
|
|
2879
2999
|
var risk = reports.risk || {};
|
|
2880
|
-
var riskTargets =
|
|
3000
|
+
var riskTargets = userFacingRiskTargets(risk);
|
|
2881
3001
|
var inboxCounts = state.inbox && state.inbox.counts ? state.inbox.counts : {};
|
|
3002
|
+
var handoff = memoryHandoffSummary(reports.handoff);
|
|
2882
3003
|
var pendingReview = Number(firstNumber(inboxCounts.pending, memoryGraph.pending_packets, (state.pendingPackets || []).length, 0));
|
|
2883
3004
|
var staleFlags = Number(firstNumber(inboxCounts.stale, 0));
|
|
2884
3005
|
var duplicateFlags = Number(firstNumber(inboxCounts.duplicates, memoryGraph.duplicate_candidate_pairs, 0));
|
|
2885
3006
|
var missingContext = Number(firstNumber(inboxCounts.missing_context, 0));
|
|
2886
3007
|
var ownerSilos = Array.isArray(risk.ownership_silos) ? risk.ownership_silos.length : 0;
|
|
2887
|
-
var hotspots =
|
|
2888
|
-
var readiness = dashboardReadiness(metrics, pendingReview, staleFlags, duplicateFlags, missingContext);
|
|
3008
|
+
var hotspots = userFacingRiskHotspots(risk).length;
|
|
3009
|
+
var readiness = handoff || dashboardReadiness(metrics, pendingReview, staleFlags, duplicateFlags, missingContext);
|
|
2889
3010
|
var memoryCoverage = dashboardMemoryCoverage(reports, memoryCodeEdges, memoryGraph, memoryNodes);
|
|
2890
|
-
var riskHealth = riskTargets.length || hotspots ? (riskTargets.length + hotspots) + "
|
|
3011
|
+
var riskHealth = riskTargets.length || hotspots ? (riskTargets.length + hotspots) + " checks" : "No flags";
|
|
2891
3012
|
var statRows = [
|
|
2892
3013
|
["Handoff", readiness.label, readiness.detail, readiness.status],
|
|
2893
3014
|
["Memory", memoryCoverage.label, memoryCoverage.detail, memoryCoverage.status],
|
|
2894
|
-
["
|
|
2895
|
-
["Code map", firstNumber(codeGraph.files, structural.files, countEntitiesByType("file")) + " files", firstNumber(codeGraph.symbols, structural.symbols, codeNodes) + " symbols indexed", "code"]
|
|
3015
|
+
["Before edit", riskHealth, riskTargets.length + " edit areas, " + ownerSilos + " ownership silos", riskTargets.length || ownerSilos || hotspots ? "warn" : "ok"]
|
|
2896
3016
|
];
|
|
2897
3017
|
els.dashboardStats.textContent = "";
|
|
2898
3018
|
statRows.forEach(function (row) {
|
|
@@ -2916,16 +3036,23 @@
|
|
|
2916
3036
|
["Coverage", codeGraph.indexer_coverage_percent != null ? codeGraph.indexer_coverage_percent + "%" : "not loaded"]
|
|
2917
3037
|
]);
|
|
2918
3038
|
setDashboardRows("dashboardIntel", [
|
|
2919
|
-
["
|
|
3039
|
+
["Edit checks", riskTargets.length || "none"],
|
|
2920
3040
|
["Ownership silos", ownerSilos || "none"],
|
|
2921
3041
|
["Decision coverage", reports.decisions && reports.decisions.coverage_percent != null ? reports.decisions.coverage_percent + "%" : "not loaded"]
|
|
2922
3042
|
]);
|
|
2923
|
-
setDashboardRows("dashboardReview", [
|
|
3043
|
+
setDashboardRows("dashboardReview", handoff ? [
|
|
3044
|
+
["Next", handoff.actionLabel],
|
|
3045
|
+
["Open", handoff.openItems ? handoff.openItems + " item" + (handoff.openItems === 1 ? "" : "s") : "none"],
|
|
3046
|
+
["Distill", handoff.distillableSessions ? handoff.distillableSessions + " session" + (handoff.distillableSessions === 1 ? "" : "s") : "none"],
|
|
3047
|
+
["Recent", handoff.recentChanges + " changes"],
|
|
3048
|
+
["Mutations", handoff.recentMutations || "none"]
|
|
3049
|
+
] : [
|
|
2924
3050
|
["Handoff", readiness.label],
|
|
2925
3051
|
["Pending", pendingReview || "none"],
|
|
2926
3052
|
["Stale / duplicate", staleFlags + " / " + duplicateFlags],
|
|
2927
3053
|
["Missing context", missingContext || "none"]
|
|
2928
3054
|
]);
|
|
3055
|
+
renderRepoXray(reports.xray);
|
|
2929
3056
|
renderDashboardCharts({
|
|
2930
3057
|
metrics: metrics,
|
|
2931
3058
|
reports: reports,
|
|
@@ -2940,7 +3067,63 @@
|
|
|
2940
3067
|
missingContext: missingContext,
|
|
2941
3068
|
riskTargets: riskTargets,
|
|
2942
3069
|
ownerSilos: ownerSilos,
|
|
2943
|
-
hotspots: hotspots
|
|
3070
|
+
hotspots: hotspots,
|
|
3071
|
+
handoff: handoff
|
|
3072
|
+
});
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
function renderRepoXray(report) {
|
|
3076
|
+
if (!els.repoXray || !els.repoXrayLayers) return;
|
|
3077
|
+
if (!report || !Array.isArray(report.layers)) {
|
|
3078
|
+
if (els.repoXrayStatus) els.repoXrayStatus.textContent = "not loaded";
|
|
3079
|
+
if (els.repoXrayScript) els.repoXrayScript.textContent = "Open with `kage viewer --project <repo>` after refresh to see the first-use repo map.";
|
|
3080
|
+
els.repoXrayLayers.textContent = "";
|
|
3081
|
+
return;
|
|
3082
|
+
}
|
|
3083
|
+
var layers = report.layers.filter(function (layer) { return Array.isArray(layer.items) && layer.items.length; });
|
|
3084
|
+
if (els.repoXrayStatus) els.repoXrayStatus.textContent = layers.length ? layers.length + " layers" : "empty";
|
|
3085
|
+
if (els.repoXrayScript) {
|
|
3086
|
+
var script = Array.isArray(report.first_use_script) ? report.first_use_script : [];
|
|
3087
|
+
els.repoXrayScript.textContent = script[0] || report.summary || "Kage mapped the repo structure.";
|
|
3088
|
+
}
|
|
3089
|
+
els.repoXrayLayers.textContent = "";
|
|
3090
|
+
if (!layers.length) {
|
|
3091
|
+
els.repoXrayLayers.className = "repo-xray-layers details-empty";
|
|
3092
|
+
els.repoXrayLayers.textContent = "No X-Ray layers have items yet. Run kage refresh so code graph, risk, tests, and memory links are current.";
|
|
3093
|
+
return;
|
|
3094
|
+
}
|
|
3095
|
+
els.repoXrayLayers.className = "repo-xray-layers";
|
|
3096
|
+
layers.slice(0, 6).forEach(function (layer) {
|
|
3097
|
+
var card = document.createElement("article");
|
|
3098
|
+
card.className = "repo-xray-layer";
|
|
3099
|
+
card.innerHTML = [
|
|
3100
|
+
"<div class=\"repo-xray-layer-head\"><div><strong></strong><span></span></div><em></em></div>",
|
|
3101
|
+
"<div class=\"repo-xray-items\"></div>"
|
|
3102
|
+
].join("");
|
|
3103
|
+
card.querySelector("strong").textContent = layer.title || layer.id || "Layer";
|
|
3104
|
+
card.querySelector("span").textContent = layer.summary || "";
|
|
3105
|
+
card.querySelector("em").textContent = String(layer.items.length || 0);
|
|
3106
|
+
var list = card.querySelector(".repo-xray-items");
|
|
3107
|
+
layer.items.slice(0, 3).forEach(function (item) {
|
|
3108
|
+
var button = document.createElement("button");
|
|
3109
|
+
button.type = "button";
|
|
3110
|
+
button.className = classNames("repo-xray-item", item.status && "repo-xray-item-" + safeCssName(item.status));
|
|
3111
|
+
button.innerHTML = [
|
|
3112
|
+
"<span><strong></strong><em></em></span>",
|
|
3113
|
+
"<i></i>"
|
|
3114
|
+
].join("");
|
|
3115
|
+
button.querySelector("strong").textContent = item.label || item.path || "signal";
|
|
3116
|
+
button.querySelector("em").textContent = Array.isArray(item.evidence) && item.evidence.length ? item.evidence.slice(0, 2).join("; ") : item.action || "";
|
|
3117
|
+
button.querySelector("i").style.width = clamp(Number(item.strength || 0), 4, 100) + "%";
|
|
3118
|
+
if (item.path) {
|
|
3119
|
+
button.title = "Focus " + item.path + " in the graph";
|
|
3120
|
+
button.addEventListener("click", function () {
|
|
3121
|
+
focusGraphPath(item.path);
|
|
3122
|
+
});
|
|
3123
|
+
}
|
|
3124
|
+
list.appendChild(button);
|
|
3125
|
+
});
|
|
3126
|
+
els.repoXrayLayers.appendChild(card);
|
|
2944
3127
|
});
|
|
2945
3128
|
}
|
|
2946
3129
|
|
|
@@ -2956,24 +3139,315 @@
|
|
|
2956
3139
|
});
|
|
2957
3140
|
var memoryGrounding = approvedPackets ? Math.round(linkedPacketIds.size / approvedPackets * 100) : 0;
|
|
2958
3141
|
var sourceCoverage = Number(firstNumber(data.codeGraph.indexer_coverage_percent, 0));
|
|
2959
|
-
var blockers = data.pendingReview + data.staleFlags + data.duplicateFlags + data.missingContext;
|
|
3142
|
+
var blockers = data.handoff ? data.handoff.openItems : data.pendingReview + data.staleFlags + data.duplicateFlags + data.missingContext;
|
|
2960
3143
|
var riskSignals = data.riskTargets.length + data.ownerSilos + data.hotspots;
|
|
2961
|
-
|
|
2962
|
-
|
|
3144
|
+
var retrieval = benchmarkRetrievalSummary(data.reports && data.reports.benchmark);
|
|
3145
|
+
var scale = benchmarkScaleSummary(data.reports && data.reports.benchmark);
|
|
3146
|
+
var access = memoryAccessSummary(data.reports && data.reports.memoryAccess, approvedPackets);
|
|
3147
|
+
var audit = memoryAuditSummary(data.reports && data.reports.memoryAudit);
|
|
3148
|
+
var lifecycle = memoryLifecycleSummary(data.reports && data.reports.lifecycle);
|
|
3149
|
+
var timeline = memoryTimelineSummary(data.reports && data.reports.timeline);
|
|
3150
|
+
var lineage = memoryLineageSummary(data.reports && data.reports.lineage);
|
|
3151
|
+
var setup = setupDoctorSummary(data.reports && data.reports.setup);
|
|
3152
|
+
var profile = projectProfileSummary(data.reports && data.reports.profile);
|
|
3153
|
+
var capabilities = capabilityAuditSummary(data.reports && data.reports.capabilities);
|
|
3154
|
+
var slots = contextSlotsSummary(data.reports && data.reports.slots);
|
|
3155
|
+
var replay = sessionReplaySummary(data.reports && data.reports.replay);
|
|
3156
|
+
var cards = [
|
|
2963
3157
|
metricDonut("Memory grounding", memoryGrounding, linkedPacketIds.size + " of " + approvedPackets + " packets linked to code", "Open Memory and fix Needs paths first.", memoryGrounding >= 70 ? "ok" : "warn"),
|
|
2964
|
-
metricDonut("Source map", sourceCoverage, firstNumber(data.codeGraph.files, data.structural.files, 0) + " files indexed for graph recall", "If this drops, refresh indexing before relying on graph answers.", sourceCoverage >= 90 ? "ok" : "warn")
|
|
2965
|
-
|
|
3158
|
+
metricDonut("Source map", sourceCoverage, firstNumber(data.codeGraph.files, data.structural.files, 0) + " files indexed for graph recall", "If this drops, refresh indexing before relying on graph answers.", sourceCoverage >= 90 ? "ok" : "warn")
|
|
3159
|
+
];
|
|
3160
|
+
if (capabilities) {
|
|
3161
|
+
cards.push(metricBars("Capability audit", capabilities.label, capabilities.rows, capabilities.action, capabilities.status));
|
|
3162
|
+
}
|
|
3163
|
+
if (profile) {
|
|
3164
|
+
cards.push(metricBars("Project profile", profile.label, [
|
|
3165
|
+
{ label: "Concepts", value: profile.concepts, score: Math.min(100, profile.concepts * 10), status: profile.concepts ? "ok" : "warn" },
|
|
3166
|
+
{ label: "Key files", value: profile.keyFiles, score: Math.min(100, profile.keyFiles * 8), status: profile.keyFiles ? "ok" : "warn" },
|
|
3167
|
+
{ label: "Commands", value: profile.commands, score: Math.min(100, profile.commands * 16), status: profile.commands ? "ok" : "warn" }
|
|
3168
|
+
], profile.action, profile.status));
|
|
3169
|
+
}
|
|
3170
|
+
if (slots) {
|
|
3171
|
+
cards.push(metricBars("Pinned context", slots.label, [
|
|
3172
|
+
{ label: "Pinned", value: slots.pinned, score: Math.min(100, slots.pinned * 30), status: slots.pinned ? "ok" : "warn" },
|
|
3173
|
+
{ label: "Slots", value: slots.total, score: Math.min(100, slots.total * 20), status: slots.total ? "ok" : "warn" },
|
|
3174
|
+
{ label: "Chars", value: slots.chars, score: slots.chars ? Math.min(100, Math.round(slots.chars / 60)) : 0, status: slots.chars ? "ok" : "warn" }
|
|
3175
|
+
], slots.action, slots.status));
|
|
3176
|
+
}
|
|
3177
|
+
if (replay) {
|
|
3178
|
+
cards.push(metricBars("Session replay", replay.label, [
|
|
3179
|
+
{ label: "Events", value: replay.events, score: Math.min(100, replay.events * 8), status: replay.events ? "ok" : "warn" },
|
|
3180
|
+
{ label: "Candidates", value: replay.candidates, score: Math.min(100, replay.candidates * 24), status: replay.candidates ? "warn" : "ok" },
|
|
3181
|
+
{ label: "Sessions", value: replay.sessions, score: Math.min(100, replay.sessions * 24), status: replay.sessions ? "ok" : "warn" }
|
|
3182
|
+
], replay.action, replay.status));
|
|
3183
|
+
}
|
|
3184
|
+
if (setup) {
|
|
3185
|
+
cards.push(metricBars("Agent setup", setup.label, [
|
|
3186
|
+
{ label: "Configured", value: setup.configured + "/" + setup.total, score: setup.total ? Math.round(setup.configured / setup.total * 100) : 0, status: setup.configured ? "ok" : "warn" },
|
|
3187
|
+
{ label: "Claude hooks", value: setup.claudeHookReady ? "ready" : "missing", score: setup.claudeHookReady ? 100 : 0, status: setup.claudeHookReady ? "ok" : "warn" },
|
|
3188
|
+
{ label: "Missing", value: setup.missingCount, score: setup.missingCount ? Math.min(100, setup.missingCount * 18) : 0, status: setup.missingCount ? "warn" : "ok" }
|
|
3189
|
+
], setup.action, setup.status));
|
|
3190
|
+
}
|
|
3191
|
+
if (access) {
|
|
3192
|
+
cards.push(metricBars("Memory reuse", access.uses30d + " recalls", [
|
|
3193
|
+
{ label: "Hot", value: access.hot, score: Math.min(100, access.hot * 24), status: access.hot ? "ok" : "warn" },
|
|
3194
|
+
{ label: "Cold", value: access.cold, score: approvedPackets ? Math.round(access.cold / approvedPackets * 100) : 0, status: access.cold ? "warn" : "ok" },
|
|
3195
|
+
{ label: "Tracked", value: access.tracked, score: approvedPackets ? Math.round(access.tracked / approvedPackets * 100) : 0, status: access.tracked ? "ok" : "warn" }
|
|
3196
|
+
], access.tracked ? "Cold packets may be stale, too broad, or never needed by agents." : "Recall a task to start measuring which memories actually help.", access.cold ? "warn" : "ok"));
|
|
3197
|
+
}
|
|
3198
|
+
if (audit) {
|
|
3199
|
+
cards.push(metricBars("Memory audit", audit.total + " mutations", [
|
|
3200
|
+
{ label: "Capture", value: audit.capture, score: Math.min(100, audit.capture * 12), status: audit.capture ? "ok" : "warn" },
|
|
3201
|
+
{ label: "Review", value: audit.review, score: Math.min(100, audit.review * 20), status: audit.review ? "ok" : "warn" },
|
|
3202
|
+
{ label: "Supersede", value: audit.supersede, score: Math.min(100, audit.supersede * 24), status: audit.supersede ? "ok" : "warn" }
|
|
3203
|
+
], audit.total ? "Memory changes are auditable for team handoff." : "No explicit memory mutations audited yet.", audit.total ? "ok" : "warn"));
|
|
3204
|
+
}
|
|
3205
|
+
if (data.handoff) {
|
|
3206
|
+
cards.push(metricBars("Memory handoff", data.handoff.label, [
|
|
3207
|
+
{ label: "Open", value: data.handoff.openItems, score: Math.min(100, data.handoff.openItems * 24), status: data.handoff.openItems ? "warn" : "ok" },
|
|
3208
|
+
{ label: "Distill", value: data.handoff.distillableSessions, score: Math.min(100, data.handoff.distillableSessions * 30), status: data.handoff.distillableSessions ? "warn" : "ok" },
|
|
3209
|
+
{ label: "Recent", value: data.handoff.recentChanges, score: Math.min(100, data.handoff.recentChanges * 8), status: data.handoff.recentChanges ? "ok" : "warn" },
|
|
3210
|
+
{ label: "Mutations", value: data.handoff.recentMutations, score: Math.min(100, data.handoff.recentMutations * 18), status: data.handoff.recentMutations ? "ok" : "warn" }
|
|
3211
|
+
], data.handoff.action, data.handoff.status));
|
|
3212
|
+
}
|
|
3213
|
+
if (lifecycle) {
|
|
3214
|
+
cards.push(metricBars("Memory lifecycle", lifecycle.needsReview ? lifecycle.needsReview + " need review" : "healthy", [
|
|
3215
|
+
{ label: "Hot/healthy", value: lifecycle.ready, score: approvedPackets ? Math.round(lifecycle.ready / approvedPackets * 100) : 0, status: lifecycle.ready ? "ok" : "warn" },
|
|
3216
|
+
{ label: "Ungrounded", value: lifecycle.ungrounded, score: approvedPackets ? Math.round(lifecycle.ungrounded / approvedPackets * 100) : 0, status: lifecycle.ungrounded ? "warn" : "ok" },
|
|
3217
|
+
{ label: "Stale/disputed", value: lifecycle.stale, score: approvedPackets ? Math.round(lifecycle.stale / approvedPackets * 100) : 0, status: lifecycle.stale ? "danger" : "ok" }
|
|
3218
|
+
], lifecycle.needsReview ? "Open Memory and resolve lifecycle actions before handoff." : "Repo memory is ready for agent handoff.", lifecycle.needsReview ? "warn" : "ok"));
|
|
3219
|
+
}
|
|
3220
|
+
if (timeline) {
|
|
3221
|
+
cards.push(metricBars("Memory timeline", timeline.total + " recent", [
|
|
3222
|
+
{ label: "Added", value: timeline.added, score: Math.min(100, timeline.added * 16), status: timeline.added ? "ok" : "warn" },
|
|
3223
|
+
{ label: "Updated", value: timeline.updated, score: Math.min(100, timeline.updated * 16), status: timeline.updated ? "ok" : "warn" },
|
|
3224
|
+
{ label: "Pending", value: timeline.pending, score: Math.min(100, timeline.pending * 24), status: timeline.pending ? "warn" : "ok" }
|
|
3225
|
+
], timeline.total ? "Review recent memory changes before teammate handoff." : "No recent memory activity loaded.", timeline.pending ? "warn" : "ok"));
|
|
3226
|
+
}
|
|
3227
|
+
if (lineage) {
|
|
3228
|
+
cards.push(metricBars("Memory lineage", lineage.chains + " chains", [
|
|
3229
|
+
{ label: "Replaced", value: lineage.superseded, score: Math.min(100, lineage.superseded * 18), status: lineage.superseded ? "ok" : "warn" },
|
|
3230
|
+
{ label: "Chains", value: lineage.chains, score: Math.min(100, lineage.chains * 24), status: lineage.chains ? "ok" : "warn" },
|
|
3231
|
+
{ label: "Needs repair", value: lineage.orphans, score: Math.min(100, lineage.orphans * 32), status: lineage.orphans ? "danger" : "ok" }
|
|
3232
|
+
], lineage.orphans ? "Fix superseded memory without replacement links." : "Retired memory points at current replacements.", lineage.orphans ? "danger" : "ok"));
|
|
3233
|
+
}
|
|
3234
|
+
if (retrieval) {
|
|
3235
|
+
cards.push(metricBars("Retrieval proof", retrieval.r10 + "% R@10", [
|
|
3236
|
+
{ label: "R@5", value: retrieval.r5 != null ? retrieval.r5 + "%" : "n/a", score: retrieval.r5 || 0, status: retrieval.r5 >= 95 ? "ok" : "warn" },
|
|
3237
|
+
{ label: "R@10", value: retrieval.r10 + "%", score: retrieval.r10, status: retrieval.r10 >= 95 ? "ok" : "warn" },
|
|
3238
|
+
{ label: "MRR", value: retrieval.mrr != null ? retrieval.mrr : "n/a", score: retrieval.mrr != null ? retrieval.mrr * 100 : 0, status: retrieval.mrr >= 0.85 ? "ok" : "warn" }
|
|
3239
|
+
], retrieval.label + ". Measures memory retrieval proof, not answer accuracy.", retrieval.r10 >= 95 ? "ok" : "warn"));
|
|
3240
|
+
}
|
|
3241
|
+
if (scale) {
|
|
3242
|
+
cards.push(metricBars("Scale proof", scale.hitRate + "% hit", [
|
|
3243
|
+
{ label: "Packets", value: scale.packets, score: Math.min(100, scale.packets / 10), status: scale.packets >= 240 ? "ok" : "warn" },
|
|
3244
|
+
{ label: "Median", value: scale.medianLatency + "ms", score: Math.max(0, 100 - scale.medianLatency), status: scale.medianLatency <= 50 ? "ok" : "warn" },
|
|
3245
|
+
{ label: "Context cut", value: scale.contextReduction + "%", score: scale.contextReduction, status: scale.contextReduction >= 80 ? "ok" : "warn" }
|
|
3246
|
+
], "Large memory corpus stays searchable without loading every packet.", scale.hitRate >= 95 ? "ok" : "warn"));
|
|
3247
|
+
}
|
|
3248
|
+
els.dashboardCharts.textContent = "";
|
|
3249
|
+
var tailCards = [];
|
|
3250
|
+
if (!data.handoff) {
|
|
3251
|
+
tailCards.push(metricBars("Handoff blockers", blockers ? blockers + " open" : "clear", [
|
|
2966
3252
|
{ label: "Pending", value: data.pendingReview, score: Math.min(100, data.pendingReview * 24), status: data.pendingReview ? "warn" : "ok" },
|
|
2967
3253
|
{ label: "Stale", value: data.staleFlags, score: Math.min(100, data.staleFlags * 24), status: data.staleFlags ? "warn" : "ok" },
|
|
2968
3254
|
{ label: "Duplicate", value: data.duplicateFlags, score: Math.min(100, data.duplicateFlags * 24), status: data.duplicateFlags ? "warn" : "ok" },
|
|
2969
3255
|
{ label: "Missing context", value: data.missingContext, score: Math.min(100, data.missingContext * 18), status: data.missingContext ? "warn" : "ok" }
|
|
2970
|
-
], blockers ? "Resolve Review before handing work to another agent." : "Memory is clean for handoff.", blockers ? "warn" : "ok")
|
|
2971
|
-
|
|
2972
|
-
|
|
3256
|
+
], blockers ? "Resolve Review before handing work to another agent." : "Memory is clean for handoff.", blockers ? "warn" : "ok"));
|
|
3257
|
+
}
|
|
3258
|
+
tailCards.push(
|
|
3259
|
+
metricBars("Before edit", riskSignals ? riskSignals + " checks" : "none", [
|
|
3260
|
+
{ label: "Edit areas", value: data.riskTargets.length, score: Math.min(100, data.riskTargets.length * 18), status: data.riskTargets.length ? "warn" : "ok" },
|
|
2973
3261
|
{ label: "Silos", value: data.ownerSilos, score: Math.min(100, data.ownerSilos * 18), status: data.ownerSilos ? "warn" : "ok" },
|
|
2974
3262
|
{ label: "Hotspots", value: data.hotspots, score: Math.min(100, data.hotspots * 18), status: data.hotspots ? "danger" : "ok" }
|
|
2975
|
-
], riskSignals ? "Open
|
|
2976
|
-
|
|
3263
|
+
], riskSignals ? "Open Before Edit and do the listed safety step before changing code." : "No loaded risk flags.", riskSignals ? "warn" : "ok")
|
|
3264
|
+
);
|
|
3265
|
+
cards.concat(tailCards).slice(0, 3).forEach(function (card) { els.dashboardCharts.appendChild(card); });
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
function projectProfileSummary(report) {
|
|
3269
|
+
if (!report || !report.totals) return null;
|
|
3270
|
+
var concepts = Array.isArray(report.top_concepts) ? report.top_concepts.length : 0;
|
|
3271
|
+
var keyFiles = Array.isArray(report.key_files) ? report.key_files.length : 0;
|
|
3272
|
+
var commands = Array.isArray(report.run_commands) ? report.run_commands.length : 0;
|
|
3273
|
+
var coverage = Number(report.totals.memory_code_coverage_percent || 0);
|
|
3274
|
+
var topConcept = concepts ? report.top_concepts[0].concept : "no concepts";
|
|
3275
|
+
var actions = Array.isArray(report.next_actions) ? report.next_actions : [];
|
|
3276
|
+
return {
|
|
3277
|
+
concepts: concepts,
|
|
3278
|
+
keyFiles: keyFiles,
|
|
3279
|
+
commands: commands,
|
|
3280
|
+
label: topConcept,
|
|
3281
|
+
action: actions[0] || report.summary || "Use this as the first orientation packet for agents.",
|
|
3282
|
+
status: coverage >= 60 && keyFiles ? "ok" : "warn"
|
|
3283
|
+
};
|
|
3284
|
+
}
|
|
3285
|
+
|
|
3286
|
+
function capabilityAuditSummary(report) {
|
|
3287
|
+
if (!report || !Array.isArray(report.pillars)) return null;
|
|
3288
|
+
var rows = report.pillars.slice(0, 4).map(function (pillar) {
|
|
3289
|
+
return {
|
|
3290
|
+
label: pillar.label || pillar.id || "pillar",
|
|
3291
|
+
value: Number(pillar.score || 0) + "%",
|
|
3292
|
+
score: Number(pillar.score || 0),
|
|
3293
|
+
status: pillar.status === "ready" ? "ok" : (pillar.status === "gap" ? "danger" : "warn")
|
|
3294
|
+
};
|
|
3295
|
+
});
|
|
3296
|
+
var open = report.pillars.filter(function (pillar) { return pillar.status !== "ready"; }).length;
|
|
3297
|
+
return {
|
|
3298
|
+
label: Number(report.overall_score || 0) + "/100",
|
|
3299
|
+
rows: rows,
|
|
3300
|
+
action: open
|
|
3301
|
+
? (Array.isArray(report.next_actions) && report.next_actions[0] ? report.next_actions[0] : "Review capability gaps before publishing claims.")
|
|
3302
|
+
: "Memory, benchmark, collaboration, and viewer proof surfaces are ready.",
|
|
3303
|
+
status: report.status === "ready" ? "ok" : (report.status === "gap" ? "danger" : "warn")
|
|
3304
|
+
};
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3307
|
+
function contextSlotsSummary(report) {
|
|
3308
|
+
if (!report || !report.totals) return null;
|
|
3309
|
+
var slots = Array.isArray(report.slots) ? report.slots : [];
|
|
3310
|
+
var pinned = Number(report.totals.pinned || 0);
|
|
3311
|
+
var total = Number(report.totals.slots || slots.length || 0);
|
|
3312
|
+
var chars = Number(report.totals.context_chars || 0);
|
|
3313
|
+
var firstPinned = slots.find(function (slot) { return slot && slot.pinned && slot.content; });
|
|
3314
|
+
return {
|
|
3315
|
+
pinned: pinned,
|
|
3316
|
+
total: total,
|
|
3317
|
+
chars: chars,
|
|
3318
|
+
label: firstPinned ? firstPinned.label : (pinned ? String(pinned) + " pinned" : "none"),
|
|
3319
|
+
action: pinned
|
|
3320
|
+
? "Pinned slots are included before task-specific recall for stable repo guidance."
|
|
3321
|
+
: "Add a slot for tiny always-relevant repo context instead of repeating it every session.",
|
|
3322
|
+
status: pinned ? "ok" : "warn"
|
|
3323
|
+
};
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
function sessionReplaySummary(report) {
|
|
3327
|
+
if (!report || !report.totals) return null;
|
|
3328
|
+
var events = Number(report.totals.events || 0);
|
|
3329
|
+
var candidates = Number(report.totals.durable_candidates || 0);
|
|
3330
|
+
var sessions = Number(report.totals.sessions || 0);
|
|
3331
|
+
return {
|
|
3332
|
+
events: events,
|
|
3333
|
+
candidates: candidates,
|
|
3334
|
+
sessions: sessions,
|
|
3335
|
+
label: candidates ? candidates + " distillable" : (events ? events + " observed" : "none"),
|
|
3336
|
+
action: candidates
|
|
3337
|
+
? "Open Memory and distill durable session observations into reviewable packets."
|
|
3338
|
+
: "Replay digest proves what agents observed without exposing raw transcripts.",
|
|
3339
|
+
status: candidates ? "warn" : (events ? "ok" : "warn")
|
|
3340
|
+
};
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
function setupDoctorSummary(report) {
|
|
3344
|
+
if (!Array.isArray(report) || !report.length) return null;
|
|
3345
|
+
var configured = report.filter(function (item) { return item && item.configured; }).length;
|
|
3346
|
+
var claude = report.find(function (item) { return item && item.agent === "claude-code"; });
|
|
3347
|
+
var hookSummary = claude && claude.hook_summary;
|
|
3348
|
+
var missing = hookSummary && Array.isArray(hookSummary.missing) ? hookSummary.missing : [];
|
|
3349
|
+
var claudeHookReady = Boolean(hookSummary && hookSummary.ready);
|
|
3350
|
+
var missingCount = missing.length;
|
|
3351
|
+
var action = "";
|
|
3352
|
+
if (missingCount) {
|
|
3353
|
+
action = "Run kage setup claude-code --project . --write before relying on automatic memory.";
|
|
3354
|
+
} else if (configured) {
|
|
3355
|
+
action = "Automatic memory setup is visible for teammate handoff.";
|
|
3356
|
+
} else {
|
|
3357
|
+
action = "Run kage setup doctor to choose the next agent setup step.";
|
|
3358
|
+
}
|
|
3359
|
+
return {
|
|
3360
|
+
total: report.length,
|
|
3361
|
+
configured: configured,
|
|
3362
|
+
claudeHookReady: claudeHookReady,
|
|
3363
|
+
missingCount: missingCount,
|
|
3364
|
+
label: missingCount ? missingCount + " missing" : configured + "/" + report.length + " ready",
|
|
3365
|
+
action: action,
|
|
3366
|
+
status: missingCount || !configured ? "warn" : "ok"
|
|
3367
|
+
};
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
function memoryAccessSummary(report, approvedPackets) {
|
|
3371
|
+
var totals = report && report.totals;
|
|
3372
|
+
if (!totals) return null;
|
|
3373
|
+
return {
|
|
3374
|
+
tracked: Number(totals.tracked_packets || 0),
|
|
3375
|
+
uses30d: Number(totals.uses_30d || 0),
|
|
3376
|
+
hot: Number(totals.hot_packets || 0),
|
|
3377
|
+
cold: Number(totals.cold_packets == null ? Math.max(0, approvedPackets) : totals.cold_packets)
|
|
3378
|
+
};
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
function memoryAuditSummary(report) {
|
|
3382
|
+
var totals = report && report.totals;
|
|
3383
|
+
if (!totals) return null;
|
|
3384
|
+
return {
|
|
3385
|
+
total: Number(totals.total || 0),
|
|
3386
|
+
capture: Number(totals.capture || 0),
|
|
3387
|
+
review: Number(totals.approve || 0) + Number(totals.reject || 0),
|
|
3388
|
+
supersede: Number(totals.supersede || 0)
|
|
3389
|
+
};
|
|
3390
|
+
}
|
|
3391
|
+
|
|
3392
|
+
function memoryHandoffSummary(report) {
|
|
3393
|
+
var totals = report && report.totals;
|
|
3394
|
+
if (!totals) return null;
|
|
3395
|
+
var primary = report.primary_action || {};
|
|
3396
|
+
var openItems = Number(firstNumber(totals.open_items, 0));
|
|
3397
|
+
var severity = primary.severity || (openItems ? "warning" : "ok");
|
|
3398
|
+
var status = severity === "blocker" ? "danger" : severity === "warning" ? "warn" : severity === "ok" ? "ok" : "memory";
|
|
3399
|
+
var label = primary.label || (openItems ? "Resolve handoff" : "Ready for handoff");
|
|
3400
|
+
return {
|
|
3401
|
+
label: label,
|
|
3402
|
+
actionLabel: label.indexOf("Resolve") === 0 ? "Resolve" : label.indexOf("Ready") === 0 ? "Ready" : label.indexOf("Review") === 0 ? "Review" : label,
|
|
3403
|
+
detail: primary.summary || report.summary || "",
|
|
3404
|
+
action: primary.action || report.summary || "Open Review before handing work to another agent.",
|
|
3405
|
+
status: status,
|
|
3406
|
+
openItems: openItems,
|
|
3407
|
+
blockers: Number(firstNumber(totals.blockers, 0)),
|
|
3408
|
+
warnings: Number(firstNumber(totals.warnings, 0)),
|
|
3409
|
+
recentChanges: Number(firstNumber(totals.recent_changes, 0)),
|
|
3410
|
+
recentMutations: Number(firstNumber(totals.recent_mutations, 0)),
|
|
3411
|
+
distillableSessions: Number(firstNumber(totals.distillable_sessions, 0)),
|
|
3412
|
+
durableObservations: Number(firstNumber(totals.durable_observations, 0))
|
|
3413
|
+
};
|
|
3414
|
+
}
|
|
3415
|
+
|
|
3416
|
+
function memoryLifecycleSummary(report) {
|
|
3417
|
+
var totals = report && report.totals;
|
|
3418
|
+
if (!totals) return null;
|
|
3419
|
+
var stale = Number(totals.stale || 0) + Number(totals.disputed || 0);
|
|
3420
|
+
var ungrounded = Number(totals.ungrounded || 0);
|
|
3421
|
+
var pending = Number(totals.pending || 0);
|
|
3422
|
+
return {
|
|
3423
|
+
ready: Number(totals.hot || 0) + Number(totals.healthy || 0),
|
|
3424
|
+
ungrounded: ungrounded,
|
|
3425
|
+
stale: stale,
|
|
3426
|
+
needsReview: stale + ungrounded + pending
|
|
3427
|
+
};
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
function memoryTimelineSummary(report) {
|
|
3431
|
+
var totals = report && report.totals;
|
|
3432
|
+
if (!totals) return null;
|
|
3433
|
+
return {
|
|
3434
|
+
total: Number(totals.total || 0),
|
|
3435
|
+
added: Number(totals.added || 0),
|
|
3436
|
+
updated: Number(totals.updated || 0),
|
|
3437
|
+
pending: Number(totals.pending || 0),
|
|
3438
|
+
deprecated: Number(totals.deprecated || 0)
|
|
3439
|
+
};
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
function memoryLineageSummary(report) {
|
|
3443
|
+
var totals = report && report.totals;
|
|
3444
|
+
if (!totals) return null;
|
|
3445
|
+
return {
|
|
3446
|
+
superseded: Number(totals.superseded || 0),
|
|
3447
|
+
chains: Number(totals.chains || 0),
|
|
3448
|
+
orphans: Number(totals.orphans || 0),
|
|
3449
|
+
replacementsMissing: Number(totals.replacements_missing || 0)
|
|
3450
|
+
};
|
|
2977
3451
|
}
|
|
2978
3452
|
|
|
2979
3453
|
function metricDonut(title, percent, detail, action, status) {
|
|
@@ -3104,6 +3578,7 @@
|
|
|
3104
3578
|
}
|
|
3105
3579
|
});
|
|
3106
3580
|
var linkedCount = memoryEntities.filter(function (entity) { return (memoryLinkCounts.get(entity.id) || 0) > 0; }).length;
|
|
3581
|
+
var accessTotals = state.reports.memoryAccess && state.reports.memoryAccess.totals;
|
|
3107
3582
|
var query = parseSearchQuery(els.memorySearch ? els.memorySearch.value : "");
|
|
3108
3583
|
var filter = els.memoryFilter ? els.memoryFilter.value : "all";
|
|
3109
3584
|
var filtered = memoryEntities.filter(function (entity) {
|
|
@@ -3118,10 +3593,17 @@
|
|
|
3118
3593
|
els.memoryStats.innerHTML = [
|
|
3119
3594
|
memoryStat("Reusable", memoryEntities.length),
|
|
3120
3595
|
memoryStat("Code-linked", linkedCount),
|
|
3596
|
+
memoryStat("Reused 30d", accessTotals ? Number(accessTotals.uses_30d || 0) : "n/a"),
|
|
3121
3597
|
memoryStat("Needs paths", memoryEntities.length - linkedCount)
|
|
3122
3598
|
].join("");
|
|
3123
3599
|
}
|
|
3124
3600
|
if (els.memoryOverview) renderMemoryOverview(memoryEntities, linkedCount);
|
|
3601
|
+
if (els.lifecycleList) renderMemoryLifecycle(memoryEntities, memoryLinkCounts);
|
|
3602
|
+
if (els.memoryReviewActions) renderMemoryReviewActions();
|
|
3603
|
+
if (els.memoryTimelineList) renderMemoryTimeline();
|
|
3604
|
+
if (els.memoryAuditList) renderMemoryAudit();
|
|
3605
|
+
if (els.memoryLineageList) renderMemoryLineage();
|
|
3606
|
+
if (els.sessionCaptureList) renderSessionCapture();
|
|
3125
3607
|
els.memoryList.textContent = "";
|
|
3126
3608
|
if (!memoryEntities.length) {
|
|
3127
3609
|
els.memoryList.className = "memory-list details-empty";
|
|
@@ -3142,6 +3624,7 @@
|
|
|
3142
3624
|
filtered.slice(0, 60).forEach(function (entity) {
|
|
3143
3625
|
var links = memoryCodeLinksForEntity(entity.id);
|
|
3144
3626
|
var firstCodeTarget = primaryCodeTargetForMemory(entity.id, links);
|
|
3627
|
+
var access = memoryAccessForEntity(entity);
|
|
3145
3628
|
var item = document.createElement("button");
|
|
3146
3629
|
item.type = "button";
|
|
3147
3630
|
var selected = state.selected && state.selected.kind === "entity" && state.selected.id === entity.id;
|
|
@@ -3158,6 +3641,9 @@
|
|
|
3158
3641
|
item.querySelector(".memory-row-target").textContent = links.length
|
|
3159
3642
|
? links.length + " code link" + (links.length === 1 ? "" : "s") + (firstCodeTarget ? " | " + trimIntelText(codeTargetLabel(firstCodeTarget), 64) : "")
|
|
3160
3643
|
: "needs code paths";
|
|
3644
|
+
if (access && access.total_uses) {
|
|
3645
|
+
item.querySelector(".memory-row-target").textContent += " | reused " + Number(access.uses_30d || 0) + "x in 30d";
|
|
3646
|
+
}
|
|
3161
3647
|
item.addEventListener("click", function () {
|
|
3162
3648
|
selectEntity(entity.id, true);
|
|
3163
3649
|
render();
|
|
@@ -3171,6 +3657,13 @@
|
|
|
3171
3657
|
return "<div><strong>" + escapeHtml(String(value)) + "</strong><span>" + escapeHtml(label) + "</span></div>";
|
|
3172
3658
|
}
|
|
3173
3659
|
|
|
3660
|
+
function memoryAccessForEntity(entity) {
|
|
3661
|
+
var report = state.reports && state.reports.memoryAccess;
|
|
3662
|
+
if (!report || !Array.isArray(report.entries)) return null;
|
|
3663
|
+
var ids = new Set([entity.id].concat(entity.aliases || []));
|
|
3664
|
+
return report.entries.find(function (entry) { return ids.has(entry.packet_id); }) || null;
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3174
3667
|
function renderMemoryOverview(memoryEntities, linkedCount) {
|
|
3175
3668
|
els.memoryOverview.textContent = "";
|
|
3176
3669
|
var total = memoryEntities.length;
|
|
@@ -3198,6 +3691,592 @@
|
|
|
3198
3691
|
}), "A healthy repo has decisions, bug fixes, runbooks, gotchas, and code explanations.", "ok"));
|
|
3199
3692
|
}
|
|
3200
3693
|
|
|
3694
|
+
function renderMemoryReviewActions() {
|
|
3695
|
+
var lifecycle = state.reports && state.reports.lifecycle;
|
|
3696
|
+
var access = state.reports && state.reports.memoryAccess;
|
|
3697
|
+
var recommendations = lifecycle && Array.isArray(lifecycle.recommendations)
|
|
3698
|
+
? lifecycle.recommendations
|
|
3699
|
+
: (access && Array.isArray(access.recommendations) ? access.recommendations : []);
|
|
3700
|
+
if (els.memoryReviewStatus) {
|
|
3701
|
+
els.memoryReviewStatus.textContent = recommendations.length ? recommendations.length + " action" + (recommendations.length === 1 ? "" : "s") : "clear";
|
|
3702
|
+
}
|
|
3703
|
+
els.memoryReviewActions.textContent = "";
|
|
3704
|
+
if (!lifecycle && !access) {
|
|
3705
|
+
els.memoryReviewActions.className = "memory-action-list details-empty";
|
|
3706
|
+
els.memoryReviewActions.textContent = "No lifecycle report loaded. Run kage lifecycle or open the local viewer after refreshing memory.";
|
|
3707
|
+
return;
|
|
3708
|
+
}
|
|
3709
|
+
if (!recommendations.length) {
|
|
3710
|
+
els.memoryReviewActions.className = "memory-action-list details-empty";
|
|
3711
|
+
els.memoryReviewActions.textContent = "No memory review actions. Recent recall usage does not show obvious hot or cold packets.";
|
|
3712
|
+
return;
|
|
3713
|
+
}
|
|
3714
|
+
els.memoryReviewActions.className = "memory-action-list";
|
|
3715
|
+
recommendations.slice(0, 6).forEach(function (item) {
|
|
3716
|
+
var card = document.createElement("article");
|
|
3717
|
+
card.className = classNames("memory-action", item.severity && "memory-action-" + item.severity);
|
|
3718
|
+
card.innerHTML = [
|
|
3719
|
+
"<div class=\"memory-action-head\"><span></span><strong></strong></div>",
|
|
3720
|
+
"<p></p>",
|
|
3721
|
+
"<em></em>"
|
|
3722
|
+
].join("");
|
|
3723
|
+
card.querySelector(".memory-action-head span").textContent = memoryActionLabel(item.kind);
|
|
3724
|
+
card.querySelector(".memory-action-head strong").textContent = item.summary || item.title || "Memory action";
|
|
3725
|
+
card.querySelector("p").textContent = item.reason || "";
|
|
3726
|
+
card.querySelector("em").textContent = item.action || "";
|
|
3727
|
+
var entity = item.packet_id ? findMemoryEntityByPacketId(item.packet_id) : null;
|
|
3728
|
+
if (entity) {
|
|
3729
|
+
card.tabIndex = 0;
|
|
3730
|
+
card.setAttribute("role", "button");
|
|
3731
|
+
card.addEventListener("click", function () {
|
|
3732
|
+
selectEntity(entity.id, true);
|
|
3733
|
+
render();
|
|
3734
|
+
});
|
|
3735
|
+
card.addEventListener("keydown", function (event) {
|
|
3736
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
3737
|
+
event.preventDefault();
|
|
3738
|
+
selectEntity(entity.id, true);
|
|
3739
|
+
render();
|
|
3740
|
+
});
|
|
3741
|
+
}
|
|
3742
|
+
els.memoryReviewActions.appendChild(card);
|
|
3743
|
+
});
|
|
3744
|
+
}
|
|
3745
|
+
|
|
3746
|
+
function renderMemoryTimeline() {
|
|
3747
|
+
var report = state.reports && state.reports.timeline;
|
|
3748
|
+
var entries = Array.isArray(report && report.entries) ? report.entries : [];
|
|
3749
|
+
var totals = report && report.totals ? report.totals : {};
|
|
3750
|
+
if (els.memoryTimelineStatus) {
|
|
3751
|
+
els.memoryTimelineStatus.textContent = report ? String(Number(totals.total || entries.length)) + " recent" : "waiting";
|
|
3752
|
+
}
|
|
3753
|
+
if (els.memoryTimelineSummary) {
|
|
3754
|
+
els.memoryTimelineSummary.textContent = "";
|
|
3755
|
+
[
|
|
3756
|
+
lifecycleSummaryStep("+", "Added", Number(totals.added || 0), "New repo memories captured for future agents."),
|
|
3757
|
+
lifecycleSummaryStep("~", "Updated", Number(totals.updated || 0), "Packets with changed rationale, evidence, or paths."),
|
|
3758
|
+
lifecycleSummaryStep("?", "Pending", Number(totals.pending || 0), "Memory waiting for teammate review.")
|
|
3759
|
+
].forEach(function (step) { els.memoryTimelineSummary.appendChild(step); });
|
|
3760
|
+
}
|
|
3761
|
+
els.memoryTimelineList.textContent = "";
|
|
3762
|
+
if (!report) {
|
|
3763
|
+
els.memoryTimelineList.className = "session-capture-list details-empty";
|
|
3764
|
+
els.memoryTimelineList.textContent = "No memory timeline report loaded. Run kage timeline or open the local viewer after refresh.";
|
|
3765
|
+
return;
|
|
3766
|
+
}
|
|
3767
|
+
if (!entries.length) {
|
|
3768
|
+
els.memoryTimelineList.className = "session-capture-list details-empty";
|
|
3769
|
+
els.memoryTimelineList.textContent = "No recent memory activity. Capture durable repo decisions, bugs, runbooks, or gotchas as work happens.";
|
|
3770
|
+
return;
|
|
3771
|
+
}
|
|
3772
|
+
els.memoryTimelineList.className = "session-capture-list";
|
|
3773
|
+
entries.slice(0, 8).forEach(function (entry) {
|
|
3774
|
+
var card = document.createElement("article");
|
|
3775
|
+
card.className = "session-capture-card";
|
|
3776
|
+
card.innerHTML = [
|
|
3777
|
+
"<div class=\"session-capture-head\"><div><strong></strong><span></span></div><em></em></div>",
|
|
3778
|
+
"<p></p>",
|
|
3779
|
+
"<div class=\"session-capture-meta\"></div>"
|
|
3780
|
+
].join("");
|
|
3781
|
+
card.querySelector("strong").textContent = entry.title || "Memory packet";
|
|
3782
|
+
card.querySelector("span").textContent = [entry.type, timelineDateLabel(entry.date), entry.source_kind].filter(Boolean).join(" | ");
|
|
3783
|
+
card.querySelector("em").textContent = timelineKindLabel(entry.kind);
|
|
3784
|
+
card.querySelector("p").textContent = entry.action || entry.summary || "Review this memory activity.";
|
|
3785
|
+
var meta = card.querySelector(".session-capture-meta");
|
|
3786
|
+
[
|
|
3787
|
+
["status", entry.status || "unknown"],
|
|
3788
|
+
["paths", Array.isArray(entry.paths) ? entry.paths.slice(0, 2).join(", ") || "none" : "none"],
|
|
3789
|
+
["tags", Array.isArray(entry.tags) ? entry.tags.slice(0, 3).join(", ") || "none" : "none"]
|
|
3790
|
+
].forEach(function (row) {
|
|
3791
|
+
var chip = document.createElement("span");
|
|
3792
|
+
chip.textContent = row[0] + ": " + row[1];
|
|
3793
|
+
meta.appendChild(chip);
|
|
3794
|
+
});
|
|
3795
|
+
var entity = entry.packet_id ? findMemoryEntityByPacketId(entry.packet_id) : null;
|
|
3796
|
+
if (entity) {
|
|
3797
|
+
card.tabIndex = 0;
|
|
3798
|
+
card.setAttribute("role", "button");
|
|
3799
|
+
card.addEventListener("click", function () {
|
|
3800
|
+
selectEntity(entity.id, true);
|
|
3801
|
+
render();
|
|
3802
|
+
});
|
|
3803
|
+
card.addEventListener("keydown", function (event) {
|
|
3804
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
3805
|
+
event.preventDefault();
|
|
3806
|
+
selectEntity(entity.id, true);
|
|
3807
|
+
render();
|
|
3808
|
+
});
|
|
3809
|
+
}
|
|
3810
|
+
els.memoryTimelineList.appendChild(card);
|
|
3811
|
+
});
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
function renderMemoryAudit() {
|
|
3815
|
+
var report = state.reports && state.reports.memoryAudit;
|
|
3816
|
+
var entries = Array.isArray(report && report.entries) ? report.entries : [];
|
|
3817
|
+
var totals = report && report.totals ? report.totals : {};
|
|
3818
|
+
if (els.memoryAuditStatus) {
|
|
3819
|
+
els.memoryAuditStatus.textContent = report ? String(Number(totals.total || entries.length)) + " mutations" : "waiting";
|
|
3820
|
+
}
|
|
3821
|
+
if (els.memoryAuditSummary) {
|
|
3822
|
+
els.memoryAuditSummary.textContent = "";
|
|
3823
|
+
[
|
|
3824
|
+
lifecycleSummaryStep("1", "Captured", Number(totals.capture || 0), "New packets written to repo memory."),
|
|
3825
|
+
lifecycleSummaryStep("2", "Reviewed", Number(totals.approve || 0) + Number(totals.reject || 0), "Pending packets approved or rejected."),
|
|
3826
|
+
lifecycleSummaryStep("3", "Replaced", Number(totals.supersede || 0), "Old knowledge linked to current replacements.")
|
|
3827
|
+
].forEach(function (step) { els.memoryAuditSummary.appendChild(step); });
|
|
3828
|
+
}
|
|
3829
|
+
els.memoryAuditList.textContent = "";
|
|
3830
|
+
if (!report) {
|
|
3831
|
+
els.memoryAuditList.className = "session-capture-list details-empty";
|
|
3832
|
+
els.memoryAuditList.textContent = "No memory audit report loaded. Run kage memory-audit or open the local viewer after refresh.";
|
|
3833
|
+
return;
|
|
3834
|
+
}
|
|
3835
|
+
if (!entries.length) {
|
|
3836
|
+
els.memoryAuditList.className = "session-capture-list details-empty";
|
|
3837
|
+
els.memoryAuditList.textContent = "No audited memory mutations yet. Captures, feedback, reviews, and supersedes will appear here.";
|
|
3838
|
+
return;
|
|
3839
|
+
}
|
|
3840
|
+
els.memoryAuditList.className = "session-capture-list";
|
|
3841
|
+
entries.slice(0, 8).forEach(function (entry) {
|
|
3842
|
+
var card = document.createElement("article");
|
|
3843
|
+
card.className = "session-capture-card";
|
|
3844
|
+
card.innerHTML = [
|
|
3845
|
+
"<div class=\"session-capture-head\"><div><strong></strong><span></span></div><em></em></div>",
|
|
3846
|
+
"<p></p>",
|
|
3847
|
+
"<div class=\"session-capture-meta\"></div>"
|
|
3848
|
+
].join("");
|
|
3849
|
+
card.querySelector("strong").textContent = (entry.packet_titles || []).join(", ") || (entry.packet_ids || []).join(", ") || "Memory mutation";
|
|
3850
|
+
card.querySelector("span").textContent = [entry.actor, entry.branch, timelineDateLabel(entry.timestamp)].filter(Boolean).join(" | ");
|
|
3851
|
+
card.querySelector("em").textContent = entry.operation || "audit";
|
|
3852
|
+
card.querySelector("p").textContent = memoryAuditAction(entry);
|
|
3853
|
+
var meta = card.querySelector(".session-capture-meta");
|
|
3854
|
+
[
|
|
3855
|
+
["packets", Array.isArray(entry.packet_ids) ? String(entry.packet_ids.length) : "0"],
|
|
3856
|
+
["head", entry.head ? String(entry.head).slice(0, 8) : "none"],
|
|
3857
|
+
["details", auditDetailsLabel(entry.details)]
|
|
3858
|
+
].forEach(function (row) {
|
|
3859
|
+
var chip = document.createElement("span");
|
|
3860
|
+
chip.textContent = row[0] + ": " + row[1];
|
|
3861
|
+
meta.appendChild(chip);
|
|
3862
|
+
});
|
|
3863
|
+
els.memoryAuditList.appendChild(card);
|
|
3864
|
+
});
|
|
3865
|
+
}
|
|
3866
|
+
|
|
3867
|
+
function memoryAuditAction(entry) {
|
|
3868
|
+
if (!entry) return "Review this memory mutation before handoff.";
|
|
3869
|
+
if (entry.operation === "capture") return "New repo knowledge was captured for future agents.";
|
|
3870
|
+
if (entry.operation === "feedback") return "A recalled memory received explicit usefulness or stale feedback.";
|
|
3871
|
+
if (entry.operation === "approve") return "A pending packet became shared repo memory.";
|
|
3872
|
+
if (entry.operation === "reject") return "A pending packet was rejected instead of becoming shared memory.";
|
|
3873
|
+
if (entry.operation === "supersede") return "Old repo knowledge now points at a current replacement packet.";
|
|
3874
|
+
if (entry.operation === "deprecate") return "Stale memory was retired from active use.";
|
|
3875
|
+
if (entry.operation === "delete") return "Memory was removed by an explicit cleanup action.";
|
|
3876
|
+
return "Review this memory mutation before handoff.";
|
|
3877
|
+
}
|
|
3878
|
+
|
|
3879
|
+
function auditDetailsLabel(details) {
|
|
3880
|
+
if (!details || typeof details !== "object") return "none";
|
|
3881
|
+
if (details.feedback) return "feedback " + details.feedback;
|
|
3882
|
+
if (details.reason) return String(details.reason).slice(0, 80);
|
|
3883
|
+
if (details.type) return "type " + details.type;
|
|
3884
|
+
return Object.keys(details).slice(0, 3).join(", ") || "none";
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3887
|
+
function renderMemoryLineage() {
|
|
3888
|
+
var report = state.reports && state.reports.lineage;
|
|
3889
|
+
var chains = Array.isArray(report && report.chains) ? report.chains : [];
|
|
3890
|
+
var orphans = Array.isArray(report && report.orphans) ? report.orphans : [];
|
|
3891
|
+
var totals = report && report.totals ? report.totals : {};
|
|
3892
|
+
if (els.memoryLineageStatus) {
|
|
3893
|
+
els.memoryLineageStatus.textContent = report ? String(Number(totals.chains || chains.length)) + " chains" : "waiting";
|
|
3894
|
+
}
|
|
3895
|
+
if (els.memoryLineageSummary) {
|
|
3896
|
+
els.memoryLineageSummary.textContent = "";
|
|
3897
|
+
[
|
|
3898
|
+
lifecycleSummaryStep("1", "Current", Number(totals.chains || chains.length), "Replacement packets future agents should trust."),
|
|
3899
|
+
lifecycleSummaryStep("2", "Retired", Number(totals.superseded || 0), "Old packets kept as audit history, not active recall."),
|
|
3900
|
+
lifecycleSummaryStep("3", "Repair", Number(totals.orphans || orphans.length), "Superseded packets missing a replacement link.")
|
|
3901
|
+
].forEach(function (step) { els.memoryLineageSummary.appendChild(step); });
|
|
3902
|
+
}
|
|
3903
|
+
els.memoryLineageList.textContent = "";
|
|
3904
|
+
if (!report) {
|
|
3905
|
+
els.memoryLineageList.className = "session-capture-list details-empty";
|
|
3906
|
+
els.memoryLineageList.textContent = "No lineage report loaded. Run kage lineage or open the local viewer after refresh.";
|
|
3907
|
+
return;
|
|
3908
|
+
}
|
|
3909
|
+
if (!chains.length && !orphans.length) {
|
|
3910
|
+
els.memoryLineageList.className = "session-capture-list details-empty";
|
|
3911
|
+
els.memoryLineageList.textContent = "No retired memory chains yet. Use kage supersede when a newer packet replaces old repo knowledge.";
|
|
3912
|
+
return;
|
|
3913
|
+
}
|
|
3914
|
+
els.memoryLineageList.className = "session-capture-list";
|
|
3915
|
+
chains.slice(0, 6).forEach(function (chain) {
|
|
3916
|
+
var card = document.createElement("article");
|
|
3917
|
+
card.className = "session-capture-card";
|
|
3918
|
+
card.innerHTML = [
|
|
3919
|
+
"<div class=\"session-capture-head\"><div><strong></strong><span></span></div><em>current</em></div>",
|
|
3920
|
+
"<p></p>",
|
|
3921
|
+
"<div class=\"session-capture-meta\"></div>"
|
|
3922
|
+
].join("");
|
|
3923
|
+
card.querySelector("strong").textContent = chain.current_title || "Replacement memory";
|
|
3924
|
+
card.querySelector("span").textContent = "replaces " + Number((chain.superseded_packet_ids || []).length) + " packet" + ((chain.superseded_packet_ids || []).length === 1 ? "" : "s");
|
|
3925
|
+
card.querySelector("p").textContent = chain.action || "Use the current replacement packet in recall.";
|
|
3926
|
+
var meta = card.querySelector(".session-capture-meta");
|
|
3927
|
+
[
|
|
3928
|
+
["reason", chain.reason || "replacement linked"],
|
|
3929
|
+
["paths", Array.isArray(chain.paths) ? chain.paths.slice(0, 2).join(", ") || "none" : "none"],
|
|
3930
|
+
["updated", timelineDateLabel(chain.updated_at)]
|
|
3931
|
+
].forEach(function (row) {
|
|
3932
|
+
var chip = document.createElement("span");
|
|
3933
|
+
chip.textContent = row[0] + ": " + row[1];
|
|
3934
|
+
meta.appendChild(chip);
|
|
3935
|
+
});
|
|
3936
|
+
var entity = chain.current_packet_id ? findMemoryEntityByPacketId(chain.current_packet_id) : null;
|
|
3937
|
+
if (entity) {
|
|
3938
|
+
card.tabIndex = 0;
|
|
3939
|
+
card.setAttribute("role", "button");
|
|
3940
|
+
card.addEventListener("click", function () {
|
|
3941
|
+
selectEntity(entity.id, true);
|
|
3942
|
+
render();
|
|
3943
|
+
});
|
|
3944
|
+
}
|
|
3945
|
+
els.memoryLineageList.appendChild(card);
|
|
3946
|
+
});
|
|
3947
|
+
orphans.slice(0, 4).forEach(function (orphan) {
|
|
3948
|
+
var card = document.createElement("article");
|
|
3949
|
+
card.className = "session-capture-card memory-action-danger";
|
|
3950
|
+
card.innerHTML = [
|
|
3951
|
+
"<div class=\"session-capture-head\"><div><strong></strong><span></span></div><em>repair</em></div>",
|
|
3952
|
+
"<p></p>"
|
|
3953
|
+
].join("");
|
|
3954
|
+
card.querySelector("strong").textContent = orphan.title || "Superseded memory";
|
|
3955
|
+
card.querySelector("span").textContent = timelineDateLabel(orphan.updated_at);
|
|
3956
|
+
card.querySelector("p").textContent = orphan.action || "Add a replacement link before trusting this old context.";
|
|
3957
|
+
els.memoryLineageList.appendChild(card);
|
|
3958
|
+
});
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3961
|
+
function timelineKindLabel(kind) {
|
|
3962
|
+
if (kind === "added") return "added";
|
|
3963
|
+
if (kind === "updated") return "updated";
|
|
3964
|
+
if (kind === "pending") return "pending";
|
|
3965
|
+
if (kind === "deprecated") return "retired";
|
|
3966
|
+
return "activity";
|
|
3967
|
+
}
|
|
3968
|
+
|
|
3969
|
+
function timelineDateLabel(date) {
|
|
3970
|
+
return date ? String(date).slice(0, 10) : "";
|
|
3971
|
+
}
|
|
3972
|
+
|
|
3973
|
+
function memoryActionLabel(kind) {
|
|
3974
|
+
if (kind === "promote_hot") return "Hot";
|
|
3975
|
+
if (kind === "review_cold") return "Cold";
|
|
3976
|
+
if (kind === "connect_paths") return "Ground";
|
|
3977
|
+
if (kind === "add_grounding") return "Ground";
|
|
3978
|
+
if (kind === "review_stale") return "Stale";
|
|
3979
|
+
if (kind === "resolve_feedback") return "Feedback";
|
|
3980
|
+
if (kind === "archive_generated") return "Archive";
|
|
3981
|
+
if (kind === "review_pending") return "Pending";
|
|
3982
|
+
if (kind === "keep_verified") return "Keep";
|
|
3983
|
+
if (kind === "seed_usage") return "Start";
|
|
3984
|
+
return "Review";
|
|
3985
|
+
}
|
|
3986
|
+
|
|
3987
|
+
function findMemoryEntityByPacketId(packetId) {
|
|
3988
|
+
return state.entities.find(function (entity) {
|
|
3989
|
+
if (!isMemoryPacketEntity(entity)) return false;
|
|
3990
|
+
if (entity.id === packetId || entity.packet_id === packetId) return true;
|
|
3991
|
+
return Array.isArray(entity.aliases) && entity.aliases.indexOf(packetId) !== -1;
|
|
3992
|
+
}) || null;
|
|
3993
|
+
}
|
|
3994
|
+
|
|
3995
|
+
function renderMemoryLifecycle(memoryEntities, memoryLinkCounts) {
|
|
3996
|
+
if (!els.lifecycleList) return;
|
|
3997
|
+
var report = state.reports && state.reports.lifecycle;
|
|
3998
|
+
if (report && Array.isArray(report.items)) {
|
|
3999
|
+
renderMemoryLifecycleReport(report);
|
|
4000
|
+
return;
|
|
4001
|
+
}
|
|
4002
|
+
var proofPackets = memoryEntities
|
|
4003
|
+
.map(function (entity) {
|
|
4004
|
+
var links = memoryCodeLinksForEntity(entity.id);
|
|
4005
|
+
return {
|
|
4006
|
+
entity: entity,
|
|
4007
|
+
links: links,
|
|
4008
|
+
score: lifecyclePacketScore(entity, links, memoryLinkCounts.get(entity.id) || 0)
|
|
4009
|
+
};
|
|
4010
|
+
})
|
|
4011
|
+
.sort(function (a, b) {
|
|
4012
|
+
return b.score - a.score ||
|
|
4013
|
+
lifecycleTimestamp(b.entity).localeCompare(lifecycleTimestamp(a.entity)) ||
|
|
4014
|
+
displayName(a.entity).localeCompare(displayName(b.entity));
|
|
4015
|
+
});
|
|
4016
|
+
var grounded = proofPackets.filter(function (item) { return item.links.length; }).length;
|
|
4017
|
+
var recallReady = proofPackets.filter(function (item) {
|
|
4018
|
+
return item.links.length && lifecycleEvidence(item.entity).length && lifecycleRecallQuery(item.entity);
|
|
4019
|
+
}).length;
|
|
4020
|
+
if (els.lifecycleStatus) {
|
|
4021
|
+
els.lifecycleStatus.textContent = proofPackets.length ? recallReady + " recall-ready" : "empty";
|
|
4022
|
+
}
|
|
4023
|
+
if (els.lifecycleSummary) {
|
|
4024
|
+
els.lifecycleSummary.textContent = "";
|
|
4025
|
+
[
|
|
4026
|
+
lifecycleSummaryStep("1", "Captured", proofPackets.length, "Reusable facts agents saved instead of rediscovering."),
|
|
4027
|
+
lifecycleSummaryStep("2", "Grounded", grounded, "Packets linked to files, symbols, routes, or tests."),
|
|
4028
|
+
lifecycleSummaryStep("3", "Recall-ready", recallReady, "Grounded packets with evidence and a likely future query.")
|
|
4029
|
+
].forEach(function (step) { els.lifecycleSummary.appendChild(step); });
|
|
4030
|
+
}
|
|
4031
|
+
els.lifecycleList.textContent = "";
|
|
4032
|
+
if (!proofPackets.length) {
|
|
4033
|
+
els.lifecycleList.className = "lifecycle-list details-empty";
|
|
4034
|
+
els.lifecycleList.textContent = "No memory lifecycle to show yet. Capture reusable repo knowledge with kage learn or kage propose.";
|
|
4035
|
+
return;
|
|
4036
|
+
}
|
|
4037
|
+
els.lifecycleList.className = "lifecycle-list";
|
|
4038
|
+
proofPackets.slice(0, 5).forEach(function (item) {
|
|
4039
|
+
els.lifecycleList.appendChild(renderLifecycleCard(item.entity, item.links));
|
|
4040
|
+
});
|
|
4041
|
+
}
|
|
4042
|
+
|
|
4043
|
+
function renderMemoryLifecycleReport(report) {
|
|
4044
|
+
var totals = report.totals || {};
|
|
4045
|
+
var needsReview = Number(totals.stale || 0) + Number(totals.ungrounded || 0) + Number(totals.disputed || 0) + Number(totals.pending || 0);
|
|
4046
|
+
if (els.lifecycleStatus) {
|
|
4047
|
+
els.lifecycleStatus.textContent = needsReview ? needsReview + " need review" : "healthy";
|
|
4048
|
+
}
|
|
4049
|
+
if (els.lifecycleSummary) {
|
|
4050
|
+
els.lifecycleSummary.textContent = "";
|
|
4051
|
+
[
|
|
4052
|
+
lifecycleSummaryStep("1", "Approved", Number(totals.approved || 0), "Repo-local packets available to future agents."),
|
|
4053
|
+
lifecycleSummaryStep("2", "Hot/healthy", Number(totals.hot || 0) + Number(totals.healthy || 0), "Memories that are grounded, fresh, or repeatedly recalled."),
|
|
4054
|
+
lifecycleSummaryStep("3", "Needs action", needsReview, "Packets to verify, ground, review, or resolve before trusting.")
|
|
4055
|
+
].forEach(function (step) { els.lifecycleSummary.appendChild(step); });
|
|
4056
|
+
}
|
|
4057
|
+
els.lifecycleList.textContent = "";
|
|
4058
|
+
var items = Array.isArray(report.items) ? report.items : [];
|
|
4059
|
+
if (!items.length) {
|
|
4060
|
+
els.lifecycleList.className = "lifecycle-list details-empty";
|
|
4061
|
+
els.lifecycleList.textContent = "No memory lifecycle to show yet. Capture reusable repo knowledge with kage learn or kage propose.";
|
|
4062
|
+
return;
|
|
4063
|
+
}
|
|
4064
|
+
els.lifecycleList.className = "lifecycle-list";
|
|
4065
|
+
items.slice(0, 6).forEach(function (item) {
|
|
4066
|
+
els.lifecycleList.appendChild(renderLifecycleReportCard(item));
|
|
4067
|
+
});
|
|
4068
|
+
}
|
|
4069
|
+
|
|
4070
|
+
function renderLifecycleReportCard(item) {
|
|
4071
|
+
var card = document.createElement("article");
|
|
4072
|
+
card.className = classNames("lifecycle-card", item.severity && "memory-action-" + item.severity);
|
|
4073
|
+
card.innerHTML = [
|
|
4074
|
+
"<div class=\"lifecycle-card-head\"><div><strong></strong><span></span></div><button type=\"button\">Inspect</button></div>",
|
|
4075
|
+
"<p></p>",
|
|
4076
|
+
"<div class=\"lifecycle-flow\"></div>"
|
|
4077
|
+
].join("");
|
|
4078
|
+
card.querySelector("strong").textContent = item.title || "Memory packet";
|
|
4079
|
+
card.querySelector(".lifecycle-card-head span").textContent = [
|
|
4080
|
+
memoryActionLabel(item.recommended_action),
|
|
4081
|
+
item.health,
|
|
4082
|
+
item.type
|
|
4083
|
+
].filter(Boolean).join(" | ");
|
|
4084
|
+
card.querySelector("p").textContent = item.reason || item.action || "Lifecycle action";
|
|
4085
|
+
var entity = item.packet_id ? findMemoryEntityByPacketId(item.packet_id) : null;
|
|
4086
|
+
var button = card.querySelector("button");
|
|
4087
|
+
if (entity) {
|
|
4088
|
+
button.addEventListener("click", function () {
|
|
4089
|
+
selectEntity(entity.id, true);
|
|
4090
|
+
render();
|
|
4091
|
+
});
|
|
4092
|
+
} else {
|
|
4093
|
+
button.disabled = true;
|
|
4094
|
+
button.textContent = "No node";
|
|
4095
|
+
}
|
|
4096
|
+
var flow = card.querySelector(".lifecycle-flow");
|
|
4097
|
+
[
|
|
4098
|
+
lifecycleFlowCell("Evidence", Number(item.source_refs || 0) + " source ref" + (Number(item.source_refs || 0) === 1 ? "" : "s")),
|
|
4099
|
+
lifecycleFlowCell("Grounding", item.paths && item.paths.length ? trimIntelText(item.paths[0], 100) : "Needs code path or symbol link"),
|
|
4100
|
+
lifecycleFlowCell("Recall", Number(item.uses_30d || 0) + " use" + (Number(item.uses_30d || 0) === 1 ? "" : "s") + " in 30d"),
|
|
4101
|
+
lifecycleFlowCell("Action", item.action || "Keep verified")
|
|
4102
|
+
].forEach(function (cell) { flow.appendChild(cell); });
|
|
4103
|
+
return card;
|
|
4104
|
+
}
|
|
4105
|
+
|
|
4106
|
+
function lifecycleSummaryStep(number, label, value, detail) {
|
|
4107
|
+
var item = document.createElement("article");
|
|
4108
|
+
item.className = "lifecycle-step";
|
|
4109
|
+
item.innerHTML = "<strong></strong><span></span><em></em><p></p>";
|
|
4110
|
+
item.querySelector("strong").textContent = number;
|
|
4111
|
+
item.querySelector("span").textContent = label;
|
|
4112
|
+
item.querySelector("em").textContent = formatDashboardValue(value);
|
|
4113
|
+
item.querySelector("p").textContent = detail;
|
|
4114
|
+
return item;
|
|
4115
|
+
}
|
|
4116
|
+
|
|
4117
|
+
function renderLifecycleCard(entity, links) {
|
|
4118
|
+
var card = document.createElement("article");
|
|
4119
|
+
card.className = "lifecycle-card";
|
|
4120
|
+
var codeTargets = links.map(function (edge) {
|
|
4121
|
+
return state.entityById.get(edge.from === entity.id ? edge.to : edge.from);
|
|
4122
|
+
}).filter(Boolean).sort(function (a, b) {
|
|
4123
|
+
return codeTargetScore(b) - codeTargetScore(a) || codeTargetLabel(a).localeCompare(codeTargetLabel(b));
|
|
4124
|
+
});
|
|
4125
|
+
var evidence = lifecycleEvidence(entity);
|
|
4126
|
+
var query = lifecycleRecallQuery(entity);
|
|
4127
|
+
card.innerHTML = [
|
|
4128
|
+
"<div class=\"lifecycle-card-head\"><div><strong></strong><span></span></div><button type=\"button\">Inspect</button></div>",
|
|
4129
|
+
"<p></p>",
|
|
4130
|
+
"<div class=\"lifecycle-flow\"></div>"
|
|
4131
|
+
].join("");
|
|
4132
|
+
card.querySelector("strong").textContent = displayName(entity);
|
|
4133
|
+
card.querySelector(".lifecycle-card-head span").textContent = [
|
|
4134
|
+
entity.type || "memory",
|
|
4135
|
+
lifecycleTimestampLabel(entity)
|
|
4136
|
+
].filter(Boolean).join(" | ");
|
|
4137
|
+
card.querySelector("p").textContent = trimIntelText(entity.summary || entity.description || entity.path || "No summary", 220);
|
|
4138
|
+
card.querySelector("button").addEventListener("click", function () {
|
|
4139
|
+
selectEntity(entity.id, true);
|
|
4140
|
+
render();
|
|
4141
|
+
});
|
|
4142
|
+
var flow = card.querySelector(".lifecycle-flow");
|
|
4143
|
+
[
|
|
4144
|
+
lifecycleFlowCell("Captured", evidence.length ? trimIntelText(evidence[0], 100) : "No source evidence loaded"),
|
|
4145
|
+
lifecycleFlowCell("Grounded", codeTargets.length ? trimIntelText(codeTargetLabel(codeTargets[0]), 100) : "Needs code path or symbol link"),
|
|
4146
|
+
lifecycleFlowCell("Recall", query ? "\"" + query + "\"" : "Search by title, summary, tag, or path"),
|
|
4147
|
+
lifecycleFlowCell("Handoff", lifecycleHandoffText(entity, codeTargets.length))
|
|
4148
|
+
].forEach(function (cell) { flow.appendChild(cell); });
|
|
4149
|
+
return card;
|
|
4150
|
+
}
|
|
4151
|
+
|
|
4152
|
+
function lifecycleFlowCell(label, value) {
|
|
4153
|
+
var cell = document.createElement("div");
|
|
4154
|
+
cell.className = "lifecycle-cell";
|
|
4155
|
+
cell.innerHTML = "<span></span><strong></strong>";
|
|
4156
|
+
cell.querySelector("span").textContent = label;
|
|
4157
|
+
cell.querySelector("strong").textContent = value;
|
|
4158
|
+
return cell;
|
|
4159
|
+
}
|
|
4160
|
+
|
|
4161
|
+
function lifecyclePacketScore(entity, links, linkCount) {
|
|
4162
|
+
var score = 0;
|
|
4163
|
+
score += Math.min(80, (links.length || linkCount) * 24);
|
|
4164
|
+
score += lifecycleEvidence(entity).length ? 35 : 0;
|
|
4165
|
+
score += lifecycleRecallQuery(entity) ? 25 : 0;
|
|
4166
|
+
score += ["decision", "bug_fix", "runbook", "gotcha", "code_explanation", "workflow"].indexOf(entity.type) !== -1 ? 20 : 0;
|
|
4167
|
+
score += lifecycleTimestamp(entity) ? 8 : 0;
|
|
4168
|
+
return score;
|
|
4169
|
+
}
|
|
4170
|
+
|
|
4171
|
+
function lifecycleEvidence(entity) {
|
|
4172
|
+
var evidence = [];
|
|
4173
|
+
if (Array.isArray(entity.evidence)) evidence = evidence.concat(entity.evidence);
|
|
4174
|
+
if (Array.isArray(entity.source_refs)) {
|
|
4175
|
+
entity.source_refs.forEach(function (ref) {
|
|
4176
|
+
evidence.push([ref.kind, ref.captured_at || ref.path || ref.source].filter(Boolean).join(" "));
|
|
4177
|
+
});
|
|
4178
|
+
}
|
|
4179
|
+
if (entity.freshness && entity.freshness.verification) evidence.push(entity.freshness.verification);
|
|
4180
|
+
return evidence.map(function (item) { return String(item || "").trim(); }).filter(Boolean);
|
|
4181
|
+
}
|
|
4182
|
+
|
|
4183
|
+
function lifecycleTimestamp(entity) {
|
|
4184
|
+
return String(entity.updated_at || entity.created_at || entity.last_seen_at || entity.first_seen_at || "");
|
|
4185
|
+
}
|
|
4186
|
+
|
|
4187
|
+
function lifecycleTimestampLabel(entity) {
|
|
4188
|
+
var value = lifecycleTimestamp(entity);
|
|
4189
|
+
return value ? value.slice(0, 10) : "";
|
|
4190
|
+
}
|
|
4191
|
+
|
|
4192
|
+
function lifecycleRecallQuery(entity) {
|
|
4193
|
+
var text = String(entity.summary || entity.description || displayName(entity) || "").trim();
|
|
4194
|
+
var path = Array.isArray(entity.paths) && entity.paths.length ? entity.paths[0] : entity.path;
|
|
4195
|
+
if (entity.type === "runbook") return "how to run " + trimIntelText(displayName(entity), 54);
|
|
4196
|
+
if (entity.type === "bug_fix") return "why did " + trimIntelText(displayName(entity), 54) + " break";
|
|
4197
|
+
if (entity.type === "decision") return "why was " + trimIntelText(displayName(entity), 54) + " decided";
|
|
4198
|
+
if (path) return "what should I know before changing " + path;
|
|
4199
|
+
return text ? trimIntelText(text, 76) : "";
|
|
4200
|
+
}
|
|
4201
|
+
|
|
4202
|
+
function lifecycleHandoffText(entity, linkedTargetCount) {
|
|
4203
|
+
if (linkedTargetCount) return "Future agents touching this area can recall the reason before editing.";
|
|
4204
|
+
if (entity.type === "reference") return "Useful context, but add code paths if it should guide edits.";
|
|
4205
|
+
return "Add paths so teammates and agents can use it at change time.";
|
|
4206
|
+
}
|
|
4207
|
+
|
|
4208
|
+
function renderSessionCapture() {
|
|
4209
|
+
if (!els.sessionCaptureList) return;
|
|
4210
|
+
var report = state.reports && state.reports.sessions;
|
|
4211
|
+
var replay = state.reports && state.reports.replay;
|
|
4212
|
+
var totals = report && report.totals ? report.totals : {};
|
|
4213
|
+
var sessions = Array.isArray(report && report.sessions) ? report.sessions : [];
|
|
4214
|
+
var replayEvents = Array.isArray(replay && replay.events) ? replay.events : [];
|
|
4215
|
+
if (els.sessionCaptureStatus) {
|
|
4216
|
+
els.sessionCaptureStatus.textContent = sessions.length ? String(totals.sessions || sessions.length) + " sessions" : "no sessions";
|
|
4217
|
+
}
|
|
4218
|
+
if (els.sessionCaptureSummary) {
|
|
4219
|
+
els.sessionCaptureSummary.textContent = "";
|
|
4220
|
+
[
|
|
4221
|
+
lifecycleSummaryStep("1", "Observed", Number(totals.observations || 0), "Privacy-scanned local events from agent sessions."),
|
|
4222
|
+
lifecycleSummaryStep("2", "Distillable", Number(totals.durable_observations || 0), "Reusable command, workflow, decision, or issue signals."),
|
|
4223
|
+
lifecycleSummaryStep("3", "Needs review", Number(totals.sessions_with_candidates || 0), "Sessions ready for packet review with kage distill.")
|
|
4224
|
+
].forEach(function (step) { els.sessionCaptureSummary.appendChild(step); });
|
|
4225
|
+
}
|
|
4226
|
+
els.sessionCaptureList.textContent = "";
|
|
4227
|
+
if (!sessions.length) {
|
|
4228
|
+
els.sessionCaptureList.className = "session-capture-list details-empty";
|
|
4229
|
+
els.sessionCaptureList.textContent = "No observed sessions yet. Agents can call kage_observe, then kage_distill turns durable observations into reviewable memory packets.";
|
|
4230
|
+
return;
|
|
4231
|
+
}
|
|
4232
|
+
els.sessionCaptureList.className = "session-capture-list";
|
|
4233
|
+
sessions.slice(0, 6).forEach(function (session) {
|
|
4234
|
+
var card = document.createElement("article");
|
|
4235
|
+
card.className = "session-capture-card";
|
|
4236
|
+
var candidates = Array.isArray(session.candidate_types) && session.candidate_types.length ? session.candidate_types.join(", ") : "none";
|
|
4237
|
+
var agents = Array.isArray(session.agents) && session.agents.length ? session.agents.join(", ") : "unknown agent";
|
|
4238
|
+
card.innerHTML = [
|
|
4239
|
+
"<div class=\"session-capture-head\"><div><strong></strong><span></span></div><em></em></div>",
|
|
4240
|
+
"<p></p>",
|
|
4241
|
+
"<div class=\"session-capture-meta\"></div>"
|
|
4242
|
+
].join("");
|
|
4243
|
+
card.querySelector("strong").textContent = session.session_id || "default";
|
|
4244
|
+
card.querySelector("span").textContent = [agents, session.last_at || session.first_at || ""].filter(Boolean).join(" · ");
|
|
4245
|
+
card.querySelector("em").textContent = String(session.durable_observations || 0) + " distillable";
|
|
4246
|
+
card.querySelector("p").textContent = session.next_action || "Review this session before saving durable memory.";
|
|
4247
|
+
var meta = card.querySelector(".session-capture-meta");
|
|
4248
|
+
[
|
|
4249
|
+
["events", session.observations || 0],
|
|
4250
|
+
["candidates", candidates],
|
|
4251
|
+
["commands", Array.isArray(session.commands) ? session.commands.slice(0, 2).join(", ") || "none" : "none"],
|
|
4252
|
+
["paths", Array.isArray(session.paths) ? session.paths.slice(0, 2).join(", ") || "none" : "none"]
|
|
4253
|
+
].forEach(function (item) {
|
|
4254
|
+
var chip = document.createElement("span");
|
|
4255
|
+
chip.textContent = item[0] + ": " + item[1];
|
|
4256
|
+
meta.appendChild(chip);
|
|
4257
|
+
});
|
|
4258
|
+
els.sessionCaptureList.appendChild(card);
|
|
4259
|
+
});
|
|
4260
|
+
if (replayEvents.length) {
|
|
4261
|
+
var replayCard = document.createElement("article");
|
|
4262
|
+
replayCard.className = "session-capture-card";
|
|
4263
|
+
replayCard.innerHTML = [
|
|
4264
|
+
"<div class=\"session-capture-head\"><div><strong>Replay digest</strong><span>raw transcript text excluded</span></div><em></em></div>",
|
|
4265
|
+
"<p></p>",
|
|
4266
|
+
"<div class=\"session-capture-meta\"></div>"
|
|
4267
|
+
].join("");
|
|
4268
|
+
replayCard.querySelector("em").textContent = String(replay && replay.totals ? replay.totals.durable_candidates || 0 : 0) + " candidates";
|
|
4269
|
+
replayCard.querySelector("p").textContent = replay && replay.next_action ? replay.next_action : "Review the digest, then distill durable observations into memory packets.";
|
|
4270
|
+
var replayMeta = replayCard.querySelector(".session-capture-meta");
|
|
4271
|
+
replayEvents.slice(0, 6).forEach(function (event) {
|
|
4272
|
+
var chip = document.createElement("span");
|
|
4273
|
+
chip.textContent = (event.durable_candidate ? "candidate: " : "event: ") + (event.label || event.type || "observation") + " · " + trimIntelText(event.summary || "", 90);
|
|
4274
|
+
replayMeta.appendChild(chip);
|
|
4275
|
+
});
|
|
4276
|
+
els.sessionCaptureList.appendChild(replayCard);
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4279
|
+
|
|
3201
4280
|
function memoryCodeLinksForEntity(entityId) {
|
|
3202
4281
|
return state.edges.filter(function (edge) {
|
|
3203
4282
|
if ((edge.from !== entityId && edge.to !== entityId) || !isMemoryCodeEdge(edge)) return false;
|
|
@@ -3357,17 +4436,28 @@
|
|
|
3357
4436
|
var packets = state.pendingPackets || [];
|
|
3358
4437
|
var inbox = state.inbox;
|
|
3359
4438
|
var inboxItems = inbox && Array.isArray(inbox.items) ? inbox.items : [];
|
|
4439
|
+
var handoff = state.reports && state.reports.handoff;
|
|
4440
|
+
var handoffItems = handoff && Array.isArray(handoff.items) ? handoff.items : [];
|
|
3360
4441
|
var counts = inbox && inbox.counts ? inbox.counts : {};
|
|
3361
|
-
var openCount = reviewOpenCount(counts, packets
|
|
3362
|
-
els.reviewCount.textContent = String(openCount);
|
|
4442
|
+
var openCount = reviewOpenCount(counts, packets);
|
|
4443
|
+
els.reviewCount.textContent = openCount ? String(openCount) : "clear";
|
|
3363
4444
|
els.reviewList.textContent = "";
|
|
3364
|
-
if (els.reviewOverview) renderReviewOverview(inbox, packets, inboxItems);
|
|
3365
|
-
|
|
4445
|
+
if (els.reviewOverview) renderReviewOverview(inbox, packets, inboxItems, handoff);
|
|
4446
|
+
renderMemoryHandoff(handoff, handoffItems);
|
|
4447
|
+
if (!packets.length && !inboxItems.length && !handoffItems.length && !state.reviewText) {
|
|
3366
4448
|
els.reviewList.className = "review-list details-empty";
|
|
3367
4449
|
els.reviewList.textContent = "No pending packets loaded. Launch with `kage viewer --project <repo>` to load review context automatically.";
|
|
3368
4450
|
return;
|
|
3369
4451
|
}
|
|
3370
4452
|
els.reviewList.className = "review-list";
|
|
4453
|
+
handoffItems.slice(0, 10).forEach(function (entry) {
|
|
4454
|
+
els.reviewList.appendChild(reviewCard({
|
|
4455
|
+
title: entry.title || entry.summary || entry.kind,
|
|
4456
|
+
meta: [entry.kind, entry.severity, entry.date && timelineDateLabel(entry.date)].filter(Boolean).join(" | "),
|
|
4457
|
+
summary: entry.summary || "",
|
|
4458
|
+
risk: entry.action || "Review before handoff"
|
|
4459
|
+
}, "handoff"));
|
|
4460
|
+
});
|
|
3371
4461
|
if (inbox) {
|
|
3372
4462
|
var summary = document.createElement("div");
|
|
3373
4463
|
summary.className = "review-item";
|
|
@@ -3387,7 +4477,7 @@
|
|
|
3387
4477
|
summary.querySelector(".review-summary").textContent = Array.isArray(inbox.recommendations) && inbox.recommendations.length
|
|
3388
4478
|
? inbox.recommendations.slice(0, 2).join(" ")
|
|
3389
4479
|
: "No inbox recommendations.";
|
|
3390
|
-
summary.querySelector(".review-risks").textContent = openCount ? "
|
|
4480
|
+
summary.querySelector(".review-risks").textContent = openCount ? "Inbox needs review before merge" : "Inbox: clear";
|
|
3391
4481
|
els.reviewList.appendChild(summary);
|
|
3392
4482
|
}
|
|
3393
4483
|
inboxItems.slice(0, 8).forEach(function (entry) {
|
|
@@ -3434,37 +4524,95 @@
|
|
|
3434
4524
|
}
|
|
3435
4525
|
}
|
|
3436
4526
|
|
|
3437
|
-
function renderReviewOverview(inbox, packets, inboxItems) {
|
|
4527
|
+
function renderReviewOverview(inbox, packets, inboxItems, handoff) {
|
|
3438
4528
|
els.reviewOverview.textContent = "";
|
|
3439
4529
|
var counts = inbox && inbox.counts ? inbox.counts : {};
|
|
3440
4530
|
var pending = Number(firstNumber(counts.pending, packets.length, 0));
|
|
3441
4531
|
var stale = Number(firstNumber(counts.stale, 0));
|
|
3442
4532
|
var duplicates = Number(firstNumber(counts.duplicates, 0));
|
|
3443
4533
|
var missingContext = Number(firstNumber(counts.missing_context, 0));
|
|
3444
|
-
var
|
|
4534
|
+
var inboxBlockers = reviewOpenCount(counts, packets);
|
|
4535
|
+
var handoffReviews = handoffReviewCount(handoff);
|
|
4536
|
+
var blockers = handoff ? handoffReviews : inboxBlockers;
|
|
4537
|
+
var mutations = handoff && handoff.totals ? Number(firstNumber(handoff.totals.recent_mutations, 0)) : 0;
|
|
3445
4538
|
els.reviewOverview.appendChild(metricDonut(
|
|
3446
4539
|
"Handoff readiness",
|
|
3447
4540
|
blockers ? 0 : 100,
|
|
3448
|
-
blockers ? blockers + " review
|
|
3449
|
-
blockers ? "Resolve
|
|
4541
|
+
blockers ? blockers + " handoff review item(s) need attention" : "No pending, stale, or duplicate memory",
|
|
4542
|
+
blockers ? "Resolve the handoff queue before trusting branch memory." : "Ready to hand work to another agent or teammate.",
|
|
3450
4543
|
blockers ? "warn" : "ok"
|
|
3451
4544
|
));
|
|
3452
|
-
els.reviewOverview.appendChild(metricBars("Inbox breakdown",
|
|
4545
|
+
els.reviewOverview.appendChild(metricBars(handoff ? "Inbox and handoff" : "Inbox breakdown", inboxBlockers ? inboxBlockers + " inbox blocker(s)" : "Inbox: clear", [
|
|
4546
|
+
{ label: "Handoff review", value: handoffReviews || "none", score: Math.min(100, handoffReviews * 24), status: handoffReviews ? "warn" : "ok" },
|
|
3453
4547
|
{ label: "Pending", value: pending, score: Math.min(100, pending * 24), status: pending ? "warn" : "ok" },
|
|
3454
4548
|
{ label: "Stale", value: stale, score: Math.min(100, stale * 24), status: stale ? "warn" : "ok" },
|
|
3455
4549
|
{ label: "Duplicates", value: duplicates, score: Math.min(100, duplicates * 24), status: duplicates ? "warn" : "ok" },
|
|
3456
|
-
{ label: "
|
|
3457
|
-
],
|
|
4550
|
+
{ label: "Mutations", value: mutations, score: Math.min(100, mutations * 16), status: mutations ? "ok" : "warn" }
|
|
4551
|
+
], handoff ? (handoff.summary || "Handoff review combines lifecycle, audit, timeline, and lineage.") : "These inbox metrics should block merge or handoff.", blockers ? "warn" : "ok"));
|
|
4552
|
+
}
|
|
4553
|
+
|
|
4554
|
+
function renderMemoryHandoff(handoff, items) {
|
|
4555
|
+
if (!els.handoffList) return;
|
|
4556
|
+
var totals = handoff && handoff.totals ? handoff.totals : {};
|
|
4557
|
+
if (els.handoffStatus) {
|
|
4558
|
+
els.handoffStatus.textContent = handoff ? (Number(totals.open_items || 0) ? Number(totals.open_items || 0) + " open" : "ready") : "waiting";
|
|
4559
|
+
}
|
|
4560
|
+
if (els.handoffSummary) {
|
|
4561
|
+
els.handoffSummary.textContent = "";
|
|
4562
|
+
[
|
|
4563
|
+
lifecycleSummaryStep("1", "Open", Number(totals.open_items || 0), "Blockers and warnings to resolve before another agent trusts memory."),
|
|
4564
|
+
lifecycleSummaryStep("2", "Distill", Number(totals.distillable_sessions || 0), "Observed sessions with reusable learnings that should become memory packets."),
|
|
4565
|
+
lifecycleSummaryStep("3", "Mutations", Number(totals.recent_mutations || 0), "Memory captures, reviews, feedback, and supersedes since audit logging began."),
|
|
4566
|
+
lifecycleSummaryStep("4", "Lineage", Number(totals.supersession_orphans || 0), "Retired packets missing a current replacement.")
|
|
4567
|
+
].forEach(function (step) { els.handoffSummary.appendChild(step); });
|
|
4568
|
+
}
|
|
4569
|
+
els.handoffList.textContent = "";
|
|
4570
|
+
if (!handoff) {
|
|
4571
|
+
els.handoffList.className = "session-capture-list details-empty";
|
|
4572
|
+
els.handoffList.textContent = "No handoff report loaded. Run kage handoff or open the local viewer after refresh.";
|
|
4573
|
+
return;
|
|
4574
|
+
}
|
|
4575
|
+
if (!items.length) {
|
|
4576
|
+
els.handoffList.className = "session-capture-list details-empty";
|
|
4577
|
+
els.handoffList.textContent = "No memory handoff actions are open.";
|
|
4578
|
+
return;
|
|
4579
|
+
}
|
|
4580
|
+
els.handoffList.className = "session-capture-list";
|
|
4581
|
+
items.slice(0, 8).forEach(function (entry) {
|
|
4582
|
+
els.handoffList.appendChild(reviewCard({
|
|
4583
|
+
title: entry.title || entry.summary || entry.kind,
|
|
4584
|
+
meta: [entry.kind, entry.severity, entry.date && timelineDateLabel(entry.date)].filter(Boolean).join(" | "),
|
|
4585
|
+
summary: entry.summary || "",
|
|
4586
|
+
risk: entry.action || "Review before handoff"
|
|
4587
|
+
}, "handoff"));
|
|
4588
|
+
});
|
|
3458
4589
|
}
|
|
3459
4590
|
|
|
3460
|
-
function
|
|
4591
|
+
function reviewCard(entry, extraClass) {
|
|
4592
|
+
var item = document.createElement("div");
|
|
4593
|
+
item.className = "review-item" + (extraClass ? " " + extraClass : "");
|
|
4594
|
+
item.innerHTML = [
|
|
4595
|
+
"<div class=\"review-title\"></div>",
|
|
4596
|
+
"<div class=\"review-meta\"></div>",
|
|
4597
|
+
"<div class=\"review-summary\"></div>",
|
|
4598
|
+
"<div class=\"review-risks\"></div>"
|
|
4599
|
+
].join("");
|
|
4600
|
+
item.querySelector(".review-title").textContent = entry.title || "";
|
|
4601
|
+
item.querySelector(".review-meta").textContent = entry.meta || "";
|
|
4602
|
+
item.querySelector(".review-summary").textContent = entry.summary || "";
|
|
4603
|
+
item.querySelector(".review-risks").textContent = entry.risk || "";
|
|
4604
|
+
return item;
|
|
4605
|
+
}
|
|
4606
|
+
|
|
4607
|
+
function handoffReviewCount(handoff) {
|
|
4608
|
+
return handoff && handoff.totals ? Number(firstNumber(handoff.totals.open_items, 0)) : 0;
|
|
4609
|
+
}
|
|
4610
|
+
|
|
4611
|
+
function reviewOpenCount(counts, packets) {
|
|
3461
4612
|
var pending = Number(firstNumber(counts && counts.pending, packets && packets.length, 0));
|
|
3462
4613
|
var stale = Number(firstNumber(counts && counts.stale, 0));
|
|
3463
4614
|
var duplicates = Number(firstNumber(counts && counts.duplicates, 0));
|
|
3464
|
-
|
|
3465
|
-
var counted = pending + stale + duplicates + missingContext;
|
|
3466
|
-
if (counted) return counted;
|
|
3467
|
-
return Array.isArray(inboxItems) ? inboxItems.length : 0;
|
|
4615
|
+
return pending + stale + duplicates;
|
|
3468
4616
|
}
|
|
3469
4617
|
|
|
3470
4618
|
function renderProof() {
|
|
@@ -3481,6 +4629,7 @@
|
|
|
3481
4629
|
els.proofStatus.textContent = "loaded";
|
|
3482
4630
|
els.proofList.className = "proof-list";
|
|
3483
4631
|
if (els.proofOverview) renderProofOverview(metrics, state.reports || {});
|
|
4632
|
+
renderProofLedger(state.reports && state.reports.benchmark);
|
|
3484
4633
|
var rows = [
|
|
3485
4634
|
["Validation", metrics.harness && metrics.harness.validation_ok ? "clean" : "check"],
|
|
3486
4635
|
["Evidence", metrics.memory_graph ? metrics.memory_graph.evidence_coverage_percent + "%" : "n/a"],
|
|
@@ -3497,15 +4646,58 @@
|
|
|
3497
4646
|
});
|
|
3498
4647
|
}
|
|
3499
4648
|
|
|
4649
|
+
function renderProofLedger(benchmark) {
|
|
4650
|
+
var ledger = benchmark && Array.isArray(benchmark.proof_ledger) ? benchmark.proof_ledger : [];
|
|
4651
|
+
ledger.forEach(function (entry) {
|
|
4652
|
+
var item = document.createElement("div");
|
|
4653
|
+
item.className = "proof-item proof-ledger-item " + (entry.pass ? "is-ok" : "is-warn");
|
|
4654
|
+
item.innerHTML = [
|
|
4655
|
+
"<strong></strong>",
|
|
4656
|
+
"<span></span>",
|
|
4657
|
+
"<code></code>",
|
|
4658
|
+
"<p></p>"
|
|
4659
|
+
].join("");
|
|
4660
|
+
item.querySelector("strong").textContent = entry.metric || (entry.pass ? "passing" : "check");
|
|
4661
|
+
item.querySelector("span").textContent = [entry.label, entry.target].filter(Boolean).join(" | ");
|
|
4662
|
+
item.querySelector("code").textContent = entry.command || "";
|
|
4663
|
+
item.querySelector("p").textContent = entry.next_action || "Review this proof before publishing benchmark claims.";
|
|
4664
|
+
els.proofList.appendChild(item);
|
|
4665
|
+
});
|
|
4666
|
+
}
|
|
4667
|
+
|
|
3500
4668
|
function renderProofOverview(metrics, reports) {
|
|
3501
4669
|
els.proofOverview.textContent = "";
|
|
3502
4670
|
var quality = reports.quality || {};
|
|
3503
4671
|
var benchmark = reports.benchmark || {};
|
|
3504
4672
|
var gates = Array.isArray(benchmark.gates) ? benchmark.gates : [];
|
|
4673
|
+
var retrieval = benchmarkRetrievalSummary(benchmark);
|
|
4674
|
+
var sourceDiversity = benchmarkSourceDiversitySummary(benchmark);
|
|
4675
|
+
var scale = benchmarkScaleSummary(benchmark);
|
|
3505
4676
|
var passingGates = gates.filter(function (gate) { return gate.pass; }).length;
|
|
3506
4677
|
var gatePercent = gates.length ? Math.round(passingGates / gates.length * 100) : (benchmark.ok ? 100 : 0);
|
|
3507
4678
|
var evidence = Number(firstNumber(metrics.memory_graph && metrics.memory_graph.evidence_coverage_percent, quality.evidence_coverage_percent, 0));
|
|
3508
4679
|
var pathGrounding = Number(firstNumber(quality.path_grounding_coverage_percent, 0));
|
|
4680
|
+
if (retrieval) {
|
|
4681
|
+
els.proofOverview.appendChild(metricBars("Retrieval proof", retrieval.r10 + "% R@10", [
|
|
4682
|
+
{ label: "R@5", value: retrieval.r5 != null ? retrieval.r5 + "%" : "n/a", score: retrieval.r5 || 0, status: retrieval.r5 >= 95 ? "ok" : "warn" },
|
|
4683
|
+
{ label: "R@10", value: retrieval.r10 + "%", score: retrieval.r10, status: retrieval.r10 >= 95 ? "ok" : "warn" },
|
|
4684
|
+
{ label: "NDCG@10", value: retrieval.ndcg10 != null ? retrieval.ndcg10 : "n/a", score: retrieval.ndcg10 != null ? retrieval.ndcg10 * 100 : 0, status: retrieval.ndcg10 >= 0.85 ? "ok" : "warn" }
|
|
4685
|
+
], retrieval.label + ". Measures memory retrieval proof, not answer accuracy.", retrieval.r10 >= 95 ? "ok" : "warn"));
|
|
4686
|
+
}
|
|
4687
|
+
if (sourceDiversity) {
|
|
4688
|
+
els.proofOverview.appendChild(metricBars("Source diversity proof", sourceDiversity.uniqueSources + " sources", [
|
|
4689
|
+
{ label: "Sources", value: sourceDiversity.uniqueSources, score: Math.min(100, sourceDiversity.uniqueSources * 50), status: sourceDiversity.uniqueSources >= 2 ? "ok" : "warn" },
|
|
4690
|
+
{ label: "Max/session", value: sourceDiversity.maxFromOneSource, score: sourceDiversity.maxFromOneSource <= 3 ? 100 : 45, status: sourceDiversity.maxFromOneSource <= 3 ? "ok" : "warn" },
|
|
4691
|
+
{ label: "Independent rank", value: sourceDiversity.independentRank != null ? "#" + sourceDiversity.independentRank : "miss", score: sourceDiversity.independentRank != null && sourceDiversity.independentRank <= sourceDiversity.topK ? 100 : 35, status: sourceDiversity.pass ? "ok" : "warn" }
|
|
4692
|
+
], "Noisy observed sessions should not crowd out independent teammate memory.", sourceDiversity.pass ? "ok" : "warn"));
|
|
4693
|
+
}
|
|
4694
|
+
if (scale) {
|
|
4695
|
+
els.proofOverview.appendChild(metricBars("Scale proof", scale.packets + " packets", [
|
|
4696
|
+
{ label: "Hit rate", value: scale.hitRate + "%", score: scale.hitRate, status: scale.hitRate >= 95 ? "ok" : "warn" },
|
|
4697
|
+
{ label: "Median", value: scale.medianLatency + "ms", score: Math.max(0, 100 - scale.medianLatency), status: scale.medianLatency <= 50 ? "ok" : "warn" },
|
|
4698
|
+
{ label: "Context cut", value: scale.contextReduction + "%", score: scale.contextReduction, status: scale.contextReduction >= 80 ? "ok" : "warn" }
|
|
4699
|
+
], "Scale benchmark checks large repo-memory retrieval cost.", scale.hitRate >= 95 ? "ok" : "warn"));
|
|
4700
|
+
}
|
|
3509
4701
|
els.proofOverview.appendChild(metricDonut(
|
|
3510
4702
|
"Trust gate",
|
|
3511
4703
|
gatePercent,
|
|
@@ -3520,6 +4712,70 @@
|
|
|
3520
4712
|
], "Trust memory only when it is evidence-backed and path-grounded.", evidence >= 80 ? "ok" : "warn"));
|
|
3521
4713
|
}
|
|
3522
4714
|
|
|
4715
|
+
function benchmarkRetrievalSummary(report) {
|
|
4716
|
+
if (!report || !report.summary) return null;
|
|
4717
|
+
var summary = report.summary;
|
|
4718
|
+
var r10 = Number(summary.recall_at_10_percent);
|
|
4719
|
+
if (!Number.isFinite(r10)) return null;
|
|
4720
|
+
var mode = summary.retrieval_mode || report.retrieval_mode || "";
|
|
4721
|
+
var embeddings = summary.embeddings || report.embeddings || null;
|
|
4722
|
+
var modeLabel = mode === "kage-recall-with-dense-local-embeddings"
|
|
4723
|
+
? "dense local embeddings"
|
|
4724
|
+
: mode === "kage-recall-default"
|
|
4725
|
+
? "default recall"
|
|
4726
|
+
: mode;
|
|
4727
|
+
var modelLabel = embeddings && embeddings.model ? " · " + embeddings.model : "";
|
|
4728
|
+
return {
|
|
4729
|
+
label: [summary.benchmark || report.benchmark || "Retrieval benchmark", modeLabel].filter(Boolean).join(" · "),
|
|
4730
|
+
mode: modeLabel,
|
|
4731
|
+
model: modelLabel ? modelLabel.slice(3) : "",
|
|
4732
|
+
r5: numberOrNull(summary.recall_at_5_percent),
|
|
4733
|
+
r10: r10,
|
|
4734
|
+
r20: numberOrNull(summary.recall_at_20_percent),
|
|
4735
|
+
mrr: numberOrNull(summary.mrr),
|
|
4736
|
+
ndcg10: numberOrNull(summary.ndcg_at_10),
|
|
4737
|
+
contextReduction: numberOrNull(summary.context_reduction_percent)
|
|
4738
|
+
};
|
|
4739
|
+
}
|
|
4740
|
+
|
|
4741
|
+
function benchmarkSourceDiversitySummary(report) {
|
|
4742
|
+
var diversity = report && report.memory_quality && report.memory_quality.source_diversity
|
|
4743
|
+
? report.memory_quality.source_diversity
|
|
4744
|
+
: report && report.source_diversity;
|
|
4745
|
+
if (!diversity) return null;
|
|
4746
|
+
var uniqueSources = numberOrNull(diversity.unique_sources);
|
|
4747
|
+
var maxFromOneSource = numberOrNull(diversity.max_results_from_one_source);
|
|
4748
|
+
var topK = numberOrNull(diversity.top_k) || 4;
|
|
4749
|
+
if (uniqueSources == null || maxFromOneSource == null) return null;
|
|
4750
|
+
return {
|
|
4751
|
+
uniqueSources: uniqueSources,
|
|
4752
|
+
maxFromOneSource: maxFromOneSource,
|
|
4753
|
+
independentRank: numberOrNull(diversity.independent_source_rank),
|
|
4754
|
+
topK: topK,
|
|
4755
|
+
pass: Boolean(diversity.pass)
|
|
4756
|
+
};
|
|
4757
|
+
}
|
|
4758
|
+
|
|
4759
|
+
function benchmarkScaleSummary(report) {
|
|
4760
|
+
var scale = report && report.memory_scale;
|
|
4761
|
+
var summary = scale && scale.summary;
|
|
4762
|
+
if (!summary) return null;
|
|
4763
|
+
var hitRate = numberOrNull(summary.largest_hit_rate_percent);
|
|
4764
|
+
var packets = numberOrNull(summary.largest_packets);
|
|
4765
|
+
if (hitRate == null || packets == null) return null;
|
|
4766
|
+
return {
|
|
4767
|
+
packets: packets,
|
|
4768
|
+
hitRate: hitRate,
|
|
4769
|
+
medianLatency: numberOrNull(summary.largest_median_recall_latency_ms) || 0,
|
|
4770
|
+
contextReduction: numberOrNull(summary.largest_context_reduction_percent) || 0
|
|
4771
|
+
};
|
|
4772
|
+
}
|
|
4773
|
+
|
|
4774
|
+
function numberOrNull(value) {
|
|
4775
|
+
var number = Number(value);
|
|
4776
|
+
return Number.isFinite(number) ? number : null;
|
|
4777
|
+
}
|
|
4778
|
+
|
|
3523
4779
|
function renderIntelligence() {
|
|
3524
4780
|
if (!els.intelligenceList) return;
|
|
3525
4781
|
var reports = state.reports || {};
|
|
@@ -3528,11 +4784,15 @@
|
|
|
3528
4784
|
els.intelligenceStatus.textContent = cards.length ? cards.length + " loaded" : "not loaded";
|
|
3529
4785
|
if (!cards.length) {
|
|
3530
4786
|
els.intelligenceList.className = "intelligence-list details-empty";
|
|
3531
|
-
els.intelligenceList.textContent = "No
|
|
4787
|
+
els.intelligenceList.textContent = "No before-edit reports loaded. Launch with `kage viewer --project <repo>` to load risk, module health, graph insights, and workspace reports.";
|
|
3532
4788
|
return;
|
|
3533
4789
|
}
|
|
3534
4790
|
els.intelligenceList.className = "intelligence-list";
|
|
3535
|
-
normalizeIntelCards(cards)
|
|
4791
|
+
var normalizedCards = normalizeIntelCards(cards);
|
|
4792
|
+
var riskFirst = new Set(["Before You Edit", "Change Risk", "Module Health", "Decision Memory", "Graph Insights", "Memory Quality"]);
|
|
4793
|
+
var primaryCards = normalizedCards.filter(function (card) { return riskFirst.has(card.title); }).slice(0, 3);
|
|
4794
|
+
if (!primaryCards.length) primaryCards = normalizedCards.slice(0, 3);
|
|
4795
|
+
primaryCards.forEach(function (card) {
|
|
3536
4796
|
var item = document.createElement("article");
|
|
3537
4797
|
item.className = "intel-card";
|
|
3538
4798
|
item.innerHTML = [
|
|
@@ -3549,23 +4809,27 @@
|
|
|
3549
4809
|
item.querySelector(".intel-highlight").textContent = card.highlight || card.summary || "";
|
|
3550
4810
|
item.querySelector(".intel-action span").textContent = card.action || "Review this signal before changing related code.";
|
|
3551
4811
|
var list = item.querySelector("ul");
|
|
3552
|
-
card.rows.slice(0, 3).forEach(function (row) {
|
|
4812
|
+
card.rows.slice(0, Number(card.rowLimit || (card.title === "Before You Edit" ? 3 : 2))).forEach(function (row) {
|
|
3553
4813
|
var li = document.createElement("li");
|
|
3554
4814
|
li.innerHTML = "<strong></strong> <span></span>";
|
|
3555
4815
|
li.querySelector("strong").textContent = row[0];
|
|
3556
|
-
li.querySelector("span").textContent = trimIntelText(row[1], 92);
|
|
4816
|
+
li.querySelector("span").textContent = card.title === "Before You Edit" ? row[1] : trimIntelText(row[1], 92);
|
|
3557
4817
|
list.appendChild(li);
|
|
3558
4818
|
});
|
|
3559
4819
|
els.intelligenceList.appendChild(item);
|
|
3560
4820
|
});
|
|
3561
|
-
var sections = rankIntelligenceSections(buildIntelligenceSections(reports)).slice(0,
|
|
4821
|
+
var sections = rankIntelligenceSections(buildIntelligenceSections(reports)).slice(0, 2);
|
|
3562
4822
|
if (sections.length) {
|
|
4823
|
+
var details = document.createElement("details");
|
|
4824
|
+
details.className = "intel-deep-drawer";
|
|
4825
|
+
details.innerHTML = "<summary><span>More repo signals</span><em>cycles, owners, modules</em></summary>";
|
|
3563
4826
|
var deepGrid = document.createElement("div");
|
|
3564
4827
|
deepGrid.className = "intel-deep-grid";
|
|
3565
4828
|
sections.forEach(function (section) {
|
|
3566
4829
|
deepGrid.appendChild(renderIntelligenceSection(section));
|
|
3567
4830
|
});
|
|
3568
|
-
|
|
4831
|
+
details.appendChild(deepGrid);
|
|
4832
|
+
els.intelligenceList.appendChild(details);
|
|
3569
4833
|
}
|
|
3570
4834
|
}
|
|
3571
4835
|
|
|
@@ -3621,6 +4885,7 @@
|
|
|
3621
4885
|
|
|
3622
4886
|
function intelligenceSectionPriority(section) {
|
|
3623
4887
|
var title = String(section && section.title || "").toLowerCase();
|
|
4888
|
+
if (title.indexOf("before you edit") !== -1 || title.indexOf("checklist") !== -1) return 0;
|
|
3624
4889
|
if (title.indexOf("blast") !== -1 || title.indexOf("risk") !== -1) return 0;
|
|
3625
4890
|
if (title.indexOf("onboarding") !== -1 || title.indexOf("decision") !== -1) return 1;
|
|
3626
4891
|
if (title.indexOf("module") !== -1 || title.indexOf("health") !== -1) return 2;
|
|
@@ -3628,6 +4893,81 @@
|
|
|
3628
4893
|
return 4;
|
|
3629
4894
|
}
|
|
3630
4895
|
|
|
4896
|
+
function riskTargetList(risk) {
|
|
4897
|
+
if (!risk) return [];
|
|
4898
|
+
return Array.isArray(risk.targets)
|
|
4899
|
+
? risk.targets
|
|
4900
|
+
: Object.keys(risk.targets || {}).map(function (key) { return risk.targets[key]; }).filter(Boolean);
|
|
4901
|
+
}
|
|
4902
|
+
|
|
4903
|
+
function isGeneratedMemoryPathValue(path) {
|
|
4904
|
+
var normalized = String(path || "").replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
4905
|
+
return normalized.indexOf(".agent_memory/") === 0 ||
|
|
4906
|
+
normalized.indexOf("agent_memory/") === 0 ||
|
|
4907
|
+
normalized.indexOf("/.agent_memory/") !== -1 ||
|
|
4908
|
+
normalized.indexOf("/agent_memory/") !== -1;
|
|
4909
|
+
}
|
|
4910
|
+
|
|
4911
|
+
function isUserFacingRiskPath(path) {
|
|
4912
|
+
var normalized = String(path || "").replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
4913
|
+
if (!normalized || normalized === "." || normalized === "root") return false;
|
|
4914
|
+
if (isGeneratedMemoryPathValue(normalized)) return false;
|
|
4915
|
+
if (normalized.indexOf("node_modules/") !== -1 || normalized.indexOf("dist/") === 0 || normalized.indexOf("build/") === 0) return false;
|
|
4916
|
+
return true;
|
|
4917
|
+
}
|
|
4918
|
+
|
|
4919
|
+
function userFacingRiskTargets(risk) {
|
|
4920
|
+
return riskTargetList(risk).filter(function (target) {
|
|
4921
|
+
return target && isUserFacingRiskPath(target.target);
|
|
4922
|
+
});
|
|
4923
|
+
}
|
|
4924
|
+
|
|
4925
|
+
function userFacingRiskHotspots(risk) {
|
|
4926
|
+
return Array.isArray(risk && risk.global_hotspots)
|
|
4927
|
+
? risk.global_hotspots.filter(function (hotspot) { return hotspot && isUserFacingRiskPath(hotspot.file_path); })
|
|
4928
|
+
: [];
|
|
4929
|
+
}
|
|
4930
|
+
|
|
4931
|
+
function riskExplanation(item) {
|
|
4932
|
+
var dependents = Number(item && item.dependents_count || 0);
|
|
4933
|
+
var commits = Number(item && item.git && item.git.commit_count_90d || 0);
|
|
4934
|
+
var owner = item && item.git ? shortContributor(item.git.primary_owner || "") : "";
|
|
4935
|
+
if (item && item.test_gap) {
|
|
4936
|
+
return {
|
|
4937
|
+
why: "changes here are not protected by a direct test signal",
|
|
4938
|
+
action: "find or add the closest verification before handoff"
|
|
4939
|
+
};
|
|
4940
|
+
}
|
|
4941
|
+
if (dependents >= 5) {
|
|
4942
|
+
return {
|
|
4943
|
+
why: "many other files depend on this path",
|
|
4944
|
+
action: "inspect dependents and run the full related test suite"
|
|
4945
|
+
};
|
|
4946
|
+
}
|
|
4947
|
+
if (item && item.risk_type === "single-owner") {
|
|
4948
|
+
return {
|
|
4949
|
+
why: owner ? "most history belongs to " + owner : "ownership is concentrated in one person",
|
|
4950
|
+
action: "route review to the owner or write down assumptions before editing"
|
|
4951
|
+
};
|
|
4952
|
+
}
|
|
4953
|
+
if (commits >= 20 || item && item.risk_type === "churn-heavy") {
|
|
4954
|
+
return {
|
|
4955
|
+
why: "this area changed " + commits + " time(s) in the last 90 days",
|
|
4956
|
+
action: "recall recent memory and keep the change narrow"
|
|
4957
|
+
};
|
|
4958
|
+
}
|
|
4959
|
+
if (dependents > 0) {
|
|
4960
|
+
return {
|
|
4961
|
+
why: dependents + " file(s) depend on this path",
|
|
4962
|
+
action: "check the dependent paths before editing"
|
|
4963
|
+
};
|
|
4964
|
+
}
|
|
4965
|
+
return {
|
|
4966
|
+
why: item && item.risk_type ? "Kage sees a " + item.risk_type.replace(/-/g, " ") + " signal" : "Kage sees a local change-risk signal",
|
|
4967
|
+
action: "state the expected behavior and verify it after editing"
|
|
4968
|
+
};
|
|
4969
|
+
}
|
|
4970
|
+
|
|
3631
4971
|
function buildIntelligenceSections(reports) {
|
|
3632
4972
|
var sections = [];
|
|
3633
4973
|
var contributors = reports.contributors;
|
|
@@ -3816,13 +5156,14 @@
|
|
|
3816
5156
|
}
|
|
3817
5157
|
|
|
3818
5158
|
if (risk) {
|
|
3819
|
-
var targets =
|
|
3820
|
-
var hotspots =
|
|
5159
|
+
var targets = userFacingRiskTargets(risk);
|
|
5160
|
+
var hotspots = userFacingRiskHotspots(risk);
|
|
3821
5161
|
var riskRows = targets.slice(0, 6).map(function (item) {
|
|
5162
|
+
var explanation = riskExplanation(item);
|
|
3822
5163
|
return {
|
|
3823
5164
|
label: item.target,
|
|
3824
|
-
value:
|
|
3825
|
-
meta:
|
|
5165
|
+
value: explanation.why,
|
|
5166
|
+
meta: "Do first: " + explanation.action,
|
|
3826
5167
|
score: Math.round(Number(item.hotspot_score || 0) * 100) || Math.min(100, Number(item.dependents_count || 0) * 12 + (item.test_gap ? 24 : 0)),
|
|
3827
5168
|
status: item.test_gap || item.risk_type === "single-owner" ? "warn" : "",
|
|
3828
5169
|
path: item.target,
|
|
@@ -3830,8 +5171,8 @@
|
|
|
3830
5171
|
}).concat(hotspots.slice(0, 4).map(function (hotspot) {
|
|
3831
5172
|
return {
|
|
3832
5173
|
label: hotspot.file_path,
|
|
3833
|
-
value:
|
|
3834
|
-
meta:
|
|
5174
|
+
value: "changed " + (hotspot.commit_count_90d || 0) + " time(s) in 90 days",
|
|
5175
|
+
meta: "Do first: recall recent repo memory and run the closest tests after editing.",
|
|
3835
5176
|
score: Math.round(Number(hotspot.hotspot_score || 0) * 100),
|
|
3836
5177
|
status: "danger",
|
|
3837
5178
|
path: hotspot.file_path,
|
|
@@ -3839,10 +5180,10 @@
|
|
|
3839
5180
|
}));
|
|
3840
5181
|
if (riskRows.length) {
|
|
3841
5182
|
sections.push({
|
|
3842
|
-
title: "
|
|
3843
|
-
kicker: "
|
|
3844
|
-
stat: riskRows.length + "
|
|
3845
|
-
summary: "
|
|
5183
|
+
title: "Before You Edit Checklist",
|
|
5184
|
+
kicker: "why risky / do first",
|
|
5185
|
+
stat: riskRows.length + " checks",
|
|
5186
|
+
summary: "Each row explains what can go wrong and the first safety step before an agent edits it.",
|
|
3846
5187
|
rows: riskRows,
|
|
3847
5188
|
limit: 10,
|
|
3848
5189
|
});
|
|
@@ -3904,16 +5245,24 @@
|
|
|
3904
5245
|
}
|
|
3905
5246
|
var risk = reports.risk;
|
|
3906
5247
|
if (risk) {
|
|
3907
|
-
var targets =
|
|
5248
|
+
var targets = userFacingRiskTargets(risk);
|
|
3908
5249
|
var silos = Array.isArray(risk.ownership_silos) ? risk.ownership_silos : [];
|
|
5250
|
+
var hotspots = userFacingRiskHotspots(risk);
|
|
5251
|
+
var riskRows = targets.slice(0, 4).map(function (item) {
|
|
5252
|
+
var explanation = riskExplanation(item);
|
|
5253
|
+
return [item.target || "edit area", "Why: " + explanation.why + " Do first: " + explanation.action];
|
|
5254
|
+
}).concat(targets.length ? [] : hotspots.slice(0, 2).map(function (hotspot) {
|
|
5255
|
+
return [hotspot.file_path || "hotspot", "Why: changed " + (hotspot.commit_count_90d || 0) + " time(s) in 90 days. Do first: recall recent memory and run closest tests after editing."];
|
|
5256
|
+
}));
|
|
3909
5257
|
cards.push({
|
|
3910
|
-
title: "
|
|
3911
|
-
kicker: "
|
|
3912
|
-
summary:
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
5258
|
+
title: "Before You Edit",
|
|
5259
|
+
kicker: "risk checklist",
|
|
5260
|
+
summary: riskRows.length
|
|
5261
|
+
? "These are places where an agent is more likely to break behavior, miss tests, or rely on unstated knowledge."
|
|
5262
|
+
: "No user-facing risky edit areas were found in the loaded report.",
|
|
5263
|
+
rowLimit: 3,
|
|
5264
|
+
rows: (riskRows.length ? riskRows : [["Risk", "No code-level risk checks loaded"]])
|
|
5265
|
+
.concat(silos.length ? [["Reviewer signal", silos.length + " ownership concentration(s)"]] : [])
|
|
3917
5266
|
});
|
|
3918
5267
|
}
|
|
3919
5268
|
var contributors = reports.contributors;
|
|
@@ -4020,13 +5369,26 @@
|
|
|
4020
5369
|
var benchmark = reports.benchmark;
|
|
4021
5370
|
if (benchmark) {
|
|
4022
5371
|
var checks = Array.isArray(benchmark.checks) ? benchmark.checks : [];
|
|
5372
|
+
var retrieval = benchmarkRetrievalSummary(benchmark);
|
|
5373
|
+
var scale = benchmarkScaleSummary(benchmark);
|
|
4023
5374
|
cards.push({
|
|
4024
|
-
title: "Benchmark",
|
|
4025
|
-
kicker: "local proof",
|
|
4026
|
-
summary:
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
5375
|
+
title: retrieval ? "Retrieval Proof" : "Benchmark",
|
|
5376
|
+
kicker: retrieval ? (retrieval.mode || "retrieval proof") : "local proof",
|
|
5377
|
+
summary: retrieval
|
|
5378
|
+
? retrieval.label + (scale ? " plus " + scale.packets + "-packet scale proof." : "") + " Evidence retrieval only, not answer accuracy."
|
|
5379
|
+
: benchmark.summary || "Local memory and graph benchmark signals.",
|
|
5380
|
+
rows: retrieval
|
|
5381
|
+
? [
|
|
5382
|
+
["R@5", retrieval.r5 != null ? retrieval.r5 + "%" : "n/a"],
|
|
5383
|
+
["R@10", retrieval.r10 + "%"],
|
|
5384
|
+
["MRR", retrieval.mrr != null ? String(retrieval.mrr) : "n/a"],
|
|
5385
|
+
["Context cut", retrieval.contextReduction != null ? retrieval.contextReduction + "%" : "n/a"],
|
|
5386
|
+
["Scale", scale ? scale.hitRate + "% hit / " + scale.medianLatency + "ms" : "not loaded"],
|
|
5387
|
+
["Model", retrieval.model || "dependency-free default"]
|
|
5388
|
+
]
|
|
5389
|
+
: checks.slice(0, 5).map(function (item) {
|
|
5390
|
+
return [item.name || "check", (item.pass ? "pass" : "check") + " - " + item.actual + "/" + item.target];
|
|
5391
|
+
})
|
|
4030
5392
|
});
|
|
4031
5393
|
}
|
|
4032
5394
|
return cards;
|
|
@@ -4044,13 +5406,13 @@
|
|
|
4044
5406
|
normalized.metricLabel = "memory-code links";
|
|
4045
5407
|
normalized.highlight = "Shows whether saved repo knowledge is tied to actual files, symbols, routes, and tests.";
|
|
4046
5408
|
normalized.action = "If this is low, capture memory with concrete paths so agents can recall it during edits.";
|
|
4047
|
-
} else if (card.title === "Change Risk") {
|
|
4048
|
-
var siloText = row("Silos");
|
|
5409
|
+
} else if (card.title === "Before You Edit" || card.title === "Change Risk") {
|
|
5410
|
+
var siloText = row("Reviewer signal") || row("Silos");
|
|
4049
5411
|
var siloMatch = siloText && String(siloText).match(/\d+/);
|
|
4050
|
-
normalized.metric =
|
|
4051
|
-
normalized.metricLabel = "
|
|
4052
|
-
normalized.highlight = "
|
|
4053
|
-
normalized.action = "
|
|
5412
|
+
normalized.metric = (card.rows || []).length + " checks";
|
|
5413
|
+
normalized.metricLabel = siloMatch ? siloMatch[0] + " reviewer silo(s)" : "pre-edit checklist";
|
|
5414
|
+
normalized.highlight = card.summary || "Shows what can go wrong before an agent edits a file.";
|
|
5415
|
+
normalized.action = "Start with the first row: read why it is risky, then do the listed safety step before editing.";
|
|
4054
5416
|
} else if (card.title === "Contributors") {
|
|
4055
5417
|
normalized.metric = (card.rows || []).length + " profiles";
|
|
4056
5418
|
normalized.metricLabel = "review routing";
|