GateGuard — 強制事實核查的執行前閘門 是做什麼的?
一個 PreToolUse 鉤子,強制 Claude 在編輯之前進行調查。不是自我評估(「你確定嗎?」),而是要求具體的事實。調查這一行為本身所創造的覺察,是自我評估永遠無法達到的。
何時啟用
- 在檔案編輯會影響多個模組的任何程式碼庫上工作
- 專案中包含具有特定結構或日期格式的資料檔案
- 團隊要求 AI 生成的程式碼必須符合現有模式
- 任何 Claude 傾向於猜測而非調查的工作流程
核心概念
LLM 的自我評估無效。問「你是否違反了任何策略?」答案永遠是「沒有。」這已經過實驗驗證。
但問「列出所有導入此模組的檔案」會強迫 LLM 執行 Grep 和 Read。調查本身創造了改變輸出的上下文。
三階段閘門:
1. DENY — 阻擋第一次 Edit/Write/Bash 嘗試
2. FORCE — 明確告知模型需要收集哪些事實
3. ALLOW — 在事實呈現後允許重試
沒有競爭對手能做到全部三點。大多數只停留在拒絕。
證據
兩個獨立的 A/B 測試,相同的代理,相同的任務:
| 任務 | 有閘門 | 無閘門 | 差距 |
|---|---|---|---|
| 分析模組 | 8.0/10 | 6.5/10 | +1.5 |
| Webhook 驗證器 | 10.0/10 | 7.0/10 | +3.0 |
| 平均 | 9.0 | 6.75 | +2.25 |
兩個代理產生的程式碼都能執行並通過測試。差別在於設計深度。
閘門類型
編輯 / 多重編輯閘門(每個檔案的第一次編輯)
多重編輯的處理方式相同 — 批次中的每個檔案都會被獨立設閘。
在編輯 {file_path} 之前,請提供以下事實:
1. 列出所有導入/引用此檔案的全部檔案(搜尋整個樹狀結構 — 透過 Glob/Grep,或經由 Bash 使用 find/grep)
2. 列出受此變更影響的公開函數/類別
3. 如果此檔案讀取/寫入資料檔案,請顯示欄位名稱、結構
和日期格式(使用編輯過或合成的值,而非原始生產資料)
4. 逐字引用使用者當前的指令
寫入閘門(首次建立新檔案)
在建立 {file_path} 之前,請提供以下事實:
1. 指出會呼叫此新檔案的檔案名稱和行號
2. 確認沒有現有檔案能達到相同目的(搜尋整個樹狀結構 — 透過 Glob/Grep,或經由 Bash 使用 find/grep)
3. 如果此檔案讀取/寫入資料檔案,請顯示欄位名稱、結構
和日期格式(使用編輯過或合成的值,而非原始生產資料)
4. 逐字引用使用者當前的指令
破壞性 Bash 閘門(每次破壞性指令)
觸發條件:rm -rf, git reset --hard, git push --force, drop table 等。
1. 列出此指令將修改或刪除的所有檔案/資料
2. 寫出一行的復原程序
3. 逐字引用使用者當前的指令
例行性 Bash 閘門(每個會話一次)
1. 用一句話描述目前的使用者請求
2. 這個特定指令驗證或產生了什麼
平行批次與部分套用
首次觸碰閘門會獨立評估每個工具呼叫。當對一個尚未被觸及的檔案發送多個編輯指令到同一個平行批次時,第一個呼叫會被拒絕,而拒絕會將該檔案標記為已檢查,因此該批次中的同級編輯仍會被套用。沒有什麼會回滾:檔案最終可能包含同級編輯,但不包含被拒絕的那個。
拒絕訊息會指名該檔案,並警告批次同級編輯可能已經被套用。請嚴格遵循以下規則:
- 將對尚未觸及檔案的相依編輯,以序列方式而非平行批次方式發送。定義與其首次使用,或導入與其呼叫點,不得在同一批次中。
- 在首次觸碰被拒絕後,提供事實,重試被拒絕的編輯,並在基於批次中其他任何內容繼續建構之前,重新讀取該檔案。
批次層級鎖定是不可能的:鉤子一次只能看到一個工具呼叫,因此閘門無法知道哪些呼叫是同時到來的。
快速開始
選項 A:使用 ECC 鉤子(零安裝)
位於 scripts/hooks/gateguard-fact-force.js 的鉤子已包含在此插件中。透過 hooks.json 啟用它。
如果 GateGuard 阻擋了設定或修復工作,請以 ECC_GATEGUARD=off 啟動會話。如需鉤子層級的控制,請繼續搭配 GateGuard 鉤子 ID 使用 ECC_DISABLED_HOOKS。
在長時間會話中,只有前 GATEGUARD_FACT_FORCE_FULL_DENIALS 次(預設 3 次)的強制事實拒絕會發出完整的四個事實區塊;後續的拒絕會濃縮為一行,攜帶拒絕序號,這樣一來,近乎相同的區塊就不會在對話視窗中累積,進而放大模型重複的循環(#2142)。在呈現事實後重試同一個檔案或指令,永遠不會再次觸發閘門。
漸進式控制
ECC_GATEGUARD=off(或 GATEGUARD_DISABLED=1)會完全關閉閘門。下表中的變數不會 — 每個變數只會縮窄一個行為,而承載負載的破壞性 Bash 檢查仍會持續執行:
| 變數 | 預設值 | 效果 |
|---|---|---|
GATEGUARD_BASH_ROUTINE_DISABLED | 未設定(閘門開啟) | 僅停用例行性 Bash 閘門。破壞性 Bash 閘門(rm -rf, git reset --hard, drop table, dd if=, …)不受影響。 |
GATEGUARD_EXEMPT_GLOBS | 未設定(無豁免) | 逗號分隔的 glob 模式;符合條件的 Edit/Write/MultiEdit 目標會跳過首次觸碰的事實強制。適用於導入價值低的樹狀結構(測試、生成產物、暫存目錄),在這些地方「誰導入此檔案 / 什麼結構」沒有意義。 |
GATEGUARD_FACT_FORCE_FULL_DENIALS | 3 | 多少次拒絕會發出完整的四個事實區塊,之後的拒絕會濃縮為一行。0 表示從第一次拒絕就開始濃縮。 |
GATEGUARD_BASH_EXTRA_DESTRUCTIVE | 未設定 | 額外的破壞性指令模式,作為正規表達式來源,加入內建集合中。格式錯誤的正規表達式會被視為未設定(內建仍會套用)並記錄一次到 stderr。 |
GATEGUARD_STATE_DIR | ~/.gateguard | 每個會話的閘門狀態存放位置。如果無法持久化狀態,閘門會允許操作以避免循環,並在警告中指名此變數。 |
GATEGUARD_BASH_ROUTINE_DISABLED 接受 1, true, on, enabled, enable, 或 yes(不區分大小寫與空白);任何其他值將保持閘門開啟。
完全關閉閘門
| 變數 | 效果 |
|---|---|
ECC_GATEGUARD=off | 停用該會話的 GateGuard。接受 0, false, off, disabled, 或 disable。 |
GATEGUARD_DISABLED=1 | 效果相同。僅辨識 1 — 上述的拼寫不適用於此。 |
如需鉤子層級的控制,請繼續搭配 GateGuard 鉤子 ID 使用 ECC_DISABLED_HOOKS。
GATEGUARD_EXEMPT_GLOBS 的 Glob 語意
模式比對整個相對於專案的目標路徑。專案根目錄為 CLAUDE_PROJECT_DIR,若無則回退到鉤子 payload 的 cwd,再回退到鉤子程序的工作目錄。相對 glob 從不豁免該根目錄之外的目標。明確的絕對 glob 比對整個絕對目標路徑,並可能刻意豁免專案之外的路徑。
模式和路徑都使用 / 分隔符號並進行小寫比對。* 比對一個區段內的字元,** 跨區段比對,? 比對一個非分隔符號字元。**/ 包含零個目錄,因此 **/tests/** 也會比對到 tests/foo.js。格式錯誤的模式會被丟棄,不會授予豁免。
自 2.2.1 版起,services/** 僅涵蓋專案根目錄的 services 樹狀結構,*.md 僅涵蓋其根目錄的 Markdown 檔案。對於專案內的所有 Markdown 檔案,請使用 **/*.md。現有的未錨定豁免可能需要調整:
{
"env": {
"GATEGUARD_BASH_ROUTINE_DISABLED": "1",
"GATEGUARD_EXEMPT_GLOBS": "**/tests/**,tests/**,**/*.test.*,**/docs/**,**/dist/**"
}
}
選項 B:包含設定的完整套件
pip install gateguard-ai
gateguard init
這會新增 .gateguard.yml 用於每個專案的設定(自訂訊息、忽略路徑、閘門開關)。
反模式
- 不要使用自我評估來代替。 「你確定嗎?」永遠會得到「確定。」這已經過實驗驗證。
- 不要跳過資料結構檢查。 兩個 A/B 測試代理都假設日期是 ISO-8601 格式,但實際資料使用的是
%Y/%m/%d %H:%M。檢查資料結構(使用編輯過的值)可以預防這整個類別的錯誤。 - 不要對每個 Bash 指令都設閘。 例行性 Bash 每個會話只設閘一次。破壞性 Bash 則每次都設閘。這種平衡可以在避免減速的同時,捕捉真正的風險。
最佳實務
- 讓閘門自然觸發。不要試圖預先回答閘門的問題 — 調查本身才是提升品質的關鍵。
- 根據您的領域自訂閘門訊息。如果您的專案有特定的慣例,請將它們加入閘門提示中。
- 使用
.gateguard.yml忽略如.venv/,node_modules/,.git/等路徑。
相關技能
safety-guard— 執行時安全檢查(互補,不重疊)code-reviewer— 編輯後審查(GateGuard 是編輯前調查)