dowafu 0.3.2 → 0.4.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 (32) hide show
  1. package/README.md +9 -5
  2. package/README_zh-tw.md +9 -6
  3. package/dist/audit.js +8 -2
  4. package/package.json +1 -2
  5. package/publish/en/.agents/skills/find-holes-external/SKILL.md +0 -450
  6. package/publish/en/.agents/skills/preflight/SKILL.md +0 -137
  7. package/publish/en/.agents/skills/wrap/SKILL.md +0 -64
  8. package/publish/en/.claude/agents/explore-haiku.md +0 -8
  9. package/publish/en/.claude/agents/hole-finder-cost.md +0 -15
  10. package/publish/en/.claude/agents/hole-finder-feasibility.md +0 -15
  11. package/publish/en/.claude/agents/hole-finder-safety.md +0 -15
  12. package/publish/en/.claude/agents/hole-finder.md +0 -14
  13. package/publish/en/.claude/skills/find-holes/SKILL.md +0 -114
  14. package/publish/en/.claude/skills/find-holes-external/SKILL.md +0 -463
  15. package/publish/en/.claude/skills/preflight/SKILL.md +0 -198
  16. package/publish/en/.claude/skills/wrap/SKILL.md +0 -61
  17. package/publish/en/README.md +0 -106
  18. package/publish/en/workflow_spec.md +0 -71
  19. package/publish/zh-tw/.agents/skills/find-holes-external/SKILL.md +0 -593
  20. package/publish/zh-tw/.agents/skills/preflight/SKILL.md +0 -168
  21. package/publish/zh-tw/.agents/skills/wrap/SKILL.md +0 -65
  22. package/publish/zh-tw/.claude/agents/explore-haiku.md +0 -8
  23. package/publish/zh-tw/.claude/agents/hole-finder-cost.md +0 -15
  24. package/publish/zh-tw/.claude/agents/hole-finder-feasibility.md +0 -15
  25. package/publish/zh-tw/.claude/agents/hole-finder-safety.md +0 -15
  26. package/publish/zh-tw/.claude/agents/hole-finder.md +0 -14
  27. package/publish/zh-tw/.claude/skills/find-holes/SKILL.md +0 -140
  28. package/publish/zh-tw/.claude/skills/find-holes-external/SKILL.md +0 -609
  29. package/publish/zh-tw/.claude/skills/preflight/SKILL.md +0 -241
  30. package/publish/zh-tw/.claude/skills/wrap/SKILL.md +0 -62
  31. package/publish/zh-tw/README.md +0 -91
  32. package/publish/zh-tw/workflow_spec.md +0 -65
@@ -1,609 +0,0 @@
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
- **`--lang` 決定 CLI 輸出與 spoke prompt 的語言,預設是 `en`。** 你裝的是中文套件、lens 檔也是
64
- 中文的,所以**中文專案要帶 `--lang zh-tw`**——不帶的話 CLI 會用英文輸出,spoke 也會拿到英文
65
- prompt,而**兩邊都不會報錯,只是安靜地混語**。不想每次打,可以設環境變數 `DISPATCH_LANG=zh-tw`;
66
- 優先序是 `--lang` > `DISPATCH_LANG` > 內建預設 `en`。
67
-
68
- 你的終端機不保證落在哪一個 workspace folder,而 `--repo-root` 預設取 cwd。cwd 錯了會
69
- **安靜地錯**——工單照樣解析、spoke 照樣派出去,只是白名單邊界與 lens 定義都指到別處。
70
-
71
- **二、`dowafu` 是外部全域 CLI,不在 workspace 裡。不要去找它,直接下:**
72
-
73
- ```bash
74
- dowafu --version
75
- ```
76
-
77
- 印不出版本號只有兩種情況,**兩種都不要自己搜檔案系統**:
78
-
79
- - **`command not found`**——問使用者 CLI 裝在哪(請他跑 `which dowafu`),拿到絕對路徑
80
- 之後改用絕對路徑呼叫
81
- - **`Operation not permitted`**——**沙箱擋的,不是沒安裝**。它多半裝在家目錄底下,而沙箱
82
- 預設不讀家目錄。照 host 的提示放行再跑一次;API key 與對外網路同樣被擋,所以放行是
83
- 必要的,不是可選的
84
-
85
- **三、`--yes` 一定要帶。**
86
-
87
- 不帶的話 CLI 會印出 `繼續?[y/N]` 停住等輸入,而你的 host 會把控制權交回給你,並提供一個
88
- **send input** 選項——**那條路能把 `y` 送出去,直接開始計費**。
89
-
90
- > **不准選它。** 加 `--yes` 等於代替使用者按下確認,加之前必須在對話裡取得他的明確同意。
91
-
92
- **四、實跑用背景執行,進度靠檔案而不是終端機輸出。**
93
-
94
- 用檔案讀取工具輪詢 `tmp/spoke/<ticket-id>/run.jsonl`:CLI 逐事件寫入該檔,讀得出跑到
95
- 哪一輪、有沒有 `round_error`。**完成判定見 §5**——「兩個 `spoke_end`」只是其中一半。
96
- 背景終端機的輸出不保證拿得到,而單支 spoke 可能跑上好幾分鐘。
97
-
98
- > **把終端機當啟動器,不要當資料通道。**
99
-
100
- > 本節用功能稱呼工具(「終端機工具」「檔案讀取工具」),因為工具名依版本與模型而異——
101
- > 對不上是常態,照你手上實際有的那個用。
102
-
103
- ---
104
-
105
- ## 2. 提派工計畫,等使用者確認(**確認前不准派**)
106
-
107
- | 要列的 | 說明 |
108
- | --- | --- |
109
- | 待審段落 | 哪個檔案的哪一節、幾行 |
110
- | 派幾個 spoke、哪些 lens | 見下 |
111
- | 每個 spoke 的 provider/model | 見下 |
112
- | **每題的「問題 → 答案在哪個檔 → 在清單裡嗎」對照** | **必列,見下方格式** |
113
- | 預估成本量級 | 參考值:三個 spoke、中等工單約 40k token |
114
- | **每支 spoke 的產物落點** | **必列**,見下方〈幾支 spoke 就要幾個落點〉 |
115
-
116
- ### 具體問題與允許清單**必須逐題對照著列**
117
-
118
- 不要把「問題」和「允許清單」分成兩塊各列一遍——那樣看不出哪一題沒有對應的檔案。
119
- 用這個格式:
120
-
121
- | Q | 問題 | 答案在哪個檔 | 在清單裡嗎 |
122
- | --- | --- | --- | --- |
123
- | 1 | §3.1 的 schema 變更可行嗎 | `prisma/schema.prisma` | ✅ |
124
- | 2 | §2 現況描述與實際程式碼有無出入 | `lib/a.ts`、`lib/b.ts` | ❌ **要補** |
125
-
126
- **這是最常犯的錯**:問了某題,卻沒給回答那題所需要的檔——問「這是不是唯一入口」卻沒給
127
- 該檔本身,問「現況描述有無出入」卻只給了「新宣稱所在的檔」。spoke 只能在「無法驗證」欄
128
- 記下缺什麼,**那是派工端的失誤,不是它的問題**。
129
-
130
- 逐題列出來,使用者一眼就能看出漏了什麼。**這一欄不是形式,是這個 skill 目前唯一擋得住
131
- 漏檔的機制**——`--dry-run` 檢查得了格式,檢查不了「問題與清單對不對得上」。
132
-
133
- **使用者對數量/模型/lens/問題的修改一律照辦。**
134
-
135
- **裁允許清單時,逐題自問:「這一題的答案在哪個檔?那個檔在清單裡嗎?」** 按 lens 的
136
- 名稱配檔案(safety 就給安全相關的)會配錯——lens 是**看的角度**,清單是**看的材料**。
137
- 清單全給了某問題的**產生端**、而問題問的是**顯示端**時,spoke 物理上不可能答對;
138
- 換一批對準答案位置的檔案,同一個 lens 就抓得到。**這個自問是為了在派工前擋下落差,
139
- 不是派工後才發現。**
140
-
141
- ### 每支 spoke 各一張表——而且**那張表本身就是交件物,不是「我有做」的宣告**
142
-
143
- **那張表每支 spoke 各填一張,而且兩張都要攤在使用者面前。** 說「清單都對過了」「該有的檔都在」
144
- **不能取代把表給他看**——「允許清單已涵蓋所有問題」只是一句話,真假都一樣好寫。
145
-
146
- 每一列要寫**你實際預期答案所在的那個檔的路徑**——不是目錄、不是「tags 那幾支路由」、
147
- 不是 lens 的名字。寫成與允許清單同樣的形式,兩邊才對得起來:
148
-
149
- | Q | 問題 | 答案在哪個檔 | 在清單裡嗎 |
150
- | --- | --- | --- | --- |
151
- | 1 | `requireAuth` 回傳的 userId 夠不夠做刪除自己的檢查 | `lib/auth-guard.ts` | ✅ |
152
- | 2 | 最後一位管理員的防護是不是原子的 | `prisma/schema.prisma`(其餘看待審段落本身) | ✅ |
153
-
154
- **把路徑寫進那一欄之前,先確認答案真的在那個檔裡**——grep 那個符號,或直接開檔。
155
- 這一欄是為了在派工前擋下漏檔;**憑印象填的話它什麼都擋不住**:一個看起來很合理的檔名,
156
- 與正確的那個一樣能通過格式檢查,而差額是 spoke 用錢付的。
157
-
158
- **兩支最後拿到同一批檔也可以,但要說得出為什麼。** 清單相同是**可以解釋的結論**,不是起點;
159
- 而「裁到兩份不一樣為止」是同一個錯誤的反面。
160
-
161
- **一份清單餵兩支 lens,會往交集收斂,而不是聯集。** 最先掉的是「只有其中一支需要」的檔,
162
- 而那正是那支 lens 被派去看的東西;spoke 這時只能把缺口寫進「無法驗證」欄,而用這種方式
163
- 發現要付一整輪派工的錢。為省錢裁清單是合理的——但要**各自對著自己的問題裁,不是對著另一支裁**。
164
-
165
- ### 幾支 spoke 就要幾個落點——先挖好坑再派
166
-
167
- 一支 spoke 的產物落在 `tmp/spoke/<ticket-id>/` 底下**以它的 agent 為名的那一組檔**:
168
- `<agent>.md`,以及 `raw/<agent>.request.json`/`.response.json`/`.errors.json`。
169
- 那一組同進同出,所以**落點就是 `<ticket-id>` + `<agent>` 這個組合**。
170
-
171
- **派工計畫要逐支列出落點路徑,判準只有一句:落點數要等於 spoke 數,而且兩兩不同。**
172
-
173
- | spoke | lens | provider/model | 產物落點 |
174
- | --- | --- | --- | --- |
175
- | 1 | safety | openai/gpt-5.6-luna | `tmp/spoke/auth-review-luna/hole-finder-safety.md` |
176
- | 2 | safety | deepseek/deepseek-v4-flash | `tmp/spoke/auth-review-ds/hole-finder-safety.md` |
177
- | 3 | feasibility | gemini/gemini-3.6-flash | `tmp/spoke/auth-review-luna/hole-finder-feasibility.md` |
178
-
179
- **兩支算出同一個路徑,就是坑不夠**——而且解法不是改個檔名,是**拆工單目錄**:同一個 lens 要跑
180
- 多個型號時,一個型號一個目錄(ticket-id 加後綴),各派一次。
181
- 不同 lens 共用一個目錄沒有問題,它們的 agent 名本來就不同。
182
-
183
- 這一欄要用數的,不要用看的。**你不需要知道撞名會發生什麼**——數不對就先停下來拆目錄。
184
-
185
- ### lens
186
-
187
- | agent | 視角 |
188
- | --- | --- |
189
- | `hole-finder-safety` | 安全、併發競態、失敗態 |
190
- | `hole-finder-cost` | 成本閘門、計費呼叫順序、資源消耗上限 |
191
- | `hole-finder-feasibility` | 可行性、可實作性、規格與實作的落差 |
192
-
193
- 派一個也可以,三個都派也可以。**不要因為「這個 lens 好像不適用」就先排除**——cost lens
194
- 對一個沒有任何計費呼叫的專案,照樣找得出「無登入端點的資源消耗無上限」這類問題。
195
-
196
- ### 型號(只有這些,填錯會被擋下)
197
-
198
- | provider | model |
199
- | --- | --- |
200
- | `openai` | `gpt-5.6-luna`/`gpt-5.6-terra`/`gpt-5.6-sol` |
201
- | `deepseek` | `deepseek-v4-flash`/`deepseek-v4-pro` |
202
- | `gemini` | `gemini-3.1-flash-lite`/`gemini-3.5-flash-lite`/`gemini-3.6-flash` |
203
- | `anthropic` | `claude-opus-5`/`claude-sonnet-5` |
204
-
205
- ### 使用者沒指定型號時
206
-
207
- **提一組,說明你依據什麼提**(成本量級、素材規模、這個 lens 需不需要深推理),然後
208
- **等他確認**。不要自己決定就派;他改過之後也不要因為「另一個好像比較好」就換回來。
209
-
210
- 型號怎麼取捨是使用者的專案決定,這份文件不替他決定。
211
-
212
- ---
213
-
214
- ## 3. 寫工單
215
-
216
- 寫到 `tmp/dispatch/<ticket-id>/`,`<ticket-id>` 用主題 slug(例如 `auth-review`)。
217
-
218
- ### `_dispatch.md`
219
-
220
- ```markdown
221
- <!-- format: v1 -->
222
- # dispatch <ticket-id>
223
-
224
- | agent | provider | model | effort |
225
- | --- | --- | --- | --- |
226
- | hole-finder-safety | openai | gpt-5.6-luna | |
227
- | hole-finder-cost | deepseek | deepseek-v4-flash | |
228
- | hole-finder-feasibility | gemini | gemini-3.1-flash-lite | |
229
- ```
230
-
231
- 首行 `<!-- format: v1 -->` **必須有**;`model` 必填;只列你要派的那幾個。`effort` 留白
232
- 即為 **`high`**——四家的 `reasoning.default` 目前都是 `high`。要調就查
233
- `providers.json` 的 `reasoning.allowed`,**各家值域不同**(例如 `deepseek` 沒有
234
- `medium`);填了不在值域內的值會被擋下並列出允許值,不會靜默降級。
235
-
236
- **agent 欄在同一份 `_dispatch.md` 裡不得重複。** 一個 agent 一列;要用同一個 lens 跑多個型號,
237
- 拆成多個工單目錄(見 §2〈幾支 spoke 就要幾個落點〉)。**CLI 在解析期就會擋下重複的 agent,
238
- 乾跑一樣會被擋**——但那是最後一道防線,不是可以省掉數落點那一步的理由。
239
-
240
- ### `_shared.md`(所有 spoke 共用)
241
-
242
- ```markdown
243
- # 前提(不受審)
244
- - <一行結論,例如「採 JWT 無狀態驗證,已定案」>
245
- - <沒有就寫「無」>
246
-
247
- # 待審段落
248
- <把規劃書段落逐字貼進來,不要摘要、不要改寫>
249
- ```
250
-
251
- **待審段落一定要逐字內嵌**,不能寫「見 `_docs/xxx.md` 第 3 節」——spoke 讀不到 `_docs/`
252
- (那是禁區,白名單會拒絕)。
253
-
254
- ### `<agent>.md`(每個 spoke 一份,檔名要跟 `_dispatch.md` 的 agent 欄一致)
255
-
256
- ```markdown
257
- # 具體問題
258
- 1. <問題>
259
- 2. <問題>
260
-
261
- # 允許讀取
262
- - src/foo.ts
263
- - lib/bar.ts
264
- ```
265
-
266
- **四條規則**:
267
-
268
- 1. **不要寫角色定義**(「你是一個…」「不得…」「請以…收尾」)。角色、禁令、輸出格式、
269
- 收尾句由 `dowafu` 從 `.claude/agents/<agent>.md` 讀出來組進 system prompt。
270
- 寫進工單會造成規則雙來源——同一組規則出現兩次、措辭還不一致。
271
-
272
- 2. **問題要開放,不要指向你已經發現的東西。** 把答案寫進問題,spoke 找到就只是複述工單。
273
-
274
- 3. **允許清單要涵蓋「回答這些問題實際需要的檔案」。** 逐題自問:要回答這題得讀哪些檔?
275
- 漏了的話 spoke 只能在「無法驗證」欄記下缺什麼——**那是派工端的失誤,不是它的問題**。
276
- 路徑**相對 repo 根目錄**。留空也合法(純文字審查),但那樣就讀不到程式碼,會少掉
277
- 「文件說 X、`src/foo.ts:42` 其實是 Y」這類最有價值的發現。**清單屬於這一份
278
- `<agent>.md`,不屬於整批派工**——不要把另一支的清單整份複製過來:對它有用、對這一支
279
- 沒用的檔,在讀取順序裡是純負擔;反過來就是漏洞。**沒開過的檔不能拿來證明「答案不在
280
- 那裡」**——如果你指不出某一題的答案在哪個檔,那一題現在就是沒有檔可依,不管表上填了什麼。
281
-
282
- 4. **大的檔案排在清單最後——這能省掉一半成本。** spoke **嚴格照清單順序**讀檔,而多數
283
- 模型**一輪只叫一個檔**,每一輪又會把先前讀過的全部內容重送一次。所以一個檔被重複
284
- 計費的次數 = **總輪數 − 它被讀的輪次**——**排越前面,被重送越多次**。
285
- 一個十幾 k token 的大檔,排第一 vs 排最後,該 spoke 的總量可以差到將近一倍。
286
- 做法很簡單:**依檔案大小遞增排列**,最大的放最後。不確定大小就先 `wc -l`。
287
- **打亂清單順序時這條仍然適用**——大檔壓最後,只洗其餘。
288
-
289
- ### 跑 2–3 次怎麼做
290
-
291
- 一次派工只是一次抽樣。**同一組設定跑 2–3 次,取聯集。**
292
-
293
- - ticket-id 加後綴(`<主題>-r1`/`-r2`/`-r3`),**每一次一個獨立目錄**
294
- - **第二次先用逐字相同的工單**,只換 ticket-id
295
- - **比對前兩次的結果,再決定第三次怎麼跑**:
296
- - 出現明顯的新條目 → 這個型號直接重跑就有效,第三次照樣逐字重跑
297
- - 幾乎是同一組 → 這個型號對相同的 prompt 會收斂,**不擾動就拿不到新東西**。
298
- 第三次改成**重排允許清單順序**,問題與 `_shared.md` 逐字不動
299
-
300
- 重排時**大檔仍然壓在最後**(見上方規則 4),只洗其餘。
301
-
302
- ---
303
-
304
- ## 4. 先乾跑(不花錢)
305
-
306
- **這是花錢之前唯一的檢查點。** 工單一旦真的派出去就開始計費,中途失敗或中斷,錢也拿
307
- 不回來——**沒有 resume,重跑等於重新付一次**。乾跑錯了改一改再跑,不花任何錢。
308
- **所以這一步不能跳過。**
309
-
310
- 它抓得到的:`repoRoot` 指錯專案、型號填錯、lens 定義找不到、允許清單裡的檔不存在、
311
- `tmp/` 沒被忽略、估算超過閘門上限——這些在真的呼叫 API 之前就會被擋下來。
312
-
313
- > 其中 lens 定義與 `tmp/` 你在第 1 節已經確認過了。這裡不是要你重做一次,是讓你知道
314
- > **就算第 1 節漏了,這一關還會擋下來**——但反過來不成立,所以第 1 節照樣要查。
315
-
316
- **它抓不到的是工單內容本身**:問題問得好不好、允許清單對不對得上那些問題,乾跑一律看
317
- 不出來。那是你在第 2 節就該做完的事(逐題對照表),乾跑不會替你補。
318
-
319
- ```bash
320
- dowafu tmp/dispatch/<ticket-id> --repo-root . --dry-run
321
- ```
322
-
323
- **一次乾跑只驗一個工單目錄。** 這一批如果拆成了多個目錄(同一個 lens 跑多個型號時就會),
324
- **每一個都要各乾跑一次**,並把各次的估算**加總**之後再把數字呈給使用者。
325
- 只乾跑第一個就去實跑,等於其餘目錄完全沒經過這道檢查。
326
-
327
- **跑之前先跟使用者說明這一步在幹嘛。** 他多半沒用過這個工具,看你在下指令會以為已經
328
- 開始派工、開始計費了:
329
-
330
- > 這一步只解析工單、驗證設定、估算用量,**不呼叫任何 API、不產生任何費用**。
331
- > 目的是在花錢之前,先確認要派出去的東西是對的。
332
-
333
- **跑完把報表轉述給他**,不要只說「乾跑通過」。至少這幾項:
334
-
335
- | 報表項目 | 要讓使用者看懂的 |
336
- | --- | --- |
337
- | `repoRoot` | 指到的是不是他的專案 |
338
- | `model`/`effort` | 每個 spoke 實際會用哪個型號 |
339
- | 初始 prompt 估算 | 一開始就會送出去的量 |
340
- | 允許清單估算 + 檔數 | spoke 讀得到哪些東西、多大 |
341
- | 最壞總消耗 | **這是上限不是預期值**(各 spoke 的 cap 加總),實際通常遠低於此 |
342
-
343
- **用自己的話、用表格轉述——除非你是逐位元組貼上原輸出,否則不要放進 code fence。**
344
- code fence 的語意就是「這是工具印出來的」,把改寫過的內容裝進去,等於宣稱一個你其實
345
- 沒做到的準確度。改寫沒有問題,而且常常比原輸出好讀;把改寫冒充成原輸出才有問題。
346
-
347
- **要轉述就連限定語一起轉。** 報表裡那些限定語,正是讓數字不會被誤讀的部分——總量是上限
348
- 而非預期值、價目取自哪一天、估算建立在什麼假設上、每支 lens 收尾句的確認行。它們看起來
349
- 最像可以省的,卻正是讓這些數字能拿來做決定的東西。**限定語一刪,你交給使用者的數字就
350
- 比工具給你的更硬。**
351
-
352
- **工具標了 `⚠` 或 `ℹ` 的那幾行一律逐字轉述,不得改寫。** `⚠` 是工具在說「現在有事」——
353
- 輸出目錄已經有產物、清單順序正在多花錢、某支 spoke 什麼都沒讀。把它改寫成一句比較平順的話,
354
- 是你能對這份報表做的最貴的一件事,因為讀者會失去唯一那個「需要你做決定」的訊號。
355
- 特別是:`⚠ 大檔排清單最後可降至 N(本項省 N%)` 的意思是**你現在沒有排好**,
356
- 不是「已經排好了、重排只能再省一點」。
357
-
358
- 必須跟著數字一起活下來的限定語,逐項點名:
359
-
360
- | 出現在哪 | 什麼一定要跟著轉 |
361
- | --- | --- |
362
- | 每支 spoke 那一行 | `effort=`、`lang=`、`store=`,以及它的 `cap` |
363
- | 單價子行 | 每 M token 的數字**與**`價目查證日 <日期>` |
364
- | `ℹ` 收尾句檢查 | 每支一行,照印的轉 |
365
- | 初始 prompt 估算 | 它**不含工單與允許清單**,以及閘門上限 |
366
- | 允許清單估算 | 它是上限、**不去重**,以及字元/token 的換算基礎 |
367
- | 逐個讀順序放大量 | 它是「逐個讀假設下的上限、批次讀的廠牌不適用」、排序的判定,以及**本項不含初始 prompt 與工單** |
368
- | 最壞總消耗 | 它是**上限不是預期值**,且是各 spoke cap 的加總 |
369
-
370
- 少了這些,數字讀起來會比工具的原意更硬。**自己把 token 換算成錢時,要講明那是你算的、依據哪一行單價。**
371
-
372
- 報表只給 token,不給金額,且**只在乾跑階段成立**。要換算成錢,**價目來源是
373
- `providers.json` 的 `pricing`(`inputPerM`/`cachedInputPerM`/`outputPerM`),不要查
374
- 官網**——那份數字就是 CLI 計費用的,查官網會讓「你報的錢」與「CLI 算的錢」對不上。
375
- `pricingSource.asOf` 看起來過舊就回報使用者,不要自行改數字(要改是改
376
- `providers.json`)。**實跑**的 `summary.md` 有「估算成本」欄,每支 spoke 結束也會印
377
- 一行 `cost=`,那是 CLI 自己算好的金額,**直接轉述即可,不必自己算**。
378
-
379
- 然後逐項核對:`repoRoot` 是這個專案、`model` 跟你寫的一致、**報表印出的 `effort` 是
380
- 你預期的那一級**、估算 token 量級合理、**沒有 `⚠ 輸出目錄…未被…忽略` 的警告**。
381
-
382
- 任何一項不對就修工單重跑,**不要往下走**。
383
-
384
- ---
385
-
386
- ## 5. 實跑
387
-
388
- **輸出目錄必須是一個還不存在的目錄。** 先看 `tmp/spoke/<ticket-id>/`:已經有東西了就
389
- **換一個新的 ticket-id 派出去**——換 id 不花錢,碰撞這件事就不存在了。
390
-
391
- **刪產物是使用者的決定,永遠不是你的。** 派工前不行、順手整理不行、「它擋路」也不行。
392
- 你可以問要不要清掉,但不可以自己清,也不可以直接跑上去。那底下的東西是花錢買來的,
393
- 而目錄本身不會告訴你使用者還要不要它。(第 7 節的清理是另一回事:那是**裁決之後**、
394
- 他說了才做的。)
395
-
396
- **這條有 CLI 撐著。** 目錄非空時它會在派工前中止——沒呼叫任何 API、沒花任何錢——
397
- 並要你換一個 ticket-id。**沒有可以蓋過它的旗標**:清目錄是使用者的動作,
398
- 不是你能帶的參數。
399
-
400
- CLI 開跑時會清空 `run.jsonl`(舊事件會讓你數錯),**但那一步只在它真的跑起來時才執行**。
401
- 啟動失敗的話,上一次的產物原封不動留著——而**檔案不會告訴你它是不是這一次的**。
402
-
403
- ```bash
404
- dowafu tmp/dispatch/<ticket-id> --repo-root . --yes
405
- ```
406
-
407
- > **這一步會花錢。** `--yes` 等於你代替使用者按下確認,**加上它之前必須在對話裡取得
408
- > 使用者的明確同意**。
409
- >
410
- > 不帶 `--yes` 會發生什麼,依你的 host 而定——見 §1.5。兩種情況都不會花到錢。
411
-
412
- > **單支 spoke 可能跑到十分鐘,而且快慢無法事先判斷**——同素材、同工單、token 量級相近,
413
- > 兩次的耗時仍可能差好幾倍。那是對方伺服器的負載,不可歸因、也不可預測。
414
- >
415
- > **`--timeout` 是單次 API 呼叫的逾時,不是整支 spoke 要跑多久**——一支 spoke 內有多輪
416
- > 呼叫,它不會限制總時長,不要拿它來估整支要跑多久。
417
- >
418
- > **前景執行本身另有外部工具的逾時上限**(十分鐘量級),與 `--timeout` 無關、擋不住。
419
- > **預估這次會跑久(清單大、問題多、選了較慢的型號)就改背景執行**,不要在前景硬等。
420
-
421
- ### 啟動確認:先確認它真的跑起來了,再開始等
422
-
423
- **下指令之前先記下當下時間**(`date -u +%Y-%m-%dT%H:%M:%SZ`),下完隔十幾秒讀一次
424
- `tmp/spoke/<ticket-id>/run.jsonl`:
425
-
426
- | 讀到 | 判定 |
427
- | --- | --- |
428
- | 檔案不存在 | **沒跑起來**——CLI 一開跑就會建它,這不是「還在等 API」 |
429
- | 有 `spoke_start`,`ts` 晚於你下指令的時刻 | 起來了,開始輪詢 |
430
- | 有內容,但 `ts` 早於你下指令的時刻 | **沒跑起來,你讀到的是上一次的產物** |
431
-
432
- `ts` 是每一行都有的 ISO 8601 時間戳,**這是判斷「這一份是不是這一次」的唯一機械依據**。
433
-
434
- **沒跑起來就不要繼續等。** 這個失敗形態自己看不出來:舊產物的兩個 `spoke_end` 都是
435
- `succeeded`,格式、稽核欄、成本全部正常,唯一的破綻是時間戳。
436
-
437
- ### 等多久算不正常
438
-
439
- **看的不是總耗時,是 `run.jsonl` 有沒有在長。** 單支 spoke 跑上十分鐘是正常的,但
440
- **十分鐘之內一定會有新事件寫進來**——單次 API 呼叫的逾時預設就是十分鐘,而逾時與重試
441
- 各自都會寫一行 `round_error`。
442
-
443
- > **超過十分鐘沒有任何新事件,就停下來告訴使用者**,不要繼續等、也不要自行重跑。
444
- > 把最後一個事件與它的 `ts` 一起報給他,讓他判斷。
445
-
446
- **進度一律以 `run.jsonl` 為準**——CLI 逐事件寫入該檔,中途 Ctrl-C 也讀得出跑到哪。
447
- 終端機那條通道可不可靠依 host 而定(見 §1.5),`run.jsonl` 不受影響。
448
-
449
- **跑完的判定是兩件事同時成立**:兩個 `spoke_end`,**而且**這份 `run.jsonl` 通過了上面的
450
- 啟動確認。只看 `spoke_end` 會把上一次的產物當成這一次的結果。
451
-
452
- **真的撞到失敗或逾時:要不要重跑由使用者決定,不要自行重跑。** 重跑是重新付費,中斷前已
453
- 花的錢救不回來(沒有 resume 機制)。把中斷時的狀態(`run.jsonl` 讀到哪、失敗訊息)告訴
454
- 使用者,讓他判斷要不要重來——這與「零讀取就整份重跑」不衝突:那條是「判定沒跑」,這裡是
455
- 「跑了但被中斷」,成本結構不同。
456
-
457
- ---
458
-
459
- ## 6. 回收——摘要優先,原文為例外
460
-
461
- 產物在 `tmp/spoke/<ticket-id>/`:`<agent>.md`(原文)、`summary.md`(稽核表)、
462
- `run.jsonl`(執行記錄)、`raw/`(完整請求與回應)。
463
-
464
- **`run.jsonl` 是逐事件附加寫入的,不會被覆蓋。** 產物與 `raw/` 若被蓋掉——重跑,或另一支
465
- 寫到同一個名字——那個檔裡仍然留著每一支的 `spoke_start`(provider 與型號)、逐輪 usage、
466
- 每一次讀檔(含被拒的)、錯誤,以及 `spoke_end` 的 token 與成本。
467
- **報告文字救不回來,但花了多少錢、讀了哪些檔,可以還原。**
468
-
469
- > **回收之前先確認 §5 的啟動確認過了。** 這個目錄底下的任何檔案,都不會告訴你它是不是
470
- > 這一次的產物。
471
-
472
- **呈現順序不得更動**:
473
-
474
- 1. **逐個 spoke 的內容都要讓使用者看到**,標明 lens 與模型。**摘要是預設**,滿足以下
475
- 四個必要條件:
476
- 1. **明確宣告取捨**——在摘要開頭寫明「本節為摘要,非逐字照登」,不安靜地摘掉
477
- 2. **判讀涵蓋每一條,無一略過**——每個 spoke 的每一條觀察都要在後面的「hub 判讀」出現,
478
- 這是取代「原文照登」的可稽核性來源:原文不在你眼前列出,不代表它沒被看過
479
- 3. **指出原文路徑**(`tmp/spoke/<ticket-id>/<agent>.md`),使用者隨時可自行比對
480
- 4. **只在產物尚未清理時才成立**——第 7 節清掉之後就沒有底本可查
481
- > 這條規則要防的不是「沒有原文」,是**防止 hub 只挑對自己有利的講**。滿足以上四件事,
482
- > 手段換成摘要也一樣防得住;換句話說,要守住的是目的,不是「原文」這個手段本身。
483
-
484
- **原文照登用於兩種情形**:產物已被第 7 節清掉(沒有底本可查,摘要不成立),或單份
485
- 規模小到摘要反而多此一舉。除此之外都走摘要。
486
-
487
- **做不到第 2 條就不要派這麼多 spoke**——問題出在派工規模,不在呈現方式;不要拿
488
- 「沒把握」當理由退回整份照登。
489
- 2. **`summary.md` 的稽核欄轉述進上面那一節——它印出來的每一段都要點名。** 每支 spoke
490
- 一段一行,**順序照 `summary.md` 印的**:`工具呼叫` 在最前面,排在 `收尾句` 之前。
491
- 某一段是空的時候,CLI 會自己印它的空值字(中文那套印 `無`),**照它印的抄,
492
- 而且一段都不准漏掉**。全部寫出來不比只寫大部分貴,而少一個段名使用者看得出來,
493
- 少一列他看不出來。
494
-
495
- **`⚠` 開頭的那幾段不准省,`(無法稽核)` 也不准省。** 它們不是那幾個欄位以外的裝飾,
496
- 是稽核在告訴你出事了;省掉它們,使用者手上就只剩下說「沒事」的那幾段。
497
-
498
- **有內容的段落一律逐字照抄。** 只有 `pass` 與 CLI 自己的空值字可以壓縮。說「沒事」的
499
- 段落留著最省事,說「有事」的那段才是會被刪掉的——所以這條規則是刻意不對稱的:
500
- **一段講的東西越多,你對它的處置自由越少。**
501
- 3. **之後另立「hub 判讀」一節**——去重,逐條標註你的初步判讀(成立/不成立+為什麼/
502
- 需使用者裁決)。**每一項都要標明它來自哪幾條觀察**,寫成 spoke + 條號
503
- (`safety 2、3;feasibility 9`),而且**每一條觀察都要出現在至少一項裡**。
504
- 兩支 spoke 的編號觀察,要嘛全部出現在那一欄,要嘛沒出現的一眼就看得出來——**這正是重點**:
505
- 沒有編號的話,「安靜地沒被列進來」與「你判讀過但認為不成立」長得一模一樣。
506
-
507
- ### 跑了多次時怎麼合併
508
-
509
- **取聯集,不是取交集。** 逐條標出現次數(`3/5` 這種),但**不要用出現次數當重要度**——
510
- 只出現一次的可能是嚴重級,每次都出現的也可能是假陽性。
511
- **重要度一律由 hub 開檔驗證後自己判,不看票數。**
512
-
513
- ### 判讀時必看的三件事
514
-
515
- **一、`run.jsonl` 的 `toolCalls[]`。** 那是「它實際讀了什麼」,不是「它說它讀了什麼」。
516
- 某個 spoke 若零次工具呼叫,它的回報只是文字審查,引用程式碼的說法要打折。
517
-
518
- **二、安全或正確性的宣稱,自己打開檔案驗一次再轉述。** 不要把 spoke 的宣稱直接當結論
519
- 報給使用者。
520
-
521
- **行號要重新查證。** spoke 的引用會有幾行到數十行的偏移,而**內容描述往往是對的**——
522
- 事實層可用、位置層不可用。**位置錯不等於幻覺**(幻覺是「該檔根本沒有這段」),兩者處置
523
- 不同:幻覺要重跑或換模型,位置錯只需自己重新定位。**把位置錯判成幻覺,會丟掉整份能用的
524
- 產出。**
525
-
526
- **這條對你自己寫的行號同樣成立。** 驗完 spoke 的引用、隔幾段自己憑印象寫一次行號,
527
- 等於把漂移換上你的名字放回去——**而你的份量更重**,因為你說了你開過檔。
528
-
529
- **三、驗 spoke 引用時,註解不算證據。** 「spoke 說某段有註解背書某個結論」不夠,還要驗
530
- **註解說的還成不成立**——註解會與程式碼漂移,而漂移的註解讀起來跟正確的一模一樣,
531
- 只驗「註解存在且內容吻合」會把錯的判成對的。AGENTS.md 必答檢查第 1 條「不能只憑檔名、
532
- 註解或行號」原本管的是寫規劃書時,這裡把它擴到驗收 spoke 引用時。
533
-
534
- **spoke 說「我讀不到 X 所以無法確認」是正確回報,不是假警報。** 它指出的是**你沒放進清單**的檔,
535
- 那是你的缺口、不是它的錯——把它歸到「假警報」,或自己默默查掉就往下走,
536
- 會把「清單裁錯了」這個唯一的訊號蓋掉。**你可以自己補查,但仍要明說清單當時是短的。**
537
-
538
- ### 稽核表怎麼看
539
-
540
- | 欄位 | 意思 |
541
- | --- | --- |
542
- | `工具呼叫:N(允許 N/拒絕 N)` | 讀了幾次、幾次被拒——`run.jsonl` `toolCalls[]` 的統計。**印在最前面**,排在收尾句之前 |
543
- | `收尾句` | pass/fail |
544
- | `觀察:N` | 條數;`無法計數` 表示格式無法辨識,翻原文確認 |
545
- | `清單外引用` | 引用了允許清單外的路徑,**可能是臆測**——對照 `toolCalls[]` 判斷。**此欄至今沒抓到過真正的幻覺**:抓到的多半是 spoke 照抄素材的縮寫路徑、轉述素材裡提過但自己標明讀不到的路徑,或絕對/相對路徑混用。先查該路徑是不是出現在你自己的 `_shared.md` 裡,或只是寫法不同,不要預設是編的 |
546
- | `無法驗證欄` | 有沒有照模板寫這一節。**判讀產出品質時看這欄有多具體**(例如逐項標明哪個結論依賴哪個不可讀的檔案),不要看觀察條數——條數不可靠,重疊、灌水都會推高條數但不代表品質 |
547
- | `疑似禁止內容` | **只是疑似**,關鍵詞比對必有誤判,看命中原句自行判斷 |
548
-
549
- **若欄位最前面出現 `⚠ 零原始碼讀取(允許 N 檔)`**——見下方「零讀取時怎麼辦」,先處理這個
550
- 再看其他欄位。
551
-
552
- ### 零讀取時怎麼辦
553
-
554
- 看到 `⚠ 零原始碼讀取` 時:
555
-
556
- **先確認允許清單是不是空的。** 清單留空是合法設定(純文字審查),此時零讀取是預期行為,
557
- 不是異常。
558
-
559
- **清單非空卻零讀取——判定為「這份工單沒有被執行」。**
560
-
561
- > 不是「品質差」、不是「部分可用」,是**沒跑**。工單的問題是在「有程式碼」的前提下寫的
562
- > (例如「現有程式碼中是否已存在可直接複用的取值路徑」),前提不成立時整組問題都失效。
563
-
564
- **處置:整份重跑,不要分析內容、不要挑「還有用的部分」。**
565
-
566
- 發現零讀取之後還去分析報告、搶救可用的部分,**是本末倒置**。理由是資訊來源:
567
-
568
- - spoke 只拿到工單,而**工單是你自己寫的**
569
- - 所以報告內容只有兩種來源:**你自己寫的東西反射回來**,或**編的**
570
- - 沒有第三類——它沒有任何你不知道的資訊管道
571
- - **你讀到「跟我想的一樣」會覺得有價值,那是自己的回音**;而唯一「新」的部分往往正是幻覺
572
- (例如指名某個常數存在於某個檔,而那個檔完全沒有那個字串)
573
-
574
- 可搶救的文字層觀察,**重跑一次就會再拿到,而且是更好的版本**。重跑很便宜。
575
-
576
- **重跑方式**:換一個型號,或重排允許清單順序後重跑(見第 3 節「跑 2–3 次怎麼做」)。
577
- 清單特別大的時候比較容易發生——重跑時順便把清單收斂到「真正回答得了這幾題的那些檔」。
578
-
579
- 最後把這次的情況告訴使用者。
580
-
581
- ---
582
-
583
- ## 7. 清理(使用者裁決完之後)
584
-
585
- ```bash
586
- rm -rf tmp/spoke/<ticket-id> tmp/dispatch/<ticket-id>
587
- ```
588
-
589
- **這是整套流程裡唯一會刪東西的地方,而且要等使用者裁決完、他說了才刪。** 其他任何時候
590
- ——包括派工前那個擋路的目錄——一律照第 5 節:可以問,不可以自己刪。
591
-
592
- lens 定義留著,下次還會用。
593
-
594
- ---
595
-
596
- ## 出問題怎麼辦
597
-
598
- | 症狀 | 處置 |
599
- | --- | --- |
600
- | 報表數值不對 | 停在 `--dry-run`,檢查 `_dispatch.md` |
601
- | 中途失敗 | 讀 `run.jsonl` 的 `round_error` 事件(狀態碼、訊息、是第幾輪)與 `raw/<agent>.errors.json`;已完成輪次的內容仍會落檔 |
602
- | 真失敗或逾時 | **要不要重跑問使用者,不要自行重跑**——重跑是重新付費,中斷前的花費救不回來 |
603
- | 撞 429 | 加 `--concurrency 1` 重跑 |
604
- | 報告品質差 | 換一個型號重跑,或同一組設定再跑一次取聯集(見第 3 節「跑 2–3 次怎麼做」) |
605
- | 缺 API key | 使用者要在 `~/.config/dowafu/.env` 設定,**你不要去碰那個檔** |
606
- | 找不到 `dowafu` | **查不到不代表沒安裝**——見 §1.5,多半是沙箱不讀家目錄 |
607
- | `Operation not permitted` | host 的沙箱擋的,**不是工單或指令的問題**。照提示放行再跑一次 |
608
-
609
- **撞到任何異常,告訴使用者**——不要自己吞掉或繞過去。