「留言已解決」不是「改動已驗證」:AI code review 的證據契約
一個 pricing checkout 的 PR,被 AI reviewer 指出年度方案的計價方式有問題:前端可能先把月費四捨五入,再乘以十二;後端 invoice 則直接計算年度總額。兩邊各自看都合理,最後卻可能差一個最小貨幣單位。
開發者推送 follow-up commit。重新 review 後,原本的留言自動變成 resolved,commit message 也準確寫著修正 annual-price rounding。PR 畫面乾淨多了,未處理的意見不再淹沒在舊 thread 裡。
但這個畫面仍看不出前後端是否使用同一條規則、測試跑在哪個 revision,也不知道後來修改 currency formatter 的 commit,是否讓剛才的綠燈過期。
自動關閉留言很實用。問題只出在團隊把 resolved 當成 verified,又把 verified 當成可以合併的決定。
Resolved 管理注意力,不負責證明正確
GitHub Copilot code review 現在可以在 rereview 時,自動關閉被後續 commit 處理的 Copilot 留言;仍未處理的 feedback 則保持 open。這解決的是 PR 日常裡很真實的整理成本。否則 reviewer 每次回來,都得重新判斷哪些 thread 已經失去作用。
所以我不會關掉 auto-resolution。Open threads 愈接近「現在還需要注意的事」,review 愈容易進行。
只是 resolved 的語意應該到此為止:系統判定這項 feedback 已被後續改動處理,目前不再需要佔據注意力。它沒有順便證明需求正確,也沒有表示 repository 規定的檢查已在 current head 通過。
同一波更新也讓 review agent 能在防火牆後使用 shell tools,執行 build、test 或 targeted script。這讓分析有機會更接近真實執行環境,但「能呼叫工具」仍是一種能力。團隊真正能拿來驗收的是另一組東西:執行了什麼命令、結果如何、產物在哪裡,以及結果綁定哪個 revision。
一個 finding,其實有三種狀態
Review UI 往往只給我們一個很醒目的綠色訊號。工程流程裡至少要拆成三層:
resolved:這項 finding 是否仍需要人處理。
verified:針對某個明確 revision,指定檢查是否已產生可定位的成功證據。
accepted:有權負責的人是否確認需求已滿足,並接受剩餘風險。
三者偶爾會在同一時間發生,但不該互相繼承權威。
AI reviewer 可以判斷自己的留言已被處理,CI 可以證明某組測試在某個 commit 通過,產品 owner 則決定年度價格究竟該遵守哪一條商業規則。它們回答的不是同一個問題。
這個區分也能避免另一個常見誤會:更精準的 commit message 很適合當索引,卻不是驗證證據。「Fix annual price rounding」可以幫 reviewer 快速找到相關改動,不能替代實際 diff、測試結果或需求來源。
為重要 finding 留一份五欄契約
小團隊不需要先導入一套龐大的治理平台。可以從每個高風險 finding 多留五個欄位開始。下面不是 GitHub 定義的格式,而是一份可以放進 review bot、check run 或內部 PR metadata 的工作契約:
finding: observed_at: 8c91a2e paths: - src/pricing/annual.ts - api/invoice/calculate.ts invariant: annual total must match the invoice total for each currencyaddressed_by: d43f710validation: revision: d43f710 checks: - command: pnpm test:pricing-contract result: passed artifact: ci://runs/1842acceptance: owner: product-pricing decision: acceptedinvalidation: paths: - src/pricing/** - api/invoice/** - fixtures/currency/**finding 保存問題成立時的座標。除了原始 revision 與 affected paths,最好再留下可重現條件或 invariant。Comment 可以被關閉,問題原本在什麼情況下成立,不能跟著消失。
addressed_by 只記錄哪個 follow-up revision 宣稱處理了 finding。它不偷渡「已驗證」的意思。若一次修正同時動到許多無關檔案,也可以在這裡縮小真正相關的 diff 範圍,讓後續檢查容易追蹤。
validation 保存檢查名稱、結果、artifact 與 revision。最容易漏掉的是最後一項。測試曾經通過,不代表目前 PR head 仍然通過;驗證完成後只要又寫入相關檔案,舊結果就不能繼續替新程式碼背書。
acceptance 指向最後承擔決定的人或政策。Code owner 可以確認實作與風險,產品 owner 確認計價規則,branch policy 則決定哪些條件滿足後才可合併。這個欄位不一定都由人手動填寫,但權限來源要說得清楚。
最後是 invalidation。它決定證據何時失效,也是整份契約最值得自動化的部分。
證據會過期,thread 不一定會自己打開
回到年度價格的例子。修正 rounding 的 commit 通過了前後端 contract test,原始留言也已關閉。半小時後,另一個 commit 更新 currency formatter 與測試 fixture,卻沒有碰到最初被標記的那一行。
如果流程只看 thread 狀態,畫面依然是 resolved。如果證據契約知道 formatter 與 fixture 都屬於 invalidation 範圍,它就能把先前的 validation 標成 stale,要求在新 head 重跑相應檢查。
重跑成功,finding 可以繼續保持 resolved。若 contract test 失敗,或原本的 invariant 已經無法成立,就應 reopen 原 finding,或建立一個連回原始證據的新 finding。重點不是一定要把留言打開,而是不能讓過期證據維持有效外觀。
路徑規則也不必只靠 glob。上游 dependency 版本、pricing schema、feature flag 與產生測試資料的 fixture,都可能改變證據基礎。成熟一點的流程可以由 dependency graph 判斷;剛開始時,即使只是列出幾個高風險目錄,也比把所有綠燈永久保存可靠。
證據成本要跟風險相稱
不是每一則 review comment 都值得五欄契約。Typo、純格式調整或無行為差異的 rename,保留一般 thread 狀態就夠了。若要求每則建議都填滿 metadata,PR 很快會變成表單工廠,團隊最後只會想辦法繞過流程。
我會把完整契約留給會改變金額、權限、資料完整性、公共 API 或關鍵 UI 行為的 finding。其他項目可以共用較輕的 policy,例如只記 addressed-by revision,不強制獨立 acceptance owner。
分級的好處是,review automation 可以繼續替人省時間,而不是新增一套人人討厭的文書工作。高風險項目保留可追溯證據,低風險項目維持原本速度。
讓自動化清理 thread,不要清理責任
AI code review 的分析能力還會繼續變動。模型 routing、agent ensemble、背景 automation 與執行控制都可能更新。產品實驗裡看到的改善,不會自動變成每個 repository 的品質保證;對自己的程式碼而言,仍要回到可定位的 revision 與檢查結果。
好的 auto-resolution 讓人少整理 thread。它不該讓團隊少想一層。
只要重要 finding 還找得到它在哪個 revision 成立、由哪個 commit 處理、用什麼檢查驗證、誰接受剩餘風險,以及什麼變更會讓證據作廢,自動關閉就能安心負責注意力管理。留言可以消失,證據與責任要留下來。
Source notes
喜欢我的作品吗?别忘了给予支持与赞赏,让我知道在创作的路上有你陪伴,一起延续这份热忱!