0024d697f9
leo 2026-08-27:「每個 repo 只要有改動就要出貨⋯⋯這個 session 有改但要 結束沒出貨就抓,你可以久一點再出貨累積幾個 PR,但最後一定要出貨」。 四個判準(票要求自己判、寫得出理由,不照抄總管): - 「改過」=這個 session 期間 HEAD 新長出來的 commit(不是工作區髒、不是 push)。 與「出貨」用同一個單位(commit),才數得出「最後 commit 與最新 release 差多少」。 session 起點=transcript 第一列時間戳(同 countdown.py 的基準)。 - 「該不該出貨」=有沒有動到知識(wiki/docs/_archive)以外的檔。只改 wiki/docs 不擋。 - 「哪些 repo 不適用」=repo 根目錄有 .isep-release-exempt(自帶理由,非中央手列清單, 新 repo 預設納管)。 - 「這次先不出」=.claude/release-holds.md 寫一行理由(放行且留痕;形狀同 branch-holds.md, 是「要求某個東西在場」而非文字層封路)。 「出貨」=git tag --contains HEAD 非空(Gitea release 就是 tag,本機答得準、不打網路)。 只掛 Stop、不掛 SubagentStop:subagent 交回分支、release 由總管出,逼 subagent 出貨純屬誤攔。 fail-open:拿不到 transcript 起點/非 git/git 問不出來一律放行(誤攔比漏擋嚴重)。 盤點:61→62 支、84→85 條(在自己的樹上實數,未加減推算);未掛上仍 3 支(不變)。 測試:scripts/test-unreleased-police.sh 13/13(該擋/不該擋/出路走得通/鑑別力四群,全離線)。 docs/hooks-inventory.md F 組加一列+版本註記;docs/TESTING.md A27。 plugin.json 待總管定版:版本號與 tag 由總管在打 release 那一刻同 commit 改(升版是傳輸 機制本身,check-version-consistency 要求 version==最新 tag,故不在分支上先動)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M6AcoT4ALtPLsoCKab6pXt
103 lines
5.3 KiB
Bash
Executable File
103 lines
5.3 KiB
Bash
Executable File
#!/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 <repo> <date> <相對路徑> <訊息>
|
||
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> <repo目錄> [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 寫「<repo 名>: <理由>」
|
||
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 ]
|