agentflowctl 0.3.0 → 0.5.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 +166 -42
- package/dist/agentConfig.js +8 -25
- package/dist/agents/index.js +0 -4
- package/dist/arbitration.js +10 -0
- package/dist/cleanup.js +54 -0
- package/dist/cli.js +130 -60
- package/dist/config.js +2 -2
- package/dist/detect.js +82 -0
- package/dist/engine.js +62 -28
- package/dist/logs.js +173 -0
- package/dist/paths.js +4 -2
- package/dist/proc.js +1 -4
- package/dist/roles.js +70 -39
- package/dist/runner.js +25 -29
- package/dist/schemas.js +10 -6
- package/dist/setup.js +70 -0
- package/examples/flow.config.json +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,15 +10,33 @@
|
|
|
10
10
|
|
|
11
11
|
## 快速開始
|
|
12
12
|
|
|
13
|
-
需要 Node.js 22 以上與 git。不需要全域安裝,直接用 `npx` 執行。先讓各家 CLI
|
|
13
|
+
需要 Node.js 22 以上與 git。不需要全域安裝,直接用 `npx` 執行。先讓各家 CLI 完成登入:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
claude # 完成登入
|
|
17
17
|
codex # 完成登入
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
沒有內建的 agent,只會使用 `flow.config.json` 的 `agents` 裡設定的。用 `agent setup` 互動設定:它會偵測本機的 `claude`、`codex`、`gemini`,逐一詢問要不要加入、名稱與 model,再設定參與的 agent;確認後才一次寫入,最後自動跑一次 `doctor`:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx agentflowctl agent setup
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
也可以不經互動,直接用指令新增:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx agentflowctl agent add claude --adapter claude
|
|
30
|
+
npx agentflowctl agent add codex --adapter codex
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
之後改了設定或換了環境,用 `doctor` 檢查。它會列出設定的 agent 的 CLI 是否已安裝,並印出即將參與的 agent。沒有設定 `cycle` 時,依 `agents` 的順序取已安裝的;一個都沒偵測到就無法執行。
|
|
34
|
+
|
|
35
|
+
```bash
|
|
18
36
|
npx agentflowctl doctor
|
|
19
37
|
```
|
|
20
38
|
|
|
21
|
-
`
|
|
39
|
+
從舊版升級:以前沒寫 `agents` 時會自動使用內建的 claude、codex、gemini,現在不會了,要先用 `agent setup` 或 `agent add` 補上。舊設定裡的 `removedAgents` 已不再使用,可以刪掉。
|
|
22
40
|
|
|
23
41
|
在專案資料夾內開始一次 run:
|
|
24
42
|
|
|
@@ -46,26 +64,28 @@ pnpm run build
|
|
|
46
64
|
pnpm link --global
|
|
47
65
|
```
|
|
48
66
|
|
|
49
|
-
##
|
|
67
|
+
## 角色分配怎麼打破同溫層
|
|
50
68
|
|
|
51
69
|
同一個模型審查自己的程式碼,容易放過自己的寫法;測試和實作出自同一個模型,也容易寫出剛好會過的測試。角色分配只有三條規則,定義在 `src/roles.ts`:
|
|
52
70
|
|
|
53
71
|
| 規則 | 效果 |
|
|
54
72
|
|---|---|
|
|
55
|
-
| 審查者永遠不是最後寫程式的 agent | 每一輪審查都由另一家看 |
|
|
73
|
+
| 審查者永遠不是最後寫程式的 agent,多位審查者彼此不重複 | 每一輪審查都由另一家看 |
|
|
56
74
|
| 同一個任務的測試與實作由不同 agent 負責(`tddSplit`) | A 寫的測試,B 實作到通過,而且不能改測試 |
|
|
57
|
-
|
|
|
75
|
+
| 人選隨機決定,不依 `cycle` 的順序 | 各家輪流當作者、審查者、修正者,不會固定由同一家起頭 |
|
|
76
|
+
|
|
77
|
+
`cycle` 只決定哪些 agent 參與。隨機以 run id 為種子,同一個 run 的同一步驟 `resume` 後仍是同一家。寫測試的人每 N 個任務(N 為參與的家數)洗一次牌,每家各輪一次,換輪時也不會連續兩個任務由同一家寫測試。
|
|
58
78
|
|
|
59
|
-
|
|
79
|
+
兩家時是乒乓,例如:
|
|
60
80
|
|
|
61
81
|
```
|
|
62
|
-
計畫:
|
|
82
|
+
計畫:codex 撰寫 → claude 審查(要求修改)→ codex 修改 → claude 審查(核准)
|
|
63
83
|
T-1 測試:claude 實作:codex
|
|
64
84
|
T-2 測試:codex 實作:claude
|
|
65
85
|
審查:codex(最後作者是 claude)→ 要求修改 → claude 修正 → codex 審查(核准)
|
|
66
86
|
```
|
|
67
87
|
|
|
68
|
-
|
|
88
|
+
三家時,每一輪的審查者從作者以外隨機挑,修正者從審查者以外隨機挑;仲裁交給沒寫過這份計畫、也沒審過它的那一家(有多家時隨機挑一家)。
|
|
69
89
|
|
|
70
90
|
每個 commit 訊息結尾會標上實際作者,例如 `feat(T-2): 匯出 [gemini]`。
|
|
71
91
|
|
|
@@ -81,25 +101,47 @@ agentflowctl run --req "..." --manual-plan # 計畫通過 AI 審查後,仍
|
|
|
81
101
|
agentflowctl approve f-xxxx # 搭配 --manual-plan
|
|
82
102
|
agentflowctl status f-xxxx # 階段、任務進度、各 agent 用量、代打紀錄
|
|
83
103
|
agentflowctl list
|
|
84
|
-
agentflowctl logs f-xxxx
|
|
104
|
+
agentflowctl logs f-xxxx # 列出每一份 log 的編號、結果、階段、步驟、agent
|
|
105
|
+
agentflowctl logs f-xxxx 7 # 解析第 7 份 log,最後附上錯誤整理(--latest 看最新一份)
|
|
106
|
+
agentflowctl logs f-xxxx 7 --raw # 原始內容(agent 的 JSON 行)
|
|
85
107
|
agentflowctl resume f-xxxx # 從暫停、Ctrl-C 或失敗處接續
|
|
86
108
|
agentflowctl cancel f-xxxx
|
|
87
109
|
agentflowctl clean f-xxxx # 移除 worktree 與 run 紀錄,分支保留
|
|
110
|
+
agentflowctl clean --all # 清掉所有已結束的 run 與中斷留下的 worktree
|
|
88
111
|
```
|
|
89
112
|
|
|
90
113
|
| 選項 | 作用 |
|
|
91
114
|
|---|---|
|
|
92
115
|
| `--req` / `--req-file` | 需求文字,或從檔案讀取 |
|
|
93
116
|
| `--base` | 基底分支,預設為目前分支 |
|
|
94
|
-
| `--cycle` | 這次 run
|
|
117
|
+
| `--cycle` | 這次 run 參與的 agent,例如 `claude,codex,gemini`;順序不影響分工;建立後就固定,`resume` 沿用 |
|
|
95
118
|
| `--max-agent-runs` | 這次 run 的 agent 執行次數上限 |
|
|
96
119
|
| `--manual-plan` | 計畫通過審查後進入 `awaiting_approval`,等 `approve` 才開始實作 |
|
|
120
|
+
| `-v` / `--verbose` | 執行時印出 agent 的文字、工具呼叫與專案指令;`run`、`resume`、`approve` 都適用,也可設 `AGENTFLOWCTL_VERBOSE=1` |
|
|
97
121
|
|
|
98
122
|
`status` 會列出任務。進行中的任務會標出正在寫測試還是正在寫實作。
|
|
99
123
|
|
|
124
|
+
### 清除 worktree
|
|
125
|
+
|
|
126
|
+
`run` 建好 worktree 就會寫入 run 紀錄,所以不論在哪一步中斷(包括安裝相依套件時),都能用 `resume` 接續,或用 `clean` 清掉。
|
|
127
|
+
|
|
128
|
+
- `clean <id>`:移除該 run 的 worktree 與 `.agentflowctl/runs/<id>/`,並清掉 git 裡已失效的 worktree 登記。沒有 run 紀錄的 worktree 也能清,worktree 資料夾被手動刪掉時也一樣。
|
|
129
|
+
- `clean --all`:清掉所有 `done`、`failed` 的 run,以及沒有 run 紀錄的 worktree。進行中、`paused`、`awaiting_approval` 的不動;Ctrl-C 中斷、之後不打算接續的 run,先 `cancel` 再 `clean --all`,或直接 `clean <id>`。
|
|
130
|
+
|
|
131
|
+
兩者都保留 `flow/<id>` 分支,不需要時用 `git branch -D` 刪除。
|
|
132
|
+
|
|
100
133
|
### 執行中的終端機輸出
|
|
101
134
|
|
|
102
|
-
|
|
135
|
+
預設是安靜模式,只印出 `[run-id]` 開頭的階段進度(📝 🧐 ✓ ✗ ⚠️ 等)。agent 執行失敗、測試或檢查沒過時,會附上對應 log 的查看指令:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
[f-xxxx] 🔍 執行驗證
|
|
139
|
+
[f-xxxx] ✓ typecheck
|
|
140
|
+
[f-xxxx] ✗ lint(agentflowctl logs f-xxxx 15)
|
|
141
|
+
✗ codex 執行失敗(結束碼 1),可用 agentflowctl logs f-xxxx 16 查看
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
加上 `-v` 會另外印出 agent 每一段文字的第一行、每次工具呼叫的完整指令或主要參數(不截斷,多行指令的後續行縮排對齊),以及 install、測試、verify 這些以 `$ ` 開頭的專案指令:
|
|
103
145
|
|
|
104
146
|
```
|
|
105
147
|
💬 [claude] 先讀現有的表單元件
|
|
@@ -110,11 +152,75 @@ agent 每次使用工具,都會印出完整的指令或主要參數,不截
|
|
|
110
152
|
$ pnpm install
|
|
111
153
|
```
|
|
112
154
|
|
|
113
|
-
工具參數依序取 command、檔案路徑、path、pattern、url、query,都沒有時印出整包 JSON
|
|
155
|
+
工具參數依序取 command、檔案路徑、path、pattern、url、query,都沒有時印出整包 JSON。
|
|
156
|
+
|
|
157
|
+
### Log
|
|
158
|
+
|
|
159
|
+
每次執行 agent 或專案指令都會留一份 log,放在 `.agentflowctl/runs/<id>/logs/`,檔名是「序號-階段-步驟-agent」,專案指令的 agent 欄位是 `cmd`:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
001-setup-install-cmd.log
|
|
163
|
+
002-spec-spec-claude.log
|
|
164
|
+
007-implement-T1-tests-codex.log
|
|
165
|
+
008-implement-T1-red-cmd.log
|
|
166
|
+
015-verify-lint-cmd.log
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
檔案保留 agent 的原始輸出,也就是各家 CLI 的 JSON 行。第一行 `# agentflowctl {...}` 記錄階段、步驟、agent 與開始時間;stderr 接在 `[stderr]` 之後;最後一行 `# exit {...}` 記錄結束碼與是否成功,沒有這行就代表還在執行或被中斷。
|
|
170
|
+
|
|
171
|
+
`agentflowctl logs <id>` 列出所有 log。結果欄的 ✓ 是成功,✗ 是失敗,… 代表沒有結束紀錄:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
# 結果 階段 步驟 agent 開始時間
|
|
175
|
+
1 ✓ setup install cmd 2026-09-26 11:29:04
|
|
176
|
+
2 ✓ spec spec claude 2026-09-26 11:29:05
|
|
177
|
+
3 ✗ plan plan codex 2026-09-26 11:31:40
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`agentflowctl logs <id> <編號>` 會把原始 JSON 解析成易讀的格式:
|
|
181
|
+
|
|
182
|
+
| 標記 | 內容 |
|
|
183
|
+
|---|---|
|
|
184
|
+
| 💬 | agent 的完整文字,不截斷 |
|
|
185
|
+
| 🔧 | 工具呼叫與完整參數 |
|
|
186
|
+
| 📊 | token 用量 |
|
|
187
|
+
| 🏁 | 最後結果 |
|
|
188
|
+
| ⚠️ | 工具回報的錯誤。agent 通常會自己換方法繼續,所以不列進錯誤整理 |
|
|
189
|
+
| ❌ | adapter 不認得的錯誤事件 |
|
|
190
|
+
| 📄 | 不是 JSON 的輸出行 |
|
|
191
|
+
|
|
192
|
+
adapter 不認得、也看不出錯誤跡象的 JSON 行不會顯示,只列出行數,要看全部請加 `--raw`。專案指令的 log 本來就是純文字,會原樣顯示。
|
|
193
|
+
|
|
194
|
+
最後一段「錯誤」整理出結束碼、agent 回報的失敗、錯誤事件與 stderr:
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
#3 plan / plan / codex
|
|
198
|
+
開始 2026-09-26 11:31:40 結束 2026-09-26 11:31:52 結束碼 1 ✗ 失敗
|
|
199
|
+
檔案 /repo/.agentflowctl/runs/f-xxxx/logs/003-plan-plan-codex.log
|
|
200
|
+
|
|
201
|
+
💬 先讀 spec.md 與 acceptance.json
|
|
202
|
+
🔧 shell: bash -lc 'cat .flow/spec.md'
|
|
203
|
+
🏁 失敗:stream disconnected before completion
|
|
204
|
+
|
|
205
|
+
── 錯誤 ──
|
|
206
|
+
結束碼 1
|
|
207
|
+
agent 回報失敗:stream disconnected before completion
|
|
208
|
+
stderr:
|
|
209
|
+
Error: stream disconnected before completion
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
執行成功時,stderr 會放在「其他輸出」段落,不算錯誤。
|
|
213
|
+
|
|
214
|
+
### 出錯時怎麼查
|
|
215
|
+
|
|
216
|
+
1. run 停下時印出的摘要,或 `agentflowctl status <id>`,會列出失敗的階段、原因、最後一份 log,以及最近失敗的那一份。
|
|
217
|
+
2. `agentflowctl logs <id> <編號>` 看那份 log 的錯誤段落。
|
|
218
|
+
3. 解析結果看不出原因時,加 `--raw` 看原始輸出。
|
|
219
|
+
4. 必要時直接在 worktree(`.agentflowctl/worktrees/<id>`)裡修正,再執行 `agentflowctl resume <id>`。
|
|
114
220
|
|
|
115
221
|
## 設定
|
|
116
222
|
|
|
117
|
-
專案根目錄的 `flow.config.json`。完整範例見 `examples/flow.config.json
|
|
223
|
+
專案根目錄的 `flow.config.json`。完整範例見 `examples/flow.config.json`。未提供的欄位使用內建預設;`install`、`test`、`checks` 沒寫時,會依專案現況偵測(見下方「專案指令的偵測」)。
|
|
118
224
|
|
|
119
225
|
```json
|
|
120
226
|
{
|
|
@@ -122,7 +228,9 @@ agent 每次使用工具,都會印出完整的指令或主要參數,不截
|
|
|
122
228
|
"fixStrategy": "ring",
|
|
123
229
|
"tddSplit": true,
|
|
124
230
|
"tieBreak": "proceed",
|
|
231
|
+
"defaultModels": { "claude": "Claude 模型名稱", "codex": "Codex 模型名稱", "gemini": "Gemini 模型名稱" },
|
|
125
232
|
"agents": {
|
|
233
|
+
"claude": { "adapter": "claude" },
|
|
126
234
|
"codex": { "adapter": "codex", "model": "你要用的模型" },
|
|
127
235
|
"aider": { "adapter": "command", "command": ["aider", "--yes-always", "--no-auto-commits", "--message", "{prompt}"] }
|
|
128
236
|
}
|
|
@@ -131,43 +239,60 @@ agent 每次使用工具,都會印出完整的指令或主要參數,不截
|
|
|
131
239
|
|
|
132
240
|
| 設定 | 預設 | 說明 |
|
|
133
241
|
|---|---|---|
|
|
134
|
-
| `cycle` | 自動偵測 |
|
|
135
|
-
| `
|
|
242
|
+
| `cycle` | 自動偵測 | 參與的 agent,順序不影響分工(人選隨機決定)。未設定時依 `agents` 的順序取已安裝的 CLI。同一家 CLI 可以登記成不同 agent,例如 `claude-fast` 與 `claude-strong` |
|
|
243
|
+
| `defaultModels` | `{}` | 依 `claude`、`codex`、`gemini` adapter 指定全域預設 model;agent 的 `model` 優先,兩者都沒設時使用各 CLI 的預設。`command` adapter 不套用 |
|
|
244
|
+
| `fixStrategy` | `ring` | `ring`:審查意見隨機交給審查者以外的一家;`author`:交回最後作者 |
|
|
136
245
|
| `tddSplit` | `true` | 測試與實作是否分開 |
|
|
137
246
|
| `reviewQuorum` | `1` | 程式碼需要幾位不同審查者都 `approve` |
|
|
138
247
|
| `planReviewQuorum` | `1` | 計畫需要幾位不同審查者都 `approve` |
|
|
139
248
|
| `planArbiter` | `true` | 計畫審查僵持時交付仲裁。關掉之後,僵持會直接讓 run 失敗 |
|
|
140
249
|
| `tieBreak` | `proceed` | 兩家仲裁意見分歧時:`proceed` 繼續並記錄爭議;`stop` 停下 |
|
|
141
250
|
| `maxAgentRuns` | `60` | 單一 run 最多執行幾次 agent |
|
|
142
|
-
| `install` / `test` / `checks` |
|
|
143
|
-
| `agents` |
|
|
144
|
-
| `removedAgents` | `[]` | 移除的內建 agent,不會被自動偵測、不能放進輪替。通常用 `agent remove` 寫入 |
|
|
251
|
+
| `install` / `test` / `checks` | 依專案偵測 | 安裝、測試與 verify 階段實際執行的指令 |
|
|
252
|
+
| `agents` | `{}` | 可用的 agent,沒有內建的。每個都要指定 adapter(`claude`、`codex`、`gemini`,或用 `command` 接上其他 CLI) |
|
|
145
253
|
|
|
146
254
|
verify 失敗(型別、lint、建置)一律交回最後作者。審查意見才依 `fixStrategy` 決定修正者。
|
|
147
255
|
|
|
256
|
+
### 專案指令的偵測
|
|
257
|
+
|
|
258
|
+
`install`、`test`、`checks` 沒寫在 `flow.config.json` 時,每次讀設定都會依專案現況推出指令,不寫檔。有寫的欄位一律照你的設定。
|
|
259
|
+
|
|
260
|
+
- 套件管理器:先看 `package.json` 的 `packageManager`,再看 lockfile(`pnpm-lock.yaml`、`yarn.lock`、`bun.lock`/`bun.lockb`、`package-lock.json`),都沒有就用 npm。
|
|
261
|
+
- `install`:`pnpm install`、`yarn install`、`bun install` 或 `npm install --no-audit --no-fund`。不鎖 lockfile,因為實作時 agent 可能新增依賴。
|
|
262
|
+
- `checks`:typecheck、lint、test、build 四項。`package.json` 有對應的 script(`typecheck`/`type-check`、`lint`、`test`、`build`)就用 `<pm> run <script>`,否則用 `tsc --noEmit`、`eslint .`、`vitest run`、`vite build`,前面加上 `npx`、`pnpm exec`、`yarn` 或 `bunx`。
|
|
263
|
+
- `test`:`vitest run`,前綴同上。
|
|
264
|
+
|
|
265
|
+
`run` 建立 worktree 後會印出這次偵測到的指令:
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
[f-xxxx] 🔧 依專案偵測指令:pnpm(依 package.json 的 packageManager)
|
|
269
|
+
[f-xxxx] install:pnpm install
|
|
270
|
+
[f-xxxx] checks.typecheck:pnpm run type-check
|
|
271
|
+
```
|
|
272
|
+
|
|
148
273
|
### 用指令管理 agent
|
|
149
274
|
|
|
150
275
|
`agents` 與 `cycle` 也可以用 `agent` 指令修改,不必手動編輯 JSON。每次寫入前都會先驗證整份設定:
|
|
151
276
|
|
|
152
277
|
```bash
|
|
153
|
-
agentflowctl agent
|
|
278
|
+
agentflowctl agent setup # 互動設定 claude、codex、gemini 與參與的 agent
|
|
279
|
+
agentflowctl agent list # 設定的 agent、是否已安裝、是否參與
|
|
154
280
|
agentflowctl agent add claude-strong --adapter claude --model opus
|
|
155
281
|
agentflowctl agent add aider --adapter command -- aider --yes-always --message {prompt}
|
|
156
282
|
agentflowctl agent set codex --model 你要用的模型 --extra-arg=--search
|
|
157
283
|
agentflowctl agent set aider --adapter gemini # 換 adapter
|
|
158
284
|
agentflowctl agent remove aider
|
|
159
|
-
agentflowctl agent
|
|
160
|
-
agentflowctl agent cycle claude-strong,codex,gemini # 不帶參數時顯示目前的順序
|
|
285
|
+
agentflowctl agent cycle claude-strong,codex,gemini # 不帶參數時顯示目前參與的 agent
|
|
161
286
|
```
|
|
162
287
|
|
|
163
288
|
修改會連帶更新相關設定,並在終端機列出:
|
|
164
289
|
|
|
165
290
|
- `set --adapter` 換 adapter 時,會清掉舊 adapter 的 `model`、`extraArgs`、`command`,這次有重新指定的除外。
|
|
166
|
-
- `remove` 會一併從 `cycle` 移除。`cycle`
|
|
167
|
-
- 內建的 claude、codex、gemini 也能 `remove`:覆寫設定會一起刪掉,名稱記在 `removedAgents`,之後自動偵測會跳過它,也不能放進 `cycle`。要加回來用 `agent add gemini --adapter gemini`。
|
|
291
|
+
- `remove` 會一併從 `cycle` 移除。`cycle` 變空就刪除這個欄位,改回從 `agents` 自動偵測。
|
|
168
292
|
- `--extra-arg` 可以重複指定,會整個取代原本的 `extraArgs`。參數以 `-` 開頭時,寫成 `--extra-arg=--sandbox`。
|
|
293
|
+
- `setup` 遇到已存在的名稱會先問要不要覆寫;不覆寫時保留原設定,但仍會參與。在非互動式環境(CI、管線)裡請改用 `agent add`。`command` adapter 要自己寫指令,不在 `setup` 裡。
|
|
169
294
|
|
|
170
|
-
已建立的 run
|
|
295
|
+
已建立的 run 會沿用建立時參與的 agent,不受這些修改影響。
|
|
171
296
|
|
|
172
297
|
## 計畫怎麼在沒有人的情況下通過
|
|
173
298
|
|
|
@@ -182,27 +307,27 @@ agentflowctl agent cycle claude-strong,codex,gemini # 不帶參數時顯
|
|
|
182
307
|
| 有幾家 | 誰來仲裁 | 結果 |
|
|
183
308
|
| --- | --- | --- |
|
|
184
309
|
| 三家以上 | 沒參與這次討論的那一家 | 核准就繼續,否則 run 失敗 |
|
|
185
|
-
| 兩家 | 兩家各自在全新 context 裡判斷 |
|
|
310
|
+
| 兩家 | 兩家各自在全新 context 裡判斷 | 都核准就繼續;都不核准就依裁決意見修訂並重新審查,仲裁達重試上限才停下;分歧依 `tieBreak` |
|
|
186
311
|
| 一家 | 同一家 | 由它自己仲裁 |
|
|
187
312
|
|
|
188
313
|
兩家時的仲裁是雙盲的。仲裁者只看計畫,以及一份不含模型名稱的爭議清單(`.flow/dispute.md`)。帶有名稱的審查紀錄移到 worktree 以外。`tieBreak` 預設 `proceed`,因為後面還有測試紅燈、綠燈、verify 與程式碼審查。
|
|
189
314
|
|
|
190
|
-
|
|
315
|
+
計畫定案或仲裁最終停止時,裁決與每位仲裁者的理由附在 `plan.md` 最後的「仲裁紀錄」。需再修訂時,裁決理由寫進 `.flow/feedback.md`,供修訂者處理;重新審查會從第一輪計數。原始審查與每輪仲裁紀錄在 `.agentflowctl/runs/<id>/reviews/`。仲裁最多進行 `AGENTFLOWCTL_MAX_ATTEMPTS` 次,預設三次。
|
|
191
316
|
|
|
192
317
|
## 階段與通過條件
|
|
193
318
|
|
|
194
319
|
| 階段 | 負責的 agent | 程式認定通過的條件 | 失敗時 |
|
|
195
320
|
|---|---|---|---|
|
|
196
|
-
| spec |
|
|
197
|
-
| plan |
|
|
198
|
-
| plan_review |
|
|
321
|
+
| spec | 隨機一位 | 檔案存在、zod 驗證、id 不重複 | 重試 |
|
|
322
|
+
| plan | 與 spec 同一位 | zod、相依存在、無循環、每條驗收條件都有任務 | 重試 |
|
|
323
|
+
| plan_review | 計畫作者以外隨機挑(可多位,不重複) | 所有審查者都 `approve` | 進入 plan_fix |
|
|
199
324
|
| plan_fix | 依 `fixStrategy` | 修改後仍通過 plan 的格式與 DAG 檢查 | 還原並重試 |
|
|
200
|
-
| 仲裁 | 見上一節 | 一致核准;分歧依 `tieBreak` |
|
|
325
|
+
| 仲裁 | 見上一節 | 一致核准;分歧依 `tieBreak` | 兩家都不核准時先進入 plan_fix,達仲裁上限才失敗;第三方不核准或 `tieBreak: stop` 時失敗 |
|
|
201
326
|
| 人工確認 | 你(只有 `--manual-plan`) | `agentflowctl approve` | — |
|
|
202
|
-
| implement 紅燈 |
|
|
203
|
-
| implement 綠燈 |
|
|
327
|
+
| implement 紅燈 | 洗牌輪流,每家各一次 | 有測試變更,而且測試執行後失敗 | 還原並重試 |
|
|
328
|
+
| implement 綠燈 | 測試作者以外隨機一位 | 測試檔沒有任何修改,而且測試通過 | 還原,或帶著輸出重試 |
|
|
204
329
|
| verify | — | `install` 與所有 `checks` 通過 | 交回作者修正 |
|
|
205
|
-
| review |
|
|
330
|
+
| review | 作者以外隨機挑(可多位,不重複) | 所有審查者都 `approve` | 依 `fixStrategy` 交給他人修正 |
|
|
206
331
|
| pr | — | push 成功;有 `gh` 就開 PR | — |
|
|
207
332
|
|
|
208
333
|
驗收條件寫在 `.flow/acceptance.json`(`AC-1`…),任務寫在 `.flow/tasks.json`(`T-1`…)。
|
|
@@ -222,7 +347,7 @@ Agent 的最後回覆要附上 XML 中繼資料:
|
|
|
222
347
|
</result>
|
|
223
348
|
```
|
|
224
349
|
|
|
225
|
-
`blocked` 與 `concerns` 會印在終端機上,完整回覆留在 log
|
|
350
|
+
`blocked` 與 `concerns` 會印在終端機上,完整回覆留在 log,可用 `agentflowctl logs` 查看。這份中繼資料只給人看;缺少或格式錯誤都不影響流程,是否通過仍由上表的程式檢查決定。
|
|
226
351
|
|
|
227
352
|
## Adapter
|
|
228
353
|
|
|
@@ -237,11 +362,7 @@ Codex 沙箱預設不能連網,所以建立 worktree 時會先跑 `install`。
|
|
|
237
362
|
|
|
238
363
|
各家讀的專案說明檔不同:Claude Code 讀 `CLAUDE.md`,Codex 讀 `AGENTS.md`,Gemini 讀 `GEMINI.md`。把專案慣例寫在 `AGENTS.md`,另外兩個檔案各用一行引用它。agentflowctl 的 prompt 在 `prompts/`,不依賴任何一家的 skills 或 plugins。
|
|
239
364
|
|
|
240
|
-
##
|
|
241
|
-
|
|
242
|
-
只支援各家 CLI 的訂閱登入。
|
|
243
|
-
|
|
244
|
-
執行 agent 時,一律從子程序環境移除 `ANTHROPIC_API_KEY`、`ANTHROPIC_AUTH_TOKEN`、`CODEX_API_KEY`、`OPENAI_API_KEY`、`GEMINI_API_KEY`、`GOOGLE_API_KEY`,避免環境裡的 key 蓋過訂閱登入。`doctor` 發現這些變數時會提醒。專案指令(install、test、build)不受影響。
|
|
365
|
+
## 額度與代打
|
|
245
366
|
|
|
246
367
|
上限是執行次數(`maxAgentRuns`,預設 60),不是金額。`status` 會列出各 agent 的執行次數與 token 數。
|
|
247
368
|
|
|
@@ -250,7 +371,7 @@ Codex 沙箱預設不能連網,所以建立 worktree 時會先跑 `install`。
|
|
|
250
371
|
| 步驟 | 行為 |
|
|
251
372
|
| --- | --- |
|
|
252
373
|
| 計畫審查、程式碼審查、仲裁 | 暫停。額度恢復後 `agentflowctl resume`。換人代審會變成作者審自己 |
|
|
253
|
-
| 規格、計畫、修改計畫、寫測試、寫實作、修正 |
|
|
374
|
+
| 規格、計畫、修改計畫、寫測試、寫實作、修正 | 隨機由另一家還有額度的 agent 代打 |
|
|
254
375
|
| 每家都用完 | 暫停 |
|
|
255
376
|
|
|
256
377
|
換人或暫停前,額度用完的 agent 留下的半成品會先清掉。代打寫在 `.agentflowctl/runs/<id>/substitutions.jsonl`,`status` 會列出。commit 結尾標的是實際執行的模型。
|
|
@@ -266,7 +387,7 @@ agentflowctl 本身只依賴 Node.js 與 git。專案指令透過系統 shell
|
|
|
266
387
|
| 環境 | 適合的用法 |
|
|
267
388
|
|---|---|
|
|
268
389
|
| 自己的電腦 | 自己的專案、自己寫的需求。剛開始可以加 `--manual-plan`,確認審查品質後再拿掉 |
|
|
269
|
-
| 容器、遠端開發機 | 無人值守。先在該環境內完成各家 CLI
|
|
390
|
+
| 容器、遠端開發機 | 無人值守。先在該環境內完成各家 CLI 的登入 |
|
|
270
391
|
| Claude Code、Codex 裡面 | 讓它們用 shell 執行 `npx agentflowctl` |
|
|
271
392
|
|
|
272
393
|
沒有容器隔離時,verify 會在你的電腦上執行 agent 寫出來的程式碼。Gemini 在無人值守時是 yolo 模式。處理外部 issue,或需求文字不是你自己寫的,放到可丟棄的環境。AI 審查計畫擋不住夾在需求裡的指示。
|
|
@@ -277,10 +398,13 @@ agentflowctl 本身只依賴 Node.js 與 git。專案指令透過系統 shell
|
|
|
277
398
|
src/
|
|
278
399
|
cli.ts 指令列(run、doctor、status……)
|
|
279
400
|
engine.ts 狀態機與各階段
|
|
280
|
-
roles.ts
|
|
401
|
+
roles.ts 角色分配規則(含計畫修正者與仲裁者)
|
|
281
402
|
runner.ts 執行 agent、正規化結果、執行專案指令
|
|
403
|
+
logs.ts log 檔名、檔頭檔尾、列表與解析
|
|
282
404
|
agents/ claude、codex、gemini、command
|
|
405
|
+
setup.ts agent setup 互動精靈
|
|
283
406
|
git.ts worktree 與 git 操作
|
|
407
|
+
cleanup.ts clean:移除 worktree 與 run 紀錄
|
|
284
408
|
store.ts 狀態、用量、代打紀錄
|
|
285
409
|
tasks.ts 任務 DAG
|
|
286
410
|
schemas.ts zod schema
|
package/dist/agentConfig.js
CHANGED
|
@@ -1,20 +1,10 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { builtinAgents, DEFAULT_CYCLE } from "./agents/index.js";
|
|
4
3
|
import { AgentDef, RepoConfig } from "./schemas.js";
|
|
5
4
|
/** 這些欄位的意義取決於 adapter,換 adapter 時要清掉 */
|
|
6
5
|
const ADAPTER_FIELDS = ["model", "extraArgs", "command"];
|
|
7
|
-
const isBuiltin = (name) => DEFAULT_CYCLE.includes(name);
|
|
8
6
|
const agentsOf = (cfg) => ({ ...(cfg.agents ?? {}) });
|
|
9
|
-
const
|
|
10
|
-
const isDefined = (cfg, name) => builtinAgents(removedOf(cfg)).includes(name) || name in agentsOf(cfg);
|
|
11
|
-
/** 寫回 removedAgents;清單變空就刪掉欄位 */
|
|
12
|
-
function withRemoved(cfg, removed) {
|
|
13
|
-
const next = { ...cfg, removedAgents: removed };
|
|
14
|
-
if (!removed.length)
|
|
15
|
-
delete next.removedAgents;
|
|
16
|
-
return next;
|
|
17
|
-
}
|
|
7
|
+
const isDefined = (cfg, name) => name in agentsOf(cfg);
|
|
18
8
|
/** 只留下有值的欄位,驗證後回傳 */
|
|
19
9
|
function buildAgent(base, patch) {
|
|
20
10
|
const next = { ...base };
|
|
@@ -37,10 +27,7 @@ export function addAgent(cfg, name, def) {
|
|
|
37
27
|
if (isDefined(cfg, name))
|
|
38
28
|
throw new Error(`agent ${name} 已存在,要修改請用 agent set`);
|
|
39
29
|
const { adapter, ...patch } = def;
|
|
40
|
-
|
|
41
|
-
// 加回先前移除的內建 agent
|
|
42
|
-
const removed = removedOf(cfg);
|
|
43
|
-
return { cfg: removed.includes(name) ? withRemoved(next, removed.filter((n) => n !== name)) : next, changes: [] };
|
|
30
|
+
return { cfg: { ...cfg, agents: { ...agentsOf(cfg), [name]: buildAgent({ adapter }, patch) } }, changes: [] };
|
|
44
31
|
}
|
|
45
32
|
export function setAgent(cfg, name, patch) {
|
|
46
33
|
if (!isDefined(cfg, name))
|
|
@@ -49,8 +36,7 @@ export function setAgent(cfg, name, patch) {
|
|
|
49
36
|
throw new Error("沒有要修改的欄位(--adapter、--model、--extra-arg 或 -- <command>)");
|
|
50
37
|
}
|
|
51
38
|
const agents = agentsOf(cfg);
|
|
52
|
-
|
|
53
|
-
const base = { ...(agents[name] ?? { adapter: name }) };
|
|
39
|
+
const base = { ...agents[name] };
|
|
54
40
|
const changes = [];
|
|
55
41
|
if (patch.adapter !== undefined && patch.adapter !== base.adapter) {
|
|
56
42
|
const cleared = ADAPTER_FIELDS.filter((f) => base[f] !== undefined && patch[f] === undefined);
|
|
@@ -66,31 +52,28 @@ export function removeAgent(cfg, name) {
|
|
|
66
52
|
throw new Error(`未定義的 agent:${name}`);
|
|
67
53
|
const agents = agentsOf(cfg);
|
|
68
54
|
delete agents[name];
|
|
69
|
-
|
|
70
|
-
let next = { ...cfg, agents };
|
|
71
|
-
if (isBuiltin(name))
|
|
72
|
-
next = withRemoved(next, [...removedOf(cfg), name]);
|
|
55
|
+
const next = { ...cfg, agents };
|
|
73
56
|
const changes = [];
|
|
74
57
|
const cycle = cfg.cycle;
|
|
75
58
|
if (cycle?.includes(name)) {
|
|
76
59
|
const rest = cycle.filter((n) => n !== name);
|
|
77
60
|
if (rest.length) {
|
|
78
61
|
next.cycle = rest;
|
|
79
|
-
changes.push(
|
|
62
|
+
changes.push(`已從參與的 agent 移除,現在是 ${rest.join("、")}`);
|
|
80
63
|
}
|
|
81
64
|
else {
|
|
82
65
|
delete next.cycle;
|
|
83
|
-
changes.push("
|
|
66
|
+
changes.push("參與的 agent 因此變空,已刪除 cycle,改回從 agents 自動偵測已安裝的 CLI");
|
|
84
67
|
}
|
|
85
68
|
}
|
|
86
69
|
return { cfg: next, changes };
|
|
87
70
|
}
|
|
88
71
|
export function setCycle(cfg, names) {
|
|
89
72
|
if (!names.length)
|
|
90
|
-
throw new Error("
|
|
73
|
+
throw new Error("參與的 agent 至少要有一個");
|
|
91
74
|
const dup = names.find((n, i) => names.indexOf(n) !== i);
|
|
92
75
|
if (dup)
|
|
93
|
-
throw new Error(
|
|
76
|
+
throw new Error(`參與的 agent 裡 ${dup} 重複了`);
|
|
94
77
|
const missing = names.filter((n) => !isDefined(cfg, n));
|
|
95
78
|
if (missing.length)
|
|
96
79
|
throw new Error(`未定義的 agent:${missing.join("、")}(先用 agent add 新增)`);
|
package/dist/agents/index.js
CHANGED
|
@@ -3,8 +3,4 @@ import { codex } from "./codex.js";
|
|
|
3
3
|
import { command } from "./command.js";
|
|
4
4
|
import { gemini } from "./gemini.js";
|
|
5
5
|
export const ADAPTERS = { claude, codex, gemini, command };
|
|
6
|
-
/** 沒有設定時,依序偵測這些已安裝的 CLI 組成輪替順序 */
|
|
7
|
-
export const DEFAULT_CYCLE = ["claude", "codex", "gemini"];
|
|
8
|
-
/** 扣掉設定裡 removedAgents 之後,仍可使用的內建 agent */
|
|
9
|
-
export const builtinAgents = (removed = []) => DEFAULT_CYCLE.filter((n) => !removed.includes(n));
|
|
10
6
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** 仲裁沒有共識時,兩家先依裁決意見修訂計畫;重複仲裁仍受次數上限約束。 */
|
|
2
|
+
export function arbitrationDecision(verdicts, tieBreak, round, maxAttempts) {
|
|
3
|
+
const approvals = verdicts.filter((v) => v === "approve").length;
|
|
4
|
+
if (approvals === verdicts.length)
|
|
5
|
+
return "proceed";
|
|
6
|
+
if (approvals > 0)
|
|
7
|
+
return tieBreak;
|
|
8
|
+
return verdicts.length === 2 && round < maxAttempts ? "revise" : "stop";
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=arbitration.js.map
|
package/dist/cleanup.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
2
|
+
import { git, removeWorktree } from "./git.js";
|
|
3
|
+
import { projectRoot, runDir, runsDir, worktreeDir, worktreesDir } from "./paths.js";
|
|
4
|
+
import { getRun } from "./store.js";
|
|
5
|
+
/**
|
|
6
|
+
* 移除一個 run 的 worktree 與紀錄,分支保留。
|
|
7
|
+
* 不需要 state.json:中斷在建立 worktree 之後、寫入紀錄之前留下的孤兒也能清。
|
|
8
|
+
* 回傳是否真的找到並移除了東西。
|
|
9
|
+
*/
|
|
10
|
+
export async function cleanRun(id) {
|
|
11
|
+
// id 會拼進要遞迴刪除的路徑,擋掉空字串、..、斜線
|
|
12
|
+
if (!/^[\w-]+$/.test(id))
|
|
13
|
+
throw new Error(`不合法的 run id:${id}`);
|
|
14
|
+
const root = projectRoot();
|
|
15
|
+
const wt = worktreeDir(id);
|
|
16
|
+
const found = existsSync(wt) || existsSync(runDir(id));
|
|
17
|
+
if (existsSync(wt)) {
|
|
18
|
+
// git 不認得這個資料夾時(登記已被 prune、或 worktree add 做到一半)改成直接刪
|
|
19
|
+
await removeWorktree(root, wt).catch(() => rmSync(wt, { recursive: true, force: true }));
|
|
20
|
+
}
|
|
21
|
+
rmSync(runDir(id), { recursive: true, force: true });
|
|
22
|
+
// 清掉資料夾已不存在的 worktree 登記,否則同名分支之後無法再 checkout
|
|
23
|
+
const before = await git(root, "worktree", "list", "--porcelain");
|
|
24
|
+
await git(root, "worktree", "prune");
|
|
25
|
+
const pruned = before !== (await git(root, "worktree", "list", "--porcelain"));
|
|
26
|
+
return found || pruned;
|
|
27
|
+
}
|
|
28
|
+
/** 已結束、可以安全清掉的階段;其他階段可能還在跑,或要 resume/approve */
|
|
29
|
+
const FINISHED = ["done", "failed"];
|
|
30
|
+
/**
|
|
31
|
+
* `clean --all` 要清的 run:已結束的,加上沒有 state.json 的孤兒(stage 為 undefined)。
|
|
32
|
+
* 讀不懂的 state.json 不算孤兒,保留給使用者自己判斷。
|
|
33
|
+
*/
|
|
34
|
+
export function cleanableRuns() {
|
|
35
|
+
const ids = new Set();
|
|
36
|
+
for (const dir of [runsDir(), worktreesDir()])
|
|
37
|
+
if (existsSync(dir))
|
|
38
|
+
for (const id of readdirSync(dir))
|
|
39
|
+
ids.add(id);
|
|
40
|
+
const out = [];
|
|
41
|
+
for (const id of [...ids].sort()) {
|
|
42
|
+
let stage;
|
|
43
|
+
try {
|
|
44
|
+
stage = getRun(id)?.stage;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (stage === undefined || FINISHED.includes(stage))
|
|
50
|
+
out.push({ id, stage });
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=cleanup.js.map
|