agentflowctl 0.17.0 → 0.18.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 +5 -4
- package/dist/cli.js +38 -11
- package/dist/engine.js +86 -19
- package/dist/planReview.js +2 -0
- package/dist/schemas.js +6 -2
- package/dist/tasks.js +46 -0
- package/package.json +1 -1
- package/prompts/fix.md +1 -1
- package/prompts/implement-code.md +1 -1
- package/prompts/implement-direct.md +3 -3
- package/prompts/implement-tests.md +4 -5
- package/prompts/plan-fix.md +1 -0
- package/prompts/plan-review-group.md +1 -1
- package/prompts/plan-review.md +1 -1
- package/prompts/plan.md +3 -1
- package/prompts/task-review.md +2 -0
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ npx agentflowctl run --req-file ./requirement.md
|
|
|
33
33
|
## 執行時會發生什麼
|
|
34
34
|
|
|
35
35
|
1. agent 整理需求與驗收條件,接著寫計畫,交給其他 agent 審查。
|
|
36
|
-
2. 依計畫逐個任務寫出會失敗的測試,再由另一位 agent 實作到測試通過;每個任務都會經過審查與驗證。計畫 agent 會依改動內容在任務標記 `tdd
|
|
36
|
+
2. 依計畫逐個任務寫出會失敗的測試,再由另一位 agent 實作到測試通過;每個任務都會經過審查與驗證。計畫 agent 會依改動內容在任務標記 `tdd`:建置流程、設定、文件、型別、純重構,以及實作前就會通過的特徵化測試,會略過紅綠燈直接實作,改由任務審查與驗證把關。描述寫明不要求紅燈卻沒標 `tdd: false` 的計畫不會通過。已定案的計畫若仍帶著這個衝突,執行到該任務時仍會寫測試,但測試一開始就通過也算完成,不會再要求紅燈、也不會因此讓 run 失敗。專案沒有測試框架時,所有任務都略過紅綠燈,也不跑 `test` 檢查。只跑檢查、不改檔案的工作不要拆成實作任務;這種任務若沒有檔案變更會直接略過,不再要求 commit。需要人眼確認的任務標成 `kind: confirm`,計畫定案後寫進 `.flow/confirmations.json`,不進入實作,也不會把 run 停下來。`status` 在任務清單之外另列「待你確認(不進實作)」;`confirmations <id>` 只印這個區塊。計畫還沒通過首次驗證前查詢,清單會從尚未經檢查的草稿蒐集,標題會多帶「(計畫尚未定案,以下為草稿)」,項目最終可能不會定案。
|
|
37
37
|
3. 全部任務完成後,再執行專案檢查與整體程式碼審查。未通過的項目會交回修正。
|
|
38
38
|
4. 有 `origin` 時會推送分支;若 `gh` 可用,會嘗試建立 PR。沒有 `origin` 時,完成的分支留在本機。
|
|
39
39
|
|
|
@@ -49,7 +49,8 @@ agentflowctl 會依專案的 `packageManager`、lockfile 與 `package.json` scri
|
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
51
|
agentflowctl list # 列出 run
|
|
52
|
-
agentflowctl status f-xxxx #
|
|
52
|
+
agentflowctl status f-xxxx # 看進度、結果與下一步(任務與待人確認分區)
|
|
53
|
+
agentflowctl confirmations f-xxxx # 只列出需要人眼確認的任務
|
|
53
54
|
agentflowctl logs f-xxxx # 列出各步驟的 log
|
|
54
55
|
agentflowctl logs f-xxxx --latest # 看最新一份 log
|
|
55
56
|
agentflowctl stats f-xxxx # 各步驟耗時、執行與失敗次數
|
|
@@ -57,7 +58,7 @@ agentflowctl insights # 這個專案所有 run 的結果、失敗
|
|
|
57
58
|
agentflowctl resume f-xxxx # 從暫停、中斷或失敗處接續
|
|
58
59
|
```
|
|
59
60
|
|
|
60
|
-
`status`
|
|
61
|
+
`status` 會列出目前階段、未結的交接事項與下一步指令;失敗或暫停時也會顯示原因。任務進度與「待你確認(不進實作)」分成兩個區塊:實作任務在「任務」,`kind: confirm` 的項目在「待你確認(不進實作)」,兩邊都會列出。計畫還沒通過首次驗證時,這個區塊的標題會多帶「(計畫尚未定案,以下為草稿)」,代表清單是從尚未經檢查的草稿蒐集,項目最終可能不會定案。只想看要人眼確認的項目時,用 `confirmations <id>`。要看某一步的詳細輸出,可用 `logs <id> <編號>`;加 `--full` 看完整工具內容,或加 `--raw` 看原始輸出。
|
|
61
62
|
|
|
62
63
|
`stats` 依 log 的開始與結束時間統計每個步驟的執行次數、失敗次數、總耗時與最長一次,並分開列出 agent 與專案指令(install、測試、checks)各占多少時間,最耗時的步驟排在最前面。沒有結束紀錄的 log 列為未完成,不計入耗時;總經過時間包含暫停與等待核准。
|
|
63
64
|
|
|
@@ -255,7 +256,7 @@ pnpm release minor # 確認後建立 v0.x+1.0 的 GitHub Release
|
|
|
255
256
|
pnpm release 1.0.0 # 指定版本,必須大於目前最新的 tag
|
|
256
257
|
```
|
|
257
258
|
|
|
258
|
-
腳本會先檢查目前在 `main`、工作區乾淨、與 `origin/main` 同步、`gh` 已登入、新 tag 不存在,再跑 typecheck、test、build(通過只顯示 ✓,失敗才印出完整輸出),列出自上個 tag 以來的 commit 並等你輸入 `y`
|
|
259
|
+
腳本會先檢查目前在 `main`、工作區乾淨、與 `origin/main` 同步、`gh` 已登入、新 tag 不存在,再跑 typecheck、test、build(通過只顯示 ✓,失敗才印出完整輸出),列出自上個 tag 以來的 commit 並等你輸入 `y` 確認,接著把 `package.json` 的 `version` 更新為新版號、commit 並 push 到 `main`,再用 `gh release create --generate-notes` 建立 Release。npm 由 Release 觸發的 `npm-publish.yml` 發布。
|
|
259
260
|
|
|
260
261
|
## 更多文件
|
|
261
262
|
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import { readFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { stdin, stdout } from "node:process";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
@@ -12,11 +12,12 @@ import { cleanableRuns, cleanRun } from "./cleanup.js";
|
|
|
12
12
|
import { describeDetected, detectProjectDefaults } from "./detect.js";
|
|
13
13
|
import { CMD_AGENT, listLogs, localTime, logMark, nextLogFile, renderLog } from "./logs.js";
|
|
14
14
|
import { flowDir, logDir, projectRoot, worktreeDir } from "./paths.js";
|
|
15
|
-
import { ModelStage, ModelStrength,
|
|
15
|
+
import { ModelStage, ModelStrength, OrderedTaskList, StopAfterStage } from "./schemas.js";
|
|
16
16
|
import { computeInsights, failureLabel, retryLabel } from "./insights.js";
|
|
17
17
|
import { computeUsageInsights } from "./usageInsights.js";
|
|
18
18
|
import { computeStats, formatDuration } from "./stats.js";
|
|
19
19
|
import { agentRuns, getRun, listRetries, listRuns, listSubstitutions, listUsage, saveRun, usageByAgent, usageByModelStage, usageByStage, usageByStrength, usageByTask } from "./store.js";
|
|
20
|
+
import { confirmationLines, confirmationTasks } from "./tasks.js";
|
|
20
21
|
import { padDisplay, readJsonFile } from "./util.js";
|
|
21
22
|
import { openActions, readHandoff } from "./handoff.js";
|
|
22
23
|
import { stopReport } from "./stopReport.js";
|
|
@@ -122,6 +123,19 @@ async function resolveCycle(flag) {
|
|
|
122
123
|
}
|
|
123
124
|
/** status 任務清單中,進行中任務的標記 */
|
|
124
125
|
const TASK_PHASE_MARK = { tests: "🧪", code: "🛠️ ", review: "👀", verify: "🔍", fix: "🩹" };
|
|
126
|
+
/**
|
|
127
|
+
* 這個 run 需要人眼確認的任務。確認清單已寫出(計畫通過首次驗證後)就用它,draft 為 false;
|
|
128
|
+
* 還沒寫出時從計畫與實作清單蒐集 kind: confirm 的任務,這些項目尚未經 validatePlan 檢查,draft 為 true。
|
|
129
|
+
* ordered 可傳入呼叫端已讀過的 tasks.ordered.json,避免重複讀檔。
|
|
130
|
+
*/
|
|
131
|
+
function loadConfirmationTasks(id, ordered = readJsonFile(join(flowDir(id), "tasks.ordered.json"), OrderedTaskList)) {
|
|
132
|
+
const savedPath = join(flowDir(id), "confirmations.json");
|
|
133
|
+
const savedExists = existsSync(savedPath);
|
|
134
|
+
const saved = savedExists ? readJsonFile(savedPath, OrderedTaskList) : undefined;
|
|
135
|
+
const planned = readJsonFile(join(flowDir(id), "tasks.json"), OrderedTaskList);
|
|
136
|
+
const tasks = confirmationTasks(saved ? (saved.ok ? saved.data : []) : undefined, ordered.ok ? ordered.data : [], planned.ok ? planned.data : []);
|
|
137
|
+
return { tasks, draft: !savedExists };
|
|
138
|
+
}
|
|
125
139
|
const program = new Command()
|
|
126
140
|
.name("agentflowctl")
|
|
127
141
|
.description("在專案資料夾內執行的 Agent 開發流程:規格 → 計畫 → TDD 實作 → 驗證 → 審查 → PR")
|
|
@@ -308,15 +322,28 @@ program
|
|
|
308
322
|
console.log(` ${r.key.padEnd(19)} ${retryLabel(r.category)}${r.final ? "(達上限)" : `(第 ${r.attempt} 次)`}`);
|
|
309
323
|
}
|
|
310
324
|
}
|
|
311
|
-
const tasks = readJsonFile(join(flowDir(id), "tasks.ordered.json"),
|
|
312
|
-
if (
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
325
|
+
const tasks = readJsonFile(join(flowDir(id), "tasks.ordered.json"), OrderedTaskList);
|
|
326
|
+
if (tasks.ok && tasks.data.some((t) => t.kind !== "confirm")) {
|
|
327
|
+
console.log("\n任務");
|
|
328
|
+
tasks.data.forEach((t, i) => {
|
|
329
|
+
if (t.kind === "confirm")
|
|
330
|
+
return;
|
|
331
|
+
const active = i === run.taskIndex && run.stage === "implement";
|
|
332
|
+
const mark = i < run.taskIndex ? "✅" : active ? TASK_PHASE_MARK[run.taskPhase] : "⬜";
|
|
333
|
+
console.log(` ${mark} ${t.id} ${t.title}`);
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
const confirm = loadConfirmationTasks(id, tasks);
|
|
337
|
+
if (confirm.tasks.length)
|
|
338
|
+
console.log(`\n${confirmationLines(confirm.tasks, confirm.draft).join("\n")}`);
|
|
339
|
+
});
|
|
340
|
+
program
|
|
341
|
+
.command("confirmations <id>")
|
|
342
|
+
.description("只列出這個 run 需要人眼確認的任務")
|
|
343
|
+
.action((id) => {
|
|
344
|
+
mustGetRun(id);
|
|
345
|
+
const confirm = loadConfirmationTasks(id);
|
|
346
|
+
console.log(confirmationLines(confirm.tasks, confirm.draft).join("\n"));
|
|
320
347
|
});
|
|
321
348
|
// ───────────── agent 管理:讀寫 flow.config.json 的 agents 與 cycle ─────────────
|
|
322
349
|
const configPath = () => join(projectRoot(), "flow.config.json");
|
package/dist/engine.js
CHANGED
|
@@ -13,11 +13,11 @@ import { arbiterPanel, availableAgent, fixAgent, planAgent, planFixAgent, review
|
|
|
13
13
|
import { dropCall, loadCalls, openRound, runPool, saveCall, storedCallValid } from "./parallelReview.js";
|
|
14
14
|
import { cleanupTempWorktrees, withTempWorktree } from "./tempWorktree.js";
|
|
15
15
|
import { resolveAgent, runAgent, runCommand } from "./runner.js";
|
|
16
|
-
import { AcceptanceList, ArbiterResult, ConsistentReviewResult, RepoConfig, TaskList, } from "./schemas.js";
|
|
16
|
+
import { AcceptanceList, ArbiterResult, ConsistentReviewResult, RepoConfig, OrderedTaskList, TaskList, } from "./schemas.js";
|
|
17
17
|
import { addRetry, addSubstitution, addUsage, agentRuns, saveRun } from "./store.js";
|
|
18
18
|
import { clearModelReviewFailure, clearModelReviewStage, recordModelReviewFailure, selectModel } from "./modelSelection.js";
|
|
19
19
|
import { applyReviewVerdicts, dirtyGroups, dirtyTaskIds, extractPlanEvidence, groupReviewerCount, layeredReview, neighborTasks, planContentKey, planOverview, planReviewIndex, readPendingArbitration, readPlanReviewState, repliesForTasks, reviewFingerprint, roundProgress, } from "./planReview.js";
|
|
20
|
-
import { orderTasks, taskAcceptance, validateTaskComplexity } from "./tasks.js";
|
|
20
|
+
import { descriptionWaivesRed, orderTasks, taskAcceptance, validateTaskComplexity, validateTddFlag } from "./tasks.js";
|
|
21
21
|
import { readJsonFile, renderPrompt, tail } from "./util.js";
|
|
22
22
|
// ───────────────────────── 共用工具 ─────────────────────────
|
|
23
23
|
const info = (run, msg) => console.log(`[${run.id}] ${msg}`);
|
|
@@ -204,12 +204,52 @@ function hasTestFramework() {
|
|
|
204
204
|
function taskUsesTdd(task, framework) {
|
|
205
205
|
return framework && task.tdd !== false;
|
|
206
206
|
}
|
|
207
|
+
/** 已定案的任務寫明不要求紅燈:仍寫測試,但通過也算完成紅燈階段。 */
|
|
208
|
+
function testsRedGuidance(testCmd, waiveRed) {
|
|
209
|
+
if (!waiveRed) {
|
|
210
|
+
return {
|
|
211
|
+
roleGoal: "你的測試要精準描述任務要新增的行為,並且在功能實作前確實失敗;之後會由另一位工程師實作到通過,而且對方不能修改你的測試。",
|
|
212
|
+
redGuidance: [
|
|
213
|
+
"3. 測試必須驗證這個任務要新增的行為,並且因為功能尚未實作而**失敗**。",
|
|
214
|
+
`4. 可以先執行本任務相關的測試,確認失敗原因是斷言或找不到尚未實作的模組,而不是語法錯誤或測試本身寫錯。外部流程會再執行 \`${testCmd}\` 驗證紅燈,不需要自行重跑全套測試。`,
|
|
215
|
+
].join("\n"),
|
|
216
|
+
verifyNote: "驗證測試是否失敗",
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
roleGoal: "這個任務不要求紅燈。請直接寫出鎖定既有行為的測試;測試一開始就通過是預期結果,不要停下來,也不要為了製造失敗而改產品程式。",
|
|
221
|
+
redGuidance: [
|
|
222
|
+
"3. 這個任務的描述已寫明不要求紅燈。請寫出鎖定既有行為的測試;測試一開始就通過是預期結果,不要為了製造失敗而改產品程式,也不要停下來不寫。",
|
|
223
|
+
`4. 可以先執行本任務相關的測試,確認它們能跑完。外部流程會再執行 \`${testCmd}\`,通過即可,不需要自行重跑全套測試。`,
|
|
224
|
+
].join("\n"),
|
|
225
|
+
verifyNote: "確認測試能跑完。這個任務不要求測試失敗",
|
|
226
|
+
};
|
|
227
|
+
}
|
|
207
228
|
function loadOrderedTasks(run) {
|
|
208
|
-
const r = readJsonFile(flowFile(run, "tasks.ordered.json"),
|
|
229
|
+
const r = readJsonFile(flowFile(run, "tasks.ordered.json"), OrderedTaskList);
|
|
209
230
|
if (!r.ok)
|
|
210
231
|
throw new Error(r.error);
|
|
211
232
|
return r.data;
|
|
212
233
|
}
|
|
234
|
+
function loadConfirmations(run) {
|
|
235
|
+
if (!existsSync(flowFile(run, "confirmations.json")))
|
|
236
|
+
return [];
|
|
237
|
+
const r = readJsonFile(flowFile(run, "confirmations.json"), OrderedTaskList);
|
|
238
|
+
return r.ok ? r.data : [];
|
|
239
|
+
}
|
|
240
|
+
function rememberConfirmation(run, task) {
|
|
241
|
+
const current = loadConfirmations(run);
|
|
242
|
+
if (current.some((item) => item.id === task.id))
|
|
243
|
+
return;
|
|
244
|
+
writeFileSync(flowFile(run, "confirmations.json"), JSON.stringify([...current, task], null, 2));
|
|
245
|
+
}
|
|
246
|
+
function announceConfirmations(run) {
|
|
247
|
+
const confirm = loadConfirmations(run);
|
|
248
|
+
if (!confirm.length)
|
|
249
|
+
return;
|
|
250
|
+
info(run, `👀 另有 ${confirm.length} 項需要你確認(不進實作,不會停下):${confirm.map((t) => `${t.id} ${t.title}`).join("、")}`);
|
|
251
|
+
info(run, ` 清單:${flowFile(run, "confirmations.json")}`);
|
|
252
|
+
}
|
|
213
253
|
// ───────────────────────── 各階段 ─────────────────────────
|
|
214
254
|
async function specStage(run) {
|
|
215
255
|
const agent = specAgent(run.cycle, run.id);
|
|
@@ -235,7 +275,7 @@ async function specStage(run) {
|
|
|
235
275
|
// ── 規格與計畫檔案:計畫審查、仲裁與計畫定案後的所有階段只能讀,不能改 ──
|
|
236
276
|
const PLAN_FILES = ["spec.md", "acceptance.json", "plan.md", "tasks.json"];
|
|
237
277
|
/** 計畫定案後(實作、修正、程式碼審查)另外依賴排好的任務順序,同樣不能被改 */
|
|
238
|
-
const LOCKED_FILES = [...PLAN_FILES, "tasks.ordered.json"];
|
|
278
|
+
const LOCKED_FILES = [...PLAN_FILES, "tasks.ordered.json", "confirmations.json"];
|
|
239
279
|
/** 審查、修訂與仲裁的快照另外包含審查回應;不要併進 PLAN_FILES,定案後的階段不依賴它 */
|
|
240
280
|
const PLAN_REPLY_FILES = [...PLAN_FILES, "plan-replies.md"];
|
|
241
281
|
function snapshotPlan(run, files = PLAN_FILES) {
|
|
@@ -274,10 +314,16 @@ function validatePlan(run) {
|
|
|
274
314
|
const complexityError = validateTaskComplexity(tasks.data, run.modelMode ?? "balanced");
|
|
275
315
|
if (complexityError)
|
|
276
316
|
return complexityError;
|
|
317
|
+
const tddError = validateTddFlag(tasks.data);
|
|
318
|
+
if (tddError)
|
|
319
|
+
return tddError;
|
|
277
320
|
return orderTasks(tasks.data, new Set(ids));
|
|
278
321
|
}
|
|
279
322
|
function acceptPlan(run, ordered) {
|
|
280
|
-
|
|
323
|
+
const confirm = ordered.filter((t) => t.kind === "confirm");
|
|
324
|
+
const implement = ordered.filter((t) => t.kind !== "confirm");
|
|
325
|
+
writeFileSync(flowFile(run, "tasks.ordered.json"), JSON.stringify(implement, null, 2));
|
|
326
|
+
writeFileSync(flowFile(run, "confirmations.json"), JSON.stringify(confirm, null, 2));
|
|
281
327
|
}
|
|
282
328
|
function announceTasks(run, ordered) {
|
|
283
329
|
const cfg = loadRepoConfig();
|
|
@@ -298,6 +344,7 @@ function planSettled(run, key) {
|
|
|
298
344
|
const next = run.autopilot ? "implement" : "awaiting_approval";
|
|
299
345
|
const ordered = loadOrderedTasks(run);
|
|
300
346
|
announceTasks(run, ordered);
|
|
347
|
+
announceConfirmations(run);
|
|
301
348
|
if (!run.autopilot) {
|
|
302
349
|
info(run, `✋ 計畫已通過審查,請檢視 ${flowFile(run, "plan.md")},確認後執行 agentflowctl approve ${run.id}`);
|
|
303
350
|
}
|
|
@@ -778,6 +825,7 @@ async function implementStage(run) {
|
|
|
778
825
|
const task = tasks[run.taskIndex];
|
|
779
826
|
if (!task) {
|
|
780
827
|
info(run, "✅ 所有任務完成");
|
|
828
|
+
announceConfirmations(run);
|
|
781
829
|
return to(run, "verify");
|
|
782
830
|
}
|
|
783
831
|
const cfg = loadRepoConfig();
|
|
@@ -785,11 +833,19 @@ async function implementStage(run) {
|
|
|
785
833
|
const testRe = new RegExp(cfg.testPattern);
|
|
786
834
|
const testCmd = `${cfg.install} && ${cfg.test}`;
|
|
787
835
|
const progress = `${run.taskIndex + 1}/${tasks.length} ${task.id} ${task.title}`;
|
|
836
|
+
if (task.kind === "confirm") {
|
|
837
|
+
rememberConfirmation(run, task);
|
|
838
|
+
const rest = tasks.filter((item) => item.id !== task.id);
|
|
839
|
+
writeFileSync(flowFile(run, "tasks.ordered.json"), JSON.stringify(rest, null, 2));
|
|
840
|
+
info(run, `👀 [${progress}] 改放到待你確認的清單,實作繼續`);
|
|
841
|
+
return run;
|
|
842
|
+
}
|
|
788
843
|
const taskJson = JSON.stringify(task, null, 2);
|
|
789
844
|
const acceptance = readJsonFile(flowFile(run, "acceptance.json"), AcceptanceList);
|
|
790
845
|
if (!acceptance.ok)
|
|
791
846
|
throw new Error(acceptance.error);
|
|
792
847
|
const acceptanceJson = JSON.stringify(taskAcceptance(task, acceptance.data), null, 2);
|
|
848
|
+
const waiveRed = task.tdd !== false && descriptionWaivesRed(task.description);
|
|
793
849
|
if (run.taskPhase === "review")
|
|
794
850
|
return taskReviewStep(run, task, progress, taskJson, acceptanceJson);
|
|
795
851
|
if (run.taskPhase === "verify")
|
|
@@ -808,9 +864,12 @@ async function implementStage(run) {
|
|
|
808
864
|
if (run.taskPhase === "tests") {
|
|
809
865
|
const key = `${task.id}:tests`;
|
|
810
866
|
info(run, `🧪 [${progress}] 撰寫測試(${agents.tests})`);
|
|
867
|
+
if (waiveRed && existsSync(flowFile(run, "feedback.md")) && readFileSync(flowFile(run, "feedback.md"), "utf8").includes("請撰寫會因功能尚未實作而失敗的測試")) {
|
|
868
|
+
rmSync(flowFile(run, "feedback.md"));
|
|
869
|
+
}
|
|
811
870
|
const before = await headCommit(repo);
|
|
812
871
|
const snap = snapshotPlan(run, LOCKED_FILES);
|
|
813
|
-
const outcome = await agentStep(run, agents.tests, `${task.id}-tests`, renderPrompt("implement-tests", { task: taskJson, acceptance: acceptanceJson, testPattern: cfg.testPattern, testCmd }), { kind: "write", reset: async () => { await resetTo(repo, before); restorePlan(run, snap); } });
|
|
872
|
+
const outcome = await agentStep(run, agents.tests, `${task.id}-tests`, renderPrompt("implement-tests", { task: taskJson, acceptance: acceptanceJson, testPattern: cfg.testPattern, testCmd, ...testsRedGuidance(testCmd, waiveRed) }), { kind: "write", reset: async () => { await resetTo(repo, before); restorePlan(run, snap); } });
|
|
814
873
|
const { r, agent: testsAuthor } = outcome;
|
|
815
874
|
const tampered = restorePlan(run, snap);
|
|
816
875
|
if (!r.ok) {
|
|
@@ -830,7 +889,7 @@ async function implementStage(run) {
|
|
|
830
889
|
return retry(run, key, `沒有新增或修改任何符合 /${cfg.testPattern}/ 的測試檔。`, "implement", "tests_not_written");
|
|
831
890
|
}
|
|
832
891
|
const red = await runCommand(target(run, `${task.id}-red`, CMD_AGENT), testCmd);
|
|
833
|
-
if (red.ok) {
|
|
892
|
+
if (red.ok && !waiveRed) {
|
|
834
893
|
await resetTo(repo, before);
|
|
835
894
|
return retry(run, key, "測試在功能尚未實作前就全部通過,代表測試沒有驗證到新行為。請撰寫會因功能尚未實作而失敗的測試。", "implement", "tests_not_red");
|
|
836
895
|
}
|
|
@@ -839,8 +898,10 @@ async function implementStage(run) {
|
|
|
839
898
|
await resetTo(repo, before);
|
|
840
899
|
return retry(run, key, handoffError, "implement", "handoff_invalid");
|
|
841
900
|
}
|
|
842
|
-
writeFileSync(flowFile(run, "red-output.txt"), red.
|
|
843
|
-
|
|
901
|
+
writeFileSync(flowFile(run, "red-output.txt"), waiveRed && red.ok
|
|
902
|
+
? "此任務不要求紅燈,測試在既有實作下已經通過。不要為了製造失敗而修改產品程式;若沒有其他必須的實作,保持現況即可。"
|
|
903
|
+
: red.output);
|
|
904
|
+
info(run, waiveRed && red.ok ? `✅ [${progress}] 測試已寫好(此任務不要求紅燈)` : `🔴 [${progress}] 測試如預期失敗`);
|
|
844
905
|
return { ...succeed(run, key, "implement"), taskPhase: "code", taskBase: before, testsCommit: commit, lastTestsAuthor: testsAuthor };
|
|
845
906
|
}
|
|
846
907
|
// ── 綠燈:實作到測試通過,而且不可動測試 ──
|
|
@@ -864,8 +925,10 @@ async function implementStage(run) {
|
|
|
864
925
|
return retry(run, key, planTamperedMessage(tampered), "implement", "plan_tampered");
|
|
865
926
|
}
|
|
866
927
|
const codeCommit = await commitAll(repo, `feat(${task.id}): ${task.title} [${codeAuthor}]`);
|
|
867
|
-
if (!tdd && !codeCommit)
|
|
868
|
-
|
|
928
|
+
if (!tdd && !codeCommit) {
|
|
929
|
+
info(run, `⏭️ [${progress}] 沒有檔案變更,略過這個任務`);
|
|
930
|
+
return finishTask(succeed(run, key, "implement"));
|
|
931
|
+
}
|
|
869
932
|
const touched = tdd ? (await changedFiles(repo, testsCommit, await headCommit(repo))).filter((f) => testRe.test(f)) : [];
|
|
870
933
|
if (touched.length) {
|
|
871
934
|
await resetTo(repo, testsCommit);
|
|
@@ -918,6 +981,17 @@ async function taskReviewStep(run, task, progress, taskJson, acceptanceJson) {
|
|
|
918
981
|
lastReviewer: result.objector,
|
|
919
982
|
};
|
|
920
983
|
}
|
|
984
|
+
/** 這個任務結束,下一個從寫測試開始 */
|
|
985
|
+
function finishTask(run) {
|
|
986
|
+
return {
|
|
987
|
+
...run,
|
|
988
|
+
taskIndex: run.taskIndex + 1,
|
|
989
|
+
taskPhase: "tests",
|
|
990
|
+
taskBase: undefined,
|
|
991
|
+
testsCommit: undefined,
|
|
992
|
+
lastTestsAuthor: undefined,
|
|
993
|
+
};
|
|
994
|
+
}
|
|
921
995
|
// ── 任務驗證:通過才進入下一個任務 ──
|
|
922
996
|
async function taskVerifyStep(run, task, progress) {
|
|
923
997
|
const key = `${task.id}:verify`;
|
|
@@ -926,14 +1000,7 @@ async function taskVerifyStep(run, task, progress) {
|
|
|
926
1000
|
if (report)
|
|
927
1001
|
return { ...retry(run, key, report, "implement", "checks_failed"), taskPhase: "fix", fixSource: "verify" };
|
|
928
1002
|
info(run, `✅ [${progress}] 完成`);
|
|
929
|
-
return
|
|
930
|
-
...succeed(run, key, "implement"),
|
|
931
|
-
taskIndex: run.taskIndex + 1,
|
|
932
|
-
taskPhase: "tests",
|
|
933
|
-
taskBase: undefined,
|
|
934
|
-
testsCommit: undefined,
|
|
935
|
-
lastTestsAuthor: undefined,
|
|
936
|
-
};
|
|
1003
|
+
return finishTask(succeed(run, key, "implement"));
|
|
937
1004
|
}
|
|
938
1005
|
// ── 任務修正:修完重新審查、驗證 ──
|
|
939
1006
|
async function taskFixStep(run, task, progress) {
|
package/dist/planReview.js
CHANGED
|
@@ -192,6 +192,8 @@ export function taskFingerprint(task, acceptance, planMd) {
|
|
|
192
192
|
complexity: task.complexity ?? null,
|
|
193
193
|
dependsOn: task.dependsOn,
|
|
194
194
|
acceptance: task.acceptance.map((id) => ({ id, description: byId.get(id) ?? "" })),
|
|
195
|
+
tdd: task.tdd ?? null,
|
|
196
|
+
kind: task.kind ?? null,
|
|
195
197
|
evidence: extractPlanEvidence(planMd, [task.id]),
|
|
196
198
|
});
|
|
197
199
|
}
|
package/dist/schemas.js
CHANGED
|
@@ -74,11 +74,15 @@ export const TaskItem = z.object({
|
|
|
74
74
|
dependsOn: z.array(z.string()).default([]),
|
|
75
75
|
acceptance: z.array(z.string()).min(1, "每個任務至少要對應一條驗收條件"),
|
|
76
76
|
complexity: z.enum(["low", "medium", "high"]).optional(),
|
|
77
|
-
/** false
|
|
77
|
+
/** false=這個任務不適合先寫會失敗的測試(建置流程、設定、文件、純重構、實作前就會通過的特徵化測試等),略過紅燈直接實作;沒寫視為 true。描述寫明不要求紅燈時必須為 false */
|
|
78
78
|
tdd: z.boolean().optional(),
|
|
79
|
+
/** confirm=不進入實作佇列,另存給使用者確認;沒寫視為要實作。舊 tasks.json 沒有此欄位 */
|
|
80
|
+
kind: z.enum(["implement", "confirm"]).optional(),
|
|
79
81
|
});
|
|
82
|
+
/** 排好的實作清單與另存的確認清單都可以是空的 */
|
|
83
|
+
export const OrderedTaskList = z.array(TaskItem);
|
|
80
84
|
/** Agent 在 plan 階段產出的 .flow/tasks.json */
|
|
81
|
-
export const TaskList =
|
|
85
|
+
export const TaskList = OrderedTaskList.min(1);
|
|
82
86
|
/** Agent 在 review 階段產出的 .flow/review.json */
|
|
83
87
|
export const ReviewResult = z.object({
|
|
84
88
|
verdict: z.enum(["approve", "changes_requested"]),
|
package/dist/tasks.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
/** 一個任務最多做兩件事:對應的驗收條件超過這個數量就要再拆 */
|
|
2
2
|
export const MAX_TASK_ACCEPTANCE = 2;
|
|
3
|
+
/** 描述已明確放棄紅燈。新計畫必須把 tdd 標成 false;已定案的任務則仍寫測試,但不要求先失敗。 */
|
|
4
|
+
const RED_WAIVED = /不要求紅燈|不必紅燈|不需紅燈|無需紅燈|不用紅燈|略過紅燈|略過紅綠燈/;
|
|
5
|
+
export function descriptionWaivesRed(description) {
|
|
6
|
+
return RED_WAIVED.test(description);
|
|
7
|
+
}
|
|
8
|
+
/** 描述與 tdd 矛盾時退回計畫:寫明不要求紅燈就必須標 false。 */
|
|
9
|
+
export function validateTddFlag(tasks) {
|
|
10
|
+
const conflicts = tasks.filter((task) => task.tdd !== false && descriptionWaivesRed(task.description));
|
|
11
|
+
if (!conflicts.length)
|
|
12
|
+
return undefined;
|
|
13
|
+
return conflicts
|
|
14
|
+
.map((task) => `${task.id} 的描述寫明不要求紅燈,但 tdd 不是 false。這種任務在實作前就會通過,請改成 "tdd": false。`)
|
|
15
|
+
.join("\n");
|
|
16
|
+
}
|
|
3
17
|
/** adaptive 的新計畫必須明確標註難度;舊 run 仍可讀取缺少欄位的 task。 */
|
|
4
18
|
export function validateTaskComplexity(tasks, mode) {
|
|
5
19
|
if (mode === "balanced")
|
|
@@ -70,4 +84,36 @@ export function orderTasks(tasks, acceptanceIds) {
|
|
|
70
84
|
}
|
|
71
85
|
return ordered;
|
|
72
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* 需要人確認的任務。confirmations.json 已寫出時以它為準(空陣列代表沒有);
|
|
89
|
+
* 還沒寫出時,從實作清單與計畫裡蒐集 kind 為 confirm 的任務。
|
|
90
|
+
*/
|
|
91
|
+
export function confirmationTasks(saved, ordered, planned) {
|
|
92
|
+
if (saved)
|
|
93
|
+
return saved;
|
|
94
|
+
const seen = new Set();
|
|
95
|
+
const out = [];
|
|
96
|
+
for (const task of [...ordered, ...planned]) {
|
|
97
|
+
if (task.kind !== "confirm" || seen.has(task.id))
|
|
98
|
+
continue;
|
|
99
|
+
seen.add(task.id);
|
|
100
|
+
out.push(task);
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 只列出待人確認的任務;沒有時回一句說明。
|
|
106
|
+
* draft 代表計畫還沒通過首次驗證,清單是從未經 validatePlan 檢查的草稿蒐集來的,可能有項目最終不會定案。
|
|
107
|
+
*/
|
|
108
|
+
export function confirmationLines(tasks, draft = false) {
|
|
109
|
+
if (!tasks.length)
|
|
110
|
+
return ["沒有需要人確認的任務"];
|
|
111
|
+
const lines = [`待你確認(不進實作)${draft ? "(計畫尚未定案,以下為草稿)" : ""}`];
|
|
112
|
+
for (const task of tasks) {
|
|
113
|
+
lines.push(` ${task.id} ${task.title}`);
|
|
114
|
+
lines.push(` ${task.description}`);
|
|
115
|
+
lines.push(` 驗收:${task.acceptance.join("、")}`);
|
|
116
|
+
}
|
|
117
|
+
return lines;
|
|
118
|
+
}
|
|
73
119
|
//# sourceMappingURL=tasks.js.map
|
package/package.json
CHANGED
package/prompts/fix.md
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
- 不可刪除測試檔(檔名符合 `{{testPattern}}`),也不可用 skip、放寬斷言、`@ts-ignore`、`eslint-disable` 等方式讓檢查通過。
|
|
33
33
|
- 如果測試本身確實有誤,可以修正測試,但必須在回覆的 `<concerns>` 說明理由。
|
|
34
34
|
- 不要執行 git commit(權限設定已禁止)。
|
|
35
|
-
- **不可修改** .flow/spec.md、.flow/acceptance.json、.flow/plan.md、.flow/tasks.json、.flow/tasks.ordered.json,修改會被自動還原並視為失敗。若認為規格或驗收條件有誤,請寫進 .flow/handoff-response.json 的 newIssues。
|
|
35
|
+
- **不可修改** .flow/spec.md、.flow/acceptance.json、.flow/plan.md、.flow/tasks.json、.flow/tasks.ordered.json、.flow/confirmations.json,修改會被自動還原並視為失敗。若認為規格或驗收條件有誤,請寫進 .flow/handoff-response.json 的 newIssues。
|
|
36
36
|
</constraints>
|
|
37
37
|
|
|
38
38
|
<reply_format>
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
<constraints>
|
|
51
51
|
- **不可修改任何測試檔**,修改會被自動還原並視為失敗。若認為測試本身有誤,請寫在回覆的 `<concerns>`。
|
|
52
52
|
- 不要執行 git commit(權限設定已禁止)。
|
|
53
|
-
- **不可修改** .flow/spec.md、.flow/acceptance.json、.flow/plan.md、.flow/tasks.json、.flow/tasks.ordered.json,修改會被自動還原並視為失敗。若認為規格或驗收條件有誤,請寫進 .flow/handoff-response.json 的 newIssues。
|
|
53
|
+
- **不可修改** .flow/spec.md、.flow/acceptance.json、.flow/plan.md、.flow/tasks.json、.flow/tasks.ordered.json、.flow/confirmations.json,修改會被自動還原並視為失敗。若認為規格或驗收條件有誤,請寫進 .flow/handoff-response.json 的 newIssues。
|
|
54
54
|
</constraints>
|
|
55
55
|
|
|
56
56
|
<reply_format>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<role>
|
|
2
|
-
你是任務實作者,負責完成一個**不走 TDD**
|
|
2
|
+
你是任務實作者,負責完成一個**不走 TDD** 的任務。這個任務不適合先寫會失敗的測試(例如建置流程、設定、文件、型別、純重構,或鎖定既有行為的特徵化測試),或專案沒有測試框架,所以沒有紅燈測試可依循。你要依任務描述與驗收條件,用符合專案風格的最小改動完成它。若任務是補一個現有實作下就會通過的測試,寫出該測試即可,不要為了製造失敗而去改產品程式。
|
|
3
3
|
</role>
|
|
4
4
|
|
|
5
5
|
<context>
|
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
</steps>
|
|
47
47
|
|
|
48
48
|
<constraints>
|
|
49
|
-
-
|
|
49
|
+
- 需要改的檔案就改。這個任務若沒有要寫進 git 的變更,不要為了產生 commit 而硬改檔案;沒有變更會略過這個任務,不會重試。
|
|
50
50
|
- 不要執行 git commit(權限設定已禁止)。
|
|
51
|
-
- **不可修改** .flow/spec.md、.flow/acceptance.json、.flow/plan.md、.flow/tasks.json、.flow/tasks.ordered.json,修改會被自動還原並視為失敗。若認為規格或驗收條件有誤,請寫進 .flow/handoff-response.json 的 newIssues。
|
|
51
|
+
- **不可修改** .flow/spec.md、.flow/acceptance.json、.flow/plan.md、.flow/tasks.json、.flow/tasks.ordered.json、.flow/confirmations.json,修改會被自動還原並視為失敗。若認為規格或驗收條件有誤,請寫進 .flow/handoff-response.json 的 newIssues。
|
|
52
52
|
</constraints>
|
|
53
53
|
|
|
54
54
|
<reply_format>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<role>
|
|
2
|
-
你是測試工程師,在 TDD
|
|
2
|
+
你是測試工程師,在 TDD 的紅燈階段**只寫測試,不寫實作**。{{roleGoal}}
|
|
3
3
|
</role>
|
|
4
4
|
|
|
5
5
|
<context>
|
|
@@ -37,14 +37,13 @@
|
|
|
37
37
|
<steps>
|
|
38
38
|
1. 若 .flow/feedback.md 存在,先閱讀,並依內容調整做法。
|
|
39
39
|
2. 依任務描述撰寫測試,檔名必須符合正規表示式 `{{testPattern}}`。
|
|
40
|
-
|
|
41
|
-
4. 可以先執行本任務相關的測試,確認失敗原因是斷言或找不到尚未實作的模組,而不是語法錯誤或測試本身寫錯。外部流程會再執行 `{{testCmd}}` 驗證紅燈,不需要自行重跑全套測試。
|
|
40
|
+
{{redGuidance}}
|
|
42
41
|
</steps>
|
|
43
42
|
|
|
44
43
|
<constraints>
|
|
45
44
|
- 不可實作功能本身。可以建立讓測試能編譯所需的最小型別或空殼匯出,但不可以有真正的邏輯。
|
|
46
|
-
- 不要執行 git commit
|
|
47
|
-
- **不可修改** .flow/spec.md、.flow/acceptance.json、.flow/plan.md、.flow/tasks.json、.flow/tasks.ordered.json,修改會被自動還原並視為失敗。若認為規格或驗收條件有誤,請寫進 .flow/handoff-response.json 的 newIssues。
|
|
45
|
+
- 不要執行 git commit(權限設定已禁止),外部流程會提交並{{verifyNote}}。
|
|
46
|
+
- **不可修改** .flow/spec.md、.flow/acceptance.json、.flow/plan.md、.flow/tasks.json、.flow/tasks.ordered.json、.flow/confirmations.json,修改會被自動還原並視為失敗。若認為規格或驗收條件有誤,請寫進 .flow/handoff-response.json 的 newIssues。
|
|
48
47
|
</constraints>
|
|
49
48
|
|
|
50
49
|
<reply_format>
|
package/prompts/plan-fix.md
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
- 一個任務只做一件事,最多兩件:`acceptance` 最多列兩條驗收條件;驗收條件一條只描述一個行為。修改時若任務變大,請拆開,不要合併。
|
|
34
34
|
- 測試檔名必須符合正規表示式 `{{testPattern}}`。
|
|
35
35
|
- 修改 task 時保留或補上 `complexity`(`low`、`medium`、`high`)。依影響範圍、技術不確定性與失敗後果重新判定,取最高等級;同步更新 .flow/plan.md 中該 task 的逐項證據與最終等級。若不同意審查者建議的等級,在 .flow/plan-replies.md 對應的 `## T-<數字>` 節引用具體程式碼或測試依據。同步更新 .flow/plan.md 中該 task 的證據時,放在該 task 的 `## T-<數字>` 標題下。
|
|
36
|
+
- 只跑檢查、不改檔案的任務要併回會改檔的任務,不要留成獨立任務。需要人眼確認的任務設 `"kind": "confirm"`,它們會另存給使用者,不要留在實作佇列。
|
|
36
37
|
</output_format>
|
|
37
38
|
|
|
38
39
|
<constraints>
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
|
|
47
47
|
<review_focus>
|
|
48
48
|
只看這一群:
|
|
49
|
-
1. 每個任務是否只做一件事(最多兩件),小到一次 TDD
|
|
49
|
+
1. 每個任務是否只做一件事(最多兩件),小到一次 TDD 循環就能完成,而且能寫出實作前會失敗的測試。無法先失敗的(特徵化、實作前就會通過、描述寫明不要求紅燈)必須是 `tdd: false`。只在描述補註、卻留下 `tdd: true` 或沒寫 `tdd`,仍是 `changes_requested`,`note` 要要求改成 `tdd: false`。只跑既有檢查、不改檔案的工作不要獨立成任務,要求併回會改檔的任務。需要人眼確認的,要求改成 `"kind": "confirm"`,離開實作佇列另存給使用者。
|
|
50
50
|
2. 任務描述是否對得上上面的驗收條文。
|
|
51
51
|
3. 對照描述點名、已存在的檔案與難度摘錄,獨立核對 `complexity`。`low` 是沿用既有做法、侷限單一行為且失敗可由局部測試發現;`medium` 包括多模組或介面協調、非典型邊界、相容性或狀態遷移;`high` 包括跨系統契約、架構或資料模型變更、未知的關鍵路徑,或資料遺失、權限、難以回復的風險。摘錄是空的,而且高低估會影響選模時,要求在 .flow/plan.md 該 task 的「## T-<數字>」標題下補上證據。
|
|
52
52
|
4. 做法是否符合那些檔案中已存在者的慣例。
|
package/prompts/plan-review.md
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<review_focus>
|
|
31
31
|
1. **需求覆蓋**:規格是否完整涵蓋原始需求?有沒有遺漏、誤解,或加入需求沒要求的範圍?
|
|
32
32
|
2. **驗收條件**:每一條是否具體、可以用自動化測試驗證,而且只描述一個行為?把多個行為寫在同一條的,要求拆開。有沒有重要的邊界情況或錯誤處理沒被列入?
|
|
33
|
-
3. **任務拆解**:每個任務是否只做一件事(最多兩件),小到一次 TDD 循環就能完成,而且能寫出「實作前會失敗」的測試(標 `tdd: false`
|
|
33
|
+
3. **任務拆解**:每個任務是否只做一件事(最多兩件),小到一次 TDD 循環就能完成,而且能寫出「實作前會失敗」的測試(標 `tdd: false` 的任務除外:核對它的改動內容確實不適合先寫失敗測試,例如建置流程、設定、文件、型別、純重構,或實作前就會通過的特徵化測試,並有寫明驗收方式;會改變程式行為卻標成 `false` 的,要求改回 `true`。描述寫明不要求紅燈,`tdd` 卻不是 `false` 的,要求改成 `false`)?任務太大、一次要動很多檔案或驗證很多行為的,要求拆成更小的任務。相依順序是否合理?只跑既有檢查、不改檔案的工作不要獨立成任務,要求併回會改檔的任務。需要人眼確認、程式無法判定的,要求改成 `"kind": "confirm"`,讓它離開實作佇列、另存給使用者,不要留給 agent,也不要為此把流程停住。
|
|
34
34
|
同時依 .flow/plan.md 的逐項理由及實際程式碼,獨立核對每個 task 的 `complexity`:分別看影響範圍、技術不確定性與失敗後果,取最高等級。`low` 須是沿用既有做法、侷限單一行為或模組且失敗可由局部測試發現;`medium` 包括多模組或介面協調、非典型邊界、相容性或狀態遷移風險;`high` 包括跨系統契約、架構或資料模型變更、未知的關鍵技術路徑,或資料遺失、權限、難以回復的風險。不要只憑檔案數、程式碼行數或驗收條件數判定。
|
|
35
35
|
理由缺漏、與程式碼不符,或高低估會影響選模時,要求修正;在 `note` 指出 task ID、具體證據、建議等級及須修改的 .flow/plan.md/.flow/tasks.json 部分。不要為缺少高價值證據的細微措辭差異要求修改。
|
|
36
36
|
4. **技術方向**:是否符合專案既有的架構與慣例?有沒有更簡單的做法,或明顯的風險?
|
package/prompts/plan.md
CHANGED
|
@@ -52,7 +52,9 @@
|
|
|
52
52
|
- 每個任務是一個可獨立測試的垂直切片,小到一次 TDD 循環就能完成;只動少數幾個檔案,測試只驗證一兩個行為。
|
|
53
53
|
- `title` 用一句話說出這件事;需要用「並且」「以及」串起來的,就是兩個任務。
|
|
54
54
|
- `description` 寫清楚要動哪些檔案(寫含目錄的路徑,例如 `src/form.ts`,不要只寫檔名)、測試要驗證哪個行為,以及這個任務不做什麼。計畫審查會依這些路徑把任務分群。
|
|
55
|
-
- 依改動內容標記 `tdd`:會改變程式行為、能寫出「在實作前會失敗」的測試的任務標 `true`(預設);改動內容不適合先寫失敗測試的任務標 `false`,這類任務會略過紅燈直接實作,改由任務審查與驗證指令把關。適合標 `false` 的例子:建置流程與打包設定(build、CI、bundler、tsconfig)、依賴與版本設定、文件與 prompt
|
|
55
|
+
- 依改動內容標記 `tdd`:會改變程式行為、能寫出「在實作前會失敗」的測試的任務標 `true`(預設);改動內容不適合先寫失敗測試的任務標 `false`,這類任務會略過紅燈直接實作,改由任務審查與驗證指令把關。適合標 `false` 的例子:建置流程與打包設定(build、CI、bundler、tsconfig)、依賴與版本設定、文件與 prompt 文字、樣式與靜態資源、型別宣告、不改變行為的重構與搬移檔案,以及鎖定既有行為的特徵化測試(實作前就會通過、不要求紅燈、通常不需改產品程式)。能併入相關行為任務的設定或重構,仍請併入,不要獨立成任務。描述寫了「不要求紅燈」卻沒把 `tdd` 設成 `false`,計畫不會通過。標 `false` 時要在 .flow/plan.md 該 task 的節裡寫明理由,以及這個任務要怎麼驗收(例如「`npm run build` 通過」或「新增的測試在現有實作下通過」)。
|
|
56
|
+
- 不要把「只跑檢查、不改任何會進 git 的檔案」拆成任務。建置、打包預算、lint、型別檢查已由專案的驗證指令負責;若某條驗收要靠這些檢查證明,掛在真正改檔案的那個任務上。這種工作沒有 commit 時會被略過,不會重試。
|
|
57
|
+
- 必須由人眼確認、程式無法用檔案或指令判定的事項,不要放進實作佇列。在該任務加上 `"kind": "confirm"`(沒寫視為要實作)。計畫定案後程式把它們寫進 .flow/confirmations.json 給使用者看,實作不會做到它們,也不會因此停下。
|
|
56
58
|
- 專案沒有測試框架時,所有任務都會略過 TDD(程式會強制),此時仍請照實標記 `tdd`,並在 `description` 寫清楚驗收方式。
|
|
57
59
|
- 每個任務先檢查預計修改的程式碼,再依「影響範圍、技術不確定性、失敗後果」三個面向判定 `complexity`,取其中最高的等級;不要只憑檔案數、程式碼行數或驗收條件數判定。
|
|
58
60
|
- `low`:沿用現有做法,變更侷限在單一行為或模組,失敗容易由局部測試發現且不影響既有資料或對外契約。
|
package/prompts/task-review.md
CHANGED
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
2. 是否有明顯的錯誤、邊界情況遺漏、安全問題或效能問題。
|
|
36
36
|
3. 是否符合專案既有的架構與慣例,以及任務說明的範圍(沒有做到一半,也沒有做了其他任務的事)。
|
|
37
37
|
|
|
38
|
+
4. 任務描述寫明不要求紅燈時,測試在既有實作下一開始就通過是允許的。不要因為沒有紅燈、或 `tdd` 不是 `false`,就 `changes_requested`。只審查測試是否鎖定任務描述的行為。
|
|
39
|
+
|
|
38
40
|
只審查本任務的變更;其他任務的驗收條件不在這次審查範圍內。先根據 diff 與驗收條件定位需要查閱的檔案,只在證據不足時讀取其他檔案。不必為了審查重跑全套檢查。
|
|
39
41
|
|
|
40
42
|
風格偏好與無關緊要的小問題不需要要求修改。
|