mnemos-capture 1.6.1 → 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
@@ -1,17 +1,14 @@
1
1
  <div align="center">
2
2
  <br>
3
3
  <a href="https://xkg.xmu.sh">
4
- <picture>
5
- <source media="(prefers-color-scheme: dark)" srcset="public/xkg-hero-dark.gif">
6
- <img src="public/xkg-hero-light.gif" width="160" height="160" alt="xkg">
7
- </picture>
4
+ <img src="public/brand/xkg-icon-dark.png" width="160" height="160" alt="xkg">
8
5
  </a>
9
6
  <br>
10
7
  <br>
11
8
  <p>
12
- <a href="https://www.npmjs.com/package/@xmu-labs/xkg"><img src="https://img.shields.io/npm/v/@xmu-labs/xkg?color=1c74d8&labelColor=000820&style=flat" alt="npm" /></a>
13
- <img src="https://img.shields.io/badge/MCP-compatible-9dd8f5?labelColor=000820&style=flat" alt="MCP" />
14
- <img src="https://img.shields.io/badge/license-MIT-f0f4f8?labelColor=000820&style=flat" alt="MIT" />
9
+ <a href="https://www.npmjs.com/package/@xmu-labs/xkg"><img src="https://img.shields.io/npm/v/@xmu-labs/xkg?color=777493&labelColor=11131A&style=flat" alt="npm" /></a>
10
+ <img src="https://img.shields.io/badge/MCP-compatible-B8B5D0?labelColor=11131A&style=flat" alt="MCP" />
11
+ <img src="https://img.shields.io/badge/license-MIT-F2F1F6?labelColor=11131A&style=flat" alt="MIT" />
15
12
  </p>
16
13
  </div>
17
14
 
@@ -41,8 +38,9 @@ xkg is the knowledge graph for AI workers that bridges human knowledge into cont
41
38
  - [Anything MCP](#anything-mcp)
42
39
  - [Workflow](#workflow)
43
40
  - [What's inside](#whats-inside)
44
- - [Your context stays in GitHub](#your-context-stays-in-github)
45
41
  - [CLI](#cli)
42
+ - [Your context stays in GitHub](#your-context-stays-in-github)
43
+ - [Cost](#cost)
46
44
  - [Roadmap](#roadmap)
47
45
  - [License](#license)
48
46
 
@@ -65,8 +63,8 @@ Open **[xkg.xmu.sh](https://xkg.xmu.sh)** and sign in with GitHub.
65
63
 
66
64
  xkg will:
67
65
 
68
- 1. Create a **private** GitHub repo for your knowledge (you can opt to make it public)
69
- 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.
70
68
  3. Let you set a PIN (6+ characters) for quick unlock on this device
71
69
  4. Show your **xkg API key** once — save it if you want to connect an AI worker
72
70
 
@@ -76,7 +74,7 @@ That's the whole setup. No repo to clone. No database. No CLI required.
76
74
 
77
75
  In the app, paste a URL, a note, a doc excerpt, a transcript, or an idea, then hit **Capture**.
78
76
 
79
- 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.
80
78
 
81
79
  On a phone: open the app → **Share → Add to Home Screen**. It runs like a native app.
82
80
 
@@ -131,6 +129,24 @@ npx -y @xmu-labs/xkg@latest serve-mcp --key YOUR_API_KEY
131
129
 
132
130
  Register that command in the tool you use.
133
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
+
134
150
  #### Cursor
135
151
 
136
152
  Add this to `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (all projects):
@@ -177,6 +193,15 @@ args = ["-y", "@xmu-labs/xkg@latest", "serve-mcp", "--key", "YOUR_API_KEY"]
177
193
 
178
194
  Codex App: Settings → MCP → add the same `npx` command.
179
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
+
180
205
  #### Gemini CLI
181
206
 
182
207
  ```bash
@@ -195,7 +220,7 @@ Or the URL, no API key:
195
220
  claude mcp add --transport http xkg https://xkg.xmu.sh/api/mcp
196
221
  ```
197
222
 
198
- 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):
199
224
 
200
225
  ```bash
201
226
  npx -y @xmu-labs/xkg@latest setup-hooks --key YOUR_API_KEY --briefing --vault
@@ -249,58 +274,96 @@ No MCP support yet? Capture in the [xkg app](https://xkg.xmu.sh) and connect lat
249
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."
250
275
  ```
251
276
 
252
- 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.
286
+
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.
253
288
 
254
- 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.
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.
255
290
 
256
291
  4. **Execute** — Hand the plan to your worker in the current session, or run it in an isolated git worktree:
257
292
 
258
293
  ```bash
259
294
  npx -y @xmu-labs/xkg@latest config set agent "claude -p"
260
- npx -y @xmu-labs/xkg@latest kos --key YOUR_API_KEY
295
+ npx -y @xmu-labs/xkg@latest agent --key YOUR_API_KEY
261
296
  ```
262
297
 
263
- `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.
264
299
 
265
300
  ```bash
266
301
  npx -y @xmu-labs/xkg@latest config set agent "codex exec"
267
302
  ```
268
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
+
269
324
  ### Example capture
270
325
 
271
326
  ```text
272
- 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.
273
328
  ```
274
329
 
275
330
  ```markdown
276
331
  ---
277
- date: 2026-06-05
278
- source: The Mom Test note
279
- type: note
280
- 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
281
337
  status: inbox
338
+ source_sha256: 21275782a8bf…
282
339
  ---
283
340
 
284
- # The Mom Test
341
+ # Alert ownership importance
285
342
 
286
343
  ## Core idea
287
-
288
- Do not ask people to judge your idea. Ask about their real life,
289
- recent behavior, current workaround, and what they have already
290
- 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.
291
346
 
292
347
  ## Key takeaways
348
+ - Every alert needs one person responsible for it to prevent neglect.
349
+ - Alerts without a named owner are typically ignored.
350
+
351
+ ## Evidence
352
+ **Core idea** · `c-ac3270c3bbc8`
353
+ > "Alerts without a named owner get ignored." (s1)
293
354
 
294
- - Avoid compliments and future promises
295
- - Ask about specific past behavior
296
- - Look for existing pain, workarounds, and spending
355
+ **Takeaway 1** · `c-59d033daf0e2`
356
+ > "Every alert needs one person on the hook, not a channel." (s1)
297
357
 
298
- ## Where to apply
358
+ <details>
359
+ <summary>Source text (sha256: 21275782a8bf…)</summary>
299
360
 
300
- Discovery interview scripts, landing page tests, and product
301
- validation prompts.
361
+ [s1] Note to self after the incident review: our alerts fired, …
362
+ </details>
302
363
  ```
303
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
+
304
367
  Plain Markdown in a repo you own. Readable, editable, versioned, portable.
305
368
 
306
369
  ## What's inside
@@ -314,10 +377,11 @@ You don't have to memorize these. Ask in plain language: "save this", "what do I
314
377
  | `search_captures` | Keyword search across your captures |
315
378
  | `read_capture` | Read a capture in full |
316
379
  | `recall` | Find relevant context from a description of your task |
317
- | `briefing` | Get project-specific recommendations |
380
+ | `briefing` | Get captures that may fit the current project, for the agent to check against the code |
318
381
  | `generate_plan` | Turn selected captures into an implementation plan |
319
382
  | `list_plans` | List or read saved plans |
320
- | `apply_capture` | Mark a capture as applied |
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`) |
384
+ | `apply_stats` | Captures applied per week, and how many came with a commit |
321
385
  | `archive_capture` | Archive a capture |
322
386
  | `delete_capture` | Delete a capture |
323
387
  | `apply_to_context` | Get file-level suggestions for the current task |
@@ -339,8 +403,13 @@ npx -y @xmu-labs/xkg@latest <command>
339
403
  | `npx -y @xmu-labs/xkg@latest` | Open the hosted app |
340
404
  | `serve-mcp --key YOUR_API_KEY` | Run the local MCP proxy |
341
405
  | `setup-hooks --key YOUR_API_KEY [--briefing] [--vault]` | Install Claude Code hooks |
342
- | `config set agent "claude -p"` | Set which assistant `kos` should drive |
343
- | `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 |
344
413
  | `inbox-check --key YOUR_API_KEY [--briefing]` | Debug the session hook |
345
414
  | `vault-check --key YOUR_API_KEY` | Debug the vault hook |
346
415
  | `help` | Show help |
@@ -353,11 +422,27 @@ Captures live as structured Markdown in **your** GitHub repository. Private by d
353
422
 
354
423
  - **No lock-in** — clone it, move it, delete xkg, keep the files
355
424
  - **No proprietary format** — every capture is a readable `.md` file
356
- - **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
357
426
  - **Any tool can read it** — anything that reads Git or speaks MCP
358
427
  - **BYOK** — your provider, your key, your cost
359
428
  - **Never commit your xkg API key** to source control
360
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
+
361
446
  ### What xkg stores, and where
362
447
 
363
448
  The whole point of this repo being public is that you can check these claims rather than trust them.
@@ -373,17 +458,23 @@ The whole point of this repo being public is that you can check these claims rat
373
458
  | `github_repo` | the *name* of your hub, not its contents |
374
459
  | `pin_hash` | salted scrypt (`src/lib/pin.ts`) |
375
460
  | `api_key` | hashed — not recoverable |
376
- | `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 |
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
 
@@ -398,21 +489,36 @@ Your MCP key is shown exactly once. If you lose it, generate a new one.
398
489
 
399
490
  **PIN unlock is device-bound.** It only works on a device that has already signed in with GitHub. A PIN on its own is not enough to reach your account.
400
491
 
401
- **Revoking access.** Generating a new MCP key invalidates the old key, every signed-in session, and every connected MCP client. Sessions also expire after 30 days.
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.
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
+
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.
504
+
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.
402
506
 
403
507
  ## Cost
404
508
 
405
509
  xkg is BYOK. You bring your own API key. xkg never charges you for inference.
406
510
 
407
- 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.
408
512
 
409
- | Usage | Estimated monthly cost |
410
- | --- | --- |
411
- | 50 captures/month | ~$0.15 |
412
- | 100 captures/month | ~$0.30 |
413
- | 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 |
414
520
 
415
- 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.
416
522
 
417
523
  ## Roadmap
418
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) {