一半的 code 是 AI 寫的之後,品質靠什麼守:DeepSeek Harness 的工程門檻
假設你的團隊為某個 domain 建 agent 的專案跑了三個月,客服單據處理也好、報表產出也好,一半以上的 code 是 AI 寫的。速度確實快,但你大概也開始看到三種病:AI 興沖沖地提出兩週前才被否決過的方案;測試全綠,上線就壞;還有一堆沒人敢刪的程式碼越積越多,因為沒人記得它為什麼存在。
這三種病不是你的團隊特有的,是 AI 重度參與開發的結構性後果。DeepSeek Harness(dsh)這個 repo 有意思的地方在於,它本身就大量由 AI agent 開發,而且把「怎麼守住品質」制度化成一組機器可驗證的門檻。根目錄的 CLAUDE.md 是 AGENTS.md 的 symlink,.agents/ 目錄下躺著 506 篇決策記錄,這不是一個「偶爾用 AI 輔助」的專案,是一個把 AI 當主要開發者、然後認真回答「那品質怎麼辦」的樣本。
這是 DeepSeek Harness 系列第三篇。前兩篇拆架構(組合式設計、runtime invariant),這篇離開架構,講工程紀律。你不需要讀過前兩篇,也不需要懂 dsh,因為這篇講的問題在任何 AI 重度開發的 codebase 都會出現。
讀完精華版(2 分鐘),你會理解:
- AI 重度開發的三大品質視角(決策、驗證、文件)與四種失效模式,以及
dsh對每一種的制度化解法 - 為什麼「未覆蓋的程式碼是待刪的死碼」比「補測試到 100%」是更正確的 coverage 讀法
- Agent 寫的測試會怎麼不自覺地作弊,以及三條防作弊規則背後的真實事故
- 一人團隊起步的 domain agent 專案,可以先搬走哪三個最便宜的門檻
這篇不是測試教學,是看一個把 AI 當主要開發者的團隊,怎麼用制度而非人力去接住品質。
精華版
| 視角 | 失效模式 | dsh 的門檻 | 核心規則 |
|---|---|---|---|
| 決策 | 被否決的方案一再回來 | Agent Notes(506 篇) | 非平凡 PR 必附決策記錄,Alternatives 必填 |
| 驗證 | 測試說謊:全綠但上線壞 | 驗證世界 + guard 見紅 + 真實進入路徑 | 斷言外部世界狀態,絕不 grep agent 的自我報告 |
| 驗證 | 死碼堆積:沒人敢刪的 code 越積越多 | per-file 100% coverage | 未覆蓋 = 死碼候選,不是補測試的指令 |
| 文件 | 膨脹與脫節:越寫越長、跟現實對不上 | doc 棘輪 + 機器驗證 | 字數天花板只降不升,範例 code 真的編譯 |
- dsh 的立場:品質門檻必須機器可驗證。靠 reviewer 的自覺去擋 AI 產出的量,是用人力對抗指數,制度輸定了。
- 三個視角的共同邏輯:AI 開發便宜的是「產出」,昂貴的是「共識與記憶」。門檻全部架在後者:決策要留痕、驗證要對外部世界、刪碼要有信號、文件要能對帳。
幾個關鍵設計問題的簡答:
為什麼 AI 需要決策記錄,人類團隊不是也該做? 人類團隊忘記決策要幾個月,AI 是每個 session 都失憶。沒有可檢索的決策記憶,AI 會用完全合理的推理重新走向已經被否決的方案,而且每次的論證都很有說服力。
「驗證世界」具體是什麼意思? e2e 測試的斷言對象是重新執行命令的結果、外部重讀的檔案內容,永遠不是 agent 自己輸出裡的成功字樣。會作弊的 agent 能輕鬆騙過關鍵詞檢查,騙不過重讀一次檔案。
100% coverage 不是出了名的形式主義嗎? 取決於你怎麼讀它。「補測試直到綠」是形式主義;「這行沒被覆蓋,先問它該不該存在」是死碼偵測。AI 產 code 便宜,膨脹的速度遠超人類專案,刪碼信號比測試數字重要。
這些門檻對小團隊會不會太重? 全套很重,但每一類都有一個一天內能架好的最小版本,文末有清單。
以下是完整版,按需取用。
為什麼 AI 重度開發需要另一套門檻?
需要另一套門檻的原因是:AI 改變了開發成本的分布。產出程式碼從貴變便宜,但共識、記憶、驗證的成本一毛都沒降,反而因為產出量放大而更貴了。傳統品質實踐(code review、測試覆蓋、文件規範)的隱含前提是「產出慢,所以人看得完」,這個前提在 AI 重度開發下不成立。
dsh 的回答是把所有門檻做成機器可驗證。決策記錄的格式有 gate 驗證、文件新鮮度有 gate 重跑 generator 比對、連 gate 腳本本身都有單元測試。repo 裡還有 11 個專屬的 agent skill,其中一個叫 dsh-trim-cot-leakage,專門獵捕 AI 寫進文件裡的思維鏈洩漏,像「(decision N)」「a later PR in this stack」這種只對當下 session 有意義的殘渣。這個 skill 的存在本身就說明了他們對「AI 是主要作者」這件事想得多細。
工程含義:如果你的 domain agent 專案已經一半以上由 AI 產出,先接受一件事,品質防線裡所有依賴「人記得」和「人看完」的環節都已經是斷點,差別只在爆掉的時間。
Agent Notes:怎麼讓 AI 不再重複提出被否決過的方案?
dsh 的規則是一條鐵律:每個非平凡變更,必須在同一個 PR 裡新增或修改至少一篇 Agent Note。非平凡的定義很具體:改行為、改架構、改跨檔案契約、改流程工具、改測試策略、改磁碟或線路或設定格式。只有純機械性的本地編輯豁免。目前的規模是 506 篇 implemented、142 篇 archived、25 篇 proposed、11 篇 rejected。
幾個設計細節比數量更值得看:
Alternatives considered 是必填欄位。 repo 裡的原話是:「沒記錄打敗了什麼的決策,是在邀請重新訴訟。」這句話值得貼在每個 AI 重度開發團隊的牆上。AI 提出一個被否決過的方案時,它的推理通常完全合理,因為否決的理由不在 code 裡,在當時的討論裡。把「我們考慮過 X,因為 Y 而不採用」寫下來,AI 下次檢索到這篇 note,重新訴訟就變成引用先例。
「重新訴訟」(relitigation)是 dsh 借自法律的說法:同一個案子被重新拿出來審。AI 沒有跨 session 的記憶,只要否決理由沒有留下可檢索的記錄,同樣的提案就會帶著全新的、看起來很有說服力的論證回到桌上,每一次都要重審一遍。
Note 永遠不會被改成不同的決策。 決策被推翻時,新寫一篇並互相連結,舊的標記 superseded;archived 的 note 連同雙語配對永久凍結,用 append-only 清單加 hash 保護。決策史是 append-only 的,跟第二篇講的 session log 是同一個哲學:修正用增量表達,歷史只增不減。
分類軸上刻意沒有 refactor。 路徑編碼兩個軸:狀態(proposed / implemented / rejected / archived)和類型(feature / bug-fix / simplification / architecture / process / testing)。refactor 這個類型刻意不存在,因為它是個什麼都能塞的垃圾抽屜,塞進去的決策等於沒分類。
格式由機器驗證。 verify-agent-note-format gate 檢查每篇 note 的結構,寫壞格式的 note 過不了 CI。決策記錄如果依賴自覺,三個月後就會變成沒人寫的廢墟;做成 gate,它就是流程的一部分。
對企業 domain agent 專案的翻譯:你的 agent 為什麼用這個 prompt 結構、為什麼不用某個熱門套件、為什麼 tool 的權限這樣切,這些決策現在大概散在 Slack 和某次會議裡。AI 檢索不到 Slack,所以它會一再地「重新發現」那些被否決的路。
AI 寫的 code 和測試,怎麼驗、怎麼刪?
驗證這個視角下其實藏著兩種不同的失效,常被混為一談:一種是測試在說謊(test suite 全綠,但功能是壞的),另一種是產品碼在堆積(沒人敢刪的 code 越來越多)。前者的對象是測試本身,後者的對象是 production code,dsh 對兩者的門檻也完全不同,分開講。
測試會說謊:三條防作弊規則
Agent 寫測試時的作弊不是惡意,是目標函數使然:它的任務是「讓測試通過」,而最短路徑常常不是修好功能,是寫一個會通過的測試。dsh 用三條規則堵這件事,每條背後都有一篇真實的 postmortem。
驗證世界,而非自我報告。 e2e 測試的斷言對象必須是外部世界:重新執行一次命令看結果、用測試自己的檔案讀取重看內容,未被改動的檔案斷言 byte-identical。絕對不做的事:在 agent 的輸出裡 grep 成功關鍵詞。postmortem 0003 記錄的事故正是這個陷阱:一個 web agent 的測試「驗證」了一台替身 server,而不是真正掛著它 session 的 GUI,測試綠了很久,功能根本是壞的。會作弊的 agent 能讓自己的輸出說任何話,騙不過的是世界的實際狀態。
Guard 要見紅才算數。 寫一個防護性測試,標準流程是:先人工引入它該防的回歸,親眼看它變紅,再 revert。沒見過紅的 guard 是薛丁格的測試,你不知道它綠是因為功能對,還是因為它根本沒在測。這條規則對 AI 產出的測試尤其重要,因為 AI 很擅長寫出「看起來在測什麼」的測試。
測真實進入路徑。 「真實進入路徑」指已發佈的產物:用 bin 跑 build 出來的 lib/,而非開發用的 tsx 直跑。postmortem 0001 的事故:ACP server 一連線就崩,原因是某個 export default 寫法丟掉了 plugin 的依賴宣告,而 tsx 的載入方式恰好遮蔽了這個問題,所有開發期測試都是綠的。
這三條之上還有一層結構性的保險:keyless snapshot 測試。每個非平凡的、會影響模型輸入或協議或人類可見行為的變更,同一個 PR 必須附上一個不需要 API key 的重播場景:跑真實的組裝流程、重播錄好的 session、diff 正規化後的完整 transcript。套件級測試、mock 組合、PR 描述都不能替代它,因為只有組裝後的 transcript 能證明「整個系統疊起來之後行為是對的」。設計上有個聰明的細節:恰好一個場景釘住完整的 system prompt 原文,其餘場景把 prompt tokenize,於是改動 prompt 時只有一行 diff 會動,review 負擔不會爆炸。
Mock 的紀律也值得記:mock 只放在昂貴或不確定的邊界(LLM adapter、網路、時鐘),邊界以下全部用真的。mock 得越多,測試證明的東西離真實系統越遠,這對 AI 產出的測試是加倍成立的,因為 AI 特別喜歡 mock 到測試必然通過為止。
對 domain agent 專案的翻譯:你的 agent 宣稱「已完成報表產出」,測試該驗證的是報表檔案存在且內容正確,不是 agent 的回覆裡有「完成」兩個字。這個原則我在LLM 分析 PR 的實驗裡從另一個方向碰過:AI 的自我描述和它實際做的事,永遠要分開驗證。
死碼會堆積:100% coverage 怎麼變成刪碼信號?
dsh 對 packages/*/*/src 下的每個檔案要求 100% 行覆蓋。聽到這裡多數工程師會皺眉,因為經驗告訴我們 100% coverage 通常意味著大量無意義的湊數測試。但 dsh 的讀法把方向反了過來,repo 原話:「未覆蓋的行常是 gate 正確標記待刪的死碼,不是要補測試」,以及「行覆蓋是必要條件,永非充分」。
差別在於 gate 觸發時你問的問題。傳統讀法問「怎麼讓這行被測到」,答案是再寫一個測試,於是死碼和湊數測試一起留下來。dsh 的讀法先問「這行為什麼存在」,答不出來就刪,答得出來才補測試。前者讓 coverage 成為膨脹的幫兇,後者讓它成為每次 PR 自動執行的死碼偵測器。
這個反轉在 AI 重度開發下從「有趣的觀點」變成「必要的機制」:AI 產 code 的邊際成本趨近於零,它會順手寫下防禦性分支、預留的參數、「以後可能用到」的 helper,每一段單獨看都無害,累積起來就是三個月後那個沒人敢動的 codebase。人類專案的膨脹以年計,AI 專案以週計,你需要一個每次 PR 都自動觸發的刪碼提示,而不是每季一次的大掃除。
配套的還有一個細節:每個 registry 必有 HMR-safety 測試,dispose 掉再斷言清乾淨。這類「卸載路徑」正是 AI 最不會主動測的地方,因為 happy path 的任務描述裡從來不會提到它。
AI 寫文件不費力,怎麼防文件膨脹和脫節?
文件在 AI 重度開發下的死法跟 code 不同:不是沒人寫,是寫太多、然後跟現實脫節。AI 寫文件毫不費力,於是文件膨脹得比 code 還快,而過期的文件比沒有文件更毒,因為 AI 下一輪會把它當事實檢索進來。dsh 的 doc-sync 體系大約有 28 個 gate,幾個設計特別值得看:
字數天花板是棘輪。 關鍵文件有 wc -w 字數上限(例如 AGENTS.md 上限 1900 字),而且天花板只會往下調,調高需要書面理由。這個方向性是精髓:文件的自然趨勢是膨脹,棘輪把「精簡」變成預設方向,把「變長」變成需要辯護的例外。
範例 code 會被真的編譯。 文件裡的 TypeScript code fence 由 doc-typecheck gate 實際編譯。文件裡的範例是最容易腐爛的部分,API 改了沒人記得回來改範例;讓編譯器當 reviewer,腐爛在 CI 就被攔下。
Generated catalog 用重跑驗新鮮。 所有生成式目錄的 gate 做法是重跑一次 generator、diff 輸出,過期立刻現形。每個 export 都要有 JSDoc,格式不認識的一律 fail closed。
雙語文件用 git blob hash 對帳。 每份文件是三胞胎:foo.md、foo.zh.md、foo.i18n.yaml,第三個檔記錄兩側上次確認一致時的 git blob hash。一側被編輯後配對失效,修復方式是按編輯側的 diff 打最小化補丁,絕不整篇重翻;兩種語言同等權威,中文先寫一樣合法。對需要中英文件並行的台灣企業,這比「翻譯放另一個 repo」或「靠人記得同步」都務實得多。他們也誠實標注了極限:「綠燈只代表這對內容在此刻被確認過一致,不代表確認是對的」,語義忠實度仍是 reviewer 的那一半。
還有一個支撐 review 的小工具:change-scope 腳本產出機器版的「這次改動碰了哪些層」報告,review 流程要求先讀它再看 diff。AI 的 PR 常常又大又散,先給 reviewer 一張地圖,比要求 reviewer 自己從 diff 拼出全貌現實得多。
一人團隊的 Domain Agent 專案,先搬哪三個?
全套 28 個 gate 對起步團隊當然太重。但三個視角各有成本極低的最小版本,按回報排序,我會先搬這三個:
第一個:決策記錄,成本半天。 開一個 decisions/ 目錄,PR 模板加一個必填欄位「考慮過的替代方案與否決理由」。不用學 ADR 的完整格式,關鍵只有 Alternatives 那一欄。從此你的 AI 助手在動工前可以先檢索這個目錄,被否決的方案第一次有了可引用的先例。這是四類裡回報最快的一個,第一次擋下重新訴訟就回本。
第二個:驗證世界,成本是改寫幾個斷言。 把現有 e2e 測試裡所有「檢查 agent 輸出包含成功字樣」的斷言,改成重讀世界狀態:檔案真的存在、內容真的正確、命令重跑結果一致。再給最重要的一兩個 guard 走一次見紅流程。這一個下午的工作,換到的是你的綠燈第一次真的可信。
第三個:coverage 當死碼偵測,成本是開一份報告。 不用強推 100%,先把 per-file coverage 報告掛進 CI,規則只有一條:連續幾週未覆蓋的行,PR 裡要嘛給它一個測試,要嘛給它一個刪除。重點不是數字,是每次 PR 都有人(或 AI)被迫回答「這行為什麼存在」。
文件棘輪排第四,等你的文件多到開始互相矛盾時再上,那個時間點會自己到來。
這三個的共同點呼應這系列一貫的判斷方式:先看你的成本分布,再決定把制度架在哪。AI 讓產出變便宜之後,你的專案最貴的資產是決策記憶和可信的驗證,門檻就該架在那裡。
結語:AI 重度開發的品質問題,本質是產出的速度超過了共識的速度。
dsh的答案不是讓人看得更快,是讓決策可檢索、讓驗證對世界、讓刪碼有信號、讓文件能對帳,全部做成機器可驗證的門檻。速度是 AI 給的,品質是制度給的。
本文是 DeepSeek Harness 系列第三篇,也是完結篇。第一篇拆組合式架構,第二篇拆狀態核心的 runtime invariant,這篇收在工程紀律。三篇合起來是同一個問題的三個層面:當 AI 成為主要開發者,架構、狀態、流程各自需要什麼樣的硬保證。
延伸閱讀
- 當 Agent Harness 沒有核心:DeepSeek 的 Everything is a Plugin:系列第一篇,組合式架構與一人團隊的借鑒判斷
- 模型可見 ⟺ 已記錄:DeepSeek Harness 的 Runtime Invariant:系列第二篇,append-only 哲學在狀態層的版本
- 機器學習的本質思考:Vibe Coding 時代工程師的關鍵決策指南:本系列命題的原點,AI 時代工程師的價值在判斷
- 從 PR Review 中學習:用 LLM 分析 PR:AI 自我描述與實際行為要分開驗證的另一個實驗