SKILL.md
唯讀
名稱
click-path-audit
描述
追蹤每個使用者可見的按鈕/觸控點,透過其完整的狀態變更序列,找出那些個別功能正常運作,但彼此抵消、產生錯誤最終狀態,或讓 UI 處於不一致狀態的錯誤。使用時機:系統性除錯未發現錯誤,但使用者回報按鈕失效;或任何重大重構觸及共用狀態儲存後。
/click-path-audit — 行為流程稽核
找出靜態程式碼審查遺漏的錯誤:狀態互動副作用、順序呼叫間的競爭條件,以及默默互相取消的處理函式。
此技能解決的問題
傳統除錯檢查:
- 函式是否存在?(缺少接線)
- 是否崩潰?(執行時期錯誤)
- 是否回傳正確型別?(資料流)
但它不檢查:
- 最終 UI 狀態是否與按鈕標籤承諾的一致?
- 函式 B 是否默默取消函式 A 剛做的變更?
- 共用狀態(Zustand/Redux/context)是否有副作用抵消預期動作?
真實案例:一個「新增郵件」按鈕呼叫了 setComposeMode(true) 然後 selectThread(null)。兩者個別都正常運作。但 selectThread 有一個副作用會重設 composeMode: false。按鈕完全沒作用。系統性除錯找到了 54 個錯誤——但這個被漏掉了。
運作方式
針對目標區域中的每個互動觸控點:
1. 識別處理函式(onClick, onSubmit, onChange 等)
2. 依序追蹤處理函式中的每個函式呼叫
3. 對每個函式呼叫:
a. 它讀取了哪些狀態?
b. 它寫入了哪些狀態?
c. 它對共用狀態有副作用嗎?
d. 它是否作為副作用重設/清除任何狀態?
4. 檢查:後面的呼叫是否取消了前面呼叫的狀態變更?
5. 檢查:最終狀態是否符合使用者對按鈕標籤的預期?
6. 檢查:是否存在競爭條件(非同步呼叫以錯誤順序解析)?
執行步驟
步驟 1:繪製狀態儲存對應圖
在稽核任何觸控點之前,為每個狀態儲存動作建立副作用對應圖:
對於範圍內的每個 Zustand store / React context:
對於每個 action/setter:
- 它設定了哪些欄位?
- 它是否作為副作用重設了其他欄位?
- 記錄:actionName → {sets: [...], resets: [...]}
這是關鍵參考。如果不知道 selectThread 會重設 composeMode,就無法發現「新增郵件」的錯誤。
輸出格式:
STORE: emailStore
setComposeMode(bool) → sets: {composeMode}
selectThread(thread|null) → sets: {selectedThread, selectedThreadId, messages, drafts, selectedDraft, summary} RESETS: {composeMode: false, composeData: null, redraftOpen: false}
setDraftGenerating(bool) → sets: {draftGenerating}
...
危險重設(清除不屬於自己的狀態的動作):
selectThread → resets composeMode(由 setComposeMode 擁有)
reset → resets everything
步驟 2:稽核每個觸控點
針對目標區域中的每個按鈕/切換/表單提交:
觸控點:[按鈕標籤] 在 [元件:行號]
處理函式:onClick → {
呼叫 1: functionA() → sets {X: true}
呼叫 2: functionB() → sets {Y: null} RESETS {X: false} ← 衝突
}
預期:使用者看到 [按鈕標籤承諾的描述]
實際:X 為 false,因為 functionB 重設了它
判定:錯誤 — [描述]
檢查以下每種錯誤模式:
模式 1:順序取消
handler() {
setState_A(true) // 設定 X = true
setState_B(null) // 副作用:重設 X = false
}
// 結果:X 為 false。第一次呼叫無效。
模式 2:非同步競爭
handler() {
fetchA().then(() => setState({ loading: false }))
fetchB().then(() => setState({ loading: true }))
}
// 結果:最終 loading 狀態取決於哪個先解析
模式 3:過時閉包
const [count, setCount] = useState(0)
const handler = useCallback(() => {
setCount(count + 1) // 捕獲過時的 count
setCount(count + 1) // 同一個過時 count — 只增加 1,不是 2
}, [count])
模式 4:缺少狀態轉換
// 按鈕顯示「儲存」,但處理函式只驗證,從未實際儲存
// 按鈕顯示「刪除」,但處理函式設定一個旗標卻未呼叫 API
// 按鈕顯示「傳送」,但 API 端點已被移除/損壞
模式 5:條件式死路
handler() {
if (someState) { // 此時 someState 永遠為 false
doTheActualThing() // 永遠不會執行
}
}
模式 6:useEffect 干擾
// 按鈕設定 stateX = true
// 一個 useEffect 監聽 stateX 並將其重設為 false
// 使用者看到沒有任何反應
步驟 3:回報
針對每個發現的錯誤:
CLICK-PATH-NNN:[嚴重性:CRITICAL/HIGH/MEDIUM/LOW]
觸控點:[按鈕標籤] 在 [檔案:行號]
模式:[順序取消 / 非同步競爭 / 過時閉包 / 缺少轉換 / 死路 / useEffect 干擾]
處理函式:[函式名稱或內聯]
追蹤:
1. [呼叫] → sets {欄位: 值}
2. [呼叫] → RESETS {欄位: 值} ← 衝突
預期:[使用者預期的結果]
實際:[實際發生的結果]
修正:[具體修正方式]
範圍控制
此稽核成本較高。請適當設定範圍:
- 完整應用程式稽核: 用於上線時或重大重構後。每個頁面啟動平行代理。
- 單頁稽核: 用於建立新頁面後,或使用者回報按鈕失效後。
- 儲存聚焦稽核: 用於修改 Zustand store 後——稽核所有使用變更動作的消費者。
完整應用程式的建議代理分割:
代理 1:繪製所有狀態儲存對應圖(步驟 1)——這是所有其他代理的共用上下文
代理 2:儀表板(任務、筆記、日誌、想法)
代理 3:聊天(DanteChatColumn, JustChatPage)
代理 4:郵件(ThreadList, DraftArea, EmailsPage)
代理 5:專案(ProjectsPage, ProjectOverviewTab, NewProjectWizard)
代理 6:CRM(所有子分頁)
代理 7:個人資料、設定、保險庫、通知
代理 8:管理套件(所有頁面)
代理 1 必須先完成。其輸出是所有其他代理的輸入。
使用時機
- 系統性除錯發現「無錯誤」,但使用者回報 UI 失效時
- 修改任何 Zustand store 動作後(檢查所有呼叫者)
- 任何觸及共用狀態的重構後
- 發行前,針對關鍵使用者流程
- 當按鈕「沒反應」時——這是專門的工具
不應使用時機
- API 層級錯誤(錯誤的回應結構、缺少端點)——使用 systematic-debugging
- 樣式/佈局問題——視覺檢查
- 效能問題——效能分析工具
與其他技能的整合
- 在
/superpowers:systematic-debugging之後執行(它會找出其他 54 種錯誤類型) - 在
/superpowers:verification-before-completion之前執行(它會驗證修正是否有效) - 輸入至
/superpowers:test-driven-development——此處發現的每個錯誤都應有對應測試
範例:啟發此技能的錯誤
ThreadList.tsx「新增郵件」按鈕:
onClick={() => {
useEmailStore.getState().setComposeMode(true) // ✓ 設定 composeMode = true
useEmailStore.getState().selectThread(null) // ✗ 重設 composeMode = false
}}
儲存定義:
selectThread: (thread) => set({
selectedThread: thread,
selectedThreadId: thread?.id ?? null,
messages: [],
drafts: [],
selectedDraft: null,
summary: null,
composeMode: false, // ← 這個默默重設讓按鈕失效
composeData: null,
redraftOpen: false,
})
系統性除錯漏掉它,因為:
- 按鈕有 onClick 處理函式(不是死路)
- 兩個函式都存在(沒有缺少接線)
- 兩個函式都不會崩潰(沒有執行時期錯誤)
- 資料型別正確(沒有型別不符)
點擊路徑稽核抓到它,因為:
- 步驟 1 對應出
selectThread會重設composeMode - 步驟 2 追蹤處理函式:呼叫 1 設定 true,呼叫 2 重設 false
- 判定:順序取消——最終狀態與按鈕意圖矛盾






