@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,595 @@
|
|
|
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/55-orientation-gate.sh
|
|
5
|
+
#
|
|
6
|
+
# Per bassclef#1183 + bassclef#1266: mechanical enforcement that fires
|
|
7
|
+
# BLOCKED at SessionStart when the agent is in bassclef repo and no
|
|
8
|
+
# orientation marker exists for the current branch. /sprint + /longrun
|
|
9
|
+
# prep procedures (WU-3/WU-4) then refuse to propose work until the
|
|
10
|
+
# operator + agent have oriented at the arc level (active bet's
|
|
11
|
+
# parent_drivers + open bassclef-evolution issues + open audit-drops).
|
|
12
|
+
#
|
|
13
|
+
# Tier 0 strict-TDD per bassclef#1037; test list block + 10 sufficiency
|
|
14
|
+
# criteria per bassclef#1090 + bassclef#1098.
|
|
15
|
+
#
|
|
16
|
+
# Run: bash .claude/hooks/session-reflection.d/tests/55-orientation-gate.test.sh
|
|
17
|
+
|
|
18
|
+
set -u
|
|
19
|
+
|
|
20
|
+
# test-list:
|
|
21
|
+
# [x] Branch: fires BLOCKED in consumer repo too (bassclef#1275 — drop bassclef-only guard)
|
|
22
|
+
# [x] Branch: silent when in bassclef repo AND orientation marker exists for current branch
|
|
23
|
+
# [x] Branch: fires BLOCKED when in bassclef repo AND no orientation marker for current branch
|
|
24
|
+
# [x] Branch: fires BLOCKED on main when no main marker (cure 2 — 2026-06-30 audit)
|
|
25
|
+
# [x] Branch: silent on main when state/markers/orientation-gate/main.marker exists
|
|
26
|
+
# [x] Branch: silent on main when SKIP_ORIENTATION_GATE_ON_MAIN=1 (adopter compat per ADR-031)
|
|
27
|
+
# [x] Exit: exit 0 on all paths (SessionStart hooks emit informational BLOCKED, not blocking exit)
|
|
28
|
+
# [x] Exit: exit 0 when override SKIP_ORIENTATION_GATE=1 is set
|
|
29
|
+
# [x] Exit: exit 0 on missing binaries (graceful)
|
|
30
|
+
# [x] BLOCKED content: emits "🛑 BLOCKED: orientation-gate" header
|
|
31
|
+
# [x] BLOCKED content: includes active iteration_bet from docs/whereami.md
|
|
32
|
+
# [x] BLOCKED content: includes resolution path naming the marker file pattern
|
|
33
|
+
# [x] BLOCKED content: includes override path (SKIP_ORIENTATION_GATE=1)
|
|
34
|
+
# [x] BLOCKED content: cites bassclef#1183 + bassclef#1266
|
|
35
|
+
# [x] Override: SKIP_ORIENTATION_GATE=1 silences output entirely
|
|
36
|
+
# [x] Marker: marker at state/markers/orientation-gate/<branch-slug>.marker silences hook
|
|
37
|
+
# [x] Marker: marker on a DIFFERENT branch does NOT silence (per-branch scope)
|
|
38
|
+
# [x] Marker: handles branch slug with slash (feature/foo-bar → feature-foo-bar)
|
|
39
|
+
# [x] State-file: handles missing docs/whereami.md gracefully (silent, no crash)
|
|
40
|
+
# [x] State-file: handles whereami.md without iteration_bet field gracefully
|
|
41
|
+
# [x] External-binary: silent when git binary missing (no crash)
|
|
42
|
+
# [x] Config: substrate.config.md with bassclef_evolution_child_repos → BLOCKED scan includes each child repo
|
|
43
|
+
# [x] Config: substrate.config.md absent → BLOCKED scan defaults to current repo only
|
|
44
|
+
# [x] Config: bassclef_evolution_label override → BLOCKED scan uses configured label
|
|
45
|
+
# [x] Recursive walk: hook walks parent_bet chain when bet files exist (bassclef#1347)
|
|
46
|
+
# [x] Recursive walk: single bet (no parent_bet) does not crash
|
|
47
|
+
# [x] Recursive walk: missing bet file degrades gracefully
|
|
48
|
+
# [x] Recursive walk: cycle in chain (A→B→A) does not loop infinitely
|
|
49
|
+
# [x] Recursive walk: BLOCKED message labels the ancestor chain explicitly
|
|
50
|
+
# [x] Recursive walk: BLOCKED cites bassclef#1347
|
|
51
|
+
# [~] Trap: no tmpfiles created in hook; no trap needed. Reason: hook is read-only on filesystem.
|
|
52
|
+
|
|
53
|
+
REPO_ROOT="$(cd "$(dirname "$0")/../../../.." && pwd)"
|
|
54
|
+
HOOK="${REPO_ROOT}/.claude/hooks/session-reflection.d/55-orientation-gate.sh"
|
|
55
|
+
PASS=0
|
|
56
|
+
FAIL=0
|
|
57
|
+
FAILED_TESTS=()
|
|
58
|
+
|
|
59
|
+
assert() {
|
|
60
|
+
local name="$1"
|
|
61
|
+
local result="$2"
|
|
62
|
+
if [ "$result" = "ok" ]; then
|
|
63
|
+
PASS=$((PASS + 1))
|
|
64
|
+
echo " PASS $name"
|
|
65
|
+
else
|
|
66
|
+
FAIL=$((FAIL + 1))
|
|
67
|
+
FAILED_TESTS+=("$name")
|
|
68
|
+
echo " FAIL $name — $result"
|
|
69
|
+
fi
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setup_tmpdir() {
|
|
73
|
+
TMPDIR_TEST="$(mktemp -d)"
|
|
74
|
+
mkdir -p "$TMPDIR_TEST/.claude/hooks" "$TMPDIR_TEST/docs" "$TMPDIR_TEST/state/markers/orientation-gate"
|
|
75
|
+
# Set up a git repo so `git symbolic-ref --short HEAD` works
|
|
76
|
+
(cd "$TMPDIR_TEST" && git init -q && git config user.email t@t && git config user.name t \
|
|
77
|
+
&& git checkout -q -b feature/test-branch 2>/dev/null \
|
|
78
|
+
&& touch README.md && git add README.md && git commit -q -m init 2>/dev/null) || true
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
teardown_tmpdir() {
|
|
82
|
+
rm -rf "$TMPDIR_TEST"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# Inject a stub `gh` binary at the front of PATH that pretends we're in
|
|
86
|
+
# the bassclef repo (or not, per arg). Real gh isn't called in tests.
|
|
87
|
+
stub_gh_bassclef() {
|
|
88
|
+
cat > "$TMPDIR_TEST/gh" <<'EOF'
|
|
89
|
+
#!/usr/bin/env bash
|
|
90
|
+
# Test stub for gh CLI. Behavior controlled by GH_STUB_REPO_NAME env var.
|
|
91
|
+
if [ "$1" = "repo" ] && [ "$2" = "view" ]; then
|
|
92
|
+
echo "${GH_STUB_REPO_NAME:-bassclef}"
|
|
93
|
+
exit 0
|
|
94
|
+
fi
|
|
95
|
+
if [ "$1" = "issue" ] && [ "$2" = "list" ]; then
|
|
96
|
+
# Return a fixed line so the hook can count
|
|
97
|
+
echo "1268 Adopter-input prompts need plain English"
|
|
98
|
+
echo "1266 Dependency-aware /longrun + /sprint"
|
|
99
|
+
echo "1265 Wiki-sync mechanism not inherited"
|
|
100
|
+
exit 0
|
|
101
|
+
fi
|
|
102
|
+
exit 0
|
|
103
|
+
EOF
|
|
104
|
+
chmod +x "$TMPDIR_TEST/gh"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Run hook with CWD=$TMPDIR_TEST + PATH including stub gh
|
|
108
|
+
run_hook() {
|
|
109
|
+
HOOK_STDOUT=$(cd "$TMPDIR_TEST" \
|
|
110
|
+
&& PATH="$TMPDIR_TEST:$PATH" \
|
|
111
|
+
&& CLAUDE_PROJECT_DIR="$TMPDIR_TEST" \
|
|
112
|
+
bash "$HOOK" 2>&1)
|
|
113
|
+
HOOK_EXIT=$?
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if [ ! -x "$HOOK" ]; then
|
|
117
|
+
chmod +x "$HOOK" 2>/dev/null || true
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
echo "Testing $HOOK"
|
|
121
|
+
echo "---"
|
|
122
|
+
|
|
123
|
+
# =====================================================================
|
|
124
|
+
# Branch coverage: adopter-too (bassclef#1275 — drop bassclef-only guard)
|
|
125
|
+
# =====================================================================
|
|
126
|
+
|
|
127
|
+
# Fires BLOCKED in consumer repo too — adopter inherits the discipline
|
|
128
|
+
setup_tmpdir
|
|
129
|
+
stub_gh_bassclef
|
|
130
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
131
|
+
iteration_bet: 2026-06-20a-longrun-sprint-orientation-cure
|
|
132
|
+
EOF
|
|
133
|
+
GH_STUB_REPO_NAME=poa run_hook
|
|
134
|
+
[ "$HOOK_EXIT" = "0" ] && echo "$HOOK_STDOUT" | grep -q "orientation-gate" && \
|
|
135
|
+
assert "fires BLOCKED in consumer repo too (bassclef#1275)" "ok" || \
|
|
136
|
+
assert "fires BLOCKED in consumer repo too (bassclef#1275)" "exit=$HOOK_EXIT output=$HOOK_STDOUT"
|
|
137
|
+
teardown_tmpdir
|
|
138
|
+
|
|
139
|
+
# Fires BLOCKED when in bassclef repo + no marker
|
|
140
|
+
setup_tmpdir
|
|
141
|
+
stub_gh_bassclef
|
|
142
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
143
|
+
iteration_bet: 2026-06-20a-longrun-sprint-orientation-cure
|
|
144
|
+
EOF
|
|
145
|
+
run_hook
|
|
146
|
+
[ "$HOOK_EXIT" = "0" ] && echo "$HOOK_STDOUT" | grep -q "orientation-gate" && \
|
|
147
|
+
assert "fires BLOCKED in bassclef repo with no marker" "ok" || \
|
|
148
|
+
assert "fires BLOCKED in bassclef repo with no marker" "exit=$HOOK_EXIT output=$HOOK_STDOUT"
|
|
149
|
+
teardown_tmpdir
|
|
150
|
+
|
|
151
|
+
# Fires BLOCKED on main when no main marker (cure 2 — 2026-06-30-session-orientation-blind-spot audit)
|
|
152
|
+
setup_tmpdir
|
|
153
|
+
stub_gh_bassclef
|
|
154
|
+
(cd "$TMPDIR_TEST" && git checkout -q -b main 2>/dev/null || git checkout -q main 2>/dev/null)
|
|
155
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
156
|
+
iteration_bet: 2026-06-20a-longrun-sprint-orientation-cure
|
|
157
|
+
EOF
|
|
158
|
+
run_hook
|
|
159
|
+
[ "$HOOK_EXIT" = "0" ] && echo "$HOOK_STDOUT" | grep -q "orientation-gate" && \
|
|
160
|
+
assert "fires BLOCKED on main when no main marker (cure 2)" "ok" || \
|
|
161
|
+
assert "fires BLOCKED on main when no main marker (cure 2)" "exit=$HOOK_EXIT output=$HOOK_STDOUT"
|
|
162
|
+
teardown_tmpdir
|
|
163
|
+
|
|
164
|
+
# Silent on main when main marker exists (sister of feature-branch-marker-silences)
|
|
165
|
+
setup_tmpdir
|
|
166
|
+
stub_gh_bassclef
|
|
167
|
+
(cd "$TMPDIR_TEST" && git checkout -q -b main 2>/dev/null || git checkout -q main 2>/dev/null)
|
|
168
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
169
|
+
iteration_bet: 2026-06-20a-longrun-sprint-orientation-cure
|
|
170
|
+
EOF
|
|
171
|
+
touch "$TMPDIR_TEST/state/markers/orientation-gate/main.marker"
|
|
172
|
+
run_hook
|
|
173
|
+
[ "$HOOK_EXIT" = "0" ] && [ -z "$HOOK_STDOUT" ] && \
|
|
174
|
+
assert "silent on main when main marker exists" "ok" || \
|
|
175
|
+
assert "silent on main when main marker exists" "exit=$HOOK_EXIT output=$HOOK_STDOUT"
|
|
176
|
+
teardown_tmpdir
|
|
177
|
+
|
|
178
|
+
# Silent on main when SKIP_ORIENTATION_GATE_ON_MAIN=1 (adopter compat per ADR-031)
|
|
179
|
+
setup_tmpdir
|
|
180
|
+
stub_gh_bassclef
|
|
181
|
+
(cd "$TMPDIR_TEST" && git checkout -q -b main 2>/dev/null || git checkout -q main 2>/dev/null)
|
|
182
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
183
|
+
iteration_bet: 2026-06-20a-longrun-sprint-orientation-cure
|
|
184
|
+
EOF
|
|
185
|
+
HOOK_STDOUT=$(cd "$TMPDIR_TEST" \
|
|
186
|
+
&& PATH="$TMPDIR_TEST:$PATH" \
|
|
187
|
+
&& CLAUDE_PROJECT_DIR="$TMPDIR_TEST" \
|
|
188
|
+
&& SKIP_ORIENTATION_GATE_ON_MAIN=1 \
|
|
189
|
+
bash "$HOOK" 2>&1)
|
|
190
|
+
HOOK_EXIT=$?
|
|
191
|
+
[ "$HOOK_EXIT" = "0" ] && [ -z "$HOOK_STDOUT" ] && \
|
|
192
|
+
assert "silent on main when SKIP_ORIENTATION_GATE_ON_MAIN=1 (adopter compat)" "ok" || \
|
|
193
|
+
assert "silent on main when SKIP_ORIENTATION_GATE_ON_MAIN=1 (adopter compat)" "exit=$HOOK_EXIT output=$HOOK_STDOUT"
|
|
194
|
+
teardown_tmpdir
|
|
195
|
+
|
|
196
|
+
# =====================================================================
|
|
197
|
+
# BLOCKED content
|
|
198
|
+
# =====================================================================
|
|
199
|
+
|
|
200
|
+
# Header
|
|
201
|
+
setup_tmpdir
|
|
202
|
+
stub_gh_bassclef
|
|
203
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
204
|
+
iteration_bet: 2026-06-20a-longrun-sprint-orientation-cure
|
|
205
|
+
EOF
|
|
206
|
+
run_hook
|
|
207
|
+
echo "$HOOK_STDOUT" | grep -q "🛑 BLOCKED: orientation-gate" && \
|
|
208
|
+
assert "BLOCKED header emitted" "ok" || \
|
|
209
|
+
assert "BLOCKED header emitted" "output=$HOOK_STDOUT"
|
|
210
|
+
teardown_tmpdir
|
|
211
|
+
|
|
212
|
+
# Active bet surfaced
|
|
213
|
+
setup_tmpdir
|
|
214
|
+
stub_gh_bassclef
|
|
215
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
216
|
+
iteration_bet: 2026-06-20a-longrun-sprint-orientation-cure
|
|
217
|
+
EOF
|
|
218
|
+
run_hook
|
|
219
|
+
echo "$HOOK_STDOUT" | grep -q "2026-06-20a-longrun-sprint-orientation-cure" && \
|
|
220
|
+
assert "active iteration_bet name surfaced" "ok" || \
|
|
221
|
+
assert "active iteration_bet name surfaced" "output=$HOOK_STDOUT"
|
|
222
|
+
teardown_tmpdir
|
|
223
|
+
|
|
224
|
+
# Resolution path with marker pattern
|
|
225
|
+
setup_tmpdir
|
|
226
|
+
stub_gh_bassclef
|
|
227
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
228
|
+
iteration_bet: 2026-06-20a-longrun-sprint-orientation-cure
|
|
229
|
+
EOF
|
|
230
|
+
run_hook
|
|
231
|
+
echo "$HOOK_STDOUT" | grep -qE "state/markers/orientation-gate" && \
|
|
232
|
+
assert "resolution path names marker file pattern" "ok" || \
|
|
233
|
+
assert "resolution path names marker file pattern" "output=$HOOK_STDOUT"
|
|
234
|
+
teardown_tmpdir
|
|
235
|
+
|
|
236
|
+
# Override path in BLOCKED content
|
|
237
|
+
setup_tmpdir
|
|
238
|
+
stub_gh_bassclef
|
|
239
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
240
|
+
iteration_bet: 2026-06-20a-longrun-sprint-orientation-cure
|
|
241
|
+
EOF
|
|
242
|
+
run_hook
|
|
243
|
+
echo "$HOOK_STDOUT" | grep -q "SKIP_ORIENTATION_GATE=1" && \
|
|
244
|
+
assert "override path documented in BLOCKED" "ok" || \
|
|
245
|
+
assert "override path documented in BLOCKED" "output=$HOOK_STDOUT"
|
|
246
|
+
teardown_tmpdir
|
|
247
|
+
|
|
248
|
+
# Cites tickets
|
|
249
|
+
setup_tmpdir
|
|
250
|
+
stub_gh_bassclef
|
|
251
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
252
|
+
iteration_bet: 2026-06-20a-longrun-sprint-orientation-cure
|
|
253
|
+
EOF
|
|
254
|
+
run_hook
|
|
255
|
+
echo "$HOOK_STDOUT" | grep -q "bassclef#1183" && echo "$HOOK_STDOUT" | grep -q "bassclef#1266" && \
|
|
256
|
+
assert "BLOCKED cites bassclef#1183 + bassclef#1266" "ok" || \
|
|
257
|
+
assert "BLOCKED cites bassclef#1183 + bassclef#1266" "output=$HOOK_STDOUT"
|
|
258
|
+
teardown_tmpdir
|
|
259
|
+
|
|
260
|
+
# =====================================================================
|
|
261
|
+
# Override
|
|
262
|
+
# =====================================================================
|
|
263
|
+
|
|
264
|
+
# SKIP_ORIENTATION_GATE=1 silences
|
|
265
|
+
setup_tmpdir
|
|
266
|
+
stub_gh_bassclef
|
|
267
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
268
|
+
iteration_bet: 2026-06-20a-longrun-sprint-orientation-cure
|
|
269
|
+
EOF
|
|
270
|
+
HOOK_STDOUT=$(cd "$TMPDIR_TEST" && PATH="$TMPDIR_TEST:$PATH" CLAUDE_PROJECT_DIR="$TMPDIR_TEST" SKIP_ORIENTATION_GATE=1 bash "$HOOK" 2>&1)
|
|
271
|
+
HOOK_EXIT=$?
|
|
272
|
+
[ "$HOOK_EXIT" = "0" ] && [ -z "$HOOK_STDOUT" ] && \
|
|
273
|
+
assert "SKIP_ORIENTATION_GATE=1 silences hook" "ok" || \
|
|
274
|
+
assert "SKIP_ORIENTATION_GATE=1 silences hook" "exit=$HOOK_EXIT output=$HOOK_STDOUT"
|
|
275
|
+
teardown_tmpdir
|
|
276
|
+
|
|
277
|
+
# =====================================================================
|
|
278
|
+
# Marker
|
|
279
|
+
# =====================================================================
|
|
280
|
+
|
|
281
|
+
# Marker for current branch silences
|
|
282
|
+
setup_tmpdir
|
|
283
|
+
stub_gh_bassclef
|
|
284
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
285
|
+
iteration_bet: 2026-06-20a-longrun-sprint-orientation-cure
|
|
286
|
+
EOF
|
|
287
|
+
touch "$TMPDIR_TEST/state/markers/orientation-gate/feature-test-branch.marker"
|
|
288
|
+
run_hook
|
|
289
|
+
[ "$HOOK_EXIT" = "0" ] && [ -z "$HOOK_STDOUT" ] && \
|
|
290
|
+
assert "marker for current branch silences hook" "ok" || \
|
|
291
|
+
assert "marker for current branch silences hook" "exit=$HOOK_EXIT output=$HOOK_STDOUT"
|
|
292
|
+
teardown_tmpdir
|
|
293
|
+
|
|
294
|
+
# Marker on different branch does NOT silence
|
|
295
|
+
setup_tmpdir
|
|
296
|
+
stub_gh_bassclef
|
|
297
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
298
|
+
iteration_bet: 2026-06-20a-longrun-sprint-orientation-cure
|
|
299
|
+
EOF
|
|
300
|
+
touch "$TMPDIR_TEST/state/markers/orientation-gate/feature-OTHER-branch.marker"
|
|
301
|
+
run_hook
|
|
302
|
+
[ "$HOOK_EXIT" = "0" ] && echo "$HOOK_STDOUT" | grep -q "orientation-gate" && \
|
|
303
|
+
assert "marker on other branch does NOT silence" "ok" || \
|
|
304
|
+
assert "marker on other branch does NOT silence" "exit=$HOOK_EXIT output=$HOOK_STDOUT"
|
|
305
|
+
teardown_tmpdir
|
|
306
|
+
|
|
307
|
+
# Branch slug with slash translates to dash
|
|
308
|
+
setup_tmpdir
|
|
309
|
+
stub_gh_bassclef
|
|
310
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
311
|
+
iteration_bet: 2026-06-20a-longrun-sprint-orientation-cure
|
|
312
|
+
EOF
|
|
313
|
+
# Current branch in setup_tmpdir is "feature/test-branch" → should map to "feature-test-branch.marker"
|
|
314
|
+
touch "$TMPDIR_TEST/state/markers/orientation-gate/feature-test-branch.marker"
|
|
315
|
+
run_hook
|
|
316
|
+
[ "$HOOK_EXIT" = "0" ] && [ -z "$HOOK_STDOUT" ] && \
|
|
317
|
+
assert "slash-in-branch-name handled (feature/foo → feature-foo)" "ok" || \
|
|
318
|
+
assert "slash-in-branch-name handled (feature/foo → feature-foo)" "exit=$HOOK_EXIT output=$HOOK_STDOUT"
|
|
319
|
+
teardown_tmpdir
|
|
320
|
+
|
|
321
|
+
# =====================================================================
|
|
322
|
+
# State-file graceful handling
|
|
323
|
+
# =====================================================================
|
|
324
|
+
|
|
325
|
+
# Missing whereami.md → silent (can't determine bet, gracefully decline)
|
|
326
|
+
setup_tmpdir
|
|
327
|
+
stub_gh_bassclef
|
|
328
|
+
# No whereami.md created
|
|
329
|
+
run_hook
|
|
330
|
+
[ "$HOOK_EXIT" = "0" ] && \
|
|
331
|
+
assert "missing whereami.md does not crash" "ok" || \
|
|
332
|
+
assert "missing whereami.md does not crash" "exit=$HOOK_EXIT output=$HOOK_STDOUT"
|
|
333
|
+
teardown_tmpdir
|
|
334
|
+
|
|
335
|
+
# whereami.md without iteration_bet field → emits BLOCKED but with "—" or "none active" for bet
|
|
336
|
+
setup_tmpdir
|
|
337
|
+
stub_gh_bassclef
|
|
338
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
339
|
+
phase: Construction
|
|
340
|
+
note: some note
|
|
341
|
+
EOF
|
|
342
|
+
run_hook
|
|
343
|
+
[ "$HOOK_EXIT" = "0" ] && \
|
|
344
|
+
assert "whereami without iteration_bet field handled gracefully" "ok" || \
|
|
345
|
+
assert "whereami without iteration_bet field handled gracefully" "exit=$HOOK_EXIT output=$HOOK_STDOUT"
|
|
346
|
+
teardown_tmpdir
|
|
347
|
+
|
|
348
|
+
# =====================================================================
|
|
349
|
+
# External binary graceful handling
|
|
350
|
+
# =====================================================================
|
|
351
|
+
|
|
352
|
+
# Missing gh binary → silent (consumer-repo detection fails-soft)
|
|
353
|
+
setup_tmpdir
|
|
354
|
+
# No stub_gh_bassclef — PATH won't contain gh in tmpdir
|
|
355
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
356
|
+
iteration_bet: 2026-06-20a-longrun-sprint-orientation-cure
|
|
357
|
+
EOF
|
|
358
|
+
HOOK_STDOUT=$(cd "$TMPDIR_TEST" && PATH="/usr/bin:/bin" CLAUDE_PROJECT_DIR="$TMPDIR_TEST" bash "$HOOK" 2>&1)
|
|
359
|
+
HOOK_EXIT=$?
|
|
360
|
+
[ "$HOOK_EXIT" = "0" ] && \
|
|
361
|
+
assert "missing gh binary does not crash" "ok" || \
|
|
362
|
+
assert "missing gh binary does not crash" "exit=$HOOK_EXIT output=$HOOK_STDOUT"
|
|
363
|
+
teardown_tmpdir
|
|
364
|
+
|
|
365
|
+
# =====================================================================
|
|
366
|
+
# Config — adopter-configurable scan (bassclef#1275)
|
|
367
|
+
# =====================================================================
|
|
368
|
+
|
|
369
|
+
# substrate.config.md with bassclef_evolution_child_repos → BLOCKED scan includes children
|
|
370
|
+
setup_tmpdir
|
|
371
|
+
stub_gh_bassclef
|
|
372
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
373
|
+
iteration_bet: 2026-06-20a-longrun-sprint-orientation-cure
|
|
374
|
+
EOF
|
|
375
|
+
cat > "$TMPDIR_TEST/substrate.config.md" <<'EOF'
|
|
376
|
+
bassclef_evolution_child_repos: sunj-labs/poa, sunj-labs/twodo
|
|
377
|
+
EOF
|
|
378
|
+
run_hook
|
|
379
|
+
echo "$HOOK_STDOUT" | grep -q "sunj-labs/poa" && echo "$HOOK_STDOUT" | grep -q "sunj-labs/twodo" && \
|
|
380
|
+
assert "scan command includes child repos from substrate.config.md" "ok" || \
|
|
381
|
+
assert "scan command includes child repos from substrate.config.md" "output=$HOOK_STDOUT"
|
|
382
|
+
teardown_tmpdir
|
|
383
|
+
|
|
384
|
+
# substrate.config.md absent → defaults to current repo only (no -R flag)
|
|
385
|
+
setup_tmpdir
|
|
386
|
+
stub_gh_bassclef
|
|
387
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
388
|
+
iteration_bet: 2026-06-20a-longrun-sprint-orientation-cure
|
|
389
|
+
EOF
|
|
390
|
+
# No substrate.config.md
|
|
391
|
+
run_hook
|
|
392
|
+
# Default scan: gh issue list --label bassclef-evolution (no -R for current repo)
|
|
393
|
+
echo "$HOOK_STDOUT" | grep -qE "gh issue list[^-]*--label .?bassclef-evolution" && \
|
|
394
|
+
assert "scan defaults to current repo + bassclef-evolution label when config absent" "ok" || \
|
|
395
|
+
assert "scan defaults to current repo + bassclef-evolution label when config absent" "output=$HOOK_STDOUT"
|
|
396
|
+
teardown_tmpdir
|
|
397
|
+
|
|
398
|
+
# bassclef_evolution_label override → BLOCKED scan uses configured label
|
|
399
|
+
setup_tmpdir
|
|
400
|
+
stub_gh_bassclef
|
|
401
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
402
|
+
iteration_bet: 2026-06-20a-longrun-sprint-orientation-cure
|
|
403
|
+
EOF
|
|
404
|
+
cat > "$TMPDIR_TEST/substrate.config.md" <<'EOF'
|
|
405
|
+
bassclef_evolution_label: substrate-evolution
|
|
406
|
+
EOF
|
|
407
|
+
run_hook
|
|
408
|
+
echo "$HOOK_STDOUT" | grep -q "substrate-evolution" && \
|
|
409
|
+
assert "scan command uses configured label override" "ok" || \
|
|
410
|
+
assert "scan command uses configured label override" "output=$HOOK_STDOUT"
|
|
411
|
+
teardown_tmpdir
|
|
412
|
+
|
|
413
|
+
# =====================================================================
|
|
414
|
+
# Recursive parent_bet walk (bassclef#1347)
|
|
415
|
+
# =====================================================================
|
|
416
|
+
|
|
417
|
+
# Hook walks parent_bet chain recursively and surfaces ancestor lineage
|
|
418
|
+
setup_tmpdir
|
|
419
|
+
stub_gh_bassclef
|
|
420
|
+
mkdir -p "$TMPDIR_TEST/docs/iteration-bets"
|
|
421
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
422
|
+
iteration_bet: 2026-06-20h-marathon
|
|
423
|
+
EOF
|
|
424
|
+
cat > "$TMPDIR_TEST/docs/iteration-bets/2026-06-20h-marathon.md" <<'EOF'
|
|
425
|
+
---
|
|
426
|
+
slug: 2026-06-20h-marathon
|
|
427
|
+
parent_bet: 2026-06-20g-validate
|
|
428
|
+
---
|
|
429
|
+
EOF
|
|
430
|
+
cat > "$TMPDIR_TEST/docs/iteration-bets/2026-06-20g-validate.md" <<'EOF'
|
|
431
|
+
---
|
|
432
|
+
slug: 2026-06-20g-validate
|
|
433
|
+
parent_bet: 2026-06-20f-rename
|
|
434
|
+
---
|
|
435
|
+
EOF
|
|
436
|
+
cat > "$TMPDIR_TEST/docs/iteration-bets/2026-06-20f-rename.md" <<'EOF'
|
|
437
|
+
---
|
|
438
|
+
slug: 2026-06-20f-rename
|
|
439
|
+
---
|
|
440
|
+
EOF
|
|
441
|
+
run_hook
|
|
442
|
+
echo "$HOOK_STDOUT" | grep -q "2026-06-20g-validate" && \
|
|
443
|
+
echo "$HOOK_STDOUT" | grep -q "2026-06-20f-rename" && \
|
|
444
|
+
assert "recursive walk surfaces 3-deep ancestor chain" "ok" || \
|
|
445
|
+
assert "recursive walk surfaces 3-deep ancestor chain" "output=$HOOK_STDOUT"
|
|
446
|
+
teardown_tmpdir
|
|
447
|
+
|
|
448
|
+
# Single bet (no parent_bet) — chain is just the active bet
|
|
449
|
+
setup_tmpdir
|
|
450
|
+
stub_gh_bassclef
|
|
451
|
+
mkdir -p "$TMPDIR_TEST/docs/iteration-bets"
|
|
452
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
453
|
+
iteration_bet: 2026-06-20-alone
|
|
454
|
+
EOF
|
|
455
|
+
cat > "$TMPDIR_TEST/docs/iteration-bets/2026-06-20-alone.md" <<'EOF'
|
|
456
|
+
---
|
|
457
|
+
slug: 2026-06-20-alone
|
|
458
|
+
---
|
|
459
|
+
EOF
|
|
460
|
+
run_hook
|
|
461
|
+
[ "$HOOK_EXIT" = "0" ] && \
|
|
462
|
+
assert "single bet (no parent_bet) does not crash recursive walk" "ok" || \
|
|
463
|
+
assert "single bet (no parent_bet) does not crash recursive walk" "exit=$HOOK_EXIT output=$HOOK_STDOUT"
|
|
464
|
+
teardown_tmpdir
|
|
465
|
+
|
|
466
|
+
# Missing bet file — recursive walk degrades gracefully
|
|
467
|
+
setup_tmpdir
|
|
468
|
+
stub_gh_bassclef
|
|
469
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
470
|
+
iteration_bet: 2026-06-20-missing
|
|
471
|
+
EOF
|
|
472
|
+
# Note: no bet file at docs/iteration-bets/2026-06-20-missing.md
|
|
473
|
+
run_hook
|
|
474
|
+
[ "$HOOK_EXIT" = "0" ] && \
|
|
475
|
+
assert "missing bet file does not crash recursive walk" "ok" || \
|
|
476
|
+
assert "missing bet file does not crash recursive walk" "exit=$HOOK_EXIT output=$HOOK_STDOUT"
|
|
477
|
+
teardown_tmpdir
|
|
478
|
+
|
|
479
|
+
# Cycle protection — chain with bet A → B → A doesn't loop infinitely
|
|
480
|
+
setup_tmpdir
|
|
481
|
+
stub_gh_bassclef
|
|
482
|
+
mkdir -p "$TMPDIR_TEST/docs/iteration-bets"
|
|
483
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
484
|
+
iteration_bet: bet-A
|
|
485
|
+
EOF
|
|
486
|
+
cat > "$TMPDIR_TEST/docs/iteration-bets/bet-A.md" <<'EOF'
|
|
487
|
+
---
|
|
488
|
+
parent_bet: bet-B
|
|
489
|
+
---
|
|
490
|
+
EOF
|
|
491
|
+
cat > "$TMPDIR_TEST/docs/iteration-bets/bet-B.md" <<'EOF'
|
|
492
|
+
---
|
|
493
|
+
parent_bet: bet-A
|
|
494
|
+
---
|
|
495
|
+
EOF
|
|
496
|
+
# Cycle protection lives in the hook itself (max_depth bound); test runs normally.
|
|
497
|
+
# If the hook is buggy and loops, the test hangs — that surfaces the bug.
|
|
498
|
+
run_hook
|
|
499
|
+
[ "$HOOK_EXIT" = "0" ] && \
|
|
500
|
+
assert "cycle in parent_bet chain does not infinite-loop" "ok" || \
|
|
501
|
+
assert "cycle in parent_bet chain does not infinite-loop" "exit=$HOOK_EXIT output=$HOOK_STDOUT"
|
|
502
|
+
teardown_tmpdir
|
|
503
|
+
|
|
504
|
+
# BLOCKED message includes section labeling the ancestor chain
|
|
505
|
+
setup_tmpdir
|
|
506
|
+
stub_gh_bassclef
|
|
507
|
+
mkdir -p "$TMPDIR_TEST/docs/iteration-bets"
|
|
508
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
509
|
+
iteration_bet: 2026-06-20h-marathon
|
|
510
|
+
EOF
|
|
511
|
+
cat > "$TMPDIR_TEST/docs/iteration-bets/2026-06-20h-marathon.md" <<'EOF'
|
|
512
|
+
---
|
|
513
|
+
slug: 2026-06-20h-marathon
|
|
514
|
+
parent_bet: 2026-06-20g-validate
|
|
515
|
+
---
|
|
516
|
+
EOF
|
|
517
|
+
cat > "$TMPDIR_TEST/docs/iteration-bets/2026-06-20g-validate.md" <<'EOF'
|
|
518
|
+
---
|
|
519
|
+
slug: 2026-06-20g-validate
|
|
520
|
+
---
|
|
521
|
+
EOF
|
|
522
|
+
run_hook
|
|
523
|
+
echo "$HOOK_STDOUT" | grep -qE "[Aa]ncestor|[Cc]hain|[Ll]ineage" && \
|
|
524
|
+
assert "BLOCKED message labels the chain explicitly" "ok" || \
|
|
525
|
+
assert "BLOCKED message labels the chain explicitly" "output=$HOOK_STDOUT"
|
|
526
|
+
teardown_tmpdir
|
|
527
|
+
|
|
528
|
+
# BLOCKED cites bassclef#1347 (recursive walk source ticket)
|
|
529
|
+
setup_tmpdir
|
|
530
|
+
stub_gh_bassclef
|
|
531
|
+
cat > "$TMPDIR_TEST/docs/whereami.md" <<'EOF'
|
|
532
|
+
iteration_bet: 2026-06-20a
|
|
533
|
+
EOF
|
|
534
|
+
run_hook
|
|
535
|
+
echo "$HOOK_STDOUT" | grep -q "bassclef#1347" && \
|
|
536
|
+
assert "BLOCKED cites bassclef#1347 (recursive walk)" "ok" || \
|
|
537
|
+
assert "BLOCKED cites bassclef#1347 (recursive walk)" "output=$HOOK_STDOUT"
|
|
538
|
+
teardown_tmpdir
|
|
539
|
+
|
|
540
|
+
# =====================================================================
|
|
541
|
+
# Fixture-driven coverage (per #545 retroactive backfill)
|
|
542
|
+
# =====================================================================
|
|
543
|
+
# When FIXTURE_DIR is exported by run-with-fixtures.sh wrapper, the test
|
|
544
|
+
# runs once per declared fixture against a real adopter-shaped repo.
|
|
545
|
+
# Verifies the gate fires on main inside an adopter environment when no
|
|
546
|
+
# main marker exists and whereami is present.
|
|
547
|
+
|
|
548
|
+
if [ -n "${FIXTURE_DIR:-}" ]; then
|
|
549
|
+
fixture_name="${FIXTURE_DIR##*/}"
|
|
550
|
+
# Set up the fixture as a git repo on main + write a whereami file
|
|
551
|
+
(cd "$FIXTURE_DIR" && git init -q 2>/dev/null && git config user.email t@t && git config user.name t \
|
|
552
|
+
&& git checkout -q -b main 2>/dev/null || git checkout -q main 2>/dev/null) || true
|
|
553
|
+
mkdir -p "$FIXTURE_DIR/docs" "$FIXTURE_DIR/.claude/hooks"
|
|
554
|
+
cat > "$FIXTURE_DIR/docs/whereami.md" <<'EOF'
|
|
555
|
+
iteration_bet: fixture-driven-test-bet
|
|
556
|
+
EOF
|
|
557
|
+
# Stub gh binary at fixture root so the hook doesn't crash on a real query
|
|
558
|
+
cat > "$FIXTURE_DIR/gh" <<'EOF'
|
|
559
|
+
#!/usr/bin/env bash
|
|
560
|
+
if [ "$1" = "repo" ] && [ "$2" = "view" ]; then echo "bassclef"; exit 0; fi
|
|
561
|
+
if [ "$1" = "issue" ] && [ "$2" = "list" ]; then exit 0; fi
|
|
562
|
+
exit 0
|
|
563
|
+
EOF
|
|
564
|
+
chmod +x "$FIXTURE_DIR/gh"
|
|
565
|
+
|
|
566
|
+
FIXTURE_STDOUT=$(cd "$FIXTURE_DIR" \
|
|
567
|
+
&& PATH="$FIXTURE_DIR:$PATH" \
|
|
568
|
+
&& CLAUDE_PROJECT_DIR="$FIXTURE_DIR" \
|
|
569
|
+
bash "$HOOK" 2>&1)
|
|
570
|
+
FIXTURE_EXIT=$?
|
|
571
|
+
|
|
572
|
+
if [ "$FIXTURE_EXIT" = "0" ] && echo "$FIXTURE_STDOUT" | grep -q "orientation-gate"; then
|
|
573
|
+
assert "fixture-${fixture_name}-fires-on-main-with-no-marker" "ok"
|
|
574
|
+
else
|
|
575
|
+
assert "fixture-${fixture_name}-fires-on-main-with-no-marker" "exit=$FIXTURE_EXIT output=$FIXTURE_STDOUT"
|
|
576
|
+
fi
|
|
577
|
+
fi
|
|
578
|
+
|
|
579
|
+
# =====================================================================
|
|
580
|
+
# Summary
|
|
581
|
+
# =====================================================================
|
|
582
|
+
|
|
583
|
+
echo "---"
|
|
584
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
585
|
+
|
|
586
|
+
if [ "$FAIL" -gt 0 ]; then
|
|
587
|
+
echo ""
|
|
588
|
+
echo "Failed tests:"
|
|
589
|
+
for t in "${FAILED_TESTS[@]}"; do
|
|
590
|
+
echo " - $t"
|
|
591
|
+
done
|
|
592
|
+
exit 1
|
|
593
|
+
fi
|
|
594
|
+
|
|
595
|
+
exit 0
|