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 +157 -51
- package/dist/cli/{kos.js → agent.js} +64 -25
- package/dist/cli/code-map.js +546 -0
- package/dist/cli/config.js +1 -1
- package/dist/cli/git-hook.js +170 -0
- package/dist/cli/hooks.js +78 -35
- package/dist/cli/index.js +118 -10
- package/dist/cli/mcp-server.js +45 -20
- package/dist/cli/session-start.js +73 -0
- package/package.json +3 -2
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
|
-
<
|
|
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=
|
|
13
|
-
<img src="https://img.shields.io/badge/MCP-compatible-
|
|
14
|
-
<img src="https://img.shields.io/badge/license-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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
295
|
+
npx -y @xmu-labs/xkg@latest agent --key YOUR_API_KEY
|
|
261
296
|
```
|
|
262
297
|
|
|
263
|
-
`
|
|
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
|
-
|
|
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-
|
|
278
|
-
source:
|
|
279
|
-
type:
|
|
280
|
-
|
|
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
|
-
#
|
|
341
|
+
# Alert ownership importance
|
|
285
342
|
|
|
286
343
|
## Core idea
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
295
|
-
|
|
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
|
-
|
|
358
|
+
<details>
|
|
359
|
+
<summary>Source text (sha256: 21275782a8bf…)</summary>
|
|
299
360
|
|
|
300
|
-
|
|
301
|
-
|
|
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
|
|
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
|
-
| `
|
|
343
|
-
| `
|
|
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
|
|
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
|
|
384
|
-
3. **
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
410
|
-
|
|
411
|
-
|
|
|
412
|
-
|
|
|
413
|
-
|
|
|
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
|
-
*
|
|
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
|
|
40
|
-
export function
|
|
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/
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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 =
|
|
125
|
-
const worktreePath =
|
|
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
|
|
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(
|
|
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(`
|
|
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) {
|