klypix-mcp 1.69.0 → 1.71.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 +48 -20
- package/bin/klypix-install.mjs +25 -0
- package/bin/klypix-mcp.mjs +4 -2
- package/bin/klypix-orphans.mjs +168 -0
- package/bin/klypix-worker.mjs +11 -1
- package/package.json +4 -3
- package/src/agent-presence.mjs +305 -1
- package/src/agent-rules.mjs +67 -17
- package/src/brain-doctor.mjs +27 -6
- package/src/codex-brain-hook.mjs +22 -7
- package/src/editor-detect.mjs +117 -0
- package/src/global-brain-hook.mjs +95 -7
- package/src/klypix-format.mjs +155 -1
- package/src/mcp-presence.mjs +330 -18
- package/src/mcp-verify.mjs +136 -0
- package/src/presence-relay.mjs +13 -0
- package/src/repo-state.mjs +33 -1
- package/src/setup.mjs +286 -0
package/README.md
CHANGED
|
@@ -85,28 +85,55 @@ It verifies 15 required coordination behaviours — not the 26 tools, and not th
|
|
|
85
85
|
|
|
86
86
|
## Quick start
|
|
87
87
|
|
|
88
|
-
**
|
|
88
|
+
Run this **inside your project**:
|
|
89
89
|
|
|
90
90
|
```bash
|
|
91
91
|
npx klypix-mcp install
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
One command, every editor. It finds the project root (walking up, so running it from `src/` is
|
|
95
|
+
fine), gives the project a brain if it doesn't have one, wires the agent tools you actually have
|
|
96
|
+
installed, registers the lossless `.klypix` merge driver if it's a git repo, and then **proves the
|
|
97
|
+
result** before it exits:
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
project E:\work\api (git repository root)
|
|
101
|
+
brain created brain.klypix — a starter brain, ready for its first decision
|
|
102
|
+
editors Claude Code · Cursor · Codex · Gemini CLI · Antigravity · VS Code
|
|
103
|
+
wired 9 file(s) · 9 updated (skipped 5 for tools you don't have)
|
|
104
|
+
git lossless .klypix merge driver registered
|
|
105
|
+
verified ✓ 26 tools reachable via .mcp.json (892ms)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
That last line is the point. MCP config fails **silently** — a wrong entry means the server never
|
|
109
|
+
starts, the agent quietly loses every brain verb, and nothing reports an error. So `install` opens
|
|
110
|
+
a real stdio handshake against the config it just wrote and counts the tools that answered. A
|
|
111
|
+
broken entry dies in ~100ms with `Connection closed` and is reported, not shipped.
|
|
112
|
+
|
|
113
|
+
What goes where:
|
|
114
|
+
|
|
115
|
+
- **Machine-global, once** — the engine + runtime in `~/.claude/project-brain`, Claude Code's four
|
|
116
|
+
lifecycle hooks in `~/.claude/settings.json`, and the `~/.codex/AGENTS.md` guidance block. Claude
|
|
117
|
+
Code is therefore covered in every project on that machine that has a `./brain.klypix`.
|
|
118
|
+
- **Per project** — MCP config and rules for Cursor, Codex, Cline, Windsurf, Copilot, Gemini CLI /
|
|
119
|
+
Antigravity and Aider. Run `install` once inside each project.
|
|
97
120
|
|
|
98
|
-
|
|
121
|
+
Three things it deliberately will **not** do:
|
|
99
122
|
|
|
100
|
-
- **
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
123
|
+
- **Write for editors you don't have.** Config is projected only for hosts detected on this
|
|
124
|
+
machine — a two-person team using one editor no longer commits rules for six they never opened.
|
|
125
|
+
A file your project *already* carries stays maintained regardless, so you can't silently stop
|
|
126
|
+
updating your team's committed configs.
|
|
127
|
+
- **Wire a directory that isn't a project.** It refuses your home folder, a drive root, and
|
|
128
|
+
anything with no brain, no git repo and no project manifest. A mistyped command can't seed a
|
|
129
|
+
brain into `C:\Users\you`.
|
|
130
|
+
- **Replace a project-owned server.** A repo-relative launch like
|
|
131
|
+
`node scripts/klypix-mcp-server.mjs` is deliberate — it resolves offline and rides a bundle the
|
|
132
|
+
repo version-gates — so it's left byte-identical and reported. An explicit `link` still rewrites
|
|
133
|
+
everything: an action you didn't ask for stays more conservative than one you did.
|
|
108
134
|
|
|
109
|
-
|
|
135
|
+
Opt out with `--no-project` (CI images, scripted provisioning). `--json` emits the report as
|
|
136
|
+
structured data; `--verify-all` handshakes every written config instead of one.
|
|
110
137
|
|
|
111
138
|
Optional, opt-in, and approved inside Codex itself:
|
|
112
139
|
|
|
@@ -119,15 +146,16 @@ file-overlap warning. Codex owns the trust decision and will ask you to review t
|
|
|
119
146
|
`brain_doctor` reports this layer separately as off, execution-unverified, or active. Even with it
|
|
120
147
|
on, **Codex never captures decisions automatically** — the Codex hook never writes the brain.
|
|
121
148
|
|
|
122
|
-
**
|
|
149
|
+
**Re-project everything explicitly:**
|
|
123
150
|
|
|
124
151
|
```bash
|
|
125
152
|
npx klypix-mcp link
|
|
126
153
|
```
|
|
127
154
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
155
|
+
`install` already does this for the editors you have. Reach for `link` when you want all 14
|
|
156
|
+
managed, hash-stamped files regardless of what's installed — MCP server config for six hosts plus
|
|
157
|
+
rules files for eight — or to repair drift. Managed blocks are merged into your existing
|
|
158
|
+
instruction files and never clobber your content.
|
|
131
159
|
|
|
132
160
|
```bash
|
|
133
161
|
npx klypix-mcp link --check # audits without writing; exits non-zero on drift
|
|
@@ -481,8 +509,8 @@ The MCP verbs below are what agents call. These are what **you** call:
|
|
|
481
509
|
| Command | What it does |
|
|
482
510
|
|---|---|
|
|
483
511
|
| `npx klypix-mcp init` | Seed a starter `brain.klypix` here and print an MCP config |
|
|
484
|
-
| `npx klypix-mcp install` |
|
|
485
|
-
| `npx klypix-mcp link` |
|
|
512
|
+
| `npx klypix-mcp install` | Set up everything: machine engine + hooks, then this project — brain, config for the editors you have, merge driver, verified (see Quick start) |
|
|
513
|
+
| `npx klypix-mcp link` | Re-project all 14 managed files regardless of what is installed (`--check` audits) |
|
|
486
514
|
| `npx klypix-mcp doctor` | One verdict: version, hosts, live sessions, tool count, drift. Exits non-zero — usable as a CI gate |
|
|
487
515
|
| `npx klypix-mcp runtime` | Passive per-connection process/RAM attribution (`--json`, optional `--watch seconds`); never kills or deduplicates |
|
|
488
516
|
| `npx klypix-mcp conformance` | Launch two real MCP clients against this build and verify coordination behaviour |
|
package/bin/klypix-install.mjs
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
import { brainInstallDecision, deploySourceDecision } from '../src/install-version.mjs';
|
|
35
35
|
import { acquireInstallLockSync, releaseInstallLockSync } from '../src/install-lock.mjs';
|
|
36
36
|
import { collectRepoState } from '../src/repo-state.mjs';
|
|
37
|
+
import { runSetup, renderBrief } from '../src/setup.mjs';
|
|
37
38
|
|
|
38
39
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
39
40
|
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
@@ -48,6 +49,12 @@ const VERSION = (() => { try { return JSON.parse(fs.readFileSync(path.join(PKG_R
|
|
|
48
49
|
const FORCE = process.argv.includes('--force');
|
|
49
50
|
const CODEX_HOOKS = process.argv.includes('--codex-hooks');
|
|
50
51
|
const RUNTIME_ONLY = process.argv.includes('--runtime-only');
|
|
52
|
+
// Project wiring is the default because it is the step users did not know
|
|
53
|
+
// existed. These opt OUT for the cases that genuinely want machine-only:
|
|
54
|
+
// CI images, scripted provisioning, and anyone wiring the project by hand.
|
|
55
|
+
const NO_PROJECT = process.argv.includes('--no-project');
|
|
56
|
+
const VERIFY_ALL = process.argv.includes('--verify-all');
|
|
57
|
+
const SETUP_JSON = process.argv.includes('--json');
|
|
51
58
|
// Released-tag deploy-guard acknowledgement. Deliberately a SEPARATE axis from
|
|
52
59
|
// --force: --force is destination authority (overwrite what is installed),
|
|
53
60
|
// this is source authority (knowingly deploy an untagged working tree).
|
|
@@ -480,6 +487,24 @@ try {
|
|
|
480
487
|
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.');
|
|
481
488
|
}
|
|
482
489
|
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.');
|
|
490
|
+
|
|
491
|
+
// 9) PROJECT setup (1.71.0) — the step users never knew they had to take.
|
|
492
|
+
// `install` wired the machine; without this it wired nothing a Cursor,
|
|
493
|
+
// Antigravity, Codex, Cline or Copilot user could see, and the failure
|
|
494
|
+
// was silent. A runtime-only refresh deliberately skips it: that path
|
|
495
|
+
// exists to preserve every host/project config byte.
|
|
496
|
+
if (!RUNTIME_ONLY && !NO_PROJECT) {
|
|
497
|
+
try {
|
|
498
|
+
const report = await runSetup({ verifyAll: VERIFY_ALL });
|
|
499
|
+
if (SETUP_JSON) console.log(JSON.stringify(report, null, 2));
|
|
500
|
+
else console.log(renderBrief(report));
|
|
501
|
+
} catch (e) {
|
|
502
|
+
// The machine install already succeeded and is independently
|
|
503
|
+
// useful; a project-wiring failure must report itself, not undo it.
|
|
504
|
+
console.error(`⚠ project setup could not finish: ${e?.message || e}`);
|
|
505
|
+
console.error(' The machine install is intact — re-run inside your project, or use `npx klypix-mcp link`.');
|
|
506
|
+
}
|
|
507
|
+
}
|
|
483
508
|
console.log(' Verify anytime: `npx klypix-mcp doctor`; prove two-client behavior with `npx klypix-mcp conformance`.');
|
|
484
509
|
} catch (e) {
|
|
485
510
|
releaseInstallLockSync(installLock);
|
package/bin/klypix-mcp.mjs
CHANGED
|
@@ -19,13 +19,14 @@ const PKG_VERSION = (() => {
|
|
|
19
19
|
}
|
|
20
20
|
})();
|
|
21
21
|
|
|
22
|
-
const DIRECT = new Set(['install', 'link', 'doctor', 'runtime', 'conformance', 'garden-code', 'init', 'git-driver', 'git-hook', 'brain-history', 'brain-deleted', 'diff', 'pr-brief', 'uninstall', 'bench']);
|
|
22
|
+
const DIRECT = new Set(['install', 'link', 'doctor', 'runtime', 'conformance', 'garden-code', 'init', 'git-driver', 'git-hook', 'brain-history', 'brain-deleted', 'orphans', 'diff', 'pr-brief', 'uninstall', 'bench']);
|
|
23
23
|
|
|
24
24
|
const USAGE = [
|
|
25
25
|
`klypix-mcp ${PKG_VERSION} — shared project brain + MCP coordination server.`,
|
|
26
26
|
'',
|
|
27
27
|
'Verbs:',
|
|
28
|
-
' install [--force] [--codex-hooks]
|
|
28
|
+
' install [--force] [--codex-hooks] set up everything: machine engine + Claude hooks, then THIS project —',
|
|
29
|
+
' [--no-project] [--json] brain, config for the editors you actually have, merge driver, verified',
|
|
29
30
|
' link [dir] [--check] project this project\'s 14 managed agent config files (--check audits, writes nothing, exits 1 on drift)',
|
|
30
31
|
' doctor [--npm] [--all] [--json] read-only self-check; exits 1 on drift',
|
|
31
32
|
' runtime [--json] [--watch seconds] passive MCP process/RAM attribution; never terminates a process',
|
|
@@ -38,6 +39,7 @@ const USAGE = [
|
|
|
38
39
|
' git-hook [install|remove|status] wire the agent-neutral commit-capture hook (any agent/branch/worktree → brain cards)',
|
|
39
40
|
' brain-history [list|restore <id>] restore points for this brain — undo an accidental delete, edit, or overwrite',
|
|
40
41
|
' brain-deleted [list|restore|purge] recycle bin for this brain — cards you deleted, kept recoverable',
|
|
42
|
+
' orphans [brain] [--apply|--areas] orphan gardener backfill: report unconnected cards, link the confident subset (dry-run default)',
|
|
41
43
|
' diff [ref] [--brain <path>] readable brain diff vs a git ref (default HEAD) — markdown to stdout',
|
|
42
44
|
' pr-brief [baseRef] [--brain <path>] brain decisions touching the files changed since baseRef — PR-comment markdown',
|
|
43
45
|
'',
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// klypix-orphans — `npx klypix-mcp orphans`. The orphan gardener's backfill pass:
|
|
3
|
+
// report how many live decision/milestone cards have NO connections, and link the
|
|
4
|
+
// CONFIDENT subset in one pass using the same selector capture uses
|
|
5
|
+
// (proposeOrphanAnchorLinks): at most ONE lexical-anchor edge per card — exact
|
|
6
|
+
// [[wikilink]], exact file-slug tag, or an unambiguous ≥0.6 title overlap —
|
|
7
|
+
// never a fan-out, never a guess among multiple candidates.
|
|
8
|
+
//
|
|
9
|
+
// npx klypix-mcp orphans [brain] # DRY RUN (default) — report, write nothing
|
|
10
|
+
// npx klypix-mcp orphans --apply # draw the confident edges (restore point first)
|
|
11
|
+
// npx klypix-mcp orphans --areas # ALSO include each orphan's area-container edge
|
|
12
|
+
// npx klypix-mcp orphans --json # machine-readable report
|
|
13
|
+
//
|
|
14
|
+
// Contract: additive + lossless — no card is archived, rewritten, or moved; every
|
|
15
|
+
// arrow remains individually removable in KLYPIX. `--apply` takes a FORCED restore
|
|
16
|
+
// point via the brain-history machinery before writing, so the whole pass is one
|
|
17
|
+
// `brain-history restore` away from undone. Dry-run is the default because a
|
|
18
|
+
// backfill touches history, not just this session's capture.
|
|
19
|
+
//
|
|
20
|
+
// `--areas` is opt-in for backfill (unlike capture, where the area edge is part
|
|
21
|
+
// of the no-new-orphans contract): mass-adding hundreds of containment edges to
|
|
22
|
+
// an old brain is a layout decision the human should make deliberately.
|
|
23
|
+
//
|
|
24
|
+
// Synchronous top-level by design: the dispatcher does `await import(this);
|
|
25
|
+
// process.exit(0)`, so all work must complete during module evaluation. ARGV is
|
|
26
|
+
// the standalone shape — the dispatcher splices its verb out first (runVerb).
|
|
27
|
+
import fs from 'fs';
|
|
28
|
+
import path from 'path';
|
|
29
|
+
import { parseKlypix, brainInsights, proposeOrphanAnchorLinks, addBrainConnections, atomicWrite } from '../src/klypix-format.mjs';
|
|
30
|
+
import { snapshotBrain } from '../src/brain-history.mjs';
|
|
31
|
+
import { withAdvisoryWriteLock, brainCaptureLockPath } from '../src/brain-write-lock.mjs';
|
|
32
|
+
|
|
33
|
+
const isDir = (p) => { try { return fs.statSync(p).isDirectory(); } catch { return false; } };
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const raw = process.argv.slice(2);
|
|
37
|
+
const argv = (raw[0] === 'orphans' && !isDir(path.resolve(raw[0]))) ? raw.slice(1) : raw;
|
|
38
|
+
const apply = argv.includes('--apply');
|
|
39
|
+
const withAreas = argv.includes('--areas');
|
|
40
|
+
const json = argv.includes('--json');
|
|
41
|
+
const pathArg = argv.find(a => !a.startsWith('-'));
|
|
42
|
+
const file = path.resolve(process.cwd(), pathArg || 'brain.klypix');
|
|
43
|
+
if (!fs.existsSync(file)) {
|
|
44
|
+
console.error(`No brain at ${file} — run from the project folder (or pass a path).`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { struct } = await parseKlypix(fs.readFileSync(file));
|
|
49
|
+
const before = brainInsights(struct).orphans;
|
|
50
|
+
const byId = new Map(struct.cards.map(c => [c.id, c]));
|
|
51
|
+
const flat = (id) => {
|
|
52
|
+
const c = byId.get(id);
|
|
53
|
+
if (!c) return id;
|
|
54
|
+
const s = c.type === 'container' ? `[area] ${c.title || ''}` : String(c.text || '').replace(/\s+/g, ' ').trim();
|
|
55
|
+
return s.slice(0, 70);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const proposals = proposeOrphanAnchorLinks(struct);
|
|
59
|
+
const anchored = proposals.filter(p => p.anchor);
|
|
60
|
+
// The confident subset: anchor edges always; area edges only with --areas.
|
|
61
|
+
// Reversed twins (A anchors B, B anchors A) collapse to ONE edge here so the
|
|
62
|
+
// dry-run list, the drawn count, and the apply receipt all agree exactly.
|
|
63
|
+
const seenPair = new Set();
|
|
64
|
+
const edges = [];
|
|
65
|
+
for (const p of proposals) {
|
|
66
|
+
if (p.anchor) {
|
|
67
|
+
const key = [p.cardId, p.anchor.toId].sort().join('|');
|
|
68
|
+
if (!seenPair.has(key)) {
|
|
69
|
+
seenPair.add(key);
|
|
70
|
+
edges.push({ fromId: p.cardId, toId: p.anchor.toId, relationship: 'relates_to', label: 'auto', why: p.anchor.why });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (withAreas && p.areaId) edges.push({ fromId: p.cardId, toId: p.areaId, relationship: 'relates_to', label: 'in area', color: 'rgba(120,120,135,0.45)', width: 1, style: 'dashed', why: 'area container' });
|
|
74
|
+
}
|
|
75
|
+
// An anchor edge between two orphans repairs BOTH endpoints — project on the
|
|
76
|
+
// orphan set, not just the source card.
|
|
77
|
+
const orphanIdSet = new Set(before.map(o => o.id));
|
|
78
|
+
const repairable = new Set();
|
|
79
|
+
for (const e of edges) {
|
|
80
|
+
if (orphanIdSet.has(e.fromId)) repairable.add(e.fromId);
|
|
81
|
+
if (orphanIdSet.has(e.toId)) repairable.add(e.toId);
|
|
82
|
+
}
|
|
83
|
+
const projected = Math.max(0, before.length - repairable.size);
|
|
84
|
+
|
|
85
|
+
if (json && !apply) {
|
|
86
|
+
console.log(JSON.stringify({
|
|
87
|
+
brain: file, dryRun: true, orphans: before.length, confident: anchored.length,
|
|
88
|
+
projectedOrphans: projected,
|
|
89
|
+
edges: edges.map(e => ({ fromId: e.fromId, toId: e.toId, label: e.label, why: e.why })),
|
|
90
|
+
}, null, 2));
|
|
91
|
+
process.exit(0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!apply) {
|
|
95
|
+
console.log(`# orphan gardener — ${path.basename(file)} (DRY RUN, nothing written)`);
|
|
96
|
+
console.log('');
|
|
97
|
+
console.log(`Orphans now: ${before.length} live decision/milestone card(s) with no connections.`);
|
|
98
|
+
console.log(`Confident subset: ${anchored.length} card(s) have exactly one unambiguous lexical anchor${withAreas ? `; --areas adds ${proposals.filter(p => p.areaId).length} area-container edge(s)` : ''}.`);
|
|
99
|
+
console.log(`Projected after --apply: ${projected} orphan(s) remain (the rest need brain_connect review or a human).`);
|
|
100
|
+
if (edges.length) {
|
|
101
|
+
console.log('');
|
|
102
|
+
for (const e of edges) console.log(` - ${flat(e.fromId)} → ${flat(e.toId)} (${e.why})`);
|
|
103
|
+
console.log('');
|
|
104
|
+
console.log('Re-run with --apply to draw these. Additive only: no card is archived or rewritten;');
|
|
105
|
+
console.log('a forced restore point is taken first (undo: npx klypix-mcp brain-history).');
|
|
106
|
+
} else {
|
|
107
|
+
console.log('');
|
|
108
|
+
console.log(before.length
|
|
109
|
+
? 'No edge clears the confidence bar — review the rest with brain_connect (dry-run receipts).'
|
|
110
|
+
: 'Nothing to do — this brain has no orphaned decision/milestone cards.');
|
|
111
|
+
}
|
|
112
|
+
process.exit(0);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!edges.length) {
|
|
116
|
+
console.log(before.length
|
|
117
|
+
? `No edge clears the confidence bar — ${before.length} orphan(s) remain for brain_connect review. Nothing written.`
|
|
118
|
+
: 'Nothing to do — this brain has no orphaned decision/milestone cards. Nothing written.');
|
|
119
|
+
process.exit(0);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// APPLY — one locked snapshot + read-modify-write. The restore point is taken
|
|
123
|
+
// INSIDE the advisory lock (forced: an additive pass never trips the shrink
|
|
124
|
+
// rule, and the throttle must not skip the one snapshot that makes a whole
|
|
125
|
+
// backfill reversible): taken outside it, a concurrent capture landing between
|
|
126
|
+
// snapshot and write would be silently rolled back by a later restore.
|
|
127
|
+
let snap = null;
|
|
128
|
+
const result = await withAdvisoryWriteLock(brainCaptureLockPath(file), async (locked) => {
|
|
129
|
+
if (!locked) return { ok: false, error: 'brain is busy (another writer holds the capture lock) — retry in a moment; nothing written' };
|
|
130
|
+
snap = snapshotBrain(file, { reason: 'orphan-backfill', force: true });
|
|
131
|
+
const { buffer, added } = await addBrainConnections(fs.readFileSync(file), edges);
|
|
132
|
+
if (!added) return { ok: true, added: 0, after: before.length };
|
|
133
|
+
await atomicWrite(file, buffer, { snapshot: false }); // the forced pre-apply snapshot above is the restore point
|
|
134
|
+
let after = projected;
|
|
135
|
+
try { after = brainInsights((await parseKlypix(buffer)).struct).orphans.length; } catch { /* projected stays the honest fallback */ }
|
|
136
|
+
return { ok: true, added, after };
|
|
137
|
+
});
|
|
138
|
+
if (!result.ok) { console.error(`✗ ${result.error}`); process.exit(1); }
|
|
139
|
+
|
|
140
|
+
// A drawn count below the planned count means the brain changed between the
|
|
141
|
+
// dry-run read and the locked write (a pair already present, an endpoint
|
|
142
|
+
// gone) — slicing the planned list by count would then name the WRONG edges,
|
|
143
|
+
// so the per-edge list is only printed when it is exact.
|
|
144
|
+
const exact = result.added === edges.length;
|
|
145
|
+
// An 'unchanged' skip means the newest restore point already holds these
|
|
146
|
+
// exact bytes — the pass is just as reversible as a fresh snapshot.
|
|
147
|
+
const covered = snap && (snap.saved || snap.skipped === 'unchanged');
|
|
148
|
+
if (json) {
|
|
149
|
+
console.log(JSON.stringify({
|
|
150
|
+
brain: file, dryRun: false, added: result.added,
|
|
151
|
+
orphansBefore: before.length, orphansAfter: result.after,
|
|
152
|
+
restorePoint: covered ? (snap.id || true) : null,
|
|
153
|
+
edges: exact ? edges.map(e => ({ fromId: e.fromId, toId: e.toId, label: e.label, why: e.why })) : null,
|
|
154
|
+
...(exact ? {} : { edgesNote: `${edges.length - result.added} planned edge(s) were skipped (already present or endpoint missing) — re-run the dry run for the current pair list` }),
|
|
155
|
+
}, null, 2));
|
|
156
|
+
process.exit(0);
|
|
157
|
+
}
|
|
158
|
+
console.log(`✓ Drew ${result.added} additive connection(s). Orphan receipt: ${before.length} → ${result.after} (${Math.max(0, before.length - result.after)} repaired).`);
|
|
159
|
+
console.log(covered
|
|
160
|
+
? 'Restore point taken first — undo the whole pass: npx klypix-mcp brain-history'
|
|
161
|
+
: 'Restore point: not saved (best-effort) — arrows remain individually removable in KLYPIX.');
|
|
162
|
+
if (exact) for (const e of edges) console.log(` - ${flat(e.fromId)} → ${flat(e.toId)} (${e.why})`);
|
|
163
|
+
else console.log(` (${edges.length - result.added} planned edge(s) were already present or lost an endpoint — re-run the dry run for the current pair list)`);
|
|
164
|
+
process.exit(0);
|
|
165
|
+
} catch (e) {
|
|
166
|
+
console.error(`✗ orphans failed (brain unchanged): ${e?.message || e}`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
package/bin/klypix-worker.mjs
CHANGED
|
@@ -131,6 +131,11 @@ await runVerb('brain-history', './klypix-brain-history.mjs');
|
|
|
131
131
|
// the card's bytes to graveyard/ instead of destroying them; this lists, restores
|
|
132
132
|
// and (permanently) purges them.
|
|
133
133
|
await runVerb('brain-deleted', './klypix-brain-deleted.mjs');
|
|
134
|
+
// `npx klypix-mcp orphans` — the orphan gardener's backfill: report how many live
|
|
135
|
+
// cards sit outside the graph, link the CONFIDENT subset (one unambiguous lexical
|
|
136
|
+
// anchor each — never a fan-out). Dry-run by default; --apply takes a forced
|
|
137
|
+
// restore point first, so the whole pass is one brain-history restore from undone.
|
|
138
|
+
await runVerb('orphans', './klypix-orphans.mjs');
|
|
134
139
|
await runVerb('diff', './klypix-diff.mjs');
|
|
135
140
|
await runVerb('pr-brief', './klypix-pr-brief.mjs');
|
|
136
141
|
|
|
@@ -868,8 +873,12 @@ server.registerTool('brain_sync', {
|
|
|
868
873
|
// marker before rejecting them. A strict transport schema rejected first,
|
|
869
874
|
// allowing a later result-less completion to bypass that state entirely.
|
|
870
875
|
results: z.unknown().optional().describe('On phase complete, 1-8 result manifests for stable claim keys. The in-handler versioned validator rejects malformed, empty, unknown-field, or incomparable evidence and retains task scope.'),
|
|
876
|
+
releaseIntent: z.object({
|
|
877
|
+
version: z.string().max(64).describe('The version this session intends to release (e.g. "1.70.0").'),
|
|
878
|
+
ref: z.string().max(200).describe('The git ref (branch or tag) the release will be cut from.'),
|
|
879
|
+
}).optional().describe('Declare EXCLUSIVE intent to prepare a release of this project. The first declarer takes a ~2h lease (refreshed by checkpoints, freed by phase "complete", by expiry, or when the holder session ends); a second declarer gets a structured hard conflict naming the holder, version, and ref. While any lease is active every peer\'s sync gains a "release in preparation" footer line.'),
|
|
871
880
|
},
|
|
872
|
-
}, async ({ project, intent, files, phase, include_context, results }, extra) => {
|
|
881
|
+
}, async ({ project, intent, files, phase, include_context, results, releaseIntent }, extra) => {
|
|
873
882
|
const totalStartedAt = Date.now();
|
|
874
883
|
const report = mcpPresence.sync({
|
|
875
884
|
project,
|
|
@@ -877,6 +886,7 @@ server.registerTool('brain_sync', {
|
|
|
877
886
|
files,
|
|
878
887
|
phase,
|
|
879
888
|
results,
|
|
889
|
+
releaseIntent,
|
|
880
890
|
deliverMessages: include_context !== false,
|
|
881
891
|
actionId: extra?.klypixRequestIdentity?.actionId || '',
|
|
882
892
|
preflight: extra?.klypixBrainSyncPreflight,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "klypix-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.71.0",
|
|
4
4
|
"description": "Shared project brain and MCP coordination server for multi-agent coding.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"klypix-append": "bin/klypix-append.mjs",
|
|
45
45
|
"klypix-install": "bin/klypix-install.mjs",
|
|
46
46
|
"klypix-uninstall": "bin/klypix-uninstall.mjs",
|
|
47
|
-
"klypix-project-map": "bin/klypix-project-map.mjs"
|
|
47
|
+
"klypix-project-map": "bin/klypix-project-map.mjs",
|
|
48
|
+
"klypix-orphans": "bin/klypix-orphans.mjs"
|
|
48
49
|
},
|
|
49
50
|
"main": "index.mjs",
|
|
50
51
|
"exports": {
|
|
@@ -83,7 +84,7 @@
|
|
|
83
84
|
"bench": "node bin/klypix-mcp.mjs bench",
|
|
84
85
|
"test:bench": "node test/bench.mjs",
|
|
85
86
|
"pretest": "node test/publish-workflow.mjs",
|
|
86
|
-
"test": "node test/publish-verdict.mjs && node test/remote-client.mjs && node test/project-graph.mjs && node test/project-map-cli.mjs && node test/mcp-auto-update.mjs && node test/mcp-supervisor.mjs && node test/runtime-inspector.mjs && node test/codex-hooks.mjs && node test/request-identity.mjs && node test/session-identity-core.mjs && node test/agent-presence.mjs && node test/message-delivery-v3.mjs && node test/claude-message-delivery-v3.mjs && node test/result-reconcile.mjs && node test/evidence-publication-gate.mjs && node test/release-evidence-cli.mjs && node test/intent-guard.mjs && node test/git-capture-install.mjs && node test/brain-history.mjs && node test/brain-graveyard.mjs && node test/archived-visibility.mjs && node test/finding-routing.mjs && node test/finding-routing-hook.mjs && node test/presence-relay.mjs && node test/install-version.mjs && node test/context-gateway.mjs && node test/repo-state.mjs && node test/released-tag-guard.mjs && node test/conformance.mjs && node test/brain-doctor.mjs && node test/version-currency.mjs && node test/ship-capture.mjs && node test/lane-message.mjs && node test/brain-quality.mjs && node test/brain-connect-orphans.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/skill-staleness.mjs && node test/canvas-view.mjs && node test/status-completeness.mjs && node test/semantic-security.mjs && node test/semantic-gate.mjs && node test/memory-runtime.mjs && node test/semantic-cache.mjs && node test/decay-status.mjs && node test/decay-hook.mjs && node test/evidence-anchors.mjs && node test/presence-visibility.mjs && node test/presence-liveness.mjs && node test/completion-guard.mjs && node test/merge-brains.mjs && node test/concurrent-writes.mjs && node test/lock-interop.mjs && node test/capture-write-failure.mjs && node test/a2a-smoke.mjs && node test/cli-args.mjs && node test/format-guard.mjs && node test/git-tools.mjs && node test/uninstall.mjs",
|
|
87
|
+
"test": "node test/publish-verdict.mjs && node test/remote-client.mjs && node test/project-graph.mjs && node test/project-map-cli.mjs && node test/mcp-auto-update.mjs && node test/mcp-supervisor.mjs && node test/runtime-inspector.mjs && node test/codex-hooks.mjs && node test/request-identity.mjs && node test/session-identity-core.mjs && node test/agent-presence.mjs && node test/message-delivery-v3.mjs && node test/claude-message-delivery-v3.mjs && node test/result-reconcile.mjs && node test/evidence-publication-gate.mjs && node test/release-evidence-cli.mjs && node test/intent-guard.mjs && node test/git-capture-install.mjs && node test/brain-history.mjs && node test/brain-graveyard.mjs && node test/archived-visibility.mjs && node test/finding-routing.mjs && node test/finding-routing-hook.mjs && node test/presence-relay.mjs && node test/install-version.mjs && node test/context-gateway.mjs && node test/repo-state.mjs && node test/released-tag-guard.mjs && node test/conformance.mjs && node test/brain-doctor.mjs && node test/version-currency.mjs && node test/ship-capture.mjs && node test/lane-message.mjs && node test/brain-quality.mjs && node test/brain-connect-orphans.mjs && node test/orphan-gardener.mjs && node test/brief-and-recall.mjs && node test/layout-cluster.mjs && node test/brain-ask.mjs && node test/retrieval-fusion.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/skill-staleness.mjs && node test/canvas-view.mjs && node test/status-completeness.mjs && node test/semantic-security.mjs && node test/semantic-gate.mjs && node test/memory-runtime.mjs && node test/semantic-cache.mjs && node test/decay-status.mjs && node test/decay-hook.mjs && node test/evidence-anchors.mjs && node test/presence-visibility.mjs && node test/undeclared-active.mjs && node test/presence-liveness.mjs && node test/observed-scope.mjs && node test/release-lease.mjs && node test/completion-guard.mjs && node test/merge-brains.mjs && node test/concurrent-writes.mjs && node test/lock-interop.mjs && node test/capture-write-failure.mjs && node test/a2a-smoke.mjs && node test/one-command-setup.mjs && node test/cli-args.mjs && node test/format-guard.mjs && node test/git-tools.mjs && node test/uninstall.mjs",
|
|
87
88
|
"test:memory": "node test/memory-runtime.mjs",
|
|
88
89
|
"test:memory:soak": "node --expose-gc test/memory-soak.mjs",
|
|
89
90
|
"runtime": "node bin/klypix-runtime.mjs"
|