klypix-mcp 1.34.0 → 1.38.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,46 @@ 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. The
24
+ earlier session gets a best-effort live MCP logging notification within seconds, while
25
+ the same alert remains guaranteed on its next KLYPIX action if its host hides logs. Idle MCP
26
+ connections stay counted for diagnostics but are hidden from the task-peer list. MCP
27
+ server instructions plus a managed Codex `AGENTS.md` block teach Codex to call it at task
28
+ start, when scope changes, and on completion—no Codex hook approval required. Run inside a
29
+ project containing `brain.klypix`, or run `npx klypix-mcp link` in each brain project.
30
+ The installer removes the obsolete global KLYPIX `--vault "."` table that could resolve
31
+ outside the active project; every unrelated Codex setting and MCP server is preserved.
32
+
33
+ Optional native lifecycle capture for mechanical prompt/tool/file events:
34
+
35
+ ```bash
36
+ npx klypix-mcp install --codex-hooks
37
+ ```
38
+
39
+ Codex owns this native layer's security decision and asks the user to review/trust the
40
+ hooks on surfaces that expose hook management. Once trusted, the enhanced layer
41
+ automatically injects bounded task-ranked brain memory on every prompt and warns before
42
+ an exact overlapping file edit. `brain_doctor` reports it separately as off,
43
+ execution-unverified, or active; the approval-free smart layer remains active either way.
44
+
45
+ Prove the exact installed runtime with two isolated real MCP clients:
46
+
47
+ ```bash
48
+ npx klypix-mcp conformance
49
+ ```
50
+
51
+ The test touches only a temporary fixture. It verifies tool discovery, task memory,
52
+ truthful peers, blocking overlap detection, proactive logging, and guaranteed in-band delivery.
25
53
 
26
54
  Give a project a brain by dropping a `brain.klypix` in it — the
27
55
  [KLYPIX app](https://klypix.com) does it in one click (*Save canvas as project brain*),
@@ -41,7 +69,7 @@ managed files without changing them with `npx klypix-mcp link --check`.
41
69
 
42
70
  The difference from a folder of notes is not the shape — it's that this memory is a **mechanism, not a filing convention**:
43
71
 
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).
72
+ - **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
73
  - **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
74
  - **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
75
  - **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 +87,8 @@ And it's not a cage: everything **exports to Markdown and JSON Canvas** in one c
59
87
 
60
88
  ## Setup for plain MCP clients
61
89
 
62
- Any MCP client gets the tools without the hooks. For **Claude Desktop**, in `claude_desktop_config.json`:
90
+ Any MCP client gets the tools and connection-lifecycle presence without host hooks. For
91
+ **Claude Desktop**, in `claude_desktop_config.json`:
63
92
 
64
93
  ```json
65
94
  {
@@ -72,9 +101,14 @@ Any MCP client gets the tools without the hooks. For **Claude Desktop**, in `cla
72
101
  }
73
102
  ```
74
103
 
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."*
104
+ If the vault contains `brain.klypix`, that MCP connection also appears as an active
105
+ session. `brain_sync` adds compact task memory, expected-file coordination, task-only peer
106
+ reporting, structured exact-overlap warnings, automatic conflict alerts, timing receipts,
107
+ best-effort proactive notifications, and guaranteed one-time peer-note delivery. Then ask your
108
+ agent things like *"what did we decide about auth?"*, *"challenge this: let's switch
109
+ to polling"*, or *"turn these notes into a board."*
76
110
 
77
- ## The 17 verbs
111
+ ## The 18 verbs
78
112
 
79
113
  | Tool | What it does |
80
114
  |---|---|
@@ -83,9 +117,11 @@ Then ask your agent things like *"what did we decide about auth?"*, *"challenge
83
117
  | `brain_note` | Capture with the full lifecycle — supersede / ✓ resolve / ~ update / 🛠 skill / closes: |
84
118
  | `brain_reconcile` | Find stale-vs-correction pairs + unrecorded migrations |
85
119
  | `brain_insights` | Hubs, orphaned decisions, stale questions, area sizes |
120
+ | `brain_lens` | Machine-readable freshness, provenance, activity, timeline, orrery, and unresolved views |
86
121
  | `brain_garden` | Maintenance pass over the brain |
87
- | `brain_doctor` | Self-diagnosis: version, host adapters, active lifecycle sessions, projection drift |
122
+ | `brain_doctor` | Self-diagnosis: version, core/enhanced host adapters, active sessions, projection drift |
88
123
  | `brain_message` | Session-to-session coordination notes |
124
+ | `brain_sync` | Context Gateway: compact task memory, active-task peers, structured conflicts, automatic alerts, and timing |
89
125
  | `brain_connect` | Find + draw related-but-unlinked cards |
90
126
  | `canvas_view` | The board as an MCP App — Apps-capable chats get an interactive spatial view; everyone else gets clean text |
91
127
  | `read_canvas` | A canvas as markdown (cards, connection graph, `[[links]]`, `#tags`) |
@@ -97,23 +133,25 @@ Then ask your agent things like *"what did we decide about auth?"*, *"challenge
97
133
 
98
134
  ### Tools vs. the *automatic* brain
99
135
 
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.
136
+ 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
137
 
102
138
  ### Agent-neutral live presence
103
139
 
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.
140
+ An active session means an authorized MCP connection or host lifecycle adapter
141
+ heartbeated within 10 minutes; a row in a recent-chat list is history, not presence.
142
+ Each MCP connection registers at initialization and removes itself on disconnect.
143
+ Optional host adapters merge into that same logical session rather than double-counting
144
+ it, enrich it with intent/files, and remove only their own channel. The TTL covers crashes.
108
145
 
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.
146
+ Future hosts get baseline support merely by connecting the MCP server. A deeper adapter
147
+ can import `klypix-mcp/presence` and map lifecycle events to `upsertSession`,
148
+ `removeSession`, `peekMessages`, and `receiveMessages`. The shared contract accepts `id`, `client`,
149
+ `surface`, `model`, `branch`, `intent`, touched `files`, and adapter `channel`;
150
+ host-specific transcript parsing stays outside the protocol.
113
151
 
114
152
  ### Updates — the propagation contract
115
153
 
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.
154
+ `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
155
 
118
156
  ## Also speaks A2A (Agent-to-Agent)
119
157
 
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+ // Self-contained KLYPIX MCP conformance test.
3
+ //
4
+ // Launches two real stdio clients against this exact installed server build,
5
+ // using a temporary brain + isolated presence registry. It verifies the
6
+ // Context Gateway, task memory, truthful task counts, exact overlap detection,
7
+ // proactive MCP logging, and guaranteed next-action delivery without touching
8
+ // the user's project or durable brain.
9
+ import fs from 'fs';
10
+ import os from 'os';
11
+ import path from 'path';
12
+ import { createRequire } from 'module';
13
+ import { fileURLToPath } from 'url';
14
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
15
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
16
+ import { LoggingMessageNotificationSchema } from '@modelcontextprotocol/sdk/types.js';
17
+ import { buildKlypixMap } from '../src/klypix-core.mjs';
18
+
19
+ const here = path.dirname(fileURLToPath(import.meta.url));
20
+ const jsonMode = process.argv.includes('--json');
21
+ const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
22
+ const PKG_VERSION = (() => {
23
+ try { return createRequire(import.meta.url)('../package.json').version; }
24
+ catch {
25
+ try {
26
+ const stamp = JSON.parse(fs.readFileSync(path.join(here, '.brain-version.json'), 'utf8'));
27
+ return stamp.brainVersion || stamp.appVersion || 'deployed';
28
+ } catch { return 'deployed'; }
29
+ }
30
+ })();
31
+ const serverPath = [
32
+ path.join(here, 'klypix-mcp.mjs'),
33
+ path.join(here, 'klypix-mcp-server.mjs'),
34
+ ].find((candidate) => fs.existsSync(candidate));
35
+
36
+ if (!serverPath) {
37
+ console.error('KLYPIX conformance failed: the MCP server executable is missing beside this command.');
38
+ process.exit(1);
39
+ }
40
+
41
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'klypix-conformance-'));
42
+ const vault = path.join(tempRoot, 'project');
43
+ const isolatedHome = path.join(tempRoot, 'home');
44
+ fs.mkdirSync(vault, { recursive: true });
45
+ fs.mkdirSync(isolatedHome, { recursive: true });
46
+ fs.writeFileSync(path.join(vault, 'brain.klypix'), await buildKlypixMap({
47
+ title: 'KLYPIX conformance brain',
48
+ kind: 'brain',
49
+ areas: [{
50
+ title: 'Brain',
51
+ cards: [{
52
+ text: 'Codex Context Gateway automatically coordinates active tasks and exact file overlaps across MCP sessions.',
53
+ }],
54
+ }],
55
+ }));
56
+
57
+ const baseEnv = Object.fromEntries(
58
+ Object.entries(process.env).filter(([, value]) => typeof value === 'string'),
59
+ );
60
+
61
+ async function connect(name, sessionId) {
62
+ const logs = [];
63
+ const transport = new StdioClientTransport({
64
+ command: process.execPath,
65
+ args: [serverPath, '--vault', vault],
66
+ env: {
67
+ ...baseEnv,
68
+ USERPROFILE: isolatedHome,
69
+ HOME: isolatedHome,
70
+ KLYPIX_SESSION_ID: sessionId,
71
+ KLYPIX_MCP_INBOX_POLL_MS: '250',
72
+ KLYPIX_RERANK: '0',
73
+ },
74
+ stderr: 'pipe',
75
+ });
76
+ const client = new Client({ name, version: '1.0.0' });
77
+ client.setNotificationHandler(LoggingMessageNotificationSchema, (notification) => {
78
+ logs.push(String(notification?.params?.data || ''));
79
+ });
80
+ await client.connect(transport);
81
+ await client.setLoggingLevel('info');
82
+ return { client, transport, logs };
83
+ }
84
+
85
+ const checks = {};
86
+ const metrics = {};
87
+ let a;
88
+ let b;
89
+ try {
90
+ a = await connect('codex-klypix-conformance-a', 'klypix-conformance-a');
91
+ b = await connect('codex-klypix-conformance-b', 'klypix-conformance-b');
92
+ const tools = await a.client.listTools();
93
+ checks.brainSyncDiscoverable = tools.tools?.some((tool) => tool.name === 'brain_sync') === true;
94
+
95
+ const aStart = await a.client.callTool({
96
+ name: 'brain_sync',
97
+ arguments: {
98
+ phase: 'start',
99
+ intent: 'Validate automatic Codex Context Gateway coordination',
100
+ files: ['src/conformance-overlap.ts'],
101
+ },
102
+ });
103
+ const bStart = await b.client.callTool({
104
+ name: 'brain_sync',
105
+ arguments: {
106
+ phase: 'start',
107
+ intent: 'Validate proactive overlap delivery from a second session',
108
+ files: ['src/conformance-overlap.ts'],
109
+ },
110
+ });
111
+ const aStructured = aStart.structuredContent || {};
112
+ const bStructured = bStart.structuredContent || {};
113
+ checks.taskMemory = Array.isArray(aStructured.context?.hits) && aStructured.context.hits.length > 0;
114
+ checks.truthfulTaskCount = bStructured.counts?.activeTasks === 2
115
+ && bStructured.peers?.length === 1;
116
+ checks.exactBlockingOverlap = bStructured.conflicts?.length === 1
117
+ && bStructured.conflicts[0]?.kind === 'exact-file'
118
+ && bStructured.conflicts[0]?.severity === 'blocking';
119
+ checks.alertQueued = bStructured.alertsQueued?.length === 1;
120
+ metrics.firstClientMs = aStructured.timingMs?.total ?? null;
121
+ metrics.secondClientMs = bStructured.timingMs?.total ?? null;
122
+
123
+ const pushDeadline = Date.now() + 4_000;
124
+ while (Date.now() < pushDeadline
125
+ && !a.logs.some((line) => line.includes('Automatic KLYPIX overlap alert'))) {
126
+ await wait(100);
127
+ }
128
+ checks.proactiveLogging = a.logs.some((line) =>
129
+ line.includes('Automatic KLYPIX overlap alert'));
130
+ metrics.firstClientLogCount = a.logs.length;
131
+ if (!checks.proactiveLogging && a.logs.length) metrics.firstClientLogs = a.logs.slice(-4);
132
+
133
+ const aCheckpoint = await a.client.callTool({
134
+ name: 'brain_sync',
135
+ arguments: {
136
+ phase: 'checkpoint',
137
+ intent: 'Validate automatic Codex Context Gateway coordination',
138
+ files: ['src/conformance-overlap.ts'],
139
+ },
140
+ });
141
+ const checkpointStructured = aCheckpoint.structuredContent || {};
142
+ checks.guaranteedInBandDelivery = checkpointStructured.messages?.some((message) =>
143
+ String(message.text).includes('Automatic KLYPIX overlap alert')) === true;
144
+
145
+ await Promise.all([
146
+ a.client.callTool({ name: 'brain_sync', arguments: { phase: 'complete' } }),
147
+ b.client.callTool({ name: 'brain_sync', arguments: { phase: 'complete' } }),
148
+ ]);
149
+ } catch (error) {
150
+ checks.runtime = false;
151
+ checks.error = error?.message || String(error);
152
+ } finally {
153
+ await Promise.allSettled([a?.client?.close(), b?.client?.close()]);
154
+ fs.rmSync(tempRoot, { recursive: true, force: true });
155
+ }
156
+
157
+ const required = [
158
+ 'brainSyncDiscoverable',
159
+ 'taskMemory',
160
+ 'truthfulTaskCount',
161
+ 'exactBlockingOverlap',
162
+ 'alertQueued',
163
+ 'proactiveLogging',
164
+ 'guaranteedInBandDelivery',
165
+ ];
166
+ const ok = required.every((name) => checks[name] === true);
167
+ const result = {
168
+ ok,
169
+ packageVersion: PKG_VERSION,
170
+ checks,
171
+ metrics,
172
+ contract: {
173
+ proactive: 'best-effort MCP logging notification',
174
+ guaranteed: 'same alert on the next KLYPIX action',
175
+ },
176
+ };
177
+
178
+ if (jsonMode) {
179
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
180
+ } else {
181
+ console.log(`KLYPIX MCP conformance v${PKG_VERSION}: ${ok ? 'PASS' : 'FAIL'}`);
182
+ for (const name of required) {
183
+ console.log(` ${checks[name] ? '✓' : '✗'} ${name}`);
184
+ }
185
+ if (checks.error) console.log(` error: ${checks.error}`);
186
+ console.log(` memory/coordination: ${metrics.firstClientMs ?? '?'}ms / ${metrics.secondClientMs ?? '?'}ms`);
187
+ console.log(' proactive notifications are best-effort; next-action delivery is guaranteed.');
188
+ }
189
+ process.exit(ok ? 0 : 1);
@@ -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,10 +273,14 @@ 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
- for (const [src, dst] of [['klypix-mcp.mjs', 'klypix-mcp-server.mjs'], ['klypix-a2a.mjs', 'klypix-a2a-server.mjs']]) {
279
+ for (const [src, dst] of [
280
+ ['klypix-mcp.mjs', 'klypix-mcp-server.mjs'],
281
+ ['klypix-a2a.mjs', 'klypix-a2a-server.mjs'],
282
+ ['klypix-conformance.mjs', 'klypix-conformance.mjs'],
283
+ ]) {
218
284
  const s = path.join(BIN, src); if (exists(s)) staged.push({ dst, content: flatten(fs.readFileSync(s, 'utf8')) });
219
285
  }
220
286
  for (const st of staged) fs.writeFileSync(path.join(BRAIN_DIR, st.dst + '.klypix-new'), st.content);
@@ -282,9 +348,10 @@ try {
282
348
  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
349
  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
350
  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.');
351
+ console.log(' Claude Code keeps its existing auto-brief/capture hooks; Codex gets the brain_sync Context Gateway (compact task memory + clean peers + proactive/guaranteed conflict alerts) with no hook trust prompt.');
352
+ console.log(' Enhanced Codex auto-context + pre-edit overlap guard: re-run with `--codex-hooks`, then approve/review KLYPIX once in a Codex surface that supports hook trust.');
286
353
  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
- console.log(' Verify anytime: `npx klypix-mcp doctor` (is the brain current + wired + in sync, who else is live).');
354
+ console.log(' Verify anytime: `npx klypix-mcp doctor`; prove two-client behavior with `npx klypix-mcp conformance`.');
288
355
  } catch (e) {
289
356
  if (gotLock) releaseLock();
290
357
  console.error(`✗ install failed: ${e?.message || 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`).');