klypix-mcp 1.66.1 → 1.67.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 +25 -12
- package/bin/klypix-conformance.mjs +24 -5
- package/bin/klypix-install.mjs +40 -50
- package/bin/klypix-worker.mjs +307 -39
- package/package.json +4 -2
- package/src/agent-presence.mjs +962 -89
- package/src/agent-rules.mjs +13 -5
- package/src/brain-doctor.mjs +91 -19
- package/src/codex-brain-hook.mjs +28 -12
- package/src/finding-routing.mjs +29 -19
- package/src/global-brain-hook.mjs +615 -115
- package/src/install-lock.mjs +95 -0
- package/src/install-version.mjs +76 -0
- package/src/klypix-core.mjs +8 -2
- package/src/mcp-presence.mjs +1367 -91
- package/src/presence-relay.mjs +106 -23
- package/src/release-evidence.mjs +663 -0
- package/src/remote-client.mjs +1017 -67
- package/src/result-reconcile.mjs +618 -70
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ npx klypix-mcp conformance
|
|
|
79
79
|
|
|
80
80
|
It runs in a temporary fixture and touches nothing else. It checks tool discovery, task memory,
|
|
81
81
|
truthful peer reporting, overlap surfacing, proactive logging, and in-band delivery of a peer note.
|
|
82
|
-
It verifies 15 required coordination behaviours — not the
|
|
82
|
+
It verifies 15 required coordination behaviours — not the 26 tools, and not the retrieval engine.
|
|
83
83
|
|
|
84
84
|
---
|
|
85
85
|
|
|
@@ -333,10 +333,13 @@ have to have declared their files for the overlap to be visible at all.
|
|
|
333
333
|
`brain_message` leaves one-time coordination notes for other sessions. A supported KLYPIX action
|
|
334
334
|
offers the note in model-visible context; the next independent supported action replays it and
|
|
335
335
|
records an acknowledgement. That acknowledgement proves only that a later action followed the
|
|
336
|
-
offer — never that a person read it or that an agent acted on it.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
336
|
+
offer — never that a person read it or that an agent acted on it. The note keeps replaying until the
|
|
337
|
+
receiving model calls `brain_message_receipt` with the exact message id and per-recipient offer
|
|
338
|
+
token; only that token-bound action records `consumed`. Pending, offered, and acknowledged notes
|
|
339
|
+
survive reconnects. Expiry or bounded-capacity eviction records a failed per-recipient receipt
|
|
340
|
+
instead of silently looking delivered. The send-time audience is fixed, unresolved targeted sends
|
|
341
|
+
fail closed, the core lane is machine-local, notes expire after 24 hours, and they are never written
|
|
342
|
+
into the brain.
|
|
340
343
|
|
|
341
344
|
Durable handoffs go in the brain itself — decisions, findings, open questions and skills captured
|
|
342
345
|
as cards, each stamped with the agent that wrote it.
|
|
@@ -345,14 +348,18 @@ as cards, each stamped with the agent that wrote it.
|
|
|
345
348
|
|
|
346
349
|
When a task publishes a quantified or otherwise machine-checkable claim, it can attach one or more
|
|
347
350
|
versioned result manifests to `brain_sync { phase: "complete" }`. Each manifest binds the claim to a
|
|
348
|
-
report hash, producer/run provenance, the exact
|
|
349
|
-
|
|
350
|
-
|
|
351
|
+
report hash, producer/run provenance, the exact declared task scope, material artifact hashes,
|
|
352
|
+
evaluation outputs, public metric wording, input/configuration fingerprints, and named metrics with
|
|
353
|
+
counts and tolerances. Matching peer evidence is recorded as corroboration; conflicting or
|
|
354
|
+
incomparable evidence returns `needs-reconciliation` and keeps the task scope active.
|
|
351
355
|
|
|
352
356
|
The gate fails closed. Once a task submits result evidence, it cannot bypass an invalid or
|
|
353
357
|
conflicting result by retrying completion without the manifest, and that obligation survives worker
|
|
354
358
|
restart, hibernation, and transparent hot-swap. A fresh `phase: "start"` is the explicit boundary for
|
|
355
359
|
a new task. The strict schema and reusable validator are exported as `klypix-mcp/result-reconcile`.
|
|
360
|
+
Schema-v2 receipts can be converted into commit-bound publication evidence and independently checked
|
|
361
|
+
with `klypix-mcp/release-evidence`; legacy schema-v1 results remain usable for coordination but cannot
|
|
362
|
+
authorize publication.
|
|
356
363
|
|
|
357
364
|
## Human control in Klypix
|
|
358
365
|
|
|
@@ -488,7 +495,7 @@ The MCP verbs below are what agents call. These are what **you** call:
|
|
|
488
495
|
|
|
489
496
|
---
|
|
490
497
|
|
|
491
|
-
## The
|
|
498
|
+
## The 26 verbs
|
|
492
499
|
|
|
493
500
|
| Tool | What it does |
|
|
494
501
|
|---|---|
|
|
@@ -500,12 +507,17 @@ The MCP verbs below are what agents call. These are what **you** call:
|
|
|
500
507
|
| `brain_lens` | Machine-readable freshness, provenance, activity, timeline, orrery and unresolved views |
|
|
501
508
|
| `brain_garden` | Maintenance pass — proposes first, and cannot apply without an approval code the human generates |
|
|
502
509
|
| `brain_doctor` | Self-diagnosis: version, core/enhanced host adapters, active sessions, tool count, projection drift |
|
|
503
|
-
| `brain_message` | Session-to-session coordination notes with per-recipient offer /
|
|
510
|
+
| `brain_message` | Session-to-session coordination notes with a fixed send-time audience and per-recipient pending / offer / acknowledgement / consumption / failure receipts (24h TTL, never written into the brain) |
|
|
511
|
+
| `brain_message_receipt` | Explicitly record model-side consumption using the exact message id and per-recipient offer token; acknowledgement alone never consumes a note |
|
|
504
512
|
| `brain_sync` | Context Gateway: task capsule, active-task peers, exact-file overlap, one-time alerts, timing, and optional result-manifest reconciliation |
|
|
505
513
|
| `brain_connect` | Find and draw related-but-unlinked cards |
|
|
506
514
|
| `project_map_context` | Read-only, bounded code-graph evidence beside correction-aware brain context, with exact-path review proposals; external artifacts (e.g. Graphify) are supported but never installed or run locally |
|
|
507
515
|
| `project_map_scan` | KLYPIX's own zero-install scanner: gitignore-aware file inventory + file-level import edges (relative, tsconfig-alias, and monorepo-workspace imports resolved) written to `klypix-map/graph.json` — which then serves `project_map_context` automatically |
|
|
508
516
|
| `project_map_drift` | Read-only drift report: brain cards whose referenced files are gone or moved (with rename candidates), plus a headline when the checkout itself is behind its origin default branch |
|
|
517
|
+
| `remote_status` | Inspect the local KLYPIX tray relay and its verified Remote capabilities |
|
|
518
|
+
| `remote_sessions` | List coding-agent sessions with exact provider, host-binding and capability receipts |
|
|
519
|
+
| `remote_actions` | List pending questions, approvals, failures, conflicts and reviews reported by supported providers |
|
|
520
|
+
| `remote_command` | Control one exact verified coding-agent session using a fresh capability receipt; unsupported operations fail closed |
|
|
509
521
|
| `canvas_view` | Returns the board as a structured render spec plus a text summary, and declares an MCP Apps (SEP-1865) UI resource |
|
|
510
522
|
| `read_canvas` | A canvas as markdown (cards, connection graph, `[[links]]`, `#tags`) |
|
|
511
523
|
| `search_canvases` | Search across canvases by name and content |
|
|
@@ -514,7 +526,7 @@ The MCP verbs below are what agents call. These are what **you** call:
|
|
|
514
526
|
| `add_to_canvas` | Append cards/connections (positions preserved) |
|
|
515
527
|
| `list_canvases` | List every `.klypix` in the vault |
|
|
516
528
|
|
|
517
|
-
Exactly
|
|
529
|
+
Exactly 26, machine-verifiable with `npx klypix-mcp doctor`.
|
|
518
530
|
|
|
519
531
|
> **`canvas_view`:** no MCP Apps host has been observed rendering the UI resource yet — there is no
|
|
520
532
|
> screenshot and no host-level test. Hosts without the extension get clean text, which is the path
|
|
@@ -714,7 +726,8 @@ Your `brain.klypix` is yours — it is a plain ZIP and stays readable with or wi
|
|
|
714
726
|
Issues and pull requests: [github.com/dahshanlabs/klypix-mcp](https://github.com/dahshanlabs/klypix-mcp).
|
|
715
727
|
Questions or feedback: [hello@klypix.com](mailto:hello@klypix.com).
|
|
716
728
|
|
|
717
|
-
The repository carries
|
|
729
|
+
The repository carries 68 test files: 62 listed directly in `scripts.test`, plus the
|
|
730
|
+
`pretest` workflow gate. Together they cover the presence
|
|
718
731
|
lane and its cross-machine relay, the Context Gateway, supervisor hot-swap, auto-update, retrieval
|
|
719
732
|
quality, decay, challenge, lenses, the format guard, the git tools (including a real `git merge`
|
|
720
733
|
through the merge driver), uninstall, and conformance. Run them with `npm test` from a clone — they
|
|
@@ -214,19 +214,21 @@ try {
|
|
|
214
214
|
text: 'verified note',
|
|
215
215
|
ts: now - 1_000,
|
|
216
216
|
candidateIds: ['finding-owner'],
|
|
217
|
-
deliveryVersion:
|
|
217
|
+
deliveryVersion: 3,
|
|
218
218
|
deliveries: [{
|
|
219
219
|
recipientId: 'finding-owner',
|
|
220
|
-
state: '
|
|
220
|
+
state: 'consumed',
|
|
221
221
|
attempts: 1,
|
|
222
|
+
offerToken: 'conformance-offer-token-000000',
|
|
222
223
|
offeredAt: now - 900,
|
|
223
224
|
acknowledgedAt: now - 500,
|
|
225
|
+
consumedAt: now - 250,
|
|
224
226
|
}],
|
|
225
227
|
seen: ['finding-owner'],
|
|
226
228
|
}],
|
|
227
229
|
sessions: lane, selfId: 'finding-sender', now,
|
|
228
230
|
});
|
|
229
|
-
checks.findingReceiptRendered = /
|
|
231
|
+
checks.findingReceiptRendered = /explicitly consumed by all 1 target peer\(s\) after model-context delivery \(not human-read\)/.test(renderReceiptSummary(receipt));
|
|
230
232
|
}
|
|
231
233
|
|
|
232
234
|
// ── Cross-PC presence: simulated two-machine scenario ─────────────────────
|
|
@@ -277,6 +279,23 @@ try {
|
|
|
277
279
|
});
|
|
278
280
|
checks.crossMachineConsentGate = framesSent === 0 && gated.reason === 'no-consent';
|
|
279
281
|
|
|
282
|
+
// Give A a current B recipient before the message is created. Delivery v3
|
|
283
|
+
// snapshots concrete sessions at send time; a message created while A is
|
|
284
|
+
// still unaware of B is correctly rejected as a zero-audience broadcast.
|
|
285
|
+
const bToA = [];
|
|
286
|
+
relayOutbound({
|
|
287
|
+
sessions: listActiveSessions({ brainPath: brainB, home: homeB, now }),
|
|
288
|
+
consent: GRANT, machineId: 'xpc-mach-b', hostLabel: 'MACHINE-B', root: repoB, now,
|
|
289
|
+
send: (frame) => bToA.push(frame),
|
|
290
|
+
});
|
|
291
|
+
const rowsOnA = bToA
|
|
292
|
+
.map((frame) => relayInbound(frame, { consent: GRANT, machineId: 'xpc-mach-a', now: now + 250 }))
|
|
293
|
+
.filter((inbound) => inbound?.type === 'presence')
|
|
294
|
+
.map((inbound) => inbound.row);
|
|
295
|
+
if (rowsOnA.length) {
|
|
296
|
+
upsertRemoteSessions({ brainPath: brainA, rows: rowsOnA, machineId: 'xpc-mach-a', home: homeA, now: now + 250 });
|
|
297
|
+
}
|
|
298
|
+
|
|
280
299
|
// Live channel: A's session and message reach B exactly once.
|
|
281
300
|
const wire = [];
|
|
282
301
|
relayOutbound({
|
|
@@ -358,7 +377,7 @@ const result = {
|
|
|
358
377
|
metrics,
|
|
359
378
|
contract: {
|
|
360
379
|
proactive: 'best-effort MCP logging notification',
|
|
361
|
-
inBand: 'a retained machine-local note is offered on a supported model-context KLYPIX action,
|
|
380
|
+
inBand: 'a retained machine-local note is offered on a supported model-context KLYPIX action, acknowledged only by a later independent action, and retired only by explicit token-bound consumption; expiry/overflow are failed receipts',
|
|
362
381
|
crossMachine: 'relay primitives require caller-confirmed durable insertion and a per-recipient-machine acknowledgement; app bridge wiring is a separate conformance boundary',
|
|
363
382
|
},
|
|
364
383
|
};
|
|
@@ -372,6 +391,6 @@ if (jsonMode) {
|
|
|
372
391
|
}
|
|
373
392
|
if (checks.error) console.log(` error: ${checks.error}`);
|
|
374
393
|
console.log(` memory/coordination: ${metrics.firstClientMs ?? '?'}ms / ${metrics.secondClientMs ?? '?'}ms`);
|
|
375
|
-
console.log(' proactive notifications are best-effort; retained notes use offer → later-action acknowledgement, with explicit failure receipts.');
|
|
394
|
+
console.log(' proactive notifications are best-effort; retained notes use offer → later-action acknowledgement → explicit token-bound consumption, with explicit failure receipts.');
|
|
376
395
|
}
|
|
377
396
|
process.exit(ok ? 0 : 1);
|
package/bin/klypix-install.mjs
CHANGED
|
@@ -29,6 +29,8 @@ import {
|
|
|
29
29
|
codexPresenceHookStatus,
|
|
30
30
|
mergeCodexPresenceHooks,
|
|
31
31
|
} from '../src/codex-hooks.mjs';
|
|
32
|
+
import { brainInstallDecision } from '../src/install-version.mjs';
|
|
33
|
+
import { acquireInstallLockSync, releaseInstallLockSync } from '../src/install-lock.mjs';
|
|
32
34
|
|
|
33
35
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
34
36
|
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
@@ -147,45 +149,10 @@ function reportCodex(result) {
|
|
|
147
149
|
console.error(' Claude Code installation is intact. Fix the Codex warning, then re-run this command.');
|
|
148
150
|
}
|
|
149
151
|
|
|
150
|
-
// ── Never-downgrade gate ─────────────────────────────────────────────────────
|
|
151
|
-
// The brain version is a UNIFIED namespace = the klypix-mcp version, stamped by BOTH
|
|
152
|
-
// the npm install (here) and the desktop bundle, so the two channels compare cleanly.
|
|
153
|
-
// A dev deploy ({dev:true}) is authoritative; a strictly-newer install is not
|
|
154
|
-
// downgraded. --force overrides both.
|
|
155
|
-
const cmpSemver = (a, b) => { const pa = String(a || '').split('.').map(n => parseInt(n, 10) || 0), pb = String(b || '').split('.').map(n => parseInt(n, 10) || 0); for (let i = 0; i < 3; i++) { if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0); } return 0; };
|
|
156
|
-
const cur = (() => { try { return JSON.parse(fs.readFileSync(path.join(BRAIN_DIR, '.brain-version.json'), 'utf8')); } catch { return null; } })();
|
|
157
|
-
if (!FORCE && cur) {
|
|
158
|
-
if (cur.dev === true) {
|
|
159
|
-
console.log(`• A dev deploy owns ${BRAIN_DIR} (dev:true) — leaving it untouched. Re-run with --force to override.`);
|
|
160
|
-
if (!RUNTIME_ONLY) reportCodex(wireCodex());
|
|
161
|
-
process.exit(0);
|
|
162
|
-
}
|
|
163
|
-
if (cur.brainVersion && cmpSemver(cur.brainVersion, VERSION) > 0) {
|
|
164
|
-
console.log(`• Installed brain v${cur.brainVersion} is newer than this package v${VERSION} — not downgrading. Re-run with --force to override.`);
|
|
165
|
-
if (!RUNTIME_ONLY) reportCodex(wireCodex());
|
|
166
|
-
process.exit(0);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
152
|
// ── Install lock (auto-propagation, part D — concurrency) ─────────────────────
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
const LOCK = path.join(BRAIN_DIR, '.install.lock');
|
|
175
|
-
const sleepSync = (ms) => { try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); } catch { /* */ } };
|
|
176
|
-
function acquireLock() {
|
|
177
|
-
try { fs.mkdirSync(BRAIN_DIR, { recursive: true }); } catch { /* */ }
|
|
178
|
-
for (let i = 0; i < 120; i++) {
|
|
179
|
-
try { const fd = fs.openSync(LOCK, 'wx'); fs.writeSync(fd, String(process.pid)); fs.closeSync(fd); return true; }
|
|
180
|
-
catch (e) {
|
|
181
|
-
if (e && e.code !== 'EEXIST') return false;
|
|
182
|
-
try { if (Date.now() - fs.statSync(LOCK).mtimeMs > 60000) { fs.unlinkSync(LOCK); continue; } } catch { /* raced on the stale file — retry */ }
|
|
183
|
-
sleepSync(50);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return false;
|
|
187
|
-
}
|
|
188
|
-
const releaseLock = () => { try { fs.unlinkSync(LOCK); } catch { /* */ } };
|
|
153
|
+
// npm and desktop use this exact token-owned lock. The version decision is made
|
|
154
|
+
// only AFTER acquisition, so an older queued installer re-reads and preserves a
|
|
155
|
+
// newer runtime that committed while it waited.
|
|
189
156
|
|
|
190
157
|
// Migrate THIS project's .mcp.json klypix-canvas entry off `npx` onto the local
|
|
191
158
|
// bundle now that it's installed — the desync fix for EXISTING configs (the self-
|
|
@@ -230,12 +197,33 @@ const flatten = (code) => code
|
|
|
230
197
|
.replace(/klypix-worker\.mjs/g, 'klypix-mcp-worker.mjs')
|
|
231
198
|
.replace(/const PKG_VERSION = \(\(\) => \{[\s\S]*?\}\)\(\);/, `const PKG_VERSION = '${VERSION}'; // baked at install (flat layout has no package.json)`);
|
|
232
199
|
|
|
233
|
-
const
|
|
234
|
-
if (!
|
|
235
|
-
console.error(`✗ another KLYPIX install still owns ${
|
|
200
|
+
const installLock = acquireInstallLockSync(BRAIN_DIR);
|
|
201
|
+
if (!installLock) {
|
|
202
|
+
console.error(`✗ another KLYPIX install still owns ${path.join(BRAIN_DIR, '.install.lock')}; no files were changed`);
|
|
236
203
|
process.exit(1);
|
|
237
204
|
}
|
|
238
205
|
try {
|
|
206
|
+
// Never-downgrade gate: Brain Core semver is the unified payload identity.
|
|
207
|
+
// Read both receipts under the shared lock and conservatively keep the
|
|
208
|
+
// highest valid committed/recorded version. appVersion is provenance only.
|
|
209
|
+
const stamp = (() => { try { return JSON.parse(fs.readFileSync(path.join(BRAIN_DIR, '.brain-version.json'), 'utf8')); } catch { return null; } })();
|
|
210
|
+
const runtimeReceipt = (() => { try { return JSON.parse(fs.readFileSync(path.join(BRAIN_DIR, '.mcp-runtime.json'), 'utf8')); } catch { return null; } })();
|
|
211
|
+
const decision = brainInstallDecision({ candidateVersion: VERSION, stamp, runtime: runtimeReceipt, force: FORCE });
|
|
212
|
+
if (decision.action === 'refuse') {
|
|
213
|
+
releaseInstallLockSync(installLock);
|
|
214
|
+
console.error(`✗ package Brain Core version ${JSON.stringify(VERSION)} is invalid; refusing to install unidentified runtime files`);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
if (decision.action === 'preserve') {
|
|
218
|
+
releaseInstallLockSync(installLock);
|
|
219
|
+
if (decision.reason === 'dev-owned') {
|
|
220
|
+
console.log(`• A dev deploy owns ${BRAIN_DIR} (dev:true) — leaving it untouched. Re-run with --force to override.`);
|
|
221
|
+
} else {
|
|
222
|
+
console.log(`• Installed brain v${decision.installedVersion} is newer than this package v${VERSION} — not downgrading. Re-run with --force to override.`);
|
|
223
|
+
}
|
|
224
|
+
if (!RUNTIME_ONLY) reportCodex(wireCodex());
|
|
225
|
+
process.exit(0);
|
|
226
|
+
}
|
|
239
227
|
fs.mkdirSync(BRAIN_DIR, { recursive: true });
|
|
240
228
|
// 1) runtime dependency CLOSURE (jszip+fractional-indexing for the hook/engine,
|
|
241
229
|
// @modelcontextprotocol/sdk+zod for the local MCP server). Resolve each via
|
|
@@ -280,7 +268,7 @@ try {
|
|
|
280
268
|
if (!exists(path.join(destMods, name))) { copyDir(dir, path.join(destMods, name)); deps++; }
|
|
281
269
|
try { const pj = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')); for (const d of Object.keys(pj?.dependencies || {})) queue.push({ name: d, fromDir: dir }); } catch { /* no readable package.json */ }
|
|
282
270
|
}
|
|
283
|
-
if (missing.length) {
|
|
271
|
+
if (missing.length) { releaseInstallLockSync(installLock); console.error(`✗ could not resolve required dep(s): ${missing.join(', ')} — aborting (the brain hook needs them).`); process.exit(1); }
|
|
284
272
|
|
|
285
273
|
// 2) STAGE the scripts (engine + hook + flattened servers), write each to
|
|
286
274
|
// `<name>.klypix-new`, back up the current copy to .prev/, then atomically
|
|
@@ -337,7 +325,7 @@ try {
|
|
|
337
325
|
rawSettings = fs.readFileSync(SETTINGS, 'utf8');
|
|
338
326
|
if (rawSettings.trim()) {
|
|
339
327
|
try { settings = JSON.parse(rawSettings); }
|
|
340
|
-
catch (e) {
|
|
328
|
+
catch (e) { releaseInstallLockSync(installLock); console.error(`✗ ${SETTINGS} is invalid JSON (${e.message}). Fix it and re-run — refusing to overwrite a broken config.`); process.exit(1); }
|
|
341
329
|
}
|
|
342
330
|
}
|
|
343
331
|
if (!RUNTIME_ONLY) {
|
|
@@ -351,12 +339,11 @@ try {
|
|
|
351
339
|
fs.renameSync(tmp, SETTINGS);
|
|
352
340
|
}
|
|
353
341
|
|
|
354
|
-
// 6)
|
|
342
|
+
// 6) Commit the runtime pointer and version receipt atomically while the
|
|
343
|
+
// shared cross-channel lock is still held. The runtime receipt goes first;
|
|
344
|
+
// a crash between receipts remains recoverable because the next installer
|
|
345
|
+
// compares both and keeps the highest valid Brain Core version.
|
|
355
346
|
const installedAt = new Date().toISOString();
|
|
356
|
-
fs.writeFileSync(path.join(BRAIN_DIR, '.brain-version.json'), JSON.stringify({ brainVersion: VERSION, via: 'npm', dirty: false, installedAt }, null, 2));
|
|
357
|
-
// Commit the runtime pointer LAST. A running supervisor watches only this
|
|
358
|
-
// atomic file, validates every staged hash, and keeps the old worker if the
|
|
359
|
-
// candidate is incomplete or incompatible.
|
|
360
347
|
const runtime = {
|
|
361
348
|
protocol: 1,
|
|
362
349
|
version: VERSION,
|
|
@@ -368,6 +355,9 @@ try {
|
|
|
368
355
|
const runtimePath = path.join(BRAIN_DIR, '.mcp-runtime.json');
|
|
369
356
|
fs.writeFileSync(runtimePath + '.klypix-new', JSON.stringify(runtime, null, 2) + '\n', 'utf8');
|
|
370
357
|
fs.renameSync(runtimePath + '.klypix-new', runtimePath);
|
|
358
|
+
const versionPath = path.join(BRAIN_DIR, '.brain-version.json');
|
|
359
|
+
fs.writeFileSync(versionPath + '.klypix-new', JSON.stringify({ brainVersion: VERSION, via: 'npm', dirty: false, installedAt }, null, 2), 'utf8');
|
|
360
|
+
fs.renameSync(versionPath + '.klypix-new', versionPath);
|
|
371
361
|
|
|
372
362
|
// 7) migrate THIS project's .mcp.json off npx onto the now-installed local bundle
|
|
373
363
|
// (heals an existing stale config so the next MCP server spawn runs current).
|
|
@@ -383,7 +373,7 @@ try {
|
|
|
383
373
|
const wiredFor = (evt) => Array.isArray(verify?.hooks?.[evt]) && verify.hooks[evt].some(g => Array.isArray(g?.hooks) && g.hooks.some(h => typeof h?.command === 'string' && h.command.includes(HOOK_MARK)));
|
|
384
374
|
const notWired = ['SessionStart', 'UserPromptSubmit', 'Stop', 'PostToolUse'].filter(e => !wiredFor(e));
|
|
385
375
|
|
|
386
|
-
|
|
376
|
+
releaseInstallLockSync(installLock);
|
|
387
377
|
// Users who already enabled the optional local semantic runtime should not
|
|
388
378
|
// pay a multi-minute first question after a model/cache contract upgrade.
|
|
389
379
|
// Migrate registered brains once in a detached process after the atomic
|
|
@@ -419,7 +409,7 @@ try {
|
|
|
419
409
|
console.log(' Compatible brain-core updates hot-swap behind the same MCP connection. Only the one-time legacy→supervisor migration, a supervisor change, or an intentionally breaking tool/protocol change needs reconnect.');
|
|
420
410
|
console.log(' Verify anytime: `npx klypix-mcp doctor`; prove two-client behavior with `npx klypix-mcp conformance`.');
|
|
421
411
|
} catch (e) {
|
|
422
|
-
|
|
412
|
+
releaseInstallLockSync(installLock);
|
|
423
413
|
console.error(`✗ install failed: ${e?.message || e}`);
|
|
424
414
|
process.exit(1);
|
|
425
415
|
}
|