撰寫優質規格
你很少從空白頁面開始撰寫規格。你用白話描述一個變更,/opsx:propose 起草需求和場景,然後你將它們打磨到位。本頁專注於最後這一步——「優質」是什麼樣子,以及如何引導 AI 朝這個方向走。
它是 Reviewing a Change 的姊妹篇:審查是找出草稿中的弱點,撰寫則是知道一份強勁的規格由什麼構成。
規格描述的是行為,而非程式碼
規格說明你的系統做了什麼,以任何人都能驗證的術語——而非它是如何建構的。它由需求(行為陳述)和場景(證明這些行為的具體範例)組成。
### Requirement: Session Timeout
The system SHALL expire a session after 30 minutes of inactivity.
#### Scenario: Idle timeout
- GIVEN an authenticated session
- WHEN 30 minutes pass with no activity
- THEN the session is invalidated and the user must re-authenticate將怎麼做——佇列、函式庫、資料表結構——留在 design.md 或程式碼中。當行為與實作混在同一個需求裡時,該需求就不再可測試,而且程式碼一變就會過時。
什麼樣的才算好需求
一個好需求只描述一個行為,且陳述得如此直白,你可以直接交給別人去測試。
一句陳述,一個
SHALL/MUST。 如果一個需求有三個「還有」子句,那它其實是三個需求。拆開它們。可觀察的。 程式碼以外的人應該能判斷它是否成立。「系統 SHALL 在上傳超過 10 MB 時顯示錯誤橫幅」是可觀察的。「系統 SHALL 優雅地處理大型上傳」則不是。
強度要恰當。 OpenSpec 使用 RFC 2119 關鍵字,它們代表不同的含義:
關鍵字 含義 MUST/SHALL硬性需求。不可妥協。 SHOULD強烈建議,但允許有正當理由的例外。 MAY真正可選的。 預設使用
MUST/SHALL。只有在你真的意味著「除非有正當理由不這麼做」時,才使用SHOULD。
需求的檢驗標準:一個從未看過程式碼的測試人員能否判斷它是否通過? 如果不能,就需要再精煉。
什麼樣的才算好場景
場景是需求證明其價值的所在。每個場景都是一個具體的 GIVEN / WHEN / THEN,可以成為自動化測試。
- 它要驗證對應的需求。 一個只是換句話重述需求的場景什麼都沒測試到。把它做成一個有具體情境和具體結果的場景。
- 涵蓋重要的案例,而不只是正常路徑。 有效的登入很容易。空輸入、過期令牌、第二次點擊、出錯的情況——這些才是 bug 藏身之處,也是場景最有價值的所在。
- 在標題中命名案例。 「Scenario: Rejects an expired token」讓審查者一眼就能看出涵蓋了什麼;「Scenario: Test 2」則做不到。
一個有用的習慣:在核准之前,問自己如果哪個案例壞了我會最生氣?——然後確保有一個場景明確命名了它。
選擇正確的 delta 類型
一個變更使用三種章節類型來描述對規格的編輯。使用正確的那一種可以讓你的歸檔規格保持誠實:
## ADDED Requirements— 全新的、之前不存在的行為。## MODIFIED Requirements— 已存在且正在變化的行為。包含完整的新版本;簡短說明改了什麼有助於審查者。## REMOVED Requirements— 即將移除的行為,附一行說明原因。
歸檔時,ADDED 會被附加到主規格中,MODIFIED 會取代舊版本,REMOVED 則從中移除。移除某項能力的最後一個需求即為退役該能力:與其留下一份空白的規格,歸檔會直接刪除 openspec/specs/<capability>/spec.md。因為這是唯一會移除檔案的歸檔步驟,所以必須明確請求——在變更的 .openspec.yaml 中加入 retire_capabilities: true,與該檔案已有的 schema: 並列。沒有它,歸檔會中止並告知你。退役會刪除整個檔案,因此當規格中除了標題、## Purpose 和需求區塊之外還有其他內容時(例如 ## Notes 章節、需求下方的註解),退役也會被拒絕。中止訊息會列出那些行;將它們移入 ## Purpose 或某個需求中,或手動刪除該規格。對於呼叫者 checkout 中的規格,歸檔輸出還會標明用於還原已提交檔案的 git checkout 指令;選定的儲存庫則會收到 checkout 範圍的復原指引。如果你將一個真實的變更標記為 ADDED,最終會得到兩個互相衝突的需求;如果你將新行為描述為 MODIFIED,則沒有東西可以取代。拿不準時,打開目前的規格看看該需求是否已經存在。
還有一個章節值得了解。當你的 delta 建立了一個尚不存在的能力時,以 ## Purpose 開頭——一兩句話說明該能力的用途。歸檔會將它作為所建立主規格的 Purpose;跳過它則會得到一個需要手動填寫的 TBD 佔位符。已存在的規格已經有 Purpose,因此 delta 中的 Purpose 在那裡會被忽略——要修改請直接編輯 openspec/specs/<capability-path>/spec.md。此處 <capability-path> 是相對於 specs/ 的目錄路徑,例如扁平專案中的 user-auth,或按領域組織的專案中的 identity/user-auth。
讓變更規模恰當
最常見的撰寫錯誤不是需求措辭不佳——而是一個變更試圖同時做三件事。
一個好的變更只有一個你能用一句話說明的意圖。 「新增深色模式切換。」「為登入端點加入速率限制。」「將 session 從 cookie 遷移出去。」如果描述變更需要很多「還有」,那就是該拆分它的訊號。
變更過大的跡象:
- 提案的範圍讀起來像是一串不相關的功能清單。
- 審查它需要一個下午,所以沒人會做。
- 兩個人無法在不衝突的情況下同時處理。
- 一半的任務可以獨立發布。
較小的變更更容易審查、更容易在一次專注的會話中完成,也更容易在六個月後只剩歸檔時進行推理。你隨時可以並行執行多個變更——見 Editing & iterating 和 Workflows。
相反的情況也會發生:一個單行錯字修正不需要三個需求和一份設計文件。讓儀式感與重要性相匹配。
如何引導 AI 產出優質草稿
因為 /opsx:propose 負責起草,你收到的品質取決於你給它的品質。你不需要手動撰寫需求——你需要的是準確地引導 AI:
- 說明意圖和邊界。 「新增一個深色模式切換,首次載入時跟隨系統設定——不要動現有的主題 API。」 不在範圍內的部分與在範圍內的部分同等重要。
- 點名你在意的案例。 「確保有一個場景涵蓋已手動選擇主題的用戶。」 AI 會覆蓋你指出的部分。
- 然後編輯。 它就是純 Markdown。精煉一個含糊的
SHALL,刪除一個什麼都沒測試的場景,補上它遺漏的案例——或者請 AI 幫你做:「逾時需求太模糊了,把它固定為 30 分鐘。」
起草、精煉、重複。幾輪下來就能產出一份你信賴的規格,這正是全部目的所在。
快速檢查清單
接下來去哪裡
- Reviewing a Change — 兩分鐘的快速掃描,抓住遺漏之處。
- Concepts — 規格、變更和 delta 背後的深層模型。
- Examples & Recipes — 從頭到尾的真實變更範例。