@cairnkeep/cli 2.3.1 → 2.5.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.
Files changed (151) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +232 -6
  3. package/bin/cairn +43 -2
  4. package/claude/capability-contract/hooks/capability-command-finish.sh +81 -0
  5. package/claude/capability-contract/hooks/capability-command-start.sh +76 -0
  6. package/claude/hooks/compaction-capture.sh +34 -0
  7. package/claude/hooks/memory-capture.sh +47 -5
  8. package/claude/hooks/memory-wakeup.sh +55 -2
  9. package/docs/building-an-overlay.md +4 -3
  10. package/docs/design/meta-agent.md +439 -0
  11. package/docs/harness-compatibility.md +117 -0
  12. package/docs/learning/CURRICULUM-MAP.md +91 -0
  13. package/docs/learning/FEATURE-GUIDE.md +86 -0
  14. package/docs/learning/PRODUCTION-PLAN.md +45 -14
  15. package/docs/learning/README.md +20 -1
  16. package/docs/learning/instructor-guide.md +6 -0
  17. package/docs/learning/lessons/L00-why-cairnkeep.md +20 -8
  18. package/docs/learning/lessons/L01-safe-trial.md +3 -3
  19. package/docs/learning/lessons/L02-installation.md +23 -5
  20. package/docs/learning/lessons/L03-first-project.md +7 -2
  21. package/docs/learning/lessons/L04-memory-fundamentals.md +4 -0
  22. package/docs/learning/lessons/L05-daily-workflow.md +4 -0
  23. package/docs/learning/lessons/L07-privacy-and-storage.md +3 -0
  24. package/docs/learning/lessons/L10-context-exploration.md +4 -2
  25. package/docs/learning/lessons/L12-managed-overlays.md +2 -0
  26. package/docs/learning/lessons/L13-session-evidence.md +71 -0
  27. package/docs/learning/lessons/L14-typed-memory.md +51 -0
  28. package/docs/learning/lessons/L15-capability-governance.md +56 -0
  29. package/docs/learning/lessons/L16-evaluation.md +67 -0
  30. package/docs/learning/lessons/L17-agent-boundary.md +55 -0
  31. package/docs/learning/templates/lesson-template.md +4 -1
  32. package/docs/learning/tracks/evidence-and-evaluation.md +20 -0
  33. package/docs/learning/tracks/operator.md +8 -2
  34. package/docs/learning/tracks/practitioner.md +6 -1
  35. package/docs/learning/tracks/quickstart.md +3 -0
  36. package/docs/learning/video-scripts/L00-why-cairnkeep.md +21 -17
  37. package/docs/learning/video-scripts/L02-installation.md +9 -2
  38. package/docs/learning/video-scripts/L03-first-project.md +3 -0
  39. package/docs/operating.md +567 -7
  40. package/docs/privacy-and-data-flow.md +417 -0
  41. package/docs/storage.md +252 -0
  42. package/examples/eval/README.md +42 -0
  43. package/examples/eval/adapter.json +12 -0
  44. package/examples/eval/bundled-fake.json +1 -0
  45. package/examples/eval/task-set.json +1 -0
  46. package/mcp-memory-server/dist/artifact-cli.js +184 -0
  47. package/mcp-memory-server/dist/artifact-schema.js +218 -0
  48. package/mcp-memory-server/dist/artifact-store.js +812 -0
  49. package/mcp-memory-server/dist/capability-adapter.js +286 -0
  50. package/mcp-memory-server/dist/capability-cli.js +361 -0
  51. package/mcp-memory-server/dist/capability-config.js +310 -0
  52. package/mcp-memory-server/dist/capability-harness.js +496 -0
  53. package/mcp-memory-server/dist/capability-registry.js +123 -0
  54. package/mcp-memory-server/dist/capability-schema.js +65 -0
  55. package/mcp-memory-server/dist/capability-store.js +431 -0
  56. package/mcp-memory-server/dist/compaction-normalize.js +267 -0
  57. package/mcp-memory-server/dist/eval-cli.js +523 -0
  58. package/mcp-memory-server/dist/eval-plan.js +346 -0
  59. package/mcp-memory-server/dist/eval-process.js +273 -0
  60. package/mcp-memory-server/dist/eval-report.js +609 -0
  61. package/mcp-memory-server/dist/eval-runner.js +827 -0
  62. package/mcp-memory-server/dist/eval-schema.js +368 -0
  63. package/mcp-memory-server/dist/eval-statistics.js +161 -0
  64. package/mcp-memory-server/dist/eval-workspace.js +313 -0
  65. package/mcp-memory-server/dist/failure-signature.js +141 -0
  66. package/mcp-memory-server/dist/index.js +600 -55
  67. package/mcp-memory-server/dist/node-cli.js +84 -0
  68. package/mcp-memory-server/dist/node-schema.js +170 -0
  69. package/mcp-memory-server/dist/node-store.js +455 -0
  70. package/mcp-memory-server/dist/note-cli.js +235 -0
  71. package/mcp-memory-server/dist/note-distiller.js +177 -0
  72. package/mcp-memory-server/dist/note-enrichment.js +93 -0
  73. package/mcp-memory-server/dist/note-schema.js +4 -0
  74. package/mcp-memory-server/dist/note-store.js +985 -0
  75. package/mcp-memory-server/dist/trajectory-cli.js +141 -0
  76. package/mcp-memory-server/dist/trajectory-normalize.js +448 -0
  77. package/mcp-memory-server/dist/trajectory-redaction.js +155 -0
  78. package/mcp-memory-server/dist/trajectory-schema.js +73 -0
  79. package/mcp-memory-server/dist/trajectory-store.js +339 -0
  80. package/mcp-memory-server/package.json +24 -2
  81. package/mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.219.json +9 -0
  82. package/mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.220.json +9 -0
  83. package/mcp-memory-server/scripts/fixtures/compaction-opencode-1.17.20-event.json +6 -0
  84. package/mcp-memory-server/scripts/fixtures/compaction-opencode-1.17.20-messages.json +119 -0
  85. package/mcp-memory-server/scripts/fixtures/compaction-unknown-version.json +17 -0
  86. package/opencode/capability-contract/plugins/capability-command.ts +319 -0
  87. package/opencode/command/graphify.md +25 -14
  88. package/opencode/plugins/memory-capture.ts +76 -10
  89. package/opencode/plugins/memory-wakeup.ts +58 -6
  90. package/package.json +8 -4
  91. package/pi/extensions/cairnkeep-trajectory.ts +55 -0
  92. package/schemas/artifact.schema.json +133 -0
  93. package/schemas/capability-callback.schema.json +68 -0
  94. package/schemas/capability-contract.schema.json +126 -0
  95. package/schemas/eval-adapter.schema.json +43 -0
  96. package/schemas/eval-protocol.schema.json +114 -0
  97. package/schemas/eval-report.schema.json +423 -0
  98. package/schemas/eval-task-set.schema.json +114 -0
  99. package/schemas/memory-node.schema.json +90 -0
  100. package/schemas/note.schema.json +112 -0
  101. package/schemas/trajectory-redaction.schema.json +25 -0
  102. package/schemas/trajectory.schema.json +82 -0
  103. package/scripts/bootstrap.sh +6 -0
  104. package/scripts/completion.sh +119 -5
  105. package/scripts/doctor.sh +284 -0
  106. package/scripts/fake-eval-adapter.mjs +120 -0
  107. package/scripts/fixtures/capability-harness-contracts.json +408 -0
  108. package/scripts/install-audit-timer.sh +3 -1
  109. package/scripts/lib/capability-opencode-plugin-harness.mjs +291 -0
  110. package/scripts/lib/trajectory-opencode-plugin-harness.mjs +25 -0
  111. package/scripts/lib/trajectory-pi-extension-harness.mjs +42 -0
  112. package/scripts/memory-wiki-audit.sh +49 -0
  113. package/scripts/smoke-bash32.sh +9 -0
  114. package/scripts/sync-claude-assets.sh +99 -6
  115. package/scripts/sync-opencode-graphify-assets.sh +24 -3
  116. package/scripts/sync-opencode-plugin-assets.sh +31 -5
  117. package/scripts/sync-opencode-security-assets.sh +24 -3
  118. package/scripts/sync-opencode-wiki-assets.sh +24 -3
  119. package/scripts/sync-pi-assets.sh +72 -0
  120. package/scripts/test-audit-timer.sh +98 -0
  121. package/scripts/test-bootstrap-untracked.sh +49 -0
  122. package/scripts/test-cli-dispatch.sh +41 -2
  123. package/scripts/test-compaction-hooks.sh +495 -0
  124. package/scripts/test-completion.sh +77 -0
  125. package/scripts/test-doctor.sh +348 -1
  126. package/scripts/test-eval-cli.sh +175 -0
  127. package/scripts/test-launcher-hooks.sh +630 -3
  128. package/scripts/test-learning-docs.sh +20 -4
  129. package/scripts/test-notes-cli.sh +114 -0
  130. package/scripts/test-package-install.sh +254 -0
  131. package/scripts/test-phase16-node-lifecycle.sh +50 -0
  132. package/scripts/test-phase17-artifact-lifecycle.sh +370 -0
  133. package/scripts/test-phase18-capability-lifecycle.sh +504 -0
  134. package/scripts/test-phase18-harness-boundary.sh +1060 -0
  135. package/scripts/test-phase19-eval-lifecycle.sh +1015 -0
  136. package/scripts/test-trajectory-cli.sh +87 -0
  137. package/scripts/test-trajectory-hooks.sh +142 -0
  138. package/scripts/test-uninstall.sh +231 -5
  139. package/scripts/uninstall.sh +38 -5
  140. package/scripts/verify-dependency-security.mjs +121 -0
  141. package/scripts/verify-docs-parity.sh +527 -1
  142. package/scripts/verify-phase18-runtime-evidence.mjs +895 -0
  143. package/scripts/verify-phase19-runtime-evidence.mjs +947 -0
  144. package/templates/capabilities.json.template +7 -0
  145. package/templates/env.example.template +51 -0
  146. package/templates/memory-wiki-audit.service.template +1 -1
  147. package/templates/start-claude.sh.template +36 -0
  148. package/templates/start-kimi.sh.template +39 -0
  149. package/templates/start-opencode.sh.template +47 -5
  150. package/templates/start-pi.sh.template +30 -0
  151. package/templates/trajectory-redaction.json.template +4 -0
package/CHANGELOG.md CHANGED
@@ -3,6 +3,88 @@
3
3
  All notable user-facing changes are documented here. This project follows
4
4
  [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [Unreleased]
7
+
8
+ ## [2.5.0] - 2026-07-29
9
+
10
+ ### Added
11
+
12
+ - Add Kimi Code as a supported memory client with a generated project launcher,
13
+ local stdio guidance, and tested authenticated remote HTTP configuration.
14
+ - Add an explicit harness compatibility matrix and a documented validation
15
+ process for future MCP clients.
16
+
17
+ ### Fixed
18
+
19
+ - Document that Kimi requires a literal MCP URL and a token reference through
20
+ `bearerTokenEnvVar`, preventing invalid `${VAR}` URL configurations without
21
+ writing bearer-token values to project files.
22
+
23
+ ## [2.4.0] - 2026-07-29
24
+
25
+ ### Added
26
+
27
+ - Add opt-in, local-only, structured Claude Code and OpenCode session
28
+ trajectories with pre-write redaction, reasoning omission, bounded retention
29
+ and no model or network dependency.
30
+ - Add `cairn trajectory list|show|prune`, shell completion, and explicit
31
+ `cairn doctor --repair` support for trajectory metadata and indexes.
32
+ - Add native Pi trajectory capture through its `session_shutdown` extension,
33
+ `cairn sync-pi`, a generic Pi launcher, uninstall coverage, and the same
34
+ opt-in local redaction, size, and retention boundary as other harnesses.
35
+ - Add default-off deterministic hindsight-note distillation, exact repeated
36
+ error lookup, typed local Markdown hierarchy, lifecycle history, explicit
37
+ corroborated shared promotion, and `cairn notes` CLI/completion support.
38
+ - Add separately opted-in provider-neutral note prose enrichment and integrate
39
+ all-project incremental distillation into the existing audit timer without
40
+ adding online-agent latency.
41
+ - Add default-off typed memory metadata/tags, hard-filtered discovery,
42
+ history-aware structured `memory_import`, and logical project/shared note
43
+ address spaces with journaled crash recovery and doctor integration.
44
+ - Add independent default-off compaction continuity for supported Claude Code
45
+ and OpenCode seams, capturing only harness-produced summaries into local,
46
+ redacted, bounded immutable revisions with structured fresh-session recovery.
47
+ - Add a separate local artifact store for compaction summaries, diffs, test
48
+ output, and generated-file metadata/snapshots; four gated MCP tools;
49
+ `cairn artifact list|show|delete|prune`; doctor, completion, package, and
50
+ backup-first uninstall coverage; and separately consented HTTP access.
51
+ - Add a default-off managed contract for eight existing capabilities, including
52
+ project CLI overrides, compatibility-aware status/digests, exact MCP tool
53
+ omission, guarded installed workflows, and separately consented payload-free
54
+ local callback records.
55
+ - Add a default-off, adapter-driven two-pass evaluation harness with immutable
56
+ task sets, paired seeds, confidence intervals, per-capability ablations,
57
+ deterministic offline fixtures, bounded reports, and exact-source runtime
58
+ evidence without claiming an unmeasured quality improvement.
59
+ - Add a design-only meta-agent contract describing how future configuration
60
+ proposals could be evaluated through the existing harness without giving
61
+ Cairnkeep ownership of an agent or inference loop.
62
+
63
+ ### Fixed
64
+
65
+ - Load the native OpenCode capability plugin through its supported single-export
66
+ shape, run its coordinator with Node under OpenCode's Bun host, accept the
67
+ pinned runtime event envelope, and settle terminal callbacks before the host
68
+ can exit.
69
+ - Layer `CAIRN_EXTRA_SETTINGS` through OpenCode's supported
70
+ `OPENCODE_CONFIG` environment seam and keep headless graph commands in the
71
+ tool-calling path until their real owner operation completes.
72
+ - Pin the Claude Code 2.1.220 `PostCompact` `prompt_id` payload shape alongside
73
+ the retained 2.1.219 adapter while continuing to refuse unknown versions.
74
+ - Preserve redacted Pi provider failures as trajectory system events and treat
75
+ a closed trajectory CLI output pipe as a normal consumer exit.
76
+ - Make the audit script executable so its rendered systemd service target can
77
+ run directly.
78
+ - Bind evaluation note distillation to the admitted task workspace so nested
79
+ task trajectories cannot be missed or replaced by a source-root fallback.
80
+ - Upgrade the MCP SDK to 1.30.0 so both package graphs use the upstream-owned,
81
+ patched Hono transport and report zero production vulnerabilities.
82
+
83
+ Trajectory/compaction capture, artifact access, and note distillation/enrichment remain disabled by default. Existing
84
+ MCP tools, memory database paths, and remote HTTP behavior are unchanged. No
85
+ memory-quality or efficiency improvement is claimed before the evaluation
86
+ harness measures it.
87
+
6
88
  ## [2.3.1] - 2026-07-25
7
89
 
8
90
  ### Fixed
@@ -129,6 +211,9 @@ stored memories.
129
211
  - Preserve executable permissions and Bash 3.2 portability on macOS.
130
212
  - Add backup-first uninstall and SQLite-safe memory export/import guidance.
131
213
 
214
+ [Unreleased]: https://github.com/cairnkeep/cairnkeep/compare/v2.5.0...HEAD
215
+ [2.5.0]: https://github.com/cairnkeep/cairnkeep/compare/v2.4.0...v2.5.0
216
+ [2.4.0]: https://github.com/cairnkeep/cairnkeep/compare/v2.3.1...v2.4.0
132
217
  [2.3.1]: https://github.com/cairnkeep/cairnkeep/compare/v2.3.0...v2.3.1
133
218
  [2.3.0]: https://github.com/cairnkeep/cairnkeep/compare/v2.2.1...v2.3.0
134
219
  [2.2.1]: https://github.com/cairnkeep/cairnkeep/compare/v2.2.0...v2.2.1
package/README.md CHANGED
@@ -9,15 +9,17 @@
9
9
  A *cairn* is a stack of stones left as a trail marker for whoever follows; a
10
10
  *keep* is where you store what matters. **Cairnkeep** is where coding agents
11
11
  stack durable memory — decisions, pitfalls, patterns — and follow the trail
12
- across sessions, projects, and harnesses (Claude Code, OpenCode, …).
12
+ across sessions, projects, and harnesses (Claude Code, OpenCode, Kimi Code,
13
+ Pi, and other MCP clients).
13
14
 
14
15
  ## Status
15
16
 
16
- Shipped: the memory server, the `cairn` CLI (`bootstrap`, `memory-server`, `sync`,
17
- `doctor`, `memory`, `audit-timer`, `completion`, `uninstall`) installable via
17
+ Shipped: the memory server, the `cairn` CLI (`bootstrap`, `memory-server`, `sync`, `sync-pi`,
18
+ `doctor`, `trajectory`, `artifact`, `notes`, `memory`, `eval`, `audit-timer`, `completion`, `uninstall`) installable via
18
19
  `npm i -g @cairnkeep/cli`, and the
19
20
  operating layer (commands,
20
- agents, hooks) installed on both Claude Code and OpenCode. The generic launchers
21
+ agents, hooks) installed on Claude Code and OpenCode, plus a native Pi
22
+ trajectory extension. The generic launchers
21
23
  expose wrapper seams (`.ai/pre-launch.sh`, `CAIRN_EXTRA_SETTINGS`,
22
24
  `.ai/post-exit.sh`) so an enterprise wrapper can add provider/credential setup
23
25
  without forking them. Also shipped: context exploration (`/context-explore`) and
@@ -35,6 +37,8 @@ and 20 are end-of-life upstream.
35
37
  |---|---|
36
38
  | Claude Code on Linux/macOS | Memory server plus commands, agents, hooks, and launchers |
37
39
  | OpenCode on Linux/macOS | Memory server plus commands, plugins, hooks, and launchers |
40
+ | Kimi Code on Linux/macOS | Memory MCP server plus project launcher; no Cairnkeep operating-layer assets yet |
41
+ | Pi on Linux/macOS | Native opt-in trajectory extension and launcher; no bundled MCP bridge |
38
42
  | Codex CLI | Memory MCP server; no Cairnkeep operating-layer assets |
39
43
  | Other MCP clients | Memory and optional domain-knowledge MCP tools |
40
44
  | Native Windows | Not supported by the Bash-based installer; use WSL (not yet CI-verified) |
@@ -42,6 +46,9 @@ and 20 are end-of-life upstream.
42
46
  Linux, macOS, Bash 3.2 portability, and clean Node 22/24/26 runtime checks
43
47
  are exercised in CI.
44
48
 
49
+ See [Harness compatibility](docs/harness-compatibility.md) for exact support
50
+ levels, Kimi configuration, and candidates that are not yet runtime-tested.
51
+
45
52
  ## Try with Podman
46
53
 
47
54
  The minimal OCI image runs the memory server as an unprivileged user and keeps
@@ -67,10 +74,17 @@ storage paths, secrets, and private derived images, see
67
74
  embedding-ranked search against any OpenAI-compatible endpoint.
68
75
  - **`bin/cairn`** — the CLI. `cairn bootstrap [path]` scaffolds a project's
69
76
  `.ai/` launchers + env; `cairn doctor` health-checks the configured pieces;
77
+ `cairn trajectory list|show|prune` manages opt-in local session trajectories;
78
+ `cairn artifact list|show|delete|prune` manages opt-in local artifacts;
79
+ `cairn eval validate|run|ablate|report|prune|delete` coordinates explicit
80
+ local two-pass and one-capability-at-a-time measurements;
81
+ `cairn notes distill|search-error|promote|doctor` compiles and searches
82
+ default-off local hindsight notes outside the online agent path;
70
83
  `cairn memory export|import` relocates the durable store between machines
71
84
  (`export` requires the optional `sqlite3` CLI);
72
85
  `cairn audit-timer` installs the scheduled memory+wiki audit;
73
86
  `cairn completion bash|zsh|fish` generates shell completion definitions; and
87
+ `cairn sync-pi` installs the native Pi trajectory extension;
74
88
  `cairn uninstall` reverses the install (backup-first, revertible).
75
89
  - **`templates/`** — project scaffolding (generic launchers, env) plus the
76
90
  derived-knowledge layer (wiki, alignment, graph, security, planning).
@@ -139,6 +153,41 @@ patterns/transactional-migrations: Use transactional migrations for schema chang
139
153
  The exact command rendering depends on the client. Any MCP client can call
140
154
  `memory_write` and `memory_search` directly.
141
155
 
156
+ For Kimi Code, bootstrap supplies a launcher and Kimi reads the repository's
157
+ MCP configuration. A local stdio entry uses `cairn memory-server`. Remote Kimi
158
+ configuration requires a literal URL and supports a token reference through
159
+ `bearerTokenEnvVar`; it does not expand `${VAR}` in the URL. See
160
+ [Harness compatibility](docs/harness-compatibility.md#kimi-code).
161
+
162
+ For Pi, install the local trajectory adapter and use the scaffolded launcher:
163
+
164
+ ```bash
165
+ cairn sync-pi --apply
166
+ cairn bootstrap /path/to/project
167
+ cd /path/to/project && ./.ai/start-pi.sh
168
+ ```
169
+
170
+ This installs trajectory capture only. Cairnkeep does not bundle or select a Pi
171
+ MCP bridge; configure a user-chosen bridge separately if you also want the MCP
172
+ memory tools inside Pi.
173
+
174
+ Closed trajectories can be compiled into local hindsight notes without a model
175
+ or embedding service. Capture and distillation are separate opt-ins:
176
+
177
+ ```bash
178
+ export CAIRN_TRAJECTORY_CAPTURE=1 # affects future closed sessions
179
+ export CAIRN_NOTE_DISTILLATION=1
180
+ cairn notes distill --project /path/to/project --json
181
+ printf '%s\n' 'TypeError: example' | cairn notes search-error --project /path/to/project --json
182
+ ```
183
+
184
+ Notes keep deterministic failure, resolution, abandonment, and recurrence
185
+ history under `~/.cairnkeep/notes/`. Shared promotion is never automatic: it
186
+ requires compatible evidence from two distinct projects and an explicit
187
+ `cairn notes promote NOTE-ID --with NOTE-ID --confirm`. See the
188
+ [operating guide](docs/operating.md#hindsight-notes-opt-in) before enabling
189
+ optional model enrichment.
190
+
142
191
  Prefer working from a clone? Build the server with `cd mcp-memory-server && npm
143
192
  install && npm run build`, then use `scripts/sync-claude-assets.sh` and
144
193
  `bin/cairn` in place of the installed `cairn`.
@@ -159,8 +208,9 @@ unless a wrapper opts in:
159
208
  export env (e.g. a provider base URL) or abort the launch by returning
160
209
  non-zero. This is where credential refresh / connectivity setup lives.
161
210
  - **`CAIRN_EXTRA_SETTINGS`** — path to a settings file layered onto the harness
162
- (`--settings` for Claude Code, `--config` for OpenCode). Process env still
163
- wins, so an inline value beats the profile.
211
+ (`--settings` for Claude Code, `OPENCODE_CONFIG` for OpenCode). Pi has no equivalent
212
+ generic settings-file flag, so its launcher leaves this variable unused.
213
+ Process env still wins, so an inline value beats the profile.
164
214
  - **`.ai/post-exit.sh`** — sourced after the harness exits, with
165
215
  `CAIRN_EXIT_STATUS` set to its exit code.
166
216
 
@@ -188,6 +238,37 @@ search):
188
238
  | `CAIRN_MEMORY_EMBEDDING_MODEL` | Embedding model name (required for semantic search) |
189
239
  | `CAIRN_MEMORY_EMBEDDING_TIMEOUT_MS` | Embedding request timeout before substring fallback (default `15000`) |
190
240
  | `CAIRN_AGENTFS_BASE_DIR` | Base dir for global memory scopes (default `~/.cairnkeep`) |
241
+ | `CAIRN_TRAJECTORY_CAPTURE` | Opt in to local structured session capture (`1`, `true`, `yes`, or `on`; default off) |
242
+ | `CAIRN_TRAJECTORY_SESSION_MAX_BYTES` | Maximum serialized bytes per captured session (default `5242880`, 5 MiB) |
243
+ | `CAIRN_TRAJECTORY_STORE_MAX_BYTES` | Maximum logical bytes across local trajectories (default `268435456`, 256 MiB) |
244
+ | `CAIRN_TRAJECTORY_RETENTION_DAYS` | Retain captured sessions for this many days (default `30`) |
245
+ | `CAIRN_TRAJECTORY_REDACTION_FILE` | Optional project-contained redaction config (default `.ai/trajectory-redaction.json` when present) |
246
+ | `CAIRN_REDACTION_FILE` | Optional project-contained redaction config shared by trajectories and artifacts (falls back to `CAIRN_TRAJECTORY_REDACTION_FILE`) |
247
+ | `CAIRN_COMPACTION_CAPTURE` | Opt in to local harness-produced compaction capture and fresh-session recovery (off by default) |
248
+ | `CAIRN_ARTIFACT_STORE` | Expose four local stdio artifact tools (off by default; independent of compaction capture) |
249
+ | `CAIRN_ARTIFACT_HTTP` | Separately consent to artifact tools over authenticated HTTP (off by default; also requires `CAIRN_ARTIFACT_STORE`) |
250
+ | `CAIRN_ARTIFACT_MAX_BYTES` | Maximum stored bytes per artifact (default `1048576`, 1 MiB) |
251
+ | `CAIRN_ARTIFACT_SESSION_MAX_BYTES` | Maximum logical bytes per artifact session (default `16777216`, 16 MiB) |
252
+ | `CAIRN_ARTIFACT_STORE_MAX_BYTES` | Maximum logical bytes in one artifact store (default `268435456`, 256 MiB) |
253
+ | `CAIRN_ARTIFACT_RETENTION_DAYS` | Artifact age retention (default `30`) |
254
+ | `CAIRN_COMPACTION_MAX_REVISIONS` | Retained compaction revisions per session (default `8`) |
255
+ | `CAIRN_ARTIFACT_GENERATED_FILE_SNAPSHOT_MAX_BYTES` | Generated-file inline snapshot cap (default `262144`, 256 KiB; lower if the artifact cap is lower) |
256
+ | `CAIRN_NOTE_DISTILLATION` | Opt in to one-shot/scheduled local note distillation and lookup (default off) |
257
+ | `CAIRN_NOTE_ENRICHMENT` | Separately opt in to remote/local prose enrichment; never implied by credentials (default off) |
258
+ | `CAIRN_NOTE_ENRICHMENT_MODEL` | Explicit chat model for optional note enrichment (no default) |
259
+ | `CAIRN_NOTE_ENRICHMENT_TIMEOUT_MS` | Optional enrichment timeout (default `15000`) |
260
+ | `CAIRN_TYPED_MEMORY_NODES` | Opt in to typed metadata, filters, logical note address spaces, and `memory_import` (default off; restart the server after changing) |
261
+ | `CAIRN_CAPABILITY_CONTRACT` | Opt in to the managed eight-capability contract (default off) |
262
+ | `CAIRN_CAPABILITY_LOGGING` | Override payload-free local callback logging (`1`/`0`; default off) |
263
+ | `CAIRN_CAPABILITY_MEMORY_WRITE` | Override `memory.write` for this process |
264
+ | `CAIRN_CAPABILITY_MEMORY_SEARCH` | Override `memory.search` for this process |
265
+ | `CAIRN_CAPABILITY_NOTES_DISTILL` | Override `notes.distill` for this process |
266
+ | `CAIRN_CAPABILITY_WIKI` | Override `wiki` for this process |
267
+ | `CAIRN_CAPABILITY_GRAPH` | Override `graph` for this process |
268
+ | `CAIRN_CAPABILITY_SECURITY_AUDIT` | Override `security.audit` for this process |
269
+ | `CAIRN_CAPABILITY_ROUTE_CHECK` | Override `route.check` for this process |
270
+ | `CAIRN_CAPABILITY_CONTEXT_EXPLORE` | Override `context.explore` for this process |
271
+ | `CAIRN_EVAL` | Opt in to the local evaluation coordinator (default off; does not select a harness, model, endpoint, credential, or network) |
191
272
  | `CAIRN_GIT_PROVIDER` | Git host for collaboration commands: `github`\|`gitlab`\|`codeberg`\|`forgejo`\|`none` ([docs/git-providers.md](docs/git-providers.md)) |
192
273
  | `CAIRN_ROUTE_ENDPOINT` | Base URL of an already-running token-miser routing/tiering proxy (unset → `route_check` is inert) |
193
274
  | `CAIRN_EXPLORE_BINARY` | Absolute path to the `token_miser` binary used by `context_explore` (unset → the tool throws) |
@@ -203,6 +284,151 @@ remote memory, and delegated exploration can send content to endpoints you
203
284
  configure. Review [Privacy and data flow](docs/privacy-and-data-flow.md) before
204
285
  enabling them.
205
286
 
287
+ ### Managed capability contract (opt-in)
288
+
289
+ `CAIRN_CAPABILITY_CONTRACT=1` enables one versioned, typed boundary around the
290
+ existing capability owners. The master flag and callback logging are both off
291
+ by default. With the master flag unset, MCP registration, installed
292
+ Markdown/workflow bytes, commands, processes, configuration reads, logging,
293
+ output, and performance remain on the legacy path.
294
+
295
+ | Capability ID | Kind | Existing owner | Compatibility default | Process override |
296
+ |---|---|---|---:|---|
297
+ | `memory.write` | MCP tool | `memory_write` | on | `CAIRN_CAPABILITY_MEMORY_WRITE` |
298
+ | `memory.search` | MCP tool | `memory_search` | on | `CAIRN_CAPABILITY_MEMORY_SEARCH` |
299
+ | `notes.distill` | offline job | note distillation CLI/timer | off | `CAIRN_CAPABILITY_NOTES_DISTILL` |
300
+ | `wiki` | operating workflow | installed wiki commands/workflows | on | `CAIRN_CAPABILITY_WIKI` |
301
+ | `graph` | operating workflow | installed graph commands | inherited from `graphify.enabled`, otherwise off | `CAIRN_CAPABILITY_GRAPH` |
302
+ | `security.audit` | operating workflow | installed security commands/workflows | on | `CAIRN_CAPABILITY_SECURITY_AUDIT` |
303
+ | `route.check` | MCP tool | existing token-miser routing delegate | on | `CAIRN_CAPABILITY_ROUTE_CHECK` |
304
+ | `context.explore` | MCP tool | existing token-miser exploration delegate | on | `CAIRN_CAPABILITY_CONTEXT_EXPLORE` |
305
+
306
+ Manage project overrides in `.ai/capabilities.json` through the CLI rather than
307
+ editing JSON:
308
+
309
+ ```bash
310
+ CAIRN_CAPABILITY_CONTRACT=1 cairn capabilities list
311
+ CAIRN_CAPABILITY_CONTRACT=1 cairn capabilities status --json
312
+ CAIRN_CAPABILITY_CONTRACT=1 cairn capabilities disable context.explore
313
+ CAIRN_CAPABILITY_CONTRACT=1 cairn capabilities reset context.explore
314
+ CAIRN_CAPABILITY_CONTRACT=1 cairn capabilities logging enable
315
+ ```
316
+
317
+ Process overrides take precedence over project values, which take precedence
318
+ over compatibility defaults. `reset` removes an explicit override and restores
319
+ the inherited state; it does not mean enabled. Changes to the four MCP
320
+ capabilities (`memory.write`, `memory.search`, `route.check`, and
321
+ `context.explore`) require a fresh memory-server start and disabled tools are
322
+ omitted from `tools/list`. Offline and operating workflows resolve state at
323
+ each invocation. Enabling the master does not enable `notes.distill` or graph
324
+ when their compatibility inputs remain off.
325
+
326
+ Callback records are local, payload-free operating evidence, not evaluation
327
+ results or telemetry. They require the contract, callback logging, and local
328
+ trajectory capture to be enabled together. Cairnkeep makes no capability gain,
329
+ memory-quality, or efficiency claim from this instrumentation; Phase 19 must
330
+ first create an explicit all-enabled baseline and measure one-disabled states.
331
+ See the [operating guide](docs/operating.md#managed-capability-contract-opt-in),
332
+ [storage contract](docs/storage.md#capability-callback-storage), and
333
+ [privacy flow](docs/privacy-and-data-flow.md#capability-callback-flow).
334
+
335
+ ### Evaluation harness (opt-in, measurement only)
336
+
337
+ `CAIRN_EVAL=1` enables `cairn eval` for a committed schema-v1 task set and an
338
+ explicit operator-owned adapter program. `validate` resolves immutable inputs,
339
+ limits, commands, digests, output containment, and a deterministic serial
340
+ schedule without invoking the adapter. `run` measures from-scratch Run 1 versus
341
+ a fresh Run 2 that can see only its own task's immutable note snapshot.
342
+ `ablate` measures an explicit all-eight-capabilities-on baseline against one
343
+ exactly-one-disabled treatment, with both passes in both arms. Execution prints
344
+ the invocation estimate and requires `--yes`; there is no retry, inference,
345
+ provider, endpoint, credential, model, or network default.
346
+
347
+ The canonical local JSON report records optional reported turns/tokens/cost,
348
+ independently verified pass state, full and note-eligible populations,
349
+ missingness, paired deltas, capability/notes/revision digests, and seeded
350
+ bootstrap uncertainty. Turns compare only under exact matching turn-semantics
351
+ compatibility IDs; token totals are never inferred and unknown verifier results
352
+ are never counted as pass or fail. Reports describe estimates and
353
+ inconclusive/small-sample conditions; they do not claim causality,
354
+ significance, quality change, or efficiency change.
355
+
356
+ The bundled fake task set and adapter are deterministic, offline framework
357
+ fixtures. They cannot support a Cairnkeep performance statement. Any future
358
+ efficiency statement requires a separate committed `live-evaluation` artifact
359
+ whose source, task-set, report, schema, note-snapshot, missingness, runtime, and
360
+ claim anchors all pass `scripts/verify-phase19-runtime-evidence.mjs`. No such
361
+ live claim or evidence artifact is shipped here, and external paper calibration
362
+ is neither a Cairnkeep result nor an acceptance target.
363
+
364
+ Reports and note snapshots stay under
365
+ `.agentfs/eval/experiments/<experiment-id>/`; default uninstall retains them,
366
+ while explicit purge is backup-first and revertible. Cancellation retains an
367
+ inspectable partial report. POSIX process-group cleanup is tested; Windows
368
+ descendant-process-tree termination is not guaranteed. See the
369
+ [operating guide](docs/operating.md#evaluation-harness-opt-in),
370
+ [storage contract](docs/storage.md#evaluation-report-and-note-snapshot-storage),
371
+ and [privacy flow](docs/privacy-and-data-flow.md#evaluation-adapter-and-report-flow).
372
+
373
+ ### Typed memory nodes and structured import (opt-in)
374
+
375
+ `CAIRN_TYPED_MEMORY_NODES=1` adds schema-v1 `node_type`, canonical `tags`,
376
+ `address_space`, hard list/search filters (`node_types`, `tags_all`,
377
+ `tags_any`), and exactly one new MCP tool: `memory_import`. The flag is read
378
+ when each server/session is created, so restart after changing it. Unset keeps
379
+ the existing 14 tools, schemas, response roots, raw values, files, processes,
380
+ network behavior, and output unchanged. It adds no read/edit/update/delete
381
+ aliases; `memory_supersede` remains the sole edit operation.
382
+
383
+ Core types are `memory`, `knowledge`, `hindsight`, `shared`, and `provenance`;
384
+ extensions use a namespaced form such as `team:runbook`. Tags are ASCII
385
+ lowercase, with whitespace and underscores normalized to hyphens, deduplicated,
386
+ and sorted. Filters are applied before ranking. Exact key/tag/type hits precede
387
+ semantic results; without a working configured embedding endpoint, search uses
388
+ stable local substring matching.
389
+
390
+ `memory_import` accepts one inline schema-v1 batch for one concrete scope and
391
+ one `memory`, `project-notes`, or `shared-notes` address. It supports 1–256
392
+ unique relative keys, values up to 256 KiB each and 5 MiB total, optional
393
+ `import_id`, `dry_run`, and `conflict_policy: reject|supersede`. Results contain
394
+ only digests, counts, keys, and actions—never supplied values. The default
395
+ reject policy cannot overwrite; explicit supersede preserves typed history.
396
+ Note addresses require `scope: project` and logical keys, never filesystem
397
+ paths. See the [operating guide](docs/operating.md#typed-memory-nodes-and-note-address-spaces-opt-in).
398
+
399
+ ### Compaction continuity and artifacts (opt-in)
400
+
401
+ `CAIRN_COMPACTION_CAPTURE=1` captures only harness-produced compaction summaries
402
+ from the pinned Claude Code `PostCompact` 2.1.219 and 2.1.220 payloads and
403
+ OpenCode `session.compacted` 1.17.20 seam. It stores redacted immutable revisions locally and injects the latest
404
+ valid structured goals, decisions, TODOs, and critical errors when a session
405
+ starts. A resumed session is preferred; a fresh session falls back to the
406
+ newest valid project summary and marks stale context for validation. The raw
407
+ redacted summary is available only through an explicit read. Cairnkeep never
408
+ generates or triggers compaction.
409
+
410
+ `CAIRN_ARTIFACT_STORE=1` independently exposes exactly four stdio MCP tools:
411
+ `artifact_write`, `artifact_read`, `artifact_list`, and `artifact_delete`.
412
+ The initial kinds are exactly `compaction_summary`, `diff`, `test_output`, and
413
+ `generated_file`. Generated-file paths are labels only; the server never reads
414
+ them. Operators can inspect retained local data whether or not MCP writes are
415
+ currently enabled:
416
+
417
+ ```bash
418
+ cairn artifact list --kind compaction_summary --json
419
+ cairn artifact show ARTIFACT-ID --json
420
+ cairn artifact delete ARTIFACT-ID --dry-run --json
421
+ cairn artifact prune --dry-run --include-protected --json
422
+ ```
423
+
424
+ Both capabilities are default-off, local, and require no key, model, endpoint,
425
+ telemetry, or network. Remote tools require double consent:
426
+ `CAIRN_ARTIFACT_STORE=1` plus `CAIRN_ARTIFACT_HTTP=1`, as well as the existing
427
+ bearer, `Host`, and validated `X-Cairn-Project` controls. Artifact HTTP never
428
+ exposes trajectory data. See the [operating guide](docs/operating.md#compaction-continuity-and-artifacts-opt-in),
429
+ [storage contract](docs/storage.md#artifact-storage), and
430
+ [privacy flows](docs/privacy-and-data-flow.md#compaction-and-artifact-flows).
431
+
206
432
  ## More
207
433
 
208
434
  - **Guided learning paths and video scripts** — [docs/learning/README.md](docs/learning/README.md)
package/bin/cairn CHANGED
@@ -32,8 +32,30 @@ Usage:
32
32
  Install/update the Claude Code operating layer (commands,
33
33
  agents, hooks) into ~/.claude, or into DIR for a single
34
34
  project (--live-root <project>/.claude).
35
+ cairn sync-pi [--apply] [--live-root DIR]
36
+ Install/update the Pi trajectory extension into
37
+ ~/.pi/agent, or into DIR for an isolated Pi root.
35
38
  cairn doctor Health-check the memory server and configured LLM /
36
- embedding endpoints, git provider, and store.
39
+ embedding endpoints, git provider, and stores.
40
+ Use --repair for derived trajectory/typed metadata,
41
+ indexes, and interrupted note transactions.
42
+ cairn trajectory <list|show|prune>
43
+ Inspect and prune opt-in local session trajectories.
44
+ cairn artifact <list|show|delete|prune>
45
+ Inspect and delete opt-in local artifacts. List/show
46
+ support --json; delete supports --dry-run; prune
47
+ supports --dry-run and --include-protected.
48
+ cairn capabilities <list|status|enable|disable|reset|logging>
49
+ Inspect and manage project capability state. MCP changes
50
+ require a memory-server restart; operating changes apply
51
+ on the next invocation.
52
+ cairn notes <distill|search-error|promote|doctor>
53
+ Compile closed trajectories into local hindsight notes,
54
+ search exact failure signatures, promote corroborated
55
+ notes, or validate the note store.
56
+ cairn eval <validate|run|ablate|report|prune|delete>
57
+ Validate or run default-off local evaluations, inspect
58
+ reports, and manage retained experiment results.
37
59
  cairn memory <path|export|import>
38
60
  Relocate global-scope memory. `path` prints its location;
39
61
  `export <file.tgz>` takes a WAL-safe snapshot (requires
@@ -41,7 +63,8 @@ Usage:
41
63
  cairn audit-timer [--on-calendar SPEC] [--para-root PATH] [--render-only DIR]
42
64
  Install (opt-in) a systemd user timer that runs the
43
65
  memory+wiki audit on a schedule; prints a cron alternative.
44
- cairn uninstall [--dry-run] [--yes] [--purge-memory] [--live-root DIR] [PROJECT ...]
66
+ cairn uninstall [--dry-run] [--yes] [--purge-memory] [--live-root DIR]
67
+ [--pi-live-root DIR] [PROJECT ...]
45
68
  Reverse what cairnkeep installed/modified (operating
46
69
  layer, hook registrations, MCP registration, audit
47
70
  timer, and any given projects' bootstrap scaffold).
@@ -72,9 +95,27 @@ case "$cmd" in
72
95
  # claude mcp add cairn-memory -s user -- cairn memory-server
73
96
  exec node "$CAIRN_ROOT/mcp-memory-server/dist/index.js" "$@"
74
97
  ;;
98
+ trajectory)
99
+ exec node "$CAIRN_ROOT/mcp-memory-server/dist/trajectory-cli.js" "$@"
100
+ ;;
101
+ artifact)
102
+ exec node "$CAIRN_ROOT/mcp-memory-server/dist/artifact-cli.js" "$@"
103
+ ;;
104
+ capabilities)
105
+ exec node "$CAIRN_ROOT/mcp-memory-server/dist/capability-cli.js" "$@"
106
+ ;;
107
+ notes)
108
+ exec node "$CAIRN_ROOT/mcp-memory-server/dist/note-cli.js" "$@"
109
+ ;;
110
+ eval)
111
+ exec node "$CAIRN_ROOT/mcp-memory-server/dist/eval-cli.js" "$@"
112
+ ;;
75
113
  sync)
76
114
  exec "$CAIRN_ROOT/scripts/sync-claude-assets.sh" "$@"
77
115
  ;;
116
+ sync-pi)
117
+ exec "$CAIRN_ROOT/scripts/sync-pi-assets.sh" "$@"
118
+ ;;
78
119
  audit-timer)
79
120
  exec "$CAIRN_ROOT/scripts/install-audit-timer.sh" "$@"
80
121
  ;;
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env bash
2
+ # Stop/StopFailure/CwdChanged/SessionEnd hook: lifecycle settlement transport.
3
+ set -euo pipefail
4
+
5
+ case "${CAIRN_CAPABILITY_CONTRACT:-}" in
6
+ 1|true|TRUE|yes|YES|on|ON) ;;
7
+ *) exit 0 ;;
8
+ esac
9
+
10
+ INFRA_ROOT="@@INFRA_ROOT@@"
11
+ COORDINATOR="$INFRA_ROOT/mcp-memory-server/dist/capability-cli.js"
12
+ [ -f "$COORDINATOR" ] || exit 0
13
+
14
+ input=$(cat) || exit 0
15
+ validated=$(printf '%s' "$input" | python3 /dev/fd/3 3<<'PY'
16
+ import json
17
+ import re
18
+ import sys
19
+
20
+ common = {"session_id", "transcript_path", "cwd", "hook_event_name"}
21
+ optional = {"prompt_id", "permission_mode", "effort", "agent_id", "agent_type"}
22
+ shapes = {
23
+ "Stop": ({"stop_hook_active", "last_assistant_message", "background_tasks", "session_crons"}, set()),
24
+ "StopFailure": ({"error"}, {"error_details", "last_assistant_message"}),
25
+ "CwdChanged": ({"old_cwd", "new_cwd"}, set()),
26
+ "SessionEnd": ({"reason"}, set()),
27
+ }
28
+ errors = {
29
+ "rate_limit", "overloaded", "authentication_failed", "oauth_org_not_allowed",
30
+ "billing_error", "invalid_request", "model_not_found", "server_error",
31
+ "max_output_tokens", "unknown",
32
+ }
33
+ reasons = {"clear", "resume", "logout", "prompt_input_exit", "bypass_permissions_disabled", "other"}
34
+
35
+ try:
36
+ value = json.load(sys.stdin)
37
+ if not isinstance(value, dict) or not common <= set(value):
38
+ raise ValueError
39
+ event = value["hook_event_name"]
40
+ if event not in shapes:
41
+ raise ValueError
42
+ event_required, event_optional = shapes[event]
43
+ required = common | event_required
44
+ if not required <= set(value) or set(value) - required - optional - event_optional:
45
+ raise ValueError
46
+ for name in ("session_id", "transcript_path", "cwd"):
47
+ if not isinstance(value[name], str):
48
+ raise ValueError
49
+ session = value["session_id"]
50
+ if not isinstance(session, str) or not re.fullmatch(r"(?:cairn:)?[A-Za-z0-9][A-Za-z0-9._:-]{0,255}", session) or session == "unknown":
51
+ raise ValueError
52
+ if event == "Stop":
53
+ if not isinstance(value["stop_hook_active"], bool) or not isinstance(value["last_assistant_message"], str):
54
+ raise ValueError
55
+ if not isinstance(value["background_tasks"], list) or not isinstance(value["session_crons"], list):
56
+ raise ValueError
57
+ operation, body = "harness-terminal", {"outcome": "success"}
58
+ elif event == "StopFailure":
59
+ if value["error"] not in errors:
60
+ raise ValueError
61
+ operation, body = "harness-terminal", {"outcome": "error"}
62
+ elif event == "SessionEnd":
63
+ if value["reason"] not in reasons:
64
+ raise ValueError
65
+ operation, body = "harness-terminal", {"outcome": "abandoned"}
66
+ else:
67
+ if not isinstance(value["old_cwd"], str) or not isinstance(value["new_cwd"], str):
68
+ raise ValueError
69
+ operation, body = "harness-cwd", {"old_cwd": value["old_cwd"], "new_cwd": value["new_cwd"]}
70
+ body.update({"schema_version": 1, "harness": "claude-code", "session_id": session})
71
+ print(operation)
72
+ print(json.dumps(body, separators=(",", ":")))
73
+ except Exception:
74
+ sys.exit(1)
75
+ PY
76
+ ) || exit 0
77
+
78
+ operation=${validated%%$'\n'*}
79
+ payload=${validated#*$'\n'}
80
+ printf '%s' "$payload" | node "$COORDINATOR" "$operation" >/dev/null 2>&1 || true
81
+ exit 0
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env bash
2
+ # UserPromptExpansion hook: deterministic operating-capability admission.
3
+ set -euo pipefail
4
+
5
+ case "${CAIRN_CAPABILITY_CONTRACT:-}" in
6
+ 1|true|TRUE|yes|YES|on|ON) ;;
7
+ *) printf '{}\n'; exit 0 ;;
8
+ esac
9
+
10
+ INFRA_ROOT="@@INFRA_ROOT@@"
11
+ COORDINATOR="$INFRA_ROOT/mcp-memory-server/dist/capability-cli.js"
12
+ FIXED_BLOCK='{"decision":"block","reason":"capability disabled"}'
13
+
14
+ block() {
15
+ printf '%s\n' "$FIXED_BLOCK"
16
+ exit 2
17
+ }
18
+
19
+ [ -f "$COORDINATOR" ] || block
20
+ input=$(cat) || block
21
+
22
+ validated=$(printf '%s' "$input" | python3 /dev/fd/3 "$PWD" 3<<'PY'
23
+ import json
24
+ import os
25
+ import re
26
+ import sys
27
+
28
+ required = {
29
+ "session_id", "transcript_path", "cwd", "hook_event_name",
30
+ "expansion_type", "command_name", "command_args", "command_source", "prompt",
31
+ }
32
+ optional = {"prompt_id", "permission_mode", "effort", "agent_id", "agent_type"}
33
+ commands = {"wiki-ingest", "wiki-query", "wiki-lint", "graphify", "security-audit"}
34
+
35
+ try:
36
+ value = json.load(sys.stdin)
37
+ if not isinstance(value, dict) or set(value) - required - optional or not required <= set(value):
38
+ raise ValueError
39
+ if value["hook_event_name"] != "UserPromptExpansion":
40
+ raise ValueError
41
+ if value["expansion_type"] not in {"slash_command", "mcp_prompt"}:
42
+ raise ValueError
43
+ if value["command_name"] not in commands:
44
+ raise ValueError
45
+ for name in required - {"hook_event_name", "expansion_type"}:
46
+ if not isinstance(value[name], str):
47
+ raise ValueError
48
+ session = value["session_id"]
49
+ if not isinstance(session, str) or not re.fullmatch(r"(?:cairn:)?[A-Za-z0-9][A-Za-z0-9._:-]{0,255}", session) or session == "unknown":
50
+ raise ValueError
51
+ event_root = os.path.realpath(value["cwd"])
52
+ process_root = os.path.realpath(sys.argv[1])
53
+ if not os.path.isabs(value["cwd"]) or event_root != process_root or not os.path.isdir(event_root):
54
+ raise ValueError
55
+ print(json.dumps({
56
+ "schema_version": 1,
57
+ "harness": "claude-code",
58
+ "command": value["command_name"],
59
+ "session_id": session,
60
+ "project_root": event_root,
61
+ }, separators=(",", ":")))
62
+ except Exception:
63
+ sys.exit(1)
64
+ PY
65
+ ) || block
66
+
67
+ decision=$(printf '%s' "$validated" | node "$COORDINATOR" harness-before 2>/dev/null) || {
68
+ printf '{}\n'
69
+ exit 0
70
+ }
71
+
72
+ case "$decision" in
73
+ '{"schema_version":1,"decision":"block","reason":"capability-disabled"}') block ;;
74
+ '{"schema_version":1,"decision":"allow"}') printf '{}\n' ;;
75
+ *) printf '{}\n' ;;
76
+ esac
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bash
2
+ # PostCompact hook: persist the harness-produced compaction summary locally.
3
+ # The hook never generates or changes a summary and always fails open.
4
+ set -euo pipefail
5
+
6
+ case "${CAIRN_COMPACTION_CAPTURE:-}" in
7
+ 1|true|TRUE|yes|YES|on|ON) ;;
8
+ *) exit 0 ;;
9
+ esac
10
+
11
+ repo="$(pwd)"
12
+ ARTIFACT_ENTRY="@@INFRA_ROOT@@/mcp-memory-server/dist/artifact-cli.js"
13
+ [ -f "$ARTIFACT_ENTRY" ] || exit 0
14
+
15
+ # The payload has no version field. Resolve the running harness locally so an
16
+ # unpinned future shape cannot be accepted merely because its keys look known.
17
+ harness_version=$(claude --version 2>/dev/null | sed -nE 's/[^0-9]*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' | head -1)
18
+ [ -n "$harness_version" ] || harness_version=unknown
19
+
20
+ # Forward the documented PostCompact payload unchanged. The local CLI owns
21
+ # shape validation, redaction, and persistence; this boundary stays bounded
22
+ # and cannot block or fail the harness event.
23
+ node "$ARTIFACT_ENTRY" capture-claude "$repo" --harness-version "$harness_version" <&0 >/dev/null 2>&1 &
24
+ capture_pid=$!
25
+ (
26
+ sleep 3
27
+ kill -KILL "$capture_pid" >/dev/null 2>&1 || true
28
+ ) &
29
+ watchdog_pid=$!
30
+ wait "$capture_pid" >/dev/null 2>&1 || true
31
+ kill "$watchdog_pid" >/dev/null 2>&1 || true
32
+ wait "$watchdog_pid" >/dev/null 2>&1 || true
33
+
34
+ exit 0