@kage-core/kage-graph-mcp 2.5.3 → 2.5.4
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 +16 -39
- package/dist/kernel.js +2 -62
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1396,58 +1396,35 @@ async function main() {
|
|
|
1396
1396
|
const path = takeArg(args, "--path");
|
|
1397
1397
|
if (!path)
|
|
1398
1398
|
usage();
|
|
1399
|
-
const
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
if (args.includes("--json")) {
|
|
1406
|
-
console.log(JSON.stringify({ ...result, nudges: nudges.items }, null, 2));
|
|
1407
|
-
return;
|
|
1408
|
-
}
|
|
1409
|
-
const parts = [];
|
|
1410
|
-
if (result.context_block)
|
|
1411
|
-
parts.push(result.context_block);
|
|
1412
|
-
if (nudges.block)
|
|
1413
|
-
parts.push(nudges.block.trimStart());
|
|
1414
|
-
if (parts.length)
|
|
1415
|
-
console.log(parts.join("\n\n"));
|
|
1416
|
-
// No verified packets cite this file and no nudge targets it: print nothing so hooks
|
|
1417
|
-
// can gate on empty output.
|
|
1399
|
+
const result = (0, kernel_js_1.kageFileContext)(projectArg(args), path);
|
|
1400
|
+
if (args.includes("--json"))
|
|
1401
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1402
|
+
else if (result.context_block)
|
|
1403
|
+
console.log(result.context_block);
|
|
1404
|
+
// No verified packets cite this file: print nothing so hooks can gate on empty output.
|
|
1418
1405
|
return;
|
|
1419
1406
|
}
|
|
1420
1407
|
if (command === "prompt-context") {
|
|
1421
1408
|
// Top-of-task recall for an ambient UserPromptSubmit hook: recall on the user's prompt
|
|
1422
|
-
// and emit the memory
|
|
1423
|
-
// Silent when nothing relevant is found, so hooks can gate on empty output.
|
|
1409
|
+
// and emit the memory. Silent when nothing relevant is found, so hooks can gate on empty.
|
|
1424
1410
|
const query = takeArg(args, "--query") ?? firstPositional(args);
|
|
1425
1411
|
if (!query)
|
|
1426
1412
|
usage();
|
|
1427
1413
|
const project = projectArg(args);
|
|
1428
1414
|
const result = (0, kernel_js_1.recall)(project, query, 5, false, {});
|
|
1429
|
-
// Surface any nudges the kage-watcher wrote (marks them surfaced so each shows once) —
|
|
1430
|
-
// this is what makes recall/nudges felt instead of silently sitting in a file.
|
|
1431
|
-
const nudges = (0, kernel_js_1.surfacePendingNudges)(project);
|
|
1432
1415
|
if (args.includes("--json")) {
|
|
1433
|
-
console.log(JSON.stringify(
|
|
1416
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1434
1417
|
return;
|
|
1435
1418
|
}
|
|
1436
|
-
|
|
1437
|
-
if (result.results.length) {
|
|
1438
|
-
// Lead with felt behavior, not a vanity/gameable token number: tell the agent these are
|
|
1439
|
-
// verified team memories to follow. (The tokens/$ ledger lives in `kage gains`.) Keep
|
|
1440
|
-
// only the trust-relevant stale-withheld signal here.
|
|
1441
|
-
const plural = result.results.length === 1 ? "y" : "ies";
|
|
1442
|
-
const withheld = result.value_receipt?.stale_withheld ?? 0;
|
|
1443
|
-
parts.push(result.context_block);
|
|
1444
|
-
parts.push(`_${result.results.length} verified team memor${plural} above — follow them.${withheld ? ` ${withheld} stale memor${withheld === 1 ? "y" : "ies"} withheld (code changed under them).` : ""}_`);
|
|
1445
|
-
}
|
|
1446
|
-
if (nudges.block)
|
|
1447
|
-
parts.push(nudges.block.trimStart());
|
|
1448
|
-
if (!parts.length)
|
|
1419
|
+
if (!result.results.length)
|
|
1449
1420
|
return;
|
|
1450
|
-
|
|
1421
|
+
// Lead with felt behavior, not a vanity/gameable token number: tell the agent these are
|
|
1422
|
+
// verified team memories to follow. (The tokens/$ ledger lives in `kage gains`.) Keep
|
|
1423
|
+
// only the trust-relevant stale-withheld signal here.
|
|
1424
|
+
const plural = result.results.length === 1 ? "y" : "ies";
|
|
1425
|
+
const withheld = result.value_receipt?.stale_withheld ?? 0;
|
|
1426
|
+
console.log(result.context_block);
|
|
1427
|
+
console.log(`_${result.results.length} verified team memor${plural} above — follow them.${withheld ? ` ${withheld} stale memor${withheld === 1 ? "y" : "ies"} withheld (code changed under them).` : ""}_`);
|
|
1451
1428
|
return;
|
|
1452
1429
|
}
|
|
1453
1430
|
if (command === "memory-access") {
|
package/dist/kernel.js
CHANGED
|
@@ -102,7 +102,6 @@ exports.compactProject = compactProject;
|
|
|
102
102
|
exports.installAgentPolicy = installAgentPolicy;
|
|
103
103
|
exports.createDenseEmbeddingProvider = createDenseEmbeddingProvider;
|
|
104
104
|
exports.buildEmbeddingIndex = buildEmbeddingIndex;
|
|
105
|
-
exports.surfacePendingNudges = surfacePendingNudges;
|
|
106
105
|
exports.recall = recall;
|
|
107
106
|
exports.recallWithEmbeddings = recallWithEmbeddings;
|
|
108
107
|
exports.queryCodeGraph = queryCodeGraph;
|
|
@@ -1204,9 +1203,8 @@ function replayTokensSaved(packets, contextBlock) {
|
|
|
1204
1203
|
}
|
|
1205
1204
|
const FILE_CONTEXT_PACKET_CAP = 3;
|
|
1206
1205
|
// Normalize a file path to the repo-relative form used for packet citation matching:
|
|
1207
|
-
// absolute paths are made relative to the project root, backslashes/leading "./"
|
|
1208
|
-
// stripped. Returns "" for an empty/blank input.
|
|
1209
|
-
// and edit-time nudge matching so both compare paths identically.
|
|
1206
|
+
// absolute paths are made relative to the project root, backslashes/leading "./" and
|
|
1207
|
+
// trailing slashes are stripped. Returns "" for an empty/blank input.
|
|
1210
1208
|
function normalizeRepoPath(projectDir, filePath) {
|
|
1211
1209
|
let rel = (filePath ?? "").trim().replace(/\\/g, "/");
|
|
1212
1210
|
if (!rel)
|
|
@@ -7972,64 +7970,6 @@ function isSerializedDumpBody(body) {
|
|
|
7972
7970
|
return isSerializedDumpTitle(t)
|
|
7973
7971
|
|| /<task-notification\b|<tool-use-id\b|"hookSpecificOutput"|"isImage"\s*:|"noOutputExpected"\s*:|"interrupted"\s*:\s*(true|false)/i.test(t.slice(0, 4000));
|
|
7974
7972
|
}
|
|
7975
|
-
// Surface watcher nudges: read the nudge inbox the kage-watcher writes (.agent_memory/
|
|
7976
|
-
// nudges/pending.jsonl), return the unsurfaced ones as a prominent block, and mark them
|
|
7977
|
-
// surfaced so each shows exactly once. This is what makes recall/nudges FELT instead of
|
|
7978
|
-
// sitting silently in a file. Two trigger points share this: prompt-context
|
|
7979
|
-
// (UserPromptSubmit) passes no filter and drains every pending nudge; file-context
|
|
7980
|
-
// (PreToolUse Edit/Read) passes opts.path so only a nudge targeting the file being touched
|
|
7981
|
-
// fires — surfacing the warning at the edit moment, the highest-value trigger. A file-less
|
|
7982
|
-
// nudge has no edit moment, so it waits for the next prompt-context drain.
|
|
7983
|
-
function surfacePendingNudges(projectDir, opts = {}) {
|
|
7984
|
-
const path = (0, node_path_1.join)(projectDir, ".agent_memory", "nudges", "pending.jsonl");
|
|
7985
|
-
if (!(0, node_fs_1.existsSync)(path))
|
|
7986
|
-
return { block: "", items: [], count: 0 };
|
|
7987
|
-
let raw;
|
|
7988
|
-
try {
|
|
7989
|
-
raw = (0, node_fs_1.readFileSync)(path, "utf8");
|
|
7990
|
-
}
|
|
7991
|
-
catch {
|
|
7992
|
-
return { block: "", items: [], count: 0 };
|
|
7993
|
-
}
|
|
7994
|
-
const records = [];
|
|
7995
|
-
for (const line of raw.split("\n")) {
|
|
7996
|
-
const trimmed = line.trim();
|
|
7997
|
-
if (!trimmed)
|
|
7998
|
-
continue;
|
|
7999
|
-
try {
|
|
8000
|
-
const parsed = JSON.parse(trimmed);
|
|
8001
|
-
if (parsed && typeof parsed === "object")
|
|
8002
|
-
records.push(parsed);
|
|
8003
|
-
}
|
|
8004
|
-
catch { /* drop malformed lines */ }
|
|
8005
|
-
}
|
|
8006
|
-
const wantPath = opts.path !== undefined ? normalizeRepoPath(projectDir, opts.path) : undefined;
|
|
8007
|
-
const unsurfaced = records.filter((record) => record.surfaced !== true &&
|
|
8008
|
-
typeof record.message === "string" &&
|
|
8009
|
-
(wantPath === undefined ||
|
|
8010
|
-
(typeof record.file === "string" && normalizeRepoPath(projectDir, record.file) === wantPath)));
|
|
8011
|
-
if (!unsurfaced.length)
|
|
8012
|
-
return { block: "", items: [], count: 0 };
|
|
8013
|
-
const items = unsurfaced.map((record) => ({
|
|
8014
|
-
message: String(record.message),
|
|
8015
|
-
file: typeof record.file === "string" ? record.file : undefined,
|
|
8016
|
-
kind: typeof record.kind === "string" ? record.kind : undefined,
|
|
8017
|
-
}));
|
|
8018
|
-
for (const record of unsurfaced)
|
|
8019
|
-
record.surfaced = true;
|
|
8020
|
-
// Atomic write (tmp + rename): file-context now rewrites this inbox on every edit, so the
|
|
8021
|
-
// window where prompt-context could read a half-written file is wider. rename is atomic, so
|
|
8022
|
-
// a concurrent reader sees either the old or the new file, never a torn one.
|
|
8023
|
-
try {
|
|
8024
|
-
const tmp = `${path}.${process.pid}.tmp`;
|
|
8025
|
-
(0, node_fs_1.writeFileSync)(tmp, `${records.map((record) => JSON.stringify(record)).join("\n")}\n`, "utf8");
|
|
8026
|
-
(0, node_fs_1.renameSync)(tmp, path);
|
|
8027
|
-
}
|
|
8028
|
-
catch { /* best-effort; recall still works */ }
|
|
8029
|
-
const block = ["", "## ⚠ Kage nudges — act on these before proceeding",
|
|
8030
|
-
...items.map((item) => `- ${item.file ? `${item.file}: ` : ""}${item.message}`)].join("\n");
|
|
8031
|
-
return { block, items, count: items.length };
|
|
8032
|
-
}
|
|
8033
7973
|
// Collapse whitespace and hard-cap a value rendered inline in a context block, so one
|
|
8034
7974
|
// oversized field (e.g. a graph fact whose body is a raw transcript) can never blow up
|
|
8035
7975
|
// the assembled output — the 270k-char overflow that motivated this guard.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kage-core/kage-graph-mcp",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.4",
|
|
4
4
|
"description": "Team memory for coding agents: captures the decisions, runbooks, and bug fixes that get lost, verified against your code and shared via git. MCP server, zero deps, no account.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|