兩支閘改看「指令要動的那個 repo」,不看 hook 自己的環境/cwd(inkstone/ISEP#109 → comment 6629)

① line-needs-own-worktree.sh:出路 `WORKTREE_OK=1 git -C … checkout …` 改認指令字串裡的字面前綴
   (舊版讀 hook 自己的環境變數,PreToolUse hook 跟指令不是同一個行程,那行永遠走不通)。
   判準是位置不是字:前綴必須掛在會移動 HEAD 的那條 git 指令上。
   lib/checkout_target_dir.py 加 `--escape NAME=1`;push_target_dir._classify 在 strip_env 時
   把前綴留在 verb 事件裡(find_push_target 行為不變,主線閘 10+19 條照綠)。
② github-contact-guard.sh:remote 名在「這條指令實際會推的那個 repo」裡解(沿用 lib/push_target_dir.py
   解 cd 鏈/-C/子殼),解不出來才退回 cwd。判準仍是 remote URL 主機,不是「有 -C 就放行」。

測試:A24 59→69(E 群把閘印的那一行原樣餵回去;舊閘 3 條紅)、
      A39 14→26(payload 帶 cwd、cwd≠目標 repo;舊閘 7 條紅=4 誤攔+3 漏擋)。
盤點表:61 支/85 條,改判準不加閘,當場數的。
版本:待總管定版。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0178ef1fGw3XeZtpN7LaZrm4
This commit is contained in:
isep-hand
2026-09-07 06:31:45 +00:00
parent bcf7c05a7b
commit 49a7145e70
9 changed files with 242 additions and 28 deletions
+33 -2
View File
@@ -624,11 +624,18 @@ bash hooks/tests/parallel-lines-cap-guard.test.sh
`PostToolUse:Agent` 在**送出後 5 秒**就觸發(不是收工);
subagent **不是獨立行程**`ps` 只有一個 claude-code 行程,所以數行程數不到線)。
### A24 — 一條線要有自己的工作目錄:59 條
### A24 — 一條線要有自己的工作目錄:69 條
```
bash hooks/tests/line-needs-own-worktree.test.sh
```
**該看到**`59/59 通過`。離線,每個案例自己開**兩顆**真的 git repo +一份真的 linked worktree。
**該看到**`69/69 通過`。離線,每個案例自己開**兩顆**真的 git repo +一份真的 linked worktree。
**E 群在守什麼**comment 65872026-09-07 補):**閘印出來的那條出路要真的走得通。**
閘訊息教的是 `WORKTREE_OK=1 git -C … checkout …`,舊版讀的卻是 hook 自己的環境變數——PreToolUse hook
跟指令不是同一個行程,指令前綴的賦值到不了它 ⇒ 總管三次照貼三次被擋。E 群的測資是**閘自己印出來的那一行**
((61) 先擋一次、(62) 抽出那行、(63) 原樣餵回去要放行);(64)–(67) 釘住判準是**位置不是字**:
`echo WORKTREE_OK=1; git checkout`、前綴掛在別的指令上、值不是 1、寫在 commit 訊息裡,四種都照擋。
修之前拿新測試跑舊閘:(60)(63)(68) 三條紅,其餘全綠——紅的那三條就是真的改到的。
**D 群在守什麼**comment 53982026-08-29 補):不只要擋對,還要**說得出是哪一個 repo**。
舊版一律拿 payload 的 `cwd` 當答案,於是 `cd <別的 repo> && git checkout` 擋是擋對了,
@@ -646,6 +653,8 @@ D 群同時釘住反面:認不出來(`cd $VAR`、`cd -`、cd 到不是 repo
擋掉它們等於線動不了工
- ① 紅 ⇒ 連總管在自己的目錄裡切分支都被擋。那不是嚴格,那是把共用目錄的主人趕出去
- ㉞㉟ 紅 ⇒ `prune` 做錯了。㉞是沒清掉說謊的登記;**㉟是把還在的 worktree 也弄掉了,那是災難**
- (63) 紅 ⇒ 閘印的出路又走不通了——被擋的人不會停下來修閘,他會去找繞過去的方法(6587 當天就是)
- (64)–(67) 任一紅 ⇒ 出路退化成關鍵字:印個字就能過,閘等於沒有
### A25 — `sdd-guard` 退役了,而且退乾淨了:8 條
```
@@ -911,6 +920,26 @@ bash hooks/tests/isep-presence-beacon.test.sh # ㉗–㉝ 那七條
**真實跑(2026-09-07**:信標不帶 fixture 打真 Gitea(匿名)→ 安靜(v0.24.0 tagrelease 都在),
留下 `.isep-chain-ok-v0.24.0` 快取;`release-check` 在本分支 → `v0.24.0 ✅✅✅ ←`、三個裁定不補的版本標 ⏭、exit 0。
### A39 — D20 閘判「push 到哪」看的是指令實際會推的那個 repo:26 條
```
bash scripts/test-github-contact-guard.sh
```
**該看到**`26/26 通過`。離線;後 12 條自己開兩顆 git repo`shell` 的 origingithub.com、
`body` 的 origin 是 Gitea 的形狀),payload 帶 `cwd`,跟 hook 真的會收到的一樣。
**它在守什麼**`inkstone/ISEP#109` → comment 66292026-09-07 實撞):雲端薄殼的 cwd 是 GitHub 那份,
總管站在那裡 `git -C <InkStoneCo> push origin x`InkStoneCo 的 originGitea),舊版拿 **hook 收到的 cwd**
`origin` ⇒ 判成「指向 GitHub」擋下,`-C` 看都沒看;arcrun-hand 同日在 `Arcrun#176` comment 6614 撞到同一支。
現在 remote 名在**這條指令實際會推的那個 repo** 裡解(沿用 `hooks/lib/push_target_dir.py`cd 鏈、`-C`
子殼不外洩,推 main 那道閘 08-23 就為同一個病接的它);判準仍是 remote 解出來的 URL 主機,**不是「有 -C 就放行」**。
修之前拿新測試跑舊閘:**7 條紅**——4 條是誤攔(推 Gitea 被擋),**3 條是漏擋**(站在 Gitea 那份 `-C` 到薄殼推 GitHub,舊版放行)。
**失敗**
- 「不該擋」6 條任一紅 ⇒ **誤攔**:雲端推 Gitea 分支又得改用完整網址繞路,久了整道閘沒人當真
- 「該擋」5 條任一紅 ⇒ D20 有洞:`-C``cd` 指到 GitHub 那份的 push 沒被攔
- `(cd body && true); git push` 那條紅 ⇒ 子殼的 cd 外洩了(08-11 穿透的形狀)
- 前 14 條紅 ⇒ 舊有判準(heredoc/引號裡的散文/讀取放行)被改壞
### A37 — 雲端第一則 prompt 的主線就是現在這條,不是兩週前的舊線:31 條
```
bash hooks/tests/mainline-gitea-fallback.test.sh
@@ -1214,6 +1243,8 @@ B2(信標那行)/B3(setup 輸出)/B4(閘的訊息)三個畫面
| **A12 留言身份欄(兩道門)** | 總管 | ✅ 11/112026-08-27 |
| **A23 同時跑的線有上限** | isep-guard | ✅ 33/332026-08-29inkstone/ISEP#109 |
| **A24 一條線要有自己的工作目錄** | isep-guard | ✅ 35/352026-08-29inkstone/ISEP#109→5391 |
| **A24 出路那一行原樣餵回去走得通** | isep-hand | ✅ 69/692026-09-07inkstone/ISEP#109→6587)+真路徑實跑(薄殼 cwd、`-C ISEP`:擋→印的那行→放行)+舊閘實跑 3 條紅 |
| **A39 D20 閘看 push 實際會推的 repo** | isep-hand | ✅ 26/262026-09-07inkstone/ISEP#109→6629)+真路徑實跑(薄殼 cwd:`-C InkStoneCo` 放行、裸 `git push origin` 擋)+舊閘實跑 7 條紅(4 誤攔+3 漏擋) |
| **A26 雲端憑證清單這台拿得到** | isep-hand | ✅ 11/112026-09-01inkstone/ISEP#115→5526554155425577)+兩條「該紅」方向實跑 |
| **A16 放行的門真的打得開** | 總管 | ✅ 17/172026-08-28inkstone/ISEP#90 |
| **A17 未推警察不誤攔雲端分支** | 總管 | ✅ 10/102026-08-28inkstone/ISEP#90 |
+8 -2
View File
@@ -38,6 +38,12 @@
> **61 支、85 條、60 支腳本是在這棵樹上當場數出來的**`ls hooks/*.sh | wc -l`61、
> `grep -c '"command":' hooks/hooks.json`85、`ls -p scripts | grep -v / | wc -l`60)。
> 📌 **`inkstone/ISEP#109` → comment 66292026-09-07)+0 支、+0 條**:兩支閘改判準不加閘——
> `line-needs-own-worktree.sh` 的出路改認指令字串裡的 `WORKTREE_OK=1` 前綴(舊版讀 hook 自己的環境,
> 出路永遠走不通);`github-contact-guard.sh` 判 push 目標改在「這條指令實際會推的那個 repo」裡解 remote
> (舊版在 hook 的 cwd 解,薄殼站 GitHub、推 Gitea 會誤擋,反向會漏)。
> 數字是改完當場數的:`ls hooks/*.sh | wc -l`61、`grep -c '"command":' hooks/hooks.json`85。
> 📌 **`inkstone/ISEP#130`2026-09-07)+0 支、+1 條**SessionStart 多掛一條
> `scripts/settings-allow-sync --quiet`(不是閘,是腳本:把 ISEP 的權限白名單寫進這台的
> `~/.claude/settings.json`,雲端才不會每個正門工具都被分類器擋)。
@@ -129,7 +135,7 @@
| 閘名 | 對你意味著什麼 | 動作 |
|---|---|---|
| `github-contact-guard.sh` | AI 想碰 GitHub`git push``gh api` 等)就先擋下——兩個帳號曾經因為機器人式高頻寫入被永久 flag 過。**只有讀取(clone/fetch)自由,寫入一律要你在終端機跑 `github-arm.sh` 解鎖**。 | 🛑 擋 |
| `github-contact-guard.sh` | AI 想碰 GitHub`git push``gh api` 等)就先擋下——兩個帳號曾經因為機器人式高頻寫入被永久 flag 過。**只有讀取(clone/fetch)自由,寫入一律要你在終端機跑 `github-arm.sh` 解鎖**。判「這次 push 到哪」看的是**這條指令實際會推的那個 repo**(含 `cd …&&``git -C …`、cwd 在子目錄),不是 hook 站的 cwd——雲端薄殼站在 GitHub 那份、推的是 Gitea 那份時不會誤擋,反過來也不會漏(`inkstone/ISEP#109`→6629)。 | 🛑 擋 |
| `kbdb-api-wall-guard.sh` | AI 想繞過 KBDB 的 API、直接對資料庫下 SQL 或建新表就擋下——KBDB 的鐵律是「只能用 API、永遠只有三張表」。 | 🛑 擋 |
| `stage-before-prod-guard.sh` | AI 想動「出貨鏈」相關指令(含 `arcrun-rag-bundles``github-arm``publish-github` 字樣)卻沒先在 stage 環境驗過就擋下。 | 🛑 擋 |
| `main-and-prod-push-guard.sh` | 兩層確認:① subagent 想推 Gitea `main` 分支 → 一定要交回總管,總管確認過才放行;② 任何 `wrangler deploy` 打正式環境 → 一定要你親手解鎖。 | 🛑 擋 |
@@ -143,7 +149,7 @@
| `reply-identity-guard.sh` | 直接打 Gitea API 貼留言到票上時,內文第一行沒有 `【身份】``總管``subagent``leo`)就擋下——票上多條線並行時,看不出哪一則是誰寫的。純讀取、走 `scripts/ticket` 正門、指令裡加 `reply-identity-ok` 都放行。 | 🛑 擋 |
| `comment-carries-task-guard.sh` | AI 要在票裡留一則「等某某上線才驗得了」這種**還沒做完的事**,卻沒有把它開成一張子票時,擋一次。留言沒人會回頭讀,票的 open/closed 撈一次就看得到。**擋下來時印的是一整塊可以直接貼著跑的指令**(腳本用絕對路徑、母票已經填好、內文草稿附在同一塊裡),那塊字是跟 `scripts/ticket usage subtask` 現要的,不是閘自己抄的一份(`inkstone/ISEP#112`)。 | 🛑 擋(同一輪只擋一次) |
| `search-is-not-proof-guard.sh` | AI 用知識庫的**搜尋**找到一筆東西,然後把那筆東西的原始編號直接貼進票裡或派工單裡,當成「某個功能壞了」的證據——而它從沒用正規的檢索路徑去查過那筆——擋一次。你關心的是:不會再有人拿錯的方法量出一個錯的結論,然後退回一張其實已經做完的票。 | 🛑 擋(同一輪只擋一次) |
| `line-needs-own-worktree.sh` | 一條派工線想在**大家共用的那份工作目錄**裡切分支(`git checkout``git switch`)就擋下,叫它先開一份自己的 worktree。你關心的是:不會再有人交回工作時把某個 repo 留在自己的分支上,而下一個人在同一個目錄跑出貨、讀到的是別人留下的 HEAD。**總管在自己的目錄裡切分支不管**(那是他的目錄);已經在自己 worktree 裡的切換全部放行;還原檔案(`git checkout -- 檔`)不管。擋下來的同時會順手把「指向空氣」的 worktree 登記清掉。 | 🛑 擋 |
| `line-needs-own-worktree.sh` | 一條派工線想在**大家共用的那份工作目錄**裡切分支(`git checkout``git switch`)就擋下,叫它先開一份自己的 worktree。你關心的是:不會再有人交回工作時把某個 repo 留在自己的分支上,而下一個人在同一個目錄跑出貨、讀到的是別人留下的 HEAD。**總管在自己的目錄裡切分支不管**(那是他的目錄);已經在自己 worktree 裡的切換全部放行;還原檔案(`git checkout -- 檔`)不管。擋下來的同時會順手把「指向空氣」的 worktree 登記清掉。出路是閘訊息印的那一行 `WORKTREE_OK=1 git -C … checkout …`——認的是**指令字串裡**的這個前綴,原樣貼回去就過(`inkstone/ISEP#109`→6587:舊版讀 hook 自己的環境變數,那行在 Claude Code 底下永遠走不通)。 | 🛑 擋 |
| `diagnosis-evidence-guard.sh` | 你想把一段有份量的東西寫上票,而這個 session **沒派人去查過那張票**、那段話裡也**沒有任何走得過去的出處**(檔案:行號/commit/comment 號/票號/網址/貼出來的實測輸出)就擋一次——未經調查的診斷寫在票上會長得像事實,工人會照著它去驗證而不是去查。派過人查、或把出處帶上,都放行;一句短回覆不管。 | 🛑 擋 |
## B. AI 想寫檔案/改程式碼的當下(PreToolUse / Write·Edit·MultiEdit