@kage-core/kage-graph-mcp 2.2.0 → 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 +56 -12
- package/dist/kernel.js +78 -7
- package/package.json +1 -1
- package/viewer/index.html +12 -23
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 —
|
|
342
|
-
console.log(`
|
|
343
|
-
console.log("
|
|
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.
|
|
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.
|
|
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(
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
"
|
|
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
|
|
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.";
|
|
@@ -16152,13 +16207,13 @@ function resolvePacketSyncConflict(memoryDir, file) {
|
|
|
16152
16207
|
function rebaseOntoUpstream(memoryDir, upstream) {
|
|
16153
16208
|
const conflictBackups = [];
|
|
16154
16209
|
let resolved = 0;
|
|
16155
|
-
let step = runSyncGit(memoryDir, ["-c", "core.editor=true", "rebase", upstream]);
|
|
16210
|
+
let step = runSyncGit(memoryDir, [...syncIdentityArgs(memoryDir), "-c", "core.editor=true", "rebase", upstream]);
|
|
16156
16211
|
while (!step.ok) {
|
|
16157
16212
|
const conflicted = runSyncGit(memoryDir, ["diff", "--name-only", "--diff-filter=U"])
|
|
16158
16213
|
.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
16159
16214
|
if (!conflicted.length) {
|
|
16160
16215
|
// The resolved commit became empty (we kept the upstream side wholesale).
|
|
16161
|
-
const skip = runSyncGit(memoryDir, ["-c", "core.editor=true", "rebase", "--skip"]);
|
|
16216
|
+
const skip = runSyncGit(memoryDir, [...syncIdentityArgs(memoryDir), "-c", "core.editor=true", "rebase", "--skip"]);
|
|
16162
16217
|
if (skip.ok) {
|
|
16163
16218
|
step = skip;
|
|
16164
16219
|
continue;
|
|
@@ -16182,7 +16237,7 @@ function rebaseOntoUpstream(memoryDir, upstream) {
|
|
|
16182
16237
|
const toAdd = [file, ...(resolution.backupPath ? [(0, node_path_1.relative)(memoryDir, resolution.backupPath)] : [])];
|
|
16183
16238
|
runSyncGit(memoryDir, ["add", "--", ...toAdd]);
|
|
16184
16239
|
}
|
|
16185
|
-
step = runSyncGit(memoryDir, ["-c", "core.editor=true", "rebase", "--continue"]);
|
|
16240
|
+
step = runSyncGit(memoryDir, [...syncIdentityArgs(memoryDir), "-c", "core.editor=true", "rebase", "--continue"]);
|
|
16186
16241
|
}
|
|
16187
16242
|
return { ok: true, resolved, conflictBackups };
|
|
16188
16243
|
}
|
|
@@ -16267,9 +16322,25 @@ function syncSetup(remoteUrl) {
|
|
|
16267
16322
|
}
|
|
16268
16323
|
}
|
|
16269
16324
|
result.branch = runSyncGit(memoryDir, ["rev-parse", "--abbrev-ref", "HEAD"]).stdout || null;
|
|
16270
|
-
|
|
16325
|
+
let push = runSyncGit(memoryDir, ["push", "-u", "origin", "HEAD"]);
|
|
16326
|
+
if (!push.ok && /non-fast-forward|behind|fetch first/i.test(push.stderr)) {
|
|
16327
|
+
// Some git versions leave the local branch behind the remote after the
|
|
16328
|
+
// convergence rebase (observed on CI's git, not on macOS). A second
|
|
16329
|
+
// fetch + rebase fast-forwards a behind branch, after which push succeeds.
|
|
16330
|
+
runSyncGit(memoryDir, ["fetch", "origin"]);
|
|
16331
|
+
const retryBranch = syncRemoteDefaultBranch(memoryDir) ?? result.branch;
|
|
16332
|
+
if (retryBranch && runSyncGit(memoryDir, ["rev-parse", "--verify", "--quiet", `origin/${retryBranch}`]).ok) {
|
|
16333
|
+
runSyncGit(memoryDir, ["branch", "-M", retryBranch]);
|
|
16334
|
+
const retryRebase = rebaseOntoUpstream(memoryDir, `origin/${retryBranch}`);
|
|
16335
|
+
if (retryRebase.ok)
|
|
16336
|
+
push = runSyncGit(memoryDir, ["push", "-u", "origin", "HEAD"]);
|
|
16337
|
+
}
|
|
16338
|
+
}
|
|
16271
16339
|
if (!push.ok) {
|
|
16272
|
-
|
|
16340
|
+
const state = runSyncGit(memoryDir, ["log", "--oneline", "--all", "--decorate", "-n", "8"]).stdout;
|
|
16341
|
+
result.errors.push(`git push failed: ${push.stderr}${state ? `
|
|
16342
|
+
repo state:
|
|
16343
|
+
${state}` : ""}`);
|
|
16273
16344
|
return result;
|
|
16274
16345
|
}
|
|
16275
16346
|
result.pushed = true;
|
package/package.json
CHANGED
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:
|
|
10
|
-
|
|
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:
|
|
13
|
-
--bg: #
|
|
14
|
-
--line: #
|
|
15
|
-
--ink: #
|
|
16
|
-
--gain: #
|
|
17
|
-
--code: #
|
|
18
|
-
--warn-soft: rgba(
|
|
19
|
-
--code-bg: #
|
|
20
|
-
--tip-bg: #
|
|
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
|
-
|
|
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; }
|