@kage-core/kage-graph-mcp 1.1.35 → 1.1.36
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 +56 -133
- package/package.json +1 -1
- package/viewer/app.js +1158 -262
- package/viewer/data.html +303 -0
- package/viewer/graph.html +303 -0
- package/viewer/index.html +177 -165
- package/viewer/intel.html +303 -0
- package/viewer/memory.html +303 -0
- package/viewer/owners.html +303 -0
- package/viewer/review.html +303 -0
- package/viewer/styles.css +1026 -295
package/viewer/app.js
CHANGED
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
visibleEntityIds: new Set(),
|
|
14
14
|
visibleEdgeIds: new Set(),
|
|
15
15
|
selected: null,
|
|
16
|
+
viewerPage: "overview",
|
|
16
17
|
viewerSection: "overview",
|
|
17
18
|
viewerAction: null,
|
|
18
|
-
|
|
19
|
-
workspaceOpen: false,
|
|
19
|
+
graphActionFilter: "",
|
|
20
20
|
pathHighlight: {
|
|
21
21
|
nodes: new Set(),
|
|
22
22
|
edges: new Set(),
|
|
@@ -140,6 +140,9 @@
|
|
|
140
140
|
renderMode: document.getElementById("renderMode"),
|
|
141
141
|
typeFilter: document.getElementById("typeFilter"),
|
|
142
142
|
relationFilter: document.getElementById("relationFilter"),
|
|
143
|
+
showUntrusted: document.getElementById("showUntrusted"),
|
|
144
|
+
showUncovered: document.getElementById("showUncovered"),
|
|
145
|
+
showMemoryCode: document.getElementById("showMemoryCode"),
|
|
143
146
|
scopeFilter: document.getElementById("scopeFilter"),
|
|
144
147
|
maxNodes: document.getElementById("maxNodes"),
|
|
145
148
|
showDependencies: document.getElementById("showDependencies"),
|
|
@@ -160,27 +163,75 @@
|
|
|
160
163
|
entityList: document.getElementById("entityList"),
|
|
161
164
|
edgeList: document.getElementById("edgeList"),
|
|
162
165
|
metricsSummary: document.getElementById("metricsSummary"),
|
|
166
|
+
graphInsightStatus: document.getElementById("graphInsightStatus"),
|
|
167
|
+
graphInsights: document.getElementById("graphInsights"),
|
|
163
168
|
entityCount: document.getElementById("entityCount"),
|
|
164
169
|
edgeCount: document.getElementById("edgeCount"),
|
|
165
170
|
reviewCount: document.getElementById("reviewCount"),
|
|
166
171
|
dashboardStats: document.getElementById("dashboardStats"),
|
|
172
|
+
dashboardCharts: document.getElementById("dashboardCharts"),
|
|
173
|
+
memoryStatus: document.getElementById("memoryStatus"),
|
|
174
|
+
memoryStats: document.getElementById("memoryStats"),
|
|
175
|
+
memoryOverview: document.getElementById("memoryOverview"),
|
|
176
|
+
memorySearch: document.getElementById("memorySearch"),
|
|
177
|
+
memoryFilter: document.getElementById("memoryFilter"),
|
|
178
|
+
memoryList: document.getElementById("memoryList"),
|
|
179
|
+
ownersStatus: document.getElementById("ownersStatus"),
|
|
180
|
+
ownersSummary: document.getElementById("ownersSummary"),
|
|
181
|
+
ownersList: document.getElementById("ownersList"),
|
|
182
|
+
reviewOverview: document.getElementById("reviewOverview"),
|
|
167
183
|
reviewList: document.getElementById("reviewList"),
|
|
184
|
+
proofOverview: document.getElementById("proofOverview"),
|
|
168
185
|
proofStatus: document.getElementById("proofStatus"),
|
|
169
186
|
proofList: document.getElementById("proofList"),
|
|
170
187
|
intelligenceStatus: document.getElementById("intelligenceStatus"),
|
|
171
188
|
intelligenceList: document.getElementById("intelligenceList"),
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
189
|
+
debugOverview: document.getElementById("debugOverview"),
|
|
190
|
+
pageEyebrow: document.getElementById("pageEyebrow"),
|
|
191
|
+
pageTitle: document.getElementById("pageTitle"),
|
|
192
|
+
viewerPageLinks: typeof document.querySelectorAll === "function" ? Array.from(document.querySelectorAll("[data-viewer-page]")) : [],
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
var PAGE_META = {
|
|
196
|
+
overview: {
|
|
197
|
+
eyebrow: "kage://overview",
|
|
198
|
+
title: "Repo dashboard",
|
|
199
|
+
summary: "What is safe to change next, what needs attention, and what is ready to hand off."
|
|
200
|
+
},
|
|
201
|
+
graph: {
|
|
202
|
+
eyebrow: "kage://graph",
|
|
203
|
+
title: "Dependency graph",
|
|
204
|
+
summary: "Search a file or symbol, then follow connected memory, routes, and tests before editing."
|
|
205
|
+
},
|
|
206
|
+
memory: {
|
|
207
|
+
eyebrow: "kage://memory",
|
|
208
|
+
title: "Memory library",
|
|
209
|
+
summary: "Find repo lore by file, feature, bug, command, or decision. Pick a packet to see linked code."
|
|
210
|
+
},
|
|
211
|
+
intel: {
|
|
212
|
+
eyebrow: "kage://risks",
|
|
213
|
+
title: "Risks",
|
|
214
|
+
summary: "Files, owners, and modules to inspect before changes. Each card links into the graph."
|
|
215
|
+
},
|
|
216
|
+
review: {
|
|
217
|
+
eyebrow: "kage://review",
|
|
218
|
+
title: "Review & handoff",
|
|
219
|
+
summary: "Blockers that must clear before another agent or teammate picks up this branch."
|
|
220
|
+
},
|
|
221
|
+
owners: {
|
|
222
|
+
eyebrow: "kage://owners",
|
|
223
|
+
title: "Owners & reviewers",
|
|
224
|
+
summary: "Local git ownership signal. Use it for reviewer routing and bus-factor checks."
|
|
225
|
+
},
|
|
226
|
+
data: {
|
|
227
|
+
eyebrow: "kage://artifacts",
|
|
228
|
+
title: "Artifacts & diagnostics",
|
|
229
|
+
summary: "Raw nodes, relations, and indexing health. Use only when graph or recall looks wrong."
|
|
230
|
+
}
|
|
181
231
|
};
|
|
182
232
|
|
|
183
233
|
var MEMORY_CODE_RELATIONS = new Set(["explains_symbol", "informs_symbol", "fixes_symbol", "applies_to_route", "verified_by_test", "affects_code_path"]);
|
|
234
|
+
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
235
|
var INSPECTOR_CONNECTION_LIMIT = 8;
|
|
185
236
|
var PATH_BRIDGE_EDGE_LIMIT_PER_PATH = 8;
|
|
186
237
|
var PATH_BRIDGE_EDGE_LIMIT_TOTAL = 160;
|
|
@@ -188,82 +239,40 @@
|
|
|
188
239
|
var VISIBLE_EDGE_MIN = 160;
|
|
189
240
|
var VISIBLE_EDGE_MAX = 560;
|
|
190
241
|
|
|
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);
|
|
206
|
-
});
|
|
207
|
-
});
|
|
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();
|
|
242
|
+
state.viewerPage = initialViewerPage();
|
|
243
|
+
applyViewerPage(state.viewerPage);
|
|
244
|
+
els.viewerPageLinks.forEach(function (link) {
|
|
245
|
+
link.addEventListener("click", function (event) {
|
|
246
|
+
var page = link.getAttribute("data-viewer-page") || "overview";
|
|
247
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) return;
|
|
248
|
+
event.preventDefault();
|
|
249
|
+
navigateViewerPage(page);
|
|
224
250
|
});
|
|
225
251
|
});
|
|
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
252
|
els.graphFile.addEventListener("change", handleFile);
|
|
249
253
|
els.searchInput.addEventListener("input", scheduleRender);
|
|
250
254
|
els.findPath.addEventListener("click", findDependencyPath);
|
|
251
255
|
els.clearPath.addEventListener("click", clearDependencyPath);
|
|
252
256
|
els.pathFromInput.addEventListener("keydown", function (event) { if (event.key === "Enter") findDependencyPath(); });
|
|
253
257
|
els.pathToInput.addEventListener("keydown", function (event) { if (event.key === "Enter") findDependencyPath(); });
|
|
254
|
-
els.viewMode.addEventListener("change", render);
|
|
258
|
+
els.viewMode.addEventListener("change", function () { clearGraphActionFilter(); render(); });
|
|
255
259
|
els.renderMode.addEventListener("change", function () {
|
|
256
260
|
state.lastVisibleSignature = "";
|
|
257
261
|
render();
|
|
258
262
|
});
|
|
259
|
-
els.typeFilter.addEventListener("change", render);
|
|
260
|
-
els.relationFilter.addEventListener("change", render);
|
|
263
|
+
els.typeFilter.addEventListener("change", function () { clearGraphActionFilter(); render(); });
|
|
264
|
+
els.relationFilter.addEventListener("change", function () { clearGraphActionFilter(); render(); });
|
|
261
265
|
els.scopeFilter.addEventListener("change", render);
|
|
262
266
|
els.maxNodes.addEventListener("change", render);
|
|
263
267
|
els.showDependencies.addEventListener("change", render);
|
|
268
|
+
if (els.showUntrusted) els.showUntrusted.addEventListener("click", function () { applyGraphActionFilter("untrusted"); });
|
|
269
|
+
if (els.showUncovered) els.showUncovered.addEventListener("click", function () { applyGraphActionFilter("uncovered"); });
|
|
270
|
+
if (els.showMemoryCode) els.showMemoryCode.addEventListener("click", function () { applyGraphActionFilter("memory-code"); });
|
|
264
271
|
els.zoomOut.addEventListener("click", function () { zoomGraph(0.82); });
|
|
265
272
|
els.zoomIn.addEventListener("click", function () { zoomGraph(1.22); });
|
|
266
273
|
els.fitView.addEventListener("click", fitActiveGraph);
|
|
274
|
+
if (els.memorySearch) els.memorySearch.addEventListener("input", renderMemoryLibrary);
|
|
275
|
+
if (els.memoryFilter) els.memoryFilter.addEventListener("change", renderMemoryLibrary);
|
|
267
276
|
els.canvas.addEventListener("mousedown", startCanvasPointer);
|
|
268
277
|
els.canvas.addEventListener("mousemove", moveCanvasPointer);
|
|
269
278
|
els.canvas.addEventListener("mouseup", endCanvasPointer);
|
|
@@ -284,7 +293,9 @@
|
|
|
284
293
|
window.addEventListener("resize", function () {
|
|
285
294
|
resizeActiveGraph();
|
|
286
295
|
});
|
|
287
|
-
els.resetView.addEventListener("click",
|
|
296
|
+
els.resetView.addEventListener("click", resetGraphView);
|
|
297
|
+
|
|
298
|
+
function resetGraphView() {
|
|
288
299
|
els.searchInput.value = "";
|
|
289
300
|
els.viewMode.value = "combined";
|
|
290
301
|
els.renderMode.value = "2d";
|
|
@@ -294,80 +305,160 @@
|
|
|
294
305
|
els.maxNodes.value = "90";
|
|
295
306
|
els.showDependencies.checked = false;
|
|
296
307
|
state.selected = null;
|
|
297
|
-
|
|
308
|
+
state.graphActionFilter = "";
|
|
298
309
|
clearDependencyPath(false);
|
|
299
310
|
state.lastVisibleSignature = "";
|
|
300
311
|
render();
|
|
301
|
-
}
|
|
312
|
+
}
|
|
302
313
|
loadFromUrlParams();
|
|
303
314
|
|
|
304
|
-
function
|
|
305
|
-
var
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
315
|
+
function initialViewerPage() {
|
|
316
|
+
var fileName = "";
|
|
317
|
+
try {
|
|
318
|
+
fileName = String(window.location.pathname || "").split("/").pop() || "index.html";
|
|
319
|
+
} catch (_error) {
|
|
320
|
+
fileName = "index.html";
|
|
321
|
+
}
|
|
322
|
+
var pageByFile = {
|
|
323
|
+
"index.html": "overview",
|
|
324
|
+
"": "overview",
|
|
325
|
+
"graph.html": "graph",
|
|
326
|
+
"memory.html": "memory",
|
|
327
|
+
"owners.html": "owners",
|
|
328
|
+
"intel.html": "intel",
|
|
329
|
+
"review.html": "review",
|
|
330
|
+
"data.html": "data"
|
|
331
|
+
};
|
|
332
|
+
return pageByFile[fileName] || "overview";
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function applyViewerPage(page, updateLinks) {
|
|
336
|
+
var normalized = normalizeViewerPage(page);
|
|
337
|
+
state.viewerPage = normalized;
|
|
338
|
+
if (normalized === "overview") {
|
|
339
|
+
setViewerSection("overview");
|
|
340
|
+
} else if (normalized === "graph") {
|
|
341
|
+
setViewerSection("graph");
|
|
342
|
+
} else {
|
|
343
|
+
setViewerSection("graph", pageToAction(normalized));
|
|
344
|
+
}
|
|
345
|
+
state.viewerPage = normalized;
|
|
346
|
+
applyPageHeader(normalized);
|
|
347
|
+
if (updateLinks !== false) syncViewerPageLinks();
|
|
348
|
+
syncViewerPageClass();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function applyPageHeader(page) {
|
|
352
|
+
var meta = PAGE_META[page] || PAGE_META.overview;
|
|
353
|
+
if (els.pageEyebrow) els.pageEyebrow.textContent = meta.eyebrow;
|
|
354
|
+
if (els.pageTitle) els.pageTitle.textContent = meta.title;
|
|
355
|
+
if (els.graphSummary && !state.graph) els.graphSummary.textContent = meta.summary;
|
|
356
|
+
try {
|
|
357
|
+
if (typeof document !== "undefined" && document.title !== undefined) {
|
|
358
|
+
document.title = "Kage " + meta.title.toLowerCase() + " viewer";
|
|
359
|
+
}
|
|
360
|
+
} catch (_error) {
|
|
361
|
+
// ignore
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function normalizeViewerPage(page) {
|
|
366
|
+
var normalized = String(page || "overview").toLowerCase();
|
|
367
|
+
if (normalized === "intelligence") normalized = "intel";
|
|
368
|
+
if (["overview", "graph", "memory", "owners", "intel", "review", "data"].indexOf(normalized) === -1) return "overview";
|
|
369
|
+
return normalized;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function pageToAction(page) {
|
|
373
|
+
if (page === "intel") return "intelligence";
|
|
374
|
+
if (page === "owners") return "intelligence";
|
|
375
|
+
if (page === "memory") return "memory";
|
|
376
|
+
if (page === "review") return "review";
|
|
377
|
+
if (page === "data") return "data";
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function pageFromSection(section, action) {
|
|
382
|
+
if (section === "overview") return "overview";
|
|
383
|
+
if (action === "intelligence") return "intel";
|
|
384
|
+
if (action === "memory") return "memory";
|
|
385
|
+
if (action === "review") return "review";
|
|
386
|
+
if (action === "data") return "data";
|
|
387
|
+
return "graph";
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function viewerPageHref(page) {
|
|
391
|
+
var fileByPage = {
|
|
392
|
+
overview: "./",
|
|
393
|
+
graph: "./graph.html",
|
|
394
|
+
memory: "./memory.html",
|
|
395
|
+
owners: "./owners.html",
|
|
396
|
+
intel: "./intel.html",
|
|
397
|
+
review: "./review.html",
|
|
398
|
+
data: "./data.html"
|
|
399
|
+
};
|
|
400
|
+
var search = "";
|
|
401
|
+
try {
|
|
402
|
+
search = window.location.search || "";
|
|
403
|
+
} catch (_error) {
|
|
404
|
+
search = "";
|
|
405
|
+
}
|
|
406
|
+
return (fileByPage[normalizeViewerPage(page)] || "./") + search;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function navigateViewerPage(page) {
|
|
410
|
+
window.location.href = viewerPageHref(page);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function showViewerPageInPlace(page) {
|
|
414
|
+
applyViewerPage(page);
|
|
415
|
+
try {
|
|
416
|
+
window.history.pushState({}, "", viewerPageHref(page));
|
|
417
|
+
} catch (_error) {
|
|
418
|
+
// Static/file viewers can ignore history failures; visual state is enough.
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function syncViewerPageLinks() {
|
|
423
|
+
els.viewerPageLinks.forEach(function (link) {
|
|
424
|
+
var page = normalizeViewerPage(link.getAttribute("data-viewer-page"));
|
|
425
|
+
link.setAttribute("href", viewerPageHref(page));
|
|
426
|
+
var active = page === state.viewerPage;
|
|
427
|
+
link.classList.toggle("active", active);
|
|
428
|
+
if (active) link.setAttribute("aria-current", "page");
|
|
429
|
+
else link.removeAttribute("aria-current");
|
|
323
430
|
});
|
|
324
431
|
}
|
|
325
432
|
|
|
433
|
+
function syncViewerPageClass() {
|
|
434
|
+
if (!document.body || !document.body.classList) return;
|
|
435
|
+
document.body.classList.remove(
|
|
436
|
+
"viewer-page-overview",
|
|
437
|
+
"viewer-page-graph",
|
|
438
|
+
"viewer-page-memory",
|
|
439
|
+
"viewer-page-owners",
|
|
440
|
+
"viewer-page-intel",
|
|
441
|
+
"viewer-page-review",
|
|
442
|
+
"viewer-page-data"
|
|
443
|
+
);
|
|
444
|
+
document.body.classList.add("viewer-page-" + normalizeViewerPage(state.viewerPage));
|
|
445
|
+
}
|
|
446
|
+
|
|
326
447
|
function setViewerSection(section, action) {
|
|
327
448
|
state.viewerSection = section === "graph" ? "graph" : "overview";
|
|
328
449
|
state.viewerAction = action || null;
|
|
450
|
+
state.viewerPage = pageFromSection(state.viewerSection, state.viewerAction);
|
|
329
451
|
if (state.viewerSection === "overview") closeWorkspace();
|
|
330
452
|
if (document.body && document.body.classList) {
|
|
331
453
|
document.body.classList.remove("viewer-section-overview", "viewer-section-graph");
|
|
332
454
|
document.body.classList.add("viewer-section-" + state.viewerSection);
|
|
333
455
|
}
|
|
334
|
-
|
|
456
|
+
syncViewerPageLinks();
|
|
457
|
+
syncViewerPageClass();
|
|
335
458
|
if (state.viewerSection === "graph") resizeActiveGraph();
|
|
336
459
|
}
|
|
337
460
|
|
|
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
461
|
function closeWorkspace() {
|
|
370
|
-
state.workspaceOpen = false;
|
|
371
462
|
if (document.body && document.body.classList) {
|
|
372
463
|
document.body.classList.remove("viewer-workspace-open");
|
|
373
464
|
}
|
|
@@ -375,27 +466,10 @@
|
|
|
375
466
|
|
|
376
467
|
function selectEntity(id, openInspector) {
|
|
377
468
|
state.selected = { kind: "entity", id: id };
|
|
378
|
-
setViewerSection("graph");
|
|
379
|
-
if (openInspector) setWorkspaceTab("inspector", true);
|
|
380
469
|
}
|
|
381
470
|
|
|
382
471
|
function selectEdge(id, openInspector) {
|
|
383
472
|
state.selected = { kind: "edge", id: id };
|
|
384
|
-
setViewerSection("graph");
|
|
385
|
-
if (openInspector) setWorkspaceTab("inspector", true);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
function syncQuickControls() {
|
|
389
|
-
els.quickViewButtons.forEach(function (button) {
|
|
390
|
-
var active = button.getAttribute("data-quick-view") === els.viewMode.value;
|
|
391
|
-
button.classList.toggle("active", active);
|
|
392
|
-
button.setAttribute("aria-pressed", active ? "true" : "false");
|
|
393
|
-
});
|
|
394
|
-
els.quickRenderButtons.forEach(function (button) {
|
|
395
|
-
var active = button.getAttribute("data-quick-render") === els.renderMode.value;
|
|
396
|
-
button.classList.toggle("active", active);
|
|
397
|
-
button.setAttribute("aria-pressed", active ? "true" : "false");
|
|
398
|
-
});
|
|
399
473
|
}
|
|
400
474
|
|
|
401
475
|
function handleFile(event) {
|
|
@@ -411,7 +485,8 @@
|
|
|
411
485
|
state.entityById = new Map();
|
|
412
486
|
state.episodesById = new Map();
|
|
413
487
|
els.emptyState.classList.add("hidden");
|
|
414
|
-
|
|
488
|
+
var pageMeta = PAGE_META[state.viewerPage] || PAGE_META.overview;
|
|
489
|
+
els.graphSummary.textContent = state.viewerPage === "graph" ? "Metrics loaded." : pageMeta.summary;
|
|
415
490
|
renderMetrics();
|
|
416
491
|
return;
|
|
417
492
|
}
|
|
@@ -452,7 +527,10 @@
|
|
|
452
527
|
populateFilters();
|
|
453
528
|
populatePathOptions();
|
|
454
529
|
els.emptyState.classList.add("hidden");
|
|
455
|
-
|
|
530
|
+
var meta = PAGE_META[state.viewerPage] || PAGE_META.overview;
|
|
531
|
+
els.graphSummary.textContent = state.viewerPage === "graph"
|
|
532
|
+
? fileName + " loaded: " + entities.length + " nodes, " + edges.length + " relations."
|
|
533
|
+
: meta.summary;
|
|
456
534
|
render();
|
|
457
535
|
}
|
|
458
536
|
|
|
@@ -1246,7 +1324,6 @@
|
|
|
1246
1324
|
|
|
1247
1325
|
function render() {
|
|
1248
1326
|
if (!state.graph) return;
|
|
1249
|
-
syncQuickControls();
|
|
1250
1327
|
|
|
1251
1328
|
var query = parseSearchQuery(els.searchInput.value);
|
|
1252
1329
|
state.renderQuery = query;
|
|
@@ -1283,6 +1360,10 @@
|
|
|
1283
1360
|
matchedEdgeIds = new Set(state.edges.filter(function (edge) { return mode === "combined" || edge.graph_kind === mode; }).map(function (edge) { return edge.id; }));
|
|
1284
1361
|
}
|
|
1285
1362
|
|
|
1363
|
+
var actionFiltered = applyMatchedGraphActionFilter(matchedEntityIds, matchedEdgeIds);
|
|
1364
|
+
matchedEntityIds = actionFiltered.entities;
|
|
1365
|
+
matchedEdgeIds = actionFiltered.edges;
|
|
1366
|
+
|
|
1286
1367
|
var visible = refineVisibleGraph(matchedEntityIds, matchedEdgeIds, {
|
|
1287
1368
|
query: query,
|
|
1288
1369
|
type: type,
|
|
@@ -1301,12 +1382,7 @@
|
|
|
1301
1382
|
state.lastVisibleSignature = nextSignature;
|
|
1302
1383
|
|
|
1303
1384
|
renderActiveGraph(graphChanged);
|
|
1304
|
-
|
|
1305
|
-
renderDetails();
|
|
1306
|
-
renderMetrics();
|
|
1307
|
-
renderReviewQueue();
|
|
1308
|
-
renderProof();
|
|
1309
|
-
renderIntelligence();
|
|
1385
|
+
renderPagePanels();
|
|
1310
1386
|
}
|
|
1311
1387
|
|
|
1312
1388
|
function scheduleRender() {
|
|
@@ -1317,6 +1393,88 @@
|
|
|
1317
1393
|
});
|
|
1318
1394
|
}
|
|
1319
1395
|
|
|
1396
|
+
function renderPagePanels() {
|
|
1397
|
+
if (state.viewerPage === "graph") {
|
|
1398
|
+
renderDetails();
|
|
1399
|
+
renderMetrics();
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
if (state.viewerPage === "memory") {
|
|
1403
|
+
renderDetails();
|
|
1404
|
+
renderMemoryLibrary();
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
if (state.viewerPage === "owners") {
|
|
1408
|
+
renderOwners();
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
if (state.viewerPage === "intel") {
|
|
1412
|
+
renderIntelligence();
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
if (state.viewerPage === "review") {
|
|
1416
|
+
renderReviewQueue();
|
|
1417
|
+
renderProof();
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
if (state.viewerPage === "data") {
|
|
1421
|
+
renderDetails();
|
|
1422
|
+
renderArtifactDiagnostics(state.entities, state.edges);
|
|
1423
|
+
renderLists();
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
renderDashboard();
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
function applyGraphActionFilter(filter) {
|
|
1430
|
+
state.graphActionFilter = state.graphActionFilter === filter ? "" : filter;
|
|
1431
|
+
if (filter === "memory-code") {
|
|
1432
|
+
els.viewMode.value = "combined";
|
|
1433
|
+
els.relationFilter.value = "__memory_code__";
|
|
1434
|
+
} else if (state.graphActionFilter) {
|
|
1435
|
+
els.viewMode.value = "combined";
|
|
1436
|
+
els.relationFilter.value = "";
|
|
1437
|
+
}
|
|
1438
|
+
state.lastVisibleSignature = "";
|
|
1439
|
+
render();
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
function clearGraphActionFilter() {
|
|
1443
|
+
state.graphActionFilter = "";
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
function applyMatchedGraphActionFilter(entityIds, edgeIds) {
|
|
1447
|
+
if (!state.graphActionFilter) return { entities: entityIds, edges: edgeIds };
|
|
1448
|
+
if (state.graphActionFilter === "memory-code") {
|
|
1449
|
+
var memoryCodeEdges = state.edges.filter(isMemoryCodeEdge);
|
|
1450
|
+
return entitiesForEdges(memoryCodeEdges);
|
|
1451
|
+
}
|
|
1452
|
+
if (state.graphActionFilter === "untrusted") {
|
|
1453
|
+
var flagged = state.edges.filter(function (edge) { return reviewStatus(edge) !== "ok"; });
|
|
1454
|
+
return entitiesForEdges(flagged);
|
|
1455
|
+
}
|
|
1456
|
+
if (state.graphActionFilter === "uncovered") {
|
|
1457
|
+
var covered = memoryLinkedCodeKeys();
|
|
1458
|
+
var uncovered = state.entities.filter(function (entity) {
|
|
1459
|
+
return entity.graph_kind === "code" && entity.type === "file" && !covered.has(codeCoverageKey(entity));
|
|
1460
|
+
});
|
|
1461
|
+
var entities = new Set(uncovered.map(function (entity) { return entity.id; }));
|
|
1462
|
+
return { entities: entities, edges: edgesWithVisibleEndpoints(new Set(state.edges.map(function (edge) { return edge.id; })), entities) };
|
|
1463
|
+
}
|
|
1464
|
+
return { entities: entityIds, edges: edgeIds };
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
function entitiesForEdges(edges) {
|
|
1468
|
+
var entities = new Set();
|
|
1469
|
+
var edgeIds = new Set();
|
|
1470
|
+
edges.forEach(function (edge) {
|
|
1471
|
+
edgeIds.add(edge.id);
|
|
1472
|
+
if (state.entityById.has(edge.from)) entities.add(edge.from);
|
|
1473
|
+
if (state.entityById.has(edge.to)) entities.add(edge.to);
|
|
1474
|
+
});
|
|
1475
|
+
return { entities: entities, edges: edgeIds };
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1320
1478
|
function refineVisibleGraph(entityIds, edgeIds, options) {
|
|
1321
1479
|
var entities = new Set(entityIds);
|
|
1322
1480
|
var edges = new Set(edgeIds);
|
|
@@ -1828,8 +1986,8 @@
|
|
|
1828
1986
|
ctx.fillStyle = graphPalette.background;
|
|
1829
1987
|
ctx.fillRect(0, 0, width, height);
|
|
1830
1988
|
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.
|
|
1989
|
+
gradient.addColorStop(0, "rgba(65,255,143,0.145)");
|
|
1990
|
+
gradient.addColorStop(0.48, "rgba(65,255,143,0.030)");
|
|
1833
1991
|
gradient.addColorStop(1, "rgba(2,5,3,0)");
|
|
1834
1992
|
ctx.fillStyle = gradient;
|
|
1835
1993
|
ctx.fillRect(0, 0, width, height);
|
|
@@ -1905,9 +2063,9 @@
|
|
|
1905
2063
|
var color = nodeThemeColor(entity);
|
|
1906
2064
|
ctx.save();
|
|
1907
2065
|
ctx.globalAlpha = alpha;
|
|
1908
|
-
if (selected || hovered || pathNode) {
|
|
2066
|
+
if (selected || hovered || pathNode || entity.graph_kind === "memory") {
|
|
1909
2067
|
ctx.shadowColor = pathNode ? graphPalette.bridge : color;
|
|
1910
|
-
ctx.shadowBlur = selected ?
|
|
2068
|
+
ctx.shadowBlur = selected ? 16 : pathNode ? 14 : entity.graph_kind === "memory" ? 9 : 10;
|
|
1911
2069
|
}
|
|
1912
2070
|
drawNodeShape(ctx, node.x, node.y, node.r, entity);
|
|
1913
2071
|
ctx.fillStyle = nodeFillColor(entity);
|
|
@@ -2150,12 +2308,16 @@
|
|
|
2150
2308
|
}
|
|
2151
2309
|
|
|
2152
2310
|
function renderLists() {
|
|
2153
|
-
var visibleEntities = state.
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2311
|
+
var visibleEntities = state.viewerPage === "data"
|
|
2312
|
+
? state.entities.slice()
|
|
2313
|
+
: state.entities.filter(function (entity) {
|
|
2314
|
+
return state.visibleEntityIds.has(entity.id);
|
|
2315
|
+
});
|
|
2316
|
+
var visibleEdges = (state.viewerPage === "data"
|
|
2317
|
+
? state.edges.slice()
|
|
2318
|
+
: state.edges.filter(function (edge) {
|
|
2319
|
+
return state.visibleEdgeIds.has(edge.id);
|
|
2320
|
+
})).sort(function (a, b) {
|
|
2159
2321
|
return reviewRank(a) - reviewRank(b);
|
|
2160
2322
|
});
|
|
2161
2323
|
|
|
@@ -2164,7 +2326,11 @@
|
|
|
2164
2326
|
els.entityList.textContent = "";
|
|
2165
2327
|
els.edgeList.textContent = "";
|
|
2166
2328
|
|
|
2167
|
-
|
|
2329
|
+
var rowLimit = state.viewerPage === "data" ? 40 : 80;
|
|
2330
|
+
var entityRows = visibleEntities.slice(0, rowLimit);
|
|
2331
|
+
var edgeRows = visibleEdges.slice(0, rowLimit);
|
|
2332
|
+
|
|
2333
|
+
entityRows.forEach(function (entity) {
|
|
2168
2334
|
var button = document.createElement("button");
|
|
2169
2335
|
button.type = "button";
|
|
2170
2336
|
button.className = classNames("list-item", state.selected && state.selected.kind === "entity" && state.selected.id === entity.id && "selected");
|
|
@@ -2177,8 +2343,11 @@
|
|
|
2177
2343
|
});
|
|
2178
2344
|
els.entityList.appendChild(button);
|
|
2179
2345
|
});
|
|
2346
|
+
if (visibleEntities.length > entityRows.length) {
|
|
2347
|
+
appendListNote(els.entityList, "Showing " + entityRows.length + " of " + visibleEntities.length + ". Use search to narrow.");
|
|
2348
|
+
}
|
|
2180
2349
|
|
|
2181
|
-
|
|
2350
|
+
edgeRows.forEach(function (edge) {
|
|
2182
2351
|
var button = document.createElement("button");
|
|
2183
2352
|
button.type = "button";
|
|
2184
2353
|
button.className = classNames("list-item", state.selected && state.selected.kind === "edge" && state.selected.id === edge.id && "selected");
|
|
@@ -2191,13 +2360,25 @@
|
|
|
2191
2360
|
});
|
|
2192
2361
|
els.edgeList.appendChild(button);
|
|
2193
2362
|
});
|
|
2363
|
+
if (visibleEdges.length > edgeRows.length) {
|
|
2364
|
+
appendListNote(els.edgeList, "Showing " + edgeRows.length + " of " + visibleEdges.length + ". Filter by relation or select a node first.");
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
function appendListNote(parent, text) {
|
|
2369
|
+
var note = document.createElement("div");
|
|
2370
|
+
note.className = "list-note";
|
|
2371
|
+
note.textContent = text;
|
|
2372
|
+
parent.appendChild(note);
|
|
2194
2373
|
}
|
|
2195
2374
|
|
|
2196
2375
|
function renderDetails() {
|
|
2197
2376
|
if (!state.selected) {
|
|
2377
|
+
setSelectionBodyState(null);
|
|
2198
2378
|
els.selectionDetails.className = "details-empty";
|
|
2199
|
-
els.selectionDetails.textContent = "Select
|
|
2379
|
+
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
2380
|
els.selectionStatus.textContent = "No selection";
|
|
2381
|
+
if (!state.pathHighlight.steps.length) setPathStatus("Select a code node, then trace to another code node when you need impact proof.", "");
|
|
2201
2382
|
return;
|
|
2202
2383
|
}
|
|
2203
2384
|
|
|
@@ -2206,10 +2387,12 @@
|
|
|
2206
2387
|
: state.edges.find(function (edge) { return edge.id === state.selected.id; });
|
|
2207
2388
|
|
|
2208
2389
|
if (!item) {
|
|
2390
|
+
setSelectionBodyState(null);
|
|
2209
2391
|
els.selectionDetails.textContent = "Selection no longer exists.";
|
|
2210
2392
|
return;
|
|
2211
2393
|
}
|
|
2212
2394
|
|
|
2395
|
+
setSelectionBodyState(state.selected.kind === "entity" ? item : null);
|
|
2213
2396
|
els.selectionDetails.className = "";
|
|
2214
2397
|
els.selectionDetails.textContent = "";
|
|
2215
2398
|
var title = document.createElement("div");
|
|
@@ -2246,6 +2429,32 @@
|
|
|
2246
2429
|
els.selectionDetails.appendChild(kind);
|
|
2247
2430
|
els.selectionDetails.appendChild(rows);
|
|
2248
2431
|
renderInspectorConnections(item);
|
|
2432
|
+
if (state.selected.kind === "entity" && item.graph_kind === "code") prefillPathFromSelection(true);
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
function setSelectionBodyState(entity) {
|
|
2436
|
+
if (!document.body || !document.body.classList) return;
|
|
2437
|
+
document.body.classList.toggle("has-selection", Boolean(state.selected));
|
|
2438
|
+
document.body.classList.toggle("has-code-selection", Boolean(entity && entity.graph_kind === "code"));
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
function prefillPathFromSelection(silent) {
|
|
2442
|
+
if (!state.selected || state.selected.kind !== "entity") {
|
|
2443
|
+
if (!silent) setPathStatus("Select a code node first. Path tracing is for files, symbols, routes, tests, and scripts.", "warn");
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
var entity = state.entityById.get(state.selected.id);
|
|
2447
|
+
if (!entity || entity.graph_kind !== "code" || ["file", "symbol", "route", "test", "script"].indexOf(entity.type) === -1) {
|
|
2448
|
+
if (!silent) setPathStatus("Select a code node first. Memory nodes are shown through memory-code links, not code path tracing.", "warn");
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2451
|
+
if (!els.pathFromInput.value || silent) {
|
|
2452
|
+
els.pathFromInput.value = entity.path || displayName(entity) || entity.id;
|
|
2453
|
+
}
|
|
2454
|
+
if (!silent) {
|
|
2455
|
+
els.pathToInput.focus();
|
|
2456
|
+
setPathStatus("Selected " + displayName(entity) + ". Pick a target test, route, file, or symbol to trace impact.", "ok");
|
|
2457
|
+
}
|
|
2249
2458
|
}
|
|
2250
2459
|
|
|
2251
2460
|
function entityDetailRows(entity) {
|
|
@@ -2373,6 +2582,23 @@
|
|
|
2373
2582
|
return Boolean(edge && (edge.memory_code_link || isMemoryCodeRelation(edge.relation)));
|
|
2374
2583
|
}
|
|
2375
2584
|
|
|
2585
|
+
function codeCoverageKey(entity) {
|
|
2586
|
+
if (!entity) return "";
|
|
2587
|
+
return String(entity.path || entity.id || "").replace(/\\/g, "/").replace(/^\.\//, "");
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
function memoryLinkedCodeKeys() {
|
|
2591
|
+
var keys = new Set();
|
|
2592
|
+
state.edges.filter(isMemoryCodeEdge).forEach(function (edge) {
|
|
2593
|
+
[state.entityById.get(edge.from), state.entityById.get(edge.to)].forEach(function (entity) {
|
|
2594
|
+
if (!entity || entity.graph_kind !== "code") return;
|
|
2595
|
+
var key = codeCoverageKey(entity);
|
|
2596
|
+
if (key) keys.add(key);
|
|
2597
|
+
});
|
|
2598
|
+
});
|
|
2599
|
+
return keys;
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2376
2602
|
function connectionImportance(link) {
|
|
2377
2603
|
var relation = String(link.edge.relation || "");
|
|
2378
2604
|
var score = entityImportance(link.other);
|
|
@@ -2475,13 +2701,15 @@
|
|
|
2475
2701
|
}).length;
|
|
2476
2702
|
var evidenceEdges = visibleEdges.filter(function (edge) { return Array.isArray(edge.evidence) && edge.evidence.length > 0; }).length;
|
|
2477
2703
|
var official = state.metrics;
|
|
2704
|
+
var memoryCodeLinks = state.edges.filter(isMemoryCodeEdge).length;
|
|
2705
|
+
var pendingReview = official && official.memory_graph ? Number(firstNumber(official.memory_graph.pending_packets, 0)) : 0;
|
|
2478
2706
|
var metrics = official ? [
|
|
2479
|
-
["
|
|
2480
|
-
["
|
|
2481
|
-
["
|
|
2482
|
-
["
|
|
2483
|
-
["
|
|
2484
|
-
["
|
|
2707
|
+
["Validation", official.harness && official.harness.validation_ok ? "Clean" : "Check"],
|
|
2708
|
+
["Review queue", pendingReview ? pendingReview + " pending" : "Clear"],
|
|
2709
|
+
["Reusable memory", official.memory_graph ? official.memory_graph.approved_packets + " packets" : "n/a"],
|
|
2710
|
+
["Code indexed", official.structural_index ? official.structural_index.files + " files" : official.code_graph.files + " files"],
|
|
2711
|
+
["Parser coverage", official.code_graph.indexer_coverage_percent + "%"],
|
|
2712
|
+
["Memory-code links", memoryCodeLinks]
|
|
2485
2713
|
] : [
|
|
2486
2714
|
["Nodes", visibleEntities.length + "/" + state.entities.length],
|
|
2487
2715
|
["Relations", visibleEdges.length + "/" + state.edges.length],
|
|
@@ -2503,7 +2731,136 @@
|
|
|
2503
2731
|
els.workspaceMode.textContent = (els.viewMode.value || "combined").replace(/^./, function (letter) { return letter.toUpperCase(); });
|
|
2504
2732
|
els.graphSubhead.textContent = visibleEntities.length + " visible nodes and " + visibleEdges.length + " visible relations" +
|
|
2505
2733
|
(hiddenDependencies && !els.showDependencies.checked ? " (" + hiddenDependencies + " dependency/noise nodes hidden)." : ".");
|
|
2506
|
-
|
|
2734
|
+
renderGraphInsights(visibleEntities, visibleEdges, hiddenDependencies);
|
|
2735
|
+
updateGraphActionButtons();
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
function renderGraphInsights(visibleEntities, visibleEdges, hiddenDependencies) {
|
|
2739
|
+
if (!els.graphInsights) return;
|
|
2740
|
+
els.graphInsights.textContent = "";
|
|
2741
|
+
var allMemoryCode = state.edges.filter(isMemoryCodeEdge);
|
|
2742
|
+
var visibleMemoryCode = visibleEdges.filter(isMemoryCodeEdge);
|
|
2743
|
+
var reviewFlags = visibleEdges.filter(function (edge) { return reviewStatus(edge) !== "ok"; });
|
|
2744
|
+
var visibleCodeFiles = visibleEntities.filter(function (entity) { return entity.graph_kind === "code" && entity.type === "file"; });
|
|
2745
|
+
var coveredKeys = memoryLinkedCodeKeys();
|
|
2746
|
+
var uncoveredCodeFiles = visibleCodeFiles.filter(function (entity) { return !coveredKeys.has(codeCoverageKey(entity)); });
|
|
2747
|
+
var evidenceEdges = visibleEdges.filter(function (edge) { return Array.isArray(edge.evidence) && edge.evidence.length; }).length;
|
|
2748
|
+
var evidencePercent = visibleEdges.length ? Math.round(evidenceEdges / visibleEdges.length * 100) : 0;
|
|
2749
|
+
var coveragePercent = visibleCodeFiles.length ? Math.round((visibleCodeFiles.length - uncoveredCodeFiles.length) / visibleCodeFiles.length * 100) : 0;
|
|
2750
|
+
var queryActive = parseSearchQuery(els.searchInput.value).active;
|
|
2751
|
+
var hasActiveFilters = queryActive || state.graphActionFilter || els.viewMode.value !== "combined" || els.typeFilter.value || els.relationFilter.value || els.showDependencies.checked;
|
|
2752
|
+
if (!visibleEntities.length || hasActiveFilters) {
|
|
2753
|
+
var recovery = document.createElement("article");
|
|
2754
|
+
recovery.className = classNames("metric-card graph-action-card graph-recovery-card", !visibleEntities.length && "metric-card-warn");
|
|
2755
|
+
recovery.innerHTML = [
|
|
2756
|
+
"<div class=\"metric-card-head\"><span></span><strong></strong></div>",
|
|
2757
|
+
"<p></p>",
|
|
2758
|
+
"<button type=\"button\">Clear search/filter</button>",
|
|
2759
|
+
"<em></em>"
|
|
2760
|
+
].join("");
|
|
2761
|
+
recovery.querySelector(".metric-card-head span").textContent = !visibleEntities.length ? "No graph results" : "Active graph filter";
|
|
2762
|
+
recovery.querySelector(".metric-card-head strong").textContent = !visibleEntities.length ? "0 visible" : "filtered";
|
|
2763
|
+
recovery.querySelector("p").textContent = !visibleEntities.length
|
|
2764
|
+
? "The current search or filter hides every node. Clear it to recover the graph."
|
|
2765
|
+
: "A search, relation, or journey filter is active.";
|
|
2766
|
+
recovery.querySelector("button").addEventListener("click", resetGraphView);
|
|
2767
|
+
recovery.querySelector("em").textContent = queryActive ? "Search: " + els.searchInput.value : (state.graphActionFilter || "custom filter");
|
|
2768
|
+
els.graphInsights.appendChild(recovery);
|
|
2769
|
+
}
|
|
2770
|
+
var cards = [
|
|
2771
|
+
graphActionCard("Memory coverage", coveragePercent + "%", uncoveredCodeFiles.length
|
|
2772
|
+
? uncoveredCodeFiles.length + " visible code file(s) have no linked repo memory."
|
|
2773
|
+
: "Visible code files have linked repo memory.",
|
|
2774
|
+
"Show uncovered code", "uncovered", uncoveredCodeFiles.length ? "warn" : "ok"),
|
|
2775
|
+
graphActionCard("Untrusted edges", reviewFlags.length ? reviewFlags.length + " flagged" : "clear", reviewFlags.length
|
|
2776
|
+
? "Low-confidence, missing-evidence, or invalidated relations are visible."
|
|
2777
|
+
: "Visible relations are evidence-backed enough for inspection.",
|
|
2778
|
+
"Filter to untrusted", "untrusted", reviewFlags.length ? "warn" : "ok", [
|
|
2779
|
+
{ 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" },
|
|
2780
|
+
{ 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" },
|
|
2781
|
+
{ label: "Invalidated", value: reviewFlags.filter(function (edge) { return reviewStatus(edge) === "invalidated"; }).length, score: Math.min(100, reviewFlags.length * 20), status: reviewFlags.length ? "danger" : "ok" }
|
|
2782
|
+
]),
|
|
2783
|
+
graphActionCard("Evidence in view", evidencePercent + "%", evidenceEdges + " of " + visibleEdges.length + " visible relation(s) carry evidence.",
|
|
2784
|
+
"Show memory-code links", "memory-code", evidencePercent >= 80 ? "ok" : "warn"),
|
|
2785
|
+
graphActionCard("Trace impact", visibleMemoryCode.length + " links", "Select a code node, then trace to a test, route, or symbol from the Inspector.",
|
|
2786
|
+
"Use selected node", "path", visibleMemoryCode.length ? "ok" : "warn")
|
|
2787
|
+
];
|
|
2788
|
+
cards.forEach(function (card) { els.graphInsights.appendChild(card); });
|
|
2789
|
+
if (els.graphInsightStatus) els.graphInsightStatus.textContent = state.graphActionFilter || (hiddenDependencies ? hiddenDependencies + " external hidden" : "ready");
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
function graphActionCard(title, value, detail, actionLabel, action, status, rows) {
|
|
2793
|
+
var card = document.createElement("article");
|
|
2794
|
+
card.className = classNames("metric-card graph-action-card", status && "metric-card-" + status, state.graphActionFilter === action && "active");
|
|
2795
|
+
card.innerHTML = [
|
|
2796
|
+
"<div class=\"metric-card-head\"><span></span><strong></strong></div>",
|
|
2797
|
+
"<p></p>",
|
|
2798
|
+
"<div class=\"metric-bars\"></div>",
|
|
2799
|
+
"<button type=\"button\"></button>",
|
|
2800
|
+
"<em></em>"
|
|
2801
|
+
].join("");
|
|
2802
|
+
card.querySelector(".metric-card-head span").textContent = title;
|
|
2803
|
+
card.querySelector(".metric-card-head strong").textContent = value;
|
|
2804
|
+
card.querySelector("p").textContent = detail;
|
|
2805
|
+
var bars = card.querySelector(".metric-bars");
|
|
2806
|
+
if (Array.isArray(rows) && rows.length) {
|
|
2807
|
+
rows.forEach(function (row) {
|
|
2808
|
+
var item = document.createElement("div");
|
|
2809
|
+
item.className = classNames("metric-bar", row.status && "metric-bar-" + row.status);
|
|
2810
|
+
item.innerHTML = "<span></span><strong></strong><i></i>";
|
|
2811
|
+
item.querySelector("span").textContent = row.label;
|
|
2812
|
+
item.querySelector("strong").textContent = formatDashboardValue(row.value);
|
|
2813
|
+
item.querySelector("i").style.width = clamp(Number(row.score || 0), row.value ? 8 : 0, 100) + "%";
|
|
2814
|
+
bars.appendChild(item);
|
|
2815
|
+
});
|
|
2816
|
+
} else {
|
|
2817
|
+
bars.remove();
|
|
2818
|
+
}
|
|
2819
|
+
var button = card.querySelector("button");
|
|
2820
|
+
button.textContent = actionLabel;
|
|
2821
|
+
button.addEventListener("click", function () {
|
|
2822
|
+
if (action === "path") {
|
|
2823
|
+
prefillPathFromSelection();
|
|
2824
|
+
return;
|
|
2825
|
+
}
|
|
2826
|
+
applyGraphActionFilter(action);
|
|
2827
|
+
});
|
|
2828
|
+
card.querySelector("em").textContent = state.graphActionFilter === action ? "Active filter" : "";
|
|
2829
|
+
return card;
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
function updateGraphActionButtons() {
|
|
2833
|
+
[
|
|
2834
|
+
[els.showUntrusted, "untrusted"],
|
|
2835
|
+
[els.showUncovered, "uncovered"],
|
|
2836
|
+
[els.showMemoryCode, "memory-code"]
|
|
2837
|
+
].forEach(function (entry) {
|
|
2838
|
+
if (!entry[0]) return;
|
|
2839
|
+
entry[0].classList.toggle("active", state.graphActionFilter === entry[1]);
|
|
2840
|
+
});
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
function renderArtifactDiagnostics(visibleEntities, visibleEdges) {
|
|
2844
|
+
if (!els.debugOverview) return;
|
|
2845
|
+
els.debugOverview.textContent = "";
|
|
2846
|
+
var episodes = state.episodesById ? state.episodesById.size : 0;
|
|
2847
|
+
var evidenceEdges = state.edges.filter(function (edge) { return Array.isArray(edge.evidence) && edge.evidence.length; }).length;
|
|
2848
|
+
var evidencePercent = state.edges.length ? Math.round(evidenceEdges / state.edges.length * 100) : 0;
|
|
2849
|
+
var memoryCodeEdges = state.edges.filter(isMemoryCodeEdge).length;
|
|
2850
|
+
var reviewFlags = state.edges.filter(function (edge) { return reviewStatus(edge) !== "ok"; }).length;
|
|
2851
|
+
[
|
|
2852
|
+
metricBars("Artifact shape", state.entities.length + " nodes", [
|
|
2853
|
+
{ label: "Visible nodes", value: visibleEntities.length, score: state.entities.length ? visibleEntities.length / state.entities.length * 100 : 0, status: "ok" },
|
|
2854
|
+
{ label: "Relations", value: state.edges.length, score: 100, status: "ok" },
|
|
2855
|
+
{ label: "Episodes", value: episodes, score: episodes ? 100 : 0, status: episodes ? "ok" : "warn" }
|
|
2856
|
+
], "Use this when graph generation seems incomplete.", "ok"),
|
|
2857
|
+
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"),
|
|
2858
|
+
metricBars("Link diagnostics", memoryCodeEdges + " memory-code", [
|
|
2859
|
+
{ label: "Memory-code", value: memoryCodeEdges, score: Math.min(100, memoryCodeEdges / Math.max(1, state.edges.length) * 100), status: memoryCodeEdges ? "ok" : "warn" },
|
|
2860
|
+
{ label: "Review flags", value: reviewFlags, score: Math.min(100, reviewFlags * 12), status: reviewFlags ? "warn" : "ok" },
|
|
2861
|
+
{ label: "Visible edges", value: visibleEdges.length, score: state.edges.length ? visibleEdges.length / state.edges.length * 100 : 0, status: "ok" }
|
|
2862
|
+
], "Use raw rows below to inspect exact IDs, relations, and evidence.", reviewFlags ? "warn" : "ok")
|
|
2863
|
+
].forEach(function (card) { els.debugOverview.appendChild(card); });
|
|
2507
2864
|
}
|
|
2508
2865
|
|
|
2509
2866
|
function renderDashboard() {
|
|
@@ -2519,64 +2876,174 @@
|
|
|
2519
2876
|
var memoryCodeEdges = state.edges.filter(isMemoryCodeEdge);
|
|
2520
2877
|
var reports = state.reports || {};
|
|
2521
2878
|
var reportCount = Object.keys(reports).filter(function (key) { return reports[key]; }).length;
|
|
2879
|
+
var risk = reports.risk || {};
|
|
2880
|
+
var riskTargets = Array.isArray(risk.targets) ? risk.targets : Object.keys(risk.targets || {});
|
|
2881
|
+
var inboxCounts = state.inbox && state.inbox.counts ? state.inbox.counts : {};
|
|
2882
|
+
var pendingReview = Number(firstNumber(inboxCounts.pending, memoryGraph.pending_packets, (state.pendingPackets || []).length, 0));
|
|
2883
|
+
var staleFlags = Number(firstNumber(inboxCounts.stale, 0));
|
|
2884
|
+
var duplicateFlags = Number(firstNumber(inboxCounts.duplicates, memoryGraph.duplicate_candidate_pairs, 0));
|
|
2885
|
+
var missingContext = Number(firstNumber(inboxCounts.missing_context, 0));
|
|
2886
|
+
var ownerSilos = Array.isArray(risk.ownership_silos) ? risk.ownership_silos.length : 0;
|
|
2887
|
+
var hotspots = Array.isArray(risk.global_hotspots) ? risk.global_hotspots.length : 0;
|
|
2888
|
+
var readiness = dashboardReadiness(metrics, pendingReview, staleFlags, duplicateFlags, missingContext);
|
|
2889
|
+
var memoryCoverage = dashboardMemoryCoverage(reports, memoryCodeEdges, memoryGraph, memoryNodes);
|
|
2890
|
+
var riskHealth = riskTargets.length || hotspots ? (riskTargets.length + hotspots) + " signals" : "No flags";
|
|
2522
2891
|
var statRows = [
|
|
2523
|
-
["
|
|
2524
|
-
["
|
|
2525
|
-
["
|
|
2526
|
-
["
|
|
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")]
|
|
2892
|
+
["Handoff", readiness.label, readiness.detail, readiness.status],
|
|
2893
|
+
["Memory", memoryCoverage.label, memoryCoverage.detail, memoryCoverage.status],
|
|
2894
|
+
["Risk", riskHealth, riskTargets.length + " targets, " + ownerSilos + " ownership silos", riskTargets.length || ownerSilos || hotspots ? "warn" : "ok"],
|
|
2895
|
+
["Code map", firstNumber(codeGraph.files, structural.files, countEntitiesByType("file")) + " files", firstNumber(codeGraph.symbols, structural.symbols, codeNodes) + " symbols indexed", "code"]
|
|
2529
2896
|
];
|
|
2530
2897
|
els.dashboardStats.textContent = "";
|
|
2531
2898
|
statRows.forEach(function (row) {
|
|
2532
2899
|
var item = document.createElement("div");
|
|
2533
|
-
item.className = "dashboard-stat";
|
|
2534
|
-
item.innerHTML = "<strong></strong><
|
|
2900
|
+
item.className = classNames("dashboard-stat", row[3] && "dashboard-stat-" + row[3]);
|
|
2901
|
+
item.innerHTML = "<span></span><strong></strong><em></em>";
|
|
2535
2902
|
item.querySelector("strong").textContent = formatDashboardValue(row[1]);
|
|
2536
2903
|
item.querySelector("span").textContent = row[0];
|
|
2904
|
+
item.querySelector("em").textContent = row[2] || "";
|
|
2537
2905
|
els.dashboardStats.appendChild(item);
|
|
2538
2906
|
});
|
|
2539
2907
|
|
|
2540
2908
|
setDashboardRows("dashboardMemory", [
|
|
2541
|
-
["
|
|
2542
|
-
["
|
|
2543
|
-
["
|
|
2544
|
-
["Code-linked memory", memoryCodeEdges.length]
|
|
2909
|
+
["Reusable", firstNumber(memoryGraph.approved_packets, memoryNodes) + " packets"],
|
|
2910
|
+
["Linked", memoryCodeEdges.length + " code links"],
|
|
2911
|
+
["Review", pendingReview ? pendingReview + " pending" : "clear"]
|
|
2545
2912
|
]);
|
|
2546
2913
|
setDashboardRows("dashboardGraph", [
|
|
2547
2914
|
["Files", firstNumber(codeGraph.files, structural.files, countEntitiesByType("file"))],
|
|
2548
2915
|
["Symbols", firstNumber(codeGraph.symbols, structural.symbols, countEntitiesByType("symbol"))],
|
|
2549
|
-
["
|
|
2550
|
-
["Dependency/noise hidden", state.entities.filter(function (entity) { return isDependencyEntity(entity); }).length]
|
|
2916
|
+
["Coverage", codeGraph.indexer_coverage_percent != null ? codeGraph.indexer_coverage_percent + "%" : "not loaded"]
|
|
2551
2917
|
]);
|
|
2552
2918
|
setDashboardRows("dashboardIntel", [
|
|
2553
|
-
["
|
|
2554
|
-
["
|
|
2555
|
-
["
|
|
2556
|
-
["Communities", reports.graphInsights && Array.isArray(reports.graphInsights.communities) ? reports.graphInsights.communities.length : "n/a"]
|
|
2919
|
+
["Risk targets", riskTargets.length || "none"],
|
|
2920
|
+
["Ownership silos", ownerSilos || "none"],
|
|
2921
|
+
["Decision coverage", reports.decisions && reports.decisions.coverage_percent != null ? reports.decisions.coverage_percent + "%" : "not loaded"]
|
|
2557
2922
|
]);
|
|
2558
|
-
var risk = reports.risk || {};
|
|
2559
|
-
var riskTargets = Array.isArray(risk.targets) ? risk.targets : Object.keys(risk.targets || {});
|
|
2560
|
-
setDashboardRows("dashboardRisk", [
|
|
2561
|
-
["Risk targets", riskTargets.length || "n/a"],
|
|
2562
|
-
["Hotspots", Array.isArray(risk.global_hotspots) ? risk.global_hotspots.length : "n/a"],
|
|
2563
|
-
["Ownership silos", Array.isArray(risk.ownership_silos) ? risk.ownership_silos.length : "n/a"],
|
|
2564
|
-
["Cycles", reports.graphInsights && Array.isArray(reports.graphInsights.dependency_cycles) ? reports.graphInsights.dependency_cycles.length : "n/a"]
|
|
2565
|
-
]);
|
|
2566
|
-
var inboxCounts = state.inbox && state.inbox.counts ? state.inbox.counts : {};
|
|
2567
2923
|
setDashboardRows("dashboardReview", [
|
|
2568
|
-
["
|
|
2569
|
-
["
|
|
2570
|
-
["Stale
|
|
2571
|
-
["
|
|
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"]
|
|
2924
|
+
["Handoff", readiness.label],
|
|
2925
|
+
["Pending", pendingReview || "none"],
|
|
2926
|
+
["Stale / duplicate", staleFlags + " / " + duplicateFlags],
|
|
2927
|
+
["Missing context", missingContext || "none"]
|
|
2579
2928
|
]);
|
|
2929
|
+
renderDashboardCharts({
|
|
2930
|
+
metrics: metrics,
|
|
2931
|
+
reports: reports,
|
|
2932
|
+
memoryGraph: memoryGraph,
|
|
2933
|
+
codeGraph: codeGraph,
|
|
2934
|
+
structural: structural,
|
|
2935
|
+
memoryCodeEdges: memoryCodeEdges,
|
|
2936
|
+
memoryNodes: memoryNodes,
|
|
2937
|
+
pendingReview: pendingReview,
|
|
2938
|
+
staleFlags: staleFlags,
|
|
2939
|
+
duplicateFlags: duplicateFlags,
|
|
2940
|
+
missingContext: missingContext,
|
|
2941
|
+
riskTargets: riskTargets,
|
|
2942
|
+
ownerSilos: ownerSilos,
|
|
2943
|
+
hotspots: hotspots
|
|
2944
|
+
});
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
function renderDashboardCharts(data) {
|
|
2948
|
+
if (!els.dashboardCharts) return;
|
|
2949
|
+
var approvedPackets = Number(firstNumber(data.memoryGraph.approved_packets, data.memoryNodes, 0));
|
|
2950
|
+
var linkedPacketIds = new Set();
|
|
2951
|
+
data.memoryCodeEdges.forEach(function (edge) {
|
|
2952
|
+
var from = state.entityById.get(edge.from);
|
|
2953
|
+
var to = state.entityById.get(edge.to);
|
|
2954
|
+
if (from && isMemoryPacketEntity(from)) linkedPacketIds.add(from.id);
|
|
2955
|
+
if (to && isMemoryPacketEntity(to)) linkedPacketIds.add(to.id);
|
|
2956
|
+
});
|
|
2957
|
+
var memoryGrounding = approvedPackets ? Math.round(linkedPacketIds.size / approvedPackets * 100) : 0;
|
|
2958
|
+
var sourceCoverage = Number(firstNumber(data.codeGraph.indexer_coverage_percent, 0));
|
|
2959
|
+
var blockers = data.pendingReview + data.staleFlags + data.duplicateFlags + data.missingContext;
|
|
2960
|
+
var riskSignals = data.riskTargets.length + data.ownerSilos + data.hotspots;
|
|
2961
|
+
els.dashboardCharts.textContent = "";
|
|
2962
|
+
[
|
|
2963
|
+
metricDonut("Memory grounding", memoryGrounding, linkedPacketIds.size + " of " + approvedPackets + " packets linked to code", "Open Memory and fix Needs paths first.", memoryGrounding >= 70 ? "ok" : "warn"),
|
|
2964
|
+
metricDonut("Source map", sourceCoverage, firstNumber(data.codeGraph.files, data.structural.files, 0) + " files indexed for graph recall", "If this drops, refresh indexing before relying on graph answers.", sourceCoverage >= 90 ? "ok" : "warn"),
|
|
2965
|
+
metricBars("Handoff blockers", blockers ? blockers + " open" : "clear", [
|
|
2966
|
+
{ label: "Pending", value: data.pendingReview, score: Math.min(100, data.pendingReview * 24), status: data.pendingReview ? "warn" : "ok" },
|
|
2967
|
+
{ label: "Stale", value: data.staleFlags, score: Math.min(100, data.staleFlags * 24), status: data.staleFlags ? "warn" : "ok" },
|
|
2968
|
+
{ label: "Duplicate", value: data.duplicateFlags, score: Math.min(100, data.duplicateFlags * 24), status: data.duplicateFlags ? "warn" : "ok" },
|
|
2969
|
+
{ label: "Missing context", value: data.missingContext, score: Math.min(100, data.missingContext * 18), status: data.missingContext ? "warn" : "ok" }
|
|
2970
|
+
], blockers ? "Resolve Review before handing work to another agent." : "Memory is clean for handoff.", blockers ? "warn" : "ok"),
|
|
2971
|
+
metricBars("Change risk", riskSignals ? riskSignals + " signals" : "none", [
|
|
2972
|
+
{ label: "Targets", value: data.riskTargets.length, score: Math.min(100, data.riskTargets.length * 18), status: data.riskTargets.length ? "warn" : "ok" },
|
|
2973
|
+
{ label: "Silos", value: data.ownerSilos, score: Math.min(100, data.ownerSilos * 18), status: data.ownerSilos ? "warn" : "ok" },
|
|
2974
|
+
{ label: "Hotspots", value: data.hotspots, score: Math.min(100, data.hotspots * 18), status: data.hotspots ? "danger" : "ok" }
|
|
2975
|
+
], riskSignals ? "Open Intel or Owners before editing risky files." : "No loaded risk flags.", riskSignals ? "warn" : "ok")
|
|
2976
|
+
].forEach(function (card) { els.dashboardCharts.appendChild(card); });
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
function metricDonut(title, percent, detail, action, status) {
|
|
2980
|
+
var card = document.createElement("article");
|
|
2981
|
+
var value = clamp(Number(percent || 0), 0, 100);
|
|
2982
|
+
card.className = classNames("metric-card", status && "metric-card-" + status);
|
|
2983
|
+
card.innerHTML = [
|
|
2984
|
+
"<div class=\"metric-card-head\"><span></span><strong></strong></div>",
|
|
2985
|
+
"<div class=\"metric-visual\"><div class=\"metric-donut\"><span></span></div><p></p></div>",
|
|
2986
|
+
"<em></em>"
|
|
2987
|
+
].join("");
|
|
2988
|
+
card.querySelector(".metric-card-head span").textContent = title;
|
|
2989
|
+
card.querySelector(".metric-card-head strong").textContent = value + "%";
|
|
2990
|
+
card.querySelector(".metric-donut").style.setProperty("--value", value);
|
|
2991
|
+
card.querySelector(".metric-donut span").textContent = value + "%";
|
|
2992
|
+
card.querySelector("p").textContent = detail || "";
|
|
2993
|
+
card.querySelector("em").textContent = action || "";
|
|
2994
|
+
return card;
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
function metricBars(title, value, rows, action, status) {
|
|
2998
|
+
var card = document.createElement("article");
|
|
2999
|
+
card.className = classNames("metric-card", status && "metric-card-" + status);
|
|
3000
|
+
card.innerHTML = [
|
|
3001
|
+
"<div class=\"metric-card-head\"><span></span><strong></strong></div>",
|
|
3002
|
+
"<div class=\"metric-bars\"></div>",
|
|
3003
|
+
"<em></em>"
|
|
3004
|
+
].join("");
|
|
3005
|
+
card.querySelector(".metric-card-head span").textContent = title;
|
|
3006
|
+
card.querySelector(".metric-card-head strong").textContent = formatDashboardValue(value);
|
|
3007
|
+
var list = card.querySelector(".metric-bars");
|
|
3008
|
+
rows.forEach(function (row) {
|
|
3009
|
+
var item = document.createElement("div");
|
|
3010
|
+
item.className = classNames("metric-bar", row.status && "metric-bar-" + row.status);
|
|
3011
|
+
item.innerHTML = "<span></span><strong></strong><i></i>";
|
|
3012
|
+
item.querySelector("span").textContent = row.label;
|
|
3013
|
+
item.querySelector("strong").textContent = formatDashboardValue(row.value);
|
|
3014
|
+
item.querySelector("i").style.width = clamp(Number(row.score || 0), row.value ? 8 : 0, 100) + "%";
|
|
3015
|
+
list.appendChild(item);
|
|
3016
|
+
});
|
|
3017
|
+
card.querySelector("em").textContent = action || "";
|
|
3018
|
+
return card;
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
function dashboardReadiness(metrics, pendingReview, staleFlags, duplicateFlags, missingContext) {
|
|
3022
|
+
if (pendingReview || staleFlags || duplicateFlags || missingContext) {
|
|
3023
|
+
return { label: "Needs review", detail: pendingReview + " pending, " + staleFlags + " stale, " + duplicateFlags + " duplicate, " + missingContext + " missing context", status: "warn" };
|
|
3024
|
+
}
|
|
3025
|
+
if (metrics && metrics.harness && metrics.harness.validation_ok) {
|
|
3026
|
+
return { label: "Ready", detail: "Memory and graph checks are clean", status: "ok" };
|
|
3027
|
+
}
|
|
3028
|
+
return { label: "Unknown", detail: "Run kage refresh or open local viewer with metrics", status: "warn" };
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
function dashboardMemoryCoverage(reports, memoryCodeEdges, memoryGraph, memoryNodes) {
|
|
3032
|
+
var coverage = reports.decisions && reports.decisions.coverage_percent;
|
|
3033
|
+
if (coverage != null) {
|
|
3034
|
+
return {
|
|
3035
|
+
label: coverage + "%",
|
|
3036
|
+
detail: "Decision memory coverage for important code paths",
|
|
3037
|
+
status: Number(coverage) >= 70 ? "ok" : "warn"
|
|
3038
|
+
};
|
|
3039
|
+
}
|
|
3040
|
+
var packets = Number(firstNumber(memoryGraph.approved_packets, memoryNodes, 0));
|
|
3041
|
+
if (!packets) return { label: "No memory", detail: "Agents will rediscover repo context", status: "warn" };
|
|
3042
|
+
return {
|
|
3043
|
+
label: memoryCodeEdges.length + " links",
|
|
3044
|
+
detail: "Memory packets connected back to code",
|
|
3045
|
+
status: memoryCodeEdges.length ? "ok" : "warn"
|
|
3046
|
+
};
|
|
2580
3047
|
}
|
|
2581
3048
|
|
|
2582
3049
|
function setDashboardRows(cardId, rows) {
|
|
@@ -2611,13 +3078,290 @@
|
|
|
2611
3078
|
return String(value == null ? "n/a" : value);
|
|
2612
3079
|
}
|
|
2613
3080
|
|
|
3081
|
+
function escapeHtml(value) {
|
|
3082
|
+
return String(value == null ? "" : value).replace(/[&<>"']/g, function (char) {
|
|
3083
|
+
return { "&": "&", "<": "<", ">": ">", "\"": """, "'": "'" }[char] || char;
|
|
3084
|
+
});
|
|
3085
|
+
}
|
|
3086
|
+
|
|
3087
|
+
function renderMemoryLibrary() {
|
|
3088
|
+
if (!els.memoryList) return;
|
|
3089
|
+
var memoryEntities = state.entities.filter(function (entity) {
|
|
3090
|
+
return isMemoryPacketEntity(entity);
|
|
3091
|
+
}).sort(function (a, b) {
|
|
3092
|
+
return entityImportance(b) - entityImportance(a) || displayName(a).localeCompare(displayName(b));
|
|
3093
|
+
});
|
|
3094
|
+
var memoryLinkCounts = new Map();
|
|
3095
|
+
state.edges.forEach(function (edge) {
|
|
3096
|
+
if (!isMemoryCodeEdge(edge)) return;
|
|
3097
|
+
var fromEntity = state.entityById.get(edge.from);
|
|
3098
|
+
var toEntity = state.entityById.get(edge.to);
|
|
3099
|
+
if (fromEntity && fromEntity.graph_kind === "memory" && toEntity && toEntity.graph_kind === "code") {
|
|
3100
|
+
memoryLinkCounts.set(edge.from, (memoryLinkCounts.get(edge.from) || 0) + 1);
|
|
3101
|
+
}
|
|
3102
|
+
if (toEntity && toEntity.graph_kind === "memory" && fromEntity && fromEntity.graph_kind === "code") {
|
|
3103
|
+
memoryLinkCounts.set(edge.to, (memoryLinkCounts.get(edge.to) || 0) + 1);
|
|
3104
|
+
}
|
|
3105
|
+
});
|
|
3106
|
+
var linkedCount = memoryEntities.filter(function (entity) { return (memoryLinkCounts.get(entity.id) || 0) > 0; }).length;
|
|
3107
|
+
var query = parseSearchQuery(els.memorySearch ? els.memorySearch.value : "");
|
|
3108
|
+
var filter = els.memoryFilter ? els.memoryFilter.value : "all";
|
|
3109
|
+
var filtered = memoryEntities.filter(function (entity) {
|
|
3110
|
+
var linkCount = memoryLinkCounts.get(entity.id) || 0;
|
|
3111
|
+
if (filter === "linked" && !linkCount) return false;
|
|
3112
|
+
if (filter === "needs-paths" && linkCount) return false;
|
|
3113
|
+
if (["decision", "runbook", "bug_fix"].indexOf(filter) !== -1 && !memoryMatchesKind(entity, filter)) return false;
|
|
3114
|
+
return matchesSearchQuery(entity, query);
|
|
3115
|
+
});
|
|
3116
|
+
els.memoryStatus.textContent = filtered.length + " shown";
|
|
3117
|
+
if (els.memoryStats) {
|
|
3118
|
+
els.memoryStats.innerHTML = [
|
|
3119
|
+
memoryStat("Reusable", memoryEntities.length),
|
|
3120
|
+
memoryStat("Code-linked", linkedCount),
|
|
3121
|
+
memoryStat("Needs paths", memoryEntities.length - linkedCount)
|
|
3122
|
+
].join("");
|
|
3123
|
+
}
|
|
3124
|
+
if (els.memoryOverview) renderMemoryOverview(memoryEntities, linkedCount);
|
|
3125
|
+
els.memoryList.textContent = "";
|
|
3126
|
+
if (!memoryEntities.length) {
|
|
3127
|
+
els.memoryList.className = "memory-list details-empty";
|
|
3128
|
+
els.memoryList.textContent = "No memory packets loaded. Launch with `kage viewer --project <repo>` to load repo memory.";
|
|
3129
|
+
return;
|
|
3130
|
+
}
|
|
3131
|
+
if (!filtered.length) {
|
|
3132
|
+
els.memoryList.className = "memory-list details-empty";
|
|
3133
|
+
els.memoryList.textContent = "No matching memory. Clear search or switch the filter.";
|
|
3134
|
+
return;
|
|
3135
|
+
}
|
|
3136
|
+
filtered.sort(function (a, b) {
|
|
3137
|
+
return (memoryLinkCounts.get(b.id) || 0) - (memoryLinkCounts.get(a.id) || 0) ||
|
|
3138
|
+
entityImportance(b) - entityImportance(a) ||
|
|
3139
|
+
displayName(a).localeCompare(displayName(b));
|
|
3140
|
+
});
|
|
3141
|
+
els.memoryList.className = "memory-list";
|
|
3142
|
+
filtered.slice(0, 60).forEach(function (entity) {
|
|
3143
|
+
var links = memoryCodeLinksForEntity(entity.id);
|
|
3144
|
+
var firstCodeTarget = primaryCodeTargetForMemory(entity.id, links);
|
|
3145
|
+
var item = document.createElement("button");
|
|
3146
|
+
item.type = "button";
|
|
3147
|
+
var selected = state.selected && state.selected.kind === "entity" && state.selected.id === entity.id;
|
|
3148
|
+
item.className = classNames("memory-row", selected && "selected");
|
|
3149
|
+
item.setAttribute("aria-selected", selected ? "true" : "false");
|
|
3150
|
+
item.innerHTML = [
|
|
3151
|
+
"<span class=\"memory-row-main\"><strong></strong><em></em></span>",
|
|
3152
|
+
"<span class=\"memory-row-meta\"></span>",
|
|
3153
|
+
"<span class=\"memory-row-target\"></span>"
|
|
3154
|
+
].join("");
|
|
3155
|
+
item.querySelector("strong").textContent = displayName(entity);
|
|
3156
|
+
item.querySelector("em").textContent = entity.type || "memory";
|
|
3157
|
+
item.querySelector(".memory-row-meta").textContent = trimIntelText(entity.summary || entity.description || entity.path || "No summary", 150);
|
|
3158
|
+
item.querySelector(".memory-row-target").textContent = links.length
|
|
3159
|
+
? links.length + " code link" + (links.length === 1 ? "" : "s") + (firstCodeTarget ? " | " + trimIntelText(codeTargetLabel(firstCodeTarget), 64) : "")
|
|
3160
|
+
: "needs code paths";
|
|
3161
|
+
item.addEventListener("click", function () {
|
|
3162
|
+
selectEntity(entity.id, true);
|
|
3163
|
+
render();
|
|
3164
|
+
});
|
|
3165
|
+
els.memoryList.appendChild(item);
|
|
3166
|
+
});
|
|
3167
|
+
if (filtered.length > 60) appendListNote(els.memoryList, "Showing 60 of " + filtered.length + ". Search a path or topic to narrow.");
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
function memoryStat(label, value) {
|
|
3171
|
+
return "<div><strong>" + escapeHtml(String(value)) + "</strong><span>" + escapeHtml(label) + "</span></div>";
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
function renderMemoryOverview(memoryEntities, linkedCount) {
|
|
3175
|
+
els.memoryOverview.textContent = "";
|
|
3176
|
+
var total = memoryEntities.length;
|
|
3177
|
+
var linkedPercent = total ? Math.round(linkedCount / total * 100) : 0;
|
|
3178
|
+
var typeCounts = new Map();
|
|
3179
|
+
memoryEntities.forEach(function (entity) {
|
|
3180
|
+
typeCounts.set(entity.type || "memory", (typeCounts.get(entity.type || "memory") || 0) + 1);
|
|
3181
|
+
});
|
|
3182
|
+
var topTypes = Array.from(typeCounts.entries()).sort(function (a, b) { return b[1] - a[1] || a[0].localeCompare(b[0]); }).slice(0, 4);
|
|
3183
|
+
var maxType = Math.max(1, topTypes.reduce(function (max, row) { return Math.max(max, row[1]); }, 0));
|
|
3184
|
+
els.memoryOverview.appendChild(metricDonut(
|
|
3185
|
+
"Code grounding",
|
|
3186
|
+
linkedPercent,
|
|
3187
|
+
linkedCount + " packet(s) are connected to files, symbols, routes, or tests",
|
|
3188
|
+
linkedPercent >= 70 ? "Use linked memory before edits." : "Filter Needs paths and add concrete code references.",
|
|
3189
|
+
linkedPercent >= 70 ? "ok" : "warn"
|
|
3190
|
+
));
|
|
3191
|
+
els.memoryOverview.appendChild(metricBars("Memory mix", total + " packets", topTypes.map(function (row) {
|
|
3192
|
+
return {
|
|
3193
|
+
label: row[0],
|
|
3194
|
+
value: row[1],
|
|
3195
|
+
score: row[1] / maxType * 100,
|
|
3196
|
+
status: row[0] === "memory" ? "" : "ok"
|
|
3197
|
+
};
|
|
3198
|
+
}), "A healthy repo has decisions, bug fixes, runbooks, gotchas, and code explanations.", "ok"));
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
function memoryCodeLinksForEntity(entityId) {
|
|
3202
|
+
return state.edges.filter(function (edge) {
|
|
3203
|
+
if ((edge.from !== entityId && edge.to !== entityId) || !isMemoryCodeEdge(edge)) return false;
|
|
3204
|
+
var other = state.entityById.get(edge.from === entityId ? edge.to : edge.from);
|
|
3205
|
+
return Boolean(other && other.graph_kind === "code");
|
|
3206
|
+
});
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3209
|
+
function primaryCodeTargetForMemory(entityId, links) {
|
|
3210
|
+
return links.map(function (edge) {
|
|
3211
|
+
return state.entityById.get(edge.from === entityId ? edge.to : edge.from);
|
|
3212
|
+
}).filter(Boolean).sort(function (a, b) {
|
|
3213
|
+
return codeTargetScore(b) - codeTargetScore(a) || codeTargetLabel(a).localeCompare(codeTargetLabel(b));
|
|
3214
|
+
})[0] || null;
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
function codeTargetScore(entity) {
|
|
3218
|
+
var score = 0;
|
|
3219
|
+
if (!entity) return score;
|
|
3220
|
+
if (entity.type === "file") score += 80;
|
|
3221
|
+
if (entity.type === "route" || entity.type === "test") score += 60;
|
|
3222
|
+
if (entity.type === "symbol") score += 45;
|
|
3223
|
+
if (entity.path) score += 35;
|
|
3224
|
+
if (entity.line) score += 8;
|
|
3225
|
+
if (String(entity.path || "").indexOf(".agent_memory/") === 0) score -= 100;
|
|
3226
|
+
return score;
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
function codeTargetLabel(entity) {
|
|
3230
|
+
if (!entity) return "";
|
|
3231
|
+
if (entity.type === "file" && entity.path) return entity.path;
|
|
3232
|
+
if (entity.type === "route") {
|
|
3233
|
+
return (entity.method && entity.route_path ? entity.method + " " + entity.route_path : displayName(entity)) +
|
|
3234
|
+
(entity.path ? " in " + entity.path : "");
|
|
3235
|
+
}
|
|
3236
|
+
if ((entity.type === "symbol" || entity.type === "test") && entity.path) {
|
|
3237
|
+
return displayName(entity) + " in " + entity.path;
|
|
3238
|
+
}
|
|
3239
|
+
return entity.path || displayName(entity);
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
function memoryMatchesKind(entity, kind) {
|
|
3243
|
+
if (!entity) return false;
|
|
3244
|
+
if (entity.type === kind) return true;
|
|
3245
|
+
var normalizedKind = String(kind || "").replace(/_/g, " ");
|
|
3246
|
+
var text = [
|
|
3247
|
+
entity.type,
|
|
3248
|
+
entity.name,
|
|
3249
|
+
entity.summary,
|
|
3250
|
+
entity.description,
|
|
3251
|
+
entity.path
|
|
3252
|
+
].join(" ").toLowerCase();
|
|
3253
|
+
return text.indexOf(kind) !== -1 || text.indexOf(normalizedKind) !== -1;
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
function isMemoryPacketEntity(entity) {
|
|
3257
|
+
return Boolean(entity && entity.graph_kind === "memory" && MEMORY_PACKET_TYPES.has(entity.type));
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
function renderOwners() {
|
|
3261
|
+
if (!els.ownersList) return;
|
|
3262
|
+
var contributors = state.reports && state.reports.contributors;
|
|
3263
|
+
var risk = state.reports && state.reports.risk;
|
|
3264
|
+
var profiles = contributors && Array.isArray(contributors.contributors) ? contributors.contributors : [];
|
|
3265
|
+
var silos = risk && Array.isArray(risk.ownership_silos) ? risk.ownership_silos : [];
|
|
3266
|
+
els.ownersStatus.textContent = profiles.length ? profiles.length + " contributors" : "not loaded";
|
|
3267
|
+
els.ownersList.textContent = "";
|
|
3268
|
+
if (els.ownersSummary) renderOwnersSummary(profiles, silos);
|
|
3269
|
+
if (!profiles.length && !silos.length) {
|
|
3270
|
+
els.ownersList.className = "owners-list details-empty";
|
|
3271
|
+
els.ownersList.textContent = "No owner report loaded. Launch with `kage viewer --project <repo>` to load contributor and ownership reports.";
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
els.ownersList.className = "owners-list";
|
|
3275
|
+
profiles.slice(0, 24).forEach(function (profile) {
|
|
3276
|
+
var item = document.createElement("article");
|
|
3277
|
+
item.className = "owner-card";
|
|
3278
|
+
item.innerHTML = [
|
|
3279
|
+
"<div class=\"owner-head\"><strong></strong><span></span></div>",
|
|
3280
|
+
"<div class=\"owner-stats\"></div>",
|
|
3281
|
+
"<p></p>"
|
|
3282
|
+
].join("");
|
|
3283
|
+
item.querySelector("strong").textContent = shortContributor(profile.contributor);
|
|
3284
|
+
item.querySelector(".owner-head span").textContent = (profile.primary_owned_files || 0) + " owned files";
|
|
3285
|
+
item.querySelector(".owner-stats").textContent = [
|
|
3286
|
+
(profile.commits_total || 0) + " commits",
|
|
3287
|
+
(profile.commits_90d || 0) + " in 90d",
|
|
3288
|
+
(profile.silo_files && profile.silo_files.length ? profile.silo_files.length + " silo files" : "no silo flags")
|
|
3289
|
+
].join(" | ");
|
|
3290
|
+
item.querySelector("p").textContent = Array.isArray(profile.top_modules) && profile.top_modules.length
|
|
3291
|
+
? "Modules: " + profile.top_modules.slice(0, 4).join(", ")
|
|
3292
|
+
: "Local git ownership signal.";
|
|
3293
|
+
els.ownersList.appendChild(item);
|
|
3294
|
+
});
|
|
3295
|
+
if (silos.length) {
|
|
3296
|
+
var siloSection = document.createElement("section");
|
|
3297
|
+
siloSection.className = "owner-silos";
|
|
3298
|
+
siloSection.innerHTML = "<h3>Ownership Silos</h3>";
|
|
3299
|
+
silos.slice(0, 16).forEach(function (silo) {
|
|
3300
|
+
var row = document.createElement("button");
|
|
3301
|
+
row.type = "button";
|
|
3302
|
+
row.className = "owner-silo-row";
|
|
3303
|
+
row.innerHTML = "<strong></strong><span></span>";
|
|
3304
|
+
row.querySelector("strong").textContent = silo.file_path || "file";
|
|
3305
|
+
row.querySelector("span").textContent = [
|
|
3306
|
+
shortContributor(silo.primary_owner || "unknown"),
|
|
3307
|
+
silo.primary_owner_pct != null ? Math.round(Number(silo.primary_owner_pct || 0) * 100) + "% ownership" : "",
|
|
3308
|
+
(silo.commit_count_total || 0) + " commits"
|
|
3309
|
+
].filter(Boolean).join(" | ");
|
|
3310
|
+
row.addEventListener("click", function () {
|
|
3311
|
+
focusGraphPath(silo.file_path);
|
|
3312
|
+
});
|
|
3313
|
+
siloSection.appendChild(row);
|
|
3314
|
+
});
|
|
3315
|
+
els.ownersList.appendChild(siloSection);
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
function renderOwnersSummary(profiles, silos) {
|
|
3320
|
+
els.ownersSummary.textContent = "";
|
|
3321
|
+
if (!profiles.length && !silos.length) return;
|
|
3322
|
+
var totalOwned = profiles.reduce(function (sum, profile) { return sum + Number(profile.primary_owned_files || 0); }, 0);
|
|
3323
|
+
var topOwned = profiles.reduce(function (max, profile) { return Math.max(max, Number(profile.primary_owned_files || 0)); }, 0);
|
|
3324
|
+
var topOwnerShare = totalOwned ? Math.round(topOwned / totalOwned * 100) : 0;
|
|
3325
|
+
var commits90 = profiles.reduce(function (sum, profile) { return sum + Number(profile.commits_90d || 0); }, 0);
|
|
3326
|
+
var maxOwned = Math.max(1, topOwned);
|
|
3327
|
+
els.ownersSummary.appendChild(metricDonut(
|
|
3328
|
+
"Backup coverage",
|
|
3329
|
+
Math.max(0, 100 - topOwnerShare),
|
|
3330
|
+
silos.length ? silos.length + " single-owner file(s) need backup reviewers" : "No loaded ownership silos",
|
|
3331
|
+
silos.length ? "Click silo rows to inspect the file in Graph." : "Keep ownership spread visible before large changes.",
|
|
3332
|
+
silos.length ? "warn" : "ok"
|
|
3333
|
+
));
|
|
3334
|
+
els.ownersSummary.appendChild(metricBars("Owner concentration", profiles.length + " profiles", profiles.slice(0, 4).map(function (profile) {
|
|
3335
|
+
var owned = Number(profile.primary_owned_files || 0);
|
|
3336
|
+
return {
|
|
3337
|
+
label: shortContributor(profile.contributor),
|
|
3338
|
+
value: owned + " files",
|
|
3339
|
+
score: owned / maxOwned * 100,
|
|
3340
|
+
status: owned && Array.isArray(profile.silo_files) && profile.silo_files.length ? "warn" : "ok"
|
|
3341
|
+
};
|
|
3342
|
+
}), "Use this for reviewer routing and bus-factor checks.", silos.length ? "warn" : "ok"));
|
|
3343
|
+
els.ownersSummary.appendChild(metricBars("Recent activity", commits90 + " commits", profiles.slice(0, 4).map(function (profile) {
|
|
3344
|
+
var commits = Number(profile.commits_90d || 0);
|
|
3345
|
+
var maxCommits = Math.max(1, profiles.reduce(function (max, item) { return Math.max(max, Number(item.commits_90d || 0)); }, 0));
|
|
3346
|
+
return {
|
|
3347
|
+
label: shortContributor(profile.contributor),
|
|
3348
|
+
value: commits,
|
|
3349
|
+
score: commits / maxCommits * 100,
|
|
3350
|
+
status: commits ? "ok" : ""
|
|
3351
|
+
};
|
|
3352
|
+
}), "Prefer recent editors for fast review context.", "ok"));
|
|
3353
|
+
}
|
|
3354
|
+
|
|
2614
3355
|
function renderReviewQueue() {
|
|
2615
3356
|
if (!els.reviewList) return;
|
|
2616
3357
|
var packets = state.pendingPackets || [];
|
|
2617
3358
|
var inbox = state.inbox;
|
|
2618
3359
|
var inboxItems = inbox && Array.isArray(inbox.items) ? inbox.items : [];
|
|
2619
|
-
|
|
3360
|
+
var counts = inbox && inbox.counts ? inbox.counts : {};
|
|
3361
|
+
var openCount = reviewOpenCount(counts, packets, inboxItems);
|
|
3362
|
+
els.reviewCount.textContent = String(openCount);
|
|
2620
3363
|
els.reviewList.textContent = "";
|
|
3364
|
+
if (els.reviewOverview) renderReviewOverview(inbox, packets, inboxItems);
|
|
2621
3365
|
if (!packets.length && !inboxItems.length && !state.reviewText) {
|
|
2622
3366
|
els.reviewList.className = "review-list details-empty";
|
|
2623
3367
|
els.reviewList.textContent = "No pending packets loaded. Launch with `kage viewer --project <repo>` to load review context automatically.";
|
|
@@ -2627,7 +3371,6 @@
|
|
|
2627
3371
|
if (inbox) {
|
|
2628
3372
|
var summary = document.createElement("div");
|
|
2629
3373
|
summary.className = "review-item";
|
|
2630
|
-
var counts = inbox.counts || {};
|
|
2631
3374
|
summary.innerHTML = [
|
|
2632
3375
|
"<div class=\"review-title\"></div>",
|
|
2633
3376
|
"<div class=\"review-meta\"></div>",
|
|
@@ -2644,10 +3387,10 @@
|
|
|
2644
3387
|
summary.querySelector(".review-summary").textContent = Array.isArray(inbox.recommendations) && inbox.recommendations.length
|
|
2645
3388
|
? inbox.recommendations.slice(0, 2).join(" ")
|
|
2646
3389
|
: "No inbox recommendations.";
|
|
2647
|
-
summary.querySelector(".review-risks").textContent =
|
|
3390
|
+
summary.querySelector(".review-risks").textContent = openCount ? "Resolve inbox items before merge" : "Ready for handoff";
|
|
2648
3391
|
els.reviewList.appendChild(summary);
|
|
2649
3392
|
}
|
|
2650
|
-
inboxItems.slice(0,
|
|
3393
|
+
inboxItems.slice(0, 8).forEach(function (entry) {
|
|
2651
3394
|
var item = document.createElement("div");
|
|
2652
3395
|
item.className = "review-item";
|
|
2653
3396
|
item.innerHTML = [
|
|
@@ -2659,7 +3402,9 @@
|
|
|
2659
3402
|
item.querySelector(".review-title").textContent = entry.title || entry.summary || entry.kind;
|
|
2660
3403
|
item.querySelector(".review-meta").textContent = [entry.kind, entry.severity, entry.type, entry.status].filter(Boolean).join(" | ");
|
|
2661
3404
|
item.querySelector(".review-summary").textContent = entry.action || entry.summary || "";
|
|
2662
|
-
item.querySelector(".review-risks").textContent = Array.isArray(entry.reasons) && entry.reasons.length
|
|
3405
|
+
item.querySelector(".review-risks").textContent = Array.isArray(entry.reasons) && entry.reasons.length
|
|
3406
|
+
? trimIntelText(entry.reasons[0], 86)
|
|
3407
|
+
: "Review before handoff";
|
|
2663
3408
|
els.reviewList.appendChild(item);
|
|
2664
3409
|
});
|
|
2665
3410
|
packets.forEach(function (packet) {
|
|
@@ -2675,7 +3420,9 @@
|
|
|
2675
3420
|
item.querySelector(".review-title").textContent = packet.title || packet.id;
|
|
2676
3421
|
item.querySelector(".review-meta").textContent = [packet.type, packet.status, "score " + (quality.score == null ? "n/a" : quality.score + "/100")].filter(Boolean).join(" | ");
|
|
2677
3422
|
item.querySelector(".review-summary").textContent = packet.summary || "";
|
|
2678
|
-
item.querySelector(".review-risks").textContent = Array.isArray(quality.risks) && quality.risks.length
|
|
3423
|
+
item.querySelector(".review-risks").textContent = Array.isArray(quality.risks) && quality.risks.length
|
|
3424
|
+
? "Resolve " + quality.risks.join(", ")
|
|
3425
|
+
: "Evidence looks clean";
|
|
2679
3426
|
els.reviewList.appendChild(item);
|
|
2680
3427
|
});
|
|
2681
3428
|
if (state.reviewText) {
|
|
@@ -2687,25 +3434,58 @@
|
|
|
2687
3434
|
}
|
|
2688
3435
|
}
|
|
2689
3436
|
|
|
3437
|
+
function renderReviewOverview(inbox, packets, inboxItems) {
|
|
3438
|
+
els.reviewOverview.textContent = "";
|
|
3439
|
+
var counts = inbox && inbox.counts ? inbox.counts : {};
|
|
3440
|
+
var pending = Number(firstNumber(counts.pending, packets.length, 0));
|
|
3441
|
+
var stale = Number(firstNumber(counts.stale, 0));
|
|
3442
|
+
var duplicates = Number(firstNumber(counts.duplicates, 0));
|
|
3443
|
+
var missingContext = Number(firstNumber(counts.missing_context, 0));
|
|
3444
|
+
var blockers = reviewOpenCount(counts, packets, inboxItems);
|
|
3445
|
+
els.reviewOverview.appendChild(metricDonut(
|
|
3446
|
+
"Handoff readiness",
|
|
3447
|
+
blockers ? 0 : 100,
|
|
3448
|
+
blockers ? blockers + " review blocker(s) need attention" : "No pending, stale, duplicate, or missing-context memory",
|
|
3449
|
+
blockers ? "Resolve these before trusting branch memory." : "Ready to hand work to another agent or teammate.",
|
|
3450
|
+
blockers ? "warn" : "ok"
|
|
3451
|
+
));
|
|
3452
|
+
els.reviewOverview.appendChild(metricBars("Inbox breakdown", blockers ? blockers + " open" : "clear", [
|
|
3453
|
+
{ label: "Pending", value: pending, score: Math.min(100, pending * 24), status: pending ? "warn" : "ok" },
|
|
3454
|
+
{ label: "Stale", value: stale, score: Math.min(100, stale * 24), status: stale ? "warn" : "ok" },
|
|
3455
|
+
{ label: "Duplicates", value: duplicates, score: Math.min(100, duplicates * 24), status: duplicates ? "warn" : "ok" },
|
|
3456
|
+
{ label: "Missing context", value: missingContext, score: Math.min(100, missingContext * 24), status: missingContext ? "warn" : "ok" }
|
|
3457
|
+
], "These are the only review metrics that should block merge or handoff.", blockers ? "warn" : "ok"));
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3460
|
+
function reviewOpenCount(counts, packets, inboxItems) {
|
|
3461
|
+
var pending = Number(firstNumber(counts && counts.pending, packets && packets.length, 0));
|
|
3462
|
+
var stale = Number(firstNumber(counts && counts.stale, 0));
|
|
3463
|
+
var duplicates = Number(firstNumber(counts && counts.duplicates, 0));
|
|
3464
|
+
var missingContext = Number(firstNumber(counts && counts.missing_context, 0));
|
|
3465
|
+
var counted = pending + stale + duplicates + missingContext;
|
|
3466
|
+
if (counted) return counted;
|
|
3467
|
+
return Array.isArray(inboxItems) ? inboxItems.length : 0;
|
|
3468
|
+
}
|
|
3469
|
+
|
|
2690
3470
|
function renderProof() {
|
|
2691
3471
|
if (!els.proofList) return;
|
|
2692
3472
|
var metrics = state.metrics;
|
|
2693
3473
|
els.proofList.textContent = "";
|
|
2694
3474
|
if (!metrics) {
|
|
2695
3475
|
els.proofStatus.textContent = "not loaded";
|
|
3476
|
+
if (els.proofOverview) els.proofOverview.textContent = "";
|
|
2696
3477
|
els.proofList.className = "proof-list details-empty";
|
|
2697
3478
|
els.proofList.textContent = "Metrics not loaded. Run `kage metrics --project <repo> --json > .agent_memory/metrics.json` or launch with `kage viewer`.";
|
|
2698
3479
|
return;
|
|
2699
3480
|
}
|
|
2700
3481
|
els.proofStatus.textContent = "loaded";
|
|
2701
3482
|
els.proofList.className = "proof-list";
|
|
3483
|
+
if (els.proofOverview) renderProofOverview(metrics, state.reports || {});
|
|
2702
3484
|
var rows = [
|
|
2703
|
-
["
|
|
2704
|
-
["Useful memory", metrics.quality ? metrics.quality.useful_memory_ratio_percent + "%" : "n/a"],
|
|
3485
|
+
["Validation", metrics.harness && metrics.harness.validation_ok ? "clean" : "check"],
|
|
2705
3486
|
["Evidence", metrics.memory_graph ? metrics.memory_graph.evidence_coverage_percent + "%" : "n/a"],
|
|
2706
3487
|
["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"]
|
|
3488
|
+
["Recall savings", metrics.pain ? String(metrics.pain.estimated_tokens_saved) : metrics.savings ? String(metrics.savings.estimated_tokens_saved_per_recall) : "n/a"]
|
|
2709
3489
|
];
|
|
2710
3490
|
rows.forEach(function (row) {
|
|
2711
3491
|
var item = document.createElement("div");
|
|
@@ -2717,6 +3497,29 @@
|
|
|
2717
3497
|
});
|
|
2718
3498
|
}
|
|
2719
3499
|
|
|
3500
|
+
function renderProofOverview(metrics, reports) {
|
|
3501
|
+
els.proofOverview.textContent = "";
|
|
3502
|
+
var quality = reports.quality || {};
|
|
3503
|
+
var benchmark = reports.benchmark || {};
|
|
3504
|
+
var gates = Array.isArray(benchmark.gates) ? benchmark.gates : [];
|
|
3505
|
+
var passingGates = gates.filter(function (gate) { return gate.pass; }).length;
|
|
3506
|
+
var gatePercent = gates.length ? Math.round(passingGates / gates.length * 100) : (benchmark.ok ? 100 : 0);
|
|
3507
|
+
var evidence = Number(firstNumber(metrics.memory_graph && metrics.memory_graph.evidence_coverage_percent, quality.evidence_coverage_percent, 0));
|
|
3508
|
+
var pathGrounding = Number(firstNumber(quality.path_grounding_coverage_percent, 0));
|
|
3509
|
+
els.proofOverview.appendChild(metricDonut(
|
|
3510
|
+
"Trust gate",
|
|
3511
|
+
gatePercent,
|
|
3512
|
+
gates.length ? passingGates + " of " + gates.length + " benchmark gates passing" : "Benchmark report not loaded",
|
|
3513
|
+
gatePercent >= 100 ? "Keep this green before publishing or handing off." : "Fix failing proof gates before release.",
|
|
3514
|
+
gatePercent >= 100 ? "ok" : "warn"
|
|
3515
|
+
));
|
|
3516
|
+
els.proofOverview.appendChild(metricBars("Memory quality", evidence + "% evidence", [
|
|
3517
|
+
{ label: "Evidence", value: evidence + "%", score: evidence, status: evidence >= 80 ? "ok" : "warn" },
|
|
3518
|
+
{ label: "Path grounded", value: pathGrounding ? pathGrounding + "%" : "n/a", score: pathGrounding || 0, status: pathGrounding >= 80 ? "ok" : "warn" },
|
|
3519
|
+
{ 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" }
|
|
3520
|
+
], "Trust memory only when it is evidence-backed and path-grounded.", evidence >= 80 ? "ok" : "warn"));
|
|
3521
|
+
}
|
|
3522
|
+
|
|
2720
3523
|
function renderIntelligence() {
|
|
2721
3524
|
if (!els.intelligenceList) return;
|
|
2722
3525
|
var reports = state.reports || {};
|
|
@@ -2729,36 +3532,40 @@
|
|
|
2729
3532
|
return;
|
|
2730
3533
|
}
|
|
2731
3534
|
els.intelligenceList.className = "intelligence-list";
|
|
2732
|
-
cards.forEach(function (card) {
|
|
3535
|
+
normalizeIntelCards(cards).slice(0, 6).forEach(function (card) {
|
|
2733
3536
|
var item = document.createElement("article");
|
|
2734
3537
|
item.className = "intel-card";
|
|
2735
3538
|
item.innerHTML = [
|
|
2736
|
-
"<h3></h3>",
|
|
2737
|
-
"<div class=\"intel-
|
|
2738
|
-
"<
|
|
3539
|
+
"<div class=\"intel-card-head\"><div><h3></h3><span></span></div><strong></strong></div>",
|
|
3540
|
+
"<div class=\"intel-metric-label\"></div>",
|
|
3541
|
+
"<p class=\"intel-highlight\"></p>",
|
|
3542
|
+
"<p class=\"intel-action\"><b>Action:</b> <span></span></p>",
|
|
2739
3543
|
"<ul></ul>"
|
|
2740
3544
|
].join("");
|
|
2741
3545
|
item.querySelector("h3").textContent = card.title;
|
|
2742
|
-
item.querySelector(".intel-
|
|
2743
|
-
item.querySelector(".intel-
|
|
3546
|
+
item.querySelector(".intel-card-head span").textContent = card.kicker;
|
|
3547
|
+
item.querySelector(".intel-card-head strong").textContent = card.metric || "n/a";
|
|
3548
|
+
item.querySelector(".intel-metric-label").textContent = card.metricLabel || "signal";
|
|
3549
|
+
item.querySelector(".intel-highlight").textContent = card.highlight || card.summary || "";
|
|
3550
|
+
item.querySelector(".intel-action span").textContent = card.action || "Review this signal before changing related code.";
|
|
2744
3551
|
var list = item.querySelector("ul");
|
|
2745
|
-
card.rows.slice(0,
|
|
3552
|
+
card.rows.slice(0, 3).forEach(function (row) {
|
|
2746
3553
|
var li = document.createElement("li");
|
|
2747
3554
|
li.innerHTML = "<strong></strong> <span></span>";
|
|
2748
3555
|
li.querySelector("strong").textContent = row[0];
|
|
2749
|
-
li.querySelector("span").textContent = row[1];
|
|
3556
|
+
li.querySelector("span").textContent = trimIntelText(row[1], 92);
|
|
2750
3557
|
list.appendChild(li);
|
|
2751
3558
|
});
|
|
2752
3559
|
els.intelligenceList.appendChild(item);
|
|
2753
3560
|
});
|
|
2754
|
-
var sections = buildIntelligenceSections(reports);
|
|
3561
|
+
var sections = rankIntelligenceSections(buildIntelligenceSections(reports)).slice(0, 4);
|
|
2755
3562
|
if (sections.length) {
|
|
2756
|
-
var
|
|
2757
|
-
|
|
3563
|
+
var deepGrid = document.createElement("div");
|
|
3564
|
+
deepGrid.className = "intel-deep-grid";
|
|
2758
3565
|
sections.forEach(function (section) {
|
|
2759
|
-
|
|
3566
|
+
deepGrid.appendChild(renderIntelligenceSection(section));
|
|
2760
3567
|
});
|
|
2761
|
-
els.intelligenceList.appendChild(
|
|
3568
|
+
els.intelligenceList.appendChild(deepGrid);
|
|
2762
3569
|
}
|
|
2763
3570
|
}
|
|
2764
3571
|
|
|
@@ -2781,32 +3588,46 @@
|
|
|
2781
3588
|
var list = document.createElement("div");
|
|
2782
3589
|
list.className = "intel-section-list";
|
|
2783
3590
|
section.rows.slice(0, section.limit || 8).forEach(function (row) {
|
|
2784
|
-
var
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
3591
|
+
var rowEl = document.createElement(row.path ? "button" : "div");
|
|
3592
|
+
if (row.path) rowEl.type = "button";
|
|
3593
|
+
rowEl.className = classNames("intel-row", row.status && "intel-row-" + safeCssName(row.status), row.path && "clickable");
|
|
3594
|
+
rowEl.innerHTML = [
|
|
2788
3595
|
"<span class=\"intel-row-main\"><strong></strong><em></em></span>",
|
|
2789
3596
|
"<span class=\"intel-row-meta\"></span>",
|
|
2790
3597
|
"<span class=\"intel-row-bar\"><i></i></span>"
|
|
2791
3598
|
].join("");
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
3599
|
+
rowEl.querySelector("strong").textContent = row.label || "";
|
|
3600
|
+
rowEl.querySelector("em").textContent = row.value || "";
|
|
3601
|
+
rowEl.querySelector(".intel-row-meta").textContent = row.meta || "";
|
|
3602
|
+
rowEl.querySelector(".intel-row-bar i").style.width = clamp(Number(row.score || 0), 4, 100) + "%";
|
|
2796
3603
|
if (row.path) {
|
|
2797
|
-
|
|
2798
|
-
|
|
3604
|
+
rowEl.title = "Focus " + row.path + " in the graph";
|
|
3605
|
+
rowEl.setAttribute("aria-label", "Focus " + row.path + " in Graph");
|
|
3606
|
+
rowEl.addEventListener("click", function () {
|
|
2799
3607
|
focusGraphPath(row.path);
|
|
2800
3608
|
});
|
|
2801
|
-
} else {
|
|
2802
|
-
button.disabled = true;
|
|
2803
3609
|
}
|
|
2804
|
-
list.appendChild(
|
|
3610
|
+
list.appendChild(rowEl);
|
|
2805
3611
|
});
|
|
2806
3612
|
panel.appendChild(list);
|
|
2807
3613
|
return panel;
|
|
2808
3614
|
}
|
|
2809
3615
|
|
|
3616
|
+
function rankIntelligenceSections(sections) {
|
|
3617
|
+
return sections.slice().sort(function (a, b) {
|
|
3618
|
+
return intelligenceSectionPriority(a) - intelligenceSectionPriority(b);
|
|
3619
|
+
});
|
|
3620
|
+
}
|
|
3621
|
+
|
|
3622
|
+
function intelligenceSectionPriority(section) {
|
|
3623
|
+
var title = String(section && section.title || "").toLowerCase();
|
|
3624
|
+
if (title.indexOf("blast") !== -1 || title.indexOf("risk") !== -1) return 0;
|
|
3625
|
+
if (title.indexOf("onboarding") !== -1 || title.indexOf("decision") !== -1) return 1;
|
|
3626
|
+
if (title.indexOf("module") !== -1 || title.indexOf("health") !== -1) return 2;
|
|
3627
|
+
if (title.indexOf("owner") !== -1 || title.indexOf("contributor") !== -1) return 3;
|
|
3628
|
+
return 4;
|
|
3629
|
+
}
|
|
3630
|
+
|
|
2810
3631
|
function buildIntelligenceSections(reports) {
|
|
2811
3632
|
var sections = [];
|
|
2812
3633
|
var contributors = reports.contributors;
|
|
@@ -2846,7 +3667,7 @@
|
|
|
2846
3667
|
title: "Ownership Map",
|
|
2847
3668
|
kicker: "who owns what",
|
|
2848
3669
|
stat: silos.length + " silos",
|
|
2849
|
-
summary: "
|
|
3670
|
+
summary: "Action: assign backup reviewers for silo files before risky changes.",
|
|
2850
3671
|
rows: rows,
|
|
2851
3672
|
limit: 10,
|
|
2852
3673
|
});
|
|
@@ -2861,7 +3682,7 @@
|
|
|
2861
3682
|
title: "Module Health Map",
|
|
2862
3683
|
kicker: "churn / tests / ownership",
|
|
2863
3684
|
stat: modules.length + " modules",
|
|
2864
|
-
summary: "
|
|
3685
|
+
summary: "Action: start cleanup and test planning from the lowest-score modules.",
|
|
2865
3686
|
rows: modules.slice(0, 8).map(function (item) {
|
|
2866
3687
|
return {
|
|
2867
3688
|
label: item.module,
|
|
@@ -2880,7 +3701,7 @@
|
|
|
2880
3701
|
title: "Onboarding Targets",
|
|
2881
3702
|
kicker: "missing repo lore",
|
|
2882
3703
|
stat: (decisions.coverage_percent != null ? decisions.coverage_percent + "%" : "n/a"),
|
|
2883
|
-
summary: "
|
|
3704
|
+
summary: "Action: capture why-memory for these files before the next agent works there.",
|
|
2884
3705
|
rows: gaps.slice(0, 8).map(function (gap) {
|
|
2885
3706
|
var score = Math.min(100, Number(gap.dependents || 0) * 18 + Number(gap.churn_90d || 0) * 6 + 12);
|
|
2886
3707
|
return {
|
|
@@ -2906,7 +3727,7 @@
|
|
|
2906
3727
|
title: "Architecture Communities",
|
|
2907
3728
|
kicker: "module clusters",
|
|
2908
3729
|
stat: communities.length + " clusters",
|
|
2909
|
-
summary: "
|
|
3730
|
+
summary: "Action: use clusters to understand the graph before changing architecture.",
|
|
2910
3731
|
rows: communities.slice(0, 8).map(function (community) {
|
|
2911
3732
|
var files = community.files || [];
|
|
2912
3733
|
var entrypoints = community.entrypoints || [];
|
|
@@ -2928,7 +3749,7 @@
|
|
|
2928
3749
|
title: "Execution Flows",
|
|
2929
3750
|
kicker: "entrypoint traces",
|
|
2930
3751
|
stat: insights.entry_flows.length + " flows",
|
|
2931
|
-
summary: "
|
|
3752
|
+
summary: "Action: inspect entry files first when debugging runtime behavior.",
|
|
2932
3753
|
rows: insights.entry_flows.slice(0, 8).map(function (flow) {
|
|
2933
3754
|
var path = flow.path || [];
|
|
2934
3755
|
return {
|
|
@@ -2987,7 +3808,7 @@
|
|
|
2987
3808
|
title: "Workspace Map",
|
|
2988
3809
|
kicker: "deps / contracts / co-changes",
|
|
2989
3810
|
stat: workspaceRows.length + " links",
|
|
2990
|
-
summary: "
|
|
3811
|
+
summary: "Action: check linked repos before changing shared packages or contracts.",
|
|
2991
3812
|
rows: workspaceRows,
|
|
2992
3813
|
limit: 14,
|
|
2993
3814
|
});
|
|
@@ -3021,7 +3842,7 @@
|
|
|
3021
3842
|
title: "Blast Radius",
|
|
3022
3843
|
kicker: "change impact",
|
|
3023
3844
|
stat: riskRows.length + " signals",
|
|
3024
|
-
summary: "
|
|
3845
|
+
summary: "Action: review tests, owners, and dependents before editing these targets.",
|
|
3025
3846
|
rows: riskRows,
|
|
3026
3847
|
limit: 10,
|
|
3027
3848
|
});
|
|
@@ -3043,11 +3864,13 @@
|
|
|
3043
3864
|
});
|
|
3044
3865
|
if (!found) {
|
|
3045
3866
|
els.searchInput.value = normalized;
|
|
3867
|
+
showViewerPageInPlace("graph");
|
|
3046
3868
|
scheduleRender();
|
|
3047
3869
|
return;
|
|
3048
3870
|
}
|
|
3049
3871
|
selectEntity(found.id, true);
|
|
3050
3872
|
els.searchInput.value = normalized;
|
|
3873
|
+
showViewerPageInPlace("graph");
|
|
3051
3874
|
scheduleRender();
|
|
3052
3875
|
}
|
|
3053
3876
|
|
|
@@ -3209,18 +4032,86 @@
|
|
|
3209
4032
|
return cards;
|
|
3210
4033
|
}
|
|
3211
4034
|
|
|
4035
|
+
function normalizeIntelCards(cards) {
|
|
4036
|
+
return cards.map(function (card) {
|
|
4037
|
+
var normalized = Object.assign({}, card);
|
|
4038
|
+
var row = function (label) {
|
|
4039
|
+
var found = (card.rows || []).find(function (item) { return item[0] === label; });
|
|
4040
|
+
return found ? found[1] : null;
|
|
4041
|
+
};
|
|
4042
|
+
if (card.title === "Memory-Code Bridge") {
|
|
4043
|
+
normalized.metric = row("Links") || "0";
|
|
4044
|
+
normalized.metricLabel = "memory-code links";
|
|
4045
|
+
normalized.highlight = "Shows whether saved repo knowledge is tied to actual files, symbols, routes, and tests.";
|
|
4046
|
+
normalized.action = "If this is low, capture memory with concrete paths so agents can recall it during edits.";
|
|
4047
|
+
} else if (card.title === "Change Risk") {
|
|
4048
|
+
var siloText = row("Silos");
|
|
4049
|
+
var siloMatch = siloText && String(siloText).match(/\d+/);
|
|
4050
|
+
normalized.metric = siloMatch ? siloMatch[0] + " silos" : ((card.rows || []).length + " signals");
|
|
4051
|
+
normalized.metricLabel = "risk signals";
|
|
4052
|
+
normalized.highlight = "Flags files with blast radius, test gaps, or ownership concentration.";
|
|
4053
|
+
normalized.action = "Use these rows to pick tests and reviewers before touching risky files.";
|
|
4054
|
+
} else if (card.title === "Contributors") {
|
|
4055
|
+
normalized.metric = (card.rows || []).length + " profiles";
|
|
4056
|
+
normalized.metricLabel = "review routing";
|
|
4057
|
+
normalized.highlight = "Shows who recently touched or owns parts of the repo.";
|
|
4058
|
+
normalized.action = "Use this to find backup reviewers and avoid single-person knowledge bottlenecks.";
|
|
4059
|
+
} else if (card.title === "Decision Memory") {
|
|
4060
|
+
normalized.metric = row("Coverage") || "n/a";
|
|
4061
|
+
normalized.metricLabel = "why-memory coverage";
|
|
4062
|
+
normalized.highlight = "Shows whether important code paths have captured rationale and gotchas.";
|
|
4063
|
+
normalized.action = "Add memory for coverage gaps before future agents rediscover the same context.";
|
|
4064
|
+
} else if (card.title === "Module Health") {
|
|
4065
|
+
normalized.metric = (card.rows || []).length + " modules";
|
|
4066
|
+
normalized.metricLabel = "lowest scores first";
|
|
4067
|
+
normalized.highlight = "Ranks modules by churn, tests, ownership, and graph signals.";
|
|
4068
|
+
normalized.action = "Start with low-score modules when planning cleanup or refactors.";
|
|
4069
|
+
} else if (card.title === "Graph Insights") {
|
|
4070
|
+
normalized.metric = row("Cycles") || row("Communities") || "n/a";
|
|
4071
|
+
normalized.metricLabel = row("Cycles") != null ? "dependency cycles" : "architecture clusters";
|
|
4072
|
+
normalized.highlight = "Explains dense graph structure through central files, cycles, and communities.";
|
|
4073
|
+
normalized.action = "Inspect central files and cycles before making architectural changes.";
|
|
4074
|
+
} else if (card.title === "Workspace") {
|
|
4075
|
+
normalized.metric = row("Repos") || "n/a";
|
|
4076
|
+
normalized.metricLabel = "connected repos";
|
|
4077
|
+
normalized.highlight = "Shows package, route, topic, and co-change links across local repos.";
|
|
4078
|
+
normalized.action = "Check these links before changing shared contracts or packages.";
|
|
4079
|
+
} else if (card.title === "Memory Quality") {
|
|
4080
|
+
normalized.metric = row("Evidence") || row("Useful") || "n/a";
|
|
4081
|
+
normalized.metricLabel = "trust signal";
|
|
4082
|
+
normalized.highlight = "Shows whether memory is evidence-backed, grounded, and reviewable.";
|
|
4083
|
+
normalized.action = "Review pending or weak memory before relying on it for agent handoff.";
|
|
4084
|
+
} else if (card.title === "Benchmark") {
|
|
4085
|
+
normalized.metric = (card.rows || []).filter(function (item) { return String(item[1] || "").indexOf("pass") !== -1; }).length + " pass";
|
|
4086
|
+
normalized.metricLabel = "local proof checks";
|
|
4087
|
+
normalized.highlight = "Shows whether repo memory and graph behavior pass local quality checks.";
|
|
4088
|
+
normalized.action = "Use failed checks as release blockers or cleanup targets.";
|
|
4089
|
+
}
|
|
4090
|
+
return normalized;
|
|
4091
|
+
});
|
|
4092
|
+
}
|
|
4093
|
+
|
|
4094
|
+
function trimIntelText(value, limit) {
|
|
4095
|
+
var text = String(value == null ? "" : value);
|
|
4096
|
+
var max = Number(limit || 90);
|
|
4097
|
+
if (text.length <= max) return text;
|
|
4098
|
+
return text.slice(0, Math.max(0, max - 1)).replace(/\s+\S*$/, "") + "...";
|
|
4099
|
+
}
|
|
4100
|
+
|
|
3212
4101
|
function renderStatusStrip(visibleEntities, visibleEdges, official) {
|
|
3213
4102
|
if (!els.statusStrip) return;
|
|
3214
4103
|
var memoryCount = visibleEntities.filter(function (entity) { return entity.graph_kind === "memory"; }).length;
|
|
3215
4104
|
var codeCount = visibleEntities.filter(function (entity) { return entity.graph_kind === "code"; }).length;
|
|
3216
4105
|
var reviewFlags = visibleEdges.filter(function (edge) { return reviewStatus(edge) !== "ok"; }).length;
|
|
4106
|
+
var memoryCodeLinks = state.edges.filter(isMemoryCodeEdge).length;
|
|
4107
|
+
var pendingReview = official && official.memory_graph ? Number(firstNumber(official.memory_graph.pending_packets, 0)) : 0;
|
|
3217
4108
|
var pills = official ? [
|
|
3218
|
-
["
|
|
3219
|
-
["
|
|
3220
|
-
["
|
|
3221
|
-
["
|
|
3222
|
-
["
|
|
3223
|
-
["Memory
|
|
4109
|
+
["Status", official.harness && official.harness.validation_ok ? "Clean" : "Check", official.harness && official.harness.validation_ok ? "memory" : "warn"],
|
|
4110
|
+
["Review", pendingReview ? pendingReview + " pending" : "Clear", pendingReview ? "warn" : "memory"],
|
|
4111
|
+
["Source map", official.structural_index ? official.structural_index.files + " files" : official.code_graph.files + " files", "code"],
|
|
4112
|
+
["Symbols", String(official.code_graph.symbols), "code"],
|
|
4113
|
+
["Coverage", official.code_graph.indexer_coverage_percent + "%", "code"],
|
|
4114
|
+
["Memory links", String(memoryCodeLinks), memoryCodeLinks ? "memory" : "warn"]
|
|
3224
4115
|
] : [
|
|
3225
4116
|
["Memory", String(memoryCount), "memory"],
|
|
3226
4117
|
["Code", String(codeCount), "code"],
|
|
@@ -3473,10 +4364,15 @@
|
|
|
3473
4364
|
if (state.three.THREE) return Promise.resolve(state.three.THREE);
|
|
3474
4365
|
if (state.three.loading) return state.three.loading;
|
|
3475
4366
|
state.three.failed = false;
|
|
3476
|
-
|
|
3477
|
-
.
|
|
3478
|
-
|
|
3479
|
-
|
|
4367
|
+
var threeSources = [
|
|
4368
|
+
"./vendor/three/build/three.module.min.js",
|
|
4369
|
+
"../vendor/three/build/three.module.min.js",
|
|
4370
|
+
"/vendor/three/build/three.module.min.js",
|
|
4371
|
+
"https://unpkg.com/three@0.184.0/build/three.module.min.js"
|
|
4372
|
+
];
|
|
4373
|
+
state.three.loading = threeSources.reduce(function (chain, source) {
|
|
4374
|
+
return chain.catch(function () { return import(source); });
|
|
4375
|
+
}, Promise.reject())
|
|
3480
4376
|
.then(function (mod) {
|
|
3481
4377
|
state.three.THREE = mod;
|
|
3482
4378
|
return mod;
|
|
@@ -3493,7 +4389,7 @@
|
|
|
3493
4389
|
ensureThree().then(function () {
|
|
3494
4390
|
if (activeRenderMode() !== "3d") return;
|
|
3495
4391
|
setupThreeScene();
|
|
3496
|
-
rebuildThreeScene();
|
|
4392
|
+
if (graphChanged || !state.three.nodeById.size) rebuildThreeScene();
|
|
3497
4393
|
if (graphChanged || state.three.distance <= 0) fitThreeGraph();
|
|
3498
4394
|
startThreeGraph();
|
|
3499
4395
|
renderThreeFrame();
|