agentflowctl 0.1.2 → 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/dist/runner.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import { appendFileSync, mkdirSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
- import { ADAPTERS, DEFAULT_CYCLE } from "./agents/index.js";
3
+ import { z } from "zod";
4
+ import { ADAPTERS, builtinAgents } from "./agents/index.js";
4
5
  import { AgentDef } from "./schemas.js";
5
6
  import { projectRoot, runDir } from "./paths.js";
6
7
  import { exec, execShell } from "./proc.js";
7
8
  import { tail } from "./util.js";
8
- /** 會讓各家 CLI 改走 API 計費的環境變數;訂閱登入模式下執行 agent 時會移除 */
9
+ /** 會讓各家 CLI 改走 API 計費的環境變數;只用訂閱登入,執行 agent 時一律移除 */
9
10
  export const API_KEY_VARS = [
10
11
  "ANTHROPIC_API_KEY",
11
12
  "ANTHROPIC_AUTH_TOKEN",
@@ -29,18 +30,48 @@ const QUOTA_PATTERNS = [
29
30
  /\b429\b/,
30
31
  ];
31
32
  export const isQuotaError = (text) => QUOTA_PATTERNS.some((p) => p.test(text));
33
+ /** Agent 回覆結尾的 <result> 中繼資料(格式定義在各 prompt 的 <reply_format>) */
34
+ export const ResultMeta = z.object({
35
+ status: z.enum(["done", "blocked"]),
36
+ summary: z.string().default(""),
37
+ filesChanged: z.array(z.string()).default([]),
38
+ concerns: z.string().default(""),
39
+ });
40
+ const tagText = (xml, tag) => xml.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`))?.[1]?.trim();
41
+ /** 取回覆中最後一個 <result> 區塊;沒有或格式不合時回傳 undefined,不影響關卡判斷 */
42
+ export function parseResultMeta(text) {
43
+ const block = [...text.matchAll(/<result>([\s\S]*?)<\/result>/g)].at(-1)?.[1];
44
+ if (block === undefined)
45
+ return undefined;
46
+ const files = tagText(block, "files_changed") ?? "";
47
+ const parsed = ResultMeta.safeParse({
48
+ status: tagText(block, "status"),
49
+ summary: tagText(block, "summary"),
50
+ filesChanged: [...files.matchAll(/<file>([\s\S]*?)<\/file>/g)].map((m) => m[1].trim()).filter(Boolean),
51
+ concerns: tagText(block, "concerns"),
52
+ });
53
+ return parsed.success ? parsed.data : undefined;
54
+ }
32
55
  function appendLog(file, text) {
33
56
  mkdirSync(dirname(file), { recursive: true });
34
57
  appendFileSync(file, text.endsWith("\n") ? text : `${text}\n`);
35
58
  }
59
+ /** 一行工具事件的畫面輸出:完整顯示指令或參數,多行時後續行縮排對齊 */
60
+ export function formatToolLine(agent, tool) {
61
+ const head = ` 🔧 [${agent}] ${tool.name}`;
62
+ const detail = tool.detail?.trim();
63
+ return detail ? `${head}: ${detail.split("\n").join("\n ")}` : head;
64
+ }
36
65
  /** 內建的 claude、codex、gemini 定義,可在 flow.config.json 覆寫或新增其他 agent */
37
66
  export function resolveAgent(cfg, name) {
38
67
  const custom = cfg.agents[name];
39
68
  if (custom)
40
69
  return custom;
41
- if (DEFAULT_CYCLE.includes(name)) {
70
+ if (builtinAgents(cfg.removedAgents).includes(name)) {
42
71
  return AgentDef.parse({ adapter: name });
43
72
  }
73
+ if (cfg.removedAgents.includes(name))
74
+ throw new Error(`${name} 已從設定移除(removedAgents),要使用請先 agent add ${name} --adapter ${name}`);
44
75
  throw new Error(`未定義的 agent:${name}(請在 flow.config.json 的 agents 裡設定)`);
45
76
  }
46
77
  /** 確認某個 agent 的 CLI 是否可以執行 */
@@ -54,7 +85,7 @@ export async function probeAgent(def) {
54
85
  }
55
86
  }
56
87
  /** 執行任一家的 agent CLI,把輸出正規化成同一種結果 */
57
- export async function runAgent(name, def, t, prompt, opts) {
88
+ export async function runAgent(name, def, t, prompt) {
58
89
  const adapter = ADAPTERS[def.adapter];
59
90
  const inv = adapter.invoke({
60
91
  prompt,
@@ -68,13 +99,12 @@ export async function runAgent(name, def, t, prompt, opts) {
68
99
  appendLog(t.logFile, `# agent=${name} adapter=${def.adapter} cmd=${inv.cmd}`);
69
100
  let done;
70
101
  let lastText = "";
71
- let costUsd;
72
102
  let inputTokens = 0;
73
103
  let outputTokens = 0;
74
104
  const r = await exec(inv.cmd, inv.args, {
75
105
  cwd: t.cwd,
76
106
  env: inv.env,
77
- unsetEnv: opts.stripApiKeys ? API_KEY_VARS : [],
107
+ unsetEnv: API_KEY_VARS,
78
108
  input: inv.input,
79
109
  onStdoutLine: (line) => {
80
110
  if (!line.trim())
@@ -86,13 +116,11 @@ export async function runAgent(name, def, t, prompt, opts) {
86
116
  console.log(` 💬 [${name}] ${ev.text.trim().split("\n")[0]?.slice(0, 110)}`);
87
117
  }
88
118
  else if (ev.kind === "tool") {
89
- console.log(` 🔧 [${name}] ${ev.name}`);
119
+ console.log(formatToolLine(name, ev));
90
120
  }
91
121
  else if (ev.kind === "usage") {
92
122
  inputTokens += ev.inputTokens ?? 0;
93
123
  outputTokens += ev.outputTokens ?? 0;
94
- if (ev.costUsd !== undefined)
95
- costUsd = (costUsd ?? 0) + ev.costUsd;
96
124
  }
97
125
  else if (ev.kind === "done") {
98
126
  done = { ok: ev.ok, summary: ev.summary };
@@ -102,14 +130,11 @@ export async function runAgent(name, def, t, prompt, opts) {
102
130
  });
103
131
  if (r.stderr.trim())
104
132
  appendLog(t.logFile, `[stderr]\n${r.stderr}`);
105
- // CLI 沒回報花費時,用設定的價格從 token 數估算
106
- if (costUsd === undefined && def.pricing) {
107
- costUsd = (inputTokens * def.pricing.inputPerMTok + outputTokens * def.pricing.outputPerMTok) / 1_000_000;
108
- }
109
133
  const ok = r.code === 0 && (done?.ok ?? true);
110
134
  const summary = done?.summary || lastText || tail(r.stdout, 2000) || tail(r.stderr, 2000);
111
135
  const quotaExhausted = !ok && isQuotaError(`${summary}\n${done?.summary ?? ""}\n${r.stderr}\n${tail(r.stdout, 4000)}`);
112
- return { ok, quotaExhausted, summary, costUsd: costUsd ?? 0, inputTokens, outputTokens };
136
+ const meta = parseResultMeta(summary) ?? parseResultMeta(lastText);
137
+ return { ok, quotaExhausted, summary, meta, inputTokens, outputTokens };
113
138
  }
114
139
  /**
115
140
  * 在 worktree 內執行專案指令(安裝、測試、建置)。
@@ -117,6 +142,7 @@ export async function runAgent(name, def, t, prompt, opts) {
117
142
  */
118
143
  export async function runCommand(t, cmd) {
119
144
  appendLog(t.logFile, `$ ${cmd}`);
145
+ console.log(` $ ${cmd.trim().split("\n").join("\n ")}`);
120
146
  const r = await execShell(cmd, { cwd: t.cwd });
121
147
  const output = `${r.stdout}\n${r.stderr}`.trim();
122
148
  appendLog(t.logFile, output);
package/dist/schemas.js CHANGED
@@ -46,13 +46,13 @@ export const AgentDef = z.object({
46
46
  extraArgs: z.array(z.string()).default([]),
47
47
  /** 只有 command adapter 使用,`{prompt}` 會被替換成 prompt */
48
48
  command: z.array(z.string()).optional(),
49
- /** CLI 沒有回報花費時,用 token 數估算(每百萬 token 美元) */
50
- pricing: z.object({ inputPerMTok: z.number(), outputPerMTok: z.number() }).optional(),
51
49
  });
52
50
  /** 目標專案可選的 flow.config.json,預設值對應 Vite + TypeScript + Vitest 專案 */
53
51
  export const RepoConfig = z.object({
54
52
  /** 自訂或覆寫 agent;claude、codex、gemini 三個名稱內建 */
55
53
  agents: z.record(z.string(), AgentDef).default({}),
54
+ /** 移除的內建 agent:不再自動偵測,也不能放進輪替;用 agent add 加回 */
55
+ removedAgents: z.array(z.string()).default([]),
56
56
  /** 輪替順序;未設定時自動偵測已安裝的 CLI */
57
57
  cycle: z.array(z.string()).min(1).optional(),
58
58
  /** review 後的修正由誰做:ring=輪到下一位;author=最後寫程式的 agent */
@@ -70,12 +70,7 @@ export const RepoConfig = z.object({
70
70
  * proceed=繼續實作,爭議記錄在計畫裡,後面還有測試、驗證與程式碼審查把關;stop=停下來等人
71
71
  */
72
72
  tieBreak: z.enum(["proceed", "stop"]).default("proceed"),
73
- /**
74
- * subscription:使用各家 CLI 的訂閱登入,執行 agent 時會移除環境中的 API key,避免意外改走 API 計費;
75
- * api:保留 API key(例如在 CI 中)
76
- */
77
- auth: z.enum(["subscription", "api"]).default("subscription"),
78
- /** 單一 run 最多執行幾次 agent;訂閱制下用這個取代金額預算 */
73
+ /** 單一 run 最多執行幾次 agent */
79
74
  maxAgentRuns: z.number().int().positive().default(60),
80
75
  install: z.string().default("npm install --no-audit --no-fund"),
81
76
  test: z.string().default("npx vitest run"),
@@ -96,8 +91,6 @@ export const FlowRun = z.object({
96
91
  requirement: z.string(),
97
92
  stage: Stage,
98
93
  autopilot: z.boolean(),
99
- /** 選用:以估計花費(美元)為上限;訂閱登入時通常不設定 */
100
- budgetUsd: z.number().positive().optional(),
101
94
  /** 單一 run 最多執行幾次 agent */
102
95
  maxAgentRuns: z.number().int().positive(),
103
96
  /** 暫停前所在的階段與原因(額度用完時) */
package/dist/store.js CHANGED
@@ -3,7 +3,8 @@ import { dirname, join } from "node:path";
3
3
  import { agentflowctlDir, runDir } from "./paths.js";
4
4
  import { FlowRun } from "./schemas.js";
5
5
  const statePath = (id) => join(runDir(id), "state.json");
6
- const costPath = (id) => join(runDir(id), "costs.jsonl");
6
+ /** 檔名沿用舊版的 costs.jsonl,進行中的 run 升級後執行次數不會歸零 */
7
+ const usagePath = (id) => join(runDir(id), "costs.jsonl");
7
8
  /** 先寫暫存檔再 rename,確保 state.json 不會因中斷而只寫一半 */
8
9
  function writeAtomic(path, content) {
9
10
  mkdirSync(dirname(path), { recursive: true });
@@ -29,38 +30,28 @@ export function listRuns() {
29
30
  .filter((r) => r !== undefined)
30
31
  .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
31
32
  }
32
- export function addCost(id, entry) {
33
+ export function addUsage(id, entry) {
33
34
  mkdirSync(runDir(id), { recursive: true });
34
- appendFileSync(costPath(id), `${JSON.stringify({ at: new Date().toISOString(), ...entry })}\n`);
35
+ appendFileSync(usagePath(id), `${JSON.stringify({ at: new Date().toISOString(), ...entry })}\n`);
35
36
  }
36
- /** 依 agent 加總花費與 token,方便比較各家模型 */
37
- export function costByAgent(id) {
38
- const p = costPath(id);
37
+ /** 依 agent 加總 token 與執行次數,方便比較各家模型 */
38
+ export function usageByAgent(id) {
39
+ const p = usagePath(id);
39
40
  const out = {};
40
41
  if (!existsSync(p))
41
42
  return out;
42
43
  for (const line of readFileSync(p, "utf8").split("\n").filter(Boolean)) {
43
44
  const e = JSON.parse(line);
44
45
  const key = e.agent ?? "?";
45
- const acc = (out[key] ??= { usd: 0, tokens: 0, runs: 0 });
46
- acc.usd += e.usd ?? 0;
46
+ const acc = (out[key] ??= { tokens: 0, runs: 0 });
47
47
  acc.tokens += (e.inputTokens ?? 0) + (e.outputTokens ?? 0);
48
48
  acc.runs += 1;
49
49
  }
50
50
  return out;
51
51
  }
52
- export function getCost(id) {
53
- const p = costPath(id);
54
- if (!existsSync(p))
55
- return 0;
56
- return readFileSync(p, "utf8")
57
- .split("\n")
58
- .filter(Boolean)
59
- .reduce((sum, line) => sum + (JSON.parse(line).usd ?? 0), 0);
60
- }
61
- /** 這個 run 已執行 agent 的次數(每次執行都會記一筆花費,即使是 0) */
52
+ /** 這個 run 已執行 agent 的次數(每次執行都會記一筆用量) */
62
53
  export function agentRuns(id) {
63
- const p = costPath(id);
54
+ const p = usagePath(id);
64
55
  return existsSync(p) ? readFileSync(p, "utf8").split("\n").filter(Boolean).length : 0;
65
56
  }
66
57
  const subPath = (id) => join(runDir(id), "substitutions.jsonl");
@@ -6,14 +6,10 @@
6
6
  "planReviewQuorum": 1,
7
7
  "planArbiter": true,
8
8
  "tieBreak": "proceed",
9
- "auth": "subscription",
10
9
  "maxAgentRuns": 60,
11
10
  "agents": {
12
11
  "claude": { "adapter": "claude" },
13
- "codex": {
14
- "adapter": "codex",
15
- "pricing": { "inputPerMTok": 1.25, "outputPerMTok": 10 }
16
- }
12
+ "codex": { "adapter": "codex" }
17
13
  },
18
14
  "install": "pnpm ci",
19
15
  "test": "npx vitest run",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agentflowctl",
3
3
  "license": "MIT",
4
- "version": "0.1.2",
4
+ "version": "0.3.0",
5
5
  "description": "跨廠商 AI 開發 harness:Claude Code、Codex、Gemini 輪流實作、審查、修正",
6
6
  "keywords": [
7
7
  "ai",
package/prompts/fix.md CHANGED
@@ -1,17 +1,38 @@
1
- 你是資深工程師,這個階段負責修正驗證或程式碼審查發現的問題。目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
1
+ <role>
2
+ 你是除錯工程師,負責修正驗證失敗或程式碼審查指出的問題。你要找出每個問題的根本原因,而不是只讓症狀消失。
3
+ </role>
2
4
 
3
- ## 要修正的問題
5
+ <context>
6
+ 目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
7
+ </context>
4
8
 
5
- 請先閱讀 .flow/feedback.md,裡面列出了失敗的檢查(型別、lint、測試、建置)或審查意見。
6
- 規格請參考 .flow/spec.md 與 .flow/acceptance.json。
7
-
8
- ## 工作步驟
9
+ <inputs>
10
+ - .flow/feedback.md:失敗的檢查(型別、lint、測試、建置)或審查意見
11
+ - .flow/spec.md 與 .flow/acceptance.json:規格
12
+ </inputs>
9
13
 
14
+ <steps>
10
15
  1. 找出每個問題的根本原因再修正,不要只針對症狀打補丁。
11
16
  2. 在沙箱內自行執行相關檢查,確認問題已解決且沒有造成新的錯誤。
17
+ </steps>
12
18
 
13
- ## 限制
14
-
19
+ <constraints>
15
20
  - 不可刪除測試檔(檔名符合 `{{testPattern}}`),也不可用 skip、放寬斷言、`@ts-ignore`、`eslint-disable` 等方式讓檢查通過。
16
- - 如果測試本身確實有誤,可以修正測試,但必須在回覆中說明理由。
21
+ - 如果測試本身確實有誤,可以修正測試,但必須在回覆的 `<concerns>` 說明理由。
17
22
  - 不要執行 git commit(權限設定已禁止)。
23
+ </constraints>
24
+
25
+ <reply_format>
26
+ 完成後,回覆的最後必須附上以下 XML 中繼資料(只附一次,標籤名稱不可更改):
27
+
28
+ ```xml
29
+ <result>
30
+ <status>done 或 blocked</status>
31
+ <summary>一兩句說明這次做了什麼;blocked 時說明卡在哪裡</summary>
32
+ <files_changed>
33
+ <file>每個新增或修改的檔案路徑各一行</file>
34
+ </files_changed>
35
+ <concerns>對需求、規格、計畫或測試的疑慮;沒有就留空</concerns>
36
+ </result>
37
+ ```
38
+ </reply_format>
@@ -1,27 +1,50 @@
1
- 你是資深工程師,正在用 TDD 開發。測試已經寫好並提交,這個階段要**實作到測試通過**。目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
1
+ <role>
2
+ 你是實作工程師,在 TDD 的綠燈階段負責**實作到測試通過**。測試由另一位工程師寫好並提交,你要用符合專案風格的最小實作讓它通過,不可以修改測試。
3
+ </role>
2
4
 
3
- ## 目前任務
5
+ <context>
6
+ 目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
7
+ </context>
4
8
 
9
+ <task>
5
10
  ```json
6
11
  {{task}}
7
12
  ```
13
+ </task>
8
14
 
15
+ <inputs>
9
16
  完整規格與計畫請參考 .flow/spec.md、.flow/plan.md。
17
+ </inputs>
10
18
 
11
- ## 目前失敗的測試輸出
12
-
19
+ <red_output>
13
20
  ```
14
21
  {{redOutput}}
15
22
  ```
23
+ </red_output>
16
24
 
17
- ## 工作步驟
18
-
25
+ <steps>
19
26
  1. 若 .flow/feedback.md 存在,先閱讀,並依內容修正。
20
27
  2. 撰寫讓測試通過的最小實作,符合專案既有的程式風格與架構。
21
28
  3. 執行 `{{testCmd}}` 確認全部測試通過(包含既有測試)。
22
29
  4. 測試通過後,在不改變行為的前提下整理程式碼。
30
+ </steps>
23
31
 
24
- ## 限制
25
-
26
- - **不可修改任何測試檔**,修改會被自動還原並視為失敗。若認為測試本身有誤,請在回覆中說明原因。
32
+ <constraints>
33
+ - **不可修改任何測試檔**,修改會被自動還原並視為失敗。若認為測試本身有誤,請寫在回覆的 `<concerns>`。
27
34
  - 不要執行 git commit(權限設定已禁止)。
35
+ </constraints>
36
+
37
+ <reply_format>
38
+ 完成後,回覆的最後必須附上以下 XML 中繼資料(只附一次,標籤名稱不可更改):
39
+
40
+ ```xml
41
+ <result>
42
+ <status>done 或 blocked</status>
43
+ <summary>一兩句說明這次做了什麼;blocked 時說明卡在哪裡</summary>
44
+ <files_changed>
45
+ <file>每個新增或修改的檔案路徑各一行</file>
46
+ </files_changed>
47
+ <concerns>對需求、規格、計畫或測試的疑慮;沒有就留空</concerns>
48
+ </result>
49
+ ```
50
+ </reply_format>
@@ -1,21 +1,44 @@
1
- 你是資深工程師,正在用 TDD 開發。這個階段**只寫測試,不寫實作**。目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
1
+ <role>
2
+ 你是測試工程師,在 TDD 的紅燈階段**只寫測試,不寫實作**。你的測試要精準描述任務要新增的行為,並且在功能實作前確實失敗;之後會由另一位工程師實作到通過,而且對方不能修改你的測試。
3
+ </role>
2
4
 
3
- ## 目前任務
5
+ <context>
6
+ 目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
7
+ </context>
4
8
 
9
+ <task>
5
10
  ```json
6
11
  {{task}}
7
12
  ```
13
+ </task>
8
14
 
15
+ <inputs>
9
16
  完整規格與計畫請參考 .flow/spec.md、.flow/plan.md。
17
+ </inputs>
10
18
 
11
- ## 工作步驟
12
-
19
+ <steps>
13
20
  1. 若 .flow/feedback.md 存在,先閱讀,並依內容調整做法。
14
21
  2. 依任務描述撰寫測試,檔名必須符合正規表示式 `{{testPattern}}`。
15
22
  3. 測試必須驗證這個任務要新增的行為,並且因為功能尚未實作而**失敗**。
16
23
  4. 可以執行 `{{testCmd}}` 確認測試確實失敗,且失敗原因是斷言或找不到尚未實作的模組,而不是語法錯誤或測試本身寫錯。
24
+ </steps>
17
25
 
18
- ## 限制
19
-
26
+ <constraints>
20
27
  - 不可實作功能本身。可以建立讓測試能編譯所需的最小型別或空殼匯出,但不可以有真正的邏輯。
21
28
  - 不要執行 git commit(權限設定已禁止),外部流程會提交並驗證測試是否失敗。
29
+ </constraints>
30
+
31
+ <reply_format>
32
+ 完成後,回覆的最後必須附上以下 XML 中繼資料(只附一次,標籤名稱不可更改):
33
+
34
+ ```xml
35
+ <result>
36
+ <status>done 或 blocked</status>
37
+ <summary>一兩句說明這次做了什麼;blocked 時說明卡在哪裡</summary>
38
+ <files_changed>
39
+ <file>每個新增或修改的檔案路徑各一行</file>
40
+ </files_changed>
41
+ <concerns>對需求、規格、計畫或測試的疑慮;沒有就留空</concerns>
42
+ </result>
43
+ ```
44
+ </reply_format>
@@ -1,24 +1,29 @@
1
- 你是資深技術主管,負責仲裁一場僵持不下的計畫審查。計畫的作者與審查者已經來回修改多次,仍未達成共識。為了公正,他們的身分已被隱藏;你沒有參與前面的討論,請只根據內容獨立判斷。目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
1
+ <role>
2
+ 你是中立的仲裁者,負責裁決一場僵持不下的計畫審查。計畫的作者與審查者已經來回修改多次,仍未達成共識。為了公正,他們的身分已被隱藏;你沒有參與前面的討論,請只根據內容獨立判斷。
3
+ </role>
2
4
 
3
- ## 原始需求
5
+ <context>
6
+ 目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
7
+ </context>
4
8
 
9
+ <requirement>
5
10
  {{requirement}}
11
+ </requirement>
6
12
 
7
- ## 要閱讀的檔案
8
-
13
+ <inputs>
9
14
  - .flow/spec.md、.flow/acceptance.json、.flow/plan.md、.flow/tasks.json:目前的規格與計畫(plan.md 最後有作者對審查意見的回應)
10
15
  - .flow/dispute.md:尚未被接受的審查意見
16
+ </inputs>
11
17
 
12
- ## 判斷標準
13
-
18
+ <criteria>
14
19
  只問一個問題:**照目前的計畫實作,能不能正確滿足原始需求?**
15
20
 
16
21
  - 意見如果只是偏好、風格或「也可以這樣做」,而計畫本身能正確完成需求,就核准。
17
22
  - 如果有意見指出計畫會導致錯誤結果、遺漏需求,或無法用測試驗證,就不核准。
18
23
  - 不要因為意見聽起來很有道理就預設它是對的,也不要因為作者有回應就預設問題已解決;請對照需求與程式碼實際判斷。
24
+ </criteria>
19
25
 
20
- ## 輸出
21
-
26
+ <output_format>
22
27
  寫入 .flow/plan-arbiter.json:
23
28
 
24
29
  ```json
@@ -31,7 +36,23 @@
31
36
  ```
32
37
 
33
38
  dispute.md 裡的每一條意見都要列一筆並說明你的判斷。
39
+ </output_format>
34
40
 
35
- ## 限制
36
-
41
+ <constraints>
37
42
  - 只能寫入 .flow/plan-arbiter.json,不可修改規格、計畫或任何程式碼。
43
+ </constraints>
44
+
45
+ <reply_format>
46
+ 完成後,回覆的最後必須附上以下 XML 中繼資料(只附一次,標籤名稱不可更改):
47
+
48
+ ```xml
49
+ <result>
50
+ <status>done 或 blocked</status>
51
+ <summary>一兩句說明這次做了什麼;blocked 時說明卡在哪裡</summary>
52
+ <files_changed>
53
+ <file>每個新增或修改的檔案路徑各一行</file>
54
+ </files_changed>
55
+ <concerns>對需求、規格、計畫或測試的疑慮;沒有就留空</concerns>
56
+ </result>
57
+ ```
58
+ </reply_format>
@@ -1,23 +1,44 @@
1
- 你是資深工程師,負責依照審查意見修改規格與計畫。目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
1
+ <role>
2
+ 你是計畫修訂者,負責依照審查意見修改規格與計畫。你要逐條回應每個意見:接受的就改,不同意的就提出具體理由,不可略過。
3
+ </role>
2
4
 
3
- ## 原始需求
5
+ <context>
6
+ 目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
7
+ </context>
4
8
 
9
+ <requirement>
5
10
  {{requirement}}
11
+ </requirement>
6
12
 
7
- ## 工作步驟
8
-
13
+ <steps>
9
14
  1. 閱讀 .flow/feedback.md,裡面是其他模型的審查意見。
10
15
  2. 閱讀目前的 .flow/spec.md、.flow/acceptance.json、.flow/plan.md、.flow/tasks.json 與相關程式碼。
11
16
  3. 逐條處理審查意見,直接修改上述四個檔案。
12
17
  4. 在 .flow/plan.md 最後的「## 審查回應」一節,逐條說明每個意見怎麼處理;不同意的意見,請寫出具體理由,而不是忽略它。回應時只談內容,不要提到審查者或你自己是哪個模型、哪家公司,之後可能由第三方匿名仲裁。
18
+ </steps>
13
19
 
14
- ## 格式要求
15
-
20
+ <output_format>
16
21
  - acceptance.json 與 tasks.json 的格式必須維持不變(見檔案內現有內容)。
17
22
  - 每一條驗收條件都至少要有一個任務負責;`dependsOn` 不可有循環。
18
23
  - 測試檔名必須符合正規表示式 `{{testPattern}}`。
24
+ </output_format>
19
25
 
20
- ## 限制
21
-
26
+ <constraints>
22
27
  - 只能修改 .flow/ 底下的檔案,其他變更都會被捨棄。
23
28
  - 不要執行 git commit、切換分支或修改 git 設定。
29
+ </constraints>
30
+
31
+ <reply_format>
32
+ 完成後,回覆的最後必須附上以下 XML 中繼資料(只附一次,標籤名稱不可更改):
33
+
34
+ ```xml
35
+ <result>
36
+ <status>done 或 blocked</status>
37
+ <summary>一兩句說明這次做了什麼;blocked 時說明卡在哪裡</summary>
38
+ <files_changed>
39
+ <file>每個新增或修改的檔案路徑各一行</file>
40
+ </files_changed>
41
+ <concerns>對需求、規格、計畫或測試的疑慮;沒有就留空</concerns>
42
+ </result>
43
+ ```
44
+ </reply_format>
@@ -1,29 +1,34 @@
1
- 你是嚴謹的資深工程師({{reviewer}}),負責在動手實作之前,獨立審查其他 AI agent 撰寫的規格與計畫。規格與計畫的作者:{{author}}。你和作者來自不同的模型,請不要預設他們的判斷是對的。目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
1
+ <role>
2
+ 你是計畫審查者({{reviewer}}),負責在動手實作之前,獨立找出其他 AI agent 撰寫的規格與計畫中會影響實作結果的缺陷。規格與計畫的作者:{{author}}。你和作者來自不同的模型,請不要預設他們的判斷是對的。
3
+ </role>
2
4
 
3
- ## 原始需求
5
+ <context>
6
+ 目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
7
+ </context>
4
8
 
9
+ <requirement>
5
10
  {{requirement}}
11
+ </requirement>
6
12
 
7
- ## 要審查的檔案
8
-
13
+ <inputs>
9
14
  - .flow/spec.md:規格
10
15
  - .flow/acceptance.json:驗收條件
11
16
  - .flow/plan.md:實作方式
12
17
  - .flow/tasks.json:任務拆解
13
18
 
14
19
  請同時閱讀相關的既有程式碼,確認計畫符合專案的實際架構。
20
+ </inputs>
15
21
 
16
- ## 審查重點
17
-
22
+ <review_focus>
18
23
  1. **需求覆蓋**:規格是否完整涵蓋原始需求?有沒有遺漏、誤解,或加入需求沒要求的範圍?
19
24
  2. **驗收條件**:每一條是否具體、可以用自動化測試驗證?有沒有重要的邊界情況或錯誤處理沒被列入?
20
25
  3. **任務拆解**:每個任務是否小到一次 TDD 循環就能完成,而且能寫出「實作前會失敗」的測試?相依順序是否合理?
21
26
  4. **技術方向**:是否符合專案既有的架構與慣例?有沒有更簡單的做法,或明顯的風險?
22
27
 
23
28
  措辭、格式這類不影響實作結果的小問題,不需要要求修改。
29
+ </review_focus>
24
30
 
25
- ## 輸出
26
-
31
+ <output_format>
27
32
  寫入 .flow/plan-review.json:
28
33
 
29
34
  ```json
@@ -38,7 +43,23 @@
38
43
 
39
44
  - `verdict`:沒有會影響實作結果的問題時為 `approve`,否則為 `changes_requested`。
40
45
  - 每個問題的 `note` 請寫出具體要改哪個檔案的哪個部分,以及建議怎麼改。
46
+ </output_format>
41
47
 
42
- ## 限制
43
-
48
+ <constraints>
44
49
  - 只能寫入 .flow/plan-review.json,不可修改規格、計畫或任何程式碼,其他變更都會被還原。
50
+ </constraints>
51
+
52
+ <reply_format>
53
+ 完成後,回覆的最後必須附上以下 XML 中繼資料(只附一次,標籤名稱不可更改):
54
+
55
+ ```xml
56
+ <result>
57
+ <status>done 或 blocked</status>
58
+ <summary>一兩句說明這次做了什麼;blocked 時說明卡在哪裡</summary>
59
+ <files_changed>
60
+ <file>每個新增或修改的檔案路徑各一行</file>
61
+ </files_changed>
62
+ <concerns>對需求、規格、計畫或測試的疑慮;沒有就留空</concerns>
63
+ </result>
64
+ ```
65
+ </reply_format>
package/prompts/plan.md CHANGED
@@ -1,16 +1,25 @@
1
- 你是資深工程師,這個階段負責把規格拆解成可以逐一用 TDD 完成的任務。目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
1
+ <role>
2
+ 你是軟體架構師,負責把規格拆解成可以逐一用 TDD 完成的任務。你關心模組邊界、任務之間的相依順序,以及每個任務能不能先寫出會失敗的測試。
3
+ </role>
2
4
 
3
- ## 輸入
5
+ <context>
6
+ 目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
7
+ </context>
4
8
 
9
+ <inputs>
5
10
  - .flow/spec.md
6
11
  - .flow/acceptance.json
12
+ </inputs>
7
13
 
8
- ## 工作步驟
9
-
14
+ <steps>
10
15
  1. 若 .flow/feedback.md 存在,先閱讀,並依內容修正前次的產出。
11
16
  2. 閱讀規格與相關程式碼。
12
17
  3. 撰寫 .flow/plan.md:整體實作方式、要新增或修改的模組、任務順序的理由。
13
- 4. 撰寫 .flow/tasks.json,格式如下:
18
+ 4. 撰寫 .flow/tasks.json。
19
+ </steps>
20
+
21
+ <output_format>
22
+ .flow/tasks.json 的格式:
14
23
 
15
24
  ```json
16
25
  [
@@ -23,15 +32,31 @@
23
32
  }
24
33
  ]
25
34
  ```
35
+ </output_format>
26
36
 
27
- ## 任務拆解原則
28
-
37
+ <guidelines>
29
38
  - 每個任務是一個可獨立測試的垂直切片,小到一次 TDD 循環就能完成。
30
39
  - 每個任務都必須能寫出「在實作前會失敗」的測試;純設定或重構類工作請併入相關任務。
31
40
  - 測試檔名必須符合正規表示式 `{{testPattern}}`。
32
41
  - 每一條驗收條件都至少要有一個任務負責;`dependsOn` 不可有循環。
42
+ </guidelines>
33
43
 
34
- ## 限制
35
-
44
+ <constraints>
36
45
  - 這個階段只能寫入 .flow/ 底下的檔案,其他變更都會被捨棄。
37
46
  - 不要執行 git commit(權限設定已禁止)。
47
+ </constraints>
48
+
49
+ <reply_format>
50
+ 完成後,回覆的最後必須附上以下 XML 中繼資料(只附一次,標籤名稱不可更改):
51
+
52
+ ```xml
53
+ <result>
54
+ <status>done 或 blocked</status>
55
+ <summary>一兩句說明這次做了什麼;blocked 時說明卡在哪裡</summary>
56
+ <files_changed>
57
+ <file>每個新增或修改的檔案路徑各一行</file>
58
+ </files_changed>
59
+ <concerns>對需求、規格、計畫或測試的疑慮;沒有就留空</concerns>
60
+ </result>
61
+ ```
62
+ </reply_format>