mnemos-capture 1.4.0 → 1.5.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,9 +1,9 @@
1
1
  <div align="center">
2
2
  <br>
3
- <a href="https://mnemos-capture.vercel.app">
3
+ <a href="https://xkg.xmu.sh">
4
4
  <picture>
5
- <source media="(prefers-color-scheme: dark)" srcset="public/mnemos-hero-dark.gif">
6
- <img src="public/mnemos-hero-light.gif" width="160" height="160" alt="xkg">
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
7
  </picture>
8
8
  </a>
9
9
  <br>
@@ -53,7 +53,7 @@ You find useful stuff, you capture through the web app, then xkg connects it int
53
53
  xkg will continuously compound that knowledge over time — keeping AI workers' context up to date as human knowledge evolves.
54
54
 
55
55
  > [!IMPORTANT]
56
- > We are migrating from **mnemos** / **Xmu** to **xkg**. The app URL, this GitHub repo, and the npm package still say `mnemos-capture`. Sorry for the inconvenience.
56
+ > We are migrating from **mnemos** / **Xmu** to **xkg**. The app now lives at [xkg.xmu.sh](https://xkg.xmu.sh) and the code at [xmu-labs/xkg](https://github.com/xmu-labs/xkg). Connectors set up with `mnemos-capture.vercel.app` keep working. The npm package still says `mnemos-capture`. Sorry for the inconvenience.
57
57
 
58
58
  ## Start here
59
59
 
@@ -61,7 +61,7 @@ You do not need a terminal to start.
61
61
 
62
62
  ### 1. Create your account
63
63
 
64
- Open **[mnemos-capture.vercel.app](https://mnemos-capture.vercel.app)** and sign in with GitHub.
64
+ Open **[xkg.xmu.sh](https://xkg.xmu.sh)** and sign in with GitHub.
65
65
 
66
66
  xkg will:
67
67
 
@@ -91,7 +91,7 @@ xkg is LLM-agnostic. Any worker that speaks MCP can use the same knowledge.
91
91
  Two primitives:
92
92
 
93
93
  ```text
94
- https://mnemos-capture.vercel.app/api/mcp
94
+ https://xkg.xmu.sh/api/mcp
95
95
  ```
96
96
 
97
97
  ```bash
@@ -104,7 +104,7 @@ The CLI binary is **`xkg`** (also `xmu` and `mnemos-capture`). `npx` still uses
104
104
 
105
105
  ### On the web
106
106
 
107
- Name the connector `xkg`, paste `https://mnemos-capture.vercel.app/api/mcp`, sign in with GitHub when asked.
107
+ Name the connector `xkg`, paste `https://xkg.xmu.sh/api/mcp`, sign in with GitHub when asked.
108
108
 
109
109
  #### Claude
110
110
 
@@ -114,7 +114,7 @@ Name the connector `xkg`, paste `https://mnemos-capture.vercel.app/api/mcp`, sig
114
114
 
115
115
  1. Settings → Plugins → Browse plugins
116
116
  2. Name it xkg
117
- 3. Paste `https://mnemos-capture.vercel.app/api/mcp`
117
+ 3. Paste `https://xkg.xmu.sh/api/mcp`
118
118
  4. Sign in with GitHub when asked
119
119
 
120
120
  #### Grok
@@ -192,7 +192,7 @@ claude mcp add xkg -- npx -y mnemos-capture@latest serve-mcp --key YOUR_API_KEY
192
192
  Or the URL, no API key:
193
193
 
194
194
  ```bash
195
- claude mcp add --transport http xkg https://mnemos-capture.vercel.app/api/mcp
195
+ claude mcp add --transport http xkg https://xkg.xmu.sh/api/mcp
196
196
  ```
197
197
 
198
198
  Optional session hooks (MCP works without them):
@@ -233,7 +233,7 @@ If the tool can launch a local process, register the same `npx` command. If it h
233
233
  }
234
234
  ```
235
235
 
236
- No MCP support yet? Capture in the [xkg app](https://mnemos-capture.vercel.app) and connect later.
236
+ No MCP support yet? Capture in the [xkg app](https://xkg.xmu.sh) and connect later.
237
237
 
238
238
  ## Workflow
239
239
 
@@ -249,7 +249,7 @@ No MCP support yet? Capture in the [xkg app](https://mnemos-capture.vercel.app)
249
249
  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
250
  ```
251
251
 
252
- 2. **Brief** — At the start of a work session, ask your worker for a briefing. xkg looks at your project (branch, recent commits, `CLAUDE.md`, repo context) and ranks captures that could help now: **why** it matters, **what** applying it could achieve, **where** it could land. You decide what to apply.
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.
253
253
 
254
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.
255
255
 
@@ -428,12 +428,12 @@ Working on the code? See [CONTRIBUTING.md](CONTRIBUTING.md).
428
428
  If you use xkg in published work:
429
429
 
430
430
  ```bibtex
431
- @misc{2026mnemos,
431
+ @misc{2026xkg,
432
432
  title = {xkg: A Knowledge Graph for AI workers},
433
433
  author = {Sofia Padron Valdez},
434
434
  year = 2026,
435
435
  journal = {GitHub repository},
436
436
  publisher = {GitHub},
437
- howpublished = {\url{https://github.com/Soph20/mnemos-capture}}
437
+ howpublished = {\url{https://github.com/xmu-labs/xkg}}
438
438
  }
439
439
  ```
package/dist/cli/hooks.js CHANGED
@@ -2,14 +2,14 @@ import { homedir, tmpdir } from "os";
2
2
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
3
3
  import { join } from "path";
4
4
  import { execSync } from "child_process";
5
- const HOSTED_URL = "https://mnemos-capture.vercel.app/api/mcp";
5
+ const HOSTED_URL = "https://xkg.xmu.sh/api/mcp";
6
6
  const CLAUDE_SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
7
7
  // Matches both legacy ("mnemos-capture inbox-check") and modern
8
8
  // ("mnemos-capture@latest inbox-check") hook command forms.
9
- function isMnemosHook(cmd) {
9
+ function isXkgHook(cmd) {
10
10
  return cmd.includes("mnemos-capture") && cmd.includes("inbox-check");
11
11
  }
12
- function isMnemosPreToolHook(cmd) {
12
+ function isXkgPreToolHook(cmd) {
13
13
  return cmd.includes("mnemos-capture") && cmd.includes("vault-check");
14
14
  }
15
15
  function readSettings() {
@@ -73,11 +73,11 @@ export function setupHooks(apiKey, opts = {}) {
73
73
  const settings = readSettings();
74
74
  const hooks = settings.hooks ?? {};
75
75
  const sessionStart = hooks.SessionStart ?? [];
76
- // Inspect any existing mnemos hook so we can either skip, migrate, or replace
76
+ // Inspect any existing xkg hook so we can either skip, migrate, or replace
77
77
  let existingCmd = null;
78
78
  for (const group of sessionStart) {
79
79
  for (const h of group.hooks ?? []) {
80
- if (typeof h.command === "string" && isMnemosHook(h.command)) {
80
+ if (typeof h.command === "string" && isXkgHook(h.command)) {
81
81
  existingCmd = h.command;
82
82
  break;
83
83
  }
@@ -97,11 +97,11 @@ export function setupHooks(apiKey, opts = {}) {
97
97
  const command = opts.briefing
98
98
  ? `npx -y mnemos-capture@latest inbox-check --key ${apiKey} --briefing`
99
99
  : `npx -y mnemos-capture@latest inbox-check --key ${apiKey}`;
100
- // Remove any existing mnemos inbox-check hook (handles key rotation too)
100
+ // Remove any existing xkg inbox-check hook (handles key rotation too)
101
101
  const filtered = sessionStart
102
102
  .map((group) => ({
103
103
  ...group,
104
- hooks: (group.hooks ?? []).filter((h) => !(typeof h.command === "string" && isMnemosHook(h.command))),
104
+ hooks: (group.hooks ?? []).filter((h) => !(typeof h.command === "string" && isXkgHook(h.command))),
105
105
  }))
106
106
  .filter((group) => (group.hooks ?? []).length > 0);
107
107
  filtered.push({ hooks: [{ type: "command", command }] });
@@ -128,7 +128,7 @@ export function setupPreToolHook(apiKey, opts = {}) {
128
128
  let existingCmd = null;
129
129
  for (const group of preToolCall) {
130
130
  for (const h of group.hooks ?? []) {
131
- if (typeof h.command === "string" && isMnemosPreToolHook(h.command)) {
131
+ if (typeof h.command === "string" && isXkgPreToolHook(h.command)) {
132
132
  existingCmd = h.command;
133
133
  break;
134
134
  }
@@ -144,7 +144,7 @@ export function setupPreToolHook(apiKey, opts = {}) {
144
144
  const filtered = preToolCall
145
145
  .map((group) => ({
146
146
  ...group,
147
- hooks: (group.hooks ?? []).filter((h) => !(typeof h.command === "string" && isMnemosPreToolHook(h.command))),
147
+ hooks: (group.hooks ?? []).filter((h) => !(typeof h.command === "string" && isXkgPreToolHook(h.command))),
148
148
  }))
149
149
  .filter((group) => (group.hooks ?? []).length > 0);
150
150
  filtered.push({ hooks: [{ type: "command", command }] });
@@ -187,14 +187,7 @@ function detectProjectContext() {
187
187
  parts.push(`Recent commits:\n${log}`);
188
188
  }
189
189
  catch { }
190
- // CLAUDE.md — first 800 chars gives the model real project intent
191
- try {
192
- const root = run("git rev-parse --show-toplevel");
193
- const claudeMd = readFileSync(join(root, "CLAUDE.md"), "utf-8").slice(0, 800).trim();
194
- if (claudeMd)
195
- parts.push(`Project instructions (CLAUDE.md excerpt):\n${claudeMd}`);
196
- }
197
- catch { }
190
+ // Local agent instructions may contain private maintainer notes; do not upload them.
198
191
  return parts.length > 0 ? parts.join("\n\n") : "current project";
199
192
  }
200
193
  export async function callMcp(apiKey, toolName, toolArgs, timeoutMs = 15000) {
package/dist/cli/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const HOSTED_URL = "https://mnemos-capture.vercel.app";
2
+ const HOSTED_URL = "https://xkg.xmu.sh";
3
3
  async function main() {
4
4
  const args = process.argv.slice(2);
5
5
  const command = args[0];
package/dist/cli/kos.js CHANGED
@@ -1,14 +1,14 @@
1
1
  /**
2
- * kos — mnemos' Knowledge Operations orchestrator.
2
+ * kos — xkg's Knowledge Operations orchestrator.
3
3
  *
4
4
  * Drives whatever AI assistant the user configured (`xkg config set agent ...`)
5
- * to implement a generated plan in an isolated git worktree. mnemos owns the
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
8
  */
9
9
  import { tmpdir } from "os";
10
- import { join } from "path";
11
- import { writeFileSync, existsSync, appendFileSync, readFileSync } from "fs";
10
+ import { dirname, join, resolve } from "path";
11
+ import { writeFileSync, existsSync, mkdirSync, readFileSync } from "fs";
12
12
  import { execSync } from "child_process";
13
13
  import { callMcp } from "./hooks.js";
14
14
  import { readConfig } from "./config.js";
@@ -39,14 +39,53 @@ export function parseChecklist(planText) {
39
39
  /** Build a unique, sortable branch name for a kos run. */
40
40
  export function buildKosBranch(now = new Date()) {
41
41
  const stamp = now.toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
42
- return `mnemos/kos-${stamp}`;
42
+ return `xkg/kos-${stamp}`;
43
43
  }
44
+ /** Where kos drops the plan inside the worktree. Never committed — see excludePlanFiles. */
45
+ export const PLAN_FILE = "XKG_PLAN.md";
46
+ /** Plan filenames written by earlier releases; still excluded so an old worktree can't leak one. */
47
+ const LEGACY_PLAN_FILES = ["MNEMOS_PLAN.md"];
44
48
  /** The instruction handed to the assistant. The plan file is the real contract. */
45
- export const KOS_PROMPT = "Implement the plan in MNEMOS_PLAN.md in this repository. " +
49
+ export const KOS_PROMPT = `Implement the plan in ${PLAN_FILE} in this repository. ` +
46
50
  "Follow the Codebase Mapping table exactly — change only the files it lists. " +
47
51
  "For each step apply the thinking effort named in its Effort column " +
48
52
  "(low = quick/mechanical, medium = careful, high = deep reasoning). " +
49
- "When finished, make sure every item in the Verification Checklist is satisfied.";
53
+ "When finished, make sure every item in the Verification Checklist is satisfied. " +
54
+ `${PLAN_FILE} is private: never commit, push, or copy it into another file.`;
55
+ /**
56
+ * Return the exclude-file contents with every plan filename present, or null if
57
+ * nothing needs adding. Pure so the matching can be tested without a repo.
58
+ */
59
+ export function withPlanExcludes(current) {
60
+ const lines = new Set(current.split(/\r?\n/).map((l) => l.trim()));
61
+ const missing = [PLAN_FILE, ...LEGACY_PLAN_FILES].filter((f) => !lines.has(f) && !lines.has(`/${f}`));
62
+ if (missing.length === 0)
63
+ return null;
64
+ const sep = current === "" || current.endsWith("\n") ? "" : "\n";
65
+ return `${current}${sep}${missing.join("\n")}\n`;
66
+ }
67
+ /**
68
+ * Keep plan files out of git for this repository and all its worktrees.
69
+ *
70
+ * Asks git for the exclude path instead of assuming `<root>/.git/info/exclude`:
71
+ * in a linked worktree or a submodule `.git` is a file, so the assumed path does
72
+ * not exist and the plan would be silently committed by the agent's `git add`.
73
+ * Throws rather than continuing unprotected — a plan can carry private context.
74
+ */
75
+ export function excludePlanFiles(worktreePath) {
76
+ const excludePath = resolve(worktreePath, run("git rev-parse --git-path info/exclude", worktreePath));
77
+ mkdirSync(dirname(excludePath), { recursive: true });
78
+ const current = existsSync(excludePath) ? readFileSync(excludePath, "utf-8") : "";
79
+ const next = withPlanExcludes(current);
80
+ if (next !== null)
81
+ writeFileSync(excludePath, next);
82
+ try {
83
+ run(`git check-ignore -q ${PLAN_FILE}`, worktreePath);
84
+ }
85
+ catch {
86
+ throw new Error(`git would track ${PLAN_FILE} (exclude file: ${excludePath})`);
87
+ }
88
+ }
50
89
  // ── Orchestrator ──
51
90
  function run(cmd, cwd) {
52
91
  return execSync(cmd, { cwd, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
@@ -86,19 +125,18 @@ export async function kos(apiKey, opts = {}) {
86
125
  const worktreePath = join(tmpdir(), branch.replace(/\//g, "-"));
87
126
  console.log(`Creating isolated worktree on branch ${branch}...`);
88
127
  run(`git worktree add -b ${branch} "${worktreePath}"`, repoRoot);
89
- // 4. Drop the plan in and keep git from tracking it (local exclude, not committed)
90
- writeFileSync(join(worktreePath, "MNEMOS_PLAN.md"), planText);
128
+ // 4. Keep git from tracking the plan (local exclude, not committed), then drop it in.
129
+ // Exclude first: if that fails, the plan is never written where git can see it.
91
130
  try {
92
- const excludePath = join(repoRoot, ".git", "info", "exclude");
93
- if (existsSync(excludePath)) {
94
- const current = readFileSync(excludePath, "utf-8");
95
- if (!current.includes("MNEMOS_PLAN.md"))
96
- appendFileSync(excludePath, "\nMNEMOS_PLAN.md\n");
97
- }
131
+ excludePlanFiles(worktreePath);
98
132
  }
99
- catch {
100
- // best-effort
133
+ catch (err) {
134
+ console.error(`Could not keep ${PLAN_FILE} out of git, so kos stopped before writing it.`);
135
+ console.error(err instanceof Error ? err.message : String(err));
136
+ console.error(`The empty worktree is at ${worktreePath}.`);
137
+ process.exit(1);
101
138
  }
139
+ writeFileSync(join(worktreePath, PLAN_FILE), planText);
102
140
  // 5. Hand off to the user's assistant, running inside the worktree
103
141
  console.log(`Launching: ${cfg.agent}\n`);
104
142
  try {
@@ -1,6 +1,6 @@
1
1
  // MCP server that runs locally via stdio and proxies to the hosted API.
2
2
  // Used by Claude Code: claude mcp add xkg -- npx mnemos-capture serve-mcp --key <api-key>
3
- const DEFAULT_HOSTED_URL = "https://mnemos-capture.vercel.app/api/mcp";
3
+ const DEFAULT_HOSTED_URL = "https://xkg.xmu.sh/api/mcp";
4
4
  /** Override for pointing the proxy at a local instance during development. */
5
5
  const HOSTED_URL = process.env.XKG_API_URL ?? process.env.XMU_API_URL ?? process.env.MNEMOS_API_URL ?? DEFAULT_HOSTED_URL;
6
6
  function sendMessage(msg) {
@@ -74,7 +74,7 @@ export async function serveMcp() {
74
74
  if (!apiKey) {
75
75
  process.stderr.write("\nxkg MCP server requires an API key.\n");
76
76
  process.stderr.write("Usage: npx -y mnemos-capture@latest serve-mcp --key <your-api-key>\n\n");
77
- process.stderr.write("Get your API key at: https://mnemos-capture.vercel.app/onboard\n\n");
77
+ process.stderr.write("Get your API key at: https://xkg.xmu.sh/onboard\n\n");
78
78
  process.exit(1);
79
79
  }
80
80
  // Self-upgrade: if a newer version exists on npm, respawn via npx @latest.
@@ -1,8 +1,9 @@
1
- // Self-update enforcement: ensures users always run the latest mnemos-capture.
1
+ // Self-update enforcement: ensures users always run the latest xkg CLI.
2
2
  //
3
3
  // Strategy: on serve-mcp startup, query npm for the latest published version.
4
4
  // If behind, respawn the process via `npx -y mnemos-capture@latest` and proxy
5
- // stdio. A MNEMOS_UPGRADED=1 env var prevents infinite recursion.
5
+ // stdio. An XKG_UPGRADED=1 env var (MNEMOS_UPGRADED=1 from releases before
6
+ // 1.4.1) prevents infinite recursion.
6
7
  import { readFileSync } from "fs";
7
8
  import { fileURLToPath } from "url";
8
9
  import { dirname, join } from "path";
@@ -52,8 +53,9 @@ async function getLatestVersion() {
52
53
  * continue with normal startup.
53
54
  */
54
55
  export async function maybeSelfUpgrade() {
55
- // Recursion guard: the child spawned below will inherit this env var.
56
- if (process.env.MNEMOS_UPGRADED === "1")
56
+ // Recursion guard: the child spawned below will inherit this env var. Older
57
+ // releases set MNEMOS_UPGRADED, and may be the parent that spawned this one.
58
+ if (process.env.XKG_UPGRADED === "1" || process.env.MNEMOS_UPGRADED === "1")
57
59
  return false;
58
60
  const current = getCurrentVersion();
59
61
  const latest = await getLatestVersion();
@@ -62,7 +64,7 @@ export async function maybeSelfUpgrade() {
62
64
  process.stderr.write(`xkg: upgrading to v${latest} (was v${current})...\n`);
63
65
  const child = spawn("npx", ["-y", `mnemos-capture@${latest}`, ...process.argv.slice(2)], {
64
66
  stdio: "inherit",
65
- env: { ...process.env, MNEMOS_UPGRADED: "1" },
67
+ env: { ...process.env, XKG_UPGRADED: "1" },
66
68
  });
67
69
  // Keep parent alive until the child exits, then exit with its code.
68
70
  await new Promise((resolve) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnemos-capture",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "xkg — Turn what you discover into context your AI workers can use. Capture once, then brief, plan, and execute with any MCP-compatible tool.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -38,16 +38,16 @@
38
38
  "license": "MIT",
39
39
  "repository": {
40
40
  "type": "git",
41
- "url": "https://github.com/Soph20/mnemos-capture.git"
41
+ "url": "https://github.com/xmu-labs/xkg.git"
42
42
  },
43
- "homepage": "https://github.com/Soph20/mnemos-capture",
43
+ "homepage": "https://github.com/xmu-labs/xkg",
44
44
  "files": [
45
45
  "dist/cli/",
46
46
  "LICENSE",
47
47
  "README.md"
48
48
  ],
49
49
  "dependencies": {
50
- "@anthropic-ai/sdk": "^0.127.0",
50
+ "@anthropic-ai/sdk": "^0.133.0",
51
51
  "@vercel/postgres": "^0.10.0",
52
52
  "next": "^16.3.5",
53
53
  "react": "^19.3.0",