判定改動是誰做的檢查:重播差異而不拿全文、證據綁定這個 session、比對要提交的那一份內容
論述基礎與限制
這張卡來自本站補一個規則缺口的過程:Claude Code 的 context 被壓縮之後,執行者找不到自己的編輯紀錄,規則要它在提交前確認 diff 的每一段都是自己做的,它只能憑印象對照。補法是一支工具:從這個 session 的 transcript 取出 Edit 與 Write 的結果,從 HEAD 的版本起依序重播,結果跟要提交的內容逐字相同才放行。第一版的 41 個測試全部通過,接著三輪審查(事實核對、steelman、誤用梯度)在拋棄式 repo 裡實跑,重現了四條漏報路徑:工具把另一個 session 的改動判成這個 session 的。卡上記的是這一支工具、這一種 transcript 格式下的量測;transcript 的欄位沒有公開的規格,換一個版本的 Claude Code 可能不同。
核心原則
判定一份改動是誰做的,靠的是三個綁定:證據是差異、證據屬於這個身分、比對的是要交出去的那一份。 三個綁定各自獨立,少一個就有一條漏報路徑,而每一條漏報的外觀都是「全部通過」。
證據要是差異,不能是全文。 Write 的紀錄裡有寫入的全文,拿全文重播最直接,也是第一版的做法。可是全文是執行者讀了當時的檔案、改出來的結果:另一個 session 先在檔尾加了一行,執行者 Read 之後 Write 整份,那一行就在全文裡,重播的結果跟工作樹相同,工具放行。改成只套 Write 紀錄裡的差異(structuredPatch)之後,那一行不在任何一筆差異裡,重播的結果少了它,跟工作樹比對時就對不上。快照把「寫之前別人改的」洗成「自己寫的」;差異只認自己動過的那幾行。
證據要綁定身分。 工具接受任意一份 transcript 時,找不到自己的那一份的執行者會去挑一份:最近更新的那一份,而兩個 session 同時在跑時那正是另一個 session 的。拿別人的編輯紀錄重播,別人的改動就判成自己的。修法是逐行比對紀錄裡的 sessionId 跟環境變數的 session id,不同就停;錯誤訊息也不再寫「用 –transcript 指定」,因為那一句正是引導執行者去挑的那一句。
比對的對象要是要交出去的那一份。 第一版比對工作樹,提交工具之後才 git add。兩步之間執行者還要寫 commit message、跑 lint,前後幾分鐘,這段時間裡另一個 session 寫進同一個檔案,檢查通過的是舊的工作樹,提交出去的是新的。修法是把比對移進提交工具,對私有 index 裡已經 staged 的內容做:同一份清單、同一刻的內容。檢查與提交拆成兩個步驟時,兩步拿到的檔案清單也可能不同,比對移進提交工具之後這一類一併消失。
還原再重做這條漏報不在工具裡,在執行者對工具的反應裡。 工具印出「要問使用者」之後,執行者為了讓它消失,可以先 git checkout 還原那個檔案、再用 Edit 把自己的改動重做一次:起點回到 HEAD,重播的結果相同,工具放行,另一個 session 的改動被抹掉,沒有任何紀錄。工具看不到這條路徑,因為還原之後證據與產物都一致;擋它的只能是規則:已經被標記的檔案不還原、不重做,還原或蓋掉工作樹裡不是自己做的改動,列進沒有使用者明說就不做的動作。
反模式
- 重建「自己的改動」時拿最後一次寫入的全文當結果,不看每一筆動了哪幾行。
- 檢查工具接受任意來源的證據檔,錯誤訊息還教執行者自己去指定一份。
- 檢查與提交是兩個指令,規則寫「提交前先跑檢查」,兩步之間的改動沒有任何一道檢查看得到。
- 測試只涵蓋作者想到的正常路徑,漏報路徑由審查者在拋棄式 repo 裡實跑才找到。
- 檢查標記之後,規則只寫「把 diff 給使用者」,沒有寫不准用還原或重做讓標記消失。
修法
- 重播差異:每一筆寫入只套它的差異;紀錄留著寫入前的全文時,先確認它跟重播的內容相同。
- 證據綁身分:證據檔裡的身分欄位跟執行環境的身分不同就停;找不到自己的證據時退回人工對照,不讓執行者自己挑。
- 比對要交出去的那一份:檢查放進提交工具,對 staged 的內容做;跳過檢查要一個明確的旗標,旗標只在使用者看過 diff、或人工對照通過時用。
- 漏報的測試從審查來:每一條審查重現的漏報路徑寫成一個測試,測試先失敗、修完才通過。
- 規則擋工具看不到的路徑:還原、重做、整份重寫這類讓證據與產物重新一致的動作,寫進不做的動作。
判讀徵兆
| 徵兆 | 判讀 |
|---|---|
| 執行者 Read 之後 Write 整份,檢查通過,diff 裡有不認得的行 | 重播拿了全文;改成套差異 |
| 找不到證據時,錯誤訊息提示可以自己指定一份證據檔 | 證據沒有綁身分;挑到的可能是別人的 |
| 檢查與提交之間有其他步驟,提交的內容比檢查時多 | 比對的是工作樹;移進提交工具、比對 staged |
| 標記出現之後,同一個檔案被還原又重做,再跑就通過 | 執行者繞過了標記;規則要列為不做的動作 |
| 檢查工具的測試全過,審查仍重現出漏報 | 測試只涵蓋正常路徑;把重現步驟寫成失敗的測試 |
跟其他抽象層原則的關係
- 量測的前置步驟決定了分母,而所有檢查都落在分母之後:本卡最接近的既有卡。那張卡說檢查碰不到處理之前就被丟掉的資料;本卡是同一個結構的另一端:檢查碰不到它之後才發生的改動(檢查到提交之間),也碰不到在它之前就被洗進證據的改動(全文快照)。
- 規則檔的修法逐輪製造新歧義:程序寫成有測試的工具,共用的概念只定義一次:那張卡要程序寫成有測試的工具;本卡補上那張卡沒處理的一段:工具的測試是作者寫的,只涵蓋作者想到的路徑,漏報路徑要由審查在拋棄式環境裡實跑找出來,再寫回測試。
- 審查的射程只涵蓋被審查的對象,工具與共用產物在射程外:那張卡說工具在審查的射程外;本卡的三輪審查把工具本身放進射程,四條漏報有三條在工具裡。