首頁/人工智慧

AI 寫程式,誰來把關?SPEX-SDD 方法全紀錄

2026年08月01日 人工智慧

AI 寫程式,誰來把關?SPEX-SDD 方法全紀錄

SPEX-SDD 是一個安裝在 ~/.claude/skills/ 底下的 Claude skill,用途是幫軟體系統產生一套「可實作、可驗證、也能被機器把關」的技術文件:統一編號的規格骨幹(backbone)、七份正式文件、工程品質關卡(Harness)、AI 可執行任務、需求追溯資料,還有 UI 畫面稿。這套方法不是憑空設計出來的,起點是兩篇原本互不相干的文章,加上一份被逐行讀過、抓出一堆問題的 955 行草稿。這篇文章完整記錄從問題到方法的整個過程:兩個訊號怎麼疊在一起、十個設計決策怎麼一個一個定下來、系統實際長什麼樣子、怎麼真的操作,最後回頭看哪些問題解決了、哪些還沒解決。

人類讀 code 的速度,跟不上 AI 寫 code 的速度了

三個選擇,沒有一個乾脆

情境很常見:你讓一個 AI agent 實作一個功能,十分鐘後它回報完成——新增十四個檔案,改了九個,附上四十個通過的測試。這時候你有三個選擇。

第一個是逐行讀完再合併。假設你真的認真讀,三千行程式碼加測試大概要兩到四小時;這段時間裡 AI 已經可以再產出六個同樣規模的變更。審查速度會變成整條開發流程的瓶頸,AI 再快也沒用。

第二個是抽查一部分,其他相信測試。這是多數人實際上的做法,但問題是測試也是 AI 寫的。會寫出繞過規格的程式的 agent,一樣會寫出永遠是綠燈的測試——斷言太鬆、只測正常流程、把真正的邏輯 mock 掉。這種綠燈沒有參考價值。

第三個是完全不讀。聽起來冒險,但這是 Robert C. Martin(Uncle Bob)公開選擇的做法,下面會提到。前提是先建好一套機器關卡,讓「不讀」不等於「不知道」——沒有關卡就不讀,出了問題也不會有人發現。

沒人讀 code 時,先爛掉的是結構

最先出問題的通常不是行為正確性,畢竟還有測試盯著。真正容易在沒人注意時慢慢走樣的是結構品質,一般的功能測試根本不會提醒你這些事。

AI 傾向在既有函式裡加分支,而不是重構。每次只加一點點,測試照樣過,六週後某個 handler 的循環複雜度可能從 8 爬到 23,過程中沒有任何訊號提醒過任何人。同樣的傾向也發生在檔案層級:AI 喜歡把邏輯塞進已經存在的檔案,檔案從 300 行長到 1,400 行,職責混在一起,沒人敢動它,測試依然是綠的。

架構邊界也一樣脆弱。領域模型文件(DDM)上畫了漂亮的模組邊界(領域驅動設計裡稱為 Bounded Context,簡稱 BC),但沒有任何機制阻止 AI 為了讓測試過,直接引用另一個 BC 的 Infrastructure。兩個月後邊界形同虛設,那張圖還掛在文件裡當裝飾。更陰險的是假測試的累積:覆蓋率 85% 聽起來很安全,直到你把 > 改成 >=、把 return true 改成 return false,才發現一半的測試根本抓不到——覆蓋率量的是「跑過」,不是「驗過」。

這四種問題有個共同點:沒有一種會讓測試變紅。當「測試有沒有過」是你唯一的訊號,而這個訊號對結構品質完全無感,系統就會在測試全綠的情況下持續變差,直到某天一個普通的需求變更要花三週才做得完。

規格寫得再精確也救不了

直覺的做法是把規格寫得更精確:「Controller 必須保持薄層」「不可硬編 tenantId」「覆蓋率須達 90%」。這些句子本身都沒錯,問題是沒有人負責發現它被違反。寫下規範的時候,我們其實默默假設「之後會有人檢查」——在人類團隊裡這是 code review 在做的事,在 AI 產出的流程裡,沒有人做。AI 不會因為文件裡寫了「必須薄層」就真的維持薄層,遇到「加在 Controller 裡最快」的情況它就會違反,而且不會有人發現,因為沒有人在讀。

負責抓出這類違規的檢查,後面統一叫 detector(偵測器);整套自動把關機制叫 Harness;其中每一道會回報通過或失敗的檢查叫 gate(關卡)。這正是整套方法的轉折點:

傳統做法的假設是「規格寫得夠精確,AI 就會照做」。這個方法的假設是「規範若沒有偵測器,就等於沒有寫」。中間那個轉折點,是承認人類的閱讀頻寬已經跟不上 AI 的產出速度——所以人不該再花時間讀 code,而該花時間設計「機器怎麼替我讀」。 ——《SPEX-SDD 設計歷程》終章

後面還會用到幾個詞,先一併記下:validator 是專門檢查規格檔案結構與交叉引用的程式;Slice 指一次只展開一小段需求,不急著把全套規格寫完;Misfit(原意「不合身」)指關卡失敗、AI 違規,或人工才發現的漏網問題,記錄這些問題的清單就叫 Misfit Log。先知道中文意思就好,英文名稱是方便之後查對照用的。

重新分工:人審業務、機器審技術,AI 只管寫

承認「人不讀 code」之後,不代表什麼都不管,而是把人力挪到機器做不來的位置。責任大致分成三層:人類審查用業務語言寫成的驗收情境(DSL-Level Gherkin),機器驗證帶技術細節的驗收情境(ISA-Level Gherkin)與六項工程指標,AI 負責產出程式碼——這一層沒人讀,也不需要讀。DSL 與 ISA 兩層 Gherkin 的差別,先記住這一句就好,寫法留到六項指標那節再展開。

三層責任分工 人審業務規格、機器審技術規格與六項指標、AI 出程式碼——出包時只要問一句「規格漏了,還是 gate 沒攔到」,修補動作就很清楚,不會變成互相指責。

兩個訊號指向同一件事

這個方法的起點不是一張功能清單,而是兩篇原本各談各的文章疊在一起,原本模糊的缺口突然有了形狀。

第一個訊號來自泰迪軟體 Teddy 的一篇貼文。他長年推廣 Christopher Alexander 的模式語言與生成理論,這次把自己的「Pattern Language Approach for AI Coding」對應到 AI Coding 社群近年冒出的三個流行名詞,並用 Alexander 的生成理論當上層框架:

Alexander 生成理論 AI Coding 社群用語 角色
Pattern Language Context Engineering 每次局部成長依循的設計知識
Diagnosis / Misfit Detection Harness Engineering 診斷:找出不合身的地方
Piecemeal Growth Loop Engineering 一次只長一小塊的工程化機制
Better Form Better Candidate Form 產出物逐步逼近對的形狀

這張表有意思的地方在於:Context / Harness / Loop Engineering 這三個名詞當時各自流行、各自發明術語,看起來像三個不相關的技巧。Teddy 指出它們其實是同一套五十年前就有的生成理論的三個面向——這樣一來,「缺了哪一塊」就變成可以逐格檢查的問題,不用憑感覺拼裝。

Teddy 的核心命題是:人類只負責提供規格、指出 AI 的問題、把問題沉澱成規範;撰寫與重構交給 AI,逼近「人類幾乎不寫 code」的模式。這三個動作——提供規格、指出問題、沉澱規範——後來分別對應到 SPEX-SDD 的三個部分:規格骨幹與七份文件、Harness 層、Misfit Log。

第二個訊號來自 Robert C. Martin,也就是 Uncle Bob。要知道這件事有多不尋常,得先想起他是誰:《Clean Code》的作者,寫了二十年「程式碼是寫給人看的」「程式碼就是設計文件」,是最堅持「人要讀 code」的人。而他公開表示,現在完全不看 AI 寫的程式碼:

唯一能真正享受 AI 生產力的方式。人工逐行審查反而會變成新的瓶頸。 ——Robert C. Martin(Uncle Bob)

重點不只是「不看」,而是他把省下的時間拿去設計自動化關卡。他列出了自己實際在盯的六項指標——單元測試、Gherkin 行為驅動測試、測試覆蓋率、突變測試、循環複雜度與模組大小、依賴結構,下一節會逐項展開。先記住一點:這不是一份理論清單,是一個放棄逐行審查的人實際在監看的指標。

單看任何一篇都還不夠。Teddy 的框架有四個格子,但「Harness Engineering」那格只有名字沒有內容——診斷要診斷什麼、量哪些東西、門檻多少,他沒有列。Uncle Bob 有具體清單,但清單是孤立的,它跟規格、跟任務、跟規範演進怎麼接起來,他也沒有說。

兩個訊號的互補關係 Teddy 給了理論座標,Uncle Bob 把其中一格填滿了:他的六項指標,正好補上 Harness Engineering 那個空格。

把 Uncle Bob 的六項指標填進 Teddy 的 Harness 格子,四格框架就完整了:規格與規範是 Pattern Language,六項指標的機器關卡是 Misfit Detection,slice 增量是 Piecemeal Growth,misfit 沉澱成規範修訂是 Better Form。接下來要問的是:手上既有的技術文件方法,也就是那份草稿,離這個組合還差多遠?在回答這個問題之前,得先把四格框架和六項指標本身說清楚。

Christopher Alexander 的四個格子

Christopher Alexander 是建築師,軟體工程卻從他那裡借走了不少東西:Design Patterns 借走了 pattern 的形式,Wiki 的發明人 Ward Cunningham 借走了 pattern language 的組織方式。SPEX-SDD 借走的是更完整的一套——生成理論(generative theory)的整個迴圈。

第一格是 Pattern Language,局部成長依循的設計知識。Alexander 在《A Pattern Language》裡收錄了 253 個建築模式,從城市尺度到門窗細節,重點不在單一 pattern,而在「language」——模式之間彼此關聯,大模式引用小模式,每次蓋一小塊東西,就查閱相關的模式群,讓局部決策自然符合整體語言。每個 Alexander 式的 pattern 都有固定結構:情境(context)、張力(forces)、解法(solution),其中張力最重要,它說明這個解法在什麼條件下成立——少了 forces 的 pattern 只是一條命令,遇到沒見過的情境就沒辦法推理。SPEX-SDD 後面規定每條規範都要寫出 Why 與 When NOT,就是從這個觀察來的。對應到 AI Coding,Pattern Language 就是 Context Engineering:你餵給 AI 的規格、慣例、禁止事項、參考實作,AI 每次「長」一段程式碼時依循的設計知識。

第二格是 Misfit Detection,診斷不合身之處。「Misfit」一詞來自 Alexander 更早的《Notes on the Synthesis of Form》(1964)。他的觀察是:我們很難正面定義「好的形式」是什麼,但很容易指出哪裡「不合身」——門把太高、走道太窄、光線刺眼。設計的實際過程不是朝著抽象的「好」前進,而是逐一消除具體的不合身。這個轉向對軟體很有意義:「寫出高品質程式碼」無法被機器判斷,但「循環複雜度超過 15」「BC-A 依賴了 BC-B 的 Infrastructure」「突變測試殺不死一半的變異」可以。與其定義品質,不如列出可偵測的不合身訊號,一個一個裝上偵測器——這就是 Harness Engineering 在做的事,harness(測具)把產出物固定在一個可以持續量測的位置上。

第三格是 Piecemeal Growth,一次只長一小塊。Alexander 反對「總體規劃一次蓋完」的城市開發,主張城市應該像生物一樣分片成長:每次只蓋一小塊,蓋完立刻讓真實使用回饋修正下一塊的計畫。總體規劃式的做法之所以失敗,是因為它強迫你在回饋最少的時候做出最多的決定。對應到軟體規格很直接:一次產出七份文件、完整任務計畫,就是「一開始就把所有設計定完」——大量規格在沒有任何實作回饋的情況下被寫死,錯誤要到實作階段才暴露,那時所有文件都要跟著改。AI Coding 社群的 Loop Engineering(agent 的小步迭代迴圈)與 SPEX-SDD 的 Slice 模式,都是這一格的實例。

第四格是 Better Form,產出物逐步逼近對的形狀。前三格合起來構成一個迴圈:依循 pattern language 長一小塊、診斷不合身、修正後產出更好的形——然後把這次學到的東西寫回 pattern language,讓下一塊長得更對。這個回寫動作是生成理論最常被忽略、卻最關鍵的一步:語言本身也要演進。

生成理論迴圈與 SPEX-SDD 對應 這四格首尾相接:語言指導成長,成長被拿去診斷,診斷結果修正語言本身。少了最後這一步回寫,整個系統就只會單向輸出,不會跟著學。

這張圖也很適合拿來做檢查:面對任何一套 AI 開發方法,都可以逐格問——哪一格有實際產物,哪一格還停在口號。下一節就是拿它來檢查那份草稿。

六項工程指標

Uncle Bob 的清單只有六項,真正值得看的不是名稱,而是每少一項會漏掉什麼。

# 指標 回答的問題 沒有它會發生什麼
1 單元測試 邏輯對不對 行為錯誤
2 BDD/Gherkin 驗收測試 做的是不是使用者要的 做出正確但沒人要的東西
3 測試覆蓋率 有多少程式碼被測到 大片未測區靜默腐爛
4 突變測試 測試本身有沒有偵錯能力 綠燈的假測試——AI 最常見的產物
5 循環複雜度/模組大小 程式碼有沒有在膨脹 沒人讀的 code 逐步不可維護
6 依賴結構 架構邊界有沒有被穿透 BC 融化,DDM 變成一張過期的圖

單元測試回答「這段邏輯在這些輸入下給出對的輸出嗎」,是六項裡最沒有爭議的一項,也是其他指標的地基——沒有 test runner,覆蓋率、突變測試都無從談起。SPEX-SDD 對它的額外要求是反對無效測試:測試必須驗證行為而非實作細節,不可為了覆蓋率而寫沒有斷言力的測試,這個要求由指標 4 客觀化。

單元測試驗證「做對了」,BDD 驗證「做的是對的東西」。SPEX-SDD 在這一項做得比 Uncle Bob 描述的更完整:雙層 Gherkin。每個 Scenario 同時有兩層——DSL-Level 用業務語言寫,例如「假設 使用者的配額足夠/當 送出訊息/那麼 系統應接受並回覆編號」,這一層是人類審查的責任落點,業務對不對只有人能判斷;ISA-Level 帶具體 API 路徑、HTTP method、DTO 斷言,例如「Given POST /v1/messages returns 202…」,這一層給機器執行,端點必須對照實作盤點的實際路由清單。兩層之間由 Feature Mapping 表對齊,同一個 SC-xxx ID 貫穿——出包時可以問「是 DSL 層漏寫了,還是 ISA 層沒攔到」。

測試覆蓋率回答「有多少程式碼至少被執行過一次」,價值是找出完全沒被碰過的暗區:覆蓋率 60% 意味著有 40% 的程式碼連一次都沒跑過,那裡的任何錯誤都不可能被測試發現。但它有個著名的陷阱:覆蓋率量的是「跑過」不是「驗過」,一個沒有任何斷言的測試可以把覆蓋率灌到 100%。把覆蓋率當 KPI 的團隊會得到大量「執行程式碼但不驗證任何東西」的測試,AI 尤其容易產出這種測試,因為這樣最快讓數字達標——這正是指標 4 存在的原因。

突變測試問的是最根本的問題:「測試通過」這個訊號本身可信嗎?推理只有四步:沒有人逐行讀 AI 寫的 code;那麼「測試有沒有過」是唯一訊號;但測試也是 AI 寫的;所以需要一個東西來驗證測試本身有沒有偵錯能力。突變測試就是那個東西:工具會系統性地在程式碼裡注入小錯誤(變異體,mutant)——把 > 改成 >=、把 return true 改成 return false、刪掉一行條件——然後跑測試。測試紅了,變異體被「殺死」;測試還是綠的,它「存活」,代表測試對這種錯誤完全盲目。殺死比例就是 mutation score,低於 40% 通常表示測試的偵錯能力很弱,覆蓋率再高也不能讓人放心。SPEX-SDD 建議的門檻是一般程式碼 ≥ 50–60%,Domain 層 ≥ 75–80%,也讓「反對無效測試」從自我約束升級為客觀分數。

循環複雜度(cyclomatic complexity)量一個函式裡有多少條獨立執行路徑,模組大小量檔案/類別/方法的行數,兩者是成本最低的警訊:AI 在既有函式裡加分支永遠比重構容易,每次只加一點點,測試照樣過,只有這兩個數字會一路變大。建議門檻:method 複雜度 ≤ 10(硬上限 15)、檔案 ≤ 500 行、class ≤ 300 行、method ≤ 50 行,Domain 層更嚴。

依賴結構和實際程式碼隔得最遠。DDM 定義了 Bounded Context,再用 Context Map 畫出模組之間允許的依賴方向,但 AI 讓 BC-A 直接引用 BC-B.Infrastructure 時,一張圖不會有任何反應。依賴結構 gate 把 Context Map 的允許方向編譯成可執行斷言(ArchUnit、NetArchTest、dependency-cruiser、go-arch-lint 之類的工具),未列出的方向一律禁止。它還有個實務優點:不需要任何既有測試就能跑——在已有程式碼、但缺少測試的既有專案(棕地專案)裡,它通常是最值得先建立的第一道關卡。

六項指標的兩側分工 左邊守行為對不對,右邊守結構有沒有跑掉;指標 4 例外,它量的不是產品本身,而是其他測試抓不抓得到錯。草稿診斷的結果:左邊分數很高,右邊幾乎是空的。

Uncle Bob 貼文的反對者有一點說得對:高覆蓋率不代表邏輯正確,AI 寫的測試同樣會漏邊界條件,六項指標全綠也不保證系統符合客戶真實需求。所以 SPEX-SDD 強制 harness.yaml 必須有 uncovered_risks 欄位,明講機器把關的盲區——高風險領域(金流、權限、租戶隔離)仍必須有人審 DSL scenario。指標是必要條件,不是充分條件,這件事誠實列出來,是這套方法一貫的做法。

拿兩把尺回頭量 955 行草稿

SPEX-SDD 不是從零寫起的。它的前身是一套已經演進到 v5 的技術文件方法(generate-tech-docs 系列),草稿共三個檔案 955 行:SKILL.md 291 行、validation-rules.md 193 行、document-specs.md 471 行。逐行讀完,再用前面兩節的標準各量一次,重新設計的方向就清楚了。

先看生成理論四格對位:

生成理論 草稿的對應物 成熟度
Pattern Language canonical backbone、40 個 SKILL.md、forbidden_actions ★★★★☆
Harness / Misfit Detection acceptance_command、review-security / review-ui-ux ★★☆☆☆
Piecemeal Growth agent_execution_plan.json 的 depends_on ★★☆☆☆
Better Form(演進) 人腦手動修訂(無機制化回饋) ★☆☆☆☆

草稿在「設計知識」這一格已經相當成熟——backbone 是嚴謹的 ID registry,任務有依賴圖,有禁止事項。但越往迴圈的後半走越空:診斷只有零散的 acceptance command 與兩個 review skill;增量成長只有任務層級的依賴,沒有規格層級的分片;演進則完全依賴人腦——AI 違規之後,除非有人記得手動改文件,否則同樣的違規會永遠重演。

再看六項指標逐項清點:

指標 草稿現況
單元測試 spex-gen-unit-tests,且明確反對無效測試
Gherkin BDD 雙層 Gherkin(DSL 中文業務層 / ISA 英文技術層),比 Uncle Bob 描述的更完整
QA 流程 spex-gen-test-docs-v5 / spex-gen-e2e-docs-v5 / review-security / review-ui-ux
測試覆蓋率 acceptance_command 只跑測試,沒有任何門檻
突變測試 完全沒有
循環複雜度 / 模組大小 沒有
依賴結構 DDM 定義了 BC 與 Context Map,但沒有任何機器驗證

草稿在「行為正確性」這一側做得比 Uncle Bob 描述的還完整,在「結構品質與測試有效性」那一側幾乎是空的。而空掉的這一半,正是「沒人讀 code」時最容易腐爛的部分——測試會過,但複雜度爬升、模組肥大、依賴反向,不看就永遠不知道。

理論對位給出方向,逐行閱讀則抓到了四個具體的、可指認行號的問題。

第一個:驗證規則全部是「概念上檢查」。validation-rules.md 第 7 行原文寫著「概念上檢查下表並修正所有缺口」,整份 193 行的驗證規則,沒有一條是機器跑的,全靠 AI 讀完後回報「我檢查過了」。但寫文件、做檢查、宣告合格的都是同一個模型,這種流程很難算是真正的驗證。

第二個:禁止事項沒有偵測器。

forbidden_actions: ["不可硬編 tenantId", "不可繞過 outbox"]

純文字,寫得很好,但沒有任何機制會在 AI 違反時告訴你。這種禁令有沒有效,全看 AI 當下有沒有想到它——效果是隨機的。

第三個:UI/UX 與工程品質的篇幅嚴重失衡。SKILL.md 291 行裡,UI/UX 設計水準佔了相當比例(設計水準判準、避免 AI 生成感、mockup 製作方式),工程結構品質是 0 行,這些內容在 document-specs.md 又重複了一遍。SKILL.md 是每次觸發都會整份灌進 context 的東西,注意力被 UI 細節吃掉,代表其他面向被排擠。後來的修正方式是把 UI 判準集中到 document-specs.md 統一管理,SKILL.md 只留原則與指引。

第四個:過期引用,這是第一個問題的實證。草稿裡 SKILL.md 有三處、validation-rules.md 有一處仍指向 spex-gen-test-docs-v3,但 spex-gen-test-docs-v5 早已存在。如果驗證是機器跑的,這種斷鏈第一天就會被抓到。一套強調追溯性的方法,自己的內部引用卻悄悄過期了兩個版本,這件事本身就證明了自我宣稱不可信。

955 行草稿被逐行讀完,六項指標裡缺席四項(含 QA 拆分算七項),草稿裡由機器執行的驗證規則是 0 條,過期引用有 4 處。

診斷做完後,缺口剩下四個:補上 Harness(含突變測試與依賴結構)、讓驗證真的可以執行、改成增量生成、替規範補上演進機制。這四件事,就是接下來十個設計決策要處理的起點。

規範若沒有偵測器,就等於沒有寫:十個設計決策

十個決策不是彼此獨立的清單。工程品質關卡(Harness,一份把品質指標收攏成可執行檢查項目的產物)要先確立,規範可測化的原則才跟著補上,而最後把關的是一支真的會執行的規格檢查程式(validator)——沒有它,前面的規則都只是寫在紙上,跟沒寫過一樣。

核心命題:規範若沒有偵測器,就等於沒有寫

十個決策都從同一句話出發:

規範若沒有偵測器,就等於沒有寫。 ——SPEX-SDD 核心命題

這裡的「規範」泛指一切希望系統維持的性質:禁止事項(不可硬編 tenantId)、結構約束(BC 之間只能經 domain event 溝通)、品質門檻(Domain 覆蓋率 ≥ 90%)、非功能需求(NFR,例如 p95 回應時間小於 200ms)。「偵測器」(detector)則是任何會在規範被違反時主動變紅的機制:一條程式碼檢查規則(lint rule)、一個架構測試、一個覆蓋率門檻,或後面會提到的 validator。

一條規範有多少效力,很大程度取決於違反時會不會被發現。人類團隊裡,code review 至少提供了一定的機率,或高或低但不是零。沒人讀 code 的情況下,沒有偵測器的規範就算被違反,也幾乎不會被發現——效力等於零,跟沒寫過一樣。

這句話容易被誤讀成兩種意思,但都不是它要說的。一是「測不了就刪掉」:暫時無法自動檢查的規範不一定不重要,通常只是還沒做好偵測器,應該先移到建議事項(advisory_notes)並在問題紀錄裡登記待辦,重要的話再排一項開發任務去補檢查——直接刪掉反而會流失知識,後面決策二會細談。二是「機器把關等於萬無一失」:偵測器只能抓可以明確判定的違規,業務規則是否符合客戶真實需求、錯誤訊息語氣是否合宜,仍然要人判斷。所以 uncovered_risks 這個欄位的作用,就是把機器管不到的風險老實列出來,免得讀者以為所有問題都已經有工具把關。

十個決策整理起來大致是這樣:

# 決策 回應的問題
1 Harness 升為第一級產物,且在文件之前生成 結構品質沒有任何把關;後續文件需要引用 gate,不能各自發明
2 規範可測化原則 純文字禁令效力為零
3 依賴結構 gate 從 Context Map 機械式推導 領域模型文件是一張會過期的圖
4 突變測試不可省 測試也是 AI 寫的,訊號本身要被驗證
5 門檻訂在「現況 + 合理增量」 第一天全紅的 gate 一週內就會被關掉
6 寫一支真的 validator 誰來檢查這些規則有被遵守?不能又是 AI 自己
7 Slice 增量模式 一次產全套等於 Big Design Up Front
8 Misfit Log 外迴圈 規範演進不能只靠人工事後歸納
9 Pattern Forces(Why / When NOT) 只有命令沒有理由的規範,AI 無法推理
10 責任歸屬 出包時連寫的人都沒讀過,責任歸誰

決策一:先建立 Harness,再寫其他文件

第一個決策包含兩件事:新增 _HARNESS.md(人讀)與 harness.yaml(機器讀),並把它們排在其他規格文件之前生成。這個順序不是流程上的小細節,它決定了後面的文件能不能引用同一套品質關卡。

草稿階段的品質把關通常散落各處:acceptance command 藏在任務裡、review 靠兩個獨立的檢查、覆蓋率門檻根本不存在。把它們收攏成第一級產物,意味著三件事同時成立:它有自己的檔案與 ID 體系(GATE-xxx)、它被 validator 檢查(缺了 _HARNESS.md 直接判定失敗)、它出現在每次的最終回報裡。

排在前面的理由更實際:至少有四份後續產物需要引用 Harness 的內容——SRS 裡每條可測量的 NFR 必須綁一個 GATE-xxx(「p95 < 200ms」必須指向真的會量它的 gate);STO 的測試充分性判準要引用 gate 的門檻與指令,不能另訂一組數字;DEV 裡「本機怎麼跑每個 gate」一節,指令必須跟 harness.yaml 完全一致;agent_execution_plan.json 裡每個 task 也要填 gate_ids。如果 Harness 排在文件之後才生成,這四份產物寫作當下根本沒東西可引用,只能自己編一組數字,然後跟事後才出現的 harness 各自演化——這正是「文件各自發明、缺乏單一事實來源」的老問題換個形式重演。順序反過來,Harness 是源頭,其餘產物是引用者,引用鏈才成立。

有一條規則刻意寫得很硬:六項工程指標裡,每一項要嘛設有關卡,要嘛在 not_applicable 寫明不適用的理由,不能什麼都不寫。原因是「沒提到」和「評估後認為不適用」在文件上長得一模一樣——一份沒有突變測試章節的 harness,可能是評估過覺得這是純腳本專案不值得做,也可能是寫的人根本忘了有這回事。讀的人分不出來,過半年連寫的人自己也分不出來。強制每一項都要表態,就是不讓「漏掉」變成合法狀態,規格檢查程式會用 harness.metric_missing 錯誤把這種缺漏抓出來。

_HARNESS.md 大致包含這幾個部分:目的與責任分工(人審什麼、機器審什麼、AI 產出什麼)、六項指標總表(指標 × 工具 × scope × 基線 × 本期門檻 × 目標值 × blocking × 狀態)、每道關卡的細節(ID、量什麼、指令、門檻依據、失敗時怎麼辦)、偵測器清單與規範對照表、advisory_notesuncovered_risks、接進 CI 的方式(哪些擋流程、哪些只報告、哪些排 nightly)、尚未完成的關卡及對應任務、版本歷程。harness.yaml 是同一份內容的機器可讀版本,兩者必須一致。

決策二:規範可測化原則

決策一建立了關卡,決策二決定關卡要管什麼:每一條規範都要寫明由哪個偵測器負責,否則違反時不會有人知道。

具體做法是把禁止事項欄位 forbidden_actions 從單純的文字清單改成結構化資料。目的不是把格式弄複雜,而是逼每條禁令交代「誰來抓、為什麼、何時例外」:

# 舊:一個問題都不用回答
forbidden_actions:
  - "不可在 handler 內硬編 tenantId"

# 新:必須回答「誰來抓」「為什麼」「何時例外」
forbidden_actions:
  - rule: 不可在 handler 內硬編 tenantId
    detector: GATE-ARCH-03
    why: 租戶隔離被繞過時資料外洩無法由功能測試發現,只能靠靜態規則攔截
    when_not: 單租戶部署且資料表無租戶欄位時不適用

規格檢查程式對這件事沒有模糊空間:只有一句話的禁令直接判定失敗(task.plain_forbidden);偵測器欄位指向不存在的關卡,也判定失敗(task.orphan_detector)。這套體系裡沒辦法寫一條沒人會檢查的禁令——要嘛替它指定一個真的偵測器,要嘛承認它目前只能當建議。

規則一套上去,很多寫得好聽、實際上沒有效力的禁令就會現形,常見的像是「錯誤訊息語氣應與產品調性一致」「不可過度設計」「保持程式碼簡潔」——每一條都無法用機器判定。處理方式刻意設計成不是刪掉,而是三步:移到 advisory_notes,標 detector: none 與替代把關方式(如 UI review 抽查);在 misfit-log 開一筆「待建偵測器」;若該規範重要,把「建立偵測器」本身排成一個 TASK-HARNESS-xxx。一條抓不到的規範,問題通常出在還沒建偵測器,而不是這條規範不重要。直接刪掉會流失知識;留在 forbidden_actions 裡則會稀釋整個清單的效力,因為 AI 分不出哪些是真的會被抓的。advisory_notes 是介於兩者之間比較誠實的位置:我們在乎這件事,但目前只能靠人抽查。

偵測器不一定是獨立程式,也可能是既有工具的一條規則。按建置成本從低到高大致分幾類:lint rule 或靜態規則適合抓語法層面的禁止模式(window.confirmno-restricted-globals);架構測試適合抓依賴方向與命名空間邊界(BC-A 不可依賴 BC-B.Infrastructure);自訂 analyzer 適合抓語意層面的危險模式(SQL 字串拼接租戶條件);既有 gate 覆蓋的測試適合抓行為層面的繞過;門檻型 gate 則抓量化指標的劣化,像複雜度上限、覆蓋率下限、mutation score。不過有些 UI 規範終究只能靠人看——多數綁得上 lint rule,但「多個 primary action 競爭」這類判斷綁不上,只能放進 advisory_notes 並登記 misfit-log。這是設計上的誠實,不是漏掉。

決策三:依賴結構關卡要從 Context Map 直接推導

這一段最容易看出文件如何跟現實脫節。規格骨幹定義了 BC(Bounded Context,DDD 裡畫業務邊界的單位)與 Context Map(描述各 BC 之間依賴關係的圖),領域模型文件也畫出漂亮的關係圖——但沒有任何東西阻止 AI 讓 BC-A 直接 using BC-B.Infrastructure。兩個月後 BC 邊界就守不住了,而那張圖還掛在文件裡,看起來一切正常。

推導規則只有三步,不可自由發揮:讀 backbone(整套規格的骨幹文件)的 Context Map 關係表,取出每組 (from_bc, to_bc, relationship);未列出的方向一律視為禁止——這是白名單制,不是黑名單制;依程式語言產生檢查規則,並用 source_of_truth 欄位指回規格骨幹的對應章節。「機械式推導、不可自由發揮」這個限制本身就是設計的一部分——如果允許在寫架構測試時自己「補充」幾條文件裡沒有的約束,harness 與 backbone 就開始各自演化,半年後不知道哪邊才是事實來源。

同一條「BC-MSG 不得依賴 BC-QUOTA.Infrastructure」,在幾個生態系裡寫法不同。Java 用 ArchUnit:

@ArchTest
static final ArchRule bc_msg_must_not_depend_on_quota_infrastructure =
    noClasses().that().resideInAPackage("..msg..")
        .should().dependOnClassesThat().resideInAPackage("..quota.infrastructure..");

.NET 用 NetArchTest:

var result = Types.InAssembly(typeof(MessageAggregate).Assembly)
    .That().ResideInNamespace("App.Msg")
    .ShouldNot().HaveDependencyOn("App.Quota.Infrastructure")
    .GetResult();
Assert.True(result.IsSuccessful, string.Join(", ", result.FailingTypeNames ?? []));

TypeScript 用 dependency-cruiser(寫在 .dependency-cruiser.cjs):

forbidden: [
  { name: 'no-circular', severity: 'error', from: {}, to: { circular: true } },
  { name: 'bc-msg-not-to-quota-infra', severity: 'error',
    from: { path: '^src/msg' }, to: { path: '^src/quota/infrastructure' } },
]

對應的 harness.yaml gate 長這樣:

- id: GATE-ARCH-01
  metric: dependency_structure
  rule: "BC-MSG 不得依賴 BC-QUOTA.Infrastructure;BC 之間僅可經 domain event 溝通"
  source_of_truth: "_SPEC_BACKBONE.md#context-map"
  command: "dotnet test --filter Category=Architecture"
  threshold: "all pass"
  blocking: true
  schedule: per_commit

這一步讓領域模型文件不再只是一張可能過期的圖,而是有強制力的約束。它還有個實務上的好處:架構斷言不需要既有測試就能跑。覆蓋率需要測試、突變測試需要大量測試,但依賴檢查只需要原始碼本身。所以在零測試的棕地專案裡,它是投資報酬率最高的第一個 gate——在慢慢補測試的那幾個月裡,至少架構不會繼續變差。它不需要測試就能跑,分析的是原始碼的 import/using 結構,零測試專案第一天就能啟用;它是硬性規則不設百分比,零循環依賴、層級單向、BC 間僅允許 domain event 或 ACL,過或不過沒有灰階;它是白名單制,Context Map 沒列的方向就是禁止,新依賴必須先改文件、再改程式碼,順序由 gate 強制。

決策四:突變測試不可省

新增的指標裡,突變測試(mutation testing,故意在程式裡注入小錯誤,看測試能不能抓到)影響最大。它有必要,但也真的慢、真的花資源,所以才需要一個 schedule 欄位控制執行時機與範圍。

為什麼非它不可,推理其實只有四步:

突變測試的必要性邏輯鏈

沒有人逐行讀 AI 寫的 code,於是「測試有沒有過」變成唯一的訊號,但測試也是 AI 寫的,所以需要一個機制去驗證測試本身的偵錯能力。前三步都成立的話,第四步就是必然的結論。

突變測試的機制很直接:把 > 改成 >=、把 return true 改成 return false,注入各種變異,看測試殺不殺得死。它在整套體系裡扮演兩個角色。第一個角色是讓「反對無效測試」有客觀分數——過去反對淺層測試、覆蓋率導向測試、過度 mock,只能靠 AI 自我約束,實際效果有限;mutation score 把它變成客觀分數,不管測試看起來多漂亮,殺不死變異就是殺不死。判讀基準大致是:一般程式碼 ≥ 50–60% 算合格線,多數正常寫的測試套件落在這區間;Domain 層要求 ≥ 75–80%,因為核心業務邏輯幾乎沒有 I/O,沒理由測不到位;低於 40% 就需要檢查,通常代表測試的偵錯能力偏弱,不能只看覆蓋率判斷品質。

第二個角色是回應「高覆蓋率不代表正確」這條批評——AI 寫的測試同樣會漏邊界條件,這是外界對 AI 寫測試最有力的質疑之一。突變測試不能保證測試完美,但能客觀量出測試漏掉小錯誤的程度,讓「測試品質」變成可以量的東西,而不是憑感覺相信。

成本是它最大的問題:每一隻變異體都要跑一輪測試,整個 repo 全開可以讓 CI 變成幾小時。所以 harness.yaml 給每個 gate 都設計了 schedule 欄位,突變測試的標準配置像這樣:

- id: GATE-MUT-01
  metric: mutation_score
  scope: "src/Domain/**"          # 先只對 Domain 層開
  command: "dotnet stryker --threshold-break 70 --project Domain.csproj"
  threshold: ">= 70"
  baseline: "not_measured"
  status: to_be_built             # 工具尚未安裝 → 必須有對應 TASK
  blocking: false
  schedule: nightly               # PR 只跑受影響模組增量;完整跑排 nightly

scope 先限縮在 Domain 層,那是變異最有意義的地方;PR 或日常只跑受影響模組的增量突變;完整跑排 nightly,不擋 commit;而且要等測試量足夠才有意義,所以在零起點的專案裡,這個 gate 排在啟用順序的最後面,不是第一個。

決策五:門檻訂在「現況 + 合理增量」,不是理想值

門檻一旦訂錯,團隊很快就會開始繞過 gate,最後讓整套 Harness 失去作用。這個過程通常是這樣發生的:導入那天,團隊覺得「覆蓋率就該 90%」,門檻直接設 90%,但現況其實是 45%。第一天所有 PR 全紅,工程師開始繞過,先 merge 之後再補。第三天有人發現把 gate 改成 report-only 可以「解決」紅燈。一週內,gate 被關掉或永久 report-only,儀表板上它還在,但沒有人看。半年後新人問「我們不是有覆蓋率門檻嗎」,已經沒有人記得為什麼它不動了。

一個被關掉的 gate 比沒有 gate 更糟。它會製造「我們有在把關」的錯覺——沒有 gate,至少大家知道沒人把關;關掉的 gate 會讓所有人以為有人在把關,實際上沒有。

正確做法是先量再訂。實作盤點裡新增了「品質基線量測」這一項:實際跑一次覆蓋率與靜態分析,記錄當下數值,包括 line coverage、最大或中位循環複雜度(cyclomatic complexity,衡量程式碼分支複雜程度的指標)、最大檔案行數、是否存在循環依賴、既有測試數。然後每個 gate 要有四個欄位:

欄位 意義 範例
baseline 目前實測值——不是猜的,是跑出來的 78%
threshold 本期門檻:現況 + 合理增量,紅燈會真的擋人 ≥ 80%
target 理想值放這裡,不放 threshold ≥ 95%
期程 什麼時候到 target 2026-Q4

基線是 not_measured 的 gate 必須是 report-only——沒量過就設 blocking 門檻,等於拿猜的數字擋人,validator 會檢查 baselinethreshold 欄位是否存在。門檻不是訂了就不能動,同一 gate 連紅 3 次就該檢討是不是訂得不合理,但調整必須同時更新 baselinethresholdtarget、期程四個欄位,並記入版本歷程——不要偷偷把門檻調低就算了,否則半年後沒人知道為什麼標準變鬆了。留下紀錄的調整是正常動作,不聲不響地放水是反模式。第一次基線量測通常就很有價值:多數團隊會發現覆蓋率比想像低 20–30%、有幾個破千行的檔案、或某兩個模組互相引用——光是量測結果就已經有用。

決策六:寫一支真的規格檢查程式(validator)

這一部分確實多花力氣,但省不了。前面五個決策最後都會碰到同一個問題:誰來檢查這些規則有被遵守?如果答案還是「AI 自己檢查」,那整套設計就只是把自我宣稱換了一組更長的清單。

scripts/validate_spec.py 是這個角色的答案——688 行、只用 Python 標準庫寫的結構驗證器。它讀取輸出目錄裡的機器可讀產物(spec_backbone.yamlharness.yamltraceability.jsonagent_execution_plan.json),檢查的東西大致包括:ID 不可重複也不能引用不存在的 ID;Must 等級的業務需求(BR)要被功能需求(FR)或驗收情境(SC)覆蓋;FR 要對回 BR、模組、SC 或 TASK(或明確標 coverage: deferred);SC 要對回使用者故事(US)、雙層 Gherkin、test_level、test_file、dev_tasks;TASK 要對回 SC、test_first、implementation_targets、可執行的 acceptance_command;還有六項工程指標是否覆蓋齊全、每條規範的 detector 是否真的存在於 harness.yaml、可測量 NFR 是否綁了 gate、任務依賴有沒有循環(用 DFS 找環並印出完整路徑)、backbone 與 execution plan 與 traceability 三方 ID 是否同步。加上 --repo 模式,還會對照實際檔案系統檢查路徑存不存在。

執行流程有一條寫得很硬:必須實跑,輸出附在最終回報裡,有 FAIL 不得回報完成。「我已在概念上檢查過」不算通過。離開碼設計成 0 代表無 FAIL、1 代表有 FAIL、2 代表找不到必要產物,方便直接接進 CI。

寫這支程式的過程有個插曲值得記一筆。第一版用 PyYAML 解析 backbone 與 harness,實跑後發現這台機器的三個 python3 都沒有 yaml 模組。這暴露了一個設計問題:多一道安裝門檻,就多一個跳過 validator 的理由。使用者遇到 ImportError 的第一反應通常是跳過驗證,不是去裝套件。驗證器最重要的不是功能強大,而是完全沒有安裝門檻——只要有一點安裝阻力,「跳過驗證」就會變成大家的選擇,而整套方法的前提正是驗證不能跳過。解法是加一個內建的 YAML 子集解析器當 fallback,處理 2 空格縮排的 map/list、key: value、行內 [a, b]{a: b}、註解、引號字串,夠用於這套 skill 產出的格式,不支援 anchor 與多行字串,遇到會提示裝 PyYAML。有 PyYAML 就用 PyYAML,沒有就自動降級,不中斷。

validator 寫完之後,還得驗證它自己有沒有用。準備了兩組測試樣本:一組內容正確,另一組刻意放進六種錯誤。如果 validator 對破壞品也回報通過,它就只是另一種形式的自我宣稱。良品跑出來是這樣:

[WARN] harness.to_be_built: 待建置的 gate:GATE-MUT-01(需有對應 TASK)
--- FAIL 0 / WARN 1 / PASS 5 ---
結構驗證通過。語意檢查(UI 設計水準、實作對齊、文件可讀性)仍須依 validation-rules.md 人工進行。
EXIT=0

注入六種錯誤的破壞品跑出來是這樣:

[FAIL] harness.metric_missing: 六項工程指標缺少 mutation_score
[FAIL] harness.no_uncovered_risks: harness.yaml 缺少 uncovered_risks
[FAIL] task.no_gate: TASK-MSG-001 未列出 gate_ids(必填)
[FAIL] task.plain_forbidden: TASK-MSG-001 的 forbidden_actions 仍是純文字
[FAIL] plan.cycle: 任務依賴形成循環:TASK-MSG-001 -> TASK-B -> TASK-MSG-001
[FAIL] plan.extra_task: TASK-B 在 execution plan 但不在 backbone
[FAIL] trace.orphan_id: traceability.json 出現 backbone 沒有的 ID:SC-MSG-99-99
--- FAIL 7 / WARN 2 / PASS 3 ---
EXIT=1

六種刻意注入的錯誤(純文字 forbidden_actions、缺 gate_ids、刪掉突變測試 gate、刪掉 uncovered_risks、任務依賴循環、traceability 孤兒 ID)全部抓到,另外還帶出兩個沒刻意注入的(plan.extra_tasktrace.missing_sc)。第一次跑良品時其實出現過一個誤報:traceability 裡的 GATE-COV-01 被判為孤兒 ID,因為 gate 定義在 harness.yaml 不在 backbone。後來修掉了,但這個誤報反而說明 validator 夠嚴格,連自己的測試資料都會抓——標準寧可先嚴再放鬆,不要先鬆再收緊。

需要說清楚的是它的邊界:validator 能確認 SC-01 對應到 BR-01,不能確認 SC-01 真的驗證了 BR-01 的意圖。所以驗證永遠是兩層,機器跑結構,人或 AI 依規則另外跑語意——UI 設計水準、實作對齊的正確性、規範 forces 是否成立、統計數字防呆,這些仍然要人判斷。

決策七:用 Slice 模式分批展開規格

一次產出全套文件和完整任務計畫,很接近 Big Design Up Front(大霹靂式一次到位設計),也跟 piecemeal growth(逐步生長)的想法相衝突。Slice 模式改成讓規格一小塊一小塊長出來。

問題出在哪:一次生成整套規格,等於一次產出大量未經任何回饋驗證的內容。規格的錯誤要到實作階段才會暴露——某個 API 設計在真的寫 handler 時才發現不合理,某個資料表在真的下 migration 時才發現漏了欄位。而那時修改成本已經被放大,文件、traceability、task plan 全部要跟著改,改動半徑是整個文件套件。Slice 模式把改動半徑縮小到一個 slice:以單一 BR、模組或 SCREEN 為單位,增量長 backbone、產任務、跑 gate、回寫狀態。寫下的每一小塊規格,很快就會被實作與 gate 檢驗,錯了也只錯一小塊。

具體規則有五條。範圍要由使用者指定(一個 BR、一個模組、或一組 SCREEN),未指定時從實作盤點挑出風險最高且未覆蓋的一項並提議;首次執行仍要建骨架——系統邊界、角色、模組清單、BC、命名慣例、Harness gate 都要建立,但只有本 slice 的 DE/BR/FR/US/SC/TASK 完整展開,其餘模組在 registry 標 status: outline;既有文件只新增本 slice 的章節與 registry 列,不重寫既有內容也不動既有 ID;每個 slice 結束時必須跑 validator 與 Harness gate,並在 slice registry 記錄 slice ID、範圍、狀態、gate 結果、完成日期;禁止為了「文件看起來完整」而把未展開的模組寫成細節——outline 狀態必須誠實標示,編造未驗證的規格比留白更有害。

full 模式沒有被廢掉,這其實是這條規則自己的 When NOT。Slice 適合規格會被實作反覆修正的情境:棕地系統、模組數超過 8 的大系統、長期迭代的產品,棕地與大型系統預設走 slice。但投標、稽核、驗收需要一次交付完整套件時,還是用 full;系統極小(3 個模組以內)時,slice 的管理成本反而大於效益。slice 不是永遠比較好,連這條規則自己都寫了適用邊界。

決策八:用問題紀錄(Misfit Log)讓規範跟著改

原先最欠缺的是一套「從失敗中學習」的固定流程。規範如果只靠人事後整理,同樣的問題很容易再發生。misfit-log.md 想解決的就是這件事,它也是整套規格系統唯一的學習輸入。

事件分三類。gate-fail 來自 Harness gate 紅燈,處置是修程式碼,但同一 gate 連紅 3 次就要檢討門檻是否不合理,或任務是不是拆得太大。rule-violation 來自 AI 產出違反已知規範,處置是修產出,同類違規累積 3 次就要檢討規範敘述是否不清楚,或需要更強的 detector。第三類 undetected,人工發現但沒有任何 gate 抓到,優先度最高——每一筆都必須產生新增 detector 的行動項。

為什麼 undetected 排最高:前兩類事件至少代表既有 gate 有抓到問題,第三類直接指出機器把關的盲區。一個問題今天靠人工抓到了,如果不補 detector,下次同樣的問題還是只能靠人工——而整套方法一開始就不相信「人工會一直記得抓」這件事。判斷準則只問一個問題:這個問題是被 gate 抓到的,還是我人工發現的?人工發現的一律記 undetected,不管當下有沒有時間補 detector。先記下來不花什麼成本,忘掉的代價才大。

記錄格式大概是這樣:

| ID | 日期 | 類型 | 來源 | 描述 | 相關 ID | 處置 | 狀態 |
|----|------|------|------|------|---------|------|------|
| MF-0001 | 2026-08-01 | gate-fail | GATE-COV-01 | Domain 覆蓋率 84% 未達 90% | TASK-MSG-003 | 補 QuotaPolicy 邊界測試 | closed |
| MF-0002 | 2026-08-03 | rule-violation | code review | AI 在 handler 直接組 SQL 帶 tenantId | TASK-MSG-005 | 已修;GATE-ARCH-03 未涵蓋字串拼接 → 見 MF-0003 | closed |
| MF-0003 | 2026-08-03 | undetected | 人工發現 | 沒有任何 gate 能抓到字串拼接的租戶條件 | GATE-ARCH-03 | 新增 analyzer 規則(TASK-HARNESS-004) | open |

值得留意 MF-0002 與 MF-0003 的關係:同一次 code review 產生了兩筆記錄,一筆是 AI 違反了「不可硬編 tenantId」的規則違反,一筆是 GATE-ARCH-03 只檢查參數傳遞、不檢查字串拼接所以沒抓到。第二筆才是重點,它直接產生了 TASK-HARNESS-004 去補 analyzer 規則。這種同一事件產生雙記錄的模式,是這套方法處理類似狀況的標準做法。

同類事件累積到門檻,misfit-log 末段要產出規範修訂提案:

## 提案 RP-001:強化租戶隔離偵測

- 觸發:MF-0002、MF-0006、MF-0009(同類違規 3 次)
- 現行規範:「不可在 handler 內硬編 tenantId」,detector GATE-ARCH-03
- 失效原因:現行 analyzer 只檢查參數傳遞,不檢查字串拼接的 SQL
- 建議修訂:規範改為「租戶條件只能經 ITenantContext 取得」;
  detector 增加 SQL 字串掃描規則
- 影響範圍:harness.yaml GATE-ARCH-03、DEV 前後端規範各一節、
  agent_tasks.md 中 5 個 TASK 的 forbidden_actions
- 狀態:待採納

提案格式的每個欄位都有用意:「觸發」指回具體的 MF 編號,提案要有依據;「失效原因」分析現行 detector 為什麼抓不到;「影響範圍」列出採納後要改哪些檔案,讓修訂變成可以執行的工作項。misfit 累積成修訂、寫回規範,下一塊規格才長得更對。misfit-log.md 可以從只有表頭的空表起步,但不能省略——validator 會檢查它存在。沒有它,整個系統就只能單向輸出,沒有學習迴路。

決策九:規範必須寫出理由與例外(Pattern Forces)

每條規範除了寫「禁止什麼」,也要寫 Why(在什麼張力下成立)與 When NOT(何時不適用)。這組概念借用自 Christopher Alexander 的 pattern language,原文叫 Pattern Forces。AI 有了理由與邊界,碰到規範原句沒寫到的新情況時,才有機會依規範的用意判斷,而不是只做字面比對。

問題出在哪,舉個例子就清楚:規範寫「不可使用 EF Core」。AI 遇到的實際情境永遠比規範多樣——如果它想用的是 Dapper.Contrib 呢?Linq2Db 呢?規範沒提到。沒有 Why 的情況下,AI 只有兩種選擇:照抄表面形式(只擋字面上的 “EF Core”),或直接忽略,兩種都不是想要的結果。加上 Why 之後就不一樣了:「不可使用 EF Core——團隊統一用 Dapper,混用 ORM 會造成 transaction 邊界混亂」。現在 AI 遇到 Linq2Db 時可以推理:這也是另一套 ORM,同樣會造成混用,依規範精神應該避免。Forces 讓規範從一句指令,升級成可以推廣的 pattern。

完整的規範要包含四件事:Rule 說做什麼或不做什麼,這是不可少的;Why 說在什麼張力下這條規則成立,缺了 AI 在新情境就無法推理,只能字面比對;When NOT 說什麼情況不適用或有更好的替代,缺了規範容易被過度套用到不該套用的地方;Detector 說誰來抓,這是決策二談過的部分,缺了規範效力等於零。實際寫法像這樣:

rule_bindings:
  - rule: "不可在 handler 內硬編 tenantId"
    detector: GATE-ARCH-03
    why: "多租戶隔離被繞過時資料外洩無法由功能測試發現,只能靠靜態規則攔截"
    when_not: "單租戶部署且無租戶欄位時不適用"
  - rule: "Controller 必須薄層"
    detector: GATE-CX-02          # method LOC + cyclomatic complexity
    why: "業務邏輯上移到 Controller 會使其無法被 unit test 覆蓋,
          覆蓋率會假性下降或被 E2E 掩蓋"
  - rule: "不可使用 window.confirm 作為正式互動"
    detector: GATE-LINT-01        # eslint no-restricted-globals
    why: "原生 dialog 無法套用設計系統與 a11y 規格,
          且會阻塞事件迴圈與自動化測試"

這條規則自己也遵守自己訂的規則:極度機械性、沒有例外的約定(比如檔名大小寫規範)寫 forces 只是噪音,可以省略。validation-rules 的措辭是 Why 必寫,但極機械性約定可免;When NOT 在有已知例外時才必填。一條要求所有規範寫出適用範圍的規則,自己也寫了適用範圍。

決策十:責任歸屬

Uncle Bob 那篇貼文引發的社群投票是 49.9% 對 50.1%,幾乎完美的對半分裂。反對意見裡有兩條很難迴避,這套方法也必須正面回答。

第一條是「高覆蓋率不代表邏輯正確」——AI 寫的測試同樣會漏邊界條件,指標全綠不等於系統正確。這件事有兩個層面的回應。結構性的回應是決策四談過的突變測試,至少把「測試對小錯誤盲目的程度」變成可量測的分數,讓假測試無所遁形。誠實性的回應是 uncovered_risks 必填:harness.yaml 強制聲明明確不被任何 gate 覆蓋的風險(可以是空陣列,但 key 必須存在),_HARNESS.md 必須說明人工補償措施。舉例來說:

uncovered_risks:
  - "業務規則本身是否符合客戶真實需求——僅能由人審 DSL scenario"
  - "跨系統資料一致性——目前無端到端對帳 gate"

明講盲區,才不會讓人誤以為機器把關等於萬無一失。

第二條更難答:出包時連寫的人都沒讀過,責任歸誰?對完全沒有把關機制的團隊來說,這個問題確實很棘手。這套體系的做法,是把責任拆到可以修補的位置,關鍵是一張三層分工圖:

人類審查對象:  DSL-Level Gherkin(繁中業務語言)  ← 責任落點在這
機器驗證對象:  ISA-Level Gherkin + 六項工程指標
AI 產出對象:   程式碼                              ← 沒人讀,也不需要

出包時可以明確歸因到兩者之一。如果是規格漏了,那是人的責任——DSL scenario 沒涵蓋這個情境,修補動作是補 scenario,這本來就是人審查規格時該抓而沒抓到的。如果是 gate 沒攔到,那是 harness 的責任——規格有寫但機器沒抓到違反,修補動作是補 detector,並在 misfit-log 記一筆 undetected。兩種情況都有明確的修補動作,不會變成互相指責。這也再次說明為什麼 misfit-log 的 undetected 類優先度最高——它就是「gate 沒攔到」的正式登記處。檢討的目的是找出該補的洞,不是找人負責。

傳統流程出包後,常見的結論是「以後 review 仔細一點」。這種決議很難驗證,過幾週也容易被忘記。這套體系要求檢討至少落成一個具體的檔案變更:一個新 scenario,或一個新 detector。口頭決議會消失,檔案不會。

十個決策的因果鏈

先承認沒人讀 code,所以需要機器把關(決策一);把關要有實際效力,規範要可測、依賴結構要能推導、測試訊號本身要被驗證(決策二、三、四);門檻不能一開始就把人卡死(決策五);檢查者不能是被檢查者(決策六);成長要小步,不要一次到位(決策七);失敗要記錄下來,不能只靠人事後回想(決策八);規範要寫出理由,AI 才有辦法推理(決策九);出包要能歸因到具體的修補動作(決策十)。接下來就把這條鏈實際產出的東西一件一件攤開來看。

SPEX-SDD 到底產出什麼:從 backbone 到七份文件、UI 規格與品質關卡

十個決策定下來之後,接下來要把系統實際「長什麼樣子」講清楚:跑完一次生成之後,輸出目錄裡到底有哪些檔案、每份檔案在管什麼、彼此又是怎麼互相引用的。這一節本質上比較像規格書,可以照需要跳著看,不必從頭讀到尾。

產出目錄:16 個檔案在忙什麼

完整生成一次之後,輸出目錄(建議放在 docs/spec/)會出現下列檔案。編號 00 開頭的是骨幹(backbone,整個系統的唯一事實來源)與流程性產物,01–07 是七份正式文件,assets 底下放圖與畫面示意稿(mockup)。

順序 檔案 用途 誰看
00 _SPEC_BACKBONE.md Canonical ID registry;所有名稱、ID、模組、事件、Aggregate、故事、Scenario、測試、任務、錯誤碼、資料表、實作狀態、slice 記錄 全員;改任何東西前先看這份
00b spec_backbone.yaml 機器可讀的規格骨幹鏡像 自動化工具
00c traceability.json 需求到測試的機器可讀追溯圖 CI、下游 skill、AI agent
00d agent_tasks.md 原子化開發任務清單(人讀) 工程師、AI agent
00e agent_execution_plan.json 任務依賴、測試先行、實作目標、驗收指令、gate 綁定 AI agent
00f _HARNESS.md 工程品質關卡:六項指標、工具、基線、門檻、detector registry、CI 接線 技術負責人
00g harness.yaml 機器可讀的 gate 與 detector 定義 CI、validator
00h misfit-log.md gate 紅燈與 AI 違規記錄、規範修訂提案 技術負責人
01 {System}_01_需求規格書_v1.0.md SRS:BR、FR、NFR 與 gate 綁定 PM、需求方
02 {System}_02_領域模型文件_v1.0.md DDM:BC、Aggregate、Invariant、Context Map 架構師
03 {System}_03_功能規格書_v1.0.md FSD:API、資料表、Screen Contract 工程師
04 {System}_04_UserStory與驗收條件_v1.0.md USD:雙層 Gherkin;DSL 層是人類的責任落點 QA、驗收
05 {System}_05_系統架構描述_v1.0.md SAD:部署、災復、可觀測性、架構約束與斷言 架構師、維運
06 {System}_06_測試策略概要_v1.0.md STO:測試策略、Scenario-Test-Task 矩陣、harness 門檻 QA
07 {System}_07_開發指南_v1.0.md DEV:環境、專案結構、開發順序、本機怎麼跑 gate 工程師
assets assets/*.svgassets/*.png 架構圖、流程圖、UI mockup、狀態示意圖、報表版面圖 全員

產物之間怎麼互相引用

產出物引用關係

實作盤點是唯一的輸入,backbone 與 harness 是雙事實來源,七份文件與三個機器可讀檔全部從它們生成,不是各自另外發明。misfit-log 則反過來形成一條回寫的外迴圈——gate 紅燈與違規累積之後,會變成規範修訂提案寫回 backbone 或 harness。

這條鏈完整寫出來是:

實作盤點 → DDD 建模 → BDD 驗收規格 → TDD 測試先行 → Agent Task
    → DEV 實作 → Harness Gate → CI 驗證 → Misfit Log → 規範修訂
                                                ↑__________|
                                         最後兩段形成循環

沒有最後兩段,規格系統就只能單向輸出,沒辦法從失敗裡學到東西。前面提到的「Better Form 只有一顆星」的診斷案例,問題就出在缺這兩段。

產出之後,花三分鐘做這三件事就能大致判斷這批文件靠不靠譜:打開 _HARNESS.md,看六項指標是不是都有 gate 或不適用理由;打開 agent_tasks.md 的第一個 TASK,看它的禁止事項有沒有 Detector 欄;跑一次 validator,確認 FAIL 數是 0。

規格骨幹(Canonical Backbone)與機器可讀檔

Backbone 的正式名稱是 Canonical Backbone,canonical 在這裡指「唯一權威版本」——它是整個體系唯一的 ID 來源,文件、測試、gate 與 agent task 都得沿用裡面的名稱與 ID,不能各自另訂一套。

實作盤點:backbone 之前的必修課

如果系統已經有任何實作(前端、後端、DB、CI、pipeline),建立 backbone 之前必須先做實作盤點(Implementation Inventory),一共七項:

  1. Repo 與目錄佈局:每個相關 repo 的實際頂層結構。implementation_targets 必須以此為準,不可套用想像中的理想化佈局。

  2. 實際路由/端點清單:從路由註冊檔逐條列出。ISA Gherkin 與 TASK 引用的端點必須對照這份清單。

  3. 實際資料表清單:從 migrations 或 schema dump 逐表列出,包含 schema 前綴與結構事實(例如 embedding 是欄位還是獨立表)。

  4. 測試現況:實際存在的測試檔分佈、test runner 設定、CI 設定。acceptance_command 必須以此為準。

  5. 關鍵常數與預設值:門檻值、模型名、port、設定 key,從程式碼讀出實際值。

  6. 註解與現況的落差:程式碼註解宣稱的行為必須對照實際程式碼驗證,過時註解不能當事實來源。

  7. 品質基線量測:實際跑一次覆蓋率與靜態分析,記錄當下數值——這是 gate 門檻的依據。

盤點的鐵律 文件中每一項「現況宣稱」都必須能對應到盤點證據;每一項「目標設計」都必須明確標註尚未落地。已實作功能寫成待開發、或未實作功能寫成既定事實,都算驗證失敗。

Backbone 本身要定義的內容包括:系統邊界與外部 actor/system、角色;模組與 Bounded Context,以及標示允許依賴方向的 Context Map(不同模組之間允許怎麼互相依賴的關係表);Domain Events(DE-xxx)及其觸發條件與後續影響;BR/FR 表、Aggregates、Invariants、Policies、Failure Modes;User Stories(US-xxx)與 Scenario registry(SC-xxx);Scenario 的 TDD 屬性(test_level、test_type、test_file、dev_tasks);UI registry(SCREEN-xxx、routes、狀態矩陣、共用元件);Dev Task registry(依賴、測試先行、gate_ids、implementation_status);錯誤碼、權限鍵、API route groups、資料表 registry;平台表(outbox、inbox/idempotency、audit、lease/lock,未建者標規劃中);NFR registry(可測量者綁 GATE-xxx,否則寫 no_gate_reason);棕地系統必附的實作現況對照表,以及 slice 模式必附的 slice registry。

spec_backbone.yaml 長什麼樣子

機器可讀鏡像使用穩定 key。幾個最有代表性的段落:

context_map:                       # GATE-ARCH 斷言的唯一來源
  - from: BC-MSG
    to: BC-QUOTA
    relationship: customer_supplier   # 僅允許經 domain event / ACL

scenarios:
  - id: SC-MSG-01-01
    user_story: US-MSG-01
    br: [BR-01]
    fr: [FR-MSG-001]
    test_level: integration
    test_file: tests/integration/messaging/SubmitMessageTests.cs
    dev_tasks: [TASK-MSG-001]

dev_tasks:
  - id: TASK-MSG-001
    title: 實作 Message 接受流程
    implementation_status: partial   # done | partial | not_started
    existing_files:                  # done/partial 時必填
      - src/Application/Messaging/SubmitMessageHandler.cs
    test_first:
      - tests/integration/messaging/SubmitMessageTests.cs
    implementation_targets:
      - src/Application/Messaging/SubmitMessageHandler.cs
    new_files: []                    # 新檔案列此,validator 才不會誤報 path.missing
    acceptance_command: dotnet test --filter SC-MSG-01-01
    gate_ids: [GATE-UT-01, GATE-COV-01, GATE-ARCH-01]   # 必填
    forbidden_actions:
      - rule: 不可在 handler 內硬編 tenantId
        detector: GATE-ARCH-03
        why: 租戶隔離被繞過時資料外洩無法由功能測試發現
        when_not: 單租戶部署且資料表無租戶欄位時不適用

nfrs:
  - id: NFR-01
    description: 訊息送出 API p95 延遲 < 200ms
    measurable: true
    gate: GATE-PERF-01
  - id: NFR-02
    description: 錯誤訊息語氣符合產品調性
    measurable: false
    no_gate_reason: 語氣無法以規則判定,改由 UI review 抽查

slices:
  - id: SLICE-01
    scope: [BR-01]
    status: done
    gate_results: "GATE-COV-01 92% pass / GATE-ARCH-01 pass"
    completed_at: "2026-08-01"

agent_tasks.md 是同一批任務的人讀版,每個任務走同一套骨架:來源、實作狀態、目標、先寫測試、實作範圍、完成條件、gate、禁止事項。例如:

## TASK-MSG-001 實作 Message 接受流程

**來源**  BR: BR-01|FR: FR-MSG-001|Scenario: SC-MSG-01-01
         Aggregate: Message|Event: DE-001 MessageAccepted

**實作狀態**:partial —— handler 已存在
(src/Application/Messaging/SubmitMessageHandler.cs),缺 outbox 寫入與配額檢查。

**目標**  實作 POST /v1/messages 在配額足夠時建立 Message,回傳 202 與 messageId。

**先寫測試**
- tests/unit/domain/MessageTests.cs
- tests/integration/messaging/SubmitMessageTests.cs

**完成條件**
- SC-MSG-01-01 通過;Message 狀態為 accepted
- message_events 有 MessageAccepted;outbox_messages 有待發事件

**必須通過的 Gate**
- GATE-UT-01(dotnet test tests/Domain.Tests)
- GATE-COV-01 Domain 層 line coverage ≥ 90%
- GATE-ARCH-01 架構斷言全過

**禁止事項**(每條必須有 Detector 與 Why)
| 規範 | Detector | Why | When NOT |
|------|----------|-----|----------|
| 不可直接呼叫外部供應商 | GATE-ARCH-02 | 供應商呼叫必須經 ACL,否則 timeout/重試語意散落各處 | 供應商 SDK 本身即 ACL 時 |
| 不可略過配額檢查 | GATE-UT-01(SC-MSG-01-03 覆蓋) | 配額是計費依據,繞過等同免費使用 | — |

implementation_status 的撰寫規則

狀態 任務內容變成什麼 必填
done 「驗證現有實作+補齊測試」——不可叫 agent 重寫 existing_files 實際路徑
partial 明確列出缺口(缺哪個端點、哪個欄位、哪段邏輯),實作範圍只涵蓋缺口 existing_files + 缺口清單
not_started 一般開發任務 implementation_targets 標新檔、符合目標 repo 慣例

這三個狀態是棕地實作對齊的核心防呆,標錯有實際代價:done 標成 not_started,AI 會把運作中的功能重寫一遍;反過來標,缺口就永遠補不上。validator 會檢查狀態值合不合法(task.bad_status),也會檢查 done/partial 有沒有附上既有檔案(task.no_existing_files)。

下游怎麼接

Canonical 產物設計上要被下游 skill 消費,沿用既有 ID,不另行發明:spex-gen-test-docs-v5 負責全層測試(Unit/Integration/Component/E2E),把 harness.yaml 裡覆蓋率與突變 gate 需要的測試補齊;spex-gen-e2e-docs-v5 是前端/App 的 E2E 專家層,做全系統 SCREEN 掃描與 Playwright 實跑;spex-gen-unit-tests 管單元測試品質,「反對無效測試」的判準由 mutation score gate 客觀化,不再是憑感覺。

要銜接上,backbone 這邊得先做到:SC-xxx 標好 test_levelSCREEN-xxxdata-testid 約定、execution plan 帶 acceptance_commandgate_ids。harness 的覆蓋率與突變門檻,就是這些下游 skill 拿到的目標值。

七份文件

七份文件依序是需求規格、領域模型、功能規格、使用者故事與驗收條件、系統架構、測試策略、開發指南,後面為了跟檔名一致,沿用 SRS、DDM、FSD、USD、SAD、STO、DEV 這組縮寫。每份都要有 metadata、可點擊目錄、交叉引用與版本歷程;slice 模式則只增補該次 slice 涉及的章節。

七份文件依賴順序

01 SRS 需求規格書 由 backbone 的 BR/FR/DE 生成,涵蓋文件目的與範圍、系統概述與 context diagram、利害關係人、業務需求與 Domain Event 識別、依模組分組的功能需求(每項對應 BR 與 DE)、可量測的非功能需求、限制與假設、術語表、版本歷程。

SRS 的硬性要求:NFR 必須綁 gate 避免只寫「快速」「安全」這種沒有指標的描述。每條可測量 NFR 必須標對應的 GATE-xxx;無法自動驗證者標 no_gate_reason 並說明替代把關方式。沒有 gate 的「p95 < 200ms」不會有人去量,等於沒寫——validator 的 nfr.no_gate 就是盯著這條。

02 DDM 領域模型文件 由 backbone 的 BC/Aggregate/Event/Policy/Invariants 生成,章節包括 Ubiquitous Language 詞彙表、Event Storming SVG 與事件時間線、Bounded Contexts 與 Context Map SVG、Tactical design(Aggregates、Entities、Value Objects、Domain Services、Repositories)、Invariants、Policies 與補償交易、Failure Modes 與錯誤碼對應、版本歷程。文件必須說明哪些一致性是強一致、最終一致或補償式一致,每個 Aggregate 必須有 invariant,或明確說明為何沒有。DDM 的 Context Map 是 GATE-ARCH 斷言的唯一來源,有了這個對應關係,它才不會變成一張會過期的圖。

03 FSD 功能規格書 由 backbone 的 FR/API/data/UI screen contract/error/permission 生成,涵蓋系統架構(模組分層、資料流、技術選型)、資料模型(業務表、平台表、read model、audit、ER SVG)、模組規格(路由、權限、輸入輸出、業務邏輯虛擬碼、UI screen contract)、通知/整合規格、排程/背景任務、報告/匯出規格、權限與安全、錯誤處理、SVG 彙整、版本歷程。平台表有標配四張:outbox_messages(可靠事件發佈)、inbox_messages(消費端冪等)、audit_entries(append-only 稽核)、worker_leases(背景工作協調),未建者標「規劃中」,不可寫成既定事實。Secret 也有一條容易忽略的規則:API key/token 本體可以雜湊,但如果系統要計算或驗證簽章,HMAC/Webhook signing secret 必須以加密 secret material 儲存或引用 Secret Manager——雜湊掉就簽不了章。棕地系統的 FSD 還有兩條對齊鐵律:資料表以實際 migrations 為準、路由與 DTO 以實作盤點的路由清單為準,規劃中的一律明確標註。

04 USD User Story 與驗收條件 每個 User Story 包含身分/需求/目的、對應 DE/BR/FR、繁體中文 DSL-Level Gherkin、英文 ISA-Level Gherkin(含具體 API/data assertions,端點對照實際路由)、故事點與優先級、建議測試層級與對應 TASK。Scenario ID 格式是 SC-{MODULE}-{StoryNo}-{ScenarioNo}。雙層 Gherkin 的實例,以 UI 場景為例:

場景: SC-UI-MSG-01-01 - 初次載入訊息列表時顯示 loading skeleton
  假設 使用者進入「訊息發送記錄」畫面
  並且 後端資料尚未回傳
  當 畫面正在載入
  那麼 系統應顯示 skeleton 列
  並且 不得顯示「目前沒有資料」
Scenario: SC-UI-MSG-01-02 - Failed list loading shows retryable error state
  Given the operator opens "/messages"
  And the messages API returns 500
  When the page finishes loading
  Then an error state with retry action should be visible
  And the empty state should not be visible

Feature Mapping 表把每個 Scenario 的 DSL 描述、ISA 描述、測試層級、對應任務、兩層檔案路徑對齊成一列,這張表也是 traceability.json 的來源之一。

05 SAD 系統架構描述 涵蓋系統概述與邊界、利害關係人與關注點、功能視角(對應 BC)、資訊視角(對應 Aggregate/tables)、Runtime view 與關鍵序列、部署視角、營運韌性與災復、ADR、品質屬性與取捨、版本歷程。部署視角必須反映實際部署方式(systemd/容器/Pages),不可想像;架構約束章節必須列出對應的 GATE-ARCH 斷言;營運韌性要處理 RTO/RPO、備份還原演練、佇列耐久性、cache 故障行為、Secret/KMS 輪替、worker 重啟與冪等、可觀測性與告警;棕地系統的 package/模組對應必須是實際佈局。

06 STO 測試策略概要 是策略概要,不是完整測試計畫,但要有審查價值、能指導 TDD。章節包括與下游測試產生器的關係、依模組定義測試範圍、測試策略與測試金字塔 SVG、DDD/BDD/TDD/Agent Task 銜接流程、測試環境概要(含測試現況——零起點必須明說)、覆蓋率目標、BR-to-Scenario 表、Scenario-to-Test-to-Task 矩陣、UI/UX 測試矩陣、缺陷嚴重度與流程、版本歷程。

STO 的硬性要求:不可另訂一組數字 覆蓋率目標與充分性判準必須直接引用 _HARNESS.md 的 gate 門檻與指令。STO 自己發明「覆蓋率 85%」而 harness 寫 90%,半年後沒人知道哪個才是真的——這就是單一事實來源原則用在測試文件上的意思。CI 觸發條件也必須符合團隊實際流程:團隊直推 master 沒有 PR,就不能假設「PR 觸發」。

Scenario-to-Test-to-Task 矩陣長這樣:

| Scenario | 測試層級 | 先寫測試 | 驗證目標 | 對應任務 | 驗收指令 |
|----------|----------|----------|----------|----------|----------|
| SC-MSG-01-01 | integration | SubmitMessageTests.cs | 配額足夠時接受訊息 | TASK-MSG-001 | dotnet test --filter SC-MSG-01-01 |

07 DEV 開發指南 章節包括前置閱讀、開發環境、專案結構(實際 repo 佈局,多 repo 逐一列出)、資料庫設定與 migration 指引(列出既有 migration 序列與本輪新增)、開發執行順序、Frontend/UI 實作規範、各核心模組實作指南、權限設定、系統參數(預設值引自實際值)、本機如何跑 Harness gate、疑難排解、版本歷程。開發執行順序建議分四階段:Phase 1 Domain Core(Value Objects、Aggregates、Invariants、Unit Tests)、Phase 2 Application Use Cases(Command/Query、Handler、Validation、Authorization、Integration Tests)、Phase 3 Infrastructure(Repository、Outbox/Inbox、Queue Consumer、Provider Adapter)、Phase 4 API/UI/E2E(Controller、Admin UI、E2E Tests、Observability)。「本機如何跑 gate」這節要逐 gate 列出可複製的指令、預期輸出、紅燈時的處理,且必須與 harness.yaml 完全一致——這是工程師平常接觸 Harness 的地方,指令不一致的話,gate 就只存在於 CI,跟本機開發流程脫節。

UI/UX Screen Contract 與設計水準

UI 問題如果拖到 code review 才發現,通常已經改得比較痛苦。SPEX-SDD 把 UI/UX 規格前置成可驗收的合約(Screen Contract,畫面的合約):每個重要畫面有 Screen Contract、State Matrix(狀態矩陣)、Interaction Contract、RWD 策略,加上兩類必備資產——UI 畫面示意稿(mockup)與狀態示意圖,缺一視為文件不完整。

Screen Contract 要涵蓋的欄位:

欄位 內容
Screen ID SCREEN-{MODULE}-{NNN}
Route / Entry URL route、menu entry、deep link
使用角色 可進入此畫面的角色與權限
PageHeader title、subtitle、actions slot;不得與 View 內標題重複
主要任務 使用者進入此畫面要完成的 1–3 件事
Primary Action 每頁最多一個最強主操作
Secondary / Danger Actions 次要操作、批次操作;danger 必有 ConfirmDialog,不可用 window.confirm
State Matrix loading、empty、error、populated、readonly、permission denied、dirty、saving
Interaction Rules disabled reason、toast/error feedback、dirty guard、undo/confirm、long-running progress
Data Display 欄位順序、status badge、中文 label map、enum map、時間/數字格式
RWD Strategy 桌機、平板、手機;表格是否卡片化、Toolbar 是否收合
A11y label、aria、focus trap、ESC、keyboard shortcut
共用元件 PageHeader、SearchToolbar、DataTable、EmptyState 等
對應 Scenario / Task SC-xxxTASK-UI-xxx

State Matrix 的六個基本狀態:

State 觸發條件 畫面呈現 驗收重點
loading 初次載入或重新查詢 skeleton,不可顯示 empty 不得誤顯示空資料
empty 載入完成且無資料 icon + 標題 + 引導文案 + CTA CTA 對應主要任務
error API 失敗或權限錯誤 error banner / ErrorState + 重試 不可靜默空白
populated 有資料 表格/列表/卡片 欄位可掃描
permission_denied 無權限 權限提示 不顯示不可用的危險操作
dirty 有未儲存變更 dirty indicator 離開攔截

可編輯畫面另需定義 dirty/unsaved、save/cancel/reset、離開攔截或草稿保存。UI 任務不得缺少 loading/empty/error/permission/destructive state 的完成條件——這些狀態正是 AI 只做正常流程時最常漏掉的部分。

FSD/DEV 必須列出共用元件,不能讓每個 screen 各自發明按鈕、badge、empty state:PageHeader、SearchToolbar、DataTable、StatusBadge、EmptyState、LoadingState、ErrorState、ConfirmDialog、DetailDrawer、MoreActionsMenu、FormSection。按鈕階層必須一致:primary、secondary、ghost、danger、AI action——AI action 不該無差別套用最強 primary,會覆蓋資料或高成本的操作必須降階並加確認。

mockup 與狀態示意圖,兩者缺一不可

UI Mockup(命名 ui-mockup-{screen}.svg|png)畫的是真實版面:導覽列、PageHeader、表格表單、按鈕階層、真實內容文案。製作方式二擇一——手刻 SVG(向量、零建置),或是高擬真首選的 HTML→PNG 流程:用專案實際 design token 寫 mockups/*.html,再用 Playwright 無頭瀏覽器以 2x DPI 截圖;HTML 原始檔與重生指令(mockups/shot.mjs)要一併保留,這份 HTML 之後還能當實作起點。

狀態示意圖則要涵蓋預設/有資料、loading、empty、error、permission denied、destructive confirmation、dirty,以及 RWD 降級策略。它跟 mockup 並存,不能互相取代——一張講外觀,一張講行為。兩者都要以 ![](…) 內嵌在 Screen Contract 下方顯示,不能只放連結,並且要在 FSD「SVG 彙整」分節列出且檔案確實存在。validator 的連結檢查與人工的語意驗證,各盯這件事的一半。

設計水準:能跑不等於設計完成

mockup 必須以資深產品設計師設計最終上線產品的標準完稿,不是 wireframe 或 demo。優先沿用專案既有的 design system;沒有的話先呼叫 frontend-design skill 取得視覺方向;驗收門檻則用 spex-review-ui-ux 的企業級 SaaS 標準。完稿判準包括:視覺層次清楚(級距分明、對齊一致、留白有節奏)、使用真實內容(實際文案與常見長度,包含過長時的截斷,不用 Lorem 佔位)、每個狀態都設計過(empty 有圖示+CTA、error 可重試、danger 有後果說明)、無障礙(對比 ≥ WCAG AA、focus 可見、狀態不只靠顏色)、跨畫面一致且 RWD 是刻意設計而非縮放破版,以及色彩分層(品牌/中性/語意色,不可處處品牌色)。

原則很直白:每個視覺元素都必須傳達使用者需要的資訊,不然就是裝飾,應該移除。常見的 AI 生成特徵一律避免——裝飾類的問題包括卡片裝飾色條(不傳達資訊的 accent strip)、濫用漸層(每顆按鈕漸層+glow)、彩虹式分類色(每項目一個 accent)、裝飾性編號 01/02/03(內容非有序步驟時);模板類的問題包括三欄 icon+標題+說明的 value props、「大數字+小標」hero、滿屏膠囊 badge、emoji 當功能圖示、全部置中等重卡片、過圓角+到處重陰影、飄浮模糊光暈 blob、毛玻璃濫用。讓字體、間距、層次與留白撐起設計,搶眼的處理只用在一個重點元素上,其他地方保持低調。

UI 這邊也有明確的禁止事項與偵測器綁定:

禁止 Detector Why
window.alert/confirm 作為正式互動 GATE-LINT-01 原生 dialog 無法套用設計系統與 a11y,且阻塞事件迴圈與自動化測試
直出技術 enum / status code GATE-LINT-02 使用者看不懂內部代碼;必須有中文 label map 與語意 badge
忽略 loading/error/empty component test(GATE-UT-02) loading 誤顯 empty、error 靜默空白,是最常見的 AI happy-path 產物
多個 primary action 放同一層級 advisory,人工 UI review 無法用機器判定,列入 advisory_notes 並登記 misfit-log——這是刻意的安排,不是漏掉

品質關卡目錄(Harness Catalog):工具、門檻與建置順序

設計 _HARNESS.mdharness.yaml 時會用到的資料都收在這裡:六項指標在不同技術棧的工具對照、建議門檻、YAML 格式,以及零起點的建置順序。

六項指標 × 技術棧的工具對照:

指標 .NET TS / Vue / React Go Rust Java Python
單元測試 xUnit / NUnit Vitest / Jest go test cargo test JUnit 5 pytest
BDD 驗收 SpecFlow / Reqnroll Playwright + Cucumber godog cucumber-rs Cucumber-JVM pytest-bdd / behave
覆蓋率 coverlet + ReportGenerator Vitest v8 / c8 go test -cover cargo-llvm-cov JaCoCo coverage.py
突變測試 Stryker.NET StrykerJS gremlins / go-mutesting cargo-mutants PIT (pitest) mutmut / cosmic-ray
複雜度/大小 Roslyn / SonarAnalyzer ESLint complexity、max-lines gocyclo / gocognit clippy cognitive_complexity Checkstyle / PMD ruff C901 / radon
依賴結構 NetArchTest / ArchUnitNET dependency-cruiser / madge go-arch-lint / depguard cargo-modules / cargo-deny ArchUnit import-linter

跨語言還有個補充:SonarQube/SonarCloud 可以一次涵蓋覆蓋率、複雜度、重複率與 code smell。如果團隊已經有 Sonar,優先接既有的,不要另外建一套平行指標——兩套指標並存,最後通常是兩套都沒人信。

建議門檻總表:

指標 一般程式碼 Domain / 核心業務層 說明
Line coverage ≥ 70–80% ≥ 90–95% Domain 層幾乎沒有 I/O,沒有理由測不到
Branch coverage ≥ 60% ≥ 85% 比 line 更能反映條件邏輯
Mutation score ≥ 50–60% ≥ 75–80% 低於 40% 幾乎可斷定測試是裝飾品
Cyclomatic complexity method ≤ 10,硬上限 15 method ≤ 8 超過即拆或補測試
檔案/類別大小 檔案 ≤ 500 行、class ≤ 300 行 同左 純資料/產生檔可豁免,須列白名單
Method 長度 ≤ 50 行 ≤ 30 行
依賴結構 零循環依賴;層級單向;BC 間僅 domain event 或 ACL 同左 硬性規則,不設百分比
BDD 覆蓋 每個 Must BR ≥ 1 個 ISA scenario 同左 由 validator 檢查

這些是通用經驗值,不是專案自己的門檻——第一次用一定要先量基線,門檻訂在現況加合理增量,理想值放 target 欄。

harness.yaml 的完整範例:

version: 1
system: Example
baseline_measured_at: "2026-08-01"   # 由呼叫端填實際日期,勿臆造

gates:
  - id: GATE-UT-01
    metric: unit_test
    scope: "src/Domain/**"
    command: "dotnet test tests/Domain.Tests"
    threshold: "all pass"
    baseline: "142 tests, all pass"
    blocking: true
    schedule: per_commit

  - id: GATE-COV-01
    metric: line_coverage
    scope: "src/Domain/**"
    command: "dotnet test /p:CollectCoverage=true /p:Threshold=90 /p:ThresholdType=line"
    threshold: ">= 90"
    baseline: "78"
    target: ">= 95 by 2026-Q4"
    blocking: true
    schedule: per_commit

  - id: GATE-MUT-01
    metric: mutation_score
    scope: "src/Domain/**"
    command: "dotnet stryker --threshold-break 70 --project Domain.csproj"
    threshold: ">= 70"
    baseline: "not_measured"
    status: to_be_built            # 工具尚未安裝 → 必須有對應 TASK
    blocking: false
    schedule: nightly

  - id: GATE-ARCH-01
    metric: dependency_structure
    rule: "BC-MSG 不得依賴 BC-QUOTA.Infrastructure;BC 之間僅可經 domain event 溝通"
    source_of_truth: "_SPEC_BACKBONE.md#context-map"
    command: "dotnet test --filter Category=Architecture"
    threshold: "all pass"
    blocking: true
    schedule: per_commit

  - id: GATE-CX-01
    metric: cyclomatic_complexity
    scope: "src/**"
    command: "dotnet sonarscanner ...   # 或 analyzer 設定 CA1502"
    threshold: "max <= 15"
    baseline: "max 23 (SubmitMessageHandler.Handle)"
    blocking: false                 # 現況超標 → 先 report-only,並排定重構 TASK
    schedule: per_commit

  - id: GATE-LINT-01
    metric: custom_rule
    scope: "web/src/**"
    command: "npm run lint"
    threshold: "0 errors"
    blocking: true
    schedule: per_commit

rule_bindings:                      # 每條規範指向一個上面存在的 gate
  - rule: "不可在 handler 內硬編 tenantId"
    detector: GATE-ARCH-03
    why: "多租戶隔離被繞過時資料外洩無法由功能測試發現,只能靠靜態規則攔截"
    when_not: "單租戶部署且無租戶欄位時不適用"
  - rule: "不可使用 window.confirm 作為正式互動"
    detector: GATE-LINT-01
    why: "原生 dialog 無法套用設計系統與 a11y 規格,且會阻塞事件迴圈與自動化測試"

advisory_notes:                     # 無法被偵測的規範:不得放進 forbidden_actions
  - rule: "錯誤訊息語氣應與產品調性一致"
    detector: none
    reason: "語氣無法以規則判定;改由 UI review 抽查"
    misfit_ref: MF-0007             # 已在 misfit-log 開待建偵測器

uncovered_risks:                    # 必填,可為空陣列但要有這個 key
  - "業務規則本身是否符合客戶真實需求——僅能由人審 DSL scenario"
  - "跨系統資料一致性——目前無端到端對帳 gate"

ci:
  blocking_gates: [GATE-UT-01, GATE-COV-01, GATE-ARCH-01, GATE-LINT-01]
  report_only_gates: [GATE-CX-01]
  nightly_gates: [GATE-MUT-01]
  on_fail: "紅燈時記入 misfit-log;同一 gate 連紅 3 次觸發門檻檢討"

工具全無的零起點專案,不要一次要求六項全開,照下面順序拆成 TASK,每項一個 slice:先做 GATE-UT + 測試骨架,沒有 test runner 就沒有任何後續指標;接著做 GATE-ARCH(依賴結構),它最便宜、最快見效,能直接保護 DDD 邊界,而且不需要測試也能跑,防止架構在補測試期間繼續腐爛;然後接上 GATE-COV,先 report-only 兩週取得基線再設門檻;接著把既有 forbidden_actions 逐條綁上 GATE-LINT 或自訂 detector;再來是 GATE-CX(複雜度/大小),一樣先 report-only,超標處排重構 TASK;最後才輪到 GATE-MUT(突變測試)——先只對 Domain 層開,nightly 跑,因為它成本最高,而且要等測試量足夠才有意義。

規格檢查程式(validator)深入

validator 有 688 行,只用標準函式庫,另外內建一個 YAML 子集解析器作為 fallback。基本用法:

# 結構驗證
python3 ~/.claude/skills/spex-sdd/scripts/validate_spec.py docs/spec

# 加上 repo 檢查 implementation_targets 路徑是否存在
python3 .../validate_spec.py docs/spec --repo /path/to/repo

# JSON 輸出給 CI 解析
python3 .../validate_spec.py docs/spec --json

離開碼(exit code)的意思:0 代表沒有 FAIL;1 代表有 FAIL,CI 應該擋下;2 代表找不到必要產物。

輸出長這樣:

[WARN] harness.to_be_built: 待建置的 gate:GATE-MUT-01(需有對應 TASK)
[FAIL] task.no_gate: TASK TASK-MSG-001 未列出 gate_ids(必填)

--- FAIL 1 / WARN 1 / PASS 5 ---

FAIL 必須修掉才算完成,有 FAIL 不得回報完成,這是執行流程的硬性規定。WARN 必須逐條說明處置,可以是「已知,本期不處理,理由是……」,但不可以沉默略過。PASS 代表通過的檢查群組數,不是檢查項數——PASS 5 是五組檢查全綠,不是五個項目。

錯誤代碼按類別整理如下。

追溯性相關:

代碼 意義 怎麼修
id.duplicate 重複 ID 改其中一個;不要重用已廢棄的 ID
id.missing 項目缺 id 欄位 補上
br.no_fr / br.no_scenario Must BR 沒有 FR 或 SC 補;或把 priority 降為 should
fr.no_br / fr.no_module FR 缺上游或模組
fr.uncovered FR 沒有 SC 也沒有 TASK 補,或標 coverage: deferred + 理由
sc.no_us / sc.orphan_us Scenario 沒對應或指向不存在的 US 補 / 修 ID
sc.missing_layer 缺 DSL 或 ISA 層 Gherkin 補;雙層是硬性要求
sc.no_test_level / sc.no_test_file 缺測試層級或檔案 補;不自動化要寫 no_automation_reason
sc.no_task Scenario 沒有對應 TASK 通常代表規格還太抽象,拆任務

任務相關:

代碼 意義 怎麼修
task.no_test_first 沒列先寫測試 補;這是 TDD 的入口
task.no_targets 沒列實作目標
task.no_acceptance / task.vague_acceptance 沒有或太籠統的驗收指令 寫成可直接複製執行的指令,不可寫「執行測試」
task.bad_status implementation_status 值錯 只能是 done / partial / not_started
task.no_existing_files 標 done/partial 但沒附既有檔案 補實際路徑
task.no_gate 缺 gate_ids 必填;列出完成時要通過哪些 gate
task.plain_forbidden 禁止事項還是純文字 改成 {rule, detector, why}
task.unbound_rule 規範沒有對應的偵測器(detector) 移到 advisory_notes + 開 misfit 待建偵測器
task.orphan_detector detector 不存在於 harness 修 ID 或補該 gate

Harness 相關:

代碼 意義 怎麼修
harness.metric_missing 六項指標缺某一項 補 gate,或在 not_applicable 寫明理由
harness.gate_no_command / gate_no_threshold gate 缺可執行指令或門檻
harness.no_uncovered_risks 沒有顯式聲明盲區 uncovered_risks:(可為空陣列)
harness.binding_none / binding_orphan rule_bindings 的 detector 有問題
harness.md_missing 找不到 _HARNESS.md 產出它
harness.to_be_built(WARN) gate 標為待建置 確認有對應的建置 TASK

計畫與追溯檔相關:

代碼 意義
plan.cycle 任務依賴形成循環(DFS 找環,會印出完整路徑)
plan.missing_task / plan.extra_task backbone 與 execution plan 不同步
plan.orphan_dep 依賴不存在的任務
trace.orphan_id traceability 出現 backbone 沒有的 ID
trace.missing_sc(WARN) Scenario 沒出現在 traceability
path.missing 路徑不存在於 repo(新檔案要列入 new_files)
nfr.no_gate 可測量 NFR 沒綁 gate 也沒說明理由

第二層:人工語意驗證

validator 跑完只是完成了一半。第二層依 validation-rules.md 逐項對照,涵蓋機器檢查不到的語意:追溯語意要確認 SC 是不是真的驗證了該 BR 的意圖,而不是 ID 對得上就算數;實作對齊要把資料表逐表對照 migrations、ISA 端點對照實際路由(包含請求/回應方向——誰送 decision、誰回 decision 這類方向錯誤也算失敗)、系統參數引自實際值、部署/CI 符合實際流程,且註解不能當事實來源;UI 水準要檢查 Screen Contract 完整性、mockup 設計水準、AI 生成感逐項比對前面那份清單;規範 forces 要看每條規範的 Why 是否成立、When NOT 是否該補;統計數字防呆要把摘要、版本歷程、內文中的計數(模組數、Task 數等)逐一與 registry 實際計數核對——建議實際數一遍,不要沿用上游舊數字;一致性要看 TOC 涵蓋新章節、檔名與編號一致、SVG 連結存在、引用路徑大小寫與檔案系統一致(避免 Linux/CI 斷鏈);架構風險則要逐項確認 async messaging、外部供應商 ACL、API auth、webhook signing、多租戶、audit、PII、可用性、可觀測性、背景工作,各風險有設計或明確標不適用。

驗證完成的定義 validator FAIL 0(輸出已附)、WARN 逐條說明處置、語意驗證逐項對照完成——三項都做到才算驗證完成。少了任何一項,就又回到自我宣稱。

實戰手冊:怎麼真的把這套方法用起來

前面把系統的產出物講完了,接下來直接進操作:怎麼安裝、模式怎麼選、十三個常見情境該怎麼下指令、常犯的錯誤有哪些,還有一次生成算不算「做完了」該怎麼核對。

安裝在哪裡、怎麼觸發

skill 裝在全域位置後,任何專案都能用:

~/.claude/skills/spex-sdd/
├── SKILL.md                   # 每次觸發載入的主指令(383 行)
├── references/
│   ├── document-specs.md      # 七份文件與各產物的格式規格(569 行)
│   ├── harness-catalog.md     # 六項指標 × 工具、門檻、斷言產生法(203 行)
│   └── validation-rules.md    # 人工語意驗證規則(245 行)
└── scripts/
    └── validate_spec.py       # 可執行結構驗證器(688 行,stdlib only)

~/.claude/skills/ 是全域位置。要讓團隊共用,複製到 repo 的 .claude/skills/.github/skills/ 就行。不需要另外裝 PyYAML——validator 內建了一套 YAML 子集解析器當 fallback。

觸發方式有兩種。斜線強制觸發最保險:打 /spex-sdd <需求>,確定會載入,不靠語意匹配。也可以用自然語言,比如「用 SPEX-SDD 幫 LedgerPro 產技術文件,slice 模式,先做對帳流程」,只要 description 命中就會載入。不確定有沒有載入成功,可以直接問它「你現在載入了哪些 skill?SPEX-SDD 的六項工程指標是什麼?」——答得出突變測試與依賴結構,就是真的讀到了。

開始前最好先把手邊的專案資訊準備好,資料給得完整,通常可以少幾輪來回確認:

項目 為什麼重要
所有 repo 路徑 前後端分 repo 時只掃 cwd 會漏掉一半;implementation_targets 會指錯 repo
技術棧與測試框架 決定 harness.yaml 選哪套工具、acceptance_command 長什麼樣
既有 design system / UI framework 沒有的話 mockup 會變成無風格線框
部署方式(systemd/容器/Pages) SAD 部署視角與 CI 接線
有沒有 PR 流程 決定 gate 的 blocking 設定與 STO 的 CI 觸發條件
租戶與權限模型 影響架構檢查與 detector 設計
輸出目錄 建議 docs/spec/,跟程式碼同 repo 進版控

不確定的項目可以先留白,需要時再補問,或明確標成假設後繼續往下走。

模式怎麼選:full 還是 slice

full 是一次把整套文件產完,slice 是一次只展開一個模組或流程、其餘先標成 outline。用四個問題判斷就好:已有實作時通常適合 slice;full 則留給規模很小、還沒有既有程式碼的全新專案(綠地),或需要一次交付整套文件的情況。

生成模式決策樹

四個問題依序問下去:系統已有實作嗎?有的話走 slice,而且是強烈建議。沒有的話再問模組數是不是超過 8 個,超過一樣走 slice。都不是的話問需不需要一次交付完整套件(投標、稽核、驗收這類情境),需要就走 full。最後剩下模組數 3 個以下的極小系統,也走 full;其餘情況預設落在 slice。實務上多數真實專案最後都會落在 slice,full 的入口主要就是「一次交付」跟「極小系統」這兩種。

full 模式產出的規格量很大,其中相當比例沒有經過任何實作驗證,這點要留意。交付前建議在文件上標註「本文件為設計規格,實作狀態見實作現況對照表」,避免對方誤以為文件描述的都是已經做好的東西。

十三個情境

下面整理十三個常見情境,每個都附上可以直接改的 prompt。第一次在既有專案導入,看情境 1 就好。

情境 1:既有專案第一次導入(最常見)

不要一開口就叫它產全套,分三步走。

第 1 步:只做盤點 + Harness。

/spex-sdd
專案:
- 後端 /Users/leo/work/LedgerPro(Go + PostgreSQL)
- 前端 /Users/leo/work/LedgerPro-web(Vue 3 + Vite)
部署:systemd,直推 master 沒有 PR

本次只做兩件事:
1. 實作盤點(含品質基線量測)
2. 設計 Harness:_HARNESS.md + harness.yaml

七份文件先不要產。輸出到 docs/spec/

會拿到:實作現況對照表(模組 × 實作狀態 × 實際檔案位置 × 差異)、實際路由清單、實際資料表清單、測試現況、品質基線(覆蓋率、最大複雜度、最大檔案行數、有無循環依賴),以及 _HARNESS.md + harness.yaml

第一次盤點通常會有意外收穫——它量出的是專案真實的樣子,不是團隊以為的樣子。多數團隊第一次量基線都會發現覆蓋率比想像低 20–30%、有幾個破千行的檔案、或某兩個模組互相引用。中型專案跑一次大概 20–40 分鐘。

第 2 步:挑一個 slice 展開。

接著做 SLICE-01:對帳流程
展開它的 FR / US / SC / TASK,其餘模組維持 outline 狀態

怎麼挑第一個 slice:選「風險最高且目前沒有測試覆蓋」的,不要選最簡單的。第一個 slice 的目的是驗證這套流程走不走得通,簡單的 slice 驗證不出問題。

第 3 步:實作 → 跑 gate → 回寫。

依 agent_tasks.md 執行 TASK-RECON-001,先寫測試再實作
完成後跑它的 gate_ids,結果回寫到 _HARNESS.md 與 misfit-log.md

跑完一輪後檢視:gate 有沒有紅?紅的是程式碼問題還是門檻訂太高?有沒有人工發現但 gate 沒抓到的問題?那種情況要記成 undetected,優先補 detector。

情境 2:全新專案從零開始

/spex-sdd
新系統:思沛授權管理系統 v2(LMS2)
業務背景:管理產品授權發放、線上啟用、裝置綁定與稽核,供內部與經銷商使用
技術棧:.NET 9 + PostgreSQL + Redis + Vue 3
模組:授權發放、啟用驗證、裝置綁定、稽核查詢、管理後台
多租戶(經銷商即租戶),有管理後台
部署:Docker + systemd
full 模式,輸出到 ./docs/spec

綠地有幾個特殊處:沒有實作可盤點,gate 基線一律標 not_measured;所有 gate 先設 report-only,等有程式碼再轉 blocking;第一批 TASK 會包含「建立測試骨架 + 架構測試」,這是刻意的;implementation_status 全為 not_started,targets 全列 new_files

綠地反而更該把 Harness 做好——從第一天就有架構斷言,BC(bounded context,領域邊界)不會慢慢跑掉,也就不會走到難以回頭的地步。

情境 3:只想量現況,還不想產文件

/spex-sdd
只做實作盤點與品質基線量測,輸出一份現況報告
不要產 backbone、不要產文件、不要產 harness

適合接手陌生專案想快速掌握現況、決定要不要導入前先估工作量、或給主管看的技術債報告。產出實作現況對照表加六項指標實測數值,不動任何檔案。

情境 4:推進下一個 slice

/spex-sdd SLICE-03:退貨與折讓流程,接續 docs/spec/ 既有 backbone

它會讀既有 backbone,不動既有 ID,只增補本 slice 的 registry 列與文件章節,更新 slice registry,再跑 validator。檢查點:確認它沒有動到既有 slice 的內容,validator 的 id.duplicate 檢查跟版本歷程可以佐證。

情境 5:零測試專案

最常見的棕地狀況。別想一次把六項指標全開,照零起點的順序排 slice:測試骨架 → GATE-ARCH → GATE-COV(report-only 跑兩週)→ GATE-LINT → GATE-CX → GATE-MUT。

本專案目前零測試。請依 harness-catalog 的零起點順序,
把六個 gate 的建置各排成一個 TASK-HARNESS-xxx,
並在 agent_execution_plan.json 標好依賴順序

情境 6:多 repo(前後端分離)

多 repo 系統最容易出錯,本方法對這個情況有專門的檢查。

/spex-sdd
本系統橫跨三個 repo:
- /Users/leo/work/XurfGoAPI-Gateway(Go,gateway + 微服務)
- /Users/leo/work/XurfPOS_PLUS(Vue 3,POS 前端)
- /Users/leo/work/xurf-infra(部署腳本與 systemd unit)
三個都要盤點。implementation_targets 必須指對 repo。
harness.yaml 的 gate 要分 repo 定義(Go 與 TS 用不同工具)

驗證方式是分別對每個 repo 跑路徑檢查,看 path.missing 有沒有出現在不該出現的 repo:

python3 .../validate_spec.py docs/spec --repo /Users/leo/work/XurfGoAPI-Gateway
python3 .../validate_spec.py docs/spec --repo /Users/leo/work/XurfPOS_PLUS

情境 7:只想補某個模組的 UI/UX 規格

/spex-sdd
只針對「訂單管理」模組補 UI/UX 規格:
- FSD 的 Screen Contract(含 State Matrix、Interaction Contract、RWD)
- USD 的 UI 驗收場景
- STO 的 UI/UX 測試矩陣
- UI mockup(HTML→PNG 高擬真,用專案既有 Tailwind token)
- TASK-UI-xxx,含 detector 綁定
其他章節不動

UI 這塊的 detector 有它的現實限制:多數 UI 規範綁得上 lint 規則,但像「畫面上多個主要動作按鈕互相競爭」這種問題,就綁不上自動檢查,會被記進 advisory_notes 並登記到 misfit-log,靠人工review。

情境 8:交付用(投標、稽核、驗收)

/spex-sdd
full 模式,產出完整七份文件套件
用途:客戶驗收交付,需要文件完整性
Harness 部分仍要產,但 gate 全設 report-only(客戶端 CI 我們碰不到)

full 模式所有模組都完整展開,不會有 outline。記得前面提過的提醒:交付前要標註「本文件為設計規格,實作狀態見實作現況對照表」。

情境 9:AI 違反了規範,被人工發現

這是 misfit log 的核心流程,也是這個方法唯一的學習輸入。

剛才 code review 發現 AI 在 handler 裡直接組 SQL 字串帶 tenantId,
GATE-ARCH-03 沒有抓到(它只檢查參數傳遞,不檢查字串拼接)。
請記入 misfit-log.md

這會記兩筆:一筆是 rule-violation,記錄 AI 違反了「不可硬編 tenantId」;另一筆是 undetected,記錄沒有 gate 能抓到字串拼接的租戶條件——這筆才是重點,它代表 harness 有洞,應該自動建議一個 TASK-HARNESS-xxx 去補 analyzer 規則。判斷準則很單純:這個問題是被 gate 抓到的,還是我人工發現的?只要是人工發現的,一律記 undetected,不管當下有沒有時間補。

情境 10:gate 一直紅,或門檻訂錯了

GATE-COV-01 連續三次紅燈,Domain 覆蓋率卡在 82%,門檻是 90%。
請依 misfit-log 的處理規則判斷這是程式碼問題還是門檻問題

處理規則:同一 gate 連紅三次,就該檢討門檻是不是不合理,或任務是不是拆得太大。門檻要重訂時,baseline / threshold / target / 期程這四欄必須同時更新,並記入版本歷程。

不要偷偷把門檻調低就算了。調整要留痕跡,否則半年後沒人知道為什麼標準變鬆了。最危險的反模式是把 gate 從 blocking 改成 report-only 來「解決」紅燈——一個被關掉的 gate 比沒有 gate 更糟,因為它會製造「我們有在把關」的錯覺。

情境 11:接下游測試產生器

/spex-gen-test-docs-v5     # Unit / Integration / Component / E2E
/spex-gen-e2e-docs-v5      # 前端 App 的 E2E 專家層

這兩個下游 skill 會自動偵測並消費本方法的 canonical 產物,沿用 SC/SCREEN/FR/US/TASK 這些 ID。額外可以明講的是:harness.yaml 的覆蓋率與突變測試門檻,就是它們的目標值。

/spex-gen-test-docs-v5
目標門檻依 docs/spec/harness.yaml:Domain 層 line coverage ≥ 90%、mutation score ≥ 70%

情境 12:接進 CI

harness.yamlci 區段定義了分類,實際 CI 設定要自己接。建議寫法:

# .github/workflows/spec-check.yml(示意)
- name: 規格結構驗證
  run: python3 .claude/skills/spex-sdd/scripts/validate_spec.py docs/spec --repo .

- name: Harness blocking gates
  run: |
    dotnet test tests/Domain.Tests                      # GATE-UT-01
    dotnet test /p:CollectCoverage=true /p:Threshold=90 # GATE-COV-01
    dotnet test --filter Category=Architecture          # GATE-ARCH-01

把 validator 接進 CI 是這裡面最划算的一步。文件跟程式碼一起被把關,才不會又慢慢腐爛回去;exit code 1 就代表有 FAIL。也可以直接請它幫你產:「依 harness.yaml 產生對應的 GitHub Actions / GitLab CI 設定檔,blocking gate 進 PR check,nightly gate 進排程 workflow」。

情境 13:多人協作、文件進版控

建議把 docs/spec/ 進 git,跟程式碼同 repo。

衝突處理:backbone 是唯一的 ID registry,多人同時新增 slice 會在 registry 表格衝突,約定一次一個人推一個 slice,或每人負責固定模組,可以避開。misfit-log.md 是 append-only,衝突機率本來就低。

Code review 時要看什麼:backbone diff(有沒有動到既有 ID?)、validator 輸出(PR 描述裡貼出來)、_HARNESS.md 的門檻有沒有被偷偷調低。

Prompt 範本集

七個最常用的範本,對應前面的情境,直接抄去改就好。

盤點與基線(第一次導入必跑)

/spex-sdd
專案 repo:<路徑清單>
技術棧:<...>
本次只做實作盤點與品質基線量測 + Harness 設計,不產七份文件
輸出到 docs/spec/

展開一個 slice

/spex-sdd
SLICE-0X:<模組或流程名稱>
接續 docs/spec/ 既有 backbone,只展開本 slice,其餘維持 outline
完成後跑 validator 並附輸出

全套(綠地或交付)

/spex-sdd
新系統:<名稱>
業務背景:<兩三句>
技術棧:<...>
模組:<清單>
權限/租戶:<...>
部署:<...>
full 模式,輸出到 ./docs/spec

記錄 misfit

<描述發現的問題>
這是被哪個 gate 抓到的?如果是人工發現,記為 undetected 並提出 detector 方案

只補 Harness

/spex-sdd
docs/spec/ 已有完整文件。只產 _HARNESS.md 與 harness.yaml:
先量基線,門檻訂在現況+合理增量,六項指標缺的寫不適用理由

產 CI 設定

依 docs/spec/harness.yaml 產生 GitHub Actions workflow:
blocking gate 進 PR check、nightly gate 進排程、validator 每次都跑

要求驗證報告

對 docs/spec/ 跑完整驗證:
1. 實跑 scripts/validate_spec.py 並貼出原始輸出
2. 依 validation-rules.md 做語意驗證(UI 水準、實作對齊、規範 forces)
3. 列出所有 FAIL 的修正方案與所有 WARN 的處置說明

八個常見錯誤

這八種錯誤都很常見,也都會讓整套方法慢慢失去作用。前三條都跟門檻有關,因為團隊最容易先在這裡妥協。

一開口就要全套。 棕地專案叫它 full 模式產七份文件,會得到大量沒經過驗證的規格,改起來還要動七個檔案。正確做法是先盤點、再 slice——就是情境 1 的三步走。

門檻訂理想值。 「覆蓋率就該 90%」,但現況只有 45% 的專案設 90%,第一天就全紅,一週內 gate 大概就會被關掉。應該訂在現況 + 合理增量,把 90% 放進 target 欄配上期程。

把紅燈 gate 改成 report-only 了事。 這是最危險的反模式。改門檻可以,但要留痕跡、記版本歷程、寫理由。偷偷關掉等於自欺——一個被關掉的 gate 比沒有 gate 更糟,它會製造「我們有在把關」的錯覺。

不記 undetected。 「這次人工抓到了,修掉就好」——下次同樣的問題還是只能靠人工抓。每一筆人工發現都代表 harness 有洞,不記就永遠補不起來。

規範沒寫 Why。 只寫「不可使用 EF Core」,AI 遇到規範沒提到的 ORM 時就無法判斷該不該用。寫上 Why(比如「團隊統一用 Dapper,混用會造成 transaction 邊界混亂」),它才有東西可以推理。

跳過 validator。 「AI 說它檢查過了」不算數。這個方法整套設計的前提就是不信任自我宣稱,輸出一定要貼出來。

只掃 cwd。 前後端分 repo 卻只給一個路徑,會得到一半的盤點結果,implementation_targets 也會指錯 repo。對應的是情境 6 的多 repo 檢查方式。

突變測試一開始就全開。 它很慢,整個 repo 全開會讓 CI 變成跑好幾個小時。第一次只對 Domain 層開,排到 nightly 跑就好。

完成定義與實務節奏

在這套方法裡,「做完了」要能逐項核對,不是憑感覺。

一個 slice 或一次生成算完成,下面全部要打勾:

  • scripts/validate_spec.py 實跑,FAIL = 0,輸出已貼出

  • _HARNESS.mdharness.yaml 存在,六項指標各有 gate 或不適用理由

  • 每條 forbidden_actions 都是物件格式且 detector 存在

  • 每個可測量 NFR 綁 gate 或說明為何不能

  • 每個 TASK 有 gate_idstest_first、可執行的 acceptance_command

  • implementation_status 正確(不會叫 AI 重寫已完成的功能)

  • uncovered_risks 已顯式聲明,且 _HARNESS.md 說明人工補償措施

  • slice 模式:本 slice 完整、其餘 outline 且未被寫成細節、slice registry 已更新

  • 有 UI 者:Screen Contract + mockup + 狀態示意圖三者齊備

  • misfit-log 已記錄本次的 gate 結果與發現

每次生成結束,回報也要包含以下內容,缺項就代表沒做完:生成模式(full/slice)與本次 slice 範圍;建立或更新的檔案,SVG/mockup 數量分類;validator 實際輸出(PASS/FAIL/WARN 統計與未解項目);Harness 摘要表(六項指標 × 工具 × 基線 × 門檻 × 狀態);未綁定 detector 的規範清單(已列入 misfit-log 的待建偵測器);實作現況對照表(棕地必附);UI/UX screen contract 覆蓋摘要與未覆蓋畫面;開發入口摘要(第一批測試、第一批模組、建議順序);仍需使用者確認的假設。

長期節奏大概是這樣:

週期 做什麼
導入時(一次) 盤點 + 基線 + Harness 設計 + 第一個 slice
每個功能 展開 slice → 實作 → 跑 gate → 回寫 status
每次 PR validator + blocking gates
每晚 突變測試等昂貴 gate
每兩週 檢視 misfit-log:有沒有 undetected 未處理?有沒有 gate 連紅?
每季 依 misfit-log 的累積提案修訂規範;重新量基線、調整門檻

這其實是一個內外雙迴圈:內迴圈以功能為單位天天轉——規格與規範產生 Agent 任務,任務驅動 AI 實作,實作被 Harness Gate 驗證;驗證結果不管是紅燈還是違規,都進 Misfit Log。外迴圈以雙週或季為單位,把 Misfit Log 累積的東西整理成規範修訂提案,寫回規格,讓整套規格系統本身跟著演進,而不是訂了就不再變動。

回望與展望:解決了什麼,還沒解決什麼

操作講完了,最後收尾:把整套設計拿出來對照當初想解決的問題,看看哪些真的有著落,哪些還沒有——順便誠實列一張沒做到的清單。

Harness 迴圈 內迴圈以功能為單位天天轉:展開 slice、實作、跑 gate、回寫狀態。外迴圈以雙週或季為單位:累積 misfit(不合身的地方)、提出規範修訂、寫回規格。整套方法的骨架,大致就是這兩個迴圈套在一起。

解決了什麼

當初動筆寫 SPEX-SDD,是因為有八個問題反覆出現,一直沒有一個可以落地的處理方式。走完整個設計之後,這八個問題各自都有了對應的做法:

問題 這裡的解法
驗證只能「概念上檢查」,靠 AI 自我宣稱 機器結構驗證(validate_spec.py)加上人工語意驗證,分兩層做
禁止事項是純文字,沒人知道有沒有被遵守 改成 {rule, detector, why, when_not} 的物件格式,沒綁 detector(負責抓違規的偵測機制)就直接判 FAIL
沒有結構品質把關 定出六項工程指標,每一項都要有 gate(可執行的品質關卡)或明確寫出不適用的理由
Context Map 只是文件裡的一張圖 從 Context Map 推導出可執行的架構斷言,讓圖真的能拿來檢查程式碼有沒有越界
一次產出全套規格,錯誤到實作才暴露 full/slice 兩種生成模式,slice 讓錯誤在還沒展開全部規格前就先冒出來
規範演進只能靠人工事後歸納 misfit-log 累積事件,達到一定量就觸發規範修訂提案
NFR(非功能需求)只寫「必須可測試」,沒有強制力 每一條可測量的 NFR 都必須綁 gate,或說明為什麼沒辦法自動驗證
交付產物只有七份文件 另外加上 _HARNESS.mdharness.yamlmisfit-log.md 三項產物

量化一下這套設計目前的樣子:六項工程指標,缺一項就是 FAIL;validator 是一支 688 行、只用標準函式庫(stdlib only)寫成的驗證程式;文件加機器可讀檔案加流程,總共十五加三項產物;misfit 分三類事件,驅動規範演進。

誠實清單:沒做的事

harness.yaml 要求每個專案都要聲明 uncovered_risks(沒被任何 gate 覆蓋的風險),既然這樣要求別人,這套方法自己也該接受同樣的檢查。與其把限制包裝成「未來展望」,不如直接列出來。

validator 不檢查語意。 它能確認 SC-01 對應到 BR-01,但沒辦法確認 SC-01 真的驗證了 BR-01 的意圖。UI 設計水準、mockup 品質、實作對齊得對不對,還是得靠人(或 AI 依 validation-rules)一項一項看。

內建的 YAML 解析器只是子集。 anchor、多行字串、複雜的流程式語法都不支援,遇到會提示裝 PyYAML。對這個 skill 自己產出的格式來說夠用,但如果是手寫的花式 YAML,不保證能解。

misfit-log 的累積判斷還是人工在做。 「連紅三次就要檢討」這條規則寫在文件裡,但沒有工具會自動幫你數。這是下一版最明確的候選項目——把外迴圈的觸發也做成機器化。

門檻建議值是通用經驗值,不是從你的專案量出來的。 第一次用一定要先量自己的基線再訂門檻,這也是為什麼「先量基線」這個決策會被寫進兩份參考文件裡反覆提醒。

最大的一條:這套設計本身還沒在真實專案上跑過完整一輪。 fixture(測試用的樣本情境)驗證過了,但實際用起來會冒出什麼新問題,還是得跑過才知道。這正是 misfit-log 存在的意義——真的用了之後遇到的問題會被記下來,變成下一版修訂的依據。

終章:三種語言,一件事

回到最開頭那兩篇文章。走完整個設計過程,它們跟 SPEX-SDD 的關係已經清楚很多。

Alexander 說的 diagnosis(診斷)——設計是逐一消除「不合身」,而不是朝一個抽象的「好」前進。Teddy 說的 Harness Engineering——人類的位置在提供規格、指出問題、把發現沉澱成規範。Uncle Bob 列的六項指標——放棄逐行審查之後,實際要盯的東西。三個人用的詞不一樣,但關注點其實一致:當產出的速度超過閱讀速度,光靠「讀」已經守不住品質,得補上持續可以執行的量測。

傳統做法的假設是「規格寫得夠精確,AI 就會照做」。 這個方法的假設是「規範若沒有偵測器,就等於沒有寫」。

中間那個轉折點,是承認人類的閱讀頻寬已經跟不上 AI 的產出速度—— 所以人不該再花時間讀 code,而該花時間設計「機器怎麼替我讀」。

如果只帶走三件事:

  1. 規範若沒有偵測器,就等於沒有寫。 每寫一條規範,先問「誰來抓」。

  2. 門檻訂在現況,不是理想。 第一天全紅的 gate 活不過一週,被關掉的 gate 比沒有 gate 更糟。

  3. 每一筆人工發現都是 harness 的洞。 記下來、補 detector,不然同樣的問題會無限重演。

這套方法的目的不是增加文件數量,而是讓寫下來的規範能接上機器會執行的檢查。沒有偵測器,規範再完整,也可能在日常開發裡被悄悄略過。

附錄 A:名詞表

依字母/筆畫混排,收錄本文出現過的核心術語。

術語 定義
Backbone(規格骨幹) _SPEC_BACKBONE.md;所有 ID 的唯一來源(canonical ID registry),一切文件、測試、gate、任務沿用其中的名稱與 ID
BC(Bounded Context) DDD 的限界上下文;SPEX-SDD 中其邊界由 GATE-ARCH 斷言強制
BR / FR / NFR 業務需求/功能需求/非功能需求;可測量的 NFR 必須綁 gate
DE(Domain Event) 領域事件(DE-xxx),有觸發條件與後續影響
Detector 會在規範被違反時主動變紅的機制:lint rule、架構測試、analyzer、門檻型 gate、覆蓋該行為的測試
DSL / ISA Gherkin 雙層驗收規格:DSL 層為繁中業務語言(人審),ISA 層為英文技術規格含 API/data 斷言(機器跑)
Forces(張力) Alexander pattern 的核心成分:規則在什麼力量拉扯下成立;落地為規範的 Why 與 When NOT
Gate(GATE-xxx) 可執行的品質關卡:metric、scope、command、threshold、baseline、blocking、schedule 七欄俱全
Harness 工程品質關卡層的總稱:_HARNESS.md(人讀)+ harness.yaml(機器讀)
Implementation Inventory(實作盤點) 棕地系統生成前必做的七項盤點:repo 佈局、路由、資料表、測試現況、關鍵常數、註解落差、品質基線
implementation_status 任務實作狀態:done(驗證+補測試)/partial(列缺口)/not_started(一般開發)
Misfit Alexander 語彙:具體可指認的「不合身」;SPEX-SDD 中泛指 gate 紅燈、AI 違規與未被偵測的問題
Misfit Log misfit-log.md;三類事件(gate-fail/rule-violation/undetected)的 append-only 記錄與規範修訂提案
Mutation Score(突變分數) 突變測試殺死變異體的比例;測試偵錯能力的客觀量測,低於 40% 幾乎可斷定測試是裝飾品
Piecemeal Growth 一次只長一小塊、立刻接受回饋的成長方式;落地為 Slice 模式
SC / US / TASK / SCREEN Scenario/User Story/開發任務/UI 畫面的 ID 體系;SC 格式為 SC-{MODULE}-{StoryNo}-{ScenarioNo}
Slice 增量生成的單位:一個 BR、模組或一組 SCREEN;未展開者標 outline
source_of_truth GATE-ARCH 指回 backbone Context Map 章節的欄位,防止 harness 與文件各自演化
uncovered_risks harness.yaml 必填欄位:明確不被任何 gate 覆蓋的風險清單,_HARNESS.md 須附人工補償措施
undetected misfit 三類中優先度最高者:人工發現但沒有任何 gate 抓到——harness 盲區的唯一訊號
Validator scripts/validate_spec.py;688 行 stdlib-only 結構驗證器,exit 0/1/2

附錄 B:快速參考卡

貼在螢幕邊的那種。指令、判斷準則、節奏,各一張。

指令卡

# 驗證
python3 ~/.claude/skills/spex-sdd/scripts/validate_spec.py docs/spec
python3 .../validate_spec.py docs/spec --repo /path/to/repo   # 加路徑檢查
python3 .../validate_spec.py docs/spec --json                 # CI 解析用

# 觸發
/spex-sdd <需求>                      # 斜線強制觸發
/spex-gen-test-docs-v5                # 下游:全層測試
/spex-gen-e2e-docs-v5                 # 下游:E2E 專家層

判斷準則卡

情況 判斷
要不要 slice? 已有實作 → slice;模組 > 8 → slice;一次交付 → full;≤ 3 模組 → full;其餘 → slice
問題被誰發現? gate 抓到 → gate-fail/rule-violation;人工發現 → 一律記 undetected
gate 連紅 3 次? 檢討門檻是否不合理或任務太大;調整必留痕跡(四欄+版本歷程)
規範綁不上 detector? 不刪。移 advisory_notes → 開 misfit → 重要者排 TASK-HARNESS 建偵測器
第一個 slice 挑哪個? 風險最高且沒有測試覆蓋的,不要挑最簡單的
零測試專案 gate 順序? UT 骨架 → ARCH → COV(report-only 兩週)→ LINT → CX → MUT(Domain、nightly)

節奏卡

導入時:盤點 + 基線 + Harness + 第一個 slice
每功能:展開 slice → 實作 → 跑 gate → 回寫 status
每 PR :validator + blocking gates
每晚  :突變測試等昂貴 gate
每兩週:檢視 misfit-log(undetected 未處理?gate 連紅?)
每季  :依累積提案修訂規範;重量基線、調門檻

三分鐘驗收卡

  1. 打開 _HARNESS.md:六項指標都有 gate 或不適用理由?

  2. 打開 agent_tasks.md 第一個 TASK:禁止事項有 Detector 欄?

  3. 跑 validator:FAIL = 0?

相關文件:使用指南 SPEX-SDD_使用指南.md|設計歷程 spex-sdd_設計歷程_把規範變成機器可驗證的關卡.md|skill 本體 ~/.claude/skills/spex-sdd/(SKILL.md、references/、scripts/validate_spec.py)|v4 方法論(backbone/DDD-BDD-TDD 原始設計)generate-tech-docs-v4_從規格到開發的技術文件方法論.md