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 裁。
85 lines
4.8 KiB
Bash
Executable File
85 lines
4.8 KiB
Bash
Executable File
#!/bin/bash
|
||
# 管什麼: 用 Gitea API 直接開新票時,要求這一輪有跑過跨 repo 的搜尋(/tmp/.ticket-where-ok,30 分鐘內)。
|
||
# 為什麼: scripts/ticket 早就強制先搜,但那道閘只擋走正門的人。2026-08-20 總管走 API 側門開了 12 張票,每一張都跟舊票重疊。
|
||
# 誤觸時怎麼關: 跑 `scripts/ticket where <關鍵字>` 先搜(之後 30 分鐘 API 也放行),或在指令裡加 `ticket-api-ok` 留痕放行。
|
||
# ticket-api-bypass-guard.sh — 開票的「側門」也要經過同一道搜尋閘
|
||
#
|
||
# 來由(leo 2026-08-20 當場問「如何防止」):
|
||
# `scripts/ticket new` 早就強制「開票前先搜」(/tmp/.ticket-where-ok 戳記,30 分鐘失效)。
|
||
# 但總管當天開了 12 張與舊票重疊的新票——因為他**沒用那支工具,直接打 Gitea API**。
|
||
# ⇒ 規範有、閘也有,但閘長在「工具」上,而那個動作有兩條路,只封了一條。
|
||
# leo:「你在讓事情複雜化」/「每張票開以前都要搜尋現有票,你為什麼會開了不搜?」
|
||
#
|
||
# 這支封的是**動作**:任何 Bash 指令只要在對 Gitea 的 issues 端點做寫入,
|
||
# 就要有一個新鮮的搜尋戳記。它不強迫你用 scripts/ticket,只強迫你搜過。
|
||
# (同 InkStoneCo#36:「守 prod 的閘,包一層腳本就繞過去了——它看的是指令長相」。
|
||
# 本支同樣只看得到指令文字,這是 PreToolUse 這層的天花板;
|
||
# 所以判準取「端點 + 寫入動詞」兩個都命中才擋,讓純讀取一律放行。)
|
||
#
|
||
# 放行(刻意,這些都不是「開票」):
|
||
# - 只讀不寫(GET):撈清單、看票、對帳
|
||
# - 對既有票的留言/改標籤/關票(/issues/<N>/... 這種帶票號的子路徑)
|
||
# - scripts/ticket 自己(它有自己的閘,重複擋只會互相打架)
|
||
# - 指令裡出現 ticket-api-ok(逃生口,會留在指令歷史上)
|
||
set -uo pipefail
|
||
|
||
INPUT=$(cat)
|
||
CMD=$(printf '%s' "$INPUT" | python3 -c "
|
||
import sys,json
|
||
try: print(json.load(sys.stdin).get('tool_input',{}).get('command',''))
|
||
except Exception: print('')
|
||
" 2>/dev/null)
|
||
[ -n "$CMD" ] || exit 0
|
||
|
||
# 逃生口(留痕)
|
||
case "$CMD" in *ticket-api-ok*) exit 0 ;; esac
|
||
# scripts/ticket 有自己的閘
|
||
case "$CMD" in *scripts/ticket*|*"ticket where"*|*"ticket new"*|*"ticket say"*) exit 0 ;; esac
|
||
|
||
# ① 有沒有打到 Gitea 的 issues 端點(不帶票號的那個=建立新票的路徑)
|
||
printf '%s' "$CMD" | grep -qE 'repos/[^ "'"'"']*/issues([?"'"'"'`,)\\[:space:]]|$)' || exit 0
|
||
|
||
# ② 指令裡有沒有 POST 這個詞(純 GET 一律放行)
|
||
# 刻意只認一個裸字:跳脫引號、heredoc、python、curl、各種包裝的寫法無限多,
|
||
# 逐個補 pattern 追不完(leo 2026-08-17:「自然語言的變體是無限的,blacklist 永遠追不完」)。
|
||
# ①已經確定這是「開票那條端點」,純讀取的指令不會出現 POST ⇒ 一個字就夠,而且沒有跳脫的破口。
|
||
printf '%s' "$CMD" | grep -qw 'POST' || exit 0
|
||
|
||
# ③ 要有新鮮的搜尋戳記(與 scripts/ticket 共用同一個,30 分鐘)
|
||
STAMP=/tmp/.ticket-where-ok
|
||
NOW=$(date +%s)
|
||
FRESH=no
|
||
if [ -f "$STAMP" ]; then
|
||
AT=$(python3 -c "import json;print(int(json.load(open('$STAMP'))['at']))" 2>/dev/null || echo 0)
|
||
[ $((NOW - AT)) -le 1800 ] && FRESH=yes
|
||
fi
|
||
[ "$FRESH" = "yes" ] && exit 0
|
||
|
||
cat >&2 <<'MSG'
|
||
🚫 你正在用 Gitea API 直接開新票,而這一輪沒有搜尋紀錄
|
||
|
||
leo 2026-08-16:「**寫開票前先去搜尋要開在哪裡,不然你永遠會亂開新票**」
|
||
leo 2026-08-20:「**每張票開以前都要搜尋現有票,你為什麼會開了不搜?這個規範不是早就有 hook 了?**」
|
||
|
||
規範有,閘也有——但那道閘長在 `scripts/ticket` 這支工具裡,
|
||
而你走的是 API 這條側門。**本閘就是把那道門也封上。**
|
||
|
||
實錯(2026-08-20 同日):總管用 API 開了 12 張票,事後盤點**每一張都跟舊票重疊**,
|
||
全部只能關掉指回舊票。leo:「**你在讓事情複雜化**」。
|
||
|
||
── 怎麼過(擇一)───────────────────────────────
|
||
1. 先搜(預設,戳記 30 分鐘有效,之後 API 也放行):
|
||
scripts/ticket where <關鍵字...>
|
||
🔴 搜到了就**貼進那張票**,不要開新的:
|
||
scripts/ticket say <owner/repo#N> -F <內文檔>
|
||
|
||
2. 真的是新的一條線 → 直接用那支工具開,它會幫你把該檢查的檢查完:
|
||
scripts/ticket new <repo> -F <內文檔> --title <標題>
|
||
|
||
3. 這次確實不是在開新票(例如批次改標籤/關票/留言)
|
||
→ 指令裡加 `ticket-api-ok` 說明理由,留痕放行。
|
||
|
||
放行的情況(本閘不管):純讀取(GET)、對既有票 /issues/<N>/ 的留言與標籤、scripts/ticket 自己。
|
||
MSG
|
||
exit 2
|