審查一份 diff 或指定檔案裡的程式註解,逐條判 keep/add/restore/trim/delete/defer,並為該由程式碼取代的註解標 MUST KILL。用在開 PR 或推 PR 更新之前、使用者說「審查註解」「哪些註解該刪/該留」「/comment-audit」時。
Install
npx skillscat add demianli/nexus-agent/comment-audit Install via the SkillsCat registry.
註解審查
用沒看過這段程式碼的眼睛讀範圍內每一條註解,判它留、補、修、刪,或交回給人決定,每個判決都附證據。目標是 develop 上只留讀者在 HEAD 用得到的註解:契約、不變量、理由、外部限制。這是指引,不是腳本:清單外的判斷照樣要做,`docs/standards.md`〈註解〉的條文照樣適用。
判準以 dsh 的 `dsh-prose-standard` 與 `dsh-trim-cot-leakage` 為參照,本檔寫 nexus 自己的取捨與流程。判例拿不準時讀那兩份的原文。
模式
- report(預設):唯讀。不改任何檔,只交報告。
- fix:報告被接受之後,套用判決、做 MUST KILL 的最小修法。
呼叫方指定模式;沒指定就跑 report,結尾問要不要 fix。
範圍
呼叫方給的檔案或 diff;沒給就是 origin/develop...HEAD 加上工作樹的改動。只審 diff 碰到的註解與它們緊鄰的程式碼,判決都要指到範圍內的行。
一律排除:vendor/、apps/web/src/vendor/solid-glass/、references/、夾具(fixtures/)與快照、產生出來的檔。範圍外看到的問題寫進「待辦」,不動。
判決
| 判決 | 用在 |
|---|---|
| keep | 註解寫的是下面「該留的」之一,而且今天為真 |
| add | 下面「必須有的」缺了,程式碼與型別又說不出來 |
| restore | diff 刪掉了一條仍為真的契約或理由 |
| trim | 有真的命題,夾著敘事或重複,或者命題寫錯、漏了條件;只留命題,並照程式碼修正 |
| delete | 沒有任何命題在 HEAD 站得住 |
| defer | 兩種寫法都保住全部命題、卻取捨不同的原則;交回給人 |
動任何一條之前,先列出它的每個命題:誰、做什麼、條件與時機、must/may/never、例外、失敗與後果。trim 之後每個命題都還在,才算 trim;少了一個就是刪錯。
MUST KILL 是 delete 的一種:註解解釋的是我們自己程式碼的意外(名字誤導、隱藏的順序、繞道),而那個意外可以靠改名、抽函式、加型別或重新設計消掉。註解照刪,並指名要重塑的符號。
該留的
- 契約:public JSDoc 的回傳區別、throw/reject、副作用、擁有權、時機、取消、落盤保證。
- 非局部的結構:不變量、競態順序、擁有權、安全邊界、出乎意料的失敗行為。
- 外部限制:我們改不了的依賴、平台、協定逼出來的行為。
- issue 引用:
#1529、TODO(#N)這類在 HEAD 解析得到的連結。 - 回歸釘:「沒有 X 就會 Y」這種現在式的反事實。
- 量測值:「(量測:…)」校準一個常數的數字與出處。
- suppression 的理由:
eslint-disable … -- 理由、@ts-expect-error 理由、空catch的說明。理由寫錯就修理由。suppression 本身蓋住正確性或安全問題時,另標 MUST KILL 指向被蓋住的符號。 - 測試設計的理由:為什麼要這個夾具、這條斷言、這條真入口路徑或間接觀察。
必須有的
`dsh-prose-standard`〈Required coverage by prose location〉列的覆蓋:新的或改了契約的 export 要有 JSDoc;模組要寫它的角色與不明顯的架構選擇;上面「該留的」前兩項在程式碼裡看不出來時要補。程式碼本身已經講清楚的,不補。
該走的
判準只有一條:讀者在 HEAD,看不到任何 session 對話、PR 討論串或未提交的草稿,能不能解析每個引用、驗證每句話? 不能,就把站得住的命題用 repo 的視角重寫,其餘刪掉。
- 變更敘事:原本、改成、不再、以前、舊的、這一刀、這張 PR、之後的 PR、上一個 commit。改寫成現在的行為;變更的來龍去脈屬於 PR 內文。
- 審查往來:審查時決定、某某說、第 N 版、被拒的寫法。留下決定與理由,刪掉誰在何時說。
- 對審查者辯護:「這樣是安全的,因為…」。改寫成讓它安全的不變量,程式碼看得出來就刪。
- 流程敘事:「先 X,再 Y」、測試的逐步導覽、顯而易見的分支證明。
- dsh 對照紀錄:
照 dsh <path:line>、偏離 dsh(理由 1/2)…、對應 dsh 的 X。這類紀錄的家是 PR 內文 Summary 的「對照 dsh」或docs/、.docs/的 md。註解裡留下這段程式碼自己的契約與理由,用 nexus 的話寫;出處與偏離理由整段移到 PR 內文或對應的 md,不留在程式碼裡。 - 對話裡的引用:決策編號、稽核代號、草稿的 §N、階段代號。有落盤的出處就指名路徑或 issue;沒有就刪引用、留命題。
- 含糊與計畫殘渣:「先暫時」「應該夠了」「之後再說」。寫成實際的界限,或換成
TODO(#N)。 - 重述程式碼、橫幅、註解掉的程式碼。
交回給人的
- 約束註解(「不要刪」「改之前先問 X」):約束對象是我們改不了的,判 keep。否則提出最便宜的機械落實(型別、執行期檢查、測試、CI 檢查)並說明為什麼最便宜,等核准;沒核准就判 defer,報告成「未落實的約束」,不刪。
- 模型看得到的文字(prompt、工具描述、system 訊息、錯誤訊息):措辭就是行為。沒有對應的行為證據就不動,報告成 defer。
步驟
1. 新眼睛審一遍
有 Agent 工具就派一個沒看過這段程式碼的子代理來審,model: opus、effort high。給它範圍、本檔〈範圍〉與〈判決〉兩節原文,要它唯讀、把報告寫在回覆裡(不寫檔)。沒有子代理工具就自己審,並在報告裡寫明。
完成條件:範圍內每一條註解都有一個判決,每個 add 都指名缺的是哪一項覆蓋。
2. 核對單薄的主張
判決若只靠一句主張(「必須這樣」「X 要求的」「不要刪」),先對程式碼查證:
- 怎麼運作:讀附近的程式碼,追那個符號:誰呼叫、哪條執行路徑走到、不照註解做會壞什麼。追不到就是未證實。
- 為什麼:主張關於歷史或動機時,讀
git blame -L、git log --follow -p -- <file>,以及最後一次實質改動的 PR(gh pr view <n> --json title,body,comments)。分開寫歷史顯示了什麼、你推論了什麼。
keep 要三條都成立才留:主張今天為真、在活的路徑上、講的是程式碼表達不出來的事。查完仍說不清,判 defer,不刪。
3. 審子代理的報告
把子代理的報告當草稿。退回其中這些:
- 刪掉了上面「該留的」,或 trim 後少了命題;
- MUST KILL 的理由不對,或怪罪的是刻意、且我們改不了的行為;
- 判決指到範圍外;
- 否定性的宣稱(「這裡沒有呼叫方」「沒有別處用到」)沒有附 grep 或圖查詢的結果。這類自己重查一次。
另外找它漏掉的 suppression 與缺漏的 JSDoc。退回後重跑一次並指名錯在哪;第二次仍被退回,就停下,報告審查失敗與未決項目。
4. 為每個 MUST KILL 找根因
report 模式寫成建議,fix 模式照做。
- 從症狀追到系統第一次出錯的地方。繞道處理的狀態是合法的,就在它壞掉的地方處理;不該存在,就找出產生它的地方。
- 優先刪掉死路徑、拿掉參數、改用真正的 API。
- 需要新形狀時,先寫呼叫方的用法,再寫型別與簽名,比較至少兩種結構不同的寫法,選公開面較小的。report 模式停在草圖。
- 找模式,不只找這一處:範圍內同樣的繞道全部列出。
- 修法落在別的 session 擁有的目錄(見 AGENTS.md〈檔案歸屬〉),或在範圍外,就列進「待辦」,不動手。
5. 報告
短文字加表格,每個判決附證據,每句話標明是實測、推論還是猜測,沒查的寫沒查。
- 摘要:審了幾個檔、幾條註解,各判決的數量,新眼睛那一遍是子代理還是自己做的。
- MUST KILL:每列一個:檔案與行、符號、註解的主張、為什麼走、最小修法。
- add/restore:檔案與行、缺的是哪一項覆蓋、建議的文字。
- trim/delete:依「該走的」類別計數;被要求時才逐條列檔案與行。
- keep:檔案與行、屬於「該留的」哪一項、今天為真的證據。只列靠主張留下、經過第 2 步查證的;契約計數即可。
- 移出的紀錄:從註解移走的 dsh 出處與取捨理由,每條寫明要放進 PR 內文 Summary 的「對照 dsh」還是哪份 md,附要寫的文字,讓紀錄不會因為刪註解而消失。
- defer:約束、模型看得到的文字、兩難的寫法,各附選項。
- 被退回的子代理輸出與重跑。
- 待辦:範圍外的事、架構草圖、需要人決定的 suppression。
fix 模式
- 依報告套用判決,最小的先做;MUST KILL 的修法照第 4 步。
- 移出的紀錄在同一張 PR 寫進 PR 內文 Summary 的「對照 dsh」或對應的 md,再刪註解。
- 對碰到的檔跑 lint、typecheck 與測試,回報實際結果。
- 要求 commit 時,註解的增刪與程式碼的修法分成不同的 commit。