paseo-req-kit 1.11.0 → 1.12.0-beta.2

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/bin/req.mjs +1 -0
  3. package/config.example.json +3 -1
  4. package/contracts/cli-json.md +2 -0
  5. package/contracts/state.md +3 -1
  6. package/contracts/usage-record.md +4 -1
  7. package/docs/adr/0031-fewer-interventions.md +33 -0
  8. package/docs/adr/README.md +1 -0
  9. package/docs/guide//345/256/211/350/243/205/344/270/216/345/215/207/347/272/247.md +3 -2
  10. package/docs/guide//351/241/271/347/233/256/345/256/232/345/210/266/344/270/216/350/277/220/350/241/214/350/247/204/345/210/231.md +21 -3
  11. package/engine/prompts/04b-design-revise.md +15 -0
  12. package/engine/src/api.ts +33 -1
  13. package/engine/src/card-cmd.ts +8 -1
  14. package/engine/src/card.ts +9 -2
  15. package/engine/src/config.ts +23 -2
  16. package/engine/src/configure.ts +161 -36
  17. package/engine/src/control.ts +1 -0
  18. package/engine/src/factory.ts +48 -4
  19. package/engine/src/main.ts +1 -0
  20. package/engine/src/paseo-profiles.ts +2 -0
  21. package/engine/src/pipeline.ts +161 -26
  22. package/engine/src/records.ts +8 -0
  23. package/engine/src/state.ts +30 -0
  24. package/engine/src/stats.ts +90 -0
  25. package/engine/src/steps.ts +4 -1
  26. package/install.mjs +5 -1
  27. package/package.json +2 -2
  28. package/paseo-plugin/OVERVIEW.md +2 -0
  29. package/paseo-plugin/client/data.ts +15 -1
  30. package/paseo-plugin/client/models.tsx +117 -0
  31. package/paseo-plugin/client/settings.tsx +3 -1
  32. package/paseo-plugin/index.server.ts +3 -1
  33. package/paseo-plugin/package.json +1 -1
  34. package/paseo-plugin/server/handlers.ts +14 -1
  35. package/paseo-plugin/shared/contracts.ts +37 -0
  36. package/skills/req/playbooks/new.md +14 -14
  37. package/skills/req/playbooks/small.md +3 -3
  38. package/skills/req/playbooks/unattended.md +4 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  每个版本分为"使用者会看到的变化"和"升级须知"。升级步骤见 [docs/guide/安装与升级.md](docs/guide/安装与升级.md)。
4
4
 
5
+ ## 1.12.0-beta.2
6
+
7
+ 1.12.0-beta.1 的内容(见下),加一处修正:
8
+
9
+ - **修正**:安装时自动选模型,Paseo 没有运行时会一直等待连接;现在 20 秒连不上就放弃,改为提示之后运行 `req configure --auto`。1.12.0-beta.1 因此没有发布。
10
+
11
+ ## 1.12.0-beta.1
12
+
13
+ 少等人:能自动处理的中断不再停下等人,选择题改为默认值,装好就选好模型。设计决定见 ADR 0031。
14
+
15
+ ### 使用者会看到的变化
16
+
17
+ - **自愈**:步骤超时而 agent 还在做时,再等它一轮;agent 报错、没写出有效结果时,新开一个 agent 重做一次。限流重试用完时,预约在 Paseo 显示的额度恢复时间之后自动继续,而不是固定 30 分钟。
18
+ - **备用模型**(可选):配置 `agents.fallback` 后,报错重做、限流重试用完时换它把这一步做完。`req configure` 和插件的"模型"设置页会推荐一个。
19
+ - **方案审核的问题先自答**:审核说需要需求方决定时,先由 builder 从代码和文档中找答案,只把真正的业务取舍连同推荐答案交给需求方;都能回答时不停下。
20
+ - **后台进程意外退出后自动继续**:定时检查默认打开(每 60 分钟);接单器每次检查时也会发现并请对话继续。
21
+ - **装好就选好模型**:安装时自动选好已登录的 agent(两个不同厂商分别做 builder 和 reviewer,再选一个做备用),不再需要回答配置向导;`req configure --auto` 同样。插件设置中新增"模型"一页,可以直接修改。
22
+ - **`/req` 少问**:不再问是否新建工作区、档位、执行方式、现在开始还是预约,按推荐值直接走,确认摘要最后一行写明,想改再说;确认需求说明后立即开始。
23
+ - **卡片回复更宽松**:写"好的""没问题""按你说的来"也算同意;回复区只写了一句"好的"也算确认。卡片已经写清需求时,不再为提问而提问;问题附上推荐答案。
24
+ - **`req stats` 新增"人工介入"一节**:停下等人的原因、自动处理的次数、到达提测的需求中没有非必要介入的比例。
25
+
26
+ ### 升级须知
27
+
28
+ - 新配置 `selfHeal`、`answerQuestionsFirst`(默认都是 `true`)、`agents.fallback`(可选);`watchdogMinutes` 默认从 0 改为 60。想保持以前的行为,分别设为 `false`、`false`、`0`。
29
+ - 状态文件新增 `stopKind`、`recoveries`,使用记录的 `run` 新增 `stopKind`、`recoveries`;旧文件照常可用,统计按状态推断旧记录的原因。
30
+
5
31
  ## 1.11.0
6
32
 
7
33
  稳定版:包含 1.10 的"从卡片开始的需求"和 1.11 的接单器(详见下面的 beta 记录),并修正步骤超时后会重复开 agent 的问题。
package/bin/req.mjs CHANGED
@@ -56,6 +56,7 @@ Paseo 插件(在 Paseo 中查看和管理需求):
56
56
  本机维护:
57
57
  req doctor [--no-project] 体检:安装、配置、daemon、当前项目
58
58
  req configure 配置向导:选择 builder 和 reviewer 用的 agent
59
+ req configure --auto 不提问,按推荐选好 builder、reviewer 和备用模型
59
60
  req upgrade [版本] [--beta] 升级到最新版本(或指定版本),自动选能支持本机 Paseo 的版本;
60
61
  req upgrade --check 只看有没有新版本
61
62
  req versions 已安装的版本和 npm 上的版本
@@ -89,7 +89,9 @@
89
89
  "waitingReminderHours": 24,
90
90
  "autoResumeMinutes": 30,
91
91
  "autoResumeMax": 2,
92
- "watchdogMinutes": 0,
92
+ "watchdogMinutes": 60,
93
+ "selfHeal": true,
94
+ "answerQuestionsFirst": true,
93
95
  "phases": {
94
96
  "mode": "continuous",
95
97
  "verify": {
@@ -28,6 +28,8 @@
28
28
  | `req api stats [--since 30d] [--month 2026-10]` | `req stats --json` 的内容,见 [使用记录](usage-record.md) |
29
29
  | `req api index [--rebuild <目录>…] [--prune]` | 不带选项:索引的全部条目;`--rebuild`:`{ scanned, registered, pruned, total }` |
30
30
  | `req api run <approve\|stop\|resume\|verify\|sync> <需求> [--repo <目录>] [选项]` | `{ command, featureDir, ok, exitCode, message }`。在 `--repo`(默认当前目录)中执行与 `req <命令> <需求>` 完全相同的操作;`ok` 为 false 时退出码为 1 |
31
+ | `req api models` | 1.12 起。`{ current, choices, recommended }`:本机配置中现在的 `agents`;daemon 上能无人值守运行的选择(`{ id, label, provider, profile }`,Paseo profile 在前);按推荐的 `agents`。连不上 daemon 时报错 |
32
+ | `req api set-agents --json <设置>` | 1.12 起。`{ ok, message }`。`<设置>` 是 `{ builder, reviewer, fallback }`(`fallback` 可以为 null),检查后写入本机配置的 `agents`,原文件备份 |
31
33
 
32
34
  `<需求>` 的写法与 `req status` 相同:功能目录、目录名或名称中能唯一确定的一部分。需求在为它新建的工作区中开发时,`--repo` 写对话所在的目录或那个工作区都可以。
33
35
 
@@ -16,8 +16,10 @@
16
16
  | `pid` | number | 流水线进程 |
17
17
  | `startedAt` / `updatedAt` | ISO 时间 | |
18
18
  | `results` | object | 已完成步骤的结果,键为步骤键(如 `code_review-r2`) |
19
- | `history` | array | 每次步骤执行的记录:agent、起止时间、结果、错误;`usage`(可选,1.3.0 起)为 agent 报告的用量:`inputTokens`、`cachedInputTokens`、`outputTokens`、`totalCostUsd`,agent 没有报告时为 null 或缺少 |
19
+ | `history` | array | 每次步骤执行的记录:agent、起止时间、结果、错误;`usage`(可选,1.3.0 起)为 agent 报告的用量:`inputTokens`、`cachedInputTokens`、`outputTokens`、`totalCostUsd`,agent 没有报告时为 null 或缺少;`leftRunning`(可选,1.11 起)为 true 表示失败时 agent 可能还在运行,下一次运行这一步时先看它;`reattached`(可选,1.11 起)为接着等上一次 agent 的次数 |
20
20
  | `stopReason` / `outcome` | string \| null | 停止原因;最终结论 |
21
+ | `stopKind` | string \| null | 可选,1.12 起。停下等人的原因代码:必要的决定 `approval`(确认方案)、`phase_gate`(研发完成,等待测试)、`business`(需求方决定)、`reproduce`(缺陷复现不了);中断 `timeout`、`agent_error`、`invalid_result`、`transient`、`stopped`、`process_exit`、`other`。运行中或完成时为 null |
22
+ | `recoveries` | array | 可选,1.12 起。流水线自己处理掉的问题(最多保留 200 条):`at`、`key`(步骤键)、`kind`(`retry` 重试 / `reattach` 接着等上一次的 agent / `reuse_result` 沿用上一次 agent 的结果 / `extend_timeout` 超时后多等一轮 / `redo` 新开 agent 重做 / `fallback` 换备用模型 / `auto_resume` 预约自动继续 / `quota_wait` 预约在额度恢复后继续 / `self_answer` 方案审核的问题由 builder 自答)、`detail` |
21
23
  | `profile` | `quick` \| `standard` | 档位 |
22
24
  | `branch` | string \| null | 工作分支,启动时确定 |
23
25
  | `milestones` | string[] | 已发送的进展通知,续跑时不重复发送 |
@@ -30,7 +30,7 @@
30
30
  | `schema` / `kind` | `1` / `"run"` |
31
31
  | `machine` / `kitVersion` | 主机名、req-kit 版本 |
32
32
  | `requirement` | `{ key, project, name, title, type, profile }`;从卡片开始的需求(1.10 起)另有 `requester`(卡片的提出人)和 `source`(`{ system, id }`) |
33
- | `run` | `{ startedAt, endedAt, attempt, phase, phaseMode, status, outcome, stopReason }`;`status` 是运行结束时的状态,`outcome` 是完成时报告的结论 |
33
+ | `run` | `{ startedAt, endedAt, attempt, phase, phaseMode, status, outcome, stopReason, stopKind, recoveries }`;`status` 是运行结束时的状态,`outcome` 是完成时报告的结论;`stopKind`(1.12 起)是停下等人的原因代码(见 [state.md](state.md)),`recoveries`(1.12 起)是这次运行中自动处理的次数,按种类,如 `{ "retry": 2 }`。更早的记录没有这两项 |
34
34
  | `agents` | `{ builder, reviewer }`:当时配置的 agent(如 `claude/opus(bypassPermissions)`) |
35
35
  | `steps` | 这次运行中开始的步骤:`{ key, stepId, phase, attempt, round, startedAt, endedAt, minutes, outcome, inputTokens, outputTokens, costUsd }`;agent 没有报告的用量为 null |
36
36
  | `gate` | `{ runs, failed }`:这次运行中质量门运行的次数、发现新失败的次数 |
@@ -43,6 +43,9 @@
43
43
  - 交付周期:需求第一次开始到完成(最后一次运行以"已完成"结束)的小时数,用这个需求的全部运行,算在完成的那段时间。
44
44
  - 代码评审轮数、缺陷修复轮数:不同的(尝试、轮次、仓库)个数;评审分方向或评审小组的几个成员算一轮。
45
45
  - 等待:相邻两次运行之间的间隔,按前一次结束时的状态归类:等待确认方案、等待需求方决定、研发完成到开始测试验证、中断到继续。
46
+ - 停下等人的原因(1.12 起):这段时间结束的运行按 `stopKind` 计数;更早的记录按结束时的状态推断。确认方案、研发完成等待测试是必要的决定。
47
+ - 无人工到提测的比例(1.12 起):这段时间有进展、已经研发完成或完成的需求中,所有运行都只停在必要决定处的比例。
48
+ - 自动处理的比例(1.12 起):自动处理的次数 ÷(自动处理的次数 + 以中断结束的运行数);需求方决定、缺陷复现不了不算中断。
46
49
  - 合并多台机器的导出时,同一机器、同一需求、同一开始时间的运行只算一次。
47
50
 
48
51
  `req stats --json` 和 `req api stats` 输出同样的对象(`schema: 1`):`period`、`machines`、`active`、`completed`、`byType`、`cycleHours`、`steps`、`rework`、`waits`、`usage`、`byBuilder`、`requirements`(每个需求一项,字段与 `req stats --csv` 的列对应)。
@@ -0,0 +1,33 @@
1
+ # 0031 少等人:每种中断都有自动的下一步
2
+
3
+ - 状态:已采纳(1.12.0-beta.1)
4
+
5
+ ## 背景
6
+
7
+ 目标是需求方只在三处介入:确认需求、确认方案(只有界面改动时)、验收合并。核对代码、默认配置和 `/req` 手册后,现在要人的地方共 16 处,其中只有这 3 处是必要的决定;其余多数来自两类:
8
+
9
+ - **中断等人继续**:步骤超时、agent 报错、结果文件无效直接中断;限流重试用完只按固定间隔自动继续两次;后台进程意外退出的定时检查默认关闭。
10
+ - **可以有默认值的选择题**:是否新建工作区、档位、执行方式、现在开始还是预约;装好后要运行配置向导选模型;方案审核提出的问题不加筛选地交给需求方。
11
+
12
+ 另外,过去没有记录每次停下等人的原因,无法用数据判断先改哪里。
13
+
14
+ ## 决定
15
+
16
+ - **先记录**:状态文件和使用记录加上 `stopKind`(停下等人的原因代码)和 `recoveries`(自己处理掉的问题),`req stats` 新增"人工介入"一节。用两周的真实数据复核后续的优先级。
17
+ - **自愈**(`selfHeal`,默认开):
18
+ - 超时:agent 还在做就再等同样长的时间,不打断它;仍未完成才中断(1.11 已保证继续时不会另起一个 agent)。
19
+ - agent 报错、结果文件无效:新开一个 agent 重做一次。只重做一次,避免一个确实做不了的步骤反复消耗额度。
20
+ - **备用模型**(`agents.fallback`,可选):报错重做、限流重试用完时换它做一次。只用一次、只在这一步,下一步仍用主模型。
21
+ - **按额度恢复时间预约**:限流中断时,读 Paseo 报告的额度恢复时间,预约在恢复后 5 分钟自动继续;读不到时仍按 `autoResumeMinutes`。
22
+ - **后台进程退出后续跑**:个人机器的定时检查默认打开(60 分钟);接单器每次检查时发现"运行中但进程已不在"的流水线,请它的对话检查并继续,每个进程只提醒一次。
23
+ - **方案审核的问题先自答**(`answerQuestionsFirst`,默认开):审核结论为"需要需求方决定"时,先让 builder 在方案修订中从代码、文档和需求说明里找答案;只把真正的业务取舍连同推荐答案交给需求方,都能回答时不停下。
24
+ - **装好就能用**:安装时 `req configure --auto` 不提问,按推荐选好已登录的 agent(两个不同厂商分别做 builder、reviewer,再选一个做备用,都用免确认模式)。插件新增"模型"设置页,列出 Paseo 中已登录的 agent,选择后写回本机配置,与 `req configure` 用同一套逻辑。
25
+ - **默认值代替选择题**:`/req` 不再问是否新建工作区、档位、执行方式、现在开始还是预约,按推荐值直接走,在确认摘要的最后一行写明,想改再说;确认需求说明就是同意开始。
26
+ - **卡片回复更宽松**:回复区写"好的""没问题""按你说的来"也算同意;只写了这样一句、没有别的内容时,按正在等待的确认(需求说明或方案)处理。卡片已经写清需求时,需求会话不为提问而提问。
27
+
28
+ ## 影响
29
+
30
+ - 新配置:`selfHeal`、`answerQuestionsFirst`、`agents.fallback`;`watchdogMinutes` 默认从 0 改为 60。想保持以前行为的,把它们设为 `false` / `0`。
31
+ - 方案修订的结果文件可以带 `business_questions`。
32
+ - 新命令:`req configure --auto`、`req api models`、`req api set-agents`。
33
+ - 自动处理会多用一些额度(多等一轮、重做一次);每次都写进进度日志和使用记录,可以查到。
@@ -32,3 +32,4 @@ req-kit 自身的设计决定。修改其中任何一条,请新增一条 ADR
32
32
  - [0028 外部系统对接:命令写法在配置里,内容由 AI 整理,调用是确定的](0028-external-integrations.md)
33
33
  - [0029 从卡片开始的需求:需求会话代替人,卡片描述代替对话](0029-requirements-from-cards.md)
34
34
  - [0030 接单器:共享机器上定时接卡片、转达回复](0030-card-intake-process.md)
35
+ - [0031 少等人:每种中断都有自动的下一步](0031-fewer-interventions.md)
@@ -53,7 +53,7 @@ curl -fsSL https://paseo-req-kit.vercel.app/install.sh | sh
53
53
  irm https://paseo-req-kit.vercel.app/install.ps1 | iex
54
54
  ```
55
55
 
56
- 脚本依次:检查 Node.js 22+、npm、git;从 npm 安装 `paseo-req-kit`,npm 上没有时改用 GitHub Release 的安装包;npm 全局目录不可写时装到 `~/.req-kit/npm`(不需要 sudo);**检查 Paseo**(见下);运行 `req setup`;本机配置还没填时运行 `req configure`;最后提示把 `req` 加入 PATH(如需要)。选项:`install.sh` 写在 `sh -s --` 之后(`--beta`、`--version <版本>`、`--from npm|release`、`--no-configure`、`--skip-paseo`);`install.ps1` 用变量(`$ReqKitBeta = $true; irm … | iex`,还有 `$ReqKitVersion`、`$ReqKitFrom`、`$ReqKitNoConfigure`、`$ReqKitSkipPaseo`)。
56
+ 脚本依次:检查 Node.js 22+、npm、git;从 npm 安装 `paseo-req-kit`,npm 上没有时改用 GitHub Release 的安装包;npm 全局目录不可写时装到 `~/.req-kit/npm`(不需要 sudo);**检查 Paseo**(见下);运行 `req setup`;本机配置还没填时运行 `req configure --auto`(1.12:不提问,按推荐选好已登录的 agent:两个不同厂商的分别做 builder 和 reviewer,再选一个做备用模型,都用免确认模式;自动选不了时改为逐项选择);最后提示把 `req` 加入 PATH(如需要)。选项:`install.sh` 写在 `sh -s --` 之后(`--beta`、`--version <版本>`、`--from npm|release`、`--no-configure`、`--skip-paseo`);`install.ps1` 用变量(`$ReqKitBeta = $true; irm … | iex`,还有 `$ReqKitVersion`、`$ReqKitFrom`、`$ReqKitNoConfigure`、`$ReqKitSkipPaseo`)。
57
57
 
58
58
  检查 Paseo 这一步:
59
59
 
@@ -69,7 +69,8 @@ irm https://paseo-req-kit.vercel.app/install.ps1 | iex
69
69
  ```bash
70
70
  npm i -g paseo-req-kit # beta 版:npm i -g paseo-req-kit@beta
71
71
  req setup # 装进 ~/tools/req-kit/versions/<版本>,安装 skill,生成本机配置
72
- req configure # 配置向导:选 builder 和 reviewer 用的 agent
72
+ req configure # 配置向导:选 builder、reviewer 和备用模型用的 agent
73
+ req configure --auto # 不提问,按推荐选好(也可以在 Paseo 的 设置 → 插件 → req-kit → 模型 中修改)
73
74
  req doctor --no-project # 体检
74
75
  ```
75
76
 
@@ -268,7 +268,7 @@ Paseo 自带的 `/paseo-advisor`、`/paseo-committee` 等编排 skill 也可以
268
268
  | `waitingReminderHours` | 24 | 等待需求方回答、或研发完成后等待测试验证超过这么久,在对话里提醒一次;0 关闭 |
269
269
  | (启动参数 `--verify-at`) | — | 分阶段时,研发完成后在这个时间自动开始测试验证(写法同 `--at`;隐含分阶段)。研发完成时时间已过就直接继续 |
270
270
  | `autoResumeMinutes` / `autoResumeMax` | 30 / 2 | 临时错误(重试用完)中断后,这么久之后在对话里自动继续;每次尝试最多几次;0 关闭 |
271
- | `watchdogMinutes` | 0(关闭) | 运行期间对话定时检查,后台进程意外退出时自动继续。每次检查对话 agent 会运行一个回合 |
271
+ | `watchdogMinutes` | 60(1.12 起默认打开) | 运行期间对话定时检查,后台进程意外退出时自动继续。每次检查对话 agent 会运行一个回合;0 关闭。接单器(`req factory`)每次检查时也会发现这样的流水线并请它的对话继续 |
272
272
  | `maxConcurrentPipelines` | 0(不限) | 本机同时运行的流水线上限,满了排队。只能写在本机配置中才有意义 |
273
273
 
274
274
  ## 需求总览
@@ -471,7 +471,7 @@ Paseo 自带的 `/paseo-advisor`、`/paseo-committee` 等编排 skill 也可以
471
471
 
472
472
  **结果文件**:每步必须写一个 JSON 结果文件,流水线只看它,不看 agent 的回复文字。文件缺失或格式不对时,会在同一个会话里提醒 agent 补写,最多 2 次,仍不对则判定该步骤失败。
473
473
 
474
- **等待与超时**:每步有总时限(`stepTimeoutMinutes`),超时判定失败,但不停止 agent。继续(`--resume`、自动重试)时先看这一步上一次的 agent:已经做完、结果文件有效就直接用它的结果;还在做就接着等它一轮;第二次仍超时才停止它、另起一个 agent。同一步不会有两个 agent 同时在做(它们会改同样的文件、在同一个工作区提交、写同一个结果文件)。连续读不到 agent 状态时同样处理。等待被拆成每段 10 分钟,连接断开时自动重试。agent 等待权限批准时,同一个请求只通知一次,然后继续等待。
474
+ **等待与超时**:每步有总时限(`stepTimeoutMinutes`)。到时 agent 还在做时,先再给它同样长的时间(`selfHeal`,见下);仍未完成才判定失败,但不停止 agent。继续(`--resume`、自动重试)时先看这一步上一次的 agent:已经做完、结果文件有效就直接用它的结果;还在做就接着等它一轮;第二次仍超时才停止它、另起一个 agent。同一步不会有两个 agent 同时在做(它们会改同样的文件、在同一个工作区提交、写同一个结果文件)。连续读不到 agent 状态时同样处理。等待被拆成每段 10 分钟,连接断开时自动重试。agent 等待权限批准时,同一个请求只通知一次,然后继续等待。
475
475
 
476
476
  **临时错误自动重试**:步骤因限流(429)、服务暂时不可用(502/503/504/529、overloaded)、网络中断等临时错误失败时,等待后用新的 agent 重做这一步,并告诉它工作区里可能留有上一次没做完的改动。默认最多重试 2 次,分别等 1 分钟和 5 分钟:
477
477
 
@@ -479,7 +479,25 @@ Paseo 自带的 `/paseo-advisor`、`/paseo-committee` 等编排 skill 也可以
479
479
  "retry": { "attempts": 2, "backoffSeconds": [60, 300] }
480
480
  ```
481
481
 
482
- `attempts` 设为 0 关闭;`backoffSeconds` 的个数少于重试次数时,后面的重试使用最后一个值。超时、结果文件不合格、agent 自己判定失败等不是临时错误,不重试。重试用完仍失败时,中断通知会注明已重试的次数。
482
+ `attempts` 设为 0 关闭;`backoffSeconds` 的个数少于重试次数时,后面的重试使用最后一个值。超时、结果文件不合格、agent 自己判定失败等不是临时错误,不按这里重试。重试用完仍失败时,中断通知会注明已重试的次数。
483
+
484
+ **自愈**(1.12,`selfHeal`,默认 `true`):能安全自动处理的中断不再等人。
485
+
486
+ | 情况 | 自动处理 |
487
+ | --- | --- |
488
+ | 步骤超时,agent 还在做 | 不打断它,再等同样长的时间;仍未完成才中断 |
489
+ | agent 报错(不是临时错误) | 新开一个 agent 重做这一步一次;配置了备用模型时用备用模型 |
490
+ | 提醒后仍没有有效的结果文件 | 新开一个 agent 重做这一步一次 |
491
+ | 限流、额度用完,重试用完 | 配置了备用模型时,用备用模型再做一次;仍不行就中断,并按 Paseo 显示的额度恢复时间预约自动继续(恢复后 5 分钟;没有恢复时间时按 `autoResumeMinutes`) |
492
+ | 方案审核说需要需求方决定 | 先请 builder 从代码、文档和需求说明中找答案(`answerQuestionsFirst`,默认 `true`),只把真正的业务取舍连同推荐答案交给需求方;都能回答时不停下 |
493
+
494
+ **备用模型**(1.12,可选):`agents.fallback`,写法同 builder、reviewer,可以用 `paseoProfile`。`req configure` 和插件的"模型"设置页会推荐一个与主模型不同厂商的模型。
495
+
496
+ ```json
497
+ "agents": { "builder": { … }, "reviewer": { … }, "fallback": { "provider": "opencode/<模型 ID>" } }
498
+ ```
499
+
500
+ 自动处理的每一次都记在进度日志、状态文件的 `recoveries` 和使用记录中,`req stats` 的"人工介入"一节汇总:停下等人的原因、自动处理了多少次、到达提测的需求中没有非必要介入的比例。
483
501
 
484
502
  **同一个目录一次只运行一条流水线**,因为各步骤都在这个目录中修改和提交。需要并行时,为每个需求使用单独的 worktree 工作区。
485
503
 
@@ -39,3 +39,18 @@
39
39
  ```
40
40
 
41
41
  - `status`:所有"必须修改"的意见都已处理(修改或说明了不采纳的依据)时为 `"done"`;有意见没能处理时为 `"partial"`,并在修订记录中说明。
42
+
43
+ ## 审核要求需求方决定时
44
+
45
+ `<pipeline-context>` 中写着"审核结论:需要需求方决定"时,审核者认为有问题必须由需求方回答。需求方不读代码,每多问一个问题,开发就要停下来等一次,所以先自己找答案:
46
+
47
+ 1. 逐个问题,到 `spec.md`、`alignment.md`(有的话)、项目文档、术语表、相关代码和同类功能的现有做法中找依据。
48
+ 2. 能确定答案的(例如现有功能已经有一致的做法、spec 中其实写了、或者是纯技术选择):直接在方案中采用,在修订记录中写明问题、答案和依据(文件或代码位置)。
49
+ 3. 只有需要需求方在业务上取舍、而且选错了返工很大的问题,才写进结果文件的 `business_questions`。每个问题写成需求方能直接回答的样子,并附上你的推荐答案和理由,例如:"历史订单是否也显示新的配送状态?推荐:只有新订单显示(理由:历史订单没有这项数据,补录要另做)"。
50
+ 4. 方案中其余部分照常按审核意见修订。
51
+
52
+ 结果文件中 `business_questions` 为空或省略时,流水线不停下,继续审核或实现:
53
+
54
+ ```json
55
+ { "status": "done", "business_questions": ["历史订单是否也显示新的配送状态?推荐:只有新订单显示(理由:…)"] }
56
+ ```
package/engine/src/api.ts CHANGED
@@ -10,6 +10,8 @@
10
10
  // req api stats [--since 30d] [--month 2026-10]
11
11
  // req api index [--rebuild <dir>...] [--prune]
12
12
  // req api run <approve|stop|resume|verify> <需求> [--repo <dir>] [其他选项]
13
+ // req api models agents the daemon offers, the current and the recommended ones (1.12)
14
+ // req api set-agents --json <设置> writes builder, reviewer and fallback to the local config (1.12)
13
15
  //
14
16
  // Reading never changes anything. `run` performs the same command as `req <command>` in that
15
17
  // directory, so the engine stays the only one that changes a requirement's state.
@@ -313,6 +315,26 @@ export function apiRun(command: string, repoArg: string, feature: string, extra:
313
315
  return { command, featureDir: feature, ok: r.status === 0, exitCode: r.status, message: r.error ? r.error.message : output };
314
316
  }
315
317
 
318
+ /** Runs the configuration program in one of its machine modes and returns its JSON line. */
319
+ function configureJson(args: string[]): Record<string, unknown> {
320
+ const r = spawnSync(process.execPath, [path.join(KIT_ROOT, "bin", "req.mjs"), "configure", ...args], {
321
+ encoding: "utf8",
322
+ timeout: 120_000,
323
+ windowsHide: true,
324
+ env: process.env,
325
+ });
326
+ const line = (r.stdout ?? "").trim().split("\n").pop() ?? "";
327
+ let parsed: Record<string, unknown> | null = null;
328
+ try {
329
+ parsed = JSON.parse(line) as Record<string, unknown>;
330
+ } catch {
331
+ parsed = null;
332
+ }
333
+ if (!parsed) throw new ApiError("failed", `读取 agent 设置失败:${(r.stderr || r.error?.message || "没有输出").trim().split("\n").slice(-2).join(" ")}`);
334
+ if (parsed.ok === false) throw new ApiError("failed", String(parsed.message ?? "失败"));
335
+ return parsed;
336
+ }
337
+
316
338
  // ---------------------------------------------------------------------------
317
339
  // CLI
318
340
 
@@ -376,8 +398,18 @@ export function apiMain(argvIn: string[], cwd = process.cwd()): unknown {
376
398
  const what = need("命令(approve、stop、resume、verify)");
377
399
  return apiRun(what, repo, need("需求"), argv.splice(0));
378
400
  }
401
+ case "models": {
402
+ const { current, choices, recommended } = configureJson(["--catalog-json"]);
403
+ return { current, choices, recommended };
404
+ }
405
+ case "set-agents": {
406
+ const value = option(argv, "--json");
407
+ if (!value) throw new ApiError("bad_arguments", "缺少 --json <设置>");
408
+ const { message } = configureJson(["--set", value]);
409
+ return { ok: true, message };
410
+ }
379
411
  default:
380
- throw new ApiError("bad_arguments", `没有 "${command ?? ""}" 这个 api 命令。可用:version、list、status、file、stats、index、run`);
412
+ throw new ApiError("bad_arguments", `没有 "${command ?? ""}" 这个 api 命令。可用:version、list、status、file、stats、index、run、models、set-agents`);
381
413
  }
382
414
  }
383
415
 
@@ -21,6 +21,7 @@ import {
21
21
  hashText,
22
22
  listCardEntries,
23
23
  locateCardRequirement,
24
+ isYes,
24
25
  parseReply,
25
26
  planAppendix,
26
27
  publishCard,
@@ -95,7 +96,7 @@ function today(now = new Date()): string {
95
96
  return `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}`;
96
97
  }
97
98
 
98
- async function withRuntime<T>(config: PipelineConfig, use: (runtime: AgentRuntime, config: PipelineConfig) => Promise<T>): Promise<T> {
99
+ export async function withRuntime<T>(config: PipelineConfig, use: (runtime: AgentRuntime, config: PipelineConfig) => Promise<T>): Promise<T> {
99
100
  const runtime = await (await loadRuntimeFactory())({ config, log: () => undefined });
100
101
  try {
101
102
  const resolved = usesPaseoProfiles(config) ? resolveConfigProfiles(config, await runtime.listPaseoProfiles()) : config;
@@ -316,6 +317,12 @@ export async function checkOne(target: Located, opts: CardCmdOptions, notify: (a
316
317
  const key = replyKey(reply);
317
318
  let confirmed = false;
318
319
  let planConfirmed = false;
320
+ // 1.12: a bare "好的" written anywhere in the reply section answers what is being waited for.
321
+ if (!reply.confirmText && !reply.planConfirmText && reply.notes && isYes(reply.notes) && Object.keys(reply.answers).length === 0) {
322
+ const state = fs.existsSync(statePath(target.repo, target.entry.featureDir)) ? readState(statePath(target.repo, target.entry.featureDir)) : null;
323
+ if (state?.status === "waiting_for_approval") reply.planConfirmed = true;
324
+ else if (!source.confirmedAt) reply.confirmed = true;
325
+ }
319
326
  if (parts.reply !== null && source.replyHash !== null && key !== source.replyHash) {
320
327
  const answered = Object.keys(reply.answers);
321
328
  if (answered.length) changes.push(`需求方回答了 ${answered.join("、")}`);
@@ -186,8 +186,15 @@ export interface Reply {
186
186
  planConfirmText: string;
187
187
  }
188
188
 
189
- // A plain yes, nothing after it but punctuation: "是否可以改成…" is not a yes.
190
- const YES = /^(同意|确认|是|可以|好|没问题|通过|ok|yes|y)\s*[。.!!]?\s*$/i;
189
+ // A plain yes, nothing after it but particles and punctuation: "是否可以改成…" is not a yes.
190
+ // 1.12: the everyday ways of saying it count too ("好的", "没问题", "按你说的来", "👍").
191
+ const YES =
192
+ /^(同意|确认|确定|认可|是|是的|对|对的|可以|好|好的|行|没问题|没有问题|通过|就这样|按这个来|按你说的(来|做|办)?|按方案(来|做)?|ok|okay|yes|y|lgtm|👍)\s*(的|了|吧|啊|呀|哈)?\s*[。.!!~~]*\s*$/i;
193
+
194
+ /** Whether a reply is a plain yes. */
195
+ export function isYes(text: string): boolean {
196
+ return YES.test(text.trim());
197
+ }
191
198
 
192
199
  export function parseReply(reply: string | null): Reply {
193
200
  const out: Reply = { answers: {}, notes: "", confirmed: false, planConfirmed: false, confirmText: "", planConfirmText: "" };
@@ -151,7 +151,23 @@ export interface PipelineConfig {
151
151
  daemonUrl: string;
152
152
  /** Name of the environment variable holding the daemon password, if the daemon has one. */
153
153
  daemonPasswordEnv: string;
154
- agents: Record<"builder" | "reviewer", AgentProfile>;
154
+ /**
155
+ * builder and reviewer; `fallback` (1.12, optional) is used when a step's own agent keeps
156
+ * failing (rate limits or quota after the retries, an agent error): the step is run once
157
+ * more with it instead of stopping.
158
+ */
159
+ agents: Record<"builder" | "reviewer", AgentProfile> & { fallback?: AgentProfile | null };
160
+ /**
161
+ * 1.12: handle failures without a person where that is safe: wait once more for a step that
162
+ * runs over its time, redo a step once after an agent error or an invalid result file.
163
+ */
164
+ selfHeal: boolean;
165
+ /**
166
+ * 1.12: questions the design review says only the requester can decide go to the builder
167
+ * first, who answers what the code, the documents and the spec settle; only the real
168
+ * business choices, each with a recommended answer, reach the requester.
169
+ */
170
+ answerQuestionsFirst: boolean;
155
171
  /** Profile used when neither --profile nor the spec names one. */
156
172
  defaultProfile: ProfileName;
157
173
  profiles: Record<ProfileName, ProfileSettings>;
@@ -317,7 +333,9 @@ export const DEFAULT_CONFIG: PipelineConfig = {
317
333
  waitingReminderHours: 24,
318
334
  autoResumeMinutes: 30,
319
335
  autoResumeMax: 2,
320
- watchdogMinutes: 0,
336
+ watchdogMinutes: 60,
337
+ selfHeal: true,
338
+ answerQuestionsFirst: true,
321
339
  phases: { mode: "continuous", verify: { environment: "local", agents: {}, defects: "fix", serviceStart: null } },
322
340
  requirementTypes: ["defect", "refactor", "assessment"],
323
341
  checks: [],
@@ -532,6 +550,9 @@ export function resolveRelatedRepo(repo: RelatedRepo, projectDir: string): strin
532
550
 
533
551
  function validate(config: PipelineConfig): void {
534
552
  for (const role of ["builder", "reviewer"] as const) validateProvider(`agents.${role}`, config.agents[role]);
553
+ if (config.agents.fallback !== undefined && config.agents.fallback !== null) validateProvider("agents.fallback", config.agents.fallback);
554
+ if (typeof config.selfHeal !== "boolean") throw new ConfigError("selfHeal 必须是 true 或 false");
555
+ if (typeof config.answerQuestionsFirst !== "boolean") throw new ConfigError("answerQuestionsFirst 必须是 true 或 false");
535
556
  if (!PROFILE_NAMES.includes(config.defaultProfile)) {
536
557
  throw new ConfigError(`defaultProfile 必须是 ${PROFILE_NAMES.join(" 或 ")}`);
537
558
  }