@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/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
- workspace: null
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://risks",
213
- title: "Risks",
214
- summary: "Files, owners, and modules to inspect before changes. Each card links into the graph."
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.addEventListener("change", function () {
260
- state.lastVisibleSignature = "";
261
- render();
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/workspace.json").catch(function () { return null; })
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.decisions = items[6];
640
- state.reports.moduleHealth = items[7];
641
- state.reports.graphInsights = items[8];
642
- state.reports.workspace = items[9];
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 = Array.isArray(risk.targets) ? risk.targets : Object.keys(risk.targets || {});
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 = Array.isArray(risk.global_hotspots) ? risk.global_hotspots.length : 0;
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) + " signals" : "No flags";
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
- ["Risk", riskHealth, riskTargets.length + " targets, " + ownerSilos + " ownership silos", riskTargets.length || ownerSilos || hotspots ? "warn" : "ok"],
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
- ["Risk targets", riskTargets.length || "none"],
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
- els.dashboardCharts.textContent = "";
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
- metricBars("Handoff blockers", blockers ? blockers + " open" : "clear", [
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
- metricBars("Change risk", riskSignals ? riskSignals + " signals" : "none", [
2972
- { label: "Targets", value: data.riskTargets.length, score: Math.min(100, data.riskTargets.length * 18), status: data.riskTargets.length ? "warn" : "ok" },
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 Intel or Owners before editing risky files." : "No loaded risk flags.", riskSignals ? "warn" : "ok")
2976
- ].forEach(function (card) { els.dashboardCharts.appendChild(card); });
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, inboxItems);
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
- if (!packets.length && !inboxItems.length && !state.reviewText) {
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 ? "Resolve inbox items before merge" : "Ready for handoff";
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 blockers = reviewOpenCount(counts, packets, inboxItems);
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 blocker(s) need attention" : "No pending, stale, duplicate, or missing-context memory",
3449
- blockers ? "Resolve these before trusting branch memory." : "Ready to hand work to another agent or teammate.",
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", blockers ? blockers + " open" : "clear", [
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: "Missing context", value: missingContext, score: Math.min(100, missingContext * 24), status: missingContext ? "warn" : "ok" }
3457
- ], "These are the only review metrics that should block merge or handoff.", blockers ? "warn" : "ok"));
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 reviewOpenCount(counts, packets, inboxItems) {
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
- var missingContext = Number(firstNumber(counts && counts.missing_context, 0));
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 repo intelligence reports loaded. Launch with `kage viewer --project <repo>` to load risk, module health, graph insights, and workspace reports.";
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).slice(0, 6).forEach(function (card) {
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, 4);
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
- els.intelligenceList.appendChild(deepGrid);
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 = Array.isArray(risk.targets) ? risk.targets : Object.keys(risk.targets || {}).map(function (key) { return risk.targets[key]; });
3820
- var hotspots = Array.isArray(risk.global_hotspots) ? risk.global_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: item.risk_type || "risk",
3825
- meta: item.risk_summary || "",
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: Math.round(Number(hotspot.hotspot_score || 0) * 100) + "% hot",
3834
- meta: (hotspot.commit_count_90d || 0) + " commits in 90d, owner " + shortContributor(hotspot.primary_owner || "unknown"),
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: "Blast Radius",
3843
- kicker: "change impact",
3844
- stat: riskRows.length + " signals",
3845
- summary: "Action: review tests, owners, and dependents before editing these targets.",
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 = Array.isArray(risk.targets) ? risk.targets : Object.keys(risk.targets || {}).map(function (key) { return risk.targets[key]; });
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: "Change Risk",
3911
- kicker: "blast radius",
3912
- summary: risk.summary || "Local risk report from code graph and git history.",
3913
- rows: targets.slice(0, 5).map(function (item) {
3914
- return [item.target || "target", item.risk_summary || [item.risk_type, item.dependents_count != null ? item.dependents_count + " dependents" : ""].filter(Boolean).join(", ")];
3915
- }).concat(targets.length ? [] : [["Hotspots", Array.isArray(risk.global_hotspots) ? risk.global_hotspots.length + " global" : "none"]])
3916
- .concat(silos.length ? [["Silos", silos.length + " ownership concentration(s)"]] : [])
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: benchmark.summary || "Local memory and graph benchmark signals.",
4027
- rows: checks.slice(0, 5).map(function (item) {
4028
- return [item.name || "check", (item.pass ? "pass" : "check") + " - " + item.actual + "/" + item.target];
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 = siloMatch ? siloMatch[0] + " silos" : ((card.rows || []).length + " signals");
4051
- normalized.metricLabel = "risk signals";
4052
- normalized.highlight = "Flags files with blast radius, test gaps, or ownership concentration.";
4053
- normalized.action = "Use these rows to pick tests and reviewers before touching risky files.";
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";