@haiyangbg/buildbeat 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.en.md +3 -3
  3. package/SKILL.md +5 -7
  4. package/docs/CAPABILITY-MATRIX.md +1 -1
  5. package/docs/README.md +1 -0
  6. package/docs/RELEASING.md +3 -3
  7. package/docs/v2/RFC-0001-product-definition.md +1 -1
  8. package/docs/v2/RFC-0002-domain-model.md +1 -1
  9. package/docs/v2/RFC-0003-workflow-policy.md +1 -1
  10. package/docs/v2/SPEC-0001-events-v1.md +2 -2
  11. package/docs/v2/guide/00-how-to-talk.en.md +61 -0
  12. package/docs/v2/guide/00-how-to-talk.md +2 -0
  13. package/docs/v2/guide/01-quickstart.en.md +2 -2
  14. package/docs/v2/guide/01-quickstart.md +2 -2
  15. package/docs/v2/guide/02-workflow-guide.en.md +127 -0
  16. package/docs/v2/guide/02-workflow-guide.md +7 -3
  17. package/docs/v2/guide/03-policy-guide.en.md +55 -0
  18. package/docs/v2/guide/03-policy-guide.md +2 -0
  19. package/docs/v2/guide/04-adapter-guide.en.md +66 -0
  20. package/docs/v2/guide/04-adapter-guide.md +2 -0
  21. package/docs/v2/guide/05-worker-contract.en.md +60 -0
  22. package/docs/v2/guide/05-worker-contract.md +2 -0
  23. package/docs/v2/guide/07-approval-guide.en.md +2 -2
  24. package/docs/v2/guide/07-approval-guide.md +4 -3
  25. package/docs/v2/guide/09-security-boundaries.en.md +41 -0
  26. package/docs/v2/guide/09-security-boundaries.md +3 -1
  27. package/docs/v2/guide/README.en.md +36 -0
  28. package/docs/v2/guide/README.md +8 -6
  29. package/docs/v2/skill/03-collaboration-rules.md +1 -1
  30. package/docs/v2/skill/04-rhythm-and-rituals.md +3 -1
  31. package/docs/v2/skill/06-bootstrap-and-takeover.md +1 -1
  32. package/example/AGENTS.md +1 -1
  33. package/example/delivery/work/WORK-EXPORT-DATE-FILTER/run-config.yaml +1 -3
  34. package/example/delivery/work/WORK-EXPORT-DATE-FILTER/workflow.yaml +3 -5
  35. package/package.json +3 -3
  36. package/src/v2/cli/run.js +43 -22
  37. package/src/v2/presets/software-delivery.yaml +3 -5
  38. package/src/v2/runtime/decisions.js +16 -0
  39. package/src/v2/runtime/notify.js +1 -1
  40. package/src/v2/runtime/orchestrator.js +504 -404
  41. package/src/v2/runtime/overview.js +2 -1
  42. package/templates/v2/AGENTS.md +1 -1
  43. package/templates/v2/run-config.example.yaml +5 -7
package/CHANGELOG.md CHANGED
@@ -2,8 +2,29 @@
2
2
 
3
3
  > 本项目吃自己的狗粮(红线④:必更 CHANGELOG)。格式循 Keep a Changelog,倒序。
4
4
 
5
+ ## v3.3.0 — 2026-10-02(review 少停人:收敛止损、单一轮数上限、模板关分诊、intent+plan 一次接受)
6
+
7
+ - **review 按收敛止损,不再按 2 轮封顶**:review 发现阻断问题、派 fixer 之前,内核把本轮 P0/P1 与本 Run 之前各轮比较。修过的 finding 又出现(同指纹,已 dismiss 的不算),或本轮阻断数多于上一轮,就停 `enter-fix`(新 kind `review-not-converging`,理由列出又出现的指纹或前后数量,请求不带 grants,批准不动预算);阻断 finding 都是新的、数量不多于上一轮时自动继续。官方预设不再自带每 Run 2 轮的 `budgets.maxAttempts.review`,轮数上限改由下一条的 `reviewRoundsPerWork` 统一负责;run 配置里显式写的值照旧优先。开了 `reviewTriage: required` 时仍停分诊,不收敛理由并入同一请求;`nextReply` 对新 kind 给出 `findings list` / `findings adjudicate` 命令。原因:每 Run 2 轮封顶让正常修两三轮的 Run 反复停下批准扩额;本仓最近 10 个 Run 里中途 9 次批准都由驾驶会话自己批,人只出现在合并决定,审批点没起到人把关的作用。
8
+ - **review 轮数只剩一个上限**:`budgets.reviewRoundsPerWork` 不写时默认 6(此前不写即没有 Work 级上限);review 步没有显式 `maxAttempts` 时,每 Run 上限取同一个值,不会先于 Work 上限触发,官方预设不再自带 `maxAttempts.review`。`doctor` 显示 review 上限来源为 `reviewRoundsPerWork` 并标出默认值;run 配置样板删去 `maxAttempts.review`。显式写的 `maxAttempts.review` 照旧生效。原因:每 Run 与每 Work 两套计数让同一轮 review 要按两层上限解释和放行,止损线本来就按 Work 定。
9
+ - **模板默认不再开发现分诊门**:`templates/v2/run-config.example.yaml`、示例项目、`SKILL.md` 与快速开始里的 run 配置样板从 `reviewTriage: required` 改为 `reviewTriage: off`,P0/P1 finding 直接派 fixer;高风险项目仍可改回 `required`。内核默认值本来就是不分诊,未改动;已有项目的 run 配置不受影响。原因:本仓最近 10 个 Run 中途 9 次停人里 7 次是分诊门的 `enter-fix`,且全部由驾驶会话自己批准,没有起到人把关的作用。
10
+ - **intent 与 plan 一次接受**:`accept --artifact intent,plan` 一条命令接受多份工件,每份仍各记一行 `accept-<artifact>`、各自绑定 digest,policy 与台账格式不变;任一文件缺失则一份都不接受。`overview` 在 intent 未接受且 plan 待接受时提示 `--artifact intent,plan`;`SKILL.md` 改为给用户看完摘要后只问一次「接受」。原因:本仓每个 Work 的 intent 与 plan 都在约 60 毫秒内先后被接受,两次接受实际是一个决定。
11
+
12
+ ## v3.2.1 — 2026-09-26(补丁:开关参数单写、drive() 拆分、信封脚本测试、英文指南)
13
+
14
+ > **发布状态**:`@haiyangbg/buildbeat@3.2.1` 已于 2026-09-26 从 `main`(PR #54 内容、release PR #55,merge commit `8d5f7bc`,tag `v3.2.1`)经 OIDC Trusted Publishing 发布到 dist-tag **`latest`**(run 36241955690,publish 与 verify 双 job 一次 success;所有者授权「推送并发 3.2.1 / 继续」)。独立回读(直连 npmjs.org):`latest` = 3.2.1、integrity 与发布前本地候选逐字一致、SLSA v1 provenance、隔离安装 `--version` = 3.2.1、裸调用零写入、已发布 CLI 接受单写 `--apply` / `--json`、包内 12 份英文指南在位且无 pilot / 历史文档、`npm audit signatures` 通过;GitHub Release v3.2.1 标 Latest,证据见 [`docs/releases/V3.2.1-RELEASE-EVIDENCE-2026-09-26.md`](docs/releases/V3.2.1-RELEASE-EVIDENCE-2026-09-26.md)。
15
+ >
16
+ > 同日所有者本人经 npm 两步验证把 dist-tag `next` 从 3.0.1 挪到 3.2.1(此前 `@next` 比 `@latest` 旧);直连 npmjs.org 回读 `next` = `latest` = 3.2.1。
17
+
18
+ - CLI 开关参数可单写:`--json`、`--apply`、`--force`、`--once` 不必再跟 `true`(旧写法照旧有效);缺值时报错点名参数(`--config needs a value`),多余的裸词报 `unexpected argument`。
19
+ - `drive()` 从 459 行的单一循环体拆成五个有名字的阶段函数(原代码按原顺序搬移,循环本身 19 行),review 步判定收成一处 `isReviewStep()`;行为零变化——未改动任何测试,全量通过。
20
+ - CodeQL 也分析推到 `v2` 的提交。
21
+ - M-1 脚本驱动试点 `pilot/` 归档到 `docs/history/pilot/`,不再进 CI 与发布前检查;CI 必需检查「Script behavior」改测随包发给用户的信封 `worker.sh`(`tests/envelope-worker.test.sh`,18 项,macOS 上用 `/bin/bash` 3.2 跑),`test:envelope` 取代 `test:pilot`。
22
+ - 7 份只有中文的指南补齐英文版(怎么和会话说话、Workflow、Policy、Adapter、Worker 合同、安全边界、指南索引),中英互相切换;英文 README 改指英文指南;顺带更正两处 3.2.0 后过时的「每仓只有一个活动 Run」表述。
23
+
5
24
  ## v3.2.0 — 2026-09-26(并行 Run 开关、docs 归档、SKILL.md 瘦身、测试卫生)
6
25
 
26
+ > **发布状态**:`@haiyangbg/buildbeat@3.2.0` 已于 2026-09-26 从 `main`(PR #50 内容、release PR #51,merge commit `cda4bd6`,tag `v3.2.0`)经 OIDC Trusted Publishing 发布到 dist-tag **`latest`**(run 36233928546,publish 与 verify 双 job 一次 success;所有者授权「推送并发 3.2.0 / 继续」)。独立回读(直连 npmjs.org):`latest` = 3.2.0、integrity 与发布前本地候选逐字一致、SLSA v1 provenance、隔离安装 `--version` = 3.2.0、裸调用零写入、包内 7 份 `docs/v2/skill` 参考在位且无 `docs/history|releases`、已发布包的 `doctor` 正确报告 `parallel` 模式、`npm audit signatures` 通过;GitHub Release v3.2.0 标 Latest,证据见 [`docs/releases/V3.2.0-RELEASE-EVIDENCE-2026-09-26.md`](docs/releases/V3.2.0-RELEASE-EVIDENCE-2026-09-26.md)。
27
+
7
28
  - 修正 3.1.0 延后的两个 P2:run-config「显式 null 报错」只作用于会静默回落默认值的标量键(`cache: null` 重新表示不开缓存);YAML 多行列表项恢复修改前的判定与报错原文。
8
29
  - 并行 Run(开关,默认关):run 配置 `parallel: true` 的 Work 可与其他同样打开开关的 Work 同时驱动,同一 Work 的 Run 仍互斥;未打开的 Run 照旧独占仓库,两种模式互不越界(独占 Run 持有 `active-run` 全程,并行 Run 只在建立自己的标记时短暂经过它)。共享仓库的 git 写操作(建/删 worktree、分支、`.git/config`)改在短时 `@repo-git` 锁内执行;`gc` 同样回收持有者已死的 `@work` / `@parallel` / `@repo-git` 锁。`doctor` 打印当前模式。
9
30
  - 测试不再泄漏临时目录:所有测试经 `tests/support/tmp.js` 的 `tempDir()` 建临时目录并在文件结束时删除;一次全量测试从留下 147 个目录(24 MB)降到 0,`tests/v2-test-hygiene.test.js` 禁止测试文件直接调用 `mkdtempSync`。
package/README.en.md CHANGED
@@ -50,7 +50,7 @@ The project files carry the context needed to continue. A fresh session with Bui
50
50
 
51
51
  **Taking over anytime and anywhere starts with accessible records, a working environment, and appropriate permissions.** Whether you continue yourself or hand work to someone else, start from project files without carrying the old chat transcript. Git supplies version control and collaboration; repository hosting and execution platforms control access.
52
52
 
53
- Different tools can read and write the protocol. Running the Loop also requires a compatible adapter, permissions, and environment. Existing real Worker evidence covers `codex exec`, with deterministic tests for script Workers. Having a CLI alone does not establish that another tool is verified. See the [capability matrix](docs/CAPABILITY-MATRIX.md) and [adapter guide](docs/v2/guide/04-adapter-guide.md) (Chinese).
53
+ Different tools can read and write the protocol. Running the Loop also requires a compatible adapter, permissions, and environment. Existing real Worker evidence covers `codex exec`, with deterministic tests for script Workers. Having a CLI alone does not establish that another tool is verified. See the [capability matrix](docs/CAPABILITY-MATRIX.md) and [adapter guide](docs/v2/guide/04-adapter-guide.en.md) (Chinese).
54
54
 
55
55
  ## Multiple perspectives, one shared objective
56
56
 
@@ -86,7 +86,7 @@ The diagram shows normal and repair paths. Risk presets, finding triage, infrast
86
86
  - **Interruption has a recovery path:** the Runner can resume from its ledger. An interrupted step may run again; a dirty worktree requires a decision first.
87
87
  - **Loops have limits:** budgets, repeated failures, and infrastructure problems become explicit pending actions. Notifications are configurable.
88
88
 
89
- The merge decision means the candidate is ready for a merge. A human or a separately authorized tool performs merge, push, and deployment outside the Runner. The `release-readback` workflow can record release checks and observations. Runtime checks and host isolation have distinct scopes; see [security and permission boundaries (Chinese)](docs/v2/guide/09-security-boundaries.md).
89
+ The merge decision means the candidate is ready for a merge. A human or a separately authorized tool performs merge, push, and deployment outside the Runner. The `release-readback` workflow can record release checks and observations. Runtime checks and host isolation have distinct scopes; see [security and permission boundaries (Chinese)](docs/v2/guide/09-security-boundaries.en.md).
90
90
 
91
91
  ## Start your first handoff
92
92
 
@@ -142,7 +142,7 @@ Once configured, talk to the session directly:
142
142
 
143
143
  BuildBeat fits ongoing projects with frequent AI context changes, specialist collaboration, and a need for verifiable delivery records. Individuals can keep their own work moving; teams can hand work over through shared records. One-off scripts and very small changes usually do not need the full workflow. The project should have real verification commands, or establish minimum verification first.
144
144
 
145
- Teams collaborate through a shared Git repository and project agreements. BuildBeat does not provide multi-user accounts, roles and permissions. It does not collect or upload project usage data and has no telemetry collection. Configured AI tools and notification services have their own data practices. More examples are in the [conversation guide (Chinese)](docs/v2/guide/00-how-to-talk.md).
145
+ Teams collaborate through a shared Git repository and project agreements. BuildBeat does not provide multi-user accounts, roles and permissions. It does not collect or upload project usage data and has no telemetry collection. Configured AI tools and notification services have their own data practices. More examples are in the [conversation guide (Chinese)](docs/v2/guide/00-how-to-talk.en.md).
146
146
 
147
147
  ## Learn more and contribute
148
148
 
package/SKILL.md CHANGED
@@ -28,7 +28,7 @@ description: BuildBeat —— 面向人和 AI 会话的工程交付工作流,上
28
28
  | 「换会话」「删旧会话」「继续这个项目」「同事接手」 | 按 [接续指南](docs/v2/guide/11-session-handoff.md) 核对项目入口、Work、Git、`overview` / `inbox` / `status`;离开前补齐未落盘事实,接手后区分活动 Run / 中断 / 待批 / 终态 | 「已保存哪些上下文、工作停在哪、下一步」;不把删聊天当删工作树,不重启仍活动的 Run,不伪造或代批决定 |
29
29
  | 「当前进度」「待办是什么」「X 上线了吗」「离上线还差多远」 | `buildbeat overview --repo .`(每个 Work 的阶段 + 下一步该谁 + `cost:` 已花的 Run/review 轮/等人次数/worker 时长)+ `observe status --repo .` | 每件事一句:走到哪、卡在谁、下一步;**不列命令**;花费超过 intent 止损线的 Work 要主动说「已 N 轮 review / N 小时,继续还是砍」 |
30
30
  | 「有什么要我拍板」 | `buildbeat inbox --repo .` | 逐项:等什么、证据在哪、推荐 A/B;用户回「批准/拒绝」后会话调 `approve`/`reject` |
31
- | 「开个 Work:〔目标〕」 | 写 `delivery/work/<ID>/intent.md`(为什么做 + **止损线**:最多几个 Run / 几轮 review / 几小时,越线先问人)+ `plan.md`(怎么做)+ `run-config.yaml`(`budgets.reviewRoundsPerWork` 对应止损线);给用户看摘要 | 「看完说接受」;用户说「接受」→ `accept --artifact intent` / `--artifact plan`(digest 绑定) |
31
+ | 「开个 Work:〔目标〕」 | 写 `delivery/work/<ID>/intent.md`(为什么做 + **止损线**:最多几个 Run / 几轮 review / 几小时,越线先问人)+ `plan.md`(怎么做)+ `run-config.yaml`(`budgets.reviewRoundsPerWork` 对应止损线);给用户看摘要 | 「看完说接受」;用户说一次「接受」→ `accept --artifact intent,plan`(一条命令,两份各自 digest 绑定;不要分两次问) |
32
32
  | 「开工」「再来一轮」 | 先 `buildbeat doctor --config <run-config.yaml>`(会报本仓 intent/plan 是否存在且已接受、哪条 policy 会把 start 挡在哪步、每步预算),再 `start --config <run-config.yaml> --attempt new`(自动编号 RUN-X-01/02…,自动作废同 Work 的旧等待;**用 nohup/setsid 脱离启动**) | 「已起 RUN-X-02,停在合并决定时会通知/我会告诉你」 |
33
33
  | 「怎么样了」「卡住了吗」「正常吗」 | `buildbeat status --repo . --run <RUN>` | 一句:在跑第几步、跑了多久、历史通常多久、最后一次输出几分钟前;`STALLED` 就说「疑似卡住,建议停/等」;停在 kind `infra` 就说「worker 环境/后端故障,不是代码问题,恢复后我重跑,预算不扣」 |
34
34
  | 「批准 RUN-X」「拒绝,原因…」 | 先看 `inbox` 该 Run 等的是哪条 transition,再 `approve --transition <t> --by <用户名>` / `reject --reason`;非终态转换(`enter-fix` / `resume-<step>` / `enter-review`)批准后再 `resume --config <cfg>` 续跑;用 `--attempt new` 自动编号时会选择该家族唯一未终态 Run,也可 `--run <RUN-ID>` 指定;多个候选或没有未终态 Run 时按报错处理 | 说清批的是哪一步:「放行 fixer,续跑中」/「再跑一次,续跑中」/「合并决定已落,候选 <sha> 具备合并条件;合并/push/部署要你另说」。`SUCCEEDED` 不等于已合并 |
@@ -63,14 +63,12 @@ entry: build
63
63
  allowedPaths:
64
64
  - src
65
65
  - tests
66
- # P0/P1 先过人分诊再派 fixer
67
- reviewTriage: required
66
+ # off = P0/P1 直接派 fixer;高风险项目改 required,每轮先停人分诊
67
+ reviewTriage: off
68
68
  # 可省;run 配置 > 预设 > 默认。非只读步成功不扣次数;review 仍按轮计费。
69
- # review 到顶时在 enter-fix 一次批准修复、重验、再审;Run/Work 上限同时放行;
70
- # reviewRoundsPerWork 跨本 Work 所有 Run 累计 review 轮数,超了新 Run 起跑前先问人
69
+ # review 不收敛(修过的 finding 又出现/阻断数变多)才在 enter-fix 停人;
70
+ # reviewRoundsPerWork(默认 6)是 review 轮数唯一上限,跨本 Work 所有 Run 累计,到顶一次批准修复、重验、再审
71
71
  budgets:
72
- maxAttempts:
73
- review: 2
74
72
  reviewRoundsPerWork: 6
75
73
  # 同树+同命令+同信封已通过就复用证据(标 REUSED)
76
74
  cache:
@@ -16,7 +16,7 @@
16
16
 
17
17
  | 能力 | Skill-only / 手工路径 | 运行时 `buildbeat` | 权威与边界 |
18
18
  |---|---|---|---|
19
- | 工件接受 | 会话记一行到 `decisions.jsonl` | `accept --artifact intent\|plan\|spec`:digest 绑定;改过即 `stale`,`doctor` / `overview` 报出 | 接受不是开工;policy 按 riskPreset 决定 build 前要求哪些工件已接受 |
19
+ | 工件接受 | 会话记一行到 `decisions.jsonl` | `accept --artifact intent\|plan\|spec`(逗号分隔可一次接受多份,如 `intent,plan`):每份各自 digest 绑定;改过即 `stale`,`doctor` / `overview` 报出 | 接受不是开工;policy 按 riskPreset 决定 build 前要求哪些工件已接受 |
20
20
  | 自动闭环 | 无 | `start --config <run-config> [--attempt new]`:隔离 worktree、builder→verify→review→fix 自动路由、停 `WAITING_HUMAN` | 一仓同时只有一个活动 Run;worker 是配置的任意命令,内核不内置模型 |
21
21
  | 进度与等待 | 会话读目录 | `overview`(每 Work 阶段 / 下一步 / 成本)、`inbox`(等人的 Run + 下一句命令)、`status`(步、耗时、STALLED、证据、findings)、`metrics` | 全部只读;本机绝对路径不进输出 |
22
22
  | 人批 | 会话记一行 | `approve --transition <t>` / `reject`:绑定 transition + candidate + planDigest + evidenceDigest,盖章前重读实况;非终态转换后 `resume` | 合并决定 = 候选具备合并条件;合并 / push / 部署无调用路径(不变量 20) |
package/docs/README.md CHANGED
@@ -30,6 +30,7 @@
30
30
  | 发布证据([`releases/`](releases/)) | 每个发布一份 `<版本>-RELEASE-EVIDENCE-<日期>.md`;当前 `latest` 是哪一版以 [`RELEASING.md`](RELEASING.md) 顶部的现状段与 registry 回读为准 |
31
31
  | 规划与决策(2026-08) | [`V2-PLAN.md`](history/V2-PLAN.md)(执行基线,已交付)、[`V2-PROPOSAL.md`](history/V2-PROPOSAL.md)、[`V2-DECISIONS.md`](history/V2-DECISIONS.md)、[`V2-D2-DECISION-CARD.md`](history/V2-D2-DECISION-CARD.md)、[《BuildBeat v2:AI 原生软件交付控制平面》](history/BuildBeat%20v2%EF%BC%9AAI%20%E5%8E%9F%E7%94%9F%E8%BD%AF%E4%BB%B6%E4%BA%A4%E4%BB%98%E6%8E%A7%E5%88%B6%E5%B9%B3%E9%9D%A2.md) |
32
32
  | 迭代与里程碑记录 | [`history/`](history/) 下的 `V2-ITERATION-01~08.md`、[`v2/`](v2/) 下的 M1/M2/M4 验收与试点记录 |
33
+ | M-1 脚本驱动试点(2026-08,已被运行时取代,不再执行) | [`history/pilot/`](history/pilot/README.md)(`loop.sh`、试点度量与证据) |
33
34
  | 早期版本史 | [`../CHANGELOG-v1.md`](../CHANGELOG-v1.md)(2026-06 ~ 2026-08 的条目原文;当前条目在根 [`CHANGELOG.md`](../CHANGELOG.md)) |
34
35
  | 早期路线与阶段试点(2026-08,已移除的文件总线时代) | [`ROADMAP.md`](history/ROADMAP.md)、[`EXECUTION-PLAN.md`](history/EXECUTION-PLAN.md)、[`history/`](history/) 下的 `PHASE1/2/4-*.md`、[`CLI-STRATEGY-2026-08.md`](history/CLI-STRATEGY-2026-08.md)、[`CLI-PILOT-2026-08-23.md`](history/CLI-PILOT-2026-08-23.md)、[`PHASE4-STABILITY-AUDIT-2026-08-25.md`](history/PHASE4-STABILITY-AUDIT-2026-08-25.md) |
35
36
 
package/docs/RELEASING.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  This runbook governs BuildBeat's public npm distribution. The canonical package is `@haiyangbg/buildbeat` in `HaiYangBG1/BuildBeat`; the only executable is `buildbeat`. No other package name or executable alias receives publications.
4
4
 
5
- Release evidence at source package version `@haiyangbg/buildbeat@3.2.0`; latest independently verified BuildBeat npm distribution `@haiyangbg/buildbeat@3.1.0` (dist-tag `latest`; `next` stays `3.0.1`), anchored by annotated tag `v3.1.0` at commit `ace9ff5`, workflow run [36231006557](https://github.com/HaiYangBG1/BuildBeat/actions/runs/36231006557), and archived in [`V3.1.0-RELEASE-EVIDENCE-2026-09-26.md`](releases/V3.1.0-RELEASE-EVIDENCE-2026-09-26.md). The 3.0.1 chain (`latest` from 2026-09-09 until 3.1.0 took over on 2026-09-26) stays archived in [`V3.0.1-RELEASE-EVIDENCE-2026-09-09.md`](releases/V3.0.1-RELEASE-EVIDENCE-2026-09-09.md). The 3.0.0 chain (`latest` from 2026-09-09 until 3.0.1 took over the same day; the first version without the removed generation) stays archived in [`V3.0.0-RELEASE-EVIDENCE-2026-09-09.md`](releases/V3.0.0-RELEASE-EVIDENCE-2026-09-09.md). The 2.0.2 chain (`latest` from 2026-09-09 until 3.0.0 took over the same day; the last version carrying the removed generation) stays archived in [`V2.0.2-RELEASE-EVIDENCE-2026-09-09.md`](releases/V2.0.2-RELEASE-EVIDENCE-2026-09-09.md). The 2.0.1 chain (`latest` from 2026-09-06 until 2.0.2 took over on 2026-09-09) stays archived in [`V2.0.1-RELEASE-EVIDENCE-2026-09-06.md`](releases/V2.0.1-RELEASE-EVIDENCE-2026-09-06.md). The 2.0.0 chain (`latest` from 2026-09-05 until 2.0.1 took over on 2026-09-06) stays archived in [`V2.0.0-RELEASE-EVIDENCE-2026-09-05.md`](releases/V2.0.0-RELEASE-EVIDENCE-2026-09-05.md). The beta.5 chain stays archived in [`V2.0.0-BETA.5-RELEASE-EVIDENCE-2026-09-05.md`](releases/V2.0.0-BETA.5-RELEASE-EVIDENCE-2026-09-05.md). The beta.4 chain stays archived in [`V2.0.0-BETA.4-RELEASE-EVIDENCE-2026-09-03.md`](releases/V2.0.0-BETA.4-RELEASE-EVIDENCE-2026-09-03.md); the beta.3 chain in [`V2.0.0-BETA.3-RELEASE-EVIDENCE-2026-09-01.md`](releases/V2.0.0-BETA.3-RELEASE-EVIDENCE-2026-09-01.md). The beta.2 chain stays archived in [`V2.0.0-BETA.2-RELEASE-EVIDENCE-2026-08-28.md`](releases/V2.0.0-BETA.2-RELEASE-EVIDENCE-2026-08-28.md); the beta.1 chain stays archived in [`V2.0.0-BETA.1-RELEASE-EVIDENCE-2026-08-28.md`](releases/V2.0.0-BETA.1-RELEASE-EVIDENCE-2026-08-28.md). Earlier distributions and the retired legacy package name are archived in the dated evidence files under `docs/releases/`.
5
+ Release evidence at source package version `@haiyangbg/buildbeat@3.3.0`; latest independently verified BuildBeat npm distribution `@haiyangbg/buildbeat@3.2.1` (dist-tags `latest` and `next`; the owner moved `next` from `3.0.1` to `3.2.1` with npm 2FA on 2026-09-26), anchored by annotated tag `v3.2.1` at commit `8d5f7bc`, workflow run [36241955690](https://github.com/HaiYangBG1/BuildBeat/actions/runs/36241955690), and archived in [`V3.2.1-RELEASE-EVIDENCE-2026-09-26.md`](releases/V3.2.1-RELEASE-EVIDENCE-2026-09-26.md). The 3.2.0 chain (`latest` on 2026-09-26 until 3.2.1 took over the same day) stays archived in [`V3.2.0-RELEASE-EVIDENCE-2026-09-26.md`](releases/V3.2.0-RELEASE-EVIDENCE-2026-09-26.md). The 3.1.0 chain (`latest` on 2026-09-26 until 3.2.0 took over the same day) stays archived in [`V3.1.0-RELEASE-EVIDENCE-2026-09-26.md`](releases/V3.1.0-RELEASE-EVIDENCE-2026-09-26.md). The 3.0.1 chain (`latest` from 2026-09-09 until 3.1.0 took over on 2026-09-26) stays archived in [`V3.0.1-RELEASE-EVIDENCE-2026-09-09.md`](releases/V3.0.1-RELEASE-EVIDENCE-2026-09-09.md). The 3.0.0 chain (`latest` from 2026-09-09 until 3.0.1 took over the same day; the first version without the removed generation) stays archived in [`V3.0.0-RELEASE-EVIDENCE-2026-09-09.md`](releases/V3.0.0-RELEASE-EVIDENCE-2026-09-09.md). The 2.0.2 chain (`latest` from 2026-09-09 until 3.0.0 took over the same day; the last version carrying the removed generation) stays archived in [`V2.0.2-RELEASE-EVIDENCE-2026-09-09.md`](releases/V2.0.2-RELEASE-EVIDENCE-2026-09-09.md). The 2.0.1 chain (`latest` from 2026-09-06 until 2.0.2 took over on 2026-09-09) stays archived in [`V2.0.1-RELEASE-EVIDENCE-2026-09-06.md`](releases/V2.0.1-RELEASE-EVIDENCE-2026-09-06.md). The 2.0.0 chain (`latest` from 2026-09-05 until 2.0.1 took over on 2026-09-06) stays archived in [`V2.0.0-RELEASE-EVIDENCE-2026-09-05.md`](releases/V2.0.0-RELEASE-EVIDENCE-2026-09-05.md). The beta.5 chain stays archived in [`V2.0.0-BETA.5-RELEASE-EVIDENCE-2026-09-05.md`](releases/V2.0.0-BETA.5-RELEASE-EVIDENCE-2026-09-05.md). The beta.4 chain stays archived in [`V2.0.0-BETA.4-RELEASE-EVIDENCE-2026-09-03.md`](releases/V2.0.0-BETA.4-RELEASE-EVIDENCE-2026-09-03.md); the beta.3 chain in [`V2.0.0-BETA.3-RELEASE-EVIDENCE-2026-09-01.md`](releases/V2.0.0-BETA.3-RELEASE-EVIDENCE-2026-09-01.md). The beta.2 chain stays archived in [`V2.0.0-BETA.2-RELEASE-EVIDENCE-2026-08-28.md`](releases/V2.0.0-BETA.2-RELEASE-EVIDENCE-2026-08-28.md); the beta.1 chain stays archived in [`V2.0.0-BETA.1-RELEASE-EVIDENCE-2026-08-28.md`](releases/V2.0.0-BETA.1-RELEASE-EVIDENCE-2026-08-28.md). Earlier distributions and the retired legacy package name are archived in the dated evidence files under `docs/releases/`.
6
6
 
7
7
  ## Channels and branches
8
8
 
@@ -36,7 +36,7 @@ bash -n .github/scripts/*.sh tests/*.sh
36
36
  shellcheck -x .github/scripts/*.sh tests/*.sh
37
37
  actionlint .github/workflows/*.yml
38
38
  bash tests/check-docs.sh
39
- npm run test:pilot
39
+ npm run test:envelope
40
40
  npm run test:plugin
41
41
  npm test
42
42
  npm publish --dry-run --access public --registry=https://registry.npmjs.org/
@@ -69,7 +69,7 @@ npm publish --access public --tag bootstrap --registry=https://registry.npmjs.or
69
69
  npm view @haiyangbg/buildbeat dist-tags --json --registry=https://registry.npmjs.org/
70
70
  ```
71
71
 
72
- The intended bootstrap readback is `bootstrap: 0.0.0` without a stable `latest` claim. During the first BuildBeat package creation, npm also attached `latest: 0.0.0` despite the explicit non-default tag and rejected the authenticated delete with HTTP 400. Treat any such first-package `latest` as temporary bootstrap exposure: do not activate launch documentation, do not republish or unpublish the immutable version, and complete the reviewed OIDC release so the real version takes over `latest`. The readback at the time of that first scoped release (2026-08-25) was `bootstrap: 0.0.0` and `latest: 1.21.0`; since 2026-09-05 `latest` is `2.0.0` and `next` is `2.0.0-beta.5` (see the current-state paragraph at the top of this runbook). The bootstrap package page is registry-creation evidence only: it is not a BuildBeat release, is not tagged in Git, and has no provenance claim. The npm documentation requires 2FA or an allowed granular token for direct publication; no long-lived publish token belongs in Git, shell history, logs, or a chat transcript. See npm's guides for [scoped public packages](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages/) and [Trusted Publishing](https://docs.npmjs.com/trusted-publishers/).
72
+ The intended bootstrap readback is `bootstrap: 0.0.0` without a stable `latest` claim. During the first BuildBeat package creation, npm also attached `latest: 0.0.0` despite the explicit non-default tag and rejected the authenticated delete with HTTP 400. Treat any such first-package `latest` as temporary bootstrap exposure: do not activate launch documentation, do not republish or unpublish the immutable version, and complete the reviewed OIDC release so the real version takes over `latest`. The readback at the time of that first scoped release (2026-08-25) was `bootstrap: 0.0.0` and `latest: 1.21.0`; on 2026-09-05 `latest` moved to `2.0.0` with `next` at `2.0.0-beta.5`; the current tags are in the current-state paragraph at the top of this runbook. The bootstrap package page is registry-creation evidence only: it is not a BuildBeat release, is not tagged in Git, and has no provenance claim. The npm documentation requires 2FA or an allowed granular token for direct publication; no long-lived publish token belongs in Git, shell history, logs, or a chat transcript. See npm's guides for [scoped public packages](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages/) and [Trusted Publishing](https://docs.npmjs.com/trusted-publishers/).
73
73
 
74
74
  ## Independent readback
75
75
 
@@ -3,7 +3,7 @@
3
3
  > 状态:`FINAL`(2026-08-28 项目所有者定稿,`V2-D3`;M0 随三份 RFC 与 [`SPEC-0001-events-v1.md`](SPEC-0001-events-v1.md) 定稿退出)
4
4
  > 日期:2026-08-28
5
5
  > 上游:[`V2-PLAN.md`](../history/V2-PLAN.md)(执行基线,`V2-D0=B`);内核范围:完整内核(`V2-D2=A`,[`V2-DECISIONS.md`](../history/V2-DECISIONS.md))
6
- > 需求来源:M-1 试点记录——[`pilot/metrics.md`](../../pilot/metrics.md)(能力矩阵 + 卡点 1–5)、[`pilot/evidence/2026-08-28-m1-runtime-gap.md`](../../pilot/evidence/2026-08-28-m1-runtime-gap.md)(F5/F6)、[`V2-ITERATION-01.md`](../history/V2-ITERATION-01.md)
6
+ > 需求来源:M-1 试点记录——[`history/pilot/metrics.md`](../history/pilot/metrics.md)(能力矩阵 + 卡点 1–5)、[`history/pilot/evidence/2026-08-28-m1-runtime-gap.md`](../history/pilot/evidence/2026-08-28-m1-runtime-gap.md)(F5/F6)、[`V2-ITERATION-01.md`](../history/V2-ITERATION-01.md)
7
7
 
8
8
  ---
9
9
 
@@ -3,7 +3,7 @@
3
3
  > 状态:`FINAL`(2026-08-28 项目所有者定稿,`V2-D3`)
4
4
  > 日期:2026-08-28
5
5
  > 上游:[`V2-PLAN.md`](../history/V2-PLAN.md) §3–4;报告 B §6 / §13 / §14(经基线裁决修订)
6
- > 需求来源:[`pilot/metrics.md`](../../pilot/metrics.md) 卡点 1–5;[`pilot/evidence/2026-08-28-m1-runtime-gap.md`](../../pilot/evidence/2026-08-28-m1-runtime-gap.md)
6
+ > 需求来源:[`history/pilot/metrics.md`](../history/pilot/metrics.md) 卡点 1–5;[`history/pilot/evidence/2026-08-28-m1-runtime-gap.md`](../history/pilot/evidence/2026-08-28-m1-runtime-gap.md)
7
7
 
8
8
  ---
9
9
 
@@ -3,7 +3,7 @@
3
3
  > 状态:`FINAL`(2026-08-28 项目所有者定稿,`V2-D3`;§8 observe/bands schema 已随定稿冻结)
4
4
  > 日期:2026-08-28
5
5
  > 上游:[`V2-PLAN.md`](../history/V2-PLAN.md) §3.2–3.7;报告 B §8–11 / WP1.4–1.5 / WP4.3–4.5
6
- > 需求来源:[`pilot/metrics.md`](../../pilot/metrics.md) 卡点 3/5、故障矩阵 F1–F6;[`pilot/evidence/2026-08-28-m1-runtime-gap.md`](../../pilot/evidence/2026-08-28-m1-runtime-gap.md)
6
+ > 需求来源:[`history/pilot/metrics.md`](../history/pilot/metrics.md) 卡点 3/5、故障矩阵 F1–F6;[`history/pilot/evidence/2026-08-28-m1-runtime-gap.md`](../history/pilot/evidence/2026-08-28-m1-runtime-gap.md)
7
7
 
8
8
  ---
9
9
 
@@ -4,7 +4,7 @@
4
4
  > 日期:2026-08-28
5
5
  > 冻结范围:**信封字段、通用规则、损坏处理、reducer 合同、初始事件类型注册表的语义**。文件摆放位置、快照格式、CLI 展示均为非规范内容,可变。
6
6
  > 演进规则:一切修改 **additive-only**(新增可选字段、新增事件类型);破坏性变更必须升 `v` 并提供旧版读取器。
7
- > 需求来源:[`pilot/metrics.md`](../../pilot/metrics.md) 卡点 1(无统一 ledger)、卡点 5(无 Run 登记);F5/F6 见 [`pilot/evidence/2026-08-28-m1-runtime-gap.md`](../../pilot/evidence/2026-08-28-m1-runtime-gap.md)
7
+ > 需求来源:[`history/pilot/metrics.md`](../history/pilot/metrics.md) 卡点 1(无统一 ledger)、卡点 5(无 Run 登记);F5/F6 见 [`history/pilot/evidence/2026-08-28-m1-runtime-gap.md`](../history/pilot/evidence/2026-08-28-m1-runtime-gap.md)
8
8
 
9
9
  ---
10
10
 
@@ -71,7 +71,7 @@
71
71
  | `FAILURE_FINGERPRINT` | kernel | `step, command, exitCode, errorDigest, diffDigest` | 无进展/相同失败检测的输入 |
72
72
  | `BUDGET_CONSUMED` | kernel | `kind ∈ {attempts,tokens,cost,time}, amount, remaining` | 预算台账(卡点 1:token/费用不再 `UNVERIFIED`) |
73
73
  | `BUDGET_EXTENDED` | kernel | `step, amount, maxAttempts, approvalRef, scope?` | additive(迭代 09):人批准了预算耗尽的 `resume-<step>`,该步上限 +`amount`;状态 `budgetExtensions[step]` 累加,`maxAttemptsFor(step)` 据此重放。`scope: work` 时是 Work 级 review 轮数上限(`budgets.reviewRoundsPerWork`)被人放行一轮,累加到 `workReviewGrants` |
74
- | `HUMAN_REQUESTED` | kernel | `transition, subject{candidate,planDigest,evidenceDigest}, reasons`;additive:`kind ∈ {boundary,final-decision,finding-triage,stale,infra}` | 进入 WAITING_HUMAN |
74
+ | `HUMAN_REQUESTED` | kernel | `transition, subject{candidate,planDigest,evidenceDigest}, reasons`;additive:`kind ∈ {boundary,final-decision,finding-triage,stale,infra,budget,work-review-cap,review-not-converging}`(`review-not-converging`:review 修过的 finding 又出现或阻断数多于上一轮,停 `enter-fix`) | 进入 WAITING_HUMAN |
75
75
  | `DECISION_RECORDED` | human | `decision ∈ {approved,rejected}, transition, subject, decisionRef`;additive(迭代 09):`adopted?: <sha>`、`resumeAt?: <step>`(adopt 时 subject 即该提交,恢复从 `resumeAt` 起而非 transition 所指的步) | 同步落 Git 决策记录 |
76
76
  | `APPROVAL_STALE` | kernel | `approvalRef, changed ⊆ {candidate,plan,evidence}` | F6 的机器化 |
77
77
  | `CHECKPOINT` | kernel | `resumePoint{step,attempt}, workspaceStates[]` | F5 的机器化:恢复只允许从最近 CHECKPOINT 或安全推导点继续 |
@@ -0,0 +1,61 @@
1
+ # How to talk to a session that has BuildBeat (by project stage)
2
+
3
+ [简体中文](00-how-to-talk.md) | **English**
4
+
5
+ > This page is for **users**. You work in any AI coding session (any tool will do, and one session is enough) that has loaded the BuildBeat Skill; you speak plainly, and the session calls `buildbeat`, reads the output and answers you as "done → not done → next". **You do not need to remember any command.** The example phrases below are what people normally say; copy them as they are. Phrases in the same cell mean the same thing; pick whichever comes naturally.
6
+ > The session-side rules that correspond to this page are in `SKILL.md` §0.5; project-specific routing and red lines are in each project's root `AGENTS.md`.
7
+
8
+ Before switching sessions, say "write the key context down, I am closing the old session"; in the new session say "read the project entry point, check progress and pending approvals, and carry on with this work". When handing over to a teammate, say "sync the project records and the candidate so the person taking over can check the current scope and the environment the original Run lives in". The detailed steps, and what an active Run needs to keep, are in [Session and team handoff](11-session-handoff.en.md).
9
+
10
+ ## One table: a project from zero to the next phase
11
+
12
+ | Stage | What you want | Just say | What the session does behind it | What you get / watch for |
13
+ |---|---|---|---|---|
14
+ | **0. Not started** | Decide whether BuildBeat is worth it | "Is this project a good fit for BuildBeat?" | Looks at repository size, timeline, number of repositories / deployment units / sessions | A one-line verdict: worth it for multi-phase iterations / several repositories / several sessions; a job that wraps up within a week is simply done directly |
15
+ | | Set up a new project | "Set this project up with BuildBeat" "Initialise the collaboration skeleton" | Reads the code first → asks a few questions (stack, repositories, deployment units, whether there is a UI) → one confirmation screen → generates `AGENTS.md`, `delivery/` (with the envelope), `.buildbeat/observe.yaml`, `.buildbeat/notify.yaml`, a gitleaks pre-commit hook | One confirmation screen; it writes only after you say "OK", then lists the files it generated |
16
+ | | Take over an existing project | "Put this existing project on BuildBeat" "Take over this repository" | Surveys it (tests, contracts, deployment facts) → draws strangler boundaries (new ground / old ground / read-only) → phase 0 adds a minimal verification suite | A survey report plus a boundary draft; you decide the boundaries; history is not rewritten |
17
+ | | Turn on notifications | "Notify me when a Run stops" | Writes `.buildbeat/notify.yaml` (DingTalk / webhook) | It tells you which robot to create and which environment variable to `export`; the URL never enters Git |
18
+ | **1. Start a work · settle the approach** | Start something | "Open a Work: 〔one-line goal〕" "Turn the current goal into a Work" | Writes `delivery/work/<ID>/intent.md` (why, and what counts as done) + `plan.md` (how, in how many steps) + `run-config.yaml` | A summary + "say accept once you have read it"; only your "accept" makes the digest binding take effect |
19
+ | | See the approach before any code | "Don't touch the code yet, give me the approach" "Tell me the difference between A and B" | Produces drafts and comparisons only; no Run starts | A comparison + a recommendation + consequences; nothing is built before you decide |
20
+ | | Pick out what is mine to decide | "What in this approach needs my decision?" "Is there anything I need to decide?" | Compresses the acceptance list into real trade-offs (including names you will say out loud later: domains, service names, durations) | 2–5 decision items at once, each with a recommended value; no chain of one-by-one questions |
21
+ | | Decide | "Accept" "Go with all the recommendations" "Take B for the second one, recommendations for the rest" | `accept` the intent/plan; decisions land in `decisions.jsonl` / `pm/decisions.md` | A one-line confirmation; editing the plan makes the acceptance stale automatically, and it must be accepted again |
22
+ | | Set the risk level | "Use the fast track for this" "Be stricter with this one" | `riskPreset: fast / standard / controlled` | fast stops only before the merge; standard adds plan acceptance; controlled adds intent acceptance and the release gate |
23
+ | **2. Get ready to run** | Check the environment | "What does the environment need?" "Run a preflight" | Checks `requires:` (binary versions + `probe:` probes); `preflight --step` dry-runs up to the first failure boundary | Everything missing reported at once; a dry run produces no evidence, a real Run has to reproduce it |
24
+ | | Freeze the envelope | "Pin the prompts" "Freeze the envelope" | Adds `pin: <sha>` under `envelope:` | Every Run from then on records the envelopeDigest, so it can be traced |
25
+ | **3. Run · move forward** | Put it to work | "Go" "〔WORK-ID〕, your turn" | `start --config … --attempt new` (numbered automatically, older waiting runs superseded, started detached) | "RUN-X-02 has started; I will tell you when it stops at the merge decision" |
26
+ | | See progress | "Where are we?" "Current progress" "Per repository, please" | `overview` (each Work's stage + whose move is next) + `observe status` | One line per item: stage, who it is waiting on, next; no commands listed |
27
+ | | See the next step | "What's next?" "What do you need from me?" | The `next` lines of `overview`, only those in your hands | Only what you have to do: DNS, credentials, approvals, hands-on actions |
28
+ | | Worried it is stuck | "How's it going?" "Is it stuck?" "It has been half an hour, is that normal?" | `status --run` (time per step, historical median, last output, STALLED) | One line with numbers: "verify has run 14 minutes, the historical median is 6, last output 2 minutes ago, still moving"; a suspected stall is said plainly |
29
+ | | See what is waiting for me | "What needs my approval?" "What's waiting on me?" | `inbox` + `delivery/observe/intents/` | Item by item: what it waits for, where the evidence is, a recommended A/B |
30
+ | | Approve / refuse | "Approve" "Approve RUN-X" "Reject, because …" | `approve` / `reject`; after approving a non-terminal transition, `resume` continues the run | It says which step you approved: release the fixer / run once more / the merge decision; the merge decision only says the candidate is fit to merge, and merge / push / deploy are separate things you ask for |
31
+ | | Rule on a finding | "This one doesn't count, accept that one" "This is a false positive" | `findings adjudicate dismiss/accept` → releases the fixer | The same dismissed finding no longer blocks; a higher severity reopens it |
32
+ | | Another round | "One more round" "Continue" | A new attempt | The old waiting run is superseded automatically; the inbox shows only live items |
33
+ | | Stop | "Stop if it fails again this time" "Stop for now" | Caps the rounds; `stop --reason` records it, and the candidate and evidence are kept | Done / not done / next, with hashes; then you decide whether to change the approach, fix it by hand or close the Work |
34
+ | **4. Acceptance · merge** | Accept the result | "Run acceptance" "Check the RUN-X candidate" | The testing perspective verifies the exact candidate independently; the report lands in the Work directory | Pass / fail + evidence; the writer's own word is not evidence |
35
+ | | Merge | "Go ahead and merge" "You may push" | A human action carried out by the session (within the red lines in `AGENTS.md`), then read back from the remote | The hash after the merge; `overview` shows MERGED |
36
+ | **5. Release** | Release | "Release it" "Get ready to release" | The `release-readback` preset + `riskPreset: release`: read back first → stop | "Readback is all green; now it is your turn to do 〔the action〕; tell me when it is done" |
37
+ | | I have done it | "Done" "Half done, check it for me" | Approves apply-readback → readback + observation → stops at closing the window | What is still missing, item by item; any failed step stops it |
38
+ | | Decide the release card | "Approve the release" "Don't release yet" | Carries out / does not carry out the decision card | Production actions are always yours; it only reads back and records |
39
+ | | Production alert | "Production is alerting" "Run a health check" | `observe run` → drafts queued | A one-line draft + "fix_now / schedule / dismiss, your call" |
40
+ | **6. Wrap up · next phase · retrospective** | Close something | "This Work is done" "Close this Work" | Records the decision, confirms the run-record is in the Git plane | `overview` stops listing it as open |
41
+ | | Clean up | "Tidy up" | `gc` (the plan first; `--apply` once you nod) | How many worktrees were cleaned; any candidate reachable only from its branch is always kept, with the reason |
42
+ | | Next phase | "Start the next phase" "Move on to the next phase" | The phase-change compaction ritual: archive, truncate, reset pointers, ask what to feed back | One screen of checklist; the new phase starts from a clean entry point |
43
+ | | Retrospective | "Review what BuildBeat did for us" "What went well and what didn't?" | `metrics` + Run ledgers + session records | Positives / negatives / changes, with numbers |
44
+ | | Keep the lesson | "Write down what we learned from this blocker" | Writes `pm/<date>-<topic>.md` + `env-facts.md`, and turns what can be checked by machine into `requires:` probes | The next window refers to it directly instead of passing it on by word of mouth |
45
+ | | Feed back upstream | "What could BuildBeat itself learn from this?" | Compares against lessons to find new pitfalls | A candidate list, each item A/B/C |
46
+ | | Upgrade | "Update the BuildBeat version" | Updates `BUILDBEAT.md` / `AGENTS.md` / `.buildbeat/*.yaml`, runs `doctor` | The version marker + the doctor report |
47
+
48
+ ## Phrases that work at any stage
49
+
50
+ | You say | It will |
51
+ |---|---|
52
+ | "Why?" "What happens if we don't?" "Where did this name come from?" | Explain in plain words, and turn what is yours to decide (names, durations, scope) into decision items for you to approve instead of deciding for you |
53
+ | "Plain words, please" "No numbered lists" | One line each for done / not done / next; separate items only when they really differ |
54
+ | "What do you think?" | Give one recommendation and the reason, not a pile of options |
55
+ | "Authorised" or "Approved" on its own | Applies only to the one thing it explicitly proposed just before, never to other actions |
56
+
57
+ ## Three baselines (so you don't have to ask)
58
+
59
+ - **Approval ≠ execution**: merge, push, deploy, spending and deletion each need you to say so, one by one.
60
+ - **Numbers must be concrete**: asking "is this normal?" always gets elapsed time / historical median / time of last output; with no data it says there is no data.
61
+ - **Names go through you first**: domains, service names, environment names, auto-stop durations — anything you will say out loud later — come as recommended values on a decision card for you to approve; the session does not decide them itself.
@@ -1,5 +1,7 @@
1
1
  # 怎么和装了 BuildBeat 的会话说话(按项目阶段)
2
2
 
3
+ **简体中文** | [English](00-how-to-talk.en.md)
4
+
3
5
  > 这是给**用户**看的一页。你在任意一个 AI 编程会话里工作(哪家工具都可以,一个会话就够),会话装载了 BuildBeat Skill;你说人话,它去调 `buildbeat`、读输出、按「已做 → 未做 → 下一步」回你。**你不需要记任何命令**。下面的例句就是平时的说法,照抄即可;同一格里的几句话意思相同,挑顺口的。
4
6
  > 会话侧的对应规则在 `SKILL.md` §0.5;项目专属的路由与红线在各项目根的 `AGENTS.md`。
5
7
 
@@ -47,7 +47,7 @@ entry: build
47
47
  allowedPaths:
48
48
  - src
49
49
  - tests
50
- reviewTriage: required
50
+ reviewTriage: off
51
51
  envelope:
52
52
  prompts: ../../envelope/prompts
53
53
  workers:
@@ -91,7 +91,7 @@ workers:
91
91
  - `workers.<role>` is any CLI: to switch tools, change only what follows `--` (`claude -p`, any script), see the [Adapter guide](04-adapter-guide.md) (Chinese); the reviewer's output format is in the [Worker contract](05-worker-contract.md) (Chinese) and already spelled out in the prompt template.
92
92
  - **`fixer` is not optional**: without it, a failed verify or a blocking review stops the Run at `WAITING_HUMAN` (reason `no adapter configured for worker fixer`) waiting for you to fix by hand; nothing gets fixed automatically.
93
93
  - Worker subprocesses receive only `PATH HOME LANG LC_ALL TMPDIR TERM USER SHELL` by default; inject anything else by name with `env:` ([Adapter guide](04-adapter-guide.md), Chinese).
94
- - `reviewTriage: required` routes P0/P1 findings through you before a fixer is dispatched; delete the line if you do not want that.
94
+ - `reviewTriage: off` (the default) sends P0/P1 findings straight to the fixer, and review rounds stay bounded by `budgets`. Set it to `required` to see every round before a fixer runs, which suits high-risk projects.
95
95
 
96
96
  ## 3. Accept the plan
97
97
 
@@ -47,7 +47,7 @@ entry: build
47
47
  allowedPaths:
48
48
  - src
49
49
  - tests
50
- reviewTriage: required
50
+ reviewTriage: off
51
51
  envelope:
52
52
  prompts: ../../envelope/prompts
53
53
  workers:
@@ -91,7 +91,7 @@ workers:
91
91
  - `workers.<角色>` 是任意 CLI:换工具只改 `--` 后面的命令(`claude -p`、任意脚本都行),见 [Adapter 指南](04-adapter-guide.md);reviewer 的输出格式见 [Worker 合同](05-worker-contract.md),prompt 模板已写明。
92
92
  - **`fixer` 不是可选项**:没配它,verify 失败或 review 阻断时 Run 会停 `WAITING_HUMAN`(理由 `no adapter configured for worker fixer`)等你手修,不会自动修。
93
93
  - worker 子进程默认只拿到 `PATH HOME LANG LC_ALL TMPDIR TERM USER SHELL`;需要别的变量用 `env:` 点名注入([Adapter 指南](04-adapter-guide.md))。
94
- - `reviewTriage: required` 让 P0/P1 finding 先过你的手再派 fixer;不想要就删掉这行。
94
+ - `reviewTriage: off`(默认)时 P0/P1 finding 直接派 fixer,review 轮数仍受 `budgets` 上限约束。想每轮都先过你的手,改成 `required`,适合高风险项目。
95
95
 
96
96
  ## 3. 接受计划
97
97
 
@@ -0,0 +1,127 @@
1
+ # Workflow authoring guide
2
+
3
+ [简体中文](02-workflow-guide.md) | **English**
4
+
5
+ Authority: [`RFC-0003 §2`](../RFC-0003-workflow-policy.md) (Chinese); implementation: `src/v2/engine/workflow.js`. The official preset [`software-delivery.yaml`](../../../src/v2/presets/software-delivery.yaml) is the best model.
6
+
7
+ ## Shape
8
+
9
+ ```yaml
10
+ kind: workflow
11
+ version: 1
12
+ name: software-delivery
13
+ entry: intent
14
+ steps:
15
+ - id: build
16
+ worker: builder
17
+ - id: review
18
+ worker: reviewer
19
+ readonly: true
20
+ - id: wait-merge
21
+ transitions:
22
+ - from: verify
23
+ on: failed
24
+ to: fix
25
+ terminal:
26
+ - wait-merge
27
+ ```
28
+
29
+ ## Rules (checked fail-closed at load time)
30
+
31
+ 1. **Step order is the default edge**: the order in which `steps` are written defines the happy path — each step's `succeeded` goes to the next step by default; a step that should not be on the default chain (such as `fix`) goes at the end and is reached only through explicit transitions.
32
+ 2. **Explicit transitions**: `on` in `transitions` takes the worker's result (`succeeded` / `failed` / `findings-blocking`); an explicit edge takes precedence over the default one.
33
+ 3. **`readonly: true`**: any write to the worktree by that step's worker makes the step count as failed and is recorded — "the reviewer does not change code" is invariant 9, enforced by the Runner's before/after snapshot comparison, not by trusting the prompt.
34
+ 4. **`optional` / `requiredWhen`**: optional steps are skipped by default; `requiredWhen: ui-delivery` makes the step mandatory for a UI delivery (together with the [ui-render-gate](03-policy-guide.en.md) and invariant 22).
35
+ 5. **`terminal`**: the listed steps are exits. The loader checks for **cycles without an exit** — a cycle such as verify ⇄ fix must have a path to a terminal step, or the workflow is refused.
36
+ 6. **Steps without a worker** (such as `wait-merge`) are pure wait / decision points; the Runner raises `HUMAN_REQUESTED` there or stops according to `stopAt`.
37
+
38
+ ## How it relates to the run config
39
+
40
+ `entry` in the run config can override the workflow's `entry` (for example to start at `build` and skip the intent/plan steps — the digests are still bound into the approval subject); `stopAt` names stop points. The whole workflow file is hashed with sha256 into `RUN_CREATED.workflowDigest`, so it can be proved afterwards which workflow a run used.
41
+
42
+ **What a mistake looks like**: `start` / `resume` / `doctor` / `preflight` / `approve --config` validate the whole run config before doing anything, and **list every problem at once**:
43
+
44
+ ```text
45
+ error: run config delivery/work/WORK-X/run-config.yaml has 3 problem(s):
46
+ - stopat: unknown key (did you mean stopAt?)
47
+ - repo: required and missing
48
+ - workers.reviwer: no step of the workflow uses this worker (did you mean reviewer?); workers in this workflow: planner, builder, verifier, reviewer, fixer
49
+ ```
50
+
51
+ What is checked: required keys (`repo` `work` `run` `workflow` `workers`); unknown top-level keys and unknown worker / envelope fields (with the closest spelling); types and values (positive integers, lists, `inheritEnv` only `true` / `false`); every worker name must be used by a workflow step; `stopAt` / `entry` must be workflow steps; `work` / `run` may only contain letters, digits, `.` `_` `-`, and a numeric value must be quoted.
52
+
53
+ **Parallel runs (`parallel: true`, off by default)**: by default a repository drives one Run at a time, and another Work's `start` is refused with a note on whom it is queued behind (real incident: a session waited 3 hours 23 minutes behind another Work's Run).
54
+ A Work whose run config sets `parallel: true` can drive at the same time as other Works that set it too; Runs of the same Work are always mutually exclusive; a Run without the switch keeps the whole repository to itself as before — while it runs, parallel Runs cannot start, and while a parallel Run runs, it cannot start.
55
+ Before turning it on, make sure verify does not take fixed ports or share one database or other external state, or the parallel runs will collide. `doctor` prints the current mode; parallel markers left by a killed process are reclaimed by owner, like locks.
56
+
57
+ The run config can also declare (beta.3, all from real incidents in a thirty-round deployment campaign):
58
+
59
+ - **`requires:` environment contract** — the binaries and minimum versions the envelope implicitly depends on, checked fail-closed in full before the Run starts, with every problem reported at once (real incidents: `rg` present only on one session's vendored PATH, `/bin/bash` 3.2, a new shell resolving to Node 14 — each burned a whole Run before the real cause surfaced):
60
+
61
+ ```yaml
62
+ requires:
63
+ - command: bash
64
+ min: 4
65
+ - command: rg
66
+ ```
67
+
68
+ - **`reviewTriage: required` finding triage gate** — blocking review findings stop for a human to triage before a fixer is dispatched (see the [Approval guide](07-approval-guide.en.md)).
69
+
70
+ ## Review round budget
71
+
72
+ Review rounds have a single cap: `budgets.reviewRoundsPerWork` (default 6), counted across every Run of this Work (see "Review rounds counted per Work" below). A review step without an explicit `maxAttempts` takes that value as its per-Run cap, so the per-Run cap never fires before the Work cap; at the cap the Run stops at `WAITING_HUMAN` with the reason stating that the budget is exhausted. `budgets.maxAttempts.review` in the run config still sets a separate per-Run cap. (The official preset used to carry the campaign charter's "at most 2 review rounds per Run", which made a Run that needed two or three ordinary fix rounds ask for an extension again and again, and two caps had to be lifted together.)
73
+
74
+ **Stop on non-convergence**: below the cap, each time review reports blocking findings, before a fixer is dispatched, the kernel compares this round's P0/P1 findings with the earlier rounds of this Run. If a finding comes back after it was already sent to a fixer (same fingerprint; dismissed ones do not count), or this round has more blocking findings than the last one, the Run stops at `enter-fix` (kind `review-not-converging`) and the reason names the returning fingerprints or the two counts. Approving means fix + re-verify + one more review round and leaves the budget alone; `findings adjudicate --action dismiss` first stops a finding that should not block. While every blocking finding is new and there are no more of them than last round, the loop continues without asking anyone. With `reviewTriage: required` the triage stop happens as before and carries the non-convergence reason; at the cap the stop is still `budget`, with the same reason added.
75
+
76
+ For the `resume-<step>` a Run stops at once the budget is exhausted, **a human approval grants one more attempt**: the kernel records a `BUDGET_EXTENDED` (a ledger fact, replayable), raises that step's limit by one and runs it; a rejection ends the Run. Before this, approving only made the same request come straight back (two pilot app-login Runs ended CANCELLED because of it, although their candidates were already in production).
77
+
78
+ The run config can override the preset (run config > preset > global `maxAttemptsPerStep`):
79
+
80
+ ```yaml
81
+ budgets:
82
+ maxAttempts:
83
+ review: 3
84
+ verify: 6
85
+ reviewRoundsPerWork: 6 # review rounds counted across every Run of this Work (superseded ones included); see the overview guide
86
+ ```
87
+
88
+ `doctor` prints each step's effective limit and where it comes from (run config / workflow preset / default).
89
+
90
+ **Review rounds counted per Work (iteration 09)**: a per-Run budget cannot stop "a new Run for every round" — one pilot Work ran 21 Runs and 9 review rounds without the 2-round cap ever triggering. `budgets.reviewRoundsPerWork: N` (N = 6 when unset) makes the kernel count, before a review step starts, the review rounds of **every** Run of this Work (superseded ones and ones already compacted into a run-record included); at N it stops at `WAITING_HUMAN` (kind `work-review-cap`, transition `enter-review`): approving grants one more review round (ledger `BUDGET_EXTENDED scope=work`), rejecting means merging or closing on the evidence at hand. Each Work in `overview` gets a line `cost: review rounds · findings · human waits · worker time`, and the run-record carries a `cost` block — read that line before deciding "continue or cut"; the stop-loss line in the intent (at most so many Runs / review rounds / hours) is checked against it.
91
+
92
+ ## Worktrees live inside the repository: keep `.buildbeat/` out of test collection
93
+
94
+ > Since 2.0.0-beta.5 (iteration 09).
95
+ A Run's isolated worktree is `<repo>/.buildbeat/worktrees/<RUN>/`, and the runtime ledgers are in `<repo>/.buildbeat/runtime/`. Neither enters git (the template `.gitignore` excludes them; tools that honour `.gitignore`, such as `rg` and `gitleaks`, no longer walk into them), but **test frameworks collect tests from the file system**: after a pilot merge, the mainline vitest also ran the old candidates' tests in leftover worktrees, and the noise lasted until `gc`. Add to the project:
96
+
97
+ - vitest: `test.exclude: ['**/node_modules/**', '**/.buildbeat/**']`
98
+ - jest: `testPathIgnorePatterns: ['/node_modules/', '/.buildbeat/']`
99
+ - pytest: `norecursedirs = .buildbeat`
100
+ - Maven / Gradle only collect `src/**` and are unaffected; point Playwright's `testDir` at a specific directory.
101
+
102
+ When `start` is refused with "another run is active", the CLI now prints the Run holding the lock, the step it is at, how long ago its last event was, and a copyable `status` command. By default a repository still drives one Run at a time; since 3.2.0, Works whose tests do not compete for ports or databases can set `parallel: true` in their run config and drive in parallel (see "Parallel runs" above).
103
+
104
+ ## Infrastructure failures and candidate defects are counted apart
105
+
106
+ > Since 2.0.0-beta.5 (iteration 09).
107
+ A worker's timeout, crash or non-envelope output, and a worker that deliberately ends with exit code **75** (the convention: verify / a wrapper script that finds the environment unsatisfied — a command not on PATH, a port taken, a backend 404, a sandbox forbidding listening — exits 75), are all classified by the kernel as `infra`: `STEP_FINISHED.data.infra = true`, no failure fingerprint, no fixer, the step's budget is not charged (refunded through `steps[step].infraAttempts`), and the Run stops at `WAITING_HUMAN` (kind `infra`). A human approving `resume-<step>` reruns it; a rejection ends it. Any other non-zero exit is still a candidate failure and takes the `on: failed` edge.
108
+
109
+ A failure with no transition edge (`failed` of build, review and fix in the preset) is no longer terminal either: the Run stops at `resume-<step>` for a human to decide. The only terminal FAILED left is a policy `BLOCK`.
110
+
111
+ ## Run config sections added in iteration 08
112
+
113
+ - **`envelope:`** — `prompts:` (a directory, relative to the run config) + `vars:` (`{vars.x}` substitution) + optional `pin: <sha>` (read the prompts from that commit, freezing the envelope). The kernel takes the prompt `<component>-<worker>.md` → `<worker>.md`, writes it to `runs/<RUN>/prompts/<step>-<n>.md`, and hands it to the worker as `BUILDBEAT_PROMPT` (the path) and `input.envelope` (`promptRef / file / digest / vars`); worker args may use `{prompt}` and `{vars.x}`. `RUN_CREATED` records the `envelopeDigest`.
114
+ - **`start --attempt new`** — `run:` names the family (`RUN-X`) and the kernel numbers it `RUN-X-01/02…` (scanning the runtime plane and the Git-plane run-records, so deleting the runtime does not reuse a number); older waiting Runs of the same Work are superseded automatically ([Approval guide](07-approval-guide.en.md)).
115
+ - **`cache:`** — `verify: tree`: a verify with the same `HEAD^{tree}` + the same worker command + the same envelope digest that **already passed** reuses its evidence (ledger `reused`, `status` marks `(reused from RUN-X)`); failures and dirty trees are never reused. Do not turn it on for a project whose verifier depends on things outside the tree (remotes, time).
116
+ - **Incremental review** — the input of a readonly step carries `lastReviewed {candidate, run, evidenceRef, range}` (the candidate of this Work's latest review, when it is an ancestor of the current candidate); the reviewer prompt may ask to review only the diff within `range`, with anchored verdicts as before (`anchor`).
117
+ - **`redact:`** — a list of regular expressions; evidence logs are replaced with `<REDACTED>` before they are written; the digest binds the redacted text. Live streams (`.live`) are not redacted and are deleted when the step ends.
118
+ - **`probe:` entries in `requires:`** — `probe: <shell command>` + optional `expect: <regex>` + `name:`; a non-zero exit or a non-matching output fails closed, reported together with the binary version items. Turn environment facts you ran into (Redis ≥ 7, the target machine's Python version, a reachable port) into probes so the next window does not rediscover them; narrative facts go in `delivery/work/<ID>/env-facts.md`.
119
+ - **Step `grade:`** — a workflow step can declare the grade of its command evidence (L0–L4, default L2).
120
+
121
+ ## The release readback lane: `release-readback` + `riskPreset: release`
122
+
123
+ The kernel has no deployment capability (invariant 20); production actions are always a human's. This lane only records the **readbacks** before and after the action as L4 ledger entries: `preflight` (read-only checks before the action) → stop at `enter-apply-readback` (the human performs the action) → `apply-readback` (proves the action took effect) → `observe` (proves health) → `wait-close` (the human closes the window). All three readback steps are `readonly`, `grade: L4`, `maxAttempts 1`: any failing step stops for a human; there is no fix edge. The risk preset `release` provides `stopAt: apply-readback` and the window-closing evidence gate (L4 command evidence). The worker is any readback script (curl a health endpoint, read a version, compare a config fingerprint), and its exit code is the verdict. The forty hand-made readback commits of a pilot project's release day are exactly what this lane is for.
124
+
125
+ ## Change discipline
126
+
127
+ The preset is part of the product: before changing `software-delivery.yaml`, first ask whether this is a project difference — a project difference uses its own workflow file (point the run config's `workflow:` at it) and leaves the official preset alone. The schema is additive-only; a breaking change bumps `version`.
@@ -1,5 +1,7 @@
1
1
  # Workflow 编写指南
2
2
 
3
+ **简体中文** | [English](02-workflow-guide.en.md)
4
+
3
5
  权威:[`RFC-0003 §2`](../RFC-0003-workflow-policy.md);实现:`src/v2/engine/workflow.js`。官方预设 [`software-delivery.yaml`](../../../src/v2/presets/software-delivery.yaml) 是最好的范本。
4
6
 
5
7
  ## 形状
@@ -67,7 +69,9 @@ run 配置还可声明(beta.3,皆来自三十轮部署战役的真实事故
67
69
 
68
70
  ## review 轮数预算
69
71
 
70
- 官方预设自带 `budgets.maxAttempts.review: 2`(战役章程"每 Run 2 轮 review 封顶"的原生化):第三轮 review 在启动前即停 `WAITING_HUMAN`,理由写明预算耗尽。机制就是每步 `maxAttempts`,无需新概念。
72
+ review 轮数只有一个上限:`budgets.reviewRoundsPerWork`(默认 6),跨本 Work 所有 Run 累计(见下文"按 Work 累计的 review 轮数")。review 步没有显式 `maxAttempts` 时,它在单个 Run 内的上限就取这个值,所以不会先于 Work 上限触发;到顶时停 `WAITING_HUMAN`,理由写明预算耗尽。仍可在 run 配置里写 `budgets.maxAttempts.review` 另设每 Run 上限。(官方预设曾自带战役章程的"每 Run 2 轮封顶",正常修两三轮的 Run 也要一次次批准扩额,两层上限还要合并放行。)
73
+
74
+ **按收敛止损**:上限之内,review 每次发现阻断问题、派 fixer 之前,内核把本轮 P0/P1 与本 Run 之前各轮比较。只要有 finding 在修过之后又出现(指纹相同;已 dismiss 的不算),或者本轮阻断数多于上一轮,就停 `enter-fix`(kind `review-not-converging`),理由列出又出现的指纹或前后两轮的数量。批准 = 修复 + 重验 + 再审一轮,不动预算;也可以先 `findings adjudicate --action dismiss`,让不该阻断的 finding 不再阻断。阻断 finding 都是新的、数量也不多于上一轮时,自动继续,不问人。开了 `reviewTriage: required` 时照常停分诊,不收敛的理由并入同一条请求;到顶时仍是 `budget` 停车,同样附上不收敛的理由。
71
75
 
72
76
  预算耗尽后停的那次 `resume-<step>`,**人批准即多给一次**:内核落一条 `BUDGET_EXTENDED`(台账事实,可重放),该步上限 +1 再跑;拒绝即终止 Run。此前批准只会让同一请求立刻回来(试点两条应用登录 Run 因此以 CANCELLED 收场,候选却已在生产)。
73
77
 
@@ -83,7 +87,7 @@ budgets:
83
87
 
84
88
  `doctor` 打印每步生效的上限与来源(run config / workflow preset / default)。
85
89
 
86
- **按 Work 累计的 review 轮数(迭代 09)**:每 Run 的预算挡不住"每轮一个新 Run"——试点一个 Work 跑了 21 个 Run、9 轮 review,2 轮封顶从未触发。`budgets.reviewRoundsPerWork: N` 让内核在 review 步起跑前统计本 Work **所有** Run(含已作废、含已压成 run-record 的)的 review 轮数,达到 N 即停 `WAITING_HUMAN`(kind `work-review-cap`,transition `enter-review`):批准即再审一轮(台账 `BUDGET_EXTENDED scope=work`),拒绝则按手头证据合并或关闭。`overview` 每个 Work 多一行 `cost: review rounds · findings · human waits · worker 时长`,run-record 也带 `cost` 块——"继续还是砍"之前先看这一行;intent 里的止损线(最多几个 Run / 几轮 review / 几小时)就对着它核。
90
+ **按 Work 累计的 review 轮数(迭代 09)**:每 Run 的预算挡不住"每轮一个新 Run"——试点一个 Work 跑了 21 个 Run、9 轮 review,2 轮封顶从未触发。`budgets.reviewRoundsPerWork: N`(不写时 N = 6)让内核在 review 步起跑前统计本 Work **所有** Run(含已作废、含已压成 run-record 的)的 review 轮数,达到 N 即停 `WAITING_HUMAN`(kind `work-review-cap`,transition `enter-review`):批准即再审一轮(台账 `BUDGET_EXTENDED scope=work`),拒绝则按手头证据合并或关闭。`overview` 每个 Work 多一行 `cost: review rounds · findings · human waits · worker 时长`,run-record 也带 `cost` 块——"继续还是砍"之前先看这一行;intent 里的止损线(最多几个 Run / 几轮 review / 几小时)就对着它核。
87
91
 
88
92
  ## 工作树在仓内:把 `.buildbeat/` 排除出测试收集
89
93
 
@@ -95,7 +99,7 @@ Run 的隔离工作树在 `<repo>/.buildbeat/worktrees/<RUN>/`,运行时台账
95
99
  - pytest:`norecursedirs = .buildbeat`
96
100
  - Maven / Gradle 只收集 `src/**`,不受影响;Playwright 的 `testDir` 指到具体目录即可。
97
101
 
98
- `start` 被「another run is active」挡住时,CLI 现在打印持锁的 Run、它在哪一步、最后一次事件多久前,以及可复制的 `status` 命令;仓级单活动 Run 锁本身没放开——工作树已隔离,锁只剩台账与合并安全的意义,等真出现第二次多小时排队再动。
102
+ `start` 被「another run is active」挡住时,CLI 现在打印持锁的 Run、它在哪一步、最后一次事件多久前,以及可复制的 `status` 命令。默认仍是一个仓库同时驱动一个 Run;自 3.2.0 起,测试互不抢端口/数据库的 Work 可在 run 配置里打开 `parallel: true` 并行驱动(见上文「并行 Run」)。
99
103
 
100
104
  ## 基础设施故障与候选缺陷分开算
101
105
 
@@ -0,0 +1,55 @@
1
+ # Policy guide
2
+
3
+ [简体中文](03-policy-guide.md) | **English**
4
+
5
+ Authority: [`RFC-0003 §4`](../RFC-0003-workflow-policy.md) (Chinese); implementation: `src/v2/policy/policy.js`. Models: the policies embedded in the risk presets (`src/v2/presets/risk/*.yaml`) and [`ui-render-gate.yaml`](../../../src/v2/presets/policies/ui-render-gate.yaml).
6
+
7
+ ## The shape of a policy
8
+
9
+ ```yaml
10
+ kind: policy
11
+ version: 1
12
+ name: merge-evidence-floor
13
+ type: transition # pre | post | transition | action
14
+ appliesTo: enter-wait-merge # pre/post: a step id; transition: enter-<step>
15
+ enforcement: LOCAL_ENFORCED # ADVISORY | LOCAL_ENFORCED | SERVER_ENFORCED
16
+ rule:
17
+ all:
18
+ - evidence.exists:
19
+ kind: command
20
+ minGrade: L2
21
+ - finding.maxSeverity:
22
+ atMost: P2
23
+ ```
24
+
25
+ ## 8 operators and three-valued logic
26
+
27
+ | Operator | Meaning |
28
+ |---|---|
29
+ | `all` / `any` / `not` | Combinators |
30
+ | `evidence.exists: {kind, minGrade}` | Evidence of the given kind exists at or above the grade |
31
+ | `artifact.accepted: {artifact}` | The artifact (plan/intent/spec) has been accepted, bound to its digest |
32
+ | `attempts.lt: {step, max}` | A step's attempts are below the limit |
33
+ | `budget.remaining: {kind}` | The budget still has room |
34
+ | `candidate.clean` | A pinned, clean candidate exists |
35
+ | `human.approved: {transition}` | A matching approval exists and is not stale |
36
+ | `finding.maxSeverity: {atMost}` | Unresolved findings are at most this severe |
37
+
38
+ Evaluation is **three-valued**: `PASS` / `FAIL` / `UNVERIFIED`. Missing data (no evidence, no candidate) is always `UNVERIFIED`, never a pass — `UNVERIFIED` is never treated as `PASS` at any gate (the six GateResult values are in RFC-0003 §3.3).
39
+
40
+ **Candidate scope**: when the subject awaiting approval carries a candidate, `evidence.exists` and `finding.maxSeverity` count only that candidate's evidence — old review findings superseded by a newer fix round do not block a candidate that has been fixed (the permanent regressions for the real incident of 2026-08-28 live in `tests/` and `evals/`).
41
+
42
+ ## Four hook points
43
+
44
+ - `pre`: before a step starts (for example `plan-accepted` in front of build);
45
+ - `post`: after a step ends;
46
+ - `transition`: at the moment of a state transition (for example a re-check at the instant the merge decision is stamped — the approve command re-reads the live state before stamping, and refuses to stamp if the gate fails);
47
+ - `action`: before a protected action (together with [Security boundaries](09-security-boundaries.en.md)).
48
+
49
+ ## Enforcement levels
50
+
51
+ `ADVISORY` only informs the worker; `LOCAL_ENFORCED` is carried out physically by the Runner/Workspace (use it for everything that can be guaranteed locally); `SERVER_ENFORCED` declares that the gate lives on the server (branch protection / CI / deployment platform) — the Runner records it but cannot guarantee it on the server's behalf. Label honestly: do not mark as LOCAL what cannot be stopped locally.
52
+
53
+ ## Wiring it in
54
+
55
+ The run config's `policies:` list references file paths; the risk presets (`fast` / `standard` / `controlled` / `release`) bring their own policies and stop points, turned on with a single `riskPreset:` line, and project policies are layered on top.
@@ -1,5 +1,7 @@
1
1
  # Policy 指南
2
2
 
3
+ **简体中文** | [English](03-policy-guide.en.md)
4
+
3
5
  权威:[`RFC-0003 §4`](../RFC-0003-workflow-policy.md);实现:`src/v2/policy/policy.js`。范本:risk 预设内嵌策略(`src/v2/presets/risk/*.yaml`)与 [`ui-render-gate.yaml`](../../../src/v2/presets/policies/ui-render-gate.yaml)。
4
6
 
5
7
  ## 一条 Policy 的形状