@kage-core/kage-graph-mcp 2.0.2 → 2.2.0
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 +183 -11
- package/dist/daemon.js +153 -0
- package/dist/index.js +28 -2
- package/dist/kernel.js +1834 -35
- package/package.json +1 -1
- package/viewer/console.js +61 -0
- package/viewer/index.html +10 -1
package/dist/cli.js
CHANGED
|
@@ -24,6 +24,7 @@ Core commands:
|
|
|
24
24
|
kage verify --project <dir> check memory citations against code
|
|
25
25
|
kage setup <agent> --project <dir> --write wire your agent (claude-code, codex, cursor, ...)
|
|
26
26
|
kage doctor --project <dir> health check
|
|
27
|
+
kage repair --project <dir> fix what doctor finds (indexes, broken packets, wiring)
|
|
27
28
|
kage viewer --project <dir> local dashboard
|
|
28
29
|
|
|
29
30
|
Run 'kage help --all' for the full command list (lifecycle, CI, benchmarks, daemon, workspace).`;
|
|
@@ -37,6 +38,7 @@ Usage:
|
|
|
37
38
|
kage init --project <dir> [--with-policy]
|
|
38
39
|
kage policy --project <dir>
|
|
39
40
|
kage doctor --project <dir>
|
|
41
|
+
kage repair --project <dir> [--json]
|
|
40
42
|
kage setup list
|
|
41
43
|
kage setup <agent> --project <dir> [--write] [--json]
|
|
42
44
|
kage setup doctor --project <dir> [--json]
|
|
@@ -49,7 +51,8 @@ Usage:
|
|
|
49
51
|
kage hook install --project <dir> [--json]
|
|
50
52
|
kage hook status --project <dir> [--json]
|
|
51
53
|
kage hook uninstall --project <dir> [--json]
|
|
52
|
-
kage refresh --project <dir> [--full] [--json]
|
|
54
|
+
kage refresh --project <dir> [--full] [--force] [--json]
|
|
55
|
+
kage merge-packet <ours> <base> <theirs> git merge driver for .agent_memory/packets/*.json
|
|
53
56
|
kage gc --project <dir> [--dry-run] [--force] [--json]
|
|
54
57
|
kage compact --project <dir> [--dry-run] [--json]
|
|
55
58
|
kage verify --project <dir> [--id <packet-id>] [--json]
|
|
@@ -83,6 +86,7 @@ Usage:
|
|
|
83
86
|
kage workspace --project <workspace-dir> [--json]
|
|
84
87
|
kage workspace recall "<query>" --project <workspace-dir> [--json]
|
|
85
88
|
kage audit --project <dir> [--json]
|
|
89
|
+
kage audit-claude-mem [--store <path>] [--project <dir>] [--json]
|
|
86
90
|
kage inbox --project <dir> [--json]
|
|
87
91
|
kage quality --project <dir> [--json]
|
|
88
92
|
kage benchmark --project <dir> [--json]
|
|
@@ -104,11 +108,16 @@ Usage:
|
|
|
104
108
|
kage graph-registry --project <dir> [--json]
|
|
105
109
|
kage embeddings build --project <dir> [--model Xenova/all-MiniLM-L6-v2] [--json]
|
|
106
110
|
kage recall "<query>" --project <dir> [--json] [--explain] [--embeddings] [--max-context-tokens <n>] [--structural-hops <n>]
|
|
111
|
+
kage file-context --project <dir> --path <file> [--json]
|
|
107
112
|
kage observe --project <dir> --event <json>
|
|
108
113
|
kage sessions --project <dir> [--json]
|
|
109
114
|
kage replay --project <dir> [--session <id>] [--limit <n>] [--json]
|
|
110
|
-
kage distill --project <dir> --session <id>
|
|
111
|
-
kage
|
|
115
|
+
kage distill --project <dir> --session <id> [--auto] [--json]
|
|
116
|
+
kage resume --project <dir> [--json]
|
|
117
|
+
kage learn --project <dir> --learning <text> [--personal] [--title <title>] [--type <type>] [--evidence <text>] [--verified-by <text>] [--tags a,b] [--paths a,b] [--graph-nodes a,b] [--discovery-tokens <n>] [--allow-missing-paths]
|
|
118
|
+
kage sync setup --remote <git-url> init ~/.kage/memory as a git repo wired to your private remote
|
|
119
|
+
kage sync [--json] commit + pull --rebase + push personal memory (newest-wins conflicts)
|
|
120
|
+
kage sync --status [--json] ahead/behind/dirty for the personal store (fetch only)
|
|
112
121
|
kage feedback --project <dir> --packet <packet-id> --kind helpful|wrong|stale
|
|
113
122
|
kage capture --project <dir> --title <title> --body <body> [--type <type>] [--summary <summary>] [--tags a,b] [--paths a,b] [--stack a,b] [--graph-nodes a,b] [--allow-missing-paths]
|
|
114
123
|
kage propose --project <dir> --from-diff
|
|
@@ -204,6 +213,74 @@ async function main() {
|
|
|
204
213
|
console.log(args.includes("--all") ? FULL_USAGE : CORE_USAGE);
|
|
205
214
|
return;
|
|
206
215
|
}
|
|
216
|
+
if (command === "merge-packet") {
|
|
217
|
+
// Git merge driver (%A %O %B): runs before any heavy setup — merge drivers
|
|
218
|
+
// must be fast and dependency-free. Exit 0 = merged, 1 = leave conflict.
|
|
219
|
+
const [ours, base, theirs] = [args[1], args[2], args[3]];
|
|
220
|
+
if (!ours || !base || !theirs) {
|
|
221
|
+
console.error("Usage: kage merge-packet <ours> <base> <theirs>");
|
|
222
|
+
console.error(`Enable once per clone: ${kernel_js_1.PACKET_MERGE_DRIVER_CONFIG}`);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
const result = (0, kernel_js_1.mergePacketFiles)(ours, base, theirs);
|
|
226
|
+
console.error(result.detail);
|
|
227
|
+
process.exit(result.ok ? 0 : 1);
|
|
228
|
+
}
|
|
229
|
+
if (command === "sync") {
|
|
230
|
+
// Personal-store sync (docs/CLOUD.md v1): plain git under the hood, no
|
|
231
|
+
// tree-sitter or repo indexes needed, so it runs before the heavy setup.
|
|
232
|
+
if (args[1] === "setup") {
|
|
233
|
+
const remote = takeArg(args, "--remote");
|
|
234
|
+
if (!remote) {
|
|
235
|
+
console.error("Usage: kage sync setup --remote <git-url>");
|
|
236
|
+
process.exit(2);
|
|
237
|
+
}
|
|
238
|
+
const result = (0, kernel_js_1.syncSetup)(remote);
|
|
239
|
+
if (args.includes("--json")) {
|
|
240
|
+
console.log(JSON.stringify(result, null, 2));
|
|
241
|
+
process.exit(result.ok ? 0 : 2);
|
|
242
|
+
}
|
|
243
|
+
if (!result.ok) {
|
|
244
|
+
console.error(`kage sync setup failed:\n${result.errors.map((error) => ` - ${error}`).join("\n")}`);
|
|
245
|
+
process.exit(2);
|
|
246
|
+
}
|
|
247
|
+
console.log(`Personal memory store: ${result.memory_dir}${result.initialized ? " (new git repo)" : ""}`);
|
|
248
|
+
console.log(`Remote: ${result.remote}${result.remote_updated ? " (updated)" : ""}`);
|
|
249
|
+
console.log(`Pushed ${result.branch ?? "HEAD"} to origin. Run \`kage sync\` on any machine to stay in sync.`);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (args.includes("--status")) {
|
|
253
|
+
const result = (0, kernel_js_1.syncStatus)();
|
|
254
|
+
if (args.includes("--json")) {
|
|
255
|
+
console.log(JSON.stringify(result, null, 2));
|
|
256
|
+
process.exit(result.ok ? 0 : 2);
|
|
257
|
+
}
|
|
258
|
+
if (!result.ok) {
|
|
259
|
+
console.error(`kage sync status failed:\n${result.errors.map((error) => ` - ${error}`).join("\n")}`);
|
|
260
|
+
process.exit(2);
|
|
261
|
+
}
|
|
262
|
+
console.log(`Personal memory store: ${result.memory_dir}`);
|
|
263
|
+
console.log(`Remote: ${result.remote} (branch ${result.branch ?? "unknown"})`);
|
|
264
|
+
console.log(`Ahead ${result.ahead}, behind ${result.behind}, ${result.dirty ? "uncommitted local changes" : "clean"}`);
|
|
265
|
+
for (const warning of result.warnings)
|
|
266
|
+
console.log(`Warning: ${warning}`);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const result = (0, kernel_js_1.syncPersonal)();
|
|
270
|
+
if (args.includes("--json")) {
|
|
271
|
+
console.log(JSON.stringify(result, null, 2));
|
|
272
|
+
process.exit(result.ok ? 0 : 2);
|
|
273
|
+
}
|
|
274
|
+
if (!result.ok) {
|
|
275
|
+
console.error(`kage sync failed:\n${result.errors.map((error) => ` - ${error}`).join("\n")}`);
|
|
276
|
+
process.exit(2);
|
|
277
|
+
}
|
|
278
|
+
console.log(`kage sync: pushed ${result.pushed}, pulled ${result.pulled}, resolved ${result.resolved}`);
|
|
279
|
+
if (result.conflict_backups.length) {
|
|
280
|
+
console.log(`Conflict losers preserved:\n${result.conflict_backups.map((path) => ` - ${path}`).join("\n")}`);
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
207
284
|
await (0, kernel_js_1.ensureTreeSitterLanguages)();
|
|
208
285
|
if (command === "index") {
|
|
209
286
|
const result = (0, kernel_js_1.indexProject)(projectArg(args));
|
|
@@ -287,6 +364,7 @@ async function main() {
|
|
|
287
364
|
console.log(`Initialized Kage memory for ${result.index.projectDir}`);
|
|
288
365
|
console.log("\nCreated:");
|
|
289
366
|
console.log(" .agent_memory/ memory packets + indexes (only directory Kage owns)");
|
|
367
|
+
console.log(` .gitattributes kage-packet merge driver for packet JSON${result.gitAttributes.changed ? "" : " (already current)"}`);
|
|
290
368
|
if (result.policyInstalled) {
|
|
291
369
|
console.log(" AGENTS.md, CLAUDE.md agent policy (requested via --with-policy)");
|
|
292
370
|
console.log(" .claude/settings.json allowed kage tools (requested via --with-policy)");
|
|
@@ -302,6 +380,8 @@ async function main() {
|
|
|
302
380
|
console.log("\nTip — version control:");
|
|
303
381
|
console.log(" commit .agent_memory/packets/ (your team's reviewed memory)");
|
|
304
382
|
console.log(" ignore .agent_memory/indexes/ .agent_memory/reports/ (regenerated)");
|
|
383
|
+
console.log("\nEnable the packet merge driver once per clone:");
|
|
384
|
+
console.log(` ${kernel_js_1.PACKET_MERGE_DRIVER_CONFIG}`);
|
|
305
385
|
if (!result.policyInstalled) {
|
|
306
386
|
console.log("\nNot written (opt-in): agent policy files. Add them with `kage policy --project .`");
|
|
307
387
|
console.log("or rerun `kage init --with-policy` when you're ready to commit them.");
|
|
@@ -375,6 +455,8 @@ async function main() {
|
|
|
375
455
|
console.log(" kage scan --project . 60-second Truth Report on this repo");
|
|
376
456
|
console.log(" kage viewer --project . local dashboard (gains, packets, graph)");
|
|
377
457
|
console.log("\nVersion control: commit .agent_memory/packets/, ignore .agent_memory/indexes/ and reports/.");
|
|
458
|
+
console.log("Enable the packet merge driver once per clone:");
|
|
459
|
+
console.log(` ${kernel_js_1.PACKET_MERGE_DRIVER_CONFIG}`);
|
|
378
460
|
if (!init.validation.ok)
|
|
379
461
|
process.exit(2);
|
|
380
462
|
return;
|
|
@@ -408,7 +490,36 @@ async function main() {
|
|
|
408
490
|
console.log(`Warnings:\n${result.validation.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
|
|
409
491
|
console.log("\nRecall smoke test:\n");
|
|
410
492
|
console.log(result.sampleRecall);
|
|
411
|
-
if (!result.validation.ok)
|
|
493
|
+
if (!result.validation.ok) {
|
|
494
|
+
console.log("\nSomething broken? kage repair --project .");
|
|
495
|
+
process.exit(2);
|
|
496
|
+
}
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (command === "repair") {
|
|
500
|
+
const result = (0, kernel_js_1.repairProject)(projectArg(args));
|
|
501
|
+
if (args.includes("--json")) {
|
|
502
|
+
console.log(JSON.stringify(result, null, 2));
|
|
503
|
+
if (!result.ok || !result.validation.ok)
|
|
504
|
+
process.exit(2);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
console.log(`Kage repair — ${result.project_dir}\n`);
|
|
508
|
+
const areaLabel = { packets: "Memory", indexes: "Indexes", locks: "Locks", agents: "Agents" };
|
|
509
|
+
for (const action of result.actions) {
|
|
510
|
+
const mark = action.status === "fixed" ? "✓" : action.status === "failed" ? "✗" : "•";
|
|
511
|
+
console.log(` ${(areaLabel[action.area] ?? action.area).padEnd(12)}${mark} ${action.target} — ${action.detail}`);
|
|
512
|
+
}
|
|
513
|
+
console.log(`\n${result.fixed} fixed, ${result.skipped} already healthy, ${result.failed} failed`);
|
|
514
|
+
if (result.removed_packets.length) {
|
|
515
|
+
console.log(`\nRemoved ${result.removed_packets.length} unrecoverable packet(s) — backups kept in .agent_memory/backup/:`);
|
|
516
|
+
for (const removed of result.removed_packets)
|
|
517
|
+
console.log(` ${removed}`);
|
|
518
|
+
}
|
|
519
|
+
console.log(result.validation.ok ? "Validation: passed" : "Validation: still failing");
|
|
520
|
+
if (result.validation.errors.length)
|
|
521
|
+
console.log(`Errors:\n${result.validation.errors.map((error) => ` - ${error}`).join("\n")}`);
|
|
522
|
+
if (!result.ok || !result.validation.ok)
|
|
412
523
|
process.exit(2);
|
|
413
524
|
return;
|
|
414
525
|
}
|
|
@@ -666,7 +777,7 @@ async function main() {
|
|
|
666
777
|
return;
|
|
667
778
|
}
|
|
668
779
|
if (command === "refresh") {
|
|
669
|
-
const result = (0, kernel_js_1.refreshProject)(projectArg(args), { full: args.includes("--full") });
|
|
780
|
+
const result = (0, kernel_js_1.refreshProject)(projectArg(args), { full: args.includes("--full"), force: args.includes("--force") });
|
|
670
781
|
if (args.includes("--json")) {
|
|
671
782
|
console.log(JSON.stringify(result, null, 2));
|
|
672
783
|
if (!result.ok)
|
|
@@ -674,6 +785,8 @@ async function main() {
|
|
|
674
785
|
return;
|
|
675
786
|
}
|
|
676
787
|
console.log(`Refreshed ${result.project_dir}`);
|
|
788
|
+
if (result.quiet_refresh)
|
|
789
|
+
console.log("Quiet refresh (non-default branch): packet metadata not rewritten on disk; use --force to persist.");
|
|
677
790
|
console.log(`Packets indexed: ${result.index.packets}`);
|
|
678
791
|
console.log(`Packet metadata updated: ${result.updated_packets}`);
|
|
679
792
|
console.log(`Code graph: ${result.code_graph.files} files, ${result.code_graph.symbols} symbols, ${result.code_graph.imports} imports, ${result.code_graph.calls} calls`);
|
|
@@ -1155,6 +1268,23 @@ async function main() {
|
|
|
1155
1268
|
`${window.caller_answers} caller ${plural(window.caller_answers, "answer", "answers")}`;
|
|
1156
1269
|
console.log(windowLine("Today: ", summary.today));
|
|
1157
1270
|
console.log(windowLine("All time:", summary.all_time));
|
|
1271
|
+
if (summary.all_time.replay_tokens > 0) {
|
|
1272
|
+
console.log(`Knowledge replay value: ~${(0, kernel_js_1.formatTokenCount)(week.replay_tokens)} tokens this week · ` +
|
|
1273
|
+
`~${(0, kernel_js_1.formatTokenCount)(summary.all_time.replay_tokens)} all time ` +
|
|
1274
|
+
`(discovery cost of served memories vs their compressed read cost)`);
|
|
1275
|
+
}
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
if (command === "file-context") {
|
|
1279
|
+
const path = takeArg(args, "--path");
|
|
1280
|
+
if (!path)
|
|
1281
|
+
usage();
|
|
1282
|
+
const result = (0, kernel_js_1.kageFileContext)(projectArg(args), path);
|
|
1283
|
+
if (args.includes("--json"))
|
|
1284
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1285
|
+
else if (result.context_block)
|
|
1286
|
+
console.log(result.context_block);
|
|
1287
|
+
// No verified packets cite this file: print nothing so hooks can gate on empty output.
|
|
1158
1288
|
return;
|
|
1159
1289
|
}
|
|
1160
1290
|
if (command === "memory-access") {
|
|
@@ -1502,6 +1632,21 @@ async function main() {
|
|
|
1502
1632
|
console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
|
|
1503
1633
|
return;
|
|
1504
1634
|
}
|
|
1635
|
+
if (command === "audit-claude-mem") {
|
|
1636
|
+
const projectDir = projectArg(args);
|
|
1637
|
+
const storePath = takeArg(args, "--store") ?? (0, kernel_js_1.defaultClaudeMemStorePath)();
|
|
1638
|
+
const result = (0, kernel_js_1.auditClaudeMemStore)(projectDir, { storePath });
|
|
1639
|
+
if (!result.ok) {
|
|
1640
|
+
console.error(result.error);
|
|
1641
|
+
process.exit(2);
|
|
1642
|
+
}
|
|
1643
|
+
if (args.includes("--json")) {
|
|
1644
|
+
console.log(JSON.stringify(result.report, null, 2));
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
console.log((0, kernel_js_1.renderClaudeMemAuditReceipt)(result.report));
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1505
1650
|
if (command === "audit") {
|
|
1506
1651
|
const result = (0, kernel_js_1.auditProject)(projectArg(args));
|
|
1507
1652
|
if (args.includes("--json")) {
|
|
@@ -1699,7 +1844,8 @@ async function main() {
|
|
|
1699
1844
|
const learning = takeArg(args, "--learning");
|
|
1700
1845
|
if (!learning)
|
|
1701
1846
|
usage();
|
|
1702
|
-
const
|
|
1847
|
+
const personal = args.includes("--personal");
|
|
1848
|
+
const result = (personal ? kernel_js_1.learnPersonal : kernel_js_1.learn)({
|
|
1703
1849
|
projectDir: projectArg(args),
|
|
1704
1850
|
learning,
|
|
1705
1851
|
title: takeArg(args, "--title"),
|
|
@@ -1712,15 +1858,18 @@ async function main() {
|
|
|
1712
1858
|
graphNodes: listArg(takeArg(args, "--graph-nodes")),
|
|
1713
1859
|
allowMissingPaths: args.includes("--allow-missing-paths"),
|
|
1714
1860
|
strictCitations: true,
|
|
1861
|
+
discoveryTokens: args.includes("--discovery-tokens") ? numberArg(args, "--discovery-tokens", 0) : undefined,
|
|
1715
1862
|
});
|
|
1716
1863
|
if (!result.ok) {
|
|
1717
1864
|
console.error(`Learning capture blocked:\n${result.errors.map((error) => ` - ${error}`).join("\n")}`);
|
|
1718
1865
|
process.exit(2);
|
|
1719
1866
|
}
|
|
1720
|
-
console.log(`Captured session learning: ${result.path}`);
|
|
1867
|
+
console.log(`Captured ${personal ? "personal" : "session"} learning: ${result.path}`);
|
|
1721
1868
|
if (result.warnings?.length)
|
|
1722
1869
|
console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
|
|
1723
|
-
console.log(
|
|
1870
|
+
console.log(personal
|
|
1871
|
+
? "Personal memory is recalled with lower trust and never enters repo review flows. Sync it across machines with `kage sync`."
|
|
1872
|
+
: "Repo-local memory is written immediately. Promotion to org/global still requires explicit review.");
|
|
1724
1873
|
return;
|
|
1725
1874
|
}
|
|
1726
1875
|
if (command === "propose") {
|
|
@@ -1899,9 +2048,18 @@ async function main() {
|
|
|
1899
2048
|
const sessionId = takeArg(args, "--session");
|
|
1900
2049
|
if (!sessionId)
|
|
1901
2050
|
usage();
|
|
1902
|
-
const
|
|
2051
|
+
const project = projectArg(args);
|
|
2052
|
+
const auto = args.includes("--auto");
|
|
2053
|
+
const result = (0, kernel_js_1.distillSession)(project, sessionId, { auto });
|
|
1903
2054
|
if (args.includes("--json"))
|
|
1904
2055
|
console.log(JSON.stringify(result, null, 2));
|
|
2056
|
+
else if (auto) {
|
|
2057
|
+
// Auto mode is quiet: no output for empty or already-captured sessions; one line otherwise.
|
|
2058
|
+
const drafted = result.candidates.filter((candidate) => candidate.ok).length;
|
|
2059
|
+
if (!result.skipped_reason && drafted > 0) {
|
|
2060
|
+
console.log(`Auto-distilled ${drafted} pending draft${drafted === 1 ? "" : "s"} from session ${sessionId}. Review with: kage review --project ${project}`);
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
1905
2063
|
else {
|
|
1906
2064
|
console.log(`Distilled session: ${sessionId}`);
|
|
1907
2065
|
console.log(`Observations: ${result.observations}`);
|
|
@@ -1909,10 +2067,21 @@ async function main() {
|
|
|
1909
2067
|
if (result.errors.length)
|
|
1910
2068
|
console.log(`Errors:\n${result.errors.map((error) => ` - ${error}`).join("\n")}`);
|
|
1911
2069
|
}
|
|
1912
|
-
if (!result.ok)
|
|
2070
|
+
if (!result.ok && !auto)
|
|
1913
2071
|
process.exit(2);
|
|
1914
2072
|
return;
|
|
1915
2073
|
}
|
|
2074
|
+
if (command === "resume") {
|
|
2075
|
+
const result = (0, kernel_js_1.kageResume)(projectArg(args));
|
|
2076
|
+
if (args.includes("--json")) {
|
|
2077
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
// Prints nothing when there is no prior session data, so hooks can append output verbatim.
|
|
2081
|
+
if (result.has_content && result.context_block)
|
|
2082
|
+
console.log(result.context_block);
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
1916
2085
|
if (command === "feedback") {
|
|
1917
2086
|
const id = takeArg(args, "--packet");
|
|
1918
2087
|
const kind = takeArg(args, "--kind");
|
|
@@ -2004,7 +2173,10 @@ async function main() {
|
|
|
2004
2173
|
}
|
|
2005
2174
|
usage();
|
|
2006
2175
|
}
|
|
2176
|
+
// Remediation-first failure: lead with the message, follow with exactly ONE
|
|
2177
|
+
// copy-pasteable next command. Exit code stays 1, same as before.
|
|
2007
2178
|
main().catch((error) => {
|
|
2008
|
-
console.error(error);
|
|
2179
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
2180
|
+
console.error(`\nTry:\n ${(0, kernel_js_1.remediationFor)(error)}`);
|
|
2009
2181
|
process.exit(1);
|
|
2010
2182
|
});
|
package/dist/daemon.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.viewerStaticHeaders = viewerStaticHeaders;
|
|
4
4
|
exports.viewerRedirectLocation = viewerRedirectLocation;
|
|
5
5
|
exports.viewerReportPaths = viewerReportPaths;
|
|
6
|
+
exports.startLiveFeed = startLiveFeed;
|
|
6
7
|
exports.viewerUrl = viewerUrl;
|
|
7
8
|
exports.viewerBenchmarkReport = viewerBenchmarkReport;
|
|
8
9
|
exports.daemonContextReport = daemonContextReport;
|
|
@@ -105,6 +106,152 @@ function viewerReportPaths(projectRoot) {
|
|
|
105
106
|
value: (0, node_path_1.join)(reportsDir, "value.json"),
|
|
106
107
|
};
|
|
107
108
|
}
|
|
109
|
+
const LIVE_FEED_HEARTBEAT_MS = 25_000;
|
|
110
|
+
const LIVE_FEED_DEBOUNCE_MS = 100;
|
|
111
|
+
function readPacketTitle(filePath) {
|
|
112
|
+
try {
|
|
113
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(filePath, "utf8"));
|
|
114
|
+
return typeof parsed.title === "string" && parsed.title ? parsed.title : undefined;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Streams memory/value activity to viewer clients over SSE (GET /kage/events).
|
|
121
|
+
// The engine already writes packets and the value ledger to .agent_memory/, so a
|
|
122
|
+
// filesystem watch is the lightest possible event source — no queue, no new deps.
|
|
123
|
+
function startLiveFeed(projectRoot, options = {}) {
|
|
124
|
+
const heartbeatMs = options.heartbeatMs ?? LIVE_FEED_HEARTBEAT_MS;
|
|
125
|
+
const debounceMs = options.debounceMs ?? LIVE_FEED_DEBOUNCE_MS;
|
|
126
|
+
const packetsDir = (0, node_path_1.join)(projectRoot, ".agent_memory", "packets");
|
|
127
|
+
const reportsDir = (0, node_path_1.join)(projectRoot, ".agent_memory", "reports");
|
|
128
|
+
const valuePath = (0, node_path_1.join)(reportsDir, "value.json");
|
|
129
|
+
const clients = new Set();
|
|
130
|
+
const watchers = [];
|
|
131
|
+
const timers = new Map();
|
|
132
|
+
const knownPackets = new Set();
|
|
133
|
+
try {
|
|
134
|
+
(0, node_fs_1.mkdirSync)(packetsDir, { recursive: true });
|
|
135
|
+
for (const name of (0, node_fs_1.readdirSync)(packetsDir))
|
|
136
|
+
knownPackets.add(name);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// packets dir unavailable: packet events simply won't fire
|
|
140
|
+
}
|
|
141
|
+
function readValueEvents() {
|
|
142
|
+
try {
|
|
143
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(valuePath, "utf8"));
|
|
144
|
+
return Array.isArray(parsed.events) ? parsed.events : [];
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
let seenValueEvents = readValueEvents().length;
|
|
151
|
+
function broadcast(event) {
|
|
152
|
+
const payload = `data: ${JSON.stringify(event)}\n\n`;
|
|
153
|
+
for (const res of clients)
|
|
154
|
+
res.write(payload);
|
|
155
|
+
}
|
|
156
|
+
function onPacketChange(name) {
|
|
157
|
+
const filePath = (0, node_path_1.join)(packetsDir, name);
|
|
158
|
+
if (!(0, node_fs_1.existsSync)(filePath)) {
|
|
159
|
+
knownPackets.delete(name);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const isNew = !knownPackets.has(name);
|
|
163
|
+
knownPackets.add(name);
|
|
164
|
+
broadcast({
|
|
165
|
+
type: isNew ? "packet_written" : "packet_updated",
|
|
166
|
+
title: readPacketTitle(filePath) ?? name.replace(/\.json$/, ""),
|
|
167
|
+
path: (0, node_path_1.join)(".agent_memory", "packets", name),
|
|
168
|
+
ts: new Date().toISOString(),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function onValueChange() {
|
|
172
|
+
const events = readValueEvents();
|
|
173
|
+
if (events.length < seenValueEvents)
|
|
174
|
+
seenValueEvents = 0; // ledger trimmed or rewritten
|
|
175
|
+
for (const event of events.slice(seenValueEvents)) {
|
|
176
|
+
broadcast({
|
|
177
|
+
type: "value_event",
|
|
178
|
+
title: typeof event.packet_title === "string" ? event.packet_title : undefined,
|
|
179
|
+
path: (0, node_path_1.join)(".agent_memory", "reports", "value.json"),
|
|
180
|
+
event,
|
|
181
|
+
ts: typeof event.at === "string" ? event.at : new Date().toISOString(),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
seenValueEvents = events.length;
|
|
185
|
+
}
|
|
186
|
+
// fs.watch fires bursts of duplicate events for one logical write; collapse
|
|
187
|
+
// them per file with a short debounce before reading and broadcasting.
|
|
188
|
+
function debounced(key, run) {
|
|
189
|
+
const existing = timers.get(key);
|
|
190
|
+
if (existing)
|
|
191
|
+
clearTimeout(existing);
|
|
192
|
+
timers.set(key, setTimeout(() => {
|
|
193
|
+
timers.delete(key);
|
|
194
|
+
try {
|
|
195
|
+
run();
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// keep the feed alive even if a read races a write
|
|
199
|
+
}
|
|
200
|
+
}, debounceMs));
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
watchers.push((0, node_fs_1.watch)(packetsDir, (_event, filename) => {
|
|
204
|
+
const name = String(filename ?? "");
|
|
205
|
+
if (!name.endsWith(".json"))
|
|
206
|
+
return;
|
|
207
|
+
debounced(`packet:${name}`, () => onPacketChange(name));
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// packets dir missing: no packet events
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
(0, node_fs_1.mkdirSync)(reportsDir, { recursive: true });
|
|
215
|
+
watchers.push((0, node_fs_1.watch)(reportsDir, (_event, filename) => {
|
|
216
|
+
if (String(filename ?? "") !== "value.json")
|
|
217
|
+
return;
|
|
218
|
+
debounced("value", onValueChange);
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// reports dir missing: no value events
|
|
223
|
+
}
|
|
224
|
+
const heartbeat = setInterval(() => {
|
|
225
|
+
for (const res of clients)
|
|
226
|
+
res.write(`: heartbeat ${Date.now()}\n\n`);
|
|
227
|
+
}, heartbeatMs);
|
|
228
|
+
heartbeat.unref();
|
|
229
|
+
function handleRequest(req, res) {
|
|
230
|
+
res.writeHead(200, {
|
|
231
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
232
|
+
"cache-control": "no-cache, no-transform",
|
|
233
|
+
connection: "keep-alive",
|
|
234
|
+
"x-accel-buffering": "no",
|
|
235
|
+
});
|
|
236
|
+
res.write(": connected\n\n");
|
|
237
|
+
clients.add(res);
|
|
238
|
+
req.on("close", () => {
|
|
239
|
+
clients.delete(res);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function close() {
|
|
243
|
+
clearInterval(heartbeat);
|
|
244
|
+
for (const timer of timers.values())
|
|
245
|
+
clearTimeout(timer);
|
|
246
|
+
timers.clear();
|
|
247
|
+
for (const watcher of watchers)
|
|
248
|
+
watcher.close();
|
|
249
|
+
for (const res of clients)
|
|
250
|
+
res.end();
|
|
251
|
+
clients.clear();
|
|
252
|
+
}
|
|
253
|
+
return { handleRequest, broadcast, clientCount: () => clients.size, close };
|
|
254
|
+
}
|
|
108
255
|
function viewerUrl(host, port, projectRoot) {
|
|
109
256
|
const query = Object.entries(viewerReportPaths(projectRoot))
|
|
110
257
|
.map(([name, path]) => `${name}=${encodeURIComponent(path)}`)
|
|
@@ -633,8 +780,13 @@ async function startViewer(projectDir, options = {}) {
|
|
|
633
780
|
// non-fatal: viewer will show 404 for reports if generation fails
|
|
634
781
|
}
|
|
635
782
|
const url = viewerUrl(host, port, projectRoot);
|
|
783
|
+
const liveFeed = startLiveFeed(projectRoot);
|
|
636
784
|
const server = (0, node_http_1.createServer)((req, res) => {
|
|
637
785
|
const requestUrl = new URL(req.url ?? "/", `http://${host}:${port}`);
|
|
786
|
+
if (req.method === "GET" && requestUrl.pathname === "/kage/events") {
|
|
787
|
+
liveFeed.handleRequest(req, res);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
638
790
|
let filePath = null;
|
|
639
791
|
const redirectLocation = viewerRedirectLocation(requestUrl.pathname, requestUrl.search, new URL(url).search);
|
|
640
792
|
if (redirectLocation) {
|
|
@@ -673,6 +825,7 @@ async function startViewer(projectDir, options = {}) {
|
|
|
673
825
|
await new Promise((resolveListen) => server.listen(port, host, resolveListen));
|
|
674
826
|
console.log(`Kage viewer listening on ${url}`);
|
|
675
827
|
process.on("SIGTERM", () => {
|
|
828
|
+
liveFeed.close();
|
|
676
829
|
server.close(() => process.exit(0));
|
|
677
830
|
});
|
|
678
831
|
return { ok: true, project_dir: projectRoot, host, port, url };
|
package/dist/index.js
CHANGED
|
@@ -75,6 +75,15 @@ function riskContextBlock(result) {
|
|
|
75
75
|
return `\n## Risk Signals\n${lines.join("\n")}`;
|
|
76
76
|
}
|
|
77
77
|
const server = new index_js_1.Server({ name: "kage-graph", version: "2.0.0" }, { capabilities: { tools: {} } });
|
|
78
|
+
// Workflow pseudo-tool: the description itself is the documentation, so agents
|
|
79
|
+
// absorb the loop just by listing tools. The handler returns the same text.
|
|
80
|
+
const KAGE_WORKFLOW_TEXT = "Kage memory workflow (this tool performs no action; it returns this loop). " +
|
|
81
|
+
"1) Start every task with kage_context (project_dir + the task as query): it validates memory, recalls relevant packets, and queries the code and knowledge graphs in one call. " +
|
|
82
|
+
"2) Do the work, preferring repo memory over public context. " +
|
|
83
|
+
"3) Capture reusable learnings with kage_learn — bug causes and verified fixes, conventions, decisions, gotchas, run/test/build commands. Wrap anything that must never leave the repo in <private>...</private> tags; private spans are stripped before sharing. " +
|
|
84
|
+
"4) After meaningful file changes, call kage_refresh so indexes, graphs, and stale-memory checks stay current. " +
|
|
85
|
+
"5) Before finishing a branch, call kage_pr_summarize then kage_pr_check. " +
|
|
86
|
+
"Recall receipts show estimated tokens saved versus rediscovery; report memory quality with kage_feedback (helpful/wrong/stale).";
|
|
78
87
|
function listTools() {
|
|
79
88
|
return [
|
|
80
89
|
{
|
|
@@ -457,15 +466,25 @@ function listTools() {
|
|
|
457
466
|
},
|
|
458
467
|
{
|
|
459
468
|
name: "kage_refresh",
|
|
460
|
-
description: "Rebuild repo indexes, code graph, memory graph, metrics, and stale-memory metadata. Agents should run this after meaningful file/content changes before PR checks; push-only or same-tree commits do not need another refresh.",
|
|
469
|
+
description: "Rebuild repo indexes, code graph, memory graph, metrics, and stale-memory metadata. Agents should run this after meaningful file/content changes before PR checks; push-only or same-tree commits do not need another refresh. On non-default git branches metadata-only packet rewrites are skipped (quiet refresh) to avoid merge conflicts; pass force to persist them anyway.",
|
|
461
470
|
inputSchema: {
|
|
462
471
|
type: "object",
|
|
463
472
|
properties: {
|
|
464
473
|
project_dir: { type: "string" },
|
|
474
|
+
force: { type: "boolean", description: "Persist packet metadata rewrites even on a non-default branch" },
|
|
465
475
|
},
|
|
466
476
|
required: ["project_dir"],
|
|
467
477
|
},
|
|
468
478
|
},
|
|
479
|
+
{
|
|
480
|
+
name: "kage_workflow",
|
|
481
|
+
description: KAGE_WORKFLOW_TEXT,
|
|
482
|
+
inputSchema: {
|
|
483
|
+
type: "object",
|
|
484
|
+
properties: {},
|
|
485
|
+
required: [],
|
|
486
|
+
},
|
|
487
|
+
},
|
|
469
488
|
{
|
|
470
489
|
name: "kage_pr_summarize",
|
|
471
490
|
description: "Create a PR/branch memory summary from local git diff metadata and write repo-local change memory. Use when a branch is ready to hand off.",
|
|
@@ -685,6 +704,7 @@ function listTools() {
|
|
|
685
704
|
stack: { type: "array", items: { type: "string" } },
|
|
686
705
|
graph_nodes: { type: "array", items: { type: "string" } },
|
|
687
706
|
allow_missing_paths: { type: "boolean" },
|
|
707
|
+
discovery_tokens: { type: "number", description: "Approximate token cost of producing this knowledge (exploration + reasoning). Stored on the packet so recall receipts can report replay value; a conservative per-type default is estimated when omitted." },
|
|
688
708
|
},
|
|
689
709
|
required: ["project_dir", "learning"],
|
|
690
710
|
},
|
|
@@ -1243,12 +1263,17 @@ async function callTool(name, args) {
|
|
|
1243
1263
|
};
|
|
1244
1264
|
}
|
|
1245
1265
|
if (name === "kage_refresh") {
|
|
1246
|
-
const result = (0, kernel_js_1.refreshProject)(String(args?.project_dir ?? ""), { full: Boolean(args?.full) });
|
|
1266
|
+
const result = (0, kernel_js_1.refreshProject)(String(args?.project_dir ?? ""), { full: Boolean(args?.full), force: Boolean(args?.force) });
|
|
1247
1267
|
return {
|
|
1248
1268
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1249
1269
|
isError: !result.ok,
|
|
1250
1270
|
};
|
|
1251
1271
|
}
|
|
1272
|
+
if (name === "kage_workflow") {
|
|
1273
|
+
return {
|
|
1274
|
+
content: [{ type: "text", text: KAGE_WORKFLOW_TEXT }],
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1252
1277
|
if (name === "kage_pr_summarize") {
|
|
1253
1278
|
const result = (0, kernel_js_1.prSummarize)(String(args?.project_dir ?? ""));
|
|
1254
1279
|
return {
|
|
@@ -1349,6 +1374,7 @@ async function callTool(name, args) {
|
|
|
1349
1374
|
graphNodes: arrayArg(args?.graph_nodes),
|
|
1350
1375
|
allowMissingPaths: Boolean(args?.allow_missing_paths),
|
|
1351
1376
|
strictCitations: true,
|
|
1377
|
+
discoveryTokens: args?.discovery_tokens === undefined ? undefined : Number(args.discovery_tokens),
|
|
1352
1378
|
});
|
|
1353
1379
|
const learnWarnings = result.warnings?.length ? `\nWarnings:\n${result.warnings.map((warning) => `- ${warning}`).join("\n")}` : "";
|
|
1354
1380
|
return {
|