@cairnkeep/cli 2.3.0 → 2.4.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 (125) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +221 -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 +1 -1
  10. package/docs/design/meta-agent.md +439 -0
  11. package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
  12. package/docs/learning/lessons/L01-safe-trial.md +3 -3
  13. package/docs/learning/lessons/L02-installation.md +2 -2
  14. package/docs/learning/lessons/L03-first-project.md +1 -1
  15. package/docs/operating.md +531 -4
  16. package/docs/privacy-and-data-flow.md +417 -0
  17. package/docs/storage.md +252 -0
  18. package/examples/eval/README.md +42 -0
  19. package/examples/eval/adapter.json +12 -0
  20. package/examples/eval/bundled-fake.json +1 -0
  21. package/examples/eval/task-set.json +1 -0
  22. package/mcp-memory-server/dist/artifact-cli.js +184 -0
  23. package/mcp-memory-server/dist/artifact-schema.js +218 -0
  24. package/mcp-memory-server/dist/artifact-store.js +812 -0
  25. package/mcp-memory-server/dist/capability-adapter.js +286 -0
  26. package/mcp-memory-server/dist/capability-cli.js +361 -0
  27. package/mcp-memory-server/dist/capability-config.js +310 -0
  28. package/mcp-memory-server/dist/capability-harness.js +496 -0
  29. package/mcp-memory-server/dist/capability-registry.js +123 -0
  30. package/mcp-memory-server/dist/capability-schema.js +65 -0
  31. package/mcp-memory-server/dist/capability-store.js +431 -0
  32. package/mcp-memory-server/dist/compaction-normalize.js +267 -0
  33. package/mcp-memory-server/dist/eval-cli.js +523 -0
  34. package/mcp-memory-server/dist/eval-plan.js +346 -0
  35. package/mcp-memory-server/dist/eval-process.js +273 -0
  36. package/mcp-memory-server/dist/eval-report.js +609 -0
  37. package/mcp-memory-server/dist/eval-runner.js +827 -0
  38. package/mcp-memory-server/dist/eval-schema.js +368 -0
  39. package/mcp-memory-server/dist/eval-statistics.js +161 -0
  40. package/mcp-memory-server/dist/eval-workspace.js +313 -0
  41. package/mcp-memory-server/dist/failure-signature.js +141 -0
  42. package/mcp-memory-server/dist/index.js +600 -55
  43. package/mcp-memory-server/dist/node-cli.js +84 -0
  44. package/mcp-memory-server/dist/node-schema.js +170 -0
  45. package/mcp-memory-server/dist/node-store.js +455 -0
  46. package/mcp-memory-server/dist/note-cli.js +235 -0
  47. package/mcp-memory-server/dist/note-distiller.js +177 -0
  48. package/mcp-memory-server/dist/note-enrichment.js +93 -0
  49. package/mcp-memory-server/dist/note-schema.js +4 -0
  50. package/mcp-memory-server/dist/note-store.js +985 -0
  51. package/mcp-memory-server/dist/trajectory-cli.js +141 -0
  52. package/mcp-memory-server/dist/trajectory-normalize.js +448 -0
  53. package/mcp-memory-server/dist/trajectory-redaction.js +155 -0
  54. package/mcp-memory-server/dist/trajectory-schema.js +73 -0
  55. package/mcp-memory-server/dist/trajectory-store.js +339 -0
  56. package/mcp-memory-server/package.json +24 -2
  57. package/mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.219.json +9 -0
  58. package/mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.220.json +9 -0
  59. package/mcp-memory-server/scripts/fixtures/compaction-opencode-1.17.20-event.json +6 -0
  60. package/mcp-memory-server/scripts/fixtures/compaction-opencode-1.17.20-messages.json +119 -0
  61. package/mcp-memory-server/scripts/fixtures/compaction-unknown-version.json +17 -0
  62. package/opencode/capability-contract/plugins/capability-command.ts +319 -0
  63. package/opencode/command/graphify.md +25 -14
  64. package/opencode/plugins/memory-capture.ts +76 -10
  65. package/opencode/plugins/memory-wakeup.ts +58 -6
  66. package/package.json +9 -4
  67. package/pi/extensions/cairnkeep-trajectory.ts +55 -0
  68. package/schemas/artifact.schema.json +133 -0
  69. package/schemas/capability-callback.schema.json +68 -0
  70. package/schemas/capability-contract.schema.json +126 -0
  71. package/schemas/eval-adapter.schema.json +43 -0
  72. package/schemas/eval-protocol.schema.json +114 -0
  73. package/schemas/eval-report.schema.json +423 -0
  74. package/schemas/eval-task-set.schema.json +114 -0
  75. package/schemas/memory-node.schema.json +90 -0
  76. package/schemas/note.schema.json +112 -0
  77. package/schemas/trajectory-redaction.schema.json +25 -0
  78. package/schemas/trajectory.schema.json +82 -0
  79. package/scripts/bootstrap.sh +4 -0
  80. package/scripts/completion.sh +119 -5
  81. package/scripts/doctor.sh +284 -0
  82. package/scripts/fake-eval-adapter.mjs +120 -0
  83. package/scripts/fixtures/capability-harness-contracts.json +408 -0
  84. package/scripts/install-audit-timer.sh +3 -1
  85. package/scripts/lib/capability-opencode-plugin-harness.mjs +291 -0
  86. package/scripts/lib/trajectory-opencode-plugin-harness.mjs +25 -0
  87. package/scripts/lib/trajectory-pi-extension-harness.mjs +42 -0
  88. package/scripts/memory-wiki-audit.sh +49 -0
  89. package/scripts/smoke-bash32.sh +9 -0
  90. package/scripts/sync-claude-assets.sh +99 -6
  91. package/scripts/sync-opencode-graphify-assets.sh +24 -3
  92. package/scripts/sync-opencode-plugin-assets.sh +31 -5
  93. package/scripts/sync-opencode-security-assets.sh +24 -3
  94. package/scripts/sync-opencode-wiki-assets.sh +24 -3
  95. package/scripts/sync-pi-assets.sh +72 -0
  96. package/scripts/test-audit-timer.sh +98 -0
  97. package/scripts/test-bootstrap-untracked.sh +48 -0
  98. package/scripts/test-cli-dispatch.sh +41 -2
  99. package/scripts/test-compaction-hooks.sh +495 -0
  100. package/scripts/test-completion.sh +77 -0
  101. package/scripts/test-doctor.sh +348 -1
  102. package/scripts/test-eval-cli.sh +175 -0
  103. package/scripts/test-launcher-hooks.sh +602 -3
  104. package/scripts/test-notes-cli.sh +114 -0
  105. package/scripts/test-package-install.sh +251 -0
  106. package/scripts/test-phase16-node-lifecycle.sh +50 -0
  107. package/scripts/test-phase17-artifact-lifecycle.sh +370 -0
  108. package/scripts/test-phase18-capability-lifecycle.sh +504 -0
  109. package/scripts/test-phase18-harness-boundary.sh +1060 -0
  110. package/scripts/test-phase19-eval-lifecycle.sh +1015 -0
  111. package/scripts/test-trajectory-cli.sh +87 -0
  112. package/scripts/test-trajectory-hooks.sh +142 -0
  113. package/scripts/test-uninstall.sh +231 -5
  114. package/scripts/uninstall.sh +37 -5
  115. package/scripts/verify-dependency-security.mjs +121 -0
  116. package/scripts/verify-docs-parity.sh +527 -1
  117. package/scripts/verify-phase18-runtime-evidence.mjs +895 -0
  118. package/scripts/verify-phase19-runtime-evidence.mjs +947 -0
  119. package/templates/capabilities.json.template +7 -0
  120. package/templates/env.example.template +51 -0
  121. package/templates/memory-wiki-audit.service.template +1 -1
  122. package/templates/start-claude.sh.template +36 -0
  123. package/templates/start-opencode.sh.template +47 -5
  124. package/templates/start-pi.sh.template +30 -0
  125. package/templates/trajectory-redaction.json.template +4 -0
@@ -0,0 +1,370 @@
1
+ #!/usr/bin/env bash
2
+ # Phase 17 package/operator/reversibility contract. The default invocation
3
+ # exercises the complete shipped lifecycle; the historical RED gate is explicit.
4
+ set -uo pipefail
5
+
6
+ ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
7
+ MODE="${1:-}"
8
+ EXPECTED_RED_EXIT=86
9
+ RED_MARKER="PHASE17_RED:ARTIFACT_PACKAGE_LIFECYCLE_MISSING"
10
+
11
+ case "$MODE" in
12
+ ""|--expect-red) ;;
13
+ *) echo "Usage: $0 [--expect-red]" >&2; exit 2 ;;
14
+ esac
15
+ [[ "$MODE" != "" || "$#" -eq 0 ]] || exit 2
16
+ [[ "$MODE" != "--expect-red" || "$#" -eq 1 ]] || exit 2
17
+
18
+ tmp=$(mktemp -d)
19
+ trap 'rm -rf "$tmp"' EXIT
20
+ baseline_number=0
21
+ missing=0
22
+
23
+ fail() {
24
+ echo "FAIL: $1" >&2
25
+ exit 1
26
+ }
27
+
28
+ mark_missing() {
29
+ missing=$((missing + 1))
30
+ echo "MISSING: $1" >&2
31
+ }
32
+
33
+ run_baseline_check() {
34
+ baseline_number=$((baseline_number + 1))
35
+ local output="$tmp/baseline-$baseline_number.out"
36
+ if ! "$@" >"$output" 2>&1; then
37
+ cat "$output" >&2
38
+ fail "baseline failed: $*"
39
+ fi
40
+ if grep -qF 'PHASE17_RED:' "$output"; then
41
+ cat "$output" >&2
42
+ fail "baseline emitted a Phase 17 RED marker: $*"
43
+ fi
44
+ cat "$output"
45
+ }
46
+
47
+ run_shell_syntax_baseline() {
48
+ local script
49
+ while IFS= read -r script; do
50
+ bash -n "$script" || return 1
51
+ done < <(find "$ROOT/bin" "$ROOT/scripts" -type f \( -name '*.sh' -o -name 'cairn' \) | sort)
52
+ }
53
+
54
+ run_baseline() {
55
+ run_baseline_check "$ROOT/scripts/test-package-install.sh"
56
+ run_baseline_check "$ROOT/scripts/test-uninstall.sh"
57
+ run_baseline_check "$ROOT/scripts/test-doctor.sh"
58
+ run_baseline_check "$ROOT/scripts/test-cli-dispatch.sh"
59
+ run_baseline_check "$ROOT/scripts/test-completion.sh"
60
+ run_baseline_check "$ROOT/scripts/test-runtime-contract.sh"
61
+ run_baseline_check run_shell_syntax_baseline
62
+ run_baseline_check "$ROOT/scripts/test-portable-sh.sh"
63
+ echo "PASS: Phase 17 package/operator pre-feature baseline"
64
+ }
65
+
66
+ require_term() {
67
+ local path="$1"
68
+ local term="$2"
69
+ local label="$3"
70
+ if ! grep -qiF "$term" "$path"; then
71
+ mark_missing "$label"
72
+ fi
73
+ }
74
+
75
+ require_pattern() {
76
+ local path="$1"
77
+ local pattern="$2"
78
+ local label="$3"
79
+ if ! grep -qiE "$pattern" "$path"; then
80
+ mark_missing "$label"
81
+ fi
82
+ }
83
+
84
+ inspect_package() {
85
+ local listing="$tmp/pack.json"
86
+ local paths="$tmp/pack-paths.txt"
87
+ if ! (cd "$ROOT" && npm pack --dry-run --json --ignore-scripts) >"$listing" 2>"$tmp/pack.err"; then
88
+ cat "$tmp/pack.err" >&2
89
+ fail "npm pack dry-run failed"
90
+ fi
91
+ if ! node - "$listing" >"$paths" <<'NODE'
92
+ const fs = require("fs");
93
+ const input = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
94
+ if (!Array.isArray(input) || input.length !== 1 || !Array.isArray(input[0].files)) {
95
+ throw new Error("unexpected npm pack JSON shape");
96
+ }
97
+ for (const entry of input[0].files) {
98
+ if (!entry || typeof entry.path !== "string") throw new Error("invalid npm pack file entry");
99
+ process.stdout.write(`${entry.path}\n`);
100
+ }
101
+ NODE
102
+ then
103
+ fail "npm pack dry-run JSON could not be parsed"
104
+ fi
105
+
106
+ local expected
107
+ for expected in \
108
+ schemas/artifact.schema.json \
109
+ mcp-memory-server/dist/artifact-schema.js \
110
+ mcp-memory-server/dist/artifact-store.js \
111
+ mcp-memory-server/dist/compaction-normalize.js \
112
+ mcp-memory-server/dist/artifact-cli.js \
113
+ claude/hooks/compaction-capture.sh \
114
+ opencode/plugins/memory-capture.ts \
115
+ opencode/plugins/memory-wakeup.ts \
116
+ mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.219.json \
117
+ mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.220.json \
118
+ mcp-memory-server/scripts/fixtures/compaction-opencode-1.17.20-event.json \
119
+ mcp-memory-server/scripts/fixtures/compaction-opencode-1.17.20-messages.json
120
+ do
121
+ grep -qxF "$expected" "$paths" || mark_missing "npm package asset $expected"
122
+ done
123
+
124
+ if node - "$paths" <<'NODE'
125
+ const fs = require("fs");
126
+ const paths = fs.readFileSync(process.argv[2], "utf8").trim().split("\n").filter(Boolean);
127
+ const forbidden = paths.filter((path) =>
128
+ /(^|\/)\.agentfs\//.test(path)
129
+ || /\.db(?:-wal|-shm)?$/i.test(path)
130
+ || /(^|\/)runtime-evidence\//.test(path)
131
+ || /(^|\/)(?:generated-)?artifacts?\/(?:projects|sessions|data)\//i.test(path)
132
+ );
133
+ if (forbidden.length > 0) {
134
+ for (const path of forbidden) console.error(`forbidden packaged artifact data: ${path}`);
135
+ process.exit(1);
136
+ }
137
+ NODE
138
+ then
139
+ :
140
+ else
141
+ fail "npm package contains a database, sidecar, or generated artifact data"
142
+ fi
143
+ }
144
+
145
+ check_cli_and_completion() {
146
+ local help="$tmp/cairn-help.out"
147
+ "$ROOT/bin/cairn" help >"$help" 2>"$tmp/cairn-help.err" || fail "cairn help failed"
148
+ require_term "$help" 'cairn artifact <list|show|delete|prune>' "artifact CLI help tree"
149
+
150
+ if grep -qF 'cairn artifact <list|show|delete|prune>' "$help"; then
151
+ if ! "$ROOT/bin/cairn" artifact --help >"$tmp/artifact-help.out" 2>"$tmp/artifact-help.err"; then
152
+ cat "$tmp/artifact-help.err" >&2
153
+ fail "advertised artifact CLI dispatch is broken"
154
+ fi
155
+ fi
156
+
157
+ local shell output
158
+ for shell in bash zsh fish; do
159
+ output="$tmp/completion-$shell.out"
160
+ "$ROOT/bin/cairn" completion "$shell" >"$output" || fail "$shell completion generation failed"
161
+ require_pattern "$output" 'artifact.*list.*show.*delete.*prune' "$shell completion artifact list/show/delete/prune tree"
162
+ done
163
+
164
+ require_term "$ROOT/scripts/test-cli-dispatch.sh" 'artifact' "artifact CLI dispatch regression coverage"
165
+ require_term "$ROOT/scripts/test-completion.sh" 'artifact' "artifact completion regression coverage"
166
+ }
167
+
168
+ check_doctor_contract() {
169
+ require_term "$ROOT/scripts/doctor.sh" 'artifact_cli=' "doctor artifact CLI integration"
170
+ require_term "$ROOT/scripts/doctor.sh" 'artifact store (not present' "doctor absent artifact state"
171
+ require_term "$ROOT/scripts/doctor.sh" 'artifact store integrity' "doctor valid artifact state"
172
+ require_term "$ROOT/scripts/doctor.sh" 'artifact store repaired' "doctor derived-state repair"
173
+ require_pattern "$ROOT/scripts/doctor.sh" 'artifact.*could not be repaired safely|artifact.*authoritative.*corrupt' "doctor authoritative corruption failure"
174
+ require_term "$ROOT/scripts/test-doctor.sh" 'artifacts.db' "doctor artifact database fixtures"
175
+ require_pattern "$ROOT/scripts/test-doctor.sh" 'derived.*repair|repair.*derived' "doctor derived repair regression case"
176
+ require_pattern "$ROOT/scripts/test-doctor.sh" 'authoritative.*(fail|corrupt)|corrupt.*authoritative' "doctor authoritative failure regression case"
177
+ }
178
+
179
+ check_env_contract() {
180
+ local env_template="$ROOT/templates/env.example.template"
181
+ local expected key
182
+ for expected in \
183
+ 'CAIRN_COMPACTION_CAPTURE=1' \
184
+ 'CAIRN_ARTIFACT_STORE=1' \
185
+ 'CAIRN_ARTIFACT_HTTP=1' \
186
+ 'CAIRN_ARTIFACT_MAX_BYTES=1048576' \
187
+ 'CAIRN_ARTIFACT_SESSION_MAX_BYTES=16777216' \
188
+ 'CAIRN_ARTIFACT_STORE_MAX_BYTES=268435456' \
189
+ 'CAIRN_ARTIFACT_RETENTION_DAYS=30' \
190
+ 'CAIRN_COMPACTION_MAX_REVISIONS=8' \
191
+ 'CAIRN_ARTIFACT_GENERATED_FILE_SNAPSHOT_MAX_BYTES=262144'
192
+ do
193
+ grep -qxF "# $expected" "$env_template" || mark_missing "commented env default $expected"
194
+ key=${expected%%=*}
195
+ if grep -qE "^[[:space:]]*${key}=" "$env_template"; then
196
+ fail "$key is enabled in the environment template"
197
+ fi
198
+ done
199
+ require_pattern "$env_template" 'off by default|default-off|disabled by default' "artifact and compaction default-off env guidance"
200
+ }
201
+
202
+ check_uninstall_bytes() {
203
+ local fixture_home="$tmp/uninstall-home"
204
+ local fixture_bin="$tmp/uninstall-bin"
205
+ local project="$tmp/uninstall-project"
206
+ local live="$tmp/uninstall-live"
207
+ local pi_live="$tmp/uninstall-pi-live"
208
+ local original="$tmp/artifacts.original"
209
+ local bundle backup_copy
210
+
211
+ mkdir -p "$fixture_home" "$fixture_bin" "$project/.ai" "$project/.agentfs" "$live" "$pi_live"
212
+ printf '#!/usr/bin/env bash\nexit 0\n' >"$fixture_bin/claude"
213
+ printf '#!/usr/bin/env bash\nexit 0\n' >"$fixture_bin/systemctl"
214
+ chmod +x "$fixture_bin/claude" "$fixture_bin/systemctl"
215
+ git -C "$project" init -q || fail "uninstall fixture git init failed"
216
+ printf 'artifact-v1\000durable\377bytes\n' >"$project/.agentfs/artifacts.db"
217
+ cp "$project/.agentfs/artifacts.db" "$original"
218
+
219
+ HOME="$fixture_home" XDG_CONFIG_HOME="$fixture_home/.config" PATH="$fixture_bin:$PATH" \
220
+ "$ROOT/scripts/uninstall.sh" --dry-run --live-root "$live" --pi-live-root "$pi_live" "$project" \
221
+ >"$tmp/uninstall-dry-run.out" 2>&1 || fail "artifact uninstall dry-run failed"
222
+ cmp -s "$original" "$project/.agentfs/artifacts.db" || fail "uninstall dry-run mutated artifact bytes"
223
+ if find "$fixture_home" -maxdepth 1 -name '.cairnkeep-uninstall-*' | grep -q .; then
224
+ fail "uninstall dry-run created a backup bundle"
225
+ fi
226
+
227
+ HOME="$fixture_home" XDG_CONFIG_HOME="$fixture_home/.config" PATH="$fixture_bin:$PATH" \
228
+ "$ROOT/scripts/uninstall.sh" --yes --live-root "$live" --pi-live-root "$pi_live" "$project" \
229
+ >"$tmp/uninstall-keep.out" 2>&1 || fail "default artifact uninstall failed"
230
+ cmp -s "$original" "$project/.agentfs/artifacts.db" || fail "default uninstall did not retain exact artifact bytes"
231
+
232
+ HOME="$fixture_home" XDG_CONFIG_HOME="$fixture_home/.config" PATH="$fixture_bin:$PATH" \
233
+ "$ROOT/scripts/uninstall.sh" --yes --purge-memory --live-root "$live" --pi-live-root "$pi_live" "$project" \
234
+ >"$tmp/uninstall-purge.out" 2>&1 || fail "artifact purge uninstall failed"
235
+ [[ ! -e "$project/.agentfs" ]] || fail "artifact purge left the project store in place"
236
+ bundle=$(find "$fixture_home" -maxdepth 1 -type d -name '.cairnkeep-uninstall-*' | sort | tail -1)
237
+ [[ -n "$bundle" && -x "$bundle/revert.sh" ]] || fail "artifact purge did not generate a revert script"
238
+ backup_copy="$bundle/files/${project#/}/.agentfs/artifacts.db"
239
+ cmp -s "$original" "$backup_copy" || fail "artifact purge backup did not preserve exact bytes"
240
+ HOME="$fixture_home" XDG_CONFIG_HOME="$fixture_home/.config" PATH="$fixture_bin:$PATH" \
241
+ bash "$bundle/revert.sh" >"$tmp/uninstall-revert.out" 2>&1 || fail "artifact revert failed"
242
+ cmp -s "$original" "$project/.agentfs/artifacts.db" || fail "artifact revert did not restore exact bytes"
243
+ }
244
+
245
+ check_docs_contract() {
246
+ local public_doc
247
+ for public_doc in "$ROOT/README.md" "$ROOT/docs/operating.md"; do
248
+ require_term "$public_doc" 'CAIRN_COMPACTION_CAPTURE' "$(basename "$public_doc") compaction flag"
249
+ require_term "$public_doc" 'CAIRN_ARTIFACT_STORE' "$(basename "$public_doc") artifact-store flag"
250
+ require_term "$public_doc" 'CAIRN_ARTIFACT_HTTP' "$(basename "$public_doc") artifact HTTP flag"
251
+ require_term "$public_doc" 'cairn artifact' "$(basename "$public_doc") artifact CLI"
252
+ require_term "$public_doc" 'artifact_write' "$(basename "$public_doc") artifact_write tool"
253
+ require_term "$public_doc" 'artifact_read' "$(basename "$public_doc") artifact_read tool"
254
+ require_term "$public_doc" 'artifact_list' "$(basename "$public_doc") artifact_list tool"
255
+ require_term "$public_doc" 'artifact_delete' "$(basename "$public_doc") artifact_delete tool"
256
+ require_pattern "$public_doc" 'CAIRN_(COMPACTION_CAPTURE|ARTIFACT_STORE)[^.]{0,240}(off by default|default-off|disabled by default)|(off by default|default-off|disabled by default)[^.]{0,240}CAIRN_(COMPACTION_CAPTURE|ARTIFACT_STORE)' "$(basename "$public_doc") disabled behavior"
257
+ done
258
+
259
+ local storage="$ROOT/docs/storage.md"
260
+ require_term "$storage" '.agentfs/artifacts.db' "storage local artifact location"
261
+ require_term "$storage" 'server-side' "storage server-side remote location"
262
+ require_term "$storage" 'X-Cairn-Project' "storage remote project identity"
263
+ require_pattern "$storage" 'artifact.*(index|indexes)|index.*artifact' "storage artifact indexes"
264
+ require_pattern "$storage" 'artifact.*retention|retention.*artifact' "storage artifact retention"
265
+ require_pattern "$storage" 'artifact.*backup|backup.*artifact' "storage artifact backup"
266
+ require_pattern "$storage" 'artifact.*migration|migration.*artifact|no migration' "storage artifact migration"
267
+
268
+ local privacy="$ROOT/docs/privacy-and-data-flow.md"
269
+ require_term "$privacy" 'PostCompact' "privacy Claude local compaction flow"
270
+ require_term "$privacy" 'session.compacted' "privacy OpenCode local compaction flow"
271
+ require_pattern "$privacy" 'artifact.*stdio|stdio.*artifact' "privacy local stdio artifact flow"
272
+ require_term "$privacy" 'CAIRN_ARTIFACT_HTTP' "privacy explicit HTTP artifact flow"
273
+ require_pattern "$privacy" 'redact[^.]{0,160}(before|then)[^.]{0,160}(digest|index|write)' "privacy redaction-before-derived-data order"
274
+ require_pattern "$privacy" 'artifact[^.]{0,220}no default egress|no default egress[^.]{0,220}artifact' "privacy no-default-egress statement"
275
+ require_pattern "$privacy" 'artifact[^.]{0,220}(no telemetry|telemetry.*none|without telemetry)|(no telemetry|telemetry.*none|without telemetry)[^.]{0,220}artifact' "privacy no-telemetry statement"
276
+ require_pattern "$privacy" 'raw[^.]{0,100}(on demand|on-demand)|on-demand[^.]{0,100}raw' "privacy raw-summary-on-demand-only recovery"
277
+ }
278
+
279
+ check_dependency_delta() {
280
+ local first_phase_commit last_phase_commit phase_base
281
+ first_phase_commit=$(git -C "$ROOT" log --reverse --format='%H' --grep='^test(17-01):' HEAD | head -1)
282
+ [[ -n "$first_phase_commit" ]] || fail "cannot resolve the first Phase 17 source commit"
283
+ last_phase_commit=$(git -C "$ROOT" log --format='%H %s' HEAD \
284
+ | awk '$2 ~ /^[a-z]+\(17-[0-9]+\):$/ { print $1; exit }')
285
+ [[ -n "$last_phase_commit" ]] || fail "cannot resolve the last Phase 17 source commit"
286
+ phase_base=$(git -C "$ROOT" rev-parse "$first_phase_commit^") || fail "cannot resolve the pre-Phase 17 base"
287
+
288
+ if ! node - "$ROOT" "$phase_base" "$last_phase_commit" <<'NODE'
289
+ const { execFileSync } = require("child_process");
290
+ const [root, base, terminal] = process.argv.slice(2);
291
+ const loadAt = (commit, path) => JSON.parse(execFileSync(
292
+ "git",
293
+ ["-C", root, "show", `${commit}:${path}`],
294
+ { encoding: "utf8" },
295
+ ));
296
+ const normalize = (value) => Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)));
297
+ const selected = (value, includeVersion) => ({
298
+ ...(includeVersion ? { version: value.version } : {}),
299
+ dependencies: normalize(value.dependencies ?? {}),
300
+ devDependencies: normalize(value.devDependencies ?? {}),
301
+ optionalDependencies: normalize(value.optionalDependencies ?? {}),
302
+ peerDependencies: normalize(value.peerDependencies ?? {}),
303
+ });
304
+ for (const [path, includeVersion] of [["package.json", true], ["mcp-memory-server/package.json", false]]) {
305
+ const before = selected(loadAt(base, path), includeVersion);
306
+ const after = selected(loadAt(terminal, path), includeVersion);
307
+ if (JSON.stringify(before) !== JSON.stringify(after)) {
308
+ console.error(`dependency or version delta from pre-phase base: ${path}`);
309
+ process.exit(1);
310
+ }
311
+ }
312
+ NODE
313
+ then
314
+ fail "package dependency/version declarations changed during Phase 17"
315
+ fi
316
+ git -C "$ROOT" diff --quiet "$phase_base" "$last_phase_commit" -- package-lock.json mcp-memory-server/package-lock.json \
317
+ || fail "lockfile changed during Phase 17"
318
+ }
319
+
320
+ check_runtime_evidence() {
321
+ local evidence_dir="$ROOT/.planning/phases/17-compaction-capture-and-artifact-store/runtime-evidence"
322
+ local validation="$ROOT/.planning/phases/17-compaction-capture-and-artifact-store/17-VALIDATION.md"
323
+ local version log
324
+ for version in 22 24 26; do
325
+ log="$evidence_dir/node$version.log"
326
+ if [[ ! -s "$log" ]]; then
327
+ mark_missing "actual Node $version runtime evidence"
328
+ else
329
+ grep -qE 'PASS|passed|test:smoke' "$log" || fail "Node $version evidence does not record a passing changed suite"
330
+ fi
331
+ require_pattern "$validation" "Node $version observed GREEN at commit [0-9a-f]{7,40}" "Node $version tested-commit validation wording"
332
+ done
333
+ log="$evidence_dir/bash32.log"
334
+ if [[ ! -s "$log" ]]; then
335
+ mark_missing "actual Bash 3.2 runtime evidence"
336
+ else
337
+ grep -qF 'smoke-bash32: OK' "$log" || fail "Bash 3.2 evidence does not record a passing smoke run"
338
+ fi
339
+ }
340
+
341
+ run_baseline
342
+ inspect_package
343
+ check_cli_and_completion
344
+ check_doctor_contract
345
+ check_env_contract
346
+ check_uninstall_bytes
347
+ check_docs_contract
348
+ check_dependency_delta
349
+
350
+ if [[ "$missing" -gt 0 ]]; then
351
+ if [[ "$MODE" == "--expect-red" ]]; then
352
+ printf '%s\n' "$RED_MARKER" >&2
353
+ exit "$EXPECTED_RED_EXIT"
354
+ fi
355
+ fail "Phase 17 package/operator lifecycle contract is incomplete"
356
+ fi
357
+
358
+ if [[ "$MODE" != "--expect-red" ]]; then
359
+ echo "PASS: Phase 17 package/operator lifecycle contract"
360
+ exit 0
361
+ fi
362
+
363
+ check_runtime_evidence
364
+ if [[ "$missing" -gt 0 ]]; then
365
+ printf '%s\n' "$RED_MARKER" >&2
366
+ exit "$EXPECTED_RED_EXIT"
367
+ fi
368
+
369
+ echo "Phase 17 package/operator lifecycle is already complete; expected RED is stale" >&2
370
+ exit 1