@kage-core/kage-graph-mcp 1.2.0 → 1.3.0
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/dist/cli.js +60 -0
- package/dist/daemon.js +5 -1
- package/dist/index.js +22 -9
- package/dist/kernel.js +170 -3
- package/package.json +1 -1
- package/viewer/app.js +93 -4
- package/viewer/index.html +8 -6
- package/viewer/styles.css +99 -2
package/dist/cli.js
CHANGED
|
@@ -12,6 +12,7 @@ function usage() {
|
|
|
12
12
|
|
|
13
13
|
Usage:
|
|
14
14
|
kage index --project <dir>
|
|
15
|
+
kage demo [--project <dir>]
|
|
15
16
|
kage init --project <dir>
|
|
16
17
|
kage policy --project <dir>
|
|
17
18
|
kage doctor --project <dir>
|
|
@@ -31,6 +32,7 @@ Usage:
|
|
|
31
32
|
kage gc --project <dir> [--dry-run] [--force] [--json]
|
|
32
33
|
kage compact --project <dir> [--dry-run] [--json]
|
|
33
34
|
kage verify --project <dir> [--id <packet-id>] [--json]
|
|
35
|
+
kage suppressed --project <dir> [--json]
|
|
34
36
|
kage pr summarize --project <dir> [--json]
|
|
35
37
|
kage pr check --project <dir> [--json]
|
|
36
38
|
kage upgrade [--dry-run]
|
|
@@ -60,6 +62,7 @@ Usage:
|
|
|
60
62
|
kage inbox --project <dir> [--json]
|
|
61
63
|
kage quality --project <dir> [--json]
|
|
62
64
|
kage benchmark --project <dir> [--json]
|
|
65
|
+
kage benchmark --trust --project <dir> [--json]
|
|
63
66
|
kage benchmark --memory-quality [--json]
|
|
64
67
|
kage benchmark --scale [--sizes 240,1000,5000] [--json]
|
|
65
68
|
kage benchmark --project <dir> --compare --task <task> [--json]
|
|
@@ -176,6 +179,30 @@ async function main() {
|
|
|
176
179
|
console.log(`Indexes:\n${result.indexes.map((path) => ` - ${path}`).join("\n")}`);
|
|
177
180
|
return;
|
|
178
181
|
}
|
|
182
|
+
if (command === "demo") {
|
|
183
|
+
const demoDir = takeArg(args, "--project") ?? `${process.cwd()}/kage-demo`;
|
|
184
|
+
const result = (0, kernel_js_1.runDemo)(demoDir);
|
|
185
|
+
if (args.includes("--json")) {
|
|
186
|
+
console.log(JSON.stringify(result, null, 2));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
console.log("Kage demo — can you trust your agent's memory?\n");
|
|
190
|
+
console.log(`Seeded ${result.captured.length} grounded memories in ${result.project_dir}\n`);
|
|
191
|
+
console.log("1. Hallucinated citation — REJECTED on write:");
|
|
192
|
+
if (result.rejected_hallucination) {
|
|
193
|
+
console.log(` ✗ "${result.rejected_hallucination.title}"`);
|
|
194
|
+
console.log(` ${result.rejected_hallucination.error}\n`);
|
|
195
|
+
}
|
|
196
|
+
console.log("2. Stale memory (cited file deleted) — WITHHELD from recall:");
|
|
197
|
+
for (const w of result.withheld)
|
|
198
|
+
console.log(` ⊘ ${w.title}\n ${w.reason}`);
|
|
199
|
+
console.log("\n3. Recall returns only grounded, current memory:");
|
|
200
|
+
for (const t of result.recalled)
|
|
201
|
+
console.log(` ✓ ${t}`);
|
|
202
|
+
console.log(`\nTrust score: ${result.trust_score}/100`);
|
|
203
|
+
console.log(`\nSee it in the viewer: ${result.viewer_command}`);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
179
206
|
if (command === "init") {
|
|
180
207
|
const result = (0, kernel_js_1.initProject)(projectArg(args));
|
|
181
208
|
console.log(`Initialized Kage memory for ${result.index.projectDir}`);
|
|
@@ -465,6 +492,21 @@ async function main() {
|
|
|
465
492
|
}
|
|
466
493
|
return;
|
|
467
494
|
}
|
|
495
|
+
if (command === "suppressed") {
|
|
496
|
+
const result = (0, kernel_js_1.kageSuppressedMemory)(projectArg(args));
|
|
497
|
+
if (args.includes("--json")) {
|
|
498
|
+
console.log(JSON.stringify(result, null, 2));
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
console.log(`Kage suppressed memory — ${result.count} packet(s) currently withheld from recall`);
|
|
502
|
+
for (const item of result.items) {
|
|
503
|
+
console.log(` ⊘ ${item.title}`);
|
|
504
|
+
console.log(` ${item.reason}`);
|
|
505
|
+
}
|
|
506
|
+
if (!result.count)
|
|
507
|
+
console.log(" (none — all recallable memory is grounded and current)");
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
468
510
|
if (command === "refresh") {
|
|
469
511
|
const result = (0, kernel_js_1.refreshProject)(projectArg(args), { full: args.includes("--full") });
|
|
470
512
|
if (args.includes("--json")) {
|
|
@@ -1319,6 +1361,24 @@ async function main() {
|
|
|
1319
1361
|
return;
|
|
1320
1362
|
}
|
|
1321
1363
|
if (command === "benchmark") {
|
|
1364
|
+
if (args.includes("--trust")) {
|
|
1365
|
+
const result = (0, kernel_js_1.benchmarkTrust)(projectArg(args));
|
|
1366
|
+
if (args.includes("--json")) {
|
|
1367
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1368
|
+
if (!result.ok)
|
|
1369
|
+
process.exitCode = 1;
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
console.log("Kage Trust Benchmark — can this memory be trusted?");
|
|
1373
|
+
console.log(`Trust score: ${result.trust_score}/100 (${result.ok ? "PASS" : "REVIEW"})`);
|
|
1374
|
+
console.log(` Hallucinated-citation rejection: ${result.metrics.hallucinated_citation_rejection_rate}% (${result.detail.hallucination.rejected}/${result.detail.hallucination.attempted})`);
|
|
1375
|
+
console.log(` Stale-memory exclusion: ${result.metrics.stale_memory_exclusion_rate}% (${result.detail.staleness.excluded_after}/${result.detail.staleness.recallable_before})`);
|
|
1376
|
+
console.log(` Live grounding rate: ${result.metrics.live_grounding_rate}% (${result.detail.live_memory.grounded}/${result.detail.live_memory.checked} packets)`);
|
|
1377
|
+
console.log(` Wrong-advice prevented: ${result.metrics.wrong_advice_prevented_rate}%`);
|
|
1378
|
+
if (!result.ok)
|
|
1379
|
+
process.exitCode = 1;
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1322
1382
|
if (args.includes("--memory-quality")) {
|
|
1323
1383
|
const result = (0, kernel_js_1.benchmarkCodingMemoryQuality)({
|
|
1324
1384
|
topK: Number(takeArg(args, "--top-k") ?? 10),
|
package/dist/daemon.js
CHANGED
|
@@ -573,6 +573,8 @@ async function startViewer(projectDir, options = {}) {
|
|
|
573
573
|
const timelinePath = (0, node_path_1.join)(reportsDir, "timeline.json");
|
|
574
574
|
const lineagePath = (0, node_path_1.join)(reportsDir, "lineage.json");
|
|
575
575
|
const setupPath = (0, node_path_1.join)(reportsDir, "setup.json");
|
|
576
|
+
const trustPath = (0, node_path_1.join)(reportsDir, "trust.json");
|
|
577
|
+
const suppressedPath = (0, node_path_1.join)(reportsDir, "suppressed.json");
|
|
576
578
|
// Pre-generate lightweight JSON reports so the viewer can load them directly.
|
|
577
579
|
try {
|
|
578
580
|
(0, node_fs_1.mkdirSync)(reportsDir, { recursive: true });
|
|
@@ -601,11 +603,13 @@ async function startViewer(projectDir, options = {}) {
|
|
|
601
603
|
(0, node_fs_1.writeFileSync)(timelinePath, JSON.stringify((0, kernel_js_1.kageMemoryTimeline)(projectDir), null, 2));
|
|
602
604
|
(0, node_fs_1.writeFileSync)(lineagePath, JSON.stringify((0, kernel_js_1.kageMemoryLineage)(projectDir), null, 2));
|
|
603
605
|
(0, node_fs_1.writeFileSync)(setupPath, JSON.stringify((0, kernel_js_1.setupDoctor)(projectDir), null, 2));
|
|
606
|
+
(0, node_fs_1.writeFileSync)(trustPath, JSON.stringify((0, kernel_js_1.benchmarkTrust)(projectDir), null, 2));
|
|
607
|
+
(0, node_fs_1.writeFileSync)(suppressedPath, JSON.stringify((0, kernel_js_1.kageSuppressedMemory)(projectDir), null, 2));
|
|
604
608
|
}
|
|
605
609
|
catch {
|
|
606
610
|
// non-fatal: viewer will show 404 for reports if generation fails
|
|
607
611
|
}
|
|
608
|
-
const url = `http://${host}:${port}/viewer/index.html?graph=${encodeURIComponent(graphPath)}&code=${encodeURIComponent(codePath)}&metrics=${encodeURIComponent(metricsPath)}&inbox=${encodeURIComponent(inboxPath)}&review=${encodeURIComponent(reviewPath)}&pending=${encodeURIComponent(pendingDir)}&quality=${encodeURIComponent(qualityPath)}&benchmark=${encodeURIComponent(benchmarkPath)}&contributors=${encodeURIComponent(contributorsPath)}&profile=${encodeURIComponent(profilePath)}&xray=${encodeURIComponent(xrayPath)}&capabilities=${encodeURIComponent(capabilitiesPath)}&slots=${encodeURIComponent(slotsPath)}&decisions=${encodeURIComponent(decisionsPath)}&risk=${encodeURIComponent(riskPath)}&moduleHealth=${encodeURIComponent(moduleHealthPath)}&graphInsights=${encodeURIComponent(graphInsightsPath)}&workspace=${encodeURIComponent(workspacePath)}&sessions=${encodeURIComponent(sessionsPath)}&replay=${encodeURIComponent(replayPath)}&memoryAccess=${encodeURIComponent(memoryAccessPath)}&memoryAudit=${encodeURIComponent(memoryAuditPath)}&handoff=${encodeURIComponent(handoffPath)}&lifecycle=${encodeURIComponent(lifecyclePath)}&timeline=${encodeURIComponent(timelinePath)}&lineage=${encodeURIComponent(lineagePath)}&setup=${encodeURIComponent(setupPath)}&view=code`;
|
|
612
|
+
const url = `http://${host}:${port}/viewer/index.html?graph=${encodeURIComponent(graphPath)}&code=${encodeURIComponent(codePath)}&metrics=${encodeURIComponent(metricsPath)}&inbox=${encodeURIComponent(inboxPath)}&review=${encodeURIComponent(reviewPath)}&pending=${encodeURIComponent(pendingDir)}&quality=${encodeURIComponent(qualityPath)}&benchmark=${encodeURIComponent(benchmarkPath)}&contributors=${encodeURIComponent(contributorsPath)}&profile=${encodeURIComponent(profilePath)}&xray=${encodeURIComponent(xrayPath)}&capabilities=${encodeURIComponent(capabilitiesPath)}&slots=${encodeURIComponent(slotsPath)}&decisions=${encodeURIComponent(decisionsPath)}&risk=${encodeURIComponent(riskPath)}&moduleHealth=${encodeURIComponent(moduleHealthPath)}&graphInsights=${encodeURIComponent(graphInsightsPath)}&workspace=${encodeURIComponent(workspacePath)}&sessions=${encodeURIComponent(sessionsPath)}&replay=${encodeURIComponent(replayPath)}&memoryAccess=${encodeURIComponent(memoryAccessPath)}&memoryAudit=${encodeURIComponent(memoryAuditPath)}&handoff=${encodeURIComponent(handoffPath)}&lifecycle=${encodeURIComponent(lifecyclePath)}&timeline=${encodeURIComponent(timelinePath)}&lineage=${encodeURIComponent(lineagePath)}&setup=${encodeURIComponent(setupPath)}&trust=${encodeURIComponent(trustPath)}&suppressed=${encodeURIComponent(suppressedPath)}&view=code`;
|
|
609
613
|
const server = (0, node_http_1.createServer)((req, res) => {
|
|
610
614
|
const requestUrl = new URL(req.url ?? "/", `http://${host}:${port}`);
|
|
611
615
|
let filePath = null;
|
package/dist/index.js
CHANGED
|
@@ -590,7 +590,7 @@ function listTools() {
|
|
|
590
590
|
type: "object",
|
|
591
591
|
properties: {
|
|
592
592
|
project_dir: { type: "string" },
|
|
593
|
-
mode: { type: "string", enum: ["project", "memory_quality", "memory_scale"] },
|
|
593
|
+
mode: { type: "string", enum: ["project", "trust", "memory_quality", "memory_scale"] },
|
|
594
594
|
sizes: { type: "array", items: { type: "number" } },
|
|
595
595
|
top_k: { type: "number" },
|
|
596
596
|
},
|
|
@@ -1376,14 +1376,16 @@ async function callTool(name, args) {
|
|
|
1376
1376
|
}
|
|
1377
1377
|
if (name === "kage_benchmark") {
|
|
1378
1378
|
const mode = String(args?.mode ?? "project");
|
|
1379
|
-
const result = mode === "
|
|
1380
|
-
? (0, kernel_js_1.
|
|
1381
|
-
: mode === "
|
|
1382
|
-
? (0, kernel_js_1.
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1379
|
+
const result = mode === "trust"
|
|
1380
|
+
? (0, kernel_js_1.benchmarkTrust)(String(args?.project_dir ?? ""))
|
|
1381
|
+
: mode === "memory_quality"
|
|
1382
|
+
? (0, kernel_js_1.benchmarkCodingMemoryQuality)({ topK: Number(args?.top_k ?? 10) })
|
|
1383
|
+
: mode === "memory_scale"
|
|
1384
|
+
? (0, kernel_js_1.benchmarkMemoryScale)({
|
|
1385
|
+
sizes: Array.isArray(args?.sizes) ? args.sizes.map(Number) : undefined,
|
|
1386
|
+
topK: Number(args?.top_k ?? 10),
|
|
1387
|
+
})
|
|
1388
|
+
: (0, kernel_js_1.benchmarkProject)(String(args?.project_dir ?? ""));
|
|
1387
1389
|
return {
|
|
1388
1390
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1389
1391
|
};
|
|
@@ -1692,5 +1694,16 @@ async function main() {
|
|
|
1692
1694
|
await server.connect(transport);
|
|
1693
1695
|
}
|
|
1694
1696
|
if (require.main === module) {
|
|
1697
|
+
const firstArg = process.argv[2];
|
|
1698
|
+
if (firstArg && !firstArg.startsWith("-")) {
|
|
1699
|
+
// A positional subcommand (demo, init, recall, ...) means the user wants the
|
|
1700
|
+
// kage CLI, not the MCP stdio server. Delegate so a single
|
|
1701
|
+
// `npx @kage-core/kage-graph-mcp <command>` works like the `kage` binary.
|
|
1702
|
+
// MCP clients launch with no args (or flags), which falls through to the server.
|
|
1703
|
+
const { spawnSync } = require("node:child_process");
|
|
1704
|
+
const { join } = require("node:path");
|
|
1705
|
+
const result = spawnSync(process.execPath, [join(__dirname, "cli.js"), ...process.argv.slice(2)], { stdio: "inherit" });
|
|
1706
|
+
process.exit(result.status ?? 0);
|
|
1707
|
+
}
|
|
1695
1708
|
main().catch(console.error);
|
|
1696
1709
|
}
|
package/dist/kernel.js
CHANGED
|
@@ -83,6 +83,7 @@ exports.buildIndexes = buildIndexes;
|
|
|
83
83
|
exports.indexProject = indexProject;
|
|
84
84
|
exports.refreshProject = refreshProject;
|
|
85
85
|
exports.gcProject = gcProject;
|
|
86
|
+
exports.kageSuppressedMemory = kageSuppressedMemory;
|
|
86
87
|
exports.verifyCitations = verifyCitations;
|
|
87
88
|
exports.compactProject = compactProject;
|
|
88
89
|
exports.installAgentPolicy = installAgentPolicy;
|
|
@@ -114,6 +115,8 @@ exports.kageMetrics = kageMetrics;
|
|
|
114
115
|
exports.auditProject = auditProject;
|
|
115
116
|
exports.memoryInbox = memoryInbox;
|
|
116
117
|
exports.qualityReport = qualityReport;
|
|
118
|
+
exports.benchmarkTrust = benchmarkTrust;
|
|
119
|
+
exports.runDemo = runDemo;
|
|
117
120
|
exports.benchmarkProject = benchmarkProject;
|
|
118
121
|
exports.benchmarkCodingMemoryQuality = benchmarkCodingMemoryQuality;
|
|
119
122
|
exports.benchmarkMemoryScale = benchmarkMemoryScale;
|
|
@@ -5595,8 +5598,20 @@ function gcProject(projectDir, options = {}) {
|
|
|
5595
5598
|
total_scanned: packetEntries.length,
|
|
5596
5599
|
};
|
|
5597
5600
|
}
|
|
5598
|
-
//
|
|
5599
|
-
//
|
|
5601
|
+
// The memory recall is actively WITHHOLDING from agents right now (hard-stale:
|
|
5602
|
+
// cited files deleted, ttl expired, or reported stale). This is the human-facing
|
|
5603
|
+
// counterpart to the silent recall-time exclusion — surfaced, never hidden.
|
|
5604
|
+
function kageSuppressedMemory(projectDir) {
|
|
5605
|
+
ensureMemoryDirs(projectDir);
|
|
5606
|
+
const cache = new Map();
|
|
5607
|
+
const items = loadApprovedPackets(projectDir)
|
|
5608
|
+
.map((packet) => {
|
|
5609
|
+
const reason = recallHardStaleReason(projectDir, packet, cache);
|
|
5610
|
+
return reason ? { id: packet.id, title: packet.title, type: packet.type, reason, paths: packet.paths } : null;
|
|
5611
|
+
})
|
|
5612
|
+
.filter((entry) => entry !== null);
|
|
5613
|
+
return { schema_version: 1, generated_at: nowIso(), count: items.length, items };
|
|
5614
|
+
}
|
|
5600
5615
|
function verifyCitations(projectDir, options = {}) {
|
|
5601
5616
|
ensureMemoryDirs(projectDir);
|
|
5602
5617
|
const approved = loadApprovedPackets(projectDir);
|
|
@@ -9838,6 +9853,152 @@ function qualityReport(projectDir) {
|
|
|
9838
9853
|
packets: rows,
|
|
9839
9854
|
};
|
|
9840
9855
|
}
|
|
9856
|
+
// The Trust Benchmark measures what retrieval benchmarks cannot: whether the memory
|
|
9857
|
+
// system can be TRUSTED — does it refuse to store hallucinated citations, does it
|
|
9858
|
+
// withhold memory whose evidence was deleted, and is live repo memory actually grounded.
|
|
9859
|
+
// Controlled gates run in an isolated sandbox; the grounding gate runs on the real repo.
|
|
9860
|
+
function benchmarkTrust(projectDir) {
|
|
9861
|
+
const runDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "kage-trust-"));
|
|
9862
|
+
const sandbox = (0, node_path_1.join)(runDir, "project");
|
|
9863
|
+
try {
|
|
9864
|
+
ensureMemoryDirs(sandbox);
|
|
9865
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(sandbox, "src"), { recursive: true });
|
|
9866
|
+
// Gate 1 — Hallucinated-citation rejection: a strict capture whose every cited path
|
|
9867
|
+
// is missing must be rejected. (No competitor validates citations at write time.)
|
|
9868
|
+
const hallucinationAttempts = 8;
|
|
9869
|
+
let rejected = 0;
|
|
9870
|
+
for (let i = 0; i < hallucinationAttempts; i += 1) {
|
|
9871
|
+
const result = capture({
|
|
9872
|
+
projectDir: sandbox,
|
|
9873
|
+
title: `Hallucinated rule ${i}`,
|
|
9874
|
+
body: `Use the helper in src/ghost-${i}.ts for retry handling.`,
|
|
9875
|
+
type: "decision",
|
|
9876
|
+
paths: [`src/ghost-${i}.ts`],
|
|
9877
|
+
strictCitations: true,
|
|
9878
|
+
});
|
|
9879
|
+
if (!result.ok)
|
|
9880
|
+
rejected += 1;
|
|
9881
|
+
}
|
|
9882
|
+
// Gate 2 — Stale-memory exclusion: memory grounded in real files at capture time
|
|
9883
|
+
// must be withheld from recall once those files are deleted (the "deleted since
|
|
9884
|
+
// capture" signal). We only count memories that were recallable BEFORE deletion.
|
|
9885
|
+
const staleAttempts = 8;
|
|
9886
|
+
const recallableBefore = [];
|
|
9887
|
+
for (let i = 0; i < staleAttempts; i += 1) {
|
|
9888
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(sandbox, "src", `widget-${i}.ts`), `export const widget${i} = ${i};\n`, "utf8");
|
|
9889
|
+
capture({
|
|
9890
|
+
projectDir: sandbox,
|
|
9891
|
+
title: `Widget ${i} retry invariant`,
|
|
9892
|
+
body: `Widget ${i} retries use idempotency token zeta${i} in src/widget-${i}.ts to avoid duplicate charges.`,
|
|
9893
|
+
type: "decision",
|
|
9894
|
+
paths: [`src/widget-${i}.ts`],
|
|
9895
|
+
});
|
|
9896
|
+
}
|
|
9897
|
+
for (let i = 0; i < staleAttempts; i += 1) {
|
|
9898
|
+
const before = recall(sandbox, `widget ${i} retry idempotency token zeta${i}`, 5, false, { trackAccess: false });
|
|
9899
|
+
recallableBefore[i] = before.results.some((entry) => entry.packet.title === `Widget ${i} retry invariant`);
|
|
9900
|
+
}
|
|
9901
|
+
for (let i = 0; i < staleAttempts; i += 1) {
|
|
9902
|
+
(0, node_fs_1.rmSync)((0, node_path_1.join)(sandbox, "src", `widget-${i}.ts`), { force: true });
|
|
9903
|
+
}
|
|
9904
|
+
let recallableCount = 0;
|
|
9905
|
+
let excludedAfter = 0;
|
|
9906
|
+
for (let i = 0; i < staleAttempts; i += 1) {
|
|
9907
|
+
if (!recallableBefore[i])
|
|
9908
|
+
continue;
|
|
9909
|
+
recallableCount += 1;
|
|
9910
|
+
const after = recall(sandbox, `widget ${i} retry idempotency token zeta${i}`, 5, false, { trackAccess: false });
|
|
9911
|
+
const surfaced = after.results.some((entry) => entry.packet.title === `Widget ${i} retry invariant`);
|
|
9912
|
+
if (!surfaced)
|
|
9913
|
+
excludedAfter += 1;
|
|
9914
|
+
}
|
|
9915
|
+
// Gate 3 — Live grounding: how much of the real repo's approved memory is grounded
|
|
9916
|
+
// (cited files exist) and not stale.
|
|
9917
|
+
const verify = verifyCitations(projectDir);
|
|
9918
|
+
const liveChecked = verify.checked;
|
|
9919
|
+
const grounded = verify.packets.filter((entry) => entry.grounded && !entry.stale).length;
|
|
9920
|
+
const hallucinationRate = percent(rejected, hallucinationAttempts);
|
|
9921
|
+
const staleRate = percent(excludedAfter, recallableCount || staleAttempts);
|
|
9922
|
+
const liveGroundingRate = liveChecked > 0 ? percent(grounded, liveChecked) : 100;
|
|
9923
|
+
const wrongAdvicePrevented = percent(rejected + excludedAfter, hallucinationAttempts + (recallableCount || staleAttempts));
|
|
9924
|
+
const gates = [
|
|
9925
|
+
{ name: "hallucinated_citation_rejection", target: 100, actual: hallucinationRate, unit: "percent", pass: hallucinationRate >= 100 },
|
|
9926
|
+
{ name: "stale_memory_exclusion", target: 100, actual: staleRate, unit: "percent", pass: staleRate >= 100 },
|
|
9927
|
+
{ name: "live_grounding_rate", target: 80, actual: liveGroundingRate, unit: "percent", pass: liveGroundingRate >= 80 },
|
|
9928
|
+
];
|
|
9929
|
+
const trustScore = Math.round((hallucinationRate + staleRate + liveGroundingRate) / 3);
|
|
9930
|
+
return {
|
|
9931
|
+
schema_version: 1,
|
|
9932
|
+
generated_at: nowIso(),
|
|
9933
|
+
ok: gates.every((gate) => gate.pass),
|
|
9934
|
+
trust_score: trustScore,
|
|
9935
|
+
gates,
|
|
9936
|
+
metrics: {
|
|
9937
|
+
hallucinated_citation_rejection_rate: hallucinationRate,
|
|
9938
|
+
stale_memory_exclusion_rate: staleRate,
|
|
9939
|
+
live_grounding_rate: liveGroundingRate,
|
|
9940
|
+
wrong_advice_prevented_rate: wrongAdvicePrevented,
|
|
9941
|
+
},
|
|
9942
|
+
detail: {
|
|
9943
|
+
hallucination: { attempted: hallucinationAttempts, rejected },
|
|
9944
|
+
staleness: { recallable_before: recallableCount, excluded_after: excludedAfter },
|
|
9945
|
+
live_memory: { checked: liveChecked, grounded, stale: verify.stale },
|
|
9946
|
+
},
|
|
9947
|
+
};
|
|
9948
|
+
}
|
|
9949
|
+
finally {
|
|
9950
|
+
(0, node_fs_1.rmSync)(runDir, { recursive: true, force: true });
|
|
9951
|
+
}
|
|
9952
|
+
}
|
|
9953
|
+
// `kage demo`: a self-contained 60-second proof of the trust wedge. Seeds a tiny
|
|
9954
|
+
// repo with grounded memory, then shows Kage (1) reject a hallucinated citation,
|
|
9955
|
+
// (2) withhold a memory whose cited file was deleted, and (3) recall only grounded
|
|
9956
|
+
// memory — the three things that make agent memory trustworthy.
|
|
9957
|
+
function runDemo(demoDir) {
|
|
9958
|
+
(0, node_fs_1.rmSync)(demoDir, { recursive: true, force: true });
|
|
9959
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(demoDir, "src"), { recursive: true });
|
|
9960
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(demoDir, "src", "auth.ts"), "export function validateToken() { return true; }\n", "utf8");
|
|
9961
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(demoDir, "src", "payments.ts"), "export function charge() { return 'ok'; }\n", "utf8");
|
|
9962
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(demoDir, "src", "legacy-retry.ts"), "export function retry() { return 1; }\n", "utf8");
|
|
9963
|
+
ensureMemoryDirs(demoDir);
|
|
9964
|
+
const captured = [];
|
|
9965
|
+
for (const m of [
|
|
9966
|
+
{ title: "Auth uses jose, not jsonwebtoken", body: "Validate tokens with jose in src/auth.ts; jsonwebtoken was removed.", paths: ["src/auth.ts"] },
|
|
9967
|
+
{ title: "Payments must be idempotent", body: "charge() in src/payments.ts must be idempotent to avoid double charges.", paths: ["src/payments.ts"] },
|
|
9968
|
+
{ title: "Legacy retry helper is the fallback", body: "Old retry logic lives in src/legacy-retry.ts and is used as a fallback.", paths: ["src/legacy-retry.ts"] },
|
|
9969
|
+
]) {
|
|
9970
|
+
const r = capture({ projectDir: demoDir, title: m.title, body: m.body, type: "decision", paths: m.paths });
|
|
9971
|
+
if (r.ok && r.packet)
|
|
9972
|
+
captured.push(r.packet.title);
|
|
9973
|
+
}
|
|
9974
|
+
// (2) delete a cited file → that memory becomes stale and is withheld from recall.
|
|
9975
|
+
(0, node_fs_1.unlinkSync)((0, node_path_1.join)(demoDir, "src", "legacy-retry.ts"));
|
|
9976
|
+
// (1) a hallucinated citation is rejected at write time.
|
|
9977
|
+
const hallucinated = capture({
|
|
9978
|
+
projectDir: demoDir,
|
|
9979
|
+
title: "Use the helper in src/ghost.ts",
|
|
9980
|
+
body: "Retry handling lives in src/ghost.ts.",
|
|
9981
|
+
type: "decision",
|
|
9982
|
+
paths: ["src/ghost.ts"],
|
|
9983
|
+
strictCitations: true,
|
|
9984
|
+
});
|
|
9985
|
+
const rejected = hallucinated.ok ? null : { title: "Use the helper in src/ghost.ts", error: hallucinated.errors[0] ?? "rejected" };
|
|
9986
|
+
// (3) recall surfaces grounded memory; the stale one is withheld.
|
|
9987
|
+
const recall = recallWithVectorScores(demoDir, "auth token payments retry idempotency", 5, false, { trackAccess: false });
|
|
9988
|
+
const recalled = recall.results.map((entry) => entry.packet.title);
|
|
9989
|
+
const withheld = kageSuppressedMemory(demoDir).items.map((item) => ({ title: item.title, reason: item.reason }));
|
|
9990
|
+
const trust = benchmarkTrust(demoDir);
|
|
9991
|
+
return {
|
|
9992
|
+
ok: true,
|
|
9993
|
+
project_dir: demoDir,
|
|
9994
|
+
captured,
|
|
9995
|
+
rejected_hallucination: rejected,
|
|
9996
|
+
recalled,
|
|
9997
|
+
withheld,
|
|
9998
|
+
trust_score: trust.trust_score,
|
|
9999
|
+
viewer_command: `kage viewer --project ${demoDir}`,
|
|
10000
|
+
};
|
|
10001
|
+
}
|
|
9841
10002
|
function benchmarkProject(projectDir, inputs = {}) {
|
|
9842
10003
|
ensureMemoryDirs(projectDir);
|
|
9843
10004
|
const built = inputs.codeGraph && inputs.knowledgeGraph ? null : currentOrBuildGraphs(projectDir);
|
|
@@ -11150,6 +11311,10 @@ elif event_name == "PreCompact":
|
|
|
11150
11311
|
payload = {"type": "session_end", "summary": "Claude Code is compacting context; distill durable observations before compaction."}
|
|
11151
11312
|
elif event_name == "SessionEnd":
|
|
11152
11313
|
payload = {"type": "session_end", "summary": "Claude Code session ended; distill durable observations for teammate handoff."}
|
|
11314
|
+
elif event_name == "SubagentStop":
|
|
11315
|
+
payload = {"type": "session_end", "summary": "Subagent finished; distill durable observations from the subagent run."}
|
|
11316
|
+
elif event_name == "PreToolUse":
|
|
11317
|
+
payload = {"type": "tool_use", "tool": tool, "path": path, "command": command, "summary": "About to run: " + compact(command or tool or d, 200)}
|
|
11153
11318
|
else:
|
|
11154
11319
|
payload = {"type": "tool_use", "tool": tool, "path": path, "command": command, "summary": compact(d, 320), "text": compact(d)}
|
|
11155
11320
|
|
|
@@ -11161,7 +11326,7 @@ if [[ -n "$OBSERVATION" ]]; then
|
|
|
11161
11326
|
kage observe --project "$CWD" --event "$OBSERVATION" --json >/dev/null 2>&1 || true
|
|
11162
11327
|
fi
|
|
11163
11328
|
|
|
11164
|
-
if [[ "$EVENT" == "PreCompact" || "$EVENT" == "SessionEnd" ]]; then
|
|
11329
|
+
if [[ "$EVENT" == "PreCompact" || "$EVENT" == "SessionEnd" || "$EVENT" == "SubagentStop" ]]; then
|
|
11165
11330
|
kage distill --project "$CWD" --session "$SESSION" --json >/dev/null 2>&1 || true
|
|
11166
11331
|
fi
|
|
11167
11332
|
|
|
@@ -11197,11 +11362,13 @@ exit 0
|
|
|
11197
11362
|
hooks: {
|
|
11198
11363
|
SessionStart: [{ matcher: "", hooks: [{ type: "command", command: "bash ~/.claude/kage/hooks/session-start.sh", timeout: 5 }] }],
|
|
11199
11364
|
UserPromptSubmit: [{ hooks: [{ type: "command", command: "bash ~/.claude/kage/hooks/observe.sh", timeout: 12 }] }],
|
|
11365
|
+
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: "bash ~/.claude/kage/hooks/observe.sh", timeout: 5 }] }],
|
|
11200
11366
|
PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: "bash ~/.claude/kage/hooks/observe.sh", timeout: 5 }] }],
|
|
11201
11367
|
PostToolUseFailure: [{ matcher: "", hooks: [{ type: "command", command: "bash ~/.claude/kage/hooks/observe.sh", timeout: 5 }] }],
|
|
11202
11368
|
PreCompact: [{ hooks: [{ type: "command", command: "bash ~/.claude/kage/hooks/observe.sh", timeout: 20 }] }],
|
|
11203
11369
|
Stop: [{ matcher: "", hooks: [{ type: "command", command: "bash ~/.claude/kage/hooks/stop.sh", timeout: 20 }] }],
|
|
11204
11370
|
SessionEnd: [{ hooks: [{ type: "command", command: "bash ~/.claude/kage/hooks/observe.sh", timeout: 20 }] }],
|
|
11371
|
+
SubagentStop: [{ matcher: "", hooks: [{ type: "command", command: "bash ~/.claude/kage/hooks/observe.sh", timeout: 20 }] }],
|
|
11205
11372
|
},
|
|
11206
11373
|
};
|
|
11207
11374
|
setSnippet(path, JSON.stringify({ mcpServers: { kage: server } }, null, 2), [
|
package/package.json
CHANGED
package/viewer/app.js
CHANGED
|
@@ -47,7 +47,9 @@
|
|
|
47
47
|
lifecycle: null,
|
|
48
48
|
timeline: null,
|
|
49
49
|
lineage: null,
|
|
50
|
-
setup: null
|
|
50
|
+
setup: null,
|
|
51
|
+
trust: null,
|
|
52
|
+
suppressed: null
|
|
51
53
|
},
|
|
52
54
|
pendingPackets: [],
|
|
53
55
|
reviewText: "",
|
|
@@ -183,6 +185,8 @@
|
|
|
183
185
|
edgeCount: document.getElementById("edgeCount"),
|
|
184
186
|
reviewCount: document.getElementById("reviewCount"),
|
|
185
187
|
dashboardStats: document.getElementById("dashboardStats"),
|
|
188
|
+
trustHero: document.getElementById("trustHero"),
|
|
189
|
+
suppressionShelf: document.getElementById("suppressionShelf"),
|
|
186
190
|
repoXray: document.getElementById("repoXray"),
|
|
187
191
|
repoXrayStatus: document.getElementById("repoXrayStatus"),
|
|
188
192
|
repoXrayScript: document.getElementById("repoXrayScript"),
|
|
@@ -233,8 +237,8 @@
|
|
|
233
237
|
var PAGE_META = {
|
|
234
238
|
overview: {
|
|
235
239
|
eyebrow: "kage://overview",
|
|
236
|
-
title: "
|
|
237
|
-
summary: "
|
|
240
|
+
title: "Repository overview",
|
|
241
|
+
summary: "Whether this repo's agent memory can be trusted, what needs review, and what's ready to hand off."
|
|
238
242
|
},
|
|
239
243
|
graph: {
|
|
240
244
|
eyebrow: "kage://graph",
|
|
@@ -622,6 +626,8 @@
|
|
|
622
626
|
var timelinePath = params.get("timeline") || params.get("memoryTimeline") || params.get("memory-timeline");
|
|
623
627
|
var lineagePath = params.get("lineage") || params.get("memoryLineage") || params.get("memory-lineage");
|
|
624
628
|
var setupPath = params.get("setup") || params.get("setupDoctor") || params.get("setup-doctor");
|
|
629
|
+
var trustPath = params.get("trust") || params.get("trustBenchmark") || params.get("trust-benchmark");
|
|
630
|
+
var suppressedPath = params.get("suppressed") || params.get("suppressedMemory") || params.get("suppressed-memory");
|
|
625
631
|
var inferredRoot = inferMemoryRoot(graphPaths[0] || "");
|
|
626
632
|
if (!inboxPath && inferredRoot) inboxPath = inferredRoot + "/inbox.json";
|
|
627
633
|
if (!reviewPath && inferredRoot) reviewPath = inferredRoot + "/review/memory-review.md";
|
|
@@ -648,6 +654,8 @@
|
|
|
648
654
|
if (!timelinePath) timelinePath = inferredRoot + "/reports/timeline.json";
|
|
649
655
|
if (!lineagePath) lineagePath = inferredRoot + "/reports/lineage.json";
|
|
650
656
|
if (!setupPath) setupPath = inferredRoot + "/reports/setup.json";
|
|
657
|
+
if (!trustPath) trustPath = inferredRoot + "/reports/trust.json";
|
|
658
|
+
if (!suppressedPath) suppressedPath = inferredRoot + "/reports/suppressed.json";
|
|
651
659
|
}
|
|
652
660
|
var jobs = [];
|
|
653
661
|
if (metricsPath) jobs.push(fetchJson(metricsPath).then(function (metrics) { state.metrics = metrics; }));
|
|
@@ -675,6 +683,8 @@
|
|
|
675
683
|
if (timelinePath) jobs.push(fetchJson(timelinePath).then(function (report) { state.reports.timeline = report; }).catch(function () { state.reports.timeline = null; }));
|
|
676
684
|
if (lineagePath) jobs.push(fetchJson(lineagePath).then(function (report) { state.reports.lineage = report; }).catch(function () { state.reports.lineage = null; }));
|
|
677
685
|
if (setupPath) jobs.push(fetchJson(setupPath).then(function (report) { state.reports.setup = report; }).catch(function () { state.reports.setup = null; }));
|
|
686
|
+
if (trustPath) jobs.push(fetchJson(trustPath).then(function (report) { state.reports.trust = report; }).catch(function () { state.reports.trust = null; }));
|
|
687
|
+
if (suppressedPath) jobs.push(fetchJson(suppressedPath).then(function (report) { state.reports.suppressed = report; }).catch(function () { state.reports.suppressed = null; }));
|
|
678
688
|
if (!graphPaths.length && !jobs.length) {
|
|
679
689
|
loadHostedDefault();
|
|
680
690
|
return;
|
|
@@ -723,7 +733,9 @@
|
|
|
723
733
|
fetchJson("./data/kage/reports/lifecycle.json").catch(function () { return null; }),
|
|
724
734
|
fetchJson("./data/kage/reports/timeline.json").catch(function () { return null; }),
|
|
725
735
|
fetchJson("./data/kage/reports/lineage.json").catch(function () { return null; }),
|
|
726
|
-
fetchJson("./data/kage/reports/setup.json").catch(function () { return null; })
|
|
736
|
+
fetchJson("./data/kage/reports/setup.json").catch(function () { return null; }),
|
|
737
|
+
fetchJson("./data/kage/reports/trust.json").catch(function () { return null; }),
|
|
738
|
+
fetchJson("./data/kage/reports/suppressed.json").catch(function () { return null; })
|
|
727
739
|
]).then(function (items) {
|
|
728
740
|
var merged = mergeNormalizedGraphs([normalizeGraph(items[0]), normalizeGraph(items[1])]);
|
|
729
741
|
state.metrics = items[2];
|
|
@@ -747,6 +759,8 @@
|
|
|
747
759
|
state.reports.timeline = items[20];
|
|
748
760
|
state.reports.lineage = items[21];
|
|
749
761
|
state.reports.setup = items[22];
|
|
762
|
+
state.reports.trust = items[23];
|
|
763
|
+
state.reports.suppressed = items[24];
|
|
750
764
|
loadNormalizedGraph(merged, "Kage repo graph");
|
|
751
765
|
setAutoLoad("Kage repo graph loaded", true);
|
|
752
766
|
}).catch(function () {
|
|
@@ -2983,6 +2997,75 @@
|
|
|
2983
2997
|
].forEach(function (card) { els.debugOverview.appendChild(card); });
|
|
2984
2998
|
}
|
|
2985
2999
|
|
|
3000
|
+
function renderTrustHero(trust) {
|
|
3001
|
+
if (!els.trustHero) return;
|
|
3002
|
+
var metrics = (trust && trust.metrics) || {};
|
|
3003
|
+
var score = trust && typeof trust.trust_score === "number" ? trust.trust_score : null;
|
|
3004
|
+
var status = score == null ? "idle" : (score >= 90 ? "ok" : score >= 70 ? "warn" : "alert");
|
|
3005
|
+
var bars = [
|
|
3006
|
+
["Hallucinated citations rejected", metrics.hallucinated_citation_rejection_rate],
|
|
3007
|
+
["Stale memory excluded from recall", metrics.stale_memory_exclusion_rate],
|
|
3008
|
+
["Live memory grounded to code", metrics.live_grounding_rate]
|
|
3009
|
+
];
|
|
3010
|
+
var note = score == null
|
|
3011
|
+
? "Run <code>kage benchmark --trust</code> to score this repo."
|
|
3012
|
+
: (status === "ok"
|
|
3013
|
+
? "Verified — agents recall only memory that is grounded and current."
|
|
3014
|
+
: "Some memory needs review before agents should trust it.");
|
|
3015
|
+
var barsHtml = bars.map(function (bar) {
|
|
3016
|
+
var raw = bar[1];
|
|
3017
|
+
var has = !(raw === null || raw === undefined || isNaN(raw));
|
|
3018
|
+
var pct = has ? Math.max(0, Math.min(100, Number(raw))) : 0;
|
|
3019
|
+
return '<div class="trust-bar">'
|
|
3020
|
+
+ '<span class="trust-bar-label"></span>'
|
|
3021
|
+
+ '<span class="trust-bar-track"><i style="width:' + pct + '%"></i></span>'
|
|
3022
|
+
+ '<b class="trust-bar-value">' + (has ? pct + "%" : "—") + '</b>'
|
|
3023
|
+
+ '</div>';
|
|
3024
|
+
}).join("");
|
|
3025
|
+
els.trustHero.setAttribute("data-status", status);
|
|
3026
|
+
els.trustHero.innerHTML =
|
|
3027
|
+
'<div class="trust-hero-score">'
|
|
3028
|
+
+ '<span class="trust-hero-eyebrow">Memory Trust</span>'
|
|
3029
|
+
+ '<div class="trust-hero-number"><strong>' + (score == null ? "—" : score) + '</strong><span>/100</span></div>'
|
|
3030
|
+
+ '<p class="trust-hero-note">' + note + '</p>'
|
|
3031
|
+
+ '</div>'
|
|
3032
|
+
+ '<div class="trust-hero-bars">' + barsHtml + '</div>';
|
|
3033
|
+
// Set labels via textContent to avoid any HTML injection from labels.
|
|
3034
|
+
var labelEls = els.trustHero.querySelectorAll(".trust-bar-label");
|
|
3035
|
+
for (var i = 0; i < labelEls.length; i += 1) labelEls[i].textContent = bars[i][0];
|
|
3036
|
+
}
|
|
3037
|
+
|
|
3038
|
+
function renderSuppressionShelf(report) {
|
|
3039
|
+
if (!els.suppressionShelf) return;
|
|
3040
|
+
var items = (report && report.items) || [];
|
|
3041
|
+
if (!items.length) {
|
|
3042
|
+
els.suppressionShelf.innerHTML = "";
|
|
3043
|
+
els.suppressionShelf.hidden = true;
|
|
3044
|
+
return;
|
|
3045
|
+
}
|
|
3046
|
+
els.suppressionShelf.hidden = false;
|
|
3047
|
+
var plural = items.length === 1 ? "memory is" : "memories are";
|
|
3048
|
+
els.suppressionShelf.innerHTML =
|
|
3049
|
+
'<div class="suppression-head">'
|
|
3050
|
+
+ '<div><span class="suppression-eyebrow">Withheld from recall</span>'
|
|
3051
|
+
+ '<h2>' + items.length + ' ' + plural + ' being withheld from your agents</h2></div>'
|
|
3052
|
+
+ '<strong class="suppression-flag">Needs review</strong>'
|
|
3053
|
+
+ '</div>'
|
|
3054
|
+
+ '<p class="suppression-sub">Recall is hiding these because their cited evidence was deleted or expired. Verify, update, or supersede each before agents should trust them.</p>'
|
|
3055
|
+
+ '<div class="suppression-list">'
|
|
3056
|
+
+ items.slice(0, 8).map(function () {
|
|
3057
|
+
return '<div class="suppression-item"><span class="suppression-item-title"></span><span class="suppression-item-reason"></span></div>';
|
|
3058
|
+
}).join("")
|
|
3059
|
+
+ '</div>'
|
|
3060
|
+
+ (items.length > 8 ? '<p class="suppression-more">+ ' + (items.length - 8) + ' more — run <code>kage suppressed</code></p>' : '');
|
|
3061
|
+
var titleEls = els.suppressionShelf.querySelectorAll(".suppression-item-title");
|
|
3062
|
+
var reasonEls = els.suppressionShelf.querySelectorAll(".suppression-item-reason");
|
|
3063
|
+
for (var i = 0; i < titleEls.length; i += 1) {
|
|
3064
|
+
titleEls[i].textContent = items[i].title;
|
|
3065
|
+
reasonEls[i].textContent = items[i].reason;
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
|
|
2986
3069
|
function renderDashboard() {
|
|
2987
3070
|
if (!els.dashboardStats) return;
|
|
2988
3071
|
var metrics = state.metrics || {};
|
|
@@ -3009,6 +3092,8 @@
|
|
|
3009
3092
|
var readiness = handoff || dashboardReadiness(metrics, pendingReview, staleFlags, duplicateFlags, missingContext);
|
|
3010
3093
|
var memoryCoverage = dashboardMemoryCoverage(reports, memoryCodeEdges, memoryGraph, memoryNodes);
|
|
3011
3094
|
var riskHealth = riskTargets.length || hotspots ? (riskTargets.length + hotspots) + " checks" : "No flags";
|
|
3095
|
+
renderTrustHero(reports.trust);
|
|
3096
|
+
renderSuppressionShelf(reports.suppressed);
|
|
3012
3097
|
var statRows = [
|
|
3013
3098
|
["Handoff", readiness.label, readiness.detail, readiness.status],
|
|
3014
3099
|
["Memory", memoryCoverage.label, memoryCoverage.detail, memoryCoverage.status],
|
|
@@ -3547,6 +3632,10 @@
|
|
|
3547
3632
|
return state.entities.filter(function (entity) { return entity.type === type; }).length;
|
|
3548
3633
|
}
|
|
3549
3634
|
|
|
3635
|
+
function numOrDash(value) {
|
|
3636
|
+
return (value === null || value === undefined || isNaN(value)) ? "—" : value;
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3550
3639
|
function formatDashboardValue(value) {
|
|
3551
3640
|
if (typeof value === "number" && Number.isFinite(value)) return value.toLocaleString();
|
|
3552
3641
|
return String(value == null ? "n/a" : value);
|
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 viewer</title>
|
|
7
|
-
<link rel="stylesheet" href="./styles.css?v=
|
|
7
|
+
<link rel="stylesheet" href="./styles.css?v=39">
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<div class="viewer-shell">
|
|
@@ -54,16 +54,18 @@
|
|
|
54
54
|
|
|
55
55
|
<main class="layout">
|
|
56
56
|
<section class="dashboard-panel" aria-label="Kage repo dashboard">
|
|
57
|
+
<div id="trustHero" class="trust-hero" aria-label="Memory trust score"></div>
|
|
58
|
+
<div id="suppressionShelf" class="suppression-shelf" aria-label="Memory withheld from recall" hidden></div>
|
|
57
59
|
<div id="dashboardStats" class="dashboard-stats" aria-label="Repo dashboard stats"></div>
|
|
58
|
-
<section id="repoXray" class="repo-xray" aria-label="
|
|
60
|
+
<section id="repoXray" class="repo-xray" aria-label="Repository map">
|
|
59
61
|
<div class="repo-xray-head">
|
|
60
62
|
<div>
|
|
61
|
-
<span>
|
|
62
|
-
<h2>
|
|
63
|
+
<span>Repository map</span>
|
|
64
|
+
<h2>What Kage understands about this repo</h2>
|
|
63
65
|
</div>
|
|
64
66
|
<strong id="repoXrayStatus">waiting</strong>
|
|
65
67
|
</div>
|
|
66
|
-
<p id="repoXrayScript">
|
|
68
|
+
<p id="repoXrayScript">Entry points, core modules, change risk, tests, and memory overlays — mapped from the code graph.</p>
|
|
67
69
|
<div id="repoXrayLayers" class="repo-xray-layers"></div>
|
|
68
70
|
</section>
|
|
69
71
|
<div id="dashboardCharts" class="dashboard-charts" aria-label="Repo health charts"></div>
|
|
@@ -302,6 +304,6 @@
|
|
|
302
304
|
</section>
|
|
303
305
|
</div>
|
|
304
306
|
|
|
305
|
-
<script src="./app.js?v=
|
|
307
|
+
<script src="./app.js?v=53"></script>
|
|
306
308
|
</body>
|
|
307
309
|
</html>
|
package/viewer/styles.css
CHANGED
|
@@ -424,6 +424,103 @@ body.viewer-page-data .graph-panel {
|
|
|
424
424
|
white-space: nowrap;
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
.trust-hero {
|
|
428
|
+
display: grid;
|
|
429
|
+
grid-template-columns: minmax(170px, 250px) minmax(0, 1fr);
|
|
430
|
+
gap: 30px;
|
|
431
|
+
align-items: center;
|
|
432
|
+
margin-bottom: 18px;
|
|
433
|
+
padding: 24px 28px;
|
|
434
|
+
border: 1px solid rgba(65, 255, 143, 0.24);
|
|
435
|
+
border-radius: 14px;
|
|
436
|
+
background:
|
|
437
|
+
radial-gradient(circle at 0 0, rgba(65, 255, 143, 0.12), transparent 380px),
|
|
438
|
+
linear-gradient(135deg, rgba(8, 17, 13, 0.96), rgba(10, 16, 22, 0.82));
|
|
439
|
+
box-shadow: var(--shadow);
|
|
440
|
+
}
|
|
441
|
+
.trust-hero[data-status="warn"] { border-color: rgba(255, 209, 102, 0.34); }
|
|
442
|
+
.trust-hero[data-status="alert"] { border-color: rgba(255, 107, 107, 0.38); }
|
|
443
|
+
.trust-hero[data-status="idle"] { border-color: var(--line); }
|
|
444
|
+
.trust-hero-eyebrow {
|
|
445
|
+
display: block;
|
|
446
|
+
color: var(--terminal-dim);
|
|
447
|
+
font-size: 11px;
|
|
448
|
+
font-weight: 800;
|
|
449
|
+
letter-spacing: 0.14em;
|
|
450
|
+
text-transform: uppercase;
|
|
451
|
+
}
|
|
452
|
+
.trust-hero-number { display: flex; align-items: baseline; gap: 5px; margin-top: 8px; }
|
|
453
|
+
.trust-hero-number strong {
|
|
454
|
+
font-size: 60px;
|
|
455
|
+
line-height: 1;
|
|
456
|
+
color: var(--terminal-strong);
|
|
457
|
+
text-shadow: 0 0 24px rgba(65, 255, 143, 0.38);
|
|
458
|
+
}
|
|
459
|
+
.trust-hero[data-status="warn"] .trust-hero-number strong { color: var(--warn); text-shadow: 0 0 24px rgba(255, 209, 102, 0.32); }
|
|
460
|
+
.trust-hero[data-status="alert"] .trust-hero-number strong { color: var(--danger); text-shadow: 0 0 24px rgba(255, 107, 107, 0.32); }
|
|
461
|
+
.trust-hero[data-status="idle"] .trust-hero-number strong { color: var(--muted); text-shadow: none; }
|
|
462
|
+
.trust-hero-number span { font-size: 19px; color: var(--muted); }
|
|
463
|
+
.trust-hero-note { margin: 12px 0 0; color: var(--muted); font-size: 12.5px; line-height: 1.45; max-width: 250px; }
|
|
464
|
+
.trust-hero-note code { color: var(--accent); background: var(--accent-soft); padding: 1px 6px; border-radius: 5px; font-size: 11.5px; }
|
|
465
|
+
.trust-hero-bars { display: grid; gap: 15px; }
|
|
466
|
+
.trust-bar {
|
|
467
|
+
display: grid;
|
|
468
|
+
grid-template-columns: minmax(0, 1fr) 52px;
|
|
469
|
+
grid-template-areas: "label value" "track value";
|
|
470
|
+
align-items: center;
|
|
471
|
+
gap: 4px 14px;
|
|
472
|
+
}
|
|
473
|
+
.trust-bar-label { grid-area: label; color: var(--text); font-size: 13px; }
|
|
474
|
+
.trust-bar-track { grid-area: track; height: 8px; border-radius: 999px; background: rgba(215, 249, 223, 0.07); overflow: hidden; }
|
|
475
|
+
.trust-bar-track i { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--brand), var(--accent)); box-shadow: 0 0 12px rgba(65, 255, 143, 0.3); transition: width 0.5s ease; }
|
|
476
|
+
.trust-bar-value { grid-area: value; text-align: right; color: var(--terminal-strong); font-size: 15px; font-weight: 800; }
|
|
477
|
+
@media (max-width: 860px) {
|
|
478
|
+
.trust-hero { grid-template-columns: 1fr; gap: 20px; }
|
|
479
|
+
.trust-hero-note { max-width: none; }
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.suppression-shelf {
|
|
483
|
+
margin-bottom: 18px;
|
|
484
|
+
padding: 20px 24px;
|
|
485
|
+
border: 1px solid rgba(255, 209, 102, 0.34);
|
|
486
|
+
border-radius: 14px;
|
|
487
|
+
background:
|
|
488
|
+
radial-gradient(circle at 0 0, rgba(255, 209, 102, 0.08), transparent 360px),
|
|
489
|
+
linear-gradient(135deg, rgba(24, 18, 8, 0.92), rgba(16, 14, 10, 0.82));
|
|
490
|
+
}
|
|
491
|
+
.suppression-shelf[hidden] { display: none; }
|
|
492
|
+
.suppression-head { display: flex; align-items: start; justify-content: space-between; gap: 16px; }
|
|
493
|
+
.suppression-eyebrow { display: block; color: var(--warn); font-size: 11px; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase; }
|
|
494
|
+
.suppression-head h2 { margin-top: 5px; color: var(--text); text-shadow: none; font-size: 17px; }
|
|
495
|
+
.suppression-flag {
|
|
496
|
+
flex: none;
|
|
497
|
+
color: var(--warn);
|
|
498
|
+
font-size: 11px;
|
|
499
|
+
font-weight: 800;
|
|
500
|
+
text-transform: uppercase;
|
|
501
|
+
letter-spacing: 0.08em;
|
|
502
|
+
padding: 5px 10px;
|
|
503
|
+
border: 1px solid rgba(255, 209, 102, 0.4);
|
|
504
|
+
border-radius: 999px;
|
|
505
|
+
background: var(--warn-soft);
|
|
506
|
+
}
|
|
507
|
+
.suppression-sub { margin: 8px 0 14px; color: var(--muted); font-size: 12.5px; line-height: 1.45; max-width: 760px; }
|
|
508
|
+
.suppression-list { display: grid; gap: 8px; }
|
|
509
|
+
.suppression-item {
|
|
510
|
+
display: grid;
|
|
511
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
512
|
+
align-items: center;
|
|
513
|
+
gap: 16px;
|
|
514
|
+
padding: 11px 14px;
|
|
515
|
+
border: 1px solid rgba(255, 209, 102, 0.16);
|
|
516
|
+
border-radius: 9px;
|
|
517
|
+
background: rgba(8, 17, 13, 0.5);
|
|
518
|
+
}
|
|
519
|
+
.suppression-item-title { color: var(--text); font-size: 13.5px; font-weight: 650; overflow-wrap: anywhere; }
|
|
520
|
+
.suppression-item-reason { color: var(--warn); font-size: 12px; text-align: right; opacity: 0.92; }
|
|
521
|
+
.suppression-more { margin: 12px 0 0; color: var(--muted); font-size: 12px; }
|
|
522
|
+
.suppression-more code { color: var(--accent); background: var(--accent-soft); padding: 1px 5px; border-radius: 5px; }
|
|
523
|
+
|
|
427
524
|
.dashboard-stats {
|
|
428
525
|
display: grid;
|
|
429
526
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
@@ -431,8 +528,8 @@ body.viewer-page-data .graph-panel {
|
|
|
431
528
|
margin-bottom: 22px;
|
|
432
529
|
}
|
|
433
530
|
.dashboard-stat {
|
|
434
|
-
min-height:
|
|
435
|
-
padding: 22px;
|
|
531
|
+
min-height: 120px;
|
|
532
|
+
padding: 20px 22px;
|
|
436
533
|
border: 1px solid rgba(65, 255, 143, 0.12);
|
|
437
534
|
border-radius: 10px;
|
|
438
535
|
background: rgba(8, 17, 13, 0.82);
|