![]()
CLAUDE.md 中的規則是一種請求。Hook 則是一種保證。將這兩者混淆是我看到最常見的設定錯誤:團隊把「永遠不要提交機密」寫成文字,然後當機密真的被提交時感到驚訝。這篇文章是我使用的區分方式:什麼該寫成文字、什麼該放在 Hook,以及決定層級的四階梯。
文字設定改變模型「嘗試要做什麼」。Hook 和 CI 則改變「什麼事有可能做到」。任何絕對不能發生的事都不該放在文字中,因為文字是被取樣的,而不是被執行的。
對於任何你想要的規則,先問它實際上需要哪個層級:
Level 1 Linter/types/CI 確定性,捕捉大部分程式碼問題
Level 2 Hooks (PreToolUse) 在指令或檔案執行前就加以阻擋
Level 3 Scoped rules 文字,只針對符合的檔案載入
Level 4 Always-on config 文字,每一次呼叫都值得花費上下文
Enter fullscreen mode
Exit fullscreen mode
這個階梯是依照可靠性排序,而最可靠的那端是免費的(不消耗 token)。每把一條規則往下推一層,就能節省上下文並移除一種失敗模式。
我自己設定中的具體範例:
{
"hooks": {
"PreToolUse": [
{ "matcher": "Bash", "command": "block-dangerous.sh" },
{ "matcher": "Edit|Write", "command": "protect-env.sh" }
]
}
}
Enter fullscreen mode
Exit fullscreen mode
rm -rf 在目標目錄外、強制推送至 main、drop table。在 PreToolUse Hook 中用正規表達式就能確定地阻擋這些行為。寫成文字的「使用 rm 時要小心」大概十次有九次有效,但你只會聽說那第十次。.env、金鑰、產生出的檔案。Hook 會回傳阻擋原因,然後 Agent 會自行調整。只有在嘗試的那一刻才會消耗 token。Hook 無法決定的事,因為它們需要判斷力:
最明顯的跡象是 CLAUDE.md 中任何以「永遠執行」或「永遠不要提交」開頭的句子。如果 CI 可以檢查,就應該讓 CI 去檢查。每把一條 linter 規則重複寫在文字中,就是把上下文浪費在機器已經保證的事情上,而且當兩者出現落差時,就是一個等待發生的矛盾。
我們的工具組把文字保留給判斷力,並把所有可檢查的事項往下推到階梯的下層。這也是為什麼驗證器會拒絕佔位符的指令檔案:重述 linter 的文字比沒有檔案還糟糕,因為它會訓練你停止閱讀自己的設定。
較舊的設定(純 .cursorrules、裸的 AGENTS.md)沒有 Hook 層。階梯依然適用,只是往下掉一階:CI 和 linter 負責硬性保證,文字負責判斷,而「把硬性保證寫在文字中」的落差則由 git 本身的 pre-commit hook 來涵蓋,這是每個設定都有的。
我們的工具組已預先內建這個區分:文字用於判斷,Hook 與驗證器規則用於保證,皆收錄於 AgentConfig Studio。先用 Next.js 試用這個方法:免費樣本工具組(MIT)。
https://dev.to/piekwerk/config-files-vs-hooks-where-agent-enforcement-actually-belongs-2gi8