@haiyangbg/buildbeat 2.0.0-beta.4 → 2.0.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.
- package/CHANGELOG.md +27 -1
- package/README.en.md +20 -1
- package/README.md +20 -1
- package/SKILL.md +14 -4
- package/docs/CLI.md +3 -2
- package/docs/RELEASING.md +1 -1
- package/docs/V2.0.0-BETA.4-RELEASE-EVIDENCE-2026-09-03.md +9 -0
- package/docs/V2.0.0-BETA.5-RELEASE-EVIDENCE-2026-09-05.md +10 -0
- package/docs/v2/SPEC-0001-events-v1.md +5 -4
- package/docs/v2/guide/02-workflow-guide.md +34 -1
- package/docs/v2/guide/07-approval-guide.md +14 -0
- package/docs/v2/guide/10-recovery.md +3 -2
- package/example/.buildbeat/manifest.json +1 -1
- package/lessons.md +25 -0
- package/package.json +1 -1
- package/src/v2/adapters/mock.js +9 -2
- package/src/v2/cli/run.js +147 -6
- package/src/v2/domain/event-registry.js +1 -0
- package/src/v2/engine/reducer.js +27 -1
- package/src/v2/runtime/decisions.js +80 -0
- package/src/v2/runtime/orchestrator.js +122 -11
- package/src/v2/runtime/overview.js +56 -19
- package/src/v2/runtime/run-record.js +3 -0
- package/src/v2/runtime/work-cost.js +147 -0
- package/src/v2/workspace/workspace-manager.js +14 -1
- package/templates/contracts/PROTOCOL.md +4 -0
- package/templates/gitignore.template +5 -0
- package/templates/scripts/bus-check.sh +37 -12
- package/templates/v2/AGENTS.md +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,10 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
> 本项目吃自己的狗粮(红线④:必更 CHANGELOG)。格式循 Keep a Changelog,倒序。
|
|
4
4
|
|
|
5
|
+
## v2.0.0 — 2026-09-05(正式版:v2 成为 `latest`)
|
|
6
|
+
|
|
7
|
+
> **发布状态**:所有者 2026-09-05 授权(「正式发布」);从 `main` 以稳定 tag `v2.0.0` 发布到 dist-tag `latest`,发布与独立回读证据在发布后回填到 `docs/V2.0.0-RELEASE-EVIDENCE-2026-09-05.md`。
|
|
8
|
+
|
|
9
|
+
- **内容与 `2.0.0-beta.5` 同源**(迭代 01~09 的全部 v2 运行时、Skill §0.5 驾驶手册、`templates/v2/`、十件套指南、lessons #1–#25),外加 README 中英文的「当前主线是 v2」段与 `docs/CLI.md` 的 2.0.0 状态行。
|
|
10
|
+
- **对拷出项目意味着什么**:v1 文件总线、`buildbeat` 生命周期命令(`doctor` / `init` / `adopt` / `upgrade` / `version`)与安全边界**不变**,schema 仍是 2;`npm install --global @haiyangbg/buildbeat@latest` 现在同时给出 `buildbeat` 与 `buildbeat-v2`。骨架版本仍是 `v1.21`(模板未变,`buildbeat upgrade` 对 1.21 骨架报 up-to-date,不需要 `--major`);manifest 里的 `cliVersion` 只是记录,不触发升级。v2 运行时是可选叠加:按 [`docs/v2/guide/08-migration-v1.md`](docs/v2/guide/08-migration-v1.md) 建 `delivery/work/` 与 run 配置即可,不动现有 `pm/` 与 `contracts/`。
|
|
11
|
+
- **分发口径**:`latest` 从 1.21.0 切到 2.0.0;`next` 保留给后续预发布;旧 `solobaton` 包不变。
|
|
12
|
+
|
|
13
|
+
## v2.0.0-beta.5 — 2026-09-05(迭代 09:预算是刹车、故障分开算、成本看得见)
|
|
14
|
+
|
|
15
|
+
> **发布状态**:`@haiyangbg/buildbeat@2.0.0-beta.5` 已于 2026-09-05 经 OIDC Trusted Publishing 发布到 dist-tag `next`(run 33972774150,双 job success;所有者授权「发,并且迭代5轮可以直接切换到线上版本了」);`latest` 保持 v1.21.0。独立回读(直连 npmjs.org):dist-tag 路由、integrity、attestation、隔离安装、`doctor` 有界 JSON 全过,证据见 [`docs/V2.0.0-BETA.5-RELEASE-EVIDENCE-2026-09-05.md`](docs/V2.0.0-BETA.5-RELEASE-EVIDENCE-2026-09-05.md)。所有者本机 CLI 已从源码链接切回正式包。
|
|
16
|
+
> 来源:试点工作区 2026-09-03~09-05(beta.4 之后)全部驾驶会话、约 60 个 worker 会话与两个子仓 50 个 Run 台账的复盘回灌(迭代 09,lessons #23–#25),以及 2026-09-05 收尾清理回灌。台账数字:50 个 Run 成功 12、作废 16、取消 17、失败 5,支撑 7 次生产发布;所有者问"多久了正常吗"从十余次降到 1 次。
|
|
17
|
+
|
|
18
|
+
- **预算续批不再死循环,run 配置可覆盖预算(迭代 09 A1)**:预算耗尽停人后批准 `resume-<step>`,内核落 `BUDGET_EXTENDED`(台账事实,可重放)给该步 +1 再跑,不再立刻重问;run 配置 `budgets.maxAttempts.<step>` 覆盖预设(run config > preset > `maxAttemptsPerStep`);`doctor` 打印每步生效上限与来源。真实事故:两条应用登录 Run 因预设 2 轮改不动且批了没用而以 CANCELLED 收场,候选却已在生产
|
|
19
|
+
- **worker 基础设施故障停人,不杀 Run、不派 fixer、不扣预算(迭代 09 A2)**:timeout / crashed / invalid-output / 退出码 75(`EX_TEMPFAIL`,verify 或包装脚本的"环境不可用"信号)判 `infra`:`STEP_FINISHED.data.infra`、`steps[step].infraAttempts` 抵回预算、不记失败指纹、停 `WAITING_HUMAN`(kind `infra`,`resume-<step>`)。没有转移边的失败结果也改为停人;终态 FAILED 只剩 policy BLOCK。mock 适配器新增 `env-fail`。真实事故:worker 后端 404 与非 JSON 输出两天杀 5 个 Run;PATH / 端口 / 负载类 verify 失败派了 5 次 fixer
|
|
20
|
+
- **Work 级成本与跨 Run 的 review 轮数上限(迭代 09 A3)**:`overview` 每个 Work 一行 `cost: review rounds · findings · human waits · [infra failures] · worker 时长`(运行时台账优先,run-record 新增 `cost` 块兜底);`budgets.reviewRoundsPerWork` 跨本 Work 所有 Run(含作废)累计,达标在 review 起跑前停人(kind `work-review-cap`,`enter-review`),批准即多审一轮(`BUDGET_EXTENDED scope=work`)。intent 模板与 Skill 加"止损线"。真实事故:一个 Work 21 个 Run、9 轮 review,每 Run 2 轮封顶从未触发;另一个烧了 10 个 Run 一天后被砍
|
|
21
|
+
- **overview 真相修正(迭代 09 B1)**:任一候选已在当前分支即 `MERGED`(最新 Run 是 CANCELLED 也一样,`next:` 注明);`release-readback` 车道成功关窗显示 `RELEASED`,不再说 "nothing to merge";已合并/已发布/已关闭的 Work 不再提示未裁决 finding;run-record 新增 `workflow`
|
|
22
|
+
- **`doctor` 读 start 第一道门会读的事实(迭代 09 B2)**:打印本仓 `delivery/work/<ID>/` 的 intent / plan 存在与接受状态,对每条 `artifact.accepted` 类 policy 预告 start 会停在哪一步(文件缺失时提示先镜像)
|
|
23
|
+
- **`resume --adopt <sha> --by <名字>`(迭代 09 B3)**:人或驾驶会话在 Run 的 worktree 里手修并提交后,跳过 fixer 从 verify 续跑;内核回读 git(树干净、HEAD = sha),以人为 actor 落 `CANDIDATE_PINNED`(`adopted`)与 `DECISION_RECORDED`(`adopted` / `resumeAt`)
|
|
24
|
+
- **起跑被仓锁挡住时说清在等谁(迭代 09 C)**:`start` 遇 "another run is active" 打印持锁 Run、所在步与最后事件时间、可复制的 `status` 命令;锁本身未放开
|
|
25
|
+
- **模板与指南**:`.gitignore` 模板排除 `.buildbeat/runtime/` 与 `.buildbeat/worktrees/`,指南给出 vitest / jest / pytest 排除写法(试点主干测试曾把残留工作树的用例一起跑);v2 AGENTS 模板第 ⑨ 条 worker 环境事实(沙箱不能监听端口、PATH 只认 POSIX、环境不满足 `exit 75`);Skill 驾驶手册:首次写 run-config 问一句要不要启用通知(试点未启用,一张合并卡隔夜等 9.5 小时)、`infra` 停人时怎么答、手修用 adopt、开工前先 doctor
|
|
26
|
+
- 测试:新增 `v2-budget` / `v2-infra` / `v2-work-cost` / `v2-overview-stages` / `v2-doctor-work` / `v2-adopt` / `v2-active-lock` 七组 25 项;既有 `invalid-output` / 无转移边 / metrics 三处断言按新语义改写
|
|
27
|
+
|
|
28
|
+
- **`overview` 认得「已关闭」的 Work**:`delivery/work/<ID>/decisions.jsonl` 里一行 `{"transition":"close-work","decision":"closed"|"cancelled","subject":{"result":"…"}}` 即让该 Work 显示 `CLOSED` / `CANCELLED` 并带关闭时间与结果,不再把 12 个已关闭的 Work 报成 `READY_TO_RUN` 并催写 run-config;仍有 `RUNNING` / `WAITING_HUMAN` 的 Run 时活 Run 优先,关闭行藏不住待办。`READY_TO_RUN` 的 `next:` 提示改成给出这行的精确形状(此前只说「record the work as closed」却没有任何读者)。
|
|
29
|
+
- **`bus-check` 多仓 map 适配多模块仓与无契约版本域的仓**:`buildbeat-multirepo-map:v1` 行可选第 4 字段 `changelog=<repo 内模块 CHANGELOG 路径>`(根下没有 CHANGELOG 的多模块仓由某个模块 CHANGELOG 承载契约版本),`contract=n/a` 表示该仓没有契约版本域(只读存量前端、npm 包 semver 与契约版本不同域),只登记不核对。越出本仓的 `changelog=` 路径、非 `contracts/*.md` 且非 `n/a` 的契约值仍判 map 无效;被核对的 CHANGELOG 首个已发布 H2 须以契约快照版本开头(`## [v1.3 · Deployed …]`)。模板 `contracts/PROTOCOL.md` 注释与脚本测试同步。
|
|
30
|
+
|
|
5
31
|
## v2.0.0-beta.4 — 2026-09-03(迭代 08:等待要能找到人)
|
|
6
32
|
|
|
7
33
|
> 主题:试点工作区 2026-08-28~09-02 全部驾驶会话与 58 个 Run 台账的复盘回灌(复盘文档见迭代 08 记录)。台账数字:58 个 Run 成功 7、失败 17、取消 32,其中多数取消是在 WAITING_HUMAN 挂满一天后批量清掉;人批平均等 7~12 小时。beta.3 治的是"审查循环烧钱",本版治的是**人看不见 Run 在干什么、等的人不知道有东西等他、每次都要手工打扫**。
|
|
8
|
-
>
|
|
34
|
+
> **发布状态**:`@haiyangbg/buildbeat@2.0.0-beta.4` 已于 2026-09-03 经 OIDC Trusted Publishing 发布到 dist-tag `next`(run 33728863042,双 job success;所有者授权「发布 beta.4 吧,授权也一起」);`latest` 保持 v1.21.0。独立回读(直连 npmjs.org):dist-tag 路由、integrity、attestation、隔离安装、`doctor` 有界 JSON 全过,证据见 [`docs/V2.0.0-BETA.4-RELEASE-EVIDENCE-2026-09-03.md`](docs/V2.0.0-BETA.4-RELEASE-EVIDENCE-2026-09-03.md)。
|
|
9
35
|
|
|
10
36
|
- **运行中可见性(C1)**:Shell Adapter 把 worker 的 stdout/stderr **实时**流到 `.buildbeat/runtime/runs/<RUN>/<step>-<n>.{stdout,stderr}.live`,并留 `live.json` 标记(命令、开始时间);步结束即收回,证据日志仍由回读生成。`status` 现在显示每步耗时(本次 / 累计 / 同仓同步骤历史中位数 `typical … n=`)、在飞步骤的已用时间、worker 命令、最后一次输出距今多久与末三行输出;无输出超过阈值(默认 15 分钟,run 配置 `stallAfterMs` 或 `status --stall-after <分钟>`)标 **STALLED**(只标不杀)。`metrics` 增加每步中位耗时。真实事故:所有者一场会话里问了十余次"半小时了正常吗 / 十分钟了是卡住了吗",而 status 只有步骤和次数
|
|
11
37
|
- **同 Work 新 Run 自动取代旧的等待(C2)**:`start` 时同一 Work 下仍在 `WAITING_HUMAN` 的旧 Run 记 `RUN_TERMINAL SUPERSEDED` 并压成 run-record(Git 面),新 Run 的 `RUN_CREATED.data.supersedes` 记血统;inbox 只剩活的等待。run 配置 `supersede: off` 关闭。真实事故:试点子仓两个旧 Run 在 inbox 挂了一天,而后继者早已上线
|
package/README.en.md
CHANGED
|
@@ -12,6 +12,8 @@ Requirements, boards, contracts, decisions, status, and verification evidence li
|
|
|
12
12
|
|
|
13
13
|
BuildBeat began with one person coordinating four AI sessions across a complex, multi-iteration product. That is its origin, not an audience limit. One Builder can use it, or several Builders can share one Git project and close separate requirement/work packages end to end.
|
|
14
14
|
|
|
15
|
+
**v2 is the current line** (`@haiyangbg/buildbeat@latest`, 2.0.0): on top of the v1 file bus and human Gates it adds `buildbeat-v2`, a delivery runtime driven by AI sessions — an automatic Build → Verify → Review → Fix loop inside an isolated worktree that stops at the merge decision, with `overview` / `inbox` / `status` answering "where is it, who decides, is it stuck". See [the v2 runtime](#the-v2-runtime-buildbeat-v2-current-line) below and [`docs/v2/guide/`](docs/v2/guide/README.md). Since 2.0.0 `@latest` is v2: the v1 `buildbeat` lifecycle commands are unchanged and `buildbeat-v2` is a second executable in the same package; `@next` is for later pre-releases.
|
|
16
|
+
|
|
15
17
|
> **Language note:** `SKILL.md`, the scaffold templates, and script output are currently Chinese-first. The delivery protocol is language-independent, and a project can translate its generated scaffold during bootstrap.
|
|
16
18
|
|
|
17
19
|
## The problem it solves
|
|
@@ -85,6 +87,22 @@ Copied v1.16 legacy projects must not hand-author, copy, or rename a manifest to
|
|
|
85
87
|
|
|
86
88
|
The old `solobaton@latest` package stays on the legacy read-only v0 capability and points users to this scoped package; it does not gain project writes or upgrades. A write-enabled first-screen command must use `@haiyangbg/buildbeat`, still shows its plan first, and remains subject to Git, collision, ownership, and human-Gate boundaries.
|
|
87
89
|
|
|
90
|
+
### The v2 runtime: `buildbeat-v2` (current line)
|
|
91
|
+
|
|
92
|
+
v2 turns "how a work package goes from accepted to merged to released" into a machine-verifiable Run: once the intent / plan under `delivery/work/<ID>/` are accepted by digest, `buildbeat-v2 start` drives Build → Verify → Review → Fix in an isolated git worktree under the official preset. The candidate is what git reads back, never what a worker claims; the read-only reviewer is enforced by the kernel; exhausted budgets, repeated identical failures and worker-infrastructure failures all stop for a human; merge, push and deploy remain human actions after approval (the kernel has no call path for them). Go-live uses the `release-readback` lane to ledger "read back before → human acts → read back after → observe → human closes the window" as L4 evidence; `observe` runs read-only production health checks; `gc` cleans up; `.buildbeat/notify.yaml` pushes waits to DingTalk or a webhook.
|
|
93
|
+
|
|
94
|
+
It does not create agents or manage models: a worker is whatever command you put in the run config (Codex, Claude Code, a script); the kernel owns only the ledger, isolation, evidence and gates.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm install --global @haiyangbg/buildbeat@latest # since 2.0.0 latest is v2; pre-releases use @next
|
|
98
|
+
buildbeat-v2 overview --repo . # where each work is, what it has cost, who moves next
|
|
99
|
+
buildbeat-v2 inbox --repo . # what is waiting on you, and the exact reply
|
|
100
|
+
buildbeat-v2 start --config delivery/work/WORK-X/run-config.yaml --attempt new
|
|
101
|
+
buildbeat-v2 status --repo . --run RUN-X-01 # which step, for how long, typical duration, stalled or not
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
People driving BuildBeat from an AI session do not need these commands: [`SKILL.md`](SKILL.md) §0.5 is the driving manual the session reads; the user says "progress / start / how is it going / approve / go live / clean up". The one-page user view is [`docs/v2/guide/00-how-to-talk.md`](docs/v2/guide/00-how-to-talk.md), the ten-guide index is [`docs/v2/guide/README.md`](docs/v2/guide/README.md), and migration from the v1 file bus is [`docs/v2/guide/08-migration-v1.md`](docs/v2/guide/08-migration-v1.md) (Chinese). Each beta's content and release evidence is in [`CHANGELOG.md`](CHANGELOG.md) and `docs/V2.0.0-BETA.*-RELEASE-EVIDENCE-*.md`; the real incident behind every mechanism is in [`lessons.md`](lessons.md).
|
|
105
|
+
|
|
88
106
|
### Manual installation
|
|
89
107
|
|
|
90
108
|
Use this path only when you already understand the templates:
|
|
@@ -244,7 +262,8 @@ Skill-only, legacy npm v0, and scoped BuildBeat 1.21 are distinct availability s
|
|
|
244
262
|
|
|
245
263
|
## Continue reading
|
|
246
264
|
|
|
247
|
-
- [`SKILL.md`](SKILL.md): the single complete entry point for the methodology and bootstrap;
|
|
265
|
+
- [`SKILL.md`](SKILL.md): the single complete entry point for the methodology and bootstrap; §0.5 is the v2 driving manual;
|
|
266
|
+
- [`docs/v2/guide/README.md`](docs/v2/guide/README.md): the ten v2 runtime guides (how to talk to a session, quickstart, workflow / policy / adapter / worker contract / evidence / approval / migration from v1 / security boundaries / recovery);
|
|
248
267
|
- [`example/`](example/): the protocol teaching snapshot of a fictional project after one completed iteration (executable scripts still reference the template SSOT);
|
|
249
268
|
- [`lessons.md`](lessons.md): real anti-patterns, root causes, and fixes;
|
|
250
269
|
- [`docs/ROADMAP.md`](docs/ROADMAP.md): the new product direction, design principles, and the CLI execution amendment effective on 2026-08-24;
|
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@ BuildBeat(旧称 Solobaton)是一套面向人和 AI 会话的、**file-first
|
|
|
12
12
|
|
|
13
13
|
BuildBeat 最初蒸馏自一个人指挥 4 个 AI 会话、持续多期交付复杂产品的实践;这说明了方法的来源,不限定使用人数。一个 Builder 可以使用,多个 Builder 也可以共享同一 Git 项目并按需求/工作包分别闭环。
|
|
14
14
|
|
|
15
|
+
**当前主线是 v2**(`@haiyangbg/buildbeat@latest`,2.0.0):在 v1 的文件总线与人工 Gate 之上加了一个由 AI 会话调用的交付运行时 `buildbeat-v2`——隔离 worktree 内 Build → Verify → Review → Fix 自动闭环、停在合并决定,`overview` / `inbox` / `status` 回答「到哪了、谁批、卡没卡」。见下文 [v2 运行时](#v2-运行时buildbeat-v2当前主线) 与 [`docs/v2/guide/`](docs/v2/guide/README.md)。自 2.0.0 起 `@latest` 就是 v2:v1 的 `buildbeat` 生命周期命令原样保留,`buildbeat-v2` 是同一个包里的第二个可执行文件;`@next` 用于后续预发布。
|
|
16
|
+
|
|
15
17
|
## 解决什么问题
|
|
16
18
|
|
|
17
19
|
当一个项目同时打开多个 AI Coding 会话,最容易失控的不是代码生成,而是交付状态:
|
|
@@ -83,6 +85,22 @@ npm uninstall --global @haiyangbg/buildbeat # 只移除全局 CLI 包
|
|
|
83
85
|
|
|
84
86
|
旧 `solobaton@latest` 固定在 legacy v0 只读能力,并迁移提示到本 scoped package;它不会获得新的项目写入或升级能力。写入式首屏命令必须使用 `@haiyangbg/buildbeat`,并且仍先展示计划、受 Git/碰撞/所有权检查约束,不能跨人工 Gate。
|
|
85
87
|
|
|
88
|
+
### v2 运行时:`buildbeat-v2`(当前主线)
|
|
89
|
+
|
|
90
|
+
v2 把「一个工作包怎么从接受走到合并、上线」做成了机器可核验的 Run:`delivery/work/<ID>/` 里的 intent / plan 被 digest 绑定接受后,`buildbeat-v2 start` 在隔离 git worktree 里按官方预设跑 Build → Verify → Review → Fix,候选由 git 回读而不是 worker 自述,只读 reviewer 由机器强制,超预算、同指纹重复失败、worker 基础设施故障都停下来等人;合并、push、部署永远是人批之后的人类动作(内核没有这些调用路径)。上线用 `release-readback` 车道把「做之前回读 → 人做 → 做之后回读 → 观察 → 人关窗」记成 L4 证据;`observe` 做生产只读体检;`gc` 打扫;`.buildbeat/notify.yaml` 把等待推到钉钉或 webhook。
|
|
91
|
+
|
|
92
|
+
它不创建 Agent、不管理模型:worker 是你在 run 配置里写的任意命令(Codex / Claude Code / 一段脚本都行),内核只负责台账、隔离、证据和门。
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npm install --global @haiyangbg/buildbeat@latest # 2.0.0 起 latest 即 v2;预发布用 @next
|
|
96
|
+
buildbeat-v2 overview --repo . # 每个 Work 走到哪、花了多少、下一步该谁
|
|
97
|
+
buildbeat-v2 inbox --repo . # 谁在等你批,下一句该说什么
|
|
98
|
+
buildbeat-v2 start --config delivery/work/WORK-X/run-config.yaml --attempt new
|
|
99
|
+
buildbeat-v2 status --repo . --run RUN-X-01 # 在跑第几步、跑了多久、历史通常多久、卡没卡
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
在 AI 会话里用的人不需要记这些命令:[`SKILL.md`](SKILL.md) §0.5 是给会话读的驾驶手册,用户说「当前进度 / 开工 / 怎么样了 / 批准 / 上线 / 打扫卫生」即可。用户视角的一页在 [`docs/v2/guide/00-how-to-talk.md`](docs/v2/guide/00-how-to-talk.md),十件套指南索引在 [`docs/v2/guide/README.md`](docs/v2/guide/README.md),从 v1 文件总线迁移见 [`docs/v2/guide/08-migration-v1.md`](docs/v2/guide/08-migration-v1.md)。每个 beta 的内容与发布证据见 [`CHANGELOG.md`](CHANGELOG.md) 与 `docs/V2.0.0-BETA.*-RELEASE-EVIDENCE-*.md`;每条机制背后的真实事故在 [`lessons.md`](lessons.md)。
|
|
103
|
+
|
|
86
104
|
### 手动安装
|
|
87
105
|
|
|
88
106
|
只建议在你已经理解模板含义时使用:
|
|
@@ -242,7 +260,8 @@ Skill-only、legacy npm v0 和 scoped BuildBeat 1.21 是三个不同可用面;
|
|
|
242
260
|
|
|
243
261
|
## 继续阅读
|
|
244
262
|
|
|
245
|
-
- [`SKILL.md`](SKILL.md):方法论与 Bootstrap
|
|
263
|
+
- [`SKILL.md`](SKILL.md):方法论与 Bootstrap 的唯一完整入口,§0.5 是 v2 驾驶手册;
|
|
264
|
+
- [`docs/v2/guide/README.md`](docs/v2/guide/README.md):v2 运行时十件套指南(怎么和会话说话、快速开始、workflow / policy / adapter / worker 合同 / 证据 / 审批 / 从 v1 迁移 / 安全边界 / 恢复);
|
|
246
265
|
- [`example/`](example/):虚构「简账」项目一期收尾的协议教学快照(可执行脚本仍引用模板 SSOT);
|
|
247
266
|
- [`lessons.md`](lessons.md):真实反模式、根因与解法;
|
|
248
267
|
- [`docs/ROADMAP.md`](docs/ROADMAP.md):新版产品方向、设计原则与 2026-08-24 生效的 CLI 执行修订;
|
package/SKILL.md
CHANGED
|
@@ -23,12 +23,13 @@ description: BuildBeat(旧称 Solobaton)—— 面向人和 AI 会话的工
|
|
|
23
23
|
|
|
24
24
|
| 用户说 | 会话背后调什么 | 会话回给用户什么 |
|
|
25
25
|
|---|---|---|
|
|
26
|
-
| 「当前进度」「待办是什么」「X 上线了吗」「离上线还差多远」 | `buildbeat-v2 overview --repo .`(每个 Work 的阶段 + 下一步该谁)+ `observe status --repo .` |
|
|
26
|
+
| 「当前进度」「待办是什么」「X 上线了吗」「离上线还差多远」 | `buildbeat-v2 overview --repo .`(每个 Work 的阶段 + 下一步该谁 + `cost:` 已花的 Run/review 轮/等人次数/worker 时长)+ `observe status --repo .` | 每件事一句:走到哪、卡在谁、下一步;**不列命令**;花费超过 intent 止损线的 Work 要主动说「已 N 轮 review / N 小时,继续还是砍」 |
|
|
27
27
|
| 「有什么要我拍板」 | `buildbeat-v2 inbox --repo .` | 逐项:等什么、证据在哪、推荐 A/B;用户回「批准/拒绝」后会话调 `approve`/`reject` |
|
|
28
|
-
| 「开个 Work:〔目标〕」 | 写 `delivery/work/<ID>/intent.md`(为什么做)+ `plan.md`(怎么做)+ `run-config.yaml
|
|
29
|
-
| 「开工」「再来一轮」 | `buildbeat-v2 start --config <run-config.yaml> --attempt new`(自动编号 RUN-X-01/02…,自动作废同 Work 的旧等待;**用 nohup/setsid 脱离启动**) | 「已起 RUN-X-02,停在合并决定时会通知/我会告诉你」 |
|
|
30
|
-
| 「怎么样了」「卡住了吗」「正常吗」 | `buildbeat-v2 status --repo . --run <RUN>` | 一句:在跑第几步、跑了多久、历史通常多久、最后一次输出几分钟前;`STALLED`
|
|
28
|
+
| 「开个 Work:〔目标〕」 | 写 `delivery/work/<ID>/intent.md`(为什么做 + **止损线**:最多几个 Run / 几轮 review / 几小时,越线先问人)+ `plan.md`(怎么做)+ `run-config.yaml`(`budgets.reviewRoundsPerWork` 对应止损线);给用户看摘要 | 「看完说接受」;用户说「接受」→ `accept --artifact intent` / `--artifact plan`(digest 绑定) |
|
|
29
|
+
| 「开工」「再来一轮」 | 先 `buildbeat-v2 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,停在合并决定时会通知/我会告诉你」 |
|
|
30
|
+
| 「怎么样了」「卡住了吗」「正常吗」 | `buildbeat-v2 status --repo . --run <RUN>` | 一句:在跑第几步、跑了多久、历史通常多久、最后一次输出几分钟前;`STALLED` 就说「疑似卡住,建议停/等」;停在 kind `infra` 就说「worker 环境/后端故障,不是代码问题,恢复后我重跑,预算不扣」 |
|
|
31
31
|
| 「批准 RUN-X」「拒绝,原因…」 | `approve --transition <t> --by <用户名>` / `reject --reason` → 若非终态再 `resume` | 「批准=merge-ready;合并/push/部署要你另说」 |
|
|
32
|
+
| 会话自己在 Run 的 worktree 里把 finding 修完并提交了(Run 停在 enter-fix / resume-fix) | `resume --config <cfg> --adopt <sha> --by <会话名>`(跳过 fixer,从 verify 续跑;树必须干净、HEAD 必须是该 sha) | 「我已手修并提交 <sha>,验证重跑中」;**不要**为了让 fixer 空跑而 approve enter-fix |
|
|
32
33
|
| 「这条 finding 不算,那条接受」 | `findings list` / `findings adjudicate --action dismiss|accept` → `approve --transition enter-fix` | 裁决结果一句 |
|
|
33
34
|
| 「上线」「做生产动作」 | 用 `release-readback` 预设 + `riskPreset: release` 开 Run:preflight 回读 → 停 `enter-apply-readback` | 「回读全绿,现在轮到你做〔动作〕;做完说一声」→ 用户说「做完了」→ `approve enter-apply-readback` → 回读+观察 → 停关窗 |
|
|
34
35
|
| 「打扫卫生」 | `buildbeat-v2 gc --repo .`(先出计划)→ 用户点头 → `--apply true` | 清了几个工作树、留了哪些分支及为什么 |
|
|
@@ -37,6 +38,7 @@ description: BuildBeat(旧称 Solobaton)—— 面向人和 AI 会话的工
|
|
|
37
38
|
### 0.5.2 会话必须遵守的读法
|
|
38
39
|
|
|
39
40
|
- **能实查的不问人**:`overview` / `status` / `inbox` / `metrics` / `observe status` 全是只读,先跑再答;不信文档、不信上游转述。
|
|
41
|
+
- **环境故障不是候选缺陷**:超时、崩溃、非 JSON 输出、退出码 75 内核判 `infra` 停人;会话只做两件事——查后端/环境(worker 后端是否 404、端口是否被占、PATH 是否缺工具),恢复后 `approve --transition resume-<step>`;**不要**为了绕过去手写探针循环或起新 Run。verify / 包装脚本发现环境不满足就 `exit 75`。
|
|
40
42
|
- **数字要落地**:`status` 给了耗时和历史中位数,回答「正常吗」必须带对比("verify 已 14 分钟,历史中位 6 分钟,最后输出 2 分钟前,还在动");没数据就说没数据。
|
|
41
43
|
- **输出里的 `next:` 行是给会话的**,会话据此调命令,不把命令原文丢给用户;用户只需要回「批准 / 拒绝 / 接受 / 做完了 / A / B」。
|
|
42
44
|
- **人批三级**(§AGENTS 2.5):`STOP_NOW` 只用于跨发布门 / 扩范围 / 改冻结契约 / 不可逆外部动作 / 接受风险;可逆取舍攒到门前一次批 2~5 个;事实与派生约束自己定。**所有者以后要看见或念出来的名字与参数(域名、服务名、环境名、自停时长、窗口时长)属于门前决策项,不由 worker 顺手定**——真实事故:一个按内部术语起的服务名让所有者连问四轮才改成他听得懂的业务名。
|
|
@@ -56,6 +58,10 @@ allowedPaths:
|
|
|
56
58
|
- src
|
|
57
59
|
- tests
|
|
58
60
|
reviewTriage: required # P0/P1 先过人分诊再派 fixer
|
|
61
|
+
budgets: # 可省;run 配置 > 预设 > 默认。预算耗尽停人时,批准 resume-<step> 即多给一次
|
|
62
|
+
maxAttempts:
|
|
63
|
+
review: 2
|
|
64
|
+
reviewRoundsPerWork: 6 # 跨本 Work 所有 Run 累计的 review 轮数;超了新 Run 起跑前先问人
|
|
59
65
|
cache:
|
|
60
66
|
verify: tree # 同树+同命令+同信封已通过就复用证据(标 REUSED)
|
|
61
67
|
envelope:
|
|
@@ -85,6 +91,10 @@ workers:
|
|
|
85
91
|
|
|
86
92
|
(严格 YAML 子集:上面为省行用了行内 `[]`,实际文件要写成块列表。)通知通道另放 `.buildbeat/notify.yaml`(URL 只能来自环境变量),见 [docs/v2/guide/07-approval-guide.md](docs/v2/guide/07-approval-guide.md);十件套指南索引 [docs/v2/guide/README.md](docs/v2/guide/README.md)。
|
|
87
93
|
|
|
94
|
+
**第一次为一个项目写 run-config 时,会话要多问用户一句**:「Run 停下来等你批、跑完、或疑似卡住时,要不要推到钉钉/webhook?给我一个只放在环境变量里的 URL 就行」——试点一直没启用通知,一张合并卡就绪后隔夜等了 9.5 小时。用户说不要就记一句「通知未启用,等待只在 inbox 里」。
|
|
95
|
+
|
|
96
|
+
worker prompt 里要写清三条环境事实(模板 AGENTS 第 ⑨ 条):沙箱不能监听端口(socket 测试交给 verify)、PATH 只认 POSIX 工具、环境不满足就 `exit 75`。
|
|
97
|
+
|
|
88
98
|
## 1. 四根支柱(命根子,所有零件都为它们服务)
|
|
89
99
|
|
|
90
100
|
1. **端到端工作包**:一个 Builder 对一个需求/功能工作包的产品判断、实现、测试、合并与发布证据负责;需要隔离时再调用独立 AI 视角(独立 cwd / 上下文 / 写边界)。
|
package/docs/CLI.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# BuildBeat CLI lifecycle contract
|
|
2
2
|
|
|
3
|
-
Status: **BuildBeat `
|
|
3
|
+
Status: **BuildBeat `2.0.0` scoped distribution (stable `latest`; the v1 lifecycle CLI `buildbeat` is unchanged, the v2 delivery runtime `buildbeat-v2` ships alongside it — see `docs/v2/guide/`)** · previous independently verified stable `1.21.0` · canonical package `@haiyangbg/buildbeat` · canonical executable `buildbeat` · legacy package `solobaton@1.16.3` remains the independently verified read-only v0 · Node.js 20+ · zero third-party runtime dependencies. The 1.21 release keeps the verified 1.20 lifecycle command and safety boundaries, and adds the standard domain-response contract to the Skill and managed scaffold. The genuine lifecycle version-increment pilot remains archived in [`PHASE4-V1.20-PILOT-2026-08-25.md`](PHASE4-V1.20-PILOT-2026-08-25.md); exact 1.21 registry and supply-chain evidence is archived in [`V1.21-RELEASE-EVIDENCE-2026-08-25.md`](V1.21-RELEASE-EVIDENCE-2026-08-25.md).
|
|
4
4
|
|
|
5
5
|
The CLI does not replace `SKILL.md`. The Skill owns code-aware reasoning, minimal questions, project semantics, and human Gates. The CLI owns deterministic inspection, scaffold mechanics, manifest/hash bookkeeping, and bounded mechanical upgrade in the current scoped distribution. Synchronous file-bus checks remain authoritative in the project-local scripts specified by [`CHECKS.md`](CHECKS.md).
|
|
6
6
|
|
|
@@ -47,6 +47,7 @@ The target command whitelist is intentionally small:
|
|
|
47
47
|
|---|---|---|
|
|
48
48
|
| legacy `solobaton@1.16.3` | `doctor`, `init --dry-run`, `adopt --dry-run`, `version` | independently verified read-only v0; deprecated distribution ID after scoped migration |
|
|
49
49
|
| BuildBeat `1.21.0` | `doctor`, `init`, `adopt`, `upgrade`, `version` | unchanged Phase 0–3 command set; writes remain bounded by the transaction and human-Gate contracts below |
|
|
50
|
+
| BuildBeat `2.0.0` | same `buildbeat` command set; second executable `buildbeat-v2` (run / overview / inbox / status / approve / observe / gc …) | `buildbeat` lifecycle and safety boundaries unchanged; `buildbeat-v2` is the v2 runtime documented in [`v2/guide/README.md`](v2/guide/README.md) and never merges, pushes, deploys or publishes (invariant 20); the managed scaffold version stays `v1.21` (templates unchanged), so `upgrade` reports up-to-date for 1.21 scaffolds; the manifest `cliVersion` is a record, not an upgrade trigger |
|
|
50
51
|
|
|
51
52
|
`help`, `--help`, and `--version` are meta entry points. `diff` and `uninstall` stay reserved and return `command_not_available`; `gate`, `adr`, `standards`, `check`, and other workflow commands are outside the approved CLI scope. HELP text and regression tests must lock this boundary.
|
|
52
53
|
|
|
@@ -128,7 +129,7 @@ Schema 2 is the first write-capable shape targeted by Wave 1:
|
|
|
128
129
|
{
|
|
129
130
|
"schemaVersion": 2,
|
|
130
131
|
"scaffoldVersion": "v1.21",
|
|
131
|
-
"cliVersion": "2.0.0
|
|
132
|
+
"cliVersion": "2.0.0",
|
|
132
133
|
"layout": "default",
|
|
133
134
|
"installedAt": "2026-08-24T00:00:00.000Z",
|
|
134
135
|
"files": {
|
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 canonical executable is `buildbeat`, while `solobaton` remains an executable alias. The old `solobaton` npm package is a frozen legacy distribution ID and must not receive the scoped write/upgrade command surface.
|
|
4
4
|
|
|
5
|
-
Release evidence at source package version `@haiyangbg/buildbeat@2.0.0
|
|
5
|
+
Release evidence at source package version `@haiyangbg/buildbeat@2.0.0` (pending publication verification); latest independently verified BuildBeat npm distribution `@haiyangbg/buildbeat@2.0.0-beta.5` (dist-tag `next`; `latest` remains `1.21.0`), anchored by annotated tag `v2.0.0-beta.5` at commit `1d6553d`, workflow run [33972774150](https://github.com/HaiYangBG1/BuildBeat/actions/runs/33972774150), and 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). The latest stable distribution stays `@haiyangbg/buildbeat@1.21.0`, anchored by annotated tag `v1.21.0` at commit `ce69a05`, workflow run [32864438692](https://github.com/HaiYangBG1/BuildBeat/actions/runs/32864438692), and the matching [GitHub Release](https://github.com/HaiYangBG1/BuildBeat/releases/tag/v1.21.0). Exact registry identity, provenance, signatures, isolated-install readback, Environment approval, and immutable-artifact boundary are archived in [`V1.21-RELEASE-EVIDENCE-2026-08-25.md`](V1.21-RELEASE-EVIDENCE-2026-08-25.md). First-scoped-release bootstrap behavior and legacy deprecation remain archived in [`WP4.3-RELEASE-EVIDENCE-2026-08-25.md`](WP4.3-RELEASE-EVIDENCE-2026-08-25.md). The legacy distribution remains `solobaton@1.16.3`; all three published legacy versions are retained and deprecated toward the scoped package.
|
|
6
6
|
|
|
7
7
|
## Release invariants
|
|
8
8
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# v2.0.0-beta.4 发布证据(2026-09-03)
|
|
2
|
+
|
|
3
|
+
> 授权:所有者会话内「发布 beta.4 吧,授权也一起」(发布 + `npm-publish` 环境审批);流程与 [beta.3](V2.0.0-BETA.3-RELEASE-EVIDENCE-2026-09-01.md) 完全一致。
|
|
4
|
+
|
|
5
|
+
- 内容:迭代 08「等待要能找到人」——运行中可见性(实时输出、每步耗时/历史中位数、STALLED)、同 Work 新 Run 作废旧等待、`gc`、通知出站(`.buildbeat/notify.yaml` + `watch`)、`overview` Work 级总览、信封一等公民(`envelope:` / `--attempt new` / `redact:`)、verify 复用与增量审查、`release-readback` 上线回读车道、`requires:` 探针、可见命名进决策卡;Skill §0.5 驾驶手册 + `templates/v2/`;指南第 0 篇「怎么和会话说话」;仓库脱敏。明细见 [`CHANGELOG.md`](../CHANGELOG.md)、[`V2-ITERATION-08.md`](V2-ITERATION-08.md)。
|
|
6
|
+
- 候选:`6854b20`(`v2` tip),annotated tag `v2.0.0-beta.4`;本地候选门全过:`bash -n` / shellcheck / actionlint、`check:docs`(169 文件)、`test-scripts` 222 断言、plugin 7 断言、skill-only、pilot 15、node 182/182、`npm publish --dry-run`、gitleaks 无泄漏、`git diff --check`;CI run [33728721638](https://github.com/HaiYangBG1/BuildBeat/actions/runs/33728721638) success。发布前回读:registry 无 `2.0.0-beta.4`;tag ruleset `Protect release tags` = active、`refs/tags/v*`、仅 update/deletion、无 bypass。
|
|
7
|
+
- 发布:workflow run [33728863042](https://github.com/HaiYangBG1/BuildBeat/actions/runs/33728863042)(`workflow_dispatch` from `v2`,tag `v2.0.0-beta.4`)双 job success(`Publish v2.0.0-beta.4 to npm` + `Verify v2.0.0-beta.4 from npm`);`npm-publish` 环境审批按会话授权以所有者 gh 凭据落章,备注引用授权原文。
|
|
8
|
+
- 本地独立回读(直连 registry.npmjs.org,不经镜像):dist-tags `{bootstrap: 0.0.0, latest: 1.21.0, next: 2.0.0-beta.4}`(`latest` 未动);`dist.integrity = sha512-vjcJ4qzrBY1AFIiXuOpS/HvzaqfbtG3L5jmNxU1V8DNBydq3kOufjBBitvM067iP6O/kyThqcoekH4dWsBjb4Q==`;`dist.attestations.url` 存在;隔离前缀安装 `buildbeat --version = 2.0.0-beta.4`,`buildbeat-v2` usage 含 `overview` / `gc` / `watch`,`src/v2/runtime/` 含 `cache / envelope / gc / liveness / notify / overview`,包内含 `docs/v2/guide/00-how-to-talk.md`;`npm audit signatures` = 1 package has a verified attestation;`buildbeat doctor <空目录> --json` 返回有界 JSON(`cliVersion 2.0.0-beta.4`,`not-installed`),零写入。
|
|
9
|
+
- 未做:`latest` 仍指 v1.21.0(v2 仍是预发布);GitHub Release 页未建(与前几个 beta 一致,npm 面为准)。
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# v2.0.0-beta.5 发布证据(2026-09-05)
|
|
2
|
+
|
|
3
|
+
> 授权:所有者会话内「发,并且迭代5轮可以直接切换到线上版本了」(发布 + `npm-publish` 环境审批 + 发布后把本机 CLI 从源码链接切回正式包);流程与 [beta.4](V2.0.0-BETA.4-RELEASE-EVIDENCE-2026-09-03.md) 完全一致。
|
|
4
|
+
|
|
5
|
+
- 内容:迭代 09「预算是刹车、故障分开算、成本看得见」——预算耗尽后批准 `resume-<step>` 落 `BUDGET_EXTENDED` 多给一次、run 配置 `budgets:` 覆盖预设;worker 基础设施故障(timeout / crashed / invalid-output / exit 75)判 `infra` 停人、不派 fixer、不扣预算,无转移边的失败也停人;`overview` 每个 Work 一行 `cost:`、`budgets.reviewRoundsPerWork` 跨 Run 累计;overview 真相修正(MERGED 压过 CANCELLED、release 车道 `RELEASED`);`doctor` 读 start 第一道门;`resume --adopt <sha>`;起跑被仓锁挡住时说清在等谁;模板与指南(gitignore 排除运行时/工作树、worker 环境事实、通知询问);lessons #23–#25。明细见 [`CHANGELOG.md`](../CHANGELOG.md)。
|
|
6
|
+
- 候选:`1d6553d`(`v2` tip),annotated tag `v2.0.0-beta.5`;本地候选门全过:`bash -n` / shellcheck / actionlint、`check:docs`(170 文件)、`test-scripts` 228 断言、pilot 15、plugin 7、skill-only、node 205/205、`pack:check`、`npm publish --dry-run`、gitleaks 无泄漏、`git diff --check`(工作树与 `795190e...HEAD` 区间均干净;上一提交 `69bce32` 曾因 lessons.md 末尾空行让 CI 的 whitespace 检查失败,本批已改掉);CI run [33972673951](https://github.com/HaiYangBG1/BuildBeat/actions/runs/33972673951) success。发布前回读:registry `next` 仍为 `2.0.0-beta.4`、无 `2.0.0-beta.5`;tag ruleset `Protect release tags` = active。
|
|
7
|
+
- 发布:workflow run [33972774150](https://github.com/HaiYangBG1/BuildBeat/actions/runs/33972774150)(`workflow_dispatch` from `v2`,tag `v2.0.0-beta.5`)双 job success(`Publish v2.0.0-beta.5 to npm` + `Verify v2.0.0-beta.5 from npm`);`npm-publish` 环境审批按会话授权以所有者 gh 凭据落章,备注引用授权原文。
|
|
8
|
+
- 本地独立回读(直连 registry.npmjs.org,不经镜像):dist-tags `{bootstrap: 0.0.0, latest: 1.21.0, next: 2.0.0-beta.5}`(`latest` 未动);`dist.integrity = sha512-BepG2UYo5ZsPwXqY4AntszoSE9lJQETqqnXqyonoHXt9ZJ9TOG2ncPLz1YNzaTWKB61eEulk4aZ2z57lLyi0FQ==`;`dist.attestations.url` 存在;隔离前缀安装 `buildbeat --version = 2.0.0-beta.5`,`buildbeat-v2` usage 含 `resume … --adopt`,`src/v2/runtime/` 含 `work-cost.js`;临时项目 `npm audit signatures` = 1 package has a verified attestation;`buildbeat doctor <空目录> --json` 返回有界 JSON(`cliVersion 2.0.0-beta.5`,`not-installed`),零写入。
|
|
9
|
+
- 切换:所有者本机全局 `buildbeat-v2` 由源码 `npm link` 切回正式包(`npm i -g @haiyangbg/buildbeat@next`),`readlink` 指向全局 `node_modules/@haiyangbg/buildbeat/bin/buildbeat-v2.js`,`buildbeat --version = 2.0.0-beta.5`。
|
|
10
|
+
- 未做:`latest` 仍指 v1.21.0(v2 仍是预发布);GitHub Release 页未建(与前几个 beta 一致,npm 面为准)。
|
|
@@ -63,15 +63,16 @@
|
|
|
63
63
|
| `RUN_STARTED` | kernel | —— | 进入 RUNNING |
|
|
64
64
|
| `WORKSPACE_BOUND` | kernel | `workspaceId, repo, branch, worktreePath, base` | 一个 Run 可多次(多仓绑定,卡点 2/4) |
|
|
65
65
|
| `STEP_STARTED` | kernel | `step, attempt, worker, adapter, workspaceId` | Step 开跑 |
|
|
66
|
-
| `STEP_FINISHED` | kernel | `step, attempt, status ∈ {succeeded,failed,blocked,invalid-output,timeout,crashed}, exitCode
|
|
67
|
-
| `CANDIDATE_PINNED` | kernel | `workspaceId, base, candidate` | candidate 由 Git 回读后固定 |
|
|
66
|
+
| `STEP_FINISHED` | kernel | `step, attempt, status ∈ {succeeded,failed,blocked,invalid-output,timeout,crashed}, exitCode?`;additive(迭代 09):`infra?: true`(worker 基础设施故障:timeout / crashed / invalid-output / exit 75;状态 `steps[step].infraAttempts` 累加,不扣预算) | Adapter 异常退出也必须落此事件(不变量 15) |
|
|
67
|
+
| `CANDIDATE_PINNED` | kernel / human | `workspaceId, base, candidate`;additive(迭代 09):`adopted?: true`(人或驾驶会话手修后经 `resume --adopt` 供出的候选,actor 为 human) | candidate 由 Git 回读后固定 |
|
|
68
68
|
| `EVIDENCE_RECORDED` | kernel/provider | `evidenceRef, kind, subject, digest, status, grade`;additive(迭代 08):`cacheKey?`(树+命令+信封的复用键)、`reused? {run, evidenceRef, digest}`(本记录引用了哪次已通过的证据而未重跑) | 指向满足 Evidence Contract 的记录 |
|
|
69
69
|
| `POLICY_EVALUATED` | kernel | `policy, phase ∈ {pre,post,transition,action}, result ∈ GateResult, enforcement, reason` | 每次 Policy 裁决可解释 |
|
|
70
70
|
| `TRANSITION` | kernel | `from, to, cause` | 每次状态转换一条(不变量 5) |
|
|
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
|
-
| `
|
|
74
|
-
| `
|
|
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 |
|
|
75
|
+
| `DECISION_RECORDED` | human | `decision ∈ {approved,rejected}, transition, subject, decisionRef`;additive(迭代 09):`adopted?: <sha>`、`resumeAt?: <step>`(adopt 时 subject 即该提交,恢复从 `resumeAt` 起而非 transition 所指的步) | 同步落 Git 决策记录 |
|
|
75
76
|
| `APPROVAL_STALE` | kernel | `approvalRef, changed ⊆ {candidate,plan,evidence}` | F6 的机器化 |
|
|
76
77
|
| `CHECKPOINT` | kernel | `resumePoint{step,attempt}, workspaceStates[]` | F5 的机器化:恢复只允许从最近 CHECKPOINT 或安全推导点继续 |
|
|
77
78
|
| `RUN_INTERRUPTED` | kernel | `cause` | 尽力而为;崩溃时允许缺失,恢复逻辑不得依赖其存在 |
|
|
@@ -52,7 +52,40 @@ run 配置还可声明(beta.3,皆来自三十轮部署战役的真实事故
|
|
|
52
52
|
|
|
53
53
|
## review 轮数预算
|
|
54
54
|
|
|
55
|
-
官方预设自带 `budgets.maxAttempts.review: 2`(战役章程"每 Run 2 轮 review 封顶"的原生化):第三轮 review 在启动前即停 `WAITING_HUMAN
|
|
55
|
+
官方预设自带 `budgets.maxAttempts.review: 2`(战役章程"每 Run 2 轮 review 封顶"的原生化):第三轮 review 在启动前即停 `WAITING_HUMAN`,理由写明预算耗尽。机制就是每步 `maxAttempts`,无需新概念。
|
|
56
|
+
|
|
57
|
+
预算耗尽后停的那次 `resume-<step>`,**人批准即多给一次**:内核落一条 `BUDGET_EXTENDED`(台账事实,可重放),该步上限 +1 再跑;拒绝即终止 Run。此前批准只会让同一请求立刻回来(试点两条应用登录 Run 因此以 CANCELLED 收场,候选却已在生产)。
|
|
58
|
+
|
|
59
|
+
run 配置可覆盖预设(run 配置 > 预设 > 全局 `maxAttemptsPerStep`):
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
budgets:
|
|
63
|
+
maxAttempts:
|
|
64
|
+
review: 3
|
|
65
|
+
verify: 6
|
|
66
|
+
reviewRoundsPerWork: 6 # 跨本 Work 所有 Run(含已作废)累计的 review 轮数上限,见 overview 指南
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`doctor` 打印每步生效的上限与来源(run config / workflow preset / default)。
|
|
70
|
+
|
|
71
|
+
**按 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 / 几小时)就对着它核。
|
|
72
|
+
|
|
73
|
+
## 工作树在仓内:把 `.buildbeat/` 排除出测试收集(迭代 09)
|
|
74
|
+
|
|
75
|
+
Run 的隔离工作树在 `<repo>/.buildbeat/worktrees/<RUN>/`,运行时台账在 `<repo>/.buildbeat/runtime/`。两者都不入 git(模板 `.gitignore` 已排除;尊重 `.gitignore` 的工具如 `rg`、`gitleaks` 随之不再走进去),但**测试框架按文件系统收集用例**:试点合并后的主干 vitest 把残留工作树里旧候选的用例一起跑了,噪声直到 `gc` 才消失。在项目里加:
|
|
76
|
+
|
|
77
|
+
- vitest:`test.exclude: ['**/node_modules/**', '**/.buildbeat/**']`
|
|
78
|
+
- jest:`testPathIgnorePatterns: ['/node_modules/', '/.buildbeat/']`
|
|
79
|
+
- pytest:`norecursedirs = .buildbeat`
|
|
80
|
+
- Maven / Gradle 只收集 `src/**`,不受影响;Playwright 的 `testDir` 指到具体目录即可。
|
|
81
|
+
|
|
82
|
+
`start` 被「another run is active」挡住时,CLI 现在打印持锁的 Run、它在哪一步、最后一次事件多久前,以及可复制的 `status` 命令;仓级单活动 Run 锁本身没放开——工作树已隔离,锁只剩台账与合并安全的意义,等真出现第二次多小时排队再动。
|
|
83
|
+
|
|
84
|
+
## 基础设施故障与候选缺陷分开算(迭代 09)
|
|
85
|
+
|
|
86
|
+
worker 的超时、崩溃、非信封输出,以及 worker 主动以退出码 **75** 结束(约定:verify / 包装脚本发现环境不满足——命令不在 PATH、端口被占、后端 404、沙箱禁止监听——就 `exit 75`),内核一律判 `infra`:`STEP_FINISHED.data.infra = true`,不记失败指纹、不派 fixer、该步预算不扣(`steps[step].infraAttempts` 抵回),停 `WAITING_HUMAN`(kind `infra`)。人批准 `resume-<step>` 重跑,拒绝结束。其余非零退出仍是候选失败,走 `on: failed` 边。
|
|
87
|
+
|
|
88
|
+
没有转移边的失败结果(预设里 build、review、fix 的 `failed`)也不再终态,停 `resume-<step>` 交人决定。终态 FAILED 只剩 policy `BLOCK`。
|
|
56
89
|
|
|
57
90
|
## 迭代 08 新增的 run 配置段
|
|
58
91
|
|
|
@@ -79,6 +79,20 @@ merge 批准只表示 **merge-ready**:真正的合并、push、发布是你在
|
|
|
79
79
|
|
|
80
80
|
`inbox` 只知道哪个 Run 在等人;`buildbeat-v2 overview --repo .` 按 Work 回答「走到哪、下一步该谁」——intent/plan 是否被接受(接受后改过即 `stale`)、最新 Run 状态与候选、候选是否已合入当前分支、未裁决 P0/P1 数、是否有 `env-facts.md`,每行附下一句命令。运行时被删后由 Git 面 run-record 补足。会话开场先跑它,再回答用户「当前进度」。
|
|
81
81
|
|
|
82
|
+
**阶段判定的真相修正(迭代 09)**:候选只要合入了当前分支,Work 就是 `MERGED`,哪怕最新 Run 是 CANCELLED(试点一条应用登录 Run 因预算问题被取消,候选却已在生产,overview 曾报 `STOPPED_CANCELLED` 并催重试);`release-readback` 车道成功关窗的 Work 显示 `RELEASED`,不再说 "nothing to merge";已合并 / 已发布 / 已关闭的 Work 不再提示未裁决 finding 数。`overview` 每个 Work 还多一行 `cost:`(见 [Workflow 指南](02-workflow-guide.md) 的 Work 级预算)。
|
|
83
|
+
|
|
84
|
+
## 你自己改好了:`resume --adopt`(迭代 09)
|
|
85
|
+
|
|
86
|
+
Run 停在 `enter-fix` / `resume-fix` 时,驾驶会话或人常常已经在 Run 的 worktree 里把问题修掉并提交了。此时再 `approve` 会派一个无事可做的 fixer,再多跑一次 verify(试点一条前端 Run 因此跑到 verify 第 5 次、fix 第 3 次)。改用:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
buildbeat-v2 resume --config <run-config.yaml> --adopt <sha> --by <名字>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
内核回读 worktree:树必须干净、HEAD 必须就是 `<sha>`(前缀 7 位起),否则拒绝;然后以人为 actor 落 `CANDIDATE_PINNED`(`adopted: true`)、以该提交为 subject 记 `DECISION_RECORDED`(`adopted`、`resumeAt`),并从 verify 继续(预设里 fix 成功后的下一步)。台账里看得出这一版候选是谁供的。合并决定处不接受 adopt。
|
|
93
|
+
|
|
94
|
+
`doctor` 现在还打印本仓 `delivery/work/<ID>/` 里 intent / plan 的存在与接受状态,并对每条要求 `artifact.accepted` 的 policy 预告"start 会停在哪一步"——此前两次 doctor 通过、start 却被"plan 未镜像到子仓"挡住。
|
|
95
|
+
|
|
82
96
|
## 可见命名是门前决策项(迭代 08)
|
|
83
97
|
|
|
84
98
|
审批三级里 `BATCH_AT_GATE` 明确包含:域名、服务名、环境名、自停时长、窗口时长等**所有者以后要看见或念出来的名字与参数**。worker 顺手定的名字进不了台账;planner 在 intent 里列出并给推荐值,人一次批。
|
|
@@ -34,9 +34,10 @@ buildbeat-v2 stop --repo . --run RUN-X --reason "crashed; releasing lock"
|
|
|
34
34
|
|
|
35
35
|
### Worker 行为异常
|
|
36
36
|
|
|
37
|
-
-
|
|
37
|
+
- **worker 基础设施故障(迭代 09)**:超时、崩溃、输出不是信封(`invalid-output`)、或 worker 自己以退出码 **75**(`EX_TEMPFAIL`,"环境不可用")结束——内核判为 `infra`:不记失败指纹、不派 fixer、**不扣该步预算**,停 `WAITING_HUMAN`(kind `infra`,transition `resume-<step>`),通知照常出站。后端恢复后 `approve --transition resume-<step>` 重跑该步;`reject` 结束 Run。真实事故:worker 服务端 404 与非 JSON 输出两天杀掉 5 个 Run,驾驶会话手写探针每两分钟试一次;PATH 缺 rg、端口撞车、宿主负载 280 各派了一次 fixer。
|
|
38
|
+
- **没有转移边的失败**(如预设里 build / review / fix 的 `failed`)不再终态 FAILED,同样停 `resume-<step>` 由人决定重跑或结束。
|
|
38
39
|
- 越界写入 → Run BLOCK 且不固定 candidate:检查 `allowedPaths` 与 Worker prompt 的范围声明;
|
|
39
|
-
- 超时 →
|
|
40
|
+
- 超时 → 先看是不是环境(`infra` 已停人),再调 `timeoutMs`;超预算 → 这是刹车不是故障,批准 `resume-<step>` 即多给一次,或收 scope。
|
|
40
41
|
|
|
41
42
|
### observe 面
|
|
42
43
|
|
package/lessons.md
CHANGED
|
@@ -137,3 +137,28 @@
|
|
|
137
137
|
**症状**:两个子仓 58 个 Run 里 32 个被取消,多数是在等人批的状态挂满一天后被批量清掉,人批平均等 7~12 小时;另一边所有者守着一场部署战役时,在同一会话里问了十几次「半小时了正常吗」「十分钟了是卡住了吗」「现在到底是谁在干活?」。事后清理时又发现 16 个终态 Run 的工作树无人收拾。
|
|
138
138
|
**根因**:Run 的状态只存在于台账和 `inbox`,不主动出站——人只有开一个 AI 会话问才知道有东西等他;`status` 只有步骤和次数,worker 输出被同步 spawn 缓冲到步结束才可见,人手里没有任何时间读数;新 Run 起跑不作废旧等待,残留物也没有回收命令。
|
|
139
139
|
**解药**:`status` 带每步耗时、同仓历史中位数、最后一次输出距今与末几行,无输出超阈值标 `STALLED`(只标不杀);`.buildbeat/notify.yaml` 把 `HUMAN_REQUESTED / RUN_TERMINAL / STALLED` 出站到钉钉或 webhook(URL 只走环境变量,失败不影响 Run);同 Work 新 Run 自动作废旧等待;`gc` 按"终态且已压账、候选可从别处到达"的规则清工作树;`overview` 回答「到哪了、下一步该谁」。通用原则:**等待必须能找到人,时间必须能被读到;做不到这两条,人就会用反复追问来补,而追问本身就是流程债。**
|
|
140
|
+
|
|
141
|
+
## 22. 多仓 map 假设"一仓一版本一 CHANGELOG",真实工作区三条都不成立
|
|
142
|
+
|
|
143
|
+
**症状**:试点工作区登记 `buildbeat-multirepo-map:v1` 后,unverified 从 4 条涨到 10 条:后端是多模块仓(根下没有 CHANGELOG,每个服务一份);CLI 仓的 CHANGELOG 是 npm 包 semver,跟能力契约版本根本不是一个域;只读存量前端没有任何契约;两个真有契约关系的仓,CHANGELOG 首行是「Deployed 日期 · sha」而不是版本号。map 的本意(契约↔实现↔部署三源对齐)对这套仓形状一条都核不上,结果是"登记了反而更吵",会话只能在「不登记(4 条 absent)」和「登记(10 条各种缺来源)」之间选噪音更小的。
|
|
144
|
+
**根因**:模板把 Keep a Changelog 单包仓当成唯一形状;"缺来源保留 unverified"是对的,但缺的不是来源而是**表达能力**——没有办法说"这个仓的版本在这个子路径"和"这个仓就是没有契约版本域"。
|
|
145
|
+
**解药**:map 行可选 `changelog=<仓内模块路径>`;`contract=n/a` 只登记不核对(注释里写清不得拿它掩盖真实契约关系);被核对的 CHANGELOG 首个已发布标题以契约版本开头(`## [v1.3 · Deployed …]`),Deployed·sha 信息保留在版本号之后。登记后两个有契约的仓 ✅、两个无版本域的仓一行登记,把契约版本临时改成 v1.4 能触发 conflict,门确实在守。
|
|
146
|
+
**同批发现**:`overview` 的 `next:` 让人"record the work as closed in decisions.jsonl",但代码里没有任何读者,12 个已关闭 Work 常年显示 `READY_TO_RUN`——提示里出现的每个动作都要有读它的代码,否则是给人挖坑。
|
|
147
|
+
|
|
148
|
+
## 23. 预算是刹车不是墙:人批了内核当没批,按 Run 计的预算又被"每轮一个新 Run"绕过
|
|
149
|
+
|
|
150
|
+
**症状**:review 预算(预设 2 轮)耗尽后停人,人批准 `resume-review`,内核在下一轮起跑前再判一次"超预算",同一请求立刻回来;run-config 改不动预设里的数。驾驶会话只好在 Run 外另找 reviewer 做 closure,两条已上生产的应用登录 Run 都以 CANCELLED 收场。另一头,像素风 Work 每修一轮就起一个新 Run,21 个 Run、9 轮 review、22 条 finding,每 Run 2 轮的封顶一次没触发;驾驶会话口头"止损"三次才真正停手。platform-health 烧了 10 个 Run 约一天,所有者在 Gate4 后才以"成本太高"砍掉,此前没有任何地方能看到花了多少。
|
|
151
|
+
**根因**:预算只是一个数,不是一个可以被人延展的台账事实;预算的计数单位(Run)和人的决策单位(Work)不一致;成本只在事后复盘时才被算出来。
|
|
152
|
+
**解药**:批准预算耗尽的 `resume-<step>` 即落 `BUDGET_EXTENDED`,该步上限 +1;run-config `budgets:` 覆盖预设;`budgets.reviewRoundsPerWork` 跨本 Work 所有 Run(含作废、含已压账)累计 review 轮数,达标在 review 起跑前停人;`overview` 每个 Work 一行 `cost:`(review 轮 / finding / 等人次 / worker 时长),intent 模板加止损线。通用原则:**预算的计数单位要和人做决定的单位一致,预算耗尽后的人批必须改变内核状态,否则人批等于没批。**
|
|
153
|
+
|
|
154
|
+
## 24. worker 基础设施故障被当成候选失败:杀 Run、派 fixer、烧预算
|
|
155
|
+
|
|
156
|
+
**症状**:worker 后端断服(review 退出码 97)、reviewer 输出不是 JSON、fix 步超时,三种都走"no transition for (review, failed)"直接终态 FAILED,两天 5 个 Run 这样死;驾驶会话为了等后端恢复手写探针,每两分钟起一个"Reply with exactly: OK"会话,共 14 次。另一类:verify 因 PATH 缺 rg、端口 4173 撞车、宿主负载 280、守卫误报被判失败,5 次派了 fixer 去修一个没问题的候选。
|
|
157
|
+
**根因**:step 状态词汇里已有 timeout / crashed / invalid-output,但路由把它们和"候选没过"一起压成 `failed`;verify 脚本没有办法告诉内核"是环境不行不是代码不行";没有边的失败一律终态。
|
|
158
|
+
**解药**:timeout / crashed / invalid-output / 退出码 75(`EX_TEMPFAIL`,约定为"环境不可用")判 `infra`:不记失败指纹、不派 fixer、不扣预算(`infraAttempts` 抵回),停 `WAITING_HUMAN`(kind `infra`)等后端恢复;没有转移边的失败也停人而不终态,终态 FAILED 只剩 policy BLOCK。模板 worker 合同写清三条环境事实(沙箱不能监听端口、PATH 只认 POSIX、环境不满足 `exit 75`)。通用原则:**先问"这次失败说的是候选还是环境",再决定谁来修;答不上来的失败交给人,不交给 fixer。**
|
|
159
|
+
|
|
160
|
+
## 25. 台账说的和人看到的不是一回事:已上线显示为取消、已发布显示为"没东西可合"、doctor 过了 start 却被挡
|
|
161
|
+
|
|
162
|
+
**症状**:候选已合入生产的 Work 因最后一个 Run 是 CANCELLED 而显示 `STOPPED_CANCELLED`,`next:` 催重试;四个已关窗的 release 车道 Work 显示 `MERGE_READY`、"nothing to merge";已合并的 Work 仍报 14 条未裁决 finding;`doctor` 通过两次,`start` 都被"plan 未镜像到子仓"的 policy 挡在 build;驾驶会话在 worktree 里手修并提交后,只能批准 enter-fix 让 fixer 空跑再多一次 verify(前端 Run 因此 verify 5 次、fix 3 次)。
|
|
163
|
+
**根因**:阶段判定只看最后一个 Run 的终态,不看候选是否已在主干;overview 不认识 release 车道;doctor 检查的是配置合法性,不是 start 第一道门会读的事实;内核只认 worker 产出的候选,没有"人供候选"的入口。
|
|
164
|
+
**解药**:候选合入主干即 `MERGED`(哪怕最新 Run 取消),release 车道成功关窗即 `RELEASED`,已合并/已发布/已关闭不再提示未裁决数;`doctor` 打印本仓 intent/plan 存在与接受状态,并按 policy 预告 start 会停在哪一步;`resume --adopt <sha>` 以人为 actor 钉候选、从 verify 续跑。**同批发现**:仓内 `.buildbeat/worktrees/` 会被 vitest 等按文件系统收集的框架当成测试目录(模板 gitignore 与指南补排除);`start` 被仓锁挡住时只说"another run is active",现在打印持锁 Run 与 `status` 命令,锁本身未放开。通用原则:**任何"到哪了"的读数都必须从事实(主干、车道、门)推导,而不是从最后一条记录的状态字段抄。**
|
package/package.json
CHANGED
package/src/v2/adapters/mock.js
CHANGED
|
@@ -11,7 +11,7 @@ export class MockScriptError extends Error {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const BEHAVIORS = new Set(["succeed", "fail", "timeout", "crash", "invalid-output"]);
|
|
14
|
+
const BEHAVIORS = new Set(["succeed", "fail", "timeout", "crash", "invalid-output", "env-fail"]);
|
|
15
15
|
|
|
16
16
|
export function createMockAdapter(script) {
|
|
17
17
|
const remaining = {};
|
|
@@ -53,12 +53,19 @@ export function createMockAdapter(script) {
|
|
|
53
53
|
return { ...base, exitCode: 0 };
|
|
54
54
|
case "fail":
|
|
55
55
|
return { ...base, exitCode: 1, stderr: "mock failure" };
|
|
56
|
+
case "env-fail":
|
|
57
|
+
// Exit 75 (EX_TEMPFAIL): the worker says its environment, not the
|
|
58
|
+
// candidate, is what failed.
|
|
59
|
+
return { ...base, exitCode: 75, stderr: "mock environment unavailable" };
|
|
56
60
|
case "timeout":
|
|
57
61
|
return { ...base, exitCode: null, timedOut: true };
|
|
58
62
|
case "crash":
|
|
59
63
|
return { ...base, exitCode: null, signal: "SIGKILL" };
|
|
60
64
|
case "invalid-output":
|
|
61
|
-
|
|
65
|
+
// The kernel judges the envelope, not stdout: hand back a string
|
|
66
|
+
// that is not JSON so parseEnvelope fails the way a real worker's
|
|
67
|
+
// prose reply does.
|
|
68
|
+
return { ...base, exitCode: 0, stdout: "not-a-valid-worker-envelope", envelope: "not-a-valid-worker-envelope" };
|
|
62
69
|
default:
|
|
63
70
|
throw new MockScriptError(`unreachable behavior: ${behavior}`);
|
|
64
71
|
}
|