@cairnkeep/cli 2.19.1 → 2.20.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 (41) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/docs/README.md +3 -0
  3. package/docs/ecosystem.md +33 -0
  4. package/docs/harness-compatibility.md +1 -1
  5. package/docs/learning/CURRICULUM-MAP.md +4 -2
  6. package/docs/learning/FEATURE-GUIDE.md +15 -1
  7. package/docs/learning/README.md +1 -0
  8. package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
  9. package/docs/learning/lessons/L01-safe-trial.md +1 -1
  10. package/docs/learning/lessons/L02-installation.md +1 -1
  11. package/docs/learning/lessons/L03-first-project.md +1 -1
  12. package/docs/learning/lessons/L19-validated-skill-improvement.md +1 -1
  13. package/docs/learning/lessons/L20-mcp-tool-profiles.md +1 -1
  14. package/docs/learning/lessons/L21-context-packs.md +1 -1
  15. package/docs/learning/lessons/L22-native-windows.md +1 -1
  16. package/docs/learning/lessons/L23-guided-setup.md +1 -1
  17. package/docs/learning/lessons/L24-okf-exchange.md +1 -1
  18. package/docs/learning/lessons/L25-playbooks.md +1 -1
  19. package/docs/learning/lessons/L27-ecosystem-evidence.md +93 -0
  20. package/docs/learning/video-scripts/V27-ecosystem-evidence.md +80 -0
  21. package/docs/operating.md +10 -4
  22. package/docs/privacy-and-data-flow.md +13 -2
  23. package/docs/research/ecosystem-evidence-2026-10.md +113 -0
  24. package/examples/eval/README.md +7 -0
  25. package/examples/eval/bundled-fake.json +1 -1
  26. package/examples/eval/ecosystem-decisions.json +42 -0
  27. package/mcp-memory-server/dist/compaction-normalize.js +37 -6
  28. package/mcp-memory-server/scripts/fixtures/compaction-opencode-2.0.24.json +40 -0
  29. package/opencode/plugins/memory-capture.ts +354 -120
  30. package/opencode/plugins/memory-recall.ts +176 -57
  31. package/opencode/plugins/memory-wakeup.ts +147 -73
  32. package/package.json +1 -1
  33. package/scripts/lib/opencode-v2-plugin-harness.mjs +156 -0
  34. package/scripts/lib/trajectory-opencode-plugin-harness.mjs +6 -2
  35. package/scripts/spikes/run-clm-pi-evaluation.mjs +212 -0
  36. package/scripts/spikes/run-memfork-evaluation.mjs +153 -0
  37. package/scripts/spikes/run-ollaya-decision-evaluation.mjs +108 -0
  38. package/scripts/test-compaction-hooks.sh +6 -2
  39. package/scripts/test-ecosystem-spikes.sh +36 -0
  40. package/scripts/test-opencode-v2-plugins.sh +76 -0
  41. package/scripts/test-package-install.sh +9 -2
@@ -259,7 +259,10 @@ const client = { session: {
259
259
  messages: async (input) => { calls.push(["messages", input]); return { data: envelope.messages } },
260
260
  } }
261
261
  const capture = await import(`${pathToFileURL(capturePath).href}?capture=${Date.now()}`)
262
- const plugin = await capture.MemoryCapturePlugin({ client, directory: repo })
262
+ // Dual entrypoint (2026-09-08): named export (legacy) or default.server (V1/V2
263
+ // dual module); both resolve to the same V1 implementation this harness drives.
264
+ const captureFactory = capture.MemoryCapturePlugin ?? capture.default?.server
265
+ const plugin = await captureFactory({ client, directory: repo })
263
266
  await plugin.event({ event })
264
267
  if (mode === "disabled") {
265
268
  assert.deepEqual(calls, [])
@@ -282,7 +285,8 @@ const dollar = (...args) => {
282
285
  return { quiet() { return this }, nothrow: async () => ({ stdout: "" }) }
283
286
  }
284
287
  const wakeup = await import(`${pathToFileURL(wakeupPath).href}?wakeup=${Date.now()}`)
285
- const wakeupPlugin = await wakeup.MemoryWakeupPlugin({ $: dollar, directory: repo })
288
+ const wakeupFactory = wakeup.MemoryWakeupPlugin ?? wakeup.default?.server
289
+ const wakeupPlugin = await wakeupFactory({ $: dollar, directory: repo })
286
290
  const output = { system: [] }
287
291
  await wakeupPlugin["experimental.chat.system.transform"]({ sessionID: envelope.session.id }, output)
288
292
  if (mode === "disabled") {
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
5
+
6
+ for script in \
7
+ run-memfork-evaluation.mjs \
8
+ run-clm-pi-evaluation.mjs \
9
+ run-ollaya-decision-evaluation.mjs
10
+ do
11
+ node --check "$ROOT/scripts/spikes/$script"
12
+ done
13
+
14
+ node - "$ROOT/examples/eval/ecosystem-decisions.json" <<'NODE'
15
+ const fs = require("node:fs")
16
+ const data = JSON.parse(fs.readFileSync(process.argv[2], "utf8"))
17
+ if (data.schema_version !== 1 || data.id !== "cairnkeep-policy-decisions-v1") process.exit(1)
18
+ if (JSON.stringify(data.labels) !== JSON.stringify(["observe", "approval", "deny", "none"])) process.exit(1)
19
+ if (!Array.isArray(data.cases) || data.cases.length !== 32) process.exit(1)
20
+ if (new Set(data.cases.map(({ id }) => id)).size !== data.cases.length) process.exit(1)
21
+ const counts = Object.fromEntries(data.labels.map((label) => [label, 0]))
22
+ for (const item of data.cases) {
23
+ const { relevant_context, read_only, mutates_state, forbidden } = item.signals ?? {}
24
+ if (![relevant_context, read_only, mutates_state, forbidden].every((value) => typeof value === "boolean")) process.exit(1)
25
+ const expected = forbidden ? "deny" : mutates_state ? "approval" : relevant_context && read_only ? "observe" : "none"
26
+ if (item.expected !== expected) process.exit(1)
27
+ counts[item.expected] += 1
28
+ }
29
+ if (!Object.values(counts).every((count) => count === 8)) process.exit(1)
30
+ NODE
31
+
32
+ grep -qF 'MemFork | release `v0.3.0`' "$ROOT/docs/research/ecosystem-evidence-2026-10.md"
33
+ grep -qF '| Deterministic policy | 32/32 | 100% |' "$ROOT/docs/research/ecosystem-evidence-2026-10.md"
34
+ grep -qF '| Cairnkeep + pi-clm | 15/15 | 51,530 | $0.0543623 |' "$ROOT/docs/research/ecosystem-evidence-2026-10.md"
35
+
36
+ echo "PASS: ecosystem spike runners, frozen decision data, and evidence contract"
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
5
+ tmp=$(mktemp -d)
6
+ trap 'rm -rf "$tmp"' EXIT
7
+
8
+ render() {
9
+ sed "s|@@INFRA_ROOT@@|$ROOT|g" "$1" > "$2"
10
+ }
11
+
12
+ mkdir -p "$tmp/project" "$tmp/rendered"
13
+ render "$ROOT/opencode/plugins/memory-capture.ts" "$tmp/rendered/memory-capture.ts"
14
+ render "$ROOT/opencode/plugins/memory-recall.ts" "$tmp/rendered/memory-recall.ts"
15
+ render "$ROOT/opencode/plugins/memory-wakeup.ts" "$tmp/rendered/memory-wakeup.ts"
16
+
17
+ CAIRN_TRAJECTORY_CAPTURE=1 CAIRN_COMPACTION_CAPTURE=1 node --experimental-strip-types \
18
+ "$ROOT/scripts/lib/opencode-v2-plugin-harness.mjs" \
19
+ "$tmp/rendered/memory-capture.ts" \
20
+ "$tmp/rendered/memory-recall.ts" \
21
+ "$tmp/rendered/memory-wakeup.ts" \
22
+ "$tmp/project" \
23
+ "$ROOT/mcp-memory-server/dist/trajectory-cli.js" \
24
+ "$ROOT/mcp-memory-server/dist/artifact-cli.js"
25
+
26
+ # When a V2 binary is available, also exercise the real loader in a sterile
27
+ # config/data tree. CI remains deterministic without installing an unrelated
28
+ # harness package; release workstations run this second gate automatically.
29
+ if ! command -v opencode >/dev/null 2>&1; then
30
+ echo "SKIP: live OpenCode V2 loader (opencode not installed)"
31
+ exit 0
32
+ fi
33
+
34
+ version=$(opencode --version 2>/dev/null || true)
35
+ major=$(printf '%s\n' "$version" | sed -nE 's/.*v([0-9]+).*/\1/p')
36
+ if [[ -z "$major" || "$major" -lt 2 ]]; then
37
+ echo "SKIP: live OpenCode V2 loader ($version)"
38
+ exit 0
39
+ fi
40
+
41
+ git -C "$tmp/project" init -q
42
+ "$ROOT/scripts/sync-opencode-plugin-assets.sh" --apply --live-root "$tmp/config" >/dev/null
43
+
44
+ set +e
45
+ (
46
+ cd "$tmp/project"
47
+ XDG_DATA_HOME="$tmp/data" \
48
+ XDG_CACHE_HOME="$tmp/cache" \
49
+ XDG_STATE_HOME="$tmp/state" \
50
+ OPENCODE_CONFIG_DIR="$tmp/config" \
51
+ TERM=dumb \
52
+ timeout 5 opencode --standalone --print-logs --log-level debug
53
+ ) >"$tmp/live.out" 2>"$tmp/live.err"
54
+ live_status=$?
55
+ set -e
56
+
57
+ if [[ "$live_status" -ne 0 && "$live_status" -ne 124 ]]; then
58
+ echo "FAIL: live OpenCode V2 loader exited with $live_status" >&2
59
+ sed -n '1,160p' "$tmp/live.err" >&2
60
+ exit 1
61
+ fi
62
+
63
+ for plugin in memory-capture.ts memory-recall.ts memory-wakeup.ts; do
64
+ grep -q "loading plugin.*$plugin" "$tmp/live.err" || {
65
+ echo "FAIL: live OpenCode V2 did not load $plugin" >&2
66
+ exit 1
67
+ }
68
+ done
69
+
70
+ if grep -Eq 'SchemaError|Missing key at \["default"\]|failed to load plugin' "$tmp/live.err"; then
71
+ echo "FAIL: live OpenCode V2 rejected a Cairnkeep plugin" >&2
72
+ sed -n '1,200p' "$tmp/live.err" >&2
73
+ exit 1
74
+ fi
75
+
76
+ echo "PASS: live OpenCode $version loaded all Cairnkeep V2 plugins"
@@ -163,12 +163,17 @@ for required in \
163
163
  examples/eval/task-set.json \
164
164
  examples/eval/bundled-fake.json \
165
165
  examples/eval/adapter.json \
166
+ examples/eval/ecosystem-decisions.json \
166
167
  examples/eval/README.md
167
168
  do
168
169
  [[ -f "$installed_root/$required" ]] || fail "npm tarball omitted $required"
169
170
  done
170
171
  [[ -x "$installed_root/scripts/fake-eval-adapter.mjs" ]] || \
171
172
  fail "installed fake adapter is not executable"
173
+ for runner in run-memfork-evaluation.mjs run-clm-pi-evaluation.mjs run-ollaya-decision-evaluation.mjs; do
174
+ [[ -x "$installed_root/scripts/spikes/$runner" ]] || \
175
+ fail "installed ecosystem runner is missing or not executable: $runner"
176
+ done
172
177
  for asset in \
173
178
  schemas/eval-task-set.schema.json \
174
179
  schemas/eval-adapter.schema.json \
@@ -177,13 +182,14 @@ for asset in \
177
182
  examples/eval/task-set.json \
178
183
  examples/eval/bundled-fake.json \
179
184
  examples/eval/adapter.json \
185
+ examples/eval/ecosystem-decisions.json \
180
186
  examples/eval/README.md
181
187
  do
182
188
  [[ ! -x "$installed_root/$asset" ]] || \
183
189
  fail "installed public eval data asset is unexpectedly executable: $asset"
184
190
  done
185
191
  eval_assets=$(cd "$installed_root/examples/eval" && find . -type f -print | LC_ALL=C sort)
186
- [[ "$eval_assets" == $'./README.md\n./adapter.json\n./bundled-fake.json\n./task-set.json' ]] || \
192
+ [[ "$eval_assets" == $'./README.md\n./adapter.json\n./bundled-fake.json\n./ecosystem-decisions.json\n./task-set.json' ]] || \
187
193
  fail "installed eval examples contain an unexpected path"
188
194
 
189
195
  node - "$installed_root/examples/eval/adapter.json" <<'NODE'
@@ -336,7 +342,8 @@ for fixture in \
336
342
  compaction-claude-code-2.1.219.json \
337
343
  compaction-claude-code-2.1.220.json \
338
344
  compaction-opencode-1.17.20-event.json \
339
- compaction-opencode-1.17.20-messages.json
345
+ compaction-opencode-1.17.20-messages.json \
346
+ compaction-opencode-2.0.24.json
340
347
  do
341
348
  [[ -f "$installed_root/mcp-memory-server/scripts/fixtures/$fixture" ]] || \
342
349
  fail "npm tarball omitted versioned fixture $fixture"