klypix-mcp 1.34.0 → 1.37.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,18 +10,33 @@ You've seen the setup: point an AI at a folder of notes, watch the graph fill up
10
10
 
11
11
  ## Quick start
12
12
 
13
- **Claude Code + Codex (native, machine-wide):**
13
+ **Claude Code + Codex:**
14
14
 
15
15
  ```bash
16
16
  npx klypix-mcp install
17
17
  ```
18
18
 
19
- Claude Code keeps its live hooks for auto-brief + auto-capture. Codex gets a native
20
- `~/.codex/config.toml` MCP registration, conditional global guidance, and lifecycle
21
- hooks for truthful cross-agent presence and one-time messages. Existing MCP servers,
22
- hooks, settings, and personal instructions are preserved; KLYPIX owns only its fenced
23
- blocks and `codex-brain-hook.mjs` handlers, with backups before changes. Restart Codex
24
- after installation and approve the KLYPIX hooks when Codex presents its trust review.
19
+ Claude Code keeps its live hooks for auto-brief + auto-capture. Codex gets native MCP
20
+ tools, **automatic live-session presence**, and approval-free **smart task synchronization**
21
+ from the authorized MCP connection itself. `brain_sync` is a bounded **Context Gateway**:
22
+ one call returns task-relevant brain memory, meaningful active-task peers, structured
23
+ exact-file conflicts, one-time notes, and automatic late-arrival overlap alerts. Idle MCP
24
+ connections stay counted for diagnostics but are hidden from the task-peer list. MCP
25
+ server instructions plus a managed Codex `AGENTS.md` block teach Codex to call it at task
26
+ start, when scope changes, and on completion—no Codex hook approval required. Run inside a
27
+ project containing `brain.klypix`, or run `npx klypix-mcp link` in each brain project.
28
+ The installer removes the obsolete global KLYPIX `--vault "."` table that could resolve
29
+ outside the active project; every unrelated Codex setting and MCP server is preserved.
30
+
31
+ Optional native lifecycle capture for mechanical prompt/tool/file events:
32
+
33
+ ```bash
34
+ npx klypix-mcp install --codex-hooks
35
+ ```
36
+
37
+ Codex owns this native layer's security decision and asks the user to review/trust the
38
+ hooks on surfaces that expose hook management. `brain_doctor` reports it separately as
39
+ off, execution-unverified, or active; the approval-free smart layer remains active either way.
25
40
 
26
41
  Give a project a brain by dropping a `brain.klypix` in it — the
27
42
  [KLYPIX app](https://klypix.com) does it in one click (*Save canvas as project brain*),
@@ -41,7 +56,7 @@ managed files without changing them with `npx klypix-mcp link --check`.
41
56
 
42
57
  The difference from a folder of notes is not the shape — it's that this memory is a **mechanism, not a filing convention**:
43
58
 
44
- - **It briefs every session.** Each agent session starts already knowing the project's decisions, open questions, and standing rules — a compact brief (≈3–5k tokens even at 600+ cards, growing sublinearly). Measured on our own brain: **73% of past decisions recovered with one search round (55% brief-only) vs 0% cold** (n=20).
59
+ - **It briefs each task, not just each session.** `brain_sync` ranks a bounded memory capsule from the actual task intent and expected files. The full generated brief remains available for broad history/status work, while the always-loaded `AGENTS.md` fallback stays compact. Measured on our own brain: **73% of past decisions recovered with one search round (55% brief-only) vs 0% cold** (n=20).
45
60
  - **It argues back.** `brain_challenge`: propose a decision and the brain answers with receipts — *"you reversed this on June 12; here's the correction — captured by a different agent, coordinate before overriding."* Deterministic evidence only (correction-cues, opposite-polarity), never mere topical similarity. A memory that can't disagree with you is flattery.
46
61
  - **It knows when it's stale.** Decision cards can anchor to the exact code they were decided against (git blob OID). When that code moves on, the card raises its hand in the next brief. Their notes rot silently; ours confess.
47
62
  - **It answers from the past.** `brain_ask` with `as_of: 2026-03-01` answers what the project believed *then* — corrections from the future never leak backwards.
@@ -59,7 +74,8 @@ And it's not a cage: everything **exports to Markdown and JSON Canvas** in one c
59
74
 
60
75
  ## Setup for plain MCP clients
61
76
 
62
- Any MCP client gets the tools without the hooks. For **Claude Desktop**, in `claude_desktop_config.json`:
77
+ Any MCP client gets the tools and connection-lifecycle presence without host hooks. For
78
+ **Claude Desktop**, in `claude_desktop_config.json`:
63
79
 
64
80
  ```json
65
81
  {
@@ -72,9 +88,14 @@ Any MCP client gets the tools without the hooks. For **Claude Desktop**, in `cla
72
88
  }
73
89
  ```
74
90
 
75
- Then ask your agent things like *"what did we decide about auth?"*, *"challenge this: let's switch to polling"*, or *"turn these notes into a board."*
91
+ If the vault contains `brain.klypix`, that MCP connection also appears as an active
92
+ session. `brain_sync` adds compact task memory, expected-file coordination, task-only peer
93
+ reporting, structured exact-overlap warnings, automatic conflict alerts, timing receipts,
94
+ and one-time peer-note delivery. Then ask your
95
+ agent things like *"what did we decide about auth?"*, *"challenge this: let's switch
96
+ to polling"*, or *"turn these notes into a board."*
76
97
 
77
- ## The 17 verbs
98
+ ## The 18 verbs
78
99
 
79
100
  | Tool | What it does |
80
101
  |---|---|
@@ -83,9 +104,11 @@ Then ask your agent things like *"what did we decide about auth?"*, *"challenge
83
104
  | `brain_note` | Capture with the full lifecycle — supersede / ✓ resolve / ~ update / 🛠 skill / closes: |
84
105
  | `brain_reconcile` | Find stale-vs-correction pairs + unrecorded migrations |
85
106
  | `brain_insights` | Hubs, orphaned decisions, stale questions, area sizes |
107
+ | `brain_lens` | Machine-readable freshness, provenance, activity, timeline, orrery, and unresolved views |
86
108
  | `brain_garden` | Maintenance pass over the brain |
87
- | `brain_doctor` | Self-diagnosis: version, host adapters, active lifecycle sessions, projection drift |
109
+ | `brain_doctor` | Self-diagnosis: version, core/enhanced host adapters, active sessions, projection drift |
88
110
  | `brain_message` | Session-to-session coordination notes |
111
+ | `brain_sync` | Context Gateway: compact task memory, active-task peers, structured conflicts, automatic alerts, and timing |
89
112
  | `brain_connect` | Find + draw related-but-unlinked cards |
90
113
  | `canvas_view` | The board as an MCP App — Apps-capable chats get an interactive spatial view; everyone else gets clean text |
91
114
  | `read_canvas` | A canvas as markdown (cards, connection graph, `[[links]]`, `#tags`) |
@@ -97,23 +120,25 @@ Then ask your agent things like *"what did we decide about auth?"*, *"challenge
97
120
 
98
121
  ### Tools vs. the *automatic* brain
99
122
 
100
- This package is the **agent-neutral read/write surface** — any MCP client gets the tools above on demand (*pull*), in any project. `install` wires Claude Code's existing capture path plus Codex's native MCP, conditional guidance, and separate presence adapter; `link` projects the repository-level MCP and instruction files used by other coding agents. Full transcript-driven auto-capture remains a Claude Code capability. Codex reports lifecycle presence and captures durable decisions explicitly through `brain_note`; hookless clients can use the MCP surface but are not reported as active.
123
+ This package is the **agent-neutral read/write surface** — any MCP client gets the tools above on demand (*pull*), automatically registers presence for the lifetime of its authorized connection, and receives the Context Gateway workflow through standard MCP server instructions. `install` wires Claude Code's existing capture path and installs the local runtime; `link` projects repository-level MCP and instruction files for Codex and other coding agents. Codex and other clients retrieve relevant project memory and coordinate task intent/files through one `brain_sync` call, while `brain_note` captures durable decisions explicitly; optional host hooks add mechanically guaranteed lifecycle capture.
101
124
 
102
125
  ### Agent-neutral live presence
103
126
 
104
- An active session means a host lifecycle hook heartbeated within 10 minutes; a row in a
105
- recent-chat list is history, not presence. Claude Code and Codex publish into the same
106
- per-brain lane, while each host keeps its own adapter and capture behavior. `SessionEnd`
107
- removes clean exits immediately, and the TTL covers crashes.
127
+ An active session means an authorized MCP connection or host lifecycle adapter
128
+ heartbeated within 10 minutes; a row in a recent-chat list is history, not presence.
129
+ Each MCP connection registers at initialization and removes itself on disconnect.
130
+ Optional host adapters merge into that same logical session rather than double-counting
131
+ it, enrich it with intent/files, and remove only their own channel. The TTL covers crashes.
108
132
 
109
- Future hosts can import `klypix-mcp/presence` and map their lifecycle events to
110
- `upsertSession`, `removeSession`, and `receiveMessages`. The shared contract accepts
111
- `id`, `client`, `surface`, `model`, `branch`, `intent`, and touched `files`; host-specific
112
- transcript parsing stays outside the protocol.
133
+ Future hosts get baseline support merely by connecting the MCP server. A deeper adapter
134
+ can import `klypix-mcp/presence` and map lifecycle events to `upsertSession`,
135
+ `removeSession`, and `receiveMessages`. The shared contract accepts `id`, `client`,
136
+ `surface`, `model`, `branch`, `intent`, touched `files`, and adapter `channel`;
137
+ host-specific transcript parsing stays outside the protocol.
113
138
 
114
139
  ### Updates — the propagation contract
115
140
 
116
- `install` lays the whole brain (hooks + engine + local MCP server) into `~/.claude/project-brain`, then points both Claude and Codex at that installed bundle so there is no npx cache to go stale. Updates are automatic through the Claude session-start hook: it checks npm (≤ once/24h, fail-open, disable with `KLYPIX_AUTO_UPDATE=0`) and self-installs newer releases, including healing the Codex registration. One honest caveat: a running stdio server can't hot-swap — a new binary loads on the next full app launch (or `/mcp` reconnect); `brain_doctor`'s RUNNING line tells you when that's needed.
141
+ `install` lays the whole brain (hooks + engine + local MCP server) into `~/.claude/project-brain` and keeps the current brain project's Codex config machine-portable. Updates are automatic through the Claude session-start hook: it checks npm (≤ once/24h, fail-open, disable with `KLYPIX_AUTO_UPDATE=0`) and self-installs newer releases. One honest caveat: a running stdio server can't hot-swap — a new binary loads on the next full app launch (or `/mcp` reconnect); `brain_doctor`'s RUNNING line tells you when that's needed.
117
142
 
118
143
  ## Also speaks A2A (Agent-to-Agent)
119
144
 
@@ -9,6 +9,7 @@
9
9
  //
10
10
  // npx klypix-mcp install # install / update the brain on this machine
11
11
  // npx klypix-mcp install --force # overwrite even a newer / dev-deployed brain
12
+ // npx klypix-mcp install --codex-hooks # optional prompt/file awareness; Codex asks for trust
12
13
  //
13
14
  // Never-throws-silently: it's an explicit CLI, so it reports what it did and exits
14
15
  // non-zero on a real failure. Never wires a broken settings.json (refuse + restore).
@@ -18,10 +19,14 @@ import path from 'path';
18
19
  import { fileURLToPath } from 'url';
19
20
  import {
20
21
  connectCodexMcpServer,
22
+ disconnectCodexMcpServer,
21
23
  mcpServerEntry,
22
24
  mergeCodexGlobalInstructions,
23
25
  } from '../src/agent-rules.mjs';
24
- import { mergeCodexPresenceHooks } from '../src/codex-hooks.mjs';
26
+ import {
27
+ codexPresenceHookStatus,
28
+ mergeCodexPresenceHooks,
29
+ } from '../src/codex-hooks.mjs';
25
30
 
26
31
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
27
32
  const PKG_ROOT = path.resolve(__dirname, '..');
@@ -29,6 +34,7 @@ const SRC = path.join(PKG_ROOT, 'src');
29
34
  const BIN = path.join(PKG_ROOT, 'bin');
30
35
  const VERSION = (() => { try { return JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8')).version || ''; } catch { return ''; } })();
31
36
  const FORCE = process.argv.includes('--force');
37
+ const CODEX_HOOKS = process.argv.includes('--codex-hooks');
32
38
 
33
39
  const HOME = os.homedir();
34
40
  const CLAUDE_DIR = path.join(HOME, '.claude');
@@ -38,44 +44,96 @@ const CODEX_CONFIG = path.join(HOME, '.codex', 'config.toml');
38
44
  const HOOK_MARK = 'global-brain-hook';
39
45
  const exists = (p) => { try { fs.statSync(p); return true; } catch { return false; } };
40
46
  const fwd = (p) => p.replace(/\\/g, '/');
47
+ const copyRetrySleepSync = (ms) => {
48
+ try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); }
49
+ catch { /* best effort */ }
50
+ };
51
+ function copyFileRobust(src, dest, tries = 8) {
52
+ for (let attempt = 1; attempt <= tries; attempt++) {
53
+ try { fs.copyFileSync(src, dest); return; }
54
+ catch (error) {
55
+ const retryable = ['EBUSY', 'EPERM', 'EACCES'].includes(error?.code);
56
+ if (!retryable || attempt === tries) throw error;
57
+ copyRetrySleepSync(20 * attempt);
58
+ }
59
+ }
60
+ }
41
61
  function copyDir(src, dest) {
42
62
  fs.mkdirSync(dest, { recursive: true });
43
63
  for (const e of fs.readdirSync(src, { withFileTypes: true })) {
44
64
  const s = path.join(src, e.name), d = path.join(dest, e.name);
45
- if (e.isDirectory()) copyDir(s, d); else if (e.isFile()) fs.copyFileSync(s, d);
65
+ if (e.isDirectory()) copyDir(s, d); else if (e.isFile()) copyFileRobust(s, d);
46
66
  }
47
67
  }
48
68
 
49
- // Codex has three independent integration layers: MCP gives it the brain tools,
50
- // a conditional managed block in ~/.codex/AGENTS.md tells every brain project to
51
- // use them, and lifecycle hooks publish live presence to the shared session lane.
69
+ // Codex has two approval-free layers and one optional native layer:
70
+ // project-scoped MCP gives it tools + live presence + brain_sync, while the
71
+ // conditional ~/.codex/AGENTS.md block makes the Context Gateway workflow
72
+ // automatic on every task. Native lifecycle hooks add mechanical prompt/file
73
+ // capture only when the user explicitly asks for --codex-hooks and approves them
74
+ // in a Codex surface that exposes trust review.
52
75
  function wireCodex() {
53
- const mcp = connectCodexMcpServer({
54
- configPath: CODEX_CONFIG,
55
- entry: mcpServerEntry({ vault: '.', home: HOME }),
56
- });
76
+ const projectDir = process.cwd();
77
+ const hasProjectBrain = exists(path.join(projectDir, 'brain.klypix'))
78
+ || exists(path.join(projectDir, 'brain.any'));
79
+ const projectConfig = path.join(projectDir, '.codex', 'config.toml');
80
+ const mcp = hasProjectBrain
81
+ ? connectCodexMcpServer({
82
+ configPath: projectConfig,
83
+ // Project config is commonly committed. Keep it portable: the
84
+ // package invocation that is running this installer is exactly the
85
+ // version the project asked for, while an absolute home path would
86
+ // dirty the repo and break it on every other machine.
87
+ entry: { command: 'npx', args: ['-y', 'klypix-mcp', '--vault', '.'] },
88
+ cwd: '..',
89
+ })
90
+ : { ok: true, action: 'skipped-no-project-brain', path: projectConfig };
91
+ // Pre-1.35 installed a global `--vault "."` entry. A global Codex process
92
+ // resolves that dot from the app install directory, not the user's project,
93
+ // so it can silently bind the wrong brain and override the correct project
94
+ // table. Remove only KLYPIX-owned global tables; preserve every other server.
95
+ const globalMcp = disconnectCodexMcpServer({ configPath: CODEX_CONFIG });
57
96
  const instructions = mergeCodexGlobalInstructions(HOME);
58
97
  const hookScript = path.join(BRAIN_DIR, 'codex-brain-hook.mjs');
59
- const presence = exists(hookScript)
98
+ const presence = CODEX_HOOKS && exists(hookScript)
60
99
  ? mergeCodexPresenceHooks({
61
100
  home: HOME,
62
101
  command: `node "${fwd(hookScript)}"`,
63
102
  })
64
- : { ok: true, action: 'not-available' };
65
- return { mcp, instructions, presence, ok: mcp.ok && instructions.ok && presence.ok };
103
+ : {
104
+ ok: true,
105
+ action: codexPresenceHookStatus(HOME).installed ? 'existing' : 'off',
106
+ ...codexPresenceHookStatus(HOME),
107
+ };
108
+ return {
109
+ mcp,
110
+ globalMcp,
111
+ instructions,
112
+ presence,
113
+ ok: mcp.ok && globalMcp.ok && instructions.ok && presence.ok,
114
+ };
66
115
  }
67
116
 
68
117
  function reportCodex(result) {
69
118
  if (result.ok) {
70
- const live = result.presence.action === 'not-available'
71
- ? 'live presence pending a current brain bundle'
72
- : `live presence (${result.presence.action})`;
73
- console.log(`✓ wired Codex: MCP tools (${result.mcp.action}) + conditional project-brain guidance (${result.instructions.action}) + ${live}`);
119
+ const mcp = result.mcp.action === 'skipped-no-project-brain'
120
+ ? 'project MCP skipped (run `npx klypix-mcp link` inside a brain project)'
121
+ : `project MCP + automatic presence/Context Gateway (${result.mcp.action})`;
122
+ const enhanced = result.presence.action === 'off'
123
+ ? 'enhanced hooks off (optional)'
124
+ : result.presence.action === 'existing'
125
+ ? `enhanced hooks already configured (${result.presence.executionStatus || 'unverified'})`
126
+ : `enhanced hooks ${result.presence.action} — approve/review them in Codex /hooks`;
127
+ console.log(`✓ wired Codex: ${mcp} + approval-free task guidance (${result.instructions.action}) + ${enhanced}`);
128
+ if (result.globalMcp.action === 'disconnected') {
129
+ console.log('✓ removed obsolete global Codex KLYPIX MCP entry (it could resolve "." outside the project); other MCP servers were preserved');
130
+ }
74
131
  return;
75
132
  }
76
133
  if (!result.mcp.ok) console.error(`⚠ Codex MCP was not changed: ${result.mcp.error}`);
134
+ if (!result.globalMcp.ok) console.error(`⚠ obsolete global Codex MCP entry was not removed: ${result.globalMcp.error}`);
77
135
  if (!result.instructions.ok) console.error(`⚠ Codex guidance was not changed: ${result.instructions.error}`);
78
- if (!result.presence.ok) console.error(`⚠ Codex live presence was not changed: ${result.presence.error}`);
136
+ if (!result.presence.ok) console.error(`⚠ Codex enhanced hooks were not changed: ${result.presence.error}`);
79
137
  console.error(' Claude Code installation is intact. Fix the Codex warning, then re-run this command.');
80
138
  }
81
139
 
@@ -134,6 +192,10 @@ function migrateProjectMcpConfig() {
134
192
  const entry = servers && servers['klypix-canvas'];
135
193
  if (!entry || (entry.command !== 'npx' && entry.command !== 'node')) return null; // absent / hand-customized → leave it
136
194
  const args = Array.isArray(entry.args) ? entry.args : [];
195
+ // A repo-relative node launch (for example scripts/klypix-mcp-server.mjs)
196
+ // is deliberately portable and often tracks a project-owned bundle.
197
+ // Never replace it with a machine-specific ~/.claude path.
198
+ if (entry.command === 'node' && args[0] && !path.isAbsolute(String(args[0]))) return null;
137
199
  const vi = args.indexOf('--vault');
138
200
  const vault = vi >= 0 && args[vi + 1] ? args[vi + 1] : '.';
139
201
  const next = mcpServerEntry({ vault }); // local now that the bundle is installed
@@ -154,7 +216,7 @@ const flatten = (code) => code
154
216
  .replace(/\.\.\/src\/klypix-(core|format)\.mjs/g, './klypix-$1.mjs')
155
217
  // brain-doctor + agent-rules (the server's lazy `import('../src/brain-doctor.mjs')`
156
218
  // for the brain_doctor tool) → flat sibling refs in the runtime layout.
157
- .replace(/\.\.\/src\/(brain-doctor|agent-rules)\.mjs/g, './$1.mjs')
219
+ .replace(/\.\.\/src\/(brain-doctor|agent-rules|mcp-presence)\.mjs/g, './$1.mjs')
158
220
  .replace(/const PKG_VERSION = \(\(\) => \{[\s\S]*?\}\)\(\);/, `const PKG_VERSION = '${VERSION}'; // baked at install (flat layout has no package.json)`);
159
221
 
160
222
  const gotLock = acquireLock();
@@ -211,7 +273,7 @@ try {
211
273
  // canvas-view-app.html is the canvas_view MCP App UI — staged raw (an HTML
212
274
  // file must never get a JS-comment banner) beside the flat server, which
213
275
  // resolves it via its ./canvas-view-app.html candidate path.
214
- for (const f of ['global-brain-hook.mjs', 'brain-semantic.mjs', 'brain-note.mjs', 'brain-git-hook.mjs', 'klypix-format.mjs', 'klypix-core.mjs', 'agent-rules.mjs', 'brain-doctor.mjs', 'agent-presence.mjs', 'codex-brain-hook.mjs', 'codex-hooks.mjs', 'canvas-view-app.html']) {
276
+ for (const f of ['global-brain-hook.mjs', 'brain-semantic.mjs', 'brain-note.mjs', 'brain-git-hook.mjs', 'klypix-format.mjs', 'klypix-core.mjs', 'agent-rules.mjs', 'brain-doctor.mjs', 'agent-presence.mjs', 'mcp-presence.mjs', 'codex-brain-hook.mjs', 'codex-hooks.mjs', 'canvas-view-app.html']) {
215
277
  const s = path.join(SRC, f); if (exists(s)) staged.push({ dst: f, content: fs.readFileSync(s, 'utf8') });
216
278
  }
217
279
  for (const [src, dst] of [['klypix-mcp.mjs', 'klypix-mcp-server.mjs'], ['klypix-a2a.mjs', 'klypix-a2a-server.mjs']]) {
@@ -282,7 +344,8 @@ try {
282
344
  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}.`);
283
345
  console.log(`✓ MCP server runs from the local bundle (node ${fwd(path.join(BRAIN_DIR, 'klypix-mcp-server.mjs'))}) — no npx cache, works offline, always the installed version.`);
284
346
  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.`);
285
- console.log(' Claude Code keeps its existing auto-brief/capture hooks; Codex uses separate lifecycle hooks for live presence and native MCP for durable memory.');
347
+ console.log(' Claude Code keeps its existing auto-brief/capture hooks; Codex gets the brain_sync Context Gateway (compact task memory + clean peers + automatic conflict alerts) with no hook trust prompt.');
348
+ console.log(' Optional mechanical Codex lifecycle capture: re-run with `--codex-hooks`, then approve/review KLYPIX in a Codex surface that supports hook trust.');
286
349
  console.log(' ⚠ Open sessions keep their OLD server until relaunched: fully quit & reopen the app (a session resume / new chat does NOT respawn the MCP server). `brain_doctor`\'s RUNNING line confirms when you\'re current.');
287
350
  console.log(' Verify anytime: `npx klypix-mcp doctor` (is the brain current + wired + in sync, who else is live).');
288
351
  } catch (e) {
@@ -17,7 +17,7 @@
17
17
  // Synchronous top-level by design: the dispatcher does `await import(this); process.exit(0)`,
18
18
  // so all work (and its logs) must complete during module evaluation, before exit.
19
19
  import path from 'path';
20
- import { linkProject } from '../src/agent-rules.mjs';
20
+ import { compactAgentsBrief, linkProject } from '../src/agent-rules.mjs';
21
21
 
22
22
  try {
23
23
  const args = process.argv.slice(3);
@@ -25,6 +25,7 @@ try {
25
25
  const dirArg = args.find(a => !a.startsWith('-'));
26
26
  const projectDir = path.resolve(dirArg || process.cwd());
27
27
  const { rules, mcp, hasBrain, version } = linkProject(projectDir, { check });
28
+ const compactBrief = await compactAgentsBrief(projectDir, { check });
28
29
 
29
30
  if (check) {
30
31
  // Audit-only: classify, report, exit 1 on any drift.
@@ -34,7 +35,11 @@ try {
34
35
  for (const r of mcp) console.log(` ${mark(r.status)} ${r.tool.padEnd(26)} ${r.file} — ${r.status.toUpperCase()}${r.why ? ' (' + r.why + ')' : ''}`);
35
36
  console.log('\n Rules / instructions:');
36
37
  for (const r of rules) console.log(` ${mark(r.status)} ${r.tool.padEnd(26)} ${r.file} — ${r.status.toUpperCase()}${r.stampedVersion ? ' (stamped v' + r.stampedVersion + ')' : ''}`);
37
- const drift = [...rules, ...mcp].filter(r => r.status !== 'ok');
38
+ if (compactBrief.status !== 'skipped') {
39
+ console.log(` ${mark(compactBrief.status)} ${'AGENTS.md compact fallback'.padEnd(26)} AGENTS.md — ${compactBrief.status.toUpperCase()}${compactBrief.bytes ? ` (${compactBrief.bytes} bytes)` : ''}`);
40
+ }
41
+ const briefDrift = ['stale', 'error'].includes(compactBrief.status) ? [compactBrief] : [];
42
+ const drift = [...rules, ...mcp, ...briefDrift].filter(r => r.status !== 'ok' && r.status !== 'skipped');
38
43
  if (!drift.length) { console.log(`\n✓ in sync — all ${rules.length + mcp.length} managed file(s) match brain v${version}.`); process.exit(0); }
39
44
  console.log(`\n✗ ${drift.length} file(s) drifted (stale / hand-edited / missing) — run \`npx klypix-mcp link\` to re-project.`);
40
45
  process.exit(1);
@@ -46,8 +51,11 @@ try {
46
51
  for (const r of mcp) console.log(` ${mark(r.action)} ${r.tool.padEnd(26)} ${r.file}${r.why ? ' (' + r.why + ')' : ''}`);
47
52
  console.log('\n Rules (so each tool auto-reads + captures the brain):');
48
53
  for (const r of rules) console.log(` ${mark(r.action)} ${r.tool.padEnd(26)} ${r.file}${r.why ? ' (' + r.why + ')' : ''}`);
54
+ if (compactBrief.status !== 'skipped') {
55
+ console.log(` ${mark(compactBrief.action)} ${'AGENTS.md compact fallback'.padEnd(26)} AGENTS.md${compactBrief.bytes ? ` (${compactBrief.bytes} bytes)` : ''}`);
56
+ }
49
57
 
50
- const changed = [...rules, ...mcp].filter(r => r.action && !['unchanged', 'skipped'].includes(r.action)).length;
58
+ const changed = [...rules, ...mcp, compactBrief].filter(r => r.action && !['unchanged', 'skipped'].includes(r.action)).length;
51
59
  console.log(`\n✓ ${changed} file(s) written/updated — every agent opened in this project now reads + captures ./brain.klypix.`);
52
60
  console.log(' Codex gets .codex/config.toml + AGENTS.md; Cline & Windsurf use their global MCP config plus project rules.');
53
61
  console.log(' Verify anytime with `npx klypix-mcp link --check` (or `npx klypix-mcp doctor`).');
@@ -27,8 +27,10 @@ import {
27
27
  resolveVault, getEmbedder, buildKlypixMap, cardSchema, connSchema,
28
28
  opListCanvases, opReadCanvas, opSearchCanvases, opSearchAllBrains,
29
29
  opBrainInsights, opBrainConnect, opBrainReconcile, opBrainGarden, opCreateCanvas, opAddToCanvas, opBrainNote, opBrainMessage, opBrainAsk, opBrainChallenge, opCanvasView, opBrainLens,
30
+ opBrainTaskContext,
30
31
  } from '../src/klypix-core.mjs';
31
32
  import { mcpServerEntry } from '../src/agent-rules.mjs';
33
+ import { createMcpPresence, KLYPIX_MCP_INSTRUCTIONS } from '../src/mcp-presence.mjs';
32
34
 
33
35
  // Real package version for the MCP handshake (was hardcoded '1.0.0', which
34
36
  // misled every client/version diagnosis — it could never reflect the true release).
@@ -102,17 +104,21 @@ if (process.argv[2] === 'init') {
102
104
 
103
105
  const vaultArgIdx = process.argv.indexOf('--vault');
104
106
  const VAULT = resolveVault(vaultArgIdx >= 0 ? process.argv[vaultArgIdx + 1] : undefined);
107
+ const server = new McpServer(
108
+ { name: 'klypix-canvas', version: PKG_VERSION },
109
+ { instructions: KLYPIX_MCP_INSTRUCTIONS },
110
+ );
111
+ const mcpPresence = createMcpPresence({ server, initialVault: VAULT });
105
112
 
106
113
  // Map a protocol-neutral core result → an MCP tool result.
107
114
  const toContent = (r) => {
108
115
  const content = r.blocks.map(b => b.kind === 'image'
109
116
  ? { type: 'image', data: b.data, mimeType: b.mime }
110
117
  : { type: 'text', text: b.text });
111
- return r.isError ? { content, isError: true } : { content };
118
+ const result = r.isError ? { content, isError: true } : { content };
119
+ return mcpPresence.decorateToolResult(result);
112
120
  };
113
121
 
114
- const server = new McpServer({ name: 'klypix-canvas', version: PKG_VERSION });
115
-
116
122
  server.registerTool('list_canvases', {
117
123
  title: 'List KLYPIX canvases',
118
124
  description: 'List all .klypix / .any canvas files in the vault, with card and connection counts.',
@@ -265,7 +271,7 @@ server.registerTool('brain_note', {
265
271
 
266
272
  server.registerTool('brain_message', {
267
273
  title: 'Message the other live agent sessions on this project (one-time note, not a brain card)',
268
- description: 'Leave a DELIBERATE, targeted note for the OTHER active agent sessions working on this project right now ("merged the hook refactor — rebase before you commit", "don\'t touch canvasStore, mid-refactor"). Any MCP client can SEND (the twin of the `🧠 MSG [to]: text` marker); delivery is to presence-wired sessions (Claude Code and Codex, with more host adapters supported by the shared protocol). Each receives it once through its lifecycle hook. A hookless peer can send but will NOT receive. Ephemeral (expires in 24h) and NOT persisted to the brain — for a durable decision use brain_note instead.',
274
+ description: 'Leave a DELIBERATE, targeted note for the OTHER active agent sessions working on this project right now ("merged the hook refactor — rebase before you commit", "don\'t touch canvasStore, mid-refactor"). Any MCP client can send and receive through the shared presence lane. Hookless MCP peers receive each note once on their next KLYPIX tool call (and may also see a host logging notification); lifecycle hooks provide more proactive delivery. Ephemeral (expires in 24h) and NOT persisted to the brain — for a durable decision use brain_note instead.',
269
275
  inputSchema: {
270
276
  text: z.string().describe('The note to deliver (kept to 400 chars).'),
271
277
  to: z.string().optional().describe('Target hint — a peer session id-prefix or branch name; omit or "all" for every live session.'),
@@ -273,12 +279,75 @@ server.registerTool('brain_message', {
273
279
  },
274
280
  }, async ({ text, to, canvas }) => {
275
281
  let via; try { via = server.server.getClientVersion()?.name; } catch { /* optional */ }
282
+ mcpPresence.noteSent(text);
276
283
  return toContent(await opBrainMessage({ vault: VAULT, canvas, text, to, via }));
277
284
  });
278
285
 
286
+ server.registerTool('brain_sync', {
287
+ title: 'KLYPIX Context Gateway — synchronize task, peers, conflicts, and relevant memory',
288
+ description: 'APPROVAL-FREE task gateway over the authorized MCP connection. Call FIRST with a concise intent and expected files, again when scope changes, and with phase:"complete" before the final response. One bounded response returns compact task-relevant brain context, active TASK peers (idle connections hidden), one-time messages, structured exact-file conflicts, and automatic late-arrival overlap alerts. Portable across Codex/Antigravity and every MCP host; native hooks are optional.',
289
+ annotations: {
290
+ destructiveHint: false,
291
+ idempotentHint: true,
292
+ openWorldHint: false,
293
+ },
294
+ inputSchema: {
295
+ intent: z.string().max(160).optional().describe('One sentence describing the current task. Supply for start/checkpoint; completion clears it.'),
296
+ files: z.array(z.string()).max(20).optional().describe('Project-relative files you expect to touch or have touched. Exact overlaps with peers are flagged.'),
297
+ phase: z.enum(['start', 'checkpoint', 'complete']).optional().describe('start replaces prior task scope; checkpoint merges changed scope; complete clears task intent/files. Default: checkpoint.'),
298
+ include_context: z.boolean().optional().describe('Include fast task-relevant brain cards in the same response. Defaults true; ignored for phase complete.'),
299
+ },
300
+ }, async ({ intent, files, phase, include_context }) => {
301
+ const totalStartedAt = Date.now();
302
+ const report = mcpPresence.sync({ intent, files, phase });
303
+ let taskContext = null;
304
+ if (phase !== 'complete' && include_context !== false && (intent || files?.length)) {
305
+ taskContext = await opBrainTaskContext({
306
+ vault: VAULT,
307
+ intent,
308
+ files,
309
+ k: 5,
310
+ budgetChars: 2800,
311
+ });
312
+ }
313
+ const contextText = taskContext?.blocks
314
+ ?.filter((block) => block.kind === 'text')
315
+ .map((block) => block.text)
316
+ .filter(Boolean)
317
+ .join('\n\n') || '';
318
+ const totalMs = Math.max(0, Date.now() - totalStartedAt);
319
+ const structuredContent = {
320
+ ...(report.structured || {
321
+ schemaVersion: 1,
322
+ status: 'idle',
323
+ phase: phase || 'checkpoint',
324
+ }),
325
+ context: taskContext?.context || {
326
+ mode: 'not-requested',
327
+ hits: [],
328
+ sufficient: false,
329
+ durationMs: 0,
330
+ },
331
+ timingMs: {
332
+ ...(report.structured?.timingMs || {}),
333
+ context: taskContext?.context?.durationMs || 0,
334
+ total: totalMs,
335
+ },
336
+ };
337
+ const timingText = `Context Gateway total: ${totalMs}ms`
338
+ + (taskContext?.context ? ` (memory ${taskContext.context.durationMs}ms)` : '') + '.';
339
+ return {
340
+ content: [{
341
+ type: 'text',
342
+ text: [report.text, contextText, timingText].filter(Boolean).join('\n\n'),
343
+ }],
344
+ structuredContent,
345
+ };
346
+ });
347
+
279
348
  server.registerTool('brain_doctor', {
280
349
  title: 'Brain doctor — is this brain current, wired, and in sync?',
281
- description: 'Read-only self-check of the installed klypix brain, as ONE verdict: VERSION (deployed brain-core + optional npm currency), CLAUDE (existing 4-hook capture readiness), CODEX (5-hook live-presence readiness), TOOLS (discoverable MCP verbs), SESSIONS (all active lifecycle 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 MCP-callable twin of `npx klypix-mcp doctor`.',
350
+ description: 'Read-only self-check of the installed klypix brain, as ONE verdict: VERSION (deployed brain-core + optional npm currency), CLAUDE (existing 4-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 MCP-callable twin of `npx klypix-mcp doctor`.',
282
351
  inputSchema: {
283
352
  project: z.string().optional().describe('Project dir to audit harness + peers for. Defaults to the server\'s working directory.'),
284
353
  check_npm: z.boolean().optional().describe('Also fetch npm latest to flag a stale brain (default false — this one does a network `npm view`).'),
@@ -297,7 +366,7 @@ server.registerTool('brain_doctor', {
297
366
  // makes the RUNNING check report the caller's actual server version, never a
298
367
  // phantom another session's heartbeat wrote to the shared registry.
299
368
  const report = inspect({ projectDir: project ? path.resolve(project) : process.cwd(), npmLatest, self: { pid: process.pid, version: PKG_VERSION } });
300
- return { content: [{ type: 'text', text: render(report, { color: false }) }] };
369
+ return mcpPresence.decorateToolResult({ content: [{ type: 'text', text: render(report, { color: false }) }] });
301
370
  } catch (e) {
302
371
  return { content: [{ type: 'text', text: `brain_doctor unavailable: ${e?.message || e}` }], isError: true };
303
372
  }
@@ -394,9 +463,14 @@ if (!canvasViewAsApp) {
394
463
  }
395
464
 
396
465
  const transport = new StdioServerTransport();
466
+ server.server.oninitialized = () => {
467
+ mcpPresence.start(VAULT);
468
+ recordRunningServer();
469
+ log(`ready · vault=${VAULT} · presence=mcp`);
470
+ };
471
+ server.server.onclose = () => mcpPresence.stop();
472
+ process.once('exit', () => mcpPresence.stop());
397
473
  await server.connect(transport);
398
- recordRunningServer();
399
- log(`ready · vault=${VAULT}`);
400
474
  // Pre-warm the on-device embedder in the BACKGROUND so the first cross-project
401
475
  // search of a session is already semantic, not a lexical fallback.
402
476
  getEmbedder(log).then(p => log(p ? 'semantic ready (pre-warmed)' : 'semantic unavailable — lexical only')).catch(() => {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klypix-mcp",
3
- "version": "1.34.0",
3
+ "version": "1.37.0",
4
4
  "description": "Every project gets a brain — one open .klypix file your AI agents read, write, and argue from, over MCP. Works with Claude, Codex, Cursor, Cline, any model.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -39,7 +39,8 @@
39
39
  ".": "./index.mjs",
40
40
  "./format": "./src/klypix-format.mjs",
41
41
  "./core": "./src/klypix-core.mjs",
42
- "./presence": "./src/agent-presence.mjs"
42
+ "./presence": "./src/agent-presence.mjs",
43
+ "./mcp-presence": "./src/mcp-presence.mjs"
43
44
  },
44
45
  "files": [
45
46
  "src",
@@ -54,7 +55,7 @@
54
55
  "node": ">=18"
55
56
  },
56
57
  "scripts": {
57
- "test": "node test/codex-hooks.mjs && node test/agent-presence.mjs && node test/brain-doctor.mjs && node test/version-currency.mjs && node test/ship-capture.mjs && node test/lane-message.mjs && node test/brain-quality.mjs && node test/brief-and-recall.mjs && node test/layout-cluster.mjs && node test/brain-ask.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/canvas-view.mjs && node test/status-completeness.mjs"
58
+ "test": "node test/codex-hooks.mjs && node test/agent-presence.mjs && node test/context-gateway.mjs && node test/brain-doctor.mjs && node test/version-currency.mjs && node test/ship-capture.mjs && node test/lane-message.mjs && node test/brain-quality.mjs && node test/brief-and-recall.mjs && node test/layout-cluster.mjs && node test/brain-ask.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/canvas-view.mjs && node test/status-completeness.mjs"
58
59
  },
59
60
  "dependencies": {
60
61
  "@modelcontextprotocol/ext-apps": "^1.7.4",