Files
ISEP/hooks/release-tag-guard.sh
Leo 1dfc4e373a v0.2.1:43 支閘的白話盤點、測試手冊、補上兩個被抓到的洞
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 裁。
2026-08-20 17:08:00 +08:00

93 lines
4.8 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh
# release-tag-guard.sh — PreToolUseBash):打 tag 那一刻擋下版本不一致
#
# 立這道閘的來由(inkstone/ISEP#62026-08-20):
# README.md 曾寫死「狀態:0.1.0」,但這個 repo `release_counter=0`、
# 一個 tag 都沒打。leo 當場指出這是違規,且命中規範自己的 E12
# (宣稱交付但沒有 tag);leo 補充:「release 不是寫在 readme,要放在 release 裡」。
#
# 這支閘解的不是「README 寫錯字」,是**結構性防漂移**:
# 「ISEP 現在是哪一版」只有一個地方答得出來= Gitea Releasesgit 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
🚫 版本不一致,擋下這次打 taginkstone/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