---
name: check-your-own-work
description: |
  Before handing your own change over — opening a PR, asking for review, saying
  "done" — check it against nine 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, a
  mechanism you removed whose jobs nobody carried over, and something you added
  that nothing runs or that this repo has no precedent for, and a failure or
  branch path nobody walked.

  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 真的抓到的東西，不是想像出來的清單。

  不用於：看別人的 PR（那是 code review，主語是別人的改動）。
  不用於：判定某個交付達不達標——這支不判紅、不擋人，它產出一份要被處置的清單。
metadata:
  version: 1.0.0
scope: universal
---

# check-your-own-work — 交出去之前，先對一次自己寫的東西

**這不是一道關卡。** 它不回 PASS／FAIL，也不阻止任何後續動作。它產出一份 finding 清單，
而那份清單的價值完全來自**它在同一輪裡被處置掉**——一份沒有人動的報告，跟沒有報告一樣。

九問不是想出來的。前六問是從 829 則真人 review 意見逆推出來的六類反覆缺陷，而其中四類
**完全不需要任何領域知識就避得掉**：它們是「我沒有把自己剛寫的東西跟自己剛寫的宣稱對一次」，
不是「我不知道這個框架怎麼寫」。

**第七問的出處不同，要分開說。** 它來自 2026-08-27 一組十二則意見——那一批走了六輪 review，
而八則 SWE 通則裡有五則是同一個根因：拿掉一個機制，只補上它看得見的那一半。它不在 829 則
那個統計裡，所以不要把它讀成「第七類反覆缺陷」——它是一類，但樣本只有一批。

**第八問的樣本更薄，只有一批十二則。** 那一批歸出四個根因，其中三個前面七問都抓不到：
一個新加的測試檔沒有任何 CI 會跑、301 行另一種語言的測試設施在一個零先例的 repo 裡、
以及三支既有測試對唯一的行為改變全綠（第三個由第六問放寬管轄接手）。**兩問共用同一次
搜尋**，所以它們是一問不是兩問。同樣不要把它讀成一個統計結論。

**第九問來自 2026-09-17～10-01 兩週的 review。** 那段期間被抓到的五類問題裡，四類在第一到第八問裡本來
就有；第五類「失敗或分岔的那一條沒走過」沒有，而那兩週僅有的兩次要求修改都是這一類。

## 先把材料撈出來

```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`。**它唯讀，不對外寫入任何東西。**

**改動在另一棵樹上的時候，一定要給 `--repo`。** 預設是當下的目錄，而你站的地方不一定是改動
落下去的地方——例如在一個工作區裡施工另一個 repo 的工作樹。不指名的話，第二問列出的是當下
那個 repo 的規範檔。第二問那一塊跟答對了長得一樣，只有輸出開頭那一行 `repo:` 說出量的是
哪一棵。

它逐問印出那一問需要的輸入，**拿不到的那幾問指名說出為什麼拿不到**，最後印
`ANSWERABLE: n/8`。這一行是整支腳本存在的理由：一份只答了兩問的自檢，讀起來跟答滿的
一模一樣，所以它要說出自己少了哪幾問。分母是 8 不是 9：第九問讀的是第一問那份 diff，
不另外撈材料，所以腳本不替它計數——diff 拿得到，第九問就答得出來。

**答不出不是通過。** 沒有 `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 抓不到的載入路徑，讀源碼
會得到一個很有說服力的錯誤結論。**被挑戰的時候，把「被挑戰」當成「我很可能漏看某條執行
路徑」的高機率訊號**，先去跑一次再回應。

**PR 描述裡寫的執行步驟也是一句 runtime 宣稱。** 交出去之前，在那個工具上真的走一次，或查到
它的原始碼確實有那個入口。一條命令有兩種模式時，部署面就開兩個入口。標本：一顆 PR 描述寫著
「在部署工具上補參數重跑」，那個工具根本補不了參數，只好再開第二顆 PR。

### 五、上一輪 review 的每一則，在現在的 HEAD 上是什麼狀態

逐則問：修掉了、還在、還是我判斷不修？**「我記得我修過了」不算**，去看現在的檔案內容。

而且處置要**回到那則意見上**。不是「有沒有處理」——是提出的人拿不拿得到那個處置。他看的是
他留言的地方，回在別處他收不到，而「還沒被回覆」跟「我還沒動手」長得一模一樣。

標本：同一個日期處理的 bug 被三位 reviewer 在三輪裡各指一次；作者說修好了之後它還在。

### 六、這一輪唯一的行為改變，把實作改回去，有沒有任何一條紅

**整檔會紅證明不了每一條都在守。** 恆真的那一條躲在同一個區塊裡永遠看不到——要指名跑那
一條，而且每一條「回來的路」各注入一次，再配一個正向控制。

**主語是「行為改了什麼」，不是「我改了什麼」。** 既有的那幾支也算。它們在 diff 裡一行都
沒動，所以任何以「我新增的」為主語的問句都掃不到它們——而它們正是被拿來說「這裡有測試在
守」的那幾支。做法是把實作改回舊的，再看有沒有任何一條紅；一條都沒紅，就表示這一輪的行為
改變沒有任何 assertion 在守。

標本：三支既有的結構化資料測試，對某一輪唯一的行為改變全綠——把實作改回舊的，4/4、5/5、
17/17 全過。同一輪自己新加的一句紅訊息，對這個 repo 歷史上唯一的改法一個字都不印。

這次的 diff 沒有動到任何測試檔的話，**那本身就是一條 finding**：一個改了行為卻沒有任何新
assertion 的交付，等著被問「那你怎麼知道它是對的」。

### 七、這一輪拿掉了什麼，它原本做的每一件事現在由誰做

**判準是「一個既有機制不再由原本那條路徑執行」**，不是它被叫做什麼。拿掉、取代、簡化、
「順手收斂一下」——四種說法走到同一題。只要有東西不再跑，就要回答這一問。

做法是列一張對照表，不是講一句話：

1. **那個機制原本做了哪幾件事**，逐件列出來。看被刪掉的那幾行，不看你記得它在做什麼。
2. **每一件在新的路徑上落在哪裡**，逐件指出來。
3. **逐件各跑一次**。量過其中一件不算量過其餘——結構相同正是它們會一起壞的理由。

**列不出來是一個答案，而且是要說出來的那一個。** 那張表列不完整時，把「我列不完整」寫進
finding 並回去讀那段被刪掉的程式；不要讓這一問安靜地通過。一個「應該沒有別的了」跟一張
列過的表，在報告裡長得一模一樣。

這一問特別會躲，因為**被拿掉的東西不在 diff 的加號那一側**——你讀自己新寫的那段，它一定
合理，不然你不會那樣寫。要對照的是減號那一側。

**刻意不再呼叫的那一支也算被拿掉。** 新的路徑寫著「這裡不呼叫 X，因為它會蓋掉 Y」的時候，
X 就進對照表，逐件列出 X 做的每一件事。那個理由只排除得了它講到的那一件，其餘幾件仍要一件
一件指出落在哪裡。寫得出理由，表示你讀過 X；也正因為讀過，才會以為讀完了。

標本：一次改動拿掉了一個會讓元件重建的識別值，改用一支「資料晚到再重建」的函式取代。那個
重建原本連帶讓整段初始化重跑，而初始化裡有一條防呆分支——新的那支只複製了看得見的那一半，
那條分支從此沒有人跑。兩位 reviewer 用列舉法逐個呼叫點看出來，作者量了一種形狀就收工。

**同一個標本後來在線上壞了。** 新的那支函式刻意不呼叫重置那一支，理由是它會蓋掉剛還原的
選取。那個理由是真的，但重置那一支還負責依新選項重建選取。換選項之後舊的選取留著，結帳金額
算成 0，上線約十九天才被回報。回 review 的那一輪補了 reviewer 舉例的兩件，對照表沒有列完。

### 八、我加的東西誰會跑它？這個 repo 做不做這種東西

兩件事放同一問，因為證據是同一次搜尋。

**誰跑它。** 新加的檔案要指名一條真的會執行到它的路徑：CI 的哪一個工作、哪一條 glob 收得
到它、本機哪一條命令。指不出來就是沒有人跑——一個永遠不執行的測試跟沒有那個測試一樣安靜，
差別只在它看起來很完整。

**這個 repo 做不做這種東西。** 數這個 repo 裡同形狀的東西有幾個。**零就不要做**，不是
「小心一點做」。

這一問跟第三問方向相反。第三問問「同型的還有幾處，我是不是該一起改」，它已經預設了這個
repo 會做這件事；這一問問的是那個預設成不成立。`swe-knowledge`〈動手刻之前〉也不涵蓋它
——那一段的判準是**行為重疊**，而一件從來沒有人做過的事，行為重疊是零，照那一問走會通過。
零重疊的行為，配上零先例的形狀，是兩件事。

標本：往兩個 PHP repo 各塞了 301 行 JS 測試設施，其中 118 行是手寫的 PHP 剝註解器。那個
repo 有 30 支 PHP 原生的測試、零支用 JS 去讀 `.php` 的測試，而審查的人直接指了那條原生的
路。同一輪還加了一個測試檔，CI 只跑 JS、而它的 glob 只收 `.js`，所以那個檔從來沒有被執行過。

### 九、這一輪新增或改到的路徑，失敗或走另一條的時候會怎樣

逐條列出 diff 裡會失敗或會分岔的地方：throw、reject、早退的 return、被 catch 吞掉的錯誤、
條件分支的另一側、同一個目的地的另一個入口。每一條回答兩件事：**失敗之後，這一輪設下的狀態
由誰歸位**（loading 旗標、初始化完成的旗標、鎖、半寫的資料），以及**那時候使用者看到什麼**。
答不出「誰歸位」的那一條，就是一個永遠停在中間的狀態。

只在成功路徑上量過的改動，最容易漏這一問：測試全綠，因為測試只走了那一條。另一個入口也算
分岔——修好從 A 進來的那條，從 B 進來的同一個目的地可能還是舊的。

標本（2026-09-17～10-01）：一個方案區的骨架改成伺服器端就輸出、等初始化完成才收，而初始化
裡一個分組步驟拋錯時，loading 旗標沒有人歸位，桌機版的方案區整塊不渲染——同一顆 PR 在手機版
那一側補了 `finally` 還寫了測試，桌機版沒有。另一顆 PR 新加的查詢函式沒有 try/catch，上游
失敗時連帶讓整份產出掉進例外。第三顆修好了一個入口的轉址，另一個入口進來的仍然掉參數。

## 處置：這一步不做，前面九問等於沒做

每一條 finding 只有兩種結局：

- **修掉**，然後那一條就不存在了。
- **不修**，寫下一句為什麼——「這一處是刻意的，因為⋯」「這一處超出這次的範圍，另外開單」。

**兩者都沒有的 finding 存在時，這次自檢還沒跑完。** 一條被列出來然後沒有人碰的 finding，
比沒有列出來更糟：它讓這份清單看起來被處理過了。

清單要交給別人看的時候（貼進 PR、貼進討論串、寫成一份自檢結果），照
`references/report-format.md` 的段落骨架寫，第 3 段走「檢查結果」那一格。留在自己手上
邊看邊改的那一輪不必套——那個當下沒有第二個讀者。

## 它不做的事

- **不判紅、不擋人。** 沒有「不通過就不能往下」的形狀。真正該擋人的是不可逆、會出去到這個
  repo 之外、而且看 diff 的人看不出來的後果——那種東西要一道關卡，不是一份清單。
- **不對外寫入。** 不送出程式碼審查、不留審查意見、不寫入任何議題追蹤或通訊系統。這支從頭
  到尾唯讀。
- **不抄任何一個 repo 的規範進自己的目錄。** 規範每次執行時從那個 repo 讀。抄下來的那一刻
  它就開始漂，而漂掉那天沒有人在看。
- **不看別人的 PR。** 主語是自己剛寫的東西。
