paseo-req-kit 1.14.0-beta.3 → 1.14.0-beta.5

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 (133) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/bin/req.mjs +14 -0
  3. package/contracts/cli-json.md +1 -1
  4. package/contracts/notifier-event.md +1 -1
  5. package/contracts/spec.md +1 -0
  6. package/contracts/state.md +3 -1
  7. package/contracts/usage-record.md +16 -2
  8. package/docs/guide//344/275/277/347/224/250/350/200/205/346/214/207/345/215/227.md +11 -1
  9. 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 +17 -1
  10. package/engine/prompts/00-shared-context.md +3 -2
  11. package/engine/prompts/06-code-review.md +12 -1
  12. package/engine/prompts/incremental.md +2 -0
  13. package/engine/src/activity.ts +21 -0
  14. package/engine/src/api.ts +44 -7
  15. package/engine/src/cli-args.ts +4 -1
  16. package/engine/src/code-review.ts +422 -0
  17. package/engine/src/codeowners.ts +87 -0
  18. package/engine/src/config-view.ts +10 -0
  19. package/engine/src/config.ts +27 -0
  20. package/engine/src/danger-guard.ts +180 -0
  21. package/engine/src/features.ts +1 -1
  22. package/engine/src/main.ts +13 -3
  23. package/engine/src/messages.ts +29 -0
  24. package/engine/src/outcomes.ts +145 -0
  25. package/engine/src/paseo-runtime.ts +9 -1
  26. package/engine/src/pipeline.ts +135 -6
  27. package/engine/src/preflight.ts +10 -1
  28. package/engine/src/records.ts +71 -0
  29. package/engine/src/reply.ts +30 -11
  30. package/engine/src/review-assets/code.css +93 -0
  31. package/engine/src/review-assets/code.html +43 -0
  32. package/engine/src/review-assets/code.js +402 -0
  33. package/engine/src/review.ts +98 -2
  34. package/engine/src/runtime.ts +2 -0
  35. package/engine/src/secret-scan.ts +177 -0
  36. package/engine/src/state.ts +30 -0
  37. package/engine/src/stats.ts +143 -9
  38. package/engine/src/watchdog.ts +38 -4
  39. package/package.json +1 -1
  40. package/paseo-plugin/client/data.ts +2 -2
  41. package/paseo-plugin/client/detail.tsx +7 -2
  42. package/paseo-plugin/client/nav.ts +5 -0
  43. package/paseo-plugin/client/review.tsx +16 -6
  44. package/paseo-plugin/client/stats.tsx +15 -0
  45. package/paseo-plugin/package.json +1 -1
  46. package/paseo-plugin/server/handlers.ts +1 -1
  47. package/paseo-plugin/shared/contracts.ts +11 -2
  48. package/site-dist/404.html +2 -2
  49. package/site-dist/assets/{app.BpC99f5a.js → app.TlOHPtfE.js} +1 -1
  50. package/site-dist/assets/changelog.md.DiGi8kHU.js +1 -0
  51. package/site-dist/assets/{changelog.md.m4cSU5e4.lean.js → changelog.md.DiGi8kHU.lean.js} +1 -1
  52. package/site-dist/assets/chunks/@localSearchIndexroot.Dk_-Yka5.js +1 -0
  53. package/site-dist/assets/chunks/{VPLocalSearchBox.CI43rhTA.js → VPLocalSearchBox.BUVNy7_o.js} +1 -1
  54. package/site-dist/assets/chunks/{theme.S6pNEF23.js → theme.BFeiG1US.js} +2 -2
  55. package/site-dist/assets/{guide_getting-started.md.a7iNP1Ky.js → guide_getting-started.md.UhkLCUoY.js} +1 -1
  56. package/site-dist/assets/{guide_getting-started.md.a7iNP1Ky.lean.js → guide_getting-started.md.UhkLCUoY.lean.js} +1 -1
  57. package/site-dist/assets/{guide_project.md.Csi5wiyt.js → guide_project.md.DIjDRoAq.js} +1 -1
  58. package/site-dist/assets/{guide_project.md.Csi5wiyt.lean.js → guide_project.md.DIjDRoAq.lean.js} +1 -1
  59. package/site-dist/assets/{guide_user.md.D9GpGJ7y.js → guide_user.md.UV0WxdJa.js} +1 -1
  60. package/site-dist/assets/{guide_user.md.D9GpGJ7y.lean.js → guide_user.md.UV0WxdJa.lean.js} +1 -1
  61. package/site-dist/assets/{reference_contracts_cli-json.md.BV1HtTmr.js → reference_contracts_cli-json.md.CCvKLasM.js} +1 -1
  62. package/site-dist/assets/{reference_contracts_cli-json.md.BV1HtTmr.lean.js → reference_contracts_cli-json.md.CCvKLasM.lean.js} +1 -1
  63. package/site-dist/assets/{reference_contracts_notifier-event.md.CCfrkm_A.js → reference_contracts_notifier-event.md.B9WqeZdO.js} +1 -1
  64. package/site-dist/assets/{reference_contracts_notifier-event.md.CCfrkm_A.lean.js → reference_contracts_notifier-event.md.B9WqeZdO.lean.js} +1 -1
  65. package/site-dist/assets/{reference_contracts_spec.md.BwwS0L_j.js → reference_contracts_spec.md.Dh5XczZL.js} +2 -2
  66. package/site-dist/assets/{reference_contracts_spec.md.BwwS0L_j.lean.js → reference_contracts_spec.md.Dh5XczZL.lean.js} +2 -2
  67. package/site-dist/assets/reference_contracts_state.md.STlqkPPS.js +1 -0
  68. package/site-dist/assets/{reference_contracts_usage-record.md.D5krNLW7.lean.js → reference_contracts_usage-record.md.MjVps4CA.js} +2 -2
  69. package/site-dist/assets/{reference_contracts_usage-record.md.D5krNLW7.js → reference_contracts_usage-record.md.MjVps4CA.lean.js} +2 -2
  70. package/site-dist/changelog.html +5 -5
  71. package/site-dist/guide/getting-started.html +5 -5
  72. package/site-dist/guide/how-it-works.html +4 -4
  73. package/site-dist/guide/index.html +4 -4
  74. package/site-dist/guide/install.html +4 -4
  75. package/site-dist/guide/maintainer.html +4 -4
  76. package/site-dist/guide/phases.html +4 -4
  77. package/site-dist/guide/project.html +6 -6
  78. package/site-dist/guide/user.html +5 -5
  79. package/site-dist/hashmap.json +1 -1
  80. package/site-dist/index.html +4 -4
  81. package/site-dist/reference/adr/0001-step-agents-under-origin-chat.html +4 -4
  82. package/site-dist/reference/adr/0002-no-directory-switch-in-chat.html +4 -4
  83. package/site-dist/reference/adr/0003-script-engine-not-hub.html +4 -4
  84. package/site-dist/reference/adr/0004-result-files.html +4 -4
  85. package/site-dist/reference/adr/0005-no-default-branch-local-commits-only.html +4 -4
  86. package/site-dist/reference/adr/0006-skill-install-targets.html +4 -4
  87. package/site-dist/reference/adr/0007-own-interview-skill.html +4 -4
  88. package/site-dist/reference/adr/0008-entries-do-not-call-entries.html +4 -4
  89. package/site-dist/reference/adr/0009-versioned-install-layout.html +4 -4
  90. package/site-dist/reference/adr/0010-generated-feature-overview.html +4 -4
  91. package/site-dist/reference/adr/0011-resume-after-completion.html +4 -4
  92. package/site-dist/reference/adr/0012-review-panel.html +4 -4
  93. package/site-dist/reference/adr/0013-no-release-tooling.html +4 -4
  94. package/site-dist/reference/adr/0014-notifier-slot.html +4 -4
  95. package/site-dist/reference/adr/0015-incremental-continue.html +4 -4
  96. package/site-dist/reference/adr/0016-upgrade-from-tags.html +4 -4
  97. package/site-dist/reference/adr/0017-use-paseo-capabilities.html +4 -4
  98. package/site-dist/reference/adr/0018-own-worktree-option.html +4 -4
  99. package/site-dist/reference/adr/0019-parallel-orchestration.html +4 -4
  100. package/site-dist/reference/adr/0020-scheduling-through-the-chat.html +4 -4
  101. package/site-dist/reference/adr/0021-npm-version-manager.html +4 -4
  102. package/site-dist/reference/adr/0022-develop-and-verify-phases.html +4 -4
  103. package/site-dist/reference/adr/0023-types-gate-slices.html +4 -4
  104. package/site-dist/reference/adr/0024-confirm-early-review-axes.html +4 -4
  105. package/site-dist/reference/adr/0025-joint-requirements.html +4 -4
  106. package/site-dist/reference/adr/0026-machine-records-and-api.html +4 -4
  107. package/site-dist/reference/adr/0027-paseo-plugin.html +4 -4
  108. package/site-dist/reference/adr/0028-external-integrations.html +4 -4
  109. package/site-dist/reference/adr/0029-requirements-from-cards.html +4 -4
  110. package/site-dist/reference/adr/0030-card-intake-process.html +4 -4
  111. package/site-dist/reference/adr/0031-fewer-interventions.html +4 -4
  112. package/site-dist/reference/adr/0032-npm-only-install.html +4 -4
  113. package/site-dist/reference/adr/0033-prototype-review-and-ui-check.html +4 -4
  114. package/site-dist/reference/adr/index.html +4 -4
  115. package/site-dist/reference/commands.html +4 -4
  116. package/site-dist/reference/config.html +4 -4
  117. package/site-dist/reference/contracts/cli-json.html +5 -5
  118. package/site-dist/reference/contracts/feature-files.html +4 -4
  119. package/site-dist/reference/contracts/index.html +4 -4
  120. package/site-dist/reference/contracts/notifier-event.html +6 -6
  121. package/site-dist/reference/contracts/result-file.html +4 -4
  122. package/site-dist/reference/contracts/spec.html +6 -6
  123. package/site-dist/reference/contracts/state.html +5 -5
  124. package/site-dist/reference/contracts/usage-record.html +6 -6
  125. package/site-dist/reference/contracts/verify.html +4 -4
  126. package/site-dist/reference/scope.html +4 -4
  127. package/skills/req/SKILL.md +2 -0
  128. package/skills/req/playbooks/incident.md +1 -1
  129. package/skills/spec-writing/SKILL.md +2 -1
  130. package/site-dist/assets/changelog.md.m4cSU5e4.js +0 -1
  131. package/site-dist/assets/chunks/@localSearchIndexroot.D6ywcTJ5.js +0 -1
  132. package/site-dist/assets/reference_contracts_state.md.EEBd_l2P.js +0 -1
  133. /package/site-dist/assets/{reference_contracts_state.md.EEBd_l2P.lean.js → reference_contracts_state.md.STlqkPPS.lean.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,38 @@
2
2
 
3
3
  每个版本分为"使用者会看到的变化"和"升级须知"。升级步骤见 [docs/guide/安装与升级.md](docs/guide/安装与升级.md)。
4
4
 
5
+ ## 1.14.0-beta.5
6
+
7
+ ### 使用者会看到的变化
8
+
9
+ - **拦截危险命令**(`dangerGuard`,默认开):步骤 agent 推送代码、发布制品、改动集群或基础设施、连接其他机器上的数据库或主机、删除仓库以外的目录、下载脚本直接执行,或者命令里出现配置的生产环境地址时,流水线打断它(还在运行时)、告诉它把需要人做的写进产物,并通知需求的对话;每次都记下来,写进完成报告。按命令文字检查,不用模型;能拦住常见的情况,不是沙箱。
10
+ - **密钥扫描**(`secretScan`,默认开):每个改代码的步骤之后,检查本需求新增的内容(每次提交加的行、未提交的改动、新文件)有没有密钥、令牌、口令;发现了交回这一步的 agent 去掉(包括从还没推送的提交里去掉),仍去不掉的通知需求的对话、写进代码评审和完成报告;配置了推送的需求不会推送有疑似密钥的分支。
11
+ - **记录合并之后的结果**:`req stats` 和插件的使用统计新增"人工把关和合并后":人工代码评审几次、退回几次,验收几次、不通过几次,第一次就通过的需求数,合并进主干的需求和"完成到合并"的时间,合并后发现的问题。合并是从本机仓库已有的分支判断的(需求的流水线文件进了主干);缺陷需求写了 `- 引入问题的需求:<目录名>` 的,算作那个需求合并后的问题;也可以 `req outcome <需求> --issue "<问题>"` 手动记一笔。
12
+ - **代码审查和验收的结论进使用记录**:每次在代码审查页、验收页提交,结论、评审人和意见条数记入本机使用记录(全文仍在随分支提交的 `code-review.md`、`acceptance.md` 里);`req stats --export` 一起导出。
13
+ - **确认了却没开始开发的需求,在谈需求的对话里提醒一次**(插件的后台检查,确认 30 分钟后;只提醒 3 天内确认的)。
14
+ - **修复:同一个需求在主目录和它自己的工作区里各有一份时,插件列出两次。** 现在只列一次,优先列在运行的、有运行状态的那一份。
15
+
16
+ ### 升级须知
17
+
18
+ - 新配置都可选:`dangerGuard`(默认 `{ "enabled": true, "production": [], "allow": [] }`,建议团队配置里把生产环境的主机名、地址写进 `production`)、`secretScan`(默认 `{ "enabled": true, "allow": [] }`)。打开 `dangerGuard` 时流水线每 15 秒读一次步骤 agent 的命令记录,不占模型 token。
19
+ - 使用记录 `usage/runs.jsonl` 新增 `review`、`acceptance`、`merge`、`issue` 几种记录(见 `contracts/usage-record.md`),旧版本读取时会跳过;`req stats --json` / `req api stats` 新增 `oversight`,每个需求新增 `afterRun`,CSV 新增 7 列。状态文件新增 `dangers`、`secretScans`;外部通知 `alert` 事件新增 `details.kind: "danger"`。
20
+ - 新命令 `req outcome`;`req reply` 新增 `--notice`(后台检查用)。
21
+ - 提示词里"不改写历史"加了一个例外:流水线要求从本需求还没推送的提交里去掉密钥时。
22
+
23
+ ## 1.14.0-beta.4
24
+
25
+ ### 使用者会看到的变化
26
+
27
+ - **代码审查**(插件需求页面上的"代码审查",`req code-review <需求>`):列出需求分支相对起点的全部改动(需求文档放最后、折叠),AI 代码评审的意见放在对应的代码行旁边,带级别和修复步骤的处理结果;评审人可以在任意一行写意见、把 AI 意见标为"要改 / 不用改"、写总体意见。
28
+ - **审核人默认取自 `CODEOWNERS`**(支持 GitLab / GitHub 格式和 GitLab 的分节),按改动的文件给出,提交前可以修改,以后建合并请求时使用。
29
+ - **退回修改**:意见写进 `code-review.md` 的"人工评审"一节(HR-1 起)和 `changes.md` 并提交,流水线开始新的一次尝试(`req review-fix`),需求说明不变、方案确认不用重来,从方案设计开始只处理这些意见,之后照常经过 AI 代码评审和验证。**评审通过**:记下评审人、审核人和评审的提交;之后又有新提交时提示需要重新评审。
30
+ - AI 代码评审的结果文件新增 `findings`(每条意见的级别、文件、行号、问题、修复方向);没有它的旧需求从 `code-review.md` 的 CR 条目中读取。
31
+
32
+ ### 升级须知
33
+
34
+ - 合并请求还没有接入:等 shenbing 提供类似 `gh pr create / gh pr merge` 的命令后,用评审通过时记下的审核人建合并请求。
35
+ - `req api status` 新增 `humanReview`;`req api review-url` 的 `--page` 新增 `code`;启动参数新增 `--review-fix`(`req review-fix <需求>`)。
36
+
5
37
  ## 1.14.0-beta.3
6
38
 
7
39
  ### 使用者会看到的变化
package/bin/req.mjs CHANGED
@@ -31,6 +31,8 @@ const HELP = `req-kit ${version}(${kitRoot})
31
31
  让 Paseo 按时间表发送需求总览(例子为每周一 9:00);取消:req digest --cancel <ID>
32
32
  req sync <需求> 把需求同步到对接的外部系统(补发失败的调用、补建卡片、更新状态)
33
33
  req review <需求> 在浏览器打开原型审查页:点选要改的地方、直接改文字,提交意见或确认方案
34
+ req code-review <需求> 代码审查页:看这次的改动和 AI 评审意见,在代码行上写意见,退回修改或评审通过
35
+ req review-fix <需求> 按代码审查页上退回的意见开始修改(插件里提交"退回修改"时自动执行)
34
36
  req try <需求> [--stop] 把做好的需求跑起来试用:给出访问地址和测试数据(--stop 停止)
35
37
  req checklist <需求> 验收清单:按验收标准逐条勾选,提交后写成验收记录
36
38
  req ui-check <网址> [--prototype <原型文件>] [--width 1280,390]
@@ -50,6 +52,9 @@ const HELP = `req-kit ${version}(${kitRoot})
50
52
  req stats --report [--out 目录]
51
53
  生成汇报用的汇总(.md)和每个需求一行的表格(.csv)
52
54
  req stats --export [文件] 导出本机的运行记录;汇总多台机器:req stats --merge <文件…> --report
55
+ req outcome 查找已合并进主干的需求和合并后发现的问题,记入使用记录(req stats 也会自动查)
56
+ req outcome <需求> --merged | --issue "<问题>"
57
+ 手动记一笔:已合并,或合并后发现的问题
53
58
  req index [--rebuild [目录…]] [--prune]
54
59
  本机记录的需求(Paseo 插件据此找到所有需求);1.9 之前运行过的用 --rebuild 补登
55
60
  req api <命令> … 给程序用的 JSON 输出(见 contracts/cli-json.md)
@@ -168,6 +173,12 @@ switch (command) {
168
173
  case "approve":
169
174
  code = node([launcher, "--approve", ...feature(rest, true)]);
170
175
  break;
176
+ case "review-fix":
177
+ code = node([launcher, "--review-fix", ...feature(rest, true)]);
178
+ break;
179
+ case "code-review":
180
+ code = needTsx() ? node([tsxCli, path.join(kitRoot, "engine", "src", "review.ts"), "--page", "code", ...rest]) : 1;
181
+ break;
171
182
  case "start":
172
183
  // From a card in the requirement management system (1.10): no chat needed to begin.
173
184
  if (rest.some((a) => a === "--from" || a.startsWith("--from="))) code = needTsx() ? node([tsxCli, path.join(kitRoot, "engine", "src", "card-cmd.ts"), "start", ...rest]) : 1;
@@ -229,6 +240,9 @@ switch (command) {
229
240
  case "checklist":
230
241
  code = needTsx() ? node([tsxCli, path.join(kitRoot, "engine", "src", "checklist-cmd.ts"), ...rest]) : 1;
231
242
  break;
243
+ case "outcome":
244
+ code = needTsx() ? node([tsxCli, path.join(kitRoot, "engine", "src", "outcomes.ts"), ...rest]) : 1;
245
+ break;
232
246
  case "ui-check":
233
247
  code = needTsx() ? node([tsxCli, path.join(kitRoot, "engine", "src", "ui-check.ts"), ...rest]) : 1;
234
248
  break;
@@ -32,7 +32,7 @@
32
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
33
  | `req api set-agents --json <设置>` | 1.12 起。`{ ok, message }`。`<设置>` 是 `{ builder, reviewer, fallback }`(`fallback` 可以为 null),检查后写入本机配置的 `agents`,原文件备份 |
34
34
  | `req api watchdog [--by plugin\|factory] [--dry-run]` | 1.13 起。`{ checked, resumed, failed, skipped }`:把本机"运行中但后台进程已不在"的需求直接继续(`req resume`)。每个进程只继续一次,每个需求 24 小时内最多 3 次;`--by plugin` 同时记下插件在检查(流水线据此不再预约对话的定时检查) |
35
- | `req api review-url <需求> [--repo <目录>] [--read-only] [--page check]` | 1.13 起;`--page check`(1.14)给出同一服务上的验收清单页地址(需要 `spec.md`)。`{ url, readOnly }`:这个需求的原型审查页地址(需要时在后台启动审查页的服务)。`--read-only` 的地址只能看,不能提交意见或确认方案。需求不在本机的需求索引中或还没有 `prototype/` 时报 `not_found` |
35
+ | `req api review-url <需求> [--repo <目录>] [--read-only] [--page check]` | 1.13 起;`--page check`(1.14)给出同一服务上的验收清单页地址(需要 `spec.md`),`--page code`(1.14)给出代码审查页地址(需要已经写过代码)。`{ url, readOnly }`:这个需求的原型审查页地址(需要时在后台启动审查页的服务)。`--read-only` 的地址只能看,不能提交意见或确认方案。需求不在本机的需求索引中或还没有 `prototype/` 时报 `not_found` |
36
36
 
37
37
  `<需求>` 的写法与 `req status` 相同:功能目录、目录名或名称中能唯一确定的一部分。需求在为它新建的工作区中开发时,`--repo` 写对话所在的目录或那个工作区都可以。
38
38
 
@@ -79,7 +79,7 @@ export default async function notify(event, options) { /* 发送 */ }
79
79
  | `completed` | 流水线结束,报告已生成 | `outcome`(`completed`、`completed_with_issues`、`not_completed`)、`summary`、`reportPath`。"只做研发"时另有 `mode`(`develop-only`)和 `handoffPath`,报告是研发报告,需要人工测试 |
80
80
  | `approval` | 1.8.0 起。方案确认点(`designApproval`):方案已通过审核,等待需求方确认"做完后会看到什么",还没有开始写代码 | `previewPath`(要确认的内容)、`approveCommand`(确认后继续的命令)、`resumeCommand`(修改需求后继续的命令),都在发起的对话中执行 |
81
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` |
82
+ | `alert` | 1.12 起。接单器(`req factory`)自检没有通过(Paseo 连不上、agent 没有登录、额度用完、需求管理系统的命令不可用),暂停接新卡片;或者恢复正常。同样的问题只发一次。`feature.title` 为"接单器",`feature.dir`、`repo` 为空 | `source`(`factory`)、`machine`、`problems`(字符串数组,恢复时为空)、`recovered`。1.14 起,步骤 agent 运行或开始运行危险命令(`dangerGuard`)时也发:`details.kind` 为 `danger`,另有 `stepTitle`、`command`、`label`、`ran`;这时 `feature`、`repo` 是那个需求的 |
83
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` |
84
84
 
85
85
  ## 兼容性
package/contracts/spec.md CHANGED
@@ -22,6 +22,7 @@
22
22
  | `- 执行方式:` | 引擎 | "连续"、"分阶段"或"只做研发"(也接受 `continuous` / `gated` / `develop-only`)。可以没有;没有时用配置中的 `phases.mode`,启动参数 `--phases` 优先(1.7 起) |
23
23
  | `- 类型:` | 引擎(1.8 起)、`/req` | 新需求 / 小改动 / 缺陷 / 重构 / 评估(也接受 `defect`、`bug`、`refactor`、`assessment`;"技术改进"同重构,"调研"同评估)。缺陷、重构、评估按各自的做法运行(`engine/prompts/types/`),配置 `requirementTypes` 可以关掉某个类型;其他写法按新需求处理 |
24
24
  | `- 联合开发:` | 引擎(1.8 起) | 这个需求还要改的其他仓库,写项目配置 `relatedRepos` 中的名称,最多两个,用顿号分开。可以没有。见 ADR 0025 |
25
+ | `- 引入问题的需求:` | `req stats`、`req outcome`(1.14 起) | 缺陷需求才写:引入这个问题的需求的目录名(或功能目录、标题)。记为那个需求"合并后发现的问题"。可以没有 |
25
26
  | `- 版本:` | `/req` | 从 1 开始,每次修改需求加 1 |
26
27
  | `- 格式:` | `/req`、引擎(将来) | `req-kit/spec@<n>`。没有这一行的旧 spec 按 `req-kit/spec@1` 处理 |
27
28
 
@@ -22,6 +22,8 @@
22
22
  | `budget` | object | 可选,1.13 起,配置了 `budget.tokens` 时。`{ allowance, warnedAt }`:停下前允许用到的 tokens(预算,每次用完后继续再加一份);已经提醒过的那一档 |
23
23
  | `waitingPermission` | object \| null | 可选,1.13 起。步骤 agent 在等权限批准时:`{ agentId, stepTitle, requests, since }`;没有在等时为 null |
24
24
  | `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 自答 / `stop_scan` 打断了搜索大目录的命令,1.13 起)、`detail` |
25
+ | `dangers` | array | 可选,1.14 起。步骤 agent 运行(或开始运行)的危险命令(`dangerGuard`,最多保留 50 条):`at`、`key`(步骤键)、`stepTitle`、`command`、`label`(如"推送到远程仓库")、`ran`(发现时已经运行完为 true,正在运行、已打断为 false)、`agentId`、`callId`(同一个调用只处理一次) |
26
+ | `secretScans` | array | 可选,1.14 起。改代码的步骤之后的密钥扫描(`secretScan`),每个步骤、每次尝试保留最新一次:`key`、`attempt`、`at`、`findings`(`{ file, line, commit, kind, preview }`,`preview` 里的值已遮住)、`fixRounds`、`passed` |
25
27
  | `profile` | `quick` \| `standard` | 档位 |
26
28
  | `branch` | string \| null | 工作分支,启动时确定 |
27
29
  | `milestones` | string[] | 已发送的进展通知,续跑时不重复发送 |
@@ -39,7 +41,7 @@
39
41
  | `joint` | array \| null | 可选,1.8.0 起。联合需求(spec 的 `- 联合开发:`)中其他仓库的工作区:`name`(`relatedRepos` 中的名称)、`slug`(步骤键的后缀,如 `implement@backend`)、`sourceRepo`(切出工作区的那份检出)、`repo`(工作区目录)、`workspaceId`、`branch`、`specHash`(最近一次同步过去的 spec),以及这个仓库自己的 `codeBase` 和 `gate`(格式同顶层)。见 ADR 0025 |
40
42
  | `approval` | object \| null | 可选,1.8.0 起。方案确认点:`attempt`(哪一次尝试的方案)、`status`(`waiting` / `approved`)、`at`。需求变更后的新尝试需要重新确认 |
41
43
  | `external` | object \| null | 可选,1.9.0 起。外部系统中的卡片,按集成名称:`id`(卡片编号)、`url`、`status`(最近设置的状态)、`syncedAt`、`pending`(没有成功、等待补发的调用:`action` 为 `create` / `status`、`event`、`status`、`at`、`attempts`、`error`)。见 ADR 0028 |
42
- | `pushed` | array \| null | 可选,1.10 起。从卡片开始、配置了 `push` 的需求推送过的分支,每个仓库一条(主仓库在前):`repo`、`remote`、`branch`、`at`、`error`(成功时为 null)。见 ADR 0029 |
44
+ | `pushed` | array \| null | 可选,1.10 起。从卡片开始、配置了 `push` 的需求推送过的分支,每个仓库一条(主仓库在前):`repo`、`remote`、`branch`、`at`、`error`(成功时为 null;1.14 起,发现疑似密钥时不推送,`error` 写明原因)。见 ADR 0029 |
43
45
  | `gate` | object \| null | 可选,1.8.0 起。质量门:`baseline`(`attempt`、`commit`、`at`、`results`:每项检查改动前 `pass` / `fail`),`runs`(每次运行:`key` 步骤键、`attempt`、`at`、`checks`(`name`、`result`:`pass` / `fail` / `skipped`、`failedBefore`)、`newFailures`、`fixRounds`、`passed`)。各次运行的完整输出在 `.pipeline/checks/<步骤键>/` |
44
46
 
45
47
  `schedules[].kind` 在 1.7.0 起还可能是 `verify-start`(`--verify-at` 预约的测试验证开始),1.8.0 起还可能是 `approval-continue`(方案确认超时后按方案继续,`designApproval.autoContinueHours`)。`history` 中每条步骤记录在 1.7.0 起有可选的 `phase`(`develop` / `verify`),用于按阶段统计用时与用量;没有时按研发阶段统计。
@@ -42,6 +42,19 @@
42
42
 
43
43
  不记录需求内容、代码和对话。
44
44
 
45
+ ### 运行之后的事(1.14 起)
46
+
47
+ 同一个文件里还有几种记录,都有 `schema`(1)、`kind`、`machine`、`kitVersion`、`at` 和 `requirement`(`{ key, project, name, title }`,`key` 与运行记录相同)。读运行记录的旧版本会跳过它们。
48
+
49
+ | `kind` | 何时写 | 字段 |
50
+ | --- | --- | --- |
51
+ | `review` | 插件代码审查页提交"退回修改"或"评审通过" | `review: { verdict: "approved" \| "changes_requested", reviewer, items, head }`:评审人、意见条数、评审的提交 |
52
+ | `acceptance` | 验收清单页提交 | `acceptance: { verdict: "通过" \| "不通过", reviewer, total, passed, failed, unchecked }` |
53
+ | `merge` | `req stats`、`req api stats`、`req outcome` 发现需求的流水线文件(`implementation.md`、`report.md` 等)已经在主干(`origin/HEAD`,没有时 `origin/main`、`main` 等)里;或者 `req outcome <需求> --merged` | `merge: { into, commit, mergedAt, how: "files" \| "manual" }`:`commit`、`mergedAt` 是把它带进主干的那次提交(合并提交或压缩合并的提交);只看本机已有的分支,不 fetch |
54
+ | `issue` | 缺陷需求的 spec 写了 `- 引入问题的需求:<目录名>`(扫描时发现),或者 `req outcome <需求> --issue "<问题>"` | `issue: { text, by, id }`:`by` 是那个缺陷需求的目录名,手动记的为 null;同一个 `id` 只算一次 |
55
+
56
+ 每个需求的 `merge` 只记一次。评审和验收的全文在需求目录的 `code-review.md`、`acceptance.md` 里,随需求分支提交;这里只记结论和数量,用于统计。
57
+
45
58
  ## 统计口径(`req stats`)
46
59
 
47
60
  - 一段时间的用量、步骤用时:结束时间在这段时间内的运行。
@@ -52,6 +65,7 @@
52
65
  - 停下等人的原因(1.12 起):这段时间结束的运行按 `stopKind` 计数;更早的记录按结束时的状态推断。确认方案、研发完成等待测试是必要的决定。
53
66
  - 无人工到提测的比例(1.12 起):这段时间有进展、已经研发完成或完成的需求中,所有运行都只停在必要决定处的比例。
54
67
  - 自动处理的比例(1.12 起):自动处理的次数 ÷(自动处理的次数 + 以中断结束的运行数);需求方决定、缺陷复现不了不算中断。
55
- - 合并多台机器的导出时,同一机器、同一需求、同一开始时间的运行只算一次。
68
+ - 人工把关和合并后(1.14 起,`oversight`):这段时间里的人工代码评审次数、退回修改次数、意见条数,验收次数和不通过次数;"第一次就通过"看每个需求最早的那次评审(验收),它在这段时间里并且是通过;合并进主干的需求数和"完成到合并"的小时数中位数;合并后发现的问题数和涉及的需求数。每个需求一项里的 `afterRun` 汇总它全部的这类记录(`humanReviews`、`sentBack`、`lastReview`、`acceptance`、`acceptanceFailed`、`mergedAt`、`issues`)。
69
+ - 合并多台机器的导出时,同一机器、同一需求、同一开始时间的运行只算一次;同一个需求、同一种、同一时间的事件(问题按 `id`)只算一次。`req stats --export` 同时导出这些需求的事件。
56
70
 
57
- `req stats --json` 和 `req api stats` 输出同样的对象(`schema: 1`):`period`、`machines`、`active`、`completed`、`byType`、`cycleHours`、`steps`、`rework`、`waits`、`usage`、`byBuilder`、`requirements`(每个需求一项,字段与 `req stats --csv` 的列对应)。
71
+ `req stats --json` 和 `req api stats` 输出同样的对象(`schema: 1`):`period`、`machines`、`active`、`completed`、`byType`、`cycleHours`、`steps`、`rework`、`waits`、`usage`、`tokens`、`oversight`、`byBuilder`、`requirements`(每个需求一项,字段与 `req stats --csv` 的列对应)。
@@ -97,6 +97,14 @@ req-kit 帮你把一个需求从"我想要……"做到"可以验收":你在 P
97
97
 
98
98
  然后说"我要验收"。AI 会按验收标准一条一条带你核对:告诉你怎么操作、应该看到什么;能用命令验证的给出命令,你可以自己执行。每条结果记入功能目录的 `acceptance.md`。
99
99
 
100
+ **代码审查(1.14 起,给开发同事和评审人)。** 需求页面上点**"代码审查"**(终端里是 `req code-review <需求>`),代码审查页在 Paseo 里打开:
101
+
102
+ - 列出这次改动的全部文件(相对需求分支的起点,需求文档排在最后、默认折叠),上面是提测说明里"这次改了什么"。
103
+ - AI 代码评审的每条意见放在它说的那一行旁边,标着级别(阻塞 / 重要 / 建议)和修复步骤的处理结果(已修复、判为不成立、决定不修复);可以标**要改**或**不用改**,补一句话。
104
+ - 点任意一行代码写意见;最下面可以写总体意见。
105
+ - **审核人**默认取自仓库的 `CODEOWNERS`(按改动的文件),提交前可以改;以后建合并请求时会用它。
106
+ - **退回修改**:意见写进 `code-review.md` 的"人工评审"一节(HR-1 起)和 `changes.md`,流水线随即开始新的一次尝试,需求说明不变,从方案设计开始只处理这些意见,改完再经过 AI 代码评审和验证,进展发回需求的对话。**评审通过**:记下评审人和审核人;之后代码又有改动时,页面会提示需要重新评审。
107
+
100
108
  **在插件里自己验收(1.14 起)。** 需求页面上点**"验收清单(可一键试用)"**(终端里是 `req checklist <需求>`),验收清单在 Paseo 里打开:
101
109
 
102
110
  - 每条验收标准写着前置条件、怎么操作、应该看到什么,以及流水线的验证结果;流水线没验证或没通过的排在最前面,报告里"需要你确认的决定"也列在后面请你表态。
@@ -109,9 +117,11 @@ req-kit 帮你把一个需求从"我想要……"做到"可以验收":你在 P
109
117
 
110
118
  测试同事可以把验收标准直接转成测试用例:每条都写成了"前置条件 / 操作 / 预期结果"。
111
119
 
120
+ **记录会留下来(1.14 起)。** 代码审查和验收的结论、评审人、每条意见都写在需求目录的 `code-review.md`、`acceptance.md`(退回修改的原因在 `changes.md`)里,随需求分支提交,合并后在主干上也能查。结论和数量另外记在本机的使用记录里,`req stats` 能看到人工评审退回了几次、验收通过率、合并进主干的需求和合并后发现的问题。
121
+
112
122
  ## 上线后发现问题
113
123
 
114
- 说"线上发现……不对"。AI 会先问这个问题是否正在影响真实用户:是的话,请马上联系开发同事或值班人员处理线上情况(req-kit 不碰生产环境)。然后它会问清现象、复现步骤、影响范围,关联到原来的需求,生成一份缺陷说明,按快速流程修复,修复后照常验收。
124
+ 说"线上发现……不对"。AI 会先问这个问题是否正在影响真实用户:是的话,请马上联系开发同事或值班人员处理线上情况(req-kit 不碰生产环境)。然后它会问清现象、复现步骤、影响范围,关联到原来的需求,生成一份缺陷说明,按快速流程修复,修复后照常验收。缺陷说明里写明是哪个需求引入的问题,`req stats` 会把它算作那个需求"合并后发现的问题";也可以在终端里手动记一笔:`req outcome <需求> --issue "<问题>"`。
115
125
 
116
126
  ## 注意
117
127
 
@@ -533,7 +533,7 @@ Paseo 自带的 `/paseo-advisor`、`/paseo-committee` 等编排 skill 也可以
533
533
 
534
534
  **结果文件**:每步必须写一个 JSON 结果文件,流水线只看它,不看 agent 的回复文字。文件缺失或格式不对时,会在同一个会话里提醒 agent 补写,最多 2 次,仍不对则判定该步骤失败。
535
535
 
536
- **等待与超时**:每步有总时限(`stepTimeoutMinutes`)。到时 agent 还在做时,先再给它同样长的时间(`selfHeal`,见下);仍未完成才判定失败,但不停止 agent。继续(`--resume`、自动重试)时先看这一步上一次的 agent:已经做完、结果文件有效就直接用它的结果;还在做就接着等它一轮(这一轮不再额外延长);第二次仍超时才停止它、另起一个 agent。需求改过、开始了新一次尝试时,上一次留下的 agent 直接停掉,不接手(1.12.1)。同一步不会有两个 agent 同时在做(它们会改同样的文件、在同一个工作区提交、写同一个结果文件)。连续读不到 agent 状态时同样处理。等待被拆成每段 10 分钟(打开 `scanGuard` 时每段 1 分钟,以便查看正在运行的命令),连接断开时自动重试。agent 等待权限批准时,同一个请求只通知一次,然后继续等待。
536
+ **等待与超时**:每步有总时限(`stepTimeoutMinutes`)。到时 agent 还在做时,先再给它同样长的时间(`selfHeal`,见下);仍未完成才判定失败,但不停止 agent。继续(`--resume`、自动重试)时先看这一步上一次的 agent:已经做完、结果文件有效就直接用它的结果;还在做就接着等它一轮(这一轮不再额外延长);第二次仍超时才停止它、另起一个 agent。需求改过、开始了新一次尝试时,上一次留下的 agent 直接停掉,不接手(1.12.1)。同一步不会有两个 agent 同时在做(它们会改同样的文件、在同一个工作区提交、写同一个结果文件)。连续读不到 agent 状态时同样处理。等待被拆成每段 10 分钟(打开 `scanGuard` 时每段 1 分钟、打开 `dangerGuard` 时每段 15 秒,以便查看正在运行的命令;只是读取状态,不占模型 token),连接断开时自动重试。agent 等待权限批准时,同一个请求只通知一次,然后继续等待。
537
537
 
538
538
  **临时错误自动重试**:步骤因限流(429)、服务暂时不可用(502/503/504/529、overloaded)、网络中断等临时错误失败时,等待后用新的 agent 重做这一步,并告诉它工作区里可能留有上一次没做完的改动。默认最多重试 2 次,分别等 1 分钟和 5 分钟:
539
539
 
@@ -569,6 +569,22 @@ Paseo 自带的 `/paseo-advisor`、`/paseo-committee` 等编排 skill 也可以
569
569
 
570
570
  `paths`:另外不允许整个搜索的目录(例如共享盘);`enabled: false` 关闭。
571
571
 
572
+ **拦截危险命令**(1.14,`dangerGuard`,默认开):步骤 agent 通常以免确认模式运行命令,"只做本地提交、不碰生产"以前只写在提示词里。现在步骤运行时,流水线每 15 秒看一次 agent 运行过和正在运行的命令(读 Paseo 的记录,不用模型)。下面这些会被处理:推送(`git push`,强推、删除远程分支单独标出)、改远程地址;发布制品(`npm publish`、`mvn deploy`、`gradle publish`、`docker push` 等);改动集群和基础设施(`kubectl apply/delete/exec…`、`helm install/upgrade…`、`terraform apply/destroy`、`ansible-playbook`);连接其他机器上的数据库(`mysql -h <带点的主机名或地址>` 等,`localhost` 和 docker compose 里的服务名如 `db` 不算);登录或复制到其他机器(`ssh`、`scp`、`rsync`,`git@` 不算);删除仓库以外的目录(`rm -rf ~`、`/`、`..`、仓库外的绝对路径,临时目录除外);下载脚本直接执行(`curl … | sh`);关机、格式化等系统级操作;以及命令里出现 `production` 中任何一项(生产环境的主机名、地址、库名)。还在运行的命令立即打断,告诉 agent 不要再做、把需要人做的写进产物;已经运行完的也会告诉它。每次都通知需求的对话(和外部通知的 `alert` 事件,`details.kind` 为 `danger`),记在状态文件的 `dangers` 里,并写进完成报告。这是按命令文字的检查,能拦住常见的情况,不是沙箱。
573
+
574
+ ```json
575
+ "dangerGuard": { "enabled": true, "production": ["prod.example.com", "10.8.0."], "allow": ["^ssh test-box"] }
576
+ ```
577
+
578
+ `production`:生产环境的标记(出现在命令或访问的地址里就拦),建议团队配置里写上;`allow`:放行的命令(正则表达式),例如测试机的登录;`enabled: false` 关闭。
579
+
580
+ **密钥扫描**(1.14,`secretScan`,默认开):每个改代码的步骤之后(和质量门一起),流水线检查本需求新增的内容:从这次尝试开始时的提交以来,每次提交加进来的行、还没提交的改动和新文件。常见格式的密钥(私钥、AWS、GitHub、GitLab、Slack、模型服务的 API 密钥、JWT、地址里带的口令)在任何文件里都算;`password = "…"`、`token: …` 这样的赋值只在代码和配置里算,测试和文档不算,值是占位符(`${…}`、`<your-token>`、`changeme`)或从环境变量读取的也不算。发现了就交回这一步的 agent:改成从配置或环境变量读取;已经写进本需求提交的,用 `git commit --fixup` 和非交互的 `git rebase --autosquash` 从这些还没推送的提交里去掉;确实不是密钥的,在那一行加注释 `req-kit:allow-secret`。最多要求 `qualityGate.fixAttempts` 次,仍有的通知需求的对话、写进代码评审和完成报告;配置了 `push` 的需求**不会推送**有疑似密钥的分支。
581
+
582
+ ```json
583
+ "secretScan": { "enabled": true, "allow": ["test/fixtures/**"] }
584
+ ```
585
+
586
+ `allow`:不扫描的文件(路径或通配符);`enabled: false` 关闭。
587
+
572
588
  **同一个目录一次只运行一条流水线**,因为各步骤都在这个目录中修改和提交。需要并行时,为每个需求使用单独的 worktree 工作区。
573
589
 
574
590
  ## 注意事项
@@ -78,12 +78,13 @@ time step ref decision alternatives reason kind needs_human
78
78
  - 所有改动都提交到这个分支。不要切换分支,也不要新建分支;发现当前分支不是工作分支时,不要提交,直接结束本步骤并在最后的回复中说明。
79
79
  - 工作分支写着"不是 git 仓库"时,跳过所有 git 操作。
80
80
  - 你的工作结束时提交一次(实现类步骤可以按任务多次提交)。只暂存你这一步改动或新建的文件,不要用 `git add -A` 把别人的未提交改动带进来。`.pipeline/` 目录不提交。
81
- - 只做本地提交。不 push,不合并,不改写历史,不动其他分支。合并上线由人来决定。
81
+ - 只做本地提交。不 push,不合并,不改写历史(流水线要求你从本需求还没推送的提交里去掉密钥时除外),不动其他分支。合并上线由人来决定。
82
82
 
83
83
  **这是一台真实的机器,没有人在旁边。**
84
84
  - 不操作仓库以外的文件,不做全局安装,不修改系统配置。
85
85
  - 不连接生产环境,不使用生产凭据,不触发真实的对外动作(发邮件、发短信、扣款、调用会产生费用或影响真实用户的外部接口)。需要时使用项目自带的测试替身或本地环境。
86
- - 不把密钥、口令、个人信息写进任何文件或提交。
86
+ - 不把密钥、口令、个人信息写进任何文件或提交。流水线会扫描本需求新增的内容,发现了会要求你去掉。
87
+ - 不发布制品(npm publish、mvn deploy、docker push),不改动集群和基础设施(kubectl、helm、terraform),不登录其他机器,不删除仓库以外的目录,不下载脚本直接执行。流水线会打断这类命令并通知用户;需要做的写进产物的"需要人处理"。
87
88
  - 启动的进程在本步骤结束前关掉。
88
89
 
89
90
  **如实报告。** 最终读者没有能力核对你的结论,他们会直接相信你写的内容。
@@ -49,6 +49,10 @@
49
49
  6. **评审意见**:按级别排列
50
50
  7. **处理记录**:留空,由下一步填写
51
51
 
52
+ ## 人工评审意见
53
+
54
+ `code-review.md` 中有"人工评审"一节、列出 HR-1、HR-2… 时,那是评审人在插件的代码审查页上提出的意见,这一次尝试就是为了处理它们。逐条核对是否已经解决:没有解决的按"重要"重新提出(在 CR 意见里注明对应的 HR 编号);评审人的意见与 spec 冲突时,按 spec 判断,并写明冲突、记入 `decisions.tsv`(`needs_human` 写 `yes`)。
55
+
52
56
  ## 第 2 轮及以后
53
57
 
54
58
  上一轮的意见已经由修复步骤处理,处理记录在 `code-review.md` 中。这一轮:
@@ -60,11 +64,18 @@
60
64
  ## 结果文件
61
65
 
62
66
  ```json
63
- { "verdict": "pass" }
67
+ {
68
+ "verdict": "changes_required",
69
+ "findings": [
70
+ { "id": "CR-1", "severity": "阻塞", "file": "src/order/export.ts", "line": 42, "title": "导出没有校验权限,普通用户也能导出全部订单", "suggestion": "调用 checkPermission('order:export')" }
71
+ ]
72
+ }
64
73
  ```
65
74
 
66
75
  - `verdict`,二选一:
67
76
  - `pass`:没有尚未解决的"阻塞"或"重要"级别意见。
68
77
  - `changes_required`:存在尚未解决的"阻塞"或"重要"级别意见,接下来会执行评审问题修复。
69
78
 
79
+ - `findings`:这一轮提出的每条意见(和 `code-review.md` 中的 CR 编号一致),给 Paseo 插件的代码审查页使用:人在改动的代码旁边看到它们,决定要不要改。`severity` 写"阻塞"、"重要"、"建议"或"待核实";`file` 是相对仓库根目录的路径,`line` 是改动后文件中的行号(不确定就省略);`title` 一句话说清问题,`suggestion` 一句话说修复方向。没有意见时写空数组。
80
+
70
81
  代码改动不存在,或者无法确定评审范围时,选择 `changes_required` 并在结论里说明情况。
@@ -20,3 +20,5 @@
20
20
  - **完成报告**:在开头说明这是第几次尝试、因为什么变化,验收对照表中标出哪些 AC 是这次新增或修改的。
21
21
 
22
22
  上一次没有写过代码时(`<pipeline-context>` 中"上一次是否已经写过代码:否"),实现类步骤按正常方式进行,只是方案设计要在已有的 `design.md` 基础上修订。
23
+
24
+ **人工代码评审退回的**(`changes.md` 最后一节的来源是"人工代码评审"):这次要处理的是 `code-review.md` 中最后一个"人工评审"小节里的 HR-1、HR-2…意见,需求说明没有变。方案只写针对这些意见的改法;实现逐条处理,并在 `implementation.md` 中逐条写明每个 HR 怎么改的(或为什么不改,记入 `decisions.tsv`,`needs_human` 写 `yes`);不要借机做意见之外的改动。
@@ -108,3 +108,24 @@ export function runningToolOf(entries: TimelineEntry[]): import("./scan-guard.js
108
108
  const text = Array.isArray(raw) ? raw.join(" ") : String(raw ?? "");
109
109
  return { kind, text, startedAt: call.first, callId };
110
110
  }
111
+
112
+ /**
113
+ * 1.14: the tool calls at the end of the timeline (for the danger guard), oldest first: each
114
+ * call's latest status, its command (shell), pattern (search) or address (fetch), and when it started.
115
+ */
116
+ export function toolCallsOf(entries: TimelineEntry[]): import("./danger-guard.js").ToolCall[] {
117
+ const calls = new Map<string, { first: string; last: Record<string, unknown> }>();
118
+ for (const entry of entries) {
119
+ const it = entry.item as Record<string, unknown> | null;
120
+ if (!it || typeof it !== "object" || it.type !== "tool_call" || typeof it.callId !== "string") continue;
121
+ const seen = calls.get(it.callId);
122
+ if (seen) seen.last = it;
123
+ else calls.set(it.callId, { first: entry.timestamp, last: it });
124
+ }
125
+ return [...calls.entries()].map(([callId, call]) => {
126
+ const detail = (call.last.detail ?? {}) as Record<string, unknown>;
127
+ const kind = String(detail.type ?? "unknown");
128
+ const raw = kind === "shell" ? detail.command : kind === "search" ? detail.query : kind === "fetch" ? detail.url : (detail.command ?? "");
129
+ return { callId, kind, text: Array.isArray(raw) ? raw.join(" ") : String(raw ?? ""), status: String(call.last.status ?? ""), startedAt: call.first };
130
+ });
131
+ }
package/engine/src/api.ts CHANGED
@@ -27,9 +27,12 @@ import { defaultConfigPath, KIT_ROOT, kitVersion } from "./locations.js";
27
27
  import { configView } from "./config-view.js";
28
28
  import { pruneIndex, readIndex, registerRequirement, requirementKey } from "./records.js";
29
29
  import { listWorktreeRecords } from "./worktree.js";
30
+ import { readHumanReview } from "./code-review.js";
30
31
  import { isAlive, type PipelineState, readState, statePath, type StepRecord } from "./state.js";
32
+ import { notStartedNotice } from "./messages.js";
33
+ import { scanOutcomes } from "./outcomes.js";
31
34
  import { parsePeriod, summarizeRuns } from "./stats.js";
32
- import { readRuns, runsFile } from "./records.js";
35
+ import { readEvents, readRuns, runsFile } from "./records.js";
33
36
  import { totals } from "./usage.js";
34
37
 
35
38
  export const API_SCHEMA = 1;
@@ -241,13 +244,14 @@ function waitingOf(repo: string, featureDir: string, state: PipelineState | null
241
244
  export function apiList(input: { repo: string; all: boolean }): ListItem[] {
242
245
  if (!input.all) {
243
246
  const repo = fs.realpathSync(input.repo);
244
- return listFeatures(repo)
247
+ const items = listFeatures(repo)
245
248
  .map((f) => {
246
249
  const state = readStateSafe(f.worktree, f.featureDir);
247
250
  const source = state?.worktree?.sourceRepo ?? null;
248
251
  return item(f.worktree, f.branch, f.featureDir, { sourceRepo: source, workspaceId: state?.worktree?.workspaceId ?? null, project: path.basename(source ?? repo) });
249
252
  })
250
253
  .filter((x): x is ListItem => x !== null);
254
+ return onePerRequirement(items);
251
255
  }
252
256
  const out: ListItem[] = [];
253
257
  const index = readIndex();
@@ -300,7 +304,21 @@ export function apiList(input: { repo: string; all: boolean }): ListItem[] {
300
304
  }
301
305
  }
302
306
  }
303
- return out.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
307
+ return onePerRequirement(out).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
308
+ }
309
+
310
+ /**
311
+ * 1.14: a requirement found in two places (the main checkout still has its spec, its own worktree
312
+ * has the run) is listed once: the copy that runs, else the one with a run state, else the latest.
313
+ */
314
+ export function onePerRequirement(items: ListItem[]): ListItem[] {
315
+ const rank = (x: ListItem) => (x.running ? 3 : 0) + (x.status ? 2 : 0) + (x.present === false ? -4 : 0);
316
+ const best = new Map<string, ListItem>();
317
+ for (const x of items) {
318
+ const b = best.get(x.key);
319
+ if (!b || rank(x) > rank(b) || (rank(x) === rank(b) && x.updatedAt > b.updatedAt)) best.set(x.key, x);
320
+ }
321
+ return items.filter((x) => best.get(x.key) === x);
304
322
  }
305
323
 
306
324
  function samePathSafe(a: string, b: string): boolean {
@@ -376,12 +394,21 @@ export function apiStatus(repoArg: string, feature: string) {
376
394
  steps: state ? state.history.map((h) => stepJson(h, state)) : [],
377
395
  // 1.13: what to expect, from this machine's completed requirements of the same profile.
378
396
  estimate: estimateOf(state),
397
+ // 1.14: the person's code review (plugin's code review page).
398
+ humanReview: humanReviewOf(repo, featureDir),
379
399
  gate: state?.gate ?? null,
380
400
  external: Object.entries(state?.external ?? {}).map(([name, r]) => ({ name, id: r.id, url: r.url, status: r.status, syncedAt: r.syncedAt, pending: r.pending.length, lastError: r.pending.at(-1)?.error ?? null })),
381
401
  joint: (state?.joint ?? []).map((j) => ({ name: j.name, slug: j.slug, repo: j.repo, sourceRepo: j.sourceRepo, branch: j.branch, workspaceId: j.workspaceId, gate: j.gate ?? null })),
382
402
  };
383
403
  }
384
404
 
405
+ function humanReviewOf(repo: string, featureDir: string) {
406
+ const r = readHumanReview(path.join(repo, featureDir));
407
+ if (!r) return null;
408
+ const head = spawnSync("git", ["rev-parse", "HEAD"], { cwd: repo, encoding: "utf8", windowsHide: true }).stdout?.trim() || null;
409
+ return { status: r.status, reviewer: r.reviewer, at: r.at, reviewers: r.reviewers, items: r.items.length, stale: r.status === "approved" && !!head && r.head !== head, consumedAttempt: r.consumedAttempt };
410
+ }
411
+
385
412
  function estimateOf(state: PipelineState | null) {
386
413
  if (!state || state.status === "completed") return null;
387
414
  const t = typical(state.profile ?? "standard");
@@ -537,7 +564,9 @@ export function apiMain(argvIn: string[], cwd = process.cwd()): unknown {
537
564
  case "stats": {
538
565
  const since = option(argv, "--since");
539
566
  const month = option(argv, "--month");
540
- return summarizeRuns(readRuns(runsFile()), parsePeriod({ since, month }));
567
+ // 1.14: merges and post-merge problems this machine can see, then runs and events.
568
+ scanOutcomes();
569
+ return summarizeRuns(readRuns(runsFile()), parsePeriod({ since, month }), readEvents(runsFile()));
541
570
  }
542
571
  case "index": {
543
572
  const prune = flag(argv, "--prune");
@@ -572,7 +601,13 @@ export function apiMain(argvIn: string[], cwd = process.cwd()): unknown {
572
601
  const by = (option(argv, "--by") ?? "manual") as "plugin" | "factory" | "manual";
573
602
  const dryRun = flag(argv, "--dry-run");
574
603
  return import("./watchdog.js").then(({ watchdogPass }) =>
575
- watchdogPass({ by: ["plugin", "factory"].includes(by) ? by : "manual", dryRun, resume: (r, f) => apiRun("resume", r, f, []) }),
604
+ watchdogPass({
605
+ by: ["plugin", "factory"].includes(by) ? by : "manual",
606
+ dryRun,
607
+ resume: (r, f) => apiRun("resume", r, f, []),
608
+ // 1.14: a confirmed requirement nobody started: one reminder in its chat.
609
+ remind: (r, f) => apiRun("reply", r, f, ["--notice", notStartedNotice(f)]),
610
+ }),
576
611
  );
577
612
  }
578
613
  case "review-url": {
@@ -580,15 +615,17 @@ export function apiMain(argvIn: string[], cwd = process.cwd()): unknown {
580
615
  const feature = need("需求");
581
616
  const readOnly = flag(argv, "--read-only");
582
617
  // 1.14 --page check: the acceptance checklist page of the same server.
583
- const page = option(argv, "--page") === "check" ? "check" : "prototype";
618
+ const pageArg = option(argv, "--page");
619
+ const page = pageArg === "check" ? "check" : pageArg === "code" ? "code" : "prototype";
584
620
  const { repo: r, featureDir } = locate(repo, feature);
585
621
  return import("./review.js").then(async ({ ensureReviewServer, keyOf, reviewUrl }) => {
586
622
  const key = keyOf(r, featureDir);
587
623
  if (!key) throw new ApiError("not_found", `${featureDir} 不在本机的需求列表中`);
588
624
  if (page === "prototype" && !fs.existsSync(path.join(r, featureDir, "prototype"))) throw new ApiError("not_found", `${featureDir} 还没有界面原型`);
589
625
  if (page === "check" && !fs.existsSync(path.join(r, featureDir, "spec.md"))) throw new ApiError("not_found", `${featureDir} 中没有 spec.md`);
626
+ if (page === "code" && !["implementation.md", "code-review.md"].some((f) => fs.existsSync(path.join(r, featureDir, f)))) throw new ApiError("not_found", `${featureDir} 还没有写代码,没有可以审查的改动`);
590
627
  const url = reviewUrl(await ensureReviewServer(), key, readOnly);
591
- return { url: page === "check" ? `${url}check/` : url, readOnly };
628
+ return { url: page === "check" ? `${url}check/` : page === "code" ? `${url}code/` : url, readOnly };
592
629
  });
593
630
  }
594
631
  default:
@@ -56,6 +56,7 @@ export function parseLaunchArgs(argv: string[]): LaunchArgs {
56
56
  list: { type: "boolean", default: false },
57
57
  "card-fields": { type: "boolean", default: false },
58
58
  track: { type: "boolean", default: false },
59
+ "review-fix": { type: "boolean", default: false },
59
60
  "spec-review": { type: "boolean", default: false },
60
61
  worktree: { type: "boolean", default: false },
61
62
  "source-repo": { type: "string" },
@@ -118,7 +119,7 @@ export function parseLaunchArgs(argv: string[]): LaunchArgs {
118
119
  repo: path.resolve(values.repo ?? process.env.PASEO_AGENT_CWD ?? process.cwd()),
119
120
  featureDir: values.feature ?? "",
120
121
  originAgentId: origin ? origin : null,
121
- resume: (values.resume ?? false) || phase === "verify" || (values.approve ?? false),
122
+ resume: (values.resume ?? false) || phase === "verify" || (values.approve ?? false) || (values["review-fix"] ?? false),
122
123
  restart: values.restart ?? false,
123
124
  configPath: values.config ? path.resolve(values.config) : null,
124
125
  profile: (profile as ProfileName | undefined) ?? null,
@@ -127,6 +128,7 @@ export function parseLaunchArgs(argv: string[]): LaunchArgs {
127
128
  list: values.list ?? false,
128
129
  cardFields: values["card-fields"] ?? false,
129
130
  track: values.track ?? false,
131
+ reviewFix: values["review-fix"] ?? false,
130
132
  specReview: values["spec-review"] ?? false,
131
133
  worktree: values.worktree ?? false,
132
134
  sourceRepo: values["source-repo"] ? path.resolve(values["source-repo"]) : null,
@@ -210,6 +212,7 @@ export function launchArgv(args: LaunchArgs): string[] {
210
212
  if (args.profile) out.push("--profile", args.profile);
211
213
  if (args.resume) out.push("--resume");
212
214
  if (args.restart) out.push("--restart");
215
+ if (args.reviewFix) out.push("--review-fix");
213
216
  if (args.sourceRepo) out.push("--source-repo", args.sourceRepo);
214
217
  if (args.workspaceId) out.push("--workspace-id", args.workspaceId);
215
218
  if (args.specReview) out.push("--spec-review");