1dfc4e373a
leo 2026-08-20 問「InkStoneCo#40 加入了嗎?如果是這樣我應該可以白話文看到 hooks 的內容?」
答案是不行——43 支閘沒有任何白話清單。這一版補上。
docs/hooks-inventory.md 43 支逐支一行,按「你會在什麼時候撞到它」分 9 組
抽驗 5 支逐行核對源碼;順帶抓到 3 支有檔案沒註冊
docs/TESTING.md A1-A8 + B1-B5,每格都有「怎麼跑/該看到什麼/什麼算失敗」
scripts/test-*.sh 兩支閘的測試,共 21 條,全過
兩個實撞的洞:
- release-tag-guard 的排除清單是前綴比對,x 整條放行
(A8 那個新 session 抓到的,總管複驗屬實)。改用 #23 驗證過的判準:
關鍵字要在指令位置才算執行。補 3 條複合指令測試,8/8。
⇒ 這是 InkStoneCo#36「包一層就繞過去」的同一個病,發生在同一天新寫的閘上。
- scripts/ticket 寫死只認名叫 gitea 的 remote,在 ISEP(remote 叫 origin)整個跑不起來
⇒「開票前先搜」那道閘在新 repo 等於不存在。改成掃所有指向本站的 remote + 環境變數 fallback。
A8 已通過:新 session 裡 plugin 的閘真的觸發(exit 2、tag 未建立、訊息來自 plugin 路徑)。
文件漂移訂正:plugin.json 與 README 寫 42 支/52 條,實際 43 支/53 條。
兩支新閘補上 #40 §1 要求的三行中文檔頭。
🔴 但仍違反 #40 §3「新規則一律先 warn」——兩支都是 block。理由記在 #40 留言,等 leo 裁。
93 lines
4.8 KiB
Bash
Executable File
93 lines
4.8 KiB
Bash
Executable File
#!/bin/sh
|
||
# release-tag-guard.sh — PreToolUse(Bash):打 tag 那一刻擋下版本不一致
|
||
#
|
||
# 立這道閘的來由(inkstone/ISEP#6,2026-08-20):
|
||
# README.md 曾寫死「狀態:0.1.0」,但這個 repo `release_counter=0`、
|
||
# 一個 tag 都沒打。leo 當場指出這是違規,且命中規範自己的 E12
|
||
# (宣稱交付但沒有 tag);leo 補充:「release 不是寫在 readme,要放在 release 裡」。
|
||
#
|
||
# 這支閘解的不是「README 寫錯字」,是**結構性防漂移**:
|
||
# 「ISEP 現在是哪一版」只有一個地方答得出來= Gitea Releases(git tag)。
|
||
# `.claude-plugin/plugin.json` 的 `version` 欄位必須跟這個 tag 完全一致,
|
||
# 不然又回到「版本號在不同地方各說各話」的老路——只是這次換成
|
||
# 「manifest 一個號碼、tag 又一個號碼」而不是「README 一個號碼、repo 裡沒 tag」。
|
||
# 同一套判準也寫在 `scripts/check-version-consistency.sh`(可以隨時手動跑,
|
||
# 不必等到打 tag那一刻);這支 hook 是「結構性做不到」的那一半——
|
||
# 在真正動手打 tag 的當下擋住,而不是靠事後補跑腳本才發現。
|
||
#
|
||
# 🔴 紅線:**這支 hook 不打 tag、不建 release**——它只在別人(人類/總管)要打 tag 時
|
||
# 檢查一致性。真正打哪個版本號的 tag,是總管驗過整個 milestone 之後的動作。
|
||
#
|
||
# 設計紀律(沿用本 repo既有 guard 的兩條,見 main-and-prod-push-guard.sh 註解):
|
||
# • **先排除「談論/讀取/刪除/列出」**,只擋「真的要打一個新 tag」的那個命令形狀。
|
||
# • **抽不出版本號就不擋**(fail-open on 解析失敗,不是 fail-open on 檢查結果)——
|
||
# 避免因為指令格式特殊(例如帶簽名 `-s`、多行訊息)誤攔到人,
|
||
# 那正是「閘被誤攔多次會被繞過」的病根(README 段落引的 D95 同一款教訓)。
|
||
set -eu
|
||
|
||
INPUT="$(cat)"
|
||
CMD=$(printf '%s' "$INPUT" | python3 -c '
|
||
import sys, json
|
||
try: print(json.load(sys.stdin).get("tool_input", {}).get("command", "") or "")
|
||
except Exception: print("")
|
||
' 2>/dev/null || printf '')
|
||
|
||
[ -z "$CMD" ] && exit 0
|
||
|
||
# ── 判準:`git tag` 要出現在「指令位置」才算真的要打 tag ──────────────
|
||
# 🔴 這裡原本用前綴比對排除 sed/cat/grep/echo/ls…(`echo\ *)` 這種),
|
||
# 2026-08-20 被一個新 session 抓到洞、總管複驗屬實:
|
||
# echo 開始 && git tag -a v9.9.9 -m test → 整條放行
|
||
# ls && git tag -a v9.9.9 -m x → 整條放行
|
||
# 因為前綴是 `echo `/`ls ` 就整條 exit 0,後面串什麼都不看。
|
||
# ⇒ 就是 inkstone/InkStoneCo#36「守 prod 的閘,包一層腳本就繞過去」的同一個病,
|
||
# 而且發生在同一天新寫的閘上。
|
||
# 修法用 #23 已驗證過的判準:**關鍵字要在指令位置才算執行**
|
||
# (行首、或跟在 ; & | ( && || 之後),只是被別的指令當成文字提到就不算。
|
||
printf '%s' "$CMD" | grep -qE '(^|[;&|(`]|&&|\|\|)[[:space:]]*git[[:space:]]+tag[[:space:]]' || exit 0
|
||
|
||
# 讀取/刪除類的 tag 動作不是「打新 tag」,放行
|
||
case "$CMD" in
|
||
*"git tag -d"*|*"git tag --delete"*|*"git tag -l"*|*"git tag --list"*|*"git tag -n"*) exit 0 ;;
|
||
*" --dry-run"*|*"--dry-run "*) exit 0 ;;
|
||
esac
|
||
|
||
case "$CMD" in
|
||
*"git tag "*) ;;
|
||
*) exit 0 ;;
|
||
esac
|
||
|
||
# ── 從命令裡萃取版本號(第一個 vX.Y.Z 或 X.Y.Z 樣式的 token)───────────
|
||
TAGNAME=$(printf '%s' "$CMD" | grep -oE 'v?[0-9]+\.[0-9]+\.[0-9]+' | head -1 || printf '')
|
||
[ -n "$TAGNAME" ] || exit 0 # 抽不出版本號=不是本閘管的形狀,不擋(見上方設計紀律)
|
||
|
||
VER=${TAGNAME#v}
|
||
|
||
PROJ="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
||
PLUGIN_JSON="$PROJ/.claude-plugin/plugin.json"
|
||
[ -f "$PLUGIN_JSON" ] || exit 0 # 不在 ISEP repo 裡(沒有這個檔案)=不是本閘管的 repo
|
||
|
||
PJVER=$(python3 -c "import json;print(json.load(open('$PLUGIN_JSON')).get('version',''))" 2>/dev/null || printf '')
|
||
[ -n "$PJVER" ] || exit 0
|
||
|
||
if [ "$PJVER" != "$VER" ]; then
|
||
cat >&2 <<MSG
|
||
🚫 版本不一致,擋下這次打 tag(inkstone/ISEP#6:版本結構性防漂移閘)
|
||
|
||
你想打的 tag:$TAGNAME(版本號 $VER)
|
||
.claude-plugin/plugin.json 現在的 version:$PJVER
|
||
|
||
兩者必須完全一致——「ISEP 現在是哪一版」只有 Gitea Releases 答得出來,
|
||
而 Releases 的 tag 名稱跟 plugin.json 的宣稱要是同一個數字,不然又是各說各話。
|
||
|
||
【怎麼過】把 .claude-plugin/plugin.json 的 version 改成 $VER,
|
||
連同這次要發的其他改動一起 commit,再重新打 tag $TAGNAME。
|
||
(或者你要打的其實是 $PJVER 這個號碼,那就把 tag 名稱改對。)
|
||
|
||
驗法一致的獨立腳本:scripts/check-version-consistency.sh
|
||
MSG
|
||
exit 2
|
||
fi
|
||
|
||
exit 0
|