@kage-core/kage-graph-mcp 1.1.36 → 1.1.37

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,23 @@
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
+ workspace: null,
41
+ sessions: null,
42
+ replay: null,
43
+ memoryAccess: null,
44
+ memoryAudit: null,
45
+ handoff: null,
46
+ lifecycle: null,
47
+ timeline: null,
48
+ lineage: null,
49
+ setup: null
37
50
  },
38
51
  pendingPackets: [],
39
52
  reviewText: "",
@@ -173,6 +186,23 @@
173
186
  memoryStatus: document.getElementById("memoryStatus"),
174
187
  memoryStats: document.getElementById("memoryStats"),
175
188
  memoryOverview: document.getElementById("memoryOverview"),
189
+ lifecycleStatus: document.getElementById("lifecycleStatus"),
190
+ lifecycleSummary: document.getElementById("lifecycleSummary"),
191
+ lifecycleList: document.getElementById("lifecycleList"),
192
+ memoryReviewStatus: document.getElementById("memoryReviewStatus"),
193
+ memoryReviewActions: document.getElementById("memoryReviewActions"),
194
+ memoryTimelineStatus: document.getElementById("memoryTimelineStatus"),
195
+ memoryTimelineSummary: document.getElementById("memoryTimelineSummary"),
196
+ memoryTimelineList: document.getElementById("memoryTimelineList"),
197
+ memoryAuditStatus: document.getElementById("memoryAuditStatus"),
198
+ memoryAuditSummary: document.getElementById("memoryAuditSummary"),
199
+ memoryAuditList: document.getElementById("memoryAuditList"),
200
+ memoryLineageStatus: document.getElementById("memoryLineageStatus"),
201
+ memoryLineageSummary: document.getElementById("memoryLineageSummary"),
202
+ memoryLineageList: document.getElementById("memoryLineageList"),
203
+ sessionCaptureStatus: document.getElementById("sessionCaptureStatus"),
204
+ sessionCaptureSummary: document.getElementById("sessionCaptureSummary"),
205
+ sessionCaptureList: document.getElementById("sessionCaptureList"),
176
206
  memorySearch: document.getElementById("memorySearch"),
177
207
  memoryFilter: document.getElementById("memoryFilter"),
178
208
  memoryList: document.getElementById("memoryList"),
@@ -180,6 +210,9 @@
180
210
  ownersSummary: document.getElementById("ownersSummary"),
181
211
  ownersList: document.getElementById("ownersList"),
182
212
  reviewOverview: document.getElementById("reviewOverview"),
213
+ handoffStatus: document.getElementById("handoffStatus"),
214
+ handoffSummary: document.getElementById("handoffSummary"),
215
+ handoffList: document.getElementById("handoffList"),
183
216
  reviewList: document.getElementById("reviewList"),
184
217
  proofOverview: document.getElementById("proofOverview"),
185
218
  proofStatus: document.getElementById("proofStatus"),
@@ -256,10 +289,12 @@
256
289
  els.pathFromInput.addEventListener("keydown", function (event) { if (event.key === "Enter") findDependencyPath(); });
257
290
  els.pathToInput.addEventListener("keydown", function (event) { if (event.key === "Enter") findDependencyPath(); });
258
291
  els.viewMode.addEventListener("change", function () { clearGraphActionFilter(); render(); });
259
- els.renderMode.addEventListener("change", function () {
260
- state.lastVisibleSignature = "";
261
- render();
262
- });
292
+ if (els.renderMode) {
293
+ els.renderMode.addEventListener("change", function () {
294
+ state.lastVisibleSignature = "";
295
+ render();
296
+ });
297
+ }
263
298
  els.typeFilter.addEventListener("change", function () { clearGraphActionFilter(); render(); });
264
299
  els.relationFilter.addEventListener("change", function () { clearGraphActionFilter(); render(); });
265
300
  els.scopeFilter.addEventListener("change", render);
@@ -298,7 +333,7 @@
298
333
  function resetGraphView() {
299
334
  els.searchInput.value = "";
300
335
  els.viewMode.value = "combined";
301
- els.renderMode.value = "2d";
336
+ if (els.renderMode) els.renderMode.value = "2d";
302
337
  els.typeFilter.value = "";
303
338
  els.relationFilter.value = "";
304
339
  els.scopeFilter.value = "signal";
@@ -466,10 +501,12 @@
466
501
 
467
502
  function selectEntity(id, openInspector) {
468
503
  state.selected = { kind: "entity", id: id };
504
+ state.revealSelection = Boolean(openInspector);
469
505
  }
470
506
 
471
507
  function selectEdge(id, openInspector) {
472
508
  state.selected = { kind: "edge", id: id };
509
+ state.revealSelection = Boolean(openInspector);
473
510
  }
474
511
 
475
512
  function handleFile(event) {
@@ -562,11 +599,23 @@
562
599
  var qualityPath = params.get("quality");
563
600
  var benchmarkPath = params.get("benchmark");
564
601
  var contributorsPath = params.get("contributors");
602
+ var profilePath = params.get("profile") || params.get("projectProfile") || params.get("project-profile");
603
+ var capabilitiesPath = params.get("capabilities") || params.get("capabilityAudit") || params.get("capability-audit");
604
+ var slotsPath = params.get("slots") || params.get("contextSlots") || params.get("context-slots");
565
605
  var decisionsPath = params.get("decisions");
566
606
  var riskPath = params.get("risk");
567
607
  var moduleHealthPath = params.get("moduleHealth") || params.get("module-health");
568
608
  var graphInsightsPath = params.get("graphInsights") || params.get("graph-insights");
569
609
  var workspacePath = params.get("workspace");
610
+ var sessionsPath = params.get("sessions");
611
+ var replayPath = params.get("replay") || params.get("sessionReplay") || params.get("session-replay");
612
+ var memoryAccessPath = params.get("memoryAccess") || params.get("memory-access");
613
+ var memoryAuditPath = params.get("memoryAudit") || params.get("memory-audit") || params.get("auditLog") || params.get("audit-log");
614
+ var handoffPath = params.get("handoff") || params.get("memoryHandoff") || params.get("memory-handoff");
615
+ var lifecyclePath = params.get("lifecycle") || params.get("memoryLifecycle") || params.get("memory-lifecycle");
616
+ var timelinePath = params.get("timeline") || params.get("memoryTimeline") || params.get("memory-timeline");
617
+ var lineagePath = params.get("lineage") || params.get("memoryLineage") || params.get("memory-lineage");
618
+ var setupPath = params.get("setup") || params.get("setupDoctor") || params.get("setup-doctor");
570
619
  var inferredRoot = inferMemoryRoot(graphPaths[0] || "");
571
620
  if (!inboxPath && inferredRoot) inboxPath = inferredRoot + "/inbox.json";
572
621
  if (!reviewPath && inferredRoot) reviewPath = inferredRoot + "/review/memory-review.md";
@@ -575,11 +624,23 @@
575
624
  if (!qualityPath) qualityPath = inferredRoot + "/reports/quality.json";
576
625
  if (!benchmarkPath) benchmarkPath = inferredRoot + "/reports/benchmark.json";
577
626
  if (!contributorsPath) contributorsPath = inferredRoot + "/reports/contributors.json";
627
+ if (!profilePath) profilePath = inferredRoot + "/reports/profile.json";
628
+ if (!capabilitiesPath) capabilitiesPath = inferredRoot + "/reports/capabilities.json";
629
+ if (!slotsPath) slotsPath = inferredRoot + "/reports/context-slots.json";
578
630
  if (!decisionsPath) decisionsPath = inferredRoot + "/reports/decisions.json";
579
631
  if (!riskPath) riskPath = inferredRoot + "/reports/risk.json";
580
632
  if (!moduleHealthPath) moduleHealthPath = inferredRoot + "/reports/module-health.json";
581
633
  if (!graphInsightsPath) graphInsightsPath = inferredRoot + "/reports/graph-insights.json";
582
634
  if (!workspacePath) workspacePath = inferredRoot + "/reports/workspace.json";
635
+ if (!sessionsPath) sessionsPath = inferredRoot + "/reports/sessions.json";
636
+ if (!replayPath) replayPath = inferredRoot + "/reports/replay.json";
637
+ if (!memoryAccessPath) memoryAccessPath = inferredRoot + "/reports/memory-access.json";
638
+ if (!memoryAuditPath) memoryAuditPath = inferredRoot + "/reports/memory-audit.json";
639
+ if (!handoffPath) handoffPath = inferredRoot + "/reports/handoff.json";
640
+ if (!lifecyclePath) lifecyclePath = inferredRoot + "/reports/lifecycle.json";
641
+ if (!timelinePath) timelinePath = inferredRoot + "/reports/timeline.json";
642
+ if (!lineagePath) lineagePath = inferredRoot + "/reports/lineage.json";
643
+ if (!setupPath) setupPath = inferredRoot + "/reports/setup.json";
583
644
  }
584
645
  var jobs = [];
585
646
  if (metricsPath) jobs.push(fetchJson(metricsPath).then(function (metrics) { state.metrics = metrics; }));
@@ -589,11 +650,23 @@
589
650
  if (qualityPath) jobs.push(fetchJson(qualityPath).then(function (report) { state.reports.quality = report; }).catch(function () { state.reports.quality = null; }));
590
651
  if (benchmarkPath) jobs.push(fetchJson(benchmarkPath).then(function (report) { state.reports.benchmark = report; }).catch(function () { state.reports.benchmark = null; }));
591
652
  if (contributorsPath) jobs.push(fetchJson(contributorsPath).then(function (report) { state.reports.contributors = report; }).catch(function () { state.reports.contributors = null; }));
653
+ if (profilePath) jobs.push(fetchJson(profilePath).then(function (report) { state.reports.profile = report; }).catch(function () { state.reports.profile = null; }));
654
+ if (capabilitiesPath) jobs.push(fetchJson(capabilitiesPath).then(function (report) { state.reports.capabilities = report; }).catch(function () { state.reports.capabilities = null; }));
655
+ if (slotsPath) jobs.push(fetchJson(slotsPath).then(function (report) { state.reports.slots = report; }).catch(function () { state.reports.slots = null; }));
592
656
  if (decisionsPath) jobs.push(fetchJson(decisionsPath).then(function (report) { state.reports.decisions = report; }).catch(function () { state.reports.decisions = null; }));
593
657
  if (riskPath) jobs.push(fetchJson(riskPath).then(function (report) { state.reports.risk = report; }).catch(function () { state.reports.risk = null; }));
594
658
  if (moduleHealthPath) jobs.push(fetchJson(moduleHealthPath).then(function (report) { state.reports.moduleHealth = report; }).catch(function () { state.reports.moduleHealth = null; }));
595
659
  if (graphInsightsPath) jobs.push(fetchJson(graphInsightsPath).then(function (report) { state.reports.graphInsights = report; }).catch(function () { state.reports.graphInsights = null; }));
596
660
  if (workspacePath) jobs.push(fetchJson(workspacePath).then(function (report) { state.reports.workspace = report; }).catch(function () { state.reports.workspace = null; }));
661
+ if (sessionsPath) jobs.push(fetchJson(sessionsPath).then(function (report) { state.reports.sessions = report; }).catch(function () { state.reports.sessions = null; }));
662
+ if (replayPath) jobs.push(fetchJson(replayPath).then(function (report) { state.reports.replay = report; }).catch(function () { state.reports.replay = null; }));
663
+ if (memoryAccessPath) jobs.push(fetchJson(memoryAccessPath).then(function (report) { state.reports.memoryAccess = report; }).catch(function () { state.reports.memoryAccess = null; }));
664
+ if (memoryAuditPath) jobs.push(fetchJson(memoryAuditPath).then(function (report) { state.reports.memoryAudit = report; }).catch(function () { state.reports.memoryAudit = null; }));
665
+ if (handoffPath) jobs.push(fetchJson(handoffPath).then(function (report) { state.reports.handoff = report; }).catch(function () { state.reports.handoff = null; }));
666
+ if (lifecyclePath) jobs.push(fetchJson(lifecyclePath).then(function (report) { state.reports.lifecycle = report; }).catch(function () { state.reports.lifecycle = null; }));
667
+ if (timelinePath) jobs.push(fetchJson(timelinePath).then(function (report) { state.reports.timeline = report; }).catch(function () { state.reports.timeline = null; }));
668
+ if (lineagePath) jobs.push(fetchJson(lineagePath).then(function (report) { state.reports.lineage = report; }).catch(function () { state.reports.lineage = null; }));
669
+ if (setupPath) jobs.push(fetchJson(setupPath).then(function (report) { state.reports.setup = report; }).catch(function () { state.reports.setup = null; }));
597
670
  if (!graphPaths.length && !jobs.length) {
598
671
  loadHostedDefault();
599
672
  return;
@@ -626,20 +699,44 @@
626
699
  fetchJson("./data/kage/inbox.json").catch(function () { return null; }),
627
700
  fetchJson("./data/kage/reports/risk.json").catch(function () { return null; }),
628
701
  fetchJson("./data/kage/reports/contributors.json").catch(function () { return null; }),
702
+ fetchJson("./data/kage/reports/profile.json").catch(function () { return null; }),
703
+ fetchJson("./data/kage/reports/capabilities.json").catch(function () { return null; }),
704
+ fetchJson("./data/kage/reports/context-slots.json").catch(function () { return null; }),
629
705
  fetchJson("./data/kage/reports/decisions.json").catch(function () { return null; }),
630
706
  fetchJson("./data/kage/reports/module-health.json").catch(function () { return null; }),
631
707
  fetchJson("./data/kage/reports/graph-insights.json").catch(function () { return null; }),
632
- fetchJson("./data/kage/reports/workspace.json").catch(function () { return null; })
708
+ fetchJson("./data/kage/reports/workspace.json").catch(function () { return null; }),
709
+ fetchJson("./data/kage/reports/sessions.json").catch(function () { return null; }),
710
+ fetchJson("./data/kage/reports/replay.json").catch(function () { return null; }),
711
+ fetchJson("./data/kage/reports/memory-access.json").catch(function () { return null; }),
712
+ fetchJson("./data/kage/reports/memory-audit.json").catch(function () { return null; }),
713
+ fetchJson("./data/kage/reports/handoff.json").catch(function () { return null; }),
714
+ fetchJson("./data/kage/reports/lifecycle.json").catch(function () { return null; }),
715
+ fetchJson("./data/kage/reports/timeline.json").catch(function () { return null; }),
716
+ fetchJson("./data/kage/reports/lineage.json").catch(function () { return null; }),
717
+ fetchJson("./data/kage/reports/setup.json").catch(function () { return null; })
633
718
  ]).then(function (items) {
634
719
  var merged = mergeNormalizedGraphs([normalizeGraph(items[0]), normalizeGraph(items[1])]);
635
720
  state.metrics = items[2];
636
721
  state.inbox = items[3];
637
722
  state.reports.risk = items[4];
638
723
  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];
724
+ state.reports.profile = items[6];
725
+ state.reports.capabilities = items[7];
726
+ state.reports.slots = items[8];
727
+ state.reports.decisions = items[9];
728
+ state.reports.moduleHealth = items[10];
729
+ state.reports.graphInsights = items[11];
730
+ state.reports.workspace = items[12];
731
+ state.reports.sessions = items[13];
732
+ state.reports.replay = items[14];
733
+ state.reports.memoryAccess = items[15];
734
+ state.reports.memoryAudit = items[16];
735
+ state.reports.handoff = items[17];
736
+ state.reports.lifecycle = items[18];
737
+ state.reports.timeline = items[19];
738
+ state.reports.lineage = items[20];
739
+ state.reports.setup = items[21];
643
740
  loadNormalizedGraph(merged, "Kage repo graph");
644
741
  setAutoLoad("Kage repo graph loaded", true);
645
742
  }).catch(function () {
@@ -1383,6 +1480,7 @@
1383
1480
 
1384
1481
  renderActiveGraph(graphChanged);
1385
1482
  renderPagePanels();
1483
+ revealSelectionIfRequested();
1386
1484
  }
1387
1485
 
1388
1486
  function scheduleRender() {
@@ -2438,6 +2536,18 @@
2438
2536
  document.body.classList.toggle("has-code-selection", Boolean(entity && entity.graph_kind === "code"));
2439
2537
  }
2440
2538
 
2539
+ function revealSelectionIfRequested() {
2540
+ if (!state.revealSelection) return;
2541
+ state.revealSelection = false;
2542
+ window.requestAnimationFrame(function () {
2543
+ var panel = document.querySelector(".details-panel");
2544
+ if (!panel || window.getComputedStyle(panel).display === "none") return;
2545
+ panel.scrollIntoView({ block: "nearest", inline: "nearest" });
2546
+ var selectedRow = document.querySelector("[aria-selected=\"true\"], .node.selected");
2547
+ if (selectedRow && selectedRow.scrollIntoView) selectedRow.scrollIntoView({ block: "nearest", inline: "nearest" });
2548
+ });
2549
+ }
2550
+
2441
2551
  function prefillPathFromSelection(silent) {
2442
2552
  if (!state.selected || state.selected.kind !== "entity") {
2443
2553
  if (!silent) setPathStatus("Select a code node first. Path tracing is for files, symbols, routes, tests, and scripts.", "warn");
@@ -2879,20 +2989,20 @@
2879
2989
  var risk = reports.risk || {};
2880
2990
  var riskTargets = Array.isArray(risk.targets) ? risk.targets : Object.keys(risk.targets || {});
2881
2991
  var inboxCounts = state.inbox && state.inbox.counts ? state.inbox.counts : {};
2992
+ var handoff = memoryHandoffSummary(reports.handoff);
2882
2993
  var pendingReview = Number(firstNumber(inboxCounts.pending, memoryGraph.pending_packets, (state.pendingPackets || []).length, 0));
2883
2994
  var staleFlags = Number(firstNumber(inboxCounts.stale, 0));
2884
2995
  var duplicateFlags = Number(firstNumber(inboxCounts.duplicates, memoryGraph.duplicate_candidate_pairs, 0));
2885
2996
  var missingContext = Number(firstNumber(inboxCounts.missing_context, 0));
2886
2997
  var ownerSilos = Array.isArray(risk.ownership_silos) ? risk.ownership_silos.length : 0;
2887
2998
  var hotspots = Array.isArray(risk.global_hotspots) ? risk.global_hotspots.length : 0;
2888
- var readiness = dashboardReadiness(metrics, pendingReview, staleFlags, duplicateFlags, missingContext);
2999
+ var readiness = handoff || dashboardReadiness(metrics, pendingReview, staleFlags, duplicateFlags, missingContext);
2889
3000
  var memoryCoverage = dashboardMemoryCoverage(reports, memoryCodeEdges, memoryGraph, memoryNodes);
2890
3001
  var riskHealth = riskTargets.length || hotspots ? (riskTargets.length + hotspots) + " signals" : "No flags";
2891
3002
  var statRows = [
2892
3003
  ["Handoff", readiness.label, readiness.detail, readiness.status],
2893
3004
  ["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"]
3005
+ ["Risk", riskHealth, riskTargets.length + " targets, " + ownerSilos + " ownership silos", riskTargets.length || ownerSilos || hotspots ? "warn" : "ok"]
2896
3006
  ];
2897
3007
  els.dashboardStats.textContent = "";
2898
3008
  statRows.forEach(function (row) {
@@ -2920,7 +3030,13 @@
2920
3030
  ["Ownership silos", ownerSilos || "none"],
2921
3031
  ["Decision coverage", reports.decisions && reports.decisions.coverage_percent != null ? reports.decisions.coverage_percent + "%" : "not loaded"]
2922
3032
  ]);
2923
- setDashboardRows("dashboardReview", [
3033
+ setDashboardRows("dashboardReview", handoff ? [
3034
+ ["Next", handoff.actionLabel],
3035
+ ["Open", handoff.openItems ? handoff.openItems + " item" + (handoff.openItems === 1 ? "" : "s") : "none"],
3036
+ ["Distill", handoff.distillableSessions ? handoff.distillableSessions + " session" + (handoff.distillableSessions === 1 ? "" : "s") : "none"],
3037
+ ["Recent", handoff.recentChanges + " changes"],
3038
+ ["Mutations", handoff.recentMutations || "none"]
3039
+ ] : [
2924
3040
  ["Handoff", readiness.label],
2925
3041
  ["Pending", pendingReview || "none"],
2926
3042
  ["Stale / duplicate", staleFlags + " / " + duplicateFlags],
@@ -2940,7 +3056,8 @@
2940
3056
  missingContext: missingContext,
2941
3057
  riskTargets: riskTargets,
2942
3058
  ownerSilos: ownerSilos,
2943
- hotspots: hotspots
3059
+ hotspots: hotspots,
3060
+ handoff: handoff
2944
3061
  });
2945
3062
  }
2946
3063
 
@@ -2956,24 +3073,315 @@
2956
3073
  });
2957
3074
  var memoryGrounding = approvedPackets ? Math.round(linkedPacketIds.size / approvedPackets * 100) : 0;
2958
3075
  var sourceCoverage = Number(firstNumber(data.codeGraph.indexer_coverage_percent, 0));
2959
- var blockers = data.pendingReview + data.staleFlags + data.duplicateFlags + data.missingContext;
3076
+ var blockers = data.handoff ? data.handoff.openItems : data.pendingReview + data.staleFlags + data.duplicateFlags + data.missingContext;
2960
3077
  var riskSignals = data.riskTargets.length + data.ownerSilos + data.hotspots;
2961
- els.dashboardCharts.textContent = "";
2962
- [
3078
+ var retrieval = benchmarkRetrievalSummary(data.reports && data.reports.benchmark);
3079
+ var scale = benchmarkScaleSummary(data.reports && data.reports.benchmark);
3080
+ var access = memoryAccessSummary(data.reports && data.reports.memoryAccess, approvedPackets);
3081
+ var audit = memoryAuditSummary(data.reports && data.reports.memoryAudit);
3082
+ var lifecycle = memoryLifecycleSummary(data.reports && data.reports.lifecycle);
3083
+ var timeline = memoryTimelineSummary(data.reports && data.reports.timeline);
3084
+ var lineage = memoryLineageSummary(data.reports && data.reports.lineage);
3085
+ var setup = setupDoctorSummary(data.reports && data.reports.setup);
3086
+ var profile = projectProfileSummary(data.reports && data.reports.profile);
3087
+ var capabilities = capabilityAuditSummary(data.reports && data.reports.capabilities);
3088
+ var slots = contextSlotsSummary(data.reports && data.reports.slots);
3089
+ var replay = sessionReplaySummary(data.reports && data.reports.replay);
3090
+ var cards = [
2963
3091
  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", [
3092
+ 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")
3093
+ ];
3094
+ if (capabilities) {
3095
+ cards.push(metricBars("Capability audit", capabilities.label, capabilities.rows, capabilities.action, capabilities.status));
3096
+ }
3097
+ if (profile) {
3098
+ cards.push(metricBars("Project profile", profile.label, [
3099
+ { label: "Concepts", value: profile.concepts, score: Math.min(100, profile.concepts * 10), status: profile.concepts ? "ok" : "warn" },
3100
+ { label: "Key files", value: profile.keyFiles, score: Math.min(100, profile.keyFiles * 8), status: profile.keyFiles ? "ok" : "warn" },
3101
+ { label: "Commands", value: profile.commands, score: Math.min(100, profile.commands * 16), status: profile.commands ? "ok" : "warn" }
3102
+ ], profile.action, profile.status));
3103
+ }
3104
+ if (slots) {
3105
+ cards.push(metricBars("Pinned context", slots.label, [
3106
+ { label: "Pinned", value: slots.pinned, score: Math.min(100, slots.pinned * 30), status: slots.pinned ? "ok" : "warn" },
3107
+ { label: "Slots", value: slots.total, score: Math.min(100, slots.total * 20), status: slots.total ? "ok" : "warn" },
3108
+ { label: "Chars", value: slots.chars, score: slots.chars ? Math.min(100, Math.round(slots.chars / 60)) : 0, status: slots.chars ? "ok" : "warn" }
3109
+ ], slots.action, slots.status));
3110
+ }
3111
+ if (replay) {
3112
+ cards.push(metricBars("Session replay", replay.label, [
3113
+ { label: "Events", value: replay.events, score: Math.min(100, replay.events * 8), status: replay.events ? "ok" : "warn" },
3114
+ { label: "Candidates", value: replay.candidates, score: Math.min(100, replay.candidates * 24), status: replay.candidates ? "warn" : "ok" },
3115
+ { label: "Sessions", value: replay.sessions, score: Math.min(100, replay.sessions * 24), status: replay.sessions ? "ok" : "warn" }
3116
+ ], replay.action, replay.status));
3117
+ }
3118
+ if (setup) {
3119
+ cards.push(metricBars("Agent setup", setup.label, [
3120
+ { label: "Configured", value: setup.configured + "/" + setup.total, score: setup.total ? Math.round(setup.configured / setup.total * 100) : 0, status: setup.configured ? "ok" : "warn" },
3121
+ { label: "Claude hooks", value: setup.claudeHookReady ? "ready" : "missing", score: setup.claudeHookReady ? 100 : 0, status: setup.claudeHookReady ? "ok" : "warn" },
3122
+ { label: "Missing", value: setup.missingCount, score: setup.missingCount ? Math.min(100, setup.missingCount * 18) : 0, status: setup.missingCount ? "warn" : "ok" }
3123
+ ], setup.action, setup.status));
3124
+ }
3125
+ if (access) {
3126
+ cards.push(metricBars("Memory reuse", access.uses30d + " recalls", [
3127
+ { label: "Hot", value: access.hot, score: Math.min(100, access.hot * 24), status: access.hot ? "ok" : "warn" },
3128
+ { label: "Cold", value: access.cold, score: approvedPackets ? Math.round(access.cold / approvedPackets * 100) : 0, status: access.cold ? "warn" : "ok" },
3129
+ { label: "Tracked", value: access.tracked, score: approvedPackets ? Math.round(access.tracked / approvedPackets * 100) : 0, status: access.tracked ? "ok" : "warn" }
3130
+ ], 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"));
3131
+ }
3132
+ if (audit) {
3133
+ cards.push(metricBars("Memory audit", audit.total + " mutations", [
3134
+ { label: "Capture", value: audit.capture, score: Math.min(100, audit.capture * 12), status: audit.capture ? "ok" : "warn" },
3135
+ { label: "Review", value: audit.review, score: Math.min(100, audit.review * 20), status: audit.review ? "ok" : "warn" },
3136
+ { label: "Supersede", value: audit.supersede, score: Math.min(100, audit.supersede * 24), status: audit.supersede ? "ok" : "warn" }
3137
+ ], audit.total ? "Memory changes are auditable for team handoff." : "No explicit memory mutations audited yet.", audit.total ? "ok" : "warn"));
3138
+ }
3139
+ if (data.handoff) {
3140
+ cards.push(metricBars("Memory handoff", data.handoff.label, [
3141
+ { label: "Open", value: data.handoff.openItems, score: Math.min(100, data.handoff.openItems * 24), status: data.handoff.openItems ? "warn" : "ok" },
3142
+ { label: "Distill", value: data.handoff.distillableSessions, score: Math.min(100, data.handoff.distillableSessions * 30), status: data.handoff.distillableSessions ? "warn" : "ok" },
3143
+ { label: "Recent", value: data.handoff.recentChanges, score: Math.min(100, data.handoff.recentChanges * 8), status: data.handoff.recentChanges ? "ok" : "warn" },
3144
+ { label: "Mutations", value: data.handoff.recentMutations, score: Math.min(100, data.handoff.recentMutations * 18), status: data.handoff.recentMutations ? "ok" : "warn" }
3145
+ ], data.handoff.action, data.handoff.status));
3146
+ }
3147
+ if (lifecycle) {
3148
+ cards.push(metricBars("Memory lifecycle", lifecycle.needsReview ? lifecycle.needsReview + " need review" : "healthy", [
3149
+ { label: "Hot/healthy", value: lifecycle.ready, score: approvedPackets ? Math.round(lifecycle.ready / approvedPackets * 100) : 0, status: lifecycle.ready ? "ok" : "warn" },
3150
+ { label: "Ungrounded", value: lifecycle.ungrounded, score: approvedPackets ? Math.round(lifecycle.ungrounded / approvedPackets * 100) : 0, status: lifecycle.ungrounded ? "warn" : "ok" },
3151
+ { label: "Stale/disputed", value: lifecycle.stale, score: approvedPackets ? Math.round(lifecycle.stale / approvedPackets * 100) : 0, status: lifecycle.stale ? "danger" : "ok" }
3152
+ ], lifecycle.needsReview ? "Open Memory and resolve lifecycle actions before handoff." : "Repo memory is ready for agent handoff.", lifecycle.needsReview ? "warn" : "ok"));
3153
+ }
3154
+ if (timeline) {
3155
+ cards.push(metricBars("Memory timeline", timeline.total + " recent", [
3156
+ { label: "Added", value: timeline.added, score: Math.min(100, timeline.added * 16), status: timeline.added ? "ok" : "warn" },
3157
+ { label: "Updated", value: timeline.updated, score: Math.min(100, timeline.updated * 16), status: timeline.updated ? "ok" : "warn" },
3158
+ { label: "Pending", value: timeline.pending, score: Math.min(100, timeline.pending * 24), status: timeline.pending ? "warn" : "ok" }
3159
+ ], timeline.total ? "Review recent memory changes before teammate handoff." : "No recent memory activity loaded.", timeline.pending ? "warn" : "ok"));
3160
+ }
3161
+ if (lineage) {
3162
+ cards.push(metricBars("Memory lineage", lineage.chains + " chains", [
3163
+ { label: "Replaced", value: lineage.superseded, score: Math.min(100, lineage.superseded * 18), status: lineage.superseded ? "ok" : "warn" },
3164
+ { label: "Chains", value: lineage.chains, score: Math.min(100, lineage.chains * 24), status: lineage.chains ? "ok" : "warn" },
3165
+ { label: "Needs repair", value: lineage.orphans, score: Math.min(100, lineage.orphans * 32), status: lineage.orphans ? "danger" : "ok" }
3166
+ ], lineage.orphans ? "Fix superseded memory without replacement links." : "Retired memory points at current replacements.", lineage.orphans ? "danger" : "ok"));
3167
+ }
3168
+ if (retrieval) {
3169
+ cards.push(metricBars("Retrieval proof", retrieval.r10 + "% R@10", [
3170
+ { label: "R@5", value: retrieval.r5 != null ? retrieval.r5 + "%" : "n/a", score: retrieval.r5 || 0, status: retrieval.r5 >= 95 ? "ok" : "warn" },
3171
+ { label: "R@10", value: retrieval.r10 + "%", score: retrieval.r10, status: retrieval.r10 >= 95 ? "ok" : "warn" },
3172
+ { 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" }
3173
+ ], retrieval.label + ". Measures memory retrieval proof, not answer accuracy.", retrieval.r10 >= 95 ? "ok" : "warn"));
3174
+ }
3175
+ if (scale) {
3176
+ cards.push(metricBars("Scale proof", scale.hitRate + "% hit", [
3177
+ { label: "Packets", value: scale.packets, score: Math.min(100, scale.packets / 10), status: scale.packets >= 240 ? "ok" : "warn" },
3178
+ { label: "Median", value: scale.medianLatency + "ms", score: Math.max(0, 100 - scale.medianLatency), status: scale.medianLatency <= 50 ? "ok" : "warn" },
3179
+ { label: "Context cut", value: scale.contextReduction + "%", score: scale.contextReduction, status: scale.contextReduction >= 80 ? "ok" : "warn" }
3180
+ ], "Large memory corpus stays searchable without loading every packet.", scale.hitRate >= 95 ? "ok" : "warn"));
3181
+ }
3182
+ els.dashboardCharts.textContent = "";
3183
+ var tailCards = [];
3184
+ if (!data.handoff) {
3185
+ tailCards.push(metricBars("Handoff blockers", blockers ? blockers + " open" : "clear", [
2966
3186
  { label: "Pending", value: data.pendingReview, score: Math.min(100, data.pendingReview * 24), status: data.pendingReview ? "warn" : "ok" },
2967
3187
  { label: "Stale", value: data.staleFlags, score: Math.min(100, data.staleFlags * 24), status: data.staleFlags ? "warn" : "ok" },
2968
3188
  { label: "Duplicate", value: data.duplicateFlags, score: Math.min(100, data.duplicateFlags * 24), status: data.duplicateFlags ? "warn" : "ok" },
2969
3189
  { 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"),
3190
+ ], blockers ? "Resolve Review before handing work to another agent." : "Memory is clean for handoff.", blockers ? "warn" : "ok"));
3191
+ }
3192
+ tailCards.push(
2971
3193
  metricBars("Change risk", riskSignals ? riskSignals + " signals" : "none", [
2972
3194
  { label: "Targets", value: data.riskTargets.length, score: Math.min(100, data.riskTargets.length * 18), status: data.riskTargets.length ? "warn" : "ok" },
2973
3195
  { label: "Silos", value: data.ownerSilos, score: Math.min(100, data.ownerSilos * 18), status: data.ownerSilos ? "warn" : "ok" },
2974
3196
  { label: "Hotspots", value: data.hotspots, score: Math.min(100, data.hotspots * 18), status: data.hotspots ? "danger" : "ok" }
2975
3197
  ], riskSignals ? "Open Intel or Owners before editing risky files." : "No loaded risk flags.", riskSignals ? "warn" : "ok")
2976
- ].forEach(function (card) { els.dashboardCharts.appendChild(card); });
3198
+ );
3199
+ cards.concat(tailCards).slice(0, 3).forEach(function (card) { els.dashboardCharts.appendChild(card); });
3200
+ }
3201
+
3202
+ function projectProfileSummary(report) {
3203
+ if (!report || !report.totals) return null;
3204
+ var concepts = Array.isArray(report.top_concepts) ? report.top_concepts.length : 0;
3205
+ var keyFiles = Array.isArray(report.key_files) ? report.key_files.length : 0;
3206
+ var commands = Array.isArray(report.run_commands) ? report.run_commands.length : 0;
3207
+ var coverage = Number(report.totals.memory_code_coverage_percent || 0);
3208
+ var topConcept = concepts ? report.top_concepts[0].concept : "no concepts";
3209
+ var actions = Array.isArray(report.next_actions) ? report.next_actions : [];
3210
+ return {
3211
+ concepts: concepts,
3212
+ keyFiles: keyFiles,
3213
+ commands: commands,
3214
+ label: topConcept,
3215
+ action: actions[0] || report.summary || "Use this as the first orientation packet for agents.",
3216
+ status: coverage >= 60 && keyFiles ? "ok" : "warn"
3217
+ };
3218
+ }
3219
+
3220
+ function capabilityAuditSummary(report) {
3221
+ if (!report || !Array.isArray(report.pillars)) return null;
3222
+ var rows = report.pillars.slice(0, 4).map(function (pillar) {
3223
+ return {
3224
+ label: pillar.label || pillar.id || "pillar",
3225
+ value: Number(pillar.score || 0) + "%",
3226
+ score: Number(pillar.score || 0),
3227
+ status: pillar.status === "ready" ? "ok" : (pillar.status === "gap" ? "danger" : "warn")
3228
+ };
3229
+ });
3230
+ var open = report.pillars.filter(function (pillar) { return pillar.status !== "ready"; }).length;
3231
+ return {
3232
+ label: Number(report.overall_score || 0) + "/100",
3233
+ rows: rows,
3234
+ action: open
3235
+ ? (Array.isArray(report.next_actions) && report.next_actions[0] ? report.next_actions[0] : "Review capability gaps before publishing claims.")
3236
+ : "Memory, benchmark, collaboration, and viewer proof surfaces are ready.",
3237
+ status: report.status === "ready" ? "ok" : (report.status === "gap" ? "danger" : "warn")
3238
+ };
3239
+ }
3240
+
3241
+ function contextSlotsSummary(report) {
3242
+ if (!report || !report.totals) return null;
3243
+ var slots = Array.isArray(report.slots) ? report.slots : [];
3244
+ var pinned = Number(report.totals.pinned || 0);
3245
+ var total = Number(report.totals.slots || slots.length || 0);
3246
+ var chars = Number(report.totals.context_chars || 0);
3247
+ var firstPinned = slots.find(function (slot) { return slot && slot.pinned && slot.content; });
3248
+ return {
3249
+ pinned: pinned,
3250
+ total: total,
3251
+ chars: chars,
3252
+ label: firstPinned ? firstPinned.label : (pinned ? String(pinned) + " pinned" : "none"),
3253
+ action: pinned
3254
+ ? "Pinned slots are included before task-specific recall for stable repo guidance."
3255
+ : "Add a slot for tiny always-relevant repo context instead of repeating it every session.",
3256
+ status: pinned ? "ok" : "warn"
3257
+ };
3258
+ }
3259
+
3260
+ function sessionReplaySummary(report) {
3261
+ if (!report || !report.totals) return null;
3262
+ var events = Number(report.totals.events || 0);
3263
+ var candidates = Number(report.totals.durable_candidates || 0);
3264
+ var sessions = Number(report.totals.sessions || 0);
3265
+ return {
3266
+ events: events,
3267
+ candidates: candidates,
3268
+ sessions: sessions,
3269
+ label: candidates ? candidates + " distillable" : (events ? events + " observed" : "none"),
3270
+ action: candidates
3271
+ ? "Open Memory and distill durable session observations into reviewable packets."
3272
+ : "Replay digest proves what agents observed without exposing raw transcripts.",
3273
+ status: candidates ? "warn" : (events ? "ok" : "warn")
3274
+ };
3275
+ }
3276
+
3277
+ function setupDoctorSummary(report) {
3278
+ if (!Array.isArray(report) || !report.length) return null;
3279
+ var configured = report.filter(function (item) { return item && item.configured; }).length;
3280
+ var claude = report.find(function (item) { return item && item.agent === "claude-code"; });
3281
+ var hookSummary = claude && claude.hook_summary;
3282
+ var missing = hookSummary && Array.isArray(hookSummary.missing) ? hookSummary.missing : [];
3283
+ var claudeHookReady = Boolean(hookSummary && hookSummary.ready);
3284
+ var missingCount = missing.length;
3285
+ var action = "";
3286
+ if (missingCount) {
3287
+ action = "Run kage setup claude-code --project . --write before relying on automatic memory.";
3288
+ } else if (configured) {
3289
+ action = "Automatic memory setup is visible for teammate handoff.";
3290
+ } else {
3291
+ action = "Run kage setup doctor to choose the next agent setup step.";
3292
+ }
3293
+ return {
3294
+ total: report.length,
3295
+ configured: configured,
3296
+ claudeHookReady: claudeHookReady,
3297
+ missingCount: missingCount,
3298
+ label: missingCount ? missingCount + " missing" : configured + "/" + report.length + " ready",
3299
+ action: action,
3300
+ status: missingCount || !configured ? "warn" : "ok"
3301
+ };
3302
+ }
3303
+
3304
+ function memoryAccessSummary(report, approvedPackets) {
3305
+ var totals = report && report.totals;
3306
+ if (!totals) return null;
3307
+ return {
3308
+ tracked: Number(totals.tracked_packets || 0),
3309
+ uses30d: Number(totals.uses_30d || 0),
3310
+ hot: Number(totals.hot_packets || 0),
3311
+ cold: Number(totals.cold_packets == null ? Math.max(0, approvedPackets) : totals.cold_packets)
3312
+ };
3313
+ }
3314
+
3315
+ function memoryAuditSummary(report) {
3316
+ var totals = report && report.totals;
3317
+ if (!totals) return null;
3318
+ return {
3319
+ total: Number(totals.total || 0),
3320
+ capture: Number(totals.capture || 0),
3321
+ review: Number(totals.approve || 0) + Number(totals.reject || 0),
3322
+ supersede: Number(totals.supersede || 0)
3323
+ };
3324
+ }
3325
+
3326
+ function memoryHandoffSummary(report) {
3327
+ var totals = report && report.totals;
3328
+ if (!totals) return null;
3329
+ var primary = report.primary_action || {};
3330
+ var openItems = Number(firstNumber(totals.open_items, 0));
3331
+ var severity = primary.severity || (openItems ? "warning" : "ok");
3332
+ var status = severity === "blocker" ? "danger" : severity === "warning" ? "warn" : severity === "ok" ? "ok" : "memory";
3333
+ var label = primary.label || (openItems ? "Resolve handoff" : "Ready for handoff");
3334
+ return {
3335
+ label: label,
3336
+ actionLabel: label.indexOf("Resolve") === 0 ? "Resolve" : label.indexOf("Ready") === 0 ? "Ready" : label.indexOf("Review") === 0 ? "Review" : label,
3337
+ detail: primary.summary || report.summary || "",
3338
+ action: primary.action || report.summary || "Open Review before handing work to another agent.",
3339
+ status: status,
3340
+ openItems: openItems,
3341
+ blockers: Number(firstNumber(totals.blockers, 0)),
3342
+ warnings: Number(firstNumber(totals.warnings, 0)),
3343
+ recentChanges: Number(firstNumber(totals.recent_changes, 0)),
3344
+ recentMutations: Number(firstNumber(totals.recent_mutations, 0)),
3345
+ distillableSessions: Number(firstNumber(totals.distillable_sessions, 0)),
3346
+ durableObservations: Number(firstNumber(totals.durable_observations, 0))
3347
+ };
3348
+ }
3349
+
3350
+ function memoryLifecycleSummary(report) {
3351
+ var totals = report && report.totals;
3352
+ if (!totals) return null;
3353
+ var stale = Number(totals.stale || 0) + Number(totals.disputed || 0);
3354
+ var ungrounded = Number(totals.ungrounded || 0);
3355
+ var pending = Number(totals.pending || 0);
3356
+ return {
3357
+ ready: Number(totals.hot || 0) + Number(totals.healthy || 0),
3358
+ ungrounded: ungrounded,
3359
+ stale: stale,
3360
+ needsReview: stale + ungrounded + pending
3361
+ };
3362
+ }
3363
+
3364
+ function memoryTimelineSummary(report) {
3365
+ var totals = report && report.totals;
3366
+ if (!totals) return null;
3367
+ return {
3368
+ total: Number(totals.total || 0),
3369
+ added: Number(totals.added || 0),
3370
+ updated: Number(totals.updated || 0),
3371
+ pending: Number(totals.pending || 0),
3372
+ deprecated: Number(totals.deprecated || 0)
3373
+ };
3374
+ }
3375
+
3376
+ function memoryLineageSummary(report) {
3377
+ var totals = report && report.totals;
3378
+ if (!totals) return null;
3379
+ return {
3380
+ superseded: Number(totals.superseded || 0),
3381
+ chains: Number(totals.chains || 0),
3382
+ orphans: Number(totals.orphans || 0),
3383
+ replacementsMissing: Number(totals.replacements_missing || 0)
3384
+ };
2977
3385
  }
2978
3386
 
2979
3387
  function metricDonut(title, percent, detail, action, status) {
@@ -3104,6 +3512,7 @@
3104
3512
  }
3105
3513
  });
3106
3514
  var linkedCount = memoryEntities.filter(function (entity) { return (memoryLinkCounts.get(entity.id) || 0) > 0; }).length;
3515
+ var accessTotals = state.reports.memoryAccess && state.reports.memoryAccess.totals;
3107
3516
  var query = parseSearchQuery(els.memorySearch ? els.memorySearch.value : "");
3108
3517
  var filter = els.memoryFilter ? els.memoryFilter.value : "all";
3109
3518
  var filtered = memoryEntities.filter(function (entity) {
@@ -3118,10 +3527,17 @@
3118
3527
  els.memoryStats.innerHTML = [
3119
3528
  memoryStat("Reusable", memoryEntities.length),
3120
3529
  memoryStat("Code-linked", linkedCount),
3530
+ memoryStat("Reused 30d", accessTotals ? Number(accessTotals.uses_30d || 0) : "n/a"),
3121
3531
  memoryStat("Needs paths", memoryEntities.length - linkedCount)
3122
3532
  ].join("");
3123
3533
  }
3124
3534
  if (els.memoryOverview) renderMemoryOverview(memoryEntities, linkedCount);
3535
+ if (els.lifecycleList) renderMemoryLifecycle(memoryEntities, memoryLinkCounts);
3536
+ if (els.memoryReviewActions) renderMemoryReviewActions();
3537
+ if (els.memoryTimelineList) renderMemoryTimeline();
3538
+ if (els.memoryAuditList) renderMemoryAudit();
3539
+ if (els.memoryLineageList) renderMemoryLineage();
3540
+ if (els.sessionCaptureList) renderSessionCapture();
3125
3541
  els.memoryList.textContent = "";
3126
3542
  if (!memoryEntities.length) {
3127
3543
  els.memoryList.className = "memory-list details-empty";
@@ -3142,6 +3558,7 @@
3142
3558
  filtered.slice(0, 60).forEach(function (entity) {
3143
3559
  var links = memoryCodeLinksForEntity(entity.id);
3144
3560
  var firstCodeTarget = primaryCodeTargetForMemory(entity.id, links);
3561
+ var access = memoryAccessForEntity(entity);
3145
3562
  var item = document.createElement("button");
3146
3563
  item.type = "button";
3147
3564
  var selected = state.selected && state.selected.kind === "entity" && state.selected.id === entity.id;
@@ -3158,6 +3575,9 @@
3158
3575
  item.querySelector(".memory-row-target").textContent = links.length
3159
3576
  ? links.length + " code link" + (links.length === 1 ? "" : "s") + (firstCodeTarget ? " | " + trimIntelText(codeTargetLabel(firstCodeTarget), 64) : "")
3160
3577
  : "needs code paths";
3578
+ if (access && access.total_uses) {
3579
+ item.querySelector(".memory-row-target").textContent += " | reused " + Number(access.uses_30d || 0) + "x in 30d";
3580
+ }
3161
3581
  item.addEventListener("click", function () {
3162
3582
  selectEntity(entity.id, true);
3163
3583
  render();
@@ -3171,6 +3591,13 @@
3171
3591
  return "<div><strong>" + escapeHtml(String(value)) + "</strong><span>" + escapeHtml(label) + "</span></div>";
3172
3592
  }
3173
3593
 
3594
+ function memoryAccessForEntity(entity) {
3595
+ var report = state.reports && state.reports.memoryAccess;
3596
+ if (!report || !Array.isArray(report.entries)) return null;
3597
+ var ids = new Set([entity.id].concat(entity.aliases || []));
3598
+ return report.entries.find(function (entry) { return ids.has(entry.packet_id); }) || null;
3599
+ }
3600
+
3174
3601
  function renderMemoryOverview(memoryEntities, linkedCount) {
3175
3602
  els.memoryOverview.textContent = "";
3176
3603
  var total = memoryEntities.length;
@@ -3198,6 +3625,592 @@
3198
3625
  }), "A healthy repo has decisions, bug fixes, runbooks, gotchas, and code explanations.", "ok"));
3199
3626
  }
3200
3627
 
3628
+ function renderMemoryReviewActions() {
3629
+ var lifecycle = state.reports && state.reports.lifecycle;
3630
+ var access = state.reports && state.reports.memoryAccess;
3631
+ var recommendations = lifecycle && Array.isArray(lifecycle.recommendations)
3632
+ ? lifecycle.recommendations
3633
+ : (access && Array.isArray(access.recommendations) ? access.recommendations : []);
3634
+ if (els.memoryReviewStatus) {
3635
+ els.memoryReviewStatus.textContent = recommendations.length ? recommendations.length + " action" + (recommendations.length === 1 ? "" : "s") : "clear";
3636
+ }
3637
+ els.memoryReviewActions.textContent = "";
3638
+ if (!lifecycle && !access) {
3639
+ els.memoryReviewActions.className = "memory-action-list details-empty";
3640
+ els.memoryReviewActions.textContent = "No lifecycle report loaded. Run kage lifecycle or open the local viewer after refreshing memory.";
3641
+ return;
3642
+ }
3643
+ if (!recommendations.length) {
3644
+ els.memoryReviewActions.className = "memory-action-list details-empty";
3645
+ els.memoryReviewActions.textContent = "No memory review actions. Recent recall usage does not show obvious hot or cold packets.";
3646
+ return;
3647
+ }
3648
+ els.memoryReviewActions.className = "memory-action-list";
3649
+ recommendations.slice(0, 6).forEach(function (item) {
3650
+ var card = document.createElement("article");
3651
+ card.className = classNames("memory-action", item.severity && "memory-action-" + item.severity);
3652
+ card.innerHTML = [
3653
+ "<div class=\"memory-action-head\"><span></span><strong></strong></div>",
3654
+ "<p></p>",
3655
+ "<em></em>"
3656
+ ].join("");
3657
+ card.querySelector(".memory-action-head span").textContent = memoryActionLabel(item.kind);
3658
+ card.querySelector(".memory-action-head strong").textContent = item.summary || item.title || "Memory action";
3659
+ card.querySelector("p").textContent = item.reason || "";
3660
+ card.querySelector("em").textContent = item.action || "";
3661
+ var entity = item.packet_id ? findMemoryEntityByPacketId(item.packet_id) : null;
3662
+ if (entity) {
3663
+ card.tabIndex = 0;
3664
+ card.setAttribute("role", "button");
3665
+ card.addEventListener("click", function () {
3666
+ selectEntity(entity.id, true);
3667
+ render();
3668
+ });
3669
+ card.addEventListener("keydown", function (event) {
3670
+ if (event.key !== "Enter" && event.key !== " ") return;
3671
+ event.preventDefault();
3672
+ selectEntity(entity.id, true);
3673
+ render();
3674
+ });
3675
+ }
3676
+ els.memoryReviewActions.appendChild(card);
3677
+ });
3678
+ }
3679
+
3680
+ function renderMemoryTimeline() {
3681
+ var report = state.reports && state.reports.timeline;
3682
+ var entries = Array.isArray(report && report.entries) ? report.entries : [];
3683
+ var totals = report && report.totals ? report.totals : {};
3684
+ if (els.memoryTimelineStatus) {
3685
+ els.memoryTimelineStatus.textContent = report ? String(Number(totals.total || entries.length)) + " recent" : "waiting";
3686
+ }
3687
+ if (els.memoryTimelineSummary) {
3688
+ els.memoryTimelineSummary.textContent = "";
3689
+ [
3690
+ lifecycleSummaryStep("+", "Added", Number(totals.added || 0), "New repo memories captured for future agents."),
3691
+ lifecycleSummaryStep("~", "Updated", Number(totals.updated || 0), "Packets with changed rationale, evidence, or paths."),
3692
+ lifecycleSummaryStep("?", "Pending", Number(totals.pending || 0), "Memory waiting for teammate review.")
3693
+ ].forEach(function (step) { els.memoryTimelineSummary.appendChild(step); });
3694
+ }
3695
+ els.memoryTimelineList.textContent = "";
3696
+ if (!report) {
3697
+ els.memoryTimelineList.className = "session-capture-list details-empty";
3698
+ els.memoryTimelineList.textContent = "No memory timeline report loaded. Run kage timeline or open the local viewer after refresh.";
3699
+ return;
3700
+ }
3701
+ if (!entries.length) {
3702
+ els.memoryTimelineList.className = "session-capture-list details-empty";
3703
+ els.memoryTimelineList.textContent = "No recent memory activity. Capture durable repo decisions, bugs, runbooks, or gotchas as work happens.";
3704
+ return;
3705
+ }
3706
+ els.memoryTimelineList.className = "session-capture-list";
3707
+ entries.slice(0, 8).forEach(function (entry) {
3708
+ var card = document.createElement("article");
3709
+ card.className = "session-capture-card";
3710
+ card.innerHTML = [
3711
+ "<div class=\"session-capture-head\"><div><strong></strong><span></span></div><em></em></div>",
3712
+ "<p></p>",
3713
+ "<div class=\"session-capture-meta\"></div>"
3714
+ ].join("");
3715
+ card.querySelector("strong").textContent = entry.title || "Memory packet";
3716
+ card.querySelector("span").textContent = [entry.type, timelineDateLabel(entry.date), entry.source_kind].filter(Boolean).join(" | ");
3717
+ card.querySelector("em").textContent = timelineKindLabel(entry.kind);
3718
+ card.querySelector("p").textContent = entry.action || entry.summary || "Review this memory activity.";
3719
+ var meta = card.querySelector(".session-capture-meta");
3720
+ [
3721
+ ["status", entry.status || "unknown"],
3722
+ ["paths", Array.isArray(entry.paths) ? entry.paths.slice(0, 2).join(", ") || "none" : "none"],
3723
+ ["tags", Array.isArray(entry.tags) ? entry.tags.slice(0, 3).join(", ") || "none" : "none"]
3724
+ ].forEach(function (row) {
3725
+ var chip = document.createElement("span");
3726
+ chip.textContent = row[0] + ": " + row[1];
3727
+ meta.appendChild(chip);
3728
+ });
3729
+ var entity = entry.packet_id ? findMemoryEntityByPacketId(entry.packet_id) : null;
3730
+ if (entity) {
3731
+ card.tabIndex = 0;
3732
+ card.setAttribute("role", "button");
3733
+ card.addEventListener("click", function () {
3734
+ selectEntity(entity.id, true);
3735
+ render();
3736
+ });
3737
+ card.addEventListener("keydown", function (event) {
3738
+ if (event.key !== "Enter" && event.key !== " ") return;
3739
+ event.preventDefault();
3740
+ selectEntity(entity.id, true);
3741
+ render();
3742
+ });
3743
+ }
3744
+ els.memoryTimelineList.appendChild(card);
3745
+ });
3746
+ }
3747
+
3748
+ function renderMemoryAudit() {
3749
+ var report = state.reports && state.reports.memoryAudit;
3750
+ var entries = Array.isArray(report && report.entries) ? report.entries : [];
3751
+ var totals = report && report.totals ? report.totals : {};
3752
+ if (els.memoryAuditStatus) {
3753
+ els.memoryAuditStatus.textContent = report ? String(Number(totals.total || entries.length)) + " mutations" : "waiting";
3754
+ }
3755
+ if (els.memoryAuditSummary) {
3756
+ els.memoryAuditSummary.textContent = "";
3757
+ [
3758
+ lifecycleSummaryStep("1", "Captured", Number(totals.capture || 0), "New packets written to repo memory."),
3759
+ lifecycleSummaryStep("2", "Reviewed", Number(totals.approve || 0) + Number(totals.reject || 0), "Pending packets approved or rejected."),
3760
+ lifecycleSummaryStep("3", "Replaced", Number(totals.supersede || 0), "Old knowledge linked to current replacements.")
3761
+ ].forEach(function (step) { els.memoryAuditSummary.appendChild(step); });
3762
+ }
3763
+ els.memoryAuditList.textContent = "";
3764
+ if (!report) {
3765
+ els.memoryAuditList.className = "session-capture-list details-empty";
3766
+ els.memoryAuditList.textContent = "No memory audit report loaded. Run kage memory-audit or open the local viewer after refresh.";
3767
+ return;
3768
+ }
3769
+ if (!entries.length) {
3770
+ els.memoryAuditList.className = "session-capture-list details-empty";
3771
+ els.memoryAuditList.textContent = "No audited memory mutations yet. Captures, feedback, reviews, and supersedes will appear here.";
3772
+ return;
3773
+ }
3774
+ els.memoryAuditList.className = "session-capture-list";
3775
+ entries.slice(0, 8).forEach(function (entry) {
3776
+ var card = document.createElement("article");
3777
+ card.className = "session-capture-card";
3778
+ card.innerHTML = [
3779
+ "<div class=\"session-capture-head\"><div><strong></strong><span></span></div><em></em></div>",
3780
+ "<p></p>",
3781
+ "<div class=\"session-capture-meta\"></div>"
3782
+ ].join("");
3783
+ card.querySelector("strong").textContent = (entry.packet_titles || []).join(", ") || (entry.packet_ids || []).join(", ") || "Memory mutation";
3784
+ card.querySelector("span").textContent = [entry.actor, entry.branch, timelineDateLabel(entry.timestamp)].filter(Boolean).join(" | ");
3785
+ card.querySelector("em").textContent = entry.operation || "audit";
3786
+ card.querySelector("p").textContent = memoryAuditAction(entry);
3787
+ var meta = card.querySelector(".session-capture-meta");
3788
+ [
3789
+ ["packets", Array.isArray(entry.packet_ids) ? String(entry.packet_ids.length) : "0"],
3790
+ ["head", entry.head ? String(entry.head).slice(0, 8) : "none"],
3791
+ ["details", auditDetailsLabel(entry.details)]
3792
+ ].forEach(function (row) {
3793
+ var chip = document.createElement("span");
3794
+ chip.textContent = row[0] + ": " + row[1];
3795
+ meta.appendChild(chip);
3796
+ });
3797
+ els.memoryAuditList.appendChild(card);
3798
+ });
3799
+ }
3800
+
3801
+ function memoryAuditAction(entry) {
3802
+ if (!entry) return "Review this memory mutation before handoff.";
3803
+ if (entry.operation === "capture") return "New repo knowledge was captured for future agents.";
3804
+ if (entry.operation === "feedback") return "A recalled memory received explicit usefulness or stale feedback.";
3805
+ if (entry.operation === "approve") return "A pending packet became shared repo memory.";
3806
+ if (entry.operation === "reject") return "A pending packet was rejected instead of becoming shared memory.";
3807
+ if (entry.operation === "supersede") return "Old repo knowledge now points at a current replacement packet.";
3808
+ if (entry.operation === "deprecate") return "Stale memory was retired from active use.";
3809
+ if (entry.operation === "delete") return "Memory was removed by an explicit cleanup action.";
3810
+ return "Review this memory mutation before handoff.";
3811
+ }
3812
+
3813
+ function auditDetailsLabel(details) {
3814
+ if (!details || typeof details !== "object") return "none";
3815
+ if (details.feedback) return "feedback " + details.feedback;
3816
+ if (details.reason) return String(details.reason).slice(0, 80);
3817
+ if (details.type) return "type " + details.type;
3818
+ return Object.keys(details).slice(0, 3).join(", ") || "none";
3819
+ }
3820
+
3821
+ function renderMemoryLineage() {
3822
+ var report = state.reports && state.reports.lineage;
3823
+ var chains = Array.isArray(report && report.chains) ? report.chains : [];
3824
+ var orphans = Array.isArray(report && report.orphans) ? report.orphans : [];
3825
+ var totals = report && report.totals ? report.totals : {};
3826
+ if (els.memoryLineageStatus) {
3827
+ els.memoryLineageStatus.textContent = report ? String(Number(totals.chains || chains.length)) + " chains" : "waiting";
3828
+ }
3829
+ if (els.memoryLineageSummary) {
3830
+ els.memoryLineageSummary.textContent = "";
3831
+ [
3832
+ lifecycleSummaryStep("1", "Current", Number(totals.chains || chains.length), "Replacement packets future agents should trust."),
3833
+ lifecycleSummaryStep("2", "Retired", Number(totals.superseded || 0), "Old packets kept as audit history, not active recall."),
3834
+ lifecycleSummaryStep("3", "Repair", Number(totals.orphans || orphans.length), "Superseded packets missing a replacement link.")
3835
+ ].forEach(function (step) { els.memoryLineageSummary.appendChild(step); });
3836
+ }
3837
+ els.memoryLineageList.textContent = "";
3838
+ if (!report) {
3839
+ els.memoryLineageList.className = "session-capture-list details-empty";
3840
+ els.memoryLineageList.textContent = "No lineage report loaded. Run kage lineage or open the local viewer after refresh.";
3841
+ return;
3842
+ }
3843
+ if (!chains.length && !orphans.length) {
3844
+ els.memoryLineageList.className = "session-capture-list details-empty";
3845
+ els.memoryLineageList.textContent = "No retired memory chains yet. Use kage supersede when a newer packet replaces old repo knowledge.";
3846
+ return;
3847
+ }
3848
+ els.memoryLineageList.className = "session-capture-list";
3849
+ chains.slice(0, 6).forEach(function (chain) {
3850
+ var card = document.createElement("article");
3851
+ card.className = "session-capture-card";
3852
+ card.innerHTML = [
3853
+ "<div class=\"session-capture-head\"><div><strong></strong><span></span></div><em>current</em></div>",
3854
+ "<p></p>",
3855
+ "<div class=\"session-capture-meta\"></div>"
3856
+ ].join("");
3857
+ card.querySelector("strong").textContent = chain.current_title || "Replacement memory";
3858
+ card.querySelector("span").textContent = "replaces " + Number((chain.superseded_packet_ids || []).length) + " packet" + ((chain.superseded_packet_ids || []).length === 1 ? "" : "s");
3859
+ card.querySelector("p").textContent = chain.action || "Use the current replacement packet in recall.";
3860
+ var meta = card.querySelector(".session-capture-meta");
3861
+ [
3862
+ ["reason", chain.reason || "replacement linked"],
3863
+ ["paths", Array.isArray(chain.paths) ? chain.paths.slice(0, 2).join(", ") || "none" : "none"],
3864
+ ["updated", timelineDateLabel(chain.updated_at)]
3865
+ ].forEach(function (row) {
3866
+ var chip = document.createElement("span");
3867
+ chip.textContent = row[0] + ": " + row[1];
3868
+ meta.appendChild(chip);
3869
+ });
3870
+ var entity = chain.current_packet_id ? findMemoryEntityByPacketId(chain.current_packet_id) : null;
3871
+ if (entity) {
3872
+ card.tabIndex = 0;
3873
+ card.setAttribute("role", "button");
3874
+ card.addEventListener("click", function () {
3875
+ selectEntity(entity.id, true);
3876
+ render();
3877
+ });
3878
+ }
3879
+ els.memoryLineageList.appendChild(card);
3880
+ });
3881
+ orphans.slice(0, 4).forEach(function (orphan) {
3882
+ var card = document.createElement("article");
3883
+ card.className = "session-capture-card memory-action-danger";
3884
+ card.innerHTML = [
3885
+ "<div class=\"session-capture-head\"><div><strong></strong><span></span></div><em>repair</em></div>",
3886
+ "<p></p>"
3887
+ ].join("");
3888
+ card.querySelector("strong").textContent = orphan.title || "Superseded memory";
3889
+ card.querySelector("span").textContent = timelineDateLabel(orphan.updated_at);
3890
+ card.querySelector("p").textContent = orphan.action || "Add a replacement link before trusting this old context.";
3891
+ els.memoryLineageList.appendChild(card);
3892
+ });
3893
+ }
3894
+
3895
+ function timelineKindLabel(kind) {
3896
+ if (kind === "added") return "added";
3897
+ if (kind === "updated") return "updated";
3898
+ if (kind === "pending") return "pending";
3899
+ if (kind === "deprecated") return "retired";
3900
+ return "activity";
3901
+ }
3902
+
3903
+ function timelineDateLabel(date) {
3904
+ return date ? String(date).slice(0, 10) : "";
3905
+ }
3906
+
3907
+ function memoryActionLabel(kind) {
3908
+ if (kind === "promote_hot") return "Hot";
3909
+ if (kind === "review_cold") return "Cold";
3910
+ if (kind === "connect_paths") return "Ground";
3911
+ if (kind === "add_grounding") return "Ground";
3912
+ if (kind === "review_stale") return "Stale";
3913
+ if (kind === "resolve_feedback") return "Feedback";
3914
+ if (kind === "archive_generated") return "Archive";
3915
+ if (kind === "review_pending") return "Pending";
3916
+ if (kind === "keep_verified") return "Keep";
3917
+ if (kind === "seed_usage") return "Start";
3918
+ return "Review";
3919
+ }
3920
+
3921
+ function findMemoryEntityByPacketId(packetId) {
3922
+ return state.entities.find(function (entity) {
3923
+ if (!isMemoryPacketEntity(entity)) return false;
3924
+ if (entity.id === packetId || entity.packet_id === packetId) return true;
3925
+ return Array.isArray(entity.aliases) && entity.aliases.indexOf(packetId) !== -1;
3926
+ }) || null;
3927
+ }
3928
+
3929
+ function renderMemoryLifecycle(memoryEntities, memoryLinkCounts) {
3930
+ if (!els.lifecycleList) return;
3931
+ var report = state.reports && state.reports.lifecycle;
3932
+ if (report && Array.isArray(report.items)) {
3933
+ renderMemoryLifecycleReport(report);
3934
+ return;
3935
+ }
3936
+ var proofPackets = memoryEntities
3937
+ .map(function (entity) {
3938
+ var links = memoryCodeLinksForEntity(entity.id);
3939
+ return {
3940
+ entity: entity,
3941
+ links: links,
3942
+ score: lifecyclePacketScore(entity, links, memoryLinkCounts.get(entity.id) || 0)
3943
+ };
3944
+ })
3945
+ .sort(function (a, b) {
3946
+ return b.score - a.score ||
3947
+ lifecycleTimestamp(b.entity).localeCompare(lifecycleTimestamp(a.entity)) ||
3948
+ displayName(a.entity).localeCompare(displayName(b.entity));
3949
+ });
3950
+ var grounded = proofPackets.filter(function (item) { return item.links.length; }).length;
3951
+ var recallReady = proofPackets.filter(function (item) {
3952
+ return item.links.length && lifecycleEvidence(item.entity).length && lifecycleRecallQuery(item.entity);
3953
+ }).length;
3954
+ if (els.lifecycleStatus) {
3955
+ els.lifecycleStatus.textContent = proofPackets.length ? recallReady + " recall-ready" : "empty";
3956
+ }
3957
+ if (els.lifecycleSummary) {
3958
+ els.lifecycleSummary.textContent = "";
3959
+ [
3960
+ lifecycleSummaryStep("1", "Captured", proofPackets.length, "Reusable facts agents saved instead of rediscovering."),
3961
+ lifecycleSummaryStep("2", "Grounded", grounded, "Packets linked to files, symbols, routes, or tests."),
3962
+ lifecycleSummaryStep("3", "Recall-ready", recallReady, "Grounded packets with evidence and a likely future query.")
3963
+ ].forEach(function (step) { els.lifecycleSummary.appendChild(step); });
3964
+ }
3965
+ els.lifecycleList.textContent = "";
3966
+ if (!proofPackets.length) {
3967
+ els.lifecycleList.className = "lifecycle-list details-empty";
3968
+ els.lifecycleList.textContent = "No memory lifecycle to show yet. Capture reusable repo knowledge with kage learn or kage propose.";
3969
+ return;
3970
+ }
3971
+ els.lifecycleList.className = "lifecycle-list";
3972
+ proofPackets.slice(0, 5).forEach(function (item) {
3973
+ els.lifecycleList.appendChild(renderLifecycleCard(item.entity, item.links));
3974
+ });
3975
+ }
3976
+
3977
+ function renderMemoryLifecycleReport(report) {
3978
+ var totals = report.totals || {};
3979
+ var needsReview = Number(totals.stale || 0) + Number(totals.ungrounded || 0) + Number(totals.disputed || 0) + Number(totals.pending || 0);
3980
+ if (els.lifecycleStatus) {
3981
+ els.lifecycleStatus.textContent = needsReview ? needsReview + " need review" : "healthy";
3982
+ }
3983
+ if (els.lifecycleSummary) {
3984
+ els.lifecycleSummary.textContent = "";
3985
+ [
3986
+ lifecycleSummaryStep("1", "Approved", Number(totals.approved || 0), "Repo-local packets available to future agents."),
3987
+ lifecycleSummaryStep("2", "Hot/healthy", Number(totals.hot || 0) + Number(totals.healthy || 0), "Memories that are grounded, fresh, or repeatedly recalled."),
3988
+ lifecycleSummaryStep("3", "Needs action", needsReview, "Packets to verify, ground, review, or resolve before trusting.")
3989
+ ].forEach(function (step) { els.lifecycleSummary.appendChild(step); });
3990
+ }
3991
+ els.lifecycleList.textContent = "";
3992
+ var items = Array.isArray(report.items) ? report.items : [];
3993
+ if (!items.length) {
3994
+ els.lifecycleList.className = "lifecycle-list details-empty";
3995
+ els.lifecycleList.textContent = "No memory lifecycle to show yet. Capture reusable repo knowledge with kage learn or kage propose.";
3996
+ return;
3997
+ }
3998
+ els.lifecycleList.className = "lifecycle-list";
3999
+ items.slice(0, 6).forEach(function (item) {
4000
+ els.lifecycleList.appendChild(renderLifecycleReportCard(item));
4001
+ });
4002
+ }
4003
+
4004
+ function renderLifecycleReportCard(item) {
4005
+ var card = document.createElement("article");
4006
+ card.className = classNames("lifecycle-card", item.severity && "memory-action-" + item.severity);
4007
+ card.innerHTML = [
4008
+ "<div class=\"lifecycle-card-head\"><div><strong></strong><span></span></div><button type=\"button\">Inspect</button></div>",
4009
+ "<p></p>",
4010
+ "<div class=\"lifecycle-flow\"></div>"
4011
+ ].join("");
4012
+ card.querySelector("strong").textContent = item.title || "Memory packet";
4013
+ card.querySelector(".lifecycle-card-head span").textContent = [
4014
+ memoryActionLabel(item.recommended_action),
4015
+ item.health,
4016
+ item.type
4017
+ ].filter(Boolean).join(" | ");
4018
+ card.querySelector("p").textContent = item.reason || item.action || "Lifecycle action";
4019
+ var entity = item.packet_id ? findMemoryEntityByPacketId(item.packet_id) : null;
4020
+ var button = card.querySelector("button");
4021
+ if (entity) {
4022
+ button.addEventListener("click", function () {
4023
+ selectEntity(entity.id, true);
4024
+ render();
4025
+ });
4026
+ } else {
4027
+ button.disabled = true;
4028
+ button.textContent = "No node";
4029
+ }
4030
+ var flow = card.querySelector(".lifecycle-flow");
4031
+ [
4032
+ lifecycleFlowCell("Evidence", Number(item.source_refs || 0) + " source ref" + (Number(item.source_refs || 0) === 1 ? "" : "s")),
4033
+ lifecycleFlowCell("Grounding", item.paths && item.paths.length ? trimIntelText(item.paths[0], 100) : "Needs code path or symbol link"),
4034
+ lifecycleFlowCell("Recall", Number(item.uses_30d || 0) + " use" + (Number(item.uses_30d || 0) === 1 ? "" : "s") + " in 30d"),
4035
+ lifecycleFlowCell("Action", item.action || "Keep verified")
4036
+ ].forEach(function (cell) { flow.appendChild(cell); });
4037
+ return card;
4038
+ }
4039
+
4040
+ function lifecycleSummaryStep(number, label, value, detail) {
4041
+ var item = document.createElement("article");
4042
+ item.className = "lifecycle-step";
4043
+ item.innerHTML = "<strong></strong><span></span><em></em><p></p>";
4044
+ item.querySelector("strong").textContent = number;
4045
+ item.querySelector("span").textContent = label;
4046
+ item.querySelector("em").textContent = formatDashboardValue(value);
4047
+ item.querySelector("p").textContent = detail;
4048
+ return item;
4049
+ }
4050
+
4051
+ function renderLifecycleCard(entity, links) {
4052
+ var card = document.createElement("article");
4053
+ card.className = "lifecycle-card";
4054
+ var codeTargets = links.map(function (edge) {
4055
+ return state.entityById.get(edge.from === entity.id ? edge.to : edge.from);
4056
+ }).filter(Boolean).sort(function (a, b) {
4057
+ return codeTargetScore(b) - codeTargetScore(a) || codeTargetLabel(a).localeCompare(codeTargetLabel(b));
4058
+ });
4059
+ var evidence = lifecycleEvidence(entity);
4060
+ var query = lifecycleRecallQuery(entity);
4061
+ card.innerHTML = [
4062
+ "<div class=\"lifecycle-card-head\"><div><strong></strong><span></span></div><button type=\"button\">Inspect</button></div>",
4063
+ "<p></p>",
4064
+ "<div class=\"lifecycle-flow\"></div>"
4065
+ ].join("");
4066
+ card.querySelector("strong").textContent = displayName(entity);
4067
+ card.querySelector(".lifecycle-card-head span").textContent = [
4068
+ entity.type || "memory",
4069
+ lifecycleTimestampLabel(entity)
4070
+ ].filter(Boolean).join(" | ");
4071
+ card.querySelector("p").textContent = trimIntelText(entity.summary || entity.description || entity.path || "No summary", 220);
4072
+ card.querySelector("button").addEventListener("click", function () {
4073
+ selectEntity(entity.id, true);
4074
+ render();
4075
+ });
4076
+ var flow = card.querySelector(".lifecycle-flow");
4077
+ [
4078
+ lifecycleFlowCell("Captured", evidence.length ? trimIntelText(evidence[0], 100) : "No source evidence loaded"),
4079
+ lifecycleFlowCell("Grounded", codeTargets.length ? trimIntelText(codeTargetLabel(codeTargets[0]), 100) : "Needs code path or symbol link"),
4080
+ lifecycleFlowCell("Recall", query ? "\"" + query + "\"" : "Search by title, summary, tag, or path"),
4081
+ lifecycleFlowCell("Handoff", lifecycleHandoffText(entity, codeTargets.length))
4082
+ ].forEach(function (cell) { flow.appendChild(cell); });
4083
+ return card;
4084
+ }
4085
+
4086
+ function lifecycleFlowCell(label, value) {
4087
+ var cell = document.createElement("div");
4088
+ cell.className = "lifecycle-cell";
4089
+ cell.innerHTML = "<span></span><strong></strong>";
4090
+ cell.querySelector("span").textContent = label;
4091
+ cell.querySelector("strong").textContent = value;
4092
+ return cell;
4093
+ }
4094
+
4095
+ function lifecyclePacketScore(entity, links, linkCount) {
4096
+ var score = 0;
4097
+ score += Math.min(80, (links.length || linkCount) * 24);
4098
+ score += lifecycleEvidence(entity).length ? 35 : 0;
4099
+ score += lifecycleRecallQuery(entity) ? 25 : 0;
4100
+ score += ["decision", "bug_fix", "runbook", "gotcha", "code_explanation", "workflow"].indexOf(entity.type) !== -1 ? 20 : 0;
4101
+ score += lifecycleTimestamp(entity) ? 8 : 0;
4102
+ return score;
4103
+ }
4104
+
4105
+ function lifecycleEvidence(entity) {
4106
+ var evidence = [];
4107
+ if (Array.isArray(entity.evidence)) evidence = evidence.concat(entity.evidence);
4108
+ if (Array.isArray(entity.source_refs)) {
4109
+ entity.source_refs.forEach(function (ref) {
4110
+ evidence.push([ref.kind, ref.captured_at || ref.path || ref.source].filter(Boolean).join(" "));
4111
+ });
4112
+ }
4113
+ if (entity.freshness && entity.freshness.verification) evidence.push(entity.freshness.verification);
4114
+ return evidence.map(function (item) { return String(item || "").trim(); }).filter(Boolean);
4115
+ }
4116
+
4117
+ function lifecycleTimestamp(entity) {
4118
+ return String(entity.updated_at || entity.created_at || entity.last_seen_at || entity.first_seen_at || "");
4119
+ }
4120
+
4121
+ function lifecycleTimestampLabel(entity) {
4122
+ var value = lifecycleTimestamp(entity);
4123
+ return value ? value.slice(0, 10) : "";
4124
+ }
4125
+
4126
+ function lifecycleRecallQuery(entity) {
4127
+ var text = String(entity.summary || entity.description || displayName(entity) || "").trim();
4128
+ var path = Array.isArray(entity.paths) && entity.paths.length ? entity.paths[0] : entity.path;
4129
+ if (entity.type === "runbook") return "how to run " + trimIntelText(displayName(entity), 54);
4130
+ if (entity.type === "bug_fix") return "why did " + trimIntelText(displayName(entity), 54) + " break";
4131
+ if (entity.type === "decision") return "why was " + trimIntelText(displayName(entity), 54) + " decided";
4132
+ if (path) return "what should I know before changing " + path;
4133
+ return text ? trimIntelText(text, 76) : "";
4134
+ }
4135
+
4136
+ function lifecycleHandoffText(entity, linkedTargetCount) {
4137
+ if (linkedTargetCount) return "Future agents touching this area can recall the reason before editing.";
4138
+ if (entity.type === "reference") return "Useful context, but add code paths if it should guide edits.";
4139
+ return "Add paths so teammates and agents can use it at change time.";
4140
+ }
4141
+
4142
+ function renderSessionCapture() {
4143
+ if (!els.sessionCaptureList) return;
4144
+ var report = state.reports && state.reports.sessions;
4145
+ var replay = state.reports && state.reports.replay;
4146
+ var totals = report && report.totals ? report.totals : {};
4147
+ var sessions = Array.isArray(report && report.sessions) ? report.sessions : [];
4148
+ var replayEvents = Array.isArray(replay && replay.events) ? replay.events : [];
4149
+ if (els.sessionCaptureStatus) {
4150
+ els.sessionCaptureStatus.textContent = sessions.length ? String(totals.sessions || sessions.length) + " sessions" : "no sessions";
4151
+ }
4152
+ if (els.sessionCaptureSummary) {
4153
+ els.sessionCaptureSummary.textContent = "";
4154
+ [
4155
+ lifecycleSummaryStep("1", "Observed", Number(totals.observations || 0), "Privacy-scanned local events from agent sessions."),
4156
+ lifecycleSummaryStep("2", "Distillable", Number(totals.durable_observations || 0), "Reusable command, workflow, decision, or issue signals."),
4157
+ lifecycleSummaryStep("3", "Needs review", Number(totals.sessions_with_candidates || 0), "Sessions ready for packet review with kage distill.")
4158
+ ].forEach(function (step) { els.sessionCaptureSummary.appendChild(step); });
4159
+ }
4160
+ els.sessionCaptureList.textContent = "";
4161
+ if (!sessions.length) {
4162
+ els.sessionCaptureList.className = "session-capture-list details-empty";
4163
+ els.sessionCaptureList.textContent = "No observed sessions yet. Agents can call kage_observe, then kage_distill turns durable observations into reviewable memory packets.";
4164
+ return;
4165
+ }
4166
+ els.sessionCaptureList.className = "session-capture-list";
4167
+ sessions.slice(0, 6).forEach(function (session) {
4168
+ var card = document.createElement("article");
4169
+ card.className = "session-capture-card";
4170
+ var candidates = Array.isArray(session.candidate_types) && session.candidate_types.length ? session.candidate_types.join(", ") : "none";
4171
+ var agents = Array.isArray(session.agents) && session.agents.length ? session.agents.join(", ") : "unknown agent";
4172
+ card.innerHTML = [
4173
+ "<div class=\"session-capture-head\"><div><strong></strong><span></span></div><em></em></div>",
4174
+ "<p></p>",
4175
+ "<div class=\"session-capture-meta\"></div>"
4176
+ ].join("");
4177
+ card.querySelector("strong").textContent = session.session_id || "default";
4178
+ card.querySelector("span").textContent = [agents, session.last_at || session.first_at || ""].filter(Boolean).join(" · ");
4179
+ card.querySelector("em").textContent = String(session.durable_observations || 0) + " distillable";
4180
+ card.querySelector("p").textContent = session.next_action || "Review this session before saving durable memory.";
4181
+ var meta = card.querySelector(".session-capture-meta");
4182
+ [
4183
+ ["events", session.observations || 0],
4184
+ ["candidates", candidates],
4185
+ ["commands", Array.isArray(session.commands) ? session.commands.slice(0, 2).join(", ") || "none" : "none"],
4186
+ ["paths", Array.isArray(session.paths) ? session.paths.slice(0, 2).join(", ") || "none" : "none"]
4187
+ ].forEach(function (item) {
4188
+ var chip = document.createElement("span");
4189
+ chip.textContent = item[0] + ": " + item[1];
4190
+ meta.appendChild(chip);
4191
+ });
4192
+ els.sessionCaptureList.appendChild(card);
4193
+ });
4194
+ if (replayEvents.length) {
4195
+ var replayCard = document.createElement("article");
4196
+ replayCard.className = "session-capture-card";
4197
+ replayCard.innerHTML = [
4198
+ "<div class=\"session-capture-head\"><div><strong>Replay digest</strong><span>raw transcript text excluded</span></div><em></em></div>",
4199
+ "<p></p>",
4200
+ "<div class=\"session-capture-meta\"></div>"
4201
+ ].join("");
4202
+ replayCard.querySelector("em").textContent = String(replay && replay.totals ? replay.totals.durable_candidates || 0 : 0) + " candidates";
4203
+ replayCard.querySelector("p").textContent = replay && replay.next_action ? replay.next_action : "Review the digest, then distill durable observations into memory packets.";
4204
+ var replayMeta = replayCard.querySelector(".session-capture-meta");
4205
+ replayEvents.slice(0, 6).forEach(function (event) {
4206
+ var chip = document.createElement("span");
4207
+ chip.textContent = (event.durable_candidate ? "candidate: " : "event: ") + (event.label || event.type || "observation") + " · " + trimIntelText(event.summary || "", 90);
4208
+ replayMeta.appendChild(chip);
4209
+ });
4210
+ els.sessionCaptureList.appendChild(replayCard);
4211
+ }
4212
+ }
4213
+
3201
4214
  function memoryCodeLinksForEntity(entityId) {
3202
4215
  return state.edges.filter(function (edge) {
3203
4216
  if ((edge.from !== entityId && edge.to !== entityId) || !isMemoryCodeEdge(edge)) return false;
@@ -3357,17 +4370,29 @@
3357
4370
  var packets = state.pendingPackets || [];
3358
4371
  var inbox = state.inbox;
3359
4372
  var inboxItems = inbox && Array.isArray(inbox.items) ? inbox.items : [];
4373
+ var handoff = state.reports && state.reports.handoff;
4374
+ var handoffItems = handoff && Array.isArray(handoff.items) ? handoff.items : [];
3360
4375
  var counts = inbox && inbox.counts ? inbox.counts : {};
3361
4376
  var openCount = reviewOpenCount(counts, packets, inboxItems);
4377
+ if (handoff && handoff.totals) openCount = Number(firstNumber(handoff.totals.open_items, openCount));
3362
4378
  els.reviewCount.textContent = String(openCount);
3363
4379
  els.reviewList.textContent = "";
3364
- if (els.reviewOverview) renderReviewOverview(inbox, packets, inboxItems);
3365
- if (!packets.length && !inboxItems.length && !state.reviewText) {
4380
+ if (els.reviewOverview) renderReviewOverview(inbox, packets, inboxItems, handoff);
4381
+ renderMemoryHandoff(handoff, handoffItems);
4382
+ if (!packets.length && !inboxItems.length && !handoffItems.length && !state.reviewText) {
3366
4383
  els.reviewList.className = "review-list details-empty";
3367
4384
  els.reviewList.textContent = "No pending packets loaded. Launch with `kage viewer --project <repo>` to load review context automatically.";
3368
4385
  return;
3369
4386
  }
3370
4387
  els.reviewList.className = "review-list";
4388
+ handoffItems.slice(0, 10).forEach(function (entry) {
4389
+ els.reviewList.appendChild(reviewCard({
4390
+ title: entry.title || entry.summary || entry.kind,
4391
+ meta: [entry.kind, entry.severity, entry.date && timelineDateLabel(entry.date)].filter(Boolean).join(" | "),
4392
+ summary: entry.summary || "",
4393
+ risk: entry.action || "Review before handoff"
4394
+ }, "handoff"));
4395
+ });
3371
4396
  if (inbox) {
3372
4397
  var summary = document.createElement("div");
3373
4398
  summary.className = "review-item";
@@ -3434,7 +4459,7 @@
3434
4459
  }
3435
4460
  }
3436
4461
 
3437
- function renderReviewOverview(inbox, packets, inboxItems) {
4462
+ function renderReviewOverview(inbox, packets, inboxItems, handoff) {
3438
4463
  els.reviewOverview.textContent = "";
3439
4464
  var counts = inbox && inbox.counts ? inbox.counts : {};
3440
4465
  var pending = Number(firstNumber(counts.pending, packets.length, 0));
@@ -3442,19 +4467,74 @@
3442
4467
  var duplicates = Number(firstNumber(counts.duplicates, 0));
3443
4468
  var missingContext = Number(firstNumber(counts.missing_context, 0));
3444
4469
  var blockers = reviewOpenCount(counts, packets, inboxItems);
4470
+ if (handoff && handoff.totals) blockers = Number(firstNumber(handoff.totals.open_items, blockers));
4471
+ var mutations = handoff && handoff.totals ? Number(firstNumber(handoff.totals.recent_mutations, 0)) : 0;
3445
4472
  els.reviewOverview.appendChild(metricDonut(
3446
4473
  "Handoff readiness",
3447
4474
  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.",
4475
+ blockers ? blockers + " memory handoff item(s) need attention" : "No pending, stale, duplicate, or missing-context memory",
4476
+ blockers ? "Resolve the handoff queue before trusting branch memory." : "Ready to hand work to another agent or teammate.",
3450
4477
  blockers ? "warn" : "ok"
3451
4478
  ));
3452
- els.reviewOverview.appendChild(metricBars("Inbox breakdown", blockers ? blockers + " open" : "clear", [
4479
+ els.reviewOverview.appendChild(metricBars(handoff ? "Handoff queue" : "Inbox breakdown", blockers ? blockers + " open" : "clear", [
3453
4480
  { label: "Pending", value: pending, score: Math.min(100, pending * 24), status: pending ? "warn" : "ok" },
3454
4481
  { label: "Stale", value: stale, score: Math.min(100, stale * 24), status: stale ? "warn" : "ok" },
3455
4482
  { 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"));
4483
+ { label: "Mutations", value: mutations, score: Math.min(100, mutations * 16), status: mutations ? "ok" : "warn" }
4484
+ ], handoff ? (handoff.summary || "Combined inbox, lifecycle, audit, timeline, and lineage.") : "These are the review metrics that should block merge or handoff.", blockers ? "warn" : "ok"));
4485
+ }
4486
+
4487
+ function renderMemoryHandoff(handoff, items) {
4488
+ if (!els.handoffList) return;
4489
+ var totals = handoff && handoff.totals ? handoff.totals : {};
4490
+ if (els.handoffStatus) {
4491
+ els.handoffStatus.textContent = handoff ? (Number(totals.open_items || 0) ? Number(totals.open_items || 0) + " open" : "ready") : "waiting";
4492
+ }
4493
+ if (els.handoffSummary) {
4494
+ els.handoffSummary.textContent = "";
4495
+ [
4496
+ lifecycleSummaryStep("1", "Open", Number(totals.open_items || 0), "Blockers and warnings to resolve before another agent trusts memory."),
4497
+ lifecycleSummaryStep("2", "Distill", Number(totals.distillable_sessions || 0), "Observed sessions with reusable learnings that should become memory packets."),
4498
+ lifecycleSummaryStep("3", "Mutations", Number(totals.recent_mutations || 0), "Memory captures, reviews, feedback, and supersedes since audit logging began."),
4499
+ lifecycleSummaryStep("4", "Lineage", Number(totals.supersession_orphans || 0), "Retired packets missing a current replacement.")
4500
+ ].forEach(function (step) { els.handoffSummary.appendChild(step); });
4501
+ }
4502
+ els.handoffList.textContent = "";
4503
+ if (!handoff) {
4504
+ els.handoffList.className = "session-capture-list details-empty";
4505
+ els.handoffList.textContent = "No handoff report loaded. Run kage handoff or open the local viewer after refresh.";
4506
+ return;
4507
+ }
4508
+ if (!items.length) {
4509
+ els.handoffList.className = "session-capture-list details-empty";
4510
+ els.handoffList.textContent = "No memory handoff actions are open.";
4511
+ return;
4512
+ }
4513
+ els.handoffList.className = "session-capture-list";
4514
+ items.slice(0, 8).forEach(function (entry) {
4515
+ els.handoffList.appendChild(reviewCard({
4516
+ title: entry.title || entry.summary || entry.kind,
4517
+ meta: [entry.kind, entry.severity, entry.date && timelineDateLabel(entry.date)].filter(Boolean).join(" | "),
4518
+ summary: entry.summary || "",
4519
+ risk: entry.action || "Review before handoff"
4520
+ }, "handoff"));
4521
+ });
4522
+ }
4523
+
4524
+ function reviewCard(entry, extraClass) {
4525
+ var item = document.createElement("div");
4526
+ item.className = "review-item" + (extraClass ? " " + extraClass : "");
4527
+ item.innerHTML = [
4528
+ "<div class=\"review-title\"></div>",
4529
+ "<div class=\"review-meta\"></div>",
4530
+ "<div class=\"review-summary\"></div>",
4531
+ "<div class=\"review-risks\"></div>"
4532
+ ].join("");
4533
+ item.querySelector(".review-title").textContent = entry.title || "";
4534
+ item.querySelector(".review-meta").textContent = entry.meta || "";
4535
+ item.querySelector(".review-summary").textContent = entry.summary || "";
4536
+ item.querySelector(".review-risks").textContent = entry.risk || "";
4537
+ return item;
3458
4538
  }
3459
4539
 
3460
4540
  function reviewOpenCount(counts, packets, inboxItems) {
@@ -3481,6 +4561,7 @@
3481
4561
  els.proofStatus.textContent = "loaded";
3482
4562
  els.proofList.className = "proof-list";
3483
4563
  if (els.proofOverview) renderProofOverview(metrics, state.reports || {});
4564
+ renderProofLedger(state.reports && state.reports.benchmark);
3484
4565
  var rows = [
3485
4566
  ["Validation", metrics.harness && metrics.harness.validation_ok ? "clean" : "check"],
3486
4567
  ["Evidence", metrics.memory_graph ? metrics.memory_graph.evidence_coverage_percent + "%" : "n/a"],
@@ -3497,15 +4578,58 @@
3497
4578
  });
3498
4579
  }
3499
4580
 
4581
+ function renderProofLedger(benchmark) {
4582
+ var ledger = benchmark && Array.isArray(benchmark.proof_ledger) ? benchmark.proof_ledger : [];
4583
+ ledger.forEach(function (entry) {
4584
+ var item = document.createElement("div");
4585
+ item.className = "proof-item proof-ledger-item " + (entry.pass ? "is-ok" : "is-warn");
4586
+ item.innerHTML = [
4587
+ "<strong></strong>",
4588
+ "<span></span>",
4589
+ "<code></code>",
4590
+ "<p></p>"
4591
+ ].join("");
4592
+ item.querySelector("strong").textContent = entry.metric || (entry.pass ? "passing" : "check");
4593
+ item.querySelector("span").textContent = [entry.label, entry.target].filter(Boolean).join(" | ");
4594
+ item.querySelector("code").textContent = entry.command || "";
4595
+ item.querySelector("p").textContent = entry.next_action || "Review this proof before publishing benchmark claims.";
4596
+ els.proofList.appendChild(item);
4597
+ });
4598
+ }
4599
+
3500
4600
  function renderProofOverview(metrics, reports) {
3501
4601
  els.proofOverview.textContent = "";
3502
4602
  var quality = reports.quality || {};
3503
4603
  var benchmark = reports.benchmark || {};
3504
4604
  var gates = Array.isArray(benchmark.gates) ? benchmark.gates : [];
4605
+ var retrieval = benchmarkRetrievalSummary(benchmark);
4606
+ var sourceDiversity = benchmarkSourceDiversitySummary(benchmark);
4607
+ var scale = benchmarkScaleSummary(benchmark);
3505
4608
  var passingGates = gates.filter(function (gate) { return gate.pass; }).length;
3506
4609
  var gatePercent = gates.length ? Math.round(passingGates / gates.length * 100) : (benchmark.ok ? 100 : 0);
3507
4610
  var evidence = Number(firstNumber(metrics.memory_graph && metrics.memory_graph.evidence_coverage_percent, quality.evidence_coverage_percent, 0));
3508
4611
  var pathGrounding = Number(firstNumber(quality.path_grounding_coverage_percent, 0));
4612
+ if (retrieval) {
4613
+ els.proofOverview.appendChild(metricBars("Retrieval proof", retrieval.r10 + "% R@10", [
4614
+ { label: "R@5", value: retrieval.r5 != null ? retrieval.r5 + "%" : "n/a", score: retrieval.r5 || 0, status: retrieval.r5 >= 95 ? "ok" : "warn" },
4615
+ { label: "R@10", value: retrieval.r10 + "%", score: retrieval.r10, status: retrieval.r10 >= 95 ? "ok" : "warn" },
4616
+ { 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" }
4617
+ ], retrieval.label + ". Measures memory retrieval proof, not answer accuracy.", retrieval.r10 >= 95 ? "ok" : "warn"));
4618
+ }
4619
+ if (sourceDiversity) {
4620
+ els.proofOverview.appendChild(metricBars("Source diversity proof", sourceDiversity.uniqueSources + " sources", [
4621
+ { label: "Sources", value: sourceDiversity.uniqueSources, score: Math.min(100, sourceDiversity.uniqueSources * 50), status: sourceDiversity.uniqueSources >= 2 ? "ok" : "warn" },
4622
+ { label: "Max/session", value: sourceDiversity.maxFromOneSource, score: sourceDiversity.maxFromOneSource <= 3 ? 100 : 45, status: sourceDiversity.maxFromOneSource <= 3 ? "ok" : "warn" },
4623
+ { 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" }
4624
+ ], "Noisy observed sessions should not crowd out independent teammate memory.", sourceDiversity.pass ? "ok" : "warn"));
4625
+ }
4626
+ if (scale) {
4627
+ els.proofOverview.appendChild(metricBars("Scale proof", scale.packets + " packets", [
4628
+ { label: "Hit rate", value: scale.hitRate + "%", score: scale.hitRate, status: scale.hitRate >= 95 ? "ok" : "warn" },
4629
+ { label: "Median", value: scale.medianLatency + "ms", score: Math.max(0, 100 - scale.medianLatency), status: scale.medianLatency <= 50 ? "ok" : "warn" },
4630
+ { label: "Context cut", value: scale.contextReduction + "%", score: scale.contextReduction, status: scale.contextReduction >= 80 ? "ok" : "warn" }
4631
+ ], "Scale benchmark checks large repo-memory retrieval cost.", scale.hitRate >= 95 ? "ok" : "warn"));
4632
+ }
3509
4633
  els.proofOverview.appendChild(metricDonut(
3510
4634
  "Trust gate",
3511
4635
  gatePercent,
@@ -3520,6 +4644,70 @@
3520
4644
  ], "Trust memory only when it is evidence-backed and path-grounded.", evidence >= 80 ? "ok" : "warn"));
3521
4645
  }
3522
4646
 
4647
+ function benchmarkRetrievalSummary(report) {
4648
+ if (!report || !report.summary) return null;
4649
+ var summary = report.summary;
4650
+ var r10 = Number(summary.recall_at_10_percent);
4651
+ if (!Number.isFinite(r10)) return null;
4652
+ var mode = summary.retrieval_mode || report.retrieval_mode || "";
4653
+ var embeddings = summary.embeddings || report.embeddings || null;
4654
+ var modeLabel = mode === "kage-recall-with-dense-local-embeddings"
4655
+ ? "dense local embeddings"
4656
+ : mode === "kage-recall-default"
4657
+ ? "default recall"
4658
+ : mode;
4659
+ var modelLabel = embeddings && embeddings.model ? " · " + embeddings.model : "";
4660
+ return {
4661
+ label: [summary.benchmark || report.benchmark || "Retrieval benchmark", modeLabel].filter(Boolean).join(" · "),
4662
+ mode: modeLabel,
4663
+ model: modelLabel ? modelLabel.slice(3) : "",
4664
+ r5: numberOrNull(summary.recall_at_5_percent),
4665
+ r10: r10,
4666
+ r20: numberOrNull(summary.recall_at_20_percent),
4667
+ mrr: numberOrNull(summary.mrr),
4668
+ ndcg10: numberOrNull(summary.ndcg_at_10),
4669
+ contextReduction: numberOrNull(summary.context_reduction_percent)
4670
+ };
4671
+ }
4672
+
4673
+ function benchmarkSourceDiversitySummary(report) {
4674
+ var diversity = report && report.memory_quality && report.memory_quality.source_diversity
4675
+ ? report.memory_quality.source_diversity
4676
+ : report && report.source_diversity;
4677
+ if (!diversity) return null;
4678
+ var uniqueSources = numberOrNull(diversity.unique_sources);
4679
+ var maxFromOneSource = numberOrNull(diversity.max_results_from_one_source);
4680
+ var topK = numberOrNull(diversity.top_k) || 4;
4681
+ if (uniqueSources == null || maxFromOneSource == null) return null;
4682
+ return {
4683
+ uniqueSources: uniqueSources,
4684
+ maxFromOneSource: maxFromOneSource,
4685
+ independentRank: numberOrNull(diversity.independent_source_rank),
4686
+ topK: topK,
4687
+ pass: Boolean(diversity.pass)
4688
+ };
4689
+ }
4690
+
4691
+ function benchmarkScaleSummary(report) {
4692
+ var scale = report && report.memory_scale;
4693
+ var summary = scale && scale.summary;
4694
+ if (!summary) return null;
4695
+ var hitRate = numberOrNull(summary.largest_hit_rate_percent);
4696
+ var packets = numberOrNull(summary.largest_packets);
4697
+ if (hitRate == null || packets == null) return null;
4698
+ return {
4699
+ packets: packets,
4700
+ hitRate: hitRate,
4701
+ medianLatency: numberOrNull(summary.largest_median_recall_latency_ms) || 0,
4702
+ contextReduction: numberOrNull(summary.largest_context_reduction_percent) || 0
4703
+ };
4704
+ }
4705
+
4706
+ function numberOrNull(value) {
4707
+ var number = Number(value);
4708
+ return Number.isFinite(number) ? number : null;
4709
+ }
4710
+
3523
4711
  function renderIntelligence() {
3524
4712
  if (!els.intelligenceList) return;
3525
4713
  var reports = state.reports || {};
@@ -3532,7 +4720,11 @@
3532
4720
  return;
3533
4721
  }
3534
4722
  els.intelligenceList.className = "intelligence-list";
3535
- normalizeIntelCards(cards).slice(0, 6).forEach(function (card) {
4723
+ var normalizedCards = normalizeIntelCards(cards);
4724
+ var riskFirst = new Set(["Change Risk", "Module Health", "Decision Memory", "Graph Insights", "Memory Quality"]);
4725
+ var primaryCards = normalizedCards.filter(function (card) { return riskFirst.has(card.title); }).slice(0, 3);
4726
+ if (!primaryCards.length) primaryCards = normalizedCards.slice(0, 3);
4727
+ primaryCards.forEach(function (card) {
3536
4728
  var item = document.createElement("article");
3537
4729
  item.className = "intel-card";
3538
4730
  item.innerHTML = [
@@ -3549,7 +4741,7 @@
3549
4741
  item.querySelector(".intel-highlight").textContent = card.highlight || card.summary || "";
3550
4742
  item.querySelector(".intel-action span").textContent = card.action || "Review this signal before changing related code.";
3551
4743
  var list = item.querySelector("ul");
3552
- card.rows.slice(0, 3).forEach(function (row) {
4744
+ card.rows.slice(0, 2).forEach(function (row) {
3553
4745
  var li = document.createElement("li");
3554
4746
  li.innerHTML = "<strong></strong> <span></span>";
3555
4747
  li.querySelector("strong").textContent = row[0];
@@ -3558,14 +4750,18 @@
3558
4750
  });
3559
4751
  els.intelligenceList.appendChild(item);
3560
4752
  });
3561
- var sections = rankIntelligenceSections(buildIntelligenceSections(reports)).slice(0, 4);
4753
+ var sections = rankIntelligenceSections(buildIntelligenceSections(reports)).slice(0, 2);
3562
4754
  if (sections.length) {
4755
+ var details = document.createElement("details");
4756
+ details.className = "intel-deep-drawer";
4757
+ details.innerHTML = "<summary><span>More repo signals</span><em>cycles, owners, modules</em></summary>";
3563
4758
  var deepGrid = document.createElement("div");
3564
4759
  deepGrid.className = "intel-deep-grid";
3565
4760
  sections.forEach(function (section) {
3566
4761
  deepGrid.appendChild(renderIntelligenceSection(section));
3567
4762
  });
3568
- els.intelligenceList.appendChild(deepGrid);
4763
+ details.appendChild(deepGrid);
4764
+ els.intelligenceList.appendChild(details);
3569
4765
  }
3570
4766
  }
3571
4767
 
@@ -4020,13 +5216,26 @@
4020
5216
  var benchmark = reports.benchmark;
4021
5217
  if (benchmark) {
4022
5218
  var checks = Array.isArray(benchmark.checks) ? benchmark.checks : [];
5219
+ var retrieval = benchmarkRetrievalSummary(benchmark);
5220
+ var scale = benchmarkScaleSummary(benchmark);
4023
5221
  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
- })
5222
+ title: retrieval ? "Retrieval Proof" : "Benchmark",
5223
+ kicker: retrieval ? (retrieval.mode || "retrieval proof") : "local proof",
5224
+ summary: retrieval
5225
+ ? retrieval.label + (scale ? " plus " + scale.packets + "-packet scale proof." : "") + " Evidence retrieval only, not answer accuracy."
5226
+ : benchmark.summary || "Local memory and graph benchmark signals.",
5227
+ rows: retrieval
5228
+ ? [
5229
+ ["R@5", retrieval.r5 != null ? retrieval.r5 + "%" : "n/a"],
5230
+ ["R@10", retrieval.r10 + "%"],
5231
+ ["MRR", retrieval.mrr != null ? String(retrieval.mrr) : "n/a"],
5232
+ ["Context cut", retrieval.contextReduction != null ? retrieval.contextReduction + "%" : "n/a"],
5233
+ ["Scale", scale ? scale.hitRate + "% hit / " + scale.medianLatency + "ms" : "not loaded"],
5234
+ ["Model", retrieval.model || "dependency-free default"]
5235
+ ]
5236
+ : checks.slice(0, 5).map(function (item) {
5237
+ return [item.name || "check", (item.pass ? "pass" : "check") + " - " + item.actual + "/" + item.target];
5238
+ })
4030
5239
  });
4031
5240
  }
4032
5241
  return cards;