@kage-core/kage-graph-mcp 1.1.2 → 1.1.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/README.md +21 -2
- package/dist/cli.js +96 -0
- package/dist/index.js +54 -0
- package/dist/kernel.js +301 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ kage init --project /path/to/repo
|
|
|
27
27
|
kage policy --project /path/to/repo
|
|
28
28
|
kage doctor --project /path/to/repo
|
|
29
29
|
kage index --project /path/to/repo
|
|
30
|
+
kage refresh --project /path/to/repo
|
|
30
31
|
kage branch --project /path/to/repo
|
|
31
32
|
kage code-graph --project /path/to/repo
|
|
32
33
|
kage code-graph "createApp routes tests" --project /path/to/repo
|
|
@@ -45,6 +46,8 @@ kage learn --project /path/to/repo --learning "Decision: use kage_learn for actu
|
|
|
45
46
|
kage feedback --project /path/to/repo --packet <approved-packet-id> --kind stale
|
|
46
47
|
kage capture --project /path/to/repo --type runbook --title "Webhook tests" --body "Run pnpm test:api -- webhooks."
|
|
47
48
|
kage propose --project /path/to/repo --from-diff
|
|
49
|
+
kage pr summarize --project /path/to/repo
|
|
50
|
+
kage pr check --project /path/to/repo
|
|
48
51
|
kage review-artifact --project /path/to/repo
|
|
49
52
|
kage registry --project /path/to/repo
|
|
50
53
|
kage marketplace --project /path/to/repo
|
|
@@ -57,6 +60,7 @@ kage layered-recall "how do I run tests" --project /path/to/repo --org acme --gl
|
|
|
57
60
|
kage global build --project /path/to/repo --org acme
|
|
58
61
|
kage review --project /path/to/repo
|
|
59
62
|
kage validate --project /path/to/repo
|
|
63
|
+
kage upgrade --dry-run
|
|
60
64
|
```
|
|
61
65
|
|
|
62
66
|
`kage init` is the first-run command. It creates `.agent_memory/`, migrates
|
|
@@ -121,6 +125,16 @@ and parser coverage, code graph counts, evidence coverage, approved vs pending
|
|
|
121
125
|
memory, validation status, estimated tokens saved per recall, duplicate
|
|
122
126
|
candidates, average memory quality, and a readiness score.
|
|
123
127
|
|
|
128
|
+
Use `kage refresh --project <repo>` or the `kage_refresh` MCP tool after
|
|
129
|
+
meaningful file changes. Refresh rebuilds indexes, code graph, memory graph,
|
|
130
|
+
metrics, and stale-memory metadata. Memory is marked stale when status or
|
|
131
|
+
feedback says it is stale, its TTL expires, or grounded paths disappear.
|
|
132
|
+
|
|
133
|
+
Use `kage pr summarize --project <repo>` / `kage_pr_summarize` before handoff to
|
|
134
|
+
write branch review metadata and repo-local change memory from the git diff.
|
|
135
|
+
Use `kage pr check --project <repo>` / `kage_pr_check` before merge to verify
|
|
136
|
+
validation, graph freshness, stale packets, and memory packet changes.
|
|
137
|
+
|
|
124
138
|
Review artifacts include memory quality reasons, risks, duplicate candidates,
|
|
125
139
|
and estimated token savings for legacy pending/quarantine packets and promotion
|
|
126
140
|
review.
|
|
@@ -185,6 +199,9 @@ Local repo tools:
|
|
|
185
199
|
- `kage_recall`
|
|
186
200
|
- `kage_code_graph`
|
|
187
201
|
- `kage_metrics`
|
|
202
|
+
- `kage_refresh`
|
|
203
|
+
- `kage_pr_summarize`
|
|
204
|
+
- `kage_pr_check`
|
|
188
205
|
- `kage_quality`
|
|
189
206
|
- `kage_benchmark`
|
|
190
207
|
- `kage_setup_agent`
|
|
@@ -267,8 +284,10 @@ Before code changes or repo-specific answers:
|
|
|
267
284
|
2. Call `kage_recall` with the user task as the query.
|
|
268
285
|
3. Call `kage_graph` with the user task as the query.
|
|
269
286
|
4. Capture reusable learnings with `kage_learn` or `kage_capture`.
|
|
270
|
-
5.
|
|
271
|
-
6.
|
|
287
|
+
5. After meaningful file changes, call `kage_refresh`.
|
|
288
|
+
6. Before finishing changed-file tasks, call `kage_propose_from_diff` or `kage_pr_summarize`.
|
|
289
|
+
7. Before merge, call `kage_pr_check`.
|
|
290
|
+
8. Never publish or promote org/global memory automatically.
|
|
272
291
|
```
|
|
273
292
|
|
|
274
293
|
Run `kage setup verify-agent --agent codex --project <repo>` after setup. The
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
4
5
|
const promises_1 = require("node:readline/promises");
|
|
5
6
|
const node_process_1 = require("node:process");
|
|
6
7
|
const daemon_js_1 = require("./daemon.js");
|
|
@@ -22,6 +23,10 @@ Usage:
|
|
|
22
23
|
kage daemon status --project <dir> [--json]
|
|
23
24
|
kage daemon doctor --project <dir> [--json]
|
|
24
25
|
kage viewer --project <dir> [--port 3113]
|
|
26
|
+
kage refresh --project <dir> [--json]
|
|
27
|
+
kage pr summarize --project <dir> [--json]
|
|
28
|
+
kage pr check --project <dir> [--json]
|
|
29
|
+
kage upgrade [--dry-run]
|
|
25
30
|
kage branch --project <dir> [--json]
|
|
26
31
|
kage metrics --project <dir> [--json]
|
|
27
32
|
kage quality --project <dir> [--json]
|
|
@@ -296,6 +301,97 @@ async function main() {
|
|
|
296
301
|
await (0, daemon_js_1.startViewer)(projectArg(args), { port: numberArg(args, "--port", 3113) });
|
|
297
302
|
return;
|
|
298
303
|
}
|
|
304
|
+
if (command === "refresh") {
|
|
305
|
+
const result = (0, kernel_js_1.refreshProject)(projectArg(args));
|
|
306
|
+
if (args.includes("--json")) {
|
|
307
|
+
console.log(JSON.stringify(result, null, 2));
|
|
308
|
+
if (!result.ok)
|
|
309
|
+
process.exit(2);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
console.log(`Refreshed ${result.project_dir}`);
|
|
313
|
+
console.log(`Packets indexed: ${result.index.packets}`);
|
|
314
|
+
console.log(`Packet metadata updated: ${result.updated_packets}`);
|
|
315
|
+
console.log(`Code graph: ${result.code_graph.files} files, ${result.code_graph.symbols} symbols, ${result.code_graph.imports} imports, ${result.code_graph.calls} calls`);
|
|
316
|
+
console.log(`Memory graph: ${result.memory_graph.entities} entities, ${result.memory_graph.edges} edges, ${result.memory_graph.episodes} episodes`);
|
|
317
|
+
console.log(`Stale packets: ${result.stale_packets.length}`);
|
|
318
|
+
for (const packet of result.stale_packets.slice(0, 8)) {
|
|
319
|
+
console.log(` - ${packet.title} (${packet.id}): ${packet.reasons.join("; ")}`);
|
|
320
|
+
}
|
|
321
|
+
console.log(result.validation.ok ? "Validation: passed" : "Validation: failed");
|
|
322
|
+
if (result.validation.errors.length)
|
|
323
|
+
console.log(`Errors:\n${result.validation.errors.map((error) => ` - ${error}`).join("\n")}`);
|
|
324
|
+
if (result.validation.warnings.length)
|
|
325
|
+
console.log(`Warnings:\n${result.validation.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
|
|
326
|
+
console.log(`Next actions:\n${result.next_actions.map((action) => ` - ${action}`).join("\n")}`);
|
|
327
|
+
if (!result.ok)
|
|
328
|
+
process.exit(2);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (command === "pr") {
|
|
332
|
+
const action = args[1];
|
|
333
|
+
if (action === "summarize") {
|
|
334
|
+
const result = (0, kernel_js_1.prSummarize)(projectArg(args));
|
|
335
|
+
if (args.includes("--json")) {
|
|
336
|
+
console.log(JSON.stringify(result, null, 2));
|
|
337
|
+
if (!result.ok)
|
|
338
|
+
process.exit(2);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
console.log(`PR summary for ${result.project_dir}`);
|
|
342
|
+
console.log(`Branch: ${result.branch ?? "(detached)"}`);
|
|
343
|
+
console.log(`Changed files: ${result.changed_files.join(", ") || "(none)"}`);
|
|
344
|
+
if (result.diff_memory_packet_id)
|
|
345
|
+
console.log(`Repo memory: ${result.diff_memory_packet_id}`);
|
|
346
|
+
if (result.branch_summary_path)
|
|
347
|
+
console.log(`Branch summary: ${result.branch_summary_path}`);
|
|
348
|
+
if (result.review_artifact_path)
|
|
349
|
+
console.log(`Review artifact: ${result.review_artifact_path}`);
|
|
350
|
+
if (result.warnings.length)
|
|
351
|
+
console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
|
|
352
|
+
if (result.errors.length)
|
|
353
|
+
console.log(`Errors:\n${result.errors.map((error) => ` - ${error}`).join("\n")}`);
|
|
354
|
+
if (!result.ok)
|
|
355
|
+
process.exit(2);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (action === "check") {
|
|
359
|
+
const result = (0, kernel_js_1.prCheck)(projectArg(args));
|
|
360
|
+
if (args.includes("--json")) {
|
|
361
|
+
console.log(JSON.stringify(result, null, 2));
|
|
362
|
+
if (!result.ok)
|
|
363
|
+
process.exit(2);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
console.log(`PR memory check for ${result.project_dir}`);
|
|
367
|
+
console.log(`Branch: ${result.branch ?? "(detached)"}`);
|
|
368
|
+
console.log(`Changed files: ${result.changed_files.length}`);
|
|
369
|
+
console.log(`Memory packet changes: ${result.memory_packet_changes.length}`);
|
|
370
|
+
console.log(`Code graph current: ${result.code_graph_current ? "yes" : "no"}`);
|
|
371
|
+
console.log(`Memory graph current: ${result.memory_graph_current ? "yes" : "no"}`);
|
|
372
|
+
console.log(`Stale packets: ${result.stale_packets.length}`);
|
|
373
|
+
if (result.warnings.length)
|
|
374
|
+
console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
|
|
375
|
+
if (result.errors.length)
|
|
376
|
+
console.log(`Errors:\n${result.errors.map((error) => ` - ${error}`).join("\n")}`);
|
|
377
|
+
console.log(`Required actions:\n${result.required_actions.map((action) => ` - ${action}`).join("\n")}`);
|
|
378
|
+
if (!result.ok)
|
|
379
|
+
process.exit(2);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
usage();
|
|
383
|
+
}
|
|
384
|
+
if (command === "upgrade") {
|
|
385
|
+
const commandLine = "npm install -g @kage-core/kage-graph-mcp@latest";
|
|
386
|
+
if (args.includes("--dry-run")) {
|
|
387
|
+
console.log(commandLine);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
console.log(`Running: ${commandLine}`);
|
|
391
|
+
(0, node_child_process_1.execFileSync)("npm", ["install", "-g", "@kage-core/kage-graph-mcp@latest"], { stdio: "inherit" });
|
|
392
|
+
console.log("Kage upgraded. Restart Codex or Claude Code so the MCP process reloads the new package.");
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
299
395
|
if (command === "graph") {
|
|
300
396
|
const query = firstPositional(args);
|
|
301
397
|
if (query) {
|
package/dist/index.js
CHANGED
|
@@ -154,6 +154,39 @@ function listTools() {
|
|
|
154
154
|
required: ["project_dir"],
|
|
155
155
|
},
|
|
156
156
|
},
|
|
157
|
+
{
|
|
158
|
+
name: "kage_refresh",
|
|
159
|
+
description: "Rebuild repo indexes, code graph, memory graph, metrics, and stale-memory metadata. Agents should run this after meaningful file changes and before PR checks.",
|
|
160
|
+
inputSchema: {
|
|
161
|
+
type: "object",
|
|
162
|
+
properties: {
|
|
163
|
+
project_dir: { type: "string" },
|
|
164
|
+
},
|
|
165
|
+
required: ["project_dir"],
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "kage_pr_summarize",
|
|
170
|
+
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.",
|
|
171
|
+
inputSchema: {
|
|
172
|
+
type: "object",
|
|
173
|
+
properties: {
|
|
174
|
+
project_dir: { type: "string" },
|
|
175
|
+
},
|
|
176
|
+
required: ["project_dir"],
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: "kage_pr_check",
|
|
181
|
+
description: "Check whether repo memory, code graph, memory graph, and stale-memory state are ready for merge.",
|
|
182
|
+
inputSchema: {
|
|
183
|
+
type: "object",
|
|
184
|
+
properties: {
|
|
185
|
+
project_dir: { type: "string" },
|
|
186
|
+
},
|
|
187
|
+
required: ["project_dir"],
|
|
188
|
+
},
|
|
189
|
+
},
|
|
157
190
|
{
|
|
158
191
|
name: "kage_quality",
|
|
159
192
|
description: "Return memory quality metrics: useful memory ratio, duplicate burden, stale/wrong feedback, evidence coverage, path grounding, and review queue size.",
|
|
@@ -595,6 +628,27 @@ async function callTool(name, args) {
|
|
|
595
628
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
596
629
|
};
|
|
597
630
|
}
|
|
631
|
+
if (name === "kage_refresh") {
|
|
632
|
+
const result = (0, kernel_js_1.refreshProject)(String(args?.project_dir ?? ""));
|
|
633
|
+
return {
|
|
634
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
635
|
+
isError: !result.ok,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
if (name === "kage_pr_summarize") {
|
|
639
|
+
const result = (0, kernel_js_1.prSummarize)(String(args?.project_dir ?? ""));
|
|
640
|
+
return {
|
|
641
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
642
|
+
isError: !result.ok,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
if (name === "kage_pr_check") {
|
|
646
|
+
const result = (0, kernel_js_1.prCheck)(String(args?.project_dir ?? ""));
|
|
647
|
+
return {
|
|
648
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
649
|
+
isError: !result.ok,
|
|
650
|
+
};
|
|
651
|
+
}
|
|
598
652
|
if (name === "kage_quality") {
|
|
599
653
|
const result = (0, kernel_js_1.qualityReport)(String(args?.project_dir ?? ""));
|
|
600
654
|
return {
|
package/dist/kernel.js
CHANGED
|
@@ -66,6 +66,7 @@ exports.buildCodeGraph = buildCodeGraph;
|
|
|
66
66
|
exports.buildKnowledgeGraph = buildKnowledgeGraph;
|
|
67
67
|
exports.buildIndexes = buildIndexes;
|
|
68
68
|
exports.indexProject = indexProject;
|
|
69
|
+
exports.refreshProject = refreshProject;
|
|
69
70
|
exports.installAgentPolicy = installAgentPolicy;
|
|
70
71
|
exports.recall = recall;
|
|
71
72
|
exports.queryCodeGraph = queryCodeGraph;
|
|
@@ -86,6 +87,8 @@ exports.distillSession = distillSession;
|
|
|
86
87
|
exports.proposeFromDiff = proposeFromDiff;
|
|
87
88
|
exports.buildBranchOverlay = buildBranchOverlay;
|
|
88
89
|
exports.createReviewArtifact = createReviewArtifact;
|
|
90
|
+
exports.prSummarize = prSummarize;
|
|
91
|
+
exports.prCheck = prCheck;
|
|
89
92
|
exports.exportPublicBundle = exportPublicBundle;
|
|
90
93
|
exports.orgStatus = orgStatus;
|
|
91
94
|
exports.orgUploadPacket = orgUploadPacket;
|
|
@@ -177,11 +180,23 @@ Keep captures concise and future-facing. Do not store raw transcripts.
|
|
|
177
180
|
|
|
178
181
|
## End-Of-Task Proposal
|
|
179
182
|
|
|
180
|
-
|
|
183
|
+
After meaningful file changes, call \`kage_refresh\` so indexes, code graph,
|
|
184
|
+
memory graph, metrics, and stale-memory checks are current.
|
|
181
185
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
186
|
+
Before finishing a task that changed files, call \`kage_pr_summarize\` or
|
|
187
|
+
\`kage_propose_from_diff\`, then call \`kage_pr_check\`.
|
|
188
|
+
|
|
189
|
+
\`kage_pr_summarize\` writes a branch review summary and a repo-local
|
|
190
|
+
change-memory packet. \`kage_pr_check\` verifies validation, graph freshness,
|
|
191
|
+
stale packets, and whether repo memory changed with the branch. If the check
|
|
192
|
+
fails, explain the required actions instead of hiding the failure. Git or PR
|
|
193
|
+
review is the repo-level review boundary.
|
|
194
|
+
|
|
195
|
+
## Package Updates
|
|
196
|
+
|
|
197
|
+
If the user asks to update Kage, run \`kage upgrade\`, then verify setup with
|
|
198
|
+
\`kage setup verify-agent --agent <agent> --project <repo>\`. Tell the user to
|
|
199
|
+
restart the agent when MCP tools need to reload.
|
|
185
200
|
|
|
186
201
|
## Feedback
|
|
187
202
|
|
|
@@ -207,7 +222,9 @@ For normal coding tasks:
|
|
|
207
222
|
4. \`kage_graph\` for remembered decisions, bugs, workflows, and conventions
|
|
208
223
|
5. Work on the task
|
|
209
224
|
6. \`kage_learn\` for concrete learnings
|
|
210
|
-
7. \`
|
|
225
|
+
7. \`kage_refresh\` after meaningful file changes
|
|
226
|
+
8. \`kage_pr_summarize\` or \`kage_propose_from_diff\` before the final response to create repo-local change memory
|
|
227
|
+
9. \`kage_pr_check\` before final handoff or merge readiness claims
|
|
211
228
|
|
|
212
229
|
For quick factual questions, \`kage_recall\` alone is enough. For status or demo requests, call \`kage_metrics\`.
|
|
213
230
|
${AGENTS_POLICY_END}
|
|
@@ -447,12 +464,41 @@ function packetFeedbackScore(packet) {
|
|
|
447
464
|
const quality = packet.quality;
|
|
448
465
|
return Number(quality.votes_up ?? 0) * 2 - Number(quality.votes_down ?? 0) * 3 - Number(quality.reports_stale ?? 0) * 4;
|
|
449
466
|
}
|
|
467
|
+
function meaningfulMemoryPath(path) {
|
|
468
|
+
return path !== "root" && path !== "." && !isNoisePath(path);
|
|
469
|
+
}
|
|
470
|
+
function staleMemoryReasons(projectDir, packet) {
|
|
471
|
+
const reasons = [];
|
|
472
|
+
const quality = packet.quality;
|
|
473
|
+
const freshness = packet.freshness;
|
|
474
|
+
if (packet.status === "deprecated" || packet.status === "superseded") {
|
|
475
|
+
reasons.push(`packet status is ${packet.status}`);
|
|
476
|
+
}
|
|
477
|
+
if (Number(quality.reports_stale ?? 0) > 0) {
|
|
478
|
+
reasons.push("user or agent reported this memory stale");
|
|
479
|
+
}
|
|
480
|
+
const ttlDays = Number(freshness.ttl_days ?? freshness.ttlDays ?? 0);
|
|
481
|
+
const verifiedAt = Date.parse(String(freshness.last_verified_at ?? packet.updated_at ?? packet.created_at));
|
|
482
|
+
if (Number.isFinite(ttlDays) && ttlDays > 0 && Number.isFinite(verifiedAt)) {
|
|
483
|
+
const ageDays = (Date.now() - verifiedAt) / (1000 * 60 * 60 * 24);
|
|
484
|
+
if (ageDays > ttlDays)
|
|
485
|
+
reasons.push(`freshness ttl expired (${Math.floor(ageDays)}d old, ttl ${ttlDays}d)`);
|
|
486
|
+
}
|
|
487
|
+
const paths = packet.paths.filter(meaningfulMemoryPath);
|
|
488
|
+
const missingPaths = paths.filter((path) => !(0, node_fs_1.existsSync)((0, node_path_1.join)(projectDir, path)));
|
|
489
|
+
if (paths.length > 0 && missingPaths.length === paths.length) {
|
|
490
|
+
reasons.push(`all referenced paths are missing: ${missingPaths.slice(0, 4).join(", ")}`);
|
|
491
|
+
}
|
|
492
|
+
else if (missingPaths.length > 0) {
|
|
493
|
+
reasons.push(`some referenced paths are missing: ${missingPaths.slice(0, 4).join(", ")}`);
|
|
494
|
+
}
|
|
495
|
+
return unique(reasons);
|
|
496
|
+
}
|
|
450
497
|
function classifyPacket(projectDir, packet) {
|
|
451
498
|
const quality = evaluateMemoryQuality(projectDir, packet);
|
|
452
499
|
const score = Number(quality.score);
|
|
453
500
|
const duplicates = quality.duplicate_candidates;
|
|
454
|
-
|
|
455
|
-
if (Number(q.reports_stale ?? 0) > 0 || packet.status === "deprecated" || packet.status === "superseded")
|
|
501
|
+
if (staleMemoryReasons(projectDir, packet).length)
|
|
456
502
|
return "stale";
|
|
457
503
|
if (duplicates.length)
|
|
458
504
|
return "duplicate";
|
|
@@ -522,11 +568,17 @@ function evaluateMemoryQuality(projectDir, packet) {
|
|
|
522
568
|
score -= 18;
|
|
523
569
|
risks.push("possible duplicate memory");
|
|
524
570
|
}
|
|
571
|
+
const staleReasons = staleMemoryReasons(projectDir, packet);
|
|
572
|
+
if (staleReasons.length) {
|
|
573
|
+
score -= 22;
|
|
574
|
+
risks.push(...staleReasons);
|
|
575
|
+
}
|
|
525
576
|
return {
|
|
526
577
|
score: Math.max(0, Math.min(100, score)),
|
|
527
578
|
reasons,
|
|
528
579
|
risks,
|
|
529
580
|
duplicate_candidates: duplicates,
|
|
581
|
+
stale_reasons: staleReasons,
|
|
530
582
|
estimated_tokens_saved: Math.max(40, estimateTokens(packet.body) * 2),
|
|
531
583
|
};
|
|
532
584
|
}
|
|
@@ -769,6 +821,17 @@ function loadPacketsFromDir(dir) {
|
|
|
769
821
|
.sort()
|
|
770
822
|
.map((name) => readJson((0, node_path_1.join)(dir, name)));
|
|
771
823
|
}
|
|
824
|
+
function loadPacketEntriesFromDir(dir) {
|
|
825
|
+
if (!(0, node_fs_1.existsSync)(dir))
|
|
826
|
+
return [];
|
|
827
|
+
return (0, node_fs_1.readdirSync)(dir)
|
|
828
|
+
.filter((name) => name.endsWith(".json"))
|
|
829
|
+
.sort()
|
|
830
|
+
.map((name) => {
|
|
831
|
+
const path = (0, node_path_1.join)(dir, name);
|
|
832
|
+
return { path, packet: readJson(path) };
|
|
833
|
+
});
|
|
834
|
+
}
|
|
772
835
|
function loadApprovedPackets(projectDir) {
|
|
773
836
|
return loadPacketsFromDir(packetsDir(projectDir)).filter((packet) => packet.status === "approved");
|
|
774
837
|
}
|
|
@@ -837,14 +900,15 @@ function isNoisePath(filePath) {
|
|
|
837
900
|
function parsePorcelainStatus(status) {
|
|
838
901
|
return unique(status
|
|
839
902
|
.split(/\r?\n/)
|
|
840
|
-
.map(
|
|
841
|
-
const raw = line.length > 2 && line[2] === " " ? line.slice(3) : line.slice(2);
|
|
842
|
-
return raw.trim();
|
|
843
|
-
})
|
|
903
|
+
.map(parsePorcelainPath)
|
|
844
904
|
.map((path) => path.replace(/^.* -> /, ""))
|
|
845
905
|
.filter(Boolean)
|
|
846
906
|
.filter((path) => !shouldSkipRepoMemoryPath(path))).sort();
|
|
847
907
|
}
|
|
908
|
+
function parsePorcelainPath(line) {
|
|
909
|
+
const raw = line.length > 2 && line[2] === " " ? line.slice(3) : line.slice(2);
|
|
910
|
+
return raw.trim();
|
|
911
|
+
}
|
|
848
912
|
function shouldSkipRepoMemoryPath(relativePath) {
|
|
849
913
|
return isNoisePath(relativePath) || shouldSkipCodePath(relativePath);
|
|
850
914
|
}
|
|
@@ -2342,6 +2406,104 @@ function indexProject(projectDir) {
|
|
|
2342
2406
|
policyPath: (0, node_path_1.relative)(projectDir, policy.path),
|
|
2343
2407
|
};
|
|
2344
2408
|
}
|
|
2409
|
+
function staleSuggestedAction(reasons) {
|
|
2410
|
+
if (reasons.some((reason) => reason.includes("status is")))
|
|
2411
|
+
return "mark_stale";
|
|
2412
|
+
if (reasons.some((reason) => reason.includes("missing")))
|
|
2413
|
+
return "update";
|
|
2414
|
+
if (reasons.some((reason) => reason.includes("reported")))
|
|
2415
|
+
return "supersede";
|
|
2416
|
+
return "verify";
|
|
2417
|
+
}
|
|
2418
|
+
function staleFinding(packet, reasons) {
|
|
2419
|
+
return {
|
|
2420
|
+
id: packet.id,
|
|
2421
|
+
title: packet.title,
|
|
2422
|
+
type: packet.type,
|
|
2423
|
+
status: packet.status,
|
|
2424
|
+
paths: packet.paths,
|
|
2425
|
+
reasons,
|
|
2426
|
+
suggested_action: staleSuggestedAction(reasons),
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
function refreshPacketStaleness(projectDir) {
|
|
2430
|
+
const findings = [];
|
|
2431
|
+
let updated = 0;
|
|
2432
|
+
for (const entry of loadPacketEntriesFromDir(packetsDir(projectDir))) {
|
|
2433
|
+
const reasons = staleMemoryReasons(projectDir, entry.packet);
|
|
2434
|
+
const oldQuality = entry.packet.quality;
|
|
2435
|
+
const oldFreshness = entry.packet.freshness;
|
|
2436
|
+
let nextQuality;
|
|
2437
|
+
if (reasons.length) {
|
|
2438
|
+
const finding = staleFinding(entry.packet, reasons);
|
|
2439
|
+
findings.push(finding);
|
|
2440
|
+
nextQuality = {
|
|
2441
|
+
...oldQuality,
|
|
2442
|
+
stale: true,
|
|
2443
|
+
stale_reasons: reasons,
|
|
2444
|
+
suggested_action: finding.suggested_action,
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
else {
|
|
2448
|
+
const { stale: _stale, stale_reasons: _staleReasons, suggested_action: _suggestedAction, ...rest } = oldQuality;
|
|
2449
|
+
nextQuality = rest;
|
|
2450
|
+
}
|
|
2451
|
+
const nextFreshness = oldFreshness;
|
|
2452
|
+
const changed = JSON.stringify(oldQuality) !== JSON.stringify(nextQuality)
|
|
2453
|
+
|| JSON.stringify(oldFreshness) !== JSON.stringify(nextFreshness);
|
|
2454
|
+
if (changed) {
|
|
2455
|
+
writeJson(entry.path, {
|
|
2456
|
+
...entry.packet,
|
|
2457
|
+
freshness: nextFreshness,
|
|
2458
|
+
quality: nextQuality,
|
|
2459
|
+
updated_at: nowIso(),
|
|
2460
|
+
});
|
|
2461
|
+
updated += 1;
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
return { findings, updated };
|
|
2465
|
+
}
|
|
2466
|
+
function refreshProject(projectDir) {
|
|
2467
|
+
const index = indexProject(projectDir);
|
|
2468
|
+
const stale = refreshPacketStaleness(projectDir);
|
|
2469
|
+
const indexes = stale.updated > 0 ? buildIndexes(projectDir).map((path) => (0, node_path_1.relative)(projectDir, path)) : index.indexes;
|
|
2470
|
+
const validation = validateProject(projectDir);
|
|
2471
|
+
const metrics = kageMetrics(projectDir);
|
|
2472
|
+
const nextActions = [];
|
|
2473
|
+
if (stale.findings.length)
|
|
2474
|
+
nextActions.push("Update, verify, or supersede stale repo memories before relying on them.");
|
|
2475
|
+
if (!validation.ok)
|
|
2476
|
+
nextActions.push("Fix validation errors before merging or sharing memory.");
|
|
2477
|
+
if (validation.warnings.length)
|
|
2478
|
+
nextActions.push("Review validation warnings for grounding, indexes, or generated artifacts.");
|
|
2479
|
+
if (!nextActions.length)
|
|
2480
|
+
nextActions.push("Repo memory, code graph, and indexes are current.");
|
|
2481
|
+
return {
|
|
2482
|
+
ok: validation.ok,
|
|
2483
|
+
project_dir: projectDir,
|
|
2484
|
+
generated_at: nowIso(),
|
|
2485
|
+
index,
|
|
2486
|
+
validation,
|
|
2487
|
+
metrics,
|
|
2488
|
+
stale_packets: stale.findings,
|
|
2489
|
+
updated_packets: stale.updated,
|
|
2490
|
+
indexes,
|
|
2491
|
+
code_graph: {
|
|
2492
|
+
files: metrics.code_graph.files,
|
|
2493
|
+
symbols: metrics.code_graph.symbols,
|
|
2494
|
+
imports: metrics.code_graph.imports,
|
|
2495
|
+
calls: metrics.code_graph.calls,
|
|
2496
|
+
routes: metrics.code_graph.routes,
|
|
2497
|
+
tests: metrics.code_graph.tests,
|
|
2498
|
+
},
|
|
2499
|
+
memory_graph: {
|
|
2500
|
+
entities: metrics.memory_graph.entities,
|
|
2501
|
+
edges: metrics.memory_graph.edges,
|
|
2502
|
+
episodes: metrics.memory_graph.episodes,
|
|
2503
|
+
},
|
|
2504
|
+
next_actions: nextActions,
|
|
2505
|
+
};
|
|
2506
|
+
}
|
|
2345
2507
|
function installAgentPolicy(projectDir) {
|
|
2346
2508
|
const agentsPath = (0, node_path_1.join)(projectDir, "AGENTS.md");
|
|
2347
2509
|
const claudePath = (0, node_path_1.join)(projectDir, "CLAUDE.md");
|
|
@@ -3266,22 +3428,42 @@ Before making code changes or answering implementation questions:
|
|
|
3266
3428
|
3. Call kage_code_graph for file, symbol, route, test, or dependency questions.
|
|
3267
3429
|
4. Call kage_graph for decisions, bugs, workflows, and conventions.
|
|
3268
3430
|
When you learn something reusable: kage_learn.
|
|
3269
|
-
|
|
3431
|
+
After meaningful file changes: kage_refresh.
|
|
3432
|
+
Before finishing a task that changed files: kage_pr_summarize or kage_propose_from_diff, then kage_pr_check.
|
|
3270
3433
|
If recalled memory helped: kage_feedback helpful. If wrong or stale: kage_feedback wrong or stale."
|
|
3271
3434
|
fi
|
|
3272
3435
|
|
|
3273
3436
|
KAGE_MSG="$POLICY" python3 -c "import json,os; print(json.dumps({'systemMessage': os.environ['KAGE_MSG']}))"
|
|
3437
|
+
`;
|
|
3438
|
+
const stopHookScript = `#!/usr/bin/env bash
|
|
3439
|
+
# Kage Stop hook — best-effort repo memory refresh before Claude Code finishes.
|
|
3440
|
+
# Silent if Kage is not initialized in the current project or no git changes exist.
|
|
3441
|
+
set -euo pipefail
|
|
3442
|
+
|
|
3443
|
+
PAYLOAD="$(cat || true)"
|
|
3444
|
+
CWD="$(printf "%s" "$PAYLOAD" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('cwd',''))" 2>/dev/null || echo "")"
|
|
3445
|
+
|
|
3446
|
+
[[ -d "$CWD/.agent_memory" ]] || exit 0
|
|
3447
|
+
command -v kage >/dev/null 2>&1 || exit 0
|
|
3448
|
+
|
|
3449
|
+
if git -C "$CWD" status --porcelain -uall >/dev/null 2>&1 && [[ -n "$(git -C "$CWD" status --porcelain -uall)" ]]; then
|
|
3450
|
+
kage refresh --project "$CWD" --json >/dev/null 2>&1 || true
|
|
3451
|
+
kage pr summarize --project "$CWD" --json >/dev/null 2>&1 || true
|
|
3452
|
+
fi
|
|
3453
|
+
|
|
3454
|
+
exit 0
|
|
3274
3455
|
`;
|
|
3275
3456
|
const settingsPath = (0, node_path_1.join)(home, ".claude", "settings.json");
|
|
3276
3457
|
const hookEntry = {
|
|
3277
3458
|
hooks: {
|
|
3278
3459
|
SessionStart: [{ matcher: "", hooks: [{ type: "command", command: "bash ~/.claude/kage/hooks/session-start.sh", timeout: 5 }] }],
|
|
3460
|
+
Stop: [{ matcher: "", hooks: [{ type: "command", command: "bash ~/.claude/kage/hooks/stop.sh", timeout: 20 }] }],
|
|
3279
3461
|
},
|
|
3280
3462
|
};
|
|
3281
3463
|
setSnippet(path, JSON.stringify({ mcpServers: { kage: server } }, null, 2), [
|
|
3282
3464
|
"Add the MCP server to ~/.claude.json, then restart Claude Code.",
|
|
3283
3465
|
"alwaysLoad: true makes Kage tools immediately visible without requiring ToolSearch.",
|
|
3284
|
-
`Also create ${hookDir}/session-start.sh with the hook
|
|
3466
|
+
`Also create ${hookDir}/session-start.sh and ${hookDir}/stop.sh with the hook scripts and add SessionStart/Stop hooks to ~/.claude/settings.json.`,
|
|
3285
3467
|
"Run `kage init --project <repo>` inside each repo to install the ambient memory policy.",
|
|
3286
3468
|
], true);
|
|
3287
3469
|
if (options.write) {
|
|
@@ -3289,6 +3471,7 @@ KAGE_MSG="$POLICY" python3 -c "import json,os; print(json.dumps({'systemMessage'
|
|
|
3289
3471
|
// Install the ambient session-start hook
|
|
3290
3472
|
(0, node_fs_1.mkdirSync)(hookDir, { recursive: true });
|
|
3291
3473
|
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(hookDir, "session-start.sh"), hookScript, { mode: 0o755 });
|
|
3474
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(hookDir, "stop.sh"), stopHookScript, { mode: 0o755 });
|
|
3292
3475
|
upsertJsonSettings(settingsPath, hookEntry);
|
|
3293
3476
|
result.wrote = true;
|
|
3294
3477
|
}
|
|
@@ -3714,11 +3897,25 @@ function distillSession(projectDir, sessionId) {
|
|
|
3714
3897
|
function createDiffChangeMemory(projectDir, summary) {
|
|
3715
3898
|
const branch = summary.branch ?? "detached";
|
|
3716
3899
|
const head = summary.head ?? "unknown";
|
|
3717
|
-
const fingerprint = (0, node_crypto_1.createHash)("sha256")
|
|
3718
|
-
.update(`${branch}\n${head}\n${summary.changed_files.join("\n")}\n${summary.diff_stat}`)
|
|
3719
|
-
.digest("hex")
|
|
3720
|
-
.slice(0, 10);
|
|
3721
3900
|
const title = `Change memory: ${branch}`;
|
|
3901
|
+
// Remove any stale change-memory packets for this branch so propose_from_diff
|
|
3902
|
+
// replaces rather than accumulates. The stable ID (branch-only, no fingerprint)
|
|
3903
|
+
// makes writePacket idempotent going forward; this sweep handles packets that
|
|
3904
|
+
// were written with the old fingerprint-based ID.
|
|
3905
|
+
const stalePrefix = `workflow-${slugify(title)}-`;
|
|
3906
|
+
const stableId = makePacketId(projectDir, "workflow", title);
|
|
3907
|
+
const stableFileName = `${stalePrefix}${(0, node_crypto_1.createHash)("sha256").update(stableId).digest("hex").slice(0, 8)}.json`;
|
|
3908
|
+
try {
|
|
3909
|
+
const existing = (0, node_fs_1.readdirSync)(packetsDir(projectDir)).filter((name) => name.startsWith(stalePrefix) && name !== stableFileName);
|
|
3910
|
+
for (const name of existing) {
|
|
3911
|
+
const stale = (0, node_path_1.join)(packetsDir(projectDir), name);
|
|
3912
|
+
const stalePacket = readJson(stale);
|
|
3913
|
+
if (stalePacket?.type === "workflow" && stalePacket?.title === title) {
|
|
3914
|
+
(0, node_fs_1.unlinkSync)(stale);
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
catch { /* non-fatal */ }
|
|
3722
3919
|
const verifyCommands = npmScriptCommands(projectDir)
|
|
3723
3920
|
.filter((command) => /(test|check|lint|build|type|verify)/i.test(command))
|
|
3724
3921
|
.slice(0, 8);
|
|
@@ -3752,7 +3949,7 @@ function createDiffChangeMemory(projectDir, summary) {
|
|
|
3752
3949
|
const now = nowIso();
|
|
3753
3950
|
const packet = {
|
|
3754
3951
|
schema_version: exports.PACKET_SCHEMA_VERSION,
|
|
3755
|
-
id:
|
|
3952
|
+
id: stableId,
|
|
3756
3953
|
title,
|
|
3757
3954
|
summary: `Repo-local context for ${summary.changed_files.length} changed repo path${summary.changed_files.length === 1 ? "" : "s"} on ${branch}.`,
|
|
3758
3955
|
body,
|
|
@@ -3847,7 +4044,7 @@ function proposeFromDiff(projectDir) {
|
|
|
3847
4044
|
}
|
|
3848
4045
|
function buildBranchOverlay(projectDir) {
|
|
3849
4046
|
ensureMemoryDirs(projectDir);
|
|
3850
|
-
const status = readGit(projectDir, ["status", "--porcelain"]) ?? "";
|
|
4047
|
+
const status = readGit(projectDir, ["status", "--porcelain", "-uall"]) ?? "";
|
|
3851
4048
|
const overlay = {
|
|
3852
4049
|
schema_version: 1,
|
|
3853
4050
|
project_dir: projectDir,
|
|
@@ -3925,6 +4122,91 @@ function createReviewArtifact(projectDir) {
|
|
|
3925
4122
|
(0, node_fs_1.writeFileSync)(path, `${lines.join("\n").trim()}\n`, "utf8");
|
|
3926
4123
|
return { path, pending: pending.length };
|
|
3927
4124
|
}
|
|
4125
|
+
function graphIsCurrent(projectDir, relativePath, head) {
|
|
4126
|
+
const path = (0, node_path_1.join)(projectDir, relativePath);
|
|
4127
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
4128
|
+
return false;
|
|
4129
|
+
if (!head)
|
|
4130
|
+
return true;
|
|
4131
|
+
try {
|
|
4132
|
+
const graph = readJson(path);
|
|
4133
|
+
return graph.repo_state?.head === head;
|
|
4134
|
+
}
|
|
4135
|
+
catch {
|
|
4136
|
+
return false;
|
|
4137
|
+
}
|
|
4138
|
+
}
|
|
4139
|
+
function prSummarize(projectDir) {
|
|
4140
|
+
ensureMemoryDirs(projectDir);
|
|
4141
|
+
const proposal = proposeFromDiff(projectDir);
|
|
4142
|
+
const artifact = createReviewArtifact(projectDir);
|
|
4143
|
+
const validation = validateProject(projectDir);
|
|
4144
|
+
const warnings = [...validation.warnings];
|
|
4145
|
+
if (!proposal.ok)
|
|
4146
|
+
warnings.push(...proposal.errors);
|
|
4147
|
+
return {
|
|
4148
|
+
ok: proposal.ok && validation.ok,
|
|
4149
|
+
project_dir: projectDir,
|
|
4150
|
+
branch: gitBranch(projectDir),
|
|
4151
|
+
head: gitHead(projectDir),
|
|
4152
|
+
changed_files: proposal.changedFiles,
|
|
4153
|
+
diff_memory_packet_id: proposal.packet?.id,
|
|
4154
|
+
diff_memory_packet_path: proposal.packetPath,
|
|
4155
|
+
branch_summary_path: proposal.path,
|
|
4156
|
+
review_artifact_path: artifact.path,
|
|
4157
|
+
validation,
|
|
4158
|
+
errors: validation.errors,
|
|
4159
|
+
warnings,
|
|
4160
|
+
};
|
|
4161
|
+
}
|
|
4162
|
+
function prCheck(projectDir) {
|
|
4163
|
+
ensureMemoryDirs(projectDir);
|
|
4164
|
+
const overlay = buildBranchOverlay(projectDir);
|
|
4165
|
+
const rawStatus = readGit(projectDir, ["status", "--porcelain", "-uall"]) ?? "";
|
|
4166
|
+
const validation = validateProject(projectDir);
|
|
4167
|
+
const stalePackets = loadPacketsFromDir(packetsDir(projectDir))
|
|
4168
|
+
.map((packet) => ({ packet, reasons: staleMemoryReasons(projectDir, packet) }))
|
|
4169
|
+
.filter((entry) => entry.reasons.length)
|
|
4170
|
+
.map((entry) => staleFinding(entry.packet, entry.reasons));
|
|
4171
|
+
const memoryPacketChanges = unique(rawStatus
|
|
4172
|
+
.split(/\r?\n/)
|
|
4173
|
+
.map(parsePorcelainPath)
|
|
4174
|
+
.map((path) => path.replace(/^.* -> /, ""))
|
|
4175
|
+
.filter((path) => path.startsWith(".agent_memory/packets/") && path.endsWith(".json"))).sort();
|
|
4176
|
+
const codeGraphCurrent = graphIsCurrent(projectDir, ".agent_memory/code_graph/graph.json", overlay.head);
|
|
4177
|
+
const memoryGraphCurrent = graphIsCurrent(projectDir, ".agent_memory/graph/graph.json", overlay.head);
|
|
4178
|
+
const errors = [...validation.errors];
|
|
4179
|
+
const warnings = [...validation.warnings];
|
|
4180
|
+
const requiredActions = [];
|
|
4181
|
+
if (stalePackets.length) {
|
|
4182
|
+
errors.push(`${stalePackets.length} stale memory packet(s) require update, verification, or supersession.`);
|
|
4183
|
+
requiredActions.push("Run kage refresh, then update or supersede stale packets.");
|
|
4184
|
+
}
|
|
4185
|
+
if (!codeGraphCurrent || !memoryGraphCurrent) {
|
|
4186
|
+
errors.push("Generated graph artifacts are missing or not current for this branch head.");
|
|
4187
|
+
requiredActions.push("Run kage refresh --project <dir> before merge.");
|
|
4188
|
+
}
|
|
4189
|
+
if (!memoryPacketChanges.length && overlay.changed_files.some((path) => !path.startsWith(".agent_memory/"))) {
|
|
4190
|
+
warnings.push("No repo memory packet changed for this branch. If durable knowledge was learned, run kage propose --from-diff or kage learn.");
|
|
4191
|
+
}
|
|
4192
|
+
if (!requiredActions.length)
|
|
4193
|
+
requiredActions.push("PR memory and graph checks passed.");
|
|
4194
|
+
return {
|
|
4195
|
+
ok: errors.length === 0,
|
|
4196
|
+
project_dir: projectDir,
|
|
4197
|
+
branch: overlay.branch,
|
|
4198
|
+
head: overlay.head,
|
|
4199
|
+
changed_files: overlay.changed_files,
|
|
4200
|
+
validation,
|
|
4201
|
+
stale_packets: stalePackets,
|
|
4202
|
+
memory_packet_changes: memoryPacketChanges,
|
|
4203
|
+
code_graph_current: codeGraphCurrent,
|
|
4204
|
+
memory_graph_current: memoryGraphCurrent,
|
|
4205
|
+
errors,
|
|
4206
|
+
warnings,
|
|
4207
|
+
required_actions: requiredActions,
|
|
4208
|
+
};
|
|
4209
|
+
}
|
|
3928
4210
|
function exportPublicBundle(projectDir) {
|
|
3929
4211
|
ensureMemoryDirs(projectDir);
|
|
3930
4212
|
const candidates = loadPacketsFromDir(publicCandidatesDir(projectDir));
|