agentflowctl 0.4.0 → 0.6.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/roles.js CHANGED
@@ -2,78 +2,109 @@
2
2
  * 決定每個步驟由哪個 agent 執行。
3
3
  *
4
4
  * 規則只有三條:
5
- * 1. 審查者不能是最後寫程式的 agent。
5
+ * 1. 審查者不能是最後寫程式的 agent,多位審查者彼此不重複。
6
6
  * 2. 開啟 tddSplit 時,同一個任務的測試與實作由不同 agent 負責。
7
- * 3. 所有選擇都沿著同一個輪替順序(cycle)前進,所以不同公司的模型會輪流扮演各個角色。
7
+ * 3. 人選隨機決定,不依 cycle 的順序。cycle 只代表有哪些 agent 參與。
8
+ *
9
+ * 隨機以 seed(run id 加上步驟與輪次)決定:同一個 run 的同一步驟重算時會得到同樣的人,
10
+ * 所以 resume、或同一步驟在不同地方重算時結果一致。
8
11
  */
9
- /** 從 from 的下一位開始,找第一個不在 exclude 裡的 agent;全部被排除時退回下一位(單一 agent 也能運作) */
10
- export function nextAfter(cycle, from, exclude = []) {
11
- const n = cycle.length;
12
- const start = from === undefined ? -1 : cycle.indexOf(from);
13
- for (let i = 1; i <= n; i++) {
14
- const candidate = cycle[(start + i + n) % n];
15
- if (!exclude.includes(candidate))
16
- return candidate;
12
+ /** FNV-1a 雜湊:把 seed 字串轉成 32 位元整數 */
13
+ function hash(seed) {
14
+ let h = 0x811c9dc5;
15
+ for (let i = 0; i < seed.length; i++) {
16
+ h ^= seed.charCodeAt(i);
17
+ h = Math.imul(h, 0x01000193);
17
18
  }
18
- return cycle[(start + 1 + n) % n];
19
+ return h >>> 0;
19
20
  }
20
- export const specAgent = (cycle) => cycle[0];
21
- export const planAgent = (cycle) => cycle[0];
22
- /** 第 i 個任務的測試由輪替順序的第 i 位負責,實作交給下一位 */
23
- export function taskAgents(cycle, taskIndex, tddSplit) {
24
- const tests = cycle[taskIndex % cycle.length];
25
- return { tests, code: tddSplit ? nextAfter(cycle, tests, [tests]) : tests };
21
+ /** mulberry32:由 seed 決定的亂數產生器 */
22
+ function rng(seed) {
23
+ let a = hash(seed);
24
+ return () => {
25
+ a = (a + 0x6d2b79f5) >>> 0;
26
+ let t = a;
27
+ t = Math.imul(t ^ (t >>> 15), t | 1);
28
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
29
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
30
+ };
26
31
  }
27
- /** 從最後寫程式的 agent 之後,依序挑出 quorum 位不重複、而且不是作者的 reviewer */
28
- export function reviewers(cycle, lastWriter, quorum) {
29
- const picked = [];
30
- let cursor = lastWriter;
31
- for (let i = 0; i < cycle.length && picked.length < quorum; i++) {
32
- const next = nextAfter(cycle, cursor, [...(lastWriter ? [lastWriter] : []), ...picked]);
33
- if (picked.includes(next) || (next === lastWriter && cycle.length > 1))
34
- break;
35
- picked.push(next);
36
- cursor = next;
32
+ /** 由 seed 決定的洗牌,不改動原陣列 */
33
+ export function shuffled(items, seed) {
34
+ const out = [...items];
35
+ const rand = rng(seed);
36
+ for (let i = out.length - 1; i > 0; i--) {
37
+ const j = Math.floor(rand() * (i + 1));
38
+ [out[i], out[j]] = [out[j], out[i]];
37
39
  }
38
- return picked.length ? picked : [nextAfter(cycle, lastWriter)];
40
+ return out;
41
+ }
42
+ /** 隨機挑一位不在 exclude 裡的 agent;全部被排除時退回任一位(單一 agent 也能運作) */
43
+ export function pick(cycle, seed, exclude = []) {
44
+ const order = shuffled(cycle, seed);
45
+ return order.find((c) => !exclude.includes(c)) ?? order[0];
46
+ }
47
+ /** 規格與計畫由同一位撰寫,計畫審查才能同時避開兩者的作者 */
48
+ export const specAgent = (cycle, seed) => pick(cycle, `${seed}:author`);
49
+ export const planAgent = specAgent;
50
+ /**
51
+ * 每 cycle.length 個任務為一輪,每輪洗一次牌,所以每家輪完一次才會再輪到;
52
+ * 換輪時若新一輪第一位和上一輪最後一位相同,就把它往後移,避免同一家連續撰寫測試。
53
+ */
54
+ function taskBag(cycle, seed, bag) {
55
+ const order = shuffled(cycle, `${seed}:tasks:${bag}`);
56
+ if (bag === 0 || order.length < 2)
57
+ return order;
58
+ const prevLast = taskBag(cycle, seed, bag - 1).at(-1);
59
+ return order[0] === prevLast ? [...order.slice(1), order[0]] : order;
60
+ }
61
+ /** 第 i 個任務的測試作者從洗好的牌依序取;實作者隨機挑一位測試作者以外的 agent */
62
+ export function taskAgents(cycle, taskIndex, tddSplit, seed) {
63
+ const n = cycle.length;
64
+ const tests = taskBag(cycle, seed, Math.floor(taskIndex / n))[taskIndex % n];
65
+ return { tests, code: tddSplit ? pick(cycle, `${seed}:code:${taskIndex}`, [tests]) : tests };
66
+ }
67
+ /** 隨機挑出 quorum 位彼此不重複、而且不是作者的 reviewer */
68
+ export function reviewers(cycle, lastWriter, quorum, seed) {
69
+ const picked = shuffled(cycle.filter((c) => c !== lastWriter), seed).slice(0, quorum);
70
+ return picked.length ? picked : [lastWriter ?? cycle[0]];
39
71
  }
40
72
  /**
41
73
  * 誰來修正:
42
74
  * - verify 失敗(型別、lint、建置這類機械性錯誤):交給最後寫程式的 agent
43
- * - review 要求修改:ring 策略輪到 reviewer 的下一位;author 策略交回作者
75
+ * - review 要求修改:ring 策略隨機挑一位 reviewer 以外的 agent;author 策略交回作者
44
76
  */
45
77
  export function fixAgent(cycle, opts) {
46
78
  if (opts.source === "verify" || opts.strategy === "author")
47
79
  return opts.lastWriter ?? cycle[0];
48
- return nextAfter(cycle, opts.lastReviewer, opts.lastReviewer ? [opts.lastReviewer] : []);
80
+ return pick(cycle, opts.seed, opts.lastReviewer ? [opts.lastReviewer] : []);
49
81
  }
50
- /** 計畫修正者:ring 策略輪到審查者的下一位;author 策略交回計畫作者 */
82
+ /** 計畫修正者:ring 策略隨機挑一位審查者以外的 agent;author 策略交回計畫作者 */
51
83
  export function planFixAgent(cycle, opts) {
52
84
  if (opts.strategy === "author")
53
85
  return opts.planWriter ?? cycle[0];
54
- return nextAfter(cycle, opts.planReviewer, opts.planReviewer ? [opts.planReviewer] : []);
86
+ return pick(cycle, opts.seed, opts.planReviewer ? [opts.planReviewer] : []);
55
87
  }
56
88
  /**
57
89
  * 仲裁小組:
58
- * - 有第三方(作者與最後審查者以外的 agent)→ 由第三方單獨仲裁
90
+ * - 有第三方(作者與最後審查者以外的 agent)→ 隨機挑一位第三方單獨仲裁
59
91
  * - 只有兩家 → 兩家各自在全新 context 中雙盲仲裁,避免讓一直反對的審查者同時當裁判
60
92
  * - 只有一家 → 只能由它自己仲裁
61
93
  */
62
- export function arbiterPanel(cycle, writer, reviewer) {
94
+ export function arbiterPanel(cycle, seed, writer, reviewer) {
63
95
  const involved = [writer, reviewer].filter((x) => Boolean(x));
64
- const third = cycle.find((c) => !involved.includes(c));
96
+ const third = shuffled(cycle, seed).find((c) => !involved.includes(c));
65
97
  if (third)
66
98
  return [third];
67
99
  return [...new Set(cycle)];
68
100
  }
69
101
  /**
70
- * 代打人選:原本的 agent 可用就用它;額度用完時,沿輪替順序找下一位還有額度的 agent。
102
+ * 代打人選:原本的 agent 可用就用它;額度用完時,隨機挑一位還有額度的 agent。
71
103
  * 全部都用完時回傳 undefined。
72
104
  */
73
- export function availableAgent(cycle, preferred, exhausted) {
105
+ export function availableAgent(cycle, preferred, exhausted, seed) {
74
106
  if (!exhausted.includes(preferred))
75
107
  return preferred;
76
- const next = nextAfter(cycle, preferred, exhausted);
77
- return exhausted.includes(next) ? undefined : next;
108
+ return shuffled(cycle, seed).find((c) => !exhausted.includes(c));
78
109
  }
79
110
  //# sourceMappingURL=roles.js.map
package/dist/runner.js CHANGED
@@ -59,7 +59,8 @@ export function resolveAgent(cfg, name) {
59
59
  const def = cfg.agents[name];
60
60
  if (!def)
61
61
  throw new Error(`未定義的 agent:${name}(請先用 agent add ${name} --adapter <adapter> 新增)`);
62
- return def;
62
+ const defaultModel = def.adapter === "command" ? undefined : cfg.defaultModels[def.adapter];
63
+ return { ...def, model: def.model ?? defaultModel };
63
64
  }
64
65
  /** 確認某個 agent 的 CLI 是否可以執行 */
65
66
  export async function probeAgent(def) {
package/dist/schemas.js CHANGED
@@ -14,6 +14,50 @@ export const Stage = z.enum([
14
14
  "done",
15
15
  "failed",
16
16
  ]);
17
+ export const HandoffSource = z.object({
18
+ stage: Stage,
19
+ step: z.string().min(1),
20
+ agent: z.string().min(1),
21
+ callKey: z.string().min(1),
22
+ });
23
+ const HandoffKind = z.enum(["action", "info"]);
24
+ const HandoffTarget = z.enum(["plan", "code"]);
25
+ const HandoffStatus = z.enum(["open", "proposed_resolved", "resolved", "accepted"]);
26
+ export const HandoffResponse = z.object({
27
+ newIssues: z.array(z.object({
28
+ kind: HandoffKind,
29
+ summary: z.string().trim().min(1),
30
+ evidence: z.string().trim().min(1),
31
+ targetStage: HandoffTarget,
32
+ })),
33
+ dispositions: z.array(z.object({
34
+ id: z.string().min(1),
35
+ status: z.enum(["proposed_resolved", "resolved", "accepted"]),
36
+ reason: z.string().trim().min(1),
37
+ evidence: z.string().trim().min(1),
38
+ })),
39
+ });
40
+ export const HandoffIssue = z.object({
41
+ id: z.string().min(1),
42
+ source: HandoffSource,
43
+ kind: HandoffKind,
44
+ summary: z.string().min(1),
45
+ evidence: z.string().min(1),
46
+ targetStage: HandoffTarget,
47
+ status: HandoffStatus,
48
+ resolution: z.object({
49
+ agent: z.string(),
50
+ reason: z.string(),
51
+ evidence: z.string(),
52
+ }).optional(),
53
+ updatedAt: z.string(),
54
+ });
55
+ export const HandoffLedger = z.object({
56
+ version: z.literal(1),
57
+ issues: z.array(HandoffIssue),
58
+ /** 用來辨識沒有新增事項的回覆是否已經合併。 */
59
+ appliedCalls: z.array(z.string()).optional(),
60
+ });
17
61
  /** Agent 在 spec 階段產出的 .flow/acceptance.json */
18
62
  export const AcceptanceList = z
19
63
  .array(z.object({
@@ -39,7 +83,12 @@ export const ReviewResult = z.object({
39
83
  note: z.string().default(""),
40
84
  })),
41
85
  });
42
- /** 一個 agent 的定義;名稱(agents 的 key)用在輪替順序裡 */
86
+ /** 仲裁者可能用 reject 表示否決;讀取時正規化,保留相同的理由欄位。 */
87
+ export const ArbiterResult = ReviewResult.extend({
88
+ verdict: z.enum(["approve", "changes_requested", "reject"])
89
+ .transform((verdict) => verdict === "reject" ? "changes_requested" : verdict),
90
+ });
91
+ /** 一個 agent 的定義;名稱(agents 的 key)用在 cycle 裡 */
43
92
  export const AgentDef = z.object({
44
93
  adapter: z.enum(["claude", "codex", "gemini", "command"]),
45
94
  model: z.string().optional(),
@@ -51,7 +100,13 @@ export const AgentDef = z.object({
51
100
  export const RepoConfig = z.object({
52
101
  /** 可用的 agent;沒有內建,全部都要在這裡定義(通常用 agent add) */
53
102
  agents: z.record(z.string(), AgentDef).default({}),
54
- /** 輪替順序;未設定時依 agents 的順序,取已安裝的 CLI */
103
+ /** 各 CLI adapter 的預設模型;agent 自己指定 model 時優先使用個別設定 */
104
+ defaultModels: z.strictObject({
105
+ claude: z.string().trim().min(1).optional(),
106
+ codex: z.string().trim().min(1).optional(),
107
+ gemini: z.string().trim().min(1).optional(),
108
+ }).default({}),
109
+ /** 參與的 agent(順序不影響分工);未設定時取 agents 裡已安裝的 CLI */
55
110
  cycle: z.array(z.string()).min(1).optional(),
56
111
  /** review 後的修正由誰做:ring=輪到下一位;author=最後寫程式的 agent */
57
112
  fixStrategy: z.enum(["ring", "author"]).default("ring"),
@@ -94,7 +149,7 @@ export const FlowRun = z.object({
94
149
  /** 暫停前所在的階段與原因(額度用完時) */
95
150
  pausedStage: Stage.optional(),
96
151
  pauseReason: z.string().optional(),
97
- /** 這個 run 的 agent 輪替順序(建立時決定,resume 時沿用) */
152
+ /** 這個 run 參與的 agent(建立時決定,resume 時沿用) */
98
153
  cycle: z.array(z.string()).min(1),
99
154
  /** 最後一個寫程式的 agent,reviewer 不可以是它 */
100
155
  lastWriter: z.string().optional(),
package/dist/setup.js CHANGED
@@ -46,7 +46,7 @@ export async function runSetup(initial, deps) {
46
46
  return null;
47
47
  }
48
48
  for (;;) {
49
- const list = (await ask("輪替順序,用逗號分隔", chosen.join(","))).split(",").map((s) => s.trim()).filter(Boolean);
49
+ const list = (await ask("參與的 agent,用逗號分隔", chosen.join(","))).split(",").map((s) => s.trim()).filter(Boolean);
50
50
  try {
51
51
  cfg = setCycle(cfg, list).cfg;
52
52
  break;
@@ -59,7 +59,7 @@ export async function runSetup(initial, deps) {
59
59
  log("\n即將寫入:");
60
60
  for (const name of chosen)
61
61
  log(` ${name.padEnd(14)} adapter=${agents[name].adapter}${agents[name].model ? ` model=${agents[name].model}` : ""}`);
62
- log(` 輪替順序:${cfg.cycle.join(" → ")}`);
62
+ log(` 參與的 agent:${cfg.cycle.join("、")}`);
63
63
  log(" 需要自訂指令的 CLI 請改用 agent add <name> --adapter command -- <指令>");
64
64
  if (!(await confirm("寫入 flow.config.json?", true))) {
65
65
  log("已取消,設定沒有變更");
@@ -7,6 +7,7 @@
7
7
  "planArbiter": true,
8
8
  "tieBreak": "proceed",
9
9
  "maxAgentRuns": 60,
10
+ "defaultModels": { "claude": "Claude 模型名稱", "codex": "Codex 模型名稱" },
10
11
  "agents": {
11
12
  "claude": { "adapter": "claude" },
12
13
  "codex": { "adapter": "codex" }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agentflowctl",
3
3
  "license": "MIT",
4
- "version": "0.4.0",
4
+ "version": "0.6.0",
5
5
  "description": "跨廠商 AI 開發 harness:Claude Code、Codex、Gemini 輪流實作、審查、修正",
6
6
  "keywords": [
7
7
  "ai",
package/prompts/fix.md CHANGED
@@ -6,6 +6,17 @@
6
6
  目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
7
7
  </context>
8
8
 
9
+ <handoff>
10
+ 先閱讀 .flow/handoff-context.md,處理與本階段有關的待辦事項。完成時寫入 .flow/handoff-response.json;即使沒有事項也必須寫出空陣列:
11
+
12
+ ```json
13
+ { "newIssues": [], "dispositions": [] }
14
+ ```
15
+
16
+ 新增事項格式:{ "kind": "action 或 info", "summary": "具體問題", "evidence": "檔案位置或檢查證據", "targetStage": "plan 或 code" }。
17
+ 處置格式:{ "id": "既有事項 ID", "status": "proposed_resolved、resolved 或 accepted", "reason": "具體處理理由", "evidence": "檔案、commit 或檢查結果" }。撰寫者只能用 proposed_resolved 提出修正;審查者可以用 resolved 或 accepted 結案。重要疑慮必須放在這份檔案,不能只寫在回覆的 <concerns>。
18
+ </handoff>
19
+
9
20
  <inputs>
10
21
  - .flow/feedback.md:失敗的檢查(型別、lint、測試、建置)或審查意見
11
22
  - .flow/spec.md 與 .flow/acceptance.json:規格
@@ -6,6 +6,17 @@
6
6
  目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
7
7
  </context>
8
8
 
9
+ <handoff>
10
+ 先閱讀 .flow/handoff-context.md,處理與本階段有關的待辦事項。完成時寫入 .flow/handoff-response.json;即使沒有事項也必須寫出空陣列:
11
+
12
+ ```json
13
+ { "newIssues": [], "dispositions": [] }
14
+ ```
15
+
16
+ 新增事項格式:{ "kind": "action 或 info", "summary": "具體問題", "evidence": "檔案位置或檢查證據", "targetStage": "plan 或 code" }。
17
+ 處置格式:{ "id": "既有事項 ID", "status": "proposed_resolved、resolved 或 accepted", "reason": "具體處理理由", "evidence": "檔案、commit 或檢查結果" }。撰寫者只能用 proposed_resolved 提出修正;審查者可以用 resolved 或 accepted 結案。重要疑慮必須放在這份檔案,不能只寫在回覆的 <concerns>。
18
+ </handoff>
19
+
9
20
  <task>
10
21
  ```json
11
22
  {{task}}
@@ -6,6 +6,17 @@
6
6
  目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
7
7
  </context>
8
8
 
9
+ <handoff>
10
+ 先閱讀 .flow/handoff-context.md,處理與本階段有關的待辦事項。完成時寫入 .flow/handoff-response.json;即使沒有事項也必須寫出空陣列:
11
+
12
+ ```json
13
+ { "newIssues": [], "dispositions": [] }
14
+ ```
15
+
16
+ 新增事項格式:{ "kind": "action 或 info", "summary": "具體問題", "evidence": "檔案位置或檢查證據", "targetStage": "plan 或 code" }。
17
+ 處置格式:{ "id": "既有事項 ID", "status": "proposed_resolved、resolved 或 accepted", "reason": "具體處理理由", "evidence": "檔案、commit 或檢查結果" }。撰寫者只能用 proposed_resolved 提出修正;審查者可以用 resolved 或 accepted 結案。重要疑慮必須放在這份檔案,不能只寫在回覆的 <concerns>。
18
+ </handoff>
19
+
9
20
  <task>
10
21
  ```json
11
22
  {{task}}
@@ -6,6 +6,17 @@
6
6
  目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
7
7
  </context>
8
8
 
9
+ <handoff>
10
+ 先閱讀 .flow/handoff-context.md,處理與本階段有關的待辦事項。完成時寫入 .flow/handoff-response.json;即使沒有事項也必須寫出空陣列:
11
+
12
+ ```json
13
+ { "newIssues": [], "dispositions": [] }
14
+ ```
15
+
16
+ 新增事項格式:{ "kind": "action 或 info", "summary": "具體問題", "evidence": "檔案位置或檢查證據", "targetStage": "plan 或 code" }。
17
+ 處置格式:{ "id": "既有事項 ID", "status": "proposed_resolved、resolved 或 accepted", "reason": "具體處理理由", "evidence": "檔案、commit 或檢查結果" }。撰寫者只能用 proposed_resolved 提出修正;審查者可以用 resolved 或 accepted 結案。重要疑慮必須放在這份檔案,不能只寫在回覆的 <concerns>。
18
+ </handoff>
19
+
9
20
  <requirement>
10
21
  {{requirement}}
11
22
  </requirement>
@@ -36,10 +47,11 @@
36
47
  ```
37
48
 
38
49
  dispute.md 裡的每一條意見都要列一筆並說明你的判斷。
50
+ `verdict` 只能寫 `approve` 或 `changes_requested`。若計畫有會導致錯誤結果、遺漏需求或無法驗收的問題,請寫 `changes_requested`,不要寫 `reject`。
39
51
  </output_format>
40
52
 
41
53
  <constraints>
42
- - 只能寫入 .flow/plan-arbiter.json,不可修改規格、計畫或任何程式碼。
54
+ - 只能寫入 .flow/plan-arbiter.json 與 .flow/handoff-response.json,不可修改規格、計畫或任何程式碼。
43
55
  </constraints>
44
56
 
45
57
  <reply_format>
@@ -6,6 +6,17 @@
6
6
  目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
7
7
  </context>
8
8
 
9
+ <handoff>
10
+ 先閱讀 .flow/handoff-context.md,處理與本階段有關的待辦事項。完成時寫入 .flow/handoff-response.json;即使沒有事項也必須寫出空陣列:
11
+
12
+ ```json
13
+ { "newIssues": [], "dispositions": [] }
14
+ ```
15
+
16
+ 新增事項格式:{ "kind": "action 或 info", "summary": "具體問題", "evidence": "檔案位置或檢查證據", "targetStage": "plan 或 code" }。
17
+ 處置格式:{ "id": "既有事項 ID", "status": "proposed_resolved、resolved 或 accepted", "reason": "具體處理理由", "evidence": "檔案、commit 或檢查結果" }。撰寫者只能用 proposed_resolved 提出修正;審查者可以用 resolved 或 accepted 結案。重要疑慮必須放在這份檔案,不能只寫在回覆的 <concerns>。
18
+ </handoff>
19
+
9
20
  <requirement>
10
21
  {{requirement}}
11
22
  </requirement>
@@ -6,6 +6,17 @@
6
6
  目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
7
7
  </context>
8
8
 
9
+ <handoff>
10
+ 先閱讀 .flow/handoff-context.md,處理與本階段有關的待辦事項。完成時寫入 .flow/handoff-response.json;即使沒有事項也必須寫出空陣列:
11
+
12
+ ```json
13
+ { "newIssues": [], "dispositions": [] }
14
+ ```
15
+
16
+ 新增事項格式:{ "kind": "action 或 info", "summary": "具體問題", "evidence": "檔案位置或檢查證據", "targetStage": "plan 或 code" }。
17
+ 處置格式:{ "id": "既有事項 ID", "status": "proposed_resolved、resolved 或 accepted", "reason": "具體處理理由", "evidence": "檔案、commit 或檢查結果" }。撰寫者只能用 proposed_resolved 提出修正;審查者可以用 resolved 或 accepted 結案。重要疑慮必須放在這份檔案,不能只寫在回覆的 <concerns>。
18
+ </handoff>
19
+
9
20
  <requirement>
10
21
  {{requirement}}
11
22
  </requirement>
@@ -46,7 +57,7 @@
46
57
  </output_format>
47
58
 
48
59
  <constraints>
49
- - 只能寫入 .flow/plan-review.json,不可修改規格、計畫或任何程式碼,其他變更都會被還原。
60
+ - 只能寫入 .flow/plan-review.json 與 .flow/handoff-response.json,不可修改規格、計畫或任何程式碼,其他變更都會被還原。
50
61
  </constraints>
51
62
 
52
63
  <reply_format>
package/prompts/plan.md CHANGED
@@ -6,6 +6,17 @@
6
6
  目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
7
7
  </context>
8
8
 
9
+ <handoff>
10
+ 先閱讀 .flow/handoff-context.md,處理與本階段有關的待辦事項。完成時寫入 .flow/handoff-response.json;即使沒有事項也必須寫出空陣列:
11
+
12
+ ```json
13
+ { "newIssues": [], "dispositions": [] }
14
+ ```
15
+
16
+ 新增事項格式:{ "kind": "action 或 info", "summary": "具體問題", "evidence": "檔案位置或檢查證據", "targetStage": "plan 或 code" }。
17
+ 處置格式:{ "id": "既有事項 ID", "status": "proposed_resolved、resolved 或 accepted", "reason": "具體處理理由", "evidence": "檔案、commit 或檢查結果" }。撰寫者只能用 proposed_resolved 提出修正;審查者可以用 resolved 或 accepted 結案。重要疑慮必須放在這份檔案,不能只寫在回覆的 <concerns>。
18
+ </handoff>
19
+
9
20
  <inputs>
10
21
  - .flow/spec.md
11
22
  - .flow/acceptance.json
package/prompts/review.md CHANGED
@@ -6,6 +6,17 @@
6
6
  目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
7
7
  </context>
8
8
 
9
+ <handoff>
10
+ 先閱讀 .flow/handoff-context.md,處理與本階段有關的待辦事項。完成時寫入 .flow/handoff-response.json;即使沒有事項也必須寫出空陣列:
11
+
12
+ ```json
13
+ { "newIssues": [], "dispositions": [] }
14
+ ```
15
+
16
+ 新增事項格式:{ "kind": "action 或 info", "summary": "具體問題", "evidence": "檔案位置或檢查證據", "targetStage": "plan 或 code" }。
17
+ 處置格式:{ "id": "既有事項 ID", "status": "proposed_resolved、resolved 或 accepted", "reason": "具體處理理由", "evidence": "檔案、commit 或檢查結果" }。撰寫者只能用 proposed_resolved 提出修正;審查者可以用 resolved 或 accepted 結案。重要疑慮必須放在這份檔案,不能只寫在回覆的 <concerns>。
18
+ </handoff>
19
+
9
20
  <inputs>
10
21
  - 規格:.flow/spec.md
11
22
  - 驗收條件:.flow/acceptance.json
@@ -39,7 +50,7 @@
39
50
  </output_format>
40
51
 
41
52
  <constraints>
42
- - 只能寫入 .flow/review.json,不可修改任何程式碼,其他變更都會被捨棄。
53
+ - 只能寫入 .flow/review.json 與 .flow/handoff-response.json,不可修改任何程式碼,其他變更都會被捨棄。
43
54
  </constraints>
44
55
 
45
56
  <reply_format>
package/prompts/spec.md CHANGED
@@ -6,6 +6,17 @@
6
6
  目前的工作目錄就是專案(agentflowctl 為這次任務建立的專用 git worktree)。
7
7
  </context>
8
8
 
9
+ <handoff>
10
+ 先閱讀 .flow/handoff-context.md,處理與本階段有關的待辦事項。完成時寫入 .flow/handoff-response.json;即使沒有事項也必須寫出空陣列:
11
+
12
+ ```json
13
+ { "newIssues": [], "dispositions": [] }
14
+ ```
15
+
16
+ 新增事項格式:{ "kind": "action 或 info", "summary": "具體問題", "evidence": "檔案位置或檢查證據", "targetStage": "plan 或 code" }。
17
+ 處置格式:{ "id": "既有事項 ID", "status": "proposed_resolved、resolved 或 accepted", "reason": "具體處理理由", "evidence": "檔案、commit 或檢查結果" }。撰寫者只能用 proposed_resolved 提出修正;審查者可以用 resolved 或 accepted 結案。重要疑慮必須放在這份檔案,不能只寫在回覆的 <concerns>。
18
+ </handoff>
19
+
9
20
  <requirement>
10
21
  {{requirement}}
11
22
  </requirement>