身為總管,我要這道閘擋的是「宣告」而不是「提到宣告」,我才不會一舉報 bug 就被它擋下 #54

Merged
claude-code merged 2 commits from fix/factory-idle-guard-blocks-actions-not-mentions into main 2026-08-23 09:58:42 +00:00
Member

【工單】inkstone/ISEP#30 —— 讓閘擋對東西
(雲端驗收 2026-08-23 實撞,脈絡在 inkstone/InkStoneCo#57 comment 3970 之後那輪)

病灶:這道閘在懲罰誠實與謹慎

雲端驗收連續三次被 factory-idle-guard.sh 擋下,三次都不是宣告意圖:

# 那句話 它其實在做什麼
「那句是等你,不是我的下一步」 否認自己有下一步
「這不是『下一步我要做 X』」 引用閘自己的訊息來說明沒在做
貼出閘自己的正則原始碼 舉報這個 bug,code block 裡的引用

而閘的訊息教人走「選項③:改寫那句話,說明它在等什麼」,
程式碼裡沒有任何一條分支對應它——放行只有「有 Agent/Task」與「附近有 #票號」兩條。
⇒ 唯一走得通的路是「不寫那三個字」,而那正是同一則訊息 🔴 明文禁止的動作。

六個真兇,沒有一個是「例外沒列夠」

(a) DECL 會匹配裸的「下一步」三個字。
下一步(我|就是|是)?[::]?\s*(?!不是宣告) 每一節都可選 ⇒ 整條退化成「文中出現『下一步』」
只要提到這道閘在講什麼,就被這道閘咬
收緊:每一條 alternative 都必須接到一個動作動詞才算命中。方向與「加例外清單」相反。

(b) 「引用」被當成「主張」。 只剝 > 與長「」,不認 code fence 與行內 code。
→ 引用性標記蓋成哨兵(不是刪掉):內層宣告消失、外層句構留著。
  刪掉正是 2026-08-17 漏掉「回『規劃』我就派人」的原因 ⇒ 誤攔與漏攔一起修好。

(c) 看錯回合收尾。blocks_text[-1],但那則文字後面可能還有 tool_use ⇒ 宣告其實已兌現。
→ 只看最後一個動作之後的文字;收尾在動作上就不觸發。

(d) 訊息承諾的出路,程式碼裡沒有。
→ 出路③ 給一個真的存在的機械形式 ⏸ 等:<在等什麼>
  白名單標記(要刻意寫、不會誤觸)而且留痕 ⇒ 滿足「不准把那句話刪掉再送一次」。


🔴 以下兩個,是被交付警察擋回來、改用真 transcript 重驗才找到的

第一顆 commit 只驗了我自己造的假 transcript。
改用本機一條 2068 行的真 session(切出 26 個真實回合終止點)重跑,當場多抓到兩個:

(e) 主詞是「你」的下一步,被當成我的宣告。我原本一向都沒列到的第四類誤攔
舊閘在 26 個真實回合裡擋了 2 次,兩次咬的都是我在交代 leo 該做什麼

turn-003  「**下一步還是那一個動作**:**你**把 feat/isep-dynamic-install 併進 main」
turn-014  「## **你下一步**(兩招,先便宜的)」

主詞檢查(誰要動手),只掛在「下一步」那條 alternative 上。
  用 finditer 逐個檢查前 8 字有沒有第二人稱,蓋得到「你的下一步」這種
  單字 lookbehind 蓋不到的變體。「你點頭我就做」主詞本來就是我,不受影響。

(f) 票號放寬把閘變鈍了。
我把 #票號 從「宣告句附近 ±N 字」放寬成「整段收尾文字」(因為票號常寫在行內 code 裡,
剝掉就找不到)。真數據打臉:26 個真實回合裡 20 個是靠「文中某處剛好有票號」放行的
——而報告幾乎一定會提到票號 ⇒ 這道閘在實務上等於不會響

→ 改成等長替換(蓋成同樣長度的哨兵而非刪除)⇒ 位移可以原封不動對回原文
  ⇒ locality 與「行內 code 裡的票號也算數」兩件同時成立assert len(text)==len(raw))。

放行理由分佈(同一批 26 個真實回合):

放寬版:  20 ticket-referenced /  3 no-declaration / 3 dispatched   ← 靠漏洞過關
收緊後:  21 no-declaration    /  3 dispatched     / 2 ticket-referenced   ← 真的沒宣告

守封路哲學

leo 2026-08-17:「紅線寫得越細,命中關鍵字的機率越高 ⇒ 那些閘在懲罰謹慎。
本 PR 沒有加任何關鍵字例外清單——六個修法全是「收緊命中條件」「改變量測對象」
或「主詞/位移這類結構性判斷」。例外清單會越加越長、越長越誤攔,那是同一個病的下一輪。

逐向實測輸出(33 向,先紅後綠)

bash hooks/tests/factory-idle-guard.test.sh                      # 新版 → 33/33
git show main:hooks/factory-idle-guard.sh > /tmp/old.sh
bash hooks/tests/factory-idle-guard.test.sh /tmp/old.sh          # 舊版 → 21/33
                                                     舊版              新版
── A 群:該放行(雲端三次誤攔的原形狀 + 真 transcript 抓到的第四類)──────
① 否認自己有下一步(『那句是等你,不是我的下一步』)  ❌ exit=2        ✅ exit=0
① 長版:連同被否認的原句一起引用                      ❌ exit=2        ✅ exit=0
② 引用閘自己的訊息來說明沒在做                        ❌ exit=2        ✅ exit=0
③ code block 裡貼閘自己的正則原始碼(舉報 bug)       ❌ exit=2        ✅ exit=0
③ 行內 code 貼同一段正則                              ❌ exit=2        ✅ exit=0
引用 leo 的話(blockquote)含宣告字樣                  ✅               ✅
User Story 票名(身為…我要…我才…)不是宣告            ✅               ✅
④ 主詞是 leo 的下一步(『你下一步去把 X 併進 main』)  ❌ exit=2        ✅ exit=0
④ 同上,交代對方要跑的動作                            ✅               ✅
④ 『你的下一步』(單字 lookbehind 蓋不到的變體)       ❌ exit=2        ✅ exit=0
④ 的反面:主詞換回我自己 ⇒ 照樣擋                     ✅ exit=2        ✅ exit=2

── A 群:既有放行路徑,不准變鬆(迴歸)─────────────────────────────────
純對話回合,沒有任何宣告                               ✅               ✅
宣告了,但這個回合真的呼叫了 Agent(=按下 trigger)   ✅               ✅
宣告了,但這個回合真的呼叫了 Task                      ✅               ✅
宣告句帶票號指涉(已經在別人手上)                     ✅               ✅
宣告之後真的動手了(文字後面還有工具呼叫)             ❌ exit=2        ✅ exit=0

── B 群:該擋(真的宣告了未來動作,且沒按下任何 trigger)───────────────
『下一步我去把 X 補上』,無派工無票號                  ✅ exit=2        ✅ exit=2
『接下來我會去補測試』                                 ✅ exit=2        ✅ exit=2
把請示寫成條件句(2026-08-17 leo 實撞那族)            ✅ exit=2        ✅ exit=2
『我等你確認』——空手停在等人身上                      ✅ exit=2        ✅ exit=2
『我現在就去跑一遍』                                   ✅ exit=2        ✅ exit=2
『確認後我才動』                                       ✅ exit=2        ✅ exit=2
票號寫在行內 code 裡也算數(等長替換才量得到)         ✅ exit=0        ✅ exit=0
票號離宣告句太遠(別處提到而已)⇒ 不算,照樣擋        ❌ exit=0        ✅ exit=2

── C 群:訊息承諾的每一條出路,都要真的走得通 ─────────────────────────
出路①:這個回合直接呼叫 Agent 派出去                   ✅ exit=0        ✅ exit=0
出路②:把票號寫進那句話                               ✅ exit=0        ✅ exit=0
出路③:用 ⏸ 等: 說明它在等什麼                        ❌ exit=2        ✅ exit=0
出路③:宣告 + 標記同時存在也放行                      ❌ exit=2        ✅ exit=0
出路④:這個回合收尾在動作上                            ❌ exit=2        ✅ exit=0

── 出路③ 的反面:沒寫標記就照樣擋(不准靠刪掉那句話矇混)──────────────
同一句話拿掉 ⏸ 等: 標記 ⇒ 擋                          ✅ exit=2        ✅ exit=2

── 壞輸入不准亂擋(fail-open)─────────────────────────────────────────
{} / 不存在的 transcript / not-json                   ✅ exit=0        ✅ exit=0

────── 舊版 通過 21 / 失敗 12    新版 通過 33 / 失敗 0 (共 33 向)

真實資料上的實測(不是我造的樣本)

本機一條 2068 行真 session,切出 26 個真實回合終止點,兩支閘各跑一次:

26 個真實回合終止點:舊閘擋 2 次(兩次都是誤攔,見 (e))→ 新閘擋 0 次
新閘放行理由:21 OK:no-declaration / 3 OK:dispatched / 2 OK:ticket-referenced

真實 schema 也對過了(assistant.message.content[]usertool_result
、還有 attachmentsystembridge-session 等 411+300 列雜訊)——
造的假 transcript 與真檔同構,27→33 向不是假綠。

從遠端 clone 出來複驗(不是從我的工作目錄)

PR head commit:e3d05df
版號:0.3.8
clone 出來的那份跑測試        → 33/33
同一份測試 × main 上的舊閘    → 21/33
真 transcript 26 回合 × 新閘  → 擋 0 次

兄弟閘沒有退步

main-and-prod-push-guard            通過 8  / 失敗 0
main-and-prod-push-guard-cross-repo 通過 17 / 失敗 0
prod-write-guard                    通過 29 / 失敗 0
stage-before-prod-guard             通過 16 / 失敗 0
sdd-guard                           通過 8  / 失敗 0

順手一起處理的

  • 版號.claude-plugin/plugin.json 0.3.6 → 0.3.8(兩顆 commit 各跟一版)
  • 留痕:擋下與放行都寫 .claude/hooks/factory-idle-guard.log
    (InkStoneCo#48:只記「擋下」的話分母未知)。
    上面 (f) 那份判定分佈就是靠這個 log 量出來的——留痕不是裝飾,它是這次的量測工具。
  • stderr 乾淨:log 目錄不在(測試環境/別的 repo)時安靜跳過
  • 白話盤點docs/hooks-inventory.md 那一列補上「過閘的四條路」

刻意的取捨(寫進檔頭了,下一輪不要當成 bug 又改回去)

  1. 「下一步」後面不吃逗號(下一步,我去補 X 會漏)。
    吃了逗號,「不是我的下一步,我剛剛已經做完了」就會被咬(過去式當未來式)。
  2. 「身為…我要…我才…」(本 repo 的 User Story 票名格式)整段蓋掉——
    票名是被命名的物件,不是我對下一步的主張。
  3. 不設 stop_hook_active 逃生門(與 empty-handed-stop-guard 不同)。
    leo 2026-08-16:「提醒可以忽略,deny 不能。」
    之所以敢這樣,是因為出路③現在真的存在 ⇒ 不可能把人鎖死在無解迴圈裡。

🔴 交回總管時要知道的一件事(我沒動它)

InkStoneCo/.claude/hooks/factory-idle-guard.sh 與本 PR 的舊版逐位元組相同
diff -q 實測)。這個 PR 併了,本機那份還是舊的、還是會誤攔。
InkStoneCo#72(「身為總管,我要本機的閘跟 plugin 同一份,我才不會被舊版誤攔」)
講的就是這件事——但同步屬於 InkStoneCo 那個 repo、要推它的 main,
依兩層手動確認閘不是 subagent 能做的。

📌 另外:KBDB 對這支閘的開發史 0 命中(語義搜尋最高分 0.48,全是 pms/undici 噪音)。
這筆「查了沒有」本身就是餵食清單——ISEP 的閘開發史還沒進 KBDB。

🤖 Generated with Claude Code

【工單】inkstone/ISEP#30 —— 讓閘擋對東西 (雲端驗收 2026-08-23 實撞,脈絡在 inkstone/InkStoneCo#57 comment 3970 之後那輪) ## 病灶:這道閘在懲罰誠實與謹慎 雲端驗收連續三次被 `factory-idle-guard.sh` 擋下,三次都不是宣告意圖: | # | 那句話 | 它其實在做什麼 | |---|---|---| | ① | 「那句是等你,不是我的下一步」 | **否認**自己有下一步 | | ② | 「這不是『下一步我要做 X』」 | **引用**閘自己的訊息來說明沒在做 | | ③ | 貼出閘自己的正則原始碼 | **舉報**這個 bug,code block 裡的引用 | 而閘的訊息教人走「選項③:改寫那句話,說明它在等什麼」, **程式碼裡沒有任何一條分支對應它**——放行只有「有 Agent/Task」與「附近有 #票號」兩條。 ⇒ 唯一走得通的路是「不寫那三個字」,而那正是同一則訊息 🔴 明文禁止的動作。 ## 六個真兇,沒有一個是「例外沒列夠」 **(a) DECL 會匹配裸的「下一步」三個字。** `下一步(我|就是|是)?[::]?\s*(?!不是宣告)` 每一節都可選 ⇒ 整條退化成「文中出現『下一步』」 ⇒ **只要提到這道閘在講什麼,就被這道閘咬**。 → **收緊**:每一條 alternative 都必須接到一個動作動詞才算命中。方向與「加例外清單」相反。 **(b) 「引用」被當成「主張」。** 只剝 `>` 與長「」,不認 code fence 與行內 code。 → 引用性標記蓋成**哨兵**(不是刪掉):內層宣告消失、外層句構留著。   刪掉正是 2026-08-17 漏掉「回『規劃』我就派人」的原因 ⇒ 誤攔與漏攔一起修好。 **(c) 看錯回合收尾。** 取 `blocks_text[-1]`,但那則文字後面可能還有 tool_use ⇒ 宣告其實已兌現。 → 只看**最後一個動作之後**的文字;收尾在動作上就不觸發。 **(d) 訊息承諾的出路,程式碼裡沒有。** → 出路③ 給一個真的存在的機械形式 `⏸ 等:<在等什麼>`。   **白名單標記**(要刻意寫、不會誤觸)而且**留痕** ⇒ 滿足「不准把那句話刪掉再送一次」。 --- ### 🔴 以下兩個,是被交付警察擋回來、改用**真 transcript** 重驗才找到的 第一顆 commit 只驗了我自己造的假 transcript。 改用本機一條 **2068 行的真 session**(切出 **26 個真實回合終止點**)重跑,當場多抓到兩個: **(e) 主詞是「你」的下一步,被當成我的宣告。**(**我原本一向都沒列到的第四類誤攔**) 舊閘在 26 個真實回合裡擋了 2 次,**兩次咬的都是我在交代 leo 該做什麼**: ``` turn-003 「**下一步還是那一個動作**:**你**把 feat/isep-dynamic-install 併進 main」 turn-014 「## **你下一步**(兩招,先便宜的)」 ``` → **主詞檢查**(誰要動手),只掛在「下一步」那條 alternative 上。   用 `finditer` 逐個檢查前 8 字有沒有第二人稱,蓋得到「**你的**下一步」這種   單字 lookbehind 蓋不到的變體。「你點頭我就做」主詞本來就是我,不受影響。 **(f) 票號放寬把閘變鈍了。** 我把 `#票號` 從「宣告句附近 ±N 字」放寬成「整段收尾文字」(因為票號常寫在行內 code 裡, 剝掉就找不到)。**真數據打臉**:26 個真實回合裡 **20 個是靠「文中某處剛好有票號」放行的** ——而報告幾乎一定會提到票號 ⇒ **這道閘在實務上等於不會響**。 → 改成**等長**替換(蓋成同樣長度的哨兵而非刪除)⇒ 位移可以原封不動對回原文   ⇒ **locality 與「行內 code 裡的票號也算數」兩件同時成立**(`assert len(text)==len(raw)`)。 放行理由分佈(同一批 26 個真實回合): ``` 放寬版: 20 ticket-referenced / 3 no-declaration / 3 dispatched ← 靠漏洞過關 收緊後: 21 no-declaration / 3 dispatched / 2 ticket-referenced ← 真的沒宣告 ``` ## 守封路哲學 leo 2026-08-17:「**紅線寫得越細,命中關鍵字的機率越高 ⇒ 那些閘在懲罰謹慎。**」 本 PR **沒有加任何關鍵字例外清單**——六個修法全是「收緊命中條件」「改變量測對象」 或「主詞/位移這類結構性判斷」。例外清單會越加越長、越長越誤攔,那是同一個病的下一輪。 ## 逐向實測輸出(33 向,先紅後綠) ``` bash hooks/tests/factory-idle-guard.test.sh # 新版 → 33/33 git show main:hooks/factory-idle-guard.sh > /tmp/old.sh bash hooks/tests/factory-idle-guard.test.sh /tmp/old.sh # 舊版 → 21/33 ``` ``` 舊版 新版 ── A 群:該放行(雲端三次誤攔的原形狀 + 真 transcript 抓到的第四類)────── ① 否認自己有下一步(『那句是等你,不是我的下一步』) ❌ exit=2 ✅ exit=0 ① 長版:連同被否認的原句一起引用 ❌ exit=2 ✅ exit=0 ② 引用閘自己的訊息來說明沒在做 ❌ exit=2 ✅ exit=0 ③ code block 裡貼閘自己的正則原始碼(舉報 bug) ❌ exit=2 ✅ exit=0 ③ 行內 code 貼同一段正則 ❌ exit=2 ✅ exit=0 引用 leo 的話(blockquote)含宣告字樣 ✅ ✅ User Story 票名(身為…我要…我才…)不是宣告 ✅ ✅ ④ 主詞是 leo 的下一步(『你下一步去把 X 併進 main』) ❌ exit=2 ✅ exit=0 ④ 同上,交代對方要跑的動作 ✅ ✅ ④ 『你的下一步』(單字 lookbehind 蓋不到的變體) ❌ exit=2 ✅ exit=0 ④ 的反面:主詞換回我自己 ⇒ 照樣擋 ✅ exit=2 ✅ exit=2 ── A 群:既有放行路徑,不准變鬆(迴歸)───────────────────────────────── 純對話回合,沒有任何宣告 ✅ ✅ 宣告了,但這個回合真的呼叫了 Agent(=按下 trigger) ✅ ✅ 宣告了,但這個回合真的呼叫了 Task ✅ ✅ 宣告句帶票號指涉(已經在別人手上) ✅ ✅ 宣告之後真的動手了(文字後面還有工具呼叫) ❌ exit=2 ✅ exit=0 ── B 群:該擋(真的宣告了未來動作,且沒按下任何 trigger)─────────────── 『下一步我去把 X 補上』,無派工無票號 ✅ exit=2 ✅ exit=2 『接下來我會去補測試』 ✅ exit=2 ✅ exit=2 把請示寫成條件句(2026-08-17 leo 實撞那族) ✅ exit=2 ✅ exit=2 『我等你確認』——空手停在等人身上 ✅ exit=2 ✅ exit=2 『我現在就去跑一遍』 ✅ exit=2 ✅ exit=2 『確認後我才動』 ✅ exit=2 ✅ exit=2 票號寫在行內 code 裡也算數(等長替換才量得到) ✅ exit=0 ✅ exit=0 票號離宣告句太遠(別處提到而已)⇒ 不算,照樣擋 ❌ exit=0 ✅ exit=2 ── C 群:訊息承諾的每一條出路,都要真的走得通 ───────────────────────── 出路①:這個回合直接呼叫 Agent 派出去 ✅ exit=0 ✅ exit=0 出路②:把票號寫進那句話 ✅ exit=0 ✅ exit=0 出路③:用 ⏸ 等: 說明它在等什麼 ❌ exit=2 ✅ exit=0 出路③:宣告 + 標記同時存在也放行 ❌ exit=2 ✅ exit=0 出路④:這個回合收尾在動作上 ❌ exit=2 ✅ exit=0 ── 出路③ 的反面:沒寫標記就照樣擋(不准靠刪掉那句話矇混)────────────── 同一句話拿掉 ⏸ 等: 標記 ⇒ 擋 ✅ exit=2 ✅ exit=2 ── 壞輸入不准亂擋(fail-open)───────────────────────────────────────── {} / 不存在的 transcript / not-json ✅ exit=0 ✅ exit=0 ────── 舊版 通過 21 / 失敗 12 新版 通過 33 / 失敗 0 (共 33 向) ``` ## 真實資料上的實測(不是我造的樣本) 本機一條 2068 行真 session,切出 26 個真實回合終止點,兩支閘各跑一次: ``` 26 個真實回合終止點:舊閘擋 2 次(兩次都是誤攔,見 (e))→ 新閘擋 0 次 新閘放行理由:21 OK:no-declaration / 3 OK:dispatched / 2 OK:ticket-referenced ``` 真實 schema 也對過了(`assistant.message.content[]`、`user` 帶 `tool_result` 、還有 `attachment`/`system`/`bridge-session` 等 411+300 列雜訊)—— **造的假 transcript 與真檔同構,27→33 向不是假綠。** ## 從遠端 clone 出來複驗(不是從我的工作目錄) ``` PR head commit:e3d05df 版號:0.3.8 clone 出來的那份跑測試 → 33/33 同一份測試 × main 上的舊閘 → 21/33 真 transcript 26 回合 × 新閘 → 擋 0 次 ``` ## 兄弟閘沒有退步 ``` main-and-prod-push-guard 通過 8 / 失敗 0 main-and-prod-push-guard-cross-repo 通過 17 / 失敗 0 prod-write-guard 通過 29 / 失敗 0 stage-before-prod-guard 通過 16 / 失敗 0 sdd-guard 通過 8 / 失敗 0 ``` ## 順手一起處理的 - **版號**:`.claude-plugin/plugin.json` 0.3.6 → **0.3.8**(兩顆 commit 各跟一版) - **留痕**:擋下與放行都寫 `.claude/hooks/factory-idle-guard.log` (InkStoneCo#48:只記「擋下」的話分母未知)。 **上面 (f) 那份判定分佈就是靠這個 log 量出來的——留痕不是裝飾,它是這次的量測工具。** - **stderr 乾淨**:log 目錄不在(測試環境/別的 repo)時安靜跳過 - **白話盤點**:`docs/hooks-inventory.md` 那一列補上「過閘的四條路」 ## 刻意的取捨(寫進檔頭了,下一輪不要當成 bug 又改回去) 1. 「下一步」後面不吃逗號(`下一步,我去補 X` 會漏)。 吃了逗號,「不是我的下一步,我剛剛已經做完了」就會被咬(過去式當未來式)。 2. 「身為…我要…我才…」(本 repo 的 User Story 票名格式)整段蓋掉—— 票名是**被命名的物件**,不是我對下一步的主張。 3. **不設 `stop_hook_active` 逃生門**(與 empty-handed-stop-guard 不同)。 leo 2026-08-16:「提醒可以忽略,deny 不能。」 之所以敢這樣,是因為出路③現在**真的存在** ⇒ 不可能把人鎖死在無解迴圈裡。 ## 🔴 交回總管時要知道的一件事(我沒動它) `InkStoneCo/.claude/hooks/factory-idle-guard.sh` 與本 PR 的**舊版逐位元組相同** (`diff -q` 實測)。**這個 PR 併了,本機那份還是舊的、還是會誤攔。** InkStoneCo#72(「身為總管,我要本機的閘跟 plugin 同一份,我才不會被舊版誤攔」) 講的就是這件事——但同步屬於 InkStoneCo 那個 repo、要推它的 main, 依兩層手動確認閘不是 subagent 能做的。 📌 另外:KBDB 對這支閘的開發史 **0 命中**(語義搜尋最高分 0.48,全是 pms/undici 噪音)。 這筆「查了沒有」本身就是餵食清單——ISEP 的閘開發史還沒進 KBDB。 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude-code added 1 commit 2026-08-23 09:44:00 +00:00
2026-08-23 雲端驗收連續三次被這道閘誤攔,三次都不是宣告意圖:
① 否認自己有下一步 ②引用閘自己的訊息 ③貼閘自己的正則原始碼舉報 bug。
而訊息教人走的「選項③:說明它在等什麼」,程式碼裡根本沒有那條分支
——唯一走得通的路是不寫那三個字,正是同一則訊息明文禁止的動作。

四個真兇,沒有一個是「例外沒列夠」:
(a) DECL 會匹配裸的「下一步」三個字(每一節都可選 ⇒ 退化成關鍵字)
    ⇒ 收緊:每一條 alternative 都必須接到動作動詞才算命中
(b) 只剝 > 引言與長「」,不認 code fence 與行內 code ⇒ 引用被當成主張
    ⇒ 引用性標記整段換成哨兵(不是刪掉):內層宣告消失、外層句構留著
      ——刪掉正是 08-17 漏掉「回『規劃』我就派人」的原因,兩個方向一起修
(c) 取 blocks_text[-1],但那則文字後面可能還有 tool_use ⇒ 宣告其實兌現了
    ⇒ 只看「最後一個動作之後」的文字;收尾在動作上就不觸發
(d) 訊息承諾的出路只有兩條真的存在
    ⇒ 出路③ 給一個機械形式 ⏸ 等:<在等什麼>(白名單標記,要刻意寫,留痕)

方向刻意與「再加幾個關鍵字例外」相反——例外清單會越加越長、越長越誤攔。
守 leo 的封路哲學:紅線寫得越細,命中關鍵字的機率越高 ⇒ 那些閘在懲罰謹慎。

順手:擋下與放行都留痕(InkStoneCo#48:只記擋下的話分母未知);
log 目錄不在時安靜跳過,不再噴 redirect 錯誤到 stderr。

測試 hooks/tests/factory-idle-guard.test.sh 27 向,誤攔與漏攔兩個方向都測:
舊版 18/27(9 敗)→ 新版 27/27。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
claude-code added 1 commit 2026-08-23 09:54:20 +00:00
交付警察擋回來是對的:前一顆只驗了「我自己造的假 transcript」。
改用本機一條 2068 行的真 session(26 個真實回合終止點)重驗,當場多找到兩個問題:

① 主詞是「你」的下一步,被當成我的宣告。
   舊閘在 26 個真實回合裡擋了 2 次,兩次咬的都是我在交代 leo 該做什麼:
     「下一步還是那一個動作:你把 feat/... 併進 main」
     「## 你下一步(兩招,先便宜的)」
   ⇒ 這是全新的第四類誤攔,我原本一向都沒列到。
   修法是主詞檢查(誰要動手),只掛在「下一步」這條 alternative 上;
   用 finditer 逐個檢查前 8 字有沒有第二人稱,蓋得到「你的下一步」這種
   單字 lookbehind 蓋不到的變體。「你點頭我就做」主詞本來就是我,不受影響。

② 票號從「宣告句附近」放寬成「整段」,把閘變鈍了。
   真數據:26 個真實回合有 20 個是靠「文中某處剛好有票號」放行的——
   而報告幾乎一定會提到票號 ⇒ 這道閘在實務上等於不會響。
   當初放寬是因為票號常寫在行內 code 裡,剝掉就找不到。
   ⇒ 改成**等長**替換(蓋成同樣長度的哨兵而非刪除),位移就能對回原文,
     locality 與「行內 code 裡的票號也算數」兩件同時成立。
   收緊後的判定分佈:21 no-declaration / 3 dispatched / 2 ticket-referenced
   (原本是 20 ticket-referenced / 3 no-declaration / 3 dispatched)。

真 transcript 實測:舊閘擋 2 次(兩次都是誤攔)→ 新閘擋 0 次。
測試 33 向(+6):舊版 21/33 → 新版 33/33。
版號 0.3.7 → 0.3.8。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
claude-code merged commit 1b95388a0f into main 2026-08-23 09:58:42 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: inkstone/ISEP#54