@kage-core/kage-graph-mcp 1.1.25 → 1.1.27
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 +43 -2
- package/dist/cli.js +237 -0
- package/dist/daemon.js +19 -1
- package/dist/index.js +208 -0
- package/dist/kernel.js +1676 -12
- package/package.json +1 -1
- package/viewer/app.js +489 -2
- package/viewer/index.html +10 -2
- package/viewer/styles.css +174 -4
package/package.json
CHANGED
package/viewer/app.js
CHANGED
|
@@ -15,6 +15,16 @@
|
|
|
15
15
|
selected: null,
|
|
16
16
|
metrics: null,
|
|
17
17
|
inbox: null,
|
|
18
|
+
reports: {
|
|
19
|
+
quality: null,
|
|
20
|
+
benchmark: null,
|
|
21
|
+
contributors: null,
|
|
22
|
+
decisions: null,
|
|
23
|
+
risk: null,
|
|
24
|
+
moduleHealth: null,
|
|
25
|
+
graphInsights: null,
|
|
26
|
+
workspace: null
|
|
27
|
+
},
|
|
18
28
|
pendingPackets: [],
|
|
19
29
|
reviewText: "",
|
|
20
30
|
viewBox: { x: 0, y: 0, width: 1000, height: 660 },
|
|
@@ -138,7 +148,9 @@
|
|
|
138
148
|
reviewCount: document.getElementById("reviewCount"),
|
|
139
149
|
reviewList: document.getElementById("reviewList"),
|
|
140
150
|
proofStatus: document.getElementById("proofStatus"),
|
|
141
|
-
proofList: document.getElementById("proofList")
|
|
151
|
+
proofList: document.getElementById("proofList"),
|
|
152
|
+
intelligenceStatus: document.getElementById("intelligenceStatus"),
|
|
153
|
+
intelligenceList: document.getElementById("intelligenceList")
|
|
142
154
|
};
|
|
143
155
|
|
|
144
156
|
var MEMORY_CODE_RELATIONS = new Set(["explains_symbol", "informs_symbol", "fixes_symbol", "applies_to_route", "verified_by_test", "affects_code_path"]);
|
|
@@ -277,15 +289,41 @@
|
|
|
277
289
|
var inboxPath = params.get("inbox");
|
|
278
290
|
var reviewPath = params.get("review");
|
|
279
291
|
var pendingPath = params.get("pending");
|
|
292
|
+
var qualityPath = params.get("quality");
|
|
293
|
+
var benchmarkPath = params.get("benchmark");
|
|
294
|
+
var contributorsPath = params.get("contributors");
|
|
295
|
+
var decisionsPath = params.get("decisions");
|
|
296
|
+
var riskPath = params.get("risk");
|
|
297
|
+
var moduleHealthPath = params.get("moduleHealth") || params.get("module-health");
|
|
298
|
+
var graphInsightsPath = params.get("graphInsights") || params.get("graph-insights");
|
|
299
|
+
var workspacePath = params.get("workspace");
|
|
280
300
|
var inferredRoot = inferMemoryRoot(graphPaths[0] || "");
|
|
281
301
|
if (!inboxPath && inferredRoot) inboxPath = inferredRoot + "/inbox.json";
|
|
282
302
|
if (!reviewPath && inferredRoot) reviewPath = inferredRoot + "/review/memory-review.md";
|
|
283
303
|
if (!pendingPath && inferredRoot) pendingPath = inferredRoot + "/pending";
|
|
304
|
+
if (inferredRoot) {
|
|
305
|
+
if (!qualityPath) qualityPath = inferredRoot + "/reports/quality.json";
|
|
306
|
+
if (!benchmarkPath) benchmarkPath = inferredRoot + "/reports/benchmark.json";
|
|
307
|
+
if (!contributorsPath) contributorsPath = inferredRoot + "/reports/contributors.json";
|
|
308
|
+
if (!decisionsPath) decisionsPath = inferredRoot + "/reports/decisions.json";
|
|
309
|
+
if (!riskPath) riskPath = inferredRoot + "/reports/risk.json";
|
|
310
|
+
if (!moduleHealthPath) moduleHealthPath = inferredRoot + "/reports/module-health.json";
|
|
311
|
+
if (!graphInsightsPath) graphInsightsPath = inferredRoot + "/reports/graph-insights.json";
|
|
312
|
+
if (!workspacePath) workspacePath = inferredRoot + "/reports/workspace.json";
|
|
313
|
+
}
|
|
284
314
|
var jobs = [];
|
|
285
315
|
if (metricsPath) jobs.push(fetchJson(metricsPath).then(function (metrics) { state.metrics = metrics; }));
|
|
286
316
|
if (inboxPath) jobs.push(fetchJson(inboxPath).then(function (inbox) { state.inbox = inbox; }).catch(function () { state.inbox = null; }));
|
|
287
317
|
if (reviewPath) jobs.push(fetchText(reviewPath).then(function (text) { state.reviewText = text; }).catch(function () { state.reviewText = ""; }));
|
|
288
318
|
if (pendingPath) jobs.push(loadPending(pendingPath).then(function (packets) { state.pendingPackets = packets; }));
|
|
319
|
+
if (qualityPath) jobs.push(fetchJson(qualityPath).then(function (report) { state.reports.quality = report; }).catch(function () { state.reports.quality = null; }));
|
|
320
|
+
if (benchmarkPath) jobs.push(fetchJson(benchmarkPath).then(function (report) { state.reports.benchmark = report; }).catch(function () { state.reports.benchmark = null; }));
|
|
321
|
+
if (contributorsPath) jobs.push(fetchJson(contributorsPath).then(function (report) { state.reports.contributors = report; }).catch(function () { state.reports.contributors = null; }));
|
|
322
|
+
if (decisionsPath) jobs.push(fetchJson(decisionsPath).then(function (report) { state.reports.decisions = report; }).catch(function () { state.reports.decisions = null; }));
|
|
323
|
+
if (riskPath) jobs.push(fetchJson(riskPath).then(function (report) { state.reports.risk = report; }).catch(function () { state.reports.risk = null; }));
|
|
324
|
+
if (moduleHealthPath) jobs.push(fetchJson(moduleHealthPath).then(function (report) { state.reports.moduleHealth = report; }).catch(function () { state.reports.moduleHealth = null; }));
|
|
325
|
+
if (graphInsightsPath) jobs.push(fetchJson(graphInsightsPath).then(function (report) { state.reports.graphInsights = report; }).catch(function () { state.reports.graphInsights = null; }));
|
|
326
|
+
if (workspacePath) jobs.push(fetchJson(workspacePath).then(function (report) { state.reports.workspace = report; }).catch(function () { state.reports.workspace = null; }));
|
|
289
327
|
if (!graphPaths.length && !jobs.length) {
|
|
290
328
|
loadHostedDefault();
|
|
291
329
|
return;
|
|
@@ -315,11 +353,23 @@
|
|
|
315
353
|
loadGraphPath("./data/kage/graph.json"),
|
|
316
354
|
loadGraphPath("./data/kage/code_graph/graph.json"),
|
|
317
355
|
fetchJson("./data/kage/metrics.json").catch(function () { return null; }),
|
|
318
|
-
fetchJson("./data/kage/inbox.json").catch(function () { return null; })
|
|
356
|
+
fetchJson("./data/kage/inbox.json").catch(function () { return null; }),
|
|
357
|
+
fetchJson("./data/kage/reports/risk.json").catch(function () { return null; }),
|
|
358
|
+
fetchJson("./data/kage/reports/contributors.json").catch(function () { return null; }),
|
|
359
|
+
fetchJson("./data/kage/reports/decisions.json").catch(function () { return null; }),
|
|
360
|
+
fetchJson("./data/kage/reports/module-health.json").catch(function () { return null; }),
|
|
361
|
+
fetchJson("./data/kage/reports/graph-insights.json").catch(function () { return null; }),
|
|
362
|
+
fetchJson("./data/kage/reports/workspace.json").catch(function () { return null; })
|
|
319
363
|
]).then(function (items) {
|
|
320
364
|
var merged = mergeNormalizedGraphs([normalizeGraph(items[0]), normalizeGraph(items[1])]);
|
|
321
365
|
state.metrics = items[2];
|
|
322
366
|
state.inbox = items[3];
|
|
367
|
+
state.reports.risk = items[4];
|
|
368
|
+
state.reports.contributors = items[5];
|
|
369
|
+
state.reports.decisions = items[6];
|
|
370
|
+
state.reports.moduleHealth = items[7];
|
|
371
|
+
state.reports.graphInsights = items[8];
|
|
372
|
+
state.reports.workspace = items[9];
|
|
323
373
|
loadNormalizedGraph(merged, "Kage repo graph");
|
|
324
374
|
setAutoLoad("Kage repo graph loaded", true);
|
|
325
375
|
}).catch(function () {
|
|
@@ -875,6 +925,7 @@
|
|
|
875
925
|
renderMetrics();
|
|
876
926
|
renderReviewQueue();
|
|
877
927
|
renderProof();
|
|
928
|
+
renderIntelligence();
|
|
878
929
|
}
|
|
879
930
|
|
|
880
931
|
function scheduleRender() {
|
|
@@ -2167,6 +2218,442 @@
|
|
|
2167
2218
|
});
|
|
2168
2219
|
}
|
|
2169
2220
|
|
|
2221
|
+
function renderIntelligence() {
|
|
2222
|
+
if (!els.intelligenceList) return;
|
|
2223
|
+
var reports = state.reports || {};
|
|
2224
|
+
var cards = buildIntelligenceCards(reports);
|
|
2225
|
+
els.intelligenceList.textContent = "";
|
|
2226
|
+
els.intelligenceStatus.textContent = cards.length ? cards.length + " loaded" : "not loaded";
|
|
2227
|
+
if (!cards.length) {
|
|
2228
|
+
els.intelligenceList.className = "intelligence-list details-empty";
|
|
2229
|
+
els.intelligenceList.textContent = "No repo intelligence reports loaded. Launch with `kage viewer --project <repo>` to load risk, module health, graph insights, and workspace reports.";
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
els.intelligenceList.className = "intelligence-list";
|
|
2233
|
+
cards.forEach(function (card) {
|
|
2234
|
+
var item = document.createElement("article");
|
|
2235
|
+
item.className = "intel-card";
|
|
2236
|
+
item.innerHTML = [
|
|
2237
|
+
"<h3></h3>",
|
|
2238
|
+
"<div class=\"intel-kicker\"></div>",
|
|
2239
|
+
"<div class=\"intel-summary\"></div>",
|
|
2240
|
+
"<ul></ul>"
|
|
2241
|
+
].join("");
|
|
2242
|
+
item.querySelector("h3").textContent = card.title;
|
|
2243
|
+
item.querySelector(".intel-kicker").textContent = card.kicker;
|
|
2244
|
+
item.querySelector(".intel-summary").textContent = card.summary;
|
|
2245
|
+
var list = item.querySelector("ul");
|
|
2246
|
+
card.rows.slice(0, 5).forEach(function (row) {
|
|
2247
|
+
var li = document.createElement("li");
|
|
2248
|
+
li.innerHTML = "<strong></strong> <span></span>";
|
|
2249
|
+
li.querySelector("strong").textContent = row[0];
|
|
2250
|
+
li.querySelector("span").textContent = row[1];
|
|
2251
|
+
list.appendChild(li);
|
|
2252
|
+
});
|
|
2253
|
+
els.intelligenceList.appendChild(item);
|
|
2254
|
+
});
|
|
2255
|
+
var sections = buildIntelligenceSections(reports);
|
|
2256
|
+
if (sections.length) {
|
|
2257
|
+
var grid = document.createElement("div");
|
|
2258
|
+
grid.className = "intel-deep-grid";
|
|
2259
|
+
sections.forEach(function (section) {
|
|
2260
|
+
grid.appendChild(renderIntelligenceSection(section));
|
|
2261
|
+
});
|
|
2262
|
+
els.intelligenceList.appendChild(grid);
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
function renderIntelligenceSection(section) {
|
|
2267
|
+
var panel = document.createElement("article");
|
|
2268
|
+
panel.className = "intel-section";
|
|
2269
|
+
var header = document.createElement("div");
|
|
2270
|
+
header.className = "intel-section-header";
|
|
2271
|
+
header.innerHTML = "<div><h3></h3><span></span></div><strong></strong>";
|
|
2272
|
+
header.querySelector("h3").textContent = section.title;
|
|
2273
|
+
header.querySelector("span").textContent = section.kicker || "";
|
|
2274
|
+
header.querySelector("strong").textContent = section.stat || "";
|
|
2275
|
+
panel.appendChild(header);
|
|
2276
|
+
if (section.summary) {
|
|
2277
|
+
var summary = document.createElement("p");
|
|
2278
|
+
summary.className = "intel-section-summary";
|
|
2279
|
+
summary.textContent = section.summary;
|
|
2280
|
+
panel.appendChild(summary);
|
|
2281
|
+
}
|
|
2282
|
+
var list = document.createElement("div");
|
|
2283
|
+
list.className = "intel-section-list";
|
|
2284
|
+
section.rows.slice(0, section.limit || 8).forEach(function (row) {
|
|
2285
|
+
var button = document.createElement("button");
|
|
2286
|
+
button.type = "button";
|
|
2287
|
+
button.className = classNames("intel-row", row.status && "intel-row-" + safeCssName(row.status), row.path && "clickable");
|
|
2288
|
+
button.innerHTML = [
|
|
2289
|
+
"<span class=\"intel-row-main\"><strong></strong><em></em></span>",
|
|
2290
|
+
"<span class=\"intel-row-meta\"></span>",
|
|
2291
|
+
"<span class=\"intel-row-bar\"><i></i></span>"
|
|
2292
|
+
].join("");
|
|
2293
|
+
button.querySelector("strong").textContent = row.label || "";
|
|
2294
|
+
button.querySelector("em").textContent = row.value || "";
|
|
2295
|
+
button.querySelector(".intel-row-meta").textContent = row.meta || "";
|
|
2296
|
+
button.querySelector(".intel-row-bar i").style.width = clamp(Number(row.score || 0), 4, 100) + "%";
|
|
2297
|
+
if (row.path) {
|
|
2298
|
+
button.title = "Focus " + row.path + " in the graph";
|
|
2299
|
+
button.addEventListener("click", function () {
|
|
2300
|
+
focusGraphPath(row.path);
|
|
2301
|
+
});
|
|
2302
|
+
} else {
|
|
2303
|
+
button.disabled = true;
|
|
2304
|
+
}
|
|
2305
|
+
list.appendChild(button);
|
|
2306
|
+
});
|
|
2307
|
+
panel.appendChild(list);
|
|
2308
|
+
return panel;
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
function buildIntelligenceSections(reports) {
|
|
2312
|
+
var sections = [];
|
|
2313
|
+
var contributors = reports.contributors;
|
|
2314
|
+
var risk = reports.risk;
|
|
2315
|
+
var decisions = reports.decisions;
|
|
2316
|
+
var health = reports.moduleHealth;
|
|
2317
|
+
var insights = reports.graphInsights;
|
|
2318
|
+
|
|
2319
|
+
if (contributors || risk) {
|
|
2320
|
+
var profiles = contributors && Array.isArray(contributors.contributors) ? contributors.contributors : [];
|
|
2321
|
+
var silos = risk && Array.isArray(risk.ownership_silos) ? risk.ownership_silos : [];
|
|
2322
|
+
var maxOwned = Math.max(1, profiles.reduce(function (max, profile) {
|
|
2323
|
+
return Math.max(max, Number(profile.primary_owned_files || 0));
|
|
2324
|
+
}, 0));
|
|
2325
|
+
var rows = profiles.slice(0, 6).map(function (profile) {
|
|
2326
|
+
var owned = Number(profile.primary_owned_files || 0);
|
|
2327
|
+
return {
|
|
2328
|
+
label: shortContributor(profile.contributor),
|
|
2329
|
+
value: owned + " owned",
|
|
2330
|
+
meta: profile.commits_90d + " commits in 90d, " + (profile.silo_files ? profile.silo_files.length : 0) + " silo files",
|
|
2331
|
+
score: owned / maxOwned * 100,
|
|
2332
|
+
status: owned > 0 && profile.silo_files && profile.silo_files.length ? "warn" : "ok",
|
|
2333
|
+
};
|
|
2334
|
+
}).concat(silos.slice(0, 4).map(function (silo) {
|
|
2335
|
+
return {
|
|
2336
|
+
label: silo.file_path,
|
|
2337
|
+
value: Math.round(Number(silo.primary_owner_pct || 0) * 100) + "% owner",
|
|
2338
|
+
meta: shortContributor(silo.primary_owner || "unknown") + ", " + (silo.commit_count_total || 0) + " commits",
|
|
2339
|
+
score: Number(silo.primary_owner_pct || 0) * 100,
|
|
2340
|
+
status: "warn",
|
|
2341
|
+
path: silo.file_path,
|
|
2342
|
+
};
|
|
2343
|
+
}));
|
|
2344
|
+
if (rows.length) {
|
|
2345
|
+
sections.push({
|
|
2346
|
+
title: "Ownership Map",
|
|
2347
|
+
kicker: "who owns what",
|
|
2348
|
+
stat: silos.length + " silos",
|
|
2349
|
+
summary: "Repowise has a dedicated ownership page. Kage now surfaces the same reviewer-critical signal inside the memory viewer, tied back to selectable files.",
|
|
2350
|
+
rows: rows,
|
|
2351
|
+
limit: 10,
|
|
2352
|
+
});
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
if (health && Array.isArray(health.modules)) {
|
|
2357
|
+
var modules = health.modules.slice().sort(function (a, b) {
|
|
2358
|
+
return Number(a.score || 0) - Number(b.score || 0) || String(a.module).localeCompare(String(b.module));
|
|
2359
|
+
});
|
|
2360
|
+
sections.push({
|
|
2361
|
+
title: "Module Health Map",
|
|
2362
|
+
kicker: "churn / tests / ownership",
|
|
2363
|
+
stat: modules.length + " modules",
|
|
2364
|
+
summary: "Lowest-scoring modules are shown first so the viewer points people toward the riskiest areas instead of only drawing nodes.",
|
|
2365
|
+
rows: modules.slice(0, 8).map(function (item) {
|
|
2366
|
+
return {
|
|
2367
|
+
label: item.module,
|
|
2368
|
+
value: item.grade + " / " + item.score,
|
|
2369
|
+
meta: Array.isArray(item.reasons) ? item.reasons.slice(0, 2).join("; ") : "",
|
|
2370
|
+
score: Number(item.score || 0),
|
|
2371
|
+
status: Number(item.score || 0) < 60 ? "danger" : Number(item.score || 0) < 75 ? "warn" : "ok",
|
|
2372
|
+
};
|
|
2373
|
+
}),
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
if (decisions && Array.isArray(decisions.coverage_gaps)) {
|
|
2378
|
+
var gaps = decisions.coverage_gaps;
|
|
2379
|
+
sections.push({
|
|
2380
|
+
title: "Onboarding Targets",
|
|
2381
|
+
kicker: "missing repo lore",
|
|
2382
|
+
stat: (decisions.coverage_percent != null ? decisions.coverage_percent + "%" : "n/a"),
|
|
2383
|
+
summary: "Files with centrality, churn, test gaps, or ownership but no linked why-memory. These are the places a future agent is most likely to rediscover context.",
|
|
2384
|
+
rows: gaps.slice(0, 8).map(function (gap) {
|
|
2385
|
+
var score = Math.min(100, Number(gap.dependents || 0) * 18 + Number(gap.churn_90d || 0) * 6 + 12);
|
|
2386
|
+
return {
|
|
2387
|
+
label: gap.path,
|
|
2388
|
+
value: "needs memory",
|
|
2389
|
+
meta: gap.reason || "",
|
|
2390
|
+
score: score,
|
|
2391
|
+
status: "warn",
|
|
2392
|
+
path: gap.path,
|
|
2393
|
+
};
|
|
2394
|
+
}),
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
if (insights && Array.isArray(insights.communities)) {
|
|
2399
|
+
var communities = insights.communities.slice().sort(function (a, b) {
|
|
2400
|
+
return (b.files ? b.files.length : 0) - (a.files ? a.files.length : 0);
|
|
2401
|
+
});
|
|
2402
|
+
var maxFiles = Math.max(1, communities.reduce(function (max, community) {
|
|
2403
|
+
return Math.max(max, community.files ? community.files.length : 0);
|
|
2404
|
+
}, 0));
|
|
2405
|
+
sections.push({
|
|
2406
|
+
title: "Architecture Communities",
|
|
2407
|
+
kicker: "module clusters",
|
|
2408
|
+
stat: communities.length + " clusters",
|
|
2409
|
+
summary: "Repowise exposes architecture/community views. Kage can show the same high-level clusters next to the memory-code graph so users know what a dense graph means.",
|
|
2410
|
+
rows: communities.slice(0, 8).map(function (community) {
|
|
2411
|
+
var files = community.files || [];
|
|
2412
|
+
var entrypoints = community.entrypoints || [];
|
|
2413
|
+
var routes = community.routes || [];
|
|
2414
|
+
return {
|
|
2415
|
+
label: community.label || ("community " + community.id),
|
|
2416
|
+
value: files.length + " files",
|
|
2417
|
+
meta: entrypoints.length + " entrypoints, " + routes.length + " routes",
|
|
2418
|
+
score: files.length / maxFiles * 100,
|
|
2419
|
+
status: routes.length || entrypoints.length ? "ok" : "",
|
|
2420
|
+
path: files[0],
|
|
2421
|
+
};
|
|
2422
|
+
}),
|
|
2423
|
+
});
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
if (insights && Array.isArray(insights.entry_flows) && insights.entry_flows.length) {
|
|
2427
|
+
sections.push({
|
|
2428
|
+
title: "Execution Flows",
|
|
2429
|
+
kicker: "entrypoint traces",
|
|
2430
|
+
stat: insights.entry_flows.length + " flows",
|
|
2431
|
+
summary: "Short traces make the code graph explainable: where execution starts, what it crosses, and which file to inspect first.",
|
|
2432
|
+
rows: insights.entry_flows.slice(0, 8).map(function (flow) {
|
|
2433
|
+
var path = flow.path || [];
|
|
2434
|
+
return {
|
|
2435
|
+
label: flow.entry,
|
|
2436
|
+
value: Math.max(0, path.length - 1) + " hops",
|
|
2437
|
+
meta: path.slice(0, 5).join(" -> "),
|
|
2438
|
+
score: Math.min(100, Math.max(12, path.length * 18)),
|
|
2439
|
+
status: "ok",
|
|
2440
|
+
path: flow.entry,
|
|
2441
|
+
};
|
|
2442
|
+
}),
|
|
2443
|
+
});
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
if (risk) {
|
|
2447
|
+
var targets = Array.isArray(risk.targets) ? risk.targets : Object.keys(risk.targets || {}).map(function (key) { return risk.targets[key]; });
|
|
2448
|
+
var hotspots = Array.isArray(risk.global_hotspots) ? risk.global_hotspots : [];
|
|
2449
|
+
var riskRows = targets.slice(0, 6).map(function (item) {
|
|
2450
|
+
return {
|
|
2451
|
+
label: item.target,
|
|
2452
|
+
value: item.risk_type || "risk",
|
|
2453
|
+
meta: item.risk_summary || "",
|
|
2454
|
+
score: Math.round(Number(item.hotspot_score || 0) * 100) || Math.min(100, Number(item.dependents_count || 0) * 12 + (item.test_gap ? 24 : 0)),
|
|
2455
|
+
status: item.test_gap || item.risk_type === "single-owner" ? "warn" : "",
|
|
2456
|
+
path: item.target,
|
|
2457
|
+
};
|
|
2458
|
+
}).concat(hotspots.slice(0, 4).map(function (hotspot) {
|
|
2459
|
+
return {
|
|
2460
|
+
label: hotspot.file_path,
|
|
2461
|
+
value: Math.round(Number(hotspot.hotspot_score || 0) * 100) + "% hot",
|
|
2462
|
+
meta: (hotspot.commit_count_90d || 0) + " commits in 90d, owner " + shortContributor(hotspot.primary_owner || "unknown"),
|
|
2463
|
+
score: Math.round(Number(hotspot.hotspot_score || 0) * 100),
|
|
2464
|
+
status: "danger",
|
|
2465
|
+
path: hotspot.file_path,
|
|
2466
|
+
};
|
|
2467
|
+
}));
|
|
2468
|
+
if (riskRows.length) {
|
|
2469
|
+
sections.push({
|
|
2470
|
+
title: "Blast Radius",
|
|
2471
|
+
kicker: "change impact",
|
|
2472
|
+
stat: riskRows.length + " signals",
|
|
2473
|
+
summary: "Change risk is useful only if it is browsable. Rows focus the graph on affected files when the graph node exists.",
|
|
2474
|
+
rows: riskRows,
|
|
2475
|
+
limit: 10,
|
|
2476
|
+
});
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
return sections.filter(function (section) { return section.rows && section.rows.length; });
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
function focusGraphPath(path) {
|
|
2484
|
+
if (!path) return;
|
|
2485
|
+
var normalized = String(path).replace(/\\/g, "/").replace(/^\.\//, "");
|
|
2486
|
+
var found = state.entities.find(function (entity) {
|
|
2487
|
+
var entityPath = String(entity.path || "").replace(/\\/g, "/").replace(/^\.\//, "");
|
|
2488
|
+
return entityPath === normalized || entity.id === normalized || entity.id === "code:file:" + normalized;
|
|
2489
|
+
}) || state.entities.find(function (entity) {
|
|
2490
|
+
var text = [entity.id, entity.path, entity.name].filter(Boolean).join(" ");
|
|
2491
|
+
return text.indexOf(normalized) !== -1;
|
|
2492
|
+
});
|
|
2493
|
+
if (!found) {
|
|
2494
|
+
els.searchInput.value = normalized;
|
|
2495
|
+
scheduleRender();
|
|
2496
|
+
return;
|
|
2497
|
+
}
|
|
2498
|
+
state.selected = { kind: "entity", id: found.id };
|
|
2499
|
+
els.searchInput.value = normalized;
|
|
2500
|
+
scheduleRender();
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
function shortContributor(value) {
|
|
2504
|
+
var text = String(value || "unknown");
|
|
2505
|
+
return text.replace(/\s*<[^>]+>\s*$/, "");
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
function buildIntelligenceCards(reports) {
|
|
2509
|
+
var cards = [];
|
|
2510
|
+
var memoryCodeEdges = state.edges.filter(isMemoryCodeEdge);
|
|
2511
|
+
if (state.edges.length) {
|
|
2512
|
+
var byRelation = new Map();
|
|
2513
|
+
memoryCodeEdges.forEach(function (edge) {
|
|
2514
|
+
byRelation.set(edge.relation, (byRelation.get(edge.relation) || 0) + 1);
|
|
2515
|
+
});
|
|
2516
|
+
cards.push({
|
|
2517
|
+
title: "Memory-Code Bridge",
|
|
2518
|
+
kicker: "shareable repo lore",
|
|
2519
|
+
summary: memoryCodeEdges.length
|
|
2520
|
+
? "Approved memory is linked back to concrete code artifacts so future agents can recall why the code exists."
|
|
2521
|
+
: "No memory-code links are visible in the loaded graph.",
|
|
2522
|
+
rows: [
|
|
2523
|
+
["Links", String(memoryCodeEdges.length)],
|
|
2524
|
+
["Visible", String(memoryCodeEdges.filter(function (edge) { return state.visibleEdgeIds.has(edge.id); }).length)],
|
|
2525
|
+
["Precise", String(memoryCodeEdges.filter(function (edge) { return edge.relation !== "affects_code_path"; }).length)]
|
|
2526
|
+
].concat(Array.from(byRelation.entries()).sort(function (a, b) { return b[1] - a[1] || a[0].localeCompare(b[0]); }).slice(0, 2).map(function (entry) {
|
|
2527
|
+
return [entry[0], String(entry[1])];
|
|
2528
|
+
}))
|
|
2529
|
+
});
|
|
2530
|
+
}
|
|
2531
|
+
var risk = reports.risk;
|
|
2532
|
+
if (risk) {
|
|
2533
|
+
var targets = Array.isArray(risk.targets) ? risk.targets : Object.keys(risk.targets || {}).map(function (key) { return risk.targets[key]; });
|
|
2534
|
+
var silos = Array.isArray(risk.ownership_silos) ? risk.ownership_silos : [];
|
|
2535
|
+
cards.push({
|
|
2536
|
+
title: "Change Risk",
|
|
2537
|
+
kicker: "blast radius",
|
|
2538
|
+
summary: risk.summary || "Local risk report from code graph and git history.",
|
|
2539
|
+
rows: targets.slice(0, 5).map(function (item) {
|
|
2540
|
+
return [item.target || "target", item.risk_summary || [item.risk_type, item.dependents_count != null ? item.dependents_count + " dependents" : ""].filter(Boolean).join(", ")];
|
|
2541
|
+
}).concat(targets.length ? [] : [["Hotspots", Array.isArray(risk.global_hotspots) ? risk.global_hotspots.length + " global" : "none"]])
|
|
2542
|
+
.concat(silos.length ? [["Silos", silos.length + " ownership concentration(s)"]] : [])
|
|
2543
|
+
});
|
|
2544
|
+
}
|
|
2545
|
+
var contributors = reports.contributors;
|
|
2546
|
+
if (contributors) {
|
|
2547
|
+
var profiles = Array.isArray(contributors.contributors) ? contributors.contributors : [];
|
|
2548
|
+
cards.push({
|
|
2549
|
+
title: "Contributors",
|
|
2550
|
+
kicker: "local git profiles",
|
|
2551
|
+
summary: contributors.summary || "Contributor profiles from git history, ownership, modules, and hotspots.",
|
|
2552
|
+
rows: profiles.slice(0, 5).map(function (profile) {
|
|
2553
|
+
return [
|
|
2554
|
+
profile.contributor || "contributor",
|
|
2555
|
+
profile.commits_total + " commits, " + profile.commits_90d + " in 90d, " + profile.primary_owned_files + " owned file(s)"
|
|
2556
|
+
];
|
|
2557
|
+
}).concat(profiles.length ? [] : [["Profiles", "No contributor profiles loaded"]])
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
var decisions = reports.decisions;
|
|
2561
|
+
if (decisions) {
|
|
2562
|
+
var topDecisions = Array.isArray(decisions.top_decisions) ? decisions.top_decisions : [];
|
|
2563
|
+
var gaps = Array.isArray(decisions.coverage_gaps) ? decisions.coverage_gaps : [];
|
|
2564
|
+
cards.push({
|
|
2565
|
+
title: "Decision Memory",
|
|
2566
|
+
kicker: "why / gotchas / runbooks",
|
|
2567
|
+
summary: decisions.summary || "Decision memory connected to code paths, plus important uncovered files.",
|
|
2568
|
+
rows: [
|
|
2569
|
+
["Coverage", (decisions.coverage_percent != null ? decisions.coverage_percent + "%" : "n/a") + " of code paths"],
|
|
2570
|
+
["Why-memory", String(decisions.decision_memory_count || 0)],
|
|
2571
|
+
["Coverage gaps", String(gaps.length)]
|
|
2572
|
+
].concat(topDecisions.slice(0, 2).map(function (item) {
|
|
2573
|
+
return [item.type || "memory", item.title || item.summary || "decision memory"];
|
|
2574
|
+
})).concat(gaps.slice(0, 1).map(function (gap) {
|
|
2575
|
+
return ["Uncovered", gap.path + " - " + gap.reason];
|
|
2576
|
+
}))
|
|
2577
|
+
});
|
|
2578
|
+
}
|
|
2579
|
+
var health = reports.moduleHealth;
|
|
2580
|
+
if (health) {
|
|
2581
|
+
var modules = Array.isArray(health.modules) ? health.modules : [];
|
|
2582
|
+
cards.push({
|
|
2583
|
+
title: "Module Health",
|
|
2584
|
+
kicker: "churn / tests / ownership",
|
|
2585
|
+
summary: health.summary || "Module scorecards from local graph and git signals.",
|
|
2586
|
+
rows: modules.slice(0, 5).map(function (item) {
|
|
2587
|
+
return [item.module + " " + item.grade, "score " + item.score + " - " + (Array.isArray(item.reasons) ? item.reasons.slice(0, 2).join("; ") : "")];
|
|
2588
|
+
}).concat(modules.length ? [] : [["Modules", "No module scorecards loaded"]])
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
var insights = reports.graphInsights;
|
|
2592
|
+
if (insights) {
|
|
2593
|
+
var central = Array.isArray(insights.central_files) ? insights.central_files : [];
|
|
2594
|
+
var cycles = Array.isArray(insights.dependency_cycles) ? insights.dependency_cycles : [];
|
|
2595
|
+
var communities = Array.isArray(insights.communities) ? insights.communities : [];
|
|
2596
|
+
var coverage = Array.isArray(insights.language_coverage) ? insights.language_coverage : [];
|
|
2597
|
+
var edgeMix = insights.edge_mix || {};
|
|
2598
|
+
cards.push({
|
|
2599
|
+
title: "Graph Insights",
|
|
2600
|
+
kicker: "centrality / cycles / communities",
|
|
2601
|
+
summary: insights.summary || "Deterministic source graph intelligence.",
|
|
2602
|
+
rows: [
|
|
2603
|
+
["Central", central[0] ? central[0].path + " (" + central[0].dependents + " dependents)" : "none"],
|
|
2604
|
+
["Cycles", String(cycles.length)],
|
|
2605
|
+
["Communities", String(communities.length)],
|
|
2606
|
+
["Edges", [edgeMix.imports || 0, "imports", edgeMix.calls || 0, "calls"].join(" ")]
|
|
2607
|
+
].concat(central.slice(1, 3).map(function (item) { return ["Central", item.path]; }))
|
|
2608
|
+
.concat(coverage.slice(0, 1).map(function (item) { return [item.language, item.coverage_percent + "% indexed across " + item.files + " files"]; }))
|
|
2609
|
+
});
|
|
2610
|
+
}
|
|
2611
|
+
var workspace = reports.workspace;
|
|
2612
|
+
if (workspace) {
|
|
2613
|
+
var repos = Array.isArray(workspace.repos) ? workspace.repos : [];
|
|
2614
|
+
var deps = Array.isArray(workspace.package_dependencies) ? workspace.package_dependencies : [];
|
|
2615
|
+
var contracts = Array.isArray(workspace.route_contracts) ? workspace.route_contracts : [];
|
|
2616
|
+
cards.push({
|
|
2617
|
+
title: "Workspace",
|
|
2618
|
+
kicker: "multi-repo memory",
|
|
2619
|
+
summary: workspace.summary || "Workspace scan across local git repos.",
|
|
2620
|
+
rows: [
|
|
2621
|
+
["Repos", String(repos.length)],
|
|
2622
|
+
["Indexed", String(repos.filter(function (repo) { return repo.indexed; }).length)],
|
|
2623
|
+
["Package deps", String(deps.length)],
|
|
2624
|
+
["Route links", String(contracts.length)]
|
|
2625
|
+
].concat(repos.slice(0, 2).map(function (repo) { return [repo.alias, repo.approved_packets + " packets, " + repo.code_files + " files"]; }))
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
var quality = reports.quality;
|
|
2629
|
+
if (quality) {
|
|
2630
|
+
cards.push({
|
|
2631
|
+
title: "Memory Quality",
|
|
2632
|
+
kicker: "review gate",
|
|
2633
|
+
summary: quality.summary || "Packet quality, duplicates, staleness, and grounding.",
|
|
2634
|
+
rows: [
|
|
2635
|
+
["Useful", quality.useful_memory_ratio_percent != null ? quality.useful_memory_ratio_percent + "%" : "n/a"],
|
|
2636
|
+
["Evidence", quality.evidence_coverage_percent != null ? quality.evidence_coverage_percent + "%" : "n/a"],
|
|
2637
|
+
["Path grounded", quality.path_grounding_coverage_percent != null ? quality.path_grounding_coverage_percent + "%" : "n/a"],
|
|
2638
|
+
["Pending", quality.totals ? String(quality.totals.pending) : "n/a"]
|
|
2639
|
+
]
|
|
2640
|
+
});
|
|
2641
|
+
}
|
|
2642
|
+
var benchmark = reports.benchmark;
|
|
2643
|
+
if (benchmark) {
|
|
2644
|
+
var checks = Array.isArray(benchmark.checks) ? benchmark.checks : [];
|
|
2645
|
+
cards.push({
|
|
2646
|
+
title: "Benchmark",
|
|
2647
|
+
kicker: "local proof",
|
|
2648
|
+
summary: benchmark.summary || "Local memory and graph benchmark signals.",
|
|
2649
|
+
rows: checks.slice(0, 5).map(function (item) {
|
|
2650
|
+
return [item.name || "check", (item.pass ? "pass" : "check") + " - " + item.actual + "/" + item.target];
|
|
2651
|
+
})
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
return cards;
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2170
2657
|
function renderStatusStrip(visibleEntities, visibleEdges, official) {
|
|
2171
2658
|
if (!els.statusStrip) return;
|
|
2172
2659
|
var memoryCount = visibleEntities.filter(function (entity) { return entity.graph_kind === "memory"; }).length;
|
package/viewer/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>Kage Memory Terminal</title>
|
|
7
|
-
<link rel="stylesheet" href="./styles.css?v=
|
|
7
|
+
<link rel="stylesheet" href="./styles.css?v=21">
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<header class="app-header">
|
|
@@ -146,6 +146,14 @@
|
|
|
146
146
|
<div id="proofList" class="proof-list"></div>
|
|
147
147
|
</section>
|
|
148
148
|
|
|
149
|
+
<section class="intelligence-panel" aria-label="Repo intelligence">
|
|
150
|
+
<div class="panel-heading">
|
|
151
|
+
<h2>Repo Intelligence</h2>
|
|
152
|
+
<span id="intelligenceStatus">reports</span>
|
|
153
|
+
</div>
|
|
154
|
+
<div id="intelligenceList" class="intelligence-list"></div>
|
|
155
|
+
</section>
|
|
156
|
+
|
|
149
157
|
<section class="table-panel entities-panel" aria-label="Nodes">
|
|
150
158
|
<div class="panel-heading">
|
|
151
159
|
<h2>Nodes</h2>
|
|
@@ -163,6 +171,6 @@
|
|
|
163
171
|
</section>
|
|
164
172
|
</main>
|
|
165
173
|
|
|
166
|
-
<script src="./app.js?v=
|
|
174
|
+
<script src="./app.js?v=21"></script>
|
|
167
175
|
</body>
|
|
168
176
|
</html>
|