Finds root cause before applying fixes for errors, crashes, regressions, failing tests, broken behavior, and screenshot-reported defects. Use when users report in any language errors, crashes, broken behavior, regressions, failing tests, screenshot evidence, or something that used to work and now fails. Not for code review or new features.
Hunt:先診斷再修復
第一行前面加上 🥷 符號,不要自成一段。
更新檢查(非阻塞)。 每次對話執行一次 bash <skill-base-dir>/scripts/check-update.sh,將 <skill-base-dir> 替換為此技能的基礎目錄;若有任何輸出則轉達,否則靜默繼續(若腳本已執行過、不存在或發生錯誤也同樣處理)。此腳本每天最多檢查一次,僅讀取公開版本檔案,且不傳送任何資料。
對症狀貼上的修補,只會在其他地方產生新的錯誤。
成果合約
- 成果:在套用任何修復前,找出根本原因。
- 完成條件:一句話解釋原因,所有觀察到的症狀都符合該原因,且修復或交接已透過可重現的檢查驗證。
- 證據:原始碼追蹤、重現指令或 UI 路徑、日誌或狀態、針對性的測試/建置輸出,以及 UI 或原生缺陷的執行時期證據。
- 輸出:根本原因、修復或交接、驗證結果,以及任何未處理的同類型風險。
在能用一句話說出根本原因之前,不要碰程式碼:
「我認為根本原因是 [X],因為 [證據]。」
指出具體的檔案、函式、行號或條件。「狀態管理問題」無法測試。「useUser 的依賴陣列缺少 userId,導致 src/hooks/user.ts:42 的快取過期」則可測試。如果你無法如此具體,表示你還沒有假設。
診斷訊號
良好進展:日誌行符合假設、能在執行前預測下一個錯誤、理解從根本原因到症狀的傳播路徑、能寫出一個在舊程式碼上會失敗的測試。每當出現這些訊號時,在確認前再找一個獨立的證據。
假設品質門檻:在根據假設行動前,列出所有可觀察的症狀(不只是使用者最先回報的那個)。假設必須解釋所有症狀;如果只涵蓋部分,那只是症狀層級的猜測,不是根本原因。對於時間相關的問題(閃爍、間歇性失敗、競爭條件),先可靠重現再診斷。
合理化警告:「我先試試這個」表示沒有假設,先寫下假設。「我很確定」表示執行一個能證明它的工具。「大概是同一個問題」表示從頭重新讀取執行路徑。「在我機器上可以」表示在排除前列出所有環境差異。「再重啟一次」表示逐字讀取最後一個錯誤;沒有新證據時,不要重啟超過兩次。
持久上下文預檢
請參閱 references/durable-context.md 了解何時讀取持久上下文、讀取順序預算以及記憶體類型對應。
對於 /hunt:持久上下文僅作為假設的燃料,且當前程式碼、日誌和重現證據優先於記憶體。它永遠不能取代全新的根本原因陳述或可重現的症狀列表。
嚴格規則
- 修復後症狀相同是硬性停止;「讓我先試試這個」也是。 兩者都表示假設尚未完成。在再次碰程式碼前,從頭重新讀取執行路徑。
- 連續三個假設失敗後,停止。 使用下方的交接格式,列出已檢查、已排除和未知的項目。詢問如何繼續。
- 在宣稱前先驗證。 不要憑記憶陳述版本、函式名稱或檔案位置。先執行
sw_vers/node --version/ grep。沒有結果 = 重新檢查路徑。 - 外部工具失敗:先診斷再切換。 當 MCP 工具或 API 失敗時,先確定原因(伺服器是否執行?API 金鑰是否有效?設定是否正確?)再嘗試替代方案。
- 系統/工具鏈症狀需要底層基準。 在指責可見的應用程式、產生的檔案或頂層功能之前,先測量原始底層:作業系統擷取 vs 後處理、執行時期服務 vs UI、編譯器/工具鏈 vs 測試斷言、網路/API vs 客戶端處理。排除基準反駁的假設,不要繞著它們打轉。
- 注意轉移焦點。 當有人說「那部分不重要」時,將其視為訊號。某人避免檢查的區域往往是問題所在。
- 視覺/渲染錯誤:先靜態分析。 在加入 console.log 或視覺除錯疊加層之前,先在 DevTools 中追蹤繪製圖層、堆疊上下文和圖層順序。日誌無法捕捉合成器所做的動作。只有在靜態分析失敗後才加入儀器。
- 行為/生命週期/非同步錯誤:先加入儀器,不要在失敗後才加。 視窗生命週期、事件傳遞、導航、焦點、計時器、狀態機和非同步順序錯誤幾乎無法僅靠靜態閱讀解決。不要等到修復失敗才加入日誌。一旦你的假設涉及「這個回呼在另一個之前/之後觸發」、「這個狀態在 Y 執行時應該是 X」或「這個物件在這裡應該還活著」,在形成假設時立即加入日誌,在寫任何修復之前。沒有執行時期證據的假設是猜測;連續兩個猜測就是硬性停止訊號。與視覺渲染錯誤(合成器行為需要 DevTools,而非日誌)和純邏輯錯誤(錯誤公式、差一錯誤)區分開來,後者靜態分析就足夠。
- 調整魔術數字超過三次:停止,統一。 當間距/大小/閾值已調整三次仍然看起來不對時,錯誤是結構性的,而非數值性的。將 N 個獨立值替換為一個命名 token(
Spacing.s4、--gap-content等),並驗證不對稱性是否隱藏了遺漏的約束。經調整仍存在的不對稱性是結構性的;更多調整不會收斂。 - 效能抱怨需要數字。 對於非原生應用凍結模式的「慢」、「卡頓」或記憶體成長報告,先測量基準(牆壁時鐘時間、效能分析樣本、記憶體佔用),修復,然後重新測量並報告前後數字。「感覺變快了」不是證據。
- 修復原因,而非症狀。 如果修復觸及超過 5 個檔案,暫停並與使用者確認範圍。
修復範圍紀律
如果錯誤確實需要先重構(例如,原因無法在不更改共享介面的情況下解決),暫停,明確命名重構並詢問。不要默默打包。一個長成重構的錯誤修復是單獨的 PR。
二分搜尋模式
在以下情況啟用:「以前是好的」、「之前是好的」、「used to work」、「上一次提交還是對的」、「broke after update」,或使用者記得某個特定的好提交或版本。
- 先保護使用者的工作目錄:執行
git status --short --branch -uall。如果存在已修改、暫存或未追蹤的檔案,不要在當前 checkout 中進行二分搜尋。從同一個 HEAD 建立一個暫時的分離工作目錄,在那裡執行二分搜尋,完成後執行git bisect reset並移除暫時工作目錄。如果無法建立暫時工作目錄,停止並要求明確的清理/暫存批准。 - 尋找候選的好標籤:
git tag --sort=-version:refname | head -10或詢問使用者最後已知的好提交。
1b. 如果最後好版本只差一兩個發行版,先直接執行git diff <last-good-tag>..HEAD -- <suspect path>並閱讀差異。回歸通常可以在那個 diff 中看到,閱讀它的成本遠低於進行完整的二分搜尋。僅在 diff 太大或原因不明顯時才進行二分搜尋。 - 在開始二分搜尋前,定義一個非互動的通過/失敗測試指令。沒有可重現的檢查,二分搜尋就沒有價值。
- 執行:
git bisect start && git bisect bad HEAD && git bisect good <tag-or-hash> - 每一步二分搜尋會 checkout 一個提交。執行測試指令。標記:
git bisect good或git bisect bad。 - 讓二分搜尋驅動。除非明確要求,否則不要跳躍或跳過提交。
- 當二分搜尋指出罪魁禍首提交時,只閱讀那個 diff。找出引入回歸的具體行。
- 完成後執行
git bisect reset。
大型檔案只讀一次,並從筆記中引用,而不是在每個二分搜尋步驟中重新讀取。
重複回歸 / 截圖參考模式
在使用者表示同一個問題在修復後仍然錯誤、提供「好」的截圖/版本/檔案,或描述先前正確的視覺結果時啟用。
將參考視為證據,而非裝飾:
- 列出所有回報和可見的症狀,在有用時保留使用者的具體措辭(「仍然慢」、「不清楚」、「尖刺」、「先顯示上一個內容」)。
- 識別參考基準:最後好提交/標籤、舊建置、測試夾具、截圖、下載的成品,或使用者描述的預期狀態。
- 在編輯前定義通過/失敗檢查。對於視覺錯誤,這可能是一個狹窄的截圖檢查清單加上渲染該視圖的指令;對於行為錯誤,偏好自動化回歸測試或確定性重現。
- 比較當前與參考,並指出確切的差異。不要將視覺缺陷概括為「樣式打磨」,當證據指向損壞的渲染、競爭條件、字型管線或狀態路徑時。
- 如果嘗試一次修復後相同症狀仍然存在,停止並從證據重建假設。不要在已被反駁的解釋上堆疊更多修補。
如果問題純屬主觀 UI 品味,導向 /ui。如果是渲染、狀態、時序、建置輸出、字型生成,或來自已知好版本的回歸,則留在 /hunt。
範圍爆炸模式
在修復根本原因模式後、宣告錯誤完成前啟用;也適用於使用者說「舉一反三」、「舉一反三深入看看」或「其他地方有沒有同樣問題」時。同樣的形狀通常隱藏在 N 個其他地方;一個忽略爆炸的局部修復會在樹中留下 N - 1 個錯誤。
- 提取模式特徵:產生錯誤的具體函式名稱、正規表示式、API 呼叫、CSS 選擇器、鎖獲取、驗證跳過或輸入邊界。
- 在整個儲存庫中執行
grep -rn <pattern>(排除生成的目錄、建置輸出、供應商依賴)。對於錯誤類別模式(例如「任何缺少鎖的處理器」),搜尋周圍的形狀,而不只是文字本身。 - 列出每個匹配。對每個匹配,書面回答:這裡有同樣的錯誤嗎?選擇修復/保留(解釋為什麼安全)/不確定(詢問使用者)。不要默默跳過匹配。
- 在爆炸報告出現在輸出區塊之前,不要宣稱「已修復」。
常見觸發:
- 在一個頁面上修復了視覺錯誤:檢查使用相同元件、佈局或媒體查詢斷點的其他每個頁面。
- 在一個處理器中修復了一個競爭條件:檢查獲取相同鎖或觸碰相同共享狀態的每個處理器。
- 在一個入口點修補了一個驗證跳過:檢查到達相同下游接收器的每個入口點。
- 為一個輸入形狀修復了一個正規表示式/解析器:檢查相同正規表示式/解析器的每個呼叫者。
如果爆炸發現了不相關的錯誤,列出它們,但除非使用者同意,否則不要在此 PR 中修復;範圍蔓延本身就是一種反模式。
確認或捨棄
執行一個如果假設錯誤就會失敗的探針,然後讀取結果。如果證據與假設矛盾,完全捨棄它,並根據探針顯示的內容重新定位。不要在已被反駁的假設上堆疊修復,也不要因為程式碼「看起來像」原因就保留它。
執行時期證據階梯
在宣稱錯誤已修復前,使用此階梯:
- 原始碼追蹤:指出能產生症狀的確切函式、狀態轉換、檔案、行號或條件。
- 確定性重現:執行或撰寫能產生它的最小指令、測試夾具、UI 路徑或情境。
- 日誌/狀態/快取:檢查證明路徑已達到的執行時期狀態,包括佇列、資料庫行、快取、暫存檔、產生的輸出或外部工具日誌。
- 建置/測試:執行執行修復的狹窄測試或建置。
- 真實執行時期檢查:對於 UI、原生應用、瀏覽器、渲染或視覺錯誤,開啟應用程式/頁面/成品,並透過截圖或具體檢查清單驗證可見結果。
僅編譯對於 UI、原生應用、視覺、渲染或生成成品錯誤是不夠的。如果執行時期檢查在環境中不可行,說明原因並交出確切的畫面、指令或成品以供驗證。
對於重複發生的失敗類別,在加入第二個修復前載入 references/failure-patterns.md。
原生應用凍結模式
在桌面或行動原生應用回報沙灘球、無回應、分頁切換凍結、首次開啟延遲、閒置喚醒停滯、覆蓋層鎖定,或截圖顯示應用程式凍結時啟用。
在更改程式碼前收集的證據:
- 確切的使用者路徑和版本:首次啟動 vs 溫啟動、分頁或視窗轉換、閒置時間、權限、顯示器數量,以及任何讓凍結消失的設定。
- 凍結時的執行時期擷取:
sample <process>、最近的應用程式日誌、CPU 和記憶體佔用、執行緒數量,以及主執行緒是否被阻塞、旋轉或分配記憶體。 - 首幀表面:視圖主體工作、第一個
.task、同步圖示或中繼資料查詢、檔案系統掃描、URL 父目錄遍歷、通知回呼,以及應用程式/視窗喚醒處理器。 - 修復後的爆炸搜尋:在整個儲存庫中 grep 相同的 API 形狀,特別是路徑父目錄遍歷、同步圖示載入、渲染路徑中的中繼資料讀取,以及在主執行緒上執行的回呼。
常見的原生凍結陷阱:
- 啟動、終止、權限、音訊、顯示或工作區通知在主執行緒上執行路徑遍歷、圖示查詢、檔案系統掃描或程序列舉。
- 首次繪製在顯示互動殼層前,填充完整的應用程式列表、目錄樹、媒體縮圖集或系統狀態表。
- 輸入鎖定或全螢幕覆蓋層,沒有針對 Escape、應用程式停用、權限拒絕、程序終止和視窗關閉的保證拆卸路徑。
- 計時器或取樣工作在隱藏視窗、長時間閒置、睡眠/喚醒或應用程式重新啟用後仍然存活。
僅編譯和僅原始碼檢查對此模式不足。輸出必須包含執行時期擷取、根本原因幀或狀態轉換、有針對性的回歸防護,以及任何已修復或明確保留安全的同類匹配。
目標性日誌
將日誌當作手術刀,而非噪音。在加入日誌前,寫下它要回答的問題:
「如果這個日誌在 Y 之前印出 X,假設 A 仍有可能;如果沒有,假設 A 是錯的。」
載入 references/logging-techniques.md 取得完整的日誌操作手冊:二分搜尋儀器、區分性日誌內容、邊界優先放置、時序錯誤日誌記錄和移除紀律。
快速規則:
- 將第一個日誌放在執行路徑的中點,而非症狀處。從那裡開始二分搜尋。
- 只記錄區分性的事實:序號、輸入鍵、採取的分支、舊/新狀態、錯誤碼。
- 在完成前移除暫時日誌。將持久診斷隱藏在專案的除錯旗標之後。
如果加入日誌改變了行為,將其視為時序、生命週期或並發問題的證據。
陷阱
| 發生情況 | 規則 |
|---|---|
| 修補了客戶端面板而非本地面板 | 在碰任何檔案前,向後追蹤執行路徑 |
| MCP 未載入,切換工具而非診斷 | 在切換方法前檢查伺服器狀態、API 金鑰、設定 |
| 在測量原始系統/工具鏈層之前指責可見應用程式 | 先測量底層,然後明確排除被反駁的假設 |
| Orchestrator 顯示 RUNNING 但 TTS 供應商設定錯誤 | 在多階段管線中,隔離測試每個階段 |
| 競爭條件被診斷為過期狀態錯誤 | 對於時序敏感問題,在檢查狀態前先檢查事件時間戳和順序 |
| 到處加入日誌仍然無法解釋錯誤 | 將每個日誌重寫為是/否問題。刪除不能排除或確認假設的日誌 |
| 本地可重現但 CI 失敗 | 先對齊環境(執行時期版本、環境變數、時區),然後追蹤程式碼 |
| 堆疊追蹤指向函式庫深處 | 向後回溯 3 幀到自己的程式碼;錯誤幾乎總是在那裡,而非依賴中 |
| 從應用程式啟動時正常,透過檔案關聯/拖放/深層連結/外部代理開啟時壞掉 | 使用使用者描述的確切入口點重現。應用程式內部初始化與冷啟動加檔案初始化不同;文件到達時狀態可能尚未準備好 |
| 建置通過但 UI 仍然看起來不對 | 向上移動執行時期證據階梯,驗證真實的渲染表面或成品 |
| 修復符合回報者的設定但對其他人沒有改變,或使預設值回歸 | 缺陷報告是證據,不是完整範圍。說明修復是否改變所有使用者的預設體驗或僅回報者的設定,並偏好修復預設路徑 |
| 更改了演算法但輸出仍然錯誤 | 讀取器可能正在讀取舊程式碼寫入的持久輸出(掃描結果、分析快取、具有 TTL 的快照)。更改生成後持久化的資料需要在同一更改中使舊快取失效或版本提升;在重新診斷前,確認執行時期沒有讀取過期資料 |
| 回報者可重現,本地機器正常,代理盲目修補 | 先產生一個複製貼上的診斷指令(單一指令、靜默收集、一個輸出檔案、隱私說明),從返回的證據診斷,然後修復 |
渲染錯誤模式
在以下情況啟用:「PDF 看起來不對」、「分頁問題」、「字型未渲染」、PDF 輸出損壞,或列印佈局錯誤。
載入 references/rendering-debug.md 取得完整的診斷檢查清單(WeasyPrint 怪癖、字型載入、頁面溢出、瀏覽器列印 CSS)。先靜態分析,必要時再重現。
IME / Unicode 問題
對於輸入法、字元渲染或文字編碼錯誤(IME 狀態、游標漂移、表情符號分割、組字事件),在形成假設前先檢查 references/ime-unicode.md。
輸出
成功格式
用一行純文字陳述結果以及更改是否已提交;下方的區塊支援該行,不取代它。
Root cause: [什麼錯了,檔案:行號]
Fix: [什麼更改了,檔案:行號]
Sibling sweep: [檢查了 N 個相同形狀的位置,N 個已修復 / 未發現 / 未執行,原因]
Confirmed: [證明修復的證據或測試]
Tests: [通過/失敗計數,回歸測試位置]
Regression guard: [測試檔案:行號] 或 [無,原因]
狀態:已解決、有附帶條件的已解決(陳述條件),或受阻(陳述未知事項)。
回歸防護規則:對於任何重複發生或先前「已修復」的錯誤,修復尚未完成,直到:
- 存在一個回歸測試,在未修復的程式碼上失敗,在已修復的程式碼上通過。
- 測試位於專案的測試套件中,而非暫存檔。
- 提交訊息說明錯誤為何重複發生,以及此修復為何能防止它。
交接格式(連續 3 個假設失敗後)
Symptom:
[原始錯誤描述,一句話]
Hypotheses Tested:
1. [假設 1] → [測試方法] → [結果:已排除,因為...]
2. [假設 2] → [測試方法] → [結果:已排除,因為...]
3. [假設 3] → [測試方法] → [結果:已排除,因為...]
Evidence Collected:
- [日誌片段 / 堆疊追蹤 / 檔案內容]
- [重現步驟]
- [環境資訊:版本、設定、執行時期]
Ruled Out:
- [已被排除的根本原因]
Unknowns:
- [仍然不清楚的事項]
- [缺少的資訊]
Suggested Next Steps:
1. [下一步調查方向]
2. [可能需要的外部工具或權限]
3. [使用者應提供的額外上下文]
狀態:受阻






