dowafu 0.1.0 → 0.3.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 (51) hide show
  1. package/README.md +44 -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 +22 -14
  7. package/dist/audit.js +17 -1
  8. package/dist/cli-args.js +88 -38
  9. package/dist/cli.js +60 -43
  10. package/dist/dispatch-home.js +24 -6
  11. package/dist/gate.js +3 -2
  12. package/dist/mask.js +16 -3
  13. package/dist/messages.js +342 -0
  14. package/dist/output.js +60 -37
  15. package/dist/prompt.js +5 -3
  16. package/dist/providers.js +46 -34
  17. package/dist/raw-integrity.js +6 -5
  18. package/dist/report.js +76 -22
  19. package/dist/runner.js +21 -10
  20. package/dist/ticket.js +27 -25
  21. package/dist/validate.js +21 -20
  22. package/dist/whitelist.js +9 -1
  23. package/package.json +3 -2
  24. package/publish/en/.agents/skills/find-holes-external/SKILL.md +394 -0
  25. package/publish/en/.agents/skills/preflight/SKILL.md +120 -0
  26. package/publish/en/.agents/skills/wrap/SKILL.md +64 -0
  27. package/publish/en/.claude/agents/explore-haiku.md +8 -0
  28. package/publish/en/.claude/agents/hole-finder-cost.md +15 -0
  29. package/publish/en/.claude/agents/hole-finder-feasibility.md +15 -0
  30. package/publish/en/.claude/agents/hole-finder-safety.md +15 -0
  31. package/publish/en/.claude/agents/hole-finder.md +14 -0
  32. package/publish/en/.claude/skills/find-holes/SKILL.md +109 -0
  33. package/publish/en/.claude/skills/find-holes-external/SKILL.md +407 -0
  34. package/publish/en/.claude/skills/preflight/SKILL.md +179 -0
  35. package/publish/en/.claude/skills/wrap/SKILL.md +61 -0
  36. package/publish/en/README.md +106 -0
  37. package/publish/en/workflow_spec.md +71 -0
  38. package/publish/{.agents → zh-tw/.agents}/skills/find-holes-external/SKILL.md +109 -18
  39. package/publish/{.agents → zh-tw/.agents}/skills/preflight/SKILL.md +22 -5
  40. package/publish/{.claude → zh-tw/.claude}/skills/find-holes/SKILL.md +21 -4
  41. package/publish/{.claude → zh-tw/.claude}/skills/find-holes-external/SKILL.md +108 -17
  42. package/publish/{.claude → zh-tw/.claude}/skills/preflight/SKILL.md +21 -4
  43. package/publish/{README.md → zh-tw/README.md} +16 -0
  44. /package/publish/{.agents → zh-tw/.agents}/skills/wrap/SKILL.md +0 -0
  45. /package/publish/{.claude → zh-tw/.claude}/agents/explore-haiku.md +0 -0
  46. /package/publish/{.claude → zh-tw/.claude}/agents/hole-finder-cost.md +0 -0
  47. /package/publish/{.claude → zh-tw/.claude}/agents/hole-finder-feasibility.md +0 -0
  48. /package/publish/{.claude → zh-tw/.claude}/agents/hole-finder-safety.md +0 -0
  49. /package/publish/{.claude → zh-tw/.claude}/agents/hole-finder.md +0 -0
  50. /package/publish/{.claude → zh-tw/.claude}/skills/wrap/SKILL.md +0 -0
  51. /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: "904b0dd5df9af5faf24408fa5403b392a6d64ad595f3eeaa16a681aaa01ad1ef"
7
7
  ---
8
8
 
9
9
  # find-holes-external — 外派找漏洞
@@ -70,8 +70,8 @@ dowafu --version
70
70
  **四、實跑用背景執行,進度靠檔案而不是終端機輸出。**
71
71
 
72
72
  用檔案讀取工具輪詢 `tmp/spoke/<ticket-id>/run.jsonl`:CLI 逐事件寫入該檔,讀得出跑到
73
- 哪一輪、有沒有 `round_error`,看到兩個 `spoke_end` 就是跑完。背景終端機的輸出不保證拿
74
- 得到,而單支 spoke 可能跑上好幾分鐘。
73
+ 哪一輪、有沒有 `round_error`。**完成判定見 §5**——「兩個 `spoke_end`」只是其中一半。
74
+ 背景終端機的輸出不保證拿得到,而單支 spoke 可能跑上好幾分鐘。
75
75
 
76
76
  > **把終端機當啟動器,不要當資料通道。**
77
77
 
@@ -115,6 +115,16 @@ dowafu --version
115
115
  換一批對準答案位置的檔案,同一個 lens 就抓得到。**這個自問是為了在派工前擋下落差,
116
116
  不是派工後才發現。**
117
117
 
118
+ ### 每支 spoke 各一張表——清單是按 lens 裁的,不是整批派工裁一次
119
+
120
+ **那張表每支 spoke 各填一張。** 每支的問題不同,清單就不同;因為「湊一份比較省事」而拿
121
+ 同一份餵兩支 lens,只有其中一支需要的那個檔就是這樣掉的。兩支最後真的拿到同一批檔也
122
+ 可以,但要說得出為什麼——**清單相同是結論,不是起點**。
123
+
124
+ **一份清單餵兩支 lens,會往交集收斂,而不是聯集。** 最先掉的是「只有其中一支需要」的檔,
125
+ 而那正是那支 lens 被派去看的東西;spoke 這時只能把缺口寫進「無法驗證」欄,而用這種方式
126
+ 發現要付一整輪派工的錢。為省錢裁清單是合理的——但要**各自對著自己的問題裁**。
127
+
118
128
  ### lens
119
129
 
120
130
  | agent | 視角 |
@@ -161,8 +171,10 @@ dowafu --version
161
171
  | hole-finder-feasibility | gemini | gemini-3.1-flash-lite | |
162
172
  ```
163
173
 
164
- 首行 `<!-- format: v1 -->` **必須有**;`model` 必填;`effort` 留白即可(自動用各家預設);
165
- 只列你要派的那幾個。
174
+ 首行 `<!-- format: v1 -->` **必須有**;`model` 必填;只列你要派的那幾個。`effort` 留白
175
+ 即為 **`high`**——四家的 `reasoning.default` 目前都是 `high`。要調就查
176
+ `providers.json` 的 `reasoning.allowed`,**各家值域不同**(例如 `deepseek` 沒有
177
+ `medium`);填了不在值域內的值會被擋下並列出允許值,不會靜默降級。
166
178
 
167
179
  ### `_shared.md`(所有 spoke 共用)
168
180
 
@@ -201,7 +213,9 @@ dowafu --version
201
213
  3. **允許清單要涵蓋「回答這些問題實際需要的檔案」。** 逐題自問:要回答這題得讀哪些檔?
202
214
  漏了的話 spoke 只能在「無法驗證」欄記下缺什麼——**那是派工端的失誤,不是它的問題**。
203
215
  路徑**相對 repo 根目錄**。留空也合法(純文字審查),但那樣就讀不到程式碼,會少掉
204
- 「文件說 X、`src/foo.ts:42` 其實是 Y」這類最有價值的發現。
216
+ 「文件說 X、`src/foo.ts:42` 其實是 Y」這類最有價值的發現。**清單屬於這一份
217
+ `<agent>.md`,不屬於整批派工**——不要把另一支的清單整份複製過來:對它有用、對這一支
218
+ 沒用的檔,在讀取順序裡是純負擔;反過來就是漏洞。
205
219
 
206
220
  4. **大的檔案排在清單最後——這能省掉一半成本。** spoke **嚴格照清單順序**讀檔,而多數
207
221
  模型**一輪只叫一個檔**,每一輪又會把先前讀過的全部內容重送一次。所以一個檔被重複
@@ -260,10 +274,24 @@ dowafu tmp/dispatch/<ticket-id> --repo-root . --dry-run
260
274
  | 允許清單估算 + 檔數 | spoke 讀得到哪些東西、多大 |
261
275
  | 最壞總消耗 | **這是上限不是預期值**(各 spoke 的 cap 加總),實際通常遠低於此 |
262
276
 
263
- 報表只給 token,不給金額——**要換算成錢就自己依型號價目算給他看**,別讓他自己猜。
277
+ **用自己的話、用表格轉述——除非你是逐位元組貼上原輸出,否則不要放進 code fence。**
278
+ code fence 的語意就是「這是工具印出來的」,把改寫過的內容裝進去,等於宣稱一個你其實
279
+ 沒做到的準確度。改寫沒有問題,而且常常比原輸出好讀;把改寫冒充成原輸出才有問題。
264
280
 
265
- 然後逐項核對:`repoRoot` 是這個專案、`model` 跟你寫的一致、`effort` 不是空的、估算
266
- token 量級合理、**沒有 `⚠ 輸出目錄…未被…忽略` 的警告**。
281
+ **要轉述就連限定語一起轉。** 報表裡那些限定語,正是讓數字不會被誤讀的部分——總量是上限
282
+ 而非預期值、價目取自哪一天、估算建立在什麼假設上、每支 lens 收尾句的確認行。它們看起來
283
+ 最像可以省的,卻正是讓這些數字能拿來做決定的東西。**限定語一刪,你交給使用者的數字就
284
+ 比工具給你的更硬。**
285
+
286
+ 報表只給 token,不給金額,且**只在乾跑階段成立**。要換算成錢,**價目來源是
287
+ `providers.json` 的 `pricing`(`inputPerM`/`cachedInputPerM`/`outputPerM`),不要查
288
+ 官網**——那份數字就是 CLI 計費用的,查官網會讓「你報的錢」與「CLI 算的錢」對不上。
289
+ `pricingSource.asOf` 看起來過舊就回報使用者,不要自行改數字(要改是改
290
+ `providers.json`)。**實跑**的 `summary.md` 有「估算成本」欄,每支 spoke 結束也會印
291
+ 一行 `cost=`,那是 CLI 自己算好的金額,**直接轉述即可,不必自己算**。
292
+
293
+ 然後逐項核對:`repoRoot` 是這個專案、`model` 跟你寫的一致、**報表印出的 `effort` 是
294
+ 你預期的那一級**、估算 token 量級合理、**沒有 `⚠ 輸出目錄…未被…忽略` 的警告**。
267
295
 
268
296
  任何一項不對就修工單重跑,**不要往下走**。
269
297
 
@@ -271,6 +299,21 @@ token 量級合理、**沒有 `⚠ 輸出目錄…未被…忽略` 的警告**
271
299
 
272
300
  ## 5. 實跑
273
301
 
302
+ **輸出目錄必須是一個還不存在的目錄。** 先看 `tmp/spoke/<ticket-id>/`:已經有東西了就
303
+ **換一個新的 ticket-id 派出去**——換 id 不花錢,碰撞這件事就不存在了。
304
+
305
+ **刪產物是使用者的決定,永遠不是你的。** 派工前不行、順手整理不行、「它擋路」也不行。
306
+ 你可以問要不要清掉,但不可以自己清,也不可以直接跑上去。那底下的東西是花錢買來的,
307
+ 而目錄本身不會告訴你使用者還要不要它。(第 7 節的清理是另一回事:那是**裁決之後**、
308
+ 他說了才做的。)
309
+
310
+ **這條有 CLI 撐著。** 目錄非空時它會在派工前中止——沒呼叫任何 API、沒花任何錢——
311
+ 並要你換一個 ticket-id。**沒有可以蓋過它的旗標**:清目錄是使用者的動作,
312
+ 不是你能帶的參數。
313
+
314
+ CLI 開跑時會清空 `run.jsonl`(舊事件會讓你數錯),**但那一步只在它真的跑起來時才執行**。
315
+ 啟動失敗的話,上一次的產物原封不動留著——而**檔案不會告訴你它是不是這一次的**。
316
+
274
317
  ```bash
275
318
  dowafu tmp/dispatch/<ticket-id> --repo-root . --yes
276
319
  ```
@@ -290,9 +333,37 @@ dowafu tmp/dispatch/<ticket-id> --repo-root . --yes
290
333
  > **前景執行本身另有外部工具的逾時上限**(十分鐘量級),與 `--timeout` 無關、擋不住。
291
334
  > **預估這次會跑久(清單大、問題多、選了較慢的型號)就改背景執行**,不要在前景硬等。
292
335
 
336
+ ### 啟動確認:先確認它真的跑起來了,再開始等
337
+
338
+ **下指令之前先記下當下時間**(`date -u +%Y-%m-%dT%H:%M:%SZ`),下完隔十幾秒讀一次
339
+ `tmp/spoke/<ticket-id>/run.jsonl`:
340
+
341
+ | 讀到 | 判定 |
342
+ | --- | --- |
343
+ | 檔案不存在 | **沒跑起來**——CLI 一開跑就會建它,這不是「還在等 API」 |
344
+ | 有 `spoke_start`,`ts` 晚於你下指令的時刻 | 起來了,開始輪詢 |
345
+ | 有內容,但 `ts` 早於你下指令的時刻 | **沒跑起來,你讀到的是上一次的產物** |
346
+
347
+ `ts` 是每一行都有的 ISO 8601 時間戳,**這是判斷「這一份是不是這一次」的唯一機械依據**。
348
+
349
+ **沒跑起來就不要繼續等。** 這個失敗形態自己看不出來:舊產物的兩個 `spoke_end` 都是
350
+ `succeeded`,格式、稽核欄、成本全部正常,唯一的破綻是時間戳。
351
+
352
+ ### 等多久算不正常
353
+
354
+ **看的不是總耗時,是 `run.jsonl` 有沒有在長。** 單支 spoke 跑上十分鐘是正常的,但
355
+ **十分鐘之內一定會有新事件寫進來**——單次 API 呼叫的逾時預設就是十分鐘,而逾時與重試
356
+ 各自都會寫一行 `round_error`。
357
+
358
+ > **超過十分鐘沒有任何新事件,就停下來告訴使用者**,不要繼續等、也不要自行重跑。
359
+ > 把最後一個事件與它的 `ts` 一起報給他,讓他判斷。
360
+
293
361
  **進度一律以 `run.jsonl` 為準**——CLI 逐事件寫入該檔,中途 Ctrl-C 也讀得出跑到哪。
294
362
  終端機那條通道不保證拿得到(見 §1.5 第四條),`run.jsonl` 不受影響。
295
363
 
364
+ **跑完的判定是兩件事同時成立**:兩個 `spoke_end`,**而且**這份 `run.jsonl` 通過了上面的
365
+ 啟動確認。只看 `spoke_end` 會把上一次的產物當成這一次的結果。
366
+
296
367
  **真的撞到失敗或逾時:要不要重跑由使用者決定,不要自行重跑。** 重跑是重新付費,中斷前已
297
368
  花的錢救不回來(沒有 resume 機制)。把中斷時的狀態(`run.jsonl` 讀到哪、失敗訊息)告訴
298
369
  使用者,讓他判斷要不要重來——這與「零讀取就整份重跑」不衝突:那條是「判定沒跑」,這裡是
@@ -300,26 +371,43 @@ dowafu tmp/dispatch/<ticket-id> --repo-root . --yes
300
371
 
301
372
  ---
302
373
 
303
- ## 6. 回收——先原文、後融合
374
+ ## 6. 回收——摘要優先,原文為例外
304
375
 
305
376
  產物在 `tmp/spoke/<ticket-id>/`:`<agent>.md`(原文)、`summary.md`(稽核表)、
306
377
  `run.jsonl`(執行記錄)、`raw/`(完整請求與回應)。
307
378
 
379
+ > **回收之前先確認 §5 的啟動確認過了。** 這個目錄底下的任何檔案,都不會告訴你它是不是
380
+ > 這一次的產物。
381
+
308
382
  **呈現順序不得更動**:
309
383
 
310
- 1. **逐個 spoke 的內容都要讓使用者看到**,標明 lens 與模型。規模小(單份幾十行)就直接
311
- 原文照登。**規模大到不適合整份貼出時**,可以摘要,但四件事缺一不可:
384
+ 1. **逐個 spoke 的內容都要讓使用者看到**,標明 lens 與模型。**摘要是預設**,滿足以下
385
+ 四個必要條件:
312
386
  1. **明確宣告取捨**——在摘要開頭寫明「本節為摘要,非逐字照登」,不安靜地摘掉
313
387
  2. **判讀涵蓋每一條,無一略過**——每個 spoke 的每一條觀察都要在後面的「hub 判讀」出現,
314
388
  這是取代「原文照登」的可稽核性來源:原文不在你眼前列出,不代表它沒被看過
315
389
  3. **指出原文路徑**(`tmp/spoke/<ticket-id>/<agent>.md`),使用者隨時可自行比對
316
- 4. **只在產物尚未清理時才成立**——第 7 節清掉之後就沒有底本可查,那個情境下原文照登
317
- 仍是唯一選擇,不能摘要
390
+ 4. **只在產物尚未清理時才成立**——第 7 節清掉之後就沒有底本可查
318
391
  > 這條規則要防的不是「沒有原文」,是**防止 hub 只挑對自己有利的講**。滿足以上四件事,
319
392
  > 手段換成摘要也一樣防得住;換句話說,要守住的是目的,不是「原文」這個手段本身。
320
393
 
321
- > **沒有把握做到這四件事,就不要摘要——整份照登。**
322
- 2. **`summary.md` 的稽核表併進原文區一併照登。**
394
+ **原文照登用於兩種情形**:產物已被第 7 節清掉(沒有底本可查,摘要不成立),或單份
395
+ 規模小到摘要反而多此一舉。除此之外都走摘要。
396
+
397
+ **做不到第 2 條就不要派這麼多 spoke**——問題出在派工規模,不在呈現方式;不要拿
398
+ 「沒把握」當理由退回整份照登。
399
+ 2. **`summary.md` 的稽核欄轉述進上面那一節——它印出來的每一段都要點名。** 每支 spoke
400
+ 一段一行,**順序照 `summary.md` 印的**:`工具呼叫` 在最前面,排在 `收尾句` 之前。
401
+ 某一段是空的時候,CLI 會自己印它的空值字(中文那套印 `無`),**照它印的抄,
402
+ 而且一段都不准漏掉**。全部寫出來不比只寫大部分貴,而少一個段名使用者看得出來,
403
+ 少一列他看不出來。
404
+
405
+ **`⚠` 開頭的那幾段不准省,`(無法稽核)` 也不准省。** 它們不是那幾個欄位以外的裝飾,
406
+ 是稽核在告訴你出事了;省掉它們,使用者手上就只剩下說「沒事」的那幾段。
407
+
408
+ **有內容的段落一律逐字照抄。** 只有 `pass` 與 CLI 自己的空值字可以壓縮。說「沒事」的
409
+ 段落留著最省事,說「有事」的那段才是會被刪掉的——所以這條規則是刻意不對稱的:
410
+ **一段講的東西越多,你對它的處置自由越少。**
323
411
  3. **之後另立「hub 判讀」一節**——去重,逐條標註你的初步判讀(成立/不成立+為什麼/
324
412
  需使用者裁決)。
325
413
 
@@ -351,8 +439,8 @@ dowafu tmp/dispatch/<ticket-id> --repo-root . --yes
351
439
 
352
440
  | 欄位 | 意思 |
353
441
  | --- | --- |
442
+ | `工具呼叫:N(允許 N/拒絕 N)` | 讀了幾次、幾次被拒——`run.jsonl` `toolCalls[]` 的統計。**印在最前面**,排在收尾句之前 |
354
443
  | `收尾句` | pass/fail |
355
- | `工具呼叫:N(允許 N/拒絕 N)` | 讀了幾次、幾次被拒——`run.jsonl` `toolCalls[]` 的統計 |
356
444
  | `觀察:N` | 條數;`無法計數` 表示格式無法辨識,翻原文確認 |
357
445
  | `清單外引用` | 引用了允許清單外的路徑,**可能是臆測**——對照 `toolCalls[]` 判斷。**此欄至今沒抓到過真正的幻覺**:抓到的多半是 spoke 照抄素材的縮寫路徑、轉述素材裡提過但自己標明讀不到的路徑,或絕對/相對路徑混用。先查該路徑是不是出現在你自己的 `_shared.md` 裡,或只是寫法不同,不要預設是編的 |
358
446
  | `無法驗證欄` | 有沒有照模板寫這一節。**判讀產出品質時看這欄有多具體**(例如逐項標明哪個結論依賴哪個不可讀的檔案),不要看觀察條數——條數不可靠,重疊、灌水都會推高條數但不代表品質 |
@@ -398,6 +486,9 @@ dowafu tmp/dispatch/<ticket-id> --repo-root . --yes
398
486
  rm -rf tmp/spoke/<ticket-id> tmp/dispatch/<ticket-id>
399
487
  ```
400
488
 
489
+ **這是整套流程裡唯一會刪東西的地方,而且要等使用者裁決完、他說了才刪。** 其他任何時候
490
+ ——包括派工前那個擋路的目錄——一律照第 5 節:可以問,不可以自己刪。
491
+
401
492
  lens 定義留著,下次還會用。
402
493
 
403
494
  ---
@@ -411,7 +502,7 @@ lens 定義留著,下次還會用。
411
502
  | 真失敗或逾時 | **要不要重跑問使用者,不要自行重跑**——重跑是重新付費,中斷前的花費救不回來 |
412
503
  | 撞 429 | 加 `--concurrency 1` 重跑 |
413
504
  | 報告品質差 | 換一個型號重跑,或同一組設定再跑一次取聯集(見第 3 節「跑 2–3 次怎麼做」) |
414
- | 缺 API key | 使用者要在 `~/.config/dispatch/.env` 設定,**你不要去碰那個檔** |
505
+ | 缺 API key | 使用者要在 `~/.config/dowafu/.env` 設定,**你不要去碰那個檔** |
415
506
  | 找不到 `dowafu` | **查不到不代表沒安裝**——見 §1.5,多半是沙箱不讀家目錄 |
416
507
  | `Operation not permitted` | host 的沙箱擋的,**不是工單或指令的問題**。照提示放行再跑一次 |
417
508
 
@@ -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: "76de7252b80e1de0256a858467415a99607914f49afae3e1b181f5db43ab796e"
7
7
  ---
8
8
 
9
9
  # preflight — 環境前置檢查
@@ -35,7 +35,7 @@ grep -n "規劃→實作→驗收流程規範" CLAUDE.md AGENTS.md workflow_spec
35
35
 
36
36
  echo "=== skill 與 lens ==="
37
37
  ls .claude/skills/ 2>/dev/null
38
- ls .claude/agents/hole-finder-*.md 2>/dev/null
38
+ ls .claude/agents/hole-finder*.md 2>/dev/null
39
39
 
40
40
  echo "=== tmp/ ==="
41
41
  git check-ignore -q tmp && echo "已忽略" || echo "未忽略"
@@ -52,6 +52,11 @@ git check-ignore -q tmp && echo "已忽略" || echo "未忽略"
52
52
  讀完在回報裡註明「規範不在自動載入範圍內,本次是手動讀取的」——讓使用者知道換個
53
53
  session 又會漏掉。
54
54
 
55
+ **找到不只一份,就要講明哪一份會被自動載入。** 專案可能本來就把這一章內嵌在入口檔裡,
56
+ 而根目錄又多一份 `workflow_spec.md`——兩份的語言還可能不同(語言套件各裝各的)。
57
+ 這時只回報「內容讀得到」不夠:要指出**你 context 裡的是哪一份**,以及兩份之間沒有任何
58
+ 同步機制。自動載入的那一份,才是之後每個 session 真正生效的那一份。
59
+
55
60
  > **為什麼會讀不到?** 入口檔因 host 而異:Claude Code 讀 `CLAUDE.md`(**不讀
56
61
  > `AGENTS.md`**),其他 host 多半讀 repo 根的 `AGENTS.md`。而 `@xxx.md` 是 Claude Code
57
62
  > 的 import 語法,**別的 host 不會展開它**——那時你看到的只是一行字,規範內容從來沒進
@@ -59,8 +64,20 @@ session 又會漏掉。
59
64
 
60
65
  ### skill 與 lens
61
66
 
62
- `.claude/skills/find-holes-external/` 與 `.claude/agents/hole-finder-*.md` 在不在。
63
- (後者 glob 會列出通用的 `hole-finder.md` 加三個 lens,四個都算正常。)
67
+ `.claude/skills/find-holes-external/` 與 lens 定義在不在。**回報你實際看到的檔名,
68
+ 缺哪個就點名**——「看到三個」不算檢查,是哪三個才算。
69
+
70
+ 應該有四個,四個都算正常:
71
+
72
+ | 檔 | 是什麼 |
73
+ | --- | --- |
74
+ | `hole-finder.md` | 通用 lens |
75
+ | `hole-finder-cost.md` | 成本 |
76
+ | `hole-finder-feasibility.md` | 可行性 |
77
+ | `hole-finder-safety.md` | 安全、併發、失敗態 |
78
+
79
+ > 上面那道 glob 的 `*` 前面沒有連字號是刻意的:`hole-finder-*.md` 配不到
80
+ > `hole-finder.md`,拿它去數卻期待四個,怎麼數都對不起來。
64
81
 
65
82
  **lens 缺了不要自己補寫**——它是 spoke 的 system prompt 來源,自己寫的版本會讓產出跟
66
83
  稽核判準對不上。
@@ -89,7 +106,7 @@ dowafu --version
89
106
 
90
107
  | 症狀 | 意思 | 怎麼回報 |
91
108
  | --- | --- | --- |
92
- | `Operation not permitted` | **沙箱擋的**,不是沒安裝。CLI 多半裝在家目錄底下,而沙箱預設不讀家目錄 | 照 host 的提示放行後重試。順帶告訴使用者:API key(`~/.config/dispatch/.env`)與對外網路同樣被擋,派工時一併要放行 |
109
+ | `Operation not permitted` | **沙箱擋的**,不是沒安裝。CLI 多半裝在家目錄底下,而沙箱預設不讀家目錄 | 照 host 的提示放行後重試。順帶告訴使用者:API key(`~/.config/dowafu/.env`)與對外網路同樣被擋,派工時一併要放行 |
93
110
  | `command not found` | 可能沒裝,也可能裝在 PATH 之外 | 問使用者 CLI 裝在哪(請他跑 `which dowafu`),**不要自己搜檔案系統** |
94
111
 
95
112
  **`command -v dowafu` 查不到不代表沒安裝**,別拿那個當判準。
@@ -70,7 +70,8 @@ description: 【Claude Code 內派專用;VS Code 環境改用 find-holes-exter
70
70
  裁這份清單時**逐題自問:「這一題的答案在哪個檔?那個檔在清單裡嗎?」**
71
71
  按 lens 的名稱配檔案(safety 就給安全相關的)會配錯——**lens 是看的角度,
72
72
  清單是看的材料**。清單對不準問題的答案位置,spoke 物理上不可能答對。
73
- **漏了是派工端的失誤,不是 spoke 的問題。**
73
+ **漏了是派工端的失誤,不是 spoke 的問題。每支 spoke 的清單各自對著自己的問題裁**
74
+ ——不要因為湊一份比較省事就給兩支同一份;掉的會是只有其中一支需要的那個檔。
74
75
 
75
76
  - **清單內把大檔排在最後**(依檔案大小遞增)。spoke 照清單順序讀檔,每輪會重送先前
76
77
  讀過的全部內容,所以排越前面被重複計費越多次,差距可以到將近一倍。
@@ -79,10 +80,26 @@ description: 【Claude Code 內派專用;VS Code 環境改用 find-holes-exter
79
80
  - **產出格式**:「觀察+依據(檔案:行號 或 推理)」的清單。**不得**給結論裁決、
80
81
  嚴重度分級、「應該改成」的替代設計、採用建議;不確定的寫成問題,不寫成缺陷。
81
82
 
82
- ### 4. 回收:先原文、後融合
83
+ ### 4. 回收——摘要優先,原文為例外
83
84
 
84
- 每個 spoke 的回報**逐個原文照登**,標明 lens 與模型,**不得刪節、改寫或只給總結**——
85
- 使用者必須看得到每個 spoke 各自說了什麼。
85
+ 每個 spoke 的回報都要讓使用者看到,標明 lens 與模型。**摘要是預設**,滿足以下四個
86
+ 必要條件:
87
+
88
+ 1. **明確宣告取捨**——在摘要開頭寫明「本節為摘要,非逐字照登」,不安靜地摘掉
89
+ 2. **判讀涵蓋每一條,無一略過**——每個 spoke 的每一條觀察都要在後面的「hub 判讀」出現,
90
+ 這是取代「原文照登」的可稽核性來源:原文不在你眼前列出,不代表它沒被看過
91
+ 3. **原文仍在這輪對話裡**——sub-agent 回傳的完整內容留在上下文中,使用者要求時可以
92
+ 重新貼出核對
93
+ 4. **只在原文還找得到時才成立**——原文若已不在上下文範圍內,就沒有底本可查
94
+
95
+ > 這條規則要防的不是「沒有原文」,是**防止 hub 只挑對自己有利的講**。滿足以上四件事,
96
+ > 手段換成摘要也一樣防得住;換句話說,要守住的是目的,不是「原文」這個手段本身。
97
+
98
+ **原文照登用於兩種情形**:原文已不在上下文裡(沒有底本可查,摘要不成立),或單份規模
99
+ 小到摘要反而多此一舉。除此之外都走摘要。
100
+
101
+ **做不到第 2 條就不要派這麼多 spoke**——問題出在派工規模,不在呈現方式;不要拿
102
+ 「沒把握」當理由退回整份照登。
86
103
 
87
104
  之後**另立「hub 判讀」一節**:去重,逐條標註你的初步判讀(成立/不成立+為什麼/
88
105
  需使用者裁決)。
@@ -87,8 +87,8 @@ dowafu --version
87
87
  **四、實跑用背景執行,進度靠檔案而不是終端機輸出。**
88
88
 
89
89
  用檔案讀取工具輪詢 `tmp/spoke/<ticket-id>/run.jsonl`:CLI 逐事件寫入該檔,讀得出跑到
90
- 哪一輪、有沒有 `round_error`,看到兩個 `spoke_end` 就是跑完。背景終端機的輸出不保證拿
91
- 得到,而單支 spoke 可能跑上好幾分鐘。
90
+ 哪一輪、有沒有 `round_error`。**完成判定見 §5**——「兩個 `spoke_end`」只是其中一半。
91
+ 背景終端機的輸出不保證拿得到,而單支 spoke 可能跑上好幾分鐘。
92
92
 
93
93
  > **把終端機當啟動器,不要當資料通道。**
94
94
 
@@ -132,6 +132,16 @@ dowafu --version
132
132
  換一批對準答案位置的檔案,同一個 lens 就抓得到。**這個自問是為了在派工前擋下落差,
133
133
  不是派工後才發現。**
134
134
 
135
+ ### 每支 spoke 各一張表——清單是按 lens 裁的,不是整批派工裁一次
136
+
137
+ **那張表每支 spoke 各填一張。** 每支的問題不同,清單就不同;因為「湊一份比較省事」而拿
138
+ 同一份餵兩支 lens,只有其中一支需要的那個檔就是這樣掉的。兩支最後真的拿到同一批檔也
139
+ 可以,但要說得出為什麼——**清單相同是結論,不是起點**。
140
+
141
+ **一份清單餵兩支 lens,會往交集收斂,而不是聯集。** 最先掉的是「只有其中一支需要」的檔,
142
+ 而那正是那支 lens 被派去看的東西;spoke 這時只能把缺口寫進「無法驗證」欄,而用這種方式
143
+ 發現要付一整輪派工的錢。為省錢裁清單是合理的——但要**各自對著自己的問題裁**。
144
+
135
145
  ### lens
136
146
 
137
147
  | agent | 視角 |
@@ -178,8 +188,10 @@ dowafu --version
178
188
  | hole-finder-feasibility | gemini | gemini-3.1-flash-lite | |
179
189
  ```
180
190
 
181
- 首行 `<!-- format: v1 -->` **必須有**;`model` 必填;`effort` 留白即可(自動用各家預設);
182
- 只列你要派的那幾個。
191
+ 首行 `<!-- format: v1 -->` **必須有**;`model` 必填;只列你要派的那幾個。`effort` 留白
192
+ 即為 **`high`**——四家的 `reasoning.default` 目前都是 `high`。要調就查
193
+ `providers.json` 的 `reasoning.allowed`,**各家值域不同**(例如 `deepseek` 沒有
194
+ `medium`);填了不在值域內的值會被擋下並列出允許值,不會靜默降級。
183
195
 
184
196
  ### `_shared.md`(所有 spoke 共用)
185
197
 
@@ -218,7 +230,9 @@ dowafu --version
218
230
  3. **允許清單要涵蓋「回答這些問題實際需要的檔案」。** 逐題自問:要回答這題得讀哪些檔?
219
231
  漏了的話 spoke 只能在「無法驗證」欄記下缺什麼——**那是派工端的失誤,不是它的問題**。
220
232
  路徑**相對 repo 根目錄**。留空也合法(純文字審查),但那樣就讀不到程式碼,會少掉
221
- 「文件說 X、`src/foo.ts:42` 其實是 Y」這類最有價值的發現。
233
+ 「文件說 X、`src/foo.ts:42` 其實是 Y」這類最有價值的發現。**清單屬於這一份
234
+ `<agent>.md`,不屬於整批派工**——不要把另一支的清單整份複製過來:對它有用、對這一支
235
+ 沒用的檔,在讀取順序裡是純負擔;反過來就是漏洞。
222
236
 
223
237
  4. **大的檔案排在清單最後——這能省掉一半成本。** spoke **嚴格照清單順序**讀檔,而多數
224
238
  模型**一輪只叫一個檔**,每一輪又會把先前讀過的全部內容重送一次。所以一個檔被重複
@@ -277,10 +291,24 @@ dowafu tmp/dispatch/<ticket-id> --repo-root . --dry-run
277
291
  | 允許清單估算 + 檔數 | spoke 讀得到哪些東西、多大 |
278
292
  | 最壞總消耗 | **這是上限不是預期值**(各 spoke 的 cap 加總),實際通常遠低於此 |
279
293
 
280
- 報表只給 token,不給金額——**要換算成錢就自己依型號價目算給他看**,別讓他自己猜。
294
+ **用自己的話、用表格轉述——除非你是逐位元組貼上原輸出,否則不要放進 code fence。**
295
+ code fence 的語意就是「這是工具印出來的」,把改寫過的內容裝進去,等於宣稱一個你其實
296
+ 沒做到的準確度。改寫沒有問題,而且常常比原輸出好讀;把改寫冒充成原輸出才有問題。
281
297
 
282
- 然後逐項核對:`repoRoot` 是這個專案、`model` 跟你寫的一致、`effort` 不是空的、估算
283
- token 量級合理、**沒有 `⚠ 輸出目錄…未被…忽略` 的警告**。
298
+ **要轉述就連限定語一起轉。** 報表裡那些限定語,正是讓數字不會被誤讀的部分——總量是上限
299
+ 而非預期值、價目取自哪一天、估算建立在什麼假設上、每支 lens 收尾句的確認行。它們看起來
300
+ 最像可以省的,卻正是讓這些數字能拿來做決定的東西。**限定語一刪,你交給使用者的數字就
301
+ 比工具給你的更硬。**
302
+
303
+ 報表只給 token,不給金額,且**只在乾跑階段成立**。要換算成錢,**價目來源是
304
+ `providers.json` 的 `pricing`(`inputPerM`/`cachedInputPerM`/`outputPerM`),不要查
305
+ 官網**——那份數字就是 CLI 計費用的,查官網會讓「你報的錢」與「CLI 算的錢」對不上。
306
+ `pricingSource.asOf` 看起來過舊就回報使用者,不要自行改數字(要改是改
307
+ `providers.json`)。**實跑**的 `summary.md` 有「估算成本」欄,每支 spoke 結束也會印
308
+ 一行 `cost=`,那是 CLI 自己算好的金額,**直接轉述即可,不必自己算**。
309
+
310
+ 然後逐項核對:`repoRoot` 是這個專案、`model` 跟你寫的一致、**報表印出的 `effort` 是
311
+ 你預期的那一級**、估算 token 量級合理、**沒有 `⚠ 輸出目錄…未被…忽略` 的警告**。
284
312
 
285
313
  任何一項不對就修工單重跑,**不要往下走**。
286
314
 
@@ -288,6 +316,21 @@ token 量級合理、**沒有 `⚠ 輸出目錄…未被…忽略` 的警告**
288
316
 
289
317
  ## 5. 實跑
290
318
 
319
+ **輸出目錄必須是一個還不存在的目錄。** 先看 `tmp/spoke/<ticket-id>/`:已經有東西了就
320
+ **換一個新的 ticket-id 派出去**——換 id 不花錢,碰撞這件事就不存在了。
321
+
322
+ **刪產物是使用者的決定,永遠不是你的。** 派工前不行、順手整理不行、「它擋路」也不行。
323
+ 你可以問要不要清掉,但不可以自己清,也不可以直接跑上去。那底下的東西是花錢買來的,
324
+ 而目錄本身不會告訴你使用者還要不要它。(第 7 節的清理是另一回事:那是**裁決之後**、
325
+ 他說了才做的。)
326
+
327
+ **這條有 CLI 撐著。** 目錄非空時它會在派工前中止——沒呼叫任何 API、沒花任何錢——
328
+ 並要你換一個 ticket-id。**沒有可以蓋過它的旗標**:清目錄是使用者的動作,
329
+ 不是你能帶的參數。
330
+
331
+ CLI 開跑時會清空 `run.jsonl`(舊事件會讓你數錯),**但那一步只在它真的跑起來時才執行**。
332
+ 啟動失敗的話,上一次的產物原封不動留著——而**檔案不會告訴你它是不是這一次的**。
333
+
291
334
  ```bash
292
335
  dowafu tmp/dispatch/<ticket-id> --repo-root . --yes
293
336
  ```
@@ -306,9 +349,37 @@ dowafu tmp/dispatch/<ticket-id> --repo-root . --yes
306
349
  > **前景執行本身另有外部工具的逾時上限**(十分鐘量級),與 `--timeout` 無關、擋不住。
307
350
  > **預估這次會跑久(清單大、問題多、選了較慢的型號)就改背景執行**,不要在前景硬等。
308
351
 
352
+ ### 啟動確認:先確認它真的跑起來了,再開始等
353
+
354
+ **下指令之前先記下當下時間**(`date -u +%Y-%m-%dT%H:%M:%SZ`),下完隔十幾秒讀一次
355
+ `tmp/spoke/<ticket-id>/run.jsonl`:
356
+
357
+ | 讀到 | 判定 |
358
+ | --- | --- |
359
+ | 檔案不存在 | **沒跑起來**——CLI 一開跑就會建它,這不是「還在等 API」 |
360
+ | 有 `spoke_start`,`ts` 晚於你下指令的時刻 | 起來了,開始輪詢 |
361
+ | 有內容,但 `ts` 早於你下指令的時刻 | **沒跑起來,你讀到的是上一次的產物** |
362
+
363
+ `ts` 是每一行都有的 ISO 8601 時間戳,**這是判斷「這一份是不是這一次」的唯一機械依據**。
364
+
365
+ **沒跑起來就不要繼續等。** 這個失敗形態自己看不出來:舊產物的兩個 `spoke_end` 都是
366
+ `succeeded`,格式、稽核欄、成本全部正常,唯一的破綻是時間戳。
367
+
368
+ ### 等多久算不正常
369
+
370
+ **看的不是總耗時,是 `run.jsonl` 有沒有在長。** 單支 spoke 跑上十分鐘是正常的,但
371
+ **十分鐘之內一定會有新事件寫進來**——單次 API 呼叫的逾時預設就是十分鐘,而逾時與重試
372
+ 各自都會寫一行 `round_error`。
373
+
374
+ > **超過十分鐘沒有任何新事件,就停下來告訴使用者**,不要繼續等、也不要自行重跑。
375
+ > 把最後一個事件與它的 `ts` 一起報給他,讓他判斷。
376
+
309
377
  **進度一律以 `run.jsonl` 為準**——CLI 逐事件寫入該檔,中途 Ctrl-C 也讀得出跑到哪。
310
378
  終端機那條通道可不可靠依 host 而定(見 §1.5),`run.jsonl` 不受影響。
311
379
 
380
+ **跑完的判定是兩件事同時成立**:兩個 `spoke_end`,**而且**這份 `run.jsonl` 通過了上面的
381
+ 啟動確認。只看 `spoke_end` 會把上一次的產物當成這一次的結果。
382
+
312
383
  **真的撞到失敗或逾時:要不要重跑由使用者決定,不要自行重跑。** 重跑是重新付費,中斷前已
313
384
  花的錢救不回來(沒有 resume 機制)。把中斷時的狀態(`run.jsonl` 讀到哪、失敗訊息)告訴
314
385
  使用者,讓他判斷要不要重來——這與「零讀取就整份重跑」不衝突:那條是「判定沒跑」,這裡是
@@ -316,26 +387,43 @@ dowafu tmp/dispatch/<ticket-id> --repo-root . --yes
316
387
 
317
388
  ---
318
389
 
319
- ## 6. 回收——先原文、後融合
390
+ ## 6. 回收——摘要優先,原文為例外
320
391
 
321
392
  產物在 `tmp/spoke/<ticket-id>/`:`<agent>.md`(原文)、`summary.md`(稽核表)、
322
393
  `run.jsonl`(執行記錄)、`raw/`(完整請求與回應)。
323
394
 
395
+ > **回收之前先確認 §5 的啟動確認過了。** 這個目錄底下的任何檔案,都不會告訴你它是不是
396
+ > 這一次的產物。
397
+
324
398
  **呈現順序不得更動**:
325
399
 
326
- 1. **逐個 spoke 的內容都要讓使用者看到**,標明 lens 與模型。規模小(單份幾十行)就直接
327
- 原文照登。**規模大到不適合整份貼出時**,可以摘要,但四件事缺一不可:
400
+ 1. **逐個 spoke 的內容都要讓使用者看到**,標明 lens 與模型。**摘要是預設**,滿足以下
401
+ 四個必要條件:
328
402
  1. **明確宣告取捨**——在摘要開頭寫明「本節為摘要,非逐字照登」,不安靜地摘掉
329
403
  2. **判讀涵蓋每一條,無一略過**——每個 spoke 的每一條觀察都要在後面的「hub 判讀」出現,
330
404
  這是取代「原文照登」的可稽核性來源:原文不在你眼前列出,不代表它沒被看過
331
405
  3. **指出原文路徑**(`tmp/spoke/<ticket-id>/<agent>.md`),使用者隨時可自行比對
332
- 4. **只在產物尚未清理時才成立**——第 7 節清掉之後就沒有底本可查,那個情境下原文照登
333
- 仍是唯一選擇,不能摘要
406
+ 4. **只在產物尚未清理時才成立**——第 7 節清掉之後就沒有底本可查
334
407
  > 這條規則要防的不是「沒有原文」,是**防止 hub 只挑對自己有利的講**。滿足以上四件事,
335
408
  > 手段換成摘要也一樣防得住;換句話說,要守住的是目的,不是「原文」這個手段本身。
336
409
 
337
- > **沒有把握做到這四件事,就不要摘要——整份照登。**
338
- 2. **`summary.md` 的稽核表併進原文區一併照登。**
410
+ **原文照登用於兩種情形**:產物已被第 7 節清掉(沒有底本可查,摘要不成立),或單份
411
+ 規模小到摘要反而多此一舉。除此之外都走摘要。
412
+
413
+ **做不到第 2 條就不要派這麼多 spoke**——問題出在派工規模,不在呈現方式;不要拿
414
+ 「沒把握」當理由退回整份照登。
415
+ 2. **`summary.md` 的稽核欄轉述進上面那一節——它印出來的每一段都要點名。** 每支 spoke
416
+ 一段一行,**順序照 `summary.md` 印的**:`工具呼叫` 在最前面,排在 `收尾句` 之前。
417
+ 某一段是空的時候,CLI 會自己印它的空值字(中文那套印 `無`),**照它印的抄,
418
+ 而且一段都不准漏掉**。全部寫出來不比只寫大部分貴,而少一個段名使用者看得出來,
419
+ 少一列他看不出來。
420
+
421
+ **`⚠` 開頭的那幾段不准省,`(無法稽核)` 也不准省。** 它們不是那幾個欄位以外的裝飾,
422
+ 是稽核在告訴你出事了;省掉它們,使用者手上就只剩下說「沒事」的那幾段。
423
+
424
+ **有內容的段落一律逐字照抄。** 只有 `pass` 與 CLI 自己的空值字可以壓縮。說「沒事」的
425
+ 段落留著最省事,說「有事」的那段才是會被刪掉的——所以這條規則是刻意不對稱的:
426
+ **一段講的東西越多,你對它的處置自由越少。**
339
427
  3. **之後另立「hub 判讀」一節**——去重,逐條標註你的初步判讀(成立/不成立+為什麼/
340
428
  需使用者裁決)。
341
429
 
@@ -367,8 +455,8 @@ dowafu tmp/dispatch/<ticket-id> --repo-root . --yes
367
455
 
368
456
  | 欄位 | 意思 |
369
457
  | --- | --- |
458
+ | `工具呼叫:N(允許 N/拒絕 N)` | 讀了幾次、幾次被拒——`run.jsonl` `toolCalls[]` 的統計。**印在最前面**,排在收尾句之前 |
370
459
  | `收尾句` | pass/fail |
371
- | `工具呼叫:N(允許 N/拒絕 N)` | 讀了幾次、幾次被拒——`run.jsonl` `toolCalls[]` 的統計 |
372
460
  | `觀察:N` | 條數;`無法計數` 表示格式無法辨識,翻原文確認 |
373
461
  | `清單外引用` | 引用了允許清單外的路徑,**可能是臆測**——對照 `toolCalls[]` 判斷。**此欄至今沒抓到過真正的幻覺**:抓到的多半是 spoke 照抄素材的縮寫路徑、轉述素材裡提過但自己標明讀不到的路徑,或絕對/相對路徑混用。先查該路徑是不是出現在你自己的 `_shared.md` 裡,或只是寫法不同,不要預設是編的 |
374
462
  | `無法驗證欄` | 有沒有照模板寫這一節。**判讀產出品質時看這欄有多具體**(例如逐項標明哪個結論依賴哪個不可讀的檔案),不要看觀察條數——條數不可靠,重疊、灌水都會推高條數但不代表品質 |
@@ -414,6 +502,9 @@ dowafu tmp/dispatch/<ticket-id> --repo-root . --yes
414
502
  rm -rf tmp/spoke/<ticket-id> tmp/dispatch/<ticket-id>
415
503
  ```
416
504
 
505
+ **這是整套流程裡唯一會刪東西的地方,而且要等使用者裁決完、他說了才刪。** 其他任何時候
506
+ ——包括派工前那個擋路的目錄——一律照第 5 節:可以問,不可以自己刪。
507
+
417
508
  lens 定義留著,下次還會用。
418
509
 
419
510
  ---
@@ -427,7 +518,7 @@ lens 定義留著,下次還會用。
427
518
  | 真失敗或逾時 | **要不要重跑問使用者,不要自行重跑**——重跑是重新付費,中斷前的花費救不回來 |
428
519
  | 撞 429 | 加 `--concurrency 1` 重跑 |
429
520
  | 報告品質差 | 換一個型號重跑,或同一組設定再跑一次取聯集(見第 3 節「跑 2–3 次怎麼做」) |
430
- | 缺 API key | 使用者要在 `~/.config/dispatch/.env` 設定,**你不要去碰那個檔** |
521
+ | 缺 API key | 使用者要在 `~/.config/dowafu/.env` 設定,**你不要去碰那個檔** |
431
522
  | 找不到 `dowafu` | **查不到不代表沒安裝**——見 §1.5,多半是沙箱不讀家目錄 |
432
523
  | `Operation not permitted` | host 的沙箱擋的,**不是工單或指令的問題**。照提示放行再跑一次 |
433
524
 
@@ -45,7 +45,7 @@ grep -n "規劃→實作→驗收流程規範" CLAUDE.md AGENTS.md workflow_spec
45
45
 
46
46
  echo "=== skill 與 lens ==="
47
47
  ls .claude/skills/ 2>/dev/null
48
- ls .claude/agents/hole-finder-*.md 2>/dev/null
48
+ ls .claude/agents/hole-finder*.md 2>/dev/null
49
49
 
50
50
  echo "=== tmp/ ==="
51
51
  git check-ignore -q tmp && echo "已忽略" || echo "未忽略"
@@ -62,6 +62,11 @@ git check-ignore -q tmp && echo "已忽略" || echo "未忽略"
62
62
  讀完在回報裡註明「規範不在自動載入範圍內,本次是手動讀取的」——讓使用者知道換個
63
63
  session 又會漏掉。
64
64
 
65
+ **找到不只一份,就要講明哪一份會被自動載入。** 專案可能本來就把這一章內嵌在入口檔裡,
66
+ 而根目錄又多一份 `workflow_spec.md`——兩份的語言還可能不同(語言套件各裝各的)。
67
+ 這時只回報「內容讀得到」不夠:要指出**你 context 裡的是哪一份**,以及兩份之間沒有任何
68
+ 同步機制。自動載入的那一份,才是之後每個 session 真正生效的那一份。
69
+
65
70
  > **為什麼會讀不到?** 入口檔因 host 而異:Claude Code 讀 `CLAUDE.md`(**不讀
66
71
  > `AGENTS.md`**),其他 host 多半讀 repo 根的 `AGENTS.md`。而 `@xxx.md` 是 Claude Code
67
72
  > 的 import 語法,**別的 host 不會展開它**——那時你看到的只是一行字,規範內容從來沒進
@@ -69,8 +74,20 @@ session 又會漏掉。
69
74
 
70
75
  ### skill 與 lens
71
76
 
72
- `.claude/skills/find-holes-external/` 與 `.claude/agents/hole-finder-*.md` 在不在。
73
- (後者 glob 會列出通用的 `hole-finder.md` 加三個 lens,四個都算正常。)
77
+ `.claude/skills/find-holes-external/` 與 lens 定義在不在。**回報你實際看到的檔名,
78
+ 缺哪個就點名**——「看到三個」不算檢查,是哪三個才算。
79
+
80
+ 應該有四個,四個都算正常:
81
+
82
+ | 檔 | 是什麼 |
83
+ | --- | --- |
84
+ | `hole-finder.md` | 通用 lens |
85
+ | `hole-finder-cost.md` | 成本 |
86
+ | `hole-finder-feasibility.md` | 可行性 |
87
+ | `hole-finder-safety.md` | 安全、併發、失敗態 |
88
+
89
+ > 上面那道 glob 的 `*` 前面沒有連字號是刻意的:`hole-finder-*.md` 配不到
90
+ > `hole-finder.md`,拿它去數卻期待四個,怎麼數都對不起來。
74
91
 
75
92
  **lens 缺了不要自己補寫**——它是 spoke 的 system prompt 來源,自己寫的版本會讓產出跟
76
93
  稽核判準對不上。
@@ -158,7 +175,7 @@ dowafu --version
158
175
 
159
176
  | 症狀 | 意思 | 怎麼回報 |
160
177
  | --- | --- | --- |
161
- | `Operation not permitted` | **沙箱擋的**,不是沒安裝。CLI 多半裝在家目錄底下,而沙箱預設不讀家目錄 | 照 host 的提示放行後重試。順帶告訴使用者:API key(`~/.config/dispatch/.env`)與對外網路同樣被擋,派工時一併要放行 |
178
+ | `Operation not permitted` | **沙箱擋的**,不是沒安裝。CLI 多半裝在家目錄底下,而沙箱預設不讀家目錄 | 照 host 的提示放行後重試。順帶告訴使用者:API key(`~/.config/dowafu/.env`)與對外網路同樣被擋,派工時一併要放行 |
162
179
  | `command not found` | 可能沒裝,也可能裝在 PATH 之外 | 問使用者 CLI 裝在哪(請他跑 `which dowafu`),**不要自己搜檔案系統** |
163
180
 
164
181
  **`command -v dowafu` 查不到不代表沒安裝**,別拿那個當判準。
@@ -65,6 +65,22 @@ import 機制**。兩種讀者得分別交代,否則其中一邊會靜默漏
65
65
 
66
66
  目標專案若沒有 `CLAUDE.md`,另建一個、內容一行 `@AGENTS.md` 即可(官方建議做法)。
67
67
 
68
+ ### 專案本來就有一章流程規範時
69
+
70
+ 很多專案本來就有一份,直接貼在入口檔裡——而且語言可能跟你剛裝的這套不同,因為每個語言
71
+ 套件各帶各的 `workflow_spec.md`。就這樣把檔案複製進去,專案裡會變成**兩份、而且沒有任何
72
+ 機制保證同步**;真正管到每個 session 的是入口檔會自動載入的那一份——**是本來就在的那章,
73
+ 不是你剛裝的那個檔**。
74
+
75
+ 二選一,另一份要拿掉:
76
+
77
+ - **留 `workflow_spec.md`**(建議):把入口檔裡舊的那一章刪掉,換成上面那段 import
78
+ - **留內嵌的那一章**:把它的內容換成你要統一的語言那份 `workflow_spec.md` 的內容,
79
+ 並且**不要**把 `workflow_spec.md` 複製進專案
80
+
81
+ 不論走哪一條,專案裡最後**只留一份**。裝完跑一次 `preflight`——它會回報實際在 context
82
+ 裡的是哪一份,所以這個決定就算漏掉了,還有一道會抓到。
83
+
68
84
  ## 這裡不准出現的東西
69
85
 
70
86
  `publish/` 的內容會在一個不知道來源專案存在的地方執行,所以不得出現絕對路徑、來源專案的