在讓 Claude Code 或 Codex 工作之前,先建立三個規格檔案
使用 AI 代理將近一年後,我發現結構化的規格檔案比任何提示詞技巧都更能解決結果不一致的問題。
同樣的任務,不同的 session,卻得到完全不同的結果。
這件事困擾我很久。我用 Claude Code 處理某個功能,第一次跑出來的程式碼乾淨、符合專案慣例、測試也過了。隔天同樣的需求,換個方式描述,結果卻是一堆不一致的命名、缺少錯誤處理、甚至忽略了我們已經決定不用某個套件的原因。
問題不在於模型能力。問題在於我每次都用提示詞(prompt)來承載所有的脈絡,而提示詞本來就不是為這種事設計的。
根本問題:把意圖全塞進提示詞
大部分人使用 AI 代理的方式,是把所有的背景、限制、期望全部寫進一條訊息裡,然後希望代理能正確理解。這在簡單任務上還行,但稍微複雜一點就開始出問題。
研究人員把這種現象稱為「指令詛咒」(Curse of Instructions)。當一個模型在同一個上下文視窗裡要處理的資訊太多,它對不同資訊的注意力分配就會開始出現折衷。你加進去的每一條規則,都在跟其他規則搶資源。這不是 bug,是語言模型的基本架構特性。
解法不是寫出更完美的提示詞,而是把不同層次的資訊放到不同的地方。
三層結構
我目前的做法是維護三個檔案,每個有明確不同的用途:
CLAUDE.md:專案層級的規則與慣例SPEC.md:這個任務要做什麼、為什麼plan.md:代理實際要執行的步驟
這三個檔案不是同時建立的,也不是一次寫完的。它們代表三種不同的思考模式,把它們分開,可以讓每一層都做好自己的工作。
第一層:CLAUDE.md 是專案的憲法
CLAUDE.md 放在專案根目錄,Claude Code 會在每次 session 開始時自動讀取它。這個檔案回答的問題是:「這個專案是什麼,有哪些不可以違反的規則?」
裡面應該包含:
- 建置與測試指令(
bun run dev、bun run build、bun test) - 技術棧與關鍵依賴
- 命名慣例與程式碼風格
- 不能動的決策(例如:「我們不用 Prisma,原因是…」)
我發現用三個層級來組織規則最清楚:always(永遠遵守)、ask(不確定時先問)、never(絕對不要做)。
## 規則層級
### Always
- 所有公開函式都要有 JSDoc 型別標注
- PR 前先跑 `bun run typecheck`
### Ask
- 加入新的第三方套件前先確認
- 修改資料庫 schema 前先討論
### Never
- 不要 commit `console.log` 到主分支
- 不要繞過 TypeScript 型別系統用 `any`
CLAUDE.md 的重點是只放真正是專案層級的東西。如果你把某個功能的細節也塞進去,它很快就會變成一個沒人維護的垃圾桶。
建立策略:不要一次寫完。每次你發現代理做了一個你不想要的決定,問自己:「這是因為它不知道一個專案層級的規則嗎?」如果是,把那條規則加進 CLAUDE.md。用遇到問題時增量累積的方式,比預先規劃要有效得多。
第二層:SPEC.md 說明做什麼與為什麼
SPEC.md 是任務層級的文件,但它不是功能說明書,也不是 PRD。它回答的問題是:「這個任務的邊界在哪,成功長什麼樣子?」
重要的是,它不描述怎麼做。 怎麼做是代理的工作。SPEC.md 只給代理足夠的脈絡,讓它能做出正確的判斷。
我用五個區塊來組織:
目的:一兩句話說明這個任務存在的原因,以及它解決什麼問題。
需求:列出具體的功能需求。每條需求都應該是可驗證的,而不是模糊的期望。
成功標準:明確定義「完成」是什麼意思。包括測試通過、效能目標、或使用者體驗標準。
限制:哪些東西不能改、不能用。這一塊在省去很多不必要的來回溝通。
邊界:這個任務不包含什麼。明確寫出範疇之外的事,可以防止代理好心但過度地「順便」處理相鄰問題。
代理訪談法:建立 SPEC.md 的一個有效方式,是先讓代理用提問模式跟你對話。告訴它:「我要做 X,在你開始之前,問我你需要知道的所有問題。」然後把這些問答整理成 SPEC.md 的五個區塊。這個過程本身就能幫你發現你沒想清楚的地方。
第三層:plan.md 是可執行的任務清單
plan.md 是代理真正執行的腳本。這裡的每個任務都應該是明確的、有邊界的、大約 2 到 5 分鐘可以完成的。
每個任務要包含:
- 明確的目標(做什麼)
- 涉及的檔案路徑(具體到哪個檔案)
- 驗收條件(完成的判斷標準)
## 任務:為用戶 API 新增輸入驗證
**目標**:在 `/api/users` POST 端點加入請求體驗證
**相關檔案**:
- `src/pages/api/users.ts`(修改)
- `src/lib/validators/user.ts`(新建)
- `tests/api/users.test.ts`(新增測試)
**驗收條件**:
- 缺少必填欄位時回傳 400
- 無效 email 格式時回傳 400 並附錯誤說明
- 現有測試全部通過
- 新增的驗證邏輯有對應的單元測試
我習慣用 TDD 的節奏來組織 plan.md 的任務順序:先寫測試,讓它失敗,再寫實作,讓測試通過,重構,繼續下一個任務。這樣代理在每個小步驟都有明確的成功信號。
Session 分割的重要性
這三個檔案對應到三個不同的工作模式,而這三個模式不應該在同一個 session 裡混在一起。
規格撰寫 session:你跟代理一起建立 SPEC.md,定義邊界,澄清模糊地帶。這個 session 結束於 SPEC.md 定稿。
規劃 session:根據 SPEC.md,把任務拆解成 plan.md 的格式。這個 session 結束於 plan.md 可以被執行。
實作 session:代理根據 plan.md 逐步執行,你負責審查和調整。
為什麼要分開?因為當你在同一個 session 裡又規劃又實作,對話歷程很快就會變得太長,而語言模型在超長上下文的末尾對早期內容的注意力會顯著下降。一個實用的觸發點是:當 session 用了大約一半的上下文,就考慮建立一個新的 session,重新附上相關的規格檔案。
規格驅動開發的成熟度模型
使用這套方法一段時間,我觀察到大概有三個成熟度層次:
第一層:用完即棄。每個任務都重新寫一個提示詞,沒有任何持久化的文件。大部分人都在這一層。結果不穩定,但對小任務來說夠用。
第二層:活的文件。維護一個 CLAUDE.md,任務有 SPEC,但 plan.md 在任務結束後就丟掉。這一層的一致性明顯提高,代理犯重複錯誤的頻率下降。
第三層:規格即真相。所有重要的決策都記錄在規格檔案裡,而不是存在代理的對話歷史或你的記憶中。新的 session 從讀取規格開始,而不是重新描述背景。這一層的 AI 協作才真正可以擴展。
敏捷宣言的起草者之一 Alistair Cockburn 在討論規格的角色時,提到了一個關鍵觀點:文件的價值不在於文件本身,而在於它是否能讓另一個人(或代理)在沒有原始作者的情況下繼續工作。用這個標準來評估你的規格,比任何格式指南都有用。
從今天開始
如果你現在只用提示詞跟 AI 代理工作,最快看到改善的一步是這樣:
先建立你的 CLAUDE.md。把你在 code review 裡重複說的事、你不想讓代理自己決定的事、專案的技術棧和關鍵限制,整理進去。不需要一次寫完,重要的是開始建立這個習慣。
接著,在下次開始一個新功能之前,先花 15 分鐘建立一個 SPEC.md,用五個區塊定義任務的邊界。然後再開始寫程式,或者讓代理開始寫。
這兩個步驟不需要任何新工具,也不需要改變你現在的開發流程。但它們會讓你跟 AI 代理的協作,從「有時候很有用」變成「穩定可依賴」。
訂閱電子報
獲取關於我最新專案、文章以及 AI 和 Web 開發實驗的更新。