@kage-core/kage-graph-mcp 1.1.0 → 1.1.1

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 CHANGED
@@ -22,6 +22,7 @@ kage setup codex --project /path/to/repo --write
22
22
  kage setup claude-code --project /path/to/repo
23
23
  kage setup generic-mcp --project /path/to/repo
24
24
  kage setup doctor --project /path/to/repo
25
+ kage setup verify-agent --agent codex --project /path/to/repo
25
26
  kage init --project /path/to/repo
26
27
  kage policy --project /path/to/repo
27
28
  kage doctor --project /path/to/repo
@@ -121,14 +122,14 @@ memory, validation status, estimated tokens saved per recall, duplicate
121
122
  candidates, average memory quality, and a readiness score.
122
123
 
123
124
  Review artifacts include memory quality reasons, risks, duplicate candidates,
124
- and estimated token savings so reviewers can approve, reject, or merge pending
125
- memory with less manual inspection.
125
+ and estimated token savings for legacy pending/quarantine packets and promotion
126
+ review.
126
127
 
127
128
  `kage observe` is the automatic-capture primitive for agent hooks and daemon
128
129
  clients. It accepts session, prompt, tool, file-change, command, test, and
129
130
  session-end events; deduplicates them; scans for secrets and PII; and stores raw
130
- observations locally only. `kage distill` turns those observations into pending
131
- packets with observation session source refs. It never approves or publishes
131
+ observations locally only. `kage distill` turns useful observations into
132
+ repo-local packets with observation session source refs. It never publishes
132
133
  memory.
133
134
 
134
135
  `kage recall --explain --json` exposes the hybrid scoring explanation used for
@@ -149,19 +150,22 @@ is deterministic text plus graph retrieval.
149
150
  - `POST /kage/distill`
150
151
 
151
152
  The daemon is not required for stdio MCP or CLI use; it exists for agents and
152
- workflows that need REST, live observation ingestion, or Aider-style scripting.
153
+ workflows that need REST, live observation ingestion, Aider-style scripting, or
154
+ automatic index refresh. On start it indexes once, then watches repo file changes
155
+ and refreshes generated graph/index artifacts after a short debounce.
153
156
 
154
157
  ## Local Graph Viewer
155
158
 
156
159
  Run `kage viewer --project <repo>` to start the local terminal console. It
157
160
  serves the viewer and the selected repo's `.agent_memory/` files from the same
158
161
  localhost server, then prints a URL that auto-loads memory graph, code graph,
159
- metrics, review artifact, and pending packets. Manual JSON selection remains as
162
+ metrics, review artifact, and pending packets when present. Manual JSON selection remains as
160
163
  a fallback, not the main workflow.
161
164
 
162
165
  The viewer renders nodes and relations in SVG, supports memory/code/combined
163
- modes, filters by type and relation, displays metrics, shows the pending review
164
- queue, and marks review risks such as low-confidence or missing-evidence edges.
166
+ modes, filters by type and relation, displays metrics, shows packets and pending
167
+ quarantine items when present, and marks risks such as low-confidence or
168
+ missing-evidence edges.
165
169
 
166
170
  For demos or local docs, the viewer also accepts URL params:
167
171
 
@@ -264,9 +268,14 @@ Before code changes or repo-specific answers:
264
268
  3. Call `kage_graph` with the user task as the query.
265
269
  4. Capture reusable learnings with `kage_learn` or `kage_capture`.
266
270
  5. Before finishing changed-file tasks, call `kage_propose_from_diff`.
267
- 6. Never approve or publish memory automatically.
271
+ 6. Never publish or promote org/global memory automatically.
268
272
  ```
269
273
 
274
+ Run `kage setup verify-agent --agent codex --project <repo>` after setup. The
275
+ CLI verifies config, policy, indexes, recall, and code graph. It intentionally
276
+ reports `restart_required` until the active agent can call the MCP
277
+ `kage_verify_agent` tool, which proves Kage is live inside that agent session.
278
+
270
279
  The official Codex MCP docs also support adding HTTP MCP servers with:
271
280
 
272
281
  ```bash
@@ -275,20 +284,21 @@ codex mcp list
275
284
  ```
276
285
 
277
286
  Kage currently runs as a local stdio MCP server, so the TOML form is the direct
278
- fit for this MVP.
287
+ fit for the current package.
279
288
 
280
289
  ## Safety Model
281
290
 
282
291
  - `kage_learn` is the preferred surface for actual session learning. It creates
283
- pending packets with explicit learning/evidence/verification text.
284
- - `kage_capture` only creates pending packets.
292
+ repo-local packets with explicit learning/evidence/verification text.
293
+ - `kage_capture` creates repo-local packets.
285
294
  - `kage_propose_from_diff` writes a branch review summary under
286
- `.agent_memory/review/` and a pending change-memory packet under
287
- `.agent_memory/pending/`. It becomes shared recall only after human approval.
295
+ `.agent_memory/review/` and a repo-local change-memory packet under
296
+ `.agent_memory/packets/`. Promotion beyond the repo still requires explicit
297
+ review.
288
298
  - `kage_promote_public_candidate` writes a local sanitized review candidate
289
299
  under `.agent_memory/public-candidates/`; it does not publish.
290
300
  - Registry recommendations never auto-install skills, docs, or MCP servers.
291
- - Shared approved memory still requires `kage review`.
301
+ - Org/global shared memory still requires explicit review.
292
302
  - Capture blocks obvious secrets, tokens, private URL credentials, bearer
293
303
  tokens, private keys, and email addresses before writing a packet.
294
304
  - Generated indexes are disposable and can be rebuilt from packets.
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ const node_process_1 = require("node:process");
6
6
  const daemon_js_1 = require("./daemon.js");
7
7
  const kernel_js_1 = require("./kernel.js");
8
8
  function usage() {
9
- console.log(`Kage Repo-Recall MVP
9
+ console.log(`Kage repo memory and code graph
10
10
 
11
11
  Usage:
12
12
  kage index --project <dir>
@@ -16,6 +16,7 @@ Usage:
16
16
  kage setup list
17
17
  kage setup <agent> --project <dir> [--write] [--json]
18
18
  kage setup doctor --project <dir> [--json]
19
+ kage setup verify-agent --agent <agent> --project <dir> [--json]
19
20
  kage daemon start --project <dir> [--port 3111]
20
21
  kage daemon stop --project <dir>
21
22
  kage daemon status --project <dir> [--json]
@@ -49,6 +50,7 @@ Usage:
49
50
  kage org export --project <dir> --org <org> [--json]
50
51
  kage layered-recall "<query>" --project <dir> [--org <org>] [--global] [--json]
51
52
  kage global build --project <dir> [--org <org>] [--json]
53
+ kage changelog --project <dir> [--days <n>] [--json]
52
54
  kage review --project <dir>
53
55
  kage validate --project <dir>
54
56
 
@@ -196,6 +198,31 @@ async function main() {
196
198
  }
197
199
  return;
198
200
  }
201
+ if (action === "verify-agent") {
202
+ const agent = takeArg(args, "--agent") ?? "codex";
203
+ if (!kernel_js_1.SETUP_AGENTS.includes(agent))
204
+ usage();
205
+ const result = (0, kernel_js_1.verifyAgentActivation)(agent, projectArg(args));
206
+ if (args.includes("--json")) {
207
+ console.log(JSON.stringify(result, null, 2));
208
+ return;
209
+ }
210
+ console.log(`Kage agent activation: ${result.agent}`);
211
+ console.log(`Status: ${result.status}`);
212
+ console.log(`Config: ${result.checks.config_mentions_kage ? "kage configured" : result.checks.config_present ? "config present, kage missing" : "missing"}${result.config_path ? ` (${result.config_path})` : ""}`);
213
+ console.log(`Policy: ${result.checks.policy_installed ? "installed" : "missing"}`);
214
+ console.log(`Indexes: ${result.checks.indexes_present ? "present" : "missing"}`);
215
+ console.log(`Recall: ${result.checks.recall_works ? "ok" : "failed"} (${result.recall_preview})`);
216
+ console.log(`Code graph: ${result.checks.code_graph_works ? "ok" : "failed"} (${result.code_graph_summary})`);
217
+ console.log(`Active MCP tool: ${result.checks.mcp_tool_reachable ? "reachable" : "not verified from CLI"}`);
218
+ if (result.warnings.length)
219
+ console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
220
+ if (result.next_steps.length)
221
+ console.log(`Next steps:\n${result.next_steps.map((step) => ` - ${step}`).join("\n")}`);
222
+ if (result.status !== "ready")
223
+ process.exitCode = 2;
224
+ return;
225
+ }
199
226
  if (!action || !kernel_js_1.SETUP_AGENTS.includes(action))
200
227
  usage();
201
228
  const result = (0, kernel_js_1.setupAgent)(action, projectArg(args), { write: args.includes("--write") });
@@ -440,7 +467,7 @@ async function main() {
440
467
  process.exit(2);
441
468
  }
442
469
  console.log(`Captured session learning: ${result.path}`);
443
- console.log(`Review with: kage review --project ${projectArg(args)}`);
470
+ console.log("Repo-local memory is written immediately. Promotion to org/global still requires explicit review.");
444
471
  return;
445
472
  }
446
473
  if (command === "propose") {
@@ -453,10 +480,9 @@ async function main() {
453
480
  }
454
481
  console.log(`Wrote branch review summary: ${result.path}`);
455
482
  if (result.packetPath)
456
- console.log(`Captured pending change memory: ${result.packetPath}`);
483
+ console.log(`Captured repo-local change memory: ${result.packetPath}`);
457
484
  console.log(`Changed files: ${result.changedFiles.join(", ")}`);
458
- console.log(`Review memory: kage review --project ${projectArg(args)}`);
459
- console.log(`Review artifact: kage review-artifact --project ${projectArg(args)}`);
485
+ console.log("Use org/global promotion commands when this memory should leave the repo.");
460
486
  return;
461
487
  }
462
488
  if (command === "review-artifact") {
@@ -699,8 +725,39 @@ async function main() {
699
725
  console.error(`Capture blocked:\n${result.errors.map((error) => ` - ${error}`).join("\n")}`);
700
726
  process.exit(2);
701
727
  }
702
- console.log(`Captured pending packet: ${result.path}`);
703
- console.log(`Review with: kage review --project ${input.projectDir}`);
728
+ console.log(`Captured repo-local packet: ${result.path}`);
729
+ console.log("Repo-local memory is written immediately. Promotion to org/global still requires explicit review.");
730
+ return;
731
+ }
732
+ if (command === "changelog") {
733
+ const days = numberArg(args, "--days", 7);
734
+ const result = (0, kernel_js_1.changelog)(projectArg(args), days);
735
+ if (args.includes("--json")) {
736
+ console.log(JSON.stringify(result, null, 2));
737
+ return;
738
+ }
739
+ console.log(`Memory changelog: last ${result.days} day${result.days === 1 ? "" : "s"} — ${result.added.length} added, ${result.updated.length} updated, ${result.deprecated.length} deprecated (${result.total} total)`);
740
+ if (result.added.length > 0) {
741
+ console.log("\nNew packets:");
742
+ for (const entry of result.added) {
743
+ console.log(` + [${entry.type}] ${entry.title} (${entry.date.slice(0, 10)})`);
744
+ }
745
+ }
746
+ if (result.updated.length > 0) {
747
+ console.log("\nUpdated packets:");
748
+ for (const entry of result.updated) {
749
+ console.log(` ~ [${entry.type}] ${entry.title} (${entry.date.slice(0, 10)})`);
750
+ }
751
+ }
752
+ if (result.deprecated.length > 0) {
753
+ console.log("\nDeprecated packets:");
754
+ for (const entry of result.deprecated) {
755
+ console.log(` - [${entry.type}] ${entry.title} (${entry.date.slice(0, 10)})`);
756
+ }
757
+ }
758
+ if (result.total === 0) {
759
+ console.log("No memory activity in this period.");
760
+ }
704
761
  return;
705
762
  }
706
763
  if (command === "review") {
package/dist/daemon.js CHANGED
@@ -83,6 +83,10 @@ function daemonDoctor(projectDir) {
83
83
  }
84
84
  }
85
85
  const restPort = status?.rest_port ?? DEFAULT_REST_PORT;
86
+ const warnings = [
87
+ ...(running ? [] : ["Daemon is not running. Start it with `kage daemon start --project <repo>`."]),
88
+ ...(running && status && !status.index_watch ? ["Index file watcher is not active; run `kage index --project <repo>` after source changes."] : []),
89
+ ];
86
90
  return {
87
91
  configured: Boolean(status),
88
92
  running,
@@ -96,7 +100,7 @@ function daemonDoctor(projectDir) {
96
100
  `GET http://${DEFAULT_HOST}:${restPort}/kage/quality`,
97
101
  `GET http://${DEFAULT_HOST}:${restPort}/kage/benchmark`,
98
102
  ],
99
- warnings: running ? [] : ["Daemon is not running. Start it with `kage daemon start --project <repo>`."],
103
+ warnings,
100
104
  };
101
105
  }
102
106
  function stopDaemon(projectDir) {
@@ -116,6 +120,8 @@ async function startDaemon(projectDir, options = {}) {
116
120
  const restPort = options.restPort ?? DEFAULT_REST_PORT;
117
121
  const viewerPort = options.viewerPort ?? DEFAULT_VIEWER_PORT;
118
122
  (0, node_fs_1.mkdirSync)(daemonDir(projectDir), { recursive: true });
123
+ (0, kernel_js_1.indexProject)(projectDir);
124
+ let lastIndexedAt = new Date().toISOString();
119
125
  const status = {
120
126
  ok: true,
121
127
  project_dir: projectDir,
@@ -125,8 +131,41 @@ async function startDaemon(projectDir, options = {}) {
125
131
  viewer_port: viewerPort,
126
132
  started_at: new Date().toISOString(),
127
133
  status_path: statusPath(projectDir),
134
+ index_watch: false,
135
+ last_indexed_at: lastIndexedAt,
128
136
  };
129
137
  (0, node_fs_1.writeFileSync)(status.status_path, JSON.stringify(status, null, 2), "utf8");
138
+ let watcher = null;
139
+ let refreshTimer = null;
140
+ const refreshIndex = () => {
141
+ if (refreshTimer)
142
+ clearTimeout(refreshTimer);
143
+ refreshTimer = setTimeout(() => {
144
+ try {
145
+ (0, kernel_js_1.indexProject)(projectDir);
146
+ lastIndexedAt = new Date().toISOString();
147
+ status.last_indexed_at = lastIndexedAt;
148
+ (0, node_fs_1.writeFileSync)(status.status_path, JSON.stringify(status, null, 2), "utf8");
149
+ }
150
+ catch {
151
+ // Keep the daemon alive; doctor/status surfaces stale indexes separately.
152
+ }
153
+ }, 350);
154
+ };
155
+ try {
156
+ watcher = (0, node_fs_1.watch)(projectDir, { recursive: true }, (_event, filename) => {
157
+ const file = String(filename ?? "");
158
+ if (!file || file.includes("node_modules") || file.includes(".git") || file.includes(".agent_memory/indexes") || file.includes(".agent_memory/code_graph") || file.includes(".agent_memory/graph"))
159
+ return;
160
+ refreshIndex();
161
+ });
162
+ status.index_watch = true;
163
+ (0, node_fs_1.writeFileSync)(status.status_path, JSON.stringify(status, null, 2), "utf8");
164
+ }
165
+ catch {
166
+ status.index_watch = false;
167
+ (0, node_fs_1.writeFileSync)(status.status_path, JSON.stringify(status, null, 2), "utf8");
168
+ }
130
169
  const server = (0, node_http_1.createServer)(async (req, res) => {
131
170
  const url = new URL(req.url ?? "/", `http://${host}:${restPort}`);
132
171
  try {
@@ -135,6 +174,7 @@ async function startDaemon(projectDir, options = {}) {
135
174
  return;
136
175
  }
137
176
  if (req.method === "GET" && url.pathname === "/kage/status") {
177
+ status.last_indexed_at = lastIndexedAt;
138
178
  json(res, 200, status);
139
179
  return;
140
180
  }
@@ -176,6 +216,10 @@ async function startDaemon(projectDir, options = {}) {
176
216
  console.log(`Project: ${projectDir}`);
177
217
  console.log(`Status: ${status.status_path}`);
178
218
  process.on("SIGTERM", () => {
219
+ if (watcher)
220
+ watcher.close();
221
+ if (refreshTimer)
222
+ clearTimeout(refreshTimer);
179
223
  server.close(() => process.exit(0));
180
224
  });
181
225
  }
@@ -189,6 +233,14 @@ async function startViewer(projectDir, options = {}) {
189
233
  const metricsPath = (0, node_path_1.join)(projectRoot, ".agent_memory", "metrics.json");
190
234
  const reviewPath = (0, node_path_1.join)(projectRoot, ".agent_memory", "review", "memory-review.md");
191
235
  const pendingDir = (0, node_path_1.join)(projectRoot, ".agent_memory", "pending");
236
+ // Pre-generate metrics.json so the viewer can load it
237
+ try {
238
+ const metrics = (0, kernel_js_1.kageMetrics)(projectDir);
239
+ (0, node_fs_1.writeFileSync)(metricsPath, JSON.stringify(metrics, null, 2));
240
+ }
241
+ catch {
242
+ // non-fatal: viewer will show 404 for metrics if generation fails
243
+ }
192
244
  const url = `http://${host}:${port}/viewer/index.html?graph=${encodeURIComponent(graphPath)}&code=${encodeURIComponent(codePath)}&metrics=${encodeURIComponent(metricsPath)}&review=${encodeURIComponent(reviewPath)}&pending=${encodeURIComponent(pendingDir)}`;
193
245
  const server = (0, node_http_1.createServer)((req, res) => {
194
246
  const requestUrl = new URL(req.url ?? "/", `http://${host}:${port}`);
package/dist/index.js CHANGED
@@ -189,6 +189,18 @@ function listTools() {
189
189
  required: ["agent", "project_dir"],
190
190
  },
191
191
  },
192
+ {
193
+ name: "kage_verify_agent",
194
+ description: "Verify that Kage is truly active for the current agent: config, repo policy, indexes, recall, code graph, and this live MCP tool reachability.",
195
+ inputSchema: {
196
+ type: "object",
197
+ properties: {
198
+ agent: { type: "string", enum: kernel_js_1.SETUP_AGENTS },
199
+ project_dir: { type: "string" },
200
+ },
201
+ required: ["agent", "project_dir"],
202
+ },
203
+ },
192
204
  {
193
205
  name: "kage_graph_visual",
194
206
  description: "Export the repo-local Kage knowledge graph as Mermaid flowchart text for visual inspection.",
@@ -214,7 +226,7 @@ function listTools() {
214
226
  },
215
227
  {
216
228
  name: "kage_learn",
217
- description: "Capture an actual reusable learning from the current session as a pending memory packet. Prefer this over diff proposal when the agent knows what was learned.",
229
+ description: "Capture an actual reusable learning from the current session as repo-local memory. Prefer this over diff proposal when the agent knows what was learned.",
218
230
  inputSchema: {
219
231
  type: "object",
220
232
  properties: {
@@ -233,7 +245,7 @@ function listTools() {
233
245
  },
234
246
  {
235
247
  name: "kage_capture",
236
- description: "Create a pending repo-local Kage memory packet. This proposes memory only; human review is still required before sharing.",
248
+ description: "Create a repo-local Kage memory packet immediately. Org/global promotion still requires explicit human review.",
237
249
  inputSchema: {
238
250
  type: "object",
239
251
  properties: {
@@ -251,7 +263,7 @@ function listTools() {
251
263
  },
252
264
  {
253
265
  name: "kage_observe",
254
- description: "Store an automatic local observation event from an agent session. Observations are privacy-scanned, deduplicated, and never auto-approved or published.",
266
+ description: "Store an automatic local observation event from an agent session. Observations are privacy-scanned, deduplicated, and never published automatically.",
255
267
  inputSchema: {
256
268
  type: "object",
257
269
  properties: {
@@ -273,7 +285,7 @@ function listTools() {
273
285
  },
274
286
  {
275
287
  name: "kage_distill",
276
- description: "Distill stored observations for one session into pending memory candidates. Human review is still required before approved memory.",
288
+ description: "Distill stored observations for one session into repo-local memory candidates. Org/global promotion still requires explicit human review.",
277
289
  inputSchema: {
278
290
  type: "object",
279
291
  properties: {
@@ -443,7 +455,7 @@ function listTools() {
443
455
  },
444
456
  {
445
457
  name: "kage_propose_from_diff",
446
- description: "Create or update a branch review summary and pending change-memory packet from local git status and diff metadata. Human review is required before it becomes shared repo memory.",
458
+ description: "Create or update a branch review summary and repo-local change-memory packet from local git status and diff metadata. Org/global promotion still requires explicit human review.",
447
459
  inputSchema: {
448
460
  type: "object",
449
461
  properties: {
@@ -601,6 +613,12 @@ async function callTool(name, args) {
601
613
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
602
614
  };
603
615
  }
616
+ if (name === "kage_verify_agent") {
617
+ const result = (0, kernel_js_1.verifyAgentActivation)(String(args?.agent ?? ""), String(args?.project_dir ?? ""), { mcpToolReachable: true });
618
+ return {
619
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
620
+ };
621
+ }
604
622
  if (name === "kage_graph_visual") {
605
623
  const result = (0, kernel_js_1.graphMermaid)(String(args?.project_dir ?? ""), Number(args?.limit ?? 40));
606
624
  return {
@@ -630,7 +648,7 @@ async function callTool(name, args) {
630
648
  {
631
649
  type: "text",
632
650
  text: result.ok
633
- ? `Captured session learning: ${result.path}\nReview with: kage review --project ${args?.project_dir}`
651
+ ? `Captured session learning: ${result.path}\nRepo-local memory is written immediately. Org/global promotion still requires explicit review.`
634
652
  : `Learning capture blocked:\n${result.errors.map((error) => `- ${error}`).join("\n")}`,
635
653
  },
636
654
  ],
@@ -653,7 +671,7 @@ async function callTool(name, args) {
653
671
  {
654
672
  type: "text",
655
673
  text: result.ok
656
- ? `Captured pending packet: ${result.path}\nReview with: kage review --project ${args?.project_dir}`
674
+ ? `Captured repo-local packet: ${result.path}\nOrg/global promotion still requires explicit review.`
657
675
  : `Capture blocked:\n${result.errors.map((error) => `- ${error}`).join("\n")}`,
658
676
  },
659
677
  ],
@@ -810,7 +828,7 @@ async function callTool(name, args) {
810
828
  {
811
829
  type: "text",
812
830
  text: result.ok
813
- ? `Wrote branch review summary: ${result.path}\nCaptured pending change memory: ${result.packetPath ?? "(none)"}\nChanged files: ${result.changedFiles.join(", ")}`
831
+ ? `Wrote branch review summary: ${result.path}\nCaptured repo-local change memory: ${result.packetPath ?? "(none)"}\nChanged files: ${result.changedFiles.join(", ")}`
814
832
  : `Proposal blocked:\n${result.errors.map((error) => `- ${error}`).join("\n")}`,
815
833
  },
816
834
  ],