@thebassclef/lite 1.0.2 → 1.0.4

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 (44) hide show
  1. package/dist/cli.cjs +51 -7
  2. package/dist/cli.js +51 -7
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.js +1 -1
  6. package/dist/lite/.claude/hooks/session-reflection.d/00-bassclef-hook-connect.sh +63 -0
  7. package/dist/lite/.claude/hooks/session-reflection.d/05-active-bet.sh +113 -0
  8. package/dist/lite/.claude/hooks/session-reflection.d/08-settings-drift.sh +62 -0
  9. package/dist/lite/.claude/hooks/session-reflection.d/10-abrupt-stop-recovery.sh +78 -0
  10. package/dist/lite/.claude/hooks/session-reflection.d/12-session-lock.sh +63 -0
  11. package/dist/lite/.claude/hooks/session-reflection.d/15-substrate-config-validate.sh +91 -0
  12. package/dist/lite/.claude/hooks/session-reflection.d/20-artifact-staleness.sh +98 -0
  13. package/dist/lite/.claude/hooks/session-reflection.d/30-metrics-staleness.sh +61 -0
  14. package/dist/lite/.claude/hooks/session-reflection.d/35-post-merge-whereami-self-heal.sh +180 -0
  15. package/dist/lite/.claude/hooks/session-reflection.d/36-whereami-size-check.sh +117 -0
  16. package/dist/lite/.claude/hooks/session-reflection.d/40-gate-compliance.sh +108 -0
  17. package/dist/lite/.claude/hooks/session-reflection.d/45-auto-sync-status.sh +99 -0
  18. package/dist/lite/.claude/hooks/session-reflection.d/50-evolution-issues.sh +44 -0
  19. package/dist/lite/.claude/hooks/session-reflection.d/55-orientation-gate.sh +187 -0
  20. package/dist/lite/.claude/hooks/session-reflection.d/56-roadmap-reconcile-gate.sh +88 -0
  21. package/dist/lite/.claude/hooks/session-reflection.d/60-deferred-actions.sh +212 -0
  22. package/dist/lite/.claude/hooks/session-reflection.d/70-recent-strategic-artifacts.sh +101 -0
  23. package/dist/lite/.claude/hooks/session-reflection.d/75-prior-r8-findings.sh +100 -0
  24. package/dist/lite/.claude/hooks/session-reflection.d/80-hook-heartbeat-check.sh +87 -0
  25. package/dist/lite/.claude/hooks/session-reflection.d/80-workflow-staleness.sh +141 -0
  26. package/dist/lite/.claude/hooks/session-reflection.d/81-hook-manifest-staleness.sh +67 -0
  27. package/dist/lite/.claude/hooks/session-reflection.d/90-release-backlog.sh +140 -0
  28. package/dist/lite/.claude/hooks/session-reflection.d/90-secrets-expiry.sh +104 -0
  29. package/dist/lite/.claude/hooks/session-reflection.d/95-settings-hook-verify.sh +131 -0
  30. package/dist/lite/.claude/hooks/session-reflection.d/95-telemetry-emit.sh +66 -0
  31. package/dist/lite/.claude/hooks/session-reflection.d/97-install-class-dispatch.sh +57 -0
  32. package/dist/lite/.claude/hooks/session-reflection.d/tests/00-bassclef-hook-connect.test.sh +492 -0
  33. package/dist/lite/.claude/hooks/session-reflection.d/tests/05-active-bet.test.sh +336 -0
  34. package/dist/lite/.claude/hooks/session-reflection.d/tests/12-session-lock.test.sh +216 -0
  35. package/dist/lite/.claude/hooks/session-reflection.d/tests/15-substrate-config-validate.test.sh +50 -0
  36. package/dist/lite/.claude/hooks/session-reflection.d/tests/20-artifact-staleness.test.sh +348 -0
  37. package/dist/lite/.claude/hooks/session-reflection.d/tests/36-whereami-size-check.test.sh +189 -0
  38. package/dist/lite/.claude/hooks/session-reflection.d/tests/45-auto-sync-status.test.sh +263 -0
  39. package/dist/lite/.claude/hooks/session-reflection.d/tests/55-orientation-gate.test.sh +595 -0
  40. package/dist/lite/.claude/hooks/session-reflection.d/tests/56-roadmap-reconcile-gate.test.sh +227 -0
  41. package/dist/lite/.claude/hooks/session-reflection.d/tests/75-prior-r8-findings.test.sh +142 -0
  42. package/dist/lite/.claude/hooks/session-reflection.d/tests/90-release-backlog.test.sh +44 -0
  43. package/dist/lite/.claude/hooks/trace-helper.sh +22 -0
  44. package/package.json +1 -1
@@ -0,0 +1,492 @@
1
+ #!/usr/bin/env bash
2
+ # tier: standard
3
+ # fixtures: post-rename-adopter bassclef-upstream-self fully-cured-adopter
4
+ # Tier 0 test for .claude/hooks/session-reflection.d/00-bassclef-hook-connect.sh
5
+ #
6
+ # Closes sunj-labs/bassclef-upstream#644 — sync-cure peer path fix.
7
+ #
8
+ # The fragment sources presence/install/bassclef-hook-connect.sh and calls
9
+ # bassclef_hook_connect_apply at every session-start. Runs regardless of
10
+ # sync path (peer, clone, npm, curl). Renames sister fragment
11
+ # 00-settings-drift.sh -> 08-settings-drift.sh so this fragment runs first.
12
+ #
13
+ # Per #644 acceptance + four-lens pre-mortem:
14
+ # - Fixture parent path OUTSIDE ~/src/sunj-labs/ (Feathers characterization)
15
+ # - Merge populates 4 missing POA-shape drift entries
16
+ # - Merge is idempotent (Nygard steady state)
17
+ # - Fragment emits stderr log line on every merge (Ousterhout signal visibility)
18
+ # - Fragment path mirrors L682 clone-path call precisely (Cockburn walking skeleton)
19
+ #
20
+ # Run: bash .claude/hooks/session-reflection.d/tests/00-bassclef-hook-connect.test.sh
21
+
22
+ set -u
23
+
24
+ # test-list:
25
+ # [ ] Fragment file exists at expected path (RED baseline; goes GREEN after WU-2)
26
+ # [ ] Fragment sources bassclef-hook-connect.sh when BASSCLEF_DIR is set
27
+ # [ ] Fragment silently skips when BASSCLEF_DIR is unset
28
+ # [ ] Fragment skips when adopter has no .claude/ dir
29
+ # [ ] Fragment skips when bassclef settings.json is missing
30
+ # [ ] Merge populates 4 missing POA-shape drift entries
31
+ # [ ] Merge is idempotent — running twice does not duplicate entries
32
+ # [ ] Fragment emits stderr log line on every merge run (Ousterhout signal-visibility)
33
+ # [ ] SKIP_BASSCLEF_HOOK_WIRE=1 bypasses cleanly
34
+ # [ ] Fixture parent path lives OUTSIDE ~/src/sunj-labs/ (Feathers characterization; operator ask)
35
+ # [ ] jq missing — fragment skips cleanly
36
+ # [ ] Bassclef self (remote URL contains sunj-labs/bassclef) — merge skips
37
+ # [ ] Adopter settings.json missing — created with hooks {} then merged
38
+ # [ ] Adopter settings.json malformed — skip without touching
39
+ # [ ] Exit 0 on all paths (session-reflection.d fragments are informational)
40
+ # [ ] Install-class project (CLAUDE_PROJECT_DIR set) resolves BASSCLEF_DIR
41
+ # [ ] Install-class operator (HOME_OVERRIDE set) resolves BASSCLEF_DIR
42
+ # [~] Trap: fragment writes to adopter settings.json via atomic tmp file; deep module handles the trap. Reason: covered upstream.
43
+
44
+ REPO_ROOT="$(cd "$(dirname "$0")/../../../.." && pwd)"
45
+ HOOK="${REPO_ROOT}/.claude/hooks/session-reflection.d/00-bassclef-hook-connect.sh"
46
+ DEEP_MODULE="${REPO_ROOT}/presence/install/bassclef-hook-connect.sh"
47
+ PASS=0
48
+ FAIL=0
49
+ FAILED_TESTS=()
50
+
51
+ assert() {
52
+ local name="$1"
53
+ local result="$2"
54
+ if [ "$result" = "ok" ]; then
55
+ PASS=$((PASS + 1))
56
+ else
57
+ FAIL=$((FAIL + 1))
58
+ FAILED_TESTS+=("$name: $result")
59
+ fi
60
+ }
61
+
62
+ # Build a fixture with parent path OUTSIDE ~/src/sunj-labs/.
63
+ # Returns the adopter dir path; caller must clean up parent.
64
+ build_poa_shape_fixture() {
65
+ local parent="$1"
66
+ local bassclef="$parent/bassclef"
67
+ local adopter="$parent/adopter"
68
+
69
+ mkdir -p "$bassclef/.claude"
70
+ mkdir -p "$bassclef/presence/install"
71
+ mkdir -p "$adopter/.claude"
72
+
73
+ # Copy the real shared function into the fake bassclef — mirrors real layout.
74
+ if [ -f "$DEEP_MODULE" ]; then
75
+ cp "$DEEP_MODULE" "$bassclef/presence/install/bassclef-hook-connect.sh"
76
+ fi
77
+
78
+ # Bassclef settings.json — mirrors POA-drift shape from #644.
79
+ # Contains 4 entries POA's settings.json is missing.
80
+ cat > "$bassclef/.claude/settings.json" <<'BASSCLEF_SETTINGS'
81
+ {
82
+ "hooks": {
83
+ "PostToolUse": [
84
+ {
85
+ "matcher": "Read|Grep|Bash",
86
+ "hooks": [
87
+ {"type": "command", "command": "bash .claude/hooks/base-pair-reprime.sh"}
88
+ ]
89
+ }
90
+ ],
91
+ "Stop": [
92
+ {
93
+ "hooks": [
94
+ {"type": "command", "command": "bash .claude/hooks/longrun-prep-compounding-sequence-check.sh"},
95
+ {"type": "command", "command": "bash .claude/hooks/stop-assertion-shape-scan.sh"}
96
+ ]
97
+ }
98
+ ],
99
+ "PreToolUse": [
100
+ {
101
+ "matcher": "Bash",
102
+ "hooks": [
103
+ {"type": "command", "command": "bash .claude/hooks/pre-gh-git-claim-shape-scrub.sh"}
104
+ ]
105
+ }
106
+ ]
107
+ }
108
+ }
109
+ BASSCLEF_SETTINGS
110
+
111
+ # Adopter starts EMPTY (POA-shape drift — missing all 4 entries).
112
+ cat > "$adopter/.claude/settings.json" <<'ADOPTER_SETTINGS'
113
+ {
114
+ "hooks": {}
115
+ }
116
+ ADOPTER_SETTINGS
117
+
118
+ echo "$adopter"
119
+ }
120
+
121
+ # --- Test 1 — RED baseline: fragment file must exist (goes GREEN when WU-2 ships)
122
+ if [ -f "$HOOK" ]; then
123
+ assert "01-fragment-file-exists" "ok"
124
+ else
125
+ assert "01-fragment-file-exists" "fragment missing at $HOOK (expected RED baseline)"
126
+ fi
127
+
128
+ # --- Test 2 — Fragment sources deep module when BASSCLEF_DIR is set
129
+ if [ -f "$HOOK" ]; then
130
+ TMP=$(mktemp -d)
131
+ ADOPTER=$(build_poa_shape_fixture "$TMP")
132
+ out=$(cd "$ADOPTER" && CWD="$ADOPTER" BASSCLEF_DIR="$TMP/bassclef" bash "$HOOK" 2>&1)
133
+ ec=$?
134
+ rm -rf "$TMP"
135
+ if [ "$ec" = "0" ]; then
136
+ assert "02-fragment-runs-with-bassclef-dir" "ok"
137
+ else
138
+ assert "02-fragment-runs-with-bassclef-dir" "expected exit 0; got $ec ($out)"
139
+ fi
140
+ else
141
+ assert "02-fragment-runs-with-bassclef-dir" "fragment missing at $HOOK"
142
+ fi
143
+
144
+ # --- Test 3 — Fragment silently skips when BASSCLEF_DIR is unset
145
+ if [ -f "$HOOK" ]; then
146
+ TMP=$(mktemp -d)
147
+ ADOPTER=$(build_poa_shape_fixture "$TMP")
148
+ out=$(cd "$ADOPTER" && CWD="$ADOPTER" bash "$HOOK" 2>&1)
149
+ ec=$?
150
+ rm -rf "$TMP"
151
+ if [ "$ec" = "0" ]; then
152
+ assert "03-skip-when-no-bassclef-dir" "ok"
153
+ else
154
+ assert "03-skip-when-no-bassclef-dir" "expected exit 0; got $ec ($out)"
155
+ fi
156
+ else
157
+ assert "03-skip-when-no-bassclef-dir" "fragment missing at $HOOK"
158
+ fi
159
+
160
+ # --- Test 4 — Fragment skips when adopter has no .claude dir
161
+ if [ -f "$HOOK" ]; then
162
+ TMP=$(mktemp -d)
163
+ mkdir -p "$TMP/bassclef/.claude"
164
+ echo '{"hooks":{}}' > "$TMP/bassclef/.claude/settings.json"
165
+ mkdir -p "$TMP/adopter" # NO .claude subdir
166
+ out=$(cd "$TMP/adopter" && CWD="$TMP/adopter" BASSCLEF_DIR="$TMP/bassclef" bash "$HOOK" 2>&1)
167
+ ec=$?
168
+ if [ "$ec" = "0" ] && [ ! -d "$TMP/adopter/.claude" ]; then
169
+ assert "04-skip-when-no-adopter-claude-dir" "ok"
170
+ else
171
+ assert "04-skip-when-no-adopter-claude-dir" "expected exit 0 + no .claude created; got ec=$ec"
172
+ fi
173
+ rm -rf "$TMP"
174
+ else
175
+ assert "04-skip-when-no-adopter-claude-dir" "fragment missing at $HOOK"
176
+ fi
177
+
178
+ # --- Test 5 — Fragment skips when bassclef settings.json missing
179
+ if [ -f "$HOOK" ]; then
180
+ TMP=$(mktemp -d)
181
+ mkdir -p "$TMP/bassclef/.claude"
182
+ mkdir -p "$TMP/adopter/.claude"
183
+ echo '{"hooks":{}}' > "$TMP/adopter/.claude/settings.json"
184
+ out=$(cd "$TMP/adopter" && CWD="$TMP/adopter" BASSCLEF_DIR="$TMP/bassclef" bash "$HOOK" 2>&1)
185
+ ec=$?
186
+ content=$(cat "$TMP/adopter/.claude/settings.json")
187
+ if [ "$ec" = "0" ] && [ "$content" = '{"hooks":{}}' ]; then
188
+ assert "05-skip-when-bassclef-settings-missing" "ok"
189
+ else
190
+ assert "05-skip-when-bassclef-settings-missing" "expected exit 0 + unchanged; got ec=$ec"
191
+ fi
192
+ rm -rf "$TMP"
193
+ else
194
+ assert "05-skip-when-bassclef-settings-missing" "fragment missing at $HOOK"
195
+ fi
196
+
197
+ # --- Test 6 — Merge populates 4 missing POA-shape drift entries
198
+ if [ -f "$HOOK" ] && [ -f "$DEEP_MODULE" ] && command -v jq >/dev/null 2>&1; then
199
+ TMP=$(mktemp -d)
200
+ ADOPTER=$(build_poa_shape_fixture "$TMP")
201
+ out=$(cd "$ADOPTER" && CWD="$ADOPTER" BASSCLEF_DIR="$TMP/bassclef" bash "$HOOK" 2>&1)
202
+ ec=$?
203
+ merged_content=$(cat "$ADOPTER/.claude/settings.json")
204
+ count=0
205
+ echo "$merged_content" | grep -q "base-pair-reprime.sh" && count=$((count + 1))
206
+ echo "$merged_content" | grep -q "longrun-prep-compounding-sequence-check.sh" && count=$((count + 1))
207
+ echo "$merged_content" | grep -q "stop-assertion-shape-scan.sh" && count=$((count + 1))
208
+ echo "$merged_content" | grep -q "pre-gh-git-claim-shape-scrub.sh" && count=$((count + 1))
209
+ rm -rf "$TMP"
210
+ if [ "$ec" = "0" ] && [ "$count" = "4" ]; then
211
+ assert "06-merge-populates-4-poa-shape-entries" "ok"
212
+ else
213
+ assert "06-merge-populates-4-poa-shape-entries" "expected 4 entries; got $count (ec=$ec)"
214
+ fi
215
+ else
216
+ assert "06-merge-populates-4-poa-shape-entries" "prereq missing: HOOK / DEEP_MODULE / jq"
217
+ fi
218
+
219
+ # --- Test 7 — Merge is idempotent — running twice does not duplicate entries
220
+ if [ -f "$HOOK" ] && [ -f "$DEEP_MODULE" ] && command -v jq >/dev/null 2>&1; then
221
+ TMP=$(mktemp -d)
222
+ ADOPTER=$(build_poa_shape_fixture "$TMP")
223
+ (cd "$ADOPTER" && CWD="$ADOPTER" BASSCLEF_DIR="$TMP/bassclef" bash "$HOOK" >/dev/null 2>&1)
224
+ (cd "$ADOPTER" && CWD="$ADOPTER" BASSCLEF_DIR="$TMP/bassclef" bash "$HOOK" >/dev/null 2>&1)
225
+ merged_content=$(cat "$ADOPTER/.claude/settings.json")
226
+ base_pair_count=$(echo "$merged_content" | grep -c "base-pair-reprime.sh")
227
+ compounding_count=$(echo "$merged_content" | grep -c "longrun-prep-compounding-sequence-check.sh")
228
+ rm -rf "$TMP"
229
+ if [ "$base_pair_count" = "1" ] && [ "$compounding_count" = "1" ]; then
230
+ assert "07-merge-idempotent" "ok"
231
+ else
232
+ assert "07-merge-idempotent" "expected 1 of each; got base_pair=$base_pair_count compounding=$compounding_count"
233
+ fi
234
+ else
235
+ assert "07-merge-idempotent" "prereq missing: HOOK / DEEP_MODULE / jq"
236
+ fi
237
+
238
+ # --- Test 8 — Fragment emits stderr log line on every merge run (Ousterhout)
239
+ if [ -f "$HOOK" ]; then
240
+ TMP=$(mktemp -d)
241
+ ADOPTER=$(build_poa_shape_fixture "$TMP")
242
+ stderr_out=$(cd "$ADOPTER" && CWD="$ADOPTER" BASSCLEF_DIR="$TMP/bassclef" bash "$HOOK" 2>&1 >/dev/null)
243
+ rm -rf "$TMP"
244
+ if echo "$stderr_out" | grep -q "bassclef-hook-connect"; then
245
+ assert "08-stderr-log-emitted" "ok"
246
+ else
247
+ assert "08-stderr-log-emitted" "no [bassclef-hook-connect] log on stderr; got: $stderr_out"
248
+ fi
249
+ else
250
+ assert "08-stderr-log-emitted" "fragment missing at $HOOK"
251
+ fi
252
+
253
+ # --- Test 9 — SKIP_BASSCLEF_HOOK_WIRE=1 bypasses cleanly
254
+ if [ -f "$HOOK" ]; then
255
+ TMP=$(mktemp -d)
256
+ ADOPTER=$(build_poa_shape_fixture "$TMP")
257
+ out=$(cd "$ADOPTER" && CWD="$ADOPTER" BASSCLEF_DIR="$TMP/bassclef" SKIP_BASSCLEF_HOOK_WIRE=1 bash "$HOOK" 2>&1)
258
+ ec=$?
259
+ content=$(cat "$ADOPTER/.claude/settings.json")
260
+ rm -rf "$TMP"
261
+ if [ "$ec" = "0" ] && [ "$content" = '{
262
+ "hooks": {}
263
+ }' ]; then
264
+ assert "09-skip-flag-bypasses" "ok"
265
+ else
266
+ assert "09-skip-flag-bypasses" "expected exit 0 + unchanged content; got ec=$ec"
267
+ fi
268
+ else
269
+ assert "09-skip-flag-bypasses" "fragment missing at $HOOK"
270
+ fi
271
+
272
+ # --- Test 10 — Fixture parent path lives OUTSIDE ~/src/sunj-labs/ (Feathers ask)
273
+ if [ -f "$HOOK" ] && [ -f "$DEEP_MODULE" ] && command -v jq >/dev/null 2>&1; then
274
+ TMP=$(mktemp -d)
275
+ case "$TMP" in
276
+ "$HOME/src/sunj-labs"*)
277
+ assert "10-fixture-parent-outside-sunj-labs" "TMPDIR $TMP is inside ~/src/sunj-labs/; test setup broken"
278
+ rm -rf "$TMP"
279
+ ;;
280
+ *)
281
+ ADOPTER=$(build_poa_shape_fixture "$TMP")
282
+ out=$(cd "$ADOPTER" && CWD="$ADOPTER" BASSCLEF_DIR="$TMP/bassclef" bash "$HOOK" 2>&1)
283
+ ec=$?
284
+ merged=$(cat "$ADOPTER/.claude/settings.json")
285
+ rm -rf "$TMP"
286
+ if [ "$ec" = "0" ] && echo "$merged" | grep -q "base-pair-reprime.sh"; then
287
+ assert "10-fixture-parent-outside-sunj-labs" "ok"
288
+ else
289
+ assert "10-fixture-parent-outside-sunj-labs" "merge failed with outside-tree fixture; ec=$ec"
290
+ fi
291
+ ;;
292
+ esac
293
+ else
294
+ assert "10-fixture-parent-outside-sunj-labs" "prereq missing: HOOK / DEEP_MODULE / jq"
295
+ fi
296
+
297
+ # --- Test 11 — jq missing — fragment skips cleanly
298
+ # Simulate jq missing by prepending a PATH dir with a "jq" stub that returns 127.
299
+ # This keeps bash + git available while making jq lookup fail (portable across macOS + Linux).
300
+ if [ -f "$HOOK" ]; then
301
+ TMP=$(mktemp -d)
302
+ ADOPTER=$(build_poa_shape_fixture "$TMP")
303
+ FAKE_BIN="$TMP/fake-bin"
304
+ mkdir -p "$FAKE_BIN"
305
+ # Empty file named jq that is NOT executable — command -v finds it but exec fails.
306
+ # Actually simpler: put a script that exits 127 immediately.
307
+ cat > "$FAKE_BIN/jq" <<'EOF'
308
+ #!/bin/sh
309
+ exit 127
310
+ EOF
311
+ chmod -x "$FAKE_BIN/jq" # not executable — command -v returns non-zero
312
+ out=$(cd "$ADOPTER" && CWD="$ADOPTER" BASSCLEF_DIR="$TMP/bassclef" PATH="$FAKE_BIN:$PATH:__no_jq__" bash "$HOOK" 2>&1)
313
+ ec=$?
314
+ # Deep module uses `command -v jq` which checks executability — non-executable file returns non-zero.
315
+ rm -rf "$TMP"
316
+ if [ "$ec" = "0" ]; then
317
+ assert "11-jq-missing-graceful" "ok"
318
+ else
319
+ assert "11-jq-missing-graceful" "expected exit 0 when jq absent; got $ec"
320
+ fi
321
+ else
322
+ assert "11-jq-missing-graceful" "fragment missing at $HOOK"
323
+ fi
324
+
325
+ # --- Test 12 — Bassclef self (git remote URL contains sunj-labs/bassclef) — skip merge
326
+ if [ -f "$HOOK" ] && [ -f "$DEEP_MODULE" ] && command -v jq >/dev/null 2>&1 && command -v git >/dev/null 2>&1; then
327
+ TMP=$(mktemp -d)
328
+ ADOPTER=$(build_poa_shape_fixture "$TMP")
329
+ (cd "$ADOPTER" && git init -q 2>/dev/null && git remote add origin https://github.com/sunj-labs/bassclef 2>/dev/null)
330
+ original=$(cat "$ADOPTER/.claude/settings.json")
331
+ out=$(cd "$ADOPTER" && CWD="$ADOPTER" BASSCLEF_DIR="$TMP/bassclef" bash "$HOOK" 2>&1)
332
+ ec=$?
333
+ after=$(cat "$ADOPTER/.claude/settings.json")
334
+ rm -rf "$TMP"
335
+ if [ "$ec" = "0" ] && [ "$original" = "$after" ]; then
336
+ assert "12-bassclef-self-skip" "ok"
337
+ else
338
+ assert "12-bassclef-self-skip" "expected unchanged for bassclef self; ec=$ec"
339
+ fi
340
+ else
341
+ assert "12-bassclef-self-skip" "prereq missing: HOOK / DEEP_MODULE / jq / git"
342
+ fi
343
+
344
+ # --- Test 13 — Adopter settings.json missing — created with hooks{} then merged
345
+ if [ -f "$HOOK" ] && [ -f "$DEEP_MODULE" ] && command -v jq >/dev/null 2>&1; then
346
+ TMP=$(mktemp -d)
347
+ mkdir -p "$TMP/bassclef/.claude"
348
+ mkdir -p "$TMP/bassclef/presence/install"
349
+ cp "$DEEP_MODULE" "$TMP/bassclef/presence/install/bassclef-hook-connect.sh"
350
+ cat > "$TMP/bassclef/.claude/settings.json" <<'EOF'
351
+ {
352
+ "hooks": {
353
+ "Stop": [
354
+ {
355
+ "hooks": [
356
+ {"type": "command", "command": "bash .claude/hooks/base-pair-reprime.sh"}
357
+ ]
358
+ }
359
+ ]
360
+ }
361
+ }
362
+ EOF
363
+ mkdir -p "$TMP/adopter/.claude"
364
+ out=$(cd "$TMP/adopter" && CWD="$TMP/adopter" BASSCLEF_DIR="$TMP/bassclef" bash "$HOOK" 2>&1)
365
+ ec=$?
366
+ if [ "$ec" = "0" ] && [ -f "$TMP/adopter/.claude/settings.json" ] && grep -q "base-pair-reprime.sh" "$TMP/adopter/.claude/settings.json"; then
367
+ assert "13-adopter-settings-missing-created" "ok"
368
+ else
369
+ assert "13-adopter-settings-missing-created" "expected settings.json created + merged; ec=$ec"
370
+ fi
371
+ rm -rf "$TMP"
372
+ else
373
+ assert "13-adopter-settings-missing-created" "prereq missing: HOOK / DEEP_MODULE / jq"
374
+ fi
375
+
376
+ # --- Test 14 — Adopter settings.json malformed — skip without touching
377
+ if [ -f "$HOOK" ] && [ -f "$DEEP_MODULE" ] && command -v jq >/dev/null 2>&1; then
378
+ TMP=$(mktemp -d)
379
+ ADOPTER=$(build_poa_shape_fixture "$TMP")
380
+ echo '{ "hooks": broken' > "$ADOPTER/.claude/settings.json"
381
+ original=$(cat "$ADOPTER/.claude/settings.json")
382
+ out=$(cd "$ADOPTER" && CWD="$ADOPTER" BASSCLEF_DIR="$TMP/bassclef" bash "$HOOK" 2>&1)
383
+ ec=$?
384
+ after=$(cat "$ADOPTER/.claude/settings.json")
385
+ rm -rf "$TMP"
386
+ if [ "$ec" = "0" ] && [ "$original" = "$after" ]; then
387
+ assert "14-adopter-malformed-untouched" "ok"
388
+ else
389
+ assert "14-adopter-malformed-untouched" "expected unchanged malformed file; ec=$ec"
390
+ fi
391
+ else
392
+ assert "14-adopter-malformed-untouched" "prereq missing: HOOK / DEEP_MODULE / jq"
393
+ fi
394
+
395
+ # --- Test 15 — Exit 0 on all paths
396
+ if [ -f "$HOOK" ]; then
397
+ TMP=$(mktemp -d)
398
+ ADOPTER=$(build_poa_shape_fixture "$TMP")
399
+ (cd "$ADOPTER" && CWD="$ADOPTER" BASSCLEF_DIR="$TMP/bassclef" bash "$HOOK" >/dev/null 2>&1)
400
+ ec1=$?
401
+ (cd "$ADOPTER" && bash "$HOOK" >/dev/null 2>&1)
402
+ ec2=$?
403
+ (cd "$ADOPTER" && CWD="$ADOPTER" bash "$HOOK" >/dev/null 2>&1)
404
+ ec3=$?
405
+ rm -rf "$TMP"
406
+ if [ "$ec1" = "0" ] && [ "$ec2" = "0" ] && [ "$ec3" = "0" ]; then
407
+ assert "15-always-exit-0" "ok"
408
+ else
409
+ assert "15-always-exit-0" "expected all exit 0; got $ec1 / $ec2 / $ec3"
410
+ fi
411
+ else
412
+ assert "15-always-exit-0" "fragment missing at $HOOK"
413
+ fi
414
+
415
+ # --- Test 16 — Install-class project (CLAUDE_PROJECT_DIR set) resolves BASSCLEF_DIR
416
+ if [ -f "$HOOK" ] && [ -f "$DEEP_MODULE" ] && command -v jq >/dev/null 2>&1; then
417
+ TMP=$(mktemp -d)
418
+ ADOPTER=$(build_poa_shape_fixture "$TMP")
419
+ out=$(cd "$ADOPTER" && CLAUDE_PROJECT_DIR="$ADOPTER" CWD="$ADOPTER" BASSCLEF_DIR="$TMP/bassclef" bash "$HOOK" 2>&1)
420
+ ec=$?
421
+ merged=$(cat "$ADOPTER/.claude/settings.json")
422
+ rm -rf "$TMP"
423
+ if [ "$ec" = "0" ] && echo "$merged" | grep -q "base-pair-reprime.sh"; then
424
+ assert "16-install-class-project" "ok"
425
+ else
426
+ assert "16-install-class-project" "expected exit 0 + merge under CLAUDE_PROJECT_DIR; ec=$ec"
427
+ fi
428
+ else
429
+ assert "16-install-class-project" "prereq missing: HOOK / DEEP_MODULE / jq"
430
+ fi
431
+
432
+ # --- Test 17 — Install-class operator (HOME_OVERRIDE fixture) resolves BASSCLEF_DIR
433
+ if [ -f "$HOOK" ] && [ -f "$DEEP_MODULE" ] && command -v jq >/dev/null 2>&1; then
434
+ TMP=$(mktemp -d)
435
+ HOME_FAKE=$(mktemp -d)
436
+ ADOPTER=$(build_poa_shape_fixture "$TMP")
437
+ out=$(cd "$ADOPTER" && HOME="$HOME_FAKE" CWD="$ADOPTER" BASSCLEF_DIR="$TMP/bassclef" bash "$HOOK" 2>&1)
438
+ ec=$?
439
+ merged=$(cat "$ADOPTER/.claude/settings.json")
440
+ rm -rf "$TMP" "$HOME_FAKE"
441
+ if [ "$ec" = "0" ] && echo "$merged" | grep -q "base-pair-reprime.sh"; then
442
+ assert "17-install-class-operator" "ok"
443
+ else
444
+ assert "17-install-class-operator" "expected exit 0 + merge under HOME override; ec=$ec"
445
+ fi
446
+ else
447
+ assert "17-install-class-operator" "prereq missing: HOOK / DEEP_MODULE / jq"
448
+ fi
449
+
450
+ # --- Test 18 — fixture-driven coverage (rides under run-with-fixtures.sh wrapper)
451
+ if [ -n "${FIXTURE_DIR:-}" ] && [ -f "$HOOK" ] && [ -f "$DEEP_MODULE" ] && command -v jq >/dev/null 2>&1; then
452
+ FIXTURE_TMP=$(mktemp -d)
453
+ mkdir -p "$FIXTURE_TMP/bassclef/.claude"
454
+ mkdir -p "$FIXTURE_TMP/bassclef/presence/install"
455
+ # Copy the shared function so the fragment can source it.
456
+ cp "$DEEP_MODULE" "$FIXTURE_TMP/bassclef/presence/install/bassclef-hook-connect.sh"
457
+ cat > "$FIXTURE_TMP/bassclef/.claude/settings.json" <<'EOF'
458
+ {
459
+ "hooks": {
460
+ "Stop": [
461
+ {"hooks": [{"type": "command", "command": "bash .claude/hooks/base-pair-reprime.sh"}]}
462
+ ]
463
+ }
464
+ }
465
+ EOF
466
+ mkdir -p "$FIXTURE_DIR/.claude" 2>/dev/null
467
+ [ -f "$FIXTURE_DIR/.claude/settings.json" ] || echo '{"hooks":{}}' > "$FIXTURE_DIR/.claude/settings.json"
468
+ fixture_out=$(cd "$FIXTURE_DIR" && CWD="$FIXTURE_DIR" BASSCLEF_DIR="$FIXTURE_TMP/bassclef" bash "$HOOK" 2>&1)
469
+ ec=$?
470
+ fixture_name="${FIXTURE_DIR##*/}"
471
+ merged=$(cat "$FIXTURE_DIR/.claude/settings.json" 2>/dev/null || echo "")
472
+ rm -rf "$FIXTURE_TMP"
473
+ if [ "$ec" = "0" ] && echo "$merged" | grep -q "base-pair-reprime.sh"; then
474
+ assert "18-fixture-coverage-${fixture_name}" "ok"
475
+ else
476
+ assert "18-fixture-coverage-${fixture_name}" "fixture merge failed; ec=$ec"
477
+ fi
478
+ fi
479
+
480
+ # --- Summary
481
+ TOTAL=$((PASS + FAIL))
482
+ echo ""
483
+ echo "=== 00-bassclef-hook-connect.sh tests ==="
484
+ echo "Passed: $PASS / $TOTAL"
485
+ if [ "$FAIL" -gt 0 ]; then
486
+ echo "Failed:"
487
+ for t in "${FAILED_TESTS[@]}"; do
488
+ echo " - $t"
489
+ done
490
+ exit 1
491
+ fi
492
+ exit 0