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.
- package/README.md +44 -25
- package/README_zh-tw.md +197 -0
- package/dist/adapters/anthropic-messages.js +18 -11
- package/dist/adapters/gemini-native.js +19 -16
- package/dist/adapters/read-file-tool-description.js +5 -0
- package/dist/adapters/responses.js +22 -14
- package/dist/audit.js +17 -1
- package/dist/cli-args.js +88 -38
- package/dist/cli.js +60 -43
- package/dist/dispatch-home.js +24 -6
- package/dist/gate.js +3 -2
- package/dist/mask.js +16 -3
- package/dist/messages.js +342 -0
- package/dist/output.js +60 -37
- package/dist/prompt.js +5 -3
- package/dist/providers.js +46 -34
- package/dist/raw-integrity.js +6 -5
- package/dist/report.js +76 -22
- package/dist/runner.js +21 -10
- package/dist/ticket.js +27 -25
- package/dist/validate.js +21 -20
- package/dist/whitelist.js +9 -1
- package/package.json +3 -2
- package/publish/en/.agents/skills/find-holes-external/SKILL.md +394 -0
- package/publish/en/.agents/skills/preflight/SKILL.md +120 -0
- package/publish/en/.agents/skills/wrap/SKILL.md +64 -0
- package/publish/en/.claude/agents/explore-haiku.md +8 -0
- package/publish/en/.claude/agents/hole-finder-cost.md +15 -0
- package/publish/en/.claude/agents/hole-finder-feasibility.md +15 -0
- package/publish/en/.claude/agents/hole-finder-safety.md +15 -0
- package/publish/en/.claude/agents/hole-finder.md +14 -0
- package/publish/en/.claude/skills/find-holes/SKILL.md +109 -0
- package/publish/en/.claude/skills/find-holes-external/SKILL.md +407 -0
- package/publish/en/.claude/skills/preflight/SKILL.md +179 -0
- package/publish/en/.claude/skills/wrap/SKILL.md +61 -0
- package/publish/en/README.md +106 -0
- package/publish/en/workflow_spec.md +71 -0
- package/publish/{.agents → zh-tw/.agents}/skills/find-holes-external/SKILL.md +109 -18
- package/publish/{.agents → zh-tw/.agents}/skills/preflight/SKILL.md +22 -5
- package/publish/{.claude → zh-tw/.claude}/skills/find-holes/SKILL.md +21 -4
- package/publish/{.claude → zh-tw/.claude}/skills/find-holes-external/SKILL.md +108 -17
- package/publish/{.claude → zh-tw/.claude}/skills/preflight/SKILL.md +21 -4
- package/publish/{README.md → zh-tw/README.md} +16 -0
- /package/publish/{.agents → zh-tw/.agents}/skills/wrap/SKILL.md +0 -0
- /package/publish/{.claude → zh-tw/.claude}/agents/explore-haiku.md +0 -0
- /package/publish/{.claude → zh-tw/.claude}/agents/hole-finder-cost.md +0 -0
- /package/publish/{.claude → zh-tw/.claude}/agents/hole-finder-feasibility.md +0 -0
- /package/publish/{.claude → zh-tw/.claude}/agents/hole-finder-safety.md +0 -0
- /package/publish/{.claude → zh-tw/.claude}/agents/hole-finder.md +0 -0
- /package/publish/{.claude → zh-tw/.claude}/skills/wrap/SKILL.md +0 -0
- /package/publish/{workflow_spec.md → zh-tw/workflow_spec.md} +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# dowafu
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**English** | [繁體中文](https://github.com/eyesofkids/dowafu/blob/main/README_zh-tw.md)
|
|
4
|
+
|
|
5
|
+
A read-only review harness: it sends a section of a document to external models, decides
|
|
6
|
+
what each of them may read, records what they did, and audits the shape of what they
|
|
7
|
+
returned.
|
|
4
8
|
|
|
5
9
|
You write a ticket. `dowafu` calls each provider's API. Each reviewer — a *spoke* —
|
|
6
10
|
reads only the files you whitelisted, and returns observations with the evidence it
|
|
@@ -8,16 +12,24 @@ read them from. Everything lands on disk for you to check.
|
|
|
8
12
|
|
|
9
13
|
**Spokes produce observations, not verdicts.** What to do about them stays with you.
|
|
10
14
|
|
|
11
|
-
> ###
|
|
15
|
+
> ### English and Traditional Chinese are both fully supported.
|
|
16
|
+
>
|
|
17
|
+
> One flag decides the language for the whole run: `--lang en` or `--lang zh-tw`. Without
|
|
18
|
+
> it, `DISPATCH_LANG` applies; without that either, the default is **English**. The flag
|
|
19
|
+
> wins over the environment variable, and an unrecognized value in either is rejected
|
|
20
|
+
> rather than guessed at.
|
|
12
21
|
>
|
|
13
|
-
>
|
|
14
|
-
>
|
|
15
|
-
>
|
|
16
|
-
>
|
|
17
|
-
> it resolved to, per reviewer.
|
|
22
|
+
> The language reaches everything: the reviewer's prompt and report template, the audit
|
|
23
|
+
> that checks the report against it, and the CLI's own output — `--help`, error messages,
|
|
24
|
+
> the dry-run report, `summary.md`. The dry run prints the resolved language per reviewer,
|
|
25
|
+
> so you can see it before anything is sent.
|
|
18
26
|
>
|
|
19
|
-
>
|
|
20
|
-
>
|
|
27
|
+
> A ticket's section headings may be written in either language regardless — they are
|
|
28
|
+
> field names, not a language switch. See [Ticket format](#ticket-format).
|
|
29
|
+
>
|
|
30
|
+
> The skills and reviewer definitions come in both languages too, under `publish/en/` and
|
|
31
|
+
> `publish/zh-tw/`. **Install one or the other, never a mix** — a reviewer's closing line
|
|
32
|
+
> has to match the template the audit checks it against.
|
|
21
33
|
|
|
22
34
|
## Install
|
|
23
35
|
|
|
@@ -29,26 +41,27 @@ The command is `dowafu`.
|
|
|
29
41
|
|
|
30
42
|
## API keys
|
|
31
43
|
|
|
32
|
-
Keys are read from `$DISPATCH_HOME/.env`, which defaults to `~/.config/
|
|
44
|
+
Keys are read from `$DISPATCH_HOME/.env`, which defaults to `~/.config/dowafu/.env`
|
|
33
45
|
(`DISPATCH_HOME` or `XDG_CONFIG_HOME` override it). Variables already present in the
|
|
34
46
|
environment win over the file, so CI and one-off overrides need no file at all.
|
|
35
47
|
|
|
36
48
|
```bash
|
|
37
|
-
mkdir -p ~/.config/
|
|
38
|
-
cat > ~/.config/
|
|
49
|
+
mkdir -p ~/.config/dowafu
|
|
50
|
+
cat > ~/.config/dowafu/.env <<'EOF'
|
|
39
51
|
DEEPSEEK_API_KEY=
|
|
40
52
|
GEMINI_API_KEY=
|
|
41
53
|
OPENAI_API_KEY=
|
|
42
54
|
ANTHROPIC_API_KEY=
|
|
43
55
|
EOF
|
|
44
|
-
chmod 600 ~/.config/
|
|
56
|
+
chmod 600 ~/.config/dowafu/.env
|
|
45
57
|
```
|
|
46
58
|
|
|
47
59
|
Only the providers you actually dispatch to need a key. The file is plain text — it is
|
|
48
60
|
protected by nothing but its file permissions.
|
|
49
61
|
|
|
50
|
-
**The current directory's `.env` is never read.** That
|
|
51
|
-
review
|
|
62
|
+
**The current directory's `.env` is never read.** That is where you ran the command, and
|
|
63
|
+
usually the project under review; its secrets have no business in a process that is
|
|
64
|
+
talking to external APIs.
|
|
52
65
|
|
|
53
66
|
## Usage
|
|
54
67
|
|
|
@@ -101,9 +114,10 @@ parser matches** — use one of the two sets below, exactly as written.
|
|
|
101
114
|
- prisma/schema.prisma
|
|
102
115
|
```
|
|
103
116
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
117
|
+
Either set is accepted, and the choice does **not** decide the reviewer's language —
|
|
118
|
+
that comes from `--lang` / `DISPATCH_LANG` (see above). The two sets are aliases for the
|
|
119
|
+
same fields, so an English ticket can run in Chinese and vice versa. Mixing both sets
|
|
120
|
+
inside one reviewer's file is not supported — the first heading that matches wins.
|
|
107
121
|
|
|
108
122
|
Reviewer definitions live in `.claude/agents/<agent>.md` under the repo root — they are
|
|
109
123
|
the source of each spoke's system prompt, and the CLI reads them directly. Results are
|
|
@@ -117,7 +131,7 @@ requests and responses.
|
|
|
117
131
|
refused, and the refusal is recorded.
|
|
118
132
|
- **`_docs/` is off limits**, whatever the whitelist says.
|
|
119
133
|
- **Nothing is billed before you confirm.** The dry run prints the resolved repo root,
|
|
120
|
-
|
|
134
|
+
each reviewer's model and language, token estimates and the output path, and calls no API.
|
|
121
135
|
- **Secrets are masked** in `run.jsonl`, `raw/*.json` and stdout.
|
|
122
136
|
- **Failures stop the run.** A missing key, an unknown model, a file that does not
|
|
123
137
|
exist — each aborts with the path or name that caused it, before any spend.
|
|
@@ -141,17 +155,22 @@ an agent working there knows how to write a ticket, what to check before spendin
|
|
|
141
155
|
how to read the results. Copy both directories — `.claude/` holds the reviewer
|
|
142
156
|
definitions the CLI itself reads, so it is required no matter which agent you use.
|
|
143
157
|
|
|
158
|
+
It ships in both languages, `publish/en/` and `publish/zh-tw/`. **Pick one.** A mix does
|
|
159
|
+
not work: a reviewer's fixed closing line has to match the template the audit checks it
|
|
160
|
+
against.
|
|
161
|
+
|
|
144
162
|
```bash
|
|
145
163
|
TARGET=<your project>
|
|
164
|
+
SRC=publish/en # or publish/zh-tw
|
|
146
165
|
mkdir -p "$TARGET/.claude/skills" "$TARGET/.claude/agents" "$TARGET/.agents/skills"
|
|
147
|
-
cp -R
|
|
148
|
-
cp -R
|
|
149
|
-
cp
|
|
150
|
-
cp
|
|
166
|
+
cp -R "$SRC/.claude/skills/." "$TARGET/.claude/skills/"
|
|
167
|
+
cp -R "$SRC/.agents/skills/." "$TARGET/.agents/skills/"
|
|
168
|
+
cp "$SRC"/.claude/agents/*.md "$TARGET/.claude/agents/"
|
|
169
|
+
cp "$SRC/workflow_spec.md" "$TARGET/"
|
|
151
170
|
```
|
|
152
171
|
|
|
153
|
-
See `publish/README.md`
|
|
154
|
-
|
|
172
|
+
See `publish/en/README.md` — or `publish/zh-tw/README.md` — for the details, each written
|
|
173
|
+
in its own language.
|
|
155
174
|
|
|
156
175
|
## License
|
|
157
176
|
|
package/README_zh-tw.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# dowafu
|
|
2
|
+
|
|
3
|
+
[English](https://github.com/eyesofkids/dowafu/blob/main/README.md) | **繁體中文**
|
|
4
|
+
|
|
5
|
+
**一支唯讀審查用的 spoke harness**:把文件段落派給外部模型,限定它讀什麼、記下它做了什麼、機械稽核它的回報格式。
|
|
6
|
+
|
|
7
|
+
講具體一點——你(與AI agent協同)寫一份工單,`dowafu` 呼叫各家 provider 的 API;每個審查者(*spoke*)**只讀你列進白名單的檔案**,回傳帶依據的觀察,全部落檔供你查核。
|
|
8
|
+
|
|
9
|
+
相容於各種支援標準SKILL的AI Agent程式與VS Code擴充套件包含以下,但還有更多:
|
|
10
|
+
|
|
11
|
+
- Claude Code
|
|
12
|
+
- VS Code Codepilot
|
|
13
|
+
|
|
14
|
+
**spoke 產出的是意見,不是裁決。** 那些意見要怎麼處理,仍然由你決定。
|
|
15
|
+
|
|
16
|
+
> ### 英文與繁體中文都是完整支援的語言。
|
|
17
|
+
>
|
|
18
|
+
> 整次執行的語言由一個旗標決定:`--lang en` 或 `--lang zh-tw`。沒帶旗標時看 `DISPATCH_LANG`,兩者都沒有時**預設為英文**。旗標優先於環境變數;任一方填了無法辨識的值都會直接中止,不會猜。
|
|
19
|
+
>
|
|
20
|
+
> 語言涵蓋所有地方:審查者收到的 Prompt 與報告範本、稽核用來檢查報告的範本,以及 CLI 自己的輸出——`--help`、錯誤訊息、dry-run 報告、`summary.md`。Dry run 會針對每個審查者印出判定的語言,送出前就看得到。
|
|
21
|
+
>
|
|
22
|
+
> 工單的段落標題兩種語言都可以寫,與語言選擇無關——它們是欄位名稱,不是語言開關。見[工單格式](#工單格式)。
|
|
23
|
+
>
|
|
24
|
+
> Skill 與審查者定義同樣有兩種語言,分別放在 `publish/en/` 與 `publish/zh-tw/`。**擇一安裝,不可混裝**——審查者的固定收尾句必須與稽核檢查用的範本是同一種語言。
|
|
25
|
+
|
|
26
|
+
## 安裝
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install -g dowafu
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
執行指令為 `dowafu`。
|
|
33
|
+
|
|
34
|
+
## API Keys
|
|
35
|
+
|
|
36
|
+
API Key 會從 `$DISPATCH_HOME/.env` 讀取,預設位置為:
|
|
37
|
+
|
|
38
|
+
`~/.config/dowafu/.env`
|
|
39
|
+
|
|
40
|
+
可以透過 `DISPATCH_HOME` 或 `XDG_CONFIG_HOME` 覆寫這個位置。
|
|
41
|
+
|
|
42
|
+
如果環境變數中已經存在相同的變數,環境變數會優先於檔案中的值。因此在 CI 或臨時覆寫設定時,完全不需要建立 `.env` 檔案。
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
mkdir -p ~/.config/dowafu
|
|
46
|
+
cat > ~/.config/dowafu/.env <<'EOF'
|
|
47
|
+
DEEPSEEK_API_KEY=
|
|
48
|
+
GEMINI_API_KEY=
|
|
49
|
+
OPENAI_API_KEY=
|
|
50
|
+
ANTHROPIC_API_KEY=
|
|
51
|
+
EOF
|
|
52
|
+
chmod 600 ~/.config/dowafu/.env
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
只需要為實際要執行 Dispatch 的 Provider 設定 Key 即可。
|
|
56
|
+
|
|
57
|
+
這個檔案是純文字檔——它唯一的保護機制就是檔案權限。
|
|
58
|
+
|
|
59
|
+
**永遠不會讀取目前工作目錄的 `.env`。**
|
|
60
|
+
|
|
61
|
+
那個目錄是你下指令的地方,通常就是被審查的專案;它的 secrets 不該被一個正對外部 API 發請求的行程讀進去。
|
|
62
|
+
|
|
63
|
+
## 使用方式
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
dowafu <ticket-dir> --dry-run # 解析、驗證、估算。不呼叫 API,也不產生成本。
|
|
67
|
+
dowafu <ticket-dir> --yes # 執行審查。這個操作會產生成本。
|
|
68
|
+
dowafu --help # 查看所有旗標
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
沒有指定 `--yes` 時,指令會要求使用者確認。
|
|
72
|
+
|
|
73
|
+
當 stdin 不是 TTY 時——例如由 Agent 代替你執行——沒有人可以回答確認,因此程式會在呼叫任何 API 之前停止。
|
|
74
|
+
|
|
75
|
+
## 工單
|
|
76
|
+
|
|
77
|
+
工單是一個目錄,其中包含三種檔案。
|
|
78
|
+
|
|
79
|
+
其中的標題是**字面標記(literal markers)**,Parser 會直接比對這些標記——必須完全使用以下兩組標題之一。
|
|
80
|
+
|
|
81
|
+
| English | 中文 |
|
|
82
|
+
| --- | --- |
|
|
83
|
+
| `# Questions` | `# 具體問題` |
|
|
84
|
+
| `# Allowed reads` | `# 允許讀取` |
|
|
85
|
+
| `# Under review` | `# 待審段落` |
|
|
86
|
+
| `# Premises` | `# 前提(不受審)` |
|
|
87
|
+
|
|
88
|
+
| 檔案 | 內容 |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| `_dispatch.md` | 要執行哪些審查者,以及各自使用哪個 Provider 與 Model |
|
|
91
|
+
| `_shared.md` | 前提與正在審查的段落,以原始內容直接貼入 |
|
|
92
|
+
| `<agent>.md` | 每個審查者一份:包含它的問題,以及允許讀取的檔案 |
|
|
93
|
+
|
|
94
|
+
```markdown
|
|
95
|
+
<!-- _dispatch.md -->
|
|
96
|
+
<!-- format: v1 -->
|
|
97
|
+
# dispatch auth-review
|
|
98
|
+
|
|
99
|
+
| agent | provider | model | effort |
|
|
100
|
+
| --- | --- | --- | --- |
|
|
101
|
+
| hole-finder-safety | deepseek | deepseek-v4-flash | |
|
|
102
|
+
| hole-finder-feasibility | openai | gpt-5.6-luna | |
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```markdown
|
|
106
|
+
<!-- hole-finder-safety.md -->
|
|
107
|
+
# Questions
|
|
108
|
+
1. Does the permission check described here hold under concurrent requests?
|
|
109
|
+
|
|
110
|
+
# Allowed reads
|
|
111
|
+
- lib/auth-guard.ts
|
|
112
|
+
- prisma/schema.prisma
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
兩組標題都收,而且**與審查者使用的語言無關**——語言由 `--lang`/`DISPATCH_LANG` 決定(見上方)。兩組只是同一批欄位的別名,所以英文工單可以用中文跑,反之亦然。
|
|
116
|
+
|
|
117
|
+
不支援在同一個審查者檔案中混用兩組標題——第一個符合的標題會決定用哪一組欄位名,不是決定語言。
|
|
118
|
+
|
|
119
|
+
審查者定義放在 Repository 根目錄下的:
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
.claude/agents/<agent>.md
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
它們是每個 spoke 的 System Prompt 來源,CLI 會直接讀取這些檔案。
|
|
126
|
+
|
|
127
|
+
結果會寫入:
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
tmp/spoke/<ticket-id>/
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
其中包含:
|
|
134
|
+
|
|
135
|
+
- 每個 spoke 的審查報告
|
|
136
|
+
- `summary.md`:包含稽核表格與預估成本
|
|
137
|
+
- `run.jsonl`:每個事件一行
|
|
138
|
+
- `raw/`:精確保存實際送出的 Request 與收到的 Response
|
|
139
|
+
|
|
140
|
+
## 工具保證的事項
|
|
141
|
+
|
|
142
|
+
- **每次呼叫的讀取範圍都有白名單限制。** 如果 spoke 要求讀取白名單以外的檔案,會被拒絕,而且拒絕事件會被記錄。
|
|
143
|
+
- **`_docs/` 永遠禁止讀取。** 不論白名單如何設定。
|
|
144
|
+
- **在你確認之前不會產生任何費用。** Dry run 會列出解析後的 repo 根目錄、每個 spoke 的模型與語言、token 預估值與輸出路徑,而且不會呼叫任何 API。
|
|
145
|
+
- **Secrets 會被遮罩。** `run.jsonl`、`raw/*.json` 以及 stdout 中都會遮罩 Secrets。
|
|
146
|
+
- **發生錯誤就停止整個執行流程。** 例如缺少 API Key、未知的 Model、指定的檔案不存在等,都會在產生任何費用之前中止,並指出造成問題的路徑或名稱。
|
|
147
|
+
|
|
148
|
+
## Models
|
|
149
|
+
|
|
150
|
+
| Provider | Model |
|
|
151
|
+
| --- | --- |
|
|
152
|
+
| `openai` | `gpt-5.6-luna`、`gpt-5.6-terra`、`gpt-5.6-sol` |
|
|
153
|
+
| `deepseek` | `deepseek-v4-flash` |
|
|
154
|
+
| `gemini` | `gemini-3.1-flash-lite`、`gemini-3.5-flash-lite`、`gemini-3.6-flash` |
|
|
155
|
+
| `anthropic` | `claude-opus-5`、`claude-sonnet-5` |
|
|
156
|
+
|
|
157
|
+
這份清單會隨套件以 `providers.json` 一起提供。
|
|
158
|
+
|
|
159
|
+
如果想使用其他 Model,可以透過 `--providers` 指定自己的 Provider 設定檔。
|
|
160
|
+
|
|
161
|
+
## 從 Agent 驅動
|
|
162
|
+
|
|
163
|
+
`publish/` 包含可以複製到專案中的 skill 與審查者定義,讓在該專案中工作的 Agent 知道:
|
|
164
|
+
|
|
165
|
+
- 如何寫工單
|
|
166
|
+
- 在產生成本之前要檢查什麼
|
|
167
|
+
- 如何讀取審查結果
|
|
168
|
+
|
|
169
|
+
請將兩個目錄都複製過去。
|
|
170
|
+
|
|
171
|
+
其中 `.claude/` 包含 CLI 本身會讀取的審查者定義,因此無論你使用哪一種 Agent,都必須複製它。
|
|
172
|
+
|
|
173
|
+
`publish/` 提供兩種語言:`publish/en/` 與 `publish/zh-tw/`。**擇一複製,不可混裝**——審查者的固定收尾句必須與稽核檢查用的範本是同一種語言。
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
TARGET=<your project>
|
|
177
|
+
SRC=publish/zh-tw # 或 publish/en
|
|
178
|
+
|
|
179
|
+
mkdir -p "$TARGET/.claude/skills" "$TARGET/.claude/agents" "$TARGET/.agents/skills"
|
|
180
|
+
|
|
181
|
+
cp -R "$SRC/.claude/skills/." "$TARGET/.claude/skills/"
|
|
182
|
+
cp -R "$SRC/.agents/skills/." "$TARGET/.agents/skills/"
|
|
183
|
+
cp "$SRC"/.claude/agents/*.md "$TARGET/.claude/agents/"
|
|
184
|
+
cp "$SRC/workflow_spec.md" "$TARGET/"
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
詳細內容請參考該語言目錄下的 README:
|
|
188
|
+
|
|
189
|
+
```text
|
|
190
|
+
publish/zh-tw/README.md (或 publish/en/README.md)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
兩份各自以該語言撰寫。
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
MIT
|
|
@@ -21,21 +21,24 @@
|
|
|
21
21
|
import { normalizeAnthropicUsage, normalizeFinishReason } from "../usage.js";
|
|
22
22
|
import { ProviderHttpError } from "../mask.js";
|
|
23
23
|
import { checkRawObjectIntegrity } from "../raw-integrity.js";
|
|
24
|
+
import { readFileToolDescription } from "./read-file-tool-description.js";
|
|
24
25
|
const ANTHROPIC_VERSION = "2023-06-01";
|
|
25
26
|
// §29 規格四:max_tokens 是 Anthropic 專屬必要參數,SendOptions 沒有這個概念(openai/gemini
|
|
26
27
|
// 都不需要)。寫死在 adapter,不提升到 SendOptions——那會逼另外兩個 adapter 處理用不到的欄位。
|
|
27
28
|
// 初始值推導見規格四:十次派工單支最高 40,970 output token,32k 有 20% 餘裕;本版預設 effort
|
|
28
29
|
// 不會用到官方建議 64k 起跳的 xhigh/max。撞到 stop_reason:"max_tokens" 再調,不在此臆測。
|
|
29
30
|
const ANTHROPIC_MAX_TOKENS = 32768;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
31
|
+
function buildReadFileTool(lang) {
|
|
32
|
+
return {
|
|
33
|
+
name: "read_file",
|
|
34
|
+
description: readFileToolDescription(lang),
|
|
35
|
+
input_schema: {
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: { path: { type: "string" } },
|
|
38
|
+
required: ["path"],
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
39
42
|
// §29 規格二:thinking.type:"enabled" 在 Opus 5/Sonnet 5 會 400(實測確認,見
|
|
40
43
|
// facts_dispatch.md)。正確路徑是 adaptive thinking + output_config.effort,兩者一起送——
|
|
41
44
|
// 即使 output_config.effort 的 JSON 片段與 deepseek 的 reasoning.style 完全相同,這是巧合
|
|
@@ -45,6 +48,10 @@ function buildReasoningParams(style, effort) {
|
|
|
45
48
|
return {}; // §4:留白則不送任何 reasoning 參數(validate.ts 已解析為 default,實務上不會是 undefined)
|
|
46
49
|
if (style === "anthropic")
|
|
47
50
|
return { thinking: { type: "adaptive" }, output_config: { effort } };
|
|
51
|
+
// T7a〈D. 全域中文字串最終覆核〉:providers.json 的 schema 已限定 reasoning.style 只能是
|
|
52
|
+
// "openai"/"deepseek"/"gemini"/"anthropic"/null(見 providers.ts 的 parseReasoning),
|
|
53
|
+
// 此分支理論上不可達,同 raw-integrity.ts 的實作缺陷斷言。明確決定維持中文不動,不搬進
|
|
54
|
+
// messages.ts——多語系對一段不可達的防禦性文字沒有實質效益。
|
|
48
55
|
throw new Error(`anthropic-messages adapter 不支援 reasoning.style=${style}`);
|
|
49
56
|
}
|
|
50
57
|
// §29 規格五:把 conv.turns 轉成 Anthropic 的 messages 陣列。tool turn 需要前瞻合併——
|
|
@@ -82,7 +89,7 @@ function turnsToMessages(turns) {
|
|
|
82
89
|
// 不需打 API 就能測試——見 anthropic-messages.test.ts)。
|
|
83
90
|
export function buildAnthropicRequest(conv, opts, config) {
|
|
84
91
|
const messages = turnsToMessages(conv.turns);
|
|
85
|
-
checkRawObjectIntegrity(conv, messages);
|
|
92
|
+
checkRawObjectIntegrity(conv, messages, config.lang);
|
|
86
93
|
return {
|
|
87
94
|
model: opts.model,
|
|
88
95
|
max_tokens: ANTHROPIC_MAX_TOKENS,
|
|
@@ -91,7 +98,7 @@ export function buildAnthropicRequest(conv, opts, config) {
|
|
|
91
98
|
// §29 規格八:top-level 自動快取,由 API 自行管理斷點位置並隨對話推進。低於該模型最低
|
|
92
99
|
// 可快取 token 數時 API 靜默不快取、不報錯,故此欄位可無條件加,不需前置估算。
|
|
93
100
|
cache_control: { type: "ephemeral" },
|
|
94
|
-
...(opts.enableTools === false ? {} : { tools: [
|
|
101
|
+
...(opts.enableTools === false ? {} : { tools: [buildReadFileTool(config.lang)] }),
|
|
95
102
|
...buildReasoningParams(config.reasoning.style, opts.effort),
|
|
96
103
|
};
|
|
97
104
|
}
|
|
@@ -8,19 +8,22 @@
|
|
|
8
8
|
import { normalizeFinishReason, normalizeGeminiUsage } from "../usage.js";
|
|
9
9
|
import { ProviderHttpError } from "../mask.js";
|
|
10
10
|
import { checkRawObjectIntegrity } from "../raw-integrity.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
import { readFileToolDescription } from "./read-file-tool-description.js";
|
|
12
|
+
function buildGeminiTool(lang) {
|
|
13
|
+
return {
|
|
14
|
+
functionDeclarations: [
|
|
15
|
+
{
|
|
16
|
+
name: "read_file",
|
|
17
|
+
description: readFileToolDescription(lang),
|
|
18
|
+
parameters: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: { path: { type: "string" } },
|
|
21
|
+
required: ["path"],
|
|
22
|
+
},
|
|
20
23
|
},
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
24
27
|
function turnToContent(turn) {
|
|
25
28
|
if (turn.role === "user") {
|
|
26
29
|
return { role: "user", parts: [{ text: turn.text }] };
|
|
@@ -52,13 +55,13 @@ function buildReasoningParams(effort) {
|
|
|
52
55
|
}
|
|
53
56
|
// 純函式:組出實際會送出的 request body,並執行 §8 的 raw 完整性自我檢查(違反即拋
|
|
54
57
|
// RawIntegrityError,不需打 API 就能測試——見 raw-integrity.test.ts)。
|
|
55
|
-
export function buildGeminiRequest(conv, opts) {
|
|
58
|
+
export function buildGeminiRequest(conv, opts, lang) {
|
|
56
59
|
const contents = conv.turns.map(turnToContent);
|
|
57
|
-
checkRawObjectIntegrity(conv, contents);
|
|
60
|
+
checkRawObjectIntegrity(conv, contents, lang);
|
|
58
61
|
const body = {
|
|
59
62
|
contents,
|
|
60
63
|
system_instruction: { parts: [{ text: conv.systemPrompt }] },
|
|
61
|
-
...(opts.enableTools === false ? {} : { tools: [
|
|
64
|
+
...(opts.enableTools === false ? {} : { tools: [buildGeminiTool(lang)] }),
|
|
62
65
|
...buildReasoningParams(opts.effort),
|
|
63
66
|
};
|
|
64
67
|
return { contents, body };
|
|
@@ -66,7 +69,7 @@ export function buildGeminiRequest(conv, opts) {
|
|
|
66
69
|
export function createGeminiAdapter(config) {
|
|
67
70
|
return {
|
|
68
71
|
async send(conv, opts) {
|
|
69
|
-
const { body } = buildGeminiRequest(conv, opts);
|
|
72
|
+
const { body } = buildGeminiRequest(conv, opts, config.lang);
|
|
70
73
|
const res = await fetch(`${config.baseURL}/models/${opts.model}:generateContent`, {
|
|
71
74
|
method: "POST",
|
|
72
75
|
headers: { "x-goog-api-key": config.apiKey, "Content-Type": "application/json" },
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
const READ_FILE_TOOL_DESCRIPTION = "讀取指定路徑的檔案內容";
|
|
2
|
+
const READ_FILE_TOOL_DESCRIPTION_EN = "Read the contents of the file at the given path.";
|
|
3
|
+
export function readFileToolDescription(lang) {
|
|
4
|
+
return lang === "en" ? READ_FILE_TOOL_DESCRIPTION_EN : READ_FILE_TOOL_DESCRIPTION;
|
|
5
|
+
}
|
|
@@ -6,17 +6,21 @@ import OpenAI from "openai";
|
|
|
6
6
|
import { normalizeFinishReason, normalizeResponsesUsage } from "../usage.js";
|
|
7
7
|
import { ProviderHttpError } from "../mask.js";
|
|
8
8
|
import { checkRawArrayIntegrity } from "../raw-integrity.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
import { readFileToolDescription } from "./read-file-tool-description.js";
|
|
10
|
+
import { m } from "../messages.js";
|
|
11
|
+
function buildReadFileTool(lang) {
|
|
12
|
+
return {
|
|
13
|
+
type: "function",
|
|
14
|
+
name: "read_file",
|
|
15
|
+
description: readFileToolDescription(lang),
|
|
16
|
+
parameters: {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: { path: { type: "string" } },
|
|
19
|
+
required: ["path"],
|
|
20
|
+
},
|
|
21
|
+
strict: false,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
20
24
|
// plan_dispatch_v1.8.md §5「reasoning.style 的三種轉換」(實測確認,v1.7 兩處填錯已修正):
|
|
21
25
|
// deepseek 正確路徑是 output_config.effort,不是 chat/completions 章節的
|
|
22
26
|
// thinking/reasoning_effort——後者對 /v1/responses 端點回顯欄位但靜默不生效
|
|
@@ -28,6 +32,10 @@ function buildReasoningParams(style, effort) {
|
|
|
28
32
|
return { reasoning: { effort } };
|
|
29
33
|
if (style === "deepseek")
|
|
30
34
|
return { output_config: { effort } };
|
|
35
|
+
// T7a〈D. 全域中文字串最終覆核〉:providers.json 的 schema 已限定 reasoning.style 只能是
|
|
36
|
+
// "openai"/"deepseek"/"gemini"/"anthropic"/null(見 providers.ts 的 parseReasoning),
|
|
37
|
+
// 此分支理論上不可達,同 raw-integrity.ts 的實作缺陷斷言。明確決定維持中文不動,不搬進
|
|
38
|
+
// messages.ts——多語系對一段不可達的防禦性文字沒有實質效益。
|
|
31
39
|
throw new Error(`responses adapter 不支援 reasoning.style=${style}`);
|
|
32
40
|
}
|
|
33
41
|
function turnToInputItems(turn) {
|
|
@@ -44,12 +52,12 @@ function turnToInputItems(turn) {
|
|
|
44
52
|
// RawIntegrityError,不需打 API 就能測試——見 raw-integrity.test.ts)。
|
|
45
53
|
export function buildResponsesRequest(conv, opts, config) {
|
|
46
54
|
const input = conv.turns.flatMap(turnToInputItems);
|
|
47
|
-
checkRawArrayIntegrity(conv, input);
|
|
55
|
+
checkRawArrayIntegrity(conv, input, config.lang);
|
|
48
56
|
return {
|
|
49
57
|
model: opts.model,
|
|
50
58
|
instructions: conv.systemPrompt,
|
|
51
59
|
input,
|
|
52
|
-
...(opts.enableTools === false ? {} : { tools: [
|
|
60
|
+
...(opts.enableTools === false ? {} : { tools: [buildReadFileTool(config.lang)] }),
|
|
53
61
|
...(config.store === false ? { store: false } : {}),
|
|
54
62
|
include: ["reasoning.encrypted_content"],
|
|
55
63
|
...buildReasoningParams(config.reasoning.style, opts.effort),
|
|
@@ -67,7 +75,7 @@ export function createResponsesAdapter(config) {
|
|
|
67
75
|
catch (err) {
|
|
68
76
|
if (err && typeof err === "object" && "status" in err) {
|
|
69
77
|
const anyErr = err;
|
|
70
|
-
throw new ProviderHttpError(anyErr.message ??
|
|
78
|
+
throw new ProviderHttpError(anyErr.message ?? m(config.lang, "responsesAdapterCallFailed"), anyErr.status ?? 0, anyErr.headers ?? {}, anyErr.error, params);
|
|
71
79
|
}
|
|
72
80
|
throw err;
|
|
73
81
|
}
|
package/dist/audit.js
CHANGED
|
@@ -17,6 +17,11 @@ function getSection(sections, names) {
|
|
|
17
17
|
return undefined;
|
|
18
18
|
}
|
|
19
19
|
const SUSPECT_PHRASES = ["應該改成", "建議採用", "嚴重度", "高風險", "應廢止"];
|
|
20
|
+
// plan_i18n_v1.2.md §4.1/i18n_classification_t2.md §五 #8-12:英文回報若只比對中文詞,
|
|
21
|
+
// 稽核會靜默失效——不報錯、不變紅,只是什麼都抓不到。兩套並存同時比對,不隨 lang 切換
|
|
22
|
+
// (spoke 可能用另一種語言作答,稽核本來就不看工單語言)。summary.md 需要分開標示是
|
|
23
|
+
// 哪一套命中,供日後調整這份清單時有資料可依據,故 auditSpoke 分開回傳兩個陣列。
|
|
24
|
+
const SUSPECT_PHRASES_EN = ["should be changed to", "recommend adopting", "severity", "high risk", "should be deprecated"];
|
|
20
25
|
// 抓看起來像相對路徑的引用:至少一層目錄+副檔名,容許前後有反引號與 :行號。
|
|
21
26
|
// plan_dispatch_v1.12.md §15:字元類須容許中括號,否則 Next.js 動態路由段([id]、
|
|
22
27
|
// [...slug]、[[...slug]])會把路徑從中括號後截斷——截斷後的字串當然不在允許清單內,
|
|
@@ -34,6 +39,11 @@ const OBSERVATION_PATTERNS = [
|
|
|
34
39
|
/^#{2,4}\s*觀察\s*\d+/, // 標題形式:"### 觀察 1"
|
|
35
40
|
/^(?:\*\*)?Observation\s*\d+(?:\.\d+)?(?:\*\*)?[::]/i, // 英文模板的巢狀標記
|
|
36
41
|
/^#{2,4}\s*Observation\s*\d+/i, // 英文模板的標題形式
|
|
42
|
+
// real-run-i18n-lang(2026-08-12):`deepseek-v4-flash` 的中文格寫成 `## 1. 「比照 tags 路由」…`
|
|
43
|
+
// ——標題形式但編號後面直接是內容,沒有「觀察」二字,上面兩條標題樣式都認不出來,於是整份
|
|
44
|
+
// 判「無法計數」。**那是本次唯一一項「中文格看起來比英文格差」的來源,而它與語言無關。**
|
|
45
|
+
// 放在最後:前面任何一條命中就不會走到這裡,所以不會蓋掉既有判讀。
|
|
46
|
+
/^#{2,4}\s*\d+[.、]/,
|
|
37
47
|
];
|
|
38
48
|
function countObservations(observationsBody) {
|
|
39
49
|
if (observationsBody.trim().length === 0)
|
|
@@ -73,6 +83,8 @@ export function auditSpoke(finalText, allowedRelativePaths) {
|
|
|
73
83
|
citedPathsOutsideAllowlistDetail: [],
|
|
74
84
|
cannotVerifySectionPresent: false,
|
|
75
85
|
suspectPhrases: [],
|
|
86
|
+
suspectPhrasesZh: [],
|
|
87
|
+
suspectPhrasesEn: [],
|
|
76
88
|
};
|
|
77
89
|
}
|
|
78
90
|
const lines = finalText.split(/\r?\n/).map((l) => l.trim());
|
|
@@ -105,7 +117,9 @@ export function auditSpoke(finalText, allowedRelativePaths) {
|
|
|
105
117
|
const pathsCitedElsewhere = new Set([...elsewhereText.matchAll(PATH_REGEX)].map((m) => m[1]));
|
|
106
118
|
const pathsOnlyInCannotVerify = new Set([...pathsCitedInCannotVerifySection].filter((p) => !pathsCitedElsewhere.has(p)));
|
|
107
119
|
const citedPathsOutsideAllowlist = citedPaths.filter((p) => !allowedSet.has(p) && !pathsOnlyInCannotVerify.has(p));
|
|
108
|
-
const
|
|
120
|
+
const suspectPhrasesZh = SUSPECT_PHRASES.filter((phrase) => finalText.includes(phrase));
|
|
121
|
+
const suspectPhrasesEn = SUSPECT_PHRASES_EN.filter((phrase) => finalText.includes(phrase));
|
|
122
|
+
const suspectPhrases = [...suspectPhrasesZh, ...suspectPhrasesEn];
|
|
109
123
|
const citedPathsOutsideAllowlistDetail = citedPathsOutsideAllowlist.map((p) => ({
|
|
110
124
|
path: p,
|
|
111
125
|
section: findSection(sections, p),
|
|
@@ -119,5 +133,7 @@ export function auditSpoke(finalText, allowedRelativePaths) {
|
|
|
119
133
|
citedPathsOutsideAllowlistDetail,
|
|
120
134
|
cannotVerifySectionPresent,
|
|
121
135
|
suspectPhrases,
|
|
136
|
+
suspectPhrasesZh,
|
|
137
|
+
suspectPhrasesEn,
|
|
122
138
|
};
|
|
123
139
|
}
|