paseo-req-kit 1.11.0 → 1.12.0-beta.3
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/CHANGELOG.md +45 -0
- package/bin/req.mjs +5 -0
- package/config.example.json +3 -1
- package/contracts/cli-json.md +5 -1
- package/contracts/notifier-event.md +17 -1
- package/contracts/state.md +3 -1
- package/contracts/usage-record.md +4 -1
- package/docs/adr/0031-fewer-interventions.md +41 -0
- package/docs/adr/README.md +1 -0
- package/docs/guide//344/275/277/347/224/250/350/200/205/346/214/207/345/215/227.md +2 -0
- package/docs/guide//345/256/211/350/243/205/344/270/216/345/215/207/347/272/247.md +3 -2
- 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 +58 -4
- package/engine/prompts/04b-design-revise.md +15 -0
- package/engine/src/api.ts +74 -3
- package/engine/src/card-cmd.ts +21 -1
- package/engine/src/card.ts +38 -2
- package/engine/src/config-view.ts +289 -0
- package/engine/src/config.ts +65 -4
- package/engine/src/configure.ts +161 -36
- package/engine/src/control.ts +1 -0
- package/engine/src/doctor.ts +14 -0
- package/engine/src/factory.ts +162 -7
- package/engine/src/main.ts +1 -0
- package/engine/src/messages.ts +22 -0
- package/engine/src/notifiers.ts +32 -1
- package/engine/src/notify-test.ts +8 -2
- package/engine/src/paseo-profiles.ts +2 -0
- package/engine/src/paseo-runtime.ts +9 -0
- package/engine/src/pipeline.ts +222 -30
- package/engine/src/records.ts +8 -0
- package/engine/src/reply.ts +59 -0
- package/engine/src/runtime.ts +2 -0
- package/engine/src/state.ts +41 -1
- package/engine/src/stats.ts +90 -0
- package/engine/src/steps.ts +4 -1
- package/install.mjs +5 -1
- package/notifiers/im-template.mjs +31 -5
- package/package.json +2 -2
- package/paseo-plugin/OVERVIEW.md +2 -0
- package/paseo-plugin/client/config.tsx +216 -0
- package/paseo-plugin/client/data.ts +21 -2
- package/paseo-plugin/client/format.ts +1 -0
- package/paseo-plugin/client/inbox.tsx +173 -0
- package/paseo-plugin/client/models.tsx +117 -0
- package/paseo-plugin/client/overview.tsx +7 -3
- package/paseo-plugin/client/settings.tsx +3 -1
- package/paseo-plugin/index.client.tsx +10 -0
- package/paseo-plugin/index.server.ts +4 -1
- package/paseo-plugin/package.json +1 -1
- package/paseo-plugin/server/handlers.ts +21 -3
- package/paseo-plugin/shared/contracts.ts +77 -1
- package/skills/req/playbooks/new.md +14 -14
- package/skills/req/playbooks/small.md +3 -3
- package/skills/req/playbooks/unattended.md +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
每个版本分为"使用者会看到的变化"和"升级须知"。升级步骤见 [docs/guide/安装与升级.md](docs/guide/安装与升级.md)。
|
|
4
4
|
|
|
5
|
+
## 1.12.0-beta.3
|
|
6
|
+
|
|
7
|
+
接单机器能自己发现问题;插件里可以直接处理等你的事、看到全部配置。
|
|
8
|
+
|
|
9
|
+
### 使用者会看到的变化
|
|
10
|
+
|
|
11
|
+
- **插件"等你处理"**:需求首页最上面列出所有等人处理的事(确认方案、回答问题、中断、未完成),等得最久的在前,写明要做什么、等了多久。方案可以直接确认或写修改意见,问题可以直接回答,插件把回复转给需求的对话,和在对话里回答一样(`req reply`)。
|
|
12
|
+
- **插件"配置"页**:首页的"配置"按主题列出 req-kit 的全部设置,每项写明是什么、现在的值、来自本机、团队还是项目的配置文件;可以按项目查看、搜索、只看改过的。只看不改,页尾写明各项怎么改。密钥和个人账号不显示。
|
|
13
|
+
- **接单器自检与告警**:每次检查前确认 Paseo 在线、agent 已登录、额度没用完、需求管理系统的命令可用;不正常时暂停接单,并通过外部通知告警一次(新事件 `alert`),恢复时再说一次。
|
|
14
|
+
- **草稿合并请求**(可选,`push.mergeRequest`):推送分支后用机器上已登录的 `glab` 等命令建草稿合并请求,描述是提测说明和卡片链接,地址写进卡片。合并仍由人做。`req doctor` 检查是否已登录。
|
|
15
|
+
- **小改动自动确认**(可选,`autoConfirm.hours`,默认关闭):快速档、没有待答问题的卡片,AI 区写明到时没有异议就开始。
|
|
16
|
+
- **按人通知**:通知事件带上发给谁(`people`):需求方要处理的事和结果发给卡片的提出人,账号来自配置的 `people`;其余发到群。内部 IM 适配器拿到接口后可以直接用。
|
|
17
|
+
|
|
18
|
+
### 升级须知
|
|
19
|
+
|
|
20
|
+
- 新配置 `push.mergeRequest`、`autoConfirm`、`people`(都只在需要时写)。`people`、`push` 只能写在本机或团队配置中。
|
|
21
|
+
- 外部通知新增事件类型 `alert` 和字段 `people`;只订阅了部分事件的通知模块不受影响。
|
|
22
|
+
- 新命令 `req reply`、`req api config`;`req api list` 的每一项多了 `waiting`。
|
|
23
|
+
|
|
24
|
+
## 1.12.0-beta.2
|
|
25
|
+
|
|
26
|
+
1.12.0-beta.1 的内容(见下),加一处修正:
|
|
27
|
+
|
|
28
|
+
- **修正**:安装时自动选模型,Paseo 没有运行时会一直等待连接;现在 20 秒连不上就放弃,改为提示之后运行 `req configure --auto`。1.12.0-beta.1 因此没有发布。
|
|
29
|
+
|
|
30
|
+
## 1.12.0-beta.1
|
|
31
|
+
|
|
32
|
+
少等人:能自动处理的中断不再停下等人,选择题改为默认值,装好就选好模型。设计决定见 ADR 0031。
|
|
33
|
+
|
|
34
|
+
### 使用者会看到的变化
|
|
35
|
+
|
|
36
|
+
- **自愈**:步骤超时而 agent 还在做时,再等它一轮;agent 报错、没写出有效结果时,新开一个 agent 重做一次。限流重试用完时,预约在 Paseo 显示的额度恢复时间之后自动继续,而不是固定 30 分钟。
|
|
37
|
+
- **备用模型**(可选):配置 `agents.fallback` 后,报错重做、限流重试用完时换它把这一步做完。`req configure` 和插件的"模型"设置页会推荐一个。
|
|
38
|
+
- **方案审核的问题先自答**:审核说需要需求方决定时,先由 builder 从代码和文档中找答案,只把真正的业务取舍连同推荐答案交给需求方;都能回答时不停下。
|
|
39
|
+
- **后台进程意外退出后自动继续**:定时检查默认打开(每 60 分钟);接单器每次检查时也会发现并请对话继续。
|
|
40
|
+
- **装好就选好模型**:安装时自动选好已登录的 agent(两个不同厂商分别做 builder 和 reviewer,再选一个做备用),不再需要回答配置向导;`req configure --auto` 同样。插件设置中新增"模型"一页,可以直接修改。
|
|
41
|
+
- **`/req` 少问**:不再问是否新建工作区、档位、执行方式、现在开始还是预约,按推荐值直接走,确认摘要最后一行写明,想改再说;确认需求说明后立即开始。
|
|
42
|
+
- **卡片回复更宽松**:写"好的""没问题""按你说的来"也算同意;回复区只写了一句"好的"也算确认。卡片已经写清需求时,不再为提问而提问;问题附上推荐答案。
|
|
43
|
+
- **`req stats` 新增"人工介入"一节**:停下等人的原因、自动处理的次数、到达提测的需求中没有非必要介入的比例。
|
|
44
|
+
|
|
45
|
+
### 升级须知
|
|
46
|
+
|
|
47
|
+
- 新配置 `selfHeal`、`answerQuestionsFirst`(默认都是 `true`)、`agents.fallback`(可选);`watchdogMinutes` 默认从 0 改为 60。想保持以前的行为,分别设为 `false`、`false`、`0`。
|
|
48
|
+
- 状态文件新增 `stopKind`、`recoveries`,使用记录的 `run` 新增 `stopKind`、`recoveries`;旧文件照常可用,统计按状态推断旧记录的原因。
|
|
49
|
+
|
|
5
50
|
## 1.11.0
|
|
6
51
|
|
|
7
52
|
稳定版:包含 1.10 的"从卡片开始的需求"和 1.11 的接单器(详见下面的 beta 记录),并修正步骤超时后会重复开 agent 的问题。
|
package/bin/req.mjs
CHANGED
|
@@ -30,6 +30,7 @@ const HELP = `req-kit ${version}(${kitRoot})
|
|
|
30
30
|
req digest --schedule "0 9 * * 1"
|
|
31
31
|
让 Paseo 按时间表发送需求总览(例子为每周一 9:00);取消:req digest --cancel <ID>
|
|
32
32
|
req sync <需求> 把需求同步到对接的外部系统(补发失败的调用、补建卡片、更新状态)
|
|
33
|
+
req reply <需求> <回复> 把回答或方案意见转给需求的对话(插件的"等你处理"用它)
|
|
33
34
|
req integration test [需求] 只显示对接外部系统时会执行的命令,不真正调用
|
|
34
35
|
req start --from <系统>:<卡号> 从需求管理系统中的一张卡片开始需求(没有人开着对话也可以)
|
|
35
36
|
req card check [<卡号>|--all] 读取需求方在卡片中的回复,转给需求的会话
|
|
@@ -56,6 +57,7 @@ Paseo 插件(在 Paseo 中查看和管理需求):
|
|
|
56
57
|
本机维护:
|
|
57
58
|
req doctor [--no-project] 体检:安装、配置、daemon、当前项目
|
|
58
59
|
req configure 配置向导:选择 builder 和 reviewer 用的 agent
|
|
60
|
+
req configure --auto 不提问,按推荐选好 builder、reviewer 和备用模型
|
|
59
61
|
req upgrade [版本] [--beta] 升级到最新版本(或指定版本),自动选能支持本机 Paseo 的版本;
|
|
60
62
|
req upgrade --check 只看有没有新版本
|
|
61
63
|
req versions 已安装的版本和 npm 上的版本
|
|
@@ -196,6 +198,9 @@ switch (command) {
|
|
|
196
198
|
case "api":
|
|
197
199
|
code = needTsx() ? node([tsxCli, path.join(kitRoot, "engine", "src", "api.ts"), ...rest]) : 1;
|
|
198
200
|
break;
|
|
201
|
+
case "reply":
|
|
202
|
+
code = needTsx() ? node([tsxCli, path.join(kitRoot, "engine", "src", "reply.ts"), ...rest]) : 1;
|
|
203
|
+
break;
|
|
199
204
|
case "sync":
|
|
200
205
|
code = needTsx() ? node([tsxCli, path.join(kitRoot, "engine", "src", "integration-cmd.ts"), "sync", ...rest]) : 1;
|
|
201
206
|
break;
|
package/config.example.json
CHANGED
package/contracts/cli-json.md
CHANGED
|
@@ -27,7 +27,10 @@
|
|
|
27
27
|
| `req api file <需求> <文件> [--repo <目录>]` | `{ featureDir, file, size, truncated, content }`;只能读取功能目录中的文档(见下),最多 512 KB |
|
|
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
|
-
| `req api run <approve\|stop\|resume\|verify\|sync> <需求> [--repo <目录>] [选项]` | `{ command, featureDir, ok, exitCode, message }`。在 `--repo`(默认当前目录)中执行与 `req <命令> <需求>` 完全相同的操作;`ok` 为 false 时退出码为 1 |
|
|
30
|
+
| `req api run <approve\|stop\|resume\|verify\|sync\|reply> <需求> [--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 config [--repo <目录>] [--machine]` | 1.12 起。`{ sources, ignored, error, repo, groups }`:`sources` 是生效的配置文件(`layer` 为 `machine` / `team` / `project`,按叠加顺序);`groups` 按主题分组,每项 `{ key, label, description, display, value, setBy, scope, changed }`:`display` 是用大白话写的值,`value` 中看起来像密钥的值和个人账号替换为 `••••`,`setBy` 是写了它的文件,`changed` 表示与默认值不同。配置有错误时 `error` 写明原因,值按各文件所写。`--machine` 不看项目配置 |
|
|
33
|
+
| `req api set-agents --json <设置>` | 1.12 起。`{ ok, message }`。`<设置>` 是 `{ builder, reviewer, fallback }`(`fallback` 可以为 null),检查后写入本机配置的 `agents`,原文件备份 |
|
|
31
34
|
|
|
32
35
|
`<需求>` 的写法与 `req status` 相同:功能目录、目录名或名称中能唯一确定的一部分。需求在为它新建的工作区中开发时,`--repo` 写对话所在的目录或那个工作区都可以。
|
|
33
36
|
|
|
@@ -48,6 +51,7 @@
|
|
|
48
51
|
| `running` | 后台进程是否在运行 |
|
|
49
52
|
| `phase` | `develop` / `verify` |
|
|
50
53
|
| `attention` | 需要人处理的事:`approval`(确认方案)、`business`(需求方决定)、`interrupted`(中断,可以继续)、`not_completed`(结束但未完成);没有为 null |
|
|
54
|
+
| `waiting` | 1.12 起。`attention` 不为 null 时:`since`(从什么时候开始等)、`questions`(需求方要回答的问题)、`reason`(停止原因)、`card`(`{ system, id, url, requester }`,从卡片开始的需求)、`canReply`(能否用 `req api run reply <需求> <回复>` 把回复转给需求的对话);否则为 null |
|
|
51
55
|
| `current` | 正在运行的步骤 `[{ key, title, agentId }]` |
|
|
52
56
|
| `updatedAt` | 最近一次变化的时间 |
|
|
53
57
|
| `usage` | `{ steps, agentMinutes, inputTokens, outputTokens, costUsd }`;没运行过为 null |
|
|
@@ -33,7 +33,7 @@ export default async function notify(event, options) { /* 发送 */ }
|
|
|
33
33
|
- 每个事件调用一次,可以返回 Promise。超过 15 秒没有完成按失败处理。
|
|
34
34
|
- 抛出的错误和超时只记入流水线日志("外部通知失败……不影响流水线"),不会让流水线停下或重试。
|
|
35
35
|
- 模块在一次流水线运行中只加载一次;修改模块后,对之后启动或继续的流水线生效。
|
|
36
|
-
- 测试:`req notify-test [--type blocked|failed|completed|milestone|permission|phase_done|approval|digest]` 发一条带"【测试】"字样的示例事件,不需要运行流水线。
|
|
36
|
+
- 测试:`req notify-test [--type blocked|failed|completed|milestone|permission|phase_done|approval|digest|alert]` 发一条带"【测试】"字样的示例事件,不需要运行流水线。
|
|
37
37
|
|
|
38
38
|
## 事件格式(schema 1)
|
|
39
39
|
|
|
@@ -55,6 +55,21 @@ export default async function notify(event, options) { /* 发送 */ }
|
|
|
55
55
|
|
|
56
56
|
`text` 是一两句可以直接发出的话;需要自己排版时用 `details`。
|
|
57
57
|
|
|
58
|
+
**发给谁**(1.12 起,`people`):
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
"people": {
|
|
62
|
+
"audience": "requester",
|
|
63
|
+
"requester": "张广祥",
|
|
64
|
+
"card": { "system": "ALM", "id": "CMASTER-BUSINESS#9902", "url": null },
|
|
65
|
+
"recipients": [{ "name": "张广祥", "accounts": { "um": "ZHANGGX001" } }]
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- `audience`:`requester` 表示这件事要需求方处理或是给他的结果(`blocked`、`approval`、`phase_done`、`completed`,而且需求从卡片开始、知道提出人);其余为 `team`,发到群里即可。
|
|
70
|
+
- `requester`、`card`:从卡片开始的需求的提出人和卡片;其他需求为 null。
|
|
71
|
+
- `recipients`:`audience` 为 `requester` 时是需求方本人;`accounts` 来自本机或团队配置的 `people`(姓名 → 各种账号),没有写时为 `{}`,模块可以改发到群。
|
|
72
|
+
|
|
58
73
|
| type | 什么时候 | details |
|
|
59
74
|
| --- | --- | --- |
|
|
60
75
|
| `milestone` | 方案确定、开始验证、开始修复缺陷(配置 `notifyMilestones` 为 true 时,每次尝试各一次) | `milestone`:`design-settled`、`code-reviewed`、`fixing-defects`、`handback-<n>`(1.7.0 起,测试验证第 n 次把缺陷退回研发) |
|
|
@@ -64,6 +79,7 @@ export default async function notify(event, options) { /* 发送 */ }
|
|
|
64
79
|
| `completed` | 流水线结束,报告已生成 | `outcome`(`completed`、`completed_with_issues`、`not_completed`)、`summary`、`reportPath`。"只做研发"时另有 `mode`(`develop-only`)和 `handoffPath`,报告是研发报告,需要人工测试 |
|
|
65
80
|
| `approval` | 1.8.0 起。方案确认点(`designApproval`):方案已通过审核,等待需求方确认"做完后会看到什么",还没有开始写代码 | `previewPath`(要确认的内容)、`approveCommand`(确认后继续的命令)、`resumeCommand`(修改需求后继续的命令),都在发起的对话中执行 |
|
|
66
81
|
| `phase_done` | 1.7.0 起。分阶段执行时研发阶段已完成(代码已通过评审、提测说明已写好),流水线停在闸口等待测试验证。测试退回后再次修完也会发 | `handoffPath`(提测说明)、`mode`(`gated`)、`verifyCommand`(开始测试验证的命令,在发起的对话中执行)、`verifyAt`(预约的开始时间,ISO 格式,没有为 null)、`handbacks`(已退回的次数) |
|
|
82
|
+
| `alert` | 1.12 起。接单器(`req factory`)自检没有通过(Paseo 连不上、agent 没有登录、额度用完、需求管理系统的命令不可用),暂停接新卡片;或者恢复正常。同样的问题只发一次。`feature.title` 为"接单器",`feature.dir`、`repo` 为空 | `source`(`factory`)、`machine`、`problems`(字符串数组,恢复时为空)、`recovered` |
|
|
67
83
|
| `digest` | `req digest --notify`(或由 `req digest --schedule` 创建的 Paseo 定时任务)发送项目的需求总览,1.6.0 起。`feature.title` 为"<项目> 需求总览",`text` 是完整的多行总览 | `features`:每个需求的 `name`、`title`、`stage`、`branch`、`updatedAt`、`usage`;`recurringLessons`(1.8.0 起):在两个以上需求的 `lessons.tsv` 中出现的问题类别,每项 `category`、`features`、`rules` |
|
|
68
84
|
|
|
69
85
|
## 兼容性
|
package/contracts/state.md
CHANGED
|
@@ -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,41 @@
|
|
|
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
|
+
- 自动处理会多用一些额度(多等一轮、重做一次);每次都写进进度日志和使用记录,可以查到。
|
|
34
|
+
|
|
35
|
+
## 补充(1.12.0-beta.3):接单机器的运维和插件中的处理
|
|
36
|
+
|
|
37
|
+
- **接单器自检**:每次检查前确认 Paseo、agent 登录、额度和需求管理系统的命令。不正常就整次跳过(不接新单、不转达),因为这时接下来的需求也跑不起来;告警走外部通知的新事件 `alert`,按"问题的内容"去重,只在出现、变化和恢复时各发一次,避免刷屏。
|
|
38
|
+
- **草稿合并请求**:和对接配置一样,命令写在配置里、参数用占位符,req-kit 不内置 GitLab 接口、不保存令牌;只建草稿、每个仓库只建一次,合并仍由人做。
|
|
39
|
+
- **自动确认**:只对快速档、没有待答问题的卡片,默认关闭;需求方写了任何内容就不自动确认,每次摘要更新重新计时。确认仍然发生在需求会话里,req-kit 只是在到时转达"按自动确认处理"。
|
|
40
|
+
- **按人通知**:事件带上 `people`(发给需求方还是群、需求方的账号),账号来自本机或团队配置的 `people`,内部 IM 的适配器拿到接口后只需要读它。
|
|
41
|
+
- **插件"等你处理"和配置页**:回答和方案意见用 `req reply` 转给需求的对话,由对话按原来的做法更新需求说明并继续,插件不直接改需求文件或状态;配置页只读,展示 `req api config` 的结果,密钥和个人账号不显示。
|
package/docs/adr/README.md
CHANGED
|
@@ -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)
|
|
@@ -62,6 +62,8 @@ req-kit 帮你把一个需求从"我想要……"做到"可以验收":你在 P
|
|
|
62
62
|
|
|
63
63
|
装了 req-kit 插件的话(见 [安装与升级](安装与升级.md) 第 13 节),在 Paseo 侧边栏点"需求":本机所有项目的需求一页看完,"需要你处理"的排在最前;点开一个需求能看到每一步的进度和用时、读需求说明、方案、确认预览和完成报告,也可以直接确认方案、停止、继续或开始测试验证。网页端、桌面版和手机上都能用。
|
|
64
64
|
|
|
65
|
+
1.12 起,首页最上面是"等你处理":所有等你确认方案、回答问题、中断了的需求,等得最久的在前,每条写明要做什么、等了多久。方案可以直接确认,也可以写修改意见;方案审核的问题可以直接写回答,插件会转给这个需求的对话,效果和在对话里回答一样。首页的"配置"列出 req-kit 的全部设置:每一项是什么、现在的值、来自本机、团队还是项目的配置文件,可以按项目查看、搜索、只看改过的。
|
|
66
|
+
|
|
65
67
|
每个步骤都会出现在这个对话的子 agent 列表里(名字形如"[20261012-sms-login] 6-代码评审 第2轮"),点开可以看到它在做什么。功能目录下的 `progress.md` 按时间记录了每一步的结果。
|
|
66
68
|
|
|
67
69
|
可能收到的通知(团队接入了企业 IM 的话,这些通知也会发到 IM):
|
|
@@ -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
|
|
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
|
|
72
|
+
req configure # 配置向导:选 builder、reviewer 和备用模型用的 agent
|
|
73
|
+
req configure --auto # 不提问,按推荐选好(也可以在 Paseo 的 设置 → 插件 → req-kit → 模型 中修改)
|
|
73
74
|
req doctor --no-project # 体检
|
|
74
75
|
```
|
|
75
76
|
|
|
@@ -162,6 +162,12 @@ spec 头部的 `- 类型:` 决定流水线走哪种做法(1.8 起)。缺
|
|
|
162
162
|
|
|
163
163
|
**同时发到企业 IM**:配置 `notifiers` 后,这些通知还会交给你实现的通知模块,例如转发到企业内部 IM。`notifiers` 只能写在本机或团队配置中(项目配置中的会被忽略)。做法见 [安装与升级](安装与升级.md) 第 12 节,事件格式见 `contracts/notifier-event.md`。
|
|
164
164
|
|
|
165
|
+
**按人通知**(1.12 起):从卡片开始的需求,需要需求方处理的事(回答问题、确认方案)和结果(研发完成、流水线结束)在事件中标明发给需求方本人(`people.audience` 为 `requester`)。在本机或团队配置中写上姓名到账号的对应,事件里就带上账号,通知模块可以按人发送;其余事件发到群里:
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
"people": { "张广祥": { "um": "ZHANGGX001" } }
|
|
169
|
+
```
|
|
170
|
+
|
|
165
171
|
## 运行时文件
|
|
166
172
|
|
|
167
173
|
运行时的文件都在功能目录下的 `.pipeline/` 中,不会被提交:
|
|
@@ -268,7 +274,7 @@ Paseo 自带的 `/paseo-advisor`、`/paseo-committee` 等编排 skill 也可以
|
|
|
268
274
|
| `waitingReminderHours` | 24 | 等待需求方回答、或研发完成后等待测试验证超过这么久,在对话里提醒一次;0 关闭 |
|
|
269
275
|
| (启动参数 `--verify-at`) | — | 分阶段时,研发完成后在这个时间自动开始测试验证(写法同 `--at`;隐含分阶段)。研发完成时时间已过就直接继续 |
|
|
270
276
|
| `autoResumeMinutes` / `autoResumeMax` | 30 / 2 | 临时错误(重试用完)中断后,这么久之后在对话里自动继续;每次尝试最多几次;0 关闭 |
|
|
271
|
-
| `watchdogMinutes` |
|
|
277
|
+
| `watchdogMinutes` | 60(1.12 起默认打开) | 运行期间对话定时检查,后台进程意外退出时自动继续。每次检查对话 agent 会运行一个回合;0 关闭。接单器(`req factory`)每次检查时也会发现这样的流水线并请它的对话继续 |
|
|
272
278
|
| `maxConcurrentPipelines` | 0(不限) | 本机同时运行的流水线上限,满了排队。只能写在本机配置中才有意义 |
|
|
273
279
|
|
|
274
280
|
## 需求总览
|
|
@@ -368,10 +374,28 @@ Paseo 自带的 `/paseo-advisor`、`/paseo-committee` 等编排 skill 也可以
|
|
|
368
374
|
```
|
|
369
375
|
|
|
370
376
|
- 只对从卡片开始的需求,推送到 `<remote>` 的 `req/<卡号简称>` 分支(例如 `req/cmaster-business-9902`),联合需求的几个仓库各推各的。分支名写进卡片的 AI 区和状态文件。
|
|
371
|
-
-
|
|
377
|
+
- 只推这个前缀的分支;从不强推、不合并,合并仍由人做。推送被拒绝(例如远程分支有别人的提交)时记下原因、写进 AI 区,流水线照常结束。
|
|
372
378
|
- 只能写在本机或团队配置中,写在项目配置里会被忽略。推送用这台机器上 git 已配置的账号。
|
|
373
379
|
- 在个人电脑上通过对话发起的需求不受影响,仍然只在本地提交。
|
|
374
380
|
|
|
381
|
+
**草稿合并请求**(1.12 起,可选):推送后用机器上的 GitLab 命令行(或其他工具)建一个草稿合并请求。命令写法与对接配置一样,参数中用占位符:
|
|
382
|
+
|
|
383
|
+
```json
|
|
384
|
+
"push": {
|
|
385
|
+
"enabled": true,
|
|
386
|
+
"mergeRequest": {
|
|
387
|
+
"command": "glab",
|
|
388
|
+
"args": ["mr", "create", "--draft", "--source-branch", "{branch}", "--target-branch", "{target}", "--title", "{title}", "--description", "{description}", "--yes"],
|
|
389
|
+
"check": ["auth", "status"]
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
- 占位符:`{branch}` 推送的分支;`{target}` 目标分支(`target` 没写时用远程的默认分支,读不到时 `main`);`{title}` 为"[卡号] 需求标题";`{description}` 为提测说明加卡片链接,开头注明是草稿、合并前请人工评审;`{card}`、`{cardUrl}`。
|
|
395
|
+
- 在需求的仓库中执行;输出中的第一个网址记为合并请求地址,写进卡片 AI 区和状态文件。每个仓库只建一次,测试退回后再推送时合并请求自动更新。
|
|
396
|
+
- 建失败(例如没有登录)只记下原因,分支照常推送。`req doctor` 用 `check` 中的参数检查是否已登录。
|
|
397
|
+
- 需要机器上提前登录好(例如 `glab auth login`),req-kit 不保存令牌。
|
|
398
|
+
|
|
375
399
|
**使用统计**:从卡片开始的需求,运行记录带上需求方(卡片的提出人)和卡号。`req stats --report` 多一节"从卡片开始的需求(按需求方)",CSV 多 `requester`、`card` 两列。
|
|
376
400
|
|
|
377
401
|
### 接单器(1.11 起,beta)
|
|
@@ -411,6 +435,18 @@ Paseo 自带的 `/paseo-advisor`、`/paseo-committee` 等编排 skill 也可以
|
|
|
411
435
|
- Paseo daemon 要同时运行:接单器通过它创建需求会话。
|
|
412
436
|
- 现在只支持一台机器接单。几台机器同时运行接单器会重复接同一张卡片。
|
|
413
437
|
|
|
438
|
+
**自检与告警**(1.12 起):每次检查前先确认 Paseo 连得上、builder 和 reviewer 用的 agent 已登录、额度没有用完(配置了备用模型时,备用的也用完才算),以及需求管理系统的命令能列出卡片。不正常时这次不接新卡片、不转达回复,原因写进 `req factory status` 和日志,并通过外部通知发一条 `alert` 事件;同样的问题只发一次,恢复正常时再发一次。
|
|
439
|
+
|
|
440
|
+
**续跑中断的需求**(1.12 起):每次检查时,发现本机有流水线标着"运行中"但后台进程已经不在(机器重启、进程被杀),请它的对话检查并继续,每个进程只提醒一次。
|
|
441
|
+
|
|
442
|
+
**小改动自动确认**(1.12 起,默认关闭):
|
|
443
|
+
|
|
444
|
+
```json
|
|
445
|
+
"autoConfirm": { "hours": 4 }
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
快速档、没有待回答问题的卡片,AI 区写明"到某时前没有异议就开始";到时需求方没有在回复区写任何内容,接单器按"确认:同意"处理。需求方随时可以写"同意"提前开始,或写修改意见。每次摘要更新都重新计时。
|
|
449
|
+
|
|
414
450
|
## 团队配置
|
|
415
451
|
|
|
416
452
|
几台机器、多个项目共用的设置(统一的模型、评审小组、超时、要求的 req-kit 版本)可以放在一份团队配置里。把它放进一个团队共用的 git 仓库,各台机器克隆下来,在本机配置 `~/.req-kit/config.json` 中写上它的路径(绝对路径,或相对 `~/.req-kit/` 的路径):
|
|
@@ -471,7 +507,7 @@ Paseo 自带的 `/paseo-advisor`、`/paseo-committee` 等编排 skill 也可以
|
|
|
471
507
|
|
|
472
508
|
**结果文件**:每步必须写一个 JSON 结果文件,流水线只看它,不看 agent 的回复文字。文件缺失或格式不对时,会在同一个会话里提醒 agent 补写,最多 2 次,仍不对则判定该步骤失败。
|
|
473
509
|
|
|
474
|
-
**等待与超时**:每步有总时限(`stepTimeoutMinutes
|
|
510
|
+
**等待与超时**:每步有总时限(`stepTimeoutMinutes`)。到时 agent 还在做时,先再给它同样长的时间(`selfHeal`,见下);仍未完成才判定失败,但不停止 agent。继续(`--resume`、自动重试)时先看这一步上一次的 agent:已经做完、结果文件有效就直接用它的结果;还在做就接着等它一轮;第二次仍超时才停止它、另起一个 agent。同一步不会有两个 agent 同时在做(它们会改同样的文件、在同一个工作区提交、写同一个结果文件)。连续读不到 agent 状态时同样处理。等待被拆成每段 10 分钟,连接断开时自动重试。agent 等待权限批准时,同一个请求只通知一次,然后继续等待。
|
|
475
511
|
|
|
476
512
|
**临时错误自动重试**:步骤因限流(429)、服务暂时不可用(502/503/504/529、overloaded)、网络中断等临时错误失败时,等待后用新的 agent 重做这一步,并告诉它工作区里可能留有上一次没做完的改动。默认最多重试 2 次,分别等 1 分钟和 5 分钟:
|
|
477
513
|
|
|
@@ -479,7 +515,25 @@ Paseo 自带的 `/paseo-advisor`、`/paseo-committee` 等编排 skill 也可以
|
|
|
479
515
|
"retry": { "attempts": 2, "backoffSeconds": [60, 300] }
|
|
480
516
|
```
|
|
481
517
|
|
|
482
|
-
`attempts` 设为 0 关闭;`backoffSeconds` 的个数少于重试次数时,后面的重试使用最后一个值。超时、结果文件不合格、agent
|
|
518
|
+
`attempts` 设为 0 关闭;`backoffSeconds` 的个数少于重试次数时,后面的重试使用最后一个值。超时、结果文件不合格、agent 自己判定失败等不是临时错误,不按这里重试。重试用完仍失败时,中断通知会注明已重试的次数。
|
|
519
|
+
|
|
520
|
+
**自愈**(1.12,`selfHeal`,默认 `true`):能安全自动处理的中断不再等人。
|
|
521
|
+
|
|
522
|
+
| 情况 | 自动处理 |
|
|
523
|
+
| --- | --- |
|
|
524
|
+
| 步骤超时,agent 还在做 | 不打断它,再等同样长的时间;仍未完成才中断 |
|
|
525
|
+
| agent 报错(不是临时错误) | 新开一个 agent 重做这一步一次;配置了备用模型时用备用模型 |
|
|
526
|
+
| 提醒后仍没有有效的结果文件 | 新开一个 agent 重做这一步一次 |
|
|
527
|
+
| 限流、额度用完,重试用完 | 配置了备用模型时,用备用模型再做一次;仍不行就中断,并按 Paseo 显示的额度恢复时间预约自动继续(恢复后 5 分钟;没有恢复时间时按 `autoResumeMinutes`) |
|
|
528
|
+
| 方案审核说需要需求方决定 | 先请 builder 从代码、文档和需求说明中找答案(`answerQuestionsFirst`,默认 `true`),只把真正的业务取舍连同推荐答案交给需求方;都能回答时不停下 |
|
|
529
|
+
|
|
530
|
+
**备用模型**(1.12,可选):`agents.fallback`,写法同 builder、reviewer,可以用 `paseoProfile`。`req configure` 和插件的"模型"设置页会推荐一个与主模型不同厂商的模型。
|
|
531
|
+
|
|
532
|
+
```json
|
|
533
|
+
"agents": { "builder": { … }, "reviewer": { … }, "fallback": { "provider": "opencode/<模型 ID>" } }
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
自动处理的每一次都记在进度日志、状态文件的 `recoveries` 和使用记录中,`req stats` 的"人工介入"一节汇总:停下等人的原因、自动处理了多少次、到达提测的需求中没有非必要介入的比例。
|
|
483
537
|
|
|
484
538
|
**同一个目录一次只运行一条流水线**,因为各步骤都在这个目录中修改和提交。需要并行时,为每个需求使用单独的 worktree 工作区。
|
|
485
539
|
|
|
@@ -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,9 @@
|
|
|
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)
|
|
15
|
+
// req api config [--repo <dir>] [--machine] every setting, its value in words and which file set it (1.12)
|
|
13
16
|
//
|
|
14
17
|
// Reading never changes anything. `run` performs the same command as `req <command>` in that
|
|
15
18
|
// directory, so the engine stays the only one that changes a requirement's state.
|
|
@@ -19,7 +22,8 @@ import fs from "node:fs";
|
|
|
19
22
|
import path from "node:path";
|
|
20
23
|
import { resultText, stepName } from "./control.js";
|
|
21
24
|
import { FEATURES_DIR, findWorktreeRun, listFeatures, resolveFeatureArg, summarize } from "./features.js";
|
|
22
|
-
import { KIT_ROOT, kitVersion } from "./locations.js";
|
|
25
|
+
import { defaultConfigPath, KIT_ROOT, kitVersion } from "./locations.js";
|
|
26
|
+
import { configView } from "./config-view.js";
|
|
23
27
|
import { pruneIndex, readIndex, registerRequirement, requirementKey } from "./records.js";
|
|
24
28
|
import { isAlive, type PipelineState, readState, statePath, type StepRecord } from "./state.js";
|
|
25
29
|
import { parsePeriod, summarizeRuns } from "./stats.js";
|
|
@@ -118,6 +122,18 @@ export interface ListItem {
|
|
|
118
122
|
usage: ReturnType<typeof usageOf>;
|
|
119
123
|
/** False when the index still lists a requirement whose directory is gone. */
|
|
120
124
|
present: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* 1.12: for a requirement that needs a person (attention): since when, what it asks, why it
|
|
127
|
+
* stopped, and the card it came from. Null otherwise.
|
|
128
|
+
*/
|
|
129
|
+
waiting: {
|
|
130
|
+
since: string;
|
|
131
|
+
questions: string[];
|
|
132
|
+
reason: string | null;
|
|
133
|
+
card: { system: string; id: string; url: string | null; requester: string | null } | null;
|
|
134
|
+
/** Whether a reply can be sent to the requirement's chat (req reply). */
|
|
135
|
+
canReply: boolean;
|
|
136
|
+
} | null;
|
|
121
137
|
}
|
|
122
138
|
|
|
123
139
|
function item(repo: string, branch: string | null, featureDir: string, extra: { sourceRepo: string | null; workspaceId: string | null; project: string }): ListItem | null {
|
|
@@ -146,6 +162,25 @@ function item(repo: string, branch: string | null, featureDir: string, extra: {
|
|
|
146
162
|
updatedAt: summary.updatedAt,
|
|
147
163
|
usage: usageOf(state),
|
|
148
164
|
present: true,
|
|
165
|
+
waiting: waitingOf(repo, featureDir, state),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function waitingOf(repo: string, featureDir: string, state: PipelineState | null): ListItem["waiting"] {
|
|
170
|
+
if (!state || !attentionOf(state)) return null;
|
|
171
|
+
let card: NonNullable<ListItem["waiting"]>["card"] = null;
|
|
172
|
+
try {
|
|
173
|
+
const s = JSON.parse(fs.readFileSync(path.join(repo, featureDir, "source.json"), "utf8")) as { system?: string; id?: string; url?: string | null; author?: string | null };
|
|
174
|
+
if (s.system && s.id) card = { system: s.system, id: s.id, url: s.url ?? null, requester: s.author ?? null };
|
|
175
|
+
} catch {
|
|
176
|
+
card = null;
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
since: state.updatedAt,
|
|
180
|
+
questions: state.pendingQuestions ?? [],
|
|
181
|
+
reason: state.stopReason,
|
|
182
|
+
card,
|
|
183
|
+
canReply: Boolean(state.originAgentId) && (state.status === "waiting_for_business" || state.status === "waiting_for_approval"),
|
|
149
184
|
};
|
|
150
185
|
}
|
|
151
186
|
|
|
@@ -186,6 +221,7 @@ export function apiList(input: { repo: string; all: boolean }): ListItem[] {
|
|
|
186
221
|
updatedAt: e.registeredAt,
|
|
187
222
|
usage: null,
|
|
188
223
|
present: false,
|
|
224
|
+
waiting: null,
|
|
189
225
|
},
|
|
190
226
|
);
|
|
191
227
|
}
|
|
@@ -291,7 +327,7 @@ export function apiIndex(input: { rebuild: string[]; prune: boolean }) {
|
|
|
291
327
|
return { scanned: [...dirs], registered, pruned, total: readIndex().length };
|
|
292
328
|
}
|
|
293
329
|
|
|
294
|
-
const RUNNABLE = ["approve", "stop", "resume", "verify", "sync"] as const;
|
|
330
|
+
const RUNNABLE = ["approve", "stop", "resume", "verify", "sync", "reply"] as const;
|
|
295
331
|
|
|
296
332
|
/** Runs `req <command> <需求>` where people would, and reports what it said. */
|
|
297
333
|
export function apiRun(command: string, repoArg: string, feature: string, extra: string[]) {
|
|
@@ -313,6 +349,26 @@ export function apiRun(command: string, repoArg: string, feature: string, extra:
|
|
|
313
349
|
return { command, featureDir: feature, ok: r.status === 0, exitCode: r.status, message: r.error ? r.error.message : output };
|
|
314
350
|
}
|
|
315
351
|
|
|
352
|
+
/** Runs the configuration program in one of its machine modes and returns its JSON line. */
|
|
353
|
+
function configureJson(args: string[]): Record<string, unknown> {
|
|
354
|
+
const r = spawnSync(process.execPath, [path.join(KIT_ROOT, "bin", "req.mjs"), "configure", ...args], {
|
|
355
|
+
encoding: "utf8",
|
|
356
|
+
timeout: 120_000,
|
|
357
|
+
windowsHide: true,
|
|
358
|
+
env: process.env,
|
|
359
|
+
});
|
|
360
|
+
const line = (r.stdout ?? "").trim().split("\n").pop() ?? "";
|
|
361
|
+
let parsed: Record<string, unknown> | null = null;
|
|
362
|
+
try {
|
|
363
|
+
parsed = JSON.parse(line) as Record<string, unknown>;
|
|
364
|
+
} catch {
|
|
365
|
+
parsed = null;
|
|
366
|
+
}
|
|
367
|
+
if (!parsed) throw new ApiError("failed", `读取 agent 设置失败:${(r.stderr || r.error?.message || "没有输出").trim().split("\n").slice(-2).join(" ")}`);
|
|
368
|
+
if (parsed.ok === false) throw new ApiError("failed", String(parsed.message ?? "失败"));
|
|
369
|
+
return parsed;
|
|
370
|
+
}
|
|
371
|
+
|
|
316
372
|
// ---------------------------------------------------------------------------
|
|
317
373
|
// CLI
|
|
318
374
|
|
|
@@ -376,8 +432,23 @@ export function apiMain(argvIn: string[], cwd = process.cwd()): unknown {
|
|
|
376
432
|
const what = need("命令(approve、stop、resume、verify)");
|
|
377
433
|
return apiRun(what, repo, need("需求"), argv.splice(0));
|
|
378
434
|
}
|
|
435
|
+
case "config": {
|
|
436
|
+
// --machine: this machine's settings without any project's.
|
|
437
|
+
const machineOnly = flag(argv, "--machine");
|
|
438
|
+
return configView(defaultConfigPath(), machineOnly ? null : fs.realpathSync(repo));
|
|
439
|
+
}
|
|
440
|
+
case "models": {
|
|
441
|
+
const { current, choices, recommended } = configureJson(["--catalog-json"]);
|
|
442
|
+
return { current, choices, recommended };
|
|
443
|
+
}
|
|
444
|
+
case "set-agents": {
|
|
445
|
+
const value = option(argv, "--json");
|
|
446
|
+
if (!value) throw new ApiError("bad_arguments", "缺少 --json <设置>");
|
|
447
|
+
const { message } = configureJson(["--set", value]);
|
|
448
|
+
return { ok: true, message };
|
|
449
|
+
}
|
|
379
450
|
default:
|
|
380
|
-
throw new ApiError("bad_arguments", `没有 "${command ?? ""}" 这个 api 命令。可用:version、list、status、file、stats、index、run`);
|
|
451
|
+
throw new ApiError("bad_arguments", `没有 "${command ?? ""}" 这个 api 命令。可用:version、list、status、file、stats、index、run、models、set-agents、config`);
|
|
381
452
|
}
|
|
382
453
|
}
|
|
383
454
|
|
package/engine/src/card-cmd.ts
CHANGED
|
@@ -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;
|
|
@@ -139,6 +140,10 @@ export interface CardCmdOptions {
|
|
|
139
140
|
env?: NodeJS.ProcessEnv;
|
|
140
141
|
run?: Runner;
|
|
141
142
|
out?: (line: string) => void;
|
|
143
|
+
/** Tests of the intake process without a daemon to check (1.12). */
|
|
144
|
+
skipHealthCheck?: boolean;
|
|
145
|
+
/** The time, for tests (1.12). */
|
|
146
|
+
now?: Date;
|
|
142
147
|
}
|
|
143
148
|
|
|
144
149
|
export interface BeginResult {
|
|
@@ -287,6 +292,7 @@ async function publish(argv: string[], opts: CardCmdOptions): Promise<number> {
|
|
|
287
292
|
const state = fs.existsSync(statePath(target.repo, target.entry.featureDir)) ? readState(statePath(target.repo, target.entry.featureDir)) : null;
|
|
288
293
|
const waitingPlan = state?.status === "waiting_for_approval";
|
|
289
294
|
const result = await publishCard({
|
|
295
|
+
autoConfirmHours: target.pipelineConfig.autoConfirm.hours,
|
|
290
296
|
config: target.config,
|
|
291
297
|
repo: target.repo,
|
|
292
298
|
featureDir: target.entry.featureDir,
|
|
@@ -316,6 +322,12 @@ export async function checkOne(target: Located, opts: CardCmdOptions, notify: (a
|
|
|
316
322
|
const key = replyKey(reply);
|
|
317
323
|
let confirmed = false;
|
|
318
324
|
let planConfirmed = false;
|
|
325
|
+
// 1.12: a bare "好的" written anywhere in the reply section answers what is being waited for.
|
|
326
|
+
if (!reply.confirmText && !reply.planConfirmText && reply.notes && isYes(reply.notes) && Object.keys(reply.answers).length === 0) {
|
|
327
|
+
const state = fs.existsSync(statePath(target.repo, target.entry.featureDir)) ? readState(statePath(target.repo, target.entry.featureDir)) : null;
|
|
328
|
+
if (state?.status === "waiting_for_approval") reply.planConfirmed = true;
|
|
329
|
+
else if (!source.confirmedAt) reply.confirmed = true;
|
|
330
|
+
}
|
|
319
331
|
if (parts.reply !== null && source.replyHash !== null && key !== source.replyHash) {
|
|
320
332
|
const answered = Object.keys(reply.answers);
|
|
321
333
|
if (answered.length) changes.push(`需求方回答了 ${answered.join("、")}`);
|
|
@@ -330,6 +342,14 @@ export async function checkOne(target: Located, opts: CardCmdOptions, notify: (a
|
|
|
330
342
|
} else if (reply.planConfirmText && !reply.planConfirmed) changes.push(`需求方在“方案确认”一行写了:${reply.planConfirmText}`);
|
|
331
343
|
if (changes.length === 0) changes.push("需求方修改了回复区");
|
|
332
344
|
}
|
|
345
|
+
// 1.12 (autoConfirm.hours): nobody objected in time to a small requirement with nothing to answer.
|
|
346
|
+
const replied = Object.keys(reply.answers).length > 0 || Boolean(reply.notes) || Boolean(reply.confirmText) || Boolean(reply.planConfirmText);
|
|
347
|
+
const now = opts.now ?? new Date();
|
|
348
|
+
if (!confirmed && !source.confirmedAt && source.autoConfirmAt && now.getTime() >= new Date(source.autoConfirmAt).getTime() && !replied && changes.length === 0) {
|
|
349
|
+
confirmed = true;
|
|
350
|
+
source.autoConfirmed = true;
|
|
351
|
+
changes.push(`到 ${new Date(source.autoConfirmAt).toLocaleString("zh-CN", { hour12: false })} 需求方没有提出异议,按自动确认处理(autoConfirm):这就是"确认:同意",可以启动开发`);
|
|
352
|
+
}
|
|
333
353
|
if (changes.length === 0) return `${source.id}:没有新内容`;
|
|
334
354
|
if (!source.sessionAgentId) return `${source.id}:有新内容,但没有需求会话(source.json 中没有 sessionAgentId)`;
|
|
335
355
|
await notify(source.sessionAgentId, relayMessage({ id: source.id, changes, replyText: parts.reply }));
|