@kage-core/kage-graph-mcp 1.1.33 → 1.1.35
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 +10 -4
- package/package.json +1 -1
- package/viewer/app.js +579 -30
- package/viewer/index.html +218 -103
- package/viewer/styles.css +519 -55
package/viewer/app.js
CHANGED
|
@@ -13,6 +13,16 @@
|
|
|
13
13
|
visibleEntityIds: new Set(),
|
|
14
14
|
visibleEdgeIds: new Set(),
|
|
15
15
|
selected: null,
|
|
16
|
+
viewerSection: "overview",
|
|
17
|
+
viewerAction: null,
|
|
18
|
+
workspaceTab: "controls",
|
|
19
|
+
workspaceOpen: false,
|
|
20
|
+
pathHighlight: {
|
|
21
|
+
nodes: new Set(),
|
|
22
|
+
edges: new Set(),
|
|
23
|
+
direction: "",
|
|
24
|
+
steps: []
|
|
25
|
+
},
|
|
16
26
|
metrics: null,
|
|
17
27
|
inbox: null,
|
|
18
28
|
reports: {
|
|
@@ -119,6 +129,13 @@
|
|
|
119
129
|
graphSubhead: document.getElementById("graphSubhead"),
|
|
120
130
|
selectionStatus: document.getElementById("selectionStatus"),
|
|
121
131
|
searchInput: document.getElementById("searchInput"),
|
|
132
|
+
pathFromInput: document.getElementById("pathFromInput"),
|
|
133
|
+
pathToInput: document.getElementById("pathToInput"),
|
|
134
|
+
pathNodeOptions: document.getElementById("pathNodeOptions"),
|
|
135
|
+
findPath: document.getElementById("findPath"),
|
|
136
|
+
clearPath: document.getElementById("clearPath"),
|
|
137
|
+
pathStatus: document.getElementById("pathStatus"),
|
|
138
|
+
pathResult: document.getElementById("pathResult"),
|
|
122
139
|
viewMode: document.getElementById("viewMode"),
|
|
123
140
|
renderMode: document.getElementById("renderMode"),
|
|
124
141
|
typeFilter: document.getElementById("typeFilter"),
|
|
@@ -146,11 +163,21 @@
|
|
|
146
163
|
entityCount: document.getElementById("entityCount"),
|
|
147
164
|
edgeCount: document.getElementById("edgeCount"),
|
|
148
165
|
reviewCount: document.getElementById("reviewCount"),
|
|
166
|
+
dashboardStats: document.getElementById("dashboardStats"),
|
|
149
167
|
reviewList: document.getElementById("reviewList"),
|
|
150
168
|
proofStatus: document.getElementById("proofStatus"),
|
|
151
169
|
proofList: document.getElementById("proofList"),
|
|
152
170
|
intelligenceStatus: document.getElementById("intelligenceStatus"),
|
|
153
|
-
intelligenceList: document.getElementById("intelligenceList")
|
|
171
|
+
intelligenceList: document.getElementById("intelligenceList"),
|
|
172
|
+
viewerSectionButtons: typeof document.querySelectorAll === "function" ? Array.from(document.querySelectorAll("[data-viewer-section]")) : [],
|
|
173
|
+
dashboardActionButtons: typeof document.querySelectorAll === "function" ? Array.from(document.querySelectorAll("[data-dashboard-action]")) : [],
|
|
174
|
+
workspaceTabs: typeof document.querySelectorAll === "function" ? Array.from(document.querySelectorAll("[data-workspace-tab]")) : [],
|
|
175
|
+
quickViewButtons: typeof document.querySelectorAll === "function" ? Array.from(document.querySelectorAll("[data-quick-view]")) : [],
|
|
176
|
+
quickRenderButtons: typeof document.querySelectorAll === "function" ? Array.from(document.querySelectorAll("[data-quick-render]")) : [],
|
|
177
|
+
quickSearch: document.getElementById("quickSearch"),
|
|
178
|
+
quickPath: document.getElementById("quickPath"),
|
|
179
|
+
quickInspector: document.getElementById("quickInspector"),
|
|
180
|
+
closeWorkspace: document.getElementById("closeWorkspace")
|
|
154
181
|
};
|
|
155
182
|
|
|
156
183
|
var MEMORY_CODE_RELATIONS = new Set(["explains_symbol", "informs_symbol", "fixes_symbol", "applies_to_route", "verified_by_test", "affects_code_path"]);
|
|
@@ -161,8 +188,69 @@
|
|
|
161
188
|
var VISIBLE_EDGE_MIN = 160;
|
|
162
189
|
var VISIBLE_EDGE_MAX = 560;
|
|
163
190
|
|
|
191
|
+
setViewerSection(state.viewerSection);
|
|
192
|
+
setWorkspaceTab(state.workspaceTab, false);
|
|
193
|
+
els.viewerSectionButtons.forEach(function (button) {
|
|
194
|
+
button.addEventListener("click", function () {
|
|
195
|
+
setViewerSection(button.getAttribute("data-viewer-section"));
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
els.dashboardActionButtons.forEach(function (button) {
|
|
199
|
+
button.addEventListener("click", function () {
|
|
200
|
+
openDashboardAction(button.getAttribute("data-dashboard-action"));
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
els.workspaceTabs.forEach(function (button) {
|
|
204
|
+
button.addEventListener("click", function () {
|
|
205
|
+
setWorkspaceTab(button.getAttribute("data-workspace-tab"), true);
|
|
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();
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
if (els.quickSearch) {
|
|
227
|
+
els.quickSearch.addEventListener("click", function () {
|
|
228
|
+
setViewerSection("graph");
|
|
229
|
+
setWorkspaceTab("controls", true);
|
|
230
|
+
els.searchInput.focus();
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
if (els.quickPath) {
|
|
234
|
+
els.quickPath.addEventListener("click", function () {
|
|
235
|
+
setViewerSection("graph");
|
|
236
|
+
setWorkspaceTab("controls", true);
|
|
237
|
+
els.pathFromInput.focus();
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
if (els.quickInspector) {
|
|
241
|
+
els.quickInspector.addEventListener("click", function () {
|
|
242
|
+
setViewerSection("graph");
|
|
243
|
+
setWorkspaceTab("inspector", true);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
if (els.closeWorkspace) els.closeWorkspace.addEventListener("click", closeWorkspace);
|
|
247
|
+
syncQuickControls();
|
|
164
248
|
els.graphFile.addEventListener("change", handleFile);
|
|
165
249
|
els.searchInput.addEventListener("input", scheduleRender);
|
|
250
|
+
els.findPath.addEventListener("click", findDependencyPath);
|
|
251
|
+
els.clearPath.addEventListener("click", clearDependencyPath);
|
|
252
|
+
els.pathFromInput.addEventListener("keydown", function (event) { if (event.key === "Enter") findDependencyPath(); });
|
|
253
|
+
els.pathToInput.addEventListener("keydown", function (event) { if (event.key === "Enter") findDependencyPath(); });
|
|
166
254
|
els.viewMode.addEventListener("change", render);
|
|
167
255
|
els.renderMode.addEventListener("change", function () {
|
|
168
256
|
state.lastVisibleSignature = "";
|
|
@@ -206,11 +294,110 @@
|
|
|
206
294
|
els.maxNodes.value = "90";
|
|
207
295
|
els.showDependencies.checked = false;
|
|
208
296
|
state.selected = null;
|
|
297
|
+
setWorkspaceTab("controls", true);
|
|
298
|
+
clearDependencyPath(false);
|
|
209
299
|
state.lastVisibleSignature = "";
|
|
210
300
|
render();
|
|
211
301
|
});
|
|
212
302
|
loadFromUrlParams();
|
|
213
303
|
|
|
304
|
+
function setWorkspaceTab(tab, open) {
|
|
305
|
+
var allowed = new Set(["controls", "inspector", "intelligence", "review", "tables"]);
|
|
306
|
+
state.workspaceTab = allowed.has(tab) ? tab : "controls";
|
|
307
|
+
if (open !== false) state.workspaceOpen = true;
|
|
308
|
+
if (document.body && document.body.classList) {
|
|
309
|
+
document.body.classList.remove(
|
|
310
|
+
"viewer-tab-controls",
|
|
311
|
+
"viewer-tab-inspector",
|
|
312
|
+
"viewer-tab-intelligence",
|
|
313
|
+
"viewer-tab-review",
|
|
314
|
+
"viewer-tab-tables"
|
|
315
|
+
);
|
|
316
|
+
document.body.classList.add("viewer-tab-" + state.workspaceTab);
|
|
317
|
+
document.body.classList.toggle("viewer-workspace-open", state.workspaceOpen);
|
|
318
|
+
}
|
|
319
|
+
els.workspaceTabs.forEach(function (button) {
|
|
320
|
+
var active = button.getAttribute("data-workspace-tab") === state.workspaceTab;
|
|
321
|
+
button.classList.toggle("active", active);
|
|
322
|
+
button.setAttribute("aria-selected", active ? "true" : "false");
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function setViewerSection(section, action) {
|
|
327
|
+
state.viewerSection = section === "graph" ? "graph" : "overview";
|
|
328
|
+
state.viewerAction = action || null;
|
|
329
|
+
if (state.viewerSection === "overview") closeWorkspace();
|
|
330
|
+
if (document.body && document.body.classList) {
|
|
331
|
+
document.body.classList.remove("viewer-section-overview", "viewer-section-graph");
|
|
332
|
+
document.body.classList.add("viewer-section-" + state.viewerSection);
|
|
333
|
+
}
|
|
334
|
+
syncSectionControls();
|
|
335
|
+
if (state.viewerSection === "graph") resizeActiveGraph();
|
|
336
|
+
}
|
|
337
|
+
|
|
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
|
+
function closeWorkspace() {
|
|
370
|
+
state.workspaceOpen = false;
|
|
371
|
+
if (document.body && document.body.classList) {
|
|
372
|
+
document.body.classList.remove("viewer-workspace-open");
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function selectEntity(id, openInspector) {
|
|
377
|
+
state.selected = { kind: "entity", id: id };
|
|
378
|
+
setViewerSection("graph");
|
|
379
|
+
if (openInspector) setWorkspaceTab("inspector", true);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function selectEdge(id, openInspector) {
|
|
383
|
+
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
|
+
}
|
|
400
|
+
|
|
214
401
|
function handleFile(event) {
|
|
215
402
|
var files = Array.from(event.target.files || []);
|
|
216
403
|
if (!files.length) return;
|
|
@@ -258,9 +445,12 @@
|
|
|
258
445
|
return [episode.id, episode];
|
|
259
446
|
}));
|
|
260
447
|
state.selected = null;
|
|
448
|
+
closeWorkspace();
|
|
449
|
+
clearDependencyPath(false);
|
|
261
450
|
state.lastVisibleSignature = "";
|
|
262
451
|
|
|
263
452
|
populateFilters();
|
|
453
|
+
populatePathOptions();
|
|
264
454
|
els.emptyState.classList.add("hidden");
|
|
265
455
|
els.graphSummary.textContent = fileName + " loaded: " + entities.length + " nodes, " + edges.length + " relations.";
|
|
266
456
|
render();
|
|
@@ -277,6 +467,8 @@
|
|
|
277
467
|
|
|
278
468
|
function loadFromUrlParams() {
|
|
279
469
|
var params = new URLSearchParams(window.location.search);
|
|
470
|
+
var requestedSection = String(params.get("section") || "").toLowerCase();
|
|
471
|
+
if (requestedSection === "graph" || requestedSection === "overview") setViewerSection(requestedSection);
|
|
280
472
|
applyRequestedView(params.get("view") || params.get("mode"));
|
|
281
473
|
applyRequestedRenderMode(params.get("render") || params.get("graphMode"));
|
|
282
474
|
var memoryGraphPaths = splitParamValues(params.getAll("graph"));
|
|
@@ -836,6 +1028,188 @@
|
|
|
836
1028
|
});
|
|
837
1029
|
}
|
|
838
1030
|
|
|
1031
|
+
function populatePathOptions() {
|
|
1032
|
+
if (!els.pathNodeOptions) return;
|
|
1033
|
+
var seen = new Set();
|
|
1034
|
+
els.pathNodeOptions.textContent = "";
|
|
1035
|
+
pathCandidateEntities().slice(0, 500).forEach(function (entity) {
|
|
1036
|
+
[entity.path, displayName(entity), entity.id].filter(Boolean).forEach(function (value) {
|
|
1037
|
+
var text = String(value);
|
|
1038
|
+
if (seen.has(text)) return;
|
|
1039
|
+
seen.add(text);
|
|
1040
|
+
var option = document.createElement("option");
|
|
1041
|
+
option.value = text;
|
|
1042
|
+
els.pathNodeOptions.appendChild(option);
|
|
1043
|
+
});
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
function pathCandidateEntities() {
|
|
1048
|
+
return state.entities
|
|
1049
|
+
.filter(function (entity) {
|
|
1050
|
+
return entity.graph_kind === "code" && ["file", "symbol", "route", "test", "script"].indexOf(entity.type) !== -1;
|
|
1051
|
+
})
|
|
1052
|
+
.sort(function (a, b) {
|
|
1053
|
+
return entityImportance(b) - entityImportance(a) || displayName(a).localeCompare(displayName(b));
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function resolvePathEntity(value) {
|
|
1058
|
+
var query = String(value || "").trim();
|
|
1059
|
+
if (!query) return null;
|
|
1060
|
+
var lower = query.toLowerCase();
|
|
1061
|
+
var candidates = pathCandidateEntities();
|
|
1062
|
+
var exact = candidates.filter(function (entity) {
|
|
1063
|
+
return String(entity.id || "").toLowerCase() === lower ||
|
|
1064
|
+
String(entity.path || "").toLowerCase() === lower ||
|
|
1065
|
+
displayName(entity).toLowerCase() === lower;
|
|
1066
|
+
});
|
|
1067
|
+
if (exact.length) return exact[0];
|
|
1068
|
+
var partial = candidates.filter(function (entity) {
|
|
1069
|
+
return String(entity.path || "").toLowerCase().indexOf(lower) !== -1 ||
|
|
1070
|
+
displayName(entity).toLowerCase().indexOf(lower) !== -1 ||
|
|
1071
|
+
String(entity.id || "").toLowerCase().indexOf(lower) !== -1;
|
|
1072
|
+
});
|
|
1073
|
+
return partial[0] || null;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
function codePathEdges() {
|
|
1077
|
+
var relations = new Set(["imports", "imports_external", "defines_symbol", "calls", "covers", "defines_route", "handled_by"]);
|
|
1078
|
+
return state.edges.filter(function (edge) {
|
|
1079
|
+
if (!relations.has(edge.relation)) return false;
|
|
1080
|
+
var from = state.entityById.get(edge.from);
|
|
1081
|
+
var to = state.entityById.get(edge.to);
|
|
1082
|
+
return from && to && from.graph_kind === "code" && to.graph_kind === "code";
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function shortestCodePath(fromId, toId, undirected) {
|
|
1087
|
+
var adjacency = new Map();
|
|
1088
|
+
codePathEdges().forEach(function (edge) {
|
|
1089
|
+
if (!adjacency.has(edge.from)) adjacency.set(edge.from, []);
|
|
1090
|
+
adjacency.get(edge.from).push({ to: edge.to, edge: edge });
|
|
1091
|
+
if (undirected) {
|
|
1092
|
+
if (!adjacency.has(edge.to)) adjacency.set(edge.to, []);
|
|
1093
|
+
adjacency.get(edge.to).push({ to: edge.from, edge: edge });
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
var queue = [fromId];
|
|
1097
|
+
var seen = new Set([fromId]);
|
|
1098
|
+
var previous = new Map();
|
|
1099
|
+
while (queue.length) {
|
|
1100
|
+
var current = queue.shift();
|
|
1101
|
+
if (current === toId) break;
|
|
1102
|
+
(adjacency.get(current) || []).forEach(function (step) {
|
|
1103
|
+
if (seen.has(step.to)) return;
|
|
1104
|
+
seen.add(step.to);
|
|
1105
|
+
previous.set(step.to, { from: current, edge: step.edge });
|
|
1106
|
+
queue.push(step.to);
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
if (!seen.has(toId)) return null;
|
|
1110
|
+
var nodes = [toId];
|
|
1111
|
+
var edges = [];
|
|
1112
|
+
var cursor = toId;
|
|
1113
|
+
while (cursor !== fromId) {
|
|
1114
|
+
var prev = previous.get(cursor);
|
|
1115
|
+
if (!prev) return null;
|
|
1116
|
+
edges.unshift(prev.edge.id);
|
|
1117
|
+
cursor = prev.from;
|
|
1118
|
+
nodes.unshift(cursor);
|
|
1119
|
+
}
|
|
1120
|
+
return { nodes: nodes, edges: edges };
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
function findDependencyPath() {
|
|
1124
|
+
var from = resolvePathEntity(els.pathFromInput.value);
|
|
1125
|
+
var to = resolvePathEntity(els.pathToInput.value);
|
|
1126
|
+
if (!from || !to) {
|
|
1127
|
+
setPathStatus("Could not resolve both endpoints. Try a file path or exact symbol name.", "warn");
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
if (from.id === to.id) {
|
|
1131
|
+
setPathStatus("Pick two different code nodes.", "warn");
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
var direction = "forward";
|
|
1135
|
+
var path = shortestCodePath(from.id, to.id, false);
|
|
1136
|
+
if (!path) {
|
|
1137
|
+
var reverse = shortestCodePath(to.id, from.id, false);
|
|
1138
|
+
if (reverse) {
|
|
1139
|
+
direction = "reverse";
|
|
1140
|
+
path = {
|
|
1141
|
+
nodes: reverse.nodes.slice().reverse(),
|
|
1142
|
+
edges: reverse.edges.slice().reverse()
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
if (!path) {
|
|
1147
|
+
direction = "undirected";
|
|
1148
|
+
path = shortestCodePath(from.id, to.id, true);
|
|
1149
|
+
}
|
|
1150
|
+
if (!path) {
|
|
1151
|
+
state.pathHighlight = { nodes: new Set(), edges: new Set(), direction: "", steps: [] };
|
|
1152
|
+
els.pathResult.textContent = "";
|
|
1153
|
+
els.pathResult.className = "path-result";
|
|
1154
|
+
setPathStatus("No code dependency path found between those nodes.", "warn");
|
|
1155
|
+
render();
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
state.pathHighlight = {
|
|
1159
|
+
nodes: new Set(path.nodes),
|
|
1160
|
+
edges: new Set(path.edges),
|
|
1161
|
+
direction: direction,
|
|
1162
|
+
steps: path.nodes
|
|
1163
|
+
};
|
|
1164
|
+
setPathStatus(path.nodes.length + " nodes, " + path.edges.length + " edge(s), " + direction + " path.", "ok");
|
|
1165
|
+
renderPathResult(path.nodes, path.edges);
|
|
1166
|
+
state.lastVisibleSignature = "";
|
|
1167
|
+
render();
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
function setPathStatus(text, status) {
|
|
1171
|
+
els.pathStatus.textContent = text;
|
|
1172
|
+
els.pathStatus.className = "path-status" + (status ? " " + status : "");
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
function clearDependencyPath(renderNow) {
|
|
1176
|
+
state.pathHighlight = { nodes: new Set(), edges: new Set(), direction: "", steps: [] };
|
|
1177
|
+
if (els.pathFromInput) els.pathFromInput.value = "";
|
|
1178
|
+
if (els.pathToInput) els.pathToInput.value = "";
|
|
1179
|
+
if (els.pathResult) {
|
|
1180
|
+
els.pathResult.textContent = "";
|
|
1181
|
+
els.pathResult.className = "path-result";
|
|
1182
|
+
}
|
|
1183
|
+
if (els.pathStatus) setPathStatus("Pick two code nodes to trace a dependency path.", "");
|
|
1184
|
+
if (renderNow !== false) {
|
|
1185
|
+
state.lastVisibleSignature = "";
|
|
1186
|
+
render();
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
function renderPathResult(nodes, edges) {
|
|
1191
|
+
els.pathResult.textContent = "";
|
|
1192
|
+
nodes.forEach(function (id, index) {
|
|
1193
|
+
var entity = state.entityById.get(id);
|
|
1194
|
+
var edge = index > 0 ? state.edgeById.get(edges[index - 1]) : null;
|
|
1195
|
+
var button = document.createElement("button");
|
|
1196
|
+
button.type = "button";
|
|
1197
|
+
button.className = "path-step";
|
|
1198
|
+
button.innerHTML = "<strong></strong><span></span>";
|
|
1199
|
+
button.querySelector("strong").textContent = entity ? displayName(entity) : id;
|
|
1200
|
+
button.querySelector("span").textContent = [
|
|
1201
|
+
index === 0 ? "start" : edge ? edge.relation : "path",
|
|
1202
|
+
entity && entity.path ? entity.path : id
|
|
1203
|
+
].filter(Boolean).join(" | ");
|
|
1204
|
+
button.addEventListener("click", function () {
|
|
1205
|
+
selectEntity(id, true);
|
|
1206
|
+
render();
|
|
1207
|
+
});
|
|
1208
|
+
els.pathResult.appendChild(button);
|
|
1209
|
+
});
|
|
1210
|
+
els.pathResult.className = "path-result visible";
|
|
1211
|
+
}
|
|
1212
|
+
|
|
839
1213
|
function layoutGraph(visibleIds) {
|
|
840
1214
|
state.positions = new Map();
|
|
841
1215
|
var candidates = state.entities.filter(function (entity) {
|
|
@@ -872,6 +1246,7 @@
|
|
|
872
1246
|
|
|
873
1247
|
function render() {
|
|
874
1248
|
if (!state.graph) return;
|
|
1249
|
+
syncQuickControls();
|
|
875
1250
|
|
|
876
1251
|
var query = parseSearchQuery(els.searchInput.value);
|
|
877
1252
|
state.renderQuery = query;
|
|
@@ -981,7 +1356,16 @@
|
|
|
981
1356
|
}
|
|
982
1357
|
}
|
|
983
1358
|
|
|
984
|
-
|
|
1359
|
+
if (state.pathHighlight && state.pathHighlight.nodes.size) {
|
|
1360
|
+
state.pathHighlight.nodes.forEach(function (id) { entities.add(id); });
|
|
1361
|
+
state.pathHighlight.edges.forEach(function (id) { edges.add(id); });
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
var cappedEdges = capVisibleEdges(edgesWithVisibleEndpoints(edges, entities), entities, options);
|
|
1365
|
+
if (state.pathHighlight && state.pathHighlight.edges.size) {
|
|
1366
|
+
state.pathHighlight.edges.forEach(function (id) { if (edges.has(id)) cappedEdges.add(id); });
|
|
1367
|
+
}
|
|
1368
|
+
return { entities: entities, edges: cappedEdges };
|
|
985
1369
|
}
|
|
986
1370
|
|
|
987
1371
|
function capVisibleEdges(edgeIds, entityIds, options) {
|
|
@@ -1484,9 +1868,10 @@
|
|
|
1484
1868
|
var to = nodeMap.get(edge.to);
|
|
1485
1869
|
if (!from || !to) return;
|
|
1486
1870
|
var connected = focusId && (edge.from === focusId || edge.to === focusId);
|
|
1871
|
+
var pathEdge = state.pathHighlight && state.pathHighlight.edges.has(edge.id);
|
|
1487
1872
|
var matches = matchesSearchQuery(edge, query) || matchesSearchQuery(from.entity, query) || matchesSearchQuery(to.entity, query);
|
|
1488
|
-
var alpha = !matches ? 0.035 : focusId ? (connected ? 0.62 : 0.055) : (dense ? 0.13 : 0.22);
|
|
1489
|
-
var color = hexToRgb(edgeThemeColor(edge, from.entity, to.entity));
|
|
1873
|
+
var alpha = pathEdge ? 0.92 : !matches ? 0.035 : focusId ? (connected ? 0.62 : 0.055) : (dense ? 0.13 : 0.22);
|
|
1874
|
+
var color = hexToRgb(pathEdge ? graphPalette.bridge : edgeThemeColor(edge, from.entity, to.entity));
|
|
1490
1875
|
var dx = to.x - from.x;
|
|
1491
1876
|
var dy = to.y - from.y;
|
|
1492
1877
|
var dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
|
|
@@ -1497,10 +1882,10 @@
|
|
|
1497
1882
|
ctx.moveTo(from.x, from.y);
|
|
1498
1883
|
ctx.quadraticCurveTo(cx, cy, to.x, to.y);
|
|
1499
1884
|
ctx.strokeStyle = "rgba(" + color.r + "," + color.g + "," + color.b + "," + alpha + ")";
|
|
1500
|
-
ctx.lineWidth = connected ? 2.2 : 1;
|
|
1885
|
+
ctx.lineWidth = pathEdge ? 3 : connected ? 2.2 : 1;
|
|
1501
1886
|
ctx.stroke();
|
|
1502
|
-
if (connected || (!dense && state.sim.zoom > 1.25)) drawArrow(ctx, from, to, cx, cy, color, alpha);
|
|
1503
|
-
if (connected && state.sim.zoom > 0.62) drawEdgeLabel(ctx, edge, cx, cy);
|
|
1887
|
+
if (pathEdge || connected || (!dense && state.sim.zoom > 1.25)) drawArrow(ctx, from, to, cx, cy, color, alpha);
|
|
1888
|
+
if ((pathEdge || connected) && state.sim.zoom > 0.62) drawEdgeLabel(ctx, edge, cx, cy);
|
|
1504
1889
|
});
|
|
1505
1890
|
}
|
|
1506
1891
|
|
|
@@ -1514,14 +1899,15 @@
|
|
|
1514
1899
|
var selected = state.selected && state.selected.kind === "entity" && state.selected.id === node.id;
|
|
1515
1900
|
var hovered = state.sim.hoverNode && state.sim.hoverNode.id === node.id;
|
|
1516
1901
|
var connected = focusId && (node.id === focusId || (focusNeighbors && focusNeighbors.has(node.id)));
|
|
1902
|
+
var pathNode = state.pathHighlight && state.pathHighlight.nodes.has(node.id);
|
|
1517
1903
|
var matches = matchesSearchQuery(entity, query);
|
|
1518
|
-
var alpha = !matches ? 0.12 : focusId && !connected ? 0.20 : 1;
|
|
1904
|
+
var alpha = pathNode ? 1 : !matches ? 0.12 : focusId && !connected ? 0.20 : 1;
|
|
1519
1905
|
var color = nodeThemeColor(entity);
|
|
1520
1906
|
ctx.save();
|
|
1521
1907
|
ctx.globalAlpha = alpha;
|
|
1522
|
-
if (selected || hovered) {
|
|
1523
|
-
ctx.shadowColor = color;
|
|
1524
|
-
ctx.shadowBlur = selected ? 14 : 10;
|
|
1908
|
+
if (selected || hovered || pathNode) {
|
|
1909
|
+
ctx.shadowColor = pathNode ? graphPalette.bridge : color;
|
|
1910
|
+
ctx.shadowBlur = selected ? 14 : pathNode ? 12 : 10;
|
|
1525
1911
|
}
|
|
1526
1912
|
drawNodeShape(ctx, node.x, node.y, node.r, entity);
|
|
1527
1913
|
ctx.fillStyle = nodeFillColor(entity);
|
|
@@ -1538,18 +1924,18 @@
|
|
|
1538
1924
|
}
|
|
1539
1925
|
ctx.restore();
|
|
1540
1926
|
|
|
1541
|
-
if (selected || hovered) {
|
|
1927
|
+
if (selected || hovered || pathNode) {
|
|
1542
1928
|
ctx.save();
|
|
1543
1929
|
drawNodeShape(ctx, node.x, node.y, node.r + 4, entity);
|
|
1544
|
-
ctx.strokeStyle = color;
|
|
1545
|
-
ctx.lineWidth = selected ? 2.6 : 1.8;
|
|
1546
|
-
ctx.shadowColor = color;
|
|
1930
|
+
ctx.strokeStyle = pathNode ? graphPalette.bridge : color;
|
|
1931
|
+
ctx.lineWidth = selected ? 2.6 : pathNode ? 2.2 : 1.8;
|
|
1932
|
+
ctx.shadowColor = pathNode ? graphPalette.bridge : color;
|
|
1547
1933
|
ctx.shadowBlur = 8;
|
|
1548
1934
|
ctx.stroke();
|
|
1549
1935
|
ctx.restore();
|
|
1550
1936
|
}
|
|
1551
1937
|
|
|
1552
|
-
var shouldLabel = matches && (selected || hovered || (query.active && matches) || (!dense && state.sim.zoom > 0.75) || (dense && state.sim.zoom > 1.55 && node.r > 13));
|
|
1938
|
+
var shouldLabel = pathNode || (matches && (selected || hovered || (query.active && matches) || (!dense && state.sim.zoom > 0.75) || (dense && state.sim.zoom > 1.55 && node.r > 13)));
|
|
1553
1939
|
if (shouldLabel) drawNodeLabel(ctx, node, selected || hovered);
|
|
1554
1940
|
});
|
|
1555
1941
|
}
|
|
@@ -1681,7 +2067,7 @@
|
|
|
1681
2067
|
class: "edge-hit"
|
|
1682
2068
|
});
|
|
1683
2069
|
hit.addEventListener("click", function () {
|
|
1684
|
-
|
|
2070
|
+
selectEdge(edge.id, true);
|
|
1685
2071
|
render();
|
|
1686
2072
|
});
|
|
1687
2073
|
hit.addEventListener("mousedown", function (event) {
|
|
@@ -1733,7 +2119,7 @@
|
|
|
1733
2119
|
var title = svgEl("title");
|
|
1734
2120
|
title.textContent = displayName(entity) + "\n" + (entity.summary || "");
|
|
1735
2121
|
group.addEventListener("click", function () {
|
|
1736
|
-
|
|
2122
|
+
selectEntity(entity.id, true);
|
|
1737
2123
|
render();
|
|
1738
2124
|
});
|
|
1739
2125
|
group.addEventListener("mousedown", function (event) {
|
|
@@ -1786,7 +2172,7 @@
|
|
|
1786
2172
|
button.querySelector(".item-title").textContent = displayName(entity);
|
|
1787
2173
|
button.querySelector(".item-meta").textContent = (entity.type || "unknown") + " | " + entity.id;
|
|
1788
2174
|
button.addEventListener("click", function () {
|
|
1789
|
-
|
|
2175
|
+
selectEntity(entity.id, true);
|
|
1790
2176
|
render();
|
|
1791
2177
|
});
|
|
1792
2178
|
els.entityList.appendChild(button);
|
|
@@ -1800,7 +2186,7 @@
|
|
|
1800
2186
|
button.querySelector(".item-title").textContent = edge.relation || "related";
|
|
1801
2187
|
button.querySelector(".item-meta").textContent = displayName(state.entityById.get(edge.from)) + " -> " + displayName(state.entityById.get(edge.to)) + " | " + reviewStatus(edge);
|
|
1802
2188
|
button.addEventListener("click", function () {
|
|
1803
|
-
|
|
2189
|
+
selectEdge(edge.id, true);
|
|
1804
2190
|
render();
|
|
1805
2191
|
});
|
|
1806
2192
|
els.edgeList.appendChild(button);
|
|
@@ -2029,7 +2415,8 @@
|
|
|
2029
2415
|
button.querySelector(".detail-link-meta").textContent = item.meta;
|
|
2030
2416
|
button.querySelector(".detail-link-body").textContent = item.body;
|
|
2031
2417
|
button.addEventListener("click", function () {
|
|
2032
|
-
|
|
2418
|
+
if (item.entity) selectEntity(item.entity.id, true);
|
|
2419
|
+
else selectEdge(item.edge.id, true);
|
|
2033
2420
|
render();
|
|
2034
2421
|
});
|
|
2035
2422
|
list.appendChild(button);
|
|
@@ -2116,6 +2503,112 @@
|
|
|
2116
2503
|
els.workspaceMode.textContent = (els.viewMode.value || "combined").replace(/^./, function (letter) { return letter.toUpperCase(); });
|
|
2117
2504
|
els.graphSubhead.textContent = visibleEntities.length + " visible nodes and " + visibleEdges.length + " visible relations" +
|
|
2118
2505
|
(hiddenDependencies && !els.showDependencies.checked ? " (" + hiddenDependencies + " dependency/noise nodes hidden)." : ".");
|
|
2506
|
+
renderDashboard();
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
function renderDashboard() {
|
|
2510
|
+
if (!els.dashboardStats) return;
|
|
2511
|
+
var metrics = state.metrics || {};
|
|
2512
|
+
var memoryGraph = metrics.memory_graph || {};
|
|
2513
|
+
var codeGraph = metrics.code_graph || {};
|
|
2514
|
+
var structural = metrics.structural_index || {};
|
|
2515
|
+
var savings = metrics.savings || {};
|
|
2516
|
+
var pain = metrics.pain || {};
|
|
2517
|
+
var memoryNodes = state.entities.filter(function (entity) { return entity.graph_kind === "memory"; }).length;
|
|
2518
|
+
var codeNodes = state.entities.filter(function (entity) { return entity.graph_kind === "code"; }).length;
|
|
2519
|
+
var memoryCodeEdges = state.edges.filter(isMemoryCodeEdge);
|
|
2520
|
+
var reports = state.reports || {};
|
|
2521
|
+
var reportCount = Object.keys(reports).filter(function (key) { return reports[key]; }).length;
|
|
2522
|
+
var statRows = [
|
|
2523
|
+
["Memory packets", firstNumber(memoryGraph.approved_packets, memoryNodes)],
|
|
2524
|
+
["Code nodes", firstNumber(codeGraph.symbols, structural.symbols, codeNodes)],
|
|
2525
|
+
["Files", firstNumber(codeGraph.files, structural.files, countEntitiesByType("file"))],
|
|
2526
|
+
["Memory-code links", memoryCodeEdges.length],
|
|
2527
|
+
["Parser coverage", codeGraph.indexer_coverage_percent != null ? codeGraph.indexer_coverage_percent + "%" : "n/a"],
|
|
2528
|
+
["Tokens saved", firstNumber(savings.estimated_tokens_saved_per_recall, pain.estimated_tokens_saved, "n/a")]
|
|
2529
|
+
];
|
|
2530
|
+
els.dashboardStats.textContent = "";
|
|
2531
|
+
statRows.forEach(function (row) {
|
|
2532
|
+
var item = document.createElement("div");
|
|
2533
|
+
item.className = "dashboard-stat";
|
|
2534
|
+
item.innerHTML = "<strong></strong><span></span>";
|
|
2535
|
+
item.querySelector("strong").textContent = formatDashboardValue(row[1]);
|
|
2536
|
+
item.querySelector("span").textContent = row[0];
|
|
2537
|
+
els.dashboardStats.appendChild(item);
|
|
2538
|
+
});
|
|
2539
|
+
|
|
2540
|
+
setDashboardRows("dashboardMemory", [
|
|
2541
|
+
["Approved packets", firstNumber(memoryGraph.approved_packets, memoryNodes)],
|
|
2542
|
+
["Pending review", firstNumber(memoryGraph.pending_packets, (state.pendingPackets || []).length)],
|
|
2543
|
+
["Evidence coverage", memoryGraph.evidence_coverage_percent != null ? memoryGraph.evidence_coverage_percent + "%" : "n/a"],
|
|
2544
|
+
["Code-linked memory", memoryCodeEdges.length]
|
|
2545
|
+
]);
|
|
2546
|
+
setDashboardRows("dashboardGraph", [
|
|
2547
|
+
["Files", firstNumber(codeGraph.files, structural.files, countEntitiesByType("file"))],
|
|
2548
|
+
["Symbols", firstNumber(codeGraph.symbols, structural.symbols, countEntitiesByType("symbol"))],
|
|
2549
|
+
["Relations", state.edges.length],
|
|
2550
|
+
["Dependency/noise hidden", state.entities.filter(function (entity) { return isDependencyEntity(entity); }).length]
|
|
2551
|
+
]);
|
|
2552
|
+
setDashboardRows("dashboardIntel", [
|
|
2553
|
+
["Reports loaded", reportCount],
|
|
2554
|
+
["Decision coverage", reports.decisions && reports.decisions.coverage_percent != null ? reports.decisions.coverage_percent + "%" : "n/a"],
|
|
2555
|
+
["Modules scored", reports.moduleHealth && Array.isArray(reports.moduleHealth.modules) ? reports.moduleHealth.modules.length : "n/a"],
|
|
2556
|
+
["Communities", reports.graphInsights && Array.isArray(reports.graphInsights.communities) ? reports.graphInsights.communities.length : "n/a"]
|
|
2557
|
+
]);
|
|
2558
|
+
var risk = reports.risk || {};
|
|
2559
|
+
var riskTargets = Array.isArray(risk.targets) ? risk.targets : Object.keys(risk.targets || {});
|
|
2560
|
+
setDashboardRows("dashboardRisk", [
|
|
2561
|
+
["Risk targets", riskTargets.length || "n/a"],
|
|
2562
|
+
["Hotspots", Array.isArray(risk.global_hotspots) ? risk.global_hotspots.length : "n/a"],
|
|
2563
|
+
["Ownership silos", Array.isArray(risk.ownership_silos) ? risk.ownership_silos.length : "n/a"],
|
|
2564
|
+
["Cycles", reports.graphInsights && Array.isArray(reports.graphInsights.dependency_cycles) ? reports.graphInsights.dependency_cycles.length : "n/a"]
|
|
2565
|
+
]);
|
|
2566
|
+
var inboxCounts = state.inbox && state.inbox.counts ? state.inbox.counts : {};
|
|
2567
|
+
setDashboardRows("dashboardReview", [
|
|
2568
|
+
["Readiness", metrics.harness && metrics.harness.readiness_score != null ? metrics.harness.readiness_score + "/100" : "n/a"],
|
|
2569
|
+
["Inbox pending", firstNumber(inboxCounts.pending, (state.pendingPackets || []).length)],
|
|
2570
|
+
["Stale flags", firstNumber(inboxCounts.stale, 0)],
|
|
2571
|
+
["Duplicate flags", firstNumber(inboxCounts.duplicates, 0)]
|
|
2572
|
+
]);
|
|
2573
|
+
var workspace = reports.workspace || {};
|
|
2574
|
+
setDashboardRows("dashboardWorkspace", [
|
|
2575
|
+
["Repos", Array.isArray(workspace.repos) ? workspace.repos.length : "n/a"],
|
|
2576
|
+
["Package deps", Array.isArray(workspace.package_dependencies) ? workspace.package_dependencies.length : "n/a"],
|
|
2577
|
+
["Route contracts", Array.isArray(workspace.route_contracts) ? workspace.route_contracts.length : "n/a"],
|
|
2578
|
+
["Co-changes", Array.isArray(workspace.co_changes) ? workspace.co_changes.length : "n/a"]
|
|
2579
|
+
]);
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
function setDashboardRows(cardId, rows) {
|
|
2583
|
+
var card = document.getElementById(cardId);
|
|
2584
|
+
if (!card) return;
|
|
2585
|
+
var list = card.querySelector("ul");
|
|
2586
|
+
if (!list) return;
|
|
2587
|
+
list.textContent = "";
|
|
2588
|
+
rows.forEach(function (row) {
|
|
2589
|
+
var item = document.createElement("li");
|
|
2590
|
+
item.innerHTML = "<strong></strong><span></span>";
|
|
2591
|
+
item.querySelector("strong").textContent = row[0];
|
|
2592
|
+
item.querySelector("span").textContent = formatDashboardValue(row[1]);
|
|
2593
|
+
list.appendChild(item);
|
|
2594
|
+
});
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
function firstNumber() {
|
|
2598
|
+
for (var index = 0; index < arguments.length; index += 1) {
|
|
2599
|
+
var value = arguments[index];
|
|
2600
|
+
if (value !== null && value !== undefined && value !== "") return value;
|
|
2601
|
+
}
|
|
2602
|
+
return "n/a";
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
function countEntitiesByType(type) {
|
|
2606
|
+
return state.entities.filter(function (entity) { return entity.type === type; }).length;
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
function formatDashboardValue(value) {
|
|
2610
|
+
if (typeof value === "number" && Number.isFinite(value)) return value.toLocaleString();
|
|
2611
|
+
return String(value == null ? "n/a" : value);
|
|
2119
2612
|
}
|
|
2120
2613
|
|
|
2121
2614
|
function renderReviewQueue() {
|
|
@@ -2321,6 +2814,7 @@
|
|
|
2321
2814
|
var decisions = reports.decisions;
|
|
2322
2815
|
var health = reports.moduleHealth;
|
|
2323
2816
|
var insights = reports.graphInsights;
|
|
2817
|
+
var workspace = reports.workspace;
|
|
2324
2818
|
|
|
2325
2819
|
if (contributors || risk) {
|
|
2326
2820
|
var profiles = contributors && Array.isArray(contributors.contributors) ? contributors.contributors : [];
|
|
@@ -2449,6 +2943,57 @@
|
|
|
2449
2943
|
});
|
|
2450
2944
|
}
|
|
2451
2945
|
|
|
2946
|
+
if (workspace) {
|
|
2947
|
+
var deps = Array.isArray(workspace.package_dependencies) ? workspace.package_dependencies : [];
|
|
2948
|
+
var routeContracts = Array.isArray(workspace.route_contracts) ? workspace.route_contracts : [];
|
|
2949
|
+
var topicContracts = Array.isArray(workspace.topic_contracts) ? workspace.topic_contracts : [];
|
|
2950
|
+
var coChanges = Array.isArray(workspace.co_changes) ? workspace.co_changes : [];
|
|
2951
|
+
var workspaceRows = deps.slice(0, 6).map(function (dep) {
|
|
2952
|
+
return {
|
|
2953
|
+
label: dep.from + " -> " + dep.to,
|
|
2954
|
+
value: dep.package_name || "package",
|
|
2955
|
+
meta: "workspace package dependency",
|
|
2956
|
+
score: 72,
|
|
2957
|
+
status: "ok",
|
|
2958
|
+
};
|
|
2959
|
+
}).concat(routeContracts.slice(0, 6).map(function (contract) {
|
|
2960
|
+
return {
|
|
2961
|
+
label: contract.provider_repo + " -> " + contract.consumer_repo,
|
|
2962
|
+
value: [contract.method, contract.path].filter(Boolean).join(" "),
|
|
2963
|
+
meta: [contract.provider_file, contract.consumer_file].filter(Boolean).join(" -> "),
|
|
2964
|
+
score: contract.confidence === "high" ? 92 : 76,
|
|
2965
|
+
status: "ok",
|
|
2966
|
+
};
|
|
2967
|
+
})).concat(topicContracts.slice(0, 6).map(function (contract) {
|
|
2968
|
+
return {
|
|
2969
|
+
label: contract.producer_repo + " -> " + contract.consumer_repo,
|
|
2970
|
+
value: contract.topic,
|
|
2971
|
+
meta: [contract.producer_file, contract.consumer_file].filter(Boolean).join(" -> "),
|
|
2972
|
+
score: contract.confidence === "high" ? 88 : 72,
|
|
2973
|
+
status: "ok",
|
|
2974
|
+
};
|
|
2975
|
+
})).concat(coChanges.slice(0, 8).map(function (link) {
|
|
2976
|
+
var score = Math.min(100, Number(link.strength || 0) * 22 + Number(link.frequency || 0) * 12);
|
|
2977
|
+
return {
|
|
2978
|
+
label: link.source_repo + " <-> " + link.target_repo,
|
|
2979
|
+
value: (link.frequency || 0) + "x co-change",
|
|
2980
|
+
meta: [link.source_file, link.target_file].filter(Boolean).join(" <-> "),
|
|
2981
|
+
score: Math.max(20, score),
|
|
2982
|
+
status: "warn",
|
|
2983
|
+
};
|
|
2984
|
+
}));
|
|
2985
|
+
if (workspaceRows.length) {
|
|
2986
|
+
sections.push({
|
|
2987
|
+
title: "Workspace Map",
|
|
2988
|
+
kicker: "deps / contracts / co-changes",
|
|
2989
|
+
stat: workspaceRows.length + " links",
|
|
2990
|
+
summary: "Workspace links show how sibling repos relate through package dependencies, source-evidence contracts, topic/event links, and local git co-change history.",
|
|
2991
|
+
rows: workspaceRows,
|
|
2992
|
+
limit: 14,
|
|
2993
|
+
});
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2452
2997
|
if (risk) {
|
|
2453
2998
|
var targets = Array.isArray(risk.targets) ? risk.targets : Object.keys(risk.targets || {}).map(function (key) { return risk.targets[key]; });
|
|
2454
2999
|
var hotspots = Array.isArray(risk.global_hotspots) ? risk.global_hotspots : [];
|
|
@@ -2501,7 +3046,7 @@
|
|
|
2501
3046
|
scheduleRender();
|
|
2502
3047
|
return;
|
|
2503
3048
|
}
|
|
2504
|
-
|
|
3049
|
+
selectEntity(found.id, true);
|
|
2505
3050
|
els.searchInput.value = normalized;
|
|
2506
3051
|
scheduleRender();
|
|
2507
3052
|
}
|
|
@@ -2795,11 +3340,12 @@
|
|
|
2795
3340
|
|
|
2796
3341
|
function endCanvasPointer() {
|
|
2797
3342
|
if (state.sim.dragNode) {
|
|
2798
|
-
state.
|
|
3343
|
+
selectEntity(state.sim.dragNode.id, true);
|
|
2799
3344
|
state.sim.dragNode = null;
|
|
2800
3345
|
render();
|
|
2801
3346
|
} else if (state.sim.panning && !state.sim.panning.moved) {
|
|
2802
3347
|
state.selected = null;
|
|
3348
|
+
closeWorkspace();
|
|
2803
3349
|
render();
|
|
2804
3350
|
}
|
|
2805
3351
|
state.sim.panning = null;
|
|
@@ -2836,7 +3382,7 @@
|
|
|
2836
3382
|
var world = canvasToWorld(event);
|
|
2837
3383
|
var node = findCanvasNode(world.x, world.y);
|
|
2838
3384
|
if (!node) return;
|
|
2839
|
-
|
|
3385
|
+
selectEntity(node.id, true);
|
|
2840
3386
|
render();
|
|
2841
3387
|
}
|
|
2842
3388
|
|
|
@@ -3010,8 +3556,9 @@
|
|
|
3010
3556
|
state.sim.nodes.forEach(function (node, index) {
|
|
3011
3557
|
var entity = node.entity;
|
|
3012
3558
|
var selected = state.selected && state.selected.kind === "entity" && state.selected.id === node.id;
|
|
3559
|
+
var pathNode = state.pathHighlight && state.pathHighlight.nodes.has(node.id);
|
|
3013
3560
|
var material = new THREE.SpriteMaterial({
|
|
3014
|
-
map: threeNodeTexture(entity, selected),
|
|
3561
|
+
map: threeNodeTexture(entity, selected || pathNode),
|
|
3015
3562
|
transparent: true,
|
|
3016
3563
|
opacity: isDependencyEntity(entity) ? 0.70 : 1,
|
|
3017
3564
|
depthWrite: false
|
|
@@ -3019,7 +3566,7 @@
|
|
|
3019
3566
|
var mesh = new THREE.Sprite(material);
|
|
3020
3567
|
var position = threePosition(node, index);
|
|
3021
3568
|
mesh.position.set(position.x, position.y, position.z);
|
|
3022
|
-
var size = threeNodeSize(node, selected);
|
|
3569
|
+
var size = threeNodeSize(node, selected || pathNode);
|
|
3023
3570
|
mesh.scale.set(size, size, 1);
|
|
3024
3571
|
mesh.userData.node = node;
|
|
3025
3572
|
state.three.nodeGroup.add(mesh);
|
|
@@ -3033,11 +3580,12 @@
|
|
|
3033
3580
|
var fromEntity = from.node.entity;
|
|
3034
3581
|
var toEntity = to.node.entity;
|
|
3035
3582
|
var connected = state.selected && state.selected.kind === "entity" && (state.selected.id === edge.from || state.selected.id === edge.to);
|
|
3583
|
+
var pathEdge = state.pathHighlight && state.pathHighlight.edges.has(edge.id);
|
|
3036
3584
|
var geometry = new THREE.BufferGeometry().setFromPoints([from.mesh.position, to.mesh.position]);
|
|
3037
3585
|
var material = new THREE.LineBasicMaterial({
|
|
3038
|
-
color: new THREE.Color(edgeThemeColor(edge, fromEntity, toEntity)),
|
|
3586
|
+
color: new THREE.Color(pathEdge ? graphPalette.bridge : edgeThemeColor(edge, fromEntity, toEntity)),
|
|
3039
3587
|
transparent: true,
|
|
3040
|
-
opacity: threeEdgeOpacity(edge, fromEntity, toEntity, connected),
|
|
3588
|
+
opacity: pathEdge ? 0.82 : threeEdgeOpacity(edge, fromEntity, toEntity, connected),
|
|
3041
3589
|
depthWrite: false,
|
|
3042
3590
|
depthTest: false
|
|
3043
3591
|
});
|
|
@@ -3374,7 +3922,7 @@
|
|
|
3374
3922
|
: null;
|
|
3375
3923
|
state.three.drag = null;
|
|
3376
3924
|
if (picked) {
|
|
3377
|
-
|
|
3925
|
+
selectEntity(picked.id, true);
|
|
3378
3926
|
render();
|
|
3379
3927
|
}
|
|
3380
3928
|
}
|
|
@@ -3395,7 +3943,7 @@
|
|
|
3395
3943
|
function handleThreeDoubleClick(event) {
|
|
3396
3944
|
var picked = pickThreeNode(event);
|
|
3397
3945
|
if (!picked) return;
|
|
3398
|
-
|
|
3946
|
+
selectEntity(picked.id, true);
|
|
3399
3947
|
render();
|
|
3400
3948
|
}
|
|
3401
3949
|
|
|
@@ -3580,6 +4128,7 @@
|
|
|
3580
4128
|
if (state.pan && state.pan.moved) return;
|
|
3581
4129
|
if (event.target !== els.svg) return;
|
|
3582
4130
|
state.selected = null;
|
|
4131
|
+
closeWorkspace();
|
|
3583
4132
|
render();
|
|
3584
4133
|
}
|
|
3585
4134
|
|