check-your-own-work · v1.0.0 · 2026-10-13 · sha256 4300c8366976d585
check-your-own-work v1.0.0A
Immutable. This exact content never changes and is served at /api/v1/blob/4300c8366976d585.
--- 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 整棵樹找同型的地方**,說出還有幾處。腳本印的「同目錄 還有幾個同副檔名」是一個下界,不是答案。 **先劃範圍,範圍有兩個來源**: - **(a) 症狀所在的頁面或功能的每一個入口。** 單講的是哪一頁、哪個功能壞了,那一頁的另一個 bundle、手機版、modal 都在裡面。 - **(b) 這顆 PR 改到的共用程式碼的每一個消費者**,包括把這次修正蓋掉的那一層。改了一個共用 元件,卻有一層在別的頁把它蓋掉,修正在那幾頁就只做了一半。 **範圍之內、同一個 repo、同一個 bug、同一種修法的同型處,收進同一顆 PR。** 不改只有三種理由: - **不是同一個 bug。** 長得像,但壞的是另一件事。 - **使用者與爬蟲都到不了。** 那一處在任何真實路徑上都不會被執行或被讀到。 - **修法不同。** 同一個 bug,但那一處要的改法跟這一處不一樣。**要寫出不同在哪**:寫不出來, 它就是同一種修法。 「別的頁在用」「不在這張單名裡」「另開單」都不算理由。(b) 的共用元件被別的頁在用,正是它在 那些頁也壞著的意思;單名是開單的人當時看到的範圍,不是 bug 的範圍;另開單等於讓同事對同一個 repo、同一件事多看一次。 **範圍以外的同型處不收進這顆 PR**:不共用程式碼的別頁,即使同型、同修法,也落在這裡。它有 兩個去處,兩個都要做:寫成一則留言掛在母單上;PR 描述的限制段合成一段指過去,寫出幾處、在 哪些頁、留言在哪。這一類不是三種不改理由之一。沒寫回母單、沒進限制段,就是沒處置。範圍不劃 的話,每顆 PR 都會變成整個 repo 的清掃;標本:照沒有範圍的版本找同型處,一張搜尋頁的單從 2 處擴到 25 處,分類頁、特賣頁、收藏、訂單、地圖、FAQ 全進來了。 **這份清單原樣進 PR 描述的限制段**:範圍之內的每一處寫位置、改或不改、不改的話是 三種裡的哪一種;範圍以外的合成一段,指向母單上那則留言。看 PR 的人自己 grep 得到同樣的東西;清單不在描述裡,他會把每一處當成你沒看到。 「我只改了我看到的那一處」不是理由。標本:一個顯示問題的成因被作者寫進其中一個檔案的註解裡, 另外三處一模一樣的地方沒動。第二個標本:一顆 PR 的施工線讀到了同型的預設值與另外兩頁的 消費者,也 grep 到了第三處,四處全列成「範圍外,刻意沒做」,理由是「別的頁在用」,而且清單 沒進描述。reviewer 自己 grep 一次,四處全部指出來,那一輪收到五串意見。 ### 四、我講的哪些 runtime 行為是實測的,哪些只是從源碼推的 **源碼分析是假設,不是證據。** 把這一輪講過的每一句 runtime 行為列出來,逐句標記。推的那 幾句要嘛去跑一次,要嘛在交出去的時候明講它是推的。 這一類特別會躲:build 期注入、CDN 資產、外掛的載入順序——純 grep 抓不到的載入路徑,讀源碼 會得到一個很有說服力的錯誤結論。**被挑戰的時候,把「被挑戰」當成「我很可能漏看某條執行 路徑」的高機率訊號**,先去跑一次再回應。 **PR 描述裡寫的執行步驟也是一句 runtime 宣稱。** 交出去之前,在那個工具上真的走一次,或查到 它的原始碼確實有那個入口。一條命令有兩種模式時,部署面就開兩個入口。標本:一顆 PR 描述寫著 「在部署工具上補參數重跑」,那個工具根本補不了參數,只好再開第二顆 PR。 ### 五、上一輪 review 的每一則,在現在的 HEAD 上是什麼狀態 逐則問:修掉了、還在、還是我判斷不修?**「我記得我修過了」不算**,去看現在的檔案內容。 而且處置要**回到那則意見上**。不是「有沒有處理」——是提出的人拿不拿得到那個處置。他看的是 他留言的地方,回在別處他收不到,而「還沒被回覆」跟「我還沒動手」長得一模一樣。 標本:同一個日期處理的 bug 被三位 reviewer 在三輪裡各指一次;作者說修好了之後它還在。 ### 六、這一輪每一處行為改變,各自把實作改回去,有沒有一條紅 **整檔會紅證明不了每一條都在守。** 恆真的那一條躲在同一個區塊裡永遠看不到——要指名跑那 一條,而且每一條「回來的路」各注入一次,再配一個正向控制。 **主語是「行為改了什麼」,不是「我改了什麼」。** 既有的那幾支也算。它們在 diff 裡一行都 沒動,所以任何以「我新增的」為主語的問句都掃不到它們——而它們正是被拿來說「這裡有測試在 守」的那幾支。做法是把實作改回舊的,再看有沒有任何一條紅;一條都沒紅,就表示這一輪的行為 改變沒有任何 assertion 在守。 標本:三支既有的結構化資料測試,對某一輪唯一的行為改變全綠——把實作改回舊的,4/4、5/5、 17/17 全過。同一輪自己新加的一句紅訊息,對這個 repo 歷史上唯一的改法一個字都不印。 **逐處問,不是整顆 PR 問一次。** 這一輪改了幾處行為,就各自改回去幾次,每一次都要有一條紅。 沒有紅的那一處就是一條 finding,指名是哪一處。「diff 有沒有動到測試檔」不是判準:四處改動 配三支新測試,看起來有測試,第四處一條都沒有。標本:一顆 PR 改了四處行為、加了三支測試, 自審問的是「有沒有動測試檔」,於是缺的那一處留給 reviewer 指出來。 ### 七、這一輪拿掉了什麼,它原本做的每一件事現在由誰做 **判準是「一個既有機制不再由原本那條路徑執行」**,不是它被叫做什麼。拿掉、取代、簡化、 「順手收斂一下」——四種說法走到同一題。只要有東西不再跑,就要回答這一問。 做法是列一張對照表,不是講一句話: 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 進來的同一個目的地可能還是舊的。 **這一輪對一份資料加了約束,就列出寫到那份資料的每一個入口。** 約束指鎖定、權限、驗證、 狀態守衛。那份資料指同一張表、同一組欄位。入口包含端點、指令、job、service 的每一個寫入呼叫。 逐條寫它套了沒。**沒套的那一條就是一個洞**,不是另一個功能,也不是範圍外:約束守的是資料, 從哪一條路寫進去,資料都一樣被改掉。處置照下面〈處置〉段:修掉,或寫出那個入口為什麼本來 就寫不到那份資料。 標本:一顆 PR 給兩個欄位加了鎖定,檢查只套在逐欄更新那一條路徑。同一個服務另有一個整份覆蓋 翻譯的端點,同樣接受被擋下的那個平台,它的寫入函式沒做這個檢查,被鎖定的欄位照樣改得掉。 reviewer 從那個端點的既有測試看出來。 標本(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。** 主語是自己剛寫的東西。