klypix-mcp 1.83.0 → 1.85.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/A2A.md +6 -0
- package/FORMAT.md +12 -0
- package/README.md +66 -27
- package/bin/klypix-a2a.mjs +3 -2
- package/bin/klypix-install.mjs +6 -2
- package/bin/klypix-worker.mjs +123 -29
- package/package.json +4 -3
- package/src/agent-presence.mjs +6 -0
- package/src/brain-evidence.mjs +205 -0
- package/src/brain-note.mjs +21 -3
- package/src/capture-gap.mjs +78 -26
- package/src/codex-brain-hook.mjs +60 -1
- package/src/global-brain-hook.mjs +216 -63
- package/src/klypix-core.mjs +371 -25
- package/src/klypix-format.mjs +1293 -152
- package/src/repo-state.mjs +75 -0
package/A2A.md
CHANGED
|
@@ -113,6 +113,12 @@ If a write (`make_board`/`remember`) returns `input-required`, reply with a mess
|
|
|
113
113
|
carrying the same `taskId` plus the missing input to **continue that task** (the
|
|
114
114
|
server resumes it with a stable id and accumulated history).
|
|
115
115
|
|
|
116
|
+
For `remember` or `learn_skill`, one-card requests may also provide `args.evidence`
|
|
117
|
+
and `args.verify` using the [brain capture schema](README.md#capture-and-corrections).
|
|
118
|
+
Such requests use the shared capture engine even without a marker. References and
|
|
119
|
+
verification text are preserved; malformed metadata is rejected instead of discarded.
|
|
120
|
+
Verification text is never executed.
|
|
121
|
+
|
|
116
122
|
## Notes
|
|
117
123
|
|
|
118
124
|
- Tasks complete synchronously (the work is local file I/O), so `message/send`
|
package/FORMAT.md
CHANGED
|
@@ -132,6 +132,18 @@ strings: `text`, `box`, `image`, `file`, `container`, `approval`, `link`,
|
|
|
132
132
|
`createdBy` (`user` | `agent`) and the optional `createdVia` (which agent/channel
|
|
133
133
|
captured it) are the provenance bits the brain surfaces as badges and lenses.
|
|
134
134
|
|
|
135
|
+
Brain cards may also carry `evidence` and `verify`. An evidence reference has `kind`
|
|
136
|
+
(`file`, `pr`, `url`, `commit`, or `run`), `ref`, optional caller-supplied file blob `oid`,
|
|
137
|
+
and optional caller-reported ISO `verifiedAt`. The host-neutral capture API rejects
|
|
138
|
+
unknown input fields, unsafe file paths, and malformed metadata before writing.
|
|
139
|
+
It adds `capturedAt`; readable local files up to 2 MiB also receive a SHA-256 `sha256`
|
|
140
|
+
and `sourceBasis: "working-tree"`. Optional `headRevision` identifies HEAD at capture,
|
|
141
|
+
which does not imply the captured working bytes were committed. Legacy `oid`-only
|
|
142
|
+
references are read with both HEAD and working-file changes considered. These fields
|
|
143
|
+
describe source provenance and change detection, never factual verification.
|
|
144
|
+
`verify` is retained text, not executable configuration. All these optional fields
|
|
145
|
+
survive the format codec and capture lifecycle; an explicit empty amendment clears them.
|
|
146
|
+
|
|
135
147
|
The optional **`author`** answers the question a team actually asks: `createdBy` says
|
|
136
148
|
*what* wrote a card, `author` says *whose*. It is resolved from `git config user.name`
|
|
137
149
|
so brain attribution matches commit attribution with no configuration (override with
|
package/README.md
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
# Every project gets a brain.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Shared project memory for **Claude Code, Codex, Cursor** and other MCP coding tools. One
|
|
4
|
+
`brain.klypix` file, committed with your code, carries the project's decisions, corrections and open
|
|
5
|
+
questions across sessions and between tools. Corrections supersede stale decisions; sessions declare
|
|
6
|
+
the files they expect to touch and get warned about same-machine overlap. Versioned in Git. Served
|
|
7
|
+
over MCP by a process on your machine; Klypix uploads nothing (your agent's provider still receives
|
|
8
|
+
what the agent reads). Integration depth differs by host — see
|
|
9
|
+
[Supported hosts](#supported-hosts-and-their-integration-level).
|
|
5
10
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
[
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
<sub>Real output, not a mockup: both panes run a real MCP client against this server
|
|
14
|
+
([docs/demo/](docs/demo/) — the GIF is rendered by CI from a scripted tape against this server, not
|
|
15
|
+
hand-recorded, and re-rendered when the server's responses change).</sub>
|
|
16
|
+
|
|
17
|
+
Run this inside your project:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx klypix-mcp install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
It creates `brain.klypix` if the project has none, wires the editors it finds on this machine,
|
|
24
|
+
registers the `.klypix` merge driver if this is a git repo, and exits only after a real MCP
|
|
25
|
+
handshake has counted the tools that answered.
|
|
12
26
|
|
|
13
27
|
[](#supported-hosts-and-their-integration-level)
|
|
14
28
|
[](#supported-hosts-and-their-integration-level)
|
|
@@ -18,24 +32,18 @@
|
|
|
18
32
|
<sub>Host badges name the **integration level**, not a flat "compatible" — the levels and what is
|
|
19
33
|
actually tested are in [Supported hosts](#supported-hosts-and-their-integration-level).</sub>
|
|
20
34
|
|
|
21
|
-
**One actively managed project brain for multi-agent coding.** `klypix-mcp` keeps one versioned
|
|
22
|
-
`brain.klypix` in your repo: the project's active state — current decisions, corrections, evidence
|
|
23
|
-
anchors, open questions, active work, and handoffs. Corrections supersede stale decisions,
|
|
24
|
-
`brain_challenge` tests proposed decisions against standing rules and reversed approaches, and
|
|
25
|
-
sessions declare their scope and get warned about same-machine file overlap. Agents read it and
|
|
26
|
-
write to it over MCP. You read it and correct it in the [KLYPIX app](https://klypix.com).
|
|
27
|
-
|
|
28
35
|
> **One project. Many agents. One current understanding.**
|
|
29
36
|
|
|
30
|
-

|
|
31
|
-
|
|
32
|
-
<sub>Real output, not a mockup: both panes run a real MCP client against this server
|
|
33
|
-
([docs/demo/](docs/demo/) — the GIF is re-rendered by CI from a scripted tape, so it can never
|
|
34
|
-
drift from what the product actually does).</sub>
|
|
35
|
-
|
|
36
37
|
Klypix does not launch, run, supervise, or replace your agents. It is not an agent runtime, a model
|
|
37
|
-
router, a worktree manager, or a replacement for Git. It
|
|
38
|
-
|
|
38
|
+
router, a worktree manager, or a replacement for Git. It holds what the project currently believes.
|
|
39
|
+
|
|
40
|
+
[](https://github.com/dahshanlabs/klypix-mcp/actions/workflows/ci.yml)
|
|
41
|
+
[](https://www.npmjs.com/package/klypix-mcp)
|
|
42
|
+
[](LICENSE)
|
|
43
|
+
[](package.json)
|
|
44
|
+
[](https://modelcontextprotocol.io)
|
|
45
|
+
[](https://glama.ai/mcp/servers/dahshanlabs/klypix-mcp)
|
|
46
|
+
[](BENCHMARKS.md)
|
|
39
47
|
|
|
40
48
|
## See the shared project brain in action
|
|
41
49
|
|
|
@@ -346,6 +354,30 @@ there are no lifecycle hooks on those hosts.
|
|
|
346
354
|
|
|
347
355
|
## Capture and corrections
|
|
348
356
|
|
|
357
|
+
`brain_note` accepts structured supporting references and inert verification text:
|
|
358
|
+
|
|
359
|
+
```json
|
|
360
|
+
{
|
|
361
|
+
"text": "Retry failed uploads with a bounded backoff to preserve queued work.",
|
|
362
|
+
"area": "Storage",
|
|
363
|
+
"evidence": [{ "kind": "file", "ref": "src/uploads.mjs:42" }],
|
|
364
|
+
"verify": "node test/uploads.mjs"
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
File references must stay inside the project. The capture records a fingerprint of the
|
|
369
|
+
working file and, when available, the repository HEAD revision. An unchanged fingerprint
|
|
370
|
+
means **source unchanged**, not that the remembered claim is correct or that tests passed.
|
|
371
|
+
Dirty working files are fingerprinted as they are; HEAD alone does not describe those bytes.
|
|
372
|
+
Read results distinguish changed, missing, and unverified sources. External references
|
|
373
|
+
(`pr`, `url`, `commit`, `run`) are retained without fetching or verifying them. `verify` is
|
|
374
|
+
shown as recorded text and never executed. Optional `verifiedAt` is explicitly caller-reported.
|
|
375
|
+
|
|
376
|
+
On an amendment (`marker: "~"`), omitted metadata is preserved; `evidence: []` and
|
|
377
|
+
`verify: ""` clear obsolete metadata. A resolve (`✓`) archives existing evidence; attach
|
|
378
|
+
new evidence with a milestone and `closes`, or amend before resolving. The CLI accepts the
|
|
379
|
+
same JSON on stdin, or `--evidence '<JSON array>'` and `--verify '<text>'`.
|
|
380
|
+
|
|
349
381
|
On Claude Code, decisions are captured automatically at turn end from inline `🧠 BRAIN [Area]:`
|
|
350
382
|
markers in the transcript, deduped, under a capture lock.
|
|
351
383
|
|
|
@@ -563,10 +595,10 @@ The MCP verbs below are what agents call. These are what **you** call:
|
|
|
563
595
|
| `brain_ask` | Whole-brain question answering — correction-aware, `as_of` time travel |
|
|
564
596
|
| `brain_challenge` | The brain argues back: contradictions with receipts, tried-and-reversed chains, standing rules, other-agent provenance flags |
|
|
565
597
|
| `brain_note` | Capture with the full lifecycle — supersede / re-adopt / ✓ resolve / ~ update / 🛠 skill / `closes:` |
|
|
566
|
-
| `brain_reconcile` | Proposes stale-vs-correction pairs
|
|
598
|
+
| `brain_reconcile` | Proposes stale-vs-correction pairs, unrecorded migrations, and the open cards a release ref's commits look to have closed — then closes the exact pairs you confirm |
|
|
567
599
|
| `brain_insights` | Hubs, orphaned decisions, stale questions, area sizes |
|
|
568
600
|
| `brain_lens` | Machine-readable freshness, provenance, activity, timeline, orrery and unresolved views |
|
|
569
|
-
| `brain_garden` | Maintenance pass — proposes first
|
|
601
|
+
| `brain_garden` | Maintenance pass — proposes first; consolidation cannot apply without an approval code the human generates. The separate `repair:"duplicate-partials"` pass is dry-run first and needs no code (it removes only exact repeats and archives nothing) |
|
|
570
602
|
| `brain_doctor` | Self-diagnosis: version, core/enhanced host adapters, active sessions, tool count, projection drift |
|
|
571
603
|
| `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) |
|
|
572
604
|
| `brain_message_receipt` | Explicitly record model-side consumption using the exact message id and per-recipient offer token; acknowledgement alone never consumes a note |
|
|
@@ -589,8 +621,15 @@ Exactly 22, machine-verifiable with `npx klypix-mcp doctor`.
|
|
|
589
621
|
> screenshot and no host-level test. Hosts without the extension get clean text, which is the path
|
|
590
622
|
> that is actually verified.
|
|
591
623
|
|
|
592
|
-
`brain_doctor`, `brain_lens
|
|
593
|
-
|
|
624
|
+
`brain_doctor`, `brain_lens` and `brain_insights` are read-only introspection. `brain_reconcile`
|
|
625
|
+
is read-only too, with one exception: on `mode:"claims"` and `mode:"release"` you may pass
|
|
626
|
+
`confirm` / `dismiss` to close the pairs you verified. Confirm names exact card ids — nothing is
|
|
627
|
+
matched by prose — and covering only part of a multi-item clause writes `✔ partial` and keeps the
|
|
628
|
+
card open unless you pass `whole:true`. A call whose every entry is refused leaves the brain
|
|
629
|
+
byte-identical. A `dismiss` is recorded as a `not_fulfilled` edge between two CARDS, so a hint
|
|
630
|
+
whose only evidence is a raw commit has nothing to point at — name a `cardId`, or resolve the open
|
|
631
|
+
card itself. `brain_garden`, `brain_reconcile` and `brain_connect` always propose before they
|
|
632
|
+
apply.
|
|
594
633
|
`npx klypix-mcp doctor` gives one verdict and exits non-zero on drift, so it doubles as a CI gate.
|
|
595
634
|
|
|
596
635
|
## One file you can hold
|
package/bin/klypix-a2a.mjs
CHANGED
|
@@ -327,14 +327,15 @@ async function runSkill(skill, args, text, via) {
|
|
|
327
327
|
if (skill === 'learn_skill') marker = '+';
|
|
328
328
|
const single = (!args.cards && text.trim()) ? stripVerb(text) : null;
|
|
329
329
|
if (!marker && single && looksLikeSkill(single)) marker = '+'; // NL "remember this gotcha: always…" → skill
|
|
330
|
-
if (marker) {
|
|
330
|
+
if (marker || args.evidence !== undefined || args.verify !== undefined) {
|
|
331
331
|
const noteText = single ?? (Array.isArray(args.cards) && args.cards[0]?.text) ?? '';
|
|
332
332
|
if (!String(noteText).trim()) return needInput('Nothing to capture — send text or a card to remember.');
|
|
333
333
|
if (String(noteText).length > 20_000) return needInput('Captured text must be 20,000 characters or fewer.');
|
|
334
334
|
const requested = args.canvas ?? 'brain';
|
|
335
335
|
const target = confinedCanvas(requested);
|
|
336
336
|
if (!target.ok) return refusedCanvas(requested);
|
|
337
|
-
|
|
337
|
+
if (Array.isArray(args.cards) && args.cards.length !== 1) return needInput('Evidence capture requires exactly one card per request.');
|
|
338
|
+
return await opBrainNote({ vault: VAULT, canvas: target.canvas, text: noteText, area: args.area, marker, closes: args.closes, evidence: args.evidence, verify: args.verify, via });
|
|
338
339
|
}
|
|
339
340
|
// NL convenience: a bare "remember: X" becomes a single card on the brain.
|
|
340
341
|
const cards = args.cards ?? (text.trim() ? [{ text: stripVerb(text) }] : null);
|
package/bin/klypix-install.mjs
CHANGED
|
@@ -254,7 +254,11 @@ const flatten = (code) => code
|
|
|
254
254
|
// only thing standing between that and the field; it caught exactly this.
|
|
255
255
|
// (remote-client deliberately absent: the Remote feature was removed in
|
|
256
256
|
// 1.73.x, and this cherry-pick must not resurrect it — recorded rule.)
|
|
257
|
-
|
|
257
|
+
// repo-state: the worker's release-cut reconcile advisory (1.85.0) imports
|
|
258
|
+
// commitsInRange / makeContainmentProbe from it. It was already STAGED in
|
|
259
|
+
// the flat bundle (mcp-presence needs it) but never flattened, because
|
|
260
|
+
// nothing in bin/ had imported it directly before.
|
|
261
|
+
.replace(/\.\.\/src\/(bench|brain-doctor|agent-presence|agent-rules|capture-gap|enrichment|finding-routing|mcp-presence|mcp-supervisor|mcp-auto-update|presence-relay|repo-state|semantic-memory|runtime-inspector|project-graph|git-capture-install)\.mjs/g, './$1.mjs')
|
|
258
262
|
.replace(/klypix-worker\.mjs/g, 'klypix-mcp-worker.mjs')
|
|
259
263
|
.replace(/const PKG_VERSION = \(\(\) => \{[\s\S]*?\}\)\(\);/, `const PKG_VERSION = '${VERSION}'; // baked at install (flat layout has no package.json)`);
|
|
260
264
|
|
|
@@ -391,7 +395,7 @@ try {
|
|
|
391
395
|
// canvas-view-app.html is the canvas_view MCP App UI — staged raw (an HTML
|
|
392
396
|
// file must never get a JS-comment banner) beside the flat server, which
|
|
393
397
|
// resolves it via its ./canvas-view-app.html candidate path.
|
|
394
|
-
for (const f of ['global-brain-hook.mjs', 'capture-gap.mjs', 'brain-semantic.mjs', 'semantic-memory.mjs', 'enrichment.mjs', 'brain-note.mjs', 'brain-git-hook.mjs', 'git-capture-install.mjs', 'brain-history.mjs', 'brain-graveyard.mjs', 'klypix-format.mjs', 'klypix-core.mjs', 'brain-write-lock.mjs', 'agent-rules.mjs', 'brain-doctor.mjs', 'editor-detect.mjs', 'agent-presence.mjs', 'mcp-presence.mjs', 'repo-state.mjs', 'result-reconcile.mjs', 'finding-routing.mjs', 'presence-relay.mjs', 'mcp-supervisor.mjs', 'mcp-auto-update.mjs', 'runtime-inspector.mjs', 'project-graph.mjs', 'bench.mjs', 'codex-brain-hook.mjs', 'codex-hooks.mjs', 'canvas-view-app.html']) {
|
|
398
|
+
for (const f of ['global-brain-hook.mjs', 'capture-gap.mjs', 'brain-semantic.mjs', 'semantic-memory.mjs', 'enrichment.mjs', 'brain-note.mjs', 'brain-evidence.mjs', 'brain-git-hook.mjs', 'git-capture-install.mjs', 'brain-history.mjs', 'brain-graveyard.mjs', 'klypix-format.mjs', 'klypix-core.mjs', 'brain-write-lock.mjs', 'agent-rules.mjs', 'brain-doctor.mjs', 'editor-detect.mjs', 'agent-presence.mjs', 'mcp-presence.mjs', 'repo-state.mjs', 'result-reconcile.mjs', 'finding-routing.mjs', 'presence-relay.mjs', 'mcp-supervisor.mjs', 'mcp-auto-update.mjs', 'runtime-inspector.mjs', 'project-graph.mjs', 'bench.mjs', 'codex-brain-hook.mjs', 'codex-hooks.mjs', 'canvas-view-app.html']) {
|
|
395
399
|
const s = path.join(SRC, f); if (exists(s)) staged.push({ dst: f, content: fs.readFileSync(s, 'utf8') });
|
|
396
400
|
}
|
|
397
401
|
for (const [src, dst] of [
|
package/bin/klypix-worker.mjs
CHANGED
|
@@ -34,6 +34,7 @@ import { compareProjectGraphResults, projectGraphContextMarkdown, queryProjectGr
|
|
|
34
34
|
import { auditProject, compactAgentsBrief, linkProject, mcpServerEntry } from '../src/agent-rules.mjs';
|
|
35
35
|
import { createMcpPresence, KLYPIX_MCP_INSTRUCTIONS } from '../src/mcp-presence.mjs';
|
|
36
36
|
import { consumeMessageReceipt, findProjectBrain } from '../src/agent-presence.mjs';
|
|
37
|
+
import { collectRepoState, commitsInRange, makeContainmentProbe } from '../src/repo-state.mjs';
|
|
37
38
|
import {
|
|
38
39
|
reconcileRegisteredProjects,
|
|
39
40
|
registerProjectBrain,
|
|
@@ -219,6 +220,10 @@ const mcpPresence = createMcpPresence({
|
|
|
219
220
|
formatDecayAge: typeof brainFormat.formatDecayAge === 'function' ? brainFormat.formatDecayAge : undefined,
|
|
220
221
|
} : {},
|
|
221
222
|
});
|
|
223
|
+
// Release-cut reconcile: the ref each lane last scanned, so a checkpoint that
|
|
224
|
+
// merely REFRESHES the same lease does not re-walk the range. In-memory only —
|
|
225
|
+
// a worker restart rescans once, which costs one bounded git log.
|
|
226
|
+
const lastReconcileRef = new Map();
|
|
222
227
|
// Once brain_sync binds this connection to an exact project brain, all
|
|
223
228
|
// project-brain-default tools must use that same file. Leaving canvas undefined
|
|
224
229
|
// lets klypix-core's intentional cwd/env precedence substitute an ambient brain
|
|
@@ -627,18 +632,32 @@ server.registerTool('brain_connect', {
|
|
|
627
632
|
}, async ({ canvas, apply, max, threshold, scope, pairs, relationship }) => toContent(await opBrainConnect({ vault: mcpPresence.vault, canvas: boundBrainCanvas(canvas), apply, max, threshold, scope, pairs, relationship, log })));
|
|
628
633
|
|
|
629
634
|
server.registerTool('brain_reconcile', {
|
|
630
|
-
title: 'Reconcile the brain — contradictions
|
|
631
|
-
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.
|
|
635
|
+
title: 'Reconcile the brain — contradictions, unrecorded migrations, and what a release already closed',
|
|
636
|
+
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. (4) RELEASE: which open cards look fulfilled by the commits a release ref already carries (subject+body coverage, the card\'s own #commit- receipt, or a hint edge whose milestone is in the ref). READ-ONLY by default and on every other mode. THE ONE EXCEPTION: on mode "claims" and mode "release" you may pass confirm/dismiss to actually close what you verified — confirm names exact card ids, so nothing is matched by prose; covering only part of a multi-item clause writes "✔ partial" and KEEPS the card open unless you pass whole:true; a call whose every entry is refused leaves the brain byte-identical. Never reads the database or the network. Run it periodically, when recall surfaces something you believe is stale, or right before cutting a release.',
|
|
632
637
|
inputSchema: {
|
|
633
638
|
canvas: z.string().optional().describe('Brain canvas filename/path. Defaults to the project brain ("brain").'),
|
|
634
|
-
root: z.string().optional().describe("Project root holding the migrations dir (default: the brain file's folder)."),
|
|
635
|
-
mode: z.enum(['all', 'contradictions', 'migrations', 'legacy', 'claims', 'plans']).optional().describe('Which pass to run (default "all"): contradictions · migrations · legacy (pre-v1.15 raw-bash ship cards to tidy) · claims (open "remaining:/next:" clauses a later milestone likely fulfilled — receipts + ✓ markers,
|
|
639
|
+
root: z.string().optional().describe("Project root holding the migrations dir / git repo (default: the brain file's folder)."),
|
|
640
|
+
mode: z.enum(['all', 'contradictions', 'migrations', 'legacy', 'claims', 'plans', 'release']).optional().describe('Which pass to run (default "all"): contradictions · migrations · legacy (pre-v1.15 raw-bash ship cards to tidy) · claims (open "remaining:/next:" clauses a later milestone likely fulfilled — receipts + ✓ markers; confirm with {id, milestoneId}) · plans (plan / proposal / "design decided" cards a LATER 🏁 appears to have built — embedding-first because the ship is usually renamed) · release (open cards the commits in `ref` look to have fulfilled; confirm with {id, sha}).'),
|
|
641
|
+
ref: z.string().max(200).optional().describe('mode "release": the git ref being cut. Defaults to this session\'s active release lease, else HEAD.'),
|
|
642
|
+
sinceRef: z.string().max(200).optional().describe('mode "release": the baseline the range starts from. Defaults to the highest release-shaped tag in the repo.'),
|
|
643
|
+
confirm: z.array(z.object({
|
|
644
|
+
id: z.string().max(64).describe('The OPEN card id to close.'),
|
|
645
|
+
milestoneId: z.string().max(64).optional().describe('mode "claims": the live milestone card that fulfilled it. Requires an existing "likely closed by" link or a current coverage gap on this exact pair.'),
|
|
646
|
+
sha: z.string().max(40).optional().describe('mode "release": a commit the listing named as covering this card (cov ≥ 0.6, confirmable). Omit to use the card\'s own #commit- receipt.'),
|
|
647
|
+
whole: z.boolean().optional().describe('Assert the WHOLE card is done. Without it, covering one item of a multi-item clause writes "✔ partial" and the card stays open.'),
|
|
648
|
+
})).max(64).optional().describe('Pairs YOU verified. Honoured on mode "claims" and mode "release" only. Each confirmed card is stamped ✅, archived, and arrowed "closed by" to its evidence.'),
|
|
649
|
+
dismiss: z.array(z.object({
|
|
650
|
+
openId: z.string().max(64).describe('The open card the hint was wrong about.'),
|
|
651
|
+
cardId: z.string().max(64).optional().describe('The milestone/evidence card to dismiss it against (required unless the listing already named one).'),
|
|
652
|
+
sha: z.string().max(40).optional().describe('Informational only — the commit that produced the wrong hint. A dismissal is recorded against a CARD, so a hint whose only evidence is a raw commit (no milestone card) cannot be dismissed: pass a cardId, or retire the open card itself.'),
|
|
653
|
+
})).max(64).optional().describe('Wrong hints to retire permanently as "not_fulfilled" edges between two CARDS — never re-suggested by claims, release, or the self-heal. A card-to-card pair is what makes the dismissal durable; a coverage hint built straight from a commit has no card to point at and will be re-listed at the next release cut until the open card is resolved or the pair is named with a cardId.'),
|
|
654
|
+
note: z.string().max(400).optional().describe('One line of why, echoed in the receipt.'),
|
|
636
655
|
},
|
|
637
|
-
}, async ({ canvas, root, mode }) => toContent(await opBrainReconcile({ vault: mcpPresence.vault, canvas: boundBrainCanvas(canvas), root, mode, log })));
|
|
656
|
+
}, async ({ canvas, root, mode, ref, sinceRef, confirm, dismiss, note }) => toContent(await opBrainReconcile({ vault: mcpPresence.vault, canvas: boundBrainCanvas(canvas), root, mode, ref, sinceRef, confirm, dismiss, note, log })));
|
|
638
657
|
|
|
639
658
|
server.registerTool('brain_garden', {
|
|
640
659
|
title: 'Garden the brain — consolidate over-grown areas (sleep-time compute)',
|
|
641
|
-
description: 'Tidy an over-grown brain WITHOUT losing anything — SMART and non-invasive: it only consolidates DORMANT cards (old + peripheral), never load-bearing ones. Two phases: call it with no apply to get the areas that have accumulated forgotten cards (deterministic: >3 cards that are older than 14 days, beyond the area\'s newest 8, AND have ≤1 connection — so hubs and still-referenced decisions are left untouched; Focus/Instructions/Archive/Open-questions areas protected) plus their card text; YOU write one tight synthesis per area; then call again with apply:true, syntheses:[{title, synthesis}] AND the human\'s 8-char `approve` code (apply is REFUSED without it — you are never shown the code; the human generates it with `npx klypix-mcp garden-code` after reviewing your plan). Each area gets a 🌿 synthesis card, the originals are stamped "⤵ consolidated", moved to Archive, and arrowed to the synthesis — nothing is deleted, and one undo un-gardens. Run it when brain_insights or the brief shows an area has grown noisy.',
|
|
660
|
+
description: 'Tidy an over-grown brain WITHOUT losing anything — SMART and non-invasive: it only consolidates DORMANT cards (old + peripheral), never load-bearing ones. Two phases: call it with no apply to get the areas that have accumulated forgotten cards (deterministic: >3 cards that are older than 14 days, beyond the area\'s newest 8, AND have ≤1 connection — so hubs and still-referenced decisions are left untouched; Focus/Instructions/Archive/Open-questions areas protected) plus their card text; YOU write one tight synthesis per area; then call again with apply:true, syntheses:[{title, synthesis}] AND the human\'s 8-char `approve` code (apply is REFUSED without it — you are never shown the code; the human generates it with `npx klypix-mcp garden-code` after reviewing your plan). Each area gets a 🌿 synthesis card, the originals are stamped "⤵ consolidated", moved to Archive, and arrowed to the synthesis — nothing is deleted, and one undo un-gardens. Run it when brain_insights or the brief shows an area has grown noisy. SEPARATE PASS: `repair:"duplicate-partials"` lists (and with apply:true collapses) cards that carry the SAME `✔ partial` note more than once — the residue of a partial ✓ on a card that stays live by design. It keeps the earliest note of each distinct body and removes only exact repeats, so nothing is archived, nothing is deleted, no synthesis and no approval code are needed, and a second run finds nothing.',
|
|
642
661
|
inputSchema: {
|
|
643
662
|
canvas: z.string().optional().describe('Brain canvas filename/path. Defaults to the project brain ("brain").'),
|
|
644
663
|
apply: z.boolean().optional().describe('false (default) = list over-grown areas + cards to synthesize; true = consolidate using the supplied syntheses.'),
|
|
@@ -647,8 +666,9 @@ server.registerTool('brain_garden', {
|
|
|
647
666
|
synthesis: z.string().describe('3-6 sentence prose synthesis preserving every still-relevant fact/decision/number.'),
|
|
648
667
|
})).optional().describe('Required when apply:true — one entry per area you want consolidated.'),
|
|
649
668
|
approve: z.string().optional().describe('Required when apply:true — the 8-char human-approval code. You are never shown it: the human runs `npx klypix-mcp garden-code` and pastes the code into chat after reviewing your plan. Never guess or fabricate it.'),
|
|
669
|
+
repair: z.enum(['duplicate-partials']).optional().describe('Run a targeted repair instead of the consolidation pass. "duplicate-partials" collapses repeated ✔ partial notes on a card to the earliest one — lossless, idempotent, no syntheses and no approval code. Dry-run by default; apply:true writes.'),
|
|
650
670
|
},
|
|
651
|
-
}, async ({ canvas, apply, syntheses, approve }) => toContent(await opBrainGarden({ vault: mcpPresence.vault, canvas: boundBrainCanvas(canvas), apply, syntheses, approve })));
|
|
671
|
+
}, async ({ canvas, apply, syntheses, approve, repair }) => toContent(await opBrainGarden({ vault: mcpPresence.vault, canvas: boundBrainCanvas(canvas), apply, syntheses, approve, repair })));
|
|
652
672
|
|
|
653
673
|
server.registerTool('create_canvas', {
|
|
654
674
|
title: 'Create a KLYPIX canvas',
|
|
@@ -684,6 +704,13 @@ server.registerTool('brain_note', {
|
|
|
684
704
|
marker: z.enum(['', '?', '!', '+', '✓', '~']).optional().describe('(none)=decision · ?=open question · !=milestone · +=🛠️ skill (reusable how-to/gotcha; always resurfaces, never ages out) · ✓=resolve+archive the best-matching card · ~=update the matching card in place. Default: decision.'),
|
|
685
705
|
area: z.string().optional().describe('Area/topic — routes the card into that titled container and becomes a #tag (e.g. "Auth", "Release").'),
|
|
686
706
|
closes: z.string().optional().describe('Title or [[wikilink]] of a strategy/question card this note fulfils — resolves+archives it and draws a "closed by" arrow.'),
|
|
707
|
+
evidence: z.array(z.object({
|
|
708
|
+
kind: z.enum(['file', 'pr', 'url', 'commit', 'run']),
|
|
709
|
+
ref: z.string().min(1).max(1000).describe('A project-relative file path (optional :line or #Lline), or an external reference. External references are stored without fetching them.'),
|
|
710
|
+
oid: z.string().optional().describe('Optional full file blob hash from Git; a caller-supplied anchor, not proof of the claim.'),
|
|
711
|
+
verifiedAt: z.string().optional().describe('Optional caller-reported ISO verification date/time; not independently verified.'),
|
|
712
|
+
}).strict()).max(16).optional().describe('Supporting references. File bytes are fingerprinted as captured working-tree sources; hashes only detect source changes. On ~, [] clears evidence. Not accepted on resolve; use a milestone with closes to attach new evidence.'),
|
|
713
|
+
verify: z.string().max(2000).optional().describe('Verification instructions or command text to retain and display. Never executed by KLYPIX. On ~, an empty string clears it.'),
|
|
687
714
|
guard: z.object({
|
|
688
715
|
when: z.object({
|
|
689
716
|
tool: z.string().max(200).optional().describe('Regex matched against the tool name (e.g. "Bash", "Edit|Write").'),
|
|
@@ -697,10 +724,10 @@ server.registerTool('brain_note', {
|
|
|
697
724
|
}).optional().describe("GUARD CARDS: make this '+' skill fire BEFORE a matching tool call runs (Claude Code PreToolUse denies on severity block; other hosts warn), not just resurface in briefs. The card stays a normal 🛠️ rule — ✓-resolving it retires the guard, ~ with {remove:true} disarms it."),
|
|
698
725
|
canvas: z.string().optional().describe('Brain canvas filename/path. Defaults to the project brain ("brain").'),
|
|
699
726
|
},
|
|
700
|
-
}, async ({ text, marker, area, closes, guard, canvas }, extra) => {
|
|
727
|
+
}, async ({ text, marker, area, closes, evidence, verify, guard, canvas }, extra) => {
|
|
701
728
|
// Both 1.77 and 1.78 ride this call: the enrichment question (the asker's
|
|
702
729
|
// vocabulary for retrieval) AND the per-session capture receipt below.
|
|
703
|
-
const result = await opBrainNote({ vault: mcpPresence.vault, canvas: boundBrainCanvas(canvas), text, area, marker: marker || '', closes, guard, via: extra.klypixClientName, enrichmentQuestion: mcpPresence.declaredIntent });
|
|
730
|
+
const result = await opBrainNote({ vault: mcpPresence.vault, canvas: boundBrainCanvas(canvas), text, area, marker: marker || '', closes, evidence, verify, guard, via: extra.klypixClientName, enrichmentQuestion: mcpPresence.declaredIntent });
|
|
704
731
|
// Per-session capture receipt — this is what stops the uncaptured-work nudge
|
|
705
732
|
// from firing at a session that DID record its reasoning, just through MCP
|
|
706
733
|
// rather than a 🧠 marker. The Stop hook and this server share one session-id
|
|
@@ -708,7 +735,7 @@ server.registerTool('brain_note', {
|
|
|
708
735
|
// hook reads. Best-effort: a receipt failure must never fail the note.
|
|
709
736
|
try {
|
|
710
737
|
const { recordSessionCapture } = await import('../src/capture-gap.mjs');
|
|
711
|
-
recordSessionCapture(extra?.klypixRequestIdentity?.sessionId || mcpPresence.id);
|
|
738
|
+
if (!result.isError) recordSessionCapture(extra?.klypixRequestIdentity?.sessionId || mcpPresence.id, undefined, Date.now(), { project: mcpPresence.vault });
|
|
712
739
|
} catch { /* receipt is best-effort */ }
|
|
713
740
|
return toContent(result);
|
|
714
741
|
});
|
|
@@ -901,6 +928,71 @@ server.registerTool('brain_sync', {
|
|
|
901
928
|
}
|
|
902
929
|
} catch { /* observation is best-effort — never fail a sync */ }
|
|
903
930
|
}
|
|
931
|
+
// ── Release-cut reconcile advisory (1.85.0) ─────────────────────────────────
|
|
932
|
+
// A release lease was just GRANTED, so this session is about to cut a build.
|
|
933
|
+
// The one question nobody ever asked at that moment: does anything still open
|
|
934
|
+
// in the brain look like it ALREADY SHIPPED in this ref? Advisory only — it
|
|
935
|
+
// never blocks, never writes, never joins the refusal object, and any git or
|
|
936
|
+
// brain failure degrades to `{ skipped }` rather than failing a sync.
|
|
937
|
+
//
|
|
938
|
+
// Recomputed only on a NEW lease or a CHANGED ref (the worker is long-lived;
|
|
939
|
+
// a restart rescans once, which is acceptable) so a checkpoint refresh every
|
|
940
|
+
// few minutes does not re-walk 500 commits.
|
|
941
|
+
let releaseReconcileText = '';
|
|
942
|
+
{
|
|
943
|
+
const lease = report.structured?.releaseLease;
|
|
944
|
+
const granted = lease && (lease.status === 'taken' || lease.status === 'refreshed');
|
|
945
|
+
const ref = granted ? String(lease.holder?.ref || '').trim() : '';
|
|
946
|
+
const laneKey = `${String(report.structured?.project || mcpPresence.vault || '').toLowerCase()}|${String(mcpPresence.id || '')}`;
|
|
947
|
+
if (granted && ref && lastReconcileRef.get(laneKey) !== ref) {
|
|
948
|
+
lastReconcileRef.set(laneKey, ref);
|
|
949
|
+
const projectDir = report.structured?.project || mcpPresence.vault;
|
|
950
|
+
const brainPath = report.structured?.brain;
|
|
951
|
+
try {
|
|
952
|
+
const { execFileSync } = await import('child_process');
|
|
953
|
+
const repoState = (() => { try { return collectRepoState(projectDir); } catch { return null; } })();
|
|
954
|
+
const sinceRef = repoState?.latestReleaseTag?.tag
|
|
955
|
+
|| (() => {
|
|
956
|
+
try {
|
|
957
|
+
return brainFormat.readShipSignals(projectDir, (args) => execFileSync('git', String(args).split(/\s+/).filter(Boolean), {
|
|
958
|
+
cwd: projectDir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 4000,
|
|
959
|
+
})).tag || '';
|
|
960
|
+
} catch { return ''; }
|
|
961
|
+
})();
|
|
962
|
+
// NO `${ref}~50` guess. On a young repo — no release-shaped tag and
|
|
963
|
+
// fewer than 50 commits, i.e. the FIRST release — `git log <ref>~50..`
|
|
964
|
+
// exits non-zero, commitsInRange reports 'bad-range', and the lease
|
|
965
|
+
// holder is told "git history for <ref> could not be read", which reads
|
|
966
|
+
// as a broken checkout when the truth is "this repo is young".
|
|
967
|
+
// commitsInRange already walks the ref's own tip window on an empty
|
|
968
|
+
// baseline, capped at the same 500 commits / 4 s either way.
|
|
969
|
+
const range = commitsInRange(projectDir, sinceRef, ref);
|
|
970
|
+
if (range.status !== 'ok' || !brainPath) {
|
|
971
|
+
lease.reconcile = { skipped: true, reason: range.status !== 'ok' ? (range.reason || 'git-unreadable') : 'no-brain' };
|
|
972
|
+
if (range.status !== 'ok') releaseReconcileText = brainFormat.releaseReconcileNotice({ ref, skipped: true });
|
|
973
|
+
} else {
|
|
974
|
+
const { struct } = await brainFormat.parseKlypix(fs.readFileSync(brainPath));
|
|
975
|
+
const { candidates, truncated } = brainFormat.releaseFulfilledOpens(struct, range.commits, {
|
|
976
|
+
ref, containedFn: makeContainmentProbe(projectDir, ref),
|
|
977
|
+
});
|
|
978
|
+
// Zero candidates → NO key at all (RL10 parity): an absent advisory
|
|
979
|
+
// and an empty one must not look the same to a reader.
|
|
980
|
+
if (candidates.length) {
|
|
981
|
+
lease.reconcile = {
|
|
982
|
+
kind: 'open-cards-likely-fulfilled-by-release', severity: 'advisory',
|
|
983
|
+
ref, sinceRef, commitsScanned: range.commits.length, scanCapped: range.capped,
|
|
984
|
+
candidates, truncated,
|
|
985
|
+
confirmWith: brainFormat.releaseReconcileConfirmTemplate(ref),
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
releaseReconcileText = brainFormat.releaseReconcileNotice({ ref, sinceRef, candidates });
|
|
989
|
+
}
|
|
990
|
+
} catch {
|
|
991
|
+
try { lease.reconcile = { skipped: true, reason: 'error' }; } catch { /* lease is frozen — advisory only */ }
|
|
992
|
+
releaseReconcileText = brainFormat.releaseReconcileNotice({ ref, skipped: true });
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
904
996
|
// ── Uncaptured-work check, host-neutral half ────────────────────────────────
|
|
905
997
|
// The Stop hook can REFUSE a stop; every other host has no lifecycle hook at
|
|
906
998
|
// all, so brain_sync is the only place the same question can be asked. Stamp
|
|
@@ -939,32 +1031,34 @@ server.registerTool('brain_sync', {
|
|
|
939
1031
|
if (head) gap.recordTaskBaseline(sid, { head, project: projectDir });
|
|
940
1032
|
} else if (phase === 'complete' && projectDir && sid) {
|
|
941
1033
|
const baseline = gap.readTaskBaseline(sid);
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
//
|
|
945
|
-
//
|
|
946
|
-
const
|
|
947
|
-
|
|
1034
|
+
const sameProject = baseline?.project && path.resolve(baseline.project) === path.resolve(projectDir);
|
|
1035
|
+
if (sameProject && baseline?.head) {
|
|
1036
|
+
// A successful note checkpoints the observed HEAD only. Work committed
|
|
1037
|
+
// after that note belongs to a new outcome even in the same task/session.
|
|
1038
|
+
const receipt = gap.sessionCaptureReceipt(sid, projectDir);
|
|
1039
|
+
let from = baseline.head;
|
|
1040
|
+
if (receipt?.head && receipt.at >= baseline.at
|
|
1041
|
+
&& gitOk(['merge-base', '--is-ancestor', baseline.head, receipt.head])
|
|
1042
|
+
&& gitOk(['merge-base', '--is-ancestor', receipt.head, 'HEAD'])) from = receipt.head;
|
|
1043
|
+
const reachable = gitOk(['merge-base', '--is-ancestor', from, 'HEAD']);
|
|
1044
|
+
const range = from + '..HEAD';
|
|
1045
|
+
const count = reachable ? Number(gitOut(['rev-list', '--count', '--no-merges', range]) || 0) : 0;
|
|
948
1046
|
if (count > 0) {
|
|
949
|
-
const subjects = gitOut(['log', '--no-merges', '--format=%s',
|
|
1047
|
+
const subjects = gitOut(['log', '--no-merges', '--format=%s', range])
|
|
950
1048
|
.split('\n').map((s) => s.trim()).filter(Boolean).slice(0, 5);
|
|
951
|
-
|
|
952
|
-
// the hook uses, so the two halves never disagree about one session.
|
|
953
|
-
const withRationale = gitOut(['log', '--no-merges', '--format=%x1e%b', `${baseline.head}..HEAD`])
|
|
1049
|
+
const withRationale = gitOut(['log', '--no-merges', '--format=%x1e%b', range])
|
|
954
1050
|
.split('\x1e').map((b) => b.replace(/\s+/g, ' ').trim()).filter((b) => b.length >= 12).length;
|
|
1051
|
+
const outcome = projectDir + ':' + gitOut(['rev-parse', 'HEAD']);
|
|
955
1052
|
const decision = gap.captureGapDecision({
|
|
956
1053
|
commitTotal: count,
|
|
957
1054
|
commitCards: withRationale,
|
|
958
|
-
|
|
1055
|
+
alreadyNudged: gap.outcomeWasNudged(sid, outcome),
|
|
959
1056
|
});
|
|
960
1057
|
if (decision) {
|
|
961
|
-
const changed = gitOut(['diff', '--name-only',
|
|
962
|
-
const draft = gap.draftCaptureMarker({
|
|
963
|
-
commits: subjects.map((subject) => ({ subject })),
|
|
964
|
-
filesTouched: changed,
|
|
965
|
-
});
|
|
1058
|
+
const changed = gitOut(['diff', '--name-only', range]).split('\n').filter(Boolean).slice(0, 20);
|
|
1059
|
+
const draft = gap.draftCaptureMarker({ commits: subjects.map((subject) => ({ subject })), filesTouched: changed });
|
|
966
1060
|
captureGapText = gap.captureGapReason({ ...decision, draft, mode: 'advise' });
|
|
967
|
-
gap.recordCaptureGapNudge(sid);
|
|
1061
|
+
gap.recordCaptureGapNudge(sid, undefined, outcome);
|
|
968
1062
|
}
|
|
969
1063
|
}
|
|
970
1064
|
}
|
|
@@ -1022,7 +1116,7 @@ server.registerTool('brain_sync', {
|
|
|
1022
1116
|
return {
|
|
1023
1117
|
content: [{
|
|
1024
1118
|
type: 'text',
|
|
1025
|
-
text: [report.text, harnessText, shipNotice, captureGapText, contextText, timingText].filter(Boolean).join('\n\n'),
|
|
1119
|
+
text: [report.text, harnessText, shipNotice, releaseReconcileText, captureGapText, contextText, timingText].filter(Boolean).join('\n\n'),
|
|
1026
1120
|
}],
|
|
1027
1121
|
structuredContent,
|
|
1028
1122
|
...(report.isError ? { isError: true } : {}),
|
|
@@ -1031,7 +1125,7 @@ server.registerTool('brain_sync', {
|
|
|
1031
1125
|
|
|
1032
1126
|
server.registerTool('brain_doctor', {
|
|
1033
1127
|
title: 'Brain doctor — is this brain current, wired, and in sync?',
|
|
1034
|
-
description: 'Read-only self-check of the installed klypix brain, as ONE verdict: VERSION (deployed brain-core + optional npm currency), CLAUDE (existing 5-hook capture readiness), CODEX (automatic MCP presence plus optional enhanced-hook status), TOOLS (discoverable MCP verbs), SESSIONS (all active presence-adapter sessions across hosts, never recent-chat history), and HARNESS (projection drift). Use to answer "is my brain current, correctly installed, in sync, and who is actually live?" without file-spelunking. Never writes. SCOPE: only CLAUDE and CODEX get behavioural verdicts. HARNESS classifies the projected config/rules FILES on disk — a project can read fully ok while no other host has ever actually loaded them, so do not report a clean HARNESS as "Cursor/Cline/Windsurf/Copilot is working". The MCP-callable twin of `npx klypix-mcp doctor`.',
|
|
1128
|
+
description: 'Read-only self-check of the installed klypix brain, as ONE verdict: VERSION (deployed brain-core + optional npm currency), CLAUDE (existing 5-hook capture readiness), CODEX (automatic MCP presence plus optional enhanced-hook status), TOOLS (discoverable MCP verbs), SESSIONS (all active presence-adapter sessions across hosts, never recent-chat history), and HARNESS (projection drift). Use to answer "is my brain current, correctly installed, in sync, and who is actually live?" without file-spelunking. Never writes: the only side effects are read-only subprocess queries (git rev-parse / tag --list / log / merge-base with fixed argument arrays, and `npm view` only when check_npm is true) — it creates, edits, and deletes nothing. SCOPE: only CLAUDE and CODEX get behavioural verdicts. HARNESS classifies the projected config/rules FILES on disk — a project can read fully ok while no other host has ever actually loaded them, so do not report a clean HARNESS as "Cursor/Cline/Windsurf/Copilot is working". The MCP-callable twin of `npx klypix-mcp doctor`.',
|
|
1035
1129
|
inputSchema: {
|
|
1036
1130
|
project: z.string().optional().describe('Project dir to audit harness + peers for. Defaults to the server\'s working directory.'),
|
|
1037
1131
|
check_npm: z.boolean().optional().describe('Also fetch npm latest to flag a stale brain (default false — this one does a network `npm view`).'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "klypix-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.85.0",
|
|
4
4
|
"mcpName": "io.github.dahshanlabs/klypix-mcp",
|
|
5
5
|
"description": "Active state management for multi-agent coding: a shared, versioned project brain over MCP.",
|
|
6
6
|
"type": "module",
|
|
@@ -84,10 +84,11 @@
|
|
|
84
84
|
"bench": "node bin/klypix-mcp.mjs bench",
|
|
85
85
|
"test:bench": "node test/bench.mjs",
|
|
86
86
|
"pretest": "node test/publish-workflow.mjs",
|
|
87
|
-
"test": "node test/publish-verdict.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/install-rename-backoff.mjs && node test/project-binding-rebind.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/capture-gap.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/guard-cards.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/plan-fulfillment.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/enrichment.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/release-ancestry.mjs && node test/release-claim-join.mjs && node test/release-claims.mjs && node test/release-handshake.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/canvas-groups.mjs && node test/git-tools.mjs && node test/uninstall.mjs",
|
|
87
|
+
"test": "node test/publish-verdict.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/install-rename-backoff.mjs && node test/project-binding-rebind.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/capture-gap.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/guard-cards.mjs && node test/layout-cluster.mjs && node test/brain-ask.mjs && node test/retrieval-fusion.mjs && node test/eval-retrieval.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/partial-notes.mjs && node test/lifecycle-prefix.mjs && node test/close-link-safety.mjs && node test/resolve-ledger.mjs && node test/plan-fulfillment.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/enrichment.mjs && node test/decay-status.mjs && node test/decay-hook.mjs && node test/evidence-anchors.mjs && node test/brain-evidence.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/release-reconcile.mjs && node test/release-ancestry.mjs && node test/release-claim-join.mjs && node test/release-claims.mjs && node test/release-handshake.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/canvas-groups.mjs && node test/git-tools.mjs && node test/uninstall.mjs && node test/current-guidance.mjs && node test/status-shape.mjs && node test/status-hook.mjs",
|
|
88
88
|
"test:memory": "node test/memory-runtime.mjs",
|
|
89
89
|
"test:memory:soak": "node --expose-gc test/memory-soak.mjs",
|
|
90
|
-
"runtime": "node bin/klypix-runtime.mjs"
|
|
90
|
+
"runtime": "node bin/klypix-runtime.mjs",
|
|
91
|
+
"eval:retrieval": "node scripts/eval-retrieval.mjs"
|
|
91
92
|
},
|
|
92
93
|
"dependencies": {
|
|
93
94
|
"@modelcontextprotocol/ext-apps": "^1.7.5",
|
package/src/agent-presence.mjs
CHANGED
|
@@ -766,6 +766,11 @@ export function upsertSession({
|
|
|
766
766
|
identitySource = null,
|
|
767
767
|
aliases,
|
|
768
768
|
home,
|
|
769
|
+
// Status-digest dedup hash (1.85.0): sha1[0:12] of the last computed status
|
|
770
|
+
// digest this session was shown, so a repeat status prompt gets a one-line
|
|
771
|
+
// pointer instead of the same ~5k chars. ADDITIVE — written only when a
|
|
772
|
+
// writer passes it; kept verbatim by every other touch (…previous spread).
|
|
773
|
+
statusDigestHash,
|
|
769
774
|
now = Date.now(),
|
|
770
775
|
}) {
|
|
771
776
|
if (!brainPath || !id) return withWriteVerdict([], false, 'no-brain-or-id');
|
|
@@ -926,6 +931,7 @@ export function upsertSession({
|
|
|
926
931
|
...(previous.scopeStartedAt ? { scopeStartedAt: previous.scopeStartedAt } : {}),
|
|
927
932
|
...(taskCompleted ? { completedAt: now } : (previous.completedAt ? { completedAt: previous.completedAt } : {})),
|
|
928
933
|
}),
|
|
934
|
+
...(statusDigestHash !== undefined ? { statusDigestHash: String(statusDigestHash || '').slice(0, 16) } : {}),
|
|
929
935
|
lastSeen: now,
|
|
930
936
|
};
|
|
931
937
|
const kept = sessions.filter((session) => session.id !== id);
|