#!/usr/bin/env bash # unreleased-police.sh 的迴歸測試(inkstone/ISEP#68,docs/TESTING.md A27) # # 兩個方向都要有證據(本 repo 心法:誤攔比漏擋嚴重): # A 群「該擋」 —— session 改了 code 卻沒出貨(含「從未出過版」與「領先最新 release N 筆」) # B 群「不該擋」 —— 只讀不寫/只改 wiki/已出貨(tag 收進 HEAD)/排除 marker/這次先不出/stop_hook_active # C 群「出路走得通」—— 照擋下訊息教的「寫 release-holds.md 一行」做,再送一次就放行(ISEP#112 的課) # D 群「鑑別力」 —— 拿掉 marker 同一個 repo 就會擋(證明放行是 marker 造成的,不是偵測壞了) # # 🔴 全程在乾淨的 TMP 底下造假 repo/假 transcript,跑完自己清;不碰任何真 repo、不打網路。 set -u HOOK="${1:-$(cd "$(dirname "$0")/.." && pwd)/hooks/unreleased-police.sh}" TMP=$(mktemp -d) trap 'rm -rf "$TMP"' EXIT # session 起點固定成 2026-01-01T00:00:00Z(transcript 第一列的時間戳)。 # 「這個 session 的 commit」=committer-date 晚於這一刻。 T0="2026-01-01T00:00:00Z" TR="$TMP/transcript.jsonl" printf '{"timestamp":"%s","type":"user","message":{"content":[{"type":"text","text":"開工"}]}}\n' "$T0" > "$TR" AFTER="2026-06-01T00:00:00Z" # 晚於起點 = 這個 session 改的 BEFORE="2025-06-01T00:00:00Z" # 早於起點 = 不是這個 session 改的 PASS=0; FAIL=0; N=0 mkrepo() { git init -q "$1"; git -C "$1" config user.email t@t; git -C "$1" config user.name t; } # commit <相對路徑> <訊息> commit() { mkdir -p "$(dirname "$1/$3")"; echo x >> "$1/$3"; git -C "$1" add -A GIT_AUTHOR_DATE="$2" GIT_COMMITTER_DATE="$2" git -C "$1" commit -qm "$4" } # fire <說明> <期望exit> [extra-json] [--grep <字串>] fire() { desc="$1"; want="$2"; repo="$3"; extra="${4:-}"; needle="${6:-}" N=$((N+1)) payload=$(printf '{"session_id":"s%s","transcript_path":"%s"%s}' "$N" "$TR" "$extra") out=$(printf '%s' "$payload" | CLAUDE_PROJECT_DIR="$repo" bash "$HOOK" 2>&1); rc=$? ok=1 [ "$rc" = "$want" ] || ok=0 if [ -n "$needle" ] && ! printf '%s' "$out" | grep -qF "$needle"; then ok=0; fi if [ "$ok" = 1 ]; then printf ' ✅ %s\n' "$desc"; PASS=$((PASS+1)) else printf ' ❌ %s —— 期望 exit=%s,實得 exit=%s' "$desc" "$want" "$rc" [ -n "$needle" ] && printf '(找 "%s")' "$needle" printf '\n' printf '%s\n' "$out" | sed -n '1,10p' | sed 's/^/ /' FAIL=$((FAIL+1)) fi } echo "── A 群:該擋(改了 code 卻沒出貨)──" mkrepo "$TMP/a1"; commit "$TMP/a1" "$AFTER" hooks/foo.sh "加一支 hook" fire "改了 hook、從未出過版 → 擋,且點名 repo+筆數" 2 "$TMP/a1" "" --grep "從未出過版" mkrepo "$TMP/a2"; commit "$TMP/a2" "$BEFORE" hooks/a.sh a; git -C "$TMP/a2" tag v1 commit "$TMP/a2" "$AFTER" hooks/b.sh b fire "有舊 release、新 commit 未收進 → 擋,訊息說『領先它 N 筆』" 2 "$TMP/a2" "" --grep "領先它 1 筆" mkrepo "$TMP/a3"; commit "$TMP/a3" "$AFTER" scripts/x.sh x; commit "$TMP/a3" "$AFTER" system-dev/wiki/n.md n fire "同一 session 改了 code+wiki → 擋(只要有一個非知識檔)" 2 "$TMP/a3" "" --grep "未出貨" echo "── B 群:不該擋 ──" mkrepo "$TMP/b1"; commit "$TMP/b1" "$BEFORE" hooks/foo.sh old fire "這個 session 什麼都沒 commit(只讀不寫)→ 放行" 0 "$TMP/b1" mkrepo "$TMP/b2"; commit "$TMP/b2" "$AFTER" system-dev/wiki/note.md "只改 wiki" fire "這個 session 只改了 wiki → 放行" 0 "$TMP/b2" mkrepo "$TMP/b2b"; commit "$TMP/b2b" "$AFTER" docs/hooks-inventory.md "只改 docs" fire "這個 session 只改了 docs → 放行" 0 "$TMP/b2b" mkrepo "$TMP/b3"; commit "$TMP/b3" "$AFTER" hooks/foo.sh code; git -C "$TMP/b3" tag v9 fire "改了 code 而且已出貨(tag 收進 HEAD)→ 放行" 0 "$TMP/b3" mkrepo "$TMP/b4"; commit "$TMP/b4" "$AFTER" hooks/foo.sh code printf '產物 repo,管線產出,不出版\n' > "$TMP/b4/.isep-release-exempt" fire "改了 code 但有 .isep-release-exempt marker → 放行" 0 "$TMP/b4" mkrepo "$TMP/b5"; commit "$TMP/b5" "$AFTER" hooks/foo.sh code mkdir -p "$TMP/b5/.claude"; printf 'b5: 交回分支,release 由總管出\n' > "$TMP/b5/.claude/release-holds.md" fire "改了 code 沒出貨、但 release-holds.md 有這個 repo(先不出)→ 放行且留痕" 0 "$TMP/b5" mkrepo "$TMP/b6"; commit "$TMP/b6" "$AFTER" hooks/foo.sh code fire "已被別的 Stop 閘擋過這一輪(stop_hook_active)→ 放行" 0 "$TMP/b6" ',"stop_hook_active":true' echo "── C 群:擋下訊息教的出路,照做真的走得通 ──" mkrepo "$TMP/c1"; commit "$TMP/c1" "$AFTER" hooks/foo.sh code fire "先擋一次" 2 "$TMP/c1" "" --grep "release-holds.md" # 照訊息說的:在 .claude/release-holds.md 寫「: <理由>」 mkdir -p "$TMP/c1/.claude"; printf 'c1: 這次累積,下個 session 一起出\n' > "$TMP/c1/.claude/release-holds.md" fire "照訊息寫了 release-holds.md 那一行,再送 → 放行" 0 "$TMP/c1" echo "── D 群:鑑別力(放行是 marker/hold 造成的,不是偵測壞了)──" # b4 若拿掉 marker,同一個 repo 就會擋 → 證明偵測本身是會亮的 rm -f "$TMP/b4/.isep-release-exempt" fire "把 b4 的 marker 拿掉 → 同一個 repo 就擋(偵測真的會亮)" 2 "$TMP/b4" echo echo "$PASS/$N 通過" [ "$FAIL" -eq 0 ]