讓 Opus 寫規格、GPT 實作:OpenSpec 的實際使用經驗
從一句 prompt 直接叫 agent 做功能,到用 OpenSpec 讓兩個不同的 AI 分別負責設計與實作。記錄這套流程真正擋下了什麼、代價是什麼,以及哪些問題它擋不住。
原本的做法
在這之前,我只訂閱 ChatGPT,開發方式很單純:想到什麼功能,就丟一句 prompt 給 agent。
新增 email 登入與雲端存檔的功能
然後 GPT 就開始寫。寫完我自己開來測,有問題再回頭叫它改。改到我測不出問題為止。
這樣做其實能動,而且很快。但問題也很明顯:所有的設計決策都藏在實作裡。當我發現行為怪怪的,我很難判斷那是 bug、還是 agent 當初就是這樣設計的。而且每次要它改,它的「改」有沒有動到別的地方,我也不知道。
為什麼改用 OpenSpec
後來我加訂了 Claude。手上有兩個能力相當但立場可以不同的 agent,我開始想一件事:如果讓它們一個負責設計、一個負責實作,會不會比一個人從頭做到尾好?
要做到這件事,得先有個東西讓兩邊對齊——那就是規格。所以我開始用 OpenSpec。
OpenSpec 的核心概念很簡單:
openspec/specs/是基準,描述產品「現在」的行為openspec/changes/<change-id>/是這次要改什麼,寫成對基準的 delta- 實作完成後執行
openspec archive,把 delta 併回基準
流程變成這樣:
- Opus 5 產生規格(proposal、design、spec delta、tasks)
- GPT-5.6 Sol review 規格,來回幾次收斂
- GPT-5.6 Luna 實作
- 交回 Opus 5 驗收,一樣來回數次,確保實作與規格對齊
- 最後我自己測
這套流程真正擋下了什麼
講優點如果只說「品質比較好」是沒有意義的。我把這次做「帳號與雲端存檔」時,各個階段實際抓到的東西列出來。
規格階段(還沒寫任何程式碼)
Sol 在 review 規格時抓到這些:
- Web Locks 用 userId 當鍵會造成跨帳號資料污染。 因為
__Host-cookie 是同一瀏覽器共用的,兩個分頁用不同帳號登入時,第二個會覆蓋第一個的 cookie,但兩者的鎖不衝突——結果 A 分頁的存檔會寫進 B 的帳號。 - 二階段登入協定會先把 token 清掉,玩家永遠登不進去。 規格要求「consume 回傳任何結果後就清除記憶體中的 token」,但其中一種結果是「需要玩家再確認」——確認之後已經沒有 token 可送了。
- 規格要求了一件資料庫做不到的事。 我寫「在單一 D1 交易中完成解碼、遷移、寫回」,但 D1 沒有互動式交易,沒辦法「讀一列 → 回到程式碼跑 TypeScript → 繼續同一個交易」。這條規格照字面實作不出來。
- 「重試用盡時回到操作前狀態」在邏輯上不可能。 重試失敗正是因為別人已經合法改了狀態,要「回到操作前」就得把別人的變更也回滾。
這些如果沒有 review,會一路寫進程式碼裡。前兩個是資料損毀等級的問題。
實作驗收階段
Luna 實作完,Opus 驗收時抓到:
saveGame()在沒有登入時直接靜默失敗,玩家完全不會知道進度沒存到- 一般存檔會把「遷移前的備份」欄位清掉,導致「遷移出錯可回復」的保證實際上只維持幾秒
- 被接管的裝置,如果有一個寫入正在傳輸中,它仍然可以落地覆蓋新裝置的資料
還有一個很有意思的:Luna 在補寫測試的過程中,自己撞出了一個 bug——兩個請求同時消耗同一個登入連結時,落敗的那個會被誤判成「已在其他裝置登入」,於是引導玩家去按確認,但那個 token 早就死了。
這種 bug 靠讀程式碼幾乎看不出來,得真的寫測試去跑。
只有真人開瀏覽器才會發現的
然後我自己測,馬上看到 console 一整片紅字。
原因是:玩家點開登入信會落在 /auth/magic-link,而資產路徑寫成相對路徑,在那個深度會解析成 /auth/audio/...。而 Cloudflare Worker 設定了 SPA fallback,未匹配的路徑不會回 404,而是回 200 加一份 HTML。於是 Phaser 拿 HTML 去解碼音檔,音樂、圖片、音效全滅。
這個缺陷 100% 觸發——每個玩家點登入信都會遇到。但三輪規格審查、486 個自動化測試、typecheck 全部都沒抓到,因為 smoke test 只從首頁開機,從來沒從那個路徑進去過。
缺點與代價
慢
這是最直接的代價。光是規格審查就跑了三輪,每輪用高推理強度大約十分鐘,中間還有我自己讀、自己驗證、自己改的時間。「直接叫 GPT 做」可能兩小時就有東西可以測了。
阻斷問題不會收斂到零
三輪審查的「必須修正才能實作」問題數是 3 → 2 → 4。看起來完全沒有收斂。
但仔細看內容就會發現:第三輪的問題幾乎都是第二輪修正引入的。守衛條件、revision 遞增規則這些東西,在第二輪之前根本不存在。
這給了我一個結論:用純文字窮舉所有併發交錯順序,邊際效益是遞減的。有些問題寫測試會比寫散文更快得到答案。到某個點就該停止文件迭代,進實作。
寫規格的人不一定知道平台限制
「單一 D1 交易」那個問題就是這樣來的。我把「對外看起來是原子的」寫成了「單一資料庫交易」,而那兩件事在 Cloudflare Workers 上不一樣。
規格寫得再嚴謹,如果對執行環境的理解是錯的,它就只是一份漂亮的錯誤。
文件自己會腐爛
OpenSpec 的 archive 只套用 requirement 層級的 delta,不會更新每份 spec 開頭的 Purpose 散文。
結果是:我上一次封存之後,save-progression 的 Purpose 仍然寫著「本能力是玩家所有長期進度的唯一持久化來源」「定義單機進度在瀏覽器本機的保存」——而那時候存檔早就搬到雲端了。那段話是每份 spec 最先被讀到的一段,而它已經是錯的。
需要注意的事
review 的 prompt 要列出「已經採納的項目」
第二輪 review 時,我在 prompt 裡明確寫了第一輪的九個問題與修法。否則它會把同樣的東西再提一次,你得自己過濾。
不要照單全收 review 的結論
有一次 agent 回報「原先的 index.html 修改已保留未碰觸」,但 git status 顯示那個檔案確實被改了。我每一項可查證的宣稱都實際去對過檔案,而不是相信摘要。
同樣地,我也要求它「如果認為現狀是對的就直說,不要為了湊數而提」。
測試要驗證它真的會失敗
我補了一個從登入路徑開機的 smoke test,它通過了。但「通過」不代表它有用——所以我把修正暫時移除再跑一次,確認它會失敗並指出:
MISSING /auth/audio/bgm-ink-strategy.ogg
(served the SPA fallback as text/html instead of the asset)
不做這步,你不知道自己寫的是不是一個永遠會綠的空測試。
archive 是必要的一步
AGENTS.md 裡寫得很直接:archive 才是讓 delta 併入基準的動作,跳過它會讓基準悄悄失效。
而 OpenSpec 的 archive 本身也有防呆——這次它中止了一次,指出我的 MODIFIED 區塊漏列了基準裡的 scenario。因為 MODIFIED 是整條取代 requirement,漏列就等於靜默刪除。七條需求、十四個 scenario,全是我改了名字或以為不再需要的。
那次中止擋下了一次真實的內容遺失。
不是每個變更都需要這套流程
改一個文案、修一個明確的 bug、調一個平衡數值,直接做就好。OpenSpec 值得的是那種會改變既有行為、或引入新規則的變更——這次的帳號系統改動了存檔的真相來源、登入的語意、併發的規則,那才撐得起這個成本。
小結
一句 prompt 直接做,跟先寫規格再實作,差別不在「哪個比較認真」,而在錯誤在哪個階段被發現。
規格階段抓到的問題,改一段文字就好。實作後抓到的,要改程式碼加測試。上線後抓到的,玩家已經受影響了。
這次的實際分布是:規格審查抓到 4 個資料損毀等級的設計錯誤,實作驗收抓到 5 個,我自己開瀏覽器抓到 2 個。 最後那兩個,是任何自動化流程都看不見的。
所以我的結論是:這套流程值得,但它不能取代真人去點開來看。
本文由 Opus 5 協助撰寫,素材取自實際的開發對話紀錄。