@kage-core/kage-graph-mcp 2.5.0 → 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 +19 -9
- package/dist/kernel.js +101 -8
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1411,20 +1411,30 @@ async function main() {
|
|
|
1411
1411
|
const query = takeArg(args, "--query") ?? firstPositional(args);
|
|
1412
1412
|
if (!query)
|
|
1413
1413
|
usage();
|
|
1414
|
-
const
|
|
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
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
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.
|
|
1424
1428
|
const plural = result.results.length === 1 ? "y" : "ies";
|
|
1425
|
-
|
|
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).` : ""}_`);
|
|
1426
1432
|
}
|
|
1427
|
-
|
|
1433
|
+
if (nudges.block)
|
|
1434
|
+
parts.push(nudges.block.trimStart());
|
|
1435
|
+
if (!parts.length)
|
|
1436
|
+
return;
|
|
1437
|
+
console.log(parts.join("\n\n"));
|
|
1428
1438
|
return;
|
|
1429
1439
|
}
|
|
1430
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.
|
|
@@ -8102,14 +8151,23 @@ function recallWithVectorScores(projectDir, query, limit = 5, explain = false, i
|
|
|
8102
8151
|
...scored.flatMap((entry, index) => {
|
|
8103
8152
|
const contradicts = (entry.packet.quality ?? {}).contradicts;
|
|
8104
8153
|
const contested = Array.isArray(contradicts) && contradicts.length > 0;
|
|
8154
|
+
// Felt format: lead with an imperative "Team memory:" claim the agent should follow,
|
|
8155
|
+
// dated and cited to file — not machinery (confidence/why-matched/source read as vanity
|
|
8156
|
+
// and noise). The behavior change is the value, not the metadata.
|
|
8157
|
+
const when = (entry.packet.created_at || entry.packet.updated_at || "").slice(0, 10);
|
|
8158
|
+
const verb = entry.packet.type === "decision" ? "decided"
|
|
8159
|
+
: entry.packet.type === "bug_fix" ? "fixed"
|
|
8160
|
+
: entry.packet.type === "convention" ? "convention since"
|
|
8161
|
+
: "noted";
|
|
8162
|
+
const cited = entry.packet.paths.slice(0, 3).join(", ");
|
|
8163
|
+
const meta = `${verb}${when ? ` ${when}` : ""}${cited ? ` · ${cited}` : ""}`;
|
|
8105
8164
|
return [
|
|
8106
8165
|
"",
|
|
8107
|
-
`${index + 1}.
|
|
8108
|
-
`
|
|
8109
|
-
`
|
|
8110
|
-
` Source: ${sourceLabel(entry.packet)}`,
|
|
8166
|
+
`${index + 1}. Team memory: ${entry.packet.title}`,
|
|
8167
|
+
` ${entry.packet.summary}`,
|
|
8168
|
+
...(meta.trim() ? [` (${meta})`] : []),
|
|
8111
8169
|
...(contested
|
|
8112
|
-
? [` ⚠ Contested:
|
|
8170
|
+
? [` ⚠ Contested: contradicts ${contradicts.length} other packet(s) (${contradicts.join(", ")}) — resolve with kage conflicts / kage supersede before relying on it.`]
|
|
8113
8171
|
: []),
|
|
8114
8172
|
];
|
|
8115
8173
|
}),
|
|
@@ -14622,6 +14680,13 @@ function loadObservations(projectDir, sessionId) {
|
|
|
14622
14680
|
// and echoes of Kage's own demo/receipt output. Manual `kage learn`/`kage capture`
|
|
14623
14681
|
// and manual `kage distill` are never gated — explicit intent outranks the heuristic.
|
|
14624
14682
|
exports.AUTO_DISTILL_SIGNAL_THRESHOLD = 0.4;
|
|
14683
|
+
// Auto-promote gate: a distilled draft jumps straight to trusted (approved, recallable)
|
|
14684
|
+
// memory — instead of waiting in the pending inbox — only when it is clearly-good AND
|
|
14685
|
+
// code-grounded AND not a duplicate. Everything else still goes to review. This is what
|
|
14686
|
+
// makes the capture flywheel actually spin; KAGE_AUTO_PROMOTE=0 disables it. Grounding keeps
|
|
14687
|
+
// the verification wedge intact: a promoted memory is still checked against the code, just
|
|
14688
|
+
// not gated on a human.
|
|
14689
|
+
const AUTO_PROMOTE_ENABLED = process.env.KAGE_AUTO_PROMOTE !== "0";
|
|
14625
14690
|
// Markers of hook/system plumbing payloads that sometimes leak into observation text
|
|
14626
14691
|
// (e.g. a raw <task-notification> block stored as a "user prompt").
|
|
14627
14692
|
const HOOK_PAYLOAD_MARKERS = [
|
|
@@ -15274,16 +15339,44 @@ function distillSession(projectDir, sessionId, options = {}) {
|
|
|
15274
15339
|
observation_count: observations.length,
|
|
15275
15340
|
},
|
|
15276
15341
|
];
|
|
15342
|
+
const admission = evaluateMemoryAdmission(projectDir, result.packet);
|
|
15277
15343
|
result.packet.quality = {
|
|
15278
15344
|
...result.packet.quality,
|
|
15279
15345
|
...(sessionDiscoveryTokens > 0
|
|
15280
15346
|
? { discovery_tokens: sessionDiscoveryTokens, discovery_tokens_estimated: true }
|
|
15281
15347
|
: {}),
|
|
15282
15348
|
distillation: auto ? "auto_distill" : "automatic_observation_candidate",
|
|
15283
|
-
admission
|
|
15349
|
+
admission,
|
|
15284
15350
|
suggested_review_action: suggestedAction(classifyPacket(projectDir, result.packet), result.packet.status),
|
|
15285
15351
|
};
|
|
15286
|
-
|
|
15352
|
+
// Auto-promote the clearly-good, code-grounded, non-duplicate drafts straight to trusted
|
|
15353
|
+
// recall so the flywheel spins without manual review; borderline / ungrounded / path-less
|
|
15354
|
+
// drafts stay in the pending inbox for a quick review.
|
|
15355
|
+
const groundedHighSignal = AUTO_PROMOTE_ENABLED
|
|
15356
|
+
&& auto
|
|
15357
|
+
&& result.packet.status === "pending"
|
|
15358
|
+
&& admission.admit
|
|
15359
|
+
&& admission.class === "high_signal"
|
|
15360
|
+
&& result.packet.paths.length > 0
|
|
15361
|
+
&& result.packet.paths.every((path) => pathExistsInRepo(projectDir, path))
|
|
15362
|
+
&& duplicateCandidates(projectDir, result.packet).length === 0
|
|
15363
|
+
&& detectContradictions(projectDir, result.packet).length === 0;
|
|
15364
|
+
if (groundedHighSignal) {
|
|
15365
|
+
result.packet.status = "approved";
|
|
15366
|
+
result.packet.tags = unique([...result.packet.tags, "auto-promoted"]);
|
|
15367
|
+
result.packet.quality.suggested_review_action = suggestedAction(classifyPacket(projectDir, result.packet), "approved");
|
|
15368
|
+
const promotedPath = writePacket(projectDir, result.packet, "packets");
|
|
15369
|
+
if (result.path && result.path !== promotedPath) {
|
|
15370
|
+
try {
|
|
15371
|
+
(0, node_fs_1.unlinkSync)(result.path);
|
|
15372
|
+
}
|
|
15373
|
+
catch { }
|
|
15374
|
+
}
|
|
15375
|
+
result.path = promotedPath;
|
|
15376
|
+
}
|
|
15377
|
+
else {
|
|
15378
|
+
writeJson(result.path, result.packet);
|
|
15379
|
+
}
|
|
15287
15380
|
return result;
|
|
15288
15381
|
};
|
|
15289
15382
|
const autoTags = auto ? [exports.AUTO_DISTILL_TAG] : [];
|
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.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": [
|