Specification-First Convergence(規格先行收斂)不是先叫 AI 寫一份漂亮計畫,而是先逼規格與原始碼互相對質。當你接手一個缺少可用回歸測試、沒人敢碰的 TypeScript 老專案,風險不只來自改動本身,也來自把猜測寫進規格,再讓 AI 高速實作那個猜測。
這篇專為第一次維護 legacy code(歷史包袱較重、但仍在運作的程式)的讀者而寫。你不必先懂軟體架構;我會用一個小型訂單折扣改版,帶你從盤點現況、建立行為證據,到凍結規格、拆步驟與執行雙零發現閘門。看完後,你會有一份能直接放進 repo 的改版協定,而不只是一段提示詞。
先說結論:安全不是「AI 兩次說 OK」
🧭 記憶把手:安全改版=可查證規格+小步證據+雙零停止閘門。
「雙零」只代表在固定範圍、固定證據與固定規格下,連續兩輪沒有找到新問題;它是停止搜尋的規則,不是程式正確性的證明。
如果規格是錯的,兩輪零發現只會更有秩序地交付錯誤。因此順序不能顛倒:先觀察既有行為,再把每一條規格連回原始碼、執行結果或產品決策;矛盾清完才 freeze(凍結版本),之後每次改動都留下可重播的證據。這也補上大型 Repo 安全改版的下一層:前者控制爆炸半徑,本文專注在「規格何時有資格驅動改版」。
Specification-First Convergence 是什麼?
這個名稱來自一篇 2026 年 8 月的單一案例研究。作者用 coding agent 修改一個封閉原始碼的 TypeScript 應用:程式庫約 717,725 行、3,648 個檔案,目標是拆掉跨 UI 與執行階段的核心假設。規格歷經 14 輪、約 85 次修正,範圍從 110 個檔案擴到 160 個;實作後又做 17 輪稽核、記錄 116 次 code correction,第 16 與 17 輪都回報零個 confirmed bug/不需程式修正,因而停止。最後觸及 189 個檔案,作者回報三天內完成、推論成本 2,430 美元。
這些數字只能描述該案例,不能當成你的工期或成功率。約 85 次規格修正含作者估算,不是 85 個完整保存的 bug;2,430 美元也只是報告的 inference spend。論文明列沒有對照組、沒有外部人類 code review,程式碼不公開;它也未報告專案年齡或把專案分類為 legacy。「第一次手動執行未觀察到 bug」是作者回報,不等於沒有潛在缺陷。作者所屬公司也開發並銷售該 agent,解讀成效時要把這項利益關係放進證據權重。
「零發現」也不是字面上的零觀察。公開的第 16 輪與第 17 輪紀錄仍列出規格偏差、完成度估計與殘餘風險,只是判定沒有 confirmed bug/必要修正。更關鍵的是,第 17 輪判定後,人類首次操作時發現持續顯示的提示橫幅;agent 起初解釋錯誤,經人類澄清後才修改一個 source file 與測試。作者把它視為 UX 調整而非核心串流故障;截至 2026 年 8 月 19 日,公開工件索引未列出這次修改後的新一對完整零修正稽核。這正好說明:雙零只能觸發下一階段的動態與人工驗收,不能替代它。
更重要的是,論文標題的 no test oracle 不是「整個專案完全沒有測試」。案例已有大型 unit test suite,agent 也新增測試;缺的是能完整判定這項新跨生命週期行為是否正確的預先存在 oracle(判分標準)。所以本文把方法移植到缺少可用測試的老專案時,多加一道前置工作:先建立 characterization test(特徵測試),記住程式現在怎麼做,而不是先宣告它應該怎麼做。
另一篇從零合成程式的SpecFirst 研究,在 200 個有可執行判分器的任務上觀察到先建規格能改善測試通過與探索覆蓋;但那不是 legacy refactor,也不驗證雙零規則。把兩篇放在一起,最合理的結論很窄:規格先行值得用來組織搜尋,真正的安全仍取決於外部證據與檢查器。
沒有可靠測試時,先建立三層證據
老專案常見的陷阱是把 README、函式名稱或同事記憶當成真相。你要把資訊分成三層,而且不准互相冒名:
- 已觀察:目前可由畫面、API 回應、log、資料庫或指令重現的行為;要附輸入、輸出與觀察時間。
- 已決策:產品負責人明確核准的新需求,例如「VIP 訂單改為額外 5% 折扣」。這是目標,不是現況。
- 未知:找不到證據、不同路徑互相矛盾,或只能靠猜測的部分。未知要留在規格裡,不能由 AI 自動補完。
Characterization test 就像搬家前先替房間拍照:它不保證原本擺法合理,卻能在搬動後告訴你哪裡變了。先挑最重要的入口,用固定輸入保存回傳值、狀態變化或 snapshot;動態時間、隨機 ID、排序不穩定欄位要先正規化。Vitest 的官方 snapshot 說明也提醒,snapshot 是要被檢閱與提交的參考輸出;直接按更新,只會把回歸一起核准。

Specification-First Convergence 7 步驟實作
1. 先封存基線:你究竟從哪個版本開始?
痛點:如果 branch、依賴與工作目錄不固定,之後的差異就無法重播。解法是建立 baseline 驗證紀錄:記下 commit SHA、Node 與套件管理器版本、安裝方式及既有失敗。本文把這種可重播紀錄簡稱 receipt;它不是論文提出的正式標準。先執行唯讀盤點,再建立專用 branch:
git status --short
git rev-parse HEAD
node --version
npm --version
npm run
git switch -c refactor/vip-discount
npm run會列出專案已定義的 scripts。若 repo 有 lockfile 且原本使用 npm,npm ci會依鎖定版本做乾淨安裝,lockfile 與套件描述不一致時直接失敗;若專案使用 pnpm、Yarn 或自訂流程,就遵循 repo 文件,不要擅自換工具。
2. 列出行為切片:先問「現在發生什麼」
痛點:「修改折扣功能」太大,AI 會在搜尋途中自己定義邊界。解法是建立 behavior inventory:列出一般會員、VIP、空購物車、退款與跨日結算等入口;每一列保存呼叫路徑、輸入、目前輸出、證據位置與未知項。只要能重現一個切片,就先替它加測試或 smoke script。需要在難以測試的程式中插入觀察點時,可以利用 Martin Fowler 解釋的legacy seam,把時間、網路或資料庫依賴替換成可控制的接縫。
3. 寫可被反駁的規格,不寫願望清單
痛點:「改善 VIP 折扣且不要破壞其他功能」無法驗收。解法是建立 docs/change-spec.md,至少包含目標、非目標、已觀察行為、不變條件、接受條件、回滾方式與未知項;每條現況聲明都帶 evidence locator:
# Change Spec: VIP discount
baseline_commit: <git SHA>
Goal
- VIP 的商品小計額外乘以 0.95
Non-goals
- 不改運費、稅額與退款政策
Observed
- 一般會員 total = subtotal + shipping + tax
evidence: src/pricing/calculate.ts + characterization test C01
Invariants
- 同一訂單重算兩次,結果相同
- 金額四捨五入規則不變
Acceptance
- C01、C02 舊切片不變;N01 VIP 新案例通過
Unknown
- 歷史退款是否重算 VIP 折扣:owner 待決
原案例公開的最終規格也把 foundation、問題、原子性、不變條件、目標、檔案與測試放在同一份工件。你不必照抄它的規模,但要保留「一條聲明對一個可定位證據」的骨架。
4. 讓規格與原始碼互審,專門找反例
痛點:同一段對話產生規格又審規格,容易重複同一套假設。解法是做雙向 source audit:第一輪從規格逐條找 code/runtime 證據;第二輪從入口、呼叫圖、型別與資料流反查規格是否漏掉路徑。把 reviewer 放在新 context,先給固定 baseline、規格與允許讀取的證據,再用這種輸出格式:
任務:反駁規格,不要實作。
每個 finding 必須包含:
1. spec 條款;2. 反例或遺漏路徑;3. file:line/可重現輸入;
4. 影響;5. 最小修正。
找不到新問題時,只回報 ZERO_FINDINGS,並列出實際檢查範圍。
新 context 只能降低前一輪敘事的影響,不會讓同一模型真正獨立。研究也顯示,缺少外部回饋時,語言模型的自我修正可能失效或退步;因此 finding 沒有定位證據就不算,ZERO_FINDINGS 沒有 scope receipt 也不算。
5. 凍結規格:變更可以,但要重新核准
痛點:實作途中偷偷改 acceptance criteria,最後永遠都能宣稱成功。解法是用內容雜湊鎖住規格:
git hash-object docs/change-spec.md
git diff -- docs/change-spec.md
把輸出的 hash、baseline SHA、核准人與未解未知項寫進 freeze receipt。需求仍可變,但每次改規格都建立新版本、說明原因、重跑相關 characterization tests,並把雙零計數歸零。這和AGENTS.md 五道閘門的精神相同:規則要能被工具檢查,不能只期待 agent 記得。
6. 拆成可成立的小改動,每步留下 receipt
痛點:一個上萬行 diff 很容易讓因果關係消失。解法是拆成 coherent change:每一步都保持可建置、只處理一個可說明的目的,例如先抽出 pricing seam、再補 characterization test、最後加入 VIP 規則。Atomic 不代表盲目追求一行一個 commit,而是失敗時能定位、重播與回退。
git diff --check
git diff --stat
git diff --name-only
# 只執行 npm run 列得出的專案命令
npm run typecheck
npm test
git diff --check只抓 conflict marker 與 whitespace error,不是完整測試;TypeScript 的 noEmit也只讓編譯器檢查而不輸出檔案。每步 receipt 要記錄 spec hash、起訖 SHA、檔案清單、執行指令、exit code、失敗摘要與人工觀察;命令在專案不存在,就不能偽造一個綠燈。
7. 執行雙零發現:任何變動都重設計數
痛點:「再看一次」沒有停止條件,也容易把疲勞當收斂。解法是預先登記雙零協定:凍結 code 與 spec 後,用新 context 進行 code-versus-spec 審查;找到問題就修、重跑檢查並把計數設回 0。只有連續兩輪都回報 ZERO_FINDINGS,而且兩輪各自保存檢查範圍、讀過的檔案、命令與 spec hash,才允許停止靜態稽核、轉入人工與 runtime 驗收。原案例的雙零是過程中採用的 empirical rule,並非事前校準;本文要求先登記,是更保守的移植方式。
- 輪次 A:從 acceptance criteria 往 code 找反例,包含錯誤路徑與非目標。
- 輪次 B:從實際 changed files、呼叫者與狀態邊界反查規格,避免只是重播 A 的清單。
- 重設條件:程式、測試、規格、工具版本或 audit scope 任一改變,連續零發現歸零。
- 停止輸出:除了兩張 zero receipt,還要列出未執行平台、未觀察資料、未涵蓋並行與外部服務等未證明事項。
Anthropic 的loop engineering 指南同樣建議在循環開始前定義停止條件、使用 fresh context、加入 deterministic checks 與成本上限。雙零只是這些工程護欄的一種具體化;高權限、金流、個資或不可逆資料遷移,仍應加上領域專家 review、staging 演練與可驗證 rollback。
小型 TypeScript 範例:VIP 折扣怎麼走完一輪?
以下是教學用的虛構 trace,不是 AlphaLab 對某個公開 repo 的實測。假設舊函式 calculateTotal(order)同時處理商品、運費與稅。產品只核准「VIP 商品小計乘以 0.95」,沒有核准運費或退款改動。
- 先保存一般會員、VIP、空購物車與退款四個目前輸出;若退款資料無法取得,記為未知,不猜答案。
- 從呼叫者反查,發現後台重算與 checkout 共用函式,因此把兩條入口都寫進規格。
- 先抽出
calculateMerchandiseSubtotal()作為 seam,舊切片輸出必須不變;再單獨加入 VIP 乘數與新案例。 - 每步保存 diff、typecheck/test 結果與手動 smoke evidence。任何既有輸出改變,都要對應到已核准條款。
- 雙零後仍列出「歷史退款重算尚未證明、跨時區結算未執行、真實支付服務未連線」。Release owner 看到的是邊界,不是一張空泛的安全保證。
這種 receipt 做法也能降低Cognitive Debt:下一位維護者不必重新猜「為什麼這樣改」,可以從規格 hash、證據與決策一路重播。
常見失敗:雙零為什麼可能是假收斂?
- 規格與審查共用同一個錯誤假設:加入 runtime observation、使用者案例或獨立 owner 決策,不讓文字自己證明文字。
- 兩輪其實是同一輪的改寫:交換搜尋方向與入口,保存實際 scope;同一模型的新 context 仍不能宣稱統計獨立。
- Snapshot 把雜訊當變更:先固定時間、亂數與排序,再由人檢閱 snapshot,而不是自動更新。
- 規格 freeze 後暗改:任何內容變更產生新 hash,重新核准並歸零。
- 小 commit 但每一步都壞:用「可建置、可解釋、可回退」定義原子性,而不是只看行數。
- 檢查器只看它看得見的事:typecheck 抓不到產品語意,unit test 抓不到未建模環境;把未證明事項當正式產物。
- 循環失控:預先設定時間、token/費用與輪次上限;到上限仍有 finding,就升級給人決策,不把它改寫成 zero。
這套方法能證明什麼、不能證明什麼?
它能證明的是流程事實:某個 baseline 上,某份已雜湊規格經過哪些 source audit;哪些改動通過哪些固定檢查;兩輪審查在各自聲明的範圍內沒有新增 finding。它不能單憑這些 receipt 證明所有輸入、平台、時間競態與外部服務都正確,也不能替代需求 owner 對「應該怎麼運作」的決策。
因此更可稽核的 release 句子不是「AI 證明安全」,而是:「在 baseline X、spec hash Y 與 audit scope Z 下,固定檢查通過並取得兩次零發現;A、B、C 仍未證明。」如果你的團隊還沒有可重跑的評測觀念,可以先讀AI Evals 7 步教學,把最重要的使用者任務變成固定案例。
Specification-First Convergence 常見問題
1. 連續兩次 zero findings 就代表程式安全嗎?
不能。它只代表兩輪在已聲明範圍內沒找到新問題。錯誤規格、缺失的 runtime、未涵蓋平台與相關性很高的 reviewer,都可能留下盲點。
2. 完全沒有測試,也能直接開始改嗎?
不建議直接改。先替最重要的現況行為建立 characterization test 或可重跑 smoke script;無法觀察的路徑列為未知,縮小首輪範圍。
3. 規格由 AI 寫,還算 specification-first 嗎?
可以,但 AI 不能當需求 owner。它可以整理候選規格與找反例;每條現況要回到 evidence,每個新語意要由有權決策的人核准。
4. 兩輪一定要用不同模型嗎?
不一定。不同 reviewer 有助於降低共同盲點,但關鍵仍是固定 spec、fresh context、不同搜尋方向與可定位證據;換模型本身不會創造真相。
5. 找到一個新問題後,還能把前一個 zero 算進去嗎?
不能。修正 code、test 或 spec 後,受審工件已經不同,連續計數必須回到 0,再做兩輪。
6. 每個 finding 都要修嗎?
不一定。它可以被修正、由 owner 接受並寫入風險、或證明不在 scope;但不能無聲刪除。處置結果要進 decision receipt。
7. 這套流程適合緊急 hotfix 嗎?
適合縮小版,不適合照搬大流程。至少固定 baseline、寫出單一不變條件、保存最小重現、限制 diff,部署後補齊完整規格與回歸證據。
8. 什麼情況不該讓 AI 自動推進?
遇到需求矛盾、不可逆資料遷移、高權限操作、個資/金流邊界或 rollback 未驗證時就停。這些不是多跑幾輪能解決的 finding,而是需要新權限、領域判斷或真實環境證據。
給新手的 5 個重點
- 先記錄程式現在怎麼運作,再討論它應該怎麼改。
- 規格中的每條現況,都要能走回 code、runtime 或測試證據。
- 規格凍結靠版本與 hash,不靠「大家應該記得」。
- 每個小改動都保存 diff、命令結果與決策 receipt。
- 雙零是停止規則;未證明事項清單才是誠實的安全邊界。
想把這套流程擴成自己的 AI 開發工作流,可以到 AlphaLab 的線上課程,從 prompt、context、eval 到 agent harness 逐步建立共同語言;也可以先逛AI 教學專區,挑一個眼前最痛的工程問題開始。
接著閱讀
左右滑動查看更多推薦
結語:今天先建立第一張可重播收據
Specification-First Convergence 真正改變的,不是 AI 寫 code 的速度,而是團隊宣告「可以停」的標準。請先建立 docs/change-spec.md,填入 baseline SHA、選一個可觀察行為、保存目前輸出,並把不知道的部分老實寫進 Unknown。今天先不要叫 AI 實作;先讓它找出規格與原始碼的第一個矛盾。當證據能重播、規格能被反駁、小步改動有收據,雙零才有意義。





