dowafu 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +158 -0
  3. package/dist/adapters/anthropic-messages.js +145 -0
  4. package/dist/adapters/gemini-native.js +114 -0
  5. package/dist/adapters/responses.js +112 -0
  6. package/dist/audit.js +123 -0
  7. package/dist/cli-args.js +138 -0
  8. package/dist/cli.js +304 -0
  9. package/dist/cost.js +54 -0
  10. package/dist/dispatch-home.js +23 -0
  11. package/dist/dotenv-invariant.js +66 -0
  12. package/dist/error-classify.js +13 -0
  13. package/dist/gate.js +37 -0
  14. package/dist/gitignore-check.js +24 -0
  15. package/dist/json-output.js +80 -0
  16. package/dist/mask.js +83 -0
  17. package/dist/output.js +152 -0
  18. package/dist/pkg-info.js +35 -0
  19. package/dist/prompt.js +105 -0
  20. package/dist/providers.js +151 -0
  21. package/dist/rate-limit.js +27 -0
  22. package/dist/raw-integrity.js +49 -0
  23. package/dist/report.js +101 -0
  24. package/dist/runner.js +328 -0
  25. package/dist/secret-env.js +6 -0
  26. package/dist/semaphore.js +25 -0
  27. package/dist/ticket.js +156 -0
  28. package/dist/tool-call-audit.js +19 -0
  29. package/dist/types.js +11 -0
  30. package/dist/usage.js +224 -0
  31. package/dist/validate.js +100 -0
  32. package/dist/whitelist.js +38 -0
  33. package/package.json +60 -0
  34. package/providers.json +84 -0
  35. package/publish/.agents/skills/find-holes-external/SKILL.md +418 -0
  36. package/publish/.agents/skills/preflight/SKILL.md +126 -0
  37. package/publish/.agents/skills/wrap/SKILL.md +65 -0
  38. package/publish/.claude/agents/explore-haiku.md +8 -0
  39. package/publish/.claude/agents/hole-finder-cost.md +15 -0
  40. package/publish/.claude/agents/hole-finder-feasibility.md +15 -0
  41. package/publish/.claude/agents/hole-finder-safety.md +15 -0
  42. package/publish/.claude/agents/hole-finder.md +14 -0
  43. package/publish/.claude/skills/find-holes/SKILL.md +112 -0
  44. package/publish/.claude/skills/find-holes-external/SKILL.md +434 -0
  45. package/publish/.claude/skills/preflight/SKILL.md +196 -0
  46. package/publish/.claude/skills/wrap/SKILL.md +62 -0
  47. package/publish/README.md +75 -0
  48. package/publish/workflow_spec.md +65 -0
@@ -0,0 +1,434 @@
1
+ ---
2
+ name: find-holes-external
3
+ description: 把規劃書段落派給外部模型(OpenAI/DeepSeek/Gemini/Anthropic)做找漏洞審查,經本地 dowafu CLI 執行,spoke 唯讀且受白名單控管。適用於需要異質視角、或要對照真實原始碼審查規劃書時。用法:/find-holes-external <規劃書檔案路徑> [聚焦章節或問題]
4
+ ---
5
+
6
+ # find-holes-external — 外派找漏洞
7
+
8
+ 你是 hub。本 skill 把規劃書的指定範圍派給**外部模型**做找漏洞審查。
9
+ **spoke 產出意見,不產裁決;採不採用由使用者決定。**
10
+
11
+ 工具是 `dowafu`(已全域安裝的本地 CLI),用你的終端機工具呼叫。
12
+ **spoke 只讀你給它的東西**——唯讀,而且每次讀檔都經白名單判定。
13
+
14
+ > **需要知道的全部在這裡,不要去翻別的文件找補充說明。**
15
+
16
+ ---
17
+
18
+ ## 1. 前置檢查
19
+
20
+ **一、確認 lens 定義讀得到。** 讀 `.claude/agents/hole-finder-*.md`,回報你看到哪幾支、
21
+ 各自是什麼視角。讀得出來就算過。
22
+
23
+ **讀不到就停下來告訴使用者,不要自己去別處找、也不要憑空寫一份。** 那些檔是 spoke 的
24
+ system prompt 來源——角色、禁令、輸出格式、收尾句都在裡面。自己寫的版本會讓 spoke 的
25
+ 產出跟稽核判準對不上。
26
+
27
+ **二、確認 `tmp/` 有被 git 忽略。** spoke 的回報會含規劃書原文,不該進版控。
28
+ 沒被忽略就停下來問使用者要不要加,**不要自己改 `.gitignore`**。
29
+
30
+ ---
31
+
32
+ ## 1.5 執行環境的差異(依你的 host,看其中一節就好)
33
+
34
+ **工單格式、逐題對照表、判讀紀律都跟你在哪執行無關**,那些寫在後面的流程步驟裡,
35
+ 所有 host 一律照辦。這一節只講**執行層面**的差異。
36
+
37
+ > **判準是「誰在跑你」,不是「你背後是哪個模型」。** Claude Code 用 `settings.json` 接
38
+ > 相容 API 或別家模型當 BYOK 時,它**仍然是 Claude Code**——工具、設定、讀哪個入口檔
39
+ > 全都不變,走第一節。反過來,別的 host 就算選了 Claude 當模型,也不走第一節。
40
+ >
41
+ > **不確定自己算哪一種就走第二節。** 那一節的做法對 Claude Code 也完全適用,只是多打
42
+ > 幾個字;反過來走錯會直接卡住。
43
+
44
+ ### 你是 Claude Code 的 agent
45
+
46
+ 三件事,其餘照後面的流程走:
47
+
48
+ - **不帶 `--yes` 時 CLI 會直接中止、不呼叫任何 API**(stdin 不是 TTY,確認提示沒有人
49
+ 能回答)。這是機制保證——但它擋得住「沒有人在場」,擋不住「你自己決定要跑」
50
+ - **執行中終端機會即時輸出**每輪 token 與工具呼叫,那條通道可靠
51
+ - `dowafu` 在 PATH 上,指令直接下就行
52
+
53
+ ### 你不是 Claude Code 的 agent
54
+
55
+ 四件事不同。
56
+
57
+ **一、每一條 `dowafu` 指令都照這個形狀,`cd` 與 `--repo-root .` 都不要省:**
58
+
59
+ ```bash
60
+ cd <repo 根的絕對路徑> && dowafu <工單目錄> --repo-root . --dry-run
61
+ ```
62
+
63
+ 你的終端機不保證落在哪一個 workspace folder,而 `--repo-root` 預設取 cwd。cwd 錯了會
64
+ **安靜地錯**——工單照樣解析、spoke 照樣派出去,只是白名單邊界與 lens 定義都指到別處。
65
+
66
+ **二、`dowafu` 是外部全域 CLI,不在 workspace 裡。不要去找它,直接下:**
67
+
68
+ ```bash
69
+ dowafu --version
70
+ ```
71
+
72
+ 印不出版本號只有兩種情況,**兩種都不要自己搜檔案系統**:
73
+
74
+ - **`command not found`**——問使用者 CLI 裝在哪(請他跑 `which dowafu`),拿到絕對路徑
75
+ 之後改用絕對路徑呼叫
76
+ - **`Operation not permitted`**——**沙箱擋的,不是沒安裝**。它多半裝在家目錄底下,而沙箱
77
+ 預設不讀家目錄。照 host 的提示放行再跑一次;API key 與對外網路同樣被擋,所以放行是
78
+ 必要的,不是可選的
79
+
80
+ **三、`--yes` 一定要帶。**
81
+
82
+ 不帶的話 CLI 會印出 `繼續?[y/N]` 停住等輸入,而你的 host 會把控制權交回給你,並提供一個
83
+ **send input** 選項——**那條路能把 `y` 送出去,直接開始計費**。
84
+
85
+ > **不准選它。** 加 `--yes` 等於代替使用者按下確認,加之前必須在對話裡取得他的明確同意。
86
+
87
+ **四、實跑用背景執行,進度靠檔案而不是終端機輸出。**
88
+
89
+ 用檔案讀取工具輪詢 `tmp/spoke/<ticket-id>/run.jsonl`:CLI 逐事件寫入該檔,讀得出跑到
90
+ 哪一輪、有沒有 `round_error`,看到兩個 `spoke_end` 就是跑完。背景終端機的輸出不保證拿
91
+ 得到,而單支 spoke 可能跑上好幾分鐘。
92
+
93
+ > **把終端機當啟動器,不要當資料通道。**
94
+
95
+ > 本節用功能稱呼工具(「終端機工具」「檔案讀取工具」),因為工具名依版本與模型而異——
96
+ > 對不上是常態,照你手上實際有的那個用。
97
+
98
+ ---
99
+
100
+ ## 2. 提派工計畫,等使用者確認(**確認前不准派**)
101
+
102
+ | 要列的 | 說明 |
103
+ | --- | --- |
104
+ | 待審段落 | 哪個檔案的哪一節、幾行 |
105
+ | 派幾個 spoke、哪些 lens | 見下 |
106
+ | 每個 spoke 的 provider/model | 見下 |
107
+ | **每題的「問題 → 答案在哪個檔 → 在清單裡嗎」對照** | **必列,見下方格式** |
108
+ | 預估成本量級 | 參考值:三個 spoke、中等工單約 40k token |
109
+
110
+ ### 具體問題與允許清單**必須逐題對照著列**
111
+
112
+ 不要把「問題」和「允許清單」分成兩塊各列一遍——那樣看不出哪一題沒有對應的檔案。
113
+ 用這個格式:
114
+
115
+ | Q | 問題 | 答案在哪個檔 | 在清單裡嗎 |
116
+ | --- | --- | --- | --- |
117
+ | 1 | §3.1 的 schema 變更可行嗎 | `prisma/schema.prisma` | ✅ |
118
+ | 2 | §2 現況描述與實際程式碼有無出入 | `lib/a.ts`、`lib/b.ts` | ❌ **要補** |
119
+
120
+ **這是最常犯的錯**:問了某題,卻沒給回答那題所需要的檔——問「這是不是唯一入口」卻沒給
121
+ 該檔本身,問「現況描述有無出入」卻只給了「新宣稱所在的檔」。spoke 只能在「無法驗證」欄
122
+ 記下缺什麼,**那是派工端的失誤,不是它的問題**。
123
+
124
+ 逐題列出來,使用者一眼就能看出漏了什麼。**這一欄不是形式,是這個 skill 目前唯一擋得住
125
+ 漏檔的機制**——`--dry-run` 檢查得了格式,檢查不了「問題與清單對不對得上」。
126
+
127
+ **使用者對數量/模型/lens/問題的修改一律照辦。**
128
+
129
+ **裁允許清單時,逐題自問:「這一題的答案在哪個檔?那個檔在清單裡嗎?」** 按 lens 的
130
+ 名稱配檔案(safety 就給安全相關的)會配錯——lens 是**看的角度**,清單是**看的材料**。
131
+ 清單全給了某問題的**產生端**、而問題問的是**顯示端**時,spoke 物理上不可能答對;
132
+ 換一批對準答案位置的檔案,同一個 lens 就抓得到。**這個自問是為了在派工前擋下落差,
133
+ 不是派工後才發現。**
134
+
135
+ ### lens
136
+
137
+ | agent | 視角 |
138
+ | --- | --- |
139
+ | `hole-finder-safety` | 安全、併發競態、失敗態 |
140
+ | `hole-finder-cost` | 成本閘門、計費呼叫順序、資源消耗上限 |
141
+ | `hole-finder-feasibility` | 可行性、可實作性、規格與實作的落差 |
142
+
143
+ 派一個也可以,三個都派也可以。**不要因為「這個 lens 好像不適用」就先排除**——cost lens
144
+ 對一個沒有任何計費呼叫的專案,照樣找得出「無登入端點的資源消耗無上限」這類問題。
145
+
146
+ ### 型號(只有這些,填錯會被擋下)
147
+
148
+ | provider | model |
149
+ | --- | --- |
150
+ | `openai` | `gpt-5.6-luna`/`gpt-5.6-terra`/`gpt-5.6-sol` |
151
+ | `deepseek` | `deepseek-v4-flash` |
152
+ | `gemini` | `gemini-3.1-flash-lite`/`gemini-3.5-flash-lite`/`gemini-3.6-flash` |
153
+ | `anthropic` | `claude-opus-5`/`claude-sonnet-5` |
154
+
155
+ ### 使用者沒指定型號時
156
+
157
+ **提一組,說明你依據什麼提**(成本量級、素材規模、這個 lens 需不需要深推理),然後
158
+ **等他確認**。不要自己決定就派;他改過之後也不要因為「另一個好像比較好」就換回來。
159
+
160
+ 型號怎麼取捨是使用者的專案決定,這份文件不替他決定。
161
+
162
+ ---
163
+
164
+ ## 3. 寫工單
165
+
166
+ 寫到 `tmp/dispatch/<ticket-id>/`,`<ticket-id>` 用主題 slug(例如 `auth-review`)。
167
+
168
+ ### `_dispatch.md`
169
+
170
+ ```markdown
171
+ <!-- format: v1 -->
172
+ # dispatch <ticket-id>
173
+
174
+ | agent | provider | model | effort |
175
+ | --- | --- | --- | --- |
176
+ | hole-finder-safety | openai | gpt-5.6-luna | |
177
+ | hole-finder-cost | deepseek | deepseek-v4-flash | |
178
+ | hole-finder-feasibility | gemini | gemini-3.1-flash-lite | |
179
+ ```
180
+
181
+ 首行 `<!-- format: v1 -->` **必須有**;`model` 必填;`effort` 留白即可(自動用各家預設);
182
+ 只列你要派的那幾個。
183
+
184
+ ### `_shared.md`(所有 spoke 共用)
185
+
186
+ ```markdown
187
+ # 前提(不受審)
188
+ - <一行結論,例如「採 JWT 無狀態驗證,已定案」>
189
+ - <沒有就寫「無」>
190
+
191
+ # 待審段落
192
+ <把規劃書段落逐字貼進來,不要摘要、不要改寫>
193
+ ```
194
+
195
+ **待審段落一定要逐字內嵌**,不能寫「見 `_docs/xxx.md` 第 3 節」——spoke 讀不到 `_docs/`
196
+ (那是禁區,白名單會拒絕)。
197
+
198
+ ### `<agent>.md`(每個 spoke 一份,檔名要跟 `_dispatch.md` 的 agent 欄一致)
199
+
200
+ ```markdown
201
+ # 具體問題
202
+ 1. <問題>
203
+ 2. <問題>
204
+
205
+ # 允許讀取
206
+ - src/foo.ts
207
+ - lib/bar.ts
208
+ ```
209
+
210
+ **四條規則**:
211
+
212
+ 1. **不要寫角色定義**(「你是一個…」「不得…」「請以…收尾」)。角色、禁令、輸出格式、
213
+ 收尾句由 `dowafu` 從 `.claude/agents/<agent>.md` 讀出來組進 system prompt。
214
+ 寫進工單會造成規則雙來源——同一組規則出現兩次、措辭還不一致。
215
+
216
+ 2. **問題要開放,不要指向你已經發現的東西。** 把答案寫進問題,spoke 找到就只是複述工單。
217
+
218
+ 3. **允許清單要涵蓋「回答這些問題實際需要的檔案」。** 逐題自問:要回答這題得讀哪些檔?
219
+ 漏了的話 spoke 只能在「無法驗證」欄記下缺什麼——**那是派工端的失誤,不是它的問題**。
220
+ 路徑**相對 repo 根目錄**。留空也合法(純文字審查),但那樣就讀不到程式碼,會少掉
221
+ 「文件說 X、`src/foo.ts:42` 其實是 Y」這類最有價值的發現。
222
+
223
+ 4. **大的檔案排在清單最後——這能省掉一半成本。** spoke **嚴格照清單順序**讀檔,而多數
224
+ 模型**一輪只叫一個檔**,每一輪又會把先前讀過的全部內容重送一次。所以一個檔被重複
225
+ 計費的次數 = **總輪數 − 它被讀的輪次**——**排越前面,被重送越多次**。
226
+ 一個十幾 k token 的大檔,排第一 vs 排最後,該 spoke 的總量可以差到將近一倍。
227
+ 做法很簡單:**依檔案大小遞增排列**,最大的放最後。不確定大小就先 `wc -l`。
228
+ **打亂清單順序時這條仍然適用**——大檔壓最後,只洗其餘。
229
+
230
+ ### 跑 2–3 次怎麼做
231
+
232
+ 一次派工只是一次抽樣。**同一組設定跑 2–3 次,取聯集。**
233
+
234
+ - ticket-id 加後綴(`<主題>-r1`/`-r2`/`-r3`),**每一次一個獨立目錄**
235
+ - **第二次先用逐字相同的工單**,只換 ticket-id
236
+ - **比對前兩次的結果,再決定第三次怎麼跑**:
237
+ - 出現明顯的新條目 → 這個型號直接重跑就有效,第三次照樣逐字重跑
238
+ - 幾乎是同一組 → 這個型號對相同的 prompt 會收斂,**不擾動就拿不到新東西**。
239
+ 第三次改成**重排允許清單順序**,問題與 `_shared.md` 逐字不動
240
+
241
+ 重排時**大檔仍然壓在最後**(見上方規則 4),只洗其餘。
242
+
243
+ ---
244
+
245
+ ## 4. 先乾跑(不花錢)
246
+
247
+ **這是花錢之前唯一的檢查點。** 工單一旦真的派出去就開始計費,中途失敗或中斷,錢也拿
248
+ 不回來——**沒有 resume,重跑等於重新付一次**。乾跑錯了改一改再跑,不花任何錢。
249
+ **所以這一步不能跳過。**
250
+
251
+ 它抓得到的:`repoRoot` 指錯專案、型號填錯、lens 定義找不到、允許清單裡的檔不存在、
252
+ `tmp/` 沒被忽略、估算超過閘門上限——這些在真的呼叫 API 之前就會被擋下來。
253
+
254
+ > 其中 lens 定義與 `tmp/` 你在第 1 節已經確認過了。這裡不是要你重做一次,是讓你知道
255
+ > **就算第 1 節漏了,這一關還會擋下來**——但反過來不成立,所以第 1 節照樣要查。
256
+
257
+ **它抓不到的是工單內容本身**:問題問得好不好、允許清單對不對得上那些問題,乾跑一律看
258
+ 不出來。那是你在第 2 節就該做完的事(逐題對照表),乾跑不會替你補。
259
+
260
+ ```bash
261
+ dowafu tmp/dispatch/<ticket-id> --repo-root . --dry-run
262
+ ```
263
+
264
+ **跑之前先跟使用者說明這一步在幹嘛。** 他多半沒用過這個工具,看你在下指令會以為已經
265
+ 開始派工、開始計費了:
266
+
267
+ > 這一步只解析工單、驗證設定、估算用量,**不呼叫任何 API、不產生任何費用**。
268
+ > 目的是在花錢之前,先確認要派出去的東西是對的。
269
+
270
+ **跑完把報表轉述給他**,不要只說「乾跑通過」。至少這幾項:
271
+
272
+ | 報表項目 | 要讓使用者看懂的 |
273
+ | --- | --- |
274
+ | `repoRoot` | 指到的是不是他的專案 |
275
+ | `model`/`effort` | 每個 spoke 實際會用哪個型號 |
276
+ | 初始 prompt 估算 | 一開始就會送出去的量 |
277
+ | 允許清單估算 + 檔數 | spoke 讀得到哪些東西、多大 |
278
+ | 最壞總消耗 | **這是上限不是預期值**(各 spoke 的 cap 加總),實際通常遠低於此 |
279
+
280
+ 報表只給 token,不給金額——**要換算成錢就自己依型號價目算給他看**,別讓他自己猜。
281
+
282
+ 然後逐項核對:`repoRoot` 是這個專案、`model` 跟你寫的一致、`effort` 不是空的、估算
283
+ token 量級合理、**沒有 `⚠ 輸出目錄…未被…忽略` 的警告**。
284
+
285
+ 任何一項不對就修工單重跑,**不要往下走**。
286
+
287
+ ---
288
+
289
+ ## 5. 實跑
290
+
291
+ ```bash
292
+ dowafu tmp/dispatch/<ticket-id> --repo-root . --yes
293
+ ```
294
+
295
+ > **這一步會花錢。** `--yes` 等於你代替使用者按下確認,**加上它之前必須在對話裡取得
296
+ > 使用者的明確同意**。
297
+ >
298
+ > 不帶 `--yes` 會發生什麼,依你的 host 而定——見 §1.5。兩種情況都不會花到錢。
299
+
300
+ > **單支 spoke 可能跑到十分鐘,而且快慢無法事先判斷**——同素材、同工單、token 量級相近,
301
+ > 兩次的耗時仍可能差好幾倍。那是對方伺服器的負載,不可歸因、也不可預測。
302
+ >
303
+ > **`--timeout` 是單次 API 呼叫的逾時,不是整支 spoke 要跑多久**——一支 spoke 內有多輪
304
+ > 呼叫,它不會限制總時長,不要拿它來估整支要跑多久。
305
+ >
306
+ > **前景執行本身另有外部工具的逾時上限**(十分鐘量級),與 `--timeout` 無關、擋不住。
307
+ > **預估這次會跑久(清單大、問題多、選了較慢的型號)就改背景執行**,不要在前景硬等。
308
+
309
+ **進度一律以 `run.jsonl` 為準**——CLI 逐事件寫入該檔,中途 Ctrl-C 也讀得出跑到哪。
310
+ 終端機那條通道可不可靠依 host 而定(見 §1.5),`run.jsonl` 不受影響。
311
+
312
+ **真的撞到失敗或逾時:要不要重跑由使用者決定,不要自行重跑。** 重跑是重新付費,中斷前已
313
+ 花的錢救不回來(沒有 resume 機制)。把中斷時的狀態(`run.jsonl` 讀到哪、失敗訊息)告訴
314
+ 使用者,讓他判斷要不要重來——這與「零讀取就整份重跑」不衝突:那條是「判定沒跑」,這裡是
315
+ 「跑了但被中斷」,成本結構不同。
316
+
317
+ ---
318
+
319
+ ## 6. 回收——先原文、後融合
320
+
321
+ 產物在 `tmp/spoke/<ticket-id>/`:`<agent>.md`(原文)、`summary.md`(稽核表)、
322
+ `run.jsonl`(執行記錄)、`raw/`(完整請求與回應)。
323
+
324
+ **呈現順序不得更動**:
325
+
326
+ 1. **逐個 spoke 的內容都要讓使用者看到**,標明 lens 與模型。規模小(單份幾十行)就直接
327
+ 原文照登。**規模大到不適合整份貼出時**,可以摘要,但四件事缺一不可:
328
+ 1. **明確宣告取捨**——在摘要開頭寫明「本節為摘要,非逐字照登」,不安靜地摘掉
329
+ 2. **判讀涵蓋每一條,無一略過**——每個 spoke 的每一條觀察都要在後面的「hub 判讀」出現,
330
+ 這是取代「原文照登」的可稽核性來源:原文不在你眼前列出,不代表它沒被看過
331
+ 3. **指出原文路徑**(`tmp/spoke/<ticket-id>/<agent>.md`),使用者隨時可自行比對
332
+ 4. **只在產物尚未清理時才成立**——第 7 節清掉之後就沒有底本可查,那個情境下原文照登
333
+ 仍是唯一選擇,不能摘要
334
+ > 這條規則要防的不是「沒有原文」,是**防止 hub 只挑對自己有利的講**。滿足以上四件事,
335
+ > 手段換成摘要也一樣防得住;換句話說,要守住的是目的,不是「原文」這個手段本身。
336
+
337
+ > **沒有把握做到這四件事,就不要摘要——整份照登。**
338
+ 2. **`summary.md` 的稽核表併進原文區一併照登。**
339
+ 3. **之後另立「hub 判讀」一節**——去重,逐條標註你的初步判讀(成立/不成立+為什麼/
340
+ 需使用者裁決)。
341
+
342
+ ### 跑了多次時怎麼合併
343
+
344
+ **取聯集,不是取交集。** 逐條標出現次數(`3/5` 這種),但**不要用出現次數當重要度**——
345
+ 只出現一次的可能是嚴重級,每次都出現的也可能是假陽性。
346
+ **重要度一律由 hub 開檔驗證後自己判,不看票數。**
347
+
348
+ ### 判讀時必看的三件事
349
+
350
+ **一、`run.jsonl` 的 `toolCalls[]`。** 那是「它實際讀了什麼」,不是「它說它讀了什麼」。
351
+ 某個 spoke 若零次工具呼叫,它的回報只是文字審查,引用程式碼的說法要打折。
352
+
353
+ **二、安全或正確性的宣稱,自己打開檔案驗一次再轉述。** 不要把 spoke 的宣稱直接當結論
354
+ 報給使用者。
355
+
356
+ **行號要重新查證。** spoke 的引用會有幾行到數十行的偏移,而**內容描述往往是對的**——
357
+ 事實層可用、位置層不可用。**位置錯不等於幻覺**(幻覺是「該檔根本沒有這段」),兩者處置
358
+ 不同:幻覺要重跑或換模型,位置錯只需自己重新定位。**把位置錯判成幻覺,會丟掉整份能用的
359
+ 產出。**
360
+
361
+ **三、驗 spoke 引用時,註解不算證據。** 「spoke 說某段有註解背書某個結論」不夠,還要驗
362
+ **註解說的還成不成立**——註解會與程式碼漂移,而漂移的註解讀起來跟正確的一模一樣,
363
+ 只驗「註解存在且內容吻合」會把錯的判成對的。AGENTS.md 必答檢查第 1 條「不能只憑檔名、
364
+ 註解或行號」原本管的是寫規劃書時,這裡把它擴到驗收 spoke 引用時。
365
+
366
+ ### 稽核表怎麼看
367
+
368
+ | 欄位 | 意思 |
369
+ | --- | --- |
370
+ | `收尾句` | pass/fail |
371
+ | `工具呼叫:N(允許 N/拒絕 N)` | 讀了幾次、幾次被拒——`run.jsonl` `toolCalls[]` 的統計 |
372
+ | `觀察:N` | 條數;`無法計數` 表示格式無法辨識,翻原文確認 |
373
+ | `清單外引用` | 引用了允許清單外的路徑,**可能是臆測**——對照 `toolCalls[]` 判斷。**此欄至今沒抓到過真正的幻覺**:抓到的多半是 spoke 照抄素材的縮寫路徑、轉述素材裡提過但自己標明讀不到的路徑,或絕對/相對路徑混用。先查該路徑是不是出現在你自己的 `_shared.md` 裡,或只是寫法不同,不要預設是編的 |
374
+ | `無法驗證欄` | 有沒有照模板寫這一節。**判讀產出品質時看這欄有多具體**(例如逐項標明哪個結論依賴哪個不可讀的檔案),不要看觀察條數——條數不可靠,重疊、灌水都會推高條數但不代表品質 |
375
+ | `疑似禁止內容` | **只是疑似**,關鍵詞比對必有誤判,看命中原句自行判斷 |
376
+
377
+ **若欄位最前面出現 `⚠ 零原始碼讀取(允許 N 檔)`**——見下方「零讀取時怎麼辦」,先處理這個
378
+ 再看其他欄位。
379
+
380
+ ### 零讀取時怎麼辦
381
+
382
+ 看到 `⚠ 零原始碼讀取` 時:
383
+
384
+ **先確認允許清單是不是空的。** 清單留空是合法設定(純文字審查),此時零讀取是預期行為,
385
+ 不是異常。
386
+
387
+ **清單非空卻零讀取——判定為「這份工單沒有被執行」。**
388
+
389
+ > 不是「品質差」、不是「部分可用」,是**沒跑**。工單的問題是在「有程式碼」的前提下寫的
390
+ > (例如「現有程式碼中是否已存在可直接複用的取值路徑」),前提不成立時整組問題都失效。
391
+
392
+ **處置:整份重跑,不要分析內容、不要挑「還有用的部分」。**
393
+
394
+ 發現零讀取之後還去分析報告、搶救可用的部分,**是本末倒置**。理由是資訊來源:
395
+
396
+ - spoke 只拿到工單,而**工單是你自己寫的**
397
+ - 所以報告內容只有兩種來源:**你自己寫的東西反射回來**,或**編的**
398
+ - 沒有第三類——它沒有任何你不知道的資訊管道
399
+ - **你讀到「跟我想的一樣」會覺得有價值,那是自己的回音**;而唯一「新」的部分往往正是幻覺
400
+ (例如指名某個常數存在於某個檔,而那個檔完全沒有那個字串)
401
+
402
+ 可搶救的文字層觀察,**重跑一次就會再拿到,而且是更好的版本**。重跑很便宜。
403
+
404
+ **重跑方式**:換一個型號,或重排允許清單順序後重跑(見第 3 節「跑 2–3 次怎麼做」)。
405
+ 清單特別大的時候比較容易發生——重跑時順便把清單收斂到「真正回答得了這幾題的那些檔」。
406
+
407
+ 最後把這次的情況告訴使用者。
408
+
409
+ ---
410
+
411
+ ## 7. 清理(使用者裁決完之後)
412
+
413
+ ```bash
414
+ rm -rf tmp/spoke/<ticket-id> tmp/dispatch/<ticket-id>
415
+ ```
416
+
417
+ lens 定義留著,下次還會用。
418
+
419
+ ---
420
+
421
+ ## 出問題怎麼辦
422
+
423
+ | 症狀 | 處置 |
424
+ | --- | --- |
425
+ | 報表數值不對 | 停在 `--dry-run`,檢查 `_dispatch.md` |
426
+ | 中途失敗 | 讀 `run.jsonl` 的 `round_error` 事件(狀態碼、訊息、是第幾輪)與 `raw/<agent>.errors.json`;已完成輪次的內容仍會落檔 |
427
+ | 真失敗或逾時 | **要不要重跑問使用者,不要自行重跑**——重跑是重新付費,中斷前的花費救不回來 |
428
+ | 撞 429 | 加 `--concurrency 1` 重跑 |
429
+ | 報告品質差 | 換一個型號重跑,或同一組設定再跑一次取聯集(見第 3 節「跑 2–3 次怎麼做」) |
430
+ | 缺 API key | 使用者要在 `~/.config/dispatch/.env` 設定,**你不要去碰那個檔** |
431
+ | 找不到 `dowafu` | **查不到不代表沒安裝**——見 §1.5,多半是沙箱不讀家目錄 |
432
+ | `Operation not permitted` | host 的沙箱擋的,**不是工單或指令的問題**。照提示放行再跑一次 |
433
+
434
+ **撞到任何異常,告訴使用者**——不要自己吞掉或繞過去。
@@ -0,0 +1,196 @@
1
+ ---
2
+ name: preflight
3
+ description: 開工前檢查這個專案的環境有沒有把工作流程靜默停用:流程規範那一章的內容讀不讀得到、skill 與 lens 齊不齊、tmp/ 有沒有被 gitignore。Claude Code 另查 auto-compact 與 subagent 模型;其他 host 另查 dowafu 跑不跑得起來。只讀、只報告,不改任何設定。
4
+ ---
5
+
6
+ # preflight — 環境前置檢查
7
+
8
+ **第一次在一個專案用這套流程之前,先跑這個。** 派工、實作、收尾都做完了才發現環境早就
9
+ 把流程靜默停用,那整段工是白做的。
10
+
11
+ 假設你面對的是一個**完全沒接觸過這套東西**的專案:可能一樣都沒裝、可能裝一半、
12
+ 可能檔案都在、但你其實讀不到。三種狀態要分得出來,而且**它們都不會報錯**。
13
+
14
+ > **只讀、只報告,不要改任何設定檔。** 那是使用者的東西,其中幾項還是全域的,動了會
15
+ > 影響他所有專案。查完列表,讓他自己決定改不改。
16
+
17
+ **本文分三節。第 1 節所有人都要查,第 2、3 節二選一:**
18
+
19
+ | 你是 | 查哪些 |
20
+ | --- | --- |
21
+ | 任何 agent | 第 1 節 |
22
+ | **Claude Code** | 第 1 節 + **第 2 節** |
23
+ | **其他 host** | 第 1 節 + **第 3 節**(第 2 節那幾項對你不存在,查了只會得到一堆「查不到」) |
24
+
25
+ > **判準是「誰在跑你」,不是「你背後是哪個模型」。** Claude Code 用 `settings.json` 接
26
+ > 相容 API 或別家模型當 BYOK 時,它**仍然是 Claude Code**——`autoCompactEnabled`、
27
+ > `CLAUDE_CODE_SUBAGENT_MODEL` 那些照樣生效,走第 2 節。反過來,別的 host 就算選了
28
+ > Claude 當模型,也走第 3 節。
29
+ >
30
+ > **不確定就兩節都查**,把查不到的如實標成「查不到」。
31
+
32
+ ---
33
+
34
+ ## 1. 不分環境都要查
35
+
36
+ 指令在 repo 根目錄下跑。**你的工作目錄不保證落在哪裡,先 `cd` 過去再說。**
37
+
38
+ ```bash
39
+ cd <repo 根的絕對路徑>
40
+
41
+ echo "=== 流程規範 ==="
42
+ ls CLAUDE.md AGENTS.md workflow_spec.md 2>&1
43
+ # 這條只幫你定位「內容寫在哪個檔」。它有命中 ≠ 你讀得到——判準見下。
44
+ grep -n "規劃→實作→驗收流程規範" CLAUDE.md AGENTS.md workflow_spec.md 2>/dev/null
45
+
46
+ echo "=== skill 與 lens ==="
47
+ ls .claude/skills/ 2>/dev/null
48
+ ls .claude/agents/hole-finder-*.md 2>/dev/null
49
+
50
+ echo "=== tmp/ ==="
51
+ git check-ignore -q tmp && echo "已忽略" || echo "未忽略"
52
+ ```
53
+
54
+ ### 流程規範讀不讀得到
55
+
56
+ **判準只有一條:「規劃→實作→驗收流程規範(主從形態)」這一章的內容,你現在讀得到嗎?**
57
+
58
+ 讀得到就算通過。內容是直接貼在入口檔裡、還是用 `@` 之類的方式引入的,**那是使用者的
59
+ 選擇,不在檢查範圍內**。
60
+
61
+ 讀不到就標成不符合,然後**自己去找一下**(多半在 repo 根的 `workflow_spec.md`),
62
+ 讀完在回報裡註明「規範不在自動載入範圍內,本次是手動讀取的」——讓使用者知道換個
63
+ session 又會漏掉。
64
+
65
+ > **為什麼會讀不到?** 入口檔因 host 而異:Claude Code 讀 `CLAUDE.md`(**不讀
66
+ > `AGENTS.md`**),其他 host 多半讀 repo 根的 `AGENTS.md`。而 `@xxx.md` 是 Claude Code
67
+ > 的 import 語法,**別的 host 不會展開它**——那時你看到的只是一行字,規範內容從來沒進
68
+ > 過你的 context。這不代表專案設定錯了,是你這一側的差異。
69
+
70
+ ### skill 與 lens
71
+
72
+ `.claude/skills/find-holes-external/` 與 `.claude/agents/hole-finder-*.md` 在不在。
73
+ (後者 glob 會列出通用的 `hole-finder.md` 加三個 lens,四個都算正常。)
74
+
75
+ **lens 缺了不要自己補寫**——它是 spoke 的 system prompt 來源,自己寫的版本會讓產出跟
76
+ 稽核判準對不上。
77
+
78
+ ### `tmp/`
79
+
80
+ 未被 gitignore 就標成不符合:spoke 回報含規劃書原文,會被 commit 進版控。
81
+ **不要自己改 `.gitignore`**,問使用者。
82
+
83
+ ---
84
+
85
+ ## 2. 如果你是 Claude Code 的 agent
86
+
87
+ 下面兩項只影響 **Claude Code 自己的內派 sub-agent**。外派(`find-holes-external` 走
88
+ `dowafu`)的 spoke 模型由工單的 `_dispatch.md` 決定,**不受這兩項影響**——
89
+ 這個專案只跑外派的話,這一節查了也不會改變什麼。
90
+
91
+ ```bash
92
+ cd <repo 根的絕對路徑>
93
+
94
+ echo "=== settings(由低到高優先序)==="
95
+ for f in ~/.claude/settings.json .claude/settings.json .claude/settings.local.json; do
96
+ [ -f "$f" ] && { echo "--- $f"; cat "$f"; }
97
+ done
98
+
99
+ echo "=== 環境變數 ==="
100
+ echo "DISABLE_AUTO_COMPACT=${DISABLE_AUTO_COMPACT:-(未設)}"
101
+ echo "CLAUDE_CODE_SUBAGENT_MODEL=${CLAUDE_CODE_SUBAGENT_MODEL:-(未設)}"
102
+
103
+ echo "=== agent 定義的模型 ==="
104
+ grep -H "^model:" .claude/agents/*.md 2>/dev/null || echo "(沒有 agent 定義,或都沒指定 model)"
105
+
106
+ echo "=== 主模型與 effort ==="
107
+ grep -h "\"model\"\|\"effortLevel\"" ~/.claude/settings.json .claude/settings.json .claude/settings.local.json 2>/dev/null || echo "(未設,用預設)"
108
+ ```
109
+
110
+ ### auto-compact
111
+
112
+ **`autoCompactEnabled` 沒有出現在任何一層 settings = 不符合**,因為它的預設值是 `true`。
113
+ 不要把「沒看到設定」讀成「沒問題」——那會讓這條檢查永遠通過。
114
+
115
+ 流程規範要求 context 吃緊時走 `/wrap` 交接、關 session 重啟,而不是 compact
116
+ (compact 是有損壓縮,壓完之後熱 session 的價值已經沒了)。
117
+
118
+ 相關的還有 `autoCompactWindow`(100000–1000000)與環境變數 `DISABLE_AUTO_COMPACT`。
119
+
120
+ ### subagent 模型
121
+
122
+ `settings.json` **沒有**「預設 subagent 模型」這個鍵——但**有一個環境變數會一刀切**。
123
+ 解析順序由高到低四層,**全部攤出來給使用者看**:
124
+
125
+ 1. **`CLAUDE_CODE_SUBAGENT_MODEL` 環境變數**(設成別名或 model ID 時)
126
+ 2. 每次呼叫傳入的 `model` 參數
127
+ 3. `.claude/agents/*.md`(或 `~/.claude/agents/`)的 `model:` frontmatter
128
+ 4. 主對話的模型(frontmatter 省略時的預設就是這個)
129
+
130
+ **第 1 層會蓋掉所有 agent 定義檔的 `model:`**,包含刻意設成 opus 的那些——
131
+ 「一設下去內派全變輕量模型」就是它。而且它是**全域的,在 A 專案設的會影響 B 專案**。
132
+
133
+ `availableModels` 允許清單會再過濾上面三層:被擋的家族別名換成該家族允許的最新版本,
134
+ 其他情況**退回繼承主對話的模型**。所以 frontmatter 寫 `model: opus` **不保證跑 opus**。
135
+
136
+ **只查其中一層就回報「沒問題」會漏掉最常見的那個。**
137
+
138
+ **壓低的後果是靜默的**:內派 sub-agent 全部變成輕量模型,找漏洞照跑、照產出、照收尾,
139
+ 只是品質整個掉下來,沒有任何地方會提示。
140
+
141
+ ---
142
+
143
+ ## 3. 如果你不是 Claude Code 的 agent
144
+
145
+ 第 2 節那兩項對你不存在,跳過。你要確認的是下面三件事,**按這個順序**——前一項不成立,
146
+ 後面查了也沒有意義。
147
+
148
+ ### 一、`dowafu` 在哪、跑不跑得起來
149
+
150
+ **這是首要條件。** 工具起不來,工單寫得再好都派不出去;等到派工當下才發現,
151
+ 會白費一次組工單的工。
152
+
153
+ ```bash
154
+ dowafu --version
155
+ ```
156
+
157
+ 印得出版本號就過。印不出來只有兩種情況:
158
+
159
+ | 症狀 | 意思 | 怎麼回報 |
160
+ | --- | --- | --- |
161
+ | `Operation not permitted` | **沙箱擋的**,不是沒安裝。CLI 多半裝在家目錄底下,而沙箱預設不讀家目錄 | 照 host 的提示放行後重試。順帶告訴使用者:API key(`~/.config/dispatch/.env`)與對外網路同樣被擋,派工時一併要放行 |
162
+ | `command not found` | 可能沒裝,也可能裝在 PATH 之外 | 問使用者 CLI 裝在哪(請他跑 `which dowafu`),**不要自己搜檔案系統** |
163
+
164
+ **`command -v dowafu` 查不到不代表沒安裝**,別拿那個當判準。
165
+
166
+ ### 二、lens 定義與 skill 在不在
167
+
168
+ 見第 1 節的「skill 與 lens」。有一點對你特別重要:
169
+
170
+ **lens 定義是 CLI 要讀的,不是你要讀的。** 它是 `dowafu` 組 spoke system prompt
171
+ 的來源,你只要確認**檔案在**就好,不必自己讀懂內容。skill 才是你要讀的。
172
+
173
+ ### 三、流程規範的內容,在你讀得到的地方
174
+
175
+ 見第 1 節的「流程規範讀不讀得到」,判準相同:**那一章的內容,你現在讀得到嗎。**
176
+
177
+ **這一項你比 Claude Code 更容易踩空**,值得多看一眼。`@xxx.md` 是 Claude Code 的 import
178
+ 語法,你不會展開它——入口檔裡若只有那一行,你看到的就是一行字,而**你很可能以為自己
179
+ 已經讀過規範了**。實際檢查你的 context 裡有沒有那章的內容,別憑印象。
180
+
181
+ ---
182
+
183
+ ## 4. 輸出
184
+
185
+ 一張表,最多一頁:
186
+
187
+ | 項目 | 狀態 | 現況 | 怎麼改 |
188
+ | --- | :-: | --- | --- |
189
+ | 流程規範 | ✗ | 「規劃→實作→驗收流程規範」那章的內容不在我的 context 裡;已手動讀取 `workflow_spec.md` 補上 | 若希望每個 session 都自動載入,需調整入口檔的接法 |
190
+ | `tmp/` | ✓ | 已被 `.gitignore` 忽略 | — |
191
+
192
+ **表格開頭先寫明你是哪種 host、走的是第 2 節還是第 3 節**,不要讓使用者以為沒查的那幾項
193
+ 已經查過了。
194
+
195
+ **「查不到」要跟「符合」分開標。** 讀不到某個檔、或某項無法判定時,如實寫查不到,
196
+ 不要當成通過——這個 skill 存在的理由就是抓靜默失效,自己先靜默失效就沒有意義了。