klypix-mcp 1.106.2 → 1.107.2

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/A2A.md CHANGED
@@ -55,7 +55,7 @@ the skills below.
55
55
  |---|---|---|
56
56
  | `make_board` | Create a new `.klypix` from cards + connections | a `.klypix` **FilePart** + summary |
57
57
  | `remember` | Append cards/decisions to an existing canvas (positions preserved) | result summary; flat card appends also return a `.klypix` FilePart |
58
- | `learn_skill` | Capture a reusable how-to / gotcha as a 🛠 skill card — resurfaces every session | result summary |
58
+ | `learn_skill` | Capture a reusable how-to / gotcha as a 🛠 skill card — kept until corrected; shown first to sessions that touch the files it names | result summary |
59
59
  | `recall` | Search card text/titles/`#tags` across the vault | matching cards (text) |
60
60
  | `read_canvas` | Read one canvas (cards, graph, `[[links]]`) + its images | markdown + image FileParts |
61
61
  | `list_canvases` | List canvases with counts | text |
package/README.md CHANGED
@@ -56,8 +56,8 @@ You need **Node.js 20 or newer** (`node -v`).
56
56
  `brain_sync` in other hosts. A correction replaces the old decision, and the old card stays as
57
57
  history.
58
58
  - **A heads-up when work overlaps.** Each session says what it is doing and which files it expects
59
- to touch. When two sessions name the same file, both are warned. KLYPIX warns; it never blocks an
60
- edit. This works on one computer. With [KLYPIX network](https://klypix.com/network)
59
+ to touch. When two sessions name the same file, both are warned. For overlapping work KLYPIX
60
+ warns; it never blocks an edit. This works on one computer. With [KLYPIX network](https://klypix.com/network)
61
61
  installed on both computers (klypix-mcp 1.106.0 or later), it also lists sessions in the same
62
62
  project on a teammate's computer.
63
63
  - **Notes and handoffs between sessions.** An agent can leave a note for another session, even one
@@ -69,6 +69,11 @@ You need **Node.js 20 or newer** (`node -v`).
69
69
  git command that would sweep another live session's uncommitted work into your commit is stopped.
70
70
  A command that would throw work away saves a rescue copy first, then asks you. It covers git
71
71
  commands run through Claude Code's tools, on one computer.
72
+ - **Lessons that catch the repeat mistake.** A lesson the brain recorded can become a project rule
73
+ that a person approves: a check of the diff, with no AI at check time. A rule warns when a session
74
+ plans its files, while it edits and at commit. Only a rule the person approves as blocking stops
75
+ the work: Claude Code before it writes the forbidden line (Codex is warned), and an opt-in git
76
+ hook at commit, for any tool or person. Rules check the diff; they do not run your tests.
72
77
  - **A file you own.** The brain is one `.klypix` file in your repo: a plain ZIP, versioned with
73
78
  Git. The engine is Apache-2.0, runs on your machine, and works without the KLYPIX app or an
74
79
  account. Brain data stays in your files; your agent's model provider still receives what the
@@ -88,7 +93,7 @@ has the details.
88
93
  | **Claude Desktop** | A one-time config entry | The KLYPIX app's Settings, or by hand |
89
94
  | **Claude, as a plugin** | One pinned version, keyword search, no hooks | [klypix-plugin](https://github.com/dahshanlabs/klypix-plugin) |
90
95
 
91
- klypix-mcp 1.106.0 has 26 MCP tools on Linux and macOS and 27 on Windows, where `show_in_klypix` is
96
+ klypix-mcp has 27 MCP tools on Linux and macOS and 28 on Windows, where `show_in_klypix` is
92
97
  added. `npx klypix-mcp doctor` shows the count for your install.
93
98
 
94
99
  > **One project. Many agents. One current understanding.**
@@ -118,14 +123,15 @@ tool's official documentation and says that KLYPIX wrote it) · [klypix.com/klyp
118
123
  - **[Reference](#reference):** [How the brain works](#how-the-brain-works) ·
119
124
  [Supported hosts](#supported-hosts-and-their-integration-level) ·
120
125
  [Running as a Claude plugin](#running-as-a-claude-plugin) · [Task briefing](#task-briefing) ·
121
- [Capture and corrections](#capture-and-corrections) · [Presence and task intent](#presence-and-task-intent) ·
126
+ [Capture and corrections](#capture-and-corrections) · [Project rules](#project-rules-lessons-that-stop-the-repeat) ·
127
+ [Presence and task intent](#presence-and-task-intent) ·
122
128
  [Overlap warnings](#overlap-warnings) ·
123
129
  [Teammates on other computers](#teammates-on-other-computers-klypix-network) ·
124
130
  [Handoffs and messages](#handoffs-and-messages) · [Evidence-gated completion](#evidence-gated-completion) ·
125
131
  [Human control in KLYPIX](#human-control-in-klypix) · [Measure it yourself](#measure-it-yourself) ·
126
132
  [Git and concurrency](#git-and-concurrency) ·
127
133
  [The git guard for a shared checkout](#whose-is-this-the-git-guard-for-a-shared-checkout) ·
128
- [The command line](#the-command-line) · [The verbs](#the-verbs-26-and-27-on-windows) ·
134
+ [The command line](#the-command-line) · [The verbs](#the-verbs-27-and-28-on-windows) ·
129
135
  [One file you can hold](#one-file-you-can-hold) · [Use it as a library](#use-it-as-a-library) ·
130
136
  [A2A](#also-speaks-a2a-protocol-v030--experimental) ·
131
137
  [Updates](#updates--the-propagation-contract) · [Security and permissions](#security-and-permissions) ·
@@ -561,9 +567,9 @@ on cards, and, on a call that needs the running app, the app's bridge token (abo
561
567
 
562
568
  | Where | What | Why |
563
569
  |---|---|---|
564
- | Your project | Only what a tool call asks for: `brain.klypix` (`brain_note` and the other brain tools), canvases (`create_canvas`, `add_to_canvas`), `klypix-map/graph.json` when `project_map_scan` is called, and `.klypix/claims/<owner>.json` when `brain_sync` is asked to publish a release claim. During a brain write it holds `.claude/brain-capture.lock`, creating the `.claude/` folder if the project has none. The lock file is deleted after the write; the folder stays. Creating a canvas briefly holds `.klypix-create.lock` in the folder. | The KLYPIX app and every other session that writes the brain use the same locks, so two writers never overwrite each other. |
570
+ | Your project | Only what a tool call asks for: `brain.klypix` (`brain_note` and the other brain tools), canvases (`create_canvas`, `add_to_canvas`), `klypix-map/graph.json` when `project_map_scan` is called, `.klypix/claims/<owner>.json` when `brain_sync` is asked to publish a release claim, and `.klypix/rules.json` when `brain_rules` adds, approves or retires a rule. Only when you run `npx klypix-mcp check --install-hook`: a marked block in `.git/hooks/pre-commit`, `pre-push` and `post-commit`; `npx klypix-mcp rules allow` keeps its one-hour exception in `.git/klypix-rules-allow.json`. During a brain write it holds `.claude/brain-capture.lock`, creating the `.claude/` folder if the project has none. The lock file is deleted after the write; the folder stays. Creating a canvas briefly holds `.klypix-create.lock` in the folder. | The KLYPIX app and every other session that writes the brain use the same locks, so two writers never overwrite each other. |
565
571
  | The plugin's data folder (`KLYPIX_PLUGIN_DATA`) | Connection receipts (`.supervisors/`), the running-server heartbeat (`.running-servers.json`), the list of projects whose brains you used (`registry.json`, which `search_all_brains` reads), and the last version and git tag seen in each project (`ship-observations/`). Also `extracted/`: copies of the PDFs, Office and other files `read_card_contents` hands to your AI tool as a local path, taken from the canvas you asked about. | Only this server uses these files. If `KLYPIX_PLUGIN_DATA` is not set, or still contains a `${...}` that was never filled in, it uses `CLAUDE_PLUGIN_DATA`. If neither is usable, the files go in `~/.claude/project-brain`, except `extracted/`, which then goes in `<system temp>/klypix-mcp/extracted` (where it always goes outside plugin mode). |
566
- | `~/.claude/project-brain` (shared) | Presence lanes (`sessions/`), write locks (`locks/`), restore points (`history/`), and small records built from your brain: `.capture-gap.json`, and `enrichment/`, `provenance/`, `.brief-cache-*` and `.guards-*` when the tools that use them run. | Your other KLYPIX sessions on this computer (Claude Code, Codex, Cursor, the app) use the same files on purpose. Through them, a plugin session and a terminal session on the same project see each other, get warnings when they plan to edit the same files, and pass notes. Restore points are kept here so that `npx klypix-mcp brain-history` can still undo a brain write after you remove the plugin. |
572
+ | `~/.claude/project-brain` (shared) | Presence lanes (`sessions/`), write locks (`locks/`), restore points (`history/`), and small records built from your brain: `.capture-gap.json`, and `enrichment/`, `provenance/`, `.brief-cache-*` and `.guards-*` when the tools that use them run. `rules/` counts how often each project rule fired, blocked or was overridden on this computer. | Your other KLYPIX sessions on this computer (Claude Code, Codex, Cursor, the app) use the same files on purpose. Through them, a plugin session and a terminal session on the same project see each other, get warnings when they plan to edit the same files, and pass notes. Restore points are kept here so that `npx klypix-mcp brain-history` can still undo a brain write after you remove the plugin. |
567
573
 
568
574
  **`brain_reopen`.** Sometimes a session leaves a note for another session that has already closed.
569
575
  An agent can then call `brain_reopen`, and KLYPIX asks you first: in chat with *Reopen* and *Not
@@ -586,7 +592,10 @@ Every other host gets a bounded ~2.8KB task capsule from one `brain_sync` call,
586
592
  always-loaded `AGENTS.md` block that tells the agent to make that call at task start, when scope
587
593
  changes, and on completion. The gateway capsule is lexical-fast by design. A newly captured open
588
594
  gap can claim a labeled `RECENT OPEN` slot only after clearing the normal lexical-relevance floor,
589
- so fresh relevant findings are not crowded out by older area vocabulary.
595
+ so fresh relevant findings are not crowded out by older area vocabulary. When the call declares
596
+ `files`, the lessons that name those files come first, under "Lessons for your files": at most
597
+ four, ranked exact path, then file name, glob and folder. The Claude Code brief does the same for
598
+ the files the checkout is changing (uncommitted, and the last three commits), then lists the newest.
590
599
 
591
600
  Briefs are **not** injected automatically on Cursor, Cline, Copilot, Gemini CLI or Antigravity —
592
601
  there are no lifecycle hooks on those hosts.
@@ -648,7 +657,9 @@ On Claude Code, decisions are captured automatically at turn end from inline `
648
657
  markers in the transcript, deduped, under a capture lock.
649
658
 
650
659
  A marker can end with optional suffixes, in any order: `closes: <card title or [[wikilink]]>`,
651
- `ev: <file[:line]>, PR#<n>`, `verify: <command>` and `q: <the question this answers?>`. They count
660
+ `ev: <file[:line]>, PR#<n>`, `verify: <command>`, `q: <the question this answers?>` and
661
+ `rule: <glob>[,<glob>] never "<text>"` (or `never /<regex>/`, `must "<text>"`,
662
+ `listed-in <file>[,<file>]`, `keep-eol`, `costly`). They count
652
663
  only as one run at the end of the line, written lowercase as `key: value`, and each value must
653
664
  have its key's shape: references for `ev:`, a command for `verify:`, a question (question word
654
665
  first, `?` last) for `q:`. Anything else is kept as card text, so "a Q: and A: layout", "the ev:
@@ -663,6 +674,13 @@ content words and under half the card's) is appended to that card as a dated `(~
663
674
  instead. The card keeps its date, colour and probe, and previews show the newest amendment first.
664
675
  The correction lands and nothing is lost.
665
676
 
677
+ A `rule:` suffix makes a new lesson (a decision, `+` or `!`) rule-ready. Its globs must name files
678
+ tracked in the repository and a `/regex/` must compile; the rule lands as a visible
679
+ `⚙ rule candidate:` line on a 🛠️ card, and `brain_rules` propose reads it before any prose. It
680
+ checks nothing until a person approves the rule. A rule that fails those checks is not recorded,
681
+ the lesson is, and the hook says why on the next prompt. `brain_note` takes the same suffix at the
682
+ end of its text.
683
+
666
684
  The Stop hook reads the whole transcript every time, so a `~` or `✓` line applies once. A marker
667
685
  that an older hook (1.85 or 1.86.0) already captured is not captured again after an upgrade. If
668
686
  1.86.0 cut the note short, the stub is restored to the full text in place. When a marker does not
@@ -684,6 +702,28 @@ only (explicit correction cues, opposite-polarity pairs), never mere topical sim
684
702
  means no contradiction signal was found — not verified consistency. A memory that cannot disagree
685
703
  with you is flattery.
686
704
 
705
+ ### Project rules: lessons that stop the repeat
706
+
707
+ Some lessons can be checked in a diff: *every new route must also be in the route list*, *tests
708
+ in this folder must import shared code relatively*, *this file's line endings must not change*. `brain_rules` turns those
709
+ lessons into rules in `.klypix/rules.json`, each citing the card it came from. A person approves
710
+ each rule with a code only they can print (`npx klypix-mcp rules code promote <ids>`). Until then
711
+ the rule is a candidate and checks nothing.
712
+
713
+ An active rule runs in three places: `brain_sync` names the rules for a session's files, the edit
714
+ hook reports findings to Claude Code and Codex as they edit, and the opt-in git hooks
715
+ (`npx klypix-mcp check --install-hook`) print them at commit and push. Rules warn by default. A
716
+ rule the person approves with `--block` stops the commit or push for any tool or person, and stops
717
+ Claude Code before it writes a forbidden line; Codex is only warned, because its hooks cannot
718
+ deny. A check is glob, regex and line-ending work over the diff: it takes
719
+ milliseconds, runs offline and uses no model. Overrides, bypasses and false alarms are recorded in
720
+ the brain, and `npx klypix-mcp rules` shows each rule's blocks, overrides and false alarms.
721
+
722
+ How to write, approve and retire rules: [docs/brain-rules.md](docs/brain-rules.md). To see what a
723
+ set of rules would have flagged on a repository's own history, `scripts/brain-rules-proof.mjs`
724
+ replays them over its pull requests and failed Actions logs, with what those runs cost.
725
+ A walk-through for teams: [Stop AI agents repeating the same mistakes](https://klypix.com/guides/stop-agents-repeating-mistakes).
726
+
687
727
  ### Presence and task intent
688
728
 
689
729
  An active session means an authorized MCP connection or host lifecycle adapter that heartbeated
@@ -842,6 +882,22 @@ record and every list say the session wrote none. If the session resumes and com
842
882
  the drafted card is archived at the next pass. Three drafts per pass at most; a draft can never
843
883
  fail the sync that triggered it.
844
884
 
885
+ **Conversation coverage.** A card and the automatic reply tail can miss a later request.
886
+ Taking a card now checks its source conversation and reports `needs-review`, `reviewed`, or
887
+ `unknown`. `brain_handoff {action:"review", id}` returns paginated Claude Code or Codex
888
+ conversation turns, including user requests with no reply. Follow `nextCursor` with the returned
889
+ `revision`; after reading every page, acknowledge the final `reviewToken` with a `summary` of
890
+ completed and still-pending requests. Session-only takeovers use `review` with `session` to create
891
+ a reviewable draft first. The first review covers the whole conversation, including requests
892
+ that predate the card; changed source bytes invalidate the review and require another pass.
893
+ The saved review is an agent attestation, not proof of task completion. It never closes work.
894
+ Transcripts stay on this machine and are read only from the supported hosts' transcript folders.
895
+ Missing, malformed, ambiguous, oversized, or unsupported sources report `unknown`; an attachment
896
+ placeholder requires checking the original media. The reader is bounded to 128 MiB per transcript,
897
+ 16 MiB per JSONL record, 8 MiB of conversation text, and 12,000 discovery entries. A limit never
898
+ silently drops turns or certifies coverage. Reconcile the card's Next list with the conversation
899
+ and repository evidence before continuing.
900
+
845
901
  Durable decisions still go in the brain as cards — a handoff is the one card about *work in
846
902
  flight*, and it closes when the work lands.
847
903
 
@@ -1164,10 +1220,12 @@ The MCP verbs below are what agents call. These are what **you** call:
1164
1220
  | `npx klypix-mcp diff [ref]` | Card-level brain diff against a git ref, as markdown |
1165
1221
  | `npx klypix-mcp pr-brief [ref]` | Brain cards referencing the files changed since a ref, as markdown |
1166
1222
  | `npx klypix-mcp garden-code` | Print the human approval code `brain_garden` requires |
1223
+ | `npx klypix-mcp check` | Run this project's rules (`.klypix/rules.json`) on the working tree, `--staged`, or `--base <ref>`; `--install-hook` wires the opt-in pre-commit, pre-push and post-commit hooks |
1224
+ | `npx klypix-mcp rules` | The person's side of project rules: per-rule counts and suggestions, `code promote\|retire <ids>` (the approval code), `allow <id> --reason` (one hour), `false-alarm <id> --reason` |
1167
1225
  | `npx klypix-mcp rescue` | Rescue copies the git guard saved before a discard: `list`, `show <id>`, `restore <id> [paths…]`, `prune` |
1168
1226
  | `npx klypix-mcp sessions` | This project's closed sessions and the notes waiting for them; `sessions reopen <id>` reopens one |
1169
1227
 
1170
- ### The verbs: 26, and 27 on Windows
1228
+ ### The verbs: 27, and 28 on Windows
1171
1229
 
1172
1230
  | Tool | What it does |
1173
1231
  |---|---|
@@ -1178,6 +1236,7 @@ The MCP verbs below are what agents call. These are what **you** call:
1178
1236
  | `brain_insights` | Hubs, orphaned decisions, stale questions, area sizes |
1179
1237
  | `brain_lens` | Machine-readable freshness, provenance, activity, timeline, orrery and unresolved views; on a canvas open in KLYPIX (Windows), the first five come from KLYPIX's own lens code |
1180
1238
  | `brain_garden` | Maintenance pass — proposes first; consolidation cannot apply without an approval code the human generates. The separate `repair:"duplicate-partials"` pass is dry-run first and needs no code (it removes only exact repeats and archives nothing) |
1239
+ | `brain_rules` | Project rules made from the brain's lessons: `propose` routes each lesson to rule, instruction or note; `add` writes candidates to `.klypix/rules.json`; `promote` and `retire` need the person's approval code, and a rule warns unless the person approved it with `--block`; `check` runs the rules on a diff; `status` shows blocks, overrides, false alarms and retirement suggestions. No AI at check time ([docs/brain-rules.md](docs/brain-rules.md)) |
1181
1240
  | `brain_doctor` | Self-diagnosis: version, core/enhanced host adapters, active sessions, tool count, projection drift |
1182
1241
  | `brain_message` | Session-to-session coordination notes — to a live session, or queued for one that is not running until it next starts — with a fixed send-time audience and per-recipient pending / offer / acknowledgement / consumption / failure receipts (a directed note is kept 7 days, a broadcast 24h; never written into the brain) |
1183
1242
  | `brain_message_receipt` | Explicitly record model-side consumption using the exact message id and per-recipient offer token; acknowledgement alone never consumes a note |
@@ -1199,7 +1258,7 @@ The MCP verbs below are what agents call. These are what **you** call:
1199
1258
  | `add_to_canvas` | Append cards/connections (positions preserved), bordered and readable on KLYPIX's dark and Paper themes; a card's `group` puts it in that titled box. On a canvas open in KLYPIX (Windows) the cards go to KLYPIX itself: live, attributed, one undo. When KLYPIX cannot take them it refuses (`OPEN_IN_APP`, `ACCESS_OFF`, `BLOCKED`), a box locked from AI tools (`SCOPE_LOCKED`) or a frozen box (`FROZEN`), and writes nothing; project brains are the exception to the first. Returns the new card ids |
1200
1259
  | `list_canvases` | List every `.klypix` in the vault |
1201
1260
 
1202
- Exactly 26 on Linux and macOS and 27 on Windows, where `show_in_klypix` is registered; machine-verifiable with `npx klypix-mcp doctor`, whose TOOLS line applies the same rule and whose App bridge line says whether KLYPIX is running, with access on or off.
1261
+ Exactly 27 on Linux and macOS and 28 on Windows, where `show_in_klypix` is registered; machine-verifiable with `npx klypix-mcp doctor`, whose TOOLS line applies the same rule and whose App bridge line says whether KLYPIX is running, with access on or off.
1203
1262
 
1204
1263
  > **`canvas_view`:** no MCP Apps host has been observed rendering the UI resource yet — there is no
1205
1264
  > screenshot and no host-level test. Hosts without the extension get clean text, which is the path
@@ -1413,6 +1472,11 @@ Read this section before you build on any of it.
1413
1472
  `src/auth/token.ts` does not match a rename. Folder overlap is advisory. The merge test sees
1414
1473
  committed work only, needs git 2.38 or newer, and runs only for sessions on different local
1415
1474
  branches of the same repository.
1475
+ - **Project rules check the diff, not behaviour.** They see file names, added lines, line endings
1476
+ and paths; they do not run code or tests. They warn unless the person approved them with
1477
+ `--block`; Codex is warned, not stopped, while it edits, and the commit hooks are opt-in per
1478
+ clone. The approval code and the refusal inside agent shells prevent accidents; they do not stop
1479
+ a deliberate workaround.
1416
1480
  - **Overlap warnings are advisory.** Nothing is blocked. One severity string in the payload reads
1417
1481
  `blocking`; the mechanism is not.
1418
1482
  - **Codex has no automatic capture**, with or without `--codex-hooks`. The Codex hook never writes
@@ -132,7 +132,7 @@ function agentCard(publicUrl) {
132
132
  {
133
133
  id: 'learn_skill',
134
134
  name: 'Learn a reusable skill (how-to / gotcha)',
135
- description: 'Record a 🛠️ SKILL — a reusable how-to, gotcha, or convention ("always dedup zKeys before REORDER") that should resurface every session and never age out, distinct from a one-time decision. Routes through the capture engine with the "+" marker. A delegating agent uses this to teach the shared brain how work is done here, so every future agent inherits it. Plain "remember" auto-promotes to a skill when the text reads as a general rule.',
135
+ description: 'Record a 🛠️ SKILL — a reusable how-to, gotcha, or convention ("always dedup zKeys before REORDER") that is kept until corrected and shown first to sessions that touch the files it names, distinct from a one-time decision. Routes through the capture engine with the "+" marker. A delegating agent uses this to teach the shared brain how work is done here, so every future agent inherits it. Plain "remember" auto-promotes to a skill when the text reads as a general rule.',
136
136
  tags: ['memory', 'skill', 'how-to', 'procedure', 'brain', 'learn'],
137
137
  examples: ['Learn this gotcha: never set backgroundThrottling false — it breaks visibility detection', 'Remember the convention: Electron main imports must stay at top'],
138
138
  inputModes: ['text/plain', 'application/json'],
@@ -134,7 +134,8 @@ try {
134
134
  metrics.startAttempts = { first: aStarted.attempt, second: bStarted.attempt };
135
135
  const aStructured = aStart.structuredContent || {};
136
136
  const bStructured = bStart.structuredContent || {};
137
- checks.taskMemory = Array.isArray(aStructured.context?.hits) && aStructured.context.hits.length > 0;
137
+ checks.taskMemory = (Array.isArray(aStructured.context?.hits) && aStructured.context.hits.length > 0)
138
+ || (Array.isArray(aStructured.context?.fileLessons) && aStructured.context.fileLessons.length > 0);
138
139
  checks.truthfulTaskCount = bStructured.counts?.activeTasks === 2
139
140
  && bStructured.peers?.length === 1;
140
141
  checks.exactBlockingOverlap = bStructured.conflicts?.length === 1
@@ -349,7 +349,9 @@ const flatten = (code) => code
349
349
  // app bridge, which imports the protocol.
350
350
  // session-handoff (1.99.1): the worker's brain_sync imports CONTINUE_RE and
351
351
  // formatHandoffCards from it directly (1.99.0 only reached it via core).
352
- .replace(/\.\.\/src\/(bench|brain-doctor|agent-presence|agent-rules|capture-gap|enrichment|finding-routing|mcp-presence|mcp-supervisor|mcp-auto-update|presence-relay|repo-state|semantic-memory|runtime-inspector|project-graph|git-capture-install|app-lease|app-tools|app-bridge-protocol|app-bridge-client|session-handoff)\.mjs/g, './$1.mjs')
352
+ // brain-rules (project rules): the worker's brain_rules tool and the
353
+ // dispatcher's `check` / `rules` verbs import it lazily.
354
+ .replace(/\.\.\/src\/(bench|brain-doctor|agent-presence|agent-rules|capture-gap|enrichment|finding-routing|mcp-presence|mcp-supervisor|mcp-auto-update|presence-relay|repo-state|semantic-memory|runtime-inspector|project-graph|git-capture-install|app-lease|app-tools|app-bridge-protocol|app-bridge-client|session-handoff|brain-rules)\.mjs/g, './$1.mjs')
353
355
  .replace(/klypix-worker\.mjs/g, 'klypix-mcp-worker.mjs')
354
356
  .replace(/const PKG_VERSION = \(\(\) => \{[\s\S]*?\}\)\(\);/, `const PKG_VERSION = '${VERSION}'; // baked at install (flat layout has no package.json)`);
355
357
 
@@ -495,7 +497,7 @@ try {
495
497
  // a newer klypix-format cannot. merge-brains and the driver ship here so
496
498
  // brain-history's restore merge, the KLYPIX core and the git driver all
497
499
  // 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', 'network-bridge.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']) {
500
+ 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', 'brain-rules.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', 'network-bridge.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
501
  const s = path.join(SRC, f); if (exists(s)) staged.push({ dst: f, content: fs.readFileSync(s, 'utf8') });
500
502
  }
501
503
  for (const [src, dst] of [
@@ -572,7 +574,9 @@ try {
572
574
  // Guard cards (2026-08-24): pre-action lane. Fast path — reads only the
573
575
  // compiled guard sidecar; 'warn' injects context, 'block' denies with
574
576
  // the card's message. Absent guards = stat + one tiny read, then no-op.
575
- ['PreToolUse', { matcher: 'Bash|PowerShell|Edit|Write', hooks: [{ type: 'command', command: brainCmd('--guard'), timeout: 10 }] }],
577
+ // Project rules (.klypix/rules.json) ride the same lane for edits: a
578
+ // forbidden line is denied before it is written (MultiEdit included).
579
+ ['PreToolUse', { matcher: 'Bash|PowerShell|Edit|Write|MultiEdit', hooks: [{ type: 'command', command: brainCmd('--guard'), timeout: 10 }] }],
576
580
  // Session end (1.105.0): a closed or cut-off Claude Code session leaves
577
581
  // the lane at once. Before this, only the 10-minute TTL removed it, so the
578
582
  // next session read a dead peer as "live" and refused to continue its
@@ -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', 'rescue']);
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', 'check', 'rules']);
23
23
 
24
24
  const USAGE = [
25
25
  `klypix-mcp ${PKG_VERSION} — shared project brain + MCP coordination server.`,
@@ -36,6 +36,11 @@ const USAGE = [
36
36
  ' bench [--quick] [--json] [--out F] reproducible benchmark: concurrent-write safety, latency, soak, crash',
37
37
  ' init seed a starter ./brain.klypix + print an MCP config',
38
38
  ' garden-code [brain] print the human approval code for brain_garden',
39
+ ' check [--staged|--base B|--range A..B] [--strict] [--json]',
40
+ ' run this project\'s rules (.klypix/rules.json) on a diff — milliseconds, no install/tests',
41
+ ' check --install-hook|--remove-hook opt-in git pre-commit/pre-push/post-commit hooks for those rules',
42
+ ' rules [list|code promote|retire <ids>|promote|retire <ids> --code C|allow <id> --reason R|false-alarm <id> --reason R]',
43
+ ' the human side of rules: approval codes, one-hour overrides, false alarms',
39
44
  ' uninstall [--check|--yes|unlink] remove this install from the machine (--check inventories first; never deletes a .klypix)',
40
45
  ' git-driver [install|status] [repo] register the card-level .klypix merge driver for a repo (zero-command teams)',
41
46
  ' git-hook [install|remove|status] wire the agent-neutral commit-capture hook (any agent/branch/worktree → brain cards)',
@@ -78,6 +83,9 @@ if (verb === 'bench') {
78
83
  console.log(process.argv.includes('--json') ? JSON.stringify(report, null, 2) : markdown);
79
84
  const c = report.scenarios?.concurrentWriters;
80
85
  process.exit(c?.verdict === 'no-loss' ? 0 : 1);
86
+ } else if (verb === 'check' || verb === 'rules') {
87
+ const { runRulesCli } = await import('../src/brain-rules.mjs');
88
+ process.exit(await runRulesCli(verb === 'rules' ? process.argv.slice(2) : process.argv.slice(3)));
81
89
  } else if (verb === 'runtime') {
82
90
  process.argv.splice(2, 1);
83
91
  await import('./klypix-runtime.mjs');
@@ -228,7 +228,7 @@ if (process.argv[2] === 'init') {
228
228
  { title: 'Goal', cards: [{ text: '❓ What is this project for, and for whom?\nAgent: survey the repo on your first session and replace this with the real goal.' }] },
229
229
  { title: 'Architecture', cards: [{ text: '❓ Key components and how they fit.\nAgent: record the actual shape from the repo — only what a new session must know.' }] },
230
230
  { title: 'Decisions', cards: [{ text: 'Decisions land here automatically: agents emit `🧠 BRAIN [Area]: …` markers; a new decision that replaces an old one archives it (superseded). Resolve finished items with `✓`, correct in place with `~`. Drag any card into 📌 Focus to make it lead every session brief.' }] },
231
- { title: '🛠️ Skills', cards: [{ text: '🛠️ Reusable how-tos, gotchas & conventions land here — emit `🧠 BRAIN [Area] +: <skill>` (or just state a rule like "always X / never Y" and it auto-promotes). Skills resurface every session and never age out, unlike one-time decisions. This is "how we work here", inherited by every future agent.' }] },
231
+ { title: '🛠️ Skills', cards: [{ text: '🛠️ Reusable how-tos, gotchas & conventions land here — emit `🧠 BRAIN [Area] +: <skill>` (or just state a rule like "always X / never Y" and it auto-promotes). Skills are kept until corrected and shown first to sessions that touch the files they name, unlike one-time decisions. This is "how we work here", inherited by every future agent.' }] },
232
232
  { title: 'Pending / next', cards: [{ text: 'What is in flight and what comes next. Close finished items with the ✓ marker.' }] },
233
233
  { title: 'Open questions', cards: [{ text: 'Unresolved questions (the ❓ marker) live here — the session brief surfaces them first.' }] },
234
234
  { title: '📌 Focus', cards: [{ text: 'Drag any card into this area to make it lead every session brief — steer your agent by moving cards.' }] },
@@ -812,7 +812,7 @@ try {
812
812
 
813
813
  server.registerTool('brain_note', {
814
814
  title: 'Write a deliberate note to the project brain (decision / question / milestone / skill / resolve / update)',
815
- description: 'Record something in the project brain ON DEMAND — the agent-neutral twin of the Claude-Code capture hook, so any client (Cursor / Cline / Desktop) can write the brain, not just read it. Unlike add_to_canvas (a flat append), this routes through the brain\'s capture engine, so a new decision SUPERSEDES a heavily-overlapping older one, ✓ RESOLVES/archives a matching card, closes: resolves the strategy/question a milestone fulfils, and ~ UPDATES a card in place — the full decision lifecycle, with dedup. Use marker "+" to record a 🛠️ SKILL — a reusable how-to/gotcha/convention ("always dedup zKeys before REORDER") that should resurface every session and never age out, distinct from a one-time decision. Use it to remember a decision, ask an open question, mark a milestone, log a skill, resolve a finished item, or correct a card. Defaults to the project brain ("brain").',
815
+ description: 'Record something in the project brain ON DEMAND — the agent-neutral twin of the Claude-Code capture hook, so any client (Cursor / Cline / Desktop) can write the brain, not just read it. Unlike add_to_canvas (a flat append), this routes through the brain\'s capture engine, so a new decision SUPERSEDES a heavily-overlapping older one, ✓ RESOLVES/archives a matching card, closes: resolves the strategy/question a milestone fulfils, and ~ UPDATES a card in place — the full decision lifecycle, with dedup. Use marker "+" to record a 🛠️ SKILL — a reusable how-to/gotcha/convention ("always dedup zKeys before REORDER") kept until corrected and shown first to sessions that touch the files it names, distinct from a one-time decision. When the mistake is mechanical, end the text with a `rule:` suffix to make the lesson RULE-READY: repo globs, then `never "<text>"`, `never /<regex>/`, `must "<text>"`, `listed-in <file>[,<file>]`, `keep-eol` or `costly` — e.g. `rule: web/tests/**/*.test.ts never "from \'@/"`. The globs must name tracked files; it lands as a visible "⚙ rule candidate" line that nothing checks until brain_rules adds it and a person approves it. Use it to remember a decision, ask an open question, mark a milestone, log a skill, resolve a finished item, or correct a card. Defaults to the project brain ("brain").',
816
816
  inputSchema: {
817
817
  text: z.string().describe('The note — one concise idea; the first line becomes the card title.'),
818
818
  marker: z.enum(['', '?', '!', '+', '✓', '~']).optional().describe('(none)=decision · ?=open question · !=milestone · +=🛠️ skill (reusable how-to/gotcha; always resurfaces, never ages out) · ✓=resolve+archive the best-matching card · ~=update the matching card in place. Default: decision.'),
@@ -1089,7 +1089,10 @@ server.registerTool('brain_handoff', {
1089
1089
  openWorldHint: false,
1090
1090
  },
1091
1091
  inputSchema: {
1092
- action: z.enum(['write', 'take', 'done', 'list']).optional().describe('write (default): record a handoff card. take: claim a handoff card (id) or unfinished work (session) before continuing it. done: mark it finished. list: every open handoff card and every unfinished session on this project.'),
1092
+ action: z.enum(['write', 'take', 'done', 'list', 'review']).optional().describe('write: record a handoff. take: claim it and check conversation freshness. review: read source conversation pages, then acknowledge the final reviewToken with a summary of pending requests. done: mark finished. list: open handoffs and unfinished sessions.'),
1093
+ cursor: z.number().int().min(0).optional().describe('review: nextCursor from the preceding page; omit to start.'),
1094
+ revision: z.string().max(64).optional().describe('review: exact transcript revision from the preceding page.'),
1095
+ reviewToken: z.string().max(64).optional().describe('review: acknowledge only AFTER reading every conversation page; supply the final token and a summary including outstanding requests. New source activity invalidates this token.'),
1093
1096
  project: z.string().optional().describe('Absolute project root containing brain.klypix. Defaults to this connection\'s project.'),
1094
1097
  canvas: z.string().optional().describe('Brain canvas filename/path. Defaults to the project brain ("brain").'),
1095
1098
  title: z.string().max(120).optional().describe('write: the work, in a few words ("Drive upload: the Place-it-now chooser").'),
@@ -1102,9 +1105,9 @@ server.registerTool('brain_handoff', {
1102
1105
  reason: z.enum(['context_limit', 'usage_limit', 'end_of_session', 'blocked', 'switching_tool', 'other']).optional().describe('write: why the work stops here.'),
1103
1106
  area: z.string().max(40).optional().describe('write: the brain area (titled box) the card goes into. Defaults to "Handoff".'),
1104
1107
  continues: z.string().max(64).optional().describe('write: the id of an earlier handoff card this one continues — it is closed as "continued by" this one.'),
1105
- id: z.string().max(64).optional().describe('take / done: the handoff card id (from brain_sync, the session-start brief, or list).'),
1106
- session: z.string().max(160).optional().describe('take / done: the session id (or a unique prefix of 8+ characters) from the unfinished-work list, when the work has no card.'),
1107
- summary: z.string().max(400).optional().describe('done: what was finished, in one or two sentences. take: an optional note.'),
1108
+ id: z.string().max(64).optional().describe('take / done / review: the handoff card id (from brain_sync, the session-start brief, or list).'),
1109
+ session: z.string().max(160).optional().describe('take / done / review: the session id (or a unique prefix of 8+ characters) from the unfinished-work list, when the work has no card. Review creates a reviewable draft if needed.'),
1110
+ summary: z.string().max(400).optional().describe('done: what was finished. take: optional note. review acknowledgement: required account of completed and still-pending requests after reading all conversation pages.'),
1108
1111
  },
1109
1112
  }, async ({ action, project, canvas, ...rest }, extra) => {
1110
1113
  const identity = extra?.klypixRequestIdentity || {};
@@ -1456,6 +1459,7 @@ server.registerTool('brain_sync', {
1456
1459
  // record is announced as such — a lead to verify, not the session's words.
1457
1460
  drafted.length ? `🤝 KLYPIX drafted ${drafted.length} handoff card${drafted.length > 1 ? 's' : ''} just now from the record of a session that stopped without writing one: ${drafted.map((d) => `[${d.id}] “${d.title}”`).join('; ')}. A drafted card is a lead, not a verified state — confirm it with git before relying on it.` : '',
1458
1461
  formatHandoffCards(cards, { now, max: 5 }),
1462
+ cards.length ? 'Saved handoffs are not verified conversation coverage. On takeover call brain_handoff review and check later/unanswered requests before trusting Next; missing transcripts mean UNKNOWN.' : '',
1459
1463
  formatUnfinishedWork(lane, { now, max: 4, ranked: wantsContinue && words.size > 0 }),
1460
1464
  ].filter(Boolean).join('\n\n');
1461
1465
  handoffData = {
@@ -1540,6 +1544,38 @@ server.registerTool('brain_sync', {
1540
1544
  };
1541
1545
  });
1542
1546
 
1547
+ server.registerTool('brain_rules', {
1548
+ title: 'Project rules from the brain\'s lessons — enforced before CI, with no AI at check time',
1549
+ description: 'Turns lessons this brain recorded ("every new route must also be in PRIVATE_ROUTES", "a new *.test.ts must be listed in package.json", "this file is mixed CRLF/LF") into DETERMINISTIC diff checks, versioned in the repo as .klypix/rules.json, each citing the card it came from. Checked before the code reaches CI: brain_sync shows the rules for the files you declare; the edit hook (Claude Code; Codex gets the same) and an opt-in git pre-commit/pre-push hook report findings. Rules WARN by default; only a rule the human approved with --block stops an edit (Claude Code) or a commit, and a commit that skips a blocking rule (--no-verify) is recorded. Actions: "propose" routes each lesson to rule / instruction / note and drafts the checkable ones; "add" writes completed drafts as CANDIDATES (inert); "promote" and "retire" need the human\'s 8-character approval code (`npx klypix-mcp rules code promote|retire <ids>` — never shown to you, never guess it; a rule that blocks you is lifted only by the human); "check" runs the rules on the working tree, the staged change, or a range (include_candidates:true tests candidates before approval); "status" lists rules with fires / blocks / overrides / false alarms and retirement suggestions; "false_alarm" records that a finding was wrong (it does not lift the rule). Kinds: listed-in, forbid-pattern, preserve-eol, cloud-required. Checks are string/glob/regex work over the diff — milliseconds, offline; the project\'s own tests stay with its CI.',
1550
+ inputSchema: {
1551
+ action: z.enum(['propose', 'add', 'check', 'status', 'promote', 'retire', 'false_alarm']).describe('What to do. Default flow: propose → add → (human) promote; check any time; status for counts and suggestions.'),
1552
+ project: z.string().optional().describe('Absolute project root (the folder holding brain.klypix and .klypix/). Defaults to this session\'s project brain.'),
1553
+ query: z.string().optional().describe('propose: only lessons containing this text.'),
1554
+ limit: z.number().int().min(1).max(100).optional().describe('propose: at most this many lessons (default 30).'),
1555
+ rules: z.array(z.record(z.string(), z.any())).max(32).optional().describe('add: complete rules — {id, kind, severity, why, fix, source:{card}, files:[globs], …kind fields}. listed-in: when "added-files"|"added-lines", registry:[files], needle (template: {file} {basename} {name} {relpath} {1}), pattern for added-lines (capture group 1 = the value to find). forbid-pattern: pattern. preserve-eol: files only. cloud-required: files, severity "advise".'),
1556
+ ids: z.array(z.string()).max(32).optional().describe('promote / retire / false_alarm: rule ids.'),
1557
+ approve: z.string().optional().describe('promote / retire: the human\'s 8-character code. Never guess or fabricate it.'),
1558
+ reason: z.string().max(300).optional().describe('retire / false_alarm: why, in one line.'),
1559
+ mode: z.enum(['worktree', 'staged', 'range']).optional().describe('check: the working tree vs HEAD (default), the staged change, or a commit range (with base).'),
1560
+ base: z.string().optional().describe('check: base ref for a range (merge-base with head is used).'),
1561
+ head: z.string().optional().describe('check: head ref for a range (default HEAD).'),
1562
+ files: z.array(z.string()).max(50).optional().describe('check: just list the rules these files fall under (no diff).'),
1563
+ include_candidates: z.boolean().optional().describe('check: also run candidate rules — to see what a rule would catch before the human approves it.'),
1564
+ block: z.boolean().optional().describe('promote: true only when the human printed the code with --block, making these rules stop an edit or a commit instead of warning. Rules warn by default.'),
1565
+ },
1566
+ }, async ({ action, project, query, limit, rules, ids, approve, reason, mode, base, head, files, include_candidates, block }, extra) => {
1567
+ try {
1568
+ const { brainRulesTool } = await import('../src/brain-rules.mjs');
1569
+ const brainPath = project ? path.join(path.resolve(project), 'brain.klypix') : (mcpPresence.brainPath || path.join(mcpPresence.vault, 'brain.klypix'));
1570
+ return toContent(await brainRulesTool({
1571
+ projectRoot: path.dirname(brainPath), brainPath, action, query, limit, rules, ids, approve, reason, mode, base, head, files,
1572
+ includeCandidates: include_candidates === true, block: block === true, pluginMode: PLUGIN_MODE, proposedBy: extra?.klypixClientName || '',
1573
+ }));
1574
+ } catch (error) {
1575
+ return toContent({ blocks: [{ kind: 'text', text: `brain_rules failed: ${error?.message || error}` }], isError: true });
1576
+ }
1577
+ });
1578
+
1543
1579
  server.registerTool('brain_doctor', {
1544
1580
  title: 'Brain doctor — is this brain current, wired, and in sync?',
1545
1581
  description: 'Read-only self-check of the installed klypix brain, as ONE verdict: VERSION (deployed brain-core + optional npm currency), CLAUDE (existing 5-hook capture readiness), CODEX (automatic MCP presence plus optional enhanced-hook status), TOOLS (discoverable MCP verbs), SESSIONS (all active presence-adapter sessions across hosts, never recent-chat history), and HARNESS (projection drift). Use to answer "is my brain current, correctly installed, in sync, and who is actually live?" without file-spelunking. Never writes: the only side effects are read-only subprocess queries (git rev-parse / tag --list / log / merge-base with fixed argument arrays, and `npm view` only when check_npm is true) — it creates, edits, and deletes nothing. SCOPE: only CLAUDE and CODEX get behavioural verdicts. HARNESS classifies the projected config/rules FILES on disk — a project can read fully ok while no other host has ever actually loaded them, so do not report a clean HARNESS as "Cursor/Cline/Windsurf/Copilot is working". The MCP-callable twin of `npx klypix-mcp doctor`. Besides the text, the result carries structuredContent {verdict, layers, version, autoUpdate, supervisors, readinessWarnings, actions}: the same verdict as data, including when the next automatic update check runs and what it will do.',
@@ -1808,6 +1844,43 @@ server.server.oninitialized = () => {
1808
1844
  };
1809
1845
  server.server.onclose = stopRuntimePresence;
1810
1846
  process.once('exit', stopRuntimePresence);
1847
+ // Published tool schemas are a compatibility contract: a running supervisor
1848
+ // compares them byte for byte before it hot-swaps a worker, and the frozen
1849
+ // snapshots in test/fixtures pin them. zod 4.6 writes a union of plain types as
1850
+ // `type: ["number","string"]` where every snapshot and every supervisor already
1851
+ // running knows `anyOf: [{type:"number"},{type:"string"}]` — the same meaning in
1852
+ // other bytes. Rewrite each type list back into anyOf, FIRST in its object as
1853
+ // zod 4.4 wrote it (before description), so a zod bump never drifts a frozen
1854
+ // schema.
1855
+ const isTypeList = (value) => Array.isArray(value) && value.length > 1 && value.every((t) => typeof t === 'string');
1856
+ function frozenSchemaShape(node) {
1857
+ if (Array.isArray(node)) return node.map(frozenSchemaShape);
1858
+ if (!node || typeof node !== 'object') return node;
1859
+ const out = {};
1860
+ if (isTypeList(node.type)) out.anyOf = node.type.map((type) => ({ type }));
1861
+ for (const [key, value] of Object.entries(node)) {
1862
+ if (key === 'type' && isTypeList(value)) continue;
1863
+ out[key] = frozenSchemaShape(value);
1864
+ }
1865
+ return out;
1866
+ }
1867
+ const sdkListTools = server.server._requestHandlers?.get?.('tools/list');
1868
+ if (typeof sdkListTools === 'function') {
1869
+ server.server._requestHandlers.set('tools/list', async (request, extra) => {
1870
+ const result = await sdkListTools(request, extra);
1871
+ if (!result || !Array.isArray(result.tools)) return result;
1872
+ return {
1873
+ ...result,
1874
+ tools: result.tools.map((tool) => ({
1875
+ ...tool,
1876
+ ...(tool.inputSchema ? { inputSchema: frozenSchemaShape(tool.inputSchema) } : {}),
1877
+ ...(tool.outputSchema ? { outputSchema: frozenSchemaShape(tool.outputSchema) } : {}),
1878
+ })),
1879
+ };
1880
+ });
1881
+ } else {
1882
+ log('tools/list schema shaping unavailable in this SDK; published schemas follow zod as-is');
1883
+ }
1811
1884
  await server.connect(transport);
1812
1885
  // Most sessions use brain_sync/presence and should not pay for a private model
1813
1886
  // they never query. Legacy mode restores the old eager lifecycle immediately.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klypix-mcp",
3
- "version": "1.106.2",
3
+ "version": "1.107.2",
4
4
  "mcpName": "io.github.dahshanlabs/klypix-mcp",
5
5
  "description": "Shared project memory for Claude Code, Codex and Cursor: decisions, corrections, overlap warnings.",
6
6
  "type": "module",
@@ -33,7 +33,10 @@
33
33
  "team-memory",
34
34
  "decision-log",
35
35
  "overlap-warnings",
36
- "agent-collaboration"
36
+ "agent-collaboration",
37
+ "ci",
38
+ "git-hooks",
39
+ "code-review"
37
40
  ],
38
41
  "homepage": "https://klypix.com/klypix-mcp",
39
42
  "repository": {
@@ -97,7 +100,7 @@
97
100
  "bench": "node bin/klypix-mcp.mjs bench",
98
101
  "test:bench": "node test/bench.mjs",
99
102
  "pretest": "node test/publish-workflow.mjs",
100
- "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/network-bridge.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",
103
+ "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/handoff-freshness.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/network-bridge.mjs && node test/brain-rules.mjs && node test/brain-recall.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",
101
104
  "test:memory": "node test/memory-runtime.mjs",
102
105
  "test:memory:soak": "node --expose-gc test/memory-soak.mjs",
103
106
  "runtime": "node bin/klypix-runtime.mjs",
@@ -111,7 +114,7 @@
111
114
  "fractional-indexing": "^3.2.0",
112
115
  "jpeg-js": "^0.4.4",
113
116
  "jszip": "^3.10.1",
114
- "zod": "^4.3.6"
117
+ "zod": "^4.6.7"
115
118
  },
116
119
  "devDependencies": {
117
120
  "@huggingface/transformers": "^4.2.0"
package/src/app-tools.mjs CHANGED
@@ -66,7 +66,7 @@ import {
66
66
  appToolsPlatform, appVaultSetting, canonicalCanvasPath, isOpenInApp, pathHash, readEndpoint, sharedCanvasesDir,
67
67
  tellUser, TELL_USER, APP_BRIDGE_PROTOCOL, LEASE_SINCE_APP_VERSION,
68
68
  } from './app-lease.mjs';
69
- import { brainTarget, canvasTitleOf, resolveCanvasDetailed, walkVault } from './klypix-core.mjs';
69
+ import { brainTarget, canvasTitleOf, closestCanvases, notFoundText, resolveCanvasDetailed, walkVault } from './klypix-core.mjs';
70
70
  import {
71
71
  classifyLinkTier, derivedReading, DOCUMENT_TEXT_KIND, escapeInstructionLines, fenceContent, parseKlypix, readManifestCheap, recordedAuthor, savedReadingIndex, scopeLockedView,
72
72
  } from './klypix-format.mjs';
@@ -491,10 +491,12 @@ export async function readCardContents({ vault, canvas, card_ids, read_new, refr
491
491
  const resolved = resolveCanvasDetailed(vault, canvas);
492
492
  if (!resolved.file) {
493
493
  const sentence = tellUser('NOT_FOUND');
494
+ const closest = resolved.ambiguous ? [] : closestCanvases(vault, canvas);
494
495
  const first = resolved.ambiguous
495
496
  ? `More than one canvas is titled "${canvas}" — pass one of these paths:\n${resolved.ambiguous.map(p => ` - ${p}`).join('\n')}`
496
- : `Canvas not found: ${canvas}. Names match a file name or the title KLYPIX shows.`;
497
- return envelope({ ok: false, code: 'NOT_FOUND', tell_user: sentence, text: first, structured: resolved.ambiguous ? { candidates: resolved.ambiguous } : {} });
497
+ : notFoundText(canvas, null, closest);
498
+ return envelope({ ok: false, code: 'NOT_FOUND', tell_user: sentence, text: first,
499
+ structured: resolved.ambiguous ? { candidates: resolved.ambiguous } : { closest: closest.map((c) => c.path), maybe_cloud_space: true } });
498
500
  }
499
501
  const file = resolved.file;
500
502
  let parsed;