@kage-core/kage-graph-mcp 2.5.1 → 2.5.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
@@ -1411,21 +1411,30 @@ async function main() {
1411
1411
  const query = takeArg(args, "--query") ?? firstPositional(args);
1412
1412
  if (!query)
1413
1413
  usage();
1414
- const result = (0, kernel_js_1.recall)(projectArg(args), query, 5, false, {});
1414
+ const project = projectArg(args);
1415
+ const result = (0, kernel_js_1.recall)(project, query, 5, false, {});
1416
+ // Surface any nudges the kage-watcher wrote (marks them surfaced so each shows once) —
1417
+ // this is what makes recall/nudges felt instead of silently sitting in a file.
1418
+ const nudges = (0, kernel_js_1.surfacePendingNudges)(project);
1415
1419
  if (args.includes("--json")) {
1416
- console.log(JSON.stringify(result, null, 2));
1420
+ console.log(JSON.stringify({ ...result, nudges: nudges.items }, null, 2));
1417
1421
  return;
1418
1422
  }
1419
- if (!result.results.length)
1423
+ const parts = [];
1424
+ if (result.results.length) {
1425
+ // Lead with felt behavior, not a vanity/gameable token number: tell the agent these are
1426
+ // verified team memories to follow. (The tokens/$ ledger lives in `kage gains`.) Keep
1427
+ // only the trust-relevant stale-withheld signal here.
1428
+ const plural = result.results.length === 1 ? "y" : "ies";
1429
+ const withheld = result.value_receipt?.stale_withheld ?? 0;
1430
+ parts.push(result.context_block);
1431
+ 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).` : ""}_`);
1432
+ }
1433
+ if (nudges.block)
1434
+ parts.push(nudges.block.trimStart());
1435
+ if (!parts.length)
1420
1436
  return;
1421
- let out = result.context_block;
1422
- // Lead with felt behavior, not a vanity/gameable token number: tell the agent these are
1423
- // verified team memories to follow. (The tokens/$ ledger lives in `kage gains` for anyone
1424
- // who wants it.) Keep only the trust-relevant stale-withheld signal here.
1425
- const plural = result.results.length === 1 ? "y" : "ies";
1426
- const withheld = result.value_receipt?.stale_withheld ?? 0;
1427
- out += `\n\n_${result.results.length} verified team memor${plural} above — follow them.${withheld ? ` ${withheld} stale memor${withheld === 1 ? "y" : "ies"} withheld (code changed under them).` : ""}_`;
1428
- console.log(out);
1437
+ console.log(parts.join("\n\n"));
1429
1438
  return;
1430
1439
  }
1431
1440
  if (command === "memory-access") {
package/dist/kernel.js CHANGED
@@ -102,6 +102,7 @@ exports.compactProject = compactProject;
102
102
  exports.installAgentPolicy = installAgentPolicy;
103
103
  exports.createDenseEmbeddingProvider = createDenseEmbeddingProvider;
104
104
  exports.buildEmbeddingIndex = buildEmbeddingIndex;
105
+ exports.surfacePendingNudges = surfacePendingNudges;
105
106
  exports.recall = recall;
106
107
  exports.recallWithEmbeddings = recallWithEmbeddings;
107
108
  exports.queryCodeGraph = queryCodeGraph;
@@ -7942,7 +7943,10 @@ function isSerializedDumpTitle(title) {
7942
7943
  return /^(workflow|runbook)\s*:?\s*[{[]/i.test(t)
7943
7944
  || t.startsWith('{"')
7944
7945
  || /^<(task-notification|div|svg|html)\b/i.test(t)
7945
- || /\btool_use_id\b|toolu_[A-Za-z0-9]{10}/.test(title);
7946
+ || /\btool_use_id\b|toolu_[A-Za-z0-9]{10}/.test(title)
7947
+ // Shell-prompt / terminal paste, e.g. "user@host dir % cmd" or "user@host:path$ cmd" —
7948
+ // a pasted command transcript, not a durable learning.
7949
+ || /^[\w.+-]+@[\w.+-]+[\s:].*[%$#]\s/.test(t);
7946
7950
  }
7947
7951
  // Durable-learning size ceiling. A memory packet body is a distilled insight, not a
7948
7952
  // document; anything past this is almost certainly a raw transcript, file-content, or
@@ -7961,6 +7965,51 @@ function isSerializedDumpBody(body) {
7961
7965
  return isSerializedDumpTitle(t)
7962
7966
  || /<task-notification\b|<tool-use-id\b|"hookSpecificOutput"|"isImage"\s*:|"noOutputExpected"\s*:|"interrupted"\s*:\s*(true|false)/i.test(t.slice(0, 4000));
7963
7967
  }
7968
+ // Surface watcher nudges: read the nudge inbox the kage-watcher writes, return the
7969
+ // unsurfaced ones as a prominent block, and mark them surfaced so each shows exactly once.
7970
+ // This is what makes recall/nudges FELT — the UserPromptSubmit hook injects this block, so
7971
+ // the agent sees "act on this" memory instead of it sitting silently in a file.
7972
+ function surfacePendingNudges(projectDir) {
7973
+ const path = (0, node_path_1.join)(projectDir, ".agent_memory", "nudges", "pending.jsonl");
7974
+ if (!(0, node_fs_1.existsSync)(path))
7975
+ return { block: "", items: [], count: 0 };
7976
+ let raw;
7977
+ try {
7978
+ raw = (0, node_fs_1.readFileSync)(path, "utf8");
7979
+ }
7980
+ catch {
7981
+ return { block: "", items: [], count: 0 };
7982
+ }
7983
+ const records = [];
7984
+ for (const line of raw.split("\n")) {
7985
+ const trimmed = line.trim();
7986
+ if (!trimmed)
7987
+ continue;
7988
+ try {
7989
+ const parsed = JSON.parse(trimmed);
7990
+ if (parsed && typeof parsed === "object")
7991
+ records.push(parsed);
7992
+ }
7993
+ catch { /* drop malformed lines */ }
7994
+ }
7995
+ const unsurfaced = records.filter((record) => record.surfaced !== true && typeof record.message === "string");
7996
+ if (!unsurfaced.length)
7997
+ return { block: "", items: [], count: 0 };
7998
+ const items = unsurfaced.map((record) => ({
7999
+ message: String(record.message),
8000
+ file: typeof record.file === "string" ? record.file : undefined,
8001
+ kind: typeof record.kind === "string" ? record.kind : undefined,
8002
+ }));
8003
+ for (const record of unsurfaced)
8004
+ record.surfaced = true;
8005
+ try {
8006
+ (0, node_fs_1.writeFileSync)(path, `${records.map((record) => JSON.stringify(record)).join("\n")}\n`, "utf8");
8007
+ }
8008
+ catch { /* best-effort; recall still works */ }
8009
+ const block = ["", "## ⚠ Kage nudges — act on these before proceeding",
8010
+ ...items.map((item) => `- ${item.file ? `${item.file}: ` : ""}${item.message}`)].join("\n");
8011
+ return { block, items, count: items.length };
8012
+ }
7964
8013
  // Collapse whitespace and hard-cap a value rendered inline in a context block, so one
7965
8014
  // oversized field (e.g. a graph fact whose body is a raw transcript) can never blow up
7966
8015
  // 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.1",
3
+ "version": "2.5.2",
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": [