mnemos-capture 1.7.0 → 1.8.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/README.md CHANGED
@@ -38,8 +38,9 @@ xkg is the knowledge graph for AI workers that bridges human knowledge into cont
38
38
  - [Anything MCP](#anything-mcp)
39
39
  - [Workflow](#workflow)
40
40
  - [What's inside](#whats-inside)
41
- - [Your context stays in GitHub](#your-context-stays-in-github)
42
41
  - [CLI](#cli)
42
+ - [Your context stays in GitHub](#your-context-stays-in-github)
43
+ - [Cost](#cost)
43
44
  - [Roadmap](#roadmap)
44
45
  - [License](#license)
45
46
 
@@ -62,8 +63,8 @@ Open **[xkg.xmu.sh](https://xkg.xmu.sh)** and sign in with GitHub.
62
63
 
63
64
  xkg will:
64
65
 
65
- 1. Create a **private** GitHub repo for your knowledge (you can opt to make it public)
66
- 2. Ask for your LLM provider API key — Anthropic, OpenAI, or Google. Your key, your cost.
66
+ 1. Create a **private** GitHub repo for your knowledge on your account, named `xkg-knowledge` unless you pick another name (you can opt to make it public)
67
+ 2. Ask for your LLM provider and API key: Anthropic, OpenAI, Google Gemini, OpenRouter, xAI Grok, DeepSeek, Mistral or Groq. Your key, your cost. Change the provider, model or reasoning effort any time in Settings → AI model.
67
68
  3. Let you set a PIN (6+ characters) for quick unlock on this device
68
69
  4. Show your **xkg API key** once — save it if you want to connect an AI worker
69
70
 
@@ -73,7 +74,7 @@ That's the whole setup. No repo to clone. No database. No CLI required.
73
74
 
74
75
  In the app, paste a URL, a note, a doc excerpt, a transcript, or an idea, then hit **Capture**.
75
76
 
76
- xkg extracts the **core idea**, **key takeaways**, **where to apply it**, and the **capture type**, then commits Markdown to your GitHub repo.
77
+ xkg extracts the **core idea**, **key takeaways**, **where to apply it**, and the **capture type**. It backs each claim with an exact quote from the source and checks the quote is really there, then commits Markdown to your GitHub repo with the source text attached.
77
78
 
78
79
  On a phone: open the app → **Share → Add to Home Screen**. It runs like a native app.
79
80
 
@@ -128,6 +129,24 @@ npx -y @xmu-labs/xkg@latest serve-mcp --key YOUR_API_KEY
128
129
 
129
130
  Register that command in the tool you use.
130
131
 
132
+ When your agent connects, xkg tells it how to work with your knowledge: get a briefing at the start of a coding session, capture what you share, and record the commits that apply a capture. It also offers to install the git hook in your repo, after asking you. Most agents read this on their own, so there's nothing to paste into rules files. Codex CLI doesn't read it yet: see [Codex](#codex) for the lines to add.
133
+
134
+ #### Offer new captures when a session starts
135
+
136
+ `xkg session-start` first checks `HANDOFFS.md` for captures waiting for the repo you're in (its `origin`). When some wait, it prints the same text a `briefing` call with that repo returns: your agent asks you "xkg: 2 captures fit this repo (owner/name). Want to see them and apply one?", lists up to 3, and knows how to hand one off (see [Workflow](#workflow)). Otherwise it counts the captures in your inbox and, when there are some, tells your agent to ask you "You have 3 new xkg captures. Want to see them and apply one here?" before your task. It prints nothing when nothing waits or xkg can't be reached, and gives up after 3 seconds. It reads the key from `~/.xkg/config.json` (`xkg config set key YOUR_API_KEY`), so hook files carry no key.
137
+
138
+ Add it as a session-start hook in your agent. `--agent` picks the output shape that agent reads:
139
+
140
+ | Agent | File | Hook |
141
+ | --- | --- | --- |
142
+ | Claude Code | `~/.claude/settings.json` | `"hooks": { "SessionStart": [{ "hooks": [{ "type": "command", "command": "npx -y @xmu-labs/xkg@latest session-start --agent claude" }] }] }` |
143
+ | Codex | `~/.codex/hooks.json` | `{ "hooks": { "SessionStart": [{ "hooks": [{ "type": "command", "command": "npx -y @xmu-labs/xkg@latest session-start --agent codex" }] }] } }`, then trust it once in `/hooks` |
144
+ | Gemini CLI | `~/.gemini/settings.json` | `"hooks": { "SessionStart": [{ "hooks": [{ "type": "command", "command": "npx -y @xmu-labs/xkg@latest session-start --agent gemini" }] }] }` |
145
+ | GitHub Copilot CLI | `~/.copilot/hooks/xkg.json` | `{ "version": 1, "hooks": { "sessionStart": [{ "type": "command", "bash": "npx -y @xmu-labs/xkg@latest session-start --agent copilot" }] } }` |
146
+ | Cursor | `~/.cursor/hooks.json` | `{ "version": 1, "hooks": { "sessionStart": [{ "command": "npx -y @xmu-labs/xkg@latest session-start --agent cursor" }] } }` |
147
+
148
+ Merge the hook into the file if it already has other settings. Without `--agent`, the command prints plain text.
149
+
131
150
  #### Cursor
132
151
 
133
152
  Add this to `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (all projects):
@@ -174,6 +193,15 @@ args = ["-y", "@xmu-labs/xkg@latest", "serve-mcp", "--key", "YOUR_API_KEY"]
174
193
 
175
194
  Codex App: Settings → MCP → add the same `npx` command.
176
195
 
196
+ Codex CLI doesn't read the instructions xkg sends when it connects, so add these lines to `~/.codex/AGENTS.md` (all projects) or the repo's `AGENTS.md`:
197
+
198
+ ```markdown
199
+ ## xkg
200
+ - At the start of a coding session, call the xkg `briefing` tool with the repo name, branch, and last few commit messages. Check each capture it returns against the code before naming a file.
201
+ - When the user shares something worth keeping, call `capture`.
202
+ - When a commit uses a capture, add a line `xkg-capture: <filename>` to the commit message, then call `apply_capture` with the filename, the commit sha, the repo, and one line on why. If the user turns a capture down, call it with outcome `declined`, or `later` for not now.
203
+ ```
204
+
177
205
  #### Gemini CLI
178
206
 
179
207
  ```bash
@@ -192,7 +220,7 @@ Or the URL, no API key:
192
220
  claude mcp add --transport http xkg https://xkg.xmu.sh/api/mcp
193
221
  ```
194
222
 
195
- Optional session hooks (MCP works without them):
223
+ `serve-mcp` installs a light session hook the first time it runs. More hooks, all optional (MCP works without them):
196
224
 
197
225
  ```bash
198
226
  npx -y @xmu-labs/xkg@latest setup-hooks --key YOUR_API_KEY --briefing --vault
@@ -246,58 +274,96 @@ No MCP support yet? Capture in the [xkg app](https://xkg.xmu.sh) and connect lat
246
274
  capture "The Mom Test: don't ask if your idea is good. Ask about the person's real behavior, current workflow, and past spending."
247
275
  ```
248
276
 
249
- 2. **Brief** — At the start of a work session, ask your worker for a briefing. xkg uses your project name, branch, and recent commit messages to rank captures that could help now: **why** it matters, **what** applying it could achieve, **where** it could land. Local agent instruction files are not read or uploaded by the CLI hooks. You decide what to apply.
277
+ If you sync a **code map** (see step 5), the capture also gets a **Where it applies** section: the repo and up to 3 files where it may change something, from file and function names only, labeled as not yet checked in your code. xkg ranks your mapped files by the names they share with the capture, then [TypeSafe](https://typesafe.ai)'s decision model Jev rates the top 15 files of each repo: would applying the capture change this file, and does anything in the repo fit at all. Files rated Possible or better are shown (Strong when Jev is fairly sure), each with the exported names it shares with the capture. Your LLM is used only to extract the capture; if TypeSafe can't answer, your LLM picks the files instead, every path is checked against your map, a reason that names a function the file doesn't export is dropped, and the fit comes from how well the names match, never from the model rating itself. When nothing fits, it says why in one line. With no map, the capture is saved exactly as before.
278
+
279
+ A **Strong fit** is also queued for your coding agent: xkg adds a row to `HANDOFFS.md` in your knowledge repo with the capture, the repo and the suggested files. Any other fit waits for you: tap **Hand off to your coding agent** on the capture card, and pick an "also fits" repo first if you want it there instead (that row has no files; your agent finds them). The row holds names only, never code, and you can edit or delete the file at any time.
280
+
281
+ In your next coding session in that repo, your agent offers what's waiting (a `briefing` call with `repo`, or `xkg session-start` as a hook): "xkg: 2 captures fit this repo (owner/name). Want to see them and apply one?" If you say yes and pick one, your agent reads the capture and the files, writes a spec to `.xkg/handoffs/<capture>.md` in that repo (claim, evidence quoted from the capture, why here, the files it opened and the change, checks, and its confidence as High, Medium or Low), and asks "Apply this now?" The spec stays on your machine: `.xkg/` is kept out of git (`setup-git-hook` adds it to `.git/info/exclude`), and it never goes to xkg. If you say yes, your agent makes the change in its normal way, with the `xkg-capture:` trailer in the commit. A file xkg suggested that's no longer in your current map is marked "moved or deleted since the capture", so your agent looks for where it applies now. A capture offered on 3 days without an answer stops being offered (its row says `expired`).
282
+
283
+ Your agent records your answer with `apply_capture`, so xkg knows what happened. **Not now** (`outcome: later`) offers it again in 7 days. **No** (`declined`) takes a reason: `wrong-files` (it fits, but not in those files), `doesnt-fit` (not in this repo) or `other`. **Already done** is `done`. None of these counts as an apply, moves the capture or adds an `APPLIED.md` row. A commit with the trailer marks the row `applied`, and a plain `git revert` of that commit is recorded as `reverted` (see step 5). Every answer adds a line to the capture's `## Handoff log`: the date, the repo, the answer and its reason, and for an apply the commit and the files it changed (only files in your code map). When your agent and the git hook both report one commit, the apply is logged once, and the later report fills in the changed files or your agent's confidence if the first left them out. No answer carries the spec, code or a diff. Rows that are done (`applied`, `declined`, `done`, `expired`) leave `HANDOFFS.md` 90 days after their last change; the capture's Handoff log keeps the history.
284
+
285
+ To apply a spec in an isolated worktree instead of your current session, run `xkg agent --spec .xkg/handoffs/<capture>.md` (see step 4). It reads the spec from your disk and needs no key.
250
286
 
251
- 3. **Plan** — Select the captures you want and ask your worker to call `generate_plan`. Each plan is Markdown: codebase mapping, implementation steps with effort tiers (`simple`, `complex`, `architectural`), and a verification checklist.
287
+ 2. **Brief** — At the start of a work session, your agent asks for a briefing. xkg uses your project name, branch, and recent commit messages to pick up to 5 captures that may fit, with **why** each may fit and its key points. xkg never sees your code (at most file and function names from a code map), so a briefing gives candidates: your agent reads the code, keeps only real matches, names the file and the change, and asks you before changing anything. Local agent instruction files are not read or uploaded by the CLI hooks. You decide what to apply.
288
+
289
+ 3. **Plan** — Select the captures you want and ask your worker to call `generate_plan`. The worker can name the files the plan should cover; xkg passes on the paths only, since it never reads your code, and your worker checks each step against the code. Each plan is Markdown: codebase mapping, implementation steps with effort tiers (`simple`, `complex`, `architectural`), and a verification checklist.
252
290
 
253
291
  4. **Execute** — Hand the plan to your worker in the current session, or run it in an isolated git worktree:
254
292
 
255
293
  ```bash
256
294
  npx -y @xmu-labs/xkg@latest config set agent "claude -p"
257
- npx -y @xmu-labs/xkg@latest kos --key YOUR_API_KEY
295
+ npx -y @xmu-labs/xkg@latest agent --key YOUR_API_KEY
258
296
  ```
259
297
 
260
- `kos` creates a worktree, loads the plan as the contract, launches the assistant you configured, and prints the verification checklist when it finishes.
298
+ `xkg agent` creates a worktree, loads the plan as the contract, launches the assistant you configured, and prints the verification checklist when it finishes. Older CLI versions call it `xkg kos`, and that name still works for now.
261
299
 
262
300
  ```bash
263
301
  npx -y @xmu-labs/xkg@latest config set agent "codex exec"
264
302
  ```
265
303
 
304
+ 5. **Mark applied** — After committing a change that applies a capture, the worker calls `apply_capture` with the commit sha and one line on why. xkg moves the capture to `applied/`, adds an entry to its application log and to `APPLIED.md`, and counts it once per capture and commit.
305
+
306
+ Any coding agent, or you by hand, can also add a trailer to the commit message:
307
+
308
+ ```text
309
+ xkg-capture: 2026-10-28-alert-ownership.md
310
+ ```
311
+
312
+ With the git hook installed in that repo, xkg records it after each commit, with the repo and the files the commit changed. If you later undo that commit with `git revert`, the hook reports the capture as reverted: its `APPLIED.md` row is marked "reverted" with the date, and it still counts as an apply that week. Your agent offers to set it up the first time it connects, or run:
313
+
314
+ ```bash
315
+ npx -y @xmu-labs/xkg@latest setup-git-hook --key YOUR_API_KEY
316
+ ```
317
+
318
+ The same setup keeps a **code map** of the repo: file paths, exported function and class names, and which files import which. Never file contents, comments or package names. It is sent after a commit, a pull or a branch switch when a name changed, and at most once a day otherwise, so xkg can tell where a capture applies in your code. This works through git alone, so it's the same with every coding agent, or none. Run `xkg map` to send it by hand. In a cloud container, which starts with no hooks, add the setup line to the environment's setup script.
319
+
320
+ **Settings → Code repos** lists every repo xkg matches captures to, with where its map came from and when. For a repo you haven't opened with the hook, type `owner/name` under *Add a repo from GitHub*: xkg reads its file names with your GitHub sign-in (file paths only, no exported names, never a file's contents) and reads them again when they are a week old. A map from your machine replaces it the next time the hook runs there. *Remove* deletes a repo's map; captures stop matching it until it is synced or added again.
321
+
322
+ Ask *apply stats* to see how many captures you applied each week.
323
+
266
324
  ### Example capture
267
325
 
268
326
  ```text
269
- The Mom Test: don't ask if your idea is good. Ask about the person's real behavior, current workflow, and past spending.
327
+ Note to self after the incident review: our alerts fired, but nobody owned them. Alerts without a named owner get ignored. Every alert needs one person on the hook, not a channel.
270
328
  ```
271
329
 
272
330
  ```markdown
273
331
  ---
274
- date: 2026-06-05
275
- source: The Mom Test note
276
- type: note
277
- tags: customer-discovery, product, research
332
+ date: 2026-10-28
333
+ source: Alert ownership importance
334
+ type: notes
335
+ source_type: note
336
+ tags: incident-management, alerting, responsibility
278
337
  status: inbox
338
+ source_sha256: 21275782a8bf…
279
339
  ---
280
340
 
281
- # The Mom Test
341
+ # Alert ownership importance
282
342
 
283
343
  ## Core idea
284
-
285
- Do not ask people to judge your idea. Ask about their real life,
286
- recent behavior, current workaround, and what they have already
287
- tried or paid for.
344
+ Alerts in incident reviews must have a designated owner to ensure
345
+ they are acted upon, as unowned alerts tend to be ignored.
288
346
 
289
347
  ## Key takeaways
348
+ - Every alert needs one person responsible for it to prevent neglect.
349
+ - Alerts without a named owner are typically ignored.
290
350
 
291
- - Avoid compliments and future promises
292
- - Ask about specific past behavior
293
- - Look for existing pain, workarounds, and spending
351
+ ## Evidence
352
+ **Core idea** · `c-ac3270c3bbc8`
353
+ > "Alerts without a named owner get ignored." (s1)
294
354
 
295
- ## Where to apply
355
+ **Takeaway 1** · `c-59d033daf0e2`
356
+ > "Every alert needs one person on the hook, not a channel." (s1)
296
357
 
297
- Discovery interview scripts, landing page tests, and product
298
- validation prompts.
358
+ <details>
359
+ <summary>Source text (sha256: 21275782a8bf…)</summary>
360
+
361
+ [s1] Note to self after the incident review: our alerts fired, …
362
+ </details>
299
363
  ```
300
364
 
365
+ Shortened from a real capture. Every claim points to a quote that appears word for word in the source text below it, so you can check it.
366
+
301
367
  Plain Markdown in a repo you own. Readable, editable, versioned, portable.
302
368
 
303
369
  ## What's inside
@@ -311,10 +377,10 @@ You don't have to memorize these. Ask in plain language: "save this", "what do I
311
377
  | `search_captures` | Keyword search across your captures |
312
378
  | `read_capture` | Read a capture in full |
313
379
  | `recall` | Find relevant context from a description of your task |
314
- | `briefing` | Get project-specific recommendations |
380
+ | `briefing` | Get captures that may fit the current project, for the agent to check against the code |
315
381
  | `generate_plan` | Turn selected captures into an implementation plan |
316
382
  | `list_plans` | List or read saved plans |
317
- | `apply_capture` | Mark a capture as applied, with the commit that applied it |
383
+ | `apply_capture` | Mark a capture as applied, with the commit that applied it and why, or record your answer to a handoff (`declined` with a reason, `done`, `later`, `reverted`) |
318
384
  | `apply_stats` | Captures applied per week, and how many came with a commit |
319
385
  | `archive_capture` | Archive a capture |
320
386
  | `delete_capture` | Delete a capture |
@@ -337,9 +403,13 @@ npx -y @xmu-labs/xkg@latest <command>
337
403
  | `npx -y @xmu-labs/xkg@latest` | Open the hosted app |
338
404
  | `serve-mcp --key YOUR_API_KEY` | Run the local MCP proxy |
339
405
  | `setup-hooks --key YOUR_API_KEY [--briefing] [--vault]` | Install Claude Code hooks |
340
- | `setup-git-hook --key YOUR_API_KEY` | In a repo: record commits whose message has an `xkg-capture: <filename>` trailer (works with any coding agent). The key is stored in `~/.xkg/config.json`, not in the hook. Setup checks that the hook can reach xkg, and the hook runs the CLI version that installed it (run setup again to update) |
341
- | `config set agent "claude -p"` | Set which assistant `kos` should drive |
342
- | `kos --key YOUR_API_KEY` | Implement the latest plan in an isolated worktree |
406
+ | `setup-git-hook --key YOUR_API_KEY` | In a repo: record commits whose message has an `xkg-capture: <filename>` trailer, and keep the repo's code map up to date after commits, pulls and branch switches (works with any coding agent). The key is stored in `~/.xkg/config.json`, not in the hooks. Setup checks that the hook can reach xkg and sends the map once; the hooks run the CLI version that installed them (run setup again to update) |
407
+ | `map [--key YOUR_API_KEY]` | In a repo: send its code map now (file paths and exported names, never code) |
408
+ | `session-start [--agent NAME]` | For an agent's session-start hook: ask whether to see and apply new captures. `NAME` is `claude`, `codex`, `gemini`, `copilot` or `cursor`; without it the output is plain text. See [Offer new captures when a session starts](#offer-new-captures-when-a-session-starts) |
409
+ | `config set key YOUR_API_KEY` | Store the key that `session-start`, the git hooks, `map` and `agent` use |
410
+ | `config set agent "claude -p"` | Set which assistant `xkg agent` should drive |
411
+ | `agent --key YOUR_API_KEY [--plan FILE]` | Implement the latest plan (or the one named) in an isolated worktree with your own coding agent |
412
+ | `agent --spec PATH` | Apply a handoff spec your agent wrote (`.xkg/handoffs/<capture>.md`) in an isolated worktree. Reads it from disk; no key needed |
343
413
  | `inbox-check --key YOUR_API_KEY [--briefing]` | Debug the session hook |
344
414
  | `vault-check --key YOUR_API_KEY` | Debug the vault hook |
345
415
  | `help` | Show help |
@@ -352,11 +422,27 @@ Captures live as structured Markdown in **your** GitHub repository. Private by d
352
422
 
353
423
  - **No lock-in** — clone it, move it, delete xkg, keep the files
354
424
  - **No proprietary format** — every capture is a readable `.md` file
355
- - **No training on your data** — captures are sent only to the LLM provider you chose, with the key you supplied, to extract the insight you asked for
425
+ - **No training on your data** — captures are sent only to the LLM provider you chose, with the key you supplied, to extract the insight you asked for. If you sync code maps, the extracted title, core idea and takeaways also go to TypeSafe, which says where the capture applies and doesn't train on what it receives
356
426
  - **Any tool can read it** — anything that reads Git or speaks MCP
357
427
  - **BYOK** — your provider, your key, your cost
358
428
  - **Never commit your xkg API key** to source control
359
429
 
430
+ ### Find your repo
431
+
432
+ Settings → Account shows the repo your captures are saved in, with a link to it on GitHub. Inside it:
433
+
434
+ | Path | What it holds |
435
+ | --- | --- |
436
+ | `inbox/YYYY-MM-DD-<slug>.md` | one Markdown file per capture |
437
+ | `applied/` | captures that have been applied in a commit |
438
+ | `archived/` | captures you archived |
439
+ | `plans/` | plans you generated |
440
+ | `INDEX.md` | a table of every capture: date, title, keywords, tags |
441
+ | `APPLIED.md` | which capture went into which commit |
442
+ | `HANDOFFS.md` | captures waiting for your coding agent: one row per capture and repo, with the suggested files, the fit, a status (`pending`, `snoozed:<date>`, `applied`, `declined`, `done`, `expired`, `dead`), the date of its last change, and whether it was queued automatically or by you. Done rows leave after 90 days |
443
+
444
+ **Renaming it.** Rename the repo on GitHub as usual, then open xkg once. xkg reads the new name from GitHub in the background and saves it; Settings shows it from the next time you open the app. Until then, GitHub's redirect from the old name keeps captures working. Don't create a new repo with the old name before you've opened xkg, because GitHub drops the redirect when you do. You don't need to onboard again, and doing so would replace your PIN and keys.
445
+
360
446
  ### What xkg stores, and where
361
447
 
362
448
  The whole point of this repo being public is that you can check these claims rather than trust them.
@@ -372,18 +458,23 @@ The whole point of this repo being public is that you can check these claims rat
372
458
  | `github_repo` | the *name* of your hub, not its contents |
373
459
  | `pin_hash` | salted scrypt (`src/lib/pin.ts`) |
374
460
  | `api_key` | hashed — not recoverable |
375
- | `llm_provider`, `llm_api_key` | encrypted |
461
+ | `llm_provider`, `llm_model`, `llm_effort`, `llm_api_key` | the key is encrypted; the provider, model and effort are plain text |
376
462
  | `display_name`, `avatar_data` | optional in-app name and photo; never written back to GitHub |
377
463
  | `token_version` | bumped to revoke everything |
464
+ | apply counts (`apply_events`) | one row per applied capture and commit: a keyed hash of the capture's file name, the first 7 characters of the commit, the AI client's name, whether a reason was given, and the time. Never the capture's name, content or the reason |
465
+ | code maps (`code_maps`) | per repo you sync with the git hooks or add in Settings (at most 20): file paths, exported function and class names, and which mapped files import which; for a repo added from GitHub, file paths only (`source = 'github'`). Never file contents, comments or package names (`cli/code-map.ts` builds it, `src/lib/code-map.ts` checks it, `src/lib/code-repos.ts` reads GitHub's file list) |
466
+ | surface counts (`surface_events`) | one row each time xkg shows you a capture (capture card, briefing, recall, vault scan, session-start handoff offer): a keyed hash of the capture's file name, which of these showed it, and the time. For the capture card's match, also the tokens it used, the model's own confidence (never shown), the scorer (`jev` or `evidence`), the fit, and whether it was shown to you. Never the capture's name or content |
467
+ | handoff counts (`handoff_events`) | one row each time something happens to a handoff (queued, offered in a session at most once a day, expired, and your answer: applied, declined, done, later, reverted): a keyed hash of the capture's file name, the repo (`owner/name`), what happened, whether it was queued automatically or by you, the reason for a no, your agent's confidence, whether an apply came with a commit, and the time. Never the capture's name or content, and never the spec your agent writes; `HANDOFFS.md` and each capture's Handoff log in your repo are the record |
378
468
  | login attempts, OAuth clients/codes, refresh-token ids | auth plumbing |
379
469
 
380
- When you capture, the server talks to exactly three kinds of destination:
470
+ When you capture, the server talks to exactly four kinds of destination:
381
471
 
382
- 1. **GitHub** — to write the capture into your repo
383
- 2. **Your LLM provider**, with *your* key — Anthropic, OpenAI, or Google
384
- 3. **The page you captured** — if you paste a bare URL, so the model reads real content. Only http/https, never an internal address
472
+ 1. **GitHub** — to write the capture into your repo, and to read the file list of a code repo you add in Settings, or refresh it when it is a week old
473
+ 2. **Your LLM provider**, with *your* key: the one you picked in Settings, to extract the capture
474
+ 3. **TypeSafe**, with xkg's key, only if you sync code maps: the extracted title, core idea and takeaways, and the names (paths, exported names, imported paths) of up to 15 files per repo, so its model Jev can say where the capture applies. If TypeSafe can't answer, the maps of your 5 most recently synced repos (names only) go to your LLM provider instead
475
+ 4. **The page you captured** — if you paste a bare URL, so the model reads real content. Only http/https, never an internal address
385
476
 
386
- No analytics, no telemetry, no third-party tracking.
477
+ No third-party analytics, telemetry or tracking. The only usage xkg records is the apply counts above, so it can see whether captures get applied in code.
387
478
 
388
479
  ### How your credentials are stored
389
480
 
@@ -400,6 +491,15 @@ Your MCP key is shown exactly once. If you lose it, generate a new one.
400
491
 
401
492
  **Revoking access.** Generating a new MCP key invalidates the old key, every other signed-in session, and every connected MCP client. Sessions also expire after 30 days.
402
493
 
494
+ **Removing xkg from a computer.** Your account and captures are not touched:
495
+
496
+ 1. `claude mcp remove xkg` (or delete the `xkg` block from your editor's MCP config). Remove web connectors in each app's Connectors settings.
497
+ 2. Delete the xkg entries under `hooks` in `~/.claude/settings.json`.
498
+ 3. In each repo with the git hook, delete the line ending in `# xkg` from `.git/hooks/post-commit`.
499
+ 4. `rm -rf ~/.xkg`
500
+
501
+ Generate a new MCP key afterwards to cut off any connection you missed.
502
+
403
503
  **Deleting your account.** Settings → Delete account removes your account row and every record keyed to it, and asks GitHub to revoke xkg's access (`src/lib/account.ts`). Your captures stay in your repo.
404
504
 
405
505
  The hosted service's [Privacy Policy](https://xkg.xmu.sh/privacy) and [Terms of Service](https://xkg.xmu.sh/terms) say the same in legal form.
@@ -408,15 +508,17 @@ The hosted service's [Privacy Policy](https://xkg.xmu.sh/privacy) and [Terms of
408
508
 
409
509
  xkg is BYOK. You bring your own API key. xkg never charges you for inference.
410
510
 
411
- Extraction runs on a fast, low-cost model (Claude Haiku 4.5 by default):
511
+ Each provider starts on a low-cost model, which you can change in Settings → AI model. For example, Anthropic starts on Claude Haiku 4.5, OpenAI on GPT-4o mini and Google on Gemini 2.0 Flash.
412
512
 
413
- | Usage | Estimated monthly cost |
414
- | --- | --- |
415
- | 50 captures/month | ~$0.15 |
416
- | 100 captures/month | ~$0.30 |
417
- | 200 captures/month | ~$0.60 |
513
+ A capture uses about 2,000–3,700 input tokens and 750–1,100 output tokens, depending on how long the source is (measured October 2026 with the current extraction prompt). At list prices:
514
+
515
+ | Usage | Claude Haiku 4.5 | GPT-4o mini |
516
+ | --- | --- | --- |
517
+ | 50 captures/month | ~$0.40 | ~$0.05 |
518
+ | 100 captures/month | ~$0.80 | ~$0.10 |
519
+ | 200 captures/month | ~$1.60 | ~$0.20 |
418
520
 
419
- Briefing, planning, and synthesis only run when you ask for them.
521
+ Larger models cost more. Briefing, planning, and synthesis only run when you ask for them.
420
522
 
421
523
  ## Roadmap
422
524
 
@@ -1,14 +1,17 @@
1
1
  /**
2
- * kos — xkg's Knowledge Operations orchestrator.
2
+ * xkg agent — runs a saved plan, or a handoff spec, with the user's own coding agent.
3
3
  *
4
4
  * Drives whatever AI assistant the user configured (`xkg config set agent ...`)
5
5
  * to implement a generated plan in an isolated git worktree. xkg owns the
6
6
  * harness-independent parts: fetching the plan, the worktree, and reporting the
7
7
  * Verification Checklist. The assistant is a black box — model-agnostic by design.
8
+ *
9
+ * With --spec, the plan is a spec the user's agent wrote in this repo
10
+ * (.xkg/handoffs/), read from disk; nothing is fetched from xkg.
8
11
  */
9
12
  import { tmpdir } from "os";
10
13
  import { dirname, join, resolve } from "path";
11
- import { writeFileSync, existsSync, mkdirSync, readFileSync } from "fs";
14
+ import { writeFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync } from "fs";
12
15
  import { execSync } from "child_process";
13
16
  import { callMcp } from "./hooks.js";
14
17
  import { readConfig } from "./config.js";
@@ -36,22 +39,44 @@ export function parseChecklist(planText) {
36
39
  .filter((l) => /^- \[[ x]\]/i.test(l))
37
40
  .map((l) => l.replace(/^- \[[ x]\]\s*/i, ""));
38
41
  }
39
- /** Build a unique, sortable branch name for a kos run. */
40
- export function buildKosBranch(now = new Date()) {
42
+ /** Build a unique, sortable branch name for an agent run. */
43
+ export function buildAgentBranch(now = new Date()) {
41
44
  const stamp = now.toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
42
- return `xkg/kos-${stamp}`;
45
+ return `xkg/agent-${stamp}`;
46
+ }
47
+ /**
48
+ * A new empty folder for the run's worktree. Unique per run: the branch name
49
+ * only has seconds, and the temp folder is shared by every repo on the machine.
50
+ */
51
+ export function newWorktreePath(branch) {
52
+ return mkdtempSync(join(tmpdir(), `${branch.replace(/\//g, "-")}-`));
43
53
  }
44
- /** Where kos drops the plan inside the worktree. Never committed — see excludePlanFiles. */
54
+ /** Printed when the command is run under its old name, `kos`. */
55
+ export const RENAMED_NOTE = "`xkg kos` is now `xkg agent`. The old name will stop working in a later release.";
56
+ /** Where the agent run drops the plan inside the worktree. Never committed — see excludePlanFiles. */
45
57
  export const PLAN_FILE = "XKG_PLAN.md";
46
58
  /** Plan filenames written by earlier releases; still excluded so an old worktree can't leak one. */
47
59
  const LEGACY_PLAN_FILES = ["MNEMOS_PLAN.md"];
48
60
  /** The instruction handed to the assistant. The plan file is the real contract. */
49
- export const KOS_PROMPT = `Implement the plan in ${PLAN_FILE} in this repository. ` +
61
+ export const AGENT_PROMPT = `Implement the plan in ${PLAN_FILE} in this repository. ` +
50
62
  "Follow the Codebase Mapping table exactly — change only the files it lists. " +
51
63
  "For each step apply the thinking effort named in its Effort column " +
52
64
  "(low = quick/mechanical, medium = careful, high = deep reasoning). " +
53
65
  "When finished, make sure every item in the Verification Checklist is satisfied. " +
54
66
  `${PLAN_FILE} is private: never commit, push, or copy it into another file.`;
67
+ /** The instruction for a handoff spec (--spec). The spec is the contract. */
68
+ export const SPEC_PROMPT = `Apply the handoff spec in ${PLAN_FILE} to this repository. ` +
69
+ "Change only the files in its Files and changes table, and nothing it lists under Not doing. " +
70
+ "When finished, make sure every item in its Verification Checklist is satisfied. " +
71
+ 'In each commit message, add the line "xkg-capture: <filename>" with the filename from the spec\'s capture field. ' +
72
+ `${PLAN_FILE} is private: never commit, push, or copy it into another file.`;
73
+ /** A spec the user's agent wrote in this repo. Throws when it can't be read or is empty. */
74
+ export function readSpec(path, cwd = process.cwd()) {
75
+ const text = readFileSync(resolve(cwd, path), "utf-8");
76
+ if (!text.trim())
77
+ throw new Error(`${path} is empty.`);
78
+ return text;
79
+ }
55
80
  /**
56
81
  * Return the exclude-file contents with every plan filename present, or null if
57
82
  * nothing needs adding. Pure so the matching can be tested without a repo.
@@ -87,10 +112,33 @@ export function excludePlanFiles(worktreePath) {
87
112
  }
88
113
  }
89
114
  // ── Orchestrator ──
115
+ function localSpec(path) {
116
+ try {
117
+ return { file: path, text: readSpec(path) };
118
+ }
119
+ catch (err) {
120
+ console.error(`Could not read spec: ${path}`);
121
+ console.error(err instanceof Error ? err.message : String(err));
122
+ process.exit(1);
123
+ }
124
+ }
125
+ async function savedPlan(apiKey, plan) {
126
+ const file = plan ?? latestPlanFilename((await callMcp(apiKey, "list_plans", {}, 15000)) ?? "");
127
+ if (!file) {
128
+ console.error("No plans found. Generate one first with the generate_plan MCP tool.");
129
+ process.exit(1);
130
+ }
131
+ const text = await callMcp(apiKey, "list_plans", { filename: file }, 15000);
132
+ if (!text || !text.trim()) {
133
+ console.error(`Could not read plan: ${file}`);
134
+ process.exit(1);
135
+ }
136
+ return { file, text };
137
+ }
90
138
  function run(cmd, cwd) {
91
139
  return execSync(cmd, { cwd, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
92
140
  }
93
- export async function kos(apiKey, opts = {}) {
141
+ export async function runAgent(apiKey, opts = {}) {
94
142
  const cfg = readConfig();
95
143
  if (!cfg.agent) {
96
144
  console.error("No agent configured. Tell xkg which AI worker to drive:\n");
@@ -99,30 +147,21 @@ export async function kos(apiKey, opts = {}) {
99
147
  console.error(' xkg config set agent "aider --yes --message" # Aider');
100
148
  process.exit(1);
101
149
  }
102
- // 1. Resolve the plan — explicit filename, or the newest saved plan
103
- const planFile = opts.plan ?? latestPlanFilename((await callMcp(apiKey, "list_plans", {}, 15000)) ?? "");
104
- if (!planFile) {
105
- console.error("No plans found. Generate one first with the generate_plan MCP tool.");
106
- process.exit(1);
107
- }
108
- const planText = await callMcp(apiKey, "list_plans", { filename: planFile }, 15000);
109
- if (!planText || !planText.trim()) {
110
- console.error(`Could not read plan: ${planFile}`);
111
- process.exit(1);
112
- }
150
+ // 1. Resolve the plan — a local spec, an explicit filename, or the newest saved plan
151
+ const { file: planFile, text: planText } = opts.spec ? localSpec(opts.spec) : await savedPlan(apiKey, opts.plan);
113
152
  // 2. Must be inside a git repo
114
153
  let repoRoot;
115
154
  try {
116
155
  repoRoot = run("git rev-parse --show-toplevel");
117
156
  }
118
157
  catch {
119
- console.error("xkg kos must be run inside a git repository.");
158
+ console.error("xkg agent must be run inside a git repository.");
120
159
  process.exit(1);
121
160
  return;
122
161
  }
123
162
  // 3. Isolated worktree on a fresh branch
124
- const branch = buildKosBranch();
125
- const worktreePath = join(tmpdir(), branch.replace(/\//g, "-"));
163
+ const branch = buildAgentBranch();
164
+ const worktreePath = newWorktreePath(branch);
126
165
  console.log(`Creating isolated worktree on branch ${branch}...`);
127
166
  run(`git worktree add -b ${branch} "${worktreePath}"`, repoRoot);
128
167
  // 4. Keep git from tracking the plan (local exclude, not committed), then drop it in.
@@ -131,7 +170,7 @@ export async function kos(apiKey, opts = {}) {
131
170
  excludePlanFiles(worktreePath);
132
171
  }
133
172
  catch (err) {
134
- console.error(`Could not keep ${PLAN_FILE} out of git, so kos stopped before writing it.`);
173
+ console.error(`Could not keep ${PLAN_FILE} out of git, so xkg agent stopped before writing it.`);
135
174
  console.error(err instanceof Error ? err.message : String(err));
136
175
  console.error(`The empty worktree is at ${worktreePath}.`);
137
176
  process.exit(1);
@@ -140,7 +179,7 @@ export async function kos(apiKey, opts = {}) {
140
179
  // 5. Hand off to the user's assistant, running inside the worktree
141
180
  console.log(`Launching: ${cfg.agent}\n`);
142
181
  try {
143
- execSync(`${cfg.agent} ${JSON.stringify(KOS_PROMPT)}`, { cwd: worktreePath, stdio: "inherit" });
182
+ execSync(`${cfg.agent} ${JSON.stringify(opts.spec ? SPEC_PROMPT : AGENT_PROMPT)}`, { cwd: worktreePath, stdio: "inherit" });
144
183
  }
145
184
  catch {
146
185
  console.error("\nThe agent exited with an error. The worktree is preserved for inspection.");
@@ -148,7 +187,7 @@ export async function kos(apiKey, opts = {}) {
148
187
  // 6. Report — branch + the plan's own checklist as the review gate
149
188
  const checklist = parseChecklist(planText);
150
189
  console.log(`\n${"─".repeat(60)}`);
151
- console.log(`kos finished. Plan: ${planFile}`);
190
+ console.log(`xkg agent finished. ${opts.spec ? "Spec" : "Plan"}: ${planFile}`);
152
191
  console.log(`Branch: ${branch}`);
153
192
  console.log(`Worktree: ${worktreePath}`);
154
193
  if (checklist.length > 0) {