Files
ISEP/docs/hooks-inventory.md
T
Leo e474b4cb6f 棒子不會掉:子票相依+tag+指派三格,全部用 Gitea 原生欄位
leo 2026-08-27:「子票相依是 gitea 原有機制,改 tag 和指定也是,這些全部都要」

08-26 掉的那一次(arcrun-rag#136 comment 4267「等雲端那半出貨才驗得了」躺了
14 小時)三格全缺:沒有子票、tag 沒動、沒有指派給任何人。根因不是誰忘了,
是那件事沒有落在任何一個「撈一次就看得到」的欄位上。

實測(Gitea 1.26.4):子票還開著時關母票 → HTTP 412
"cannot close this issue or pull request because it still has open dependencies"
⇒ 相依是平台保證的硬擋,不是提醒。跨 repo 也成立(201)。

- scripts/ticket 新增三個動詞:subtask(長子票+掛相依)/
  handback(指派+改 tag+寫下一步,一個動作)/mine(撈一次看棒子在誰手上)
- hooks/comment-carries-task-guard.sh:留言帶未完成的未來式卻沒開子票 → 擋一次
- hooks/baton-handback-guard.sh:一條線收工,三格缺哪一格當場說出來(提醒不擋)
- docs/governance §16:三個維度/粒度(傾向多開票)/既有票只從今天起適用/
  與 s/* 的關係/實測輸出
- 測試 20+10 全綠,用 fixture 跑不打網路、不在票池留測試票

📌 踩到並記進閘的檔頭:hook 裡比對中文一律用 python3 的 re,不要用 grep 的字元類
(等[^。]{0,20}出貨 在真實 hook 呼叫路徑下對「等雲端那半出貨」不匹配,閘靜默失效)

票:inkstone/ISEP#30(comment 4334/4335/4346/4347)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 12:05:53 +08:00

22 KiB
Raw Blame History

48 支閘,白話盤點表

回應 inkstone/InkStoneCo#40:「如果加入了,我應該可以白話文看到 hooks 的內容?」 這份表就是那個「白話文」——不用點開任何 .sh 檔,一行看懂一支閘在管什麼。

最高原則(票上原文):每一條規則你都要能在 30 秒內看懂它在管什麼。

一句話結論

hooks/ 底下有 48 個 .shhooks.json 實際掛上 58 條註冊(同一支閘常被多種情境同時掛上); 其中 3 支檔案存在但沒被掛上(2 支是待人填的空範本、1 支是刻意留著沒開的止血帶,見下面「未生效」表)。 下面按「你會在什麼時候撞到它」分組,一支一行。

🔴 這兩個數字上一版是錯的(2026-08-26 實際數過才發現):本頁原本寫「43 個檔、53 條註冊」, 而當時真實是 45 個檔、55 條註冊——中間有兩支閘進來時沒有回頭改這裡。 現在的寫法是實際數出來的: ls hooks/*.sh | wc -l 48hooks.json 展開後的 command 條目 58。 一份會偷偷過期的盤點表,跟沒有盤點表差不多——見本頁最後「怎麼跟實況對帳」那段。


怎麼讀這張表

符號 意思
🛑 條件不滿足就真的擋下這個動作(exit 2),你或 AI 會看到一段紅字說明
📝 記錄 不擋任何東西,只是在背景寫一筆紀錄或送一段提示文字給 AI
💀 未生效 檔案存在,但沒有掛進 hooks.json——目前是死的,不會被執行

「對你意味著什麼」欄一律用「如果你看到 X,代表 Y」的角度寫,不寫程式邏輯。


A. 你(或 AI)在終端機打指令的當下(PreToolUse / Bash

閘名 對你意味著什麼 動作
github-contact-guard.sh AI 想碰 GitHubgit pushgh api 等)就先擋下——兩個帳號曾經因為機器人式高頻寫入被永久 flag 過。只有讀取(clone/fetch)自由,寫入一律要你在終端機跑 github-arm.sh 解鎖 🛑
kbdb-api-wall-guard.sh AI 想繞過 KBDB 的 API、直接對資料庫下 SQL 或建新表就擋下——KBDB 的鐵律是「只能用 API、永遠只有三張表」。 🛑
stage-before-prod-guard.sh AI 想動「出貨鏈」相關指令(含 arcrun-rag-bundlesgithub-armpublish-github 字樣)卻沒先在 stage 環境驗過就擋下。 🛑
main-and-prod-push-guard.sh 兩層確認:① subagent 想推 Gitea main 分支 → 一定要交回總管,總管確認過才放行;② 任何 wrangler deploy 打正式環境 → 一定要你親手解鎖。 🛑
prod-write-guard.sh AI 想做任何「會真的改到線上正式帳號(leo21c)」的操作(不只是 push,也包含直接呼叫會寫入的工具)就擋下,交回總管處理。 🛑
not-my-branch-guard.sh AI 想 commit 到「別的 subagent 正在施工中」的分支(登記在 branch-holds.md)就擋下,避免兩個 AI 互踩同一條分支。 🛑
leo21c-write-guard.sh AI 想寫東西到你私人帳號 leo21c 就擋下——這個帳號只准你自己用,AI 的實驗一律走別的測試帳號。 🛑
release-tag-guard.sh AI 想打版本 tag,但 plugin.json 裡寫的版本號跟 tag 對不上就擋下——避免「這裡說 0.2.0、那裡說 0.1.0」各說各話。 🛑
ticket-api-bypass-guard.sh AI 想繞過 scripts/ticket 工具、直接打 API 開新 Gitea 票(會漏掉「開票前先搜過」這道檢查)就擋下。 🛑
comment-carries-task-guard.sh AI 要在票裡留一則「等某某上線才驗得了」這種還沒做完的事,卻沒有把它開成一張子票時,擋一次。留言沒人會回頭讀,票的 open/closed 撈一次就看得到。 🛑 擋(同一輪只擋一次)

B. AI 想寫檔案/改程式碼的當下(PreToolUse / Write·Edit·MultiEdit

閘名 對你意味著什麼 動作
guard-cross-project.sh 總管(頂層)想直接改某個子 repo 的程式碼(非 .md)就擋下——頂層只做安排交辦,實作要進那個子 repo 自己做。 🛑
wiki-secret-scan.sh 要寫進 system-dev/wiki/ 的內容裡出現密碼/金鑰/身分證/信用卡等特徵就擋下,防止機敏資料意外留在會被反覆讀取的記憶空間裡。 🛑
component-guard.sh AI 想自己新建一個零件(component)或亂接 service binding 就擋下——逼它先想「現成零件夠不夠用」,真要建要你解鎖。 🛑
sdd-guard.sh AI 想直接動程式碼檔案,但找不到「唯一一份 active 規格文件(SDD)」對應這件事,或同時有一份以上 active 規格就擋下。 🛑
credential-only-guard.sh AI 想把金鑰真身或自製佔位符(例如 __XXX_TOKEN__)寫進設定檔就擋下——金鑰只准放在統一的 credential 中心。 🛑
arcrun-intent-guard.sh AI 寫的 Arcrun workflow 語法不對就擋下,而且直接把正確寫法回貼給它(不是只罵它錯,是教它怎麼改)。 🛑 擋(教學型)
subagent-first-guard.sh 這個對話從頭到尾都沒有派過任何 subagent,AI 卻要自己動手改程式碼,就先擋一次,逼它想一想「這件事能不能交給別人做」。 🛑
mistake-needs-ticket-guard.sh AI 想往 mistakes.md(教訓紀錄)新增一條「機制可以防止」的教訓,卻沒附對應票號就擋下——沒有票號的教訓沒有人會回頭處理。 🛑
pending-changes-retired.sh AI 想寫東西進已經廢除的 pending-changes.md 檔案就擋下——這個檔案已停用,規格變更一律改開 Gitea 票。 🛑

kbdb-api-wall-guard.sh 在這裡也重複掛了一次(見 A 組)——它同時守著「下指令」跟「寫檔案」兩種情境,詳見下方「重複掛載」一節。

C. AI 要去翻程式碼/查資料之前(PreToolUse / Grep·Glob·Read·Bash

閘名 對你意味著什麼 動作
wiki-first-search.sh AI 這回合還沒查過 wiki就要去翻程式碼或查外部資料,就先擋下、逼它用你的關鍵字先搜一次 wiki(查過一次、不論有沒有找到,這回合後面就放行)。 🛑

D. AI 要派工給別的 AIsubagent)之前(PreToolUse / Agent·Task

閘名 對你意味著什麼 動作
subagent-wiki-guard.sh AI 要派一個查證/實作類任務出去,就自動在派工單裡塞一句「先查 wiki」的提示——不擋,只是順手夾帶叮嚀。 📝 記錄
kbdb-api-wall-guard.sh 同 A/B 組,只是這裡管的是「派工單裡有沒有寫出違反 KBDB 規約的指示」。 🛑
micromanage-guard.sh 派工單寫得太細(指名檔案函式、編號步驟、要求每做一項回報一次…)就擋下——subagent 該被當成有能力的同事,不是照抄劇本的工具。 🛑
irreversible-dispatch-guard.sh 派工單裡出現「刪分支」「drop table」「rm -rf」這類不可逆動作,卻沒寫「先停下來等回覆才執行」就擋下。 🛑
no-ticket-no-dispatch.sh 派工單裡沒有寫工單號(【工單】owner/repo#N),或那張票已經關閉/根本不存在,就擋下——沒有票號的工作沒有人追得到進度。 🛑

D2. AI 想開口問你問題的當下(PreToolUse / AskUserQuestion

閘名 對你意味著什麼 動作
ask-user-question-guard.sh AI 要跳出來問你一個問題的那一刻先攔一下,用小模型(haiku)照「四題公式」判這題該不該打擾你:花錢/不可逆/跨專案結構/品味方向/只有你做得到——命中任何一題就放行(那本來就該問你),四題全否(純技術實作選擇、問「要不要開始」)就擋回去要它自己裁。同一個問題只擋一次,它重送就過得去,所以判錯不會害你收不到問題;判官掛掉/沒網路也一律放行。 🛑 擋(同一題至多一次)

為什麼要有這一組leo 2026-08-26:「今天已經好幾次問我,為什麼 hooks 沒有攔下來?」): 在這之前 AskUserQuestionhooks.json 裡出現 0 次,一支閘都沒掛。 F 組那兩支自走警察(self-drive-police / self-drive-judge)判準一樣, 但它們掛在「收工」那一刻——問題早就送到你眼前了,事後再問 AI「你查過了嗎」已經來不及。 這一組補的是時機,不是判準。

E. 每個對話一開始(SessionStart

閘名 對你意味著什麼 動作
session-start-recall.sh 對話一開始就自動把「全局現況」(Gitea 各 repo 的票、KBDB 的藏書地圖)推到 AI 眼前,不必等它自己想到要查。 📝 記錄(context 注入)
skill-deploy-drift-guard.sh 如果「全機真正在用的 skill」跟「repo 裡版控的正本」內容對不上,就在開場講出來——避免用著一份沒人知道已經跟正本分家的舊拷貝。 📝 記錄

F. AI 想結束這一輪、要收工的時候(Stop)

閘名 對你意味著什麼 動作
empty-handed-stop-guard.sh 這一輪 AI一個動作都沒做卻想停下來(等你回覆),就擋下並告訴它「你的命令就是完整授權,不用再等第二次確認」。 🛑 擋(至多攔一次)
worklist-guard.sh AI 自己列過的待辦清單裡還有沒做完的步驟,卻想收工寫報告,就擋下,逼它做完剩下的步驟。 🛑
factory-idle-guard.sh AI 該去派工卻沒派(工頭停工),就擋下要它交出「已經派工的憑證」,不是隨口說一句「我會催」就算數。過閘有四條路:現在就派工/把票號寫進那句話/寫一行 ⏸ 等:<在等什麼>/這一輪收尾在動作上。 2026-08-23inkstone/ISEP#30)修好「引用被當成主張」——貼原始碼、引用它自己的訊息、否認自己有下一步,都不再被咬。 🛑
browser-verify-guard.sh 這一輪 AI 宣稱「前端驗過了」,卻沒有真的用瀏覽器工具載入過,就擋下——curl 抓到 HTML 不算驗過。 🛑
self-drive-police.sh AI 想停下來問你「早就決定過的事」(用固定句型判斷,例如「要不要 X」「下一步做什麼」「這交給你」)就擋下,反問它查過 wiki/查過派工表了沒。 🛑
self-drive-judge.sh 跟上面同一件事,但改用小模型(haiku)判斷「換句話說」的請示句——防止 AI 只是把「要不要」改寫成「不確定是否符合期待」就閃過上一支閘。 🛑
delivery-police.sh AI 宣稱「這件事做完了」,卻看不到任何實測證據(畫面截圖、指令輸出、HTTP 狀態碼…)就擋下。 🛑
wiki-first-police.sh AI 做完事卻沒有把結論寫回 wiki 就想收工,就擋下——下次(或別的 AI)查 wiki 會查不到這次做過什麼。 🛑
unpushed-police.sh AI 改好的東西還留在本機、沒有真的推送出去給別人用,卻想收工,就擋下——「改對了但沒送到」跟沒改是一樣的。 🛑
claim-verify-police.sh Subagent 交回來的「我做完了」宣稱還沒被驗證過(對應的待驗檔案還在),你這邊卻想收工,就擋下。 🛑

G. Subagent 把工作交回來的時候(SubagentStop

閘名 對你意味著什麼 動作
subagent-claim-worksheet.sh Subagent 一交回工作,就自動把它宣稱做了什麼寫成一張「待驗清單」檔案——之後總管收工前,claim-verify-police.sh 會檢查這張清單有沒有被處理掉。 📝 記錄

這個時機還掛了 worklist-guard.shself-drive-police.shself-drive-judge.shdelivery-police.shwiki-first-police.shunpushed-police.sh,行為跟上面 F 組完全一樣,只是對象換成「subagent 交回來的這一輪」。詳見「重複掛載」一節。

H. 動作做完之後,純粹記一筆(PostToolUse,全部不擋)

閘名 對你意味著什麼 動作
kbdb-asked-stamp.sh AI 真的查過 KBDB 之後,留一個時間戳——給前面 history-first-guard.sh 判斷「這輪有沒有先查過」用。 📝 記錄
subagent-first-stamp.sh AI 真的派過工之後,留一個時間戳——給 subagent-first-guard.sh 判斷用。 📝 記錄
issue-status-autoflip.sh AI 一派工出去,就自動把對應的 Gitea 票改成「進行中(s/doing)」,不必等人手動改標籤。 📝 記錄(自動改票)
baton-handback-guard.sh 一條派工線做完了,就去看它那張票有沒有指派給人/標籤有沒有說它卡在哪/有沒有寫下一步,缺哪一格就當場說出來。你關心的是:不會再有票做完了卻沒人接手,躺在那裡沒人發現。 📝 記錄(提醒,不擋)

I. 你會撞到但跟「派工/收工」無關的一支(Edit·MultiEdit

閘名 對你意味著什麼 動作
history-first-guard.sh AI 要改一個舊檔案之前,先把這個檔案過去被改過幾次、被誰在什麼情況下改過的紀錄攤在它眼前,逼它回答「這是不是已經修過的老問題」再動手。 🛑

目前沒生效的 3 支(存在但沒掛進 hooks.json

檔名 為什麼沒掛
pre-write-guard.template.sh 官方留的空殼範本,預設不攔任何東西。要用要自己手填禁令清單、自己去掛。檔頭寫明「別誤以為裝了它就有保護」。
pre-write-guard.sh 同一個範本的另一份(看起來是填過一半的版本),同樣沒掛進 hooks.json
shadow-table-guard.sh 檔頭自己寫明「本檔目前是死的」——leo 2026-08-15 說過「提案封鎖方式,不是要你就去做」,所以先寫好、測過,但要不要真的掛上由你裁。

落差偵測(票上要求的「這件事本身值得被看到」)

方法:比對 hooks/*.sh 檔案清單 vs hooks.json 裡出現的檔名。

$ ls hooks/*.sh | xargs -n1 basename | sort > /tmp/fs_hooks.txt
$ grep -oE '[a-zA-Z0-9_-]+\.sh' hooks/hooks.json | sort -u > /tmp/registered_hooks.txt

# 有檔案、但 hooks.json 沒註冊到 → 上面「沒生效的 3 支」
$ comm -23 /tmp/fs_hooks.txt /tmp/registered_hooks.txt
pre-write-guard.sh
pre-write-guard.template.sh
shadow-table-guard.sh

# hooks.json 註冊了、但檔案不存在 → 空(目前沒有這種「指向空氣」的閘)
$ comm -13 /tmp/fs_hooks.txt /tmp/registered_hooks.txt
(無輸出)

這次順便抓到的另一個落差.claude-plugin/plugin.json 的說明文字寫「42 支機械閘(52 條註冊)」, README.md 也寫「42 支 hooks.json」「共 52 條註冊」——但實測是 43 支檔案、53 條註冊。 差 1 支、差 1 條,猜測是今天(2026-08-20)新增的 leo21c-write-guard.shrelease-tag-guard.sh ticket-api-bypass-guard.sh 這批(檔頭日期都是今天)加了之後,兩份文件的數字沒有跟著更新。 這兩個檔案本次刻意沒動(在你劃的紅線內:不准碰 .claude-plugin/),只在這裡把落差標出來給你看。


重複掛載(同一支閘在不只一個時機生效)

不是衝突,是同一支閘刻意守好幾個情境;列出來是因為你可能會納悶「怎麼同一句紅字出現在不同地方」:

閘名 掛了幾次 為什麼
kbdb-api-wall-guard.sh 3 次 下指令(Bash)、寫檔案(Write/Edit)、派工(Task)三種情境都可能違反 KBDB 規約,各掛一次
prod-write-guard.sh 2 次 一次守「下指令」,一次專門守「會直接寫進線上的 MCP 工具」(不是走終端機指令的那種)
arcrun-intent-guard.sh 2 次 一次守「所有寫檔案」,一次專門加強守「呼叫 Arcrun 部署/驗證工具」這個更精準的情境
micromanage-guard.sh / irreversible-dispatch-guard.sh / no-ticket-no-dispatch.sh 各 2 次 各自同時掛在 TaskAgent 兩個矩比對名稱上——這兩個名稱應該是同一種派工動作的新舊叫法,兩個都掛保證不漏接
worklist-guard.sh / self-drive-police.sh / self-drive-judge.sh / delivery-police.sh / wiki-first-police.sh / unpushed-police.sh 各 2 次 一次守「總管自己想收工」(Stop),一次守「subagent 交回工作」(SubagentStop)——同一套判準用在兩種角色身上

這裡有一件我看不出來是刻意還是遺留、需要人判斷micromanage-guard.shirreversible-dispatch-guard.shno-ticket-no-dispatch.sh 三支都同時掛在 TaskAgent 這兩個矩比對名稱上。如果這兩個名稱在目前版本的 Claude Code 裡指的是「同一種派工工具呼叫」, 那這是保險(兩個名字都接住,不怕哪天官方改名),沒問題;但如果其實只有一個名稱會真的觸發,另一個是舊名稱留下來沒清掉, 那就是「規則說是兩層防護,實際只有一層在動」。這個要靠實際觸發紀錄核對,我沒有把握單靠讀檔案判斷,標成 而不是硬下結論。


抽驗 5 支:一句話 vs 實際邏輯逐條對照

隨機抽了跨越不同層級/時機的 5 支,逐行核對過源碼(不是只讀檔頭):

  1. github-contact-guard.sh——表格寫「AI 想碰 GitHub 就先擋下,只有讀取自由」。 源碼核對:只擋 gh api/repo/issue/pr/... 子指令,以及 git pushgit remote add 指向 github.com(含用 remote 名稱反解出網址的情況); git clone/fetch/pull/ls-remotecurlgo get 一律放行。帶憑證的「實名讀」也放行但會留一筆紀錄。與表格描述一致。
  2. main-and-prod-push-guard.sh——表格寫「推 main 要總管戳記、prod 部署要你解鎖」。 源碼核對:git push 目標含 main/master 才擋,且要 /tmp/.main-push-ok 戳記綁對 repo 路徑、15 分鐘內、用過即丟才放行; wrangler deploy/publish/versions deploy 且指令裡看不出打的是 stage/youlin/geek6688 就擋,要 .github-armed 或 Gitea 票上核准碼才放行。與表格描述一致,且比表格寫得更細(例如 stage 與 geek6688 兩個白名單)。
  3. empty-handed-stop-guard.sh——表格寫「這輪零動作卻想停就擋,至多攔一次」。 源碼核對:讀 transcript 數這回合 tool_use 出現次數,0 次 → exit 2stop_hook_active 已為真(代表已經擋過一次)→ 直接放行,不會卡死。與表格描述一致。
  4. no-ticket-no-dispatch.sh——表格寫「派工單沒工單號,或票已關閉/不存在就擋」。 源碼核對:從 tool_input.prompt【工單】owner/repo#N,抓不到 → exit 2;抓到但打 Gitea API 查到 state=closed 或查不到(missing)→ exit 2; 查不到網路(token 拿不到、API 連不上)→ 放行(fail-open,避免網路抖動卡死工作)。與表格描述一致,且多了一個表格沒特別寫的細節:網路問題不擋。
  5. wiki-secret-scan.sh——表格寫「寫進 wiki 的內容有密碼/金鑰/身分證/信用卡特徵就擋」。 源碼核對:只在 file_path 命中 system-dev/wiki/* 時啟動;用 6 類 regex(密碼賦值、PEM 私鑰、雲端金鑰前綴、JWT、連線字串內嵌帳密、身分證、信用卡)逐條檢查要寫入的內容; 行尾標記 wiki-secret-ok 可豁免。與表格描述一致。

五支全部核對通過,沒有發現表格描述跟實際邏輯對不上的情況。


我看不懂、需要人看的地方

  • micromanage-guard.shirreversible-dispatch-guard.shno-ticket-no-dispatch.sh 的雙重矩比對(Task + Agent)是刻意保險還是舊名稱沒清掉——見上面「重複掛載」段落,我沒有把握單靠讀檔案判斷,需要看實際觸發紀錄或問總管。
  • stage-before-prod-guard.shmain-and-prod-push-guard.sh 的分工邊界main-and-prod-push-guard.sh 的檔頭明講自己是在「補 stage-before-prod-guard.sh 的破口」(那支只認 3 個關鍵字,抓不到 wrangler deploy),但兩支都還掛著、都還在管「prod 出貨」這件事。這是「新的補洞、舊的continua」還是「舊的該退休了」,這份盤點表不下判斷,留給你在下一步的分類會議裡定奪。

除了以上兩點,其餘 41 支的行為都能從檔頭與源碼直接讀出,沒有「猜」的部分。


這份表怎麼跟實況對帳(半年後怎麼發現漂移)

  1. 有沒有新閘沒被收進這張表:跑本文「落差偵測」段落的兩行 comm 指令,比對 hooks/*.sh 的檔名清單跟這張表列出的閘名清單(不是跟 hooks.json,那個只驗證有沒有註冊,驗不了有沒有寫進這張人話表)。
  2. 有沒有閘的行為跟這裡寫的不一樣了:抽幾支重新讀一次源碼,跟這裡的「一句話」對一遍——就是本文「抽驗 5 支」做的事,可以照同樣方法定期重做。
  3. .claude-plugin/plugin.jsonREADME.md 的數字:這兩處各自寫了一次「幾支、幾條註冊」,前面已經抓到一次對不上(42/52 vs 實際 43/53)。這兩個數字沒有機制保證跟著 hooks/ 目錄自動更新,是本表發現的第一個具體漂移案例。