@haiyangbg/buildbeat 3.0.1 → 3.2.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 (38) hide show
  1. package/CHANGELOG.md +40 -9
  2. package/SKILL.md +23 -300
  3. package/docs/CAPABILITY-MATRIX.md +1 -1
  4. package/docs/README.md +5 -5
  5. package/docs/RELEASING.md +5 -5
  6. package/docs/v2/RFC-0001-product-definition.md +5 -5
  7. package/docs/v2/RFC-0002-domain-model.md +1 -1
  8. package/docs/v2/RFC-0003-workflow-policy.md +2 -2
  9. package/docs/v2/SPEC-0001-events-v1.md +1 -1
  10. package/docs/v2/guide/01-quickstart.en.md +3 -1
  11. package/docs/v2/guide/01-quickstart.md +3 -1
  12. package/docs/v2/guide/02-workflow-guide.md +15 -0
  13. package/docs/v2/guide/07-approval-guide.en.md +4 -2
  14. package/docs/v2/guide/07-approval-guide.md +14 -2
  15. package/docs/v2/guide/09-security-boundaries.md +1 -1
  16. package/docs/v2/guide/10-recovery.en.md +21 -5
  17. package/docs/v2/guide/10-recovery.md +21 -5
  18. package/docs/v2/skill/01-principles.md +26 -0
  19. package/docs/v2/skill/02-project-layout.md +31 -0
  20. package/docs/v2/skill/03-collaboration-rules.md +45 -0
  21. package/docs/v2/skill/04-rhythm-and-rituals.md +102 -0
  22. package/docs/v2/skill/05-red-lines.md +13 -0
  23. package/docs/v2/skill/06-bootstrap-and-takeover.md +84 -0
  24. package/docs/v2/skill/07-templates-and-lessons.md +24 -0
  25. package/package.json +5 -14
  26. package/src/v2/cli/run-config-check.js +229 -0
  27. package/src/v2/cli/run.js +81 -15
  28. package/src/v2/engine/reducer.js +6 -0
  29. package/src/v2/engine/yaml-subset.js +52 -11
  30. package/src/v2/presets/policies/ui-render-gate.yaml +1 -1
  31. package/src/v2/runtime/decisions.js +33 -31
  32. package/src/v2/runtime/gc.js +59 -18
  33. package/src/v2/runtime/metrics.js +3 -2
  34. package/src/v2/runtime/orchestrator.js +308 -106
  35. package/src/v2/storage/event-ledger.js +22 -3
  36. package/src/v2/workspace/workspace-manager.js +244 -16
  37. package/templates/v2/CLAUDE.md +1 -1
  38. package/templates/v2/run-config.example.yaml +5 -1
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.0.1`; latest independently verified BuildBeat npm distribution `@haiyangbg/buildbeat@3.0.0` (dist-tag `latest`; `next` stays `2.0.0-beta.5`), anchored by annotated tag `v3.0.0` at commit `0289415`, workflow run [34362068004](https://github.com/HaiYangBG1/BuildBeat/actions/runs/34362068004), and archived in [`V3.0.0-RELEASE-EVIDENCE-2026-09-09.md`](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`](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`](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`](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`](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`](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`](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`](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`](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/`.
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/`.
6
6
 
7
7
  ## Channels and branches
8
8
 
@@ -32,11 +32,11 @@ Run from the exact release candidate:
32
32
 
33
33
  ```bash
34
34
  npm ci --ignore-scripts --no-audit --no-fund
35
- bash -n .github/scripts/*.sh templates/scripts/*.sh tests/*.sh
36
- shellcheck -x .github/scripts/*.sh templates/scripts/*.sh tests/*.sh
35
+ bash -n .github/scripts/*.sh tests/*.sh
36
+ shellcheck -x .github/scripts/*.sh tests/*.sh
37
37
  actionlint .github/workflows/*.yml
38
38
  bash tests/check-docs.sh
39
- bash tests/test-scripts.sh
39
+ npm run test:pilot
40
40
  npm run test:plugin
41
41
  npm test
42
42
  npm publish --dry-run --access public --registry=https://registry.npmjs.org/
@@ -131,7 +131,7 @@ The bootstrap `0.0.0` package is not retroactively provenance-backed and must re
131
131
  Publishing the artifact is one surface. These are the others; each has drifted at least once, so tick them in the same sitting as the release (the docs check catches most of them, the two GitHub-side items it cannot):
132
132
 
133
133
  - [ ] `CHANGELOG.md`: `## Unreleased` renamed to the version with date and the publication paragraph (run id, dist-tag, readback).
134
- - [ ] `docs/<VERSION>-RELEASE-EVIDENCE-<date>.md` archived; the current-state paragraph at the top of this runbook names the new version and the previous stable moves to a dated past tense — never two "current" versions in one runbook.
134
+ - [ ] `docs/releases/<VERSION>-RELEASE-EVIDENCE-<date>.md` archived; the current-state paragraph at the top of this runbook names the new version and the previous stable moves to a dated past tense — never two "current" versions in one runbook.
135
135
  - [ ] `README.md` / `README.en.md`: version and channel claims (`@latest` is what it says it is), no `@next` install line unless a pre-release is being announced as such.
136
136
  - [ ] `SKILL.md` §0.5 install line and `docs/v2/guide/01-quickstart.md` install line: stable channel.
137
137
  - [ ] `docs/CLI.md` status line, `docs/CAPABILITY-MATRIX.md` status line and distribution section.
@@ -2,8 +2,8 @@
2
2
 
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
- > 上游:[`V2-PLAN.md`](../V2-PLAN.md)(执行基线,`V2-D0=B`);内核范围:完整内核(`V2-D2=A`,[`V2-DECISIONS.md`](../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`](../V2-ITERATION-01.md)
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)
7
7
 
8
8
  ---
9
9
 
@@ -11,7 +11,7 @@
11
11
 
12
12
  > **BuildBeat v2 是一个工件驱动的 AI 交付闭环。确定性内核按 Workflow 与 Policy 推进状态,外部 Agent 作为 Worker 执行计划、构建、验证、修复与审查;一切完成以 Runner 回读的真实证据为准;人只在不可委托的判断点被请求最小决策。协议(工件 + 证据 + 决策)永远人机可读、落在 Git——Runner 是引擎,不是协议存在的前提。**
13
13
 
14
- 对外定位词可用"AI 原生交付控制面 / 交付闭环"([`V2-PLAN.md`](../V2-PLAN.md) 裁决 #9、D1);**产品之魂是协议**:厂商 runtime 正在被商品化,协议 + 参考实现才是可防守的位置。
14
+ 对外定位词可用"AI 原生交付控制面 / 交付闭环"([`V2-PLAN.md`](../history/V2-PLAN.md) 裁决 #9、D1);**产品之魂是协议**:厂商 runtime 正在被商品化,协议 + 参考实现才是可防守的位置。
15
15
 
16
16
  MVP 核心承诺:
17
17
 
@@ -41,7 +41,7 @@ M-1 的核心教训(卡点 1、卡点 5):协议工件齐备但没有 Runne
41
41
 
42
42
  ## 5. 手工模式的地位
43
43
 
44
- v1 的"Skill-only 完整等价"拆成两个承诺([`V2-PLAN.md`](../V2-PLAN.md) §5):
44
+ v1 的"Skill-only 完整等价"拆成两个承诺([`V2-PLAN.md`](../history/V2-PLAN.md) §5):
45
45
 
46
46
  | 承诺 | v2 处置 |
47
47
  |---|---|
@@ -52,7 +52,7 @@ v1 的"Skill-only 完整等价"拆成两个承诺([`V2-PLAN.md`](../V2-PLAN.md
52
52
 
53
53
  > **生效修订(2026-09-09)**:3.0.0 起 v1 文件总线、生命周期命令(`buildbeat doctor/init/adopt/upgrade`)、`solobaton` 与 `buildbeat-v2` 可执行文件全部移除,`buildbeat` 即运行时;`v1-maintenance` 维护线结束,最后一个带 v1 的版本是 2.0.2。本节原文保留为决策记录。
54
54
  >
55
- > **生效修订(2026-09-05)**:下段"`latest` 留 v1"是 beta 期策略,已按计划结束——`@haiyangbg/buildbeat@2.0.0` 于 2026-09-05 发布到 `latest`([`CHANGELOG.md`](../../CHANGELOG.md)、[发布证据](../V2.0.0-RELEASE-EVIDENCE-2026-09-05.md))。此后 `latest` = v2 系列,`next` 仅用于后续预发布;v1 生命周期命令随同一个包分发,v1 骨架版本仍是 v1.21。原文保留为决策记录。
55
+ > **生效修订(2026-09-05)**:下段"`latest` 留 v1"是 beta 期策略,已按计划结束——`@haiyangbg/buildbeat@2.0.0` 于 2026-09-05 发布到 `latest`([`CHANGELOG.md`](../../CHANGELOG.md)、[发布证据](../releases/V2.0.0-RELEASE-EVIDENCE-2026-09-05.md))。此后 `latest` = v2 系列,`next` 仅用于后续预发布;v1 生命周期命令随同一个包分发,v1 骨架版本仍是 v1.21。原文保留为决策记录。
56
56
 
57
57
  v1 进入 `v1-maintenance` 维护线,只修安全与严重缺陷;npm `latest` 留 v1,`next` 发 v2 预发布;Beta 前 `latest` 不指向 v2。v1 迁移采用半天手工 runbook(装机量 N=1),`migrate-v1` importer 已裁掉(收尾修正三)。旧概念的保留/转换/删除逐项见 [`RFC-0002`](RFC-0002-domain-model.md) §8。
58
58
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 状态:`FINAL`(2026-08-28 项目所有者定稿,`V2-D3`)
4
4
  > 日期:2026-08-28
5
- > 上游:[`V2-PLAN.md`](../V2-PLAN.md) §3–4;报告 B §6 / §13 / §14(经基线裁决修订)
5
+ > 上游:[`V2-PLAN.md`](../history/V2-PLAN.md) §3–4;报告 B §6 / §13 / §14(经基线裁决修订)
6
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)
7
7
 
8
8
  ---
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 状态:`FINAL`(2026-08-28 项目所有者定稿,`V2-D3`;§8 observe/bands schema 已随定稿冻结)
4
4
  > 日期:2026-08-28
5
- > 上游:[`V2-PLAN.md`](../V2-PLAN.md) §3.2–3.7;报告 B §8–11 / WP1.4–1.5 / WP4.3–4.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
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)
7
7
 
8
8
  ---
@@ -57,7 +57,7 @@ budgets:
57
57
  Intent → [Spec] → Plan → Build → Verify ⇄ Fix → Independent Review ⇄ Fix → WAIT_HUMAN(merge)
58
58
  ```
59
59
 
60
- - **Spec 步默认可选;识别到 UI/视觉/交互交付时强制**,且其 Approval subject 必须包含可渲染入口 + 截图 digest(不变量 22;v1 lessons #3 的 v2 化,经 [`V2-PLAN.md`](../V2-PLAN.md) 裁决 #3)。
60
+ - **Spec 步默认可选;识别到 UI/视觉/交互交付时强制**,且其 Approval subject 必须包含可渲染入口 + 截图 digest(不变量 22;v1 lessons #3 的 v2 化,经 [`V2-PLAN.md`](../history/V2-PLAN.md) 裁决 #3)。
61
61
  - 各步执行规则照报告 B §8.1:Builder 只写授权 Workspace;Fixer 输入必须含失败命令/退出码/日志摘要/candidate/允许范围,不接受泛化的"再检查一下";Reviewer fresh-context、默认只读、不改代码、产出结构化 findings(不变量 9)。
62
62
  - MVP 到 merge 决定即暂停,不自动合并(不变量 20)。
63
63
 
@@ -1,6 +1,6 @@
1
1
  # SPEC-0001:Event Ledger 格式 v1(冻结)
2
2
 
3
- > 状态:**FROZEN**(2026-08-28 项目所有者定稿,`V2-D3`;[`V2-PLAN.md`](../V2-PLAN.md) 裁决 #8:格式 day-1 冻结,此后 additive-only)
3
+ > 状态:**FROZEN**(2026-08-28 项目所有者定稿,`V2-D3`;[`V2-PLAN.md`](../history/V2-PLAN.md) 裁决 #8:格式 day-1 冻结,此后 additive-only)
4
4
  > 日期:2026-08-28
5
5
  > 冻结范围:**信封字段、通用规则、损坏处理、reducer 合同、初始事件类型注册表的语义**。文件摆放位置、快照格式、CLI 展示均为非规范内容,可变。
6
6
  > 演进规则:一切修改 **additive-only**(新增可选字段、新增事件类型);破坏性变更必须升 `v` 并提供旧版读取器。
@@ -35,7 +35,7 @@ The envelope must be committed: workers run in an isolated worktree and only see
35
35
 
36
36
  ## 2. Write the run config
37
37
 
38
- `delivery/work/WORK-DEMO-1/run-config.yaml`. Paths resolve relative to **this file**; the YAML is a strict subset: block lists and block maps only, no inline `[]` / `{}`, no anchors, comments on their own line. The config below parses as-is (machine-checked by `tests/v2-templates-firstrun.test.js`); the full sample and the envelope templates are in [`templates/v2/`](../../../templates/v2/run-config.example.yaml).
38
+ `delivery/work/WORK-DEMO-1/run-config.yaml`. Paths resolve relative to **this file**; the YAML is a strict subset: block lists and block maps only (list items may sit at their key's indentation), inline only for the empty `[]` / `{}`, no anchors, comments on their own line; quote a list item that contains `": "`. The config below parses as-is (machine-checked by `tests/v2-templates-firstrun.test.js`); the full sample and the envelope templates are in [`templates/v2/`](../../../templates/v2/run-config.example.yaml).
39
39
 
40
40
  ```yaml
41
41
  repo: ../../..
@@ -139,6 +139,8 @@ buildbeat approve --repo . --run RUN-DEMO-01 --transition enter-wait-merge --by
139
139
 
140
140
  **Be clear about which step you are approving** ([Approval guide](07-approval-guide.en.md)): `enter-wait-merge` is the merge decision; the Run reaches the terminal state `SUCCEEDED`, meaning the candidate is fit to merge. The actual merge, push and release are always your actions outside the Runner. `enter-fix` / `resume-<step>` are non-terminal transitions: after approving them, `resume --config …` lets the Run continue. When findings block, the Run routes fix → verify → review on its own; when the budget is exhausted or a failure fingerprint repeats, it stops and hands back to you ([Recovery](10-recovery.en.md)).
141
141
 
142
+ After `start --attempt new` numbers a Run, `resume --config <run-config.yaml>` resumes the family’s only non-terminal Run and prints its ID. Use `--run <RUN-ID>` to select the configured Run itself or `<family>-NN` explicitly (at least two digits). An existing ledger for the exact configured ID takes precedence. Multiple non-terminal Runs are listed with a request to select one using `--run`; if none remain, the error reports the latest ID and terminal status, or states that no ledgers were found.
143
+
142
144
  ## 7. Walk the failure branch once
143
145
 
144
146
  To see the automatic repair loop, commit a failing test case under `tests/` and `start --attempt new` again: verify fails → the fixer repairs with the failure summary → verify reruns → review. `status` shows `step fix: SUCCEEDED` and a second `verify`. To route findings through your hands first: `reviewTriage: required` together with `findings list` / `findings adjudicate` adjudicates fingerprint by fingerprint; a dismissed fingerprint no longer blocks.
@@ -35,7 +35,7 @@ git add delivery && git commit -qm "buildbeat: work WORK-DEMO-1 + envelope"
35
35
 
36
36
  ## 2. 写 run 配置
37
37
 
38
- `delivery/work/WORK-DEMO-1/run-config.yaml`。路径相对**本文件**解析;YAML 是严格子集:只有块列表与块映射,没有行内 `[]` / `{}`、没有锚点、注释必须独占一行。下面这份可以原样解析(机器验证在 `tests/v2-templates-firstrun.test.js`);完整样板与信封模板在 [`templates/v2/`](../../../templates/v2/run-config.example.yaml)。
38
+ `delivery/work/WORK-DEMO-1/run-config.yaml`。路径相对**本文件**解析;YAML 是严格子集:只有块列表与块映射(列表项可与键同缩进),行内只允许空的 `[]` / `{}`,没有锚点,注释必须独占一行;含 `": "` 的列表项要加引号。下面这份可以原样解析(机器验证在 `tests/v2-templates-firstrun.test.js`);完整样板与信封模板在 [`templates/v2/`](../../../templates/v2/run-config.example.yaml)。
39
39
 
40
40
  ```yaml
41
41
  repo: ../../..
@@ -139,6 +139,8 @@ buildbeat approve --repo . --run RUN-DEMO-01 --transition enter-wait-merge --by
139
139
 
140
140
  **批的是哪一步要分清**([Approval 指南](07-approval-guide.md)):`enter-wait-merge` 是合并决定,Run 进终态 `SUCCEEDED`,表示候选具备合并条件——真正的合并、push、发布永远是你在 Runner 之外的动作;`enter-fix` / `resume-<step>` 是非终态转换,批准后要 `resume --config …` 让它续跑。被 findings 阻断时会自动路由 fix→verify→review 重走,超预算或失败指纹重复则停下交还给你([Recovery](10-recovery.md))。
141
141
 
142
+ 使用 `start --attempt new` 自动编号时,`resume --config <run-config.yaml>` 会续跑该家族唯一未终态的 Run,并打印选中的 ID;也可用 `--run <RUN-ID>` 显式指定配置中的 Run 本身或 `<家族>-NN`(数字至少两位)。配置本身已有台账时优先使用该精确 ID。多个未终态 Run 会列出候选并要求用 `--run` 选择;没有未终态 Run 会报告最新一次的 ID 和终态,没有台账则明确说明。
143
+
142
144
  ## 7. 走一次失败分支
143
145
 
144
146
  想看自动修复闭环,把一条会失败的用例提交进 `tests/`,再 `start --attempt new`:verify 失败 → fixer 带着失败摘要修 → verify 重跑 → review。`status` 会显示 `step fix: SUCCEEDED` 与第二次 `verify`。想让 finding 先过你的手:`reviewTriage: required` 配套 `findings list` / `findings adjudicate` 逐指纹裁决,dismiss 后同指纹不再阻断。
@@ -37,6 +37,21 @@ terminal:
37
37
 
38
38
  run 配置里 `entry` 可覆盖 workflow 的 `entry`(例如从 `build` 起步、跳过 intent/plan 步——digest 仍会绑进批准对象);`stopAt` 指定停点。workflow 文件整体做 sha256 → `RUN_CREATED.workflowDigest`,事后可证明当时跑的是哪份流程。
39
39
 
40
+ **写错时的报错形态**:`start` / `resume` / `doctor` / `preflight` / `approve --config` 读 run 配置时先整体校验,做任何事之前**一次列出全部问题**:
41
+
42
+ ```text
43
+ error: run config delivery/work/WORK-X/run-config.yaml has 3 problem(s):
44
+ - stopat: unknown key (did you mean stopAt?)
45
+ - repo: required and missing
46
+ - workers.reviwer: no step of the workflow uses this worker (did you mean reviewer?); workers in this workflow: planner, builder, verifier, reviewer, fixer
47
+ ```
48
+
49
+ 校验范围:必填键(`repo` `work` `run` `workflow` `workers`);未知顶层键与 worker / envelope 的未知字段(给最接近的拼写);类型与取值(正整数、列表、`inheritEnv` 只能是 `true` / `false`);worker 名必须被工作流步骤用到;`stopAt` / `entry` 必须是工作流步骤;`work` / `run` 只允许字母、数字、`.` `_` `-`,写成数字要加引号。
50
+
51
+ **并行 Run(`parallel: true`,默认关)**:默认一个仓库同时只驱动一个 Run,其他 Work 的 `start` 会被挡并提示在谁后面排队(真实事故:一个会话在另一个 Work 的 Run 后面等了 3 小时 23 分钟)。
52
+ run 配置写 `parallel: true` 的 Work,可以与其他同样打开开关的 Work 同时驱动;同一 Work 的 Run 永远互斥;没打开的 Run 照旧独占整个仓库——它在跑时并行 Run 起不来,并行 Run 在跑时它也起不来。
53
+ 打开前先确认:verify 不抢固定端口、不共用同一个数据库或其他外部状态,否则并行会互相打架。`doctor` 会打印当前模式;被杀进程留下的并行标记与锁一样按持有者自动回收。
54
+
40
55
  run 配置还可声明(beta.3,皆来自三十轮部署战役的真实事故):
41
56
 
42
57
  - **`requires:` 环境契约**——信封隐式依赖的二进制与最低版本,Run 启动前 fail-closed 全量核验,一次报清所有问题(真实事故:`rg` 只在某会话 vendored PATH、`/bin/bash` 3.2、新 shell 解析到 Node 14,各烧掉整轮 Run 才见真因):
@@ -34,13 +34,15 @@ Sessions and documents used to mix "accept / approve / resume / succeeded / merg
34
34
  | Word | Command | Meaning | Is not |
35
35
  |---|---|---|---|
36
36
  | **Accept** | `accept --artifact intent\|plan` | A human endorses one artifact's digest; editing it makes the acceptance `stale` | Starting work; it creates no Run |
37
- | **Approve a transition** | `approve --transition <t>` | Lets the Run take **this one** transition: `enter-fix` (release the fixer after triage), `resume-<step>` (run the step once more after a budget or infra stop; records `BUDGET_EXTENDED`), `enter-review` (one more review round after the Work-level cap), `enter-apply-readback` ("I have done it" on the release lane) | Approving any other transition; after a non-terminal transition is approved the Run **does not move by itself**: `resume --config <run-config>` continues it (the `next:` line printed by `approve` says so) |
37
+ | **Approve a transition** | `approve --transition <t>` | Lets the Run take **this one** transition: `enter-fix` (release the fixer after triage; when the review budget is spent, this one approval also grants the next review round, and so does answering it with a hand fix via `resume --adopt <sha>`), `resume-<step>` (run the step once more after a budget or infra stop; records `BUDGET_EXTENDED`; successful build / verify / fix attempts are not charged, and a budget stop that hits both the run and the work review cap is lifted by one approval), `enter-review` (one more review round after the Work-level cap), `enter-apply-readback` ("I have done it" on the release lane) | Approving any other transition; after a non-terminal transition is approved the Run **does not move by itself**: `resume --config <run-config>` continues it (the `next:` line printed by `approve` says so) |
38
38
  | **Merge decision** (final approval) | `approve --transition enter-wait-merge` | The candidate is fit to merge: candidate + planDigest + evidenceDigest all hold at this instant; the Run reaches the terminal state `SUCCEEDED` and the run-record is compacted into the Git plane | Code merged, pushed or deployed: those three remain your actions outside the Runner, always |
39
39
  | **Run SUCCEEDED** | — | The Run stopped where it should and the evidence is complete | The Work is finished. `overview` shows `MERGED` only after reading back that the candidate is on the current branch |
40
40
  | **Reject** | `reject --reason` | The Run ends (`FAILED`, reason recorded) | The artifacts are invalidated; the acceptance state of intent/plan does not change |
41
41
 
42
42
  Likewise, `fix_now` on an observe draft is only acceptance; a human starts the Run. Protected actions are listed under [Security boundaries](09-security-boundaries.md) (Chinese).
43
43
 
44
+ After `start --attempt new` numbers a Run, `resume --config <run-config.yaml>` resumes the family’s only non-terminal Run and prints its ID. Use `--run <RUN-ID>` to select the configured Run itself or `<family>-NN` explicitly (at least two digits). An existing ledger for the exact configured ID takes precedence. Multiple non-terminal Runs are listed with a request to select one using `--run`; if none remain, the error reports the latest ID and terminal status, or states that no ledgers were found.
45
+
44
46
  ## Risk presets decide where humans approve
45
47
 
46
48
  `fast`: Merge only; `standard`: Plan + Merge; `controlled`: Intent + Plan + Merge + Release. A pending request must carry the findings summary and the reason, which prevents the "rubber stamp" decay; human waiting time goes into `metrics`.
@@ -102,7 +104,7 @@ Notification is not an approval channel: decisions are still made only through t
102
104
  When a Run stops at `enter-fix` / `resume-fix`, the driving session or a person has often already fixed the problem in the Run's worktree and committed it. Approving at that point dispatches a fixer with nothing to do and runs verify once more (a pilot frontend Run reached its 5th verify and 3rd fix this way). Use instead:
103
105
 
104
106
  ```bash
105
- buildbeat resume --config <run-config.yaml> --adopt <sha> --by <name>
107
+ buildbeat resume --config <run-config.yaml> --run <RUN-ID> --adopt <sha> --by <name>
106
108
  ```
107
109
 
108
110
  The kernel reads the worktree back: the tree must be clean and HEAD must be exactly `<sha>` (7-character prefix or longer), otherwise it refuses; then it records `CANDIDATE_PINNED` with a human actor (`adopted: true`), records `DECISION_RECORDED` with that commit as subject (`adopted`, `resumeAt`), and continues from verify (the step after a successful fix in the preset). The ledger shows who supplied this candidate. Adoption is not accepted at the merge decision.
@@ -34,17 +34,29 @@ buildbeat accept --repo . --work WORK-X --artifact plan --by <名字> # 工
34
34
  | 词 | 命令 | 含义 | 不等于 |
35
35
  |---|---|---|---|
36
36
  | **接受**(accept) | `accept --artifact intent\|plan` | 一份工件的 digest 被人认可;改过即 `stale` | 开工;不产生任何 Run |
37
- | **批准某转换**(approve) | `approve --transition <t>` | 允许 Run 走**这一条** transition:`enter-fix`(放行分诊后的 fixer)、`resume-<step>`(预算耗尽 / infra 停人后再跑一次,会落 `BUDGET_EXTENDED`)、`enter-review`(Work 级 review 上限后再审一轮)、`enter-apply-readback`(上线车道"我做完了") | 批准了别的转换;非终态转换批准后 Run **不会自己动**,要 `resume --config <run-config>` 续跑(`approve` 输出的 `next:` 行会写明) |
37
+ | **批准某转换**(approve) | `approve --transition <t>` | 允许 Run 走**这一条** transition:`enter-fix`(放行 fixer;附带 grants 时还放行重验后的下一轮 review)、`resume-<step>`(预算耗尽后扩额,或 infra 退款后重试)、`enter-review`(Work 级 review 上限后再审一轮)、`enter-apply-readback`(上线车道"我做完了") | 批准了别的转换;非终态转换批准后 Run **不会自己动**,要 `resume --config <run-config>` 续跑(`approve` 输出的 `next:` 行会写明) |
38
38
  | **合并决定**(最终批准) | `approve --transition enter-wait-merge` | 候选已具备合并条件:candidate + planDigest + evidenceDigest 此刻全部成立;Run 进终态 `SUCCEEDED`,run-record 压进 Git 面 | 代码已合并、已 push、已部署——这三件永远是你在 Runner 之外的动作 |
39
39
  | **Run SUCCEEDED** | — | Run 停在了它该停的地方,证据齐 | Work 完成。`overview` 只有回读到候选在当前分支上才显示 `MERGED` |
40
40
  | **拒绝**(reject) | `reject --reason` | Run 终止(`FAILED`,理由入账) | 工件失效;intent/plan 的接受状态不变 |
41
41
 
42
42
  同理 observe 草稿的 `fix_now` 只是接受,Run 由人发起。保护动作见 [安全边界](09-security-boundaries.md)。
43
43
 
44
+ 使用 `start --attempt new` 自动编号时,`resume --config <run-config.yaml>` 会续跑该家族唯一未终态的 Run,并打印选中的 ID;也可用 `--run <RUN-ID>` 显式指定配置中的 Run 本身或 `<家族>-NN`(数字至少两位)。配置本身已有台账时优先使用该精确 ID。多个未终态 Run 会列出候选并要求用 `--run` 选择;没有未终态 Run 会报告最新一次的 ID 和终态,没有台账则明确说明。
45
+
44
46
  ## 人批点由 Risk Preset 决定
45
47
 
46
48
  `fast` 仅 Merge;`standard` Plan+Merge;`controlled` Intent+Plan+Merge+Release。待批项强制携带 findings 摘要与理由——防"秒批"退化;人批等待时长进 `metrics`。
47
49
 
50
+ ## 预算停车:成功不扣次数,一轮一问
51
+
52
+ - 非只读步(build / verify / fix)成功的 attempt 不扣 `maxAttempts`,只读步仍消耗次数,review 按轮计费。`STEP_FINISHED.free: true` 与 `BUDGET_CONSUMED.amount: 0` 记录退款;没有新字段的旧台账保持原有回放结果。
53
+ - 真失败到顶仍停 `resume-<step>`,同指纹两次仍停,release 预设的 `maxAttempts: 1` 仍有效。infra 故障仍不扣次数。默认预算数值不变。
54
+ - review 发现阻断问题时,若下一轮会超过 Run 或 Work 上限,立即停 `enter-fix`,在花费修复、重验之前问一次。有分诊时 kind 为 `finding-triage`,无分诊时为 `budget`。批准表示「修复 + 重新验证 + 再审一轮」;拒绝结束本 Run,由人按现有证据决定是否合并。
55
+ - 每一次预算停车(`enter-fix`、`resume-<step>`、Work 级 `enter-review`)都在请求上记可选 `grants`,列出下一次执行会撞到的 Run/Work 上限;批准一次即同时放行两层,不再连问两次。`resume` 校验批准仍有效后逐条落 `BUDGET_EXTENDED`,并把整份放行计划钉在第一条上:放行落到一半进程被杀,再次 `resume` 按钉住的计划补齐剩余项,不从已被抬高的状态重算。过期批准、新请求不继承旧 grants。会话在 worktree 里手修并用 `resume --adopt <sha>` 回答该请求时,候选虽换成新提交,仍继承请求上的 grants(grants 属于这一轮,不属于某个候选;计划变了则不继承),修完重验后直接进入下一轮 review,不再二次停车。
56
+ - 防止自定义 workflow 的成功循环失控:同一步总 attempt 达到有效上限(配置预算 + 人批扩额)的 **3 倍**后,在下一次执行前仍以 kind `budget` 兜底停人。成功/infra 退款不增加兜底上限;批准扩额会提高它。
57
+
58
+ 停车首行显示已用次数(或 review 轮数)、有效上限与真失败次数,并保留 `budget` 标识供度量使用。`status` 的 attempt 序号和 `overview` 的成本累计仍表示实际运行量,不是失败次数;`doctor` 展示配置上限。
59
+
48
60
  ## 发现分诊门与锚定审查
49
61
 
50
62
  > 自 2.0.0-beta.3(beta.3)起。
@@ -102,7 +114,7 @@ buildbeat accept --repo . --work WORK-X --artifact plan --by <名字> # 工
102
114
  Run 停在 `enter-fix` / `resume-fix` 时,驾驶会话或人常常已经在 Run 的 worktree 里把问题修掉并提交了。此时再 `approve` 会派一个无事可做的 fixer,再多跑一次 verify(试点一条前端 Run 因此跑到 verify 第 5 次、fix 第 3 次)。改用:
103
115
 
104
116
  ```bash
105
- buildbeat resume --config <run-config.yaml> --adopt <sha> --by <名字>
117
+ buildbeat resume --config <run-config.yaml> --run <RUN-ID> --adopt <sha> --by <名字>
106
118
  ```
107
119
 
108
120
  内核回读 worktree:树必须干净、HEAD 必须就是 `<sha>`(前缀 7 位起),否则拒绝;然后以人为 actor 落 `CANDIDATE_PINNED`(`adopted: true`)、以该提交为 subject 记 `DECISION_RECORDED`(`adopted`、`resumeAt`),并从 verify 继续(预设里 fix 成功后的下一步)。台账里看得出这一版候选是谁供的。合并决定处不接受 adopt。
@@ -1,6 +1,6 @@
1
1
  # 安全与权限边界
2
2
 
3
- 权威:[`RFC-0001 §保护动作`](../RFC-0001-product-definition.md)、[`V2-PLAN.md`](../../V2-PLAN.md) §9 不变量。设计哲学:**保护动作 = 能力移除**——不是"请 Agent 别做",而是让它做不到。
3
+ 权威:[`RFC-0001 §保护动作`](../RFC-0001-product-definition.md)、[`V2-PLAN.md`](../../history/V2-PLAN.md) §9 不变量。设计哲学:**保护动作 = 能力移除**——不是"请 Agent 别做",而是让它做不到。
4
4
 
5
5
  ## Runner 侧的本地边界(LOCAL_ENFORCED,均有测试)
6
6
 
@@ -2,10 +2,14 @@
2
2
 
3
3
  [简体中文](10-recovery.md) | **English**
4
4
 
5
- Design premise (invariant 23 in [`V2-PLAN.md`](../../V2-PLAN.md), Chinese): **the whole `.buildbeat/runtime/` directory can be deleted at any time**. Accepted artifacts, decisions, Intent drafts with their triage, and the compacted records of finished Runs all live in the Git plane. "Delete and rebuild" is the default troubleshooting move, not the last resort.
5
+ Design premise (invariant 23 in [`V2-PLAN.md`](../../history/V2-PLAN.md), Chinese): **the whole `.buildbeat/runtime/` directory can be deleted at any time**. Accepted artifacts, decisions, Intent drafts with their triage, and the compacted records of finished Runs all live in the Git plane. "Delete and rebuild" is the default troubleshooting move, not the last resort.
6
6
 
7
7
  ## Symptom → action
8
8
 
9
+ ### "run config … has N problem(s)"
10
+
11
+ The run config has mistakes; no Run started and nothing changed. Fix the list item by item (each names the key, what is wrong and the closest valid spelling), then rerun the same command; `buildbeat doctor --config …` checks it on its own first.
12
+
9
13
  ### The ledger reports corrupted
10
14
 
11
15
  `status`/`inbox` shows `LEDGER CORRUPTED after seq=N (<reason>)`: the ledger truncates its view at the last valid event and **refuses to append**; recovery is a human decision, nothing is repaired silently.
@@ -20,19 +24,31 @@ Design premise (invariant 23 in [`V2-PLAN.md`](../../V2-PLAN.md), Chinese): **th
20
24
  buildbeat resume --config <run-config.yaml>
21
25
  ```
22
26
 
27
+ After `start --attempt new` numbers a Run, `resume --config <run-config.yaml>` resumes the family’s only non-terminal Run and prints its ID. Use `--run <RUN-ID>` to select the configured Run itself or `<family>-NN` explicitly (at least two digits). An existing ledger for the exact configured ID takes precedence. Multiple non-terminal Runs are listed with a request to select one using `--run`; if none remain, the error reports the latest ID and terminal status, or states that no ledgers were found.
28
+
23
29
  The in-flight step is closed as `crashed` (the fact is recorded), then **the step itself is rerun** (changed in beta.3): a dead process says nothing about the candidate; the lost attempt still counts against the step's budget, and an exhausted budget stops for a human. The earlier semantics treated a crash as a step failure and followed the failure edge; real incident (deploy-18): the host tool's timeout killed the verify worker, the crash was routed to fix, and the fixer burned a round facing zero verifier evidence. A dirty worktree still stops for a human first. Resuming with approvals re-checks candidate/plan freshness and turns `APPROVAL_STALE` over to a human if anything changed. If it cannot be recovered, delete the runtime and rerun: the candidate branch and the Git-plane records are not lost.
24
30
 
25
31
  **Launch discipline** (the other half of the same incident): a Run longer than minutes must be launched in a way that escapes the host tool's timeout (`nohup`/`setsid`); `start` prints this reminder in an interactive shell.
26
32
 
27
33
  ### A stuck lock ("another run is active")
28
34
 
29
- A Run that exited abnormally may leave the repository lock behind. Once you have confirmed that no Run is really active:
35
+ Every lock records its owner (pid, host name, time acquired, command). When a driver is killed, ended by a host-tool timeout or lost to a reboot, its locks stay behind; the next `resume` / `stop` / `start` that needs the lock reclaims it automatically when the owner is **on this host and its process no longer exists**, printing `reclaimed stale lock <id> (owner pid … is gone)`. No file needs deleting by hand. After a killed driver, simply run:
30
36
 
31
37
  ```bash
32
- buildbeat stop --repo . --run RUN-X --reason "crashed; releasing lock"
38
+ buildbeat resume --config <run-config.yaml>
33
39
  ```
34
40
 
35
- `stop` records the terminal state and the reason; for a mere leftover lock you may also delete `.buildbeat/runtime/` and start over.
41
+ The kernel takes the crash-recovery path above (`RUN_INTERRUPTED`, then the interrupted step reruns); if you would rather not continue, `buildbeat stop --repo . --run RUN-X --reason "…"`.
42
+
43
+ Only three cases still answer `another run is active` / `already locked`, and the error names the owner:
44
+
45
+ - **The owner is alive**: another Run really is driving; wait for it, or end that process once you are sure it is stuck;
46
+ - **The owner is on another host** (a repository on a shared disk): deal with it on that machine; this host never reclaims it;
47
+ - **No owner record**: a lock left by an older buildbeat, or a crash in the instant of taking it; once no buildbeat process is running, delete the lock directory the error names.
48
+
49
+ ### Ledger "changed on disk since it was read"
50
+
51
+ `ledger for RUN-X changed on disk since it was read (another writer); re-read it and retry`: another session or process wrote the same Run's ledger just before you (two approvals at once, a `stop` racing a `resume`). The kernel refused this write; **the ledger was not changed and is not corrupted**. Run the same command again: it decides afresh on the current state (and may simply tell you it is already approved or terminal). Approve, reject, `--adopt`, `stop`, `resume` and supersede all read the ledger only after taking the Run lock, so ordinary use never meets this error; seeing it means there really was a concurrent operation.
36
52
 
37
53
  ### Abnormal worker behaviour
38
54
 
@@ -78,6 +94,6 @@ Terminal Runs leave worktrees, `run/*` branches and the occasional lock. `buildb
78
94
  - It touches only Runs that are **terminal and already compacted into a run-record** (the Git plane must have the record before the runtime plane is touched);
79
95
  - Worktrees may be deleted (the commits are on the branch); a dirty worktree is left alone without `--force true`;
80
96
  - A branch is deleted only when the candidate **is reachable from another ref** (merged / tagged / on the remote) or the Run produced no candidate; otherwise it reports "reachable only from this branch, kept": that branch is the last thread to the evidence;
81
- - Leftover `locks/<RUN>.lock` of terminal Runs are cleaned; the `active-run` lock is still handled by hand as above.
97
+ - Leftover `locks/<RUN>.lock` of terminal Runs are cleaned; the `active-run` lock is reclaimed too when its owner process is gone (the plan names the owner), and kept with the reason when the owner is alive, on another host, or unrecorded.
82
98
 
83
99
  gc never writes to the ledger (after the terminal state only `RUN_COMPACTED` is allowed), so it can run at any time and repeatedly.
@@ -2,10 +2,14 @@
2
2
 
3
3
  **简体中文** | [English](10-recovery.en.md)
4
4
 
5
- 设计前提([`V2-PLAN.md`](../../V2-PLAN.md) 不变量 23):**`.buildbeat/runtime/` 整个目录随时可删**——已接受工件、Decision、Intent 草稿与分诊、已终结 Run 的压实记录全部活在 Git 面。"删了重建"是默认排障手段,不是最后手段。
5
+ 设计前提([`V2-PLAN.md`](../../history/V2-PLAN.md) 不变量 23):**`.buildbeat/runtime/` 整个目录随时可删**——已接受工件、Decision、Intent 草稿与分诊、已终结 Run 的压实记录全部活在 Git 面。"删了重建"是默认排障手段,不是最后手段。
6
6
 
7
7
  ## 症状 → 处置
8
8
 
9
+ ### 「run config … has N problem(s)」
10
+
11
+ run 配置写错了,Run 没有起跑、什么都没改。按清单逐条改(每条写明是哪个键、错在哪、最接近的正确拼写),改完再跑同一条命令;`buildbeat doctor --config …` 可以先单独核对。
12
+
9
13
  ### 台账报 corrupted
10
14
 
11
15
  `status`/`inbox` 出现 `LEDGER CORRUPTED after seq=N (<原因>)`:台账在最后一条合法事件处截断视图并**拒绝追加**——恢复是人的决定,不静默修复。
@@ -20,19 +24,31 @@
20
24
  buildbeat resume --config <run-config.yaml>
21
25
  ```
22
26
 
27
+ 使用 `start --attempt new` 自动编号时,`resume --config <run-config.yaml>` 会续跑该家族唯一未终态的 Run,并打印选中的 ID;也可用 `--run <RUN-ID>` 显式指定配置中的 Run 本身或 `<家族>-NN`(数字至少两位)。配置本身已有台账时优先使用该精确 ID。多个未终态 Run 会列出候选并要求用 `--run` 选择;没有未终态 Run 会报告最新一次的 ID 和终态,没有台账则明确说明。
28
+
23
29
  在途步会以 `crashed` 关闭(事实落账),然后**重跑该步本身**(beta.3 改):进程死掉不说明候选有问题,丢失的那次尝试照常计入该步预算,预算耗尽即停人工。此前的语义是把 crash 当步骤失败走 failure 边——真实事故(deploy-18):宿主工具超时杀掉 verify worker,crash 被路由去 fix,fixer 面对零 verifier 证据白烧一轮。工作树脏了仍然先停人工。带批准恢复时会做 candidate/plan 新鲜度检查,变了即 `APPROVAL_STALE` 转人工。恢复不了就删 runtime 重跑——候选分支与 Git 面记录不丢。
24
30
 
25
31
  **启动纪律**(同一事故的另一半):长于分钟级的 Run 必须以脱离宿主工具超时的方式启动(`nohup`/`setsid`),交互式 shell 里 `start` 会打印这条提醒。
26
32
 
27
33
  ### 锁卡住("another run is active")
28
34
 
29
- 上一个 Run 异常退出可能留下仓库锁:确认真的没有活动 Run 后
35
+ 每把锁记录持有者(进程号、主机名、获取时间、命令)。驱动进程被杀、被宿主超时结束或机器重启后,锁会残留;下一次 `resume` / `stop` / `start` 拿锁时,若持有者**在本机且进程已不存在**,自动回收并打印 `reclaimed stale lock <id> (owner pid … is gone)`,无需手删任何文件。所以驱动被杀后直接:
30
36
 
31
37
  ```bash
32
- buildbeat stop --repo . --run RUN-X --reason "crashed; releasing lock"
38
+ buildbeat resume --config <run-config.yaml>
33
39
  ```
34
40
 
35
- `stop` 落终态与理由;单纯锁残留也可删 `.buildbeat/runtime/` 后重来。
41
+ 内核走上文的崩溃恢复(`RUN_INTERRUPTED` → 重跑中断步);不想续跑就 `buildbeat stop --repo . --run RUN-X --reason "…"`。
42
+
43
+ 只有三种情况仍会报 `another run is active` / `already locked`,报错里写明持有者:
44
+
45
+ - **持有者还活着**:另一个 Run 真的在跑,等它结束;确认它卡死再结束该进程;
46
+ - **持有者在别的主机**(共享盘上的仓库):到那台机器处理,本机不会替它回收;
47
+ - **没有持有者信息**:旧版本 buildbeat 留下的锁,或拿锁瞬间崩溃;确认没有 buildbeat 进程在跑后,删除报错里给出的那个锁目录。
48
+
49
+ ### 台账「changed on disk since it was read」
50
+
51
+ `ledger for RUN-X changed on disk since it was read (another writer); re-read it and retry`:另一个会话或进程刚好在你之前写了同一个 Run 的台账(例如两边同时批准、一边 `stop` 一边 `resume`)。内核拒绝了这次写入,**台账没有被改动、也没有损坏**;重新执行同一条命令即可,它会基于最新状态重新判断(可能直接告诉你「已经批过了 / 已终态」)。批准、拒绝、`--adopt`、`stop`、`resume` 与自动取代都在拿到 Run 锁之后才读台账,正常使用不会遇到这条报错;看到它说明确实有并发操作。
36
52
 
37
53
  ### Worker 行为异常
38
54
 
@@ -78,6 +94,6 @@ rm -rf .buildbeat/runtime/
78
94
  - 只动**终态且已压成 run-record** 的 Run(Git 面有账才动运行时面);
79
95
  - 工作树可删(提交都在分支上);脏工作树不带 `--force true` 不动;
80
96
  - 分支只在候选**已可从其他 ref 到达**(已合并 / 打 tag / 在远端)或 Run 未产出候选时删;否则明示"仅此分支可达,保留"——它是证据的最后一根线;
81
- - 终态 Run 的残留 `locks/<RUN>.lock` 一并清;`active-run` 锁仍按上文人工处置。
97
+ - 终态 Run 的残留 `locks/<RUN>.lock` 一并清;`active-run` 锁在持有者进程已不存在时一并回收(计划里写明持有者),持有者还活着 / 在别的主机 / 没有持有者信息时保留并说明原因。
82
98
 
83
99
  gc 永不写台账(终态后只允许 `RUN_COMPACTED`),所以随时可跑、可重复。
@@ -0,0 +1,26 @@
1
+ # BuildBeat 方法论 · 原则与分工(原 §1–§2)
2
+
3
+ > 从 [`SKILL.md`](../../../SKILL.md) 移出的正文,原文与原节号不变,只调整了相对链接;入口、驾驶手册与红线摘要仍在 `SKILL.md`。
4
+
5
+ ## 1. 四根支柱(命根子,所有零件都为它们服务)
6
+
7
+ 1. **端到端工作包**:一个 Builder 对一个需求/功能工作包(= Work)的产品判断、实现、测试、合并与发布证据负责;需要隔离时再调用独立 AI 视角(独立 cwd / 上下文 / 写边界)。
8
+ 2. **人在决定点**:接受 intent/plan、合并决定、上线关窗必须人拍板,**不可自动跨过**;Run 内的 Build→Verify→Review→Fix 由内核自动闭环。人只当"节拍器 + 拍板者",不当信息搬运工。
9
+ 3. **上下文在项目文件**:会话间不靠人转述,长期事实全部走 Git(`AGENTS.md` → `delivery/work/<ID>/` → 契约 → 决策台账),运行态由内核落台账;开工自取,进度由内核回读而不是会话自述。
10
+ 4. **证据制完成**:任何工作包声明"完成"必须带 ① Git 回读的候选 commit ② 可核验证据(真实命令的 verify 结果 / 只读 reviewer 的 findings / 线上回读 / 截图)。**无证据 = 没完成。**
11
+
12
+ > 需求编号可以细,但**执行边界不能跟着编号碎掉**:一个 Builder 同时认领一个可验收的用户级工作包,通常覆盖多个任务 ID / 文档 / commit;同一工作包可调用多个 AI 视角按各自写边界协作。多个 Builder 默认按项目/需求工作包切分,不按人类产品→研发→测试岗位接力。子产物提交、reviewer 返回、Run 停下都只是工作包内事件,不是自动结束会话的理由。
13
+
14
+ ## 2. 工作包所有权与 AI 专业视角
15
+
16
+ **人类责任按工作包端到端闭环。** 下表是同一 Builder 可调用的默认 AI 视角,用于上下文和写边界隔离;它不是成员目录、岗位分工或审批链。多个 Builder 协作时各自拥有不同工作包,共享契约冲突线下收敛后只落最终事实。
17
+
18
+ | AI 视角 | 在当前工作包内做什么 | 典型写入边界 |
19
+ |---|---|---|
20
+ | **产品**(规格/编排) | 拆需求、写 intent/plan、定契约要点、维护决策台账、分诊 finding | `delivery/**`、`pm/decisions.md`、`contracts/` |
21
+ | **全栈**(实现,含运维) | 实现 + 改契约 + 部署;可同持多仓但**按仓分别 stage**;Run 内受 `allowedPaths` 机器约束 | 代码仓 |
22
+ | **测试**(E2E·走查) | 对精确 candidate 独立核验、视觉回归、设计走查,不合格直接提带图 bug;报告落所属 Work 目录 | `tests/**` + 所属 Work 目录 |
23
+
24
+ - **审查不是会话视角**:reviewer 是 Run 内置的 fresh-context 只读 worker,输入固定 candidate,输出结构化 findings;任何工作树写入都会被前后快照抓住并按失败落账。会话不另开"审查会话"。
25
+ - **设计生成 = 外部工具**(可选):当前工作包的产品视角写 brief → 人喂设计工具 → 稿落 `design/design_N期/`;走查归测试视角。
26
+ - **拆 AI 视角的依据是"物理边界(仓/部署单元)+ 是否需要独立核查",不是人类公司职能表。** 实践教训:按职能切出 6 个会话,两个月内被迫合并回 4 个(前端+后端合并、设计+测试合并)——每多一个上下文,编排成本和信息差面积都扩大。合并视角会丢"天然独立核查"防线,由 Run 内置 reviewer + 测试视角独立核两端补回。
@@ -0,0 +1,31 @@
1
+ # BuildBeat 方法论 · 项目文件布局(原 §3)
2
+
3
+ > 从 [`SKILL.md`](../../../SKILL.md) 移出的正文,原文与原节号不变,只调整了相对链接;入口、驾驶手册与红线摘要仍在 `SKILL.md`。
4
+
5
+ ## 3. 项目文件布局
6
+
7
+ ```
8
+ <项目根>/ ← 工作区(单仓项目就是代码仓本身;多仓项目是协调层 meta 仓)
9
+ ├── AGENTS.md # 会话路由 + 协作规则 + 红线(开放标准,按工具装载)
10
+ ├── CLAUDE.md # 一行指针 → AGENTS.md(兼容只认此名的工具;🔴 不复制内容)
11
+ ├── 指挥台.md # 给人看的一页:日常六句话、视角开场白
12
+ ├── BUILDBEAT.md # 运行时版本标记 + 升级/回灌说明
13
+ ├── ARCHITECTURE.md # 全栈总图(多仓项目;按需读,不自动装载)
14
+ ├── contracts/PROTOCOL.md # 跨边界契约唯一入口(多仓项目;单仓可无)
15
+ ├── standards/ # 可选:STACK / CODE / REVIEW / DESIGN(Policy 输入工件,默认不生成)
16
+ ├── pm/decisions.md # 🔴 平台级拍板台账(全工作区决策单点);可选 pm/adr/
17
+ ├── delivery/
18
+ │ ├── envelope/ # worker.sh + builder / reviewer / fixer prompt(仓级,进 Git)
19
+ │ ├── work/<WORK-ID>/ # intent.md / plan.md / run-config.yaml / workflow.yaml / decisions.jsonl
20
+ │ │ └── runs/<RUN-ID>/ # run-record.json(终态记录,进 Git)
21
+ │ └── observe/intents/ # observe 的 Intent 草稿(人分诊,绝不自动执行)
22
+ ├── .buildbeat/
23
+ │ ├── notify.yaml / observe.yaml # 通知通道(URL 只走环境变量)/ 生产体检配置
24
+ │ ├── runtime/ # 🔴 事件台账、锁、日志(本机,不进 Git)
25
+ │ └── worktrees/ # 🔴 每个 Run 的隔离工作树(本机,不进 Git;gc 清)
26
+ └── <代码子仓们>/ # 多仓项目:各自独立 git + 该仓自己的 AGENTS.md(只写本仓局部细节)
27
+ ```
28
+
29
+ > 🔴 **装载入口走开放标准 `AGENTS.md`,不绑厂商**(lessons.md「上下文载体绑死单一厂商」)。标准语义 = 会话从被编辑文件所在目录**向上收集沿途所有 `AGENTS.md` 合并、离得最近的优先**,所以「根写全局、子仓写局部」是白捡的层叠能力,不用自己发明。只认 `CLAUDE.md` 的工具靠根上一份**一行指针**兼容(内容单点在 `AGENTS.md`,复制过去 = 自造 SSOT 腐烂;也别用符号链接,Windows 上 git 默认 `core.symlinks=false` 会静默退化成文本文件)。同理**不要**引入 gitignore 的本地覆盖文件(如 `AGENTS.override.md`):本文件装的是红线与护栏,允许不进 git 的本地覆盖 = 给绕过护栏开后门,reviewer 与 pre-commit 都看不见。
30
+ >
31
+ > **不建进度文件、状态文件或看板**:进度由内核从台账与 Git 回读(`overview` / `status`),写进文档的进度从写下那一刻开始腐烂(lessons.md「SSOT 腐烂」)。`.gitignore` 排除 `.buildbeat/runtime/` 与 `.buildbeat/worktrees/`;有 vitest / jest / pytest 的仓另配 exclude `**/.buildbeat/**`,否则主干测试会把旧候选的用例一起跑。
@@ -0,0 +1,45 @@
1
+ # BuildBeat 方法论 · 协作规则、任务包、审批分层与决策包(原 §4)
2
+
3
+ > 从 [`SKILL.md`](../../../SKILL.md) 移出的正文,原文与原节号不变,只调整了相对链接;入口、驾驶手册与红线摘要仍在 `SKILL.md`。
4
+
5
+ ## 4. 协作规则(写进项目根 AGENTS.md,模板已含)
6
+
7
+ 规则原文在 [templates/v2/AGENTS.md](../../../templates/v2/AGENTS.md) §2(十一条),本节只列每条为什么存在:
8
+
9
+ 1. **唯一入口**:活动工作看 `delivery/`(`overview` / `inbox`),不另建进度文件——多处进度必漂移。
10
+ 2. **契约落盘不喊话(双向)**:跨边界接口先改 `contracts/` 再动代码;收到协议声明独立核查再信;实现中发现契约不够用不得就地消化,停下记契约缺口交产品视角裁决。
11
+ 3. **交接靠 candidate hash + 台账**:Run 停在合并决定时 candidate 已由 Git 回读固定,`resume --adopt <sha>` 要求树干净且 HEAD 就是该 sha;hash 不得编造。
12
+ 4. **护栏与不可逆动作**:开工 `overview`;部署/改契约/migration 等不可逆动作前再核一次并走人批;exit 0 不消除 `warning/unverified`。
13
+ 5. **风险分轨**:Risk Preset 决定人批点(§5);别用牛刀杀鸡,也别借 `fast` 绕过高风险 delta 的独立核查。
14
+ 6. **核查门**:Run 内 reviewer 只读、结构化 findings;`reviewTriage: required` 时 P0/P1 先过人分诊再派 fixer;review 每 Run 默认 2 轮封顶。**完成 = hash + 可核验证据**;证据分 L0 声称 / L1 `文件:行` / L2 编译·类型 / L3 自动化测试 / L4 线上实测,`standard` 最低 L3,上线必须 L4;`UNVERIFIED` 永不当作通过。
15
+ 7. **状态单点**:事实进 Run 证据与 Work 记录;进度看 `overview`,度量看 `metrics`。
16
+ 8. **视觉问题带图对比**:提 UI bug 必附『实现截图 ⟷ 设计稿截图』并排 + 标注差异点。
17
+ 9. **单点事实**:线上版本只信实查(`observe status` / 部署平台),任何文档不写「当前线上 vX」;每个收敛后的真实决策包只在 `pm/decisions.md` 记一行;历史台账不回改。
18
+ 10. **真渲染拍板**:有 UI 的拍板对象必须是真渲染证据(可点入口 + 截图 digest),静态稿/规范数值不充当拍板对象;上线前终签同样要含真渲染走查。
19
+ 11. **所有者可见命名进决策卡**:域名、服务名、环境名、自停时长、窗口时长等所有者以后要看见或念出来的名字与参数,不由 worker 顺手定;进 intent 或门前决策卡(`BATCH_AT_GATE`),给推荐值和理由。
20
+
21
+ > 十一条之外的一条**元原则:能实查的不问人**——查代码 / 配置 / 部署平台 / `overview` / `status` / `observe status` 能得到的事实,不拿去问用户、不信文档、不信上游转述(§8 Bootstrap 的提问三原则同源)。
22
+
23
+ ### 4.1 任务包协议:不因子任务完成而过早结束
24
+
25
+ 多步骤工作开工时,从用户目标与活动 Work 得到一个**任务包信封**(即 Work 的 intent/plan)。一个工作包可跨视角接力,但每个会话同时只认领一个并遵守自己的写边界;多个独立目标可以并行成多个 Work,不要重新退化成按文件切包。
26
+
27
+ - `objective`:这轮要交付的用户级结果,不是文件名或动作名。
28
+ - `in_scope`:为达成目标可自动继续的关联任务/AI 视角/文件边界。
29
+ - `terminal_condition`:只有以下三类——目标带证据完成;遇到必须由人处理的真实阻塞(Run 停 `WAITING_HUMAN` 或 `infra`);用户明确只要阶段性检查点。
30
+
31
+ 需求 ID、验收项和原子 commit 继续保持细粒度,用于追踪、回滚和验证;**它们不自动成为会话结束条件**。只要仍有安全、可逆、在 `in_scope` 内且能推进 `objective` 的工作,会话就继续做。单个文档提交、一次 reviewer 返回、一次 Run 停下都只发中间进展,不得用 final 把接力棒交还给用户。跨视角且当前会话只读时,落盘接力棒并派给有权视角/明确真实阻塞,而不是把"请继续"变成人工调度协议。
32
+
33
+ ### 4.2 审批分层:立即停、门前批、无需批
34
+
35
+ | 层级 | 什么时候 | 会话动作 |
36
+ |---|---|---|
37
+ | **STOP_NOW 立即停** | 跨发布门;扩大已批准范围或重开 non-goal;修改**已冻结**对外契约;部署/发布/花费/删除等不可逆外部动作;接受安全或合规风险;权威事实冲突且无法实查 | 停在动作前,一次给出推荐方案、影响和最小问题;获批后继续当前工作包 |
38
+ | **BATCH_AT_GATE 门前批** | 冻结前可逆草案选择;已批准目标内的默认值/阈值/失败态归类/实现语义;多个互相关联的产品取舍;所有者可见命名 | 先记入 intent/plan 草稿或门前决策卡,继续不依赖该决定的工作;到人批的转换(accept / merge / release)或约定节奏一次提交**默认 2–5 个真实取舍**(确实只有 1 个就单项),每项带推荐值与后果 |
39
+ | **NO_APPROVAL 无需批** | 能实查的事实;已批准信封内的派生约束;文案/归档/证据整理;普通 P2;不改变外部语义的可逆实现细节 | 自主完成并在证据/收口中说明,不把"告知"包装成"请审批" |
40
+
41
+ 判断顺序:先实查 → 再看是否越过 `in_scope`/人批转换/冻结线/不可逆线 → 只有命中 `STOP_NOW` 才立即中断。**人批预算默认每个工作包、每道人批转换只有 1 个 `BATCH_AT_GATE` 请求**;`STOP_NOW` 是越界例外。未决项不得悄悄固化成冻结事实;若它阻塞当前关键路径,把相关真实取舍合并成同一次提问,不要逐条连环问。用户只回答一部分或要求解释时,保持同一决策包编号,补充说明并更新决策卡,不得另造一轮"新审批"。
42
+
43
+ ### 4.3 决策包:验收条件不是 14 个拍板
44
+
45
+ 当前工作包的产品视角先把清单分成两类:① 人必须取舍的**独立决策变量**;② 由已选变量和现有契约推导出的验收约束。只把前者送人批,后者自动写入 plan/契约并随候选一起验收。一次门前默认提交 2–5 个决策变量;用户分轮回答时,未收敛项留在决策卡,收敛后按决策包在 `pm/decisions.md` 记一次,不为"3/14、11/14、14/14"分别制造拍板记录。