@cairnkeep/cli 2.12.0 → 2.13.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 (51) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/README.md +20 -1
  3. package/docs/harness-compatibility.md +6 -0
  4. package/docs/learning/CURRICULUM-MAP.md +3 -1
  5. package/docs/learning/FEATURE-GUIDE.md +6 -2
  6. package/docs/learning/README.md +1 -1
  7. package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
  8. package/docs/learning/lessons/L01-safe-trial.md +3 -3
  9. package/docs/learning/lessons/L02-installation.md +2 -2
  10. package/docs/learning/lessons/L03-first-project.md +1 -1
  11. package/docs/learning/lessons/L13-session-evidence.md +48 -2
  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 +2 -2
  16. package/docs/learning/lessons/L23-guided-setup.md +2 -2
  17. package/docs/learning/tracks/evidence-and-evaluation.md +2 -1
  18. package/docs/native-windows.md +9 -0
  19. package/docs/operating.md +43 -0
  20. package/docs/privacy-and-data-flow.md +36 -0
  21. package/docs/quickstart.md +5 -0
  22. package/docs/storage.md +26 -0
  23. package/docs/work-evidence.md +80 -0
  24. package/examples/eval/bundled-fake.json +1 -1
  25. package/mcp-memory-server/dist/artifact-cli.js +6 -2
  26. package/mcp-memory-server/dist/index.js +27 -0
  27. package/mcp-memory-server/dist/mcp-tool-catalog.js +2 -0
  28. package/mcp-memory-server/dist/trajectory-cli.js +9 -3
  29. package/mcp-memory-server/dist/work-evidence-cli.js +121 -0
  30. package/mcp-memory-server/dist/work-evidence-schema.js +121 -0
  31. package/mcp-memory-server/dist/work-evidence-store.js +516 -0
  32. package/mcp-memory-server/package.json +2 -1
  33. package/package.json +1 -1
  34. package/scripts/cairn-cli.mjs +2 -0
  35. package/scripts/completion.sh +11 -3
  36. package/scripts/doctor.sh +30 -1
  37. package/scripts/test-cli-dispatch.sh +3 -1
  38. package/scripts/test-completion.sh +4 -0
  39. package/scripts/test-package-install.sh +4 -0
  40. package/scripts/test-uninstall.sh +10 -0
  41. package/scripts/test-windows-native.mjs +3 -0
  42. package/scripts/test-work-evidence-cli.sh +89 -0
  43. package/scripts/verify-docs-parity.sh +36 -0
  44. package/scripts/windows-platform.mjs +15 -2
  45. package/templates/env.example.template +10 -0
  46. package/templates/start-claude.sh.template +18 -0
  47. package/templates/start-codex.sh.template +18 -0
  48. package/templates/start-kimi.sh.template +17 -0
  49. package/templates/start-opencode.sh.template +17 -0
  50. package/templates/start-pi.sh.template +17 -0
  51. package/templates/start-qwen.sh.template +17 -0
@@ -15,6 +15,7 @@ tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
15
15
  "$cairn" help | grep -q "cairn memory-server" || fail "help missing memory-server"
16
16
  "$cairn" help | grep -q "cairn notes" || fail "help missing notes"
17
17
  "$cairn" help | grep -q "cairn artifact <list|show|delete|prune>" || fail "help missing artifact"
18
+ "$cairn" help | grep -q "cairn evidence <list|show|delete|prune|doctor>" || fail "help missing evidence"
18
19
  "$cairn" help | grep -q "cairn capabilities <list|status|enable|disable|reset|logging>" || fail "help missing capabilities"
19
20
  "$cairn" help | grep -q "cairn mcp-tools <list|status|set|reset>" || fail "help missing mcp-tools"
20
21
  "$cairn" help | grep -q "cairn pack <init|lock|validate|install|list|show|remove|enable|disable|update|skills|approve-skill|revoke-skill>" || fail "help missing pack"
@@ -24,6 +25,7 @@ tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
24
25
  "$cairn" sync-kimi --help >/dev/null 2>&1 || fail "cairn sync-kimi dispatch"
25
26
  env -u CAIRN_NOTE_DISTILLATION "$cairn" notes --help >/dev/null 2>&1 || fail "cairn notes dispatch"
26
27
  "$cairn" artifact --help | grep -q "cairn artifact list" || fail "cairn artifact dispatch"
28
+ "$cairn" evidence --help | grep -q "cairn evidence list" || fail "cairn evidence dispatch"
27
29
  "$cairn" capabilities --help >"$tmp/capabilities-help" 2>/dev/null || fail "cairn capabilities dispatch"
28
30
  "$cairn" mcp-tools list --json >"$tmp/mcp-tools.json" || fail "cairn mcp-tools dispatch"
29
31
  CAIRN_PACK_BASE_DIR="$tmp/pack-store" "$cairn" pack list --json >"$tmp/packs.json" || fail "cairn pack dispatch"
@@ -70,4 +72,4 @@ CAIRN_AGENTFS_BASE_DIR="$tmp/store" "$cairn" memory path | grep -qx "$tmp/store"
70
72
  "$cairn" audit-timer --render-only "$tmp/u" >/dev/null || fail "cairn audit-timer dispatch"
71
73
  [[ -f "$tmp/u/memory-wiki-audit.timer" ]] || fail "audit-timer render via cairn produced no units"
72
74
 
73
- echo "PASS: cairn dispatch (doctor, memory, artifact, audit-timer)"
75
+ echo "PASS: cairn dispatch (doctor, memory, artifact, evidence, audit-timer)"
@@ -48,6 +48,9 @@ for shell in bash zsh fish; do
48
48
  grep -q 'pack' "$tmp/$shell"
49
49
  grep -q 'init.*lock.*validate.*install.*list.*show.*remove.*enable.*disable.*update.*skills.*approve-skill.*revoke-skill\|init lock validate install list show remove enable disable update skills approve-skill revoke-skill' "$tmp/$shell"
50
50
  grep -q -- '--purge-packs\|-l purge-packs' "$tmp/$shell"
51
+ grep -q 'evidence' "$tmp/$shell"
52
+ grep -q 'list.*show.*delete.*prune.*doctor\|list show delete prune doctor' "$tmp/$shell"
53
+ grep -q -- '--status\|-l status' "$tmp/$shell"
51
54
  done
52
55
 
53
56
  "$ROOT/bin/cairn" help >"$tmp/root-help"
@@ -64,6 +67,7 @@ if grep -q 'doctor-diagnosis' "$tmp/eval-help"; then
64
67
  exit 1
65
68
  fi
66
69
  grep -qF 'cairn skill <harvest|list|show|review|propose|evaluate|apply|rollback>' "$tmp/root-help"
70
+ grep -qF 'cairn evidence <list|show|delete|prune|doctor>' "$tmp/root-help"
67
71
  node "$ROOT/mcp-memory-server/dist/skill-cli.js" --help >"$tmp/skill-help"
68
72
  for command in $skill_commands; do
69
73
  grep -q "cairn skill $command" "$tmp/skill-help"
@@ -292,6 +292,10 @@ for module in artifact-schema artifact-store compaction-normalize artifact-cli;
292
292
  [[ -f "$installed_root/mcp-memory-server/dist/$module.js" ]] || \
293
293
  fail "npm tarball omitted compiled $module"
294
294
  done
295
+ for module in work-evidence-schema work-evidence-store work-evidence-cli; do
296
+ [[ -f "$installed_root/mcp-memory-server/dist/$module.js" ]] || \
297
+ fail "npm tarball omitted compiled $module"
298
+ done
295
299
  [[ -x "$installed_root/claude/hooks/compaction-capture.sh" ]] || \
296
300
  fail "npm tarball omitted the executable Claude compaction hook"
297
301
  [[ -f "$installed_root/opencode/plugins/memory-capture.ts" ]] || \
@@ -124,6 +124,7 @@ ADJACENT_PROJECT="$SB/adjacent-project"
124
124
  mkdir -p "$PROJECT_DATA/.ai" \
125
125
  "$PROJECT_DATA/.agentfs/eval/experiments/complete/snapshots/task-one" \
126
126
  "$PROJECT_DATA/.agentfs/eval/experiments/partial" \
127
+ "$PROJECT_DATA/.agentfs/work-evidence/v1/records" \
127
128
  "$ADJACENT_PROJECT/.agentfs/eval/experiments/adjacent"
128
129
  printf '%s\n' '{"schema_version":1,"capabilities":{"wiki":false},"logging":{"callbacks":true}}' >"$PROJECT_DATA/.ai/capabilities.json"
129
130
  chmod 600 "$PROJECT_DATA/.ai/capabilities.json"
@@ -132,17 +133,22 @@ printf 'artifact-v1\000durable\377bytes\n' >"$PROJECT_DATA/.agentfs/artifacts.db
132
133
  printf 'session-and-callback-v1\000durable\377bytes\n' >"$PROJECT_DATA/.agentfs/trajectory.db"
133
134
  printf 'callback-wal-v1\000durable\377bytes\n' >"$PROJECT_DATA/.agentfs/trajectory.db-wal"
134
135
  printf 'unrelated-memory-v1\000durable\377bytes\n' >"$PROJECT_DATA/.agentfs/project.db"
136
+ printf 'work-evidence-v1\000durable\377bytes\n' >"$PROJECT_DATA/.agentfs/work-evidence/v1/records/wev-fixture.json"
135
137
  printf 'complete-report-v1\000private\377bytes\n' >"$PROJECT_DATA/.agentfs/eval/experiments/complete/report.json"
136
138
  printf 'partial-report-v1\000private\377bytes\n' >"$PROJECT_DATA/.agentfs/eval/experiments/partial/report.json"
137
139
  printf 'note-snapshot-v1\000private\377bytes\n' >"$PROJECT_DATA/.agentfs/eval/experiments/complete/snapshots/task-one/note.md"
138
140
  printf 'adjacent-eval-v1\000must-survive\377bytes\n' >"$ADJACENT_PROJECT/.agentfs/eval/experiments/adjacent/report.json"
139
141
  chmod 700 "$PROJECT_DATA/.agentfs" "$PROJECT_DATA/.agentfs/eval" \
142
+ "$PROJECT_DATA/.agentfs/work-evidence" \
143
+ "$PROJECT_DATA/.agentfs/work-evidence/v1" \
144
+ "$PROJECT_DATA/.agentfs/work-evidence/v1/records" \
140
145
  "$PROJECT_DATA/.agentfs/eval/experiments" \
141
146
  "$PROJECT_DATA/.agentfs/eval/experiments/complete" \
142
147
  "$PROJECT_DATA/.agentfs/eval/experiments/complete/snapshots" \
143
148
  "$PROJECT_DATA/.agentfs/eval/experiments/complete/snapshots/task-one" \
144
149
  "$PROJECT_DATA/.agentfs/eval/experiments/partial"
145
150
  chmod 600 "$PROJECT_DATA/.agentfs/project.db" \
151
+ "$PROJECT_DATA/.agentfs/work-evidence/v1/records/wev-fixture.json" \
146
152
  "$PROJECT_DATA/.agentfs/eval/experiments/complete/report.json" \
147
153
  "$PROJECT_DATA/.agentfs/eval/experiments/partial/report.json"
148
154
  chmod 400 "$PROJECT_DATA/.agentfs/eval/experiments/complete/snapshots/task-one/note.md"
@@ -155,6 +161,7 @@ cp "$PROJECT_DATA/.ai/operator-state.bin" "$SB/operator-state.before.bin"
155
161
  cp "$PROJECT_DATA/.agentfs/artifacts.db" "$SB/artifacts.before.db"
156
162
  cp "$PROJECT_DATA/.agentfs/trajectory.db" "$SB/trajectory.before.db"
157
163
  cp "$PROJECT_DATA/.agentfs/trajectory.db-wal" "$SB/trajectory.before.db-wal"
164
+ cp "$PROJECT_DATA/.agentfs/work-evidence/v1/records/wev-fixture.json" "$SB/work-evidence.before.json"
158
165
  agentfs_before=$(tree_identity "$PROJECT_DATA/.agentfs")
159
166
  eval_before=$(tree_identity "$PROJECT_DATA/.agentfs/eval")
160
167
  adjacent_before=$(tree_identity "$ADJACENT_PROJECT/.agentfs")
@@ -239,6 +246,7 @@ check "default uninstall leaves normal OpenCode installation exact" "$(diff -qr
239
246
  check "default uninstall keeps unrelated .ai bytes exact" "$(cmp -s "$SB/operator-state.before.bin" "$PROJECT_DATA/.ai/operator-state.bin" && echo yes || echo no)" "yes"
240
247
  check "default uninstall keeps callback DB exact" "$(cmp -s "$SB/trajectory.before.db" "$PROJECT_DATA/.agentfs/trajectory.db" && echo yes || echo no)" "yes"
241
248
  check "default uninstall keeps callback WAL exact" "$(cmp -s "$SB/trajectory.before.db-wal" "$PROJECT_DATA/.agentfs/trajectory.db-wal" && echo yes || echo no)" "yes"
249
+ check "default uninstall keeps work evidence exact" "$(cmp -s "$SB/work-evidence.before.json" "$PROJECT_DATA/.agentfs/work-evidence/v1/records/wev-fixture.json" && echo yes || echo no)" "yes"
242
250
  check "default uninstall keeps complete and partial eval reports, snapshots, modes, and layout exact" "$(tree_identity "$PROJECT_DATA/.agentfs/eval")" "$eval_before"
243
251
  check "default uninstall keeps unrelated project memory exact" "$(tree_identity "$PROJECT_DATA/.agentfs")" "$agentfs_before"
244
252
  check "default uninstall leaves adjacent project exact" "$(tree_identity "$ADJACENT_PROJECT/.agentfs")" "$adjacent_before"
@@ -271,6 +279,7 @@ ARTIFACT_BK=$(ls -dt "$ARTIFACT_HOME/.cairnkeep-uninstall-"* 2>/dev/null | head
271
279
  check "artifact purge backup is exact" "$(cmp -s "$SB/artifacts.before.db" "$ARTIFACT_BK/files/${PROJECT_DATA#/}/.agentfs/artifacts.db" && echo yes || echo no)" "yes"
272
280
  check "callback purge backup is exact" "$(cmp -s "$SB/trajectory.before.db" "$ARTIFACT_BK/files/${PROJECT_DATA#/}/.agentfs/trajectory.db" && echo yes || echo no)" "yes"
273
281
  check "callback WAL purge backup is exact" "$(cmp -s "$SB/trajectory.before.db-wal" "$ARTIFACT_BK/files/${PROJECT_DATA#/}/.agentfs/trajectory.db-wal" && echo yes || echo no)" "yes"
282
+ check "work-evidence purge backup is exact" "$(cmp -s "$SB/work-evidence.before.json" "$ARTIFACT_BK/files/${PROJECT_DATA#/}/.agentfs/work-evidence/v1/records/wev-fixture.json" && echo yes || echo no)" "yes"
274
283
  check "purge backup contains exact eval tree, bytes, and modes" "$(tree_identity "$ARTIFACT_BK/files/${PROJECT_DATA#/}/.agentfs/eval")" "$eval_before"
275
284
  check "purge backup contains exact unrelated project memory" "$(tree_identity "$ARTIFACT_BK/files/${PROJECT_DATA#/}/.agentfs")" "$agentfs_before"
276
285
  check "selected-project purge leaves adjacent project exact" "$(tree_identity "$ADJACENT_PROJECT/.agentfs")" "$adjacent_before"
@@ -278,6 +287,7 @@ HOME="$ARTIFACT_HOME" XDG_CONFIG_HOME="$ARTIFACT_HOME/.config" bash "$ARTIFACT_B
278
287
  check "artifact revert restores exact bytes" "$(cmp -s "$SB/artifacts.before.db" "$PROJECT_DATA/.agentfs/artifacts.db" && echo yes || echo no)" "yes"
279
288
  check "callback revert restores exact bytes" "$(cmp -s "$SB/trajectory.before.db" "$PROJECT_DATA/.agentfs/trajectory.db" && echo yes || echo no)" "yes"
280
289
  check "callback WAL revert restores exact bytes" "$(cmp -s "$SB/trajectory.before.db-wal" "$PROJECT_DATA/.agentfs/trajectory.db-wal" && echo yes || echo no)" "yes"
290
+ check "work-evidence revert restores exact bytes" "$(cmp -s "$SB/work-evidence.before.json" "$PROJECT_DATA/.agentfs/work-evidence/v1/records/wev-fixture.json" && echo yes || echo no)" "yes"
281
291
  check "purge revert restores exact eval tree, bytes, and modes" "$(tree_identity "$PROJECT_DATA/.agentfs/eval")" "$eval_before"
282
292
  check "purge revert restores exact unrelated project memory" "$(tree_identity "$PROJECT_DATA/.agentfs")" "$agentfs_before"
283
293
  check "purge revert leaves adjacent project exact" "$(tree_identity "$ADJACENT_PROJECT/.agentfs")" "$adjacent_before"
@@ -36,6 +36,8 @@ try {
36
36
  assert.match(launcherModule(), /spawnSync\(command/);
37
37
  assert.match(launcherPowerShell(), /pre-launch\.ps1/);
38
38
  assert.match(launcherPowerShell(), /post-exit\.ps1/);
39
+ assert.match(launcherPowerShell(), /evidence run --harness/);
40
+ assert.match(launcherPowerShell(), /CAIRN_WORK_EVIDENCE/);
39
41
 
40
42
  const claudeRoot = join(sandbox, "Claude Config");
41
43
  await runWindowsCommand({ command: "sync", args: ["--apply", "--live-root", claudeRoot], root });
@@ -56,6 +58,7 @@ try {
56
58
  await runWindowsCommand({ command: "memory", args: ["import", archive], root });
57
59
  assert.equal(readFileSync(join(memory, "project.db"), "utf8"), "sqlite-a");
58
60
  assert.match(powershellCompletion(), /Register-ArgumentCompleter/);
61
+ assert.match(powershellCompletion(), /evidence/);
59
62
 
60
63
  let containerArgs = [];
61
64
  runNativeContainer(["stdio", "--image", "example/windows:1", "--volume", "windows-data"], root, (_engine, args) => { containerArgs = args; });
@@ -0,0 +1,89 @@
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
+ fail() { echo "FAIL: $1" >&2; exit 1; }
8
+
9
+ npm --prefix "$ROOT/mcp-memory-server" run build >/dev/null
10
+
11
+ fake_bin="$tmp/bin"
12
+ mkdir -p "$fake_bin"
13
+ cat >"$fake_bin/cairn" <<EOF
14
+ #!/usr/bin/env bash
15
+ exec node "$ROOT/bin/cairn" "\$@"
16
+ EOF
17
+ chmod +x "$fake_bin/cairn"
18
+
19
+ for harness in claude opencode pi kimi qwen codex; do
20
+ project="$tmp/$harness"
21
+ mkdir -p "$project/.ai" "$project/.agentfs"
22
+ cp "$ROOT/templates/start-$harness.sh.template" "$project/.ai/start-$harness.sh"
23
+ chmod +x "$project/.ai/start-$harness.sh"
24
+ printf '*\n!.gitignore\n' >"$project/.agentfs/.gitignore"
25
+ printf 'CAIRN_WORK_EVIDENCE=1\n' >"$project/.ai/.env"
26
+ cat >"$fake_bin/$harness" <<EOF
27
+ #!/usr/bin/env bash
28
+ printf '%s\n' "\$*" > "$project/args.txt"
29
+ printf '%s\n' changed > "$project/session.txt"
30
+ exit 17
31
+ EOF
32
+ chmod +x "$fake_bin/$harness"
33
+ cat >"$project/.ai/post-exit.sh" <<EOF
34
+ printf '%s\n' "\$CAIRN_EXIT_STATUS" > "$project/post-status.txt"
35
+ EOF
36
+ (
37
+ cd "$project"
38
+ git init --quiet
39
+ git config user.email fixture@example.invalid
40
+ git config user.name Fixture
41
+ printf '%s\n' start > session.txt
42
+ git add session.txt .agentfs/.gitignore
43
+ git commit --quiet -m start
44
+ )
45
+ status=0
46
+ PATH="$fake_bin:$PATH" "$project/.ai/start-$harness.sh" --fixture "value with spaces" || status=$?
47
+ [[ $status -eq 17 ]] || fail "$harness launcher lost exit status"
48
+ grep -q -- '--fixture value with spaces' "$project/args.txt" || fail "$harness launcher lost arguments"
49
+ [[ $(cat "$project/post-status.txt") == 17 ]] || fail "$harness post-exit seam lost status"
50
+ (cd "$project" && PATH="$fake_bin:$PATH" "$ROOT/bin/cairn" evidence list --json) >"$tmp/$harness.json"
51
+ node -e '
52
+ const value=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));
53
+ if(value.evidence.length!==1||value.evidence[0].status!=="complete"||value.evidence[0].harness!==process.argv[2]||value.evidence[0].exit_status!==17)process.exit(1);
54
+ if(!value.evidence[0].touched_paths.includes("session.txt"))process.exit(1);
55
+ ' "$tmp/$harness.json" "$harness" || fail "$harness evidence record is incomplete"
56
+ done
57
+
58
+ disabled="$tmp/disabled"
59
+ mkdir -p "$disabled/.ai"
60
+ cp "$ROOT/templates/start-codex.sh.template" "$disabled/.ai/start-codex.sh"
61
+ chmod +x "$disabled/.ai/start-codex.sh"
62
+ cat >"$fake_bin/cairn" <<'EOF'
63
+ #!/usr/bin/env bash
64
+ exit 88
65
+ EOF
66
+ cat >"$fake_bin/codex" <<'EOF'
67
+ #!/usr/bin/env bash
68
+ exit 0
69
+ EOF
70
+ chmod +x "$fake_bin/cairn" "$fake_bin/codex"
71
+ PATH="$fake_bin:$PATH" "$disabled/.ai/start-codex.sh" || fail "disabled launcher called the evidence wrapper"
72
+ [[ ! -e "$disabled/.agentfs/work-evidence" ]] || fail "disabled launcher created evidence storage"
73
+
74
+ cat >"$fake_bin/codex" <<EOF
75
+ #!/usr/bin/env bash
76
+ printf launched >"$tmp/old-cairn-fallback"
77
+ exit 19
78
+ EOF
79
+ chmod +x "$fake_bin/codex"
80
+ status=0
81
+ CAIRN_WORK_EVIDENCE=1 PATH="$fake_bin:$PATH" "$disabled/.ai/start-codex.sh" 2>"$tmp/old-cairn.err" || status=$?
82
+ [[ $status -eq 19 ]] || fail "old cairn fallback lost the harness exit status"
83
+ [[ -f "$tmp/old-cairn-fallback" ]] || fail "old cairn fallback did not launch the harness"
84
+ grep -q 'compatible evidence command unavailable' "$tmp/old-cairn.err" || fail "old cairn fallback did not explain capture loss"
85
+
86
+ "$ROOT/bin/cairn" help | grep -q 'cairn evidence' || fail "root help omits evidence"
87
+ node "$ROOT/mcp-memory-server/dist/work-evidence-cli.js" --help | grep -q 'cairn evidence list' || fail "evidence help omits list"
88
+
89
+ echo "PASS: work-evidence CLI and all harness launchers preserve lifecycle semantics"
@@ -432,6 +432,41 @@ check_artifact_remote_path_contract() {
432
432
  return "$failed"
433
433
  }
434
434
 
435
+ check_work_evidence_contract() {
436
+ local failed=0 key value source_term
437
+
438
+ while IFS='|' read -r key value source_term; do
439
+ [[ -n "$key" ]] || continue
440
+ grep -qF "$key" mcp-memory-server/src/work-evidence-schema.ts || failed=1
441
+ grep -qF "$source_term" mcp-memory-server/src/work-evidence-schema.ts || failed=1
442
+ grep -qxF "# $key=$value" templates/env.example.template || failed=1
443
+ grep -qF "$key" README.md docs/operating.md docs/work-evidence.md || failed=1
444
+ grep -qF "$value" README.md docs/operating.md docs/work-evidence.md || failed=1
445
+ done <<'EOF'
446
+ CAIRN_WORK_EVIDENCE_RETENTION_DAYS|30|WORK_EVIDENCE_DEFAULT_RETENTION_DAYS = 30
447
+ CAIRN_WORK_EVIDENCE_STORE_MAX_BYTES|67108864|WORK_EVIDENCE_DEFAULT_STORE_MAX_BYTES = 64 * 1024 * 1024
448
+ CAIRN_WORK_EVIDENCE_MAX_TOUCHED_PATHS|4096|WORK_EVIDENCE_DEFAULT_MAX_TOUCHED_PATHS = 4096
449
+ CAIRN_WORK_EVIDENCE_PATCH_MAX_BYTES|1048576|WORK_EVIDENCE_DEFAULT_PATCH_MAX_BYTES = 1024 * 1024
450
+ EOF
451
+
452
+ for key in CAIRN_WORK_EVIDENCE CAIRN_WORK_EVIDENCE_PATCH; do
453
+ grep -qF "$key" mcp-memory-server/src/work-evidence-schema.ts templates/env.example.template README.md docs/operating.md docs/privacy-and-data-flow.md || failed=1
454
+ done
455
+ for source_term in work_evidence_list work_evidence_read; do
456
+ grep -qF "\"$source_term\"" mcp-memory-server/src/index.ts || failed=1
457
+ grep -qF "$source_term" README.md docs/operating.md docs/privacy-and-data-flow.md docs/work-evidence.md || failed=1
458
+ done
459
+ for source_term in 'list [--status pending|complete] [--json]' 'show <evidence-id-or-prefix> [--json]' 'delete <evidence-id-or-prefix> [--dry-run] [--json]' 'prune [--dry-run] [--json]' 'doctor [--repair] [--json]'; do
460
+ grep -qF "$source_term" mcp-memory-server/src/work-evidence-cli.ts || failed=1
461
+ done
462
+ grep -qF '.agentfs/work-evidence/v1/' docs/storage.md docs/privacy-and-data-flow.md docs/work-evidence.md || failed=1
463
+ grep -qF 'cairn evidence' docs/learning/CURRICULUM-MAP.md docs/learning/FEATURE-GUIDE.md docs/learning/lessons/L13-session-evidence.md || failed=1
464
+ grep -qF 'never exposed by the HTTP transport' docs/work-evidence.md || failed=1
465
+
466
+ [[ "$failed" -eq 0 ]] && echo "[work-evidence-contract] OK: flags/defaults/tools/storage/privacy/learning match source"
467
+ return "$failed"
468
+ }
469
+
435
470
  check_capability_contract() {
436
471
  local failed=0 file id env tool term source_file docs_files
437
472
 
@@ -624,6 +659,7 @@ main() {
624
659
  check_typed_contract || failed=1
625
660
  check_artifact_contract || failed=1
626
661
  check_artifact_remote_path_contract || failed=1
662
+ check_work_evidence_contract || failed=1
627
663
  check_capability_contract || failed=1
628
664
  check_native_capability_docs || failed=1
629
665
 
@@ -190,7 +190,19 @@ if ($env:CAIRN_EXTRA_SETTINGS -and $Harness -eq 'claude') {
190
190
  if ($env:CAIRN_EXTRA_SETTINGS -and $Harness -eq 'opencode') { $env:OPENCODE_CONFIG = $env:CAIRN_EXTRA_SETTINGS }
191
191
  Push-Location $ProjectRoot
192
192
  try {
193
- & $Harness @HarnessArgs
193
+ $EvidenceEnabled = $env:CAIRN_WORK_EVIDENCE -match '^(1|true|yes|on)$'
194
+ $CairnCommand = if ($EvidenceEnabled) { Get-Command cairn -ErrorAction SilentlyContinue } else { $null }
195
+ $EvidenceSupported = $false
196
+ if ($EvidenceEnabled -and $CairnCommand) {
197
+ & $CairnCommand.Source evidence --help *> $null
198
+ $EvidenceSupported = ($LASTEXITCODE -eq 0)
199
+ }
200
+ if ($EvidenceSupported) {
201
+ & $CairnCommand.Source evidence run --harness $Harness -- $Harness @HarnessArgs
202
+ } else {
203
+ if ($EvidenceEnabled) { Write-Warning 'a compatible cairn evidence command is unavailable; launching without capture' }
204
+ & $Harness @HarnessArgs
205
+ }
194
206
  $Status = if ($null -eq $LASTEXITCODE) { 0 } else { $LASTEXITCODE }
195
207
  } finally {
196
208
  Pop-Location
@@ -440,6 +452,7 @@ async function doctorWindows(root, args) {
440
452
  for (const [label, file, doctorArgs] of [
441
453
  ["trajectory", "trajectory-cli.js", ["doctor", "--json", ...(repair ? ["--repair"] : [])]],
442
454
  ["artifact", "artifact-cli.js", ["doctor", "--json", ...(repair ? ["--repair"] : [])]],
455
+ ["work evidence", "work-evidence-cli.js", ["doctor", "--json", ...(repair ? ["--repair"] : [])]],
443
456
  ["typed memory", "node-cli.js", ["doctor", "--project-root", process.cwd(), ...(repair ? ["--repair"] : [])]],
444
457
  ["capability", "capability-cli.js", ["doctor", "--json"]],
445
458
  ["context pack", "context-pack-cli.js", ["doctor", "--json"]],
@@ -695,7 +708,7 @@ export function powershellCompletion() {
695
708
  const harnesses = HARNESS_IDS.map((id) => `'${id}'`).join(",");
696
709
  return `Register-ArgumentCompleter -Native -CommandName cairn -ScriptBlock {
697
710
  param($wordToComplete, $commandAst, $cursorPosition)
698
- $commands = 'bootstrap','setup','memory-server','sync','sync-pi','sync-kimi','doctor','trajectory','artifact','capabilities','mcp-tools','pack','notes','eval','skill','graph','memory','audit-timer','uninstall','completion','version','help'
711
+ $commands = 'bootstrap','setup','memory-server','sync','sync-pi','sync-kimi','doctor','trajectory','artifact','evidence','capabilities','mcp-tools','pack','notes','eval','skill','graph','memory','audit-timer','uninstall','completion','version','help'
699
712
  $setup = '--git','--harness','--memory','--policy','--yes','--json','init','existing','none',${harnesses},'local'
700
713
  $candidates = if ($commandAst.ToString() -match '^\\s*cairn\\s+setup(?:\\s|$)') { $setup } else { $commands }
701
714
  $candidates | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
@@ -65,6 +65,16 @@
65
65
  # Bootstrap creates .ai/trajectory-redaction.json for optional custom patterns.
66
66
  # CAIRN_TRAJECTORY_REDACTION_FILE=.ai/trajectory-redaction.json
67
67
  #
68
+ # Git-linked work evidence is local and OFF by default. Generated harness
69
+ # launchers capture bounded Git state before and after a session. Optional patch
70
+ # capture also requires the artifact store; no patch is ever applied by Cairnkeep.
71
+ # CAIRN_WORK_EVIDENCE=1
72
+ # CAIRN_WORK_EVIDENCE_PATCH=1
73
+ # CAIRN_WORK_EVIDENCE_RETENTION_DAYS=30
74
+ # CAIRN_WORK_EVIDENCE_STORE_MAX_BYTES=67108864
75
+ # CAIRN_WORK_EVIDENCE_MAX_TOUCHED_PATHS=4096
76
+ # CAIRN_WORK_EVIDENCE_PATCH_MAX_BYTES=1048576
77
+ #
68
78
  # Harness-produced compaction continuity and explicit artifact access are local
69
79
  # and OFF by default. Either capability may be enabled independently; neither
70
80
  # needs a model, API key, or network endpoint. HTTP artifact tools require the
@@ -68,6 +68,24 @@ if [[ -n "${CAIRN_EXTRA_SETTINGS:-}" ]]; then
68
68
  launch_args+=(--settings "$CAIRN_EXTRA_SETTINGS")
69
69
  fi
70
70
 
71
+ case "${CAIRN_WORK_EVIDENCE:-}" in
72
+ 1|true|TRUE|yes|YES|on|ON)
73
+ if command -v cairn >/dev/null 2>&1 && cairn evidence --help >/dev/null 2>&1; then
74
+ set +e
75
+ cairn evidence run --harness claude -- claude "${launch_args[@]}" "$@"
76
+ CAIRN_EXIT_STATUS=$?
77
+ set -e
78
+ export CAIRN_EXIT_STATUS
79
+ if [[ -f "$ROOT_DIR/.ai/post-exit.sh" ]]; then
80
+ # shellcheck disable=SC1091
81
+ . "$ROOT_DIR/.ai/post-exit.sh" || true
82
+ fi
83
+ exit "$CAIRN_EXIT_STATUS"
84
+ fi
85
+ echo "cairn: compatible evidence command unavailable; launching without capture" >&2
86
+ ;;
87
+ esac
88
+
71
89
  if [[ -f "$ROOT_DIR/.ai/post-exit.sh" ]]; then
72
90
  # Run (not exec) so the post-exit hook fires after the harness returns.
73
91
  set +e
@@ -17,6 +17,24 @@ if [[ -f "$ROOT_DIR/.ai/pre-launch.sh" ]]; then
17
17
  fi
18
18
  cd "$ROOT_DIR"
19
19
 
20
+ case "${CAIRN_WORK_EVIDENCE:-}" in
21
+ 1|true|TRUE|yes|YES|on|ON)
22
+ if command -v cairn >/dev/null 2>&1 && cairn evidence --help >/dev/null 2>&1; then
23
+ set +e
24
+ cairn evidence run --harness codex -- codex "$@"
25
+ CAIRN_EXIT_STATUS=$?
26
+ set -e
27
+ export CAIRN_EXIT_STATUS
28
+ if [[ -f "$ROOT_DIR/.ai/post-exit.sh" ]]; then
29
+ # shellcheck disable=SC1091
30
+ . "$ROOT_DIR/.ai/post-exit.sh" || true
31
+ fi
32
+ exit "$CAIRN_EXIT_STATUS"
33
+ fi
34
+ echo "cairn: compatible evidence command unavailable; launching without capture" >&2
35
+ ;;
36
+ esac
37
+
20
38
  if [[ -f "$ROOT_DIR/.ai/post-exit.sh" ]]; then
21
39
  set +e
22
40
  codex "$@"
@@ -26,6 +26,23 @@ fi
26
26
 
27
27
  cd "$ROOT_DIR"
28
28
 
29
+ case "${CAIRN_WORK_EVIDENCE:-}" in
30
+ 1|true|TRUE|yes|YES|on|ON)
31
+ if command -v cairn >/dev/null 2>&1 && cairn evidence --help >/dev/null 2>&1; then
32
+ set +e
33
+ cairn evidence run --harness kimi -- kimi "$@"
34
+ CAIRN_EXIT_STATUS=$?
35
+ set -e
36
+ export CAIRN_EXIT_STATUS
37
+ if [[ -f "$ROOT_DIR/.ai/post-exit.sh" ]]; then
38
+ # shellcheck disable=SC1091
39
+ . "$ROOT_DIR/.ai/post-exit.sh" || true
40
+ fi
41
+ exit "$CAIRN_EXIT_STATUS"
42
+ fi
43
+ echo "cairn: compatible evidence command unavailable; launching without capture" >&2
44
+ ;;
45
+ esac
29
46
  if [[ -f "$ROOT_DIR/.ai/post-exit.sh" ]]; then
30
47
  set +e
31
48
  kimi "$@"
@@ -70,6 +70,23 @@ if [[ -n "${CAIRN_EXTRA_SETTINGS:-}" ]]; then
70
70
  export OPENCODE_CONFIG="$CAIRN_EXTRA_SETTINGS"
71
71
  fi
72
72
 
73
+ case "${CAIRN_WORK_EVIDENCE:-}" in
74
+ 1|true|TRUE|yes|YES|on|ON)
75
+ if command -v cairn >/dev/null 2>&1 && cairn evidence --help >/dev/null 2>&1; then
76
+ set +e
77
+ cairn evidence run --harness opencode -- opencode "$@"
78
+ CAIRN_EXIT_STATUS=$?
79
+ set -e
80
+ export CAIRN_EXIT_STATUS
81
+ if [[ -f "$ROOT_DIR/.ai/post-exit.sh" ]]; then
82
+ # shellcheck disable=SC1091
83
+ . "$ROOT_DIR/.ai/post-exit.sh" || true
84
+ fi
85
+ exit "$CAIRN_EXIT_STATUS"
86
+ fi
87
+ echo "cairn: compatible evidence command unavailable; launching without capture" >&2
88
+ ;;
89
+ esac
73
90
  if [[ -f "$ROOT_DIR/.ai/post-exit.sh" ]]; then
74
91
  set +e
75
92
  opencode "$@"
@@ -17,6 +17,23 @@ if [[ -f "$ROOT_DIR/.ai/pre-launch.sh" ]]; then
17
17
  fi
18
18
  cd "$ROOT_DIR"
19
19
 
20
+ case "${CAIRN_WORK_EVIDENCE:-}" in
21
+ 1|true|TRUE|yes|YES|on|ON)
22
+ if command -v cairn >/dev/null 2>&1 && cairn evidence --help >/dev/null 2>&1; then
23
+ set +e
24
+ cairn evidence run --harness pi -- pi "$@"
25
+ CAIRN_EXIT_STATUS=$?
26
+ set -e
27
+ export CAIRN_EXIT_STATUS
28
+ if [[ -f "$ROOT_DIR/.ai/post-exit.sh" ]]; then
29
+ # shellcheck disable=SC1091
30
+ . "$ROOT_DIR/.ai/post-exit.sh" || true
31
+ fi
32
+ exit "$CAIRN_EXIT_STATUS"
33
+ fi
34
+ echo "cairn: compatible evidence command unavailable; launching without capture" >&2
35
+ ;;
36
+ esac
20
37
  if [[ -f "$ROOT_DIR/.ai/post-exit.sh" ]]; then
21
38
  set +e
22
39
  pi "$@"
@@ -19,6 +19,23 @@ fi
19
19
 
20
20
  cd "$ROOT_DIR"
21
21
 
22
+ case "${CAIRN_WORK_EVIDENCE:-}" in
23
+ 1|true|TRUE|yes|YES|on|ON)
24
+ if command -v cairn >/dev/null 2>&1 && cairn evidence --help >/dev/null 2>&1; then
25
+ set +e
26
+ cairn evidence run --harness qwen -- qwen "$@"
27
+ CAIRN_EXIT_STATUS=$?
28
+ set -e
29
+ export CAIRN_EXIT_STATUS
30
+ if [[ -f "$ROOT_DIR/.ai/post-exit.sh" ]]; then
31
+ # shellcheck disable=SC1091
32
+ . "$ROOT_DIR/.ai/post-exit.sh" || true
33
+ fi
34
+ exit "$CAIRN_EXIT_STATUS"
35
+ fi
36
+ echo "cairn: compatible evidence command unavailable; launching without capture" >&2
37
+ ;;
38
+ esac
22
39
  if [[ -f "$ROOT_DIR/.ai/post-exit.sh" ]]; then
23
40
  set +e
24
41
  qwen "$@"