klypix-mcp 1.21.2 → 1.22.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 +17 -0
- package/bin/klypix-install.mjs +2 -1
- package/bin/klypix-mcp.mjs +1 -1
- package/package.json +2 -2
- package/src/klypix-core.mjs +8 -5
- package/src/klypix-format.mjs +39 -3
package/README.md
CHANGED
|
@@ -75,6 +75,23 @@ or the Claude Code project-brain hook. (`search_all_brains` is also hook-fed —
|
|
|
75
75
|
reads the cross-project registry the hook writes, so it stays empty until a hook
|
|
76
76
|
has registered at least one brain.)
|
|
77
77
|
|
|
78
|
+
### Updates — the propagation contract
|
|
79
|
+
|
|
80
|
+
`npx klypix-mcp install` lays the whole brain (hooks + engine + local MCP server)
|
|
81
|
+
into `~/.claude/project-brain`, and the emitted MCP config runs the server **from
|
|
82
|
+
that installed bundle** (no npx cache to go stale). From then on updates are
|
|
83
|
+
automatic: at session start the hook checks npm (≤ once/24h, fail-open, disable
|
|
84
|
+
with `KLYPIX_AUTO_UPDATE=0`) and self-installs a newer release, so a publish
|
|
85
|
+
reaches every machine by its **next session**.
|
|
86
|
+
|
|
87
|
+
One honest caveat: a running stdio MCP server can't hot-swap, and **resuming a
|
|
88
|
+
session (or opening a new chat in the same app) does not respawn it** — only a
|
|
89
|
+
full app quit + reopen (or `/mcp` reconnect after the old process exits) starts
|
|
90
|
+
the new binary. `brain_doctor` tells you when that's needed: its RUNNING line
|
|
91
|
+
compares the *live server's* self-reported version against the installed bundle
|
|
92
|
+
and npm, and reads `DRIFTED → /mcp reconnect` instead of pretending a stale
|
|
93
|
+
server is current.
|
|
94
|
+
|
|
78
95
|
## Also speaks A2A (Agent-to-Agent)
|
|
79
96
|
|
|
80
97
|
The same engine is exposed as an **A2A agent** so other agents and orchestrators
|
package/bin/klypix-install.mjs
CHANGED
|
@@ -224,7 +224,8 @@ try {
|
|
|
224
224
|
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}.`);
|
|
225
225
|
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.`);
|
|
226
226
|
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.`);
|
|
227
|
-
console.log(' Every project with a ./brain.klypix now auto-reads its brief + captures decisions.
|
|
227
|
+
console.log(' Every project with a ./brain.klypix now auto-reads its brief + captures decisions.');
|
|
228
|
+
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.');
|
|
228
229
|
console.log(' Verify anytime: `npx klypix-mcp doctor` (is the brain current + wired + in sync, who else is live).');
|
|
229
230
|
} catch (e) {
|
|
230
231
|
if (gotLock) releaseLock();
|
package/bin/klypix-mcp.mjs
CHANGED
|
@@ -154,7 +154,7 @@ server.registerTool('brain_connect', {
|
|
|
154
154
|
|
|
155
155
|
server.registerTool('brain_reconcile', {
|
|
156
156
|
title: 'Reconcile the brain — contradictions between cards + unrecorded migrations',
|
|
157
|
-
description: 'Truth maintenance. (1) CONTRADICTIONS: finds same-subject live card pairs where one carries an explicit correction cue (uppercase "CORRECTION", "was WRONG", "OBSOLETE" — that side is the presumed truth) or the two use opposite polarity words (deferred↔wired, broken↔fixed, dead↔live), i.e. stale facts whose correction never got linked — candidates only, YOU confirm each: retire the stale card via brain_note ✓. Dismiss a FALSE positive (either kind) by connecting the two ids with brain_connect pairs + relationship:"not_contradiction" — persisted, so it never resurfaces. (2) MIGRATIONS: lists committed migration files (Supabase / Rails / Prisma / Knex / generic) that NO brain card references, so an applied-but-unnarrated rollout can be recorded. (3) LEGACY: pre-v1.15 raw-bash ship cards to tidy. Reads ONLY the filesystem — never the database, never the network — and changes nothing. Run it periodically, or when recall surfaces something you believe is stale.',
|
|
157
|
+
description: 'Truth maintenance. (1) CONTRADICTIONS: finds same-subject live card pairs where one carries an explicit correction cue (uppercase "CORRECTION", "was WRONG", "OBSOLETE" — that side is the presumed truth, UNLESS the cue predates its counterpart: then the pair is marked "presumed superseded" and the newer card is presumed current — verify before retiring) or the two use opposite polarity words (deferred↔wired, broken↔fixed, dead↔live), i.e. stale facts whose correction never got linked — candidates only, YOU confirm each: retire the stale card via brain_note ✓. Dismiss a FALSE positive (either kind) by connecting the two ids with brain_connect pairs + relationship:"not_contradiction" — persisted, so it never resurfaces (and its cue stops overlaying recall/ask for that pair). (2) MIGRATIONS: lists committed migration files (Supabase / Rails / Prisma / Knex / generic) that NO brain card references, so an applied-but-unnarrated rollout can be recorded. (3) LEGACY: pre-v1.15 raw-bash ship cards to tidy. Reads ONLY the filesystem — never the database, never the network — and changes nothing. Run it periodically, or when recall surfaces something you believe is stale.',
|
|
158
158
|
inputSchema: {
|
|
159
159
|
canvas: z.string().optional().describe('Brain canvas filename/path. Defaults to the project brain ("brain").'),
|
|
160
160
|
root: z.string().optional().describe("Project root holding the migrations dir (default: the brain file's folder)."),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "klypix-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"description": "An open, local-first, agent-neutral canvas file your AI reads and writes over MCP — works with Claude, Cursor, Cline, any model.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"node": ">=18"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
|
-
"test": "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"
|
|
56
|
+
"test": "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"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
package/src/klypix-core.mjs
CHANGED
|
@@ -452,10 +452,13 @@ export async function opBrainReconcile({ vault, canvas, root, mode = 'all' }) {
|
|
|
452
452
|
|
|
453
453
|
// (1) CONTRADICTIONS — the brain reconciled against ITSELF. Same-subject live
|
|
454
454
|
// pairs where one side carries an explicit correction cue (that side is the
|
|
455
|
-
// presumed truth
|
|
456
|
-
//
|
|
457
|
-
//
|
|
458
|
-
//
|
|
455
|
+
// presumed truth — UNLESS the cue predates its counterpart, then the newer
|
|
456
|
+
// card is presumed to have superseded the correction and the pair is marked
|
|
457
|
+
// "presumed superseded") or the two use opposite polarity words
|
|
458
|
+
// (deferred↔wired, broken↔fixed …). Candidates only — nothing is changed
|
|
459
|
+
// here; the agent/human confirms each. This is the retroactive cleaner for
|
|
460
|
+
// stale/correction pairs that slipped past capture (cross-area + reworded →
|
|
461
|
+
// no supersede possible).
|
|
459
462
|
if (mode === 'all' || mode === 'contradictions') {
|
|
460
463
|
const pairs = detectContradictions(struct);
|
|
461
464
|
if (pairs.length) {
|
|
@@ -464,7 +467,7 @@ export async function opBrainReconcile({ vault, canvas, root, mode = 'all' }) {
|
|
|
464
467
|
`${i + 1}. ${p.why} · overlap ${p.overlap}\n`
|
|
465
468
|
+ ` · likely STALE [${p.stale.area || '?'}] (id ${p.stale.id}) ${flat(p.stale.text).slice(0, 180)}\n`
|
|
466
469
|
+ ` · likely CURRENT [${p.fresh.area || '?'}] (id ${p.fresh.id}) ${flat(p.fresh.text).slice(0, 180)}`);
|
|
467
|
-
sections.push(`# ⚔️ ${pairs.length} contradiction candidate(s) — confirm, then reconcile\n_Candidates only — nothing was changed. For each REAL contradiction: retire the stale card with \`brain_note\` marker \`✓\` (text = what it resolved to), or record a correction-cue decision ("CORRECTION: …", uppercase) — capture auto-supersedes it across areas. Dismissing a FALSE positive (either kind — polarity OR correction-cue): \`brain_connect\` with \`pairs:[{fromId, toId}]\` and \`relationship:"not_contradiction"\` using the ids above — the dismissal is persisted, so that pair never resurfaces here
|
|
470
|
+
sections.push(`# ⚔️ ${pairs.length} contradiction candidate(s) — confirm, then reconcile\n_Candidates only — nothing was changed. For each REAL contradiction: retire the stale card with \`brain_note\` marker \`✓\` (text = what it resolved to), or record a correction-cue decision ("CORRECTION: …", uppercase) — capture auto-supersedes it across areas. A pair marked "presumed superseded" is INVERTED — its correction card PREDATES its counterpart (e.g. the old fact was re-captured after the correction): verify which side is real before retiring anything; if the correction still holds, re-assert it (a \`~\` update or a fresh CORRECTION card) instead of retiring it. Dismissing a FALSE positive (either kind — polarity OR correction-cue): \`brain_connect\` with \`pairs:[{fromId, toId}]\` and \`relationship:"not_contradiction"\` using the ids above — the dismissal is persisted, so that pair never resurfaces here (and its cue never re-attaches as a recall/ask overlay)._\n\n${lines.join('\n')}`);
|
|
468
471
|
} else if (mode === 'contradictions') {
|
|
469
472
|
sections.push('✓ No contradiction candidates — no live card pair shows a correction cue or a polarity flip over the same subject.');
|
|
470
473
|
}
|
package/src/klypix-format.mjs
CHANGED
|
@@ -1378,8 +1378,22 @@ export function detectContradictions(struct, { minOverlap = 0.45, topK = 12 } =
|
|
|
1378
1378
|
if (!subjectHit) continue;
|
|
1379
1379
|
let why = null, staleC = null, freshC = null;
|
|
1380
1380
|
if (aCue !== bCue) {
|
|
1381
|
-
|
|
1382
|
-
|
|
1381
|
+
const cueC = aCue ? a : b, otherC = aCue ? b : a;
|
|
1382
|
+
// Recency: the cue side is the presumed truth ONLY for cards that
|
|
1383
|
+
// existed when it was written. Against a STRICTLY NEWER card the
|
|
1384
|
+
// presumption inverts — the newer card superseded the correction
|
|
1385
|
+
// (field 2026-07-12: a 07-11 audit correction was flagged CURRENT
|
|
1386
|
+
// over the 07-12 R1 cards that post-dated it).
|
|
1387
|
+
const inverted = (cueC.createdAt || 0) && (otherC.createdAt || 0) && cueC.createdAt < otherC.createdAt;
|
|
1388
|
+
why = inverted ? 'correction-cue (cue predates its counterpart — presumed superseded)' : 'correction-cue';
|
|
1389
|
+
freshC = inverted ? otherC : cueC; staleC = inverted ? cueC : otherC;
|
|
1390
|
+
// Skills are standing reference (corrected in place with ~, never
|
|
1391
|
+
// retirable by ✓/supersede) — presenting one as "likely STALE"
|
|
1392
|
+
// invites a retire the engine would refuse; skip the pair. Checked
|
|
1393
|
+
// on the RESOLVED stale side, so it covers both directions — incl.
|
|
1394
|
+
// an inverted pair whose cue card is itself a 🛠 skill (a skill
|
|
1395
|
+
// documenting the CORRECTION convention carries the cue token).
|
|
1396
|
+
if (/🛠/.test(staleC.text || '')) continue;
|
|
1383
1397
|
} else if (!aCue && !linked.has(a.id + '|' + b.id)) {
|
|
1384
1398
|
const la = lower.get(a.id), lb = lower.get(b.id);
|
|
1385
1399
|
for (const { x, y, rx, ry } of POLARITY_RES) {
|
|
@@ -1598,7 +1612,13 @@ const cueMatch = (a, b, bar) => {
|
|
|
1598
1612
|
// • edge — an outgoing "superseded by"/"closed by" arrow (drawn by capture or
|
|
1599
1613
|
// a confirmed reconcile) whose successor still has text;
|
|
1600
1614
|
// • cue — a LIVE correction-cue card that lexically overlaps it ≥ `at`, ANY
|
|
1601
|
-
// area (the un-edged pair the capture-time supersede missed)
|
|
1615
|
+
// area (the un-edged pair the capture-time supersede missed) — EXCEPT:
|
|
1616
|
+
// - a cue STRICTLY OLDER than the card (recency guard — an old correction
|
|
1617
|
+
// must never be served as the current truth for a card that post-dated
|
|
1618
|
+
// it; field 2026-07-12: 17 of 32 live overlays pointed backward),
|
|
1619
|
+
// - a 🛠 skill card as the overlay TARGET (standing reference, corrected
|
|
1620
|
+
// in place with ~ — never labeled stale by a lexical match),
|
|
1621
|
+
// - a pair dismissed with a not_contradiction edge (same human verdict).
|
|
1602
1622
|
// The caller injects the corrector FIRST (labeled) and reduces the stale hit to
|
|
1603
1623
|
// a headline — the stale text never stands alone. Pure + cheap: correction-cue
|
|
1604
1624
|
// cards are rare and the hit list is ≤topK.
|
|
@@ -1608,8 +1628,14 @@ export function correctionOverlaysFor(struct, cards, { at = CORRECTION_SUPERSEDE
|
|
|
1608
1628
|
const byId = new Map(struct.cards.map(c => [c.id, c]));
|
|
1609
1629
|
const isArchived = (c) => /^archive$/i.test(c.area || '');
|
|
1610
1630
|
const successorOf = new Map();
|
|
1631
|
+
// A confirmed not_contradiction dismissal is the same human verdict for the
|
|
1632
|
+
// overlay: that cue does not correct that card — the CUE path never
|
|
1633
|
+
// re-attaches the pair (an explicit superseded-by edge still wins: both are
|
|
1634
|
+
// deliberate verdicts and the edge is the stronger one).
|
|
1635
|
+
const dismissed = new Set();
|
|
1611
1636
|
for (const cn of struct.connections || []) {
|
|
1612
1637
|
if (cn.label === 'superseded by' || cn.label === 'closed by') successorOf.set(cn.fromId, cn.toId);
|
|
1638
|
+
if (cn.relationship === 'not_contradiction' || cn.label === 'not a contradiction') { dismissed.add(cn.fromId + '|' + cn.toId); dismissed.add(cn.toId + '|' + cn.fromId); }
|
|
1613
1639
|
}
|
|
1614
1640
|
const cues = struct.cards.filter(c => c.type !== 'container' && !isArchived(c) && (c.text || '').trim() && hasCorrectionCue(c.text));
|
|
1615
1641
|
for (const card of cards) {
|
|
@@ -1617,10 +1643,20 @@ export function correctionOverlaysFor(struct, cards, { at = CORRECTION_SUPERSEDE
|
|
|
1617
1643
|
const succ = successorOf.has(card.id) ? byId.get(successorOf.get(card.id)) : null;
|
|
1618
1644
|
if (succ && (succ.text || '').trim()) { out.set(card.id, { kind: 'edge', by: succ }); continue; }
|
|
1619
1645
|
if (hasCorrectionCue(card.text)) continue; // the hit IS a correction — nothing to overlay
|
|
1646
|
+
if (/🛠/.test(card.text || '')) continue; // skills are standing reference, corrected in place with ~ — never labeled STALE by a lexical cue (mirror the supersede/resolve guards)
|
|
1620
1647
|
const cTok = tokenSet(card.text);
|
|
1621
1648
|
let best = null, bestS = 0;
|
|
1622
1649
|
for (const cue of cues) {
|
|
1623
1650
|
if (cue.id === card.id) continue;
|
|
1651
|
+
if (dismissed.has(cue.id + '|' + card.id)) continue;
|
|
1652
|
+
// Recency guard: a correction can only correct facts that existed when
|
|
1653
|
+
// it was written — a cue STRICTLY older than the card must never be
|
|
1654
|
+
// served as its "current truth" (field 2026-07-12: a 07-11 audit
|
|
1655
|
+
// correction overlaid the 07-12 cards that superseded it, and a June
|
|
1656
|
+
// "deploy did not stick" correction poisoned July version answers,
|
|
1657
|
+
// steering synthesis BACKWARD). Equal/unknown stamps keep the overlay
|
|
1658
|
+
// (same-batch captures share a timestamp; missing dates can't be judged).
|
|
1659
|
+
if ((cue.createdAt || 0) && (card.createdAt || 0) && cue.createdAt < card.createdAt) continue;
|
|
1624
1660
|
const s = cueMatch(cTok, stripCueMeta(tokenSet(cue.text)), at);
|
|
1625
1661
|
if (s > bestS) { bestS = s; best = cue; }
|
|
1626
1662
|
}
|