@thebassclef/lite 1.0.1 → 1.0.3
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.
- package/dist/cli.cjs +24 -1
- package/dist/cli.js +24 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lite/.claude/hooks/bassclef-sync.sh +98 -16
- package/dist/lite/.claude/hooks/longrun-prep-compounding-sequence-check.sh +3 -3
- package/dist/lite/.claude/hooks/session-reflection.d/00-bassclef-hook-connect.sh +63 -0
- package/dist/lite/.claude/hooks/session-reflection.d/05-active-bet.sh +113 -0
- package/dist/lite/.claude/hooks/session-reflection.d/08-settings-drift.sh +62 -0
- package/dist/lite/.claude/hooks/session-reflection.d/10-abrupt-stop-recovery.sh +78 -0
- package/dist/lite/.claude/hooks/session-reflection.d/12-session-lock.sh +63 -0
- package/dist/lite/.claude/hooks/session-reflection.d/15-substrate-config-validate.sh +91 -0
- package/dist/lite/.claude/hooks/session-reflection.d/20-artifact-staleness.sh +98 -0
- package/dist/lite/.claude/hooks/session-reflection.d/30-metrics-staleness.sh +61 -0
- package/dist/lite/.claude/hooks/session-reflection.d/35-post-merge-whereami-self-heal.sh +180 -0
- package/dist/lite/.claude/hooks/session-reflection.d/36-whereami-size-check.sh +117 -0
- package/dist/lite/.claude/hooks/session-reflection.d/40-gate-compliance.sh +108 -0
- package/dist/lite/.claude/hooks/session-reflection.d/45-auto-sync-status.sh +99 -0
- package/dist/lite/.claude/hooks/session-reflection.d/50-evolution-issues.sh +44 -0
- package/dist/lite/.claude/hooks/session-reflection.d/55-orientation-gate.sh +187 -0
- package/dist/lite/.claude/hooks/session-reflection.d/56-roadmap-reconcile-gate.sh +88 -0
- package/dist/lite/.claude/hooks/session-reflection.d/60-deferred-actions.sh +212 -0
- package/dist/lite/.claude/hooks/session-reflection.d/70-recent-strategic-artifacts.sh +101 -0
- package/dist/lite/.claude/hooks/session-reflection.d/75-prior-r8-findings.sh +100 -0
- package/dist/lite/.claude/hooks/session-reflection.d/80-hook-heartbeat-check.sh +87 -0
- package/dist/lite/.claude/hooks/session-reflection.d/80-workflow-staleness.sh +141 -0
- package/dist/lite/.claude/hooks/session-reflection.d/81-hook-manifest-staleness.sh +67 -0
- package/dist/lite/.claude/hooks/session-reflection.d/90-release-backlog.sh +140 -0
- package/dist/lite/.claude/hooks/session-reflection.d/90-secrets-expiry.sh +104 -0
- package/dist/lite/.claude/hooks/session-reflection.d/95-settings-hook-verify.sh +131 -0
- package/dist/lite/.claude/hooks/session-reflection.d/95-telemetry-emit.sh +66 -0
- package/dist/lite/.claude/hooks/session-reflection.d/97-install-class-dispatch.sh +57 -0
- package/dist/lite/.claude/hooks/session-reflection.d/tests/00-bassclef-hook-connect.test.sh +492 -0
- package/dist/lite/.claude/hooks/session-reflection.d/tests/05-active-bet.test.sh +336 -0
- package/dist/lite/.claude/hooks/session-reflection.d/tests/12-session-lock.test.sh +216 -0
- package/dist/lite/.claude/hooks/session-reflection.d/tests/15-substrate-config-validate.test.sh +50 -0
- package/dist/lite/.claude/hooks/session-reflection.d/tests/20-artifact-staleness.test.sh +348 -0
- package/dist/lite/.claude/hooks/session-reflection.d/tests/36-whereami-size-check.test.sh +189 -0
- package/dist/lite/.claude/hooks/session-reflection.d/tests/45-auto-sync-status.test.sh +263 -0
- package/dist/lite/.claude/hooks/session-reflection.d/tests/55-orientation-gate.test.sh +595 -0
- package/dist/lite/.claude/hooks/session-reflection.d/tests/56-roadmap-reconcile-gate.test.sh +227 -0
- package/dist/lite/.claude/hooks/session-reflection.d/tests/75-prior-r8-findings.test.sh +142 -0
- package/dist/lite/.claude/hooks/session-reflection.d/tests/90-release-backlog.test.sh +44 -0
- package/dist/lite/.claude/hooks/trace-helper.sh +22 -0
- package/package.json +1 -1
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# tier: standard
|
|
3
|
+
# fixtures: post-rename-adopter bassclef-upstream-self fully-cured-adopter
|
|
4
|
+
# Tests for .claude/hooks/session-reflection.d/05-active-bet.sh
|
|
5
|
+
#
|
|
6
|
+
# Per architecture/audits/2026-06-30-session-orientation-blind-spot.md cure 1:
|
|
7
|
+
# surface in_flight_bet from whereami prominently at SessionStart. The audit's
|
|
8
|
+
# root cause was the agent narrowing scope to "bet 29e wrap" from a stale
|
|
9
|
+
# branch name; whereami + commits had bet 30a as in_flight. This hook puts
|
|
10
|
+
# the in_flight_bet front of the SessionStart output so the agent cannot
|
|
11
|
+
# narrow scope without seeing it.
|
|
12
|
+
#
|
|
13
|
+
# Tier 0 strict-TDD per testing-tier-config.md; test list + 11 sufficiency
|
|
14
|
+
# criteria per test-sufficiency.md.
|
|
15
|
+
#
|
|
16
|
+
# Run: bash .claude/hooks/session-reflection.d/tests/05-active-bet.test.sh
|
|
17
|
+
|
|
18
|
+
set -u
|
|
19
|
+
|
|
20
|
+
# test-list:
|
|
21
|
+
# [ ] Branch: emits ### ACTIVE BET header when whereami has in_flight_bet field
|
|
22
|
+
# [ ] Branch: emits "no in-flight bet" message when whereami absent
|
|
23
|
+
# [ ] Branch: emits "no in-flight bet" message when whereami present but no in_flight_bet field
|
|
24
|
+
# [ ] Branch: extracts bet slug from in_flight_bet.bet: field (Shape B markdown frontmatter)
|
|
25
|
+
# [ ] Exit: exit 0 on all paths (SessionStart hooks are informational)
|
|
26
|
+
# [ ] Exit: exit 0 when SKIP_ACTIVE_BET=1 override is set
|
|
27
|
+
# [ ] Exit: exit 0 on missing binaries (graceful)
|
|
28
|
+
# [ ] Output: header includes "ACTIVE BET" + "read before scoping"
|
|
29
|
+
# [ ] Output: bet slug appears literally in output when present
|
|
30
|
+
# [ ] Output: status field appears when present in whereami
|
|
31
|
+
# [ ] Output: remaining_wus appears when present in whereami
|
|
32
|
+
# [ ] Output: includes one-line pointer to bet doc path
|
|
33
|
+
# [ ] Output: cites the cure source
|
|
34
|
+
# [ ] State-file: handles missing docs/whereami.md gracefully
|
|
35
|
+
# [ ] State-file: handles whereami.md with malformed YAML gracefully
|
|
36
|
+
# [ ] Override: SKIP_ACTIVE_BET=1 silences output entirely
|
|
37
|
+
# [~] Trap: no tmpfiles created in hook; no trap needed. Reason: hook is read-only.
|
|
38
|
+
# [ ] Install-class: hook resolves from CLAUDE_PROJECT_DIR under project install (criterion 11)
|
|
39
|
+
# [ ] Install-class: hook resolves from operator install layout (criterion 11)
|
|
40
|
+
|
|
41
|
+
REPO_ROOT="$(cd "$(dirname "$0")/../../../.." && pwd)"
|
|
42
|
+
HOOK="${REPO_ROOT}/.claude/hooks/session-reflection.d/05-active-bet.sh"
|
|
43
|
+
PASS=0
|
|
44
|
+
FAIL=0
|
|
45
|
+
FAILED_TESTS=()
|
|
46
|
+
|
|
47
|
+
assert() {
|
|
48
|
+
local name="$1"
|
|
49
|
+
local result="$2"
|
|
50
|
+
if [ "$result" = "ok" ]; then
|
|
51
|
+
PASS=$((PASS + 1))
|
|
52
|
+
else
|
|
53
|
+
FAIL=$((FAIL + 1))
|
|
54
|
+
FAILED_TESTS+=("$name: $result")
|
|
55
|
+
fi
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
run_hook_in() {
|
|
59
|
+
local tmpdir="$1"
|
|
60
|
+
(cd "$tmpdir" && bash "$HOOK" 2>&1)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
write_whereami_with_bet() {
|
|
64
|
+
local tmpdir="$1"
|
|
65
|
+
local bet_slug="$2"
|
|
66
|
+
local status="${3:-in_flight}"
|
|
67
|
+
local remaining="${4:-}"
|
|
68
|
+
mkdir -p "$tmpdir/docs"
|
|
69
|
+
cat > "$tmpdir/docs/whereami.md" <<WHEREAMI
|
|
70
|
+
---
|
|
71
|
+
project: test-repo
|
|
72
|
+
phase: Construction
|
|
73
|
+
in_flight_bet:
|
|
74
|
+
bet: docs/iteration-bets/${bet_slug}.md
|
|
75
|
+
status: ${status}
|
|
76
|
+
remaining_wus: [${remaining}]
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
# whereami — test-repo
|
|
80
|
+
|
|
81
|
+
## Role
|
|
82
|
+
test
|
|
83
|
+
WHEREAMI
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# --- Test 1
|
|
87
|
+
TMP=$(mktemp -d)
|
|
88
|
+
write_whereami_with_bet "$TMP" "2026-06-30b-substrate-anchoring-cure" "in_flight" "C, D, E, F"
|
|
89
|
+
out=$(run_hook_in "$TMP")
|
|
90
|
+
rm -rf "$TMP"
|
|
91
|
+
if echo "$out" | grep -q "### ACTIVE BET"; then
|
|
92
|
+
assert "01-header-when-bet-present" "ok"
|
|
93
|
+
else
|
|
94
|
+
assert "01-header-when-bet-present" "missing header; got: $out"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# --- Test 2
|
|
98
|
+
TMP=$(mktemp -d)
|
|
99
|
+
out=$(run_hook_in "$TMP")
|
|
100
|
+
rm -rf "$TMP"
|
|
101
|
+
if echo "$out" | grep -qi "no in-flight bet\|whereami absent\|whereami missing"; then
|
|
102
|
+
assert "02-no-bet-message-when-whereami-absent" "ok"
|
|
103
|
+
else
|
|
104
|
+
assert "02-no-bet-message-when-whereami-absent" "missing no-bet message; got: $out"
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# --- Test 3
|
|
108
|
+
TMP=$(mktemp -d)
|
|
109
|
+
mkdir -p "$TMP/docs"
|
|
110
|
+
cat > "$TMP/docs/whereami.md" <<'EOF'
|
|
111
|
+
---
|
|
112
|
+
project: test-repo
|
|
113
|
+
phase: Construction
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
# whereami — test-repo
|
|
117
|
+
EOF
|
|
118
|
+
out=$(run_hook_in "$TMP")
|
|
119
|
+
rm -rf "$TMP"
|
|
120
|
+
if echo "$out" | grep -qi "no in-flight bet"; then
|
|
121
|
+
assert "03-no-bet-message-when-field-absent" "ok"
|
|
122
|
+
else
|
|
123
|
+
assert "03-no-bet-message-when-field-absent" "missing no-bet message; got: $out"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
# --- Test 4
|
|
127
|
+
TMP=$(mktemp -d)
|
|
128
|
+
write_whereami_with_bet "$TMP" "2026-06-30b-substrate-anchoring-cure"
|
|
129
|
+
out=$(run_hook_in "$TMP")
|
|
130
|
+
rm -rf "$TMP"
|
|
131
|
+
if echo "$out" | grep -q "2026-06-30b-substrate-anchoring-cure"; then
|
|
132
|
+
assert "04-extracts-bet-slug" "ok"
|
|
133
|
+
else
|
|
134
|
+
assert "04-extracts-bet-slug" "slug not in output; got: $out"
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# --- Test 5
|
|
138
|
+
TMP=$(mktemp -d)
|
|
139
|
+
write_whereami_with_bet "$TMP" "test-slug"
|
|
140
|
+
(cd "$TMP" && bash "$HOOK" >/dev/null 2>&1)
|
|
141
|
+
ec=$?
|
|
142
|
+
rm -rf "$TMP"
|
|
143
|
+
if [ "$ec" = "0" ]; then
|
|
144
|
+
assert "05-exit-0-when-bet-present" "ok"
|
|
145
|
+
else
|
|
146
|
+
assert "05-exit-0-when-bet-present" "expected exit 0; got $ec"
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
# --- Test 6
|
|
150
|
+
TMP=$(mktemp -d)
|
|
151
|
+
write_whereami_with_bet "$TMP" "test-slug"
|
|
152
|
+
(cd "$TMP" && SKIP_ACTIVE_BET=1 bash "$HOOK" >/dev/null 2>&1)
|
|
153
|
+
ec=$?
|
|
154
|
+
rm -rf "$TMP"
|
|
155
|
+
if [ "$ec" = "0" ]; then
|
|
156
|
+
assert "06-exit-0-when-override-set" "ok"
|
|
157
|
+
else
|
|
158
|
+
assert "06-exit-0-when-override-set" "expected exit 0; got $ec"
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
# --- Test 7
|
|
162
|
+
TMP=$(mktemp -d)
|
|
163
|
+
write_whereami_with_bet "$TMP" "test-slug"
|
|
164
|
+
(cd "$TMP" && PATH=/usr/bin:/bin bash "$HOOK" >/dev/null 2>&1)
|
|
165
|
+
ec=$?
|
|
166
|
+
rm -rf "$TMP"
|
|
167
|
+
if [ "$ec" = "0" ]; then
|
|
168
|
+
assert "07-exit-0-on-missing-binaries" "ok"
|
|
169
|
+
else
|
|
170
|
+
assert "07-exit-0-on-missing-binaries" "expected exit 0; got $ec"
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
# --- Test 8
|
|
174
|
+
TMP=$(mktemp -d)
|
|
175
|
+
write_whereami_with_bet "$TMP" "test-slug"
|
|
176
|
+
out=$(run_hook_in "$TMP")
|
|
177
|
+
rm -rf "$TMP"
|
|
178
|
+
if echo "$out" | grep -qi "read before scoping work\|read before scope"; then
|
|
179
|
+
assert "08-tagline-present" "ok"
|
|
180
|
+
else
|
|
181
|
+
assert "08-tagline-present" "missing tagline; got: $out"
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
# --- Test 9
|
|
185
|
+
TMP=$(mktemp -d)
|
|
186
|
+
write_whereami_with_bet "$TMP" "2026-06-30b-substrate-anchoring-cure"
|
|
187
|
+
out=$(run_hook_in "$TMP")
|
|
188
|
+
rm -rf "$TMP"
|
|
189
|
+
if echo "$out" | grep -q "2026-06-30b-substrate-anchoring-cure"; then
|
|
190
|
+
assert "09-bet-slug-literal" "ok"
|
|
191
|
+
else
|
|
192
|
+
assert "09-bet-slug-literal" "slug not literal in output; got: $out"
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
# --- Test 10
|
|
196
|
+
TMP=$(mktemp -d)
|
|
197
|
+
write_whereami_with_bet "$TMP" "test-slug" "in_flight"
|
|
198
|
+
out=$(run_hook_in "$TMP")
|
|
199
|
+
rm -rf "$TMP"
|
|
200
|
+
if echo "$out" | grep -qi "in_flight"; then
|
|
201
|
+
assert "10-status-field-in-output" "ok"
|
|
202
|
+
else
|
|
203
|
+
assert "10-status-field-in-output" "status not in output; got: $out"
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
# --- Test 11
|
|
207
|
+
TMP=$(mktemp -d)
|
|
208
|
+
write_whereami_with_bet "$TMP" "test-slug" "in_flight" "C, D, E, F"
|
|
209
|
+
out=$(run_hook_in "$TMP")
|
|
210
|
+
rm -rf "$TMP"
|
|
211
|
+
if echo "$out" | grep -q "remaining_wus\|C, D, E, F\|C,D,E,F"; then
|
|
212
|
+
assert "11-remaining-wus-in-output" "ok"
|
|
213
|
+
else
|
|
214
|
+
assert "11-remaining-wus-in-output" "remaining_wus not in output; got: $out"
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
# --- Test 12
|
|
218
|
+
TMP=$(mktemp -d)
|
|
219
|
+
write_whereami_with_bet "$TMP" "test-slug"
|
|
220
|
+
out=$(run_hook_in "$TMP")
|
|
221
|
+
rm -rf "$TMP"
|
|
222
|
+
if echo "$out" | grep -q "docs/iteration-bets/test-slug.md"; then
|
|
223
|
+
assert "12-bet-doc-path-pointer" "ok"
|
|
224
|
+
else
|
|
225
|
+
assert "12-bet-doc-path-pointer" "bet path not in output; got: $out"
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
# --- Test 13
|
|
229
|
+
TMP=$(mktemp -d)
|
|
230
|
+
write_whereami_with_bet "$TMP" "test-slug"
|
|
231
|
+
out=$(run_hook_in "$TMP")
|
|
232
|
+
rm -rf "$TMP"
|
|
233
|
+
if echo "$out" | grep -q "2026-06-30-session-orientation-blind-spot\|bassclef-upstream#540"; then
|
|
234
|
+
assert "13-cure-source-cited" "ok"
|
|
235
|
+
else
|
|
236
|
+
assert "13-cure-source-cited" "cure source not cited; got: $out"
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
# --- Test 14
|
|
240
|
+
TMP=$(mktemp -d)
|
|
241
|
+
out=$(run_hook_in "$TMP" 2>&1)
|
|
242
|
+
ec=$?
|
|
243
|
+
rm -rf "$TMP"
|
|
244
|
+
if [ "$ec" = "0" ]; then
|
|
245
|
+
assert "14-graceful-missing-whereami" "ok"
|
|
246
|
+
else
|
|
247
|
+
assert "14-graceful-missing-whereami" "expected exit 0; got $ec"
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
# --- Test 15
|
|
251
|
+
TMP=$(mktemp -d)
|
|
252
|
+
mkdir -p "$TMP/docs"
|
|
253
|
+
cat > "$TMP/docs/whereami.md" <<'EOF'
|
|
254
|
+
---
|
|
255
|
+
project: broken
|
|
256
|
+
in_flight_bet:
|
|
257
|
+
bet: docs/iteration-bets/broken
|
|
258
|
+
status: in_flight
|
|
259
|
+
unterminated: "string
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
EOF
|
|
263
|
+
out=$(run_hook_in "$TMP" 2>&1)
|
|
264
|
+
ec=$?
|
|
265
|
+
rm -rf "$TMP"
|
|
266
|
+
if [ "$ec" = "0" ]; then
|
|
267
|
+
assert "15-graceful-malformed-yaml" "ok"
|
|
268
|
+
else
|
|
269
|
+
assert "15-graceful-malformed-yaml" "expected exit 0; got $ec"
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
# --- Test 16
|
|
273
|
+
TMP=$(mktemp -d)
|
|
274
|
+
write_whereami_with_bet "$TMP" "test-slug"
|
|
275
|
+
out=$(cd "$TMP" && SKIP_ACTIVE_BET=1 bash "$HOOK" 2>&1)
|
|
276
|
+
rm -rf "$TMP"
|
|
277
|
+
if [ -z "$out" ]; then
|
|
278
|
+
assert "16-skip-silences-output" "ok"
|
|
279
|
+
else
|
|
280
|
+
assert "16-skip-silences-output" "expected empty output; got: $out"
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
# --- Test 17
|
|
284
|
+
TMP=$(mktemp -d)
|
|
285
|
+
write_whereami_with_bet "$TMP" "test-slug"
|
|
286
|
+
out=$(cd "$TMP" && CLAUDE_PROJECT_DIR="$TMP" bash "$HOOK" 2>&1)
|
|
287
|
+
ec=$?
|
|
288
|
+
rm -rf "$TMP"
|
|
289
|
+
if [ "$ec" = "0" ] && echo "$out" | grep -q "test-slug"; then
|
|
290
|
+
assert "17-install-class-project" "ok"
|
|
291
|
+
else
|
|
292
|
+
assert "17-install-class-project" "expected exit 0 + bet visible; got ec=$ec out=$out"
|
|
293
|
+
fi
|
|
294
|
+
|
|
295
|
+
# --- Test 18
|
|
296
|
+
TMP=$(mktemp -d)
|
|
297
|
+
HOME_FAKE=$(mktemp -d)
|
|
298
|
+
write_whereami_with_bet "$TMP" "operator-install-test-slug"
|
|
299
|
+
out=$(cd "$TMP" && HOME="$HOME_FAKE" bash "$HOOK" 2>&1)
|
|
300
|
+
ec=$?
|
|
301
|
+
rm -rf "$TMP" "$HOME_FAKE"
|
|
302
|
+
if [ "$ec" = "0" ] && echo "$out" | grep -q "operator-install-test-slug"; then
|
|
303
|
+
assert "18-install-class-operator" "ok"
|
|
304
|
+
else
|
|
305
|
+
assert "18-install-class-operator" "expected exit 0 + bet visible; got ec=$ec out=$out"
|
|
306
|
+
fi
|
|
307
|
+
|
|
308
|
+
# --- Test 19: fixture-driven coverage (rides under run-with-fixtures.sh wrapper)
|
|
309
|
+
# When FIXTURE_DIR is exported by the wrapper, the test runs against a real
|
|
310
|
+
# fixture-shaped adopter repo. Per #545 retroactive backfill — every cure
|
|
311
|
+
# test on substrate-affecting paths declares fixtures it rides AND uses
|
|
312
|
+
# FIXTURE_DIR in at least one assertion to exercise the fixture's environment.
|
|
313
|
+
if [ -n "${FIXTURE_DIR:-}" ]; then
|
|
314
|
+
write_whereami_with_bet "$FIXTURE_DIR" "fixture-driven-bet-slug" "in_flight" "X, Y, Z"
|
|
315
|
+
fixture_out=$(cd "$FIXTURE_DIR" && bash "$HOOK" 2>&1)
|
|
316
|
+
fixture_name="${FIXTURE_DIR##*/}"
|
|
317
|
+
if echo "$fixture_out" | grep -q "fixture-driven-bet-slug"; then
|
|
318
|
+
assert "19-fixture-coverage-${fixture_name}" "ok"
|
|
319
|
+
else
|
|
320
|
+
assert "19-fixture-coverage-${fixture_name}" "fixture bet not surfaced; got: $fixture_out"
|
|
321
|
+
fi
|
|
322
|
+
fi
|
|
323
|
+
|
|
324
|
+
# --- Summary
|
|
325
|
+
TOTAL=$((PASS + FAIL))
|
|
326
|
+
echo ""
|
|
327
|
+
echo "=== 05-active-bet.sh tests ==="
|
|
328
|
+
echo "Passed: $PASS / $TOTAL"
|
|
329
|
+
if [ "$FAIL" -gt 0 ]; then
|
|
330
|
+
echo "Failed:"
|
|
331
|
+
for t in "${FAILED_TESTS[@]}"; do
|
|
332
|
+
echo " - $t"
|
|
333
|
+
done
|
|
334
|
+
exit 1
|
|
335
|
+
fi
|
|
336
|
+
exit 0
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# tier: standard
|
|
3
|
+
# Tests for .claude/hooks/session-reflection.d/12-session-lock.sh
|
|
4
|
+
#
|
|
5
|
+
# WU-1 of bet 2026-06-18a (bassclef#1155). Back-ports pgid self-
|
|
6
|
+
# recognition pattern from save-state.sh L123-130 + session-end.sh
|
|
7
|
+
# Guard 4 into the session-start hook. Self-held locks emit an
|
|
8
|
+
# informational "is ours" line; foreign or stale locks keep the
|
|
9
|
+
# current SESSION_LOCK warning.
|
|
10
|
+
#
|
|
11
|
+
# Run: bash .claude/hooks/session-reflection.d/tests/12-session-lock.test.sh
|
|
12
|
+
#
|
|
13
|
+
# test-list:
|
|
14
|
+
# [x] No lock file: hook is silent, exit 0
|
|
15
|
+
# [x] Lock present + our pid (same pgid): emits "is ours" line, NOT the warning
|
|
16
|
+
# [x] Lock present + foreign pid (alive, different pgid: init/PID 1): emits warning
|
|
17
|
+
# [x] Lock present + dead pid (ps lookup fails): emits warning
|
|
18
|
+
# [x] Lock present + malformed (no pid field): emits warning (fail-soft)
|
|
19
|
+
# [x] Lock present + readable age (mtime fresh): age label appears in warning
|
|
20
|
+
# [x] Lock content lines indented under "Content:" in warning output
|
|
21
|
+
|
|
22
|
+
set -u
|
|
23
|
+
|
|
24
|
+
REPO_ROOT="$(cd "$(dirname "$0")/../../../.." && pwd)"
|
|
25
|
+
HOOK="${REPO_ROOT}/.claude/hooks/session-reflection.d/12-session-lock.sh"
|
|
26
|
+
PASS=0
|
|
27
|
+
FAIL=0
|
|
28
|
+
FAILED_TESTS=()
|
|
29
|
+
|
|
30
|
+
assert() {
|
|
31
|
+
local name="$1"
|
|
32
|
+
local result="$2"
|
|
33
|
+
if [ "$result" = "ok" ]; then
|
|
34
|
+
PASS=$((PASS + 1))
|
|
35
|
+
echo " PASS $name"
|
|
36
|
+
else
|
|
37
|
+
FAIL=$((FAIL + 1))
|
|
38
|
+
FAILED_TESTS+=("$name")
|
|
39
|
+
echo " FAIL $name — $result"
|
|
40
|
+
fi
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setup_tmpdir() {
|
|
44
|
+
TMPDIR_TEST="$(mktemp -d)"
|
|
45
|
+
mkdir -p "$TMPDIR_TEST/.claude"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
teardown_tmpdir() {
|
|
49
|
+
rm -rf "$TMPDIR_TEST"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Run hook from $TMPDIR_TEST so its relative-path LOCK_FILE check sees
|
|
53
|
+
# the test-controlled .claude/SESSION_LOCK, not the real repo's one.
|
|
54
|
+
run_hook() {
|
|
55
|
+
HOOK_STDOUT=$(cd "$TMPDIR_TEST" && bash "$HOOK" 2>&1)
|
|
56
|
+
HOOK_EXIT=$?
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if [ ! -x "$HOOK" ]; then
|
|
60
|
+
chmod +x "$HOOK"
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
echo "Testing $HOOK"
|
|
64
|
+
echo "---"
|
|
65
|
+
|
|
66
|
+
# ============================================================
|
|
67
|
+
# 1. No lock file → silent, exit 0
|
|
68
|
+
# ============================================================
|
|
69
|
+
setup_tmpdir
|
|
70
|
+
run_hook
|
|
71
|
+
if [ "$HOOK_EXIT" = "0" ] && [ -z "$HOOK_STDOUT" ]; then
|
|
72
|
+
assert "no lock file → silent, exit 0" "ok"
|
|
73
|
+
else
|
|
74
|
+
assert "no lock file → silent, exit 0" "exit=$HOOK_EXIT stdout=$HOOK_STDOUT"
|
|
75
|
+
fi
|
|
76
|
+
teardown_tmpdir
|
|
77
|
+
|
|
78
|
+
# ============================================================
|
|
79
|
+
# 2. Lock present + our pid (same pgid) → "is ours" line, NOT the warning
|
|
80
|
+
# ============================================================
|
|
81
|
+
setup_tmpdir
|
|
82
|
+
NOW_ISO=$(date -u +"%Y-%m-%dT%H:%M:%S+0000")
|
|
83
|
+
OUR_PID=$$
|
|
84
|
+
cat > "$TMPDIR_TEST/.claude/SESSION_LOCK" <<EOF
|
|
85
|
+
machine: TestMachine
|
|
86
|
+
timestamp: ${NOW_ISO}
|
|
87
|
+
pid: ${OUR_PID}
|
|
88
|
+
EOF
|
|
89
|
+
run_hook
|
|
90
|
+
if [ "$HOOK_EXIT" = "0" ] \
|
|
91
|
+
&& echo "$HOOK_STDOUT" | grep -q "is ours" \
|
|
92
|
+
&& ! echo "$HOOK_STDOUT" | grep -q "PRESENT — auto-save suppressed"; then
|
|
93
|
+
assert "own-pid lock → 'is ours' line, no warning" "ok"
|
|
94
|
+
else
|
|
95
|
+
assert "own-pid lock → 'is ours' line, no warning" "exit=$HOOK_EXIT stdout=$HOOK_STDOUT"
|
|
96
|
+
fi
|
|
97
|
+
teardown_tmpdir
|
|
98
|
+
|
|
99
|
+
# ============================================================
|
|
100
|
+
# 3. Lock present + foreign pid (alive, different pgid) → warning fires
|
|
101
|
+
# ============================================================
|
|
102
|
+
setup_tmpdir
|
|
103
|
+
NOW_ISO=$(date -u +"%Y-%m-%dT%H:%M:%S+0000")
|
|
104
|
+
# init / PID 1 — guaranteed alive AND in a different pgid than this test
|
|
105
|
+
cat > "$TMPDIR_TEST/.claude/SESSION_LOCK" <<EOF
|
|
106
|
+
machine: OtherMachine
|
|
107
|
+
timestamp: ${NOW_ISO}
|
|
108
|
+
pid: 1
|
|
109
|
+
EOF
|
|
110
|
+
run_hook
|
|
111
|
+
if [ "$HOOK_EXIT" = "0" ] \
|
|
112
|
+
&& echo "$HOOK_STDOUT" | grep -q "PRESENT — auto-save suppressed" \
|
|
113
|
+
&& ! echo "$HOOK_STDOUT" | grep -q "is ours"; then
|
|
114
|
+
assert "foreign-pid alive → warning, no 'is ours'" "ok"
|
|
115
|
+
else
|
|
116
|
+
assert "foreign-pid alive → warning, no 'is ours'" "exit=$HOOK_EXIT stdout=$HOOK_STDOUT"
|
|
117
|
+
fi
|
|
118
|
+
teardown_tmpdir
|
|
119
|
+
|
|
120
|
+
# ============================================================
|
|
121
|
+
# 4. Lock present + dead pid (ps lookup fails) → warning fires
|
|
122
|
+
# ============================================================
|
|
123
|
+
setup_tmpdir
|
|
124
|
+
NOW_ISO=$(date -u +"%Y-%m-%dT%H:%M:%S+0000")
|
|
125
|
+
# Pick a PID very unlikely to exist (max + 1 territory is platform-specific;
|
|
126
|
+
# use 999999 which exceeds default pid_max on most macOS/Linux configurations)
|
|
127
|
+
DEAD_PID=999999
|
|
128
|
+
# Confirm it's actually dead; if not, skip this assertion gracefully
|
|
129
|
+
if ps -p "$DEAD_PID" >/dev/null 2>&1; then
|
|
130
|
+
echo " SKIP dead-pid → warning (PID $DEAD_PID happens to be alive)"
|
|
131
|
+
else
|
|
132
|
+
cat > "$TMPDIR_TEST/.claude/SESSION_LOCK" <<EOF
|
|
133
|
+
machine: ZombieMachine
|
|
134
|
+
timestamp: ${NOW_ISO}
|
|
135
|
+
pid: ${DEAD_PID}
|
|
136
|
+
EOF
|
|
137
|
+
run_hook
|
|
138
|
+
if [ "$HOOK_EXIT" = "0" ] \
|
|
139
|
+
&& echo "$HOOK_STDOUT" | grep -q "PRESENT — auto-save suppressed" \
|
|
140
|
+
&& ! echo "$HOOK_STDOUT" | grep -q "is ours"; then
|
|
141
|
+
assert "dead-pid lock → warning, no 'is ours'" "ok"
|
|
142
|
+
else
|
|
143
|
+
assert "dead-pid lock → warning, no 'is ours'" "exit=$HOOK_EXIT stdout=$HOOK_STDOUT"
|
|
144
|
+
fi
|
|
145
|
+
fi
|
|
146
|
+
teardown_tmpdir
|
|
147
|
+
|
|
148
|
+
# ============================================================
|
|
149
|
+
# 5. Lock present + malformed (no pid field) → warning fires (fail-soft)
|
|
150
|
+
# ============================================================
|
|
151
|
+
setup_tmpdir
|
|
152
|
+
NOW_ISO=$(date -u +"%Y-%m-%dT%H:%M:%S+0000")
|
|
153
|
+
cat > "$TMPDIR_TEST/.claude/SESSION_LOCK" <<EOF
|
|
154
|
+
machine: TestMachine
|
|
155
|
+
timestamp: ${NOW_ISO}
|
|
156
|
+
EOF
|
|
157
|
+
run_hook
|
|
158
|
+
if [ "$HOOK_EXIT" = "0" ] \
|
|
159
|
+
&& echo "$HOOK_STDOUT" | grep -q "PRESENT — auto-save suppressed" \
|
|
160
|
+
&& ! echo "$HOOK_STDOUT" | grep -q "is ours"; then
|
|
161
|
+
assert "malformed lock (no pid) → warning, fail-soft" "ok"
|
|
162
|
+
else
|
|
163
|
+
assert "malformed lock (no pid) → warning, fail-soft" "exit=$HOOK_EXIT stdout=$HOOK_STDOUT"
|
|
164
|
+
fi
|
|
165
|
+
teardown_tmpdir
|
|
166
|
+
|
|
167
|
+
# ============================================================
|
|
168
|
+
# 6. Lock present + foreign pid → age label appears in warning
|
|
169
|
+
# ============================================================
|
|
170
|
+
setup_tmpdir
|
|
171
|
+
NOW_ISO=$(date -u +"%Y-%m-%dT%H:%M:%S+0000")
|
|
172
|
+
cat > "$TMPDIR_TEST/.claude/SESSION_LOCK" <<EOF
|
|
173
|
+
machine: OtherMachine
|
|
174
|
+
timestamp: ${NOW_ISO}
|
|
175
|
+
pid: 1
|
|
176
|
+
EOF
|
|
177
|
+
run_hook
|
|
178
|
+
if echo "$HOOK_STDOUT" | grep -qE "Age:[[:space:]]+[0-9]+(s|m|h)"; then
|
|
179
|
+
assert "age label appears (seconds/minutes/hours)" "ok"
|
|
180
|
+
else
|
|
181
|
+
assert "age label appears (seconds/minutes/hours)" "stdout=$HOOK_STDOUT"
|
|
182
|
+
fi
|
|
183
|
+
teardown_tmpdir
|
|
184
|
+
|
|
185
|
+
# ============================================================
|
|
186
|
+
# 7. Lock present + foreign pid → content block indented in warning
|
|
187
|
+
# ============================================================
|
|
188
|
+
setup_tmpdir
|
|
189
|
+
NOW_ISO=$(date -u +"%Y-%m-%dT%H:%M:%S+0000")
|
|
190
|
+
cat > "$TMPDIR_TEST/.claude/SESSION_LOCK" <<EOF
|
|
191
|
+
machine: OtherMachine
|
|
192
|
+
timestamp: ${NOW_ISO}
|
|
193
|
+
pid: 1
|
|
194
|
+
EOF
|
|
195
|
+
run_hook
|
|
196
|
+
if echo "$HOOK_STDOUT" | grep -q "Content:" \
|
|
197
|
+
&& echo "$HOOK_STDOUT" | grep -q " machine: OtherMachine"; then
|
|
198
|
+
assert "content lines indented under 'Content:'" "ok"
|
|
199
|
+
else
|
|
200
|
+
assert "content lines indented under 'Content:'" "stdout=$HOOK_STDOUT"
|
|
201
|
+
fi
|
|
202
|
+
teardown_tmpdir
|
|
203
|
+
|
|
204
|
+
# ============================================================
|
|
205
|
+
# Summary
|
|
206
|
+
# ============================================================
|
|
207
|
+
echo "---"
|
|
208
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
209
|
+
if [ "$FAIL" -gt 0 ]; then
|
|
210
|
+
echo "Failed:"
|
|
211
|
+
for t in "${FAILED_TESTS[@]}"; do
|
|
212
|
+
echo " - $t"
|
|
213
|
+
done
|
|
214
|
+
exit 1
|
|
215
|
+
fi
|
|
216
|
+
exit 0
|
package/dist/lite/.claude/hooks/session-reflection.d/tests/15-substrate-config-validate.test.sh
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# tier: standard
|
|
3
|
+
# Minimal Tier 1 sibling test for 15-substrate-config-validate.sh.
|
|
4
|
+
# Coordinator-level integration coverage lives at:
|
|
5
|
+
# .claude/hooks/tests/session-reflection-coordinator.test.sh
|
|
6
|
+
# (fixture 4 pins the exit-1 → return-2 abort semantic for this module).
|
|
7
|
+
# This stub asserts the module file exists and is syntactically valid.
|
|
8
|
+
|
|
9
|
+
set -u
|
|
10
|
+
|
|
11
|
+
REPO_ROOT="$(cd "$(dirname "$0")/../../../.." && pwd)"
|
|
12
|
+
MODULE="${REPO_ROOT}/.claude/hooks/session-reflection.d/15-substrate-config-validate.sh"
|
|
13
|
+
|
|
14
|
+
PASS=0
|
|
15
|
+
FAIL=0
|
|
16
|
+
|
|
17
|
+
assert() {
|
|
18
|
+
if [ "$2" = "ok" ]; then PASS=$((PASS+1)); echo " PASS $1"; else FAIL=$((FAIL+1)); echo " FAIL $1 — $2"; fi
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
echo "Testing $MODULE (minimal stub — full coverage via coordinator test)"
|
|
22
|
+
|
|
23
|
+
if [ -f "$MODULE" ]; then
|
|
24
|
+
assert "module file present" "ok"
|
|
25
|
+
else
|
|
26
|
+
assert "module file present" "not found at $MODULE"
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
if bash -n "$MODULE" 2>/dev/null; then
|
|
30
|
+
assert "syntax valid" "ok"
|
|
31
|
+
else
|
|
32
|
+
assert "syntax valid" "bash -n reported syntax error"
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# #1157 Class B cure (2026-08-11 bet 2026-08-08b WU-2) softened the module
|
|
36
|
+
# from BLOCK (return 2) to WARN (return 0) on adopter drift. Prior
|
|
37
|
+
# characterization pinned the return-2 pattern; assertion updated to
|
|
38
|
+
# pin the new return-0 dual pattern. Intent preserved: module must use
|
|
39
|
+
# the dual return/exit shape (not raw exit), so sourced dispatcher path
|
|
40
|
+
# works AND standalone bash invocation works.
|
|
41
|
+
if grep -q "return 0 2>/dev/null || exit 0" "$MODULE"; then
|
|
42
|
+
assert "return-0 dual pattern preserved (#1157 Class B cure)" "ok"
|
|
43
|
+
else
|
|
44
|
+
assert "return-0 dual pattern preserved (#1157 Class B cure)" "expected 'return 0 2>/dev/null || exit 0' in module"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
echo ""
|
|
48
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
49
|
+
[ "$FAIL" -gt 0 ] && exit 1
|
|
50
|
+
exit 0
|