@kage-core/kage-graph-mcp 1.1.35 → 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,10 +13,11 @@
13
13
  visibleEntityIds: new Set(),
14
14
  visibleEdgeIds: new Set(),
15
15
  selected: null,
16
+ revealSelection: false,
17
+ viewerPage: "overview",
16
18
  viewerSection: "overview",
17
19
  viewerAction: null,
18
- workspaceTab: "controls",
19
- workspaceOpen: false,
20
+ graphActionFilter: "",
20
21
  pathHighlight: {
21
22
  nodes: new Set(),
22
23
  edges: new Set(),
@@ -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: "",
@@ -140,6 +153,9 @@
140
153
  renderMode: document.getElementById("renderMode"),
141
154
  typeFilter: document.getElementById("typeFilter"),
142
155
  relationFilter: document.getElementById("relationFilter"),
156
+ showUntrusted: document.getElementById("showUntrusted"),
157
+ showUncovered: document.getElementById("showUncovered"),
158
+ showMemoryCode: document.getElementById("showMemoryCode"),
143
159
  scopeFilter: document.getElementById("scopeFilter"),
144
160
  maxNodes: document.getElementById("maxNodes"),
145
161
  showDependencies: document.getElementById("showDependencies"),
@@ -160,27 +176,95 @@
160
176
  entityList: document.getElementById("entityList"),
161
177
  edgeList: document.getElementById("edgeList"),
162
178
  metricsSummary: document.getElementById("metricsSummary"),
179
+ graphInsightStatus: document.getElementById("graphInsightStatus"),
180
+ graphInsights: document.getElementById("graphInsights"),
163
181
  entityCount: document.getElementById("entityCount"),
164
182
  edgeCount: document.getElementById("edgeCount"),
165
183
  reviewCount: document.getElementById("reviewCount"),
166
184
  dashboardStats: document.getElementById("dashboardStats"),
185
+ dashboardCharts: document.getElementById("dashboardCharts"),
186
+ memoryStatus: document.getElementById("memoryStatus"),
187
+ memoryStats: document.getElementById("memoryStats"),
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"),
206
+ memorySearch: document.getElementById("memorySearch"),
207
+ memoryFilter: document.getElementById("memoryFilter"),
208
+ memoryList: document.getElementById("memoryList"),
209
+ ownersStatus: document.getElementById("ownersStatus"),
210
+ ownersSummary: document.getElementById("ownersSummary"),
211
+ ownersList: document.getElementById("ownersList"),
212
+ reviewOverview: document.getElementById("reviewOverview"),
213
+ handoffStatus: document.getElementById("handoffStatus"),
214
+ handoffSummary: document.getElementById("handoffSummary"),
215
+ handoffList: document.getElementById("handoffList"),
167
216
  reviewList: document.getElementById("reviewList"),
217
+ proofOverview: document.getElementById("proofOverview"),
168
218
  proofStatus: document.getElementById("proofStatus"),
169
219
  proofList: document.getElementById("proofList"),
170
220
  intelligenceStatus: document.getElementById("intelligenceStatus"),
171
221
  intelligenceList: document.getElementById("intelligenceList"),
172
- viewerSectionButtons: typeof document.querySelectorAll === "function" ? Array.from(document.querySelectorAll("[data-viewer-section]")) : [],
173
- dashboardActionButtons: typeof document.querySelectorAll === "function" ? Array.from(document.querySelectorAll("[data-dashboard-action]")) : [],
174
- workspaceTabs: typeof document.querySelectorAll === "function" ? Array.from(document.querySelectorAll("[data-workspace-tab]")) : [],
175
- quickViewButtons: typeof document.querySelectorAll === "function" ? Array.from(document.querySelectorAll("[data-quick-view]")) : [],
176
- quickRenderButtons: typeof document.querySelectorAll === "function" ? Array.from(document.querySelectorAll("[data-quick-render]")) : [],
177
- quickSearch: document.getElementById("quickSearch"),
178
- quickPath: document.getElementById("quickPath"),
179
- quickInspector: document.getElementById("quickInspector"),
180
- closeWorkspace: document.getElementById("closeWorkspace")
222
+ debugOverview: document.getElementById("debugOverview"),
223
+ pageEyebrow: document.getElementById("pageEyebrow"),
224
+ pageTitle: document.getElementById("pageTitle"),
225
+ viewerPageLinks: typeof document.querySelectorAll === "function" ? Array.from(document.querySelectorAll("[data-viewer-page]")) : [],
226
+ };
227
+
228
+ var PAGE_META = {
229
+ overview: {
230
+ eyebrow: "kage://overview",
231
+ title: "Repo dashboard",
232
+ summary: "What is safe to change next, what needs attention, and what is ready to hand off."
233
+ },
234
+ graph: {
235
+ eyebrow: "kage://graph",
236
+ title: "Dependency graph",
237
+ summary: "Search a file or symbol, then follow connected memory, routes, and tests before editing."
238
+ },
239
+ memory: {
240
+ eyebrow: "kage://memory",
241
+ title: "Memory library",
242
+ summary: "Find repo lore by file, feature, bug, command, or decision. Pick a packet to see linked code."
243
+ },
244
+ intel: {
245
+ eyebrow: "kage://risks",
246
+ title: "Risks",
247
+ summary: "Files, owners, and modules to inspect before changes. Each card links into the graph."
248
+ },
249
+ review: {
250
+ eyebrow: "kage://review",
251
+ title: "Review & handoff",
252
+ summary: "Blockers that must clear before another agent or teammate picks up this branch."
253
+ },
254
+ owners: {
255
+ eyebrow: "kage://owners",
256
+ title: "Owners & reviewers",
257
+ summary: "Local git ownership signal. Use it for reviewer routing and bus-factor checks."
258
+ },
259
+ data: {
260
+ eyebrow: "kage://artifacts",
261
+ title: "Artifacts & diagnostics",
262
+ summary: "Raw nodes, relations, and indexing health. Use only when graph or recall looks wrong."
263
+ }
181
264
  };
182
265
 
183
266
  var MEMORY_CODE_RELATIONS = new Set(["explains_symbol", "informs_symbol", "fixes_symbol", "applies_to_route", "verified_by_test", "affects_code_path"]);
267
+ var MEMORY_PACKET_TYPES = new Set(["memory", "command", "repo_map", "runbook", "bug_fix", "decision", "rationale", "convention", "workflow", "gotcha", "reference", "policy", "issue_context", "code_explanation", "negative_result", "constraint"]);
184
268
  var INSPECTOR_CONNECTION_LIMIT = 8;
185
269
  var PATH_BRIDGE_EDGE_LIMIT_PER_PATH = 8;
186
270
  var PATH_BRIDGE_EDGE_LIMIT_TOTAL = 160;
@@ -188,82 +272,42 @@
188
272
  var VISIBLE_EDGE_MIN = 160;
189
273
  var VISIBLE_EDGE_MAX = 560;
190
274
 
191
- setViewerSection(state.viewerSection);
192
- setWorkspaceTab(state.workspaceTab, false);
193
- els.viewerSectionButtons.forEach(function (button) {
194
- button.addEventListener("click", function () {
195
- setViewerSection(button.getAttribute("data-viewer-section"));
196
- });
197
- });
198
- els.dashboardActionButtons.forEach(function (button) {
199
- button.addEventListener("click", function () {
200
- openDashboardAction(button.getAttribute("data-dashboard-action"));
201
- });
202
- });
203
- els.workspaceTabs.forEach(function (button) {
204
- button.addEventListener("click", function () {
205
- setWorkspaceTab(button.getAttribute("data-workspace-tab"), true);
275
+ state.viewerPage = initialViewerPage();
276
+ applyViewerPage(state.viewerPage);
277
+ els.viewerPageLinks.forEach(function (link) {
278
+ link.addEventListener("click", function (event) {
279
+ var page = link.getAttribute("data-viewer-page") || "overview";
280
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) return;
281
+ event.preventDefault();
282
+ navigateViewerPage(page);
206
283
  });
207
284
  });
208
- els.quickViewButtons.forEach(function (button) {
209
- button.addEventListener("click", function () {
210
- setViewerSection("graph");
211
- els.viewMode.value = button.getAttribute("data-quick-view") || "combined";
212
- state.lastVisibleSignature = "";
213
- syncQuickControls();
214
- render();
215
- });
216
- });
217
- els.quickRenderButtons.forEach(function (button) {
218
- button.addEventListener("click", function () {
219
- setViewerSection("graph");
220
- els.renderMode.value = button.getAttribute("data-quick-render") || "2d";
221
- state.lastVisibleSignature = "";
222
- syncQuickControls();
223
- render();
224
- });
225
- });
226
- if (els.quickSearch) {
227
- els.quickSearch.addEventListener("click", function () {
228
- setViewerSection("graph");
229
- setWorkspaceTab("controls", true);
230
- els.searchInput.focus();
231
- });
232
- }
233
- if (els.quickPath) {
234
- els.quickPath.addEventListener("click", function () {
235
- setViewerSection("graph");
236
- setWorkspaceTab("controls", true);
237
- els.pathFromInput.focus();
238
- });
239
- }
240
- if (els.quickInspector) {
241
- els.quickInspector.addEventListener("click", function () {
242
- setViewerSection("graph");
243
- setWorkspaceTab("inspector", true);
244
- });
245
- }
246
- if (els.closeWorkspace) els.closeWorkspace.addEventListener("click", closeWorkspace);
247
- syncQuickControls();
248
285
  els.graphFile.addEventListener("change", handleFile);
249
286
  els.searchInput.addEventListener("input", scheduleRender);
250
287
  els.findPath.addEventListener("click", findDependencyPath);
251
288
  els.clearPath.addEventListener("click", clearDependencyPath);
252
289
  els.pathFromInput.addEventListener("keydown", function (event) { if (event.key === "Enter") findDependencyPath(); });
253
290
  els.pathToInput.addEventListener("keydown", function (event) { if (event.key === "Enter") findDependencyPath(); });
254
- els.viewMode.addEventListener("change", render);
255
- els.renderMode.addEventListener("change", function () {
256
- state.lastVisibleSignature = "";
257
- render();
258
- });
259
- els.typeFilter.addEventListener("change", render);
260
- els.relationFilter.addEventListener("change", render);
291
+ els.viewMode.addEventListener("change", function () { clearGraphActionFilter(); render(); });
292
+ if (els.renderMode) {
293
+ els.renderMode.addEventListener("change", function () {
294
+ state.lastVisibleSignature = "";
295
+ render();
296
+ });
297
+ }
298
+ els.typeFilter.addEventListener("change", function () { clearGraphActionFilter(); render(); });
299
+ els.relationFilter.addEventListener("change", function () { clearGraphActionFilter(); render(); });
261
300
  els.scopeFilter.addEventListener("change", render);
262
301
  els.maxNodes.addEventListener("change", render);
263
302
  els.showDependencies.addEventListener("change", render);
303
+ if (els.showUntrusted) els.showUntrusted.addEventListener("click", function () { applyGraphActionFilter("untrusted"); });
304
+ if (els.showUncovered) els.showUncovered.addEventListener("click", function () { applyGraphActionFilter("uncovered"); });
305
+ if (els.showMemoryCode) els.showMemoryCode.addEventListener("click", function () { applyGraphActionFilter("memory-code"); });
264
306
  els.zoomOut.addEventListener("click", function () { zoomGraph(0.82); });
265
307
  els.zoomIn.addEventListener("click", function () { zoomGraph(1.22); });
266
308
  els.fitView.addEventListener("click", fitActiveGraph);
309
+ if (els.memorySearch) els.memorySearch.addEventListener("input", renderMemoryLibrary);
310
+ if (els.memoryFilter) els.memoryFilter.addEventListener("change", renderMemoryLibrary);
267
311
  els.canvas.addEventListener("mousedown", startCanvasPointer);
268
312
  els.canvas.addEventListener("mousemove", moveCanvasPointer);
269
313
  els.canvas.addEventListener("mouseup", endCanvasPointer);
@@ -284,90 +328,172 @@
284
328
  window.addEventListener("resize", function () {
285
329
  resizeActiveGraph();
286
330
  });
287
- els.resetView.addEventListener("click", function () {
331
+ els.resetView.addEventListener("click", resetGraphView);
332
+
333
+ function resetGraphView() {
288
334
  els.searchInput.value = "";
289
335
  els.viewMode.value = "combined";
290
- els.renderMode.value = "2d";
336
+ if (els.renderMode) els.renderMode.value = "2d";
291
337
  els.typeFilter.value = "";
292
338
  els.relationFilter.value = "";
293
339
  els.scopeFilter.value = "signal";
294
340
  els.maxNodes.value = "90";
295
341
  els.showDependencies.checked = false;
296
342
  state.selected = null;
297
- setWorkspaceTab("controls", true);
343
+ state.graphActionFilter = "";
298
344
  clearDependencyPath(false);
299
345
  state.lastVisibleSignature = "";
300
346
  render();
301
- });
347
+ }
302
348
  loadFromUrlParams();
303
349
 
304
- function setWorkspaceTab(tab, open) {
305
- var allowed = new Set(["controls", "inspector", "intelligence", "review", "tables"]);
306
- state.workspaceTab = allowed.has(tab) ? tab : "controls";
307
- if (open !== false) state.workspaceOpen = true;
308
- if (document.body && document.body.classList) {
309
- document.body.classList.remove(
310
- "viewer-tab-controls",
311
- "viewer-tab-inspector",
312
- "viewer-tab-intelligence",
313
- "viewer-tab-review",
314
- "viewer-tab-tables"
315
- );
316
- document.body.classList.add("viewer-tab-" + state.workspaceTab);
317
- document.body.classList.toggle("viewer-workspace-open", state.workspaceOpen);
350
+ function initialViewerPage() {
351
+ var fileName = "";
352
+ try {
353
+ fileName = String(window.location.pathname || "").split("/").pop() || "index.html";
354
+ } catch (_error) {
355
+ fileName = "index.html";
356
+ }
357
+ var pageByFile = {
358
+ "index.html": "overview",
359
+ "": "overview",
360
+ "graph.html": "graph",
361
+ "memory.html": "memory",
362
+ "owners.html": "owners",
363
+ "intel.html": "intel",
364
+ "review.html": "review",
365
+ "data.html": "data"
366
+ };
367
+ return pageByFile[fileName] || "overview";
368
+ }
369
+
370
+ function applyViewerPage(page, updateLinks) {
371
+ var normalized = normalizeViewerPage(page);
372
+ state.viewerPage = normalized;
373
+ if (normalized === "overview") {
374
+ setViewerSection("overview");
375
+ } else if (normalized === "graph") {
376
+ setViewerSection("graph");
377
+ } else {
378
+ setViewerSection("graph", pageToAction(normalized));
379
+ }
380
+ state.viewerPage = normalized;
381
+ applyPageHeader(normalized);
382
+ if (updateLinks !== false) syncViewerPageLinks();
383
+ syncViewerPageClass();
384
+ }
385
+
386
+ function applyPageHeader(page) {
387
+ var meta = PAGE_META[page] || PAGE_META.overview;
388
+ if (els.pageEyebrow) els.pageEyebrow.textContent = meta.eyebrow;
389
+ if (els.pageTitle) els.pageTitle.textContent = meta.title;
390
+ if (els.graphSummary && !state.graph) els.graphSummary.textContent = meta.summary;
391
+ try {
392
+ if (typeof document !== "undefined" && document.title !== undefined) {
393
+ document.title = "Kage " + meta.title.toLowerCase() + " viewer";
394
+ }
395
+ } catch (_error) {
396
+ // ignore
397
+ }
398
+ }
399
+
400
+ function normalizeViewerPage(page) {
401
+ var normalized = String(page || "overview").toLowerCase();
402
+ if (normalized === "intelligence") normalized = "intel";
403
+ if (["overview", "graph", "memory", "owners", "intel", "review", "data"].indexOf(normalized) === -1) return "overview";
404
+ return normalized;
405
+ }
406
+
407
+ function pageToAction(page) {
408
+ if (page === "intel") return "intelligence";
409
+ if (page === "owners") return "intelligence";
410
+ if (page === "memory") return "memory";
411
+ if (page === "review") return "review";
412
+ if (page === "data") return "data";
413
+ return null;
414
+ }
415
+
416
+ function pageFromSection(section, action) {
417
+ if (section === "overview") return "overview";
418
+ if (action === "intelligence") return "intel";
419
+ if (action === "memory") return "memory";
420
+ if (action === "review") return "review";
421
+ if (action === "data") return "data";
422
+ return "graph";
423
+ }
424
+
425
+ function viewerPageHref(page) {
426
+ var fileByPage = {
427
+ overview: "./",
428
+ graph: "./graph.html",
429
+ memory: "./memory.html",
430
+ owners: "./owners.html",
431
+ intel: "./intel.html",
432
+ review: "./review.html",
433
+ data: "./data.html"
434
+ };
435
+ var search = "";
436
+ try {
437
+ search = window.location.search || "";
438
+ } catch (_error) {
439
+ search = "";
440
+ }
441
+ return (fileByPage[normalizeViewerPage(page)] || "./") + search;
442
+ }
443
+
444
+ function navigateViewerPage(page) {
445
+ window.location.href = viewerPageHref(page);
446
+ }
447
+
448
+ function showViewerPageInPlace(page) {
449
+ applyViewerPage(page);
450
+ try {
451
+ window.history.pushState({}, "", viewerPageHref(page));
452
+ } catch (_error) {
453
+ // Static/file viewers can ignore history failures; visual state is enough.
318
454
  }
319
- els.workspaceTabs.forEach(function (button) {
320
- var active = button.getAttribute("data-workspace-tab") === state.workspaceTab;
321
- button.classList.toggle("active", active);
322
- button.setAttribute("aria-selected", active ? "true" : "false");
455
+ }
456
+
457
+ function syncViewerPageLinks() {
458
+ els.viewerPageLinks.forEach(function (link) {
459
+ var page = normalizeViewerPage(link.getAttribute("data-viewer-page"));
460
+ link.setAttribute("href", viewerPageHref(page));
461
+ var active = page === state.viewerPage;
462
+ link.classList.toggle("active", active);
463
+ if (active) link.setAttribute("aria-current", "page");
464
+ else link.removeAttribute("aria-current");
323
465
  });
324
466
  }
325
467
 
468
+ function syncViewerPageClass() {
469
+ if (!document.body || !document.body.classList) return;
470
+ document.body.classList.remove(
471
+ "viewer-page-overview",
472
+ "viewer-page-graph",
473
+ "viewer-page-memory",
474
+ "viewer-page-owners",
475
+ "viewer-page-intel",
476
+ "viewer-page-review",
477
+ "viewer-page-data"
478
+ );
479
+ document.body.classList.add("viewer-page-" + normalizeViewerPage(state.viewerPage));
480
+ }
481
+
326
482
  function setViewerSection(section, action) {
327
483
  state.viewerSection = section === "graph" ? "graph" : "overview";
328
484
  state.viewerAction = action || null;
485
+ state.viewerPage = pageFromSection(state.viewerSection, state.viewerAction);
329
486
  if (state.viewerSection === "overview") closeWorkspace();
330
487
  if (document.body && document.body.classList) {
331
488
  document.body.classList.remove("viewer-section-overview", "viewer-section-graph");
332
489
  document.body.classList.add("viewer-section-" + state.viewerSection);
333
490
  }
334
- syncSectionControls();
491
+ syncViewerPageLinks();
492
+ syncViewerPageClass();
335
493
  if (state.viewerSection === "graph") resizeActiveGraph();
336
494
  }
337
495
 
338
- function openDashboardAction(action) {
339
- var normalized = String(action || "").toLowerCase();
340
- var tabByAction = {
341
- memory: "review",
342
- intelligence: "intelligence",
343
- review: "review",
344
- data: "tables"
345
- };
346
- setViewerSection("graph", normalized);
347
- setWorkspaceTab(tabByAction[normalized] || "controls", true);
348
- }
349
-
350
- function syncSectionControls() {
351
- els.viewerSectionButtons.forEach(function (button) {
352
- var section = button.getAttribute("data-viewer-section");
353
- var active = state.viewerSection === section && !state.viewerAction;
354
- if (button.classList && button.classList.contains("viewer-section")) {
355
- button.classList.toggle("active", active);
356
- button.setAttribute("aria-selected", active ? "true" : "false");
357
- }
358
- });
359
- els.dashboardActionButtons.forEach(function (button) {
360
- var action = button.getAttribute("data-dashboard-action");
361
- var active = state.viewerSection === "graph" && state.viewerAction === action;
362
- if (button.classList && button.classList.contains("viewer-section")) {
363
- button.classList.toggle("active", active);
364
- button.setAttribute("aria-selected", active ? "true" : "false");
365
- }
366
- });
367
- }
368
-
369
496
  function closeWorkspace() {
370
- state.workspaceOpen = false;
371
497
  if (document.body && document.body.classList) {
372
498
  document.body.classList.remove("viewer-workspace-open");
373
499
  }
@@ -375,27 +501,12 @@
375
501
 
376
502
  function selectEntity(id, openInspector) {
377
503
  state.selected = { kind: "entity", id: id };
378
- setViewerSection("graph");
379
- if (openInspector) setWorkspaceTab("inspector", true);
504
+ state.revealSelection = Boolean(openInspector);
380
505
  }
381
506
 
382
507
  function selectEdge(id, openInspector) {
383
508
  state.selected = { kind: "edge", id: id };
384
- setViewerSection("graph");
385
- if (openInspector) setWorkspaceTab("inspector", true);
386
- }
387
-
388
- function syncQuickControls() {
389
- els.quickViewButtons.forEach(function (button) {
390
- var active = button.getAttribute("data-quick-view") === els.viewMode.value;
391
- button.classList.toggle("active", active);
392
- button.setAttribute("aria-pressed", active ? "true" : "false");
393
- });
394
- els.quickRenderButtons.forEach(function (button) {
395
- var active = button.getAttribute("data-quick-render") === els.renderMode.value;
396
- button.classList.toggle("active", active);
397
- button.setAttribute("aria-pressed", active ? "true" : "false");
398
- });
509
+ state.revealSelection = Boolean(openInspector);
399
510
  }
400
511
 
401
512
  function handleFile(event) {
@@ -411,7 +522,8 @@
411
522
  state.entityById = new Map();
412
523
  state.episodesById = new Map();
413
524
  els.emptyState.classList.add("hidden");
414
- els.graphSummary.textContent = "Metrics loaded.";
525
+ var pageMeta = PAGE_META[state.viewerPage] || PAGE_META.overview;
526
+ els.graphSummary.textContent = state.viewerPage === "graph" ? "Metrics loaded." : pageMeta.summary;
415
527
  renderMetrics();
416
528
  return;
417
529
  }
@@ -452,7 +564,10 @@
452
564
  populateFilters();
453
565
  populatePathOptions();
454
566
  els.emptyState.classList.add("hidden");
455
- els.graphSummary.textContent = fileName + " loaded: " + entities.length + " nodes, " + edges.length + " relations.";
567
+ var meta = PAGE_META[state.viewerPage] || PAGE_META.overview;
568
+ els.graphSummary.textContent = state.viewerPage === "graph"
569
+ ? fileName + " loaded: " + entities.length + " nodes, " + edges.length + " relations."
570
+ : meta.summary;
456
571
  render();
457
572
  }
458
573
 
@@ -484,11 +599,23 @@
484
599
  var qualityPath = params.get("quality");
485
600
  var benchmarkPath = params.get("benchmark");
486
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");
487
605
  var decisionsPath = params.get("decisions");
488
606
  var riskPath = params.get("risk");
489
607
  var moduleHealthPath = params.get("moduleHealth") || params.get("module-health");
490
608
  var graphInsightsPath = params.get("graphInsights") || params.get("graph-insights");
491
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");
492
619
  var inferredRoot = inferMemoryRoot(graphPaths[0] || "");
493
620
  if (!inboxPath && inferredRoot) inboxPath = inferredRoot + "/inbox.json";
494
621
  if (!reviewPath && inferredRoot) reviewPath = inferredRoot + "/review/memory-review.md";
@@ -497,11 +624,23 @@
497
624
  if (!qualityPath) qualityPath = inferredRoot + "/reports/quality.json";
498
625
  if (!benchmarkPath) benchmarkPath = inferredRoot + "/reports/benchmark.json";
499
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";
500
630
  if (!decisionsPath) decisionsPath = inferredRoot + "/reports/decisions.json";
501
631
  if (!riskPath) riskPath = inferredRoot + "/reports/risk.json";
502
632
  if (!moduleHealthPath) moduleHealthPath = inferredRoot + "/reports/module-health.json";
503
633
  if (!graphInsightsPath) graphInsightsPath = inferredRoot + "/reports/graph-insights.json";
504
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";
505
644
  }
506
645
  var jobs = [];
507
646
  if (metricsPath) jobs.push(fetchJson(metricsPath).then(function (metrics) { state.metrics = metrics; }));
@@ -511,11 +650,23 @@
511
650
  if (qualityPath) jobs.push(fetchJson(qualityPath).then(function (report) { state.reports.quality = report; }).catch(function () { state.reports.quality = null; }));
512
651
  if (benchmarkPath) jobs.push(fetchJson(benchmarkPath).then(function (report) { state.reports.benchmark = report; }).catch(function () { state.reports.benchmark = null; }));
513
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; }));
514
656
  if (decisionsPath) jobs.push(fetchJson(decisionsPath).then(function (report) { state.reports.decisions = report; }).catch(function () { state.reports.decisions = null; }));
515
657
  if (riskPath) jobs.push(fetchJson(riskPath).then(function (report) { state.reports.risk = report; }).catch(function () { state.reports.risk = null; }));
516
658
  if (moduleHealthPath) jobs.push(fetchJson(moduleHealthPath).then(function (report) { state.reports.moduleHealth = report; }).catch(function () { state.reports.moduleHealth = null; }));
517
659
  if (graphInsightsPath) jobs.push(fetchJson(graphInsightsPath).then(function (report) { state.reports.graphInsights = report; }).catch(function () { state.reports.graphInsights = null; }));
518
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; }));
519
670
  if (!graphPaths.length && !jobs.length) {
520
671
  loadHostedDefault();
521
672
  return;
@@ -548,20 +699,44 @@
548
699
  fetchJson("./data/kage/inbox.json").catch(function () { return null; }),
549
700
  fetchJson("./data/kage/reports/risk.json").catch(function () { return null; }),
550
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; }),
551
705
  fetchJson("./data/kage/reports/decisions.json").catch(function () { return null; }),
552
706
  fetchJson("./data/kage/reports/module-health.json").catch(function () { return null; }),
553
707
  fetchJson("./data/kage/reports/graph-insights.json").catch(function () { return null; }),
554
- 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; })
555
718
  ]).then(function (items) {
556
719
  var merged = mergeNormalizedGraphs([normalizeGraph(items[0]), normalizeGraph(items[1])]);
557
720
  state.metrics = items[2];
558
721
  state.inbox = items[3];
559
722
  state.reports.risk = items[4];
560
723
  state.reports.contributors = items[5];
561
- state.reports.decisions = items[6];
562
- state.reports.moduleHealth = items[7];
563
- state.reports.graphInsights = items[8];
564
- 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];
565
740
  loadNormalizedGraph(merged, "Kage repo graph");
566
741
  setAutoLoad("Kage repo graph loaded", true);
567
742
  }).catch(function () {
@@ -1246,7 +1421,6 @@
1246
1421
 
1247
1422
  function render() {
1248
1423
  if (!state.graph) return;
1249
- syncQuickControls();
1250
1424
 
1251
1425
  var query = parseSearchQuery(els.searchInput.value);
1252
1426
  state.renderQuery = query;
@@ -1283,6 +1457,10 @@
1283
1457
  matchedEdgeIds = new Set(state.edges.filter(function (edge) { return mode === "combined" || edge.graph_kind === mode; }).map(function (edge) { return edge.id; }));
1284
1458
  }
1285
1459
 
1460
+ var actionFiltered = applyMatchedGraphActionFilter(matchedEntityIds, matchedEdgeIds);
1461
+ matchedEntityIds = actionFiltered.entities;
1462
+ matchedEdgeIds = actionFiltered.edges;
1463
+
1286
1464
  var visible = refineVisibleGraph(matchedEntityIds, matchedEdgeIds, {
1287
1465
  query: query,
1288
1466
  type: type,
@@ -1301,12 +1479,8 @@
1301
1479
  state.lastVisibleSignature = nextSignature;
1302
1480
 
1303
1481
  renderActiveGraph(graphChanged);
1304
- renderLists();
1305
- renderDetails();
1306
- renderMetrics();
1307
- renderReviewQueue();
1308
- renderProof();
1309
- renderIntelligence();
1482
+ renderPagePanels();
1483
+ revealSelectionIfRequested();
1310
1484
  }
1311
1485
 
1312
1486
  function scheduleRender() {
@@ -1317,6 +1491,88 @@
1317
1491
  });
1318
1492
  }
1319
1493
 
1494
+ function renderPagePanels() {
1495
+ if (state.viewerPage === "graph") {
1496
+ renderDetails();
1497
+ renderMetrics();
1498
+ return;
1499
+ }
1500
+ if (state.viewerPage === "memory") {
1501
+ renderDetails();
1502
+ renderMemoryLibrary();
1503
+ return;
1504
+ }
1505
+ if (state.viewerPage === "owners") {
1506
+ renderOwners();
1507
+ return;
1508
+ }
1509
+ if (state.viewerPage === "intel") {
1510
+ renderIntelligence();
1511
+ return;
1512
+ }
1513
+ if (state.viewerPage === "review") {
1514
+ renderReviewQueue();
1515
+ renderProof();
1516
+ return;
1517
+ }
1518
+ if (state.viewerPage === "data") {
1519
+ renderDetails();
1520
+ renderArtifactDiagnostics(state.entities, state.edges);
1521
+ renderLists();
1522
+ return;
1523
+ }
1524
+ renderDashboard();
1525
+ }
1526
+
1527
+ function applyGraphActionFilter(filter) {
1528
+ state.graphActionFilter = state.graphActionFilter === filter ? "" : filter;
1529
+ if (filter === "memory-code") {
1530
+ els.viewMode.value = "combined";
1531
+ els.relationFilter.value = "__memory_code__";
1532
+ } else if (state.graphActionFilter) {
1533
+ els.viewMode.value = "combined";
1534
+ els.relationFilter.value = "";
1535
+ }
1536
+ state.lastVisibleSignature = "";
1537
+ render();
1538
+ }
1539
+
1540
+ function clearGraphActionFilter() {
1541
+ state.graphActionFilter = "";
1542
+ }
1543
+
1544
+ function applyMatchedGraphActionFilter(entityIds, edgeIds) {
1545
+ if (!state.graphActionFilter) return { entities: entityIds, edges: edgeIds };
1546
+ if (state.graphActionFilter === "memory-code") {
1547
+ var memoryCodeEdges = state.edges.filter(isMemoryCodeEdge);
1548
+ return entitiesForEdges(memoryCodeEdges);
1549
+ }
1550
+ if (state.graphActionFilter === "untrusted") {
1551
+ var flagged = state.edges.filter(function (edge) { return reviewStatus(edge) !== "ok"; });
1552
+ return entitiesForEdges(flagged);
1553
+ }
1554
+ if (state.graphActionFilter === "uncovered") {
1555
+ var covered = memoryLinkedCodeKeys();
1556
+ var uncovered = state.entities.filter(function (entity) {
1557
+ return entity.graph_kind === "code" && entity.type === "file" && !covered.has(codeCoverageKey(entity));
1558
+ });
1559
+ var entities = new Set(uncovered.map(function (entity) { return entity.id; }));
1560
+ return { entities: entities, edges: edgesWithVisibleEndpoints(new Set(state.edges.map(function (edge) { return edge.id; })), entities) };
1561
+ }
1562
+ return { entities: entityIds, edges: edgeIds };
1563
+ }
1564
+
1565
+ function entitiesForEdges(edges) {
1566
+ var entities = new Set();
1567
+ var edgeIds = new Set();
1568
+ edges.forEach(function (edge) {
1569
+ edgeIds.add(edge.id);
1570
+ if (state.entityById.has(edge.from)) entities.add(edge.from);
1571
+ if (state.entityById.has(edge.to)) entities.add(edge.to);
1572
+ });
1573
+ return { entities: entities, edges: edgeIds };
1574
+ }
1575
+
1320
1576
  function refineVisibleGraph(entityIds, edgeIds, options) {
1321
1577
  var entities = new Set(entityIds);
1322
1578
  var edges = new Set(edgeIds);
@@ -1828,8 +2084,8 @@
1828
2084
  ctx.fillStyle = graphPalette.background;
1829
2085
  ctx.fillRect(0, 0, width, height);
1830
2086
  var gradient = ctx.createRadialGradient(width * 0.52, height * 0.44, 40, width * 0.52, height * 0.44, Math.max(width, height) * 0.72);
1831
- gradient.addColorStop(0, "rgba(65,255,143,0.080)");
1832
- gradient.addColorStop(0.48, "rgba(65,255,143,0.018)");
2087
+ gradient.addColorStop(0, "rgba(65,255,143,0.145)");
2088
+ gradient.addColorStop(0.48, "rgba(65,255,143,0.030)");
1833
2089
  gradient.addColorStop(1, "rgba(2,5,3,0)");
1834
2090
  ctx.fillStyle = gradient;
1835
2091
  ctx.fillRect(0, 0, width, height);
@@ -1905,9 +2161,9 @@
1905
2161
  var color = nodeThemeColor(entity);
1906
2162
  ctx.save();
1907
2163
  ctx.globalAlpha = alpha;
1908
- if (selected || hovered || pathNode) {
2164
+ if (selected || hovered || pathNode || entity.graph_kind === "memory") {
1909
2165
  ctx.shadowColor = pathNode ? graphPalette.bridge : color;
1910
- ctx.shadowBlur = selected ? 14 : pathNode ? 12 : 10;
2166
+ ctx.shadowBlur = selected ? 16 : pathNode ? 14 : entity.graph_kind === "memory" ? 9 : 10;
1911
2167
  }
1912
2168
  drawNodeShape(ctx, node.x, node.y, node.r, entity);
1913
2169
  ctx.fillStyle = nodeFillColor(entity);
@@ -2150,12 +2406,16 @@
2150
2406
  }
2151
2407
 
2152
2408
  function renderLists() {
2153
- var visibleEntities = state.entities.filter(function (entity) {
2154
- return state.visibleEntityIds.has(entity.id);
2155
- });
2156
- var visibleEdges = state.edges.filter(function (edge) {
2157
- return state.visibleEdgeIds.has(edge.id);
2158
- }).sort(function (a, b) {
2409
+ var visibleEntities = state.viewerPage === "data"
2410
+ ? state.entities.slice()
2411
+ : state.entities.filter(function (entity) {
2412
+ return state.visibleEntityIds.has(entity.id);
2413
+ });
2414
+ var visibleEdges = (state.viewerPage === "data"
2415
+ ? state.edges.slice()
2416
+ : state.edges.filter(function (edge) {
2417
+ return state.visibleEdgeIds.has(edge.id);
2418
+ })).sort(function (a, b) {
2159
2419
  return reviewRank(a) - reviewRank(b);
2160
2420
  });
2161
2421
 
@@ -2164,7 +2424,11 @@
2164
2424
  els.entityList.textContent = "";
2165
2425
  els.edgeList.textContent = "";
2166
2426
 
2167
- visibleEntities.forEach(function (entity) {
2427
+ var rowLimit = state.viewerPage === "data" ? 40 : 80;
2428
+ var entityRows = visibleEntities.slice(0, rowLimit);
2429
+ var edgeRows = visibleEdges.slice(0, rowLimit);
2430
+
2431
+ entityRows.forEach(function (entity) {
2168
2432
  var button = document.createElement("button");
2169
2433
  button.type = "button";
2170
2434
  button.className = classNames("list-item", state.selected && state.selected.kind === "entity" && state.selected.id === entity.id && "selected");
@@ -2177,8 +2441,11 @@
2177
2441
  });
2178
2442
  els.entityList.appendChild(button);
2179
2443
  });
2444
+ if (visibleEntities.length > entityRows.length) {
2445
+ appendListNote(els.entityList, "Showing " + entityRows.length + " of " + visibleEntities.length + ". Use search to narrow.");
2446
+ }
2180
2447
 
2181
- visibleEdges.forEach(function (edge) {
2448
+ edgeRows.forEach(function (edge) {
2182
2449
  var button = document.createElement("button");
2183
2450
  button.type = "button";
2184
2451
  button.className = classNames("list-item", state.selected && state.selected.kind === "edge" && state.selected.id === edge.id && "selected");
@@ -2191,13 +2458,25 @@
2191
2458
  });
2192
2459
  els.edgeList.appendChild(button);
2193
2460
  });
2461
+ if (visibleEdges.length > edgeRows.length) {
2462
+ appendListNote(els.edgeList, "Showing " + edgeRows.length + " of " + visibleEdges.length + ". Filter by relation or select a node first.");
2463
+ }
2464
+ }
2465
+
2466
+ function appendListNote(parent, text) {
2467
+ var note = document.createElement("div");
2468
+ note.className = "list-note";
2469
+ note.textContent = text;
2470
+ parent.appendChild(note);
2194
2471
  }
2195
2472
 
2196
2473
  function renderDetails() {
2197
2474
  if (!state.selected) {
2475
+ setSelectionBodyState(null);
2198
2476
  els.selectionDetails.className = "details-empty";
2199
- els.selectionDetails.textContent = "Select an entity or edge.";
2477
+ els.selectionDetails.textContent = "Select a node or relation to see what it means, why it exists, and which connected memory or code to inspect next.";
2200
2478
  els.selectionStatus.textContent = "No selection";
2479
+ if (!state.pathHighlight.steps.length) setPathStatus("Select a code node, then trace to another code node when you need impact proof.", "");
2201
2480
  return;
2202
2481
  }
2203
2482
 
@@ -2206,10 +2485,12 @@
2206
2485
  : state.edges.find(function (edge) { return edge.id === state.selected.id; });
2207
2486
 
2208
2487
  if (!item) {
2488
+ setSelectionBodyState(null);
2209
2489
  els.selectionDetails.textContent = "Selection no longer exists.";
2210
2490
  return;
2211
2491
  }
2212
2492
 
2493
+ setSelectionBodyState(state.selected.kind === "entity" ? item : null);
2213
2494
  els.selectionDetails.className = "";
2214
2495
  els.selectionDetails.textContent = "";
2215
2496
  var title = document.createElement("div");
@@ -2246,6 +2527,44 @@
2246
2527
  els.selectionDetails.appendChild(kind);
2247
2528
  els.selectionDetails.appendChild(rows);
2248
2529
  renderInspectorConnections(item);
2530
+ if (state.selected.kind === "entity" && item.graph_kind === "code") prefillPathFromSelection(true);
2531
+ }
2532
+
2533
+ function setSelectionBodyState(entity) {
2534
+ if (!document.body || !document.body.classList) return;
2535
+ document.body.classList.toggle("has-selection", Boolean(state.selected));
2536
+ document.body.classList.toggle("has-code-selection", Boolean(entity && entity.graph_kind === "code"));
2537
+ }
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
+
2551
+ function prefillPathFromSelection(silent) {
2552
+ if (!state.selected || state.selected.kind !== "entity") {
2553
+ if (!silent) setPathStatus("Select a code node first. Path tracing is for files, symbols, routes, tests, and scripts.", "warn");
2554
+ return;
2555
+ }
2556
+ var entity = state.entityById.get(state.selected.id);
2557
+ if (!entity || entity.graph_kind !== "code" || ["file", "symbol", "route", "test", "script"].indexOf(entity.type) === -1) {
2558
+ if (!silent) setPathStatus("Select a code node first. Memory nodes are shown through memory-code links, not code path tracing.", "warn");
2559
+ return;
2560
+ }
2561
+ if (!els.pathFromInput.value || silent) {
2562
+ els.pathFromInput.value = entity.path || displayName(entity) || entity.id;
2563
+ }
2564
+ if (!silent) {
2565
+ els.pathToInput.focus();
2566
+ setPathStatus("Selected " + displayName(entity) + ". Pick a target test, route, file, or symbol to trace impact.", "ok");
2567
+ }
2249
2568
  }
2250
2569
 
2251
2570
  function entityDetailRows(entity) {
@@ -2373,6 +2692,23 @@
2373
2692
  return Boolean(edge && (edge.memory_code_link || isMemoryCodeRelation(edge.relation)));
2374
2693
  }
2375
2694
 
2695
+ function codeCoverageKey(entity) {
2696
+ if (!entity) return "";
2697
+ return String(entity.path || entity.id || "").replace(/\\/g, "/").replace(/^\.\//, "");
2698
+ }
2699
+
2700
+ function memoryLinkedCodeKeys() {
2701
+ var keys = new Set();
2702
+ state.edges.filter(isMemoryCodeEdge).forEach(function (edge) {
2703
+ [state.entityById.get(edge.from), state.entityById.get(edge.to)].forEach(function (entity) {
2704
+ if (!entity || entity.graph_kind !== "code") return;
2705
+ var key = codeCoverageKey(entity);
2706
+ if (key) keys.add(key);
2707
+ });
2708
+ });
2709
+ return keys;
2710
+ }
2711
+
2376
2712
  function connectionImportance(link) {
2377
2713
  var relation = String(link.edge.relation || "");
2378
2714
  var score = entityImportance(link.other);
@@ -2475,13 +2811,15 @@
2475
2811
  }).length;
2476
2812
  var evidenceEdges = visibleEdges.filter(function (edge) { return Array.isArray(edge.evidence) && edge.evidence.length > 0; }).length;
2477
2813
  var official = state.metrics;
2814
+ var memoryCodeLinks = state.edges.filter(isMemoryCodeEdge).length;
2815
+ var pendingReview = official && official.memory_graph ? Number(firstNumber(official.memory_graph.pending_packets, 0)) : 0;
2478
2816
  var metrics = official ? [
2479
- ["Readiness", official.harness.readiness_score + "/100"],
2480
- ["Code Files", official.code_graph.files],
2481
- ["Structural Symbols", official.structural_index ? official.structural_index.symbols : official.code_graph.symbols],
2482
- ["Parser Coverage", official.code_graph.indexer_coverage_percent + "%"],
2483
- ["Cache Hits", official.structural_index ? official.structural_index.cache_hits : official.code_graph.cache_hits],
2484
- ["Tokens Saved", official.savings ? official.savings.estimated_tokens_saved_per_recall : "n/a"]
2817
+ ["Validation", official.harness && official.harness.validation_ok ? "Clean" : "Check"],
2818
+ ["Review queue", pendingReview ? pendingReview + " pending" : "Clear"],
2819
+ ["Reusable memory", official.memory_graph ? official.memory_graph.approved_packets + " packets" : "n/a"],
2820
+ ["Code indexed", official.structural_index ? official.structural_index.files + " files" : official.code_graph.files + " files"],
2821
+ ["Parser coverage", official.code_graph.indexer_coverage_percent + "%"],
2822
+ ["Memory-code links", memoryCodeLinks]
2485
2823
  ] : [
2486
2824
  ["Nodes", visibleEntities.length + "/" + state.entities.length],
2487
2825
  ["Relations", visibleEdges.length + "/" + state.edges.length],
@@ -2503,7 +2841,136 @@
2503
2841
  els.workspaceMode.textContent = (els.viewMode.value || "combined").replace(/^./, function (letter) { return letter.toUpperCase(); });
2504
2842
  els.graphSubhead.textContent = visibleEntities.length + " visible nodes and " + visibleEdges.length + " visible relations" +
2505
2843
  (hiddenDependencies && !els.showDependencies.checked ? " (" + hiddenDependencies + " dependency/noise nodes hidden)." : ".");
2506
- renderDashboard();
2844
+ renderGraphInsights(visibleEntities, visibleEdges, hiddenDependencies);
2845
+ updateGraphActionButtons();
2846
+ }
2847
+
2848
+ function renderGraphInsights(visibleEntities, visibleEdges, hiddenDependencies) {
2849
+ if (!els.graphInsights) return;
2850
+ els.graphInsights.textContent = "";
2851
+ var allMemoryCode = state.edges.filter(isMemoryCodeEdge);
2852
+ var visibleMemoryCode = visibleEdges.filter(isMemoryCodeEdge);
2853
+ var reviewFlags = visibleEdges.filter(function (edge) { return reviewStatus(edge) !== "ok"; });
2854
+ var visibleCodeFiles = visibleEntities.filter(function (entity) { return entity.graph_kind === "code" && entity.type === "file"; });
2855
+ var coveredKeys = memoryLinkedCodeKeys();
2856
+ var uncoveredCodeFiles = visibleCodeFiles.filter(function (entity) { return !coveredKeys.has(codeCoverageKey(entity)); });
2857
+ var evidenceEdges = visibleEdges.filter(function (edge) { return Array.isArray(edge.evidence) && edge.evidence.length; }).length;
2858
+ var evidencePercent = visibleEdges.length ? Math.round(evidenceEdges / visibleEdges.length * 100) : 0;
2859
+ var coveragePercent = visibleCodeFiles.length ? Math.round((visibleCodeFiles.length - uncoveredCodeFiles.length) / visibleCodeFiles.length * 100) : 0;
2860
+ var queryActive = parseSearchQuery(els.searchInput.value).active;
2861
+ var hasActiveFilters = queryActive || state.graphActionFilter || els.viewMode.value !== "combined" || els.typeFilter.value || els.relationFilter.value || els.showDependencies.checked;
2862
+ if (!visibleEntities.length || hasActiveFilters) {
2863
+ var recovery = document.createElement("article");
2864
+ recovery.className = classNames("metric-card graph-action-card graph-recovery-card", !visibleEntities.length && "metric-card-warn");
2865
+ recovery.innerHTML = [
2866
+ "<div class=\"metric-card-head\"><span></span><strong></strong></div>",
2867
+ "<p></p>",
2868
+ "<button type=\"button\">Clear search/filter</button>",
2869
+ "<em></em>"
2870
+ ].join("");
2871
+ recovery.querySelector(".metric-card-head span").textContent = !visibleEntities.length ? "No graph results" : "Active graph filter";
2872
+ recovery.querySelector(".metric-card-head strong").textContent = !visibleEntities.length ? "0 visible" : "filtered";
2873
+ recovery.querySelector("p").textContent = !visibleEntities.length
2874
+ ? "The current search or filter hides every node. Clear it to recover the graph."
2875
+ : "A search, relation, or journey filter is active.";
2876
+ recovery.querySelector("button").addEventListener("click", resetGraphView);
2877
+ recovery.querySelector("em").textContent = queryActive ? "Search: " + els.searchInput.value : (state.graphActionFilter || "custom filter");
2878
+ els.graphInsights.appendChild(recovery);
2879
+ }
2880
+ var cards = [
2881
+ graphActionCard("Memory coverage", coveragePercent + "%", uncoveredCodeFiles.length
2882
+ ? uncoveredCodeFiles.length + " visible code file(s) have no linked repo memory."
2883
+ : "Visible code files have linked repo memory.",
2884
+ "Show uncovered code", "uncovered", uncoveredCodeFiles.length ? "warn" : "ok"),
2885
+ graphActionCard("Untrusted edges", reviewFlags.length ? reviewFlags.length + " flagged" : "clear", reviewFlags.length
2886
+ ? "Low-confidence, missing-evidence, or invalidated relations are visible."
2887
+ : "Visible relations are evidence-backed enough for inspection.",
2888
+ "Filter to untrusted", "untrusted", reviewFlags.length ? "warn" : "ok", [
2889
+ { label: "Low confidence", value: reviewFlags.filter(function (edge) { return reviewStatus(edge) === "low confidence"; }).length, score: Math.min(100, reviewFlags.length * 20), status: reviewFlags.length ? "warn" : "ok" },
2890
+ { label: "Missing evidence", value: reviewFlags.filter(function (edge) { return reviewStatus(edge) === "missing evidence"; }).length, score: Math.min(100, reviewFlags.length * 20), status: reviewFlags.length ? "warn" : "ok" },
2891
+ { label: "Invalidated", value: reviewFlags.filter(function (edge) { return reviewStatus(edge) === "invalidated"; }).length, score: Math.min(100, reviewFlags.length * 20), status: reviewFlags.length ? "danger" : "ok" }
2892
+ ]),
2893
+ graphActionCard("Evidence in view", evidencePercent + "%", evidenceEdges + " of " + visibleEdges.length + " visible relation(s) carry evidence.",
2894
+ "Show memory-code links", "memory-code", evidencePercent >= 80 ? "ok" : "warn"),
2895
+ graphActionCard("Trace impact", visibleMemoryCode.length + " links", "Select a code node, then trace to a test, route, or symbol from the Inspector.",
2896
+ "Use selected node", "path", visibleMemoryCode.length ? "ok" : "warn")
2897
+ ];
2898
+ cards.forEach(function (card) { els.graphInsights.appendChild(card); });
2899
+ if (els.graphInsightStatus) els.graphInsightStatus.textContent = state.graphActionFilter || (hiddenDependencies ? hiddenDependencies + " external hidden" : "ready");
2900
+ }
2901
+
2902
+ function graphActionCard(title, value, detail, actionLabel, action, status, rows) {
2903
+ var card = document.createElement("article");
2904
+ card.className = classNames("metric-card graph-action-card", status && "metric-card-" + status, state.graphActionFilter === action && "active");
2905
+ card.innerHTML = [
2906
+ "<div class=\"metric-card-head\"><span></span><strong></strong></div>",
2907
+ "<p></p>",
2908
+ "<div class=\"metric-bars\"></div>",
2909
+ "<button type=\"button\"></button>",
2910
+ "<em></em>"
2911
+ ].join("");
2912
+ card.querySelector(".metric-card-head span").textContent = title;
2913
+ card.querySelector(".metric-card-head strong").textContent = value;
2914
+ card.querySelector("p").textContent = detail;
2915
+ var bars = card.querySelector(".metric-bars");
2916
+ if (Array.isArray(rows) && rows.length) {
2917
+ rows.forEach(function (row) {
2918
+ var item = document.createElement("div");
2919
+ item.className = classNames("metric-bar", row.status && "metric-bar-" + row.status);
2920
+ item.innerHTML = "<span></span><strong></strong><i></i>";
2921
+ item.querySelector("span").textContent = row.label;
2922
+ item.querySelector("strong").textContent = formatDashboardValue(row.value);
2923
+ item.querySelector("i").style.width = clamp(Number(row.score || 0), row.value ? 8 : 0, 100) + "%";
2924
+ bars.appendChild(item);
2925
+ });
2926
+ } else {
2927
+ bars.remove();
2928
+ }
2929
+ var button = card.querySelector("button");
2930
+ button.textContent = actionLabel;
2931
+ button.addEventListener("click", function () {
2932
+ if (action === "path") {
2933
+ prefillPathFromSelection();
2934
+ return;
2935
+ }
2936
+ applyGraphActionFilter(action);
2937
+ });
2938
+ card.querySelector("em").textContent = state.graphActionFilter === action ? "Active filter" : "";
2939
+ return card;
2940
+ }
2941
+
2942
+ function updateGraphActionButtons() {
2943
+ [
2944
+ [els.showUntrusted, "untrusted"],
2945
+ [els.showUncovered, "uncovered"],
2946
+ [els.showMemoryCode, "memory-code"]
2947
+ ].forEach(function (entry) {
2948
+ if (!entry[0]) return;
2949
+ entry[0].classList.toggle("active", state.graphActionFilter === entry[1]);
2950
+ });
2951
+ }
2952
+
2953
+ function renderArtifactDiagnostics(visibleEntities, visibleEdges) {
2954
+ if (!els.debugOverview) return;
2955
+ els.debugOverview.textContent = "";
2956
+ var episodes = state.episodesById ? state.episodesById.size : 0;
2957
+ var evidenceEdges = state.edges.filter(function (edge) { return Array.isArray(edge.evidence) && edge.evidence.length; }).length;
2958
+ var evidencePercent = state.edges.length ? Math.round(evidenceEdges / state.edges.length * 100) : 0;
2959
+ var memoryCodeEdges = state.edges.filter(isMemoryCodeEdge).length;
2960
+ var reviewFlags = state.edges.filter(function (edge) { return reviewStatus(edge) !== "ok"; }).length;
2961
+ [
2962
+ metricBars("Artifact shape", state.entities.length + " nodes", [
2963
+ { label: "Visible nodes", value: visibleEntities.length, score: state.entities.length ? visibleEntities.length / state.entities.length * 100 : 0, status: "ok" },
2964
+ { label: "Relations", value: state.edges.length, score: 100, status: "ok" },
2965
+ { label: "Episodes", value: episodes, score: episodes ? 100 : 0, status: episodes ? "ok" : "warn" }
2966
+ ], "Use this when graph generation seems incomplete.", "ok"),
2967
+ metricDonut("Evidence", evidencePercent, evidenceEdges + " of " + state.edges.length + " relation(s) have evidence", "Low evidence means recall may explain less than expected.", evidencePercent >= 80 ? "ok" : "warn"),
2968
+ metricBars("Link diagnostics", memoryCodeEdges + " memory-code", [
2969
+ { label: "Memory-code", value: memoryCodeEdges, score: Math.min(100, memoryCodeEdges / Math.max(1, state.edges.length) * 100), status: memoryCodeEdges ? "ok" : "warn" },
2970
+ { label: "Review flags", value: reviewFlags, score: Math.min(100, reviewFlags * 12), status: reviewFlags ? "warn" : "ok" },
2971
+ { label: "Visible edges", value: visibleEdges.length, score: state.edges.length ? visibleEdges.length / state.edges.length * 100 : 0, status: "ok" }
2972
+ ], "Use raw rows below to inspect exact IDs, relations, and evidence.", reviewFlags ? "warn" : "ok")
2973
+ ].forEach(function (card) { els.debugOverview.appendChild(card); });
2507
2974
  }
2508
2975
 
2509
2976
  function renderDashboard() {
@@ -2519,115 +2986,1416 @@
2519
2986
  var memoryCodeEdges = state.edges.filter(isMemoryCodeEdge);
2520
2987
  var reports = state.reports || {};
2521
2988
  var reportCount = Object.keys(reports).filter(function (key) { return reports[key]; }).length;
2989
+ var risk = reports.risk || {};
2990
+ var riskTargets = Array.isArray(risk.targets) ? risk.targets : Object.keys(risk.targets || {});
2991
+ var inboxCounts = state.inbox && state.inbox.counts ? state.inbox.counts : {};
2992
+ var handoff = memoryHandoffSummary(reports.handoff);
2993
+ var pendingReview = Number(firstNumber(inboxCounts.pending, memoryGraph.pending_packets, (state.pendingPackets || []).length, 0));
2994
+ var staleFlags = Number(firstNumber(inboxCounts.stale, 0));
2995
+ var duplicateFlags = Number(firstNumber(inboxCounts.duplicates, memoryGraph.duplicate_candidate_pairs, 0));
2996
+ var missingContext = Number(firstNumber(inboxCounts.missing_context, 0));
2997
+ var ownerSilos = Array.isArray(risk.ownership_silos) ? risk.ownership_silos.length : 0;
2998
+ var hotspots = Array.isArray(risk.global_hotspots) ? risk.global_hotspots.length : 0;
2999
+ var readiness = handoff || dashboardReadiness(metrics, pendingReview, staleFlags, duplicateFlags, missingContext);
3000
+ var memoryCoverage = dashboardMemoryCoverage(reports, memoryCodeEdges, memoryGraph, memoryNodes);
3001
+ var riskHealth = riskTargets.length || hotspots ? (riskTargets.length + hotspots) + " signals" : "No flags";
2522
3002
  var statRows = [
2523
- ["Memory packets", firstNumber(memoryGraph.approved_packets, memoryNodes)],
2524
- ["Code nodes", firstNumber(codeGraph.symbols, structural.symbols, codeNodes)],
2525
- ["Files", firstNumber(codeGraph.files, structural.files, countEntitiesByType("file"))],
2526
- ["Memory-code links", memoryCodeEdges.length],
2527
- ["Parser coverage", codeGraph.indexer_coverage_percent != null ? codeGraph.indexer_coverage_percent + "%" : "n/a"],
2528
- ["Tokens saved", firstNumber(savings.estimated_tokens_saved_per_recall, pain.estimated_tokens_saved, "n/a")]
3003
+ ["Handoff", readiness.label, readiness.detail, readiness.status],
3004
+ ["Memory", memoryCoverage.label, memoryCoverage.detail, memoryCoverage.status],
3005
+ ["Risk", riskHealth, riskTargets.length + " targets, " + ownerSilos + " ownership silos", riskTargets.length || ownerSilos || hotspots ? "warn" : "ok"]
2529
3006
  ];
2530
3007
  els.dashboardStats.textContent = "";
2531
3008
  statRows.forEach(function (row) {
2532
3009
  var item = document.createElement("div");
2533
- item.className = "dashboard-stat";
2534
- item.innerHTML = "<strong></strong><span></span>";
3010
+ item.className = classNames("dashboard-stat", row[3] && "dashboard-stat-" + row[3]);
3011
+ item.innerHTML = "<span></span><strong></strong><em></em>";
2535
3012
  item.querySelector("strong").textContent = formatDashboardValue(row[1]);
2536
3013
  item.querySelector("span").textContent = row[0];
3014
+ item.querySelector("em").textContent = row[2] || "";
2537
3015
  els.dashboardStats.appendChild(item);
2538
3016
  });
2539
3017
 
2540
3018
  setDashboardRows("dashboardMemory", [
2541
- ["Approved packets", firstNumber(memoryGraph.approved_packets, memoryNodes)],
2542
- ["Pending review", firstNumber(memoryGraph.pending_packets, (state.pendingPackets || []).length)],
2543
- ["Evidence coverage", memoryGraph.evidence_coverage_percent != null ? memoryGraph.evidence_coverage_percent + "%" : "n/a"],
2544
- ["Code-linked memory", memoryCodeEdges.length]
3019
+ ["Reusable", firstNumber(memoryGraph.approved_packets, memoryNodes) + " packets"],
3020
+ ["Linked", memoryCodeEdges.length + " code links"],
3021
+ ["Review", pendingReview ? pendingReview + " pending" : "clear"]
2545
3022
  ]);
2546
3023
  setDashboardRows("dashboardGraph", [
2547
3024
  ["Files", firstNumber(codeGraph.files, structural.files, countEntitiesByType("file"))],
2548
3025
  ["Symbols", firstNumber(codeGraph.symbols, structural.symbols, countEntitiesByType("symbol"))],
2549
- ["Relations", state.edges.length],
2550
- ["Dependency/noise hidden", state.entities.filter(function (entity) { return isDependencyEntity(entity); }).length]
3026
+ ["Coverage", codeGraph.indexer_coverage_percent != null ? codeGraph.indexer_coverage_percent + "%" : "not loaded"]
2551
3027
  ]);
2552
3028
  setDashboardRows("dashboardIntel", [
2553
- ["Reports loaded", reportCount],
2554
- ["Decision coverage", reports.decisions && reports.decisions.coverage_percent != null ? reports.decisions.coverage_percent + "%" : "n/a"],
2555
- ["Modules scored", reports.moduleHealth && Array.isArray(reports.moduleHealth.modules) ? reports.moduleHealth.modules.length : "n/a"],
2556
- ["Communities", reports.graphInsights && Array.isArray(reports.graphInsights.communities) ? reports.graphInsights.communities.length : "n/a"]
3029
+ ["Risk targets", riskTargets.length || "none"],
3030
+ ["Ownership silos", ownerSilos || "none"],
3031
+ ["Decision coverage", reports.decisions && reports.decisions.coverage_percent != null ? reports.decisions.coverage_percent + "%" : "not loaded"]
2557
3032
  ]);
2558
- var risk = reports.risk || {};
2559
- var riskTargets = Array.isArray(risk.targets) ? risk.targets : Object.keys(risk.targets || {});
2560
- setDashboardRows("dashboardRisk", [
2561
- ["Risk targets", riskTargets.length || "n/a"],
2562
- ["Hotspots", Array.isArray(risk.global_hotspots) ? risk.global_hotspots.length : "n/a"],
2563
- ["Ownership silos", Array.isArray(risk.ownership_silos) ? risk.ownership_silos.length : "n/a"],
2564
- ["Cycles", reports.graphInsights && Array.isArray(reports.graphInsights.dependency_cycles) ? reports.graphInsights.dependency_cycles.length : "n/a"]
2565
- ]);
2566
- var inboxCounts = state.inbox && state.inbox.counts ? state.inbox.counts : {};
2567
- setDashboardRows("dashboardReview", [
2568
- ["Readiness", metrics.harness && metrics.harness.readiness_score != null ? metrics.harness.readiness_score + "/100" : "n/a"],
2569
- ["Inbox pending", firstNumber(inboxCounts.pending, (state.pendingPackets || []).length)],
2570
- ["Stale flags", firstNumber(inboxCounts.stale, 0)],
2571
- ["Duplicate flags", firstNumber(inboxCounts.duplicates, 0)]
2572
- ]);
2573
- var workspace = reports.workspace || {};
2574
- setDashboardRows("dashboardWorkspace", [
2575
- ["Repos", Array.isArray(workspace.repos) ? workspace.repos.length : "n/a"],
2576
- ["Package deps", Array.isArray(workspace.package_dependencies) ? workspace.package_dependencies.length : "n/a"],
2577
- ["Route contracts", Array.isArray(workspace.route_contracts) ? workspace.route_contracts.length : "n/a"],
2578
- ["Co-changes", Array.isArray(workspace.co_changes) ? workspace.co_changes.length : "n/a"]
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
+ ] : [
3040
+ ["Handoff", readiness.label],
3041
+ ["Pending", pendingReview || "none"],
3042
+ ["Stale / duplicate", staleFlags + " / " + duplicateFlags],
3043
+ ["Missing context", missingContext || "none"]
2579
3044
  ]);
3045
+ renderDashboardCharts({
3046
+ metrics: metrics,
3047
+ reports: reports,
3048
+ memoryGraph: memoryGraph,
3049
+ codeGraph: codeGraph,
3050
+ structural: structural,
3051
+ memoryCodeEdges: memoryCodeEdges,
3052
+ memoryNodes: memoryNodes,
3053
+ pendingReview: pendingReview,
3054
+ staleFlags: staleFlags,
3055
+ duplicateFlags: duplicateFlags,
3056
+ missingContext: missingContext,
3057
+ riskTargets: riskTargets,
3058
+ ownerSilos: ownerSilos,
3059
+ hotspots: hotspots,
3060
+ handoff: handoff
3061
+ });
2580
3062
  }
2581
3063
 
2582
- function setDashboardRows(cardId, rows) {
2583
- var card = document.getElementById(cardId);
2584
- if (!card) return;
2585
- var list = card.querySelector("ul");
2586
- if (!list) return;
2587
- list.textContent = "";
2588
- rows.forEach(function (row) {
2589
- var item = document.createElement("li");
2590
- item.innerHTML = "<strong></strong><span></span>";
2591
- item.querySelector("strong").textContent = row[0];
2592
- item.querySelector("span").textContent = formatDashboardValue(row[1]);
2593
- list.appendChild(item);
3064
+ function renderDashboardCharts(data) {
3065
+ if (!els.dashboardCharts) return;
3066
+ var approvedPackets = Number(firstNumber(data.memoryGraph.approved_packets, data.memoryNodes, 0));
3067
+ var linkedPacketIds = new Set();
3068
+ data.memoryCodeEdges.forEach(function (edge) {
3069
+ var from = state.entityById.get(edge.from);
3070
+ var to = state.entityById.get(edge.to);
3071
+ if (from && isMemoryPacketEntity(from)) linkedPacketIds.add(from.id);
3072
+ if (to && isMemoryPacketEntity(to)) linkedPacketIds.add(to.id);
2594
3073
  });
3074
+ var memoryGrounding = approvedPackets ? Math.round(linkedPacketIds.size / approvedPackets * 100) : 0;
3075
+ var sourceCoverage = Number(firstNumber(data.codeGraph.indexer_coverage_percent, 0));
3076
+ var blockers = data.handoff ? data.handoff.openItems : data.pendingReview + data.staleFlags + data.duplicateFlags + data.missingContext;
3077
+ var riskSignals = data.riskTargets.length + data.ownerSilos + data.hotspots;
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 = [
3091
+ metricDonut("Memory grounding", memoryGrounding, linkedPacketIds.size + " of " + approvedPackets + " packets linked to code", "Open Memory and fix Needs paths first.", memoryGrounding >= 70 ? "ok" : "warn"),
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", [
3186
+ { label: "Pending", value: data.pendingReview, score: Math.min(100, data.pendingReview * 24), status: data.pendingReview ? "warn" : "ok" },
3187
+ { label: "Stale", value: data.staleFlags, score: Math.min(100, data.staleFlags * 24), status: data.staleFlags ? "warn" : "ok" },
3188
+ { label: "Duplicate", value: data.duplicateFlags, score: Math.min(100, data.duplicateFlags * 24), status: data.duplicateFlags ? "warn" : "ok" },
3189
+ { label: "Missing context", value: data.missingContext, score: Math.min(100, data.missingContext * 18), status: data.missingContext ? "warn" : "ok" }
3190
+ ], blockers ? "Resolve Review before handing work to another agent." : "Memory is clean for handoff.", blockers ? "warn" : "ok"));
3191
+ }
3192
+ tailCards.push(
3193
+ metricBars("Change risk", riskSignals ? riskSignals + " signals" : "none", [
3194
+ { label: "Targets", value: data.riskTargets.length, score: Math.min(100, data.riskTargets.length * 18), status: data.riskTargets.length ? "warn" : "ok" },
3195
+ { label: "Silos", value: data.ownerSilos, score: Math.min(100, data.ownerSilos * 18), status: data.ownerSilos ? "warn" : "ok" },
3196
+ { label: "Hotspots", value: data.hotspots, score: Math.min(100, data.hotspots * 18), status: data.hotspots ? "danger" : "ok" }
3197
+ ], riskSignals ? "Open Intel or Owners before editing risky files." : "No loaded risk flags.", riskSignals ? "warn" : "ok")
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
+ };
2595
3218
  }
2596
3219
 
2597
- function firstNumber() {
2598
- for (var index = 0; index < arguments.length; index += 1) {
2599
- var value = arguments[index];
2600
- if (value !== null && value !== undefined && value !== "") return value;
2601
- }
2602
- return "n/a";
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
+ };
2603
3239
  }
2604
3240
 
2605
- function countEntitiesByType(type) {
2606
- return state.entities.filter(function (entity) { return entity.type === type; }).length;
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
+ };
2607
3258
  }
2608
3259
 
2609
- function formatDashboardValue(value) {
2610
- if (typeof value === "number" && Number.isFinite(value)) return value.toLocaleString();
2611
- return String(value == null ? "n/a" : value);
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
+ };
2612
3275
  }
2613
3276
 
2614
- function renderReviewQueue() {
2615
- if (!els.reviewList) return;
2616
- var packets = state.pendingPackets || [];
2617
- var inbox = state.inbox;
2618
- var inboxItems = inbox && Array.isArray(inbox.items) ? inbox.items : [];
2619
- els.reviewCount.textContent = String(packets.length + inboxItems.length);
2620
- els.reviewList.textContent = "";
2621
- if (!packets.length && !inboxItems.length && !state.reviewText) {
2622
- els.reviewList.className = "review-list details-empty";
2623
- els.reviewList.textContent = "No pending packets loaded. Launch with `kage viewer --project <repo>` to load review context automatically.";
2624
- return;
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.";
2625
3292
  }
2626
- els.reviewList.className = "review-list";
2627
- if (inbox) {
2628
- var summary = document.createElement("div");
2629
- summary.className = "review-item";
2630
- var counts = inbox.counts || {};
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
+ };
3385
+ }
3386
+
3387
+ function metricDonut(title, percent, detail, action, status) {
3388
+ var card = document.createElement("article");
3389
+ var value = clamp(Number(percent || 0), 0, 100);
3390
+ card.className = classNames("metric-card", status && "metric-card-" + status);
3391
+ card.innerHTML = [
3392
+ "<div class=\"metric-card-head\"><span></span><strong></strong></div>",
3393
+ "<div class=\"metric-visual\"><div class=\"metric-donut\"><span></span></div><p></p></div>",
3394
+ "<em></em>"
3395
+ ].join("");
3396
+ card.querySelector(".metric-card-head span").textContent = title;
3397
+ card.querySelector(".metric-card-head strong").textContent = value + "%";
3398
+ card.querySelector(".metric-donut").style.setProperty("--value", value);
3399
+ card.querySelector(".metric-donut span").textContent = value + "%";
3400
+ card.querySelector("p").textContent = detail || "";
3401
+ card.querySelector("em").textContent = action || "";
3402
+ return card;
3403
+ }
3404
+
3405
+ function metricBars(title, value, rows, action, status) {
3406
+ var card = document.createElement("article");
3407
+ card.className = classNames("metric-card", status && "metric-card-" + status);
3408
+ card.innerHTML = [
3409
+ "<div class=\"metric-card-head\"><span></span><strong></strong></div>",
3410
+ "<div class=\"metric-bars\"></div>",
3411
+ "<em></em>"
3412
+ ].join("");
3413
+ card.querySelector(".metric-card-head span").textContent = title;
3414
+ card.querySelector(".metric-card-head strong").textContent = formatDashboardValue(value);
3415
+ var list = card.querySelector(".metric-bars");
3416
+ rows.forEach(function (row) {
3417
+ var item = document.createElement("div");
3418
+ item.className = classNames("metric-bar", row.status && "metric-bar-" + row.status);
3419
+ item.innerHTML = "<span></span><strong></strong><i></i>";
3420
+ item.querySelector("span").textContent = row.label;
3421
+ item.querySelector("strong").textContent = formatDashboardValue(row.value);
3422
+ item.querySelector("i").style.width = clamp(Number(row.score || 0), row.value ? 8 : 0, 100) + "%";
3423
+ list.appendChild(item);
3424
+ });
3425
+ card.querySelector("em").textContent = action || "";
3426
+ return card;
3427
+ }
3428
+
3429
+ function dashboardReadiness(metrics, pendingReview, staleFlags, duplicateFlags, missingContext) {
3430
+ if (pendingReview || staleFlags || duplicateFlags || missingContext) {
3431
+ return { label: "Needs review", detail: pendingReview + " pending, " + staleFlags + " stale, " + duplicateFlags + " duplicate, " + missingContext + " missing context", status: "warn" };
3432
+ }
3433
+ if (metrics && metrics.harness && metrics.harness.validation_ok) {
3434
+ return { label: "Ready", detail: "Memory and graph checks are clean", status: "ok" };
3435
+ }
3436
+ return { label: "Unknown", detail: "Run kage refresh or open local viewer with metrics", status: "warn" };
3437
+ }
3438
+
3439
+ function dashboardMemoryCoverage(reports, memoryCodeEdges, memoryGraph, memoryNodes) {
3440
+ var coverage = reports.decisions && reports.decisions.coverage_percent;
3441
+ if (coverage != null) {
3442
+ return {
3443
+ label: coverage + "%",
3444
+ detail: "Decision memory coverage for important code paths",
3445
+ status: Number(coverage) >= 70 ? "ok" : "warn"
3446
+ };
3447
+ }
3448
+ var packets = Number(firstNumber(memoryGraph.approved_packets, memoryNodes, 0));
3449
+ if (!packets) return { label: "No memory", detail: "Agents will rediscover repo context", status: "warn" };
3450
+ return {
3451
+ label: memoryCodeEdges.length + " links",
3452
+ detail: "Memory packets connected back to code",
3453
+ status: memoryCodeEdges.length ? "ok" : "warn"
3454
+ };
3455
+ }
3456
+
3457
+ function setDashboardRows(cardId, rows) {
3458
+ var card = document.getElementById(cardId);
3459
+ if (!card) return;
3460
+ var list = card.querySelector("ul");
3461
+ if (!list) return;
3462
+ list.textContent = "";
3463
+ rows.forEach(function (row) {
3464
+ var item = document.createElement("li");
3465
+ item.innerHTML = "<strong></strong><span></span>";
3466
+ item.querySelector("strong").textContent = row[0];
3467
+ item.querySelector("span").textContent = formatDashboardValue(row[1]);
3468
+ list.appendChild(item);
3469
+ });
3470
+ }
3471
+
3472
+ function firstNumber() {
3473
+ for (var index = 0; index < arguments.length; index += 1) {
3474
+ var value = arguments[index];
3475
+ if (value !== null && value !== undefined && value !== "") return value;
3476
+ }
3477
+ return "n/a";
3478
+ }
3479
+
3480
+ function countEntitiesByType(type) {
3481
+ return state.entities.filter(function (entity) { return entity.type === type; }).length;
3482
+ }
3483
+
3484
+ function formatDashboardValue(value) {
3485
+ if (typeof value === "number" && Number.isFinite(value)) return value.toLocaleString();
3486
+ return String(value == null ? "n/a" : value);
3487
+ }
3488
+
3489
+ function escapeHtml(value) {
3490
+ return String(value == null ? "" : value).replace(/[&<>"']/g, function (char) {
3491
+ return { "&": "&amp;", "<": "&lt;", ">": "&gt;", "\"": "&quot;", "'": "&#39;" }[char] || char;
3492
+ });
3493
+ }
3494
+
3495
+ function renderMemoryLibrary() {
3496
+ if (!els.memoryList) return;
3497
+ var memoryEntities = state.entities.filter(function (entity) {
3498
+ return isMemoryPacketEntity(entity);
3499
+ }).sort(function (a, b) {
3500
+ return entityImportance(b) - entityImportance(a) || displayName(a).localeCompare(displayName(b));
3501
+ });
3502
+ var memoryLinkCounts = new Map();
3503
+ state.edges.forEach(function (edge) {
3504
+ if (!isMemoryCodeEdge(edge)) return;
3505
+ var fromEntity = state.entityById.get(edge.from);
3506
+ var toEntity = state.entityById.get(edge.to);
3507
+ if (fromEntity && fromEntity.graph_kind === "memory" && toEntity && toEntity.graph_kind === "code") {
3508
+ memoryLinkCounts.set(edge.from, (memoryLinkCounts.get(edge.from) || 0) + 1);
3509
+ }
3510
+ if (toEntity && toEntity.graph_kind === "memory" && fromEntity && fromEntity.graph_kind === "code") {
3511
+ memoryLinkCounts.set(edge.to, (memoryLinkCounts.get(edge.to) || 0) + 1);
3512
+ }
3513
+ });
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;
3516
+ var query = parseSearchQuery(els.memorySearch ? els.memorySearch.value : "");
3517
+ var filter = els.memoryFilter ? els.memoryFilter.value : "all";
3518
+ var filtered = memoryEntities.filter(function (entity) {
3519
+ var linkCount = memoryLinkCounts.get(entity.id) || 0;
3520
+ if (filter === "linked" && !linkCount) return false;
3521
+ if (filter === "needs-paths" && linkCount) return false;
3522
+ if (["decision", "runbook", "bug_fix"].indexOf(filter) !== -1 && !memoryMatchesKind(entity, filter)) return false;
3523
+ return matchesSearchQuery(entity, query);
3524
+ });
3525
+ els.memoryStatus.textContent = filtered.length + " shown";
3526
+ if (els.memoryStats) {
3527
+ els.memoryStats.innerHTML = [
3528
+ memoryStat("Reusable", memoryEntities.length),
3529
+ memoryStat("Code-linked", linkedCount),
3530
+ memoryStat("Reused 30d", accessTotals ? Number(accessTotals.uses_30d || 0) : "n/a"),
3531
+ memoryStat("Needs paths", memoryEntities.length - linkedCount)
3532
+ ].join("");
3533
+ }
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();
3541
+ els.memoryList.textContent = "";
3542
+ if (!memoryEntities.length) {
3543
+ els.memoryList.className = "memory-list details-empty";
3544
+ els.memoryList.textContent = "No memory packets loaded. Launch with `kage viewer --project <repo>` to load repo memory.";
3545
+ return;
3546
+ }
3547
+ if (!filtered.length) {
3548
+ els.memoryList.className = "memory-list details-empty";
3549
+ els.memoryList.textContent = "No matching memory. Clear search or switch the filter.";
3550
+ return;
3551
+ }
3552
+ filtered.sort(function (a, b) {
3553
+ return (memoryLinkCounts.get(b.id) || 0) - (memoryLinkCounts.get(a.id) || 0) ||
3554
+ entityImportance(b) - entityImportance(a) ||
3555
+ displayName(a).localeCompare(displayName(b));
3556
+ });
3557
+ els.memoryList.className = "memory-list";
3558
+ filtered.slice(0, 60).forEach(function (entity) {
3559
+ var links = memoryCodeLinksForEntity(entity.id);
3560
+ var firstCodeTarget = primaryCodeTargetForMemory(entity.id, links);
3561
+ var access = memoryAccessForEntity(entity);
3562
+ var item = document.createElement("button");
3563
+ item.type = "button";
3564
+ var selected = state.selected && state.selected.kind === "entity" && state.selected.id === entity.id;
3565
+ item.className = classNames("memory-row", selected && "selected");
3566
+ item.setAttribute("aria-selected", selected ? "true" : "false");
3567
+ item.innerHTML = [
3568
+ "<span class=\"memory-row-main\"><strong></strong><em></em></span>",
3569
+ "<span class=\"memory-row-meta\"></span>",
3570
+ "<span class=\"memory-row-target\"></span>"
3571
+ ].join("");
3572
+ item.querySelector("strong").textContent = displayName(entity);
3573
+ item.querySelector("em").textContent = entity.type || "memory";
3574
+ item.querySelector(".memory-row-meta").textContent = trimIntelText(entity.summary || entity.description || entity.path || "No summary", 150);
3575
+ item.querySelector(".memory-row-target").textContent = links.length
3576
+ ? links.length + " code link" + (links.length === 1 ? "" : "s") + (firstCodeTarget ? " | " + trimIntelText(codeTargetLabel(firstCodeTarget), 64) : "")
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
+ }
3581
+ item.addEventListener("click", function () {
3582
+ selectEntity(entity.id, true);
3583
+ render();
3584
+ });
3585
+ els.memoryList.appendChild(item);
3586
+ });
3587
+ if (filtered.length > 60) appendListNote(els.memoryList, "Showing 60 of " + filtered.length + ". Search a path or topic to narrow.");
3588
+ }
3589
+
3590
+ function memoryStat(label, value) {
3591
+ return "<div><strong>" + escapeHtml(String(value)) + "</strong><span>" + escapeHtml(label) + "</span></div>";
3592
+ }
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
+
3601
+ function renderMemoryOverview(memoryEntities, linkedCount) {
3602
+ els.memoryOverview.textContent = "";
3603
+ var total = memoryEntities.length;
3604
+ var linkedPercent = total ? Math.round(linkedCount / total * 100) : 0;
3605
+ var typeCounts = new Map();
3606
+ memoryEntities.forEach(function (entity) {
3607
+ typeCounts.set(entity.type || "memory", (typeCounts.get(entity.type || "memory") || 0) + 1);
3608
+ });
3609
+ var topTypes = Array.from(typeCounts.entries()).sort(function (a, b) { return b[1] - a[1] || a[0].localeCompare(b[0]); }).slice(0, 4);
3610
+ var maxType = Math.max(1, topTypes.reduce(function (max, row) { return Math.max(max, row[1]); }, 0));
3611
+ els.memoryOverview.appendChild(metricDonut(
3612
+ "Code grounding",
3613
+ linkedPercent,
3614
+ linkedCount + " packet(s) are connected to files, symbols, routes, or tests",
3615
+ linkedPercent >= 70 ? "Use linked memory before edits." : "Filter Needs paths and add concrete code references.",
3616
+ linkedPercent >= 70 ? "ok" : "warn"
3617
+ ));
3618
+ els.memoryOverview.appendChild(metricBars("Memory mix", total + " packets", topTypes.map(function (row) {
3619
+ return {
3620
+ label: row[0],
3621
+ value: row[1],
3622
+ score: row[1] / maxType * 100,
3623
+ status: row[0] === "memory" ? "" : "ok"
3624
+ };
3625
+ }), "A healthy repo has decisions, bug fixes, runbooks, gotchas, and code explanations.", "ok"));
3626
+ }
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
+
4214
+ function memoryCodeLinksForEntity(entityId) {
4215
+ return state.edges.filter(function (edge) {
4216
+ if ((edge.from !== entityId && edge.to !== entityId) || !isMemoryCodeEdge(edge)) return false;
4217
+ var other = state.entityById.get(edge.from === entityId ? edge.to : edge.from);
4218
+ return Boolean(other && other.graph_kind === "code");
4219
+ });
4220
+ }
4221
+
4222
+ function primaryCodeTargetForMemory(entityId, links) {
4223
+ return links.map(function (edge) {
4224
+ return state.entityById.get(edge.from === entityId ? edge.to : edge.from);
4225
+ }).filter(Boolean).sort(function (a, b) {
4226
+ return codeTargetScore(b) - codeTargetScore(a) || codeTargetLabel(a).localeCompare(codeTargetLabel(b));
4227
+ })[0] || null;
4228
+ }
4229
+
4230
+ function codeTargetScore(entity) {
4231
+ var score = 0;
4232
+ if (!entity) return score;
4233
+ if (entity.type === "file") score += 80;
4234
+ if (entity.type === "route" || entity.type === "test") score += 60;
4235
+ if (entity.type === "symbol") score += 45;
4236
+ if (entity.path) score += 35;
4237
+ if (entity.line) score += 8;
4238
+ if (String(entity.path || "").indexOf(".agent_memory/") === 0) score -= 100;
4239
+ return score;
4240
+ }
4241
+
4242
+ function codeTargetLabel(entity) {
4243
+ if (!entity) return "";
4244
+ if (entity.type === "file" && entity.path) return entity.path;
4245
+ if (entity.type === "route") {
4246
+ return (entity.method && entity.route_path ? entity.method + " " + entity.route_path : displayName(entity)) +
4247
+ (entity.path ? " in " + entity.path : "");
4248
+ }
4249
+ if ((entity.type === "symbol" || entity.type === "test") && entity.path) {
4250
+ return displayName(entity) + " in " + entity.path;
4251
+ }
4252
+ return entity.path || displayName(entity);
4253
+ }
4254
+
4255
+ function memoryMatchesKind(entity, kind) {
4256
+ if (!entity) return false;
4257
+ if (entity.type === kind) return true;
4258
+ var normalizedKind = String(kind || "").replace(/_/g, " ");
4259
+ var text = [
4260
+ entity.type,
4261
+ entity.name,
4262
+ entity.summary,
4263
+ entity.description,
4264
+ entity.path
4265
+ ].join(" ").toLowerCase();
4266
+ return text.indexOf(kind) !== -1 || text.indexOf(normalizedKind) !== -1;
4267
+ }
4268
+
4269
+ function isMemoryPacketEntity(entity) {
4270
+ return Boolean(entity && entity.graph_kind === "memory" && MEMORY_PACKET_TYPES.has(entity.type));
4271
+ }
4272
+
4273
+ function renderOwners() {
4274
+ if (!els.ownersList) return;
4275
+ var contributors = state.reports && state.reports.contributors;
4276
+ var risk = state.reports && state.reports.risk;
4277
+ var profiles = contributors && Array.isArray(contributors.contributors) ? contributors.contributors : [];
4278
+ var silos = risk && Array.isArray(risk.ownership_silos) ? risk.ownership_silos : [];
4279
+ els.ownersStatus.textContent = profiles.length ? profiles.length + " contributors" : "not loaded";
4280
+ els.ownersList.textContent = "";
4281
+ if (els.ownersSummary) renderOwnersSummary(profiles, silos);
4282
+ if (!profiles.length && !silos.length) {
4283
+ els.ownersList.className = "owners-list details-empty";
4284
+ els.ownersList.textContent = "No owner report loaded. Launch with `kage viewer --project <repo>` to load contributor and ownership reports.";
4285
+ return;
4286
+ }
4287
+ els.ownersList.className = "owners-list";
4288
+ profiles.slice(0, 24).forEach(function (profile) {
4289
+ var item = document.createElement("article");
4290
+ item.className = "owner-card";
4291
+ item.innerHTML = [
4292
+ "<div class=\"owner-head\"><strong></strong><span></span></div>",
4293
+ "<div class=\"owner-stats\"></div>",
4294
+ "<p></p>"
4295
+ ].join("");
4296
+ item.querySelector("strong").textContent = shortContributor(profile.contributor);
4297
+ item.querySelector(".owner-head span").textContent = (profile.primary_owned_files || 0) + " owned files";
4298
+ item.querySelector(".owner-stats").textContent = [
4299
+ (profile.commits_total || 0) + " commits",
4300
+ (profile.commits_90d || 0) + " in 90d",
4301
+ (profile.silo_files && profile.silo_files.length ? profile.silo_files.length + " silo files" : "no silo flags")
4302
+ ].join(" | ");
4303
+ item.querySelector("p").textContent = Array.isArray(profile.top_modules) && profile.top_modules.length
4304
+ ? "Modules: " + profile.top_modules.slice(0, 4).join(", ")
4305
+ : "Local git ownership signal.";
4306
+ els.ownersList.appendChild(item);
4307
+ });
4308
+ if (silos.length) {
4309
+ var siloSection = document.createElement("section");
4310
+ siloSection.className = "owner-silos";
4311
+ siloSection.innerHTML = "<h3>Ownership Silos</h3>";
4312
+ silos.slice(0, 16).forEach(function (silo) {
4313
+ var row = document.createElement("button");
4314
+ row.type = "button";
4315
+ row.className = "owner-silo-row";
4316
+ row.innerHTML = "<strong></strong><span></span>";
4317
+ row.querySelector("strong").textContent = silo.file_path || "file";
4318
+ row.querySelector("span").textContent = [
4319
+ shortContributor(silo.primary_owner || "unknown"),
4320
+ silo.primary_owner_pct != null ? Math.round(Number(silo.primary_owner_pct || 0) * 100) + "% ownership" : "",
4321
+ (silo.commit_count_total || 0) + " commits"
4322
+ ].filter(Boolean).join(" | ");
4323
+ row.addEventListener("click", function () {
4324
+ focusGraphPath(silo.file_path);
4325
+ });
4326
+ siloSection.appendChild(row);
4327
+ });
4328
+ els.ownersList.appendChild(siloSection);
4329
+ }
4330
+ }
4331
+
4332
+ function renderOwnersSummary(profiles, silos) {
4333
+ els.ownersSummary.textContent = "";
4334
+ if (!profiles.length && !silos.length) return;
4335
+ var totalOwned = profiles.reduce(function (sum, profile) { return sum + Number(profile.primary_owned_files || 0); }, 0);
4336
+ var topOwned = profiles.reduce(function (max, profile) { return Math.max(max, Number(profile.primary_owned_files || 0)); }, 0);
4337
+ var topOwnerShare = totalOwned ? Math.round(topOwned / totalOwned * 100) : 0;
4338
+ var commits90 = profiles.reduce(function (sum, profile) { return sum + Number(profile.commits_90d || 0); }, 0);
4339
+ var maxOwned = Math.max(1, topOwned);
4340
+ els.ownersSummary.appendChild(metricDonut(
4341
+ "Backup coverage",
4342
+ Math.max(0, 100 - topOwnerShare),
4343
+ silos.length ? silos.length + " single-owner file(s) need backup reviewers" : "No loaded ownership silos",
4344
+ silos.length ? "Click silo rows to inspect the file in Graph." : "Keep ownership spread visible before large changes.",
4345
+ silos.length ? "warn" : "ok"
4346
+ ));
4347
+ els.ownersSummary.appendChild(metricBars("Owner concentration", profiles.length + " profiles", profiles.slice(0, 4).map(function (profile) {
4348
+ var owned = Number(profile.primary_owned_files || 0);
4349
+ return {
4350
+ label: shortContributor(profile.contributor),
4351
+ value: owned + " files",
4352
+ score: owned / maxOwned * 100,
4353
+ status: owned && Array.isArray(profile.silo_files) && profile.silo_files.length ? "warn" : "ok"
4354
+ };
4355
+ }), "Use this for reviewer routing and bus-factor checks.", silos.length ? "warn" : "ok"));
4356
+ els.ownersSummary.appendChild(metricBars("Recent activity", commits90 + " commits", profiles.slice(0, 4).map(function (profile) {
4357
+ var commits = Number(profile.commits_90d || 0);
4358
+ var maxCommits = Math.max(1, profiles.reduce(function (max, item) { return Math.max(max, Number(item.commits_90d || 0)); }, 0));
4359
+ return {
4360
+ label: shortContributor(profile.contributor),
4361
+ value: commits,
4362
+ score: commits / maxCommits * 100,
4363
+ status: commits ? "ok" : ""
4364
+ };
4365
+ }), "Prefer recent editors for fast review context.", "ok"));
4366
+ }
4367
+
4368
+ function renderReviewQueue() {
4369
+ if (!els.reviewList) return;
4370
+ var packets = state.pendingPackets || [];
4371
+ var inbox = state.inbox;
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 : [];
4375
+ var counts = inbox && inbox.counts ? inbox.counts : {};
4376
+ var openCount = reviewOpenCount(counts, packets, inboxItems);
4377
+ if (handoff && handoff.totals) openCount = Number(firstNumber(handoff.totals.open_items, openCount));
4378
+ els.reviewCount.textContent = String(openCount);
4379
+ els.reviewList.textContent = "";
4380
+ if (els.reviewOverview) renderReviewOverview(inbox, packets, inboxItems, handoff);
4381
+ renderMemoryHandoff(handoff, handoffItems);
4382
+ if (!packets.length && !inboxItems.length && !handoffItems.length && !state.reviewText) {
4383
+ els.reviewList.className = "review-list details-empty";
4384
+ els.reviewList.textContent = "No pending packets loaded. Launch with `kage viewer --project <repo>` to load review context automatically.";
4385
+ return;
4386
+ }
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
+ });
4396
+ if (inbox) {
4397
+ var summary = document.createElement("div");
4398
+ summary.className = "review-item";
2631
4399
  summary.innerHTML = [
2632
4400
  "<div class=\"review-title\"></div>",
2633
4401
  "<div class=\"review-meta\"></div>",
@@ -2644,10 +4412,10 @@
2644
4412
  summary.querySelector(".review-summary").textContent = Array.isArray(inbox.recommendations) && inbox.recommendations.length
2645
4413
  ? inbox.recommendations.slice(0, 2).join(" ")
2646
4414
  : "No inbox recommendations.";
2647
- summary.querySelector(".review-risks").textContent = inbox.ok ? "ready for handoff" : "requires review";
4415
+ summary.querySelector(".review-risks").textContent = openCount ? "Resolve inbox items before merge" : "Ready for handoff";
2648
4416
  els.reviewList.appendChild(summary);
2649
4417
  }
2650
- inboxItems.slice(0, 20).forEach(function (entry) {
4418
+ inboxItems.slice(0, 8).forEach(function (entry) {
2651
4419
  var item = document.createElement("div");
2652
4420
  item.className = "review-item";
2653
4421
  item.innerHTML = [
@@ -2659,7 +4427,9 @@
2659
4427
  item.querySelector(".review-title").textContent = entry.title || entry.summary || entry.kind;
2660
4428
  item.querySelector(".review-meta").textContent = [entry.kind, entry.severity, entry.type, entry.status].filter(Boolean).join(" | ");
2661
4429
  item.querySelector(".review-summary").textContent = entry.action || entry.summary || "";
2662
- item.querySelector(".review-risks").textContent = Array.isArray(entry.reasons) && entry.reasons.length ? "reasons: " + entry.reasons.slice(0, 3).join(", ") : "reasons: none";
4430
+ item.querySelector(".review-risks").textContent = Array.isArray(entry.reasons) && entry.reasons.length
4431
+ ? trimIntelText(entry.reasons[0], 86)
4432
+ : "Review before handoff";
2663
4433
  els.reviewList.appendChild(item);
2664
4434
  });
2665
4435
  packets.forEach(function (packet) {
@@ -2675,7 +4445,9 @@
2675
4445
  item.querySelector(".review-title").textContent = packet.title || packet.id;
2676
4446
  item.querySelector(".review-meta").textContent = [packet.type, packet.status, "score " + (quality.score == null ? "n/a" : quality.score + "/100")].filter(Boolean).join(" | ");
2677
4447
  item.querySelector(".review-summary").textContent = packet.summary || "";
2678
- item.querySelector(".review-risks").textContent = Array.isArray(quality.risks) && quality.risks.length ? "risks: " + quality.risks.join(", ") : "risks: none";
4448
+ item.querySelector(".review-risks").textContent = Array.isArray(quality.risks) && quality.risks.length
4449
+ ? "Resolve " + quality.risks.join(", ")
4450
+ : "Evidence looks clean";
2679
4451
  els.reviewList.appendChild(item);
2680
4452
  });
2681
4453
  if (state.reviewText) {
@@ -2687,25 +4459,114 @@
2687
4459
  }
2688
4460
  }
2689
4461
 
4462
+ function renderReviewOverview(inbox, packets, inboxItems, handoff) {
4463
+ els.reviewOverview.textContent = "";
4464
+ var counts = inbox && inbox.counts ? inbox.counts : {};
4465
+ var pending = Number(firstNumber(counts.pending, packets.length, 0));
4466
+ var stale = Number(firstNumber(counts.stale, 0));
4467
+ var duplicates = Number(firstNumber(counts.duplicates, 0));
4468
+ var missingContext = Number(firstNumber(counts.missing_context, 0));
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;
4472
+ els.reviewOverview.appendChild(metricDonut(
4473
+ "Handoff readiness",
4474
+ blockers ? 0 : 100,
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.",
4477
+ blockers ? "warn" : "ok"
4478
+ ));
4479
+ els.reviewOverview.appendChild(metricBars(handoff ? "Handoff queue" : "Inbox breakdown", blockers ? blockers + " open" : "clear", [
4480
+ { label: "Pending", value: pending, score: Math.min(100, pending * 24), status: pending ? "warn" : "ok" },
4481
+ { label: "Stale", value: stale, score: Math.min(100, stale * 24), status: stale ? "warn" : "ok" },
4482
+ { label: "Duplicates", value: duplicates, score: Math.min(100, duplicates * 24), status: duplicates ? "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;
4538
+ }
4539
+
4540
+ function reviewOpenCount(counts, packets, inboxItems) {
4541
+ var pending = Number(firstNumber(counts && counts.pending, packets && packets.length, 0));
4542
+ var stale = Number(firstNumber(counts && counts.stale, 0));
4543
+ var duplicates = Number(firstNumber(counts && counts.duplicates, 0));
4544
+ var missingContext = Number(firstNumber(counts && counts.missing_context, 0));
4545
+ var counted = pending + stale + duplicates + missingContext;
4546
+ if (counted) return counted;
4547
+ return Array.isArray(inboxItems) ? inboxItems.length : 0;
4548
+ }
4549
+
2690
4550
  function renderProof() {
2691
4551
  if (!els.proofList) return;
2692
4552
  var metrics = state.metrics;
2693
4553
  els.proofList.textContent = "";
2694
4554
  if (!metrics) {
2695
4555
  els.proofStatus.textContent = "not loaded";
4556
+ if (els.proofOverview) els.proofOverview.textContent = "";
2696
4557
  els.proofList.className = "proof-list details-empty";
2697
4558
  els.proofList.textContent = "Metrics not loaded. Run `kage metrics --project <repo> --json > .agent_memory/metrics.json` or launch with `kage viewer`.";
2698
4559
  return;
2699
4560
  }
2700
4561
  els.proofStatus.textContent = "loaded";
2701
4562
  els.proofList.className = "proof-list";
4563
+ if (els.proofOverview) renderProofOverview(metrics, state.reports || {});
4564
+ renderProofLedger(state.reports && state.reports.benchmark);
2702
4565
  var rows = [
2703
- ["Readiness", metrics.harness && metrics.harness.readiness_score != null ? metrics.harness.readiness_score + "/100" : "n/a"],
2704
- ["Useful memory", metrics.quality ? metrics.quality.useful_memory_ratio_percent + "%" : "n/a"],
4566
+ ["Validation", metrics.harness && metrics.harness.validation_ok ? "clean" : "check"],
2705
4567
  ["Evidence", metrics.memory_graph ? metrics.memory_graph.evidence_coverage_percent + "%" : "n/a"],
2706
4568
  ["Pending review", metrics.memory_graph ? String(metrics.memory_graph.pending_packets) : "n/a"],
2707
- ["Recall hit rate", metrics.pain ? metrics.pain.recall_hit_rate_percent + "%" : "n/a"],
2708
- ["Tokens saved", metrics.pain ? String(metrics.pain.estimated_tokens_saved) : metrics.savings ? String(metrics.savings.estimated_tokens_saved_per_recall) : "n/a"]
4569
+ ["Recall savings", metrics.pain ? String(metrics.pain.estimated_tokens_saved) : metrics.savings ? String(metrics.savings.estimated_tokens_saved_per_recall) : "n/a"]
2709
4570
  ];
2710
4571
  rows.forEach(function (row) {
2711
4572
  var item = document.createElement("div");
@@ -2717,6 +4578,136 @@
2717
4578
  });
2718
4579
  }
2719
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
+
4600
+ function renderProofOverview(metrics, reports) {
4601
+ els.proofOverview.textContent = "";
4602
+ var quality = reports.quality || {};
4603
+ var benchmark = reports.benchmark || {};
4604
+ var gates = Array.isArray(benchmark.gates) ? benchmark.gates : [];
4605
+ var retrieval = benchmarkRetrievalSummary(benchmark);
4606
+ var sourceDiversity = benchmarkSourceDiversitySummary(benchmark);
4607
+ var scale = benchmarkScaleSummary(benchmark);
4608
+ var passingGates = gates.filter(function (gate) { return gate.pass; }).length;
4609
+ var gatePercent = gates.length ? Math.round(passingGates / gates.length * 100) : (benchmark.ok ? 100 : 0);
4610
+ var evidence = Number(firstNumber(metrics.memory_graph && metrics.memory_graph.evidence_coverage_percent, quality.evidence_coverage_percent, 0));
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
+ }
4633
+ els.proofOverview.appendChild(metricDonut(
4634
+ "Trust gate",
4635
+ gatePercent,
4636
+ gates.length ? passingGates + " of " + gates.length + " benchmark gates passing" : "Benchmark report not loaded",
4637
+ gatePercent >= 100 ? "Keep this green before publishing or handing off." : "Fix failing proof gates before release.",
4638
+ gatePercent >= 100 ? "ok" : "warn"
4639
+ ));
4640
+ els.proofOverview.appendChild(metricBars("Memory quality", evidence + "% evidence", [
4641
+ { label: "Evidence", value: evidence + "%", score: evidence, status: evidence >= 80 ? "ok" : "warn" },
4642
+ { label: "Path grounded", value: pathGrounding ? pathGrounding + "%" : "n/a", score: pathGrounding || 0, status: pathGrounding >= 80 ? "ok" : "warn" },
4643
+ { label: "Useful", value: quality.useful_memory_ratio_percent != null ? quality.useful_memory_ratio_percent + "%" : "n/a", score: Number(quality.useful_memory_ratio_percent || 0), status: Number(quality.useful_memory_ratio_percent || 0) >= 70 ? "ok" : "warn" }
4644
+ ], "Trust memory only when it is evidence-backed and path-grounded.", evidence >= 80 ? "ok" : "warn"));
4645
+ }
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
+
2720
4711
  function renderIntelligence() {
2721
4712
  if (!els.intelligenceList) return;
2722
4713
  var reports = state.reports || {};
@@ -2729,36 +4720,48 @@
2729
4720
  return;
2730
4721
  }
2731
4722
  els.intelligenceList.className = "intelligence-list";
2732
- cards.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) {
2733
4728
  var item = document.createElement("article");
2734
4729
  item.className = "intel-card";
2735
4730
  item.innerHTML = [
2736
- "<h3></h3>",
2737
- "<div class=\"intel-kicker\"></div>",
2738
- "<div class=\"intel-summary\"></div>",
4731
+ "<div class=\"intel-card-head\"><div><h3></h3><span></span></div><strong></strong></div>",
4732
+ "<div class=\"intel-metric-label\"></div>",
4733
+ "<p class=\"intel-highlight\"></p>",
4734
+ "<p class=\"intel-action\"><b>Action:</b> <span></span></p>",
2739
4735
  "<ul></ul>"
2740
4736
  ].join("");
2741
4737
  item.querySelector("h3").textContent = card.title;
2742
- item.querySelector(".intel-kicker").textContent = card.kicker;
2743
- item.querySelector(".intel-summary").textContent = card.summary;
4738
+ item.querySelector(".intel-card-head span").textContent = card.kicker;
4739
+ item.querySelector(".intel-card-head strong").textContent = card.metric || "n/a";
4740
+ item.querySelector(".intel-metric-label").textContent = card.metricLabel || "signal";
4741
+ item.querySelector(".intel-highlight").textContent = card.highlight || card.summary || "";
4742
+ item.querySelector(".intel-action span").textContent = card.action || "Review this signal before changing related code.";
2744
4743
  var list = item.querySelector("ul");
2745
- card.rows.slice(0, 5).forEach(function (row) {
4744
+ card.rows.slice(0, 2).forEach(function (row) {
2746
4745
  var li = document.createElement("li");
2747
4746
  li.innerHTML = "<strong></strong> <span></span>";
2748
4747
  li.querySelector("strong").textContent = row[0];
2749
- li.querySelector("span").textContent = row[1];
4748
+ li.querySelector("span").textContent = trimIntelText(row[1], 92);
2750
4749
  list.appendChild(li);
2751
4750
  });
2752
4751
  els.intelligenceList.appendChild(item);
2753
4752
  });
2754
- var sections = buildIntelligenceSections(reports);
4753
+ var sections = rankIntelligenceSections(buildIntelligenceSections(reports)).slice(0, 2);
2755
4754
  if (sections.length) {
2756
- var grid = document.createElement("div");
2757
- grid.className = "intel-deep-grid";
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>";
4758
+ var deepGrid = document.createElement("div");
4759
+ deepGrid.className = "intel-deep-grid";
2758
4760
  sections.forEach(function (section) {
2759
- grid.appendChild(renderIntelligenceSection(section));
4761
+ deepGrid.appendChild(renderIntelligenceSection(section));
2760
4762
  });
2761
- els.intelligenceList.appendChild(grid);
4763
+ details.appendChild(deepGrid);
4764
+ els.intelligenceList.appendChild(details);
2762
4765
  }
2763
4766
  }
2764
4767
 
@@ -2781,32 +4784,46 @@
2781
4784
  var list = document.createElement("div");
2782
4785
  list.className = "intel-section-list";
2783
4786
  section.rows.slice(0, section.limit || 8).forEach(function (row) {
2784
- var button = document.createElement("button");
2785
- button.type = "button";
2786
- button.className = classNames("intel-row", row.status && "intel-row-" + safeCssName(row.status), row.path && "clickable");
2787
- button.innerHTML = [
4787
+ var rowEl = document.createElement(row.path ? "button" : "div");
4788
+ if (row.path) rowEl.type = "button";
4789
+ rowEl.className = classNames("intel-row", row.status && "intel-row-" + safeCssName(row.status), row.path && "clickable");
4790
+ rowEl.innerHTML = [
2788
4791
  "<span class=\"intel-row-main\"><strong></strong><em></em></span>",
2789
4792
  "<span class=\"intel-row-meta\"></span>",
2790
4793
  "<span class=\"intel-row-bar\"><i></i></span>"
2791
4794
  ].join("");
2792
- button.querySelector("strong").textContent = row.label || "";
2793
- button.querySelector("em").textContent = row.value || "";
2794
- button.querySelector(".intel-row-meta").textContent = row.meta || "";
2795
- button.querySelector(".intel-row-bar i").style.width = clamp(Number(row.score || 0), 4, 100) + "%";
4795
+ rowEl.querySelector("strong").textContent = row.label || "";
4796
+ rowEl.querySelector("em").textContent = row.value || "";
4797
+ rowEl.querySelector(".intel-row-meta").textContent = row.meta || "";
4798
+ rowEl.querySelector(".intel-row-bar i").style.width = clamp(Number(row.score || 0), 4, 100) + "%";
2796
4799
  if (row.path) {
2797
- button.title = "Focus " + row.path + " in the graph";
2798
- button.addEventListener("click", function () {
4800
+ rowEl.title = "Focus " + row.path + " in the graph";
4801
+ rowEl.setAttribute("aria-label", "Focus " + row.path + " in Graph");
4802
+ rowEl.addEventListener("click", function () {
2799
4803
  focusGraphPath(row.path);
2800
4804
  });
2801
- } else {
2802
- button.disabled = true;
2803
4805
  }
2804
- list.appendChild(button);
4806
+ list.appendChild(rowEl);
2805
4807
  });
2806
4808
  panel.appendChild(list);
2807
4809
  return panel;
2808
4810
  }
2809
4811
 
4812
+ function rankIntelligenceSections(sections) {
4813
+ return sections.slice().sort(function (a, b) {
4814
+ return intelligenceSectionPriority(a) - intelligenceSectionPriority(b);
4815
+ });
4816
+ }
4817
+
4818
+ function intelligenceSectionPriority(section) {
4819
+ var title = String(section && section.title || "").toLowerCase();
4820
+ if (title.indexOf("blast") !== -1 || title.indexOf("risk") !== -1) return 0;
4821
+ if (title.indexOf("onboarding") !== -1 || title.indexOf("decision") !== -1) return 1;
4822
+ if (title.indexOf("module") !== -1 || title.indexOf("health") !== -1) return 2;
4823
+ if (title.indexOf("owner") !== -1 || title.indexOf("contributor") !== -1) return 3;
4824
+ return 4;
4825
+ }
4826
+
2810
4827
  function buildIntelligenceSections(reports) {
2811
4828
  var sections = [];
2812
4829
  var contributors = reports.contributors;
@@ -2846,7 +4863,7 @@
2846
4863
  title: "Ownership Map",
2847
4864
  kicker: "who owns what",
2848
4865
  stat: silos.length + " silos",
2849
- summary: "Repowise has a dedicated ownership page. Kage now surfaces the same reviewer-critical signal inside the memory viewer, tied back to selectable files.",
4866
+ summary: "Action: assign backup reviewers for silo files before risky changes.",
2850
4867
  rows: rows,
2851
4868
  limit: 10,
2852
4869
  });
@@ -2861,7 +4878,7 @@
2861
4878
  title: "Module Health Map",
2862
4879
  kicker: "churn / tests / ownership",
2863
4880
  stat: modules.length + " modules",
2864
- summary: "Lowest-scoring modules are shown first so the viewer points people toward the riskiest areas instead of only drawing nodes.",
4881
+ summary: "Action: start cleanup and test planning from the lowest-score modules.",
2865
4882
  rows: modules.slice(0, 8).map(function (item) {
2866
4883
  return {
2867
4884
  label: item.module,
@@ -2880,7 +4897,7 @@
2880
4897
  title: "Onboarding Targets",
2881
4898
  kicker: "missing repo lore",
2882
4899
  stat: (decisions.coverage_percent != null ? decisions.coverage_percent + "%" : "n/a"),
2883
- summary: "Files with centrality, churn, test gaps, or ownership but no linked why-memory. These are the places a future agent is most likely to rediscover context.",
4900
+ summary: "Action: capture why-memory for these files before the next agent works there.",
2884
4901
  rows: gaps.slice(0, 8).map(function (gap) {
2885
4902
  var score = Math.min(100, Number(gap.dependents || 0) * 18 + Number(gap.churn_90d || 0) * 6 + 12);
2886
4903
  return {
@@ -2906,7 +4923,7 @@
2906
4923
  title: "Architecture Communities",
2907
4924
  kicker: "module clusters",
2908
4925
  stat: communities.length + " clusters",
2909
- summary: "Repowise exposes architecture/community views. Kage can show the same high-level clusters next to the memory-code graph so users know what a dense graph means.",
4926
+ summary: "Action: use clusters to understand the graph before changing architecture.",
2910
4927
  rows: communities.slice(0, 8).map(function (community) {
2911
4928
  var files = community.files || [];
2912
4929
  var entrypoints = community.entrypoints || [];
@@ -2928,7 +4945,7 @@
2928
4945
  title: "Execution Flows",
2929
4946
  kicker: "entrypoint traces",
2930
4947
  stat: insights.entry_flows.length + " flows",
2931
- summary: "Short traces make the code graph explainable: where execution starts, what it crosses, and which file to inspect first.",
4948
+ summary: "Action: inspect entry files first when debugging runtime behavior.",
2932
4949
  rows: insights.entry_flows.slice(0, 8).map(function (flow) {
2933
4950
  var path = flow.path || [];
2934
4951
  return {
@@ -2987,7 +5004,7 @@
2987
5004
  title: "Workspace Map",
2988
5005
  kicker: "deps / contracts / co-changes",
2989
5006
  stat: workspaceRows.length + " links",
2990
- summary: "Workspace links show how sibling repos relate through package dependencies, source-evidence contracts, topic/event links, and local git co-change history.",
5007
+ summary: "Action: check linked repos before changing shared packages or contracts.",
2991
5008
  rows: workspaceRows,
2992
5009
  limit: 14,
2993
5010
  });
@@ -3021,7 +5038,7 @@
3021
5038
  title: "Blast Radius",
3022
5039
  kicker: "change impact",
3023
5040
  stat: riskRows.length + " signals",
3024
- summary: "Change risk is useful only if it is browsable. Rows focus the graph on affected files when the graph node exists.",
5041
+ summary: "Action: review tests, owners, and dependents before editing these targets.",
3025
5042
  rows: riskRows,
3026
5043
  limit: 10,
3027
5044
  });
@@ -3043,11 +5060,13 @@
3043
5060
  });
3044
5061
  if (!found) {
3045
5062
  els.searchInput.value = normalized;
5063
+ showViewerPageInPlace("graph");
3046
5064
  scheduleRender();
3047
5065
  return;
3048
5066
  }
3049
5067
  selectEntity(found.id, true);
3050
5068
  els.searchInput.value = normalized;
5069
+ showViewerPageInPlace("graph");
3051
5070
  scheduleRender();
3052
5071
  }
3053
5072
 
@@ -3197,30 +5216,111 @@
3197
5216
  var benchmark = reports.benchmark;
3198
5217
  if (benchmark) {
3199
5218
  var checks = Array.isArray(benchmark.checks) ? benchmark.checks : [];
5219
+ var retrieval = benchmarkRetrievalSummary(benchmark);
5220
+ var scale = benchmarkScaleSummary(benchmark);
3200
5221
  cards.push({
3201
- title: "Benchmark",
3202
- kicker: "local proof",
3203
- summary: benchmark.summary || "Local memory and graph benchmark signals.",
3204
- rows: checks.slice(0, 5).map(function (item) {
3205
- return [item.name || "check", (item.pass ? "pass" : "check") + " - " + item.actual + "/" + item.target];
3206
- })
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
+ })
3207
5239
  });
3208
5240
  }
3209
5241
  return cards;
3210
5242
  }
3211
5243
 
5244
+ function normalizeIntelCards(cards) {
5245
+ return cards.map(function (card) {
5246
+ var normalized = Object.assign({}, card);
5247
+ var row = function (label) {
5248
+ var found = (card.rows || []).find(function (item) { return item[0] === label; });
5249
+ return found ? found[1] : null;
5250
+ };
5251
+ if (card.title === "Memory-Code Bridge") {
5252
+ normalized.metric = row("Links") || "0";
5253
+ normalized.metricLabel = "memory-code links";
5254
+ normalized.highlight = "Shows whether saved repo knowledge is tied to actual files, symbols, routes, and tests.";
5255
+ normalized.action = "If this is low, capture memory with concrete paths so agents can recall it during edits.";
5256
+ } else if (card.title === "Change Risk") {
5257
+ var siloText = row("Silos");
5258
+ var siloMatch = siloText && String(siloText).match(/\d+/);
5259
+ normalized.metric = siloMatch ? siloMatch[0] + " silos" : ((card.rows || []).length + " signals");
5260
+ normalized.metricLabel = "risk signals";
5261
+ normalized.highlight = "Flags files with blast radius, test gaps, or ownership concentration.";
5262
+ normalized.action = "Use these rows to pick tests and reviewers before touching risky files.";
5263
+ } else if (card.title === "Contributors") {
5264
+ normalized.metric = (card.rows || []).length + " profiles";
5265
+ normalized.metricLabel = "review routing";
5266
+ normalized.highlight = "Shows who recently touched or owns parts of the repo.";
5267
+ normalized.action = "Use this to find backup reviewers and avoid single-person knowledge bottlenecks.";
5268
+ } else if (card.title === "Decision Memory") {
5269
+ normalized.metric = row("Coverage") || "n/a";
5270
+ normalized.metricLabel = "why-memory coverage";
5271
+ normalized.highlight = "Shows whether important code paths have captured rationale and gotchas.";
5272
+ normalized.action = "Add memory for coverage gaps before future agents rediscover the same context.";
5273
+ } else if (card.title === "Module Health") {
5274
+ normalized.metric = (card.rows || []).length + " modules";
5275
+ normalized.metricLabel = "lowest scores first";
5276
+ normalized.highlight = "Ranks modules by churn, tests, ownership, and graph signals.";
5277
+ normalized.action = "Start with low-score modules when planning cleanup or refactors.";
5278
+ } else if (card.title === "Graph Insights") {
5279
+ normalized.metric = row("Cycles") || row("Communities") || "n/a";
5280
+ normalized.metricLabel = row("Cycles") != null ? "dependency cycles" : "architecture clusters";
5281
+ normalized.highlight = "Explains dense graph structure through central files, cycles, and communities.";
5282
+ normalized.action = "Inspect central files and cycles before making architectural changes.";
5283
+ } else if (card.title === "Workspace") {
5284
+ normalized.metric = row("Repos") || "n/a";
5285
+ normalized.metricLabel = "connected repos";
5286
+ normalized.highlight = "Shows package, route, topic, and co-change links across local repos.";
5287
+ normalized.action = "Check these links before changing shared contracts or packages.";
5288
+ } else if (card.title === "Memory Quality") {
5289
+ normalized.metric = row("Evidence") || row("Useful") || "n/a";
5290
+ normalized.metricLabel = "trust signal";
5291
+ normalized.highlight = "Shows whether memory is evidence-backed, grounded, and reviewable.";
5292
+ normalized.action = "Review pending or weak memory before relying on it for agent handoff.";
5293
+ } else if (card.title === "Benchmark") {
5294
+ normalized.metric = (card.rows || []).filter(function (item) { return String(item[1] || "").indexOf("pass") !== -1; }).length + " pass";
5295
+ normalized.metricLabel = "local proof checks";
5296
+ normalized.highlight = "Shows whether repo memory and graph behavior pass local quality checks.";
5297
+ normalized.action = "Use failed checks as release blockers or cleanup targets.";
5298
+ }
5299
+ return normalized;
5300
+ });
5301
+ }
5302
+
5303
+ function trimIntelText(value, limit) {
5304
+ var text = String(value == null ? "" : value);
5305
+ var max = Number(limit || 90);
5306
+ if (text.length <= max) return text;
5307
+ return text.slice(0, Math.max(0, max - 1)).replace(/\s+\S*$/, "") + "...";
5308
+ }
5309
+
3212
5310
  function renderStatusStrip(visibleEntities, visibleEdges, official) {
3213
5311
  if (!els.statusStrip) return;
3214
5312
  var memoryCount = visibleEntities.filter(function (entity) { return entity.graph_kind === "memory"; }).length;
3215
5313
  var codeCount = visibleEntities.filter(function (entity) { return entity.graph_kind === "code"; }).length;
3216
5314
  var reviewFlags = visibleEdges.filter(function (edge) { return reviewStatus(edge) !== "ok"; }).length;
5315
+ var memoryCodeLinks = state.edges.filter(isMemoryCodeEdge).length;
5316
+ var pendingReview = official && official.memory_graph ? Number(firstNumber(official.memory_graph.pending_packets, 0)) : 0;
3217
5317
  var pills = official ? [
3218
- ["Readiness", official.harness.readiness_score + "/100", ""],
3219
- ["Pending", official.memory_graph ? String(official.memory_graph.pending_packets) : "n/a", official.memory_graph && official.memory_graph.pending_packets ? "warn" : ""],
3220
- ["Structural", official.structural_index ? official.structural_index.files + " files" : official.code_graph.files + " files", "code"],
3221
- ["Code symbols", String(official.code_graph.symbols), "code"],
3222
- ["Parser coverage", official.code_graph.indexer_coverage_percent + "%", "code"],
3223
- ["Memory packets", official.memory_graph ? String(official.memory_graph.approved_packets) : "n/a", "memory"]
5318
+ ["Status", official.harness && official.harness.validation_ok ? "Clean" : "Check", official.harness && official.harness.validation_ok ? "memory" : "warn"],
5319
+ ["Review", pendingReview ? pendingReview + " pending" : "Clear", pendingReview ? "warn" : "memory"],
5320
+ ["Source map", official.structural_index ? official.structural_index.files + " files" : official.code_graph.files + " files", "code"],
5321
+ ["Symbols", String(official.code_graph.symbols), "code"],
5322
+ ["Coverage", official.code_graph.indexer_coverage_percent + "%", "code"],
5323
+ ["Memory links", String(memoryCodeLinks), memoryCodeLinks ? "memory" : "warn"]
3224
5324
  ] : [
3225
5325
  ["Memory", String(memoryCount), "memory"],
3226
5326
  ["Code", String(codeCount), "code"],
@@ -3473,10 +5573,15 @@
3473
5573
  if (state.three.THREE) return Promise.resolve(state.three.THREE);
3474
5574
  if (state.three.loading) return state.three.loading;
3475
5575
  state.three.failed = false;
3476
- state.three.loading = import("/vendor/three/build/three.module.min.js")
3477
- .catch(function () {
3478
- return import("https://unpkg.com/three@0.184.0/build/three.module.min.js");
3479
- })
5576
+ var threeSources = [
5577
+ "./vendor/three/build/three.module.min.js",
5578
+ "../vendor/three/build/three.module.min.js",
5579
+ "/vendor/three/build/three.module.min.js",
5580
+ "https://unpkg.com/three@0.184.0/build/three.module.min.js"
5581
+ ];
5582
+ state.three.loading = threeSources.reduce(function (chain, source) {
5583
+ return chain.catch(function () { return import(source); });
5584
+ }, Promise.reject())
3480
5585
  .then(function (mod) {
3481
5586
  state.three.THREE = mod;
3482
5587
  return mod;
@@ -3493,7 +5598,7 @@
3493
5598
  ensureThree().then(function () {
3494
5599
  if (activeRenderMode() !== "3d") return;
3495
5600
  setupThreeScene();
3496
- rebuildThreeScene();
5601
+ if (graphChanged || !state.three.nodeById.size) rebuildThreeScene();
3497
5602
  if (graphChanged || state.three.distance <= 0) fitThreeGraph();
3498
5603
  startThreeGraph();
3499
5604
  renderThreeFrame();