dowafu 0.1.0 → 0.3.1

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 (54) hide show
  1. package/README.md +58 -25
  2. package/README_zh-tw.md +197 -0
  3. package/dist/adapters/anthropic-messages.js +18 -11
  4. package/dist/adapters/gemini-native.js +19 -16
  5. package/dist/adapters/read-file-tool-description.js +5 -0
  6. package/dist/adapters/responses.js +26 -14
  7. package/dist/audit.js +17 -1
  8. package/dist/cli-args.js +103 -38
  9. package/dist/cli.js +78 -43
  10. package/dist/dispatch-home.js +24 -6
  11. package/dist/doctor.js +91 -0
  12. package/dist/error-classify.js +13 -4
  13. package/dist/gate.js +3 -2
  14. package/dist/mask.js +16 -3
  15. package/dist/messages.js +396 -0
  16. package/dist/output.js +60 -37
  17. package/dist/prompt.js +5 -3
  18. package/dist/providers.js +46 -34
  19. package/dist/raw-integrity.js +6 -5
  20. package/dist/report.js +76 -22
  21. package/dist/runner.js +21 -10
  22. package/dist/ticket.js +35 -25
  23. package/dist/validate.js +21 -20
  24. package/dist/whitelist.js +9 -1
  25. package/package.json +3 -2
  26. package/providers.json +8 -3
  27. package/publish/en/.agents/skills/find-holes-external/SKILL.md +450 -0
  28. package/publish/en/.agents/skills/preflight/SKILL.md +137 -0
  29. package/publish/en/.agents/skills/wrap/SKILL.md +64 -0
  30. package/publish/en/.claude/agents/explore-haiku.md +8 -0
  31. package/publish/en/.claude/agents/hole-finder-cost.md +15 -0
  32. package/publish/en/.claude/agents/hole-finder-feasibility.md +15 -0
  33. package/publish/en/.claude/agents/hole-finder-safety.md +15 -0
  34. package/publish/en/.claude/agents/hole-finder.md +14 -0
  35. package/publish/en/.claude/skills/find-holes/SKILL.md +114 -0
  36. package/publish/en/.claude/skills/find-holes-external/SKILL.md +463 -0
  37. package/publish/en/.claude/skills/preflight/SKILL.md +198 -0
  38. package/publish/en/.claude/skills/wrap/SKILL.md +61 -0
  39. package/publish/en/README.md +106 -0
  40. package/publish/en/workflow_spec.md +71 -0
  41. package/publish/{.agents → zh-tw/.agents}/skills/find-holes-external/SKILL.md +195 -20
  42. package/publish/{.agents → zh-tw/.agents}/skills/preflight/SKILL.md +49 -7
  43. package/publish/{.claude → zh-tw/.claude}/skills/find-holes/SKILL.md +32 -4
  44. package/publish/{.claude → zh-tw/.claude}/skills/find-holes-external/SKILL.md +194 -19
  45. package/publish/{.claude → zh-tw/.claude}/skills/preflight/SKILL.md +51 -6
  46. package/publish/{README.md → zh-tw/README.md} +16 -0
  47. /package/publish/{.agents → zh-tw/.agents}/skills/wrap/SKILL.md +0 -0
  48. /package/publish/{.claude → zh-tw/.claude}/agents/explore-haiku.md +0 -0
  49. /package/publish/{.claude → zh-tw/.claude}/agents/hole-finder-cost.md +0 -0
  50. /package/publish/{.claude → zh-tw/.claude}/agents/hole-finder-feasibility.md +0 -0
  51. /package/publish/{.claude → zh-tw/.claude}/agents/hole-finder-safety.md +0 -0
  52. /package/publish/{.claude → zh-tw/.claude}/agents/hole-finder.md +0 -0
  53. /package/publish/{.claude → zh-tw/.claude}/skills/wrap/SKILL.md +0 -0
  54. /package/publish/{workflow_spec.md → zh-tw/workflow_spec.md} +0 -0
@@ -3,7 +3,7 @@ name: find-holes-external
3
3
  description: 把規劃書段落派給外部模型(OpenAI/DeepSeek/Gemini/Anthropic)做找漏洞審查,經本地 dowafu CLI 執行,spoke 唯讀且受白名單控管。適用於需要異質視角、或要對照真實原始碼審查規劃書時。用法:/find-holes-external <規劃書檔案路徑> [聚焦章節或問題]
4
4
  metadata:
5
5
  derived-from: ".claude/skills/find-holes-external/SKILL.md"
6
- derived-from-sha256: "e7ac49121ec6b918fef3cb67ff9c911562cd0056e542c76957782ea68caa468c"
6
+ derived-from-sha256: "a351ab07c313f0068df0662ba9711e1bec9bb8c15ffb50f4d773fca2d70c7e0f"
7
7
  ---
8
8
 
9
9
  # find-holes-external — 外派找漏洞
@@ -43,6 +43,11 @@ system prompt 來源——角色、禁令、輸出格式、收尾句都在裡面
43
43
  cd <repo 根的絕對路徑> && dowafu <工單目錄> --repo-root . --dry-run
44
44
  ```
45
45
 
46
+ **`--lang` 決定 CLI 輸出與 spoke prompt 的語言,預設是 `en`。** 你裝的是中文套件、lens 檔也是
47
+ 中文的,所以**中文專案要帶 `--lang zh-tw`**——不帶的話 CLI 會用英文輸出,spoke 也會拿到英文
48
+ prompt,而**兩邊都不會報錯,只是安靜地混語**。不想每次打,可以設環境變數 `DISPATCH_LANG=zh-tw`;
49
+ 優先序是 `--lang` > `DISPATCH_LANG` > 內建預設 `en`。
50
+
46
51
  你的終端機不保證落在哪一個 workspace folder,而 `--repo-root` 預設取 cwd。cwd 錯了會
47
52
  **安靜地錯**——工單照樣解析、spoke 照樣派出去,只是白名單邊界與 lens 定義都指到別處。
48
53
 
@@ -70,8 +75,8 @@ dowafu --version
70
75
  **四、實跑用背景執行,進度靠檔案而不是終端機輸出。**
71
76
 
72
77
  用檔案讀取工具輪詢 `tmp/spoke/<ticket-id>/run.jsonl`:CLI 逐事件寫入該檔,讀得出跑到
73
- 哪一輪、有沒有 `round_error`,看到兩個 `spoke_end` 就是跑完。背景終端機的輸出不保證拿
74
- 得到,而單支 spoke 可能跑上好幾分鐘。
78
+ 哪一輪、有沒有 `round_error`。**完成判定見 §5**——「兩個 `spoke_end`」只是其中一半。
79
+ 背景終端機的輸出不保證拿得到,而單支 spoke 可能跑上好幾分鐘。
75
80
 
76
81
  > **把終端機當啟動器,不要當資料通道。**
77
82
 
@@ -89,6 +94,7 @@ dowafu --version
89
94
  | 每個 spoke 的 provider/model | 見下 |
90
95
  | **每題的「問題 → 答案在哪個檔 → 在清單裡嗎」對照** | **必列,見下方格式** |
91
96
  | 預估成本量級 | 參考值:三個 spoke、中等工單約 40k token |
97
+ | **每支 spoke 的產物落點** | **必列**,見下方〈幾支 spoke 就要幾個落點〉 |
92
98
 
93
99
  ### 具體問題與允許清單**必須逐題對照著列**
94
100
 
@@ -115,6 +121,50 @@ dowafu --version
115
121
  換一批對準答案位置的檔案,同一個 lens 就抓得到。**這個自問是為了在派工前擋下落差,
116
122
  不是派工後才發現。**
117
123
 
124
+ ### 每支 spoke 各一張表——而且**那張表本身就是交件物,不是「我有做」的宣告**
125
+
126
+ **那張表每支 spoke 各填一張,而且兩張都要攤在使用者面前。** 說「清單都對過了」「該有的檔都在」
127
+ **不能取代把表給他看**——「允許清單已涵蓋所有問題」只是一句話,真假都一樣好寫。
128
+
129
+ 每一列要寫**你實際預期答案所在的那個檔的路徑**——不是目錄、不是「tags 那幾支路由」、
130
+ 不是 lens 的名字。寫成與允許清單同樣的形式,兩邊才對得起來:
131
+
132
+ | Q | 問題 | 答案在哪個檔 | 在清單裡嗎 |
133
+ | --- | --- | --- | --- |
134
+ | 1 | `requireAuth` 回傳的 userId 夠不夠做刪除自己的檢查 | `lib/auth-guard.ts` | ✅ |
135
+ | 2 | 最後一位管理員的防護是不是原子的 | `prisma/schema.prisma`(其餘看待審段落本身) | ✅ |
136
+
137
+ **把路徑寫進那一欄之前,先確認答案真的在那個檔裡**——grep 那個符號,或直接開檔。
138
+ 這一欄是為了在派工前擋下漏檔;**憑印象填的話它什麼都擋不住**:一個看起來很合理的檔名,
139
+ 與正確的那個一樣能通過格式檢查,而差額是 spoke 用錢付的。
140
+
141
+ **兩支最後拿到同一批檔也可以,但要說得出為什麼。** 清單相同是**可以解釋的結論**,不是起點;
142
+ 而「裁到兩份不一樣為止」是同一個錯誤的反面。
143
+
144
+ **一份清單餵兩支 lens,會往交集收斂,而不是聯集。** 最先掉的是「只有其中一支需要」的檔,
145
+ 而那正是那支 lens 被派去看的東西;spoke 這時只能把缺口寫進「無法驗證」欄,而用這種方式
146
+ 發現要付一整輪派工的錢。為省錢裁清單是合理的——但要**各自對著自己的問題裁,不是對著另一支裁**。
147
+
148
+ ### 幾支 spoke 就要幾個落點——先挖好坑再派
149
+
150
+ 一支 spoke 的產物落在 `tmp/spoke/<ticket-id>/` 底下**以它的 agent 為名的那一組檔**:
151
+ `<agent>.md`,以及 `raw/<agent>.request.json`/`.response.json`/`.errors.json`。
152
+ 那一組同進同出,所以**落點就是 `<ticket-id>` + `<agent>` 這個組合**。
153
+
154
+ **派工計畫要逐支列出落點路徑,判準只有一句:落點數要等於 spoke 數,而且兩兩不同。**
155
+
156
+ | spoke | lens | provider/model | 產物落點 |
157
+ | --- | --- | --- | --- |
158
+ | 1 | safety | openai/gpt-5.6-luna | `tmp/spoke/auth-review-luna/hole-finder-safety.md` |
159
+ | 2 | safety | deepseek/deepseek-v4-flash | `tmp/spoke/auth-review-ds/hole-finder-safety.md` |
160
+ | 3 | feasibility | gemini/gemini-3.6-flash | `tmp/spoke/auth-review-luna/hole-finder-feasibility.md` |
161
+
162
+ **兩支算出同一個路徑,就是坑不夠**——而且解法不是改個檔名,是**拆工單目錄**:同一個 lens 要跑
163
+ 多個型號時,一個型號一個目錄(ticket-id 加後綴),各派一次。
164
+ 不同 lens 共用一個目錄沒有問題,它們的 agent 名本來就不同。
165
+
166
+ 這一欄要用數的,不要用看的。**你不需要知道撞名會發生什麼**——數不對就先停下來拆目錄。
167
+
118
168
  ### lens
119
169
 
120
170
  | agent | 視角 |
@@ -131,7 +181,7 @@ dowafu --version
131
181
  | provider | model |
132
182
  | --- | --- |
133
183
  | `openai` | `gpt-5.6-luna`/`gpt-5.6-terra`/`gpt-5.6-sol` |
134
- | `deepseek` | `deepseek-v4-flash` |
184
+ | `deepseek` | `deepseek-v4-flash`/`deepseek-v4-pro` |
135
185
  | `gemini` | `gemini-3.1-flash-lite`/`gemini-3.5-flash-lite`/`gemini-3.6-flash` |
136
186
  | `anthropic` | `claude-opus-5`/`claude-sonnet-5` |
137
187
 
@@ -161,8 +211,14 @@ dowafu --version
161
211
  | hole-finder-feasibility | gemini | gemini-3.1-flash-lite | |
162
212
  ```
163
213
 
164
- 首行 `<!-- format: v1 -->` **必須有**;`model` 必填;`effort` 留白即可(自動用各家預設);
165
- 只列你要派的那幾個。
214
+ 首行 `<!-- format: v1 -->` **必須有**;`model` 必填;只列你要派的那幾個。`effort` 留白
215
+ 即為 **`high`**——四家的 `reasoning.default` 目前都是 `high`。要調就查
216
+ `providers.json` 的 `reasoning.allowed`,**各家值域不同**(例如 `deepseek` 沒有
217
+ `medium`);填了不在值域內的值會被擋下並列出允許值,不會靜默降級。
218
+
219
+ **agent 欄在同一份 `_dispatch.md` 裡不得重複。** 一個 agent 一列;要用同一個 lens 跑多個型號,
220
+ 拆成多個工單目錄(見 §2〈幾支 spoke 就要幾個落點〉)。**CLI 在解析期就會擋下重複的 agent,
221
+ 乾跑一樣會被擋**——但那是最後一道防線,不是可以省掉數落點那一步的理由。
166
222
 
167
223
  ### `_shared.md`(所有 spoke 共用)
168
224
 
@@ -201,7 +257,10 @@ dowafu --version
201
257
  3. **允許清單要涵蓋「回答這些問題實際需要的檔案」。** 逐題自問:要回答這題得讀哪些檔?
202
258
  漏了的話 spoke 只能在「無法驗證」欄記下缺什麼——**那是派工端的失誤,不是它的問題**。
203
259
  路徑**相對 repo 根目錄**。留空也合法(純文字審查),但那樣就讀不到程式碼,會少掉
204
- 「文件說 X、`src/foo.ts:42` 其實是 Y」這類最有價值的發現。
260
+ 「文件說 X、`src/foo.ts:42` 其實是 Y」這類最有價值的發現。**清單屬於這一份
261
+ `<agent>.md`,不屬於整批派工**——不要把另一支的清單整份複製過來:對它有用、對這一支
262
+ 沒用的檔,在讀取順序裡是純負擔;反過來就是漏洞。**沒開過的檔不能拿來證明「答案不在
263
+ 那裡」**——如果你指不出某一題的答案在哪個檔,那一題現在就是沒有檔可依,不管表上填了什麼。
205
264
 
206
265
  4. **大的檔案排在清單最後——這能省掉一半成本。** spoke **嚴格照清單順序**讀檔,而多數
207
266
  模型**一輪只叫一個檔**,每一輪又會把先前讀過的全部內容重送一次。所以一個檔被重複
@@ -244,6 +303,10 @@ dowafu --version
244
303
  dowafu tmp/dispatch/<ticket-id> --repo-root . --dry-run
245
304
  ```
246
305
 
306
+ **一次乾跑只驗一個工單目錄。** 這一批如果拆成了多個目錄(同一個 lens 跑多個型號時就會),
307
+ **每一個都要各乾跑一次**,並把各次的估算**加總**之後再把數字呈給使用者。
308
+ 只乾跑第一個就去實跑,等於其餘目錄完全沒經過這道檢查。
309
+
247
310
  **跑之前先跟使用者說明這一步在幹嘛。** 他多半沒用過這個工具,看你在下指令會以為已經
248
311
  開始派工、開始計費了:
249
312
 
@@ -260,10 +323,44 @@ dowafu tmp/dispatch/<ticket-id> --repo-root . --dry-run
260
323
  | 允許清單估算 + 檔數 | spoke 讀得到哪些東西、多大 |
261
324
  | 最壞總消耗 | **這是上限不是預期值**(各 spoke 的 cap 加總),實際通常遠低於此 |
262
325
 
263
- 報表只給 token,不給金額——**要換算成錢就自己依型號價目算給他看**,別讓他自己猜。
326
+ **用自己的話、用表格轉述——除非你是逐位元組貼上原輸出,否則不要放進 code fence。**
327
+ code fence 的語意就是「這是工具印出來的」,把改寫過的內容裝進去,等於宣稱一個你其實
328
+ 沒做到的準確度。改寫沒有問題,而且常常比原輸出好讀;把改寫冒充成原輸出才有問題。
329
+
330
+ **要轉述就連限定語一起轉。** 報表裡那些限定語,正是讓數字不會被誤讀的部分——總量是上限
331
+ 而非預期值、價目取自哪一天、估算建立在什麼假設上、每支 lens 收尾句的確認行。它們看起來
332
+ 最像可以省的,卻正是讓這些數字能拿來做決定的東西。**限定語一刪,你交給使用者的數字就
333
+ 比工具給你的更硬。**
334
+
335
+ **工具標了 `⚠` 或 `ℹ` 的那幾行一律逐字轉述,不得改寫。** `⚠` 是工具在說「現在有事」——
336
+ 輸出目錄已經有產物、清單順序正在多花錢、某支 spoke 什麼都沒讀。把它改寫成一句比較平順的話,
337
+ 是你能對這份報表做的最貴的一件事,因為讀者會失去唯一那個「需要你做決定」的訊號。
338
+ 特別是:`⚠ 大檔排清單最後可降至 N(本項省 N%)` 的意思是**你現在沒有排好**,
339
+ 不是「已經排好了、重排只能再省一點」。
264
340
 
265
- 然後逐項核對:`repoRoot` 是這個專案、`model` 跟你寫的一致、`effort` 不是空的、估算
266
- token 量級合理、**沒有 `⚠ 輸出目錄…未被…忽略` 的警告**。
341
+ 必須跟著數字一起活下來的限定語,逐項點名:
342
+
343
+ | 出現在哪 | 什麼一定要跟著轉 |
344
+ | --- | --- |
345
+ | 每支 spoke 那一行 | `effort=`、`lang=`、`store=`,以及它的 `cap` |
346
+ | 單價子行 | 每 M token 的數字**與**`價目查證日 <日期>` |
347
+ | `ℹ` 收尾句檢查 | 每支一行,照印的轉 |
348
+ | 初始 prompt 估算 | 它**不含工單與允許清單**,以及閘門上限 |
349
+ | 允許清單估算 | 它是上限、**不去重**,以及字元/token 的換算基礎 |
350
+ | 逐個讀順序放大量 | 它是「逐個讀假設下的上限、批次讀的廠牌不適用」、排序的判定,以及**本項不含初始 prompt 與工單** |
351
+ | 最壞總消耗 | 它是**上限不是預期值**,且是各 spoke cap 的加總 |
352
+
353
+ 少了這些,數字讀起來會比工具的原意更硬。**自己把 token 換算成錢時,要講明那是你算的、依據哪一行單價。**
354
+
355
+ 報表只給 token,不給金額,且**只在乾跑階段成立**。要換算成錢,**價目來源是
356
+ `providers.json` 的 `pricing`(`inputPerM`/`cachedInputPerM`/`outputPerM`),不要查
357
+ 官網**——那份數字就是 CLI 計費用的,查官網會讓「你報的錢」與「CLI 算的錢」對不上。
358
+ `pricingSource.asOf` 看起來過舊就回報使用者,不要自行改數字(要改是改
359
+ `providers.json`)。**實跑**的 `summary.md` 有「估算成本」欄,每支 spoke 結束也會印
360
+ 一行 `cost=`,那是 CLI 自己算好的金額,**直接轉述即可,不必自己算**。
361
+
362
+ 然後逐項核對:`repoRoot` 是這個專案、`model` 跟你寫的一致、**報表印出的 `effort` 是
363
+ 你預期的那一級**、估算 token 量級合理、**沒有 `⚠ 輸出目錄…未被…忽略` 的警告**。
267
364
 
268
365
  任何一項不對就修工單重跑,**不要往下走**。
269
366
 
@@ -271,6 +368,21 @@ token 量級合理、**沒有 `⚠ 輸出目錄…未被…忽略` 的警告**
271
368
 
272
369
  ## 5. 實跑
273
370
 
371
+ **輸出目錄必須是一個還不存在的目錄。** 先看 `tmp/spoke/<ticket-id>/`:已經有東西了就
372
+ **換一個新的 ticket-id 派出去**——換 id 不花錢,碰撞這件事就不存在了。
373
+
374
+ **刪產物是使用者的決定,永遠不是你的。** 派工前不行、順手整理不行、「它擋路」也不行。
375
+ 你可以問要不要清掉,但不可以自己清,也不可以直接跑上去。那底下的東西是花錢買來的,
376
+ 而目錄本身不會告訴你使用者還要不要它。(第 7 節的清理是另一回事:那是**裁決之後**、
377
+ 他說了才做的。)
378
+
379
+ **這條有 CLI 撐著。** 目錄非空時它會在派工前中止——沒呼叫任何 API、沒花任何錢——
380
+ 並要你換一個 ticket-id。**沒有可以蓋過它的旗標**:清目錄是使用者的動作,
381
+ 不是你能帶的參數。
382
+
383
+ CLI 開跑時會清空 `run.jsonl`(舊事件會讓你數錯),**但那一步只在它真的跑起來時才執行**。
384
+ 啟動失敗的話,上一次的產物原封不動留著——而**檔案不會告訴你它是不是這一次的**。
385
+
274
386
  ```bash
275
387
  dowafu tmp/dispatch/<ticket-id> --repo-root . --yes
276
388
  ```
@@ -290,9 +402,37 @@ dowafu tmp/dispatch/<ticket-id> --repo-root . --yes
290
402
  > **前景執行本身另有外部工具的逾時上限**(十分鐘量級),與 `--timeout` 無關、擋不住。
291
403
  > **預估這次會跑久(清單大、問題多、選了較慢的型號)就改背景執行**,不要在前景硬等。
292
404
 
405
+ ### 啟動確認:先確認它真的跑起來了,再開始等
406
+
407
+ **下指令之前先記下當下時間**(`date -u +%Y-%m-%dT%H:%M:%SZ`),下完隔十幾秒讀一次
408
+ `tmp/spoke/<ticket-id>/run.jsonl`:
409
+
410
+ | 讀到 | 判定 |
411
+ | --- | --- |
412
+ | 檔案不存在 | **沒跑起來**——CLI 一開跑就會建它,這不是「還在等 API」 |
413
+ | 有 `spoke_start`,`ts` 晚於你下指令的時刻 | 起來了,開始輪詢 |
414
+ | 有內容,但 `ts` 早於你下指令的時刻 | **沒跑起來,你讀到的是上一次的產物** |
415
+
416
+ `ts` 是每一行都有的 ISO 8601 時間戳,**這是判斷「這一份是不是這一次」的唯一機械依據**。
417
+
418
+ **沒跑起來就不要繼續等。** 這個失敗形態自己看不出來:舊產物的兩個 `spoke_end` 都是
419
+ `succeeded`,格式、稽核欄、成本全部正常,唯一的破綻是時間戳。
420
+
421
+ ### 等多久算不正常
422
+
423
+ **看的不是總耗時,是 `run.jsonl` 有沒有在長。** 單支 spoke 跑上十分鐘是正常的,但
424
+ **十分鐘之內一定會有新事件寫進來**——單次 API 呼叫的逾時預設就是十分鐘,而逾時與重試
425
+ 各自都會寫一行 `round_error`。
426
+
427
+ > **超過十分鐘沒有任何新事件,就停下來告訴使用者**,不要繼續等、也不要自行重跑。
428
+ > 把最後一個事件與它的 `ts` 一起報給他,讓他判斷。
429
+
293
430
  **進度一律以 `run.jsonl` 為準**——CLI 逐事件寫入該檔,中途 Ctrl-C 也讀得出跑到哪。
294
431
  終端機那條通道不保證拿得到(見 §1.5 第四條),`run.jsonl` 不受影響。
295
432
 
433
+ **跑完的判定是兩件事同時成立**:兩個 `spoke_end`,**而且**這份 `run.jsonl` 通過了上面的
434
+ 啟動確認。只看 `spoke_end` 會把上一次的產物當成這一次的結果。
435
+
296
436
  **真的撞到失敗或逾時:要不要重跑由使用者決定,不要自行重跑。** 重跑是重新付費,中斷前已
297
437
  花的錢救不回來(沒有 resume 機制)。把中斷時的狀態(`run.jsonl` 讀到哪、失敗訊息)告訴
298
438
  使用者,讓他判斷要不要重來——這與「零讀取就整份重跑」不衝突:那條是「判定沒跑」,這裡是
@@ -300,28 +440,53 @@ dowafu tmp/dispatch/<ticket-id> --repo-root . --yes
300
440
 
301
441
  ---
302
442
 
303
- ## 6. 回收——先原文、後融合
443
+ ## 6. 回收——摘要優先,原文為例外
304
444
 
305
445
  產物在 `tmp/spoke/<ticket-id>/`:`<agent>.md`(原文)、`summary.md`(稽核表)、
306
446
  `run.jsonl`(執行記錄)、`raw/`(完整請求與回應)。
307
447
 
448
+ **`run.jsonl` 是逐事件附加寫入的,不會被覆蓋。** 產物與 `raw/` 若被蓋掉——重跑,或另一支
449
+ 寫到同一個名字——那個檔裡仍然留著每一支的 `spoke_start`(provider 與型號)、逐輪 usage、
450
+ 每一次讀檔(含被拒的)、錯誤,以及 `spoke_end` 的 token 與成本。
451
+ **報告文字救不回來,但花了多少錢、讀了哪些檔,可以還原。**
452
+
453
+ > **回收之前先確認 §5 的啟動確認過了。** 這個目錄底下的任何檔案,都不會告訴你它是不是
454
+ > 這一次的產物。
455
+
308
456
  **呈現順序不得更動**:
309
457
 
310
- 1. **逐個 spoke 的內容都要讓使用者看到**,標明 lens 與模型。規模小(單份幾十行)就直接
311
- 原文照登。**規模大到不適合整份貼出時**,可以摘要,但四件事缺一不可:
458
+ 1. **逐個 spoke 的內容都要讓使用者看到**,標明 lens 與模型。**摘要是預設**,滿足以下
459
+ 四個必要條件:
312
460
  1. **明確宣告取捨**——在摘要開頭寫明「本節為摘要,非逐字照登」,不安靜地摘掉
313
461
  2. **判讀涵蓋每一條,無一略過**——每個 spoke 的每一條觀察都要在後面的「hub 判讀」出現,
314
462
  這是取代「原文照登」的可稽核性來源:原文不在你眼前列出,不代表它沒被看過
315
463
  3. **指出原文路徑**(`tmp/spoke/<ticket-id>/<agent>.md`),使用者隨時可自行比對
316
- 4. **只在產物尚未清理時才成立**——第 7 節清掉之後就沒有底本可查,那個情境下原文照登
317
- 仍是唯一選擇,不能摘要
464
+ 4. **只在產物尚未清理時才成立**——第 7 節清掉之後就沒有底本可查
318
465
  > 這條規則要防的不是「沒有原文」,是**防止 hub 只挑對自己有利的講**。滿足以上四件事,
319
466
  > 手段換成摘要也一樣防得住;換句話說,要守住的是目的,不是「原文」這個手段本身。
320
467
 
321
- > **沒有把握做到這四件事,就不要摘要——整份照登。**
322
- 2. **`summary.md` 的稽核表併進原文區一併照登。**
468
+ **原文照登用於兩種情形**:產物已被第 7 節清掉(沒有底本可查,摘要不成立),或單份
469
+ 規模小到摘要反而多此一舉。除此之外都走摘要。
470
+
471
+ **做不到第 2 條就不要派這麼多 spoke**——問題出在派工規模,不在呈現方式;不要拿
472
+ 「沒把握」當理由退回整份照登。
473
+ 2. **`summary.md` 的稽核欄轉述進上面那一節——它印出來的每一段都要點名。** 每支 spoke
474
+ 一段一行,**順序照 `summary.md` 印的**:`工具呼叫` 在最前面,排在 `收尾句` 之前。
475
+ 某一段是空的時候,CLI 會自己印它的空值字(中文那套印 `無`),**照它印的抄,
476
+ 而且一段都不准漏掉**。全部寫出來不比只寫大部分貴,而少一個段名使用者看得出來,
477
+ 少一列他看不出來。
478
+
479
+ **`⚠` 開頭的那幾段不准省,`(無法稽核)` 也不准省。** 它們不是那幾個欄位以外的裝飾,
480
+ 是稽核在告訴你出事了;省掉它們,使用者手上就只剩下說「沒事」的那幾段。
481
+
482
+ **有內容的段落一律逐字照抄。** 只有 `pass` 與 CLI 自己的空值字可以壓縮。說「沒事」的
483
+ 段落留著最省事,說「有事」的那段才是會被刪掉的——所以這條規則是刻意不對稱的:
484
+ **一段講的東西越多,你對它的處置自由越少。**
323
485
  3. **之後另立「hub 判讀」一節**——去重,逐條標註你的初步判讀(成立/不成立+為什麼/
324
- 需使用者裁決)。
486
+ 需使用者裁決)。**每一項都要標明它來自哪幾條觀察**,寫成 spoke + 條號
487
+ (`safety 2、3;feasibility 9`),而且**每一條觀察都要出現在至少一項裡**。
488
+ 兩支 spoke 的編號觀察,要嘛全部出現在那一欄,要嘛沒出現的一眼就看得出來——**這正是重點**:
489
+ 沒有編號的話,「安靜地沒被列進來」與「你判讀過但認為不成立」長得一模一樣。
325
490
 
326
491
  ### 跑了多次時怎麼合併
327
492
 
@@ -342,17 +507,24 @@ dowafu tmp/dispatch/<ticket-id> --repo-root . --yes
342
507
  不同:幻覺要重跑或換模型,位置錯只需自己重新定位。**把位置錯判成幻覺,會丟掉整份能用的
343
508
  產出。**
344
509
 
510
+ **這條對你自己寫的行號同樣成立。** 驗完 spoke 的引用、隔幾段自己憑印象寫一次行號,
511
+ 等於把漂移換上你的名字放回去——**而你的份量更重**,因為你說了你開過檔。
512
+
345
513
  **三、驗 spoke 引用時,註解不算證據。** 「spoke 說某段有註解背書某個結論」不夠,還要驗
346
514
  **註解說的還成不成立**——註解會與程式碼漂移,而漂移的註解讀起來跟正確的一模一樣,
347
515
  只驗「註解存在且內容吻合」會把錯的判成對的。AGENTS.md 必答檢查第 1 條「不能只憑檔名、
348
516
  註解或行號」原本管的是寫規劃書時,這裡把它擴到驗收 spoke 引用時。
349
517
 
518
+ **spoke 說「我讀不到 X 所以無法確認」是正確回報,不是假警報。** 它指出的是**你沒放進清單**的檔,
519
+ 那是你的缺口、不是它的錯——把它歸到「假警報」,或自己默默查掉就往下走,
520
+ 會把「清單裁錯了」這個唯一的訊號蓋掉。**你可以自己補查,但仍要明說清單當時是短的。**
521
+
350
522
  ### 稽核表怎麼看
351
523
 
352
524
  | 欄位 | 意思 |
353
525
  | --- | --- |
526
+ | `工具呼叫:N(允許 N/拒絕 N)` | 讀了幾次、幾次被拒——`run.jsonl` `toolCalls[]` 的統計。**印在最前面**,排在收尾句之前 |
354
527
  | `收尾句` | pass/fail |
355
- | `工具呼叫:N(允許 N/拒絕 N)` | 讀了幾次、幾次被拒——`run.jsonl` `toolCalls[]` 的統計 |
356
528
  | `觀察:N` | 條數;`無法計數` 表示格式無法辨識,翻原文確認 |
357
529
  | `清單外引用` | 引用了允許清單外的路徑,**可能是臆測**——對照 `toolCalls[]` 判斷。**此欄至今沒抓到過真正的幻覺**:抓到的多半是 spoke 照抄素材的縮寫路徑、轉述素材裡提過但自己標明讀不到的路徑,或絕對/相對路徑混用。先查該路徑是不是出現在你自己的 `_shared.md` 裡,或只是寫法不同,不要預設是編的 |
358
530
  | `無法驗證欄` | 有沒有照模板寫這一節。**判讀產出品質時看這欄有多具體**(例如逐項標明哪個結論依賴哪個不可讀的檔案),不要看觀察條數——條數不可靠,重疊、灌水都會推高條數但不代表品質 |
@@ -398,6 +570,9 @@ dowafu tmp/dispatch/<ticket-id> --repo-root . --yes
398
570
  rm -rf tmp/spoke/<ticket-id> tmp/dispatch/<ticket-id>
399
571
  ```
400
572
 
573
+ **這是整套流程裡唯一會刪東西的地方,而且要等使用者裁決完、他說了才刪。** 其他任何時候
574
+ ——包括派工前那個擋路的目錄——一律照第 5 節:可以問,不可以自己刪。
575
+
401
576
  lens 定義留著,下次還會用。
402
577
 
403
578
  ---
@@ -411,7 +586,7 @@ lens 定義留著,下次還會用。
411
586
  | 真失敗或逾時 | **要不要重跑問使用者,不要自行重跑**——重跑是重新付費,中斷前的花費救不回來 |
412
587
  | 撞 429 | 加 `--concurrency 1` 重跑 |
413
588
  | 報告品質差 | 換一個型號重跑,或同一組設定再跑一次取聯集(見第 3 節「跑 2–3 次怎麼做」) |
414
- | 缺 API key | 使用者要在 `~/.config/dispatch/.env` 設定,**你不要去碰那個檔** |
589
+ | 缺 API key | 使用者要在 `~/.config/dowafu/.env` 設定,**你不要去碰那個檔** |
415
590
  | 找不到 `dowafu` | **查不到不代表沒安裝**——見 §1.5,多半是沙箱不讀家目錄 |
416
591
  | `Operation not permitted` | host 的沙箱擋的,**不是工單或指令的問題**。照提示放行再跑一次 |
417
592
 
@@ -3,7 +3,7 @@ name: preflight
3
3
  description: 開工前檢查這個專案的環境有沒有把工作流程靜默停用:流程規範那一章的內容讀不讀得到、skill 與 lens 齊不齊、tmp/ 有沒有被 gitignore、dowafu 跑不跑得起來。只讀、只報告,不改任何設定。
4
4
  metadata:
5
5
  derived-from: ".claude/skills/preflight/SKILL.md"
6
- derived-from-sha256: "2fd8ab69bb7b4c898c9c63bc94a53e7eb041643c343dbae3d175cdccb384bd64"
6
+ derived-from-sha256: "ad8b06b543347a387f44eb0fdaffad343b332040477d1b0ac047600a49b26ee6"
7
7
  ---
8
8
 
9
9
  # preflight — 環境前置檢查
@@ -31,11 +31,12 @@ cd <repo 根的絕對路徑>
31
31
  echo "=== 流程規範 ==="
32
32
  ls CLAUDE.md AGENTS.md workflow_spec.md 2>&1
33
33
  # 這條只幫你定位「內容寫在哪個檔」。它有命中 ≠ 你讀得到——判準見下。
34
- grep -n "規劃→實作→驗收流程規範" CLAUDE.md AGENTS.md workflow_spec.md 2>/dev/null
34
+ # 兩種寫法都要找:專案本來那一章的語言,未必和你裝的語言套件相同。
35
+ grep -nE "規劃→實作→驗收|Plan → Implement → Accept" CLAUDE.md AGENTS.md workflow_spec.md 2>/dev/null
35
36
 
36
37
  echo "=== skill 與 lens ==="
37
38
  ls .claude/skills/ 2>/dev/null
38
- ls .claude/agents/hole-finder-*.md 2>/dev/null
39
+ ls .claude/agents/hole-finder*.md 2>/dev/null
39
40
 
40
41
  echo "=== tmp/ ==="
41
42
  git check-ignore -q tmp && echo "已忽略" || echo "未忽略"
@@ -45,6 +46,10 @@ git check-ignore -q tmp && echo "已忽略" || echo "未忽略"
45
46
 
46
47
  **判準只有一條:「規劃→實作→驗收流程規範(主從形態)」這一章的內容,你現在讀得到嗎?**
47
48
 
49
+ **那一章可能是以另一種語言的標題存在的。** 語言套件各帶各的副本,所以本來就有這一章的專案
50
+ 最後會變成兩份——兩種語言,而只有一份會自動載入。上面那道 grep 兩種寫法都找就是為了這個;
51
+ **命中超過一個檔時,先讀下一段再下判斷。**
52
+
48
53
  讀得到就算通過。內容是直接貼在入口檔裡、還是用 `@` 之類的方式引入的,**那是使用者的
49
54
  選擇,不在檢查範圍內**。
50
55
 
@@ -52,6 +57,11 @@ git check-ignore -q tmp && echo "已忽略" || echo "未忽略"
52
57
  讀完在回報裡註明「規範不在自動載入範圍內,本次是手動讀取的」——讓使用者知道換個
53
58
  session 又會漏掉。
54
59
 
60
+ **找到不只一份,就要講明哪一份會被自動載入。** 專案可能本來就把這一章內嵌在入口檔裡,
61
+ 而根目錄又多一份 `workflow_spec.md`——兩份的語言還可能不同(語言套件各裝各的)。
62
+ 這時只回報「內容讀得到」不夠:要指出**你 context 裡的是哪一份**,以及兩份之間沒有任何
63
+ 同步機制。自動載入的那一份,才是之後每個 session 真正生效的那一份。
64
+
55
65
  > **為什麼會讀不到?** 入口檔因 host 而異:Claude Code 讀 `CLAUDE.md`(**不讀
56
66
  > `AGENTS.md`**),其他 host 多半讀 repo 根的 `AGENTS.md`。而 `@xxx.md` 是 Claude Code
57
67
  > 的 import 語法,**別的 host 不會展開它**——那時你看到的只是一行字,規範內容從來沒進
@@ -59,8 +69,20 @@ session 又會漏掉。
59
69
 
60
70
  ### skill 與 lens
61
71
 
62
- `.claude/skills/find-holes-external/` 與 `.claude/agents/hole-finder-*.md` 在不在。
63
- (後者 glob 會列出通用的 `hole-finder.md` 加三個 lens,四個都算正常。)
72
+ `.claude/skills/find-holes-external/` 與 lens 定義在不在。**回報你實際看到的檔名,
73
+ 缺哪個就點名**——「看到三個」不算檢查,是哪三個才算。
74
+
75
+ 應該有四個,四個都算正常:
76
+
77
+ | 檔 | 是什麼 |
78
+ | --- | --- |
79
+ | `hole-finder.md` | 通用 lens |
80
+ | `hole-finder-cost.md` | 成本 |
81
+ | `hole-finder-feasibility.md` | 可行性 |
82
+ | `hole-finder-safety.md` | 安全、併發、失敗態 |
83
+
84
+ > 上面那道 glob 的 `*` 前面沒有連字號是刻意的:`hole-finder-*.md` 配不到
85
+ > `hole-finder.md`,拿它去數卻期待四個,怎麼數都對不起來。
64
86
 
65
87
  **lens 缺了不要自己補寫**——它是 spoke 的 system prompt 來源,自己寫的版本會讓產出跟
66
88
  稽核判準對不上。
@@ -72,7 +94,7 @@ session 又會漏掉。
72
94
 
73
95
  ---
74
96
 
75
- ## 2. 這個工具鏈另外要查的三件事
97
+ ## 2. 這個工具鏈另外要查的四件事
76
98
 
77
99
  按這個順序——前一項不成立,後面查了也沒有意義。
78
100
 
@@ -89,11 +111,15 @@ dowafu --version
89
111
 
90
112
  | 症狀 | 意思 | 怎麼回報 |
91
113
  | --- | --- | --- |
92
- | `Operation not permitted` | **沙箱擋的**,不是沒安裝。CLI 多半裝在家目錄底下,而沙箱預設不讀家目錄 | 照 host 的提示放行後重試。順帶告訴使用者:API key(`~/.config/dispatch/.env`)與對外網路同樣被擋,派工時一併要放行 |
114
+ | `Operation not permitted` | **沙箱擋的**,不是沒安裝。CLI 多半裝在家目錄底下,而沙箱預設不讀家目錄 | 照 host 的提示放行後重試。順帶告訴使用者:API key(`~/.config/dowafu/.env`)與對外網路同樣被擋,派工時一併要放行 |
93
115
  | `command not found` | 可能沒裝,也可能裝在 PATH 之外 | 問使用者 CLI 裝在哪(請他跑 `which dowafu`),**不要自己搜檔案系統** |
94
116
 
95
117
  **`command -v dowafu` 查不到不代表沒安裝**,別拿那個當判準。
96
118
 
119
+ **既沒回來也沒報錯,是第三種情況:它在等。** 不帶 `--yes` 時 CLI 會印出確認提示並卡在
120
+ stdin 上,而這在不同 host 會表現成逾時、表現成沒有輸出、或表現成「要不要幫你送出輸入」。
121
+ **記下你的環境是哪一種**——派工當下會再遇到一次,而那個時機點知道就晚了。
122
+
97
123
  ### 二、lens 定義與 skill 在不在
98
124
 
99
125
  見第 1 節的「skill 與 lens」。有一點對你特別重要:
@@ -109,6 +135,22 @@ dowafu --version
109
135
  語法,你不會展開它——入口檔裡若只有那一行,你看到的就是一行字,而**你很可能以為自己
110
136
  已經讀過規範了**。實際檢查你的 context 裡有沒有那章的內容,別憑印象。
111
137
 
138
+ ### 四、CLI 的設定到位了嗎——`dowafu --doctor`
139
+
140
+ ```bash
141
+ dowafu --doctor
142
+ ```
143
+
144
+ 它會印出設定目錄解析到哪、`.env` 在不在、**哪幾家有 key**(只看有沒有,不印值)、
145
+ 內建的型號白名單、以及找到哪幾支 lens 定義。不呼叫 API、不花錢,**而且不需要工單**——
146
+ 這正是它在「什麼都還沒有」的時候能用的原因。
147
+
148
+ 缺的項目照它印的回報。**不要主動幫使用者寫 key,也不要請他把 key 貼進這段對話**——
149
+ 貼進來的東西會留在這段對話的歷史裡。幫他建目錄、放一份空範本可以,值要由他自己填進檔案。
150
+
151
+ `dowafu --doctor` 只印得出錯誤時,那與第一項是同一個發現:CLI 從這裡跑不起來,
152
+ 下面幾項都還輪不到。
153
+
112
154
  ---
113
155
 
114
156
  ## 3. 輸出
@@ -60,6 +60,11 @@ description: 【Claude Code 內派專用;VS Code 環境改用 find-holes-exter
60
60
  **這是最常犯的錯**——「問了某題,卻沒給回答那題所需要的檔」。逐題列出來,
61
61
  使用者一眼就能看出漏了什麼。**這一欄不是形式,是目前唯一擋得住漏檔的機制。**
62
62
 
63
+ **把路徑寫進那一欄之前,先確認答案真的在那個檔裡**——grep 那個符號,或直接開檔。
64
+ 憑印象填的話這一欄什麼都擋不住:一個看起來很合理的檔名,與正確的那個一樣通得過形式檢查,
65
+ 而差額要等一整輪派工之後才會浮出來。**兩支最後拿到同一批檔也可以,但要說得出為什麼**
66
+ ——清單相同是可以解釋的結論,不是起點。
67
+
63
68
  **使用者對數量/模型/lens 的修改一律照辦。**
64
69
 
65
70
  ### 3.1 確認後,每個 prompt 必含
@@ -70,7 +75,10 @@ description: 【Claude Code 內派專用;VS Code 環境改用 find-holes-exter
70
75
  裁這份清單時**逐題自問:「這一題的答案在哪個檔?那個檔在清單裡嗎?」**
71
76
  按 lens 的名稱配檔案(safety 就給安全相關的)會配錯——**lens 是看的角度,
72
77
  清單是看的材料**。清單對不準問題的答案位置,spoke 物理上不可能答對。
73
- **漏了是派工端的失誤,不是 spoke 的問題。**
78
+ **漏了是派工端的失誤,不是 spoke 的問題。每支 spoke 的清單各自對著自己的問題裁**
79
+ ——不要因為湊一份比較省事就給兩支同一份;掉的會是只有其中一支需要的那個檔。
80
+ **沒開過的檔不能拿來證明「答案不在那裡」**——指不出某一題的答案在哪個檔,
81
+ 那一題現在就是沒有檔可依,不管表上填了什麼。
74
82
 
75
83
  - **清單內把大檔排在最後**(依檔案大小遞增)。spoke 照清單順序讀檔,每輪會重送先前
76
84
  讀過的全部內容,所以排越前面被重複計費越多次,差距可以到將近一倍。
@@ -79,10 +87,26 @@ description: 【Claude Code 內派專用;VS Code 環境改用 find-holes-exter
79
87
  - **產出格式**:「觀察+依據(檔案:行號 或 推理)」的清單。**不得**給結論裁決、
80
88
  嚴重度分級、「應該改成」的替代設計、採用建議;不確定的寫成問題,不寫成缺陷。
81
89
 
82
- ### 4. 回收:先原文、後融合
90
+ ### 4. 回收——摘要優先,原文為例外
91
+
92
+ 每個 spoke 的回報都要讓使用者看到,標明 lens 與模型。**摘要是預設**,滿足以下四個
93
+ 必要條件:
94
+
95
+ 1. **明確宣告取捨**——在摘要開頭寫明「本節為摘要,非逐字照登」,不安靜地摘掉
96
+ 2. **判讀涵蓋每一條,無一略過**——每個 spoke 的每一條觀察都要在後面的「hub 判讀」出現,
97
+ 這是取代「原文照登」的可稽核性來源:原文不在你眼前列出,不代表它沒被看過
98
+ 3. **原文仍在這輪對話裡**——sub-agent 回傳的完整內容留在上下文中,使用者要求時可以
99
+ 重新貼出核對
100
+ 4. **只在原文還找得到時才成立**——原文若已不在上下文範圍內,就沒有底本可查
83
101
 
84
- 每個 spoke 的回報**逐個原文照登**,標明 lens 與模型,**不得刪節、改寫或只給總結**——
85
- 使用者必須看得到每個 spoke 各自說了什麼。
102
+ > 這條規則要防的不是「沒有原文」,是**防止 hub 只挑對自己有利的講**。滿足以上四件事,
103
+ > 手段換成摘要也一樣防得住;換句話說,要守住的是目的,不是「原文」這個手段本身。
104
+
105
+ **原文照登用於兩種情形**:原文已不在上下文裡(沒有底本可查,摘要不成立),或單份規模
106
+ 小到摘要反而多此一舉。除此之外都走摘要。
107
+
108
+ **做不到第 2 條就不要派這麼多 spoke**——問題出在派工規模,不在呈現方式;不要拿
109
+ 「沒把握」當理由退回整份照登。
86
110
 
87
111
  之後**另立「hub 判讀」一節**:去重,逐條標註你的初步判讀(成立/不成立+為什麼/
88
112
  需使用者裁決)。
@@ -105,6 +129,10 @@ description: 【Claude Code 內派專用;VS Code 環境改用 find-holes-exter
105
129
  > **位置錯不等於幻覺。** 幻覺是「該檔根本沒有這段」,處置是重跑或換模型;
106
130
  > 位置錯只需自己重新定位。把前者誤判成後者會丟掉整份能用的產出。
107
131
 
132
+ **spoke 說「我讀不到 X 所以無法確認」是正確回報,不是假警報。** 它指出的是**你沒放進清單**
133
+ 的檔,那是你的缺口、不是它的錯——把它歸到「假警報」,或自己默默查掉就往下走,
134
+ 會把「清單裁錯了」這個唯一的訊號蓋掉。**你可以自己補查,但仍要明說清單當時是短的。**
135
+
108
136
  ## 紅線
109
137
 
110
138
  - spoke 意見**不得觸碰「做不做」**。若它寫出終止/阻擋類的結論,丟棄該結論、