klypix-mcp 1.103.0 → 1.105.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
@@ -10,10 +10,17 @@ over MCP by a process on your machine. In the standalone setup, brain data stays
10
10
  your agent's provider can still receive what the agent reads. Integration depth differs by host — see
11
11
  [Supported hosts](#supported-hosts-and-their-integration-level).
12
12
 
13
- The **Apache-2.0 engine works without the KLYPIX desktop app or a KLYPIX account**. The optional
13
+ Klypix is a **portable project workspace with a shared brain**. This repository provides its
14
+ local MCP server for shared project memory in coding tools. The **Apache-2.0 engine works without the KLYPIX
15
+ desktop app or a KLYPIX account**. The optional
14
16
  [Windows workspace](https://klypix.com/developers) lets people inspect the same project brain
15
17
  beside their files. App-based sharing has its own cloud path; see [Security and permissions](#security-and-permissions).
16
18
 
19
+ For the **Projects & Ideas** path — saved project materials in ChatGPT or Claude chat — follow
20
+ [Save project materials and use them with your assistant](https://klypix.com/guides/connect-klypix-to-chatgpt-and-claude).
21
+ That uses Klypix's separate hosted connection for the spaces you authorize. Installing this local
22
+ MCP server does not set up that connection.
23
+
17
24
  [Install](#quick-start) · [Try your first decision](#your-first-decision-and-correction) ·
18
25
  [Supported hosts](#supported-hosts-and-their-integration-level) ·
19
26
  [Privacy](#security-and-permissions) · [Limits](#current-limitations) · [Uninstall](#uninstall)
@@ -38,6 +45,11 @@ It creates `brain.klypix` if the project has none, wires the editors it finds on
38
45
  registers the `.klypix` merge driver if this is a git repo, and exits only after a real MCP
39
46
  handshake has counted the tools that answered.
40
47
 
48
+ Next, reconnect your coding host and [try your first decision and correction](#your-first-decision-and-correction).
49
+ A successful trial means a fresh session finds your recorded decision and its reason, then finds
50
+ the corrected decision as current. Check the returned source cards; a successful install alone
51
+ does not prove that your host has loaded the tools or that a particular decision was retrieved.
52
+
41
53
  [![Claude Code](https://img.shields.io/badge/Claude_Code-5_lifecycle_hooks-475569?style=flat-square)](#supported-hosts-and-their-integration-level)
42
54
  [![Codex](https://img.shields.io/badge/Codex-native_MCP_%2B_presence-475569?style=flat-square)](#supported-hosts-and-their-integration-level)
43
55
  [![Cursor](https://img.shields.io/badge/Cursor-MCP_config_%2B_rules-475569?style=flat-square)](#supported-hosts-and-their-integration-level)
@@ -158,6 +170,9 @@ so running it from `src/` is fine), gives the project a brain if it doesn't have
158
170
  agent tools you actually have installed, registers the card-level `.klypix` merge driver if it's a
159
171
  git repo, and then **proves the result** before it exits:
160
172
 
173
+ Illustrative output; detected editors, file counts, tool counts and timings vary with the machine
174
+ and installed version:
175
+
161
176
  ```text
162
177
  project E:\work\api (git repository root)
163
178
  brain created brain.klypix — a starter brain, ready for its first decision
@@ -515,6 +530,33 @@ so fresh relevant findings are not crowded out by older area vocabulary.
515
530
  Briefs are **not** injected automatically on Cursor, Cline, Copilot, Gemini CLI or Antigravity —
516
531
  there are no lifecycle hooks on those hosts.
517
532
 
533
+ ### The live projection — the file the agent obeys
534
+
535
+ The coordination bench showed that an agent trusts the instruction file it was launched with
536
+ over anything a tool or hook tells it, and that each host reads a different surface. So the
537
+ brain's state is projected into those files, in two halves:
538
+
539
+ - **Committed, from the brain:** the compact brief block in `AGENTS.md` (standing rules,
540
+ corrections, open handoff cards), refreshed by the Claude Code Stop hook only when its content
541
+ changed, so diffs stay quiet.
542
+ - **Machine-local, from the lane (klypix-mcp 1.104.0):** which sessions are live on this PC and
543
+ which files they hold, the work a stopped session left, and the evidence beside every
544
+ "keep off this file" note — written at session start and at every Stop by the Claude Code and
545
+ Codex hooks into `CLAUDE.local.md` at the project root (Claude Code loads it beside
546
+ `CLAUDE.md`; KLYPIX keeps it out of git through `.git/info/exclude`, never a committed file)
547
+ and into one section per project of the Codex global instructions file
548
+ (`~/.codex/AGENTS.override.md` when you have one, else `~/.codex/AGENTS.md`). A project-level
549
+ `AGENTS.override.md` is never written: Codex loads one file per directory, so it would hide
550
+ your `AGENTS.md`.
551
+
552
+ The block is a snapshot and says so: bounded to about 2.4KB, rewritten only when its content
553
+ changed (the time stamp alone never causes a write), with the keep-off evidence kept first when
554
+ the budget bites, and "nothing live" written in words so an older snapshot never stands. It
555
+ holds the same strings the hooks print, so the file and the session context cannot disagree.
556
+ Sections for projects not touched in a day are pruned from the Codex file, at most eight are
557
+ kept, and `uninstall unlink` strips the block (deleting `CLAUDE.local.md` only when nothing else
558
+ is in it). `brain_sync` remains the live source; the block tells the agent to call it.
559
+
518
560
  ## Capture and corrections
519
561
 
520
562
  `brain_note` accepts structured supporting references and inert verification text:
@@ -1350,7 +1392,10 @@ It strips only KLYPIX's own entries — every other hook and setting in
1350
1392
  `~/.claude/settings.json` stays — backs up each file it edits, and **never deletes a `.klypix`**.
1351
1393
  Removal requires `--yes`. Without it, the command prints the plan and changes nothing;
1352
1394
  there is no interactive confirmation prompt. Run the project-local form in each project you
1353
- want to unlink. A preview exits non-zero while managed files remain.
1395
+ want to unlink. A preview exits non-zero while managed files remain. The project-local form also
1396
+ strips the live-projection block from `CLAUDE.local.md` (the file goes only when nothing else is
1397
+ in it) and this project's section from the Codex global file; the machine-global form strips
1398
+ every live section there.
1354
1399
 
1355
1400
  Your `brain.klypix` is yours — it is a plain ZIP and stays readable with or without this package.
1356
1401
 
@@ -495,7 +495,7 @@ try {
495
495
  // a newer klypix-format cannot. merge-brains and the driver ship here so
496
496
  // brain-history's restore merge, the KLYPIX core and the git driver all
497
497
  // find one engine in this directory.
498
- for (const f of ['global-brain-hook.mjs', 'klypix-format.mjs', 'brain-graveyard.mjs', 'merge-brains.mjs', 'klypix-merge-driver.mjs', 'capture-gap.mjs', 'brain-semantic.mjs', 'semantic-memory.mjs', 'enrichment.mjs', 'provenance.mjs', 'brain-note.mjs', 'brain-evidence.mjs', 'brain-git-hook.mjs', 'git-capture-install.mjs', 'brain-history.mjs', 'app-lease.mjs', 'klypix-core.mjs', 'app-bridge-protocol.mjs', 'app-bridge-client.mjs', 'app-tools.mjs', 'card-files.mjs', 'brain-write-lock.mjs', 'agent-rules.mjs', 'brain-doctor.mjs', 'editor-detect.mjs', 'agent-presence.mjs', 'mcp-presence.mjs', 'repo-state.mjs', 'result-reconcile.mjs', 'finding-routing.mjs', 'presence-relay.mjs', 'mcp-supervisor.mjs', 'mcp-auto-update.mjs', 'runtime-inspector.mjs', 'project-graph.mjs', 'bench.mjs', 'codex-brain-hook.mjs', 'codex-hooks.mjs', 'session-handoff.mjs', 'canvas-view-app.html']) {
498
+ for (const f of ['global-brain-hook.mjs', 'klypix-format.mjs', 'brain-graveyard.mjs', 'merge-brains.mjs', 'klypix-merge-driver.mjs', 'capture-gap.mjs', 'brain-semantic.mjs', 'semantic-memory.mjs', 'enrichment.mjs', 'provenance.mjs', 'brain-note.mjs', 'brain-evidence.mjs', 'brain-git-hook.mjs', 'git-capture-install.mjs', 'brain-history.mjs', 'app-lease.mjs', 'klypix-core.mjs', 'app-bridge-protocol.mjs', 'app-bridge-client.mjs', 'app-tools.mjs', 'card-files.mjs', 'brain-write-lock.mjs', 'agent-rules.mjs', 'brain-doctor.mjs', 'editor-detect.mjs', 'agent-presence.mjs', 'mcp-presence.mjs', 'repo-state.mjs', 'result-reconcile.mjs', 'finding-routing.mjs', 'presence-relay.mjs', 'mcp-supervisor.mjs', 'mcp-auto-update.mjs', 'runtime-inspector.mjs', 'project-graph.mjs', 'bench.mjs', 'codex-brain-hook.mjs', 'codex-hooks.mjs', 'session-handoff.mjs', 'live-projection.mjs', 'whose.mjs', 'whose-classify.mjs', 'whose-ledger.mjs', 'whose-rescue.mjs', 'canvas-view-app.html']) {
499
499
  const s = path.join(SRC, f); if (exists(s)) staged.push({ dst: f, content: fs.readFileSync(s, 'utf8') });
500
500
  }
501
501
  for (const [src, dst] of [
@@ -568,11 +568,16 @@ try {
568
568
  ['SessionStart', { matcher: 'startup|resume|clear', hooks: [{ type: 'command', command: brainCmd('') }] }],
569
569
  ['UserPromptSubmit', { hooks: [{ type: 'command', command: brainCmd('--prompt'), timeout: 10 }] }],
570
570
  ['Stop', { hooks: [{ type: 'command', command: brainCmd('--capture') }] }],
571
- ['PostToolUse', { matcher: 'Bash|PowerShell|Edit|Write', hooks: [{ type: 'command', command: brainCmd('--live'), timeout: 10 }] }],
571
+ ['PostToolUse', { matcher: 'Bash|PowerShell|Edit|Write|MultiEdit|NotebookEdit', hooks: [{ type: 'command', command: brainCmd('--live'), timeout: 10 }] }],
572
572
  // Guard cards (2026-08-24): pre-action lane. Fast path — reads only the
573
573
  // compiled guard sidecar; 'warn' injects context, 'block' denies with
574
574
  // the card's message. Absent guards = stat + one tiny read, then no-op.
575
575
  ['PreToolUse', { matcher: 'Bash|PowerShell|Edit|Write', hooks: [{ type: 'command', command: brainCmd('--guard'), timeout: 10 }] }],
576
+ // Session end (1.105.0): a closed or cut-off Claude Code session leaves
577
+ // the lane at once. Before this, only the 10-minute TTL removed it, so the
578
+ // next session read a dead peer as "live" and refused to continue its
579
+ // work (round-4 continuity bench, 2026-10-20). Codex already had this.
580
+ ['SessionEnd', { hooks: [{ type: 'command', command: brainCmd('--end'), timeout: 10 }] }],
576
581
  ];
577
582
  const stripOurs = (arr) => (Array.isArray(arr) ? arr : [])
578
583
  .map(g => (g && Array.isArray(g.hooks)) ? { ...g, hooks: g.hooks.filter(h => !(typeof h?.command === 'string' && h.command.includes(HOOK_MARK))) } : g)
@@ -679,7 +684,7 @@ try {
679
684
  console.log(`✓ installed klypix brain v${VERSION} → ${BRAIN_DIR} (${n} scripts, ${deps} dep packages)`);
680
685
  reportSemantic(semanticStep);
681
686
  if (RUNTIME_ONLY) console.log('✓ runtime-only update: host settings and project files were preserved');
682
- else if (!notWired.length) console.log('✓ wired 5 hooks: SessionStart · UserPromptSubmit (--prompt) · Stop (--capture) · PostToolUse (--live) · PreToolUse (--guard) → settings.json');
687
+ else if (!notWired.length) console.log('✓ wired 6 hooks: SessionEnd (--end) · SessionStart · UserPromptSubmit (--prompt) · Stop (--capture) · PostToolUse (--live) · PreToolUse (--guard) → settings.json');
683
688
  else console.error(`⚠ readiness: ${notWired.length} hook(s) did NOT take (${notWired.join(', ')}) — the brain will read but not capture/sync. Re-run \`npx klypix-mcp install --force\` or check ${SETTINGS}.`);
684
689
  console.log(`✓ MCP supervisor runs from the local bundle (node ${fwd(path.join(BRAIN_DIR, 'klypix-mcp-server.mjs'))}) — compatible core updates activate without restarting the host.`);
685
690
  if (migrated) console.log(`✓ migrated ${migrated.file} klypix-canvas server: ${migrated.from} → ${migrated.to} (backup: .mcp.json.klypix-bak). Reconnect (/mcp) or restart to pick it up.`);
@@ -19,7 +19,7 @@ const PKG_VERSION = (() => {
19
19
  }
20
20
  })();
21
21
 
22
- const DIRECT = new Set(['install', 'link', 'doctor', 'runtime', 'conformance', 'garden-code', 'init', 'git-driver', 'git-hook', 'brain-history', 'brain-deleted', 'orphans', 'diff', 'pr-brief', 'uninstall', 'bench', 'sessions']);
22
+ const DIRECT = new Set(['install', 'link', 'doctor', 'runtime', 'conformance', 'garden-code', 'init', 'git-driver', 'git-hook', 'brain-history', 'brain-deleted', 'orphans', 'diff', 'pr-brief', 'uninstall', 'bench', 'sessions', 'rescue']);
23
23
 
24
24
  const USAGE = [
25
25
  `klypix-mcp ${PKG_VERSION} — shared project brain + MCP coordination server.`,
@@ -41,6 +41,8 @@ const USAGE = [
41
41
  ' git-hook [install|remove|status] wire the agent-neutral commit-capture hook (any agent/branch/worktree → brain cards)',
42
42
  ' brain-history [list|restore <id>] restore points for this brain — undo an accidental delete, edit, or overwrite',
43
43
  ' brain-deleted [list|restore|purge] recycle bin for this brain — cards you deleted, kept recoverable',
44
+ ' rescue [list|show <id>|restore <id> [paths…]|prune]',
45
+ ' copies of uncommitted files KLYPIX saved before a git command could discard them',
44
46
  ' orphans [brain] [--apply|--areas] orphan gardener backfill: report unconnected cards, link the confident subset (dry-run default)',
45
47
  ' diff [ref] [--brain <path>] readable brain diff vs a git ref (default HEAD) — markdown to stdout',
46
48
  ' pr-brief [baseRef] [--brain <path>] brain decisions touching the files changed since baseRef — PR-comment markdown',
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+ // `klypix-mcp rescue [list|show <n|id>|restore <n|id> [paths…]|prune]`
3
+ // The human surface for the rescue copies KLYPIX takes before a git command
4
+ // could discard uncommitted files (src/whose-rescue.mjs). A copy nobody can
5
+ // find is a copy nobody trusts, so `list` is the default, `show` says exactly
6
+ // what a copy holds and what it left out, and `restore` says what it changed
7
+ // and how to undo it — a restore first saves the files it is about to replace.
8
+ //
9
+ // A restore writes the working tree only. It never stages, commits, switches a
10
+ // branch or touches the git index.
11
+ //
12
+ // ARGV: process.argv.slice(2) — the standalone shape. The dispatcher
13
+ // (bin/klypix-worker.mjs runVerb) removes its own `rescue` token first.
14
+ import path from 'path';
15
+ import {
16
+ listRescuesResult, showRescue, restoreRescue, pruneRescues,
17
+ rescueRestoreCommand, RESCUE_KEEP, RESCUE_MAX_AGE_MS,
18
+ } from '../src/whose-rescue.mjs';
19
+
20
+ const USAGE = [
21
+ 'klypix-mcp rescue — copies of uncommitted files KLYPIX saved before a git command could discard them.',
22
+ '',
23
+ 'Usage:',
24
+ ' npx klypix-mcp rescue [list] [--all] newest first (--all: every worktree of this repository)',
25
+ ' npx klypix-mcp rescue show <n|id> what one copy holds, and what it left out',
26
+ ' npx klypix-mcp rescue restore <n|id> [paths…] write its files back (all, or the paths/folders named)',
27
+ ' npx klypix-mcp rescue prune [--days 14] [--keep 50] [--dry-run]',
28
+ '',
29
+ 'Flags: --repo <dir> (default: the current folder), --json, --other-worktree (restore a copy taken in another worktree here).',
30
+ '<n> is the number `list` shows; an id may be shortened while it stays unique.',
31
+ 'A restore first saves the files it replaces as a new copy, so it can be undone the same way.',
32
+ 'It writes the working tree only: nothing is staged, committed or switched.',
33
+ ].join('\n');
34
+
35
+ const raw = process.argv.slice(2);
36
+ if (raw.includes('--help') || raw.includes('-h')) { console.log(USAGE); process.exit(0); }
37
+
38
+ const VALUE_FLAGS = new Set(['--repo', '--days', '--keep']);
39
+ const BOOL_FLAGS = new Set(['--json', '--all', '--dry-run', '--other-worktree']);
40
+ const flags = {};
41
+ const positional = [];
42
+ for (let i = 0; i < raw.length; i++) {
43
+ const a = raw[i];
44
+ if (a === '--') { positional.push(...raw.slice(i + 1)); break; }
45
+ if (VALUE_FLAGS.has(a)) {
46
+ if (i + 1 >= raw.length) { console.error(`klypix-mcp rescue: ${a} needs a value.\n\n${USAGE}`); process.exit(2); }
47
+ flags[a] = raw[++i];
48
+ } else if (BOOL_FLAGS.has(a)) flags[a] = true;
49
+ else if (a.startsWith('-') && a.length > 1) { console.error(`klypix-mcp rescue: unknown flag ${a}\n\n${USAGE}`); process.exit(2); }
50
+ else positional.push(a);
51
+ }
52
+ const ACTIONS = ['list', 'show', 'restore', 'prune'];
53
+ const action = ACTIONS.includes(positional[0]) ? positional.shift() : 'list';
54
+ if (action === 'list' && positional.length) {
55
+ console.error(`klypix-mcp rescue: unknown command "${positional[0]}".\n\n${USAGE}`);
56
+ process.exit(2);
57
+ }
58
+ const repoRoot = path.resolve(flags['--repo'] || process.cwd());
59
+ const json = Boolean(flags['--json']);
60
+ const out = (value) => console.log(JSON.stringify(value, null, 2));
61
+
62
+ const ago = (ts) => {
63
+ const m = Math.max(0, Math.round((Date.now() - ts) / 60000));
64
+ if (m < 1) return 'just now';
65
+ if (m < 60) return `${m}m ago`;
66
+ const h = Math.round(m / 60);
67
+ return h < 48 ? `${h}h ago` : `${Math.round(h / 24)}d ago`;
68
+ };
69
+ const utc = (ts) => new Date(ts).toISOString().replace('T', ' ').replace(/\.\d+Z$/, ' UTC');
70
+ const plural = (n, word, many = `${word}s`) => `${n} ${n === 1 ? word : many}`;
71
+ const SHOW = 12;
72
+ const listLines = (items, fmt) => {
73
+ for (const x of items.slice(0, SHOW)) console.log(` ${fmt(x)}`);
74
+ if (items.length > SHOW) console.log(` …and ${items.length - SHOW} more`);
75
+ };
76
+ const summary = (e) => {
77
+ const absent = e.deleted.length ? ` (${e.deleted.length} absent)` : '';
78
+ const left = e.skipped.length ? `, ${e.skipped.length} not included` : '';
79
+ return `${plural(e.paths.length, 'file')}${absent}${left}`;
80
+ };
81
+
82
+ if (action === 'list') {
83
+ const res = listRescuesResult({ repoRoot, allWorktrees: Boolean(flags['--all']) });
84
+ if (!res.ok) { console.error(`klypix-mcp rescue: ${res.error}`); process.exit(1); }
85
+ if (json) { out(res.rescues); process.exit(0); }
86
+ if (!res.rescues.length) {
87
+ console.log(`No rescue copies in ${res.top}${flags['--all'] ? '' : ' (this worktree)'}.`);
88
+ if (res.elsewhere) console.log(`${plural(res.elsewhere, 'copy', 'copies')} taken in other worktrees of this repository: add --all.`);
89
+ console.log('KLYPIX takes one before a git command could discard uncommitted files.');
90
+ process.exit(0);
91
+ }
92
+ console.log(`Rescue copies in ${res.top}, newest first:`);
93
+ // Numbers count this worktree's copies only — the same numbering `show <n>`
94
+ // and `restore <n>` use — so a copy from another worktree gets none.
95
+ let n = 0;
96
+ for (const e of res.rescues) {
97
+ const num = e.here ? String(++n) : '-';
98
+ const where = e.here ? '' : ` [${e.worktree}]`;
99
+ console.log(` ${num.padStart(3)} ${e.id} ${ago(e.at).padEnd(9)} ${e.reason} · ${summary(e)}${where}`);
100
+ }
101
+ if (!flags['--all'] && res.elsewhere) console.log(` (+${plural(res.elsewhere, 'copy', 'copies')} from other worktrees: add --all)`);
102
+ console.log('');
103
+ console.log('Show one: npx klypix-mcp rescue show <n|id>');
104
+ console.log('Restore one: npx klypix-mcp rescue restore <n|id> [paths…]');
105
+ console.log('A restore saves the current files first, so it can be undone the same way.');
106
+ process.exit(0);
107
+ }
108
+
109
+ if (action === 'show') {
110
+ if (!positional[0]) { console.error(`Usage: npx klypix-mcp rescue show <n|id>\n\n${USAGE}`); process.exit(2); }
111
+ const res = showRescue({ repoRoot, id: positional[0] });
112
+ if (json) { out(res); process.exit(res.ok ? 0 : 1); }
113
+ if (!res.ok) { console.error(`klypix-mcp rescue: ${res.error}`); process.exit(1); }
114
+ console.log(`Rescue ${res.id} — ${res.reason}`);
115
+ console.log(` taken ${ago(res.at)} (${utc(res.at)})${res.sid ? ` by session ${res.sid}` : ''}`);
116
+ console.log(` on ${res.branch || 'a detached HEAD'}${res.head ? ` @ ${res.head.slice(0, 10)}` : ' (no commit yet)'}${res.worktree ? `, in ${res.worktree}` : ''}${res.here ? '' : ' — ANOTHER worktree'}`);
117
+ console.log(` git ${res.ref} (commit ${res.commit.slice(0, 10)})`);
118
+ console.log(`\n${plural(res.files.length, 'file')}; ${res.stat.added} line(s) added and ${res.stat.removed} removed against that HEAD:`);
119
+ const width = Math.min(48, Math.max(...res.files.map((f) => f.path.length), 4));
120
+ listLines(res.files, (f) => {
121
+ const lines = f.binary ? 'binary' : f.change === 'deleted' || f.change === 'absent' ? '' : `+${f.added} -${f.removed}`;
122
+ return `${f.change.padEnd(12)} ${f.path.padEnd(width)} ${lines}`.trimEnd();
123
+ });
124
+ if (res.skipped.length) {
125
+ console.log('\nNot included:');
126
+ listLines(res.skipped, (s) => `${s.path} (${s.why})`);
127
+ }
128
+ console.log(`\nRestore: ${rescueRestoreCommand(res.id)} [paths…]`);
129
+ console.log(`Or with git: ${res.head ? `git diff ${res.commit.slice(0, 10)}^ ${res.commit.slice(0, 10)}` : `git show ${res.commit.slice(0, 10)}`}`);
130
+ process.exit(0);
131
+ }
132
+
133
+ if (action === 'restore') {
134
+ if (!positional[0]) {
135
+ console.error('Usage: npx klypix-mcp rescue restore <n|id> [paths…]');
136
+ console.error('Run `npx klypix-mcp rescue list` to see the copies.');
137
+ process.exit(2);
138
+ }
139
+ const [id, ...paths] = positional;
140
+ const res = restoreRescue({
141
+ repoRoot, id, paths: paths.length ? paths : null, sid: 'cli', cwd: process.cwd(),
142
+ allowOtherWorktree: Boolean(flags['--other-worktree']),
143
+ });
144
+ if (json) { out(res); process.exit(res.ok && !res.skipped?.length ? 0 : 1); }
145
+ if (!res.ok) {
146
+ console.error(`Restore failed: ${res.error}`);
147
+ if (res.skipped?.length) listLines(res.skipped, (s) => `${s.path} — ${s.why}`);
148
+ process.exit(1);
149
+ }
150
+ const written = res.restored.filter((p) => !res.deleted.includes(p));
151
+ if (res.restored.length) {
152
+ console.log(`Restored ${plural(res.restored.length, 'path')} from rescue ${res.from}:`);
153
+ listLines(written, (p) => p);
154
+ if (res.deleted.length) listLines(res.deleted, (p) => `removed ${p} (it did not exist when the copy was taken)`);
155
+ } else console.log(`Nothing needed writing from rescue ${res.from}.`);
156
+ if (res.unchanged.length) {
157
+ console.log('Already as in the copy:');
158
+ listLines(res.unchanged, (p) => p);
159
+ }
160
+ if (res.skipped.length) {
161
+ console.log('Left as it is:');
162
+ listLines(res.skipped, (s) => `${s.path} — ${s.why}`);
163
+ }
164
+ if (res.backupId) {
165
+ console.log(`\nWhat was there before is saved as ${res.backupId}. Undo this restore: ${rescueRestoreCommand(res.backupId)}`);
166
+ }
167
+ console.log('Only the working tree changed: nothing was staged or committed. `git status` shows the result.');
168
+ process.exit(res.skipped.length ? 1 : 0);
169
+ }
170
+
171
+ // prune
172
+ const days = flags['--days'] != null ? Number(flags['--days']) : RESCUE_MAX_AGE_MS / 86400000;
173
+ const keep = flags['--keep'] != null ? Number(flags['--keep']) : RESCUE_KEEP;
174
+ if (!Number.isFinite(days) || days < 0 || !Number.isInteger(keep) || keep < 0) {
175
+ console.error(`klypix-mcp rescue prune: --days takes a number of days and --keep a whole number.\n\n${USAGE}`);
176
+ process.exit(2);
177
+ }
178
+ const res = pruneRescues({ repoRoot, keep, maxAgeMs: days * 86400000, dryRun: Boolean(flags['--dry-run']) });
179
+ if (json) { out(res); process.exit(res.ok ? 0 : 1); }
180
+ if (!res.ok) { console.error(`klypix-mcp rescue: ${res.error}`); process.exit(1); }
181
+ const rule = `older than ${days} day(s), keeping the newest ${keep} of each worktree`;
182
+ if (flags['--dry-run']) {
183
+ const would = res.wouldDelete || [];
184
+ console.log(would.length ? `Would prune ${plural(would.length, 'copy', 'copies')} ${rule}:` : `Nothing to prune (${rule}).`);
185
+ listLines(would, (x) => x);
186
+ console.log('DRY RUN — nothing was deleted.');
187
+ } else {
188
+ console.log(res.deleted.length ? `Pruned ${plural(res.deleted.length, 'copy', 'copies')} ${rule}; ${res.kept} remain.` : `Nothing to prune (${rule}); ${res.kept} kept.`);
189
+ }
190
+ process.exit(0);
@@ -190,6 +190,11 @@ await runVerb('pr-brief', './klypix-pr-brief.mjs');
190
190
  // CLI's own --help) said no uninstall existed at all.
191
191
  await runVerb('uninstall', './klypix-uninstall.mjs');
192
192
 
193
+ // `npx klypix-mcp rescue` — list, show and restore the rescue copies KLYPIX
194
+ // takes before a git command could discard uncommitted files (whose-rescue.mjs).
195
+ // A restore writes the working tree only, after saving what it replaces.
196
+ await runVerb('rescue', './klypix-rescue.mjs');
197
+
193
198
  // `npx klypix-mcp garden-code` — the HUMAN half of the garden approval gate.
194
199
  // brain_garden's apply requires an 8-char code derived from the exact dormant
195
200
  // candidate set + day; the agent is deliberately never shown it. The human runs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klypix-mcp",
3
- "version": "1.103.0",
3
+ "version": "1.105.0",
4
4
  "mcpName": "io.github.dahshanlabs/klypix-mcp",
5
5
  "description": "Shared project memory for Claude Code, Codex and Cursor: decisions and corrections through MCP.",
6
6
  "type": "module",
@@ -91,7 +91,7 @@
91
91
  "bench": "node bin/klypix-mcp.mjs bench",
92
92
  "test:bench": "node test/bench.mjs",
93
93
  "pretest": "node test/publish-workflow.mjs",
94
- "test": "node test/publish-verdict.mjs && node test/npx-owned-names.mjs && node test/project-graph.mjs && node test/project-map-cli.mjs && node test/mcp-auto-update.mjs && node test/mcp-supervisor.mjs && node test/runtime-inspector.mjs && node test/codex-hooks.mjs && node test/request-identity.mjs && node test/session-identity-core.mjs && node test/agent-presence.mjs && node test/message-delivery-v3.mjs && node test/claude-message-delivery-v3.mjs && node test/session-mailbox.mjs && node test/session-reopen.mjs && node test/session-handoff.mjs && node test/lock-notes.mjs && node test/lane-write-retry.mjs && node test/result-reconcile.mjs && node test/evidence-publication-gate.mjs && node test/release-evidence-cli.mjs && node test/intent-guard.mjs && node test/git-capture-install.mjs && node test/brain-history.mjs && node test/brain-graveyard.mjs && node test/archived-visibility.mjs && node test/finding-routing.mjs && node test/finding-routing-hook.mjs && node test/presence-relay.mjs && node test/install-version.mjs && node test/install-rename-backoff.mjs && node test/project-binding-rebind.mjs && node test/context-gateway.mjs && node test/repo-state.mjs && node test/released-tag-guard.mjs && node test/conformance.mjs && node test/brain-doctor.mjs && node test/version-currency.mjs && node test/ship-capture.mjs && node test/capture-gap.mjs && node test/lane-message.mjs && node test/brain-quality.mjs && node test/brain-connect-orphans.mjs && node test/orphan-gardener.mjs && node test/brief-and-recall.mjs && node test/guard-cards.mjs && node test/layout-cluster.mjs && node test/brain-ask.mjs && node test/brain-ask-words.mjs && node test/retrieval-fusion.mjs && node test/eval-retrieval.mjs && node test/field-report-2026-07-04.mjs && node test/autoprop.mjs && node test/overlay-recency-2026-07-12.mjs && node test/brain-challenge.mjs && node test/brain-lens.mjs && node test/brain-kind.mjs && node test/rule-drafts.mjs && node test/claim-engine.mjs && node test/partial-notes.mjs && node test/lifecycle-prefix.mjs && node test/close-link-safety.mjs && node test/resolve-ledger.mjs && node test/plan-fulfillment.mjs && node test/arrange-receipts.mjs && node test/skill-staleness.mjs && node test/canvas-view.mjs && node test/status-completeness.mjs && node test/semantic-security.mjs && node test/semantic-gate.mjs && node test/memory-runtime.mjs && node test/semantic-cache.mjs && node test/semantic-preparation.mjs && node test/semantic-hash-parity.mjs && node test/enrichment.mjs && node test/provenance.mjs && node test/confirm-trail.mjs && node test/hook-fallback.mjs && node test/eval-hook-lane.mjs && node test/hook-unified-lane.mjs && node test/decay-status.mjs && node test/decay-hook.mjs && node test/marker-suffix-grammar.mjs && node test/marker-after-colon.mjs && node test/evidence-anchors.mjs && node test/brain-evidence.mjs && node test/presence-visibility.mjs && node test/undeclared-active.mjs && node test/presence-liveness.mjs && node test/observed-scope.mjs && node test/overlap-tiers.mjs && node test/coordination-bench.mjs && node test/release-lease.mjs && node test/release-reconcile.mjs && node test/release-ancestry.mjs && node test/release-claim-join.mjs && node test/release-claims.mjs && node test/release-handshake.mjs && node test/completion-guard.mjs && node test/results-not-manifests.mjs && node test/merge-brains.mjs && node test/merge-readings.mjs && node test/revival-map.mjs && node test/merge-scale.mjs && node test/concurrent-writes.mjs && node test/lock-interop.mjs && node test/capture-write-failure.mjs && node test/a2a-smoke.mjs && node test/one-command-setup.mjs && node test/cli-args.mjs && node test/format-guard.mjs && node test/canvas-groups.mjs && node test/git-tools.mjs && node test/link-compat.mjs && node test/uninstall.mjs && node test/current-guidance.mjs && node test/current-first.mjs && node test/status-shape.mjs && node test/status-hook.mjs && node test/read-canvas-saved-readings.mjs && node test/document-readings.mjs && node test/open-lease.mjs && node test/agent-card-style.mjs && node test/tool-schema-freeze.mjs && node test/app-lease-vectors.mjs && node test/app-tools-stdio.mjs && node test/card-contents-files.mjs && node test/codex-app-table.mjs && node test/plugin-mode.mjs && node test/app-bridge-vectors.mjs && node test/app-bridge-client.mjs && node test/app-tools-static.mjs && node test/app-tools-stdio-live.mjs",
94
+ "test": "node test/publish-verdict.mjs && node test/npx-owned-names.mjs && node test/project-graph.mjs && node test/project-map-cli.mjs && node test/mcp-auto-update.mjs && node test/mcp-supervisor.mjs && node test/runtime-inspector.mjs && node test/codex-hooks.mjs && node test/request-identity.mjs && node test/session-identity-core.mjs && node test/agent-presence.mjs && node test/message-delivery-v3.mjs && node test/claude-message-delivery-v3.mjs && node test/session-mailbox.mjs && node test/session-reopen.mjs && node test/session-handoff.mjs && node test/lock-notes.mjs && node test/whose-classify.mjs && node test/whose-ledger.mjs && node test/whose-rescue.mjs && node test/whose-hook.mjs && node test/live-projection.mjs && node test/lane-write-retry.mjs && node test/result-reconcile.mjs && node test/evidence-publication-gate.mjs && node test/release-evidence-cli.mjs && node test/intent-guard.mjs && node test/git-capture-install.mjs && node test/brain-history.mjs && node test/brain-graveyard.mjs && node test/archived-visibility.mjs && node test/finding-routing.mjs && node test/finding-routing-hook.mjs && node test/presence-relay.mjs && node test/install-version.mjs && node test/install-rename-backoff.mjs && node test/project-binding-rebind.mjs && node test/context-gateway.mjs && node test/repo-state.mjs && node test/released-tag-guard.mjs && node test/conformance.mjs && node test/brain-doctor.mjs && node test/version-currency.mjs && node test/ship-capture.mjs && node test/capture-gap.mjs && node test/lane-message.mjs && node test/brain-quality.mjs && node test/brain-connect-orphans.mjs && node test/orphan-gardener.mjs && node test/brief-and-recall.mjs && node test/guard-cards.mjs && node test/layout-cluster.mjs && node test/brain-ask.mjs && node test/brain-ask-words.mjs && node test/retrieval-fusion.mjs && node test/eval-retrieval.mjs && node test/field-report-2026-07-04.mjs && node test/autoprop.mjs && node test/overlay-recency-2026-07-12.mjs && node test/brain-challenge.mjs && node test/brain-lens.mjs && node test/brain-kind.mjs && node test/rule-drafts.mjs && node test/claim-engine.mjs && node test/partial-notes.mjs && node test/lifecycle-prefix.mjs && node test/close-link-safety.mjs && node test/resolve-ledger.mjs && node test/plan-fulfillment.mjs && node test/arrange-receipts.mjs && node test/skill-staleness.mjs && node test/canvas-view.mjs && node test/status-completeness.mjs && node test/semantic-security.mjs && node test/semantic-gate.mjs && node test/memory-runtime.mjs && node test/semantic-cache.mjs && node test/semantic-preparation.mjs && node test/semantic-hash-parity.mjs && node test/enrichment.mjs && node test/provenance.mjs && node test/confirm-trail.mjs && node test/hook-fallback.mjs && node test/eval-hook-lane.mjs && node test/hook-unified-lane.mjs && node test/decay-status.mjs && node test/decay-hook.mjs && node test/marker-suffix-grammar.mjs && node test/marker-after-colon.mjs && node test/evidence-anchors.mjs && node test/brain-evidence.mjs && node test/presence-visibility.mjs && node test/undeclared-active.mjs && node test/presence-liveness.mjs && node test/observed-scope.mjs && node test/overlap-tiers.mjs && node test/coordination-bench.mjs && node test/release-lease.mjs && node test/release-reconcile.mjs && node test/release-ancestry.mjs && node test/release-claim-join.mjs && node test/release-claims.mjs && node test/release-handshake.mjs && node test/completion-guard.mjs && node test/results-not-manifests.mjs && node test/merge-brains.mjs && node test/merge-readings.mjs && node test/revival-map.mjs && node test/merge-scale.mjs && node test/concurrent-writes.mjs && node test/lock-interop.mjs && node test/capture-write-failure.mjs && node test/a2a-smoke.mjs && node test/one-command-setup.mjs && node test/cli-args.mjs && node test/format-guard.mjs && node test/canvas-groups.mjs && node test/git-tools.mjs && node test/link-compat.mjs && node test/uninstall.mjs && node test/current-guidance.mjs && node test/current-first.mjs && node test/status-shape.mjs && node test/status-hook.mjs && node test/read-canvas-saved-readings.mjs && node test/document-readings.mjs && node test/open-lease.mjs && node test/agent-card-style.mjs && node test/tool-schema-freeze.mjs && node test/app-lease-vectors.mjs && node test/app-tools-stdio.mjs && node test/card-contents-files.mjs && node test/codex-app-table.mjs && node test/plugin-mode.mjs && node test/app-bridge-vectors.mjs && node test/app-bridge-client.mjs && node test/app-tools-static.mjs && node test/app-tools-stdio-live.mjs",
95
95
  "test:memory": "node test/memory-runtime.mjs",
96
96
  "test:memory:soak": "node --expose-gc test/memory-soak.mjs",
97
97
  "runtime": "node bin/klypix-runtime.mjs",
@@ -101,7 +101,7 @@
101
101
  },
102
102
  "dependencies": {
103
103
  "@modelcontextprotocol/ext-apps": "^1.7.5",
104
- "@modelcontextprotocol/sdk": "^1.31.0",
104
+ "@modelcontextprotocol/sdk": "^1.32.1",
105
105
  "fractional-indexing": "^3.2.0",
106
106
  "jpeg-js": "^0.4.4",
107
107
  "jszip": "^3.10.1",
@@ -515,7 +515,9 @@ commits and the checkouts it worked in (with their uncommitted files) automatica
515
515
  \`brain_sync\` may list **unfinished work** another session left without a card: if the human asks
516
516
  you to continue it, take it with \`brain_handoff\` {action:"take", session:"<id>"} first, and look
517
517
  first in the checkout the record names — it may be a side folder, not the project. That list is history, not a lock — a stopped session holds no claim on its
518
- files. Never ask the human to paste a summary into the next session.
518
+ files. A LIVE session's uncommitted edits are its work in progress: never sweep, discard or stash them
519
+ (\`git add -A\`, \`commit -a\`, \`reset --hard\`, \`stash\`); commit only your own files. A stopped session's leftovers
520
+ are yours to finish and commit when you are continuing its work; never discard them without the human. Never ask the human to paste a summary into the next session.
519
521
 
520
522
  **A note that asks agents to keep off a file**, whatever its wording (here, in AGENTS.md / CLAUDE.md or any
521
523
  notes file) can outlive the work it protected. Before you obey one, or before you edit the file