@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/README.md +157 -135
- package/dist/cli.js +429 -3
- package/dist/daemon.js +314 -7
- package/dist/index.js +329 -3
- package/dist/kernel.js +3255 -64
- package/package.json +1 -1
- package/viewer/app.js +2421 -316
- package/viewer/data.html +296 -0
- package/viewer/graph.html +296 -0
- package/viewer/index.html +170 -165
- package/viewer/intel.html +296 -0
- package/viewer/memory.html +367 -0
- package/viewer/owners.html +296 -0
- package/viewer/review.html +307 -0
- package/viewer/styles.css +1581 -371
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
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
els.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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",
|
|
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
|
-
|
|
343
|
+
state.graphActionFilter = "";
|
|
298
344
|
clearDependencyPath(false);
|
|
299
345
|
state.lastVisibleSignature = "";
|
|
300
346
|
render();
|
|
301
|
-
}
|
|
347
|
+
}
|
|
302
348
|
loadFromUrlParams();
|
|
303
349
|
|
|
304
|
-
function
|
|
305
|
-
var
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
562
|
-
state.reports.
|
|
563
|
-
state.reports.
|
|
564
|
-
state.reports.
|
|
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
|
-
|
|
1305
|
-
|
|
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.
|
|
1832
|
-
gradient.addColorStop(0.48, "rgba(65,255,143,0.
|
|
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 ?
|
|
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.
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
["
|
|
2480
|
-
["
|
|
2481
|
-
["
|
|
2482
|
-
["
|
|
2483
|
-
["
|
|
2484
|
-
["
|
|
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
|
-
|
|
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
|
-
["
|
|
2524
|
-
["
|
|
2525
|
-
["
|
|
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><
|
|
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
|
-
["
|
|
2542
|
-
["
|
|
2543
|
-
["
|
|
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
|
-
["
|
|
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
|
-
["
|
|
2554
|
-
["
|
|
2555
|
-
["
|
|
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
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
["
|
|
2562
|
-
["
|
|
2563
|
-
["
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
["
|
|
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
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
var
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
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
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
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
|
|
2606
|
-
|
|
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
|
|
2610
|
-
if (
|
|
2611
|
-
|
|
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
|
|
2615
|
-
if (!
|
|
2616
|
-
var
|
|
2617
|
-
var
|
|
2618
|
-
var
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
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
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
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 { "&": "&", "<": "<", ">": ">", "\"": """, "'": "'" }[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 =
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
["
|
|
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
|
|
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
|
-
|
|
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-
|
|
2738
|
-
"<
|
|
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-
|
|
2743
|
-
item.querySelector(".intel-
|
|
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,
|
|
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
|
|
2757
|
-
|
|
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
|
-
|
|
4761
|
+
deepGrid.appendChild(renderIntelligenceSection(section));
|
|
2760
4762
|
});
|
|
2761
|
-
|
|
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
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
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
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
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
|
-
|
|
2798
|
-
|
|
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(
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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:
|
|
3204
|
-
|
|
3205
|
-
|
|
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
|
-
["
|
|
3219
|
-
["
|
|
3220
|
-
["
|
|
3221
|
-
["
|
|
3222
|
-
["
|
|
3223
|
-
["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
|
-
|
|
3477
|
-
.
|
|
3478
|
-
|
|
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();
|