v1.0.0 to v1.0.0

43 added, 9 removed. Audit A to A.

---
name: check-your-own-work
description: |
Before handing your own change over — opening a PR, asking for review, saying
- "done" — check it against seven questions that come from what reviewers actually
+ "done" — check it against eight questions that come from what reviewers actually
caught: claims that do not match the diff, the repo's own rules not applied,
half-done pattern changes, runtime behaviour asserted from reading source,
- last round's comments still unaddressed, assertions that cannot fail, and a
- mechanism you removed whose jobs nobody carried over.
+ last round's comments still unaddressed, assertions that cannot fail, a
+ mechanism you removed whose jobs nobody carried over, and something you added
+ that nothing runs or that this repo has no precedent for.
Use when you are about to hand your own work over, or when someone asks you to
self-check, double-check, or go over your change before submitting.
交出自己的改動之前——開 PR、找人 review、說「做完了」——先對一次自己寫的東西。
- 七問來自 review 真的抓到的東西,不是想像出來的清單。
+ 八問來自 review 真的抓到的東西,不是想像出來的清單。
不用於:看別人的 PR(那是 code review,主語是別人的改動)。
不用於:判定某個交付達不達標——這支不判紅、不擋人,它產出一份要被處置的清單。
metadata:
version: 1.0.0
scope: universal
---
# check-your-own-work — 交出去之前,先對一次自己寫的東西
**這不是一道閘。** 它不回 PASS/FAIL,也不阻止任何後續動作。它產出一份 finding 清單,
而那份清單的價值完全來自**它在同一輪裡被處置掉**——一份沒有人動的報告,跟沒有報告一樣。
- 七問不是想出來的。前六問是從 829 則真人 review 意見逆推出來的六類反覆缺陷,而其中四類
+ 八問不是想出來的。前六問是從 829 則真人 review 意見逆推出來的六類反覆缺陷,而其中四類
**完全不需要任何領域知識就避得掉**:它們是「我沒有把自己剛寫的東西跟自己剛寫的宣稱對一次」,
不是「我不知道這個框架怎麼寫」。
**第七問的出處不同,要分開說。** 它來自 2026-08-27 一組十二則意見——那一批走了六輪 review,
而八則 SWE 通則裡有五則是同一個根因:拿掉一個機制,只補上它看得見的那一半。它不在 829 則
那個統計裡,所以不要把它讀成「第七類反覆缺陷」——它是一類,但樣本只有一批。
+ **第八問的樣本更薄,只有一批十二則。** 那一批歸出四個根因,其中三個前面七問都抓不到:
+ 一個新加的測試檔沒有任何 CI 會跑、301 行另一種語言的測試設施在一個零先例的 repo 裡、
+ 以及三支既有測試對唯一的行為改變全綠(第三個由第六問放寬管轄接手)。**兩問共用同一次
+ 搜尋**,所以它們是一問不是兩問。同樣不要把它讀成一個統計結論。
+
## 先把材料撈出來
```bash
bash .claude/skills/check-your-own-work/scripts/collect-self-check-inputs.sh
bash .claude/skills/check-your-own-work/scripts/collect-self-check-inputs.sh --repo <path> --base <ref> --pr <number>
```
三個參數都可以不給:repo 預設是當下的目錄,base 自己去問 `origin/HEAD`(問不到就依序試
`main`/`master`/`develop`),PR 自己去問 `gh`。**它唯讀,不對外寫入任何東西。**
它逐問印出那一問需要的輸入,**拿不到的那幾問指名說出為什麼拿不到**,最後印
- `ANSWERABLE: n/7`。這一行是整支腳本存在的理由:一份只答了兩問的自檢,讀起來跟答滿七問的
+ `ANSWERABLE: n/8`。這一行是整支腳本存在的理由:一份只答了兩問的自檢,讀起來跟答滿八問的
一模一樣,所以它要說出自己少了哪幾問。
**答不出不是通過。** 沒有 `gh`、沒有 PR、沒有上一輪意見、這個 repo 一份規範都沒有、diff
是空的——這五種都是常態,不是錯誤,但它們各自代表「這一問沒有答案」,不代表「這一問沒事」。
- ## 七問
+ ## 八問
### 一、我寫下的每一句宣稱,在 diff 裡都找得到對應的改動嗎
宣稱不只是 PR 描述。commit message、changeset、註解、docstring、型別宣告、變數名——**每一句
描述性的話都是產出的一部分,不是旁白**。它對讀的人承諾了某件事,而承諾與行為是分開演化的:
先寫下承諾,做的過程中改了做法,然後沒有回頭改那句話。
沒有東西會紅。編譯器不看,測試不看,只有下一個依它行事的人會踩到。
標本:四個 PR 各自宣稱有一組 harness 檔案,那些檔案不在 diff 裡;同時各帶一個宣告某個套件
要發版的 changeset,而那個套件一行都沒動。
### 二、我碰到的這些檔案類型,這個 repo 自己的規範說了什麼
**從 repo 現場讀,不從記憶讀。** 腳本會把它找到的規範檔逐份列出來——去讀它們。
兩件事讓「憑記憶」特別危險:
- **規範會翻面。** 同一份檔案上個月說 A,這個月改成 B,而記得舊版本的人不會發現自己記錯了。
- **被違反的正好是已經寫下來的那些。** 量到的第 2 類缺陷裡,reviewer 引用的就是這個 repo
自己的規範檔——引用它的是 reviewer,不是作者。
一份規範都找不到時,那不是「沒有規矩」,是「規矩沒有寫下來」——去讀鄰近的既有程式碼。
### 三、這個修法是不是一個 pattern
把這次的修法講成一句話,然後 **grep 整棵樹找同型的地方**,說出還有幾處、以及為什麼那幾處
不改。腳本印的「同目錄還有幾個同副檔名」是一個下界,不是答案。
「我只改了我看到的那一處」不是理由。標本:一個顯示問題的成因被作者寫進其中一個檔案的註解裡,
另外三處一模一樣的地方沒動。
### 四、我講的哪些 runtime 行為是實測的,哪些只是從源碼推的
**源碼分析是假設,不是證據。** 把這一輪講過的每一句 runtime 行為列出來,逐句標記。推的那
幾句要嘛去跑一次,要嘛在交出去的時候明講它是推的。
這一類特別會躲:build 期注入、CDN 資產、外掛的載入順序——純 grep 抓不到的載入路徑,讀源碼
會得到一個很有說服力的錯誤結論。**被挑戰的時候,把「被挑戰」當成「我很可能漏看某條執行
路徑」的高機率訊號**,先去跑一次再回應。
### 五、上一輪 review 的每一則,在現在的 HEAD 上是什麼狀態
逐則問:修掉了、還在、還是我判斷不修?**「我記得我修過了」不算**,去看現在的檔案內容。
而且處置要**回到那則意見上**。不是「有沒有處理」——是提出的人拿不拿得到那個處置。他看的是
他留言的地方,回在別處他收不到,而「還沒被回覆」跟「我還沒動手」長得一模一樣。
標本:同一個日期處理的 bug 被三位 reviewer 在三輪裡各指一次;作者說修好了之後它還在。
- ### 六、我新增的每一條斷言,注入一刀會不會紅
+ ### 六、這一輪唯一的行為改變,把實作改回去,有沒有任何一條紅
**整檔會紅證明不了每一條都在守。** 恆真的那一條躲在同一個區塊裡永遠看不到——要指名跑那
一條,而且每一條「回來的路」各注入一次,再配一個正向控制。
+ **主語是「行為改了什麼」,不是「我改了什麼」。** 既有的那幾支也算。它們在 diff 裡一行都
+ 沒動,所以任何以「我新增的」為主語的問句都掃不到它們——而它們正是被拿來說「這裡有測試在
+ 守」的那幾支。做法是把實作改回舊的,再看有沒有任何一條紅;一條都沒紅,就表示這一輪的行為
+ 改變沒有任何斷言在守。
+
+ 標本:三支既有的結構化資料測試,對某一輪唯一的行為改變全綠——把實作改回舊的,4/4、5/5、
+ 17/17 全過。同一輪自己新加的一句紅訊息,對這個 repo 歷史上唯一的改法一個字都不印。
+
這次的 diff 沒有動到任何測試檔的話,**那本身就是一條 finding**:一個改了行為卻沒有任何新
斷言的交付,等著被問「那你怎麼知道它是對的」。
### 七、這一輪拿掉了什麼,它原本做的每一件事現在由誰做
**判準是「一個既有機制不再由原本那條路徑執行」**,不是它被叫做什麼。拿掉、取代、簡化、
「順手收斂一下」——四種說法走到同一題。只要有東西不再跑,就要回答這一問。
做法是列一張對照表,不是講一句話:
1. **那個機制原本做了哪幾件事**,逐件列出來。看被刪掉的那幾行,不看你記得它在做什麼。
2. **每一件在新的路徑上落在哪裡**,逐件指出來。
3. **逐件各跑一次**。量過其中一件不算量過其餘——結構相同正是它們會一起壞的理由。
**列不出來是一個答案,而且是要說出來的那一個。** 那張表列不完整時,把「我列不完整」寫進
finding 並回去讀那段被刪掉的程式;不要讓這一問安靜地通過。一個「應該沒有別的了」跟一張
列過的表,在報告裡長得一模一樣。
這一問特別會躲,因為**被拿掉的東西不在 diff 的加號那一側**——你讀自己新寫的那段,它一定
合理,不然你不會那樣寫。要對照的是減號那一側。
標本:一次改動拿掉了一個會讓元件重建的識別值,改用一支「資料晚到再重建」的函式取代。那個
重建原本連帶讓整段初始化重跑,而初始化裡有一條防呆分支——新的那支只複製了看得見的那一半,
那條分支從此沒有人跑。兩位 reviewer 用列舉法逐個呼叫點看出來,作者量了一種形狀就收工。
- ## 處置:這一步不做,前面七問等於沒做
+ ### 八、我加的東西誰會跑它?這個 repo 做不做這種東西
+
+ 兩件事放同一問,因為證據是同一次搜尋。
+
+ **誰跑它。** 新加的檔案要指名一條真的會執行到它的路徑:CI 的哪一個工作、哪一條 glob 收得
+ 到它、本機哪一條命令。指不出來就是沒有人跑——一個永遠不執行的測試跟沒有那個測試一樣安靜,
+ 差別只在它看起來很完整。
+
+ **這個 repo 做不做這種東西。** 數這個 repo 裡同形狀的東西有幾個。**零就不要做**,不是
+ 「小心一點做」。
+
+ 這一問跟第三問方向相反。第三問問「同型的還有幾處,我是不是該一起改」,它已經預設了這個
+ repo 會做這件事;這一問問的是那個預設成不成立。`swe-knowledge`〈動手刻之前〉也不涵蓋它
+ ——那一段的判準是**行為重疊**,而一件從來沒有人做過的事,行為重疊是零,照那一問走會通過。
+ 零重疊的行為,配上零先例的形狀,是兩件事。
+
+ 標本:往兩個 PHP repo 各塞了 301 行 JS 測試設施,其中 118 行是手寫的 PHP 剝註解器。那個
+ repo 有 30 支 PHP 原生的測試、零支用 JS 去讀 `.php` 的測試,而審查的人直接指了那條原生的
+ 路。同一輪還加了一個測試檔,CI 只跑 JS、而它的 glob 只收 `.js`,所以那個檔從來沒有被執行過。
+
+ ## 處置:這一步不做,前面八問等於沒做
每一條 finding 只有兩種結局:
- **修掉**,然後那一條就不存在了。
- **不修**,寫下一句為什麼——「這一處是刻意的,因為⋯」「這一處超出這次的範圍,另外開單」。
**兩者都沒有的 finding 存在時,這次自檢還沒跑完。** 一條被列出來然後沒有人碰的 finding,
比沒有列出來更糟:它讓這份清單看起來被處理過了。
清單要交給別人看的時候(貼進 PR、貼進討論串、寫成一份自檢結果),照
`references/report-format.md` 的段落骨架寫,第 3 段走「檢查結果」那一格。留在自己手上
邊看邊改的那一輪不必套——那個當下沒有第二個讀者。
## 它不做的事
- **不判紅、不擋人。** 沒有「不通過就不能往下」的形狀。真正該擋人的是不可逆、會出去到這個
repo 之外、而且看 diff 的人看不出來的後果——那種東西要一道閘,不是一份清單。
- **不對外寫入。** 不送出程式碼審查、不留審查意見、不寫入任何議題追蹤或通訊系統。這支從頭
到尾唯讀。
- **不抄任何一個 repo 的規範進自己的目錄。** 規範每次執行時從那個 repo 讀。抄下來的那一刻
它就開始漂,而漂掉那天沒有人在看。
- **不看別人的 PR。** 主語是自己剛寫的東西。