@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
@@ -7,13 +7,22 @@ cd "$ROOT"
7
7
  version=$(node -p "require('./package.json').version")
8
8
  landing=docs/learning/README.md
9
9
  production=docs/learning/PRODUCTION-PLAN.md
10
+ coverage=docs/learning/CURRICULUM-MAP.md
11
+ feature_guide=docs/learning/FEATURE-GUIDE.md
10
12
 
11
- [[ -f "$landing" && -f "$production" ]]
13
+ [[ -f "$landing" && -f "$production" && -f "$coverage" && -f "$feature_guide" ]]
12
14
  grep -qF 'docs/learning/README.md' README.md
15
+ grep -qF "**Baseline:** Cairnkeep $version" "$coverage"
16
+ grep -qF "**Baseline:** Cairnkeep $version" "$feature_guide"
17
+ grep -qF 'https://github.com/cairnkeep/cairnkeep-course-labs' "$landing"
18
+ for checkpoint in course-00-app course-01-bootstrap course-02-memory course-03-quality \
19
+ course-04-operation course-05-evidence course-06-governance course-07-evaluation; do
20
+ grep -qF "\`$checkpoint\`" "$coverage"
21
+ done
13
22
 
14
23
  ready=0
15
24
  brief=0
16
- for number in $(seq -w 0 12); do
25
+ for number in $(seq -w 0 17); do
17
26
  matches=(docs/learning/lessons/L"$number"-*.md)
18
27
  [[ ${#matches[@]} -eq 1 && -f ${matches[0]} ]] || {
19
28
  echo "learning path must contain exactly one L$number lesson" >&2
@@ -41,17 +50,24 @@ for number in $(seq -w 0 12); do
41
50
  fi
42
51
  done
43
52
 
44
- [[ $ready -eq 4 && $brief -eq 9 ]] || {
53
+ [[ $ready -eq 4 && $brief -eq 14 ]] || {
45
54
  echo "unexpected learning status totals: ready=$ready brief=$brief" >&2
46
55
  exit 1
47
56
  }
48
57
 
49
- for track in quickstart practitioner operator; do
58
+ for track in quickstart practitioner evidence-and-evaluation operator; do
50
59
  file=docs/learning/tracks/$track.md
51
60
  [[ -f "$file" ]]
52
61
  grep -q '^# ' "$file"
53
62
  done
54
63
 
64
+ while read -r command; do
65
+ grep -qF "\`cairn $command\`" "$coverage" || {
66
+ echo "top-level command has no curriculum owner: cairn $command" >&2
67
+ exit 1
68
+ }
69
+ done < <(bin/cairn help | sed -n 's/^ cairn \([a-z-]*\).*/\1/p' | sort -u)
70
+
55
71
  node <<'NODE'
56
72
  const { existsSync, readdirSync, readFileSync } = require("node:fs");
57
73
  const { dirname, resolve } = require("node:path");
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env bash
2
+ # Public CLI contract for opt-in deterministic note distillation and lookup.
3
+ set -euo pipefail
4
+
5
+ ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
6
+ tmp=$(mktemp -d)
7
+ trap 'rm -rf "$tmp"' EXIT
8
+ fail() { echo "FAIL: $1" >&2; exit 1; }
9
+
10
+ cairn="$ROOT/bin/cairn"
11
+ fixture="$ROOT/mcp-memory-server/scripts/fixtures/notes/lifecycle-sessions.json"
12
+ project_a="$tmp/PARA/Projects/project-a"
13
+ project_b="$tmp/PARA/Projects/project-b"
14
+ project_c="$tmp/PARA/Projects/project-c"
15
+ store="$tmp/store"
16
+ mkdir -p "$project_a" "$project_b" "$project_c"
17
+
18
+ put_session() {
19
+ local name=$1 project=$2 session_id=${3:-}
20
+ (cd "$ROOT/mcp-memory-server" && PROJECT="$project" NAME="$name" SESSION_ID="$session_id" FIXTURE="$fixture" node --input-type=module - <<'EOF'
21
+ import { readFileSync } from "node:fs";
22
+ import { getTrajectoryLimits, trajectorySessionSchema } from "./dist/trajectory-schema.js";
23
+ import { putTrajectory } from "./dist/trajectory-store.js";
24
+ const all = JSON.parse(readFileSync(process.env.FIXTURE, "utf8"));
25
+ const raw = JSON.parse(JSON.stringify(all[process.env.NAME]).replaceAll("$PROJECT_ROOT", process.env.PROJECT));
26
+ if (process.env.SESSION_ID) raw.session_id = process.env.SESSION_ID;
27
+ await putTrajectory(process.env.PROJECT, trajectorySessionSchema.parse(raw), getTrajectoryLimits());
28
+ EOF
29
+ )
30
+ }
31
+
32
+ "$cairn" help | grep -q "cairn notes" || fail "help missing notes"
33
+
34
+ # Disabled means no notes root, lock, manifest, or trajectory mutation.
35
+ put_session failure "$project_a"
36
+ before=$(sha256sum "$project_a/.agentfs/trajectory.db" | cut -d' ' -f1)
37
+ CAIRN_AGENTFS_BASE_DIR="$store" env -u CAIRN_NOTE_DISTILLATION \
38
+ "$cairn" notes distill --project "$project_a" --json >"$tmp/disabled.json"
39
+ node -e 'const j=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));if(j.enabled!==false)process.exit(1)' "$tmp/disabled.json" \
40
+ || fail "disabled result is not explicit"
41
+ [[ ! -e "$store/notes" ]] || fail "disabled command created notes data"
42
+ after=$(sha256sum "$project_a/.agentfs/trajectory.db" | cut -d' ' -f1)
43
+ [[ "$before" == "$after" ]] || fail "disabled command changed trajectory bytes"
44
+
45
+ export CAIRN_AGENTFS_BASE_DIR="$store"
46
+ export CAIRN_NOTE_DISTILLATION=1
47
+ unset CAIRN_NOTE_ENRICHMENT CAIRN_LLM_API_KEY CAIRN_LLM_API_URL CAIRN_NOTE_ENRICHMENT_MODEL
48
+
49
+ "$cairn" notes distill --project "$project_a" --session note-failure-001 --json >"$tmp/created.json"
50
+ note_a=$(node -e 'const j=require(process.argv[1]);if(j.created.length!==1||j.created[0].status!=="unresolved")process.exit(1);process.stdout.write(j.created[0].id)' "$tmp/created.json") \
51
+ || fail "exact-session distillation did not create one unresolved note"
52
+ path_a=$(node -e 'process.stdout.write(require(process.argv[1]).created[0].path)' "$tmp/created.json")
53
+ printf '\n## Maintainer notes\n\nPreserve this operator sentence.\n' >>"$path_a"
54
+
55
+ put_session fix "$project_a"
56
+ "$cairn" notes distill --project "$project_a" --json >"$tmp/fixed.json"
57
+ node -e 'const j=require(process.argv[1]);if(!j.updated.some(x=>x.status==="resolved"))process.exit(1)' "$tmp/fixed.json" \
58
+ || fail "incremental current-project run did not resolve the note"
59
+ grep -q 'Preserve this operator sentence.' "$path_a" || fail "managed update removed manual content"
60
+
61
+ printf '%s\n' "TypeError: Cannot read properties of undefined (reading 'name')" \
62
+ " at loadUser (/another/root/src/user.ts:999:1)" \
63
+ | "$cairn" notes search-error --project "$project_a" --json >"$tmp/search.json"
64
+ node -e 'const j=require(process.argv[1]);if(j.results[0]?.id!==process.argv[2]||j.results[0]?.status!=="resolved")process.exit(1)' "$tmp/search.json" "$note_a" \
65
+ || fail "fresh public search did not return resolved note first"
66
+
67
+ "$cairn" notes distill --project "$project_a" --session note-fix-001 --json >"$tmp/repeat.json"
68
+ node -e 'const j=require(process.argv[1]);if(j.already_processed.length!==1)process.exit(1)' "$tmp/repeat.json" \
69
+ || fail "exact reprocessing is not idempotent"
70
+
71
+ put_session failure "$project_b" note-failure-project-b
72
+ "$cairn" notes distill --all-projects --para-root "$tmp/PARA" --json >"$tmp/all.json"
73
+ node -e 'const j=require(process.argv[1]);if(j.projects_scanned<2)process.exit(1)' "$tmp/all.json" \
74
+ || fail "all-project mode did not scan both trajectory projects"
75
+ note_b=$(node -e 'const j=require(process.argv[1]);const c=j.results.flatMap(x=>x.created||[]).find(x=>x.project_id!==process.argv[2]);if(!c)process.exit(1);process.stdout.write(c.id)' "$tmp/all.json" "$(basename "$(dirname "$(dirname "$path_a")")")") \
76
+ || fail "all-project mode did not create the corroborating note"
77
+
78
+ if "$cairn" notes promote "$note_a" --with "$note_b" --json >"$tmp/unconfirmed.out" 2>&1; then
79
+ fail "promotion succeeded without --confirm"
80
+ fi
81
+ if "$cairn" notes promote "$note_a" --with "$note_a" --confirm --json >"$tmp/same.out" 2>&1; then
82
+ fail "promotion accepted one-project evidence"
83
+ fi
84
+ "$cairn" notes promote "$note_a" --with "$note_b" --confirm --json >"$tmp/promoted.json"
85
+ node -e 'const j=require(process.argv[1]);if(j.status!=="promoted"||!j.shared_path)process.exit(1)' "$tmp/promoted.json" \
86
+ || fail "confirmed cross-project promotion failed"
87
+ [[ $(find "$store/notes/shared" -type f -name '*.md' | wc -l | tr -d ' ') == 1 ]] \
88
+ || fail "promotion did not leave exactly one shared canonical note"
89
+ grep -q 'node_type: provenance' "$path_a" || fail "project note did not become provenance"
90
+
91
+ # A live lock and an unmanaged replacement both fail without changing bytes.
92
+ put_session failure "$project_c" note-failure-project-c
93
+ "$cairn" notes distill --project "$project_c" --json >"$tmp/project-c.json"
94
+ path_c=$(node -e 'process.stdout.write(require(process.argv[1]).created[0].path)' "$tmp/project-c.json")
95
+ project_id_c=$(basename "$(dirname "$(dirname "$path_c")")")
96
+ mkdir -p "$store/notes/.cairnkeep/locks/$project_id_c.lock"
97
+ if "$cairn" notes distill --project "$project_c" --json >"$tmp/locked.out" 2>&1; then
98
+ fail "distillation ignored a live project lock"
99
+ fi
100
+ rm -rf "$store/notes/.cairnkeep/locks/$project_id_c.lock"
101
+ printf 'manual unmanaged bytes\n' >"$path_c"
102
+ collision_before=$(sha256sum "$path_c" | cut -d' ' -f1)
103
+ put_session recurrence "$project_c" note-recurrence-project-c
104
+ if "$cairn" notes distill --project "$project_c" --json >"$tmp/collision.out" 2>&1; then
105
+ fail "distillation overwrote an unmanaged collision"
106
+ fi
107
+ collision_after=$(sha256sum "$path_c" | cut -d' ' -f1)
108
+ [[ "$collision_before" == "$collision_after" ]] || fail "collision changed unmanaged bytes"
109
+
110
+ "$cairn" notes doctor --json >"$tmp/doctor.json"
111
+ node -e 'const j=require(process.argv[1]);if(j.schema_version!==1||j.ok!==true)process.exit(1)' "$tmp/doctor.json" \
112
+ || fail "notes doctor did not validate the generated tree"
113
+
114
+ echo "PASS: cairn notes CLI, disabled path, promotion, lock and collision contracts"
@@ -8,6 +8,23 @@ tmp=$(mktemp -d)
8
8
  trap 'rm -rf "$tmp"' EXIT
9
9
  fail() { echo "FAIL: $1" >&2; exit 1; }
10
10
 
11
+ manifest_field() {
12
+ node -e 'const value=require(process.argv[1]); process.stdout.write(JSON.stringify(value[process.argv[2]] ?? null))' "$1" "$2"
13
+ }
14
+
15
+ root_version_before=$(manifest_field "$ROOT/package.json" version)
16
+ root_dependencies_before=$(manifest_field "$ROOT/package.json" dependencies)
17
+ server_dependencies_before=$(manifest_field "$ROOT/mcp-memory-server/package.json" dependencies)
18
+ server_dev_dependencies_before=$(manifest_field "$ROOT/mcp-memory-server/package.json" devDependencies)
19
+ root_lock_before=$(sha256sum "$ROOT/package-lock.json" | cut -d' ' -f1)
20
+ server_lock_before=$(sha256sum "$ROOT/mcp-memory-server/package-lock.json" | cut -d' ' -f1)
21
+ node "$ROOT/mcp-memory-server/dist/eval-cli.js" --help >"$tmp/source-eval-help"
22
+ env -u CAIRN_EVAL "$ROOT/bin/cairn" eval validate \
23
+ --task-set "$tmp/source-unread-task-set" \
24
+ --adapter "$tmp/source-unread-adapter" \
25
+ --output "$tmp/source-unwritten-output" --json >"$tmp/source-eval-disabled.json"
26
+ [[ ! -e "$tmp/source-unwritten-output" ]] || fail "source disabled eval created output"
27
+
11
28
  cd "$ROOT"
12
29
  npm pack --silent --pack-destination "$tmp" --dry-run=false >/dev/null
13
30
  tarballs=("$tmp"/*.tgz)
@@ -25,12 +42,249 @@ bootstrap_output=$(cairn bootstrap "$tmp/project")
25
42
  [[ "$bootstrap_output" == *"cairn memory-server"* ]] || fail "bootstrap printed invalid memory-server setup instructions"
26
43
  [[ "$bootstrap_output" == *"cairn sync --apply"* ]] || fail "bootstrap printed invalid operating-layer setup instructions"
27
44
  [[ -x "$tmp/project/.ai/start-claude.sh" ]] || fail "bootstrap did not install an executable Claude launcher"
45
+ [[ -x "$tmp/project/.ai/start-pi.sh" ]] || fail "bootstrap did not install an executable Pi launcher"
46
+ [[ -x "$tmp/project/.ai/start-kimi.sh" ]] || fail "bootstrap did not install an executable Kimi launcher"
28
47
  [[ -f "$tmp/project/.planning/config.json" ]] || fail "bootstrap did not install the planning scaffold"
29
48
 
30
49
  (cd "$tmp/project" && cairn doctor) >/dev/null || fail "installed package failed cairn doctor"
50
+ env -u CAIRN_NOTE_DISTILLATION cairn notes --help >/dev/null \
51
+ || fail "installed package omitted the notes command"
31
52
 
32
53
  installed_root="$tmp/prefix/lib/node_modules/@cairnkeep/cli"
54
+ cairn eval --help >"$tmp/installed-eval-help"
55
+ cmp -s "$tmp/source-eval-help" "$tmp/installed-eval-help" || \
56
+ fail "source and installed eval help differ"
57
+ env -u CAIRN_EVAL cairn eval validate \
58
+ --task-set "$tmp/installed-unread-task-set" \
59
+ --adapter "$tmp/installed-unread-adapter" \
60
+ --output "$tmp/installed-unwritten-output" --json >"$tmp/installed-eval-disabled.json"
61
+ cmp -s "$tmp/source-eval-disabled.json" "$tmp/installed-eval-disabled.json" || \
62
+ fail "source and installed disabled eval identity differ"
63
+ [[ ! -e "$tmp/installed-unwritten-output" ]] || fail "installed disabled eval created output"
64
+
65
+ for required in \
66
+ schemas/capability-contract.schema.json \
67
+ schemas/capability-callback.schema.json \
68
+ templates/capabilities.json.template \
69
+ mcp-memory-server/dist/capability-schema.js \
70
+ mcp-memory-server/dist/capability-registry.js \
71
+ mcp-memory-server/dist/capability-config.js \
72
+ mcp-memory-server/dist/capability-store.js \
73
+ mcp-memory-server/dist/capability-adapter.js \
74
+ mcp-memory-server/dist/capability-cli.js \
75
+ claude/capability-contract/hooks/capability-command-start.sh \
76
+ claude/capability-contract/hooks/capability-command-finish.sh \
77
+ opencode/capability-contract/plugins/capability-command.ts
78
+ do
79
+ [[ -f "$installed_root/$required" ]] || fail "npm tarball omitted $required"
80
+ done
81
+
82
+ for required in \
83
+ schemas/eval-task-set.schema.json \
84
+ schemas/eval-adapter.schema.json \
85
+ schemas/eval-protocol.schema.json \
86
+ schemas/eval-report.schema.json \
87
+ mcp-memory-server/dist/eval-schema.js \
88
+ mcp-memory-server/dist/eval-process.js \
89
+ mcp-memory-server/dist/eval-workspace.js \
90
+ mcp-memory-server/dist/eval-plan.js \
91
+ mcp-memory-server/dist/eval-statistics.js \
92
+ mcp-memory-server/dist/eval-report.js \
93
+ mcp-memory-server/dist/eval-runner.js \
94
+ mcp-memory-server/dist/eval-cli.js \
95
+ scripts/fake-eval-adapter.mjs \
96
+ examples/eval/task-set.json \
97
+ examples/eval/bundled-fake.json \
98
+ examples/eval/adapter.json \
99
+ examples/eval/README.md
100
+ do
101
+ [[ -f "$installed_root/$required" ]] || fail "npm tarball omitted $required"
102
+ done
103
+ [[ -x "$installed_root/scripts/fake-eval-adapter.mjs" ]] || \
104
+ fail "installed fake adapter is not executable"
105
+ for asset in \
106
+ schemas/eval-task-set.schema.json \
107
+ schemas/eval-adapter.schema.json \
108
+ schemas/eval-protocol.schema.json \
109
+ schemas/eval-report.schema.json \
110
+ examples/eval/task-set.json \
111
+ examples/eval/bundled-fake.json \
112
+ examples/eval/adapter.json \
113
+ examples/eval/README.md
114
+ do
115
+ [[ ! -x "$installed_root/$asset" ]] || \
116
+ fail "installed public eval data asset is unexpectedly executable: $asset"
117
+ done
118
+ eval_assets=$(cd "$installed_root/examples/eval" && find . -type f -print | LC_ALL=C sort)
119
+ [[ "$eval_assets" == $'./README.md\n./adapter.json\n./bundled-fake.json\n./task-set.json' ]] || \
120
+ fail "installed eval examples contain an unexpected path"
121
+
122
+ node - "$installed_root/examples/eval/adapter.json" <<'NODE'
123
+ const value = require(process.argv[2]);
124
+ if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify(["command", "id", "schema_version", "turn_semantics"])) process.exit(1);
125
+ if (value.id !== "cairn-offline-fake" || value.command.program !== "../../scripts/fake-eval-adapter.mjs") process.exit(1);
126
+ if (!Array.isArray(value.command.args) || value.command.args.length !== 0) process.exit(1);
127
+ NODE
128
+ if grep -R -E 'https?://|CAIRN_LLM_API_KEY|CAIRN_LLM_ENDPOINT' \
129
+ "$installed_root/examples/eval" "$installed_root/scripts/fake-eval-adapter.mjs" >/dev/null; then
130
+ fail "installed eval fixture contains a live endpoint or credential default"
131
+ fi
132
+
133
+ installed_task_set="$installed_root/examples/eval/task-set.json"
134
+ installed_adapter="$installed_root/examples/eval/adapter.json"
135
+ installed_output="$tmp/installed-eval-output"
136
+ CAIRN_EVAL=1 cairn eval validate \
137
+ --task-set "$installed_task_set" --adapter "$installed_adapter" \
138
+ --output "$installed_output" --seed installed-package-seed --json >"$tmp/installed-eval-validate.json"
139
+ [[ ! -e "$installed_output" ]] || fail "installed eval validation created output"
140
+ CAIRN_EVAL=1 cairn eval run \
141
+ --task-set "$installed_task_set" --adapter "$installed_adapter" \
142
+ --output "$installed_output" --seed installed-package-seed --yes --json \
143
+ >"$tmp/installed-eval-run.json" 2>"$tmp/installed-eval-run.err"
144
+
145
+ node - \
146
+ "$installed_root/mcp-memory-server/dist/eval-schema.js" \
147
+ "$installed_root/package.json" \
148
+ "$installed_task_set" \
149
+ "$installed_root/examples/eval/bundled-fake.json" \
150
+ "$tmp/installed-eval-validate.json" \
151
+ "$tmp/installed-eval-run.json" <<'NODE'
152
+ const { readFileSync } = require("node:fs");
153
+ const { pathToFileURL } = require("node:url");
154
+ (async () => {
155
+ const [schemaPath, packagePath, taskSetPath, bindingPath, validationPath, runPath] = process.argv.slice(2);
156
+ const { canonicalDigest } = await import(pathToFileURL(schemaPath).href);
157
+ const manifest = JSON.parse(readFileSync(taskSetPath, "utf8"));
158
+ const binding = JSON.parse(readFileSync(bindingPath, "utf8"));
159
+ const installedPackage = JSON.parse(readFileSync(packagePath, "utf8"));
160
+ const validation = JSON.parse(readFileSync(validationPath, "utf8"));
161
+ const run = JSON.parse(readFileSync(runPath, "utf8"));
162
+ const digest = canonicalDigest(manifest);
163
+ if (binding.identifier !== "cairn-offline-fake-v1") process.exit(1);
164
+ if (binding.package_version !== installedPackage.version || binding.task_set_digest !== digest) process.exit(1);
165
+ if (validation.operation !== "validate" || validation.invocation_count !== manifest.tasks.length * 2) process.exit(1);
166
+ if (validation.plan.source.identifier !== binding.identifier
167
+ || validation.plan.source.package_version !== installedPackage.version
168
+ || validation.plan.task_set_digest !== digest) process.exit(1);
169
+ if (run.operation !== "run" || run.status !== "final" || run.invocation_count !== manifest.tasks.length * 2) process.exit(1);
170
+ const report = JSON.parse(readFileSync(run.report_path, "utf8"));
171
+ if (report.task_set_digest !== digest || report.evidence.task_set_digest !== digest) process.exit(1);
172
+ if (report.evidence.package_version !== installedPackage.version
173
+ || report.evidence.evidence_scope !== "offline-framework"
174
+ || report.observations.length !== manifest.tasks.length * 2) process.exit(1);
175
+ })().catch((error) => { console.error(error); process.exit(1); });
176
+ NODE
177
+
178
+ binding_path="$installed_root/examples/eval/bundled-fake.json"
179
+ cp "$binding_path" "$tmp/original-bundled-fake.json"
180
+ for field in identifier package_version task_set_digest; do
181
+ node - "$binding_path" "$field" <<'NODE'
182
+ const { readFileSync, writeFileSync } = require("node:fs");
183
+ const [path, field] = process.argv.slice(2);
184
+ const value = JSON.parse(readFileSync(path, "utf8"));
185
+ value[field] = field === "task_set_digest" ? "0".repeat(64) : `mutated-${field}`;
186
+ writeFileSync(path, `${JSON.stringify(value)}\n`);
187
+ NODE
188
+ if CAIRN_EVAL=1 cairn eval validate \
189
+ --task-set "$installed_task_set" --adapter "$installed_adapter" \
190
+ --output "$tmp/rejected-eval-output" --json >/dev/null 2>&1; then
191
+ fail "installed eval accepted mutated $field binding"
192
+ fi
193
+ cp "$tmp/original-bundled-fake.json" "$binding_path"
194
+ done
195
+ [[ ! -e "$tmp/rejected-eval-output" ]] || fail "rejected installed eval fixture created output"
196
+ chmod -R u+w "$installed_output"
197
+
198
+ if find \
199
+ "$installed_root/claude/capability-contract/commands" \
200
+ "$installed_root/opencode/capability-contract/command" \
201
+ "$installed_root/opencode/capability-contract/workflows" \
202
+ -type f -print 2>/dev/null | grep -q .; then
203
+ fail "npm tarball included a retired capability prompt owner"
204
+ fi
205
+
206
+ CAIRN_CAPABILITY_CONTRACT=1 cairn capabilities status --json >"$tmp/capability-status.json" \
207
+ || fail "installed package capability status failed"
208
+ node - "$tmp/capability-status.json" <<'NODE'
209
+ const fs = require("fs")
210
+ const value = JSON.parse(fs.readFileSync(process.argv[2], "utf8"))
211
+ const expected = ["memory.write", "memory.search", "notes.distill", "wiki", "graph", "security.audit", "route.check", "context.explore"]
212
+ if (value.schema_version !== 1 || value.contract_enabled !== true) process.exit(1)
213
+ if (JSON.stringify(value.capabilities.map((row) => row.id)) !== JSON.stringify(expected)) process.exit(1)
214
+ if (!/^[0-9a-f]{64}$/.test(value.configuration_digest)) process.exit(1)
215
+ NODE
33
216
  [[ -f "$installed_root/examples/anythingllm/sync_to_anythingllm.py" ]] || \
34
217
  fail "npm tarball omitted the default AnythingLLM sync script"
218
+ [[ -f "$installed_root/pi/extensions/cairnkeep-trajectory.ts" ]] || \
219
+ fail "npm tarball omitted the Pi trajectory extension"
220
+ [[ -x "$installed_root/scripts/sync-pi-assets.sh" ]] || \
221
+ fail "npm tarball omitted the Pi sync command"
222
+ [[ -f "$installed_root/templates/start-pi.sh.template" ]] || \
223
+ fail "npm tarball omitted the Pi launcher template"
224
+ [[ -f "$installed_root/templates/start-kimi.sh.template" ]] || \
225
+ fail "npm tarball omitted the Kimi launcher template"
226
+ [[ -f "$installed_root/schemas/note.schema.json" ]] || \
227
+ fail "npm tarball omitted the note schema"
228
+ [[ -f "$installed_root/schemas/memory-node.schema.json" ]] || \
229
+ fail "npm tarball omitted the typed memory-node schema"
230
+ [[ -f "$installed_root/schemas/artifact.schema.json" ]] || \
231
+ fail "npm tarball omitted the artifact schema"
232
+ for module in node-schema node-store node-cli; do
233
+ [[ -f "$installed_root/mcp-memory-server/dist/$module.js" ]] || \
234
+ fail "npm tarball omitted compiled $module"
235
+ done
236
+ [[ -f "$installed_root/mcp-memory-server/dist/note-cli.js" ]] || \
237
+ fail "npm tarball omitted the compiled note CLI"
238
+ [[ -f "$installed_root/mcp-memory-server/dist/note-enrichment.js" ]] || \
239
+ fail "npm tarball omitted optional note enrichment"
240
+ [[ -f "$installed_root/mcp-memory-server/dist/note-store.js" ]] || \
241
+ fail "npm tarball omitted the deterministic note store"
242
+ for module in artifact-schema artifact-store compaction-normalize artifact-cli; do
243
+ [[ -f "$installed_root/mcp-memory-server/dist/$module.js" ]] || \
244
+ fail "npm tarball omitted compiled $module"
245
+ done
246
+ [[ -x "$installed_root/claude/hooks/compaction-capture.sh" ]] || \
247
+ fail "npm tarball omitted the executable Claude compaction hook"
248
+ [[ -f "$installed_root/opencode/plugins/memory-capture.ts" ]] || \
249
+ fail "npm tarball omitted the OpenCode capture plugin"
250
+ [[ -f "$installed_root/opencode/plugins/memory-wakeup.ts" ]] || \
251
+ fail "npm tarball omitted the OpenCode recovery plugin"
252
+ for fixture in \
253
+ compaction-claude-code-2.1.219.json \
254
+ compaction-claude-code-2.1.220.json \
255
+ compaction-opencode-1.17.20-event.json \
256
+ compaction-opencode-1.17.20-messages.json
257
+ do
258
+ [[ -f "$installed_root/mcp-memory-server/scripts/fixtures/$fixture" ]] || \
259
+ fail "npm tarball omitted versioned fixture $fixture"
260
+ done
261
+ if find "$installed_root" -type f \( \
262
+ -path '*/.ai/capabilities.json' \
263
+ -o -name 'manifest-v1.json' \
264
+ -o -name '*.db' -o -name '*.db-wal' -o -name '*.db-shm' \
265
+ -o -path '*/notes/projects/*' -o -path '*/transactions/*' -o -path '*/backups/*' \
266
+ -o -path '*/runtime-evidence/*' -o -path '*/generated-artifacts/*' \
267
+ -o -path '*/artifacts/projects/*' -o -path '*/artifacts/sessions/*' -o -path '*/artifacts/data/*' \
268
+ -o -path '*/capability-callback/*' -o -path '*/test-tmp/*' \
269
+ -o -path '*/.agentfs/*' -o -path '*/eval/experiments/*' \
270
+ -o -path '*/eval/reports/*' -o -path '*/eval/snapshots/*' \
271
+ -o -path '*/eval/runtime-evidence/*' -o -path '*/eval/evidence/*' \
272
+ -o -path '*/eval/user-data/*' \
273
+ \) | grep -q .; then
274
+ fail "npm tarball included generated capability, callback, eval evidence, or user data"
275
+ fi
276
+
277
+ if find "$installed_root" -type f \( -name 'token_miser' -o -name 'token-miser' -o -name 'token_miser.*' -o -name 'token-miser.*' \) | grep -q .; then
278
+ fail "npm tarball included a token-miser implementation instead of the existing delegate boundary"
279
+ fi
280
+ [[ -f "$installed_root/mcp-memory-server/dist/index.js" ]] || fail "npm tarball omitted the existing MCP delegate owner"
281
+ [[ -x "$installed_root/scripts/sync-opencode-explore-assets.sh" ]] || fail "npm tarball omitted the existing explore asset delegate"
282
+
283
+ [[ "$(manifest_field "$ROOT/package.json" version)" == "$root_version_before" ]] || fail "package version changed during pack/install"
284
+ [[ "$(manifest_field "$ROOT/package.json" dependencies)" == "$root_dependencies_before" ]] || fail "root dependencies changed during pack/install"
285
+ [[ "$(manifest_field "$ROOT/mcp-memory-server/package.json" dependencies)" == "$server_dependencies_before" ]] || fail "server dependencies changed during pack/install"
286
+ [[ "$(manifest_field "$ROOT/mcp-memory-server/package.json" devDependencies)" == "$server_dev_dependencies_before" ]] || fail "server devDependencies changed during pack/install"
287
+ [[ "$(sha256sum "$ROOT/package-lock.json" | cut -d' ' -f1)" == "$root_lock_before" ]] || fail "root lockfile changed during pack/install"
288
+ [[ "$(sha256sum "$ROOT/mcp-memory-server/package-lock.json" | cut -d' ' -f1)" == "$server_lock_before" ]] || fail "server lockfile changed during pack/install"
35
289
 
36
290
  echo "PASS: npm tarball installs a self-contained CLI and MCP server"
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env bash
2
+ set -uo pipefail
3
+
4
+ ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
5
+ MARKER="PHASE16_RED:PACKAGE_UNINSTALL_CONTRACT_MISSING"
6
+
7
+ case "${1:-}" in
8
+ "")
9
+ test "$#" -eq 0 || exit 2
10
+ "$ROOT/scripts/test-package-install.sh" || exit 1
11
+ "$ROOT/scripts/test-uninstall.sh" || exit 1
12
+ echo "PASS: Phase 16 package/uninstall baseline"
13
+ ;;
14
+ --expect-red)
15
+ test "$#" -eq 1 || exit 2
16
+ tmp=$(mktemp -d)
17
+ trap 'rm -rf "$tmp"' EXIT
18
+
19
+ mkdir -p "$tmp/expected/schemas" "$tmp/expected/mcp-memory-server/dist"
20
+ mkdir -p "$tmp/store/notes/.cairnkeep/transactions/txn-prepared/history"
21
+ printf '%s\n' '{"schema_version":1,"node_type":"knowledge","tags":["release"]}' >"$tmp/store/identity-node-metadata.json"
22
+ printf '%s\n' '{"schema_version":1,"import_id":"package-fixture","batch_digest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}' >"$tmp/store/identity-import-replay.json"
23
+ printf '%s\n' '{"schema_version":1,"state":"prepared"}' >"$tmp/store/notes/.cairnkeep/transactions/txn-prepared/journal.json"
24
+ printf '%s\n' 'history bytes' >"$tmp/store/notes/.cairnkeep/transactions/txn-prepared/history/note.json"
25
+
26
+ expected="schemas/memory-node.schema.json mcp-memory-server/dist/node-schema.js mcp-memory-server/dist/node-store.js mcp-memory-server/dist/node-cli.js"
27
+ listing=$(cd "$ROOT" && npm pack --dry-run --json --ignore-scripts 2>/dev/null) || exit 1
28
+ missing=0
29
+ for path in $expected; do
30
+ if ! printf '%s\n' "$listing" | grep -Fq "\"path\": \"$path\""; then
31
+ missing=$((missing + 1))
32
+ fi
33
+ done
34
+ test "$missing" -gt 0 || {
35
+ echo "Phase 16 package artifacts are already present; expected RED is stale" >&2
36
+ exit 1
37
+ }
38
+ grep -q -- '--purge-memory' "$ROOT/scripts/uninstall.sh" || exit 1
39
+ if grep -q 'identity-node-metadata\|txn-prepared' "$ROOT/scripts/test-uninstall.sh"; then
40
+ echo "Phase 16 uninstall fixtures are already covered; expected RED is stale" >&2
41
+ exit 1
42
+ fi
43
+ printf '%s\n' "$MARKER" >&2
44
+ exit 86
45
+ ;;
46
+ *)
47
+ echo "Usage: $0 [--expect-red]" >&2
48
+ exit 2
49
+ ;;
50
+ esac