「架構分層規則已經寫進 CLAUDE.md 了,這條規則不是就一直存在嗎,怎麼還會被違反?」
這是我在推動架構守則時,最常聽到的疑問。答案是:文件裡的規則,只在 AI 讀到那段文字的當下才會生效。 對話進行到後面、context 被壓縮、或者換了一個新的 session 開始,那條規則就不再是「現在正在被遵守的東西」,而只是「理論上存在、但這一輪對話沒被載入」的一段文字。今天要講的,是怎麼把架構規則從「一段希望被記住的文字」,變成「一個不管記不記得都會被強制執行的檢查」。
前幾天講過,CLAUDE.md 太長會稀釋每一條規則的權重,也會拖慢每次載入的成本——這是「規則寫得越多、越容易被稀釋」的問題。今天要講的是另一個更根本的限制:就算規則寫得再精簡、再清楚,它依然只是一段文字,AI 有沒有把它套用到眼前這次改動,取決於這段文字有沒有在這一輪對話的 context 裡。
架構規則(例如「Repository 層不能反過來依賴 Controller」「某個模組不能直接 import 另一個模組的內部類別」)通常不是每次改動都會被提醒一次,而是寫在專案規範裡、指望 AI「記得」。這種依賴記性的強制力,在一次性的小改動裡或許夠用,但在長期、多輪對話、跨 session 的協作裡,會隨著時間慢慢失效。
真正能形成不依賴記性的強制力,是把架構規則寫成一個會在 CI 裡實際跑、違反就讓建置失敗的檢查。以 PHP 生態為例,deptrac 讓你把類別分組成「層」(layer),定義層與層之間允許的依賴方向,跑起來時掃描整個程式碼庫,任何違反規則的依賴都會被列出來、並回傳非零結束碼;TypeScript/JavaScript 生態則有 dependency-cruiser,一樣是定義模組邊界規則、驗證真實的 import 關係有沒有違規,也能抓循環依賴、抓「表面上是共用模組、實際只被一個地方引用」這類異常。
用一組對照來看這個差異:
❌ 規則只寫在文件裡:
CLAUDE.md 寫著:「Repository 層不可以依賴 Controller 層」
→ AI 讀過這段文字的那次對話裡會遵守,
但下一次任務如果沒有重新載入這段規則,
違反的改動照樣能通過測試、順利合併
✅ 規則寫成可執行檢查:
deptrac.yaml 定義 Repository 層跟 Controller 層之間的允許依賴方向,
CI pipeline 裡跑 `deptrac analyse`
→ 不管 AI 有沒有「記得」這條規則,
只要違反了,CI 就會失敗、擋下這次合併,
強制力不依賴任何一次對話的 context
這正是這個系列反覆出現的模式的另一種樣貌:不是要求 AI 更謹慎地記住規則,而是把規則收斂成一個不需要記性、可以被自動驗證的具體檢查。 跟 Day 04 講的覆蓋率門檻是同一種思路——覆蓋率報告讓「這行能不能動」變成可以量化的問題,架構檢查工具讓「這個依賴合不合規」也變成同一種可以量化、可以自動判定的問題。
要澄清一點:把規則寫成可執行檢查,不代表文件就沒有價值了。文件(CLAUDE.md、ADR)負責回答「為什麼」——為什麼這個依賴方向是被禁止的、當初考慮過哪些替代方案;可執行檢查負責回答「有沒有」——這次改動有沒有違反規則。 少了文件,違規發生時 AI(或人)不知道為什麼這條規則存在、該怎麼修;少了可執行檢查,文件寫得再清楚,違規還是可能悄悄溜過去。兩者是互補關係,不是其中一個可以取代另一個。
回想你手上系統裡寫在文件裡的架構規則:如果現在故意違反其中一條,你的 CI 會不會失敗?如果答案是不會,這條規則實際上有沒有真正的強制力,還是只存在於文件裡?
明天用一個具體案例,看一條只存在於文件裡、沒有工具強制的架構規則,多久之後會被悄悄違反而沒人發現。