@kage-core/kage-graph-mcp 2.2.1 → 2.2.2

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 CHANGED
@@ -72,6 +72,7 @@ Usage:
72
72
  kage slots delete --project <dir> --label <label> [--json]
73
73
  kage handoff --project <dir> [--json]
74
74
  kage lifecycle --project <dir> [--json]
75
+ kage reverify --project <dir> --packet <id> [--json]
75
76
  kage reconcile --project <dir> [--session <id>] [--json]
76
77
  kage timeline --project <dir> [--days <n>] [--json]
77
78
  kage lineage --project <dir> [--json]
@@ -338,24 +339,46 @@ async function main() {
338
339
  console.log(JSON.stringify(result, null, 2));
339
340
  return;
340
341
  }
341
- console.log("Kage demo — can you trust your agent's memory?\n");
342
- console.log(`Seeded ${result.captured.length} grounded memories in a throwaway project (${result.project_dir})\n`);
343
- console.log("1. Hallucinated citation REJECTED on write:");
342
+ console.log("Kage demo — a live experiment in a sandbox repo. Nothing here is canned:");
343
+ console.log(`every step below ran for real, just now, in ${result.project_dir}\n`);
344
+ console.log(" Created a small repo: src/auth.ts, src/payments.ts, src/legacy-retry.ts");
345
+ console.log(` Captured ${result.captured.length} memories, each citing real files — accepted and fingerprinted.\n`);
346
+ console.log("1. Then we tried to save a memory citing a file that does NOT exist:");
344
347
  if (result.rejected_hallucination) {
345
- console.log(` ✗ "${result.rejected_hallucination.title}"`);
348
+ console.log(` ✗ "${result.rejected_hallucination.title}" — REFUSED at write time:`);
346
349
  console.log(` ${result.rejected_hallucination.error}\n`);
347
350
  }
348
- console.log("2. Stale memory (cited file deleted) WITHHELD from recall:");
351
+ console.log("2. Then we DELETED src/legacy-retry.ts and asked for recall again:");
349
352
  for (const w of result.withheld)
350
- console.log(` ⊘ ${w.title}\n ${w.reason}`);
351
- console.log("\n3. Recall returns only grounded, current memory:");
353
+ console.log(` ⊘ "${w.title}" — WITHHELD\n ${w.reason}`);
354
+ console.log("\n3. What recall actually returns now — only memory that still checks out:");
352
355
  for (const t of result.recalled)
353
356
  console.log(` ✓ ${t}`);
354
- console.log(`\nTrust score: ${result.trust_score}/100`);
355
- console.log("\nNext, in your own repo:");
356
- console.log(" kage init --project . create repo memory");
357
- console.log(" kage setup <agent> --project . --write wire your agent (claude-code, codex, cursor, ...)");
358
- console.log(" kage viewer --project . see the dashboard");
357
+ console.log("\n Checks: write-time rejection ✓ · stale withholding ✓ · grounded recall ✓");
358
+ // The sandbox proves the mechanism; the runner's own repo makes it matter.
359
+ const here = process.cwd();
360
+ if ((0, node_fs_1.existsSync)((0, node_path_1.join)(here, ".git")) && !takeArg(args, "--project")) {
361
+ console.log("\nThat was a sandbox. This is YOUR repo — scanning (read-only, ~a minute)...\n");
362
+ try {
363
+ const report = (0, kernel_js_1.truthReport)(here);
364
+ console.log(` ${report.headline}`);
365
+ for (const finding of report.findings.slice(0, 3)) {
366
+ console.log(` • ${finding.title}`);
367
+ }
368
+ if (report.findings.length > 3)
369
+ console.log(` …and ${report.findings.length - 3} more.`);
370
+ console.log("\n Full report: npx -y @kage-core/kage-graph-mcp scan --project .");
371
+ }
372
+ catch {
373
+ console.log(" (scan skipped — run it yourself: npx -y @kage-core/kage-graph-mcp scan --project .)");
374
+ }
375
+ }
376
+ else {
377
+ console.log("\nNow point it at a real repo:");
378
+ console.log(" npx -y @kage-core/kage-graph-mcp scan --project . the Truth Report — what your repo is hiding");
379
+ }
380
+ console.log("\nWire it in (one command, auto-detects your agents):");
381
+ console.log(" npx -y @kage-core/kage-graph-mcp install");
359
382
  return;
360
383
  }
361
384
  if (command === "init") {
@@ -1342,6 +1365,27 @@ async function main() {
1342
1365
  }
1343
1366
  return;
1344
1367
  }
1368
+ if (command === "reverify") {
1369
+ const packetId = takeArg(args, "--packet");
1370
+ if (!packetId)
1371
+ usage();
1372
+ const result = (0, kernel_js_1.reverifyMemory)(projectArg(args), packetId);
1373
+ if (args.includes("--json")) {
1374
+ console.log(JSON.stringify(result, null, 2));
1375
+ }
1376
+ else if (result.ok) {
1377
+ console.log(`Reverified ${result.packet_id}`);
1378
+ console.log(` grounding refreshed for ${result.refreshed_paths.length} path(s)${result.was_stale ? " · stale flag cleared" : ""}`);
1379
+ if (result.missing_paths.length)
1380
+ console.log(` dropped missing path(s): ${result.missing_paths.join(", ")}`);
1381
+ }
1382
+ else {
1383
+ console.log(`Reverify failed: ${result.errors.join("; ")}`);
1384
+ }
1385
+ if (!result.ok)
1386
+ process.exit(2);
1387
+ return;
1388
+ }
1345
1389
  if (command === "reconcile" || command === "memory-reconcile" || command === "memory-reconciliation") {
1346
1390
  const result = (0, kernel_js_1.kageMemoryReconciliation)(projectArg(args), {
1347
1391
  sessionId: takeArg(args, "--session"),
package/dist/kernel.js CHANGED
@@ -175,6 +175,7 @@ exports.remediationFor = remediationFor;
175
175
  exports.approvePending = approvePending;
176
176
  exports.rejectPending = rejectPending;
177
177
  exports.changelog = changelog;
178
+ exports.reverifyMemory = reverifyMemory;
178
179
  exports.supersedeMemory = supersedeMemory;
179
180
  exports.kageMemoryLineage = kageMemoryLineage;
180
181
  exports.kageMemoryTimeline = kageMemoryTimeline;
@@ -1554,7 +1555,7 @@ function reconciliationInstruction(items) {
1554
1555
  return [
1555
1556
  "Memory reconciliation required before final response.",
1556
1557
  "You changed code that is linked to existing repo memory. Update the memory yourself; do not push this to the user as a manual inbox chore.",
1557
- "For each item, call kage_learn to save the new behavior and kage_supersede when replacing the old packet, or mark stale only if the memory is no longer trusted.",
1558
+ "If the memory's claim is unchanged and only the cited code moved, run kage reverify --packet <id> to refresh its grounding in place. Otherwise call kage_learn to save the new behavior and kage_supersede when replacing the old packet, or mark stale only if the memory is no longer trusted.",
1558
1559
  ...lines,
1559
1560
  ].join("\n");
1560
1561
  }
@@ -8801,7 +8802,10 @@ function truthReport(projectDir) {
8801
8802
  // Path claims only count in prose; fenced content is sample output.
8802
8803
  for (const candidate of entry.fence === null ? truthDocPathCandidates(entry.text) : []) {
8803
8804
  const key = `path:${candidate}`;
8804
- if (seenLies.has(key) || (0, node_fs_1.existsSync)((0, node_path_1.join)(projectDir, candidate)))
8805
+ // A cited path is a lie only if it resolves nowhere: docs use both
8806
+ // repo-root-relative paths and links relative to the doc's own dir.
8807
+ const resolves = (0, node_fs_1.existsSync)((0, node_path_1.join)(projectDir, candidate)) || (0, node_fs_1.existsSync)((0, node_path_1.join)(projectDir, (0, node_path_1.dirname)(entry.doc), candidate));
8808
+ if (seenLies.has(key) || resolves)
8805
8809
  continue;
8806
8810
  seenLies.add(key);
8807
8811
  docLieFindings.push({
@@ -15675,6 +15679,57 @@ function packetSupersessionReason(packet) {
15675
15679
  const evidence = edge ? packetEdgeValue(edge, "evidence") : "";
15676
15680
  return evidence || "This memory was superseded by newer repo knowledge.";
15677
15681
  }
15682
+ // Re-verify a still-true packet in place: re-check cited paths, refresh
15683
+ // fingerprints and last_verified_at, and clear stale flags. The alternative to
15684
+ // supersede churn when code changed but the memory's claim did not. Refuses
15685
+ // when ALL cited evidence is gone — that memory needs supersede or stale, not
15686
+ // a rubber stamp.
15687
+ function reverifyMemory(projectDir, packetId) {
15688
+ ensureMemoryDirs(projectDir);
15689
+ const result = {
15690
+ ok: false,
15691
+ project_dir: projectDir,
15692
+ packet_id: packetId,
15693
+ refreshed_paths: [],
15694
+ missing_paths: [],
15695
+ was_stale: false,
15696
+ errors: [],
15697
+ };
15698
+ const entries = loadPacketEntriesFromDir(packetsDir(projectDir));
15699
+ const entry = entries.find((item) => item.packet.id === packetId);
15700
+ if (!entry) {
15701
+ result.errors.push(`Packet not found: ${packetId}`);
15702
+ return result;
15703
+ }
15704
+ const packet = entry.packet;
15705
+ const quality = (packet.quality ?? {});
15706
+ result.was_stale = quality.stale === true;
15707
+ const citedPaths = unique([
15708
+ ...packet.paths,
15709
+ ...packetStoredPathFingerprints(packet).map((fingerprint) => fingerprint.path),
15710
+ ]).filter(fingerprintableMemoryPath);
15711
+ result.missing_paths = citedPaths.filter((path) => !(0, node_fs_1.existsSync)((0, node_path_1.join)(projectDir, path)));
15712
+ if (citedPaths.length && result.missing_paths.length === citedPaths.length) {
15713
+ result.errors.push("All cited paths are gone — reverify would rubber-stamp dead evidence. Use kage supersede with a replacement, or mark the packet stale.");
15714
+ return result;
15715
+ }
15716
+ const presentPaths = citedPaths.filter((path) => !result.missing_paths.includes(path));
15717
+ const now = nowIso();
15718
+ const freshness = { ...(packet.freshness ?? {}) };
15719
+ freshness.path_fingerprints = memoryPathFingerprints(projectDir, presentPaths);
15720
+ freshness.last_verified_at = now;
15721
+ const { stale: _stale, stale_reasons: _staleReasons, suggested_action: _suggestedAction, ...nextQuality } = quality;
15722
+ writeJson(entry.path, {
15723
+ ...packet,
15724
+ paths: presentPaths.length ? presentPaths : packet.paths,
15725
+ freshness,
15726
+ quality: { ...nextQuality, reverified_at: now },
15727
+ updated_at: now,
15728
+ });
15729
+ result.refreshed_paths = presentPaths;
15730
+ result.ok = true;
15731
+ return result;
15732
+ }
15678
15733
  function supersedeMemory(projectDir, oldPacketId, replacementPacketId, reason = "") {
15679
15734
  ensureMemoryDirs(projectDir);
15680
15735
  const trimmedReason = reason.trim() || "Newer repo memory supersedes this packet.";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kage-core/kage-graph-mcp",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
4
4
  "description": "Local-first repo memory, code graph, and recall MCP server for coding agents",
5
5
  "main": "dist/index.js",
6
6
  "files": [
package/viewer/index.html CHANGED
@@ -6,35 +6,24 @@
6
6
  <title>Kage — memory dashboard</title>
7
7
  <style>
8
8
  /* V2 "receipts" theme. Tokens mirror docs/assets/site.css so the dashboard
9
- matches the site: light-first warm paper, editorial serif display type,
10
- deep verified-green for gains, dark-capable via prefers-color-scheme. */
9
+ matches the site: dark-first product look, editorial serif display type,
10
+ verified-green for gains. One look everywhere. */
11
11
  :root {
12
- color-scheme: light dark;
13
- --bg: #f6f4ef; --paper: #fffdf9; --paper-2: #efece4;
14
- --line: #e2ded2; --line-strong: #c9c4b4;
15
- --ink: #1c1e1a; --muted: #5d635b; --faint: #8b9088;
16
- --gain: #0c7a4d; --gain-strong: #09935c; --gain-soft: rgba(12, 122, 77, 0.08);
17
- --code: #155e9c; --memory: #6d49b8; --warn: #9a6b08; --danger: #b03048;
18
- --warn-soft: rgba(154, 107, 8, 0.10);
19
- --code-bg: #15181b; --code-text: #d7e2da;
20
- --tip-bg: #fffdf9; --shadow: 0 14px 40px rgba(28, 30, 26, 0.12);
12
+ color-scheme: dark;
13
+ --bg: #121413; --paper: #1a1d1b; --paper-2: #212522;
14
+ --line: #2c302c; --line-strong: #41463f;
15
+ --ink: #edefe9; --muted: #a4aba1; --faint: #767d74;
16
+ --gain: #43c98a; --gain-strong: #5fdca0; --gain-soft: rgba(67, 201, 138, 0.12);
17
+ --code: #6fb4e8; --memory: #b095e8; --warn: #d9a93f; --danger: #e07a8c;
18
+ --warn-soft: rgba(217, 169, 63, 0.10);
19
+ --code-bg: #0e1110; --code-text: #cfe0d4;
20
+ --tip-bg: #212522; --shadow: 0 14px 40px rgba(0, 0, 0, 0.5);
21
21
  --sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
22
22
  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, "Times New Roman", serif;
23
23
  --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
24
24
  --r-control: 4px; --r-panel: 6px; --r-card: 8px;
25
25
  }
26
- @media (prefers-color-scheme: dark) {
27
- :root {
28
- --bg: #121413; --paper: #1a1d1b; --paper-2: #212522;
29
- --line: #2c302c; --line-strong: #41463f;
30
- --ink: #edefe9; --muted: #a4aba1; --faint: #767d74;
31
- --gain: #43c98a; --gain-strong: #5fdca0; --gain-soft: rgba(67, 201, 138, 0.12);
32
- --code: #6fb4e8; --memory: #b095e8; --warn: #d9a93f; --danger: #e07a8c;
33
- --warn-soft: rgba(217, 169, 63, 0.10);
34
- --code-bg: #0e1110; --code-text: #cfe0d4;
35
- --tip-bg: #212522; --shadow: 0 14px 40px rgba(0, 0, 0, 0.5);
36
- }
37
- }
26
+ /* Dark-first: one product look (light tokens retired 2026-06-12). */
38
27
  * { box-sizing: border-box; }
39
28
  html, body { margin: 0; }
40
29
  body { background: var(--bg); color: var(--ink); font: 400 14px/1.55 var(--sans); -webkit-font-smoothing: antialiased; }