feat(hooks): 未出貨警察 unreleased-police.sh —— 收工那刻抓「改了 repo 卻沒出貨」(inkstone/ISEP#68)

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
This commit is contained in:
2026-09-03 21:06:13 +00:00
parent 605f1fe5a6
commit 0024d697f9
5 changed files with 382 additions and 3 deletions
+22
View File
@@ -825,6 +825,28 @@ claude -p '請執行 git tag -a v9.9.9 -m test'
---
### A27 — 未出貨警察:改了 code 沒出貨要擋,只改 wiki/已出貨/先不出都不擋:13 條
```
bash scripts/test-unreleased-police.sh
```
**該看到**`13/13 通過`。全程在 `mktemp -d` 底下造假 repo 與假 transcript
不碰任何真 repo、不打網路、跑完自己清。
**它在守什麼**`inkstone/ISEP#68`leo 2026-08-27):「每個 repo 只要有改動就要出貨⋯⋯
這個 session 有改但要結束沒出貨就抓」。「改過」認的是**這個 session 期間新長出來的 commit**
committer-date 晚於 transcript 第一列的時間戳),「出貨」認的是**有沒有 release tag 收進 HEAD**
`git tag --contains HEAD`)——兩者同一個單位(commit),才數得出「最後 commit 與最新 release 差幾筆」。
**失敗**
- A 群「該擋」任一放行 ⇒ 假綠:改了 code 卻沒出貨溜過去了
- B 群「不該擋」任一被擋 ⇒ **誤攔**(比漏擋嚴重)——特別看「只改 wiki/docs」「已出貨」
「有 exempt marker」「release-holds.md 有它」四條,任何一條紅代表閘在懲罰謹慎
- C 群紅 ⇒ 訊息教的出路(寫 `.claude/release-holds.md` 一行)**跑不動**
被擋的人只會去找繞過去的方法(`mistakes.md` ISEP#112 的課)
- D 群紅 ⇒ 偵測根本不會亮,「全綠」其實是偵測壞了,不是真的乾淨
---
## B. 只有 leo 能跑的(雲端)
機器碰不到 claude.ai 的 Cloud environment 設定,這段一定要你動手。
+9 -3
View File
@@ -1,4 +1,4 @@
# 61 支閘,白話盤點表
# 62 支閘,白話盤點表
> 回應 `inkstone/InkStoneCo#40`:「如果加入了,我應該可以白話文看到 hooks 的內容?」
> 這份表就是那個「白話文」——不用點開任何 `.sh` 檔,一行看懂一支閘在管什麼。
@@ -7,14 +7,14 @@
## 一句話結論
`hooks/` 底下有 **61 個 `.sh` 檔**`hooks.json` 實際掛上 **84 條註冊**(同一支閘常被多種情境同時掛上);
`hooks/` 底下有 **62 個 `.sh` 檔**`hooks.json` 實際掛上 **85 條註冊**(同一支閘常被多種情境同時掛上);
其中 **3 支檔案存在但沒被掛上**(2 支是待人填的空範本、1 支是刻意留著沒開的止血帶,見下面「未生效」表)。
下面按「你會在什麼時候撞到它」分組,一支一行。
> 🔴 **這兩個數字上一版是錯的(2026-08-26 實際數過才發現)**:本頁原本寫「43 個檔、53 條註冊」,
> 而當時真實是 **45 個檔、55 條註冊**——中間有兩支閘進來時沒有回頭改這裡。
> 現在的寫法是實際數出來的:
> `ls hooks/*.sh | wc -l` 61`grep -c '"command":' hooks/hooks.json` 84
> `ls hooks/*.sh | wc -l` 62`grep -c '"command":' hooks/hooks.json` 85
> ⚠️ **冒號不能省**`grep -c '"command"'`(沒冒號)會連 `"type": "command"` 一起數到,回 **120**。
> 本頁 2026-08-27 之前寫的是沒冒號那版——**照著它跑會拿到一個跟本頁不符的數字**。
> **一份會偷偷過期的盤點表,跟沒有盤點表差不多**——見本頁最後「怎麼跟實況對帳」那段。
@@ -98,6 +98,11 @@
> 另外描述欄的「38 支腳本」是把 `kbdb-live-exam/``lib/``patches/` 三個**目錄**也數進去了;
> 用本頁寫死的那道指令(只數檔案)實數是 **39**。
> 📌 **`inkstone/ISEP#68`2026-09-03)進來一支**`unreleased-police.sh`F 組,Stop)。
> 61→**62** 支、84→**85** 條,兩個數字都是**在自己的樹上當場數出來的**(指令同上),
> 不是拿上一版加一。它只掛 `Stop` 一個事件(+1 支、+1 條)——刻意不掛 `SubagentStop`
> subagent 交回分支、release 由總管出,在 subagent 收工逼它出貨純屬誤攔。
---
## 怎麼讀這張表
@@ -226,6 +231,7 @@
| `delivery-police.sh` | AI 宣稱「這件事做完了」,卻看不到任何實測證據(畫面截圖、指令輸出、HTTP 狀態碼…)就擋下。 | 🛑 擋 |
| `wiki-first-police.sh` | AI 做完事卻沒有把結論寫回 wiki 就想收工,就擋下——下次(或別的 AI)查 wiki 會查不到這次做過什麼。 | 🛑 擋 |
| `unpushed-police.sh` | AI 改好的東西還留在本機、沒有真的推送出去給別人用,卻想收工,就擋下——「改對了但沒送到」跟沒改是一樣的。 | 🛑 擋 |
| `unreleased-police.sh` | 這個 session 改過的 repo,收工那一刻還沒**出貨**(沒有一個 release tag 收進 HEAD),就擋一次,並點名是哪個 repo、最後 commit 與最新 release 差幾筆。「改過」認的是**這個 session 期間新長出來的 commit**(不是工作區髒、不是 push),跟「出貨」用同一個單位(commit)才數得出差多少。**只在收工問一次,不是每次 commit 都吼**(leo 明說可以累積)。三種不擋:①只改了 wiki/docs(知識就地被讀,不需出版本)②repo 根目錄放了 `.isep-release-exempt`(產物/模板 repo 自己帶理由排除,不是中央手列清單)③在 `.claude/release-holds.md` 寫一行「這次先不出,因為 X」(放行且留痕,出貨後刪掉那行)。只掛 `Stop` 不掛 `SubagentStop`——subagent 交回分支、release 由總管出,在 subagent 收工逼它出貨純屬誤攔。 | 🛑 擋(至多攔一次) |
> 🔴 **`claim-verify-police.sh` 已於 `inkstone/ISEP#60` 移除**(連同下面原本的 G 組 `subagent-claim-worksheet.sh`)。
> 這一對是自造的「待驗單」機制(一張沒有狀態、沒有持有人的 markdown),違反 D58