@haiyangbg/buildbeat 2.0.0-beta.3 → 2.0.0-beta.4
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 +29 -7
- package/SKILL.md +76 -2
- package/docs/CLI-PILOT-2026-08-23.md +1 -1
- package/docs/CLI.md +1 -1
- package/docs/EXECUTION-PLAN.md +2 -2
- package/docs/PHASE2-PILOT-PREFLIGHT-2026-08-25.md +2 -2
- package/docs/PHASE4-V1.20-PILOT-2026-08-25.md +2 -2
- package/docs/RELEASING.md +1 -1
- package/docs/V2-D2-DECISION-CARD.md +2 -2
- package/docs/V2-DECISIONS.md +2 -2
- package/docs/V2-ITERATION-01.md +13 -13
- package/docs/V2-ITERATION-06.md +2 -2
- package/docs/V2-ITERATION-08.md +62 -0
- package/docs/V2-PLAN.md +6 -6
- package/docs/V2-PROPOSAL.md +2 -2
- package/docs/V2.0.0-BETA.1-RELEASE-EVIDENCE-2026-08-28.md +1 -1
- package/docs/V2.0.0-BETA.2-RELEASE-EVIDENCE-2026-08-28.md +1 -1
- package/docs/V2.0.0-BETA.3-RELEASE-EVIDENCE-2026-09-01.md +8 -0
- package/docs/v2/M4-EXTERNAL-PILOT-2026-08-28.md +11 -11
- package/docs/v2/{M4-CHICKAI-PILOT-2026-08-28.md → M4-PILOT-APP-2026-08-28.md} +4 -4
- package/docs/v2/M4-SELFHOST-2026-08-28.md +1 -1
- package/docs/v2/RFC-0001-product-definition.md +2 -2
- package/docs/v2/SPEC-0001-events-v1.md +2 -2
- package/docs/v2/guide/00-how-to-talk.md +57 -0
- package/docs/v2/guide/01-quickstart.md +4 -0
- package/docs/v2/guide/02-workflow-guide.md +14 -0
- package/docs/v2/guide/04-adapter-guide.md +4 -0
- package/docs/v2/guide/05-worker-contract.md +10 -0
- package/docs/v2/guide/06-evidence-guide.md +4 -0
- package/docs/v2/guide/07-approval-guide.md +33 -0
- package/docs/v2/guide/10-recovery.md +22 -1
- package/docs/v2/guide/README.md +3 -0
- package/example/.buildbeat/manifest.json +1 -1
- package/lessons.md +12 -0
- package/package.json +1 -1
- package/src/v2/adapters/shell.js +87 -14
- package/src/v2/cli/run.js +461 -25
- package/src/v2/engine/reducer.js +2 -0
- package/src/v2/engine/workflow.js +8 -1
- package/src/v2/evidence/collector.js +14 -3
- package/src/v2/presets/release-readback.yaml +36 -0
- package/src/v2/presets/risk/release.yaml +21 -0
- package/src/v2/runtime/cache.js +124 -0
- package/src/v2/runtime/env-contract.js +35 -1
- package/src/v2/runtime/envelope.js +183 -0
- package/src/v2/runtime/gc.js +182 -0
- package/src/v2/runtime/liveness.js +193 -0
- package/src/v2/runtime/metrics.js +8 -0
- package/src/v2/runtime/notify.js +223 -0
- package/src/v2/runtime/orchestrator.js +145 -8
- package/src/v2/runtime/overview.js +264 -0
- package/templates/v2/AGENTS.md +72 -0
- package/templates/v2//346/214/207/346/214/245/345/217/260.md +36 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# v2.0.0-beta.3 发布证据(2026-09-01)
|
|
2
|
+
|
|
3
|
+
> 授权:所有者会话内「发」(beta.3 发布 + 部署审批);流程与 [beta.1](V2.0.0-BETA.1-RELEASE-EVIDENCE-2026-08-28.md)/[beta.2](V2.0.0-BETA.2-RELEASE-EVIDENCE-2026-08-28.md) 完全一致。
|
|
4
|
+
|
|
5
|
+
- 内容:三十轮部署战役(meta 试点仓 `WORK-PILOT-DEPLOY-01`,DEPLOY-01~30 + L4 之夜)的机制回灌——发现分诊门(`reviewTriage: required`)、裁决台账与锚定审查(`review-findings.jsonl` + `findings` 命令)、环境契约(`requires:`)、review 预算封顶 2 轮(预设原生)、`preflight` 预检通道、崩溃恢复改重跑中断步(`f6d59c5`+`0030e77`+`4b1b466`);另含战役期所有者会话的 4 个内核修复(预算守卫 `05e535c`、porcelain 列 `eb965b9`、证据引用相对化 `ef176d5`、打包启动器测试 `ab267bd`)与 Node<20 守卫(`a3c176a`)。
|
|
6
|
+
- 候选:`02d1f5a`(`v2` tip),tag `v2.0.0-beta.3`;本地全量门 node 164/164 + docs 检查全过;CI run(`4b1b466`)conclusion=success(`0030e77` 上的唯一红项为测试自身平台差异——GNU `true --version` 在 Linux 打印版本号——已以 `4b1b466` 修复)。
|
|
7
|
+
- 发布:workflow run [33460544343](https://github.com/HaiYangBG1/BuildBeat/actions/runs/33460544343) 双 job success(publish + verify:exact integrity、dist-tag 路由、provenance、隔离安装、签名审计);npm-publish 环境审批以所有者 gh 凭据执行。
|
|
8
|
+
- 本地独立回读(直连 registry.npmjs.org,不经镜像):dist-tags `{bootstrap: 0.0.0, latest: 1.21.0, next: 2.0.0-beta.3}`(`latest` 未动);`dist.integrity = sha512-K9H/nkK8YTuWwU3u/TcwTXpO/RnAYiChskWBlDyh2TG6DBRtibOk3KH4VxPQdkdOtcUGXm6N+MoEfNH8autw8w==`;`npm audit signatures` = registry signature + attestation 双 verified;隔离安装 smoke:`buildbeat-v2` usage 含 `preflight`/`findings` 新命令、`buildbeat --version` = 2.0.0-beta.3、`src/v2/runtime/findings.js`+`env-contract.js` 在包内。
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
# M4 外部试点证据:
|
|
1
|
+
# M4 外部试点证据:pilot-auth 数仓 CLI 标识重命名(RUN-PILOT-EXT-01)
|
|
2
2
|
|
|
3
3
|
> 日期:2026-08-28
|
|
4
|
-
>
|
|
5
|
-
> 任务:项目所有者点名的真实需求 `LXJ-AUTH-
|
|
4
|
+
> 项目:`<试点工作区>/pilot-backend`(真实业务单仓:Java 多模块 + Node portal 测试 + 真实远端)
|
|
5
|
+
> 任务:项目所有者点名的真实需求 `LXJ-AUTH-PILOT-EXT-01`——数仓 CLI 公有客户端标识 `client-b-old` 精确重命名为 `client-b`(meta 仓 `pm/decisions.md` 当日拍板行)
|
|
6
6
|
> 结论上限:本地候选 + 本地真实测试;不含生产切换(提案 §4 硬门未授权)。**Run 停在合并决定,等待项目所有者。**
|
|
7
7
|
|
|
8
8
|
## 1. 为什么这个试点有分量
|
|
9
9
|
|
|
10
|
-
同一需求今天早些时候已被**人工方式**做过一遍:候选散落在两个仓的未提交工作树里,与无关改动混杂,当日人工 L3 证据自记"没有 clean candidate hash,不满足 review-ready"。本 Run 从**已提交干净基线** `a99d2ad1`(仍是 `
|
|
10
|
+
同一需求今天早些时候已被**人工方式**做过一遍:候选散落在两个仓的未提交工作树里,与无关改动混杂,当日人工 L3 证据自记"没有 clean candidate hash,不满足 review-ready"。本 Run 从**已提交干净基线** `a99d2ad1`(仍是 `client-b-old`)出发,由 v2 Runner 驱动真实 Agent 独立重做,产出可审查的干净 candidate——这正是 M-1 卡点(人是节拍器、无干净候选)的正面对照。人工候选未被触碰,不倒算、不回放。
|
|
11
11
|
|
|
12
12
|
## 2. 流程事实(5.2 分钟全自动到合并决定)
|
|
13
13
|
|
|
14
14
|
| 事实 | 值 |
|
|
15
15
|
|---|---|
|
|
16
16
|
| Workers | **codex CLI 经 Shell Adapter**(厂商中立实证:Claude CLI 未登录不可用,换 codex 零运行时改动,裁决 #5) |
|
|
17
|
-
| builder | `codex exec -s workspace-write`(沙箱内只改文件;commit 由包装脚本机械执行);15 文件 +68/−33,**全部在 `
|
|
18
|
-
| verify | `test-jdk17.sh`(Surefire 全量)+ portal node 测试 + 验收 grep(`
|
|
17
|
+
| builder | `codex exec -s workspace-write`(沙箱内只改文件;commit 由包装脚本机械执行);15 文件 +68/−33,**全部在 `pilot-auth/` 内**(allowedPaths 强制) |
|
|
18
|
+
| verify | `test-jdk17.sh`(Surefire 全量)+ portal node 测试 + 验收 grep(`client-b-old` 零残留、`client-b` 在册)——一次全绿,退出码回读 |
|
|
19
19
|
| review | `codex exec -s read-only` fresh-context 只读审查,结构化信封 `{"status":"succeeded","findings":[]}` |
|
|
20
|
-
| candidate | `f97f122`(Git 回读固定,位于 `run/RUN-
|
|
21
|
-
| UI 证据 | portal 文档页 Chrome headless 真渲染截图(页面源码含 `
|
|
22
|
-
| 治理 | `standard` 预设:plan/intent digest 绑定接受(`A-WORK-
|
|
20
|
+
| candidate | `f97f122`(Git 回读固定,位于 `run/RUN-PILOT-EXT-01` 分支,未合并) |
|
|
21
|
+
| UI 证据 | portal 文档页 Chrome headless 真渲染截图(页面源码含 `client-b`、无 `client-b-old`),digest 登记为 screenshot 证据(seq 29);`ui-render-merge-gate` 要求批准前必须存在 |
|
|
22
|
+
| 治理 | `standard` 预设:plan/intent digest 绑定接受(`A-WORK-PILOT-EXT-01-1/2`,by haiyangbg)为 build 前置门;env 白名单(宿主凭据不达 codex 子进程);真实远端 上 worktree 推送保护生效 |
|
|
23
23
|
| 台账 | 29 事件链校验通过;metrics:自动到达 `WAITING_HUMAN` 100%、证据完整率 100%(3/3 步) |
|
|
24
24
|
| 成本 | codex token/费用本轮无采集口径,记 `UNVERIFIED` |
|
|
25
25
|
|
|
@@ -31,13 +31,13 @@
|
|
|
31
31
|
|
|
32
32
|
## 4. 合并决定(已批准)
|
|
33
33
|
|
|
34
|
-
项目所有者于 2026-08-28 批准:`D-RUN-
|
|
34
|
+
项目所有者于 2026-08-28 批准:`D-RUN-PILOT-EXT-01-1`(merge-evidence-floor 与 ui-render-merge-gate 在盖章瞬间均为 PASS)。Run 终态 `SUCCEEDED`,压实为 `delivery/work/WORK-PILOT-EXT-01/runs/RUN-PILOT-EXT-01/run-record.json`,最终台账 34 事件链校验通过;worktree 已清理,candidate `f97f122` 保留在 `run/RUN-PILOT-EXT-01` 分支可达。
|
|
35
35
|
|
|
36
36
|
批准仅表示 merge-ready;将候选并入工作分支、与既有人工候选合流、契约同步与生产切换(提案 §4 硬门)均为后续人工决定,本 Run 未执行任何一项。
|
|
37
37
|
|
|
38
38
|
## 4.1 生产切换(2026-08-28 当日晚,所有者逐步授权后完成)
|
|
39
39
|
|
|
40
|
-
candidate `f97f122` 经 cherry-pick 到生产血统(`
|
|
40
|
+
candidate `f97f122` 经 cherry-pick 到生产血统(`origin/master`,规避了本地分支上未批准的 registry 在途工作与已部署内网文档的双向分叉)→ 全量验证(Surefire 全套 + Portal 29/29 + 零残留)→ 按提案 §4 硬门完成生产切换:只读盘点(唯一 `client-b-old` 行 / 零 Nacos 覆盖 / 零真实消费方登录记录)→ **有界双行窗口**破解新旧健康门顺序死锁(先 INSERT `client-b` 镜像行 → 云效 Run #31 双批发布 SUCCESS → 软删旧行收口)→ L4 全绿(`client-b` 200 ×2、`client-b-old` 400 ×2、`/index` 200 全程无扰动)。证据:meta 仓 `pm/archive/登录二期/evidence/2026-08-28-LXJ-AUTH-PILOT-EXT-01-生产切换.md`。
|
|
41
41
|
|
|
42
42
|
## 5. 对 M4 退出指标的回填
|
|
43
43
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# M4 外部试点 2:
|
|
1
|
+
# M4 外部试点 2:pilot-app Bug 看板积压批处理(RUN-CHICK-0037 / 0018)
|
|
2
2
|
|
|
3
3
|
> 日期:2026-08-28
|
|
4
|
-
>
|
|
5
|
-
> 任务来源:项目所有者点名"异常看板积压了很多 bug,可以去处理一下"。积压读取自钉钉 AI 表格(
|
|
4
|
+
> 项目:`<试点工作区>/pilot-frontend`(Next.js 生产应用,基线 `9d571b3`)
|
|
5
|
+
> 任务来源:项目所有者点名"异常看板积压了很多 bug,可以去处理一下"。积压读取自钉钉 AI 表格(pilot-app base / Bug看板表,feedback 同步目标):39 条中 **13 未修复 + 2 待定**(P1×3 / P2×4 / P3×8)
|
|
6
6
|
> 结论上限:本地候选停在合并决定;不 merge、不 push、不发布;未回写钉钉看板状态。
|
|
7
7
|
|
|
8
8
|
## 1. 本批交付(2 个 Run,全部由 codex Worker 经 Shell Adapter 驱动)
|
|
@@ -41,4 +41,4 @@ inbox 两单终态决定:`RUN-CHICK-0037`(candidate `b866d5c`)与 `RUN-CHI
|
|
|
41
41
|
|
|
42
42
|
## 5. 对 M4 的意义
|
|
43
43
|
|
|
44
|
-
外部试点项目数达到 **2(
|
|
44
|
+
外部试点项目数达到 **2(pilot-backend + pilot-app)**,D6 原文口径满足;六退出指标在两项目上同向达标(试点 Run 自动到达率 4/4)。M4 就此关闭。
|
|
@@ -50,4 +50,4 @@ RUN-SELF-001 (work WORK-SELF-001) [final-decision] enter-wait-merge
|
|
|
50
50
|
## 5. 边界
|
|
51
51
|
|
|
52
52
|
- 本次 accept 与 run 启动由受托会话以 `claude-delegated` 身份执行并如实落账;**合并决定未被代行**,留在 inbox。
|
|
53
|
-
- 外部试点(D6
|
|
53
|
+
- 外部试点(D6:试点工作区内有测试的单仓项目 + 含 UI 项目)待项目所有者点名与授权后执行,指标回填前 M4 不宣称关闭。
|
|
@@ -64,8 +64,8 @@ v1 进入 `v1-maintenance` 维护线,只修安全与严重缺陷;npm `latest
|
|
|
64
64
|
| 2 | **事件台账 + reducer + 终态压实**(events.jsonl、state 重建、run-record) | 厂商日志是私有格式、随会话消亡、不落 Git;工具中立、可重放、可审计的交付台账没有厂商会提供 | 卡点 1:attempts/token/费用无统一 ledger;度量表全列 `UNVERIFIED` |
|
|
65
65
|
| 3 | **中断恢复**(checkpoint、resume、恢复点裁决) | 厂商的 session resume 只恢复自家会话上下文,不恢复跨 Worker 的交付状态(该继续 Verify、回 Build 还是废弃候选) | F5 = `RECOVERY_MISSING`:重开只能 fail-closed,需人读现场 |
|
|
66
66
|
| 4 | **Approval 对象与 stale 检测**(transition + candidate + planDigest + evidenceDigest 绑定) | 厂商审批是工具内 UI 动作,不产生持久化、跨工具、绑定 digest 的审批对象,更不会在对象变化时自动失效 | F6 = `APPROVAL_STALE_MISSING`;卡点 3:单阶段生产滚动 P1 正是"审批未绑定 rollout plan"的真实事故形态 |
|
|
67
|
-
| 5 | **Policy/Gate 语义检查器 + 强制等级报告**(四类 Policy、`doctor` 报告实际强制等级) | 厂商各有权限系统,但没人会检查"你声称的规则实际达到哪级强制"并跨工具编译到 hook/CI | 边界节:提示词禁令只算 `ADVISORY`;
|
|
68
|
-
| 6 | **多 Workspace 绑定**(一个 Work 绑定多仓 candidate 到同一 Decision) | 厂商 Workspace 即"当前打开的仓";跨 meta 仓 + 代码仓的原子绑定是协议层需求 | 卡点 2、卡点 4:
|
|
67
|
+
| 5 | **Policy/Gate 语义检查器 + 强制等级报告**(四类 Policy、`doctor` 报告实际强制等级) | 厂商各有权限系统,但没人会检查"你声称的规则实际达到哪级强制"并跨工具编译到 hook/CI | 边界节:提示词禁令只算 `ADVISORY`;pilot-app 会话始终持有生产能力,未被机器剥离 |
|
|
68
|
+
| 6 | **多 Workspace 绑定**(一个 Work 绑定多仓 candidate 到同一 Decision) | 厂商 Workspace 即"当前打开的仓";跨 meta 仓 + 代码仓的原子绑定是协议层需求 | 卡点 2、卡点 4:pilot-app 与 AI 试点工作区均为 meta+代码多仓,单仓 loop 无法原子关联 |
|
|
69
69
|
| 7 | **统一 Evidence Contract**(回读制证据、grade L0–L4、manifest digest) | 厂商各自产出日志与测试结果,但"什么算证据、谁回读、怎么分级"的合同必须工具中立 | 能力矩阵"证据来源、digest 与未验证范围"= PARTIAL:事后人工汇总、截图无 digest |
|
|
70
70
|
|
|
71
71
|
### 7.2 组装面(一律不自研)
|
|
@@ -59,13 +59,13 @@
|
|
|
59
59
|
|
|
60
60
|
| type | actor | data 最小集 | 语义 |
|
|
61
61
|
|---|---|---|---|
|
|
62
|
-
| `RUN_CREATED` | kernel | `workflowRef, workflowDigest, base, riskPreset` | Run 登记(卡点 5 的回应:没有本事件的工作不得计入 v2 闭环) |
|
|
62
|
+
| `RUN_CREATED` | kernel | `workflowRef, workflowDigest, base, riskPreset`;additive(迭代 08):`supersedes?`(本 Run 起跑时被记为 `SUPERSEDED` 的同 Work 旧 Run id 列表)、`envelopeDigest?` / `envelopeSource?`(run 配置 `envelope:` 的 digest 与来源) | Run 登记(卡点 5 的回应:没有本事件的工作不得计入 v2 闭环) |
|
|
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
66
|
| `STEP_FINISHED` | kernel | `step, attempt, status ∈ {succeeded,failed,blocked,invalid-output,timeout,crashed}, exitCode?` | Adapter 异常退出也必须落此事件(不变量 15) |
|
|
67
67
|
| `CANDIDATE_PINNED` | kernel | `workspaceId, base, candidate` | candidate 由 Git 回读后固定 |
|
|
68
|
-
| `EVIDENCE_RECORDED` | kernel/provider | `evidenceRef, kind, subject, digest, status, grade
|
|
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` | 无进展/相同失败检测的输入 |
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# 怎么和装了 BuildBeat 的会话说话(按项目阶段)
|
|
2
|
+
|
|
3
|
+
> 这是给**用户**看的一页。你在任意一个 AI 编程会话里工作(哪家工具都可以,一个会话就够),会话装载了 BuildBeat Skill;你说人话,它去调 `buildbeat-v2`、读输出、按「已做 → 未做 → 下一步」回你。**你不需要记任何命令**。下面的例句就是平时的说法,照抄即可;同一格里的几句话意思相同,挑顺口的。
|
|
4
|
+
> 会话侧的对应规则在 `SKILL.md` §0.5;项目专属的路由与红线在各项目根的 `AGENTS.md`。
|
|
5
|
+
|
|
6
|
+
## 一张表:项目从零到换期
|
|
7
|
+
|
|
8
|
+
| 阶段 | 你想干什么 | 你就这么说 | 会话背后做什么 | 你会得到什么 / 注意 |
|
|
9
|
+
|---|---|---|---|---|
|
|
10
|
+
| **0. 未开始** | 判断值不值得上 BuildBeat | 「这个项目适合用 BuildBeat 吗」 | 看仓库规模、周期、仓/部署单元/会话数 | 一句判断:多期迭代 / 多仓 / 多会话才值得;一周收尾的小活直接干 |
|
|
11
|
+
| | 新项目搭骨架 | 「帮我用 BuildBeat 搭一下这个项目」「初始化协作骨架」 | 自查代码 → 少量提问(栈、仓、部署单元、有没有 UI)→ 一屏确认 → 生成 `AGENTS.md`(v2 模板)、`delivery/`、`.buildbeat/observe.yaml`、`.buildbeat/notify.yaml`、`scripts/bus-check.sh`、pre-commit | 一屏确认单,你说「可以」它才写;生成后给文件清单 |
|
|
12
|
+
| | 接管老项目 | 「给这个老项目套上 BuildBeat」「接管这个仓」 | 摸底(测试、契约、部署事实)→ 划绞杀边界(新地盘 / 老地盘 / 只读)→ 第 0 期补最小验证 | 摸底报告 + 边界草案,你拍板边界;历史不回改 |
|
|
13
|
+
| | 装上通知 | 「Run 停下来时通知我」 | 写 `.buildbeat/notify.yaml`(钉钉 / webhook) | 它告诉你要建什么机器人、`export` 哪个环境变量;URL 永远不进 Git |
|
|
14
|
+
| **1. 立项 · 定方案** | 立一件事 | 「开个 Work:〔一句话目标〕」「把当前目标收成一个 Work」 | 写 `delivery/work/<ID>/intent.md`(为什么做、做到什么算完)+ `plan.md`(怎么做、分几步)+ `run-config.yaml` | 摘要 + 「看完说接受」;你说「接受」才 digest 绑定生效 |
|
|
15
|
+
| | 先看方案不写代码 | 「先别动代码,给我方案」「A 和 B 的差别是什么,说给我听听」 | 只出草案与对比,不起 Run | 方案对比 + 推荐 + 后果;没拍板不动手 |
|
|
16
|
+
| | 把该我定的挑出来 | 「方案里哪些要我拍板」「有什么需要我拍板的吗」 | 把验收清单压成真实取舍(含域名 / 服务名 / 时长这类你以后要念的名字) | 一次给 2~5 个决策项,每项带推荐值;不逐条连环问 |
|
|
17
|
+
| | 拍板 | 「接受」「全部按推荐」「第 2 个选 B,其余按推荐」 | `accept` intent/plan;决策落 `decisions.jsonl` / `pm/decisions.md` | 一句确认;改了 plan 会自动变 stale,需要再接受 |
|
|
18
|
+
| | 定风险等级 | 「这个用快轨」「这个要严一点」 | `riskPreset: fast / standard / controlled` | fast 只在合并前停;standard 加 plan 接受;controlled 加 intent 接受与发布门 |
|
|
19
|
+
| **2. 准备执行** | 看环境齐不齐 | 「环境有什么要求」「预检一下」 | `requires:`(二进制版本 + `probe:` 探针)核验;`preflight --step` 干跑首个失败边界 | 一次报清缺什么;干跑不产证据,正式 Run 要复现 |
|
|
20
|
+
| | 冻结信封 | 「把 prompt 钉住」「信封冻结」 | `envelope:` 加 `pin: <sha>` | 之后每个 Run 记 envelopeDigest,可追溯 |
|
|
21
|
+
| **3. 执行 · 推进** | 让它干活 | 「开工」「〔WORK-ID〕该你了」 | `start --config … --attempt new`(自动编号、旧等待自动作废、脱离启动) | 「已起 RUN-X-02,停在合并决定会通知你」 |
|
|
22
|
+
| | 看进度 | 「当前进度」「到哪了」「按仓分别说」 | `overview`(每个 Work 的阶段 + 下一步该谁)+ `observe status` | 每件事一句:阶段、卡在谁、下一步;不列命令 |
|
|
23
|
+
| | 看下一步 | 「下一步做什么」「需要我做什么」 | overview 的 next 行,只挑「在你手里」的 | 只列你要做的:DNS、凭据、批准、亲自操作 |
|
|
24
|
+
| | 怕它卡住 | 「怎么样了」「卡住了吗」「半小时了正常吗」 | `status --run`(每步耗时、历史中位数、最后输出、STALLED) | 一句带数字:「verify 已 14 分钟,历史中位 6 分钟,最后输出 2 分钟前,还在动」;疑似卡住会直说 |
|
|
25
|
+
| | 看有什么等我 | 「有什么要我批」「有什么等我」 | `inbox` + `delivery/observe/intents/` | 逐项:等什么、证据在哪、推荐 A/B |
|
|
26
|
+
| | 批 / 不批 | 「批准」「批准 RUN-X」「拒绝,原因是…」 | `approve` / `reject`;非终态自动 `resume` | 批准 = merge-ready;合并 / push / 部署你另说 |
|
|
27
|
+
| | 裁 finding | 「这条不算,那条接受」「这个是误报」 | `findings adjudicate dismiss/accept` → 放行 fixer | 被 dismiss 的同一条以后不再阻断;严重度升级会重开 |
|
|
28
|
+
| | 再来一轮 | 「再来一轮」「继续」 | 新 attempt | 旧的等待自动作废,inbox 只剩活的 |
|
|
29
|
+
| | 停下来 | 「这次再不成功就停」「先停下来」 | 封顶轮数;`stop --reason` 记账,候选与证据保留 | 已做 / 未做 / 下一步,带 hash;之后由你决定改方案、手工修还是关掉 Work |
|
|
30
|
+
| **4. 验收 · 合并** | 验收 | 「验收」「验 RUN-X 候选」 | 测试视角对精确 candidate 独立核验,报告落 Work 目录 | 通过 / 不通过 + 证据;写者的话不算证据 |
|
|
31
|
+
| | 合并 | 「merge 吧」「授权 push」 | 人类动作由会话代执行(在 `AGENTS.md` 红线内),回读远端 | 报合并后 hash;`overview` 显示 MERGED |
|
|
32
|
+
| **5. 上线** | 上线 | 「上线」「准备 Gate4」 | `release-readback` 预设 + `riskPreset: release`:先回读 → 停下来 | 「回读全绿,现在轮到你做〔动作〕;做完说一声」 |
|
|
33
|
+
| | 我做完了 | 「做完了」「做到一半了,你核一下」 | 批准 apply-readback → 回读 + 观察 → 停关窗 | 差什么逐条说;任一步失败即停 |
|
|
34
|
+
| | 拍上线卡 | 「批准上线」「先不上线」 | 按决策卡执行 / 不执行 | 生产动作永远是你的;它只回读和记账 |
|
|
35
|
+
| | 生产报警 | 「生产有报警」「体检一下」 | `observe run` → 草稿入队 | 草稿一句 + 「fix_now / schedule / dismiss 你选」 |
|
|
36
|
+
| **6. 完结 · 换期 · 复盘** | 收尾一件事 | 「这个 Work 完了」「关掉这个 Work」 | 记决策、确认 run-record 在 Git 面 | `overview` 不再把它列为待办 |
|
|
37
|
+
| | 打扫 | 「打扫卫生」 | `gc`(先出计划,你点头再 `--apply`) | 清了几个工作树;仅此分支可达的候选一律保留并说明 |
|
|
38
|
+
| | 换期 | 「换期」「进入下一期」 | 换期压缩仪式:归档、截断、指针清零、回灌一问 | 一屏清单;新一期从干净的入口开始 |
|
|
39
|
+
| | 复盘 | 「复盘一下 BuildBeat 做了什么」「正向负向各是什么」 | `metrics` + Run 台账 + 会话记录 | 正向 / 负向 / 改革条,带数字 |
|
|
40
|
+
| | 记经验 | 「把这次卡点的经验记一下」 | 写 `pm/<日期>-<主题>.md` + `env-facts.md`,能机器化的转成 `requires:` 探针 | 下一窗直接引用,不口口相传 |
|
|
41
|
+
| | 回灌 | 「有哪些是 BuildBeat 可以吸收的」 | 对照 lessons 找新坑 | 候选清单,逐条 A/B/C |
|
|
42
|
+
| | 升级 | 「更新一下 BuildBeat 版本」 | 改 `BUILDBEAT.md` / `AGENTS.md` / `.buildbeat/*.yaml`,跑 `doctor` | 版本标记 + doctor 报告 |
|
|
43
|
+
|
|
44
|
+
## 贯穿全程的几句话
|
|
45
|
+
|
|
46
|
+
| 你说 | 它会 |
|
|
47
|
+
|---|---|
|
|
48
|
+
| 「为什么」「不做会怎样」「这名字怎么起的」 | 用人话解释,并把该你定的(名字、时长、范围)转成决策项让你批,而不是替你定 |
|
|
49
|
+
| 「说人话」「别列 1234」 | 已做 / 未做 / 下一步各一句;只有事项差异大才分条 |
|
|
50
|
+
| 「你觉得呢」 | 给一个推荐和理由,不是一堆选项 |
|
|
51
|
+
| 「授权」「批准」单独出现 | 只对它上一条明确提出的那件事生效,不外推到别的动作 |
|
|
52
|
+
|
|
53
|
+
## 三条底线(省你追问)
|
|
54
|
+
|
|
55
|
+
- **批准 ≠ 执行**:合并、push、部署、花费、删除,每一项都要你逐项说。
|
|
56
|
+
- **数字必须落地**:问「正常吗」得到的回答一定带已用时间 / 历史中位数 / 最后输出时间;没数据就说没数据。
|
|
57
|
+
- **名字先过你**:域名、服务名、环境名、自停时长这类你以后要念的东西,会话在决策卡里给推荐值让你批,不自己定。
|
|
@@ -82,6 +82,10 @@ buildbeat-v2 approve --repo . --run RUN-DEMO-1 --transition enter-wait-merge --b
|
|
|
82
82
|
|
|
83
83
|
想让 finding 先过你的手再派 fixer:run 配置加 `reviewTriage: required`,配套 `findings list` / `findings adjudicate` 逐指纹裁决(dismiss 后同指纹不再阻断);正式起 Run 前可用 `preflight --step <id>` 在主 checkout 分钟级干跑单步(不产证据);信封的环境依赖用 `requires:` 声明,启动前 fail-closed 核验。详见 [Approval 指南](07-approval-guide.md)、[Evidence 指南](06-evidence-guide.md)、[Workflow 指南](02-workflow-guide.md)。
|
|
84
84
|
|
|
85
|
+
跑起来之后三件事不用再问 AI:`status` 会说每步跑了多久、历史上通常多久、worker 最后一次输出是什么时候(无输出超过 15 分钟标 `STALLED`);`status` / `inbox` 在每个等待后面直接给出可复制的下一句命令;`.buildbeat/notify.yaml` 配一条钉钉或 webhook 通道,Run 停下来会来找你。同一个 Work 再起新 Run 时旧的等待自动作废,终态 Run 留下的工作树用 `gc --repo .` 清(默认只出计划)。详见 [Approval 指南](07-approval-guide.md) 与 [故障恢复](10-recovery.md)。
|
|
86
|
+
|
|
87
|
+
「到哪了」问 `buildbeat-v2 overview --repo .`:每个 Work 的阶段与下一步该谁。`start --attempt new` 让一份 run 配置跑到底(自动编号 `RUN-X-01/02…`);`envelope:` 让内核喂 prompt、`cache: {verify: tree}` 让同树同命令的 verify 不重跑、`requires:` 的 `probe:` 把环境事实前置核验,见 [Workflow 指南](02-workflow-guide.md)。**在 AI 会话里用 BuildBeat 的人不需要记这些命令**:`SKILL.md` §0.5 是给会话读的驾驶手册,用户说「当前进度 / 开工 / 怎么样了 / 批准 / 上线 / 打扫卫生」即可。
|
|
88
|
+
|
|
85
89
|
## 5. observe:让系统盯生产(v0)
|
|
86
90
|
|
|
87
91
|
```bash
|
|
@@ -54,6 +54,20 @@ run 配置还可声明(beta.3,皆来自三十轮部署战役的真实事故
|
|
|
54
54
|
|
|
55
55
|
官方预设自带 `budgets.maxAttempts.review: 2`(战役章程"每 Run 2 轮 review 封顶"的原生化):第三轮 review 在启动前即停 `WAITING_HUMAN`,理由写明预算耗尽。项目可用自己的 workflow 文件覆盖;机制就是每步 `maxAttempts`,无需新概念。
|
|
56
56
|
|
|
57
|
+
## 迭代 08 新增的 run 配置段
|
|
58
|
+
|
|
59
|
+
- **`envelope:`** —— `prompts:`(目录,相对 run 配置)+ `vars:`(`{vars.x}` 替换)+ 可选 `pin: <sha>`(从该提交读 prompt,冻结信封)。内核按 `<component>-<worker>.md` → `<worker>.md` 取 prompt,落到 `runs/<RUN>/prompts/<step>-<n>.md`,以 `BUILDBEAT_PROMPT`(路径)和 `input.envelope`(`promptRef / file / digest / vars`)交给 worker;worker args 里可用 `{prompt}` 与 `{vars.x}`。`RUN_CREATED` 记 `envelopeDigest`。
|
|
60
|
+
- **`start --attempt new`** —— `run:` 写家族名(`RUN-X`),内核编成 `RUN-X-01/02…`(扫运行时面与 Git 面 run-record,删 runtime 也不撞号);同 Work 旧的等待自动作废([Approval 指南](07-approval-guide.md))。
|
|
61
|
+
- **`cache:`** —— `verify: tree`:同 `HEAD^{tree}` + 同 worker 命令 + 同信封 digest 且**已通过**的 verify 复用证据(台账 `reused`,status 标 `(reused from RUN-X)`);失败、脏树不复用。verifier 依赖树外事物(远端、时间)的项目不要开。
|
|
62
|
+
- **增量审查** —— readonly 步的 input 带 `lastReviewed {candidate, run, evidenceRef, range}`(同 Work 最近一次 review 的候选且为当前候选祖先);reviewer prompt 可要求只审 `range` 内 diff,锚定裁决照旧(`anchor`)。
|
|
63
|
+
- **`redact:`** —— 正则列表,证据日志落盘前替换为 `<REDACTED>`;digest 绑脱敏后文本。实时流(`.live`)不脱敏、步结束即删。
|
|
64
|
+
- **`requires:` 的 `probe:` 条目** —— `probe: <shell 命令>` + 可选 `expect: <正则>` + `name:`;退出码非 0 或输出不匹配即 fail-closed,与二进制版本项一次报清。把踩出来的环境事实(Redis ≥ 7、目标机 Python 版本、端口可达)写成 probe,下窗不重踩;叙述性事实放 `delivery/work/<ID>/env-facts.md`。
|
|
65
|
+
- **step `grade:`** —— workflow 步骤可声明该步命令证据的等级(L0–L4,默认 L2)。
|
|
66
|
+
|
|
67
|
+
## 上线回读车道:`release-readback` + `riskPreset: release`
|
|
68
|
+
|
|
69
|
+
内核没有部署能力(不变量 20),生产动作永远是人的。这条车道只把动作前后的**回读**记成 L4 台账:`preflight`(动作前只读检查)→ 停 `enter-apply-readback`(人做动作)→ `apply-readback`(证明动作生效)→ `observe`(证明健康)→ `wait-close`(人关窗)。三个回读步全部 `readonly`、`grade: L4`、`maxAttempts 1`:任一步失败即停人批,没有 fix 边。风险预设 `release` 提供 `stopAt: apply-readback` 与关窗证据门(L4 命令证据)。worker 是任意回读脚本(curl 健康、读版本、比对配置指纹),退出码就是结论。试点项目上线那天的四十条手工 readback 提交,就是这条车道该做的事。
|
|
70
|
+
|
|
57
71
|
## 修改纪律
|
|
58
72
|
|
|
59
73
|
预设是产品的一部分:改 `software-delivery.yaml` 前先想清是不是项目差异——项目差异用自己的 workflow 文件(run 配置 `workflow:` 指过去),不改官方预设。schema additive-only,破坏性改法升 `version`。
|
|
@@ -43,3 +43,7 @@ Adapter 只报告事实:exitCode / signal / timedOut / spawnError / stdout / s
|
|
|
43
43
|
## 何时写专用 Adapter
|
|
44
44
|
|
|
45
45
|
只有当 Shell 表达不了(需要流式交互、会话保持)才写专用 Adapter;按 M3 裁决,先用 Shell 接一切,等真实试点证明不够再说。
|
|
46
|
+
|
|
47
|
+
## 实时输出(迭代 08)
|
|
48
|
+
|
|
49
|
+
编排器给 Shell Adapter 传 `liveDir` 时,子进程的 stdout/stderr 直接写到 `<liveDir>/<step>-<attempt>.{stdout,stderr}.live`(fd 直连,不经父进程缓冲),并写 `live.json`(`step / attempt / worker / command / startedAt`)。步返回后 Adapter 读回两份流作为 `stdout` / `stderr`,删掉实时文件——结果形状不变,证据收集器照旧。自写 Adapter 若想被 `status` 的"最后输出距今"识别,产出同名文件即可;不产出则 `status` 只显示已用时间。
|
|
@@ -35,3 +35,13 @@
|
|
|
35
35
|
- prompt 里明确引用 `delivery/work/<id>/plan.md`,让 Worker 的目标与被批准的 digest 是同一份文件;
|
|
36
36
|
- builder 的提交动作可以由包装脚本机械执行(M4 试点即如此:codex 只改文件,`git commit` 在包装层);
|
|
37
37
|
- reviewer 的 prompt 要求"只输出信封 JSON",并用 `-o`/重定向落到 `$BUILDBEAT_OUTPUT`。
|
|
38
|
+
|
|
39
|
+
## 迭代 08:输入里多了什么
|
|
40
|
+
|
|
41
|
+
- `BUILDBEAT_PROMPT`(环境变量,文件路径)与 `input.envelope`(`promptRef / file / digest / vars`):run 配置 `envelope:` 声明的 prompt 已由内核替换变量并落盘,worker 直接 `cat "$BUILDBEAT_PROMPT"`,不再自己 `git show`。
|
|
42
|
+
- `input.lastReviewed`(仅 readonly 步):`{candidate, run, evidenceRef, range}`——上一次 review 看过的候选与到当前候选的 `range`;reviewer 可只审增量,但**已裁决结论不得翻案**(`anchor` 仍在)。
|
|
43
|
+
- `input.findings`(写入步)与 `input.anchor`(readonly 步)不变。
|
|
44
|
+
|
|
45
|
+
## 所有者可见命名不由 worker 决定(迭代 08)
|
|
46
|
+
|
|
47
|
+
builder / planner 在实现中会顺手起名:域名、服务名、环境名、自停时长、窗口时长。**凡所有者以后要看见或念出来的名字与参数,不是实现细节,是门前决策项**:写进 intent,或攒进门前决策卡给推荐值与理由,人批后再落地。真实事故:一个按内部术语起的服务名让所有者连问四轮才改成他听得懂的业务名。prompt 里写明这条,reviewer 清单里把"引入了未经批准的可见命名"记为 P2。
|
|
@@ -42,3 +42,7 @@
|
|
|
42
42
|
## 完整率
|
|
43
43
|
|
|
44
44
|
`buildbeat-v2 metrics` 输出证据完整率(有证据的步/应有证据的步);M4/M5 退出线 ≥95%,试点实测 100%。
|
|
45
|
+
|
|
46
|
+
## 运行中的读数 ≠ 证据(迭代 08)
|
|
47
|
+
|
|
48
|
+
Shell Adapter 在步运行期间把 worker 的 stdout/stderr 实时流到 `.buildbeat/runtime/runs/<RUN>/<step>-<n>.{stdout,stderr}.live`,并留 `live.json`(命令、开始时间)。它们是**读数**:`status` 拿来回答"还在动吗、动了多久、最后一次输出是什么时候",步一结束就收回;证据日志仍由回读生成、digest 仍绑最终日志。耗时同理——每步耗时、同仓历史中位数(`metrics` 的 `typical step duration`)都从台账时间戳推导,不进台账、不进 run-record。
|
|
@@ -49,3 +49,36 @@ merge 批准只表示 **merge-ready**:真正的合并、push、发布是你在
|
|
|
49
49
|
3. **锚定注入**:Reviewer(readonly 步)的 `BUILDBEAT_INPUT` 带 `anchor`(历史 finding+裁决全表),信封 prompt 应告知 reviewer"已裁决的结论不得翻案";fixer 等写入步的 input 带 `findings`(上一轮 review 的 finding 及其裁决状态)——fixer 只修 accepted/open,不猜。
|
|
50
50
|
|
|
51
51
|
裁决记忆在 Git 面,删 runtime 不丢(不变量 23 同款测试覆盖)。
|
|
52
|
+
|
|
53
|
+
## 等待要能找到人(迭代 08)
|
|
54
|
+
|
|
55
|
+
试点工作区 58 个 Run 里 32 个被取消,多数是在 `WAITING_HUMAN` 挂满一天后批量清掉;人批平均等 7~12 小时。原因不是人慢,是**没人知道有东西等他**。三件事配套:
|
|
56
|
+
|
|
57
|
+
1. **下一句该说什么**:`status` 与 `inbox` 在每个等待后面直接给出可复制的命令(`approve` / `reject`,分诊时加 `findings list|adjudicate`);`inbox` 按 Work 分组并显示已等待时长。输出里的 `--repo` 只在项目内给相对路径,项目外给 `<repo-path>` 占位——本机绝对路径永不进输出。
|
|
58
|
+
2. **同 Work 新 Run 取代旧等待**:`start` 时同一 Work 下仍在等待的旧 Run 记 `SUPERSEDED`(终态、压成 run-record),新 Run 的 `RUN_CREATED.data.supersedes` 记血统;inbox 只剩活的等待。不想要这个行为就在 run 配置写 `supersede: off`。RUNNING 的 Run 不受影响(active 锁),被别的进程锁住的旧 Run 跳过并明示。
|
|
59
|
+
3. **通知出站**:Git 面 `.buildbeat/notify.yaml`:
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
kind: notify
|
|
63
|
+
version: 1
|
|
64
|
+
channels:
|
|
65
|
+
- id: owner
|
|
66
|
+
type: dingtalk # 或 webhook
|
|
67
|
+
urlEnv: BUILDBEAT_NOTIFY_URL # URL 只能来自环境变量;写 url 直接拒绝
|
|
68
|
+
events:
|
|
69
|
+
- HUMAN_REQUESTED
|
|
70
|
+
- RUN_TERMINAL
|
|
71
|
+
- STALLED
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Run 停在人批或到终态时由 CLI 出站;订阅 `STALLED` 时 `start`/`resume` 会派一个脱离的 `watch` 进程盯 worker 输出静默(阈值 `stallAfterMs`,默认 15 分钟)。发送失败只记 `runs/<RUN>/notify.log` 与屏幕,**永不影响 Run**;载荷只有标识、原因、候选 SHA 与下一句命令,零日志零候选内容。钉钉自定义机器人需配置关键词(默认 `BuildBeat`)。`doctor` 报告通道与环境变量是否就位。
|
|
75
|
+
|
|
76
|
+
通知不是审批通道:拍板仍只能在 CLI 完成,digest 绑定不变。
|
|
77
|
+
|
|
78
|
+
## 从「等我批」到「到哪了」:overview(迭代 08)
|
|
79
|
+
|
|
80
|
+
`inbox` 只知道哪个 Run 在等人;`buildbeat-v2 overview --repo .` 按 Work 回答「走到哪、下一步该谁」——intent/plan 是否被接受(接受后改过即 `stale`)、最新 Run 状态与候选、候选是否已合入当前分支、未裁决 P0/P1 数、是否有 `env-facts.md`,每行附下一句命令。运行时被删后由 Git 面 run-record 补足。会话开场先跑它,再回答用户「当前进度」。
|
|
81
|
+
|
|
82
|
+
## 可见命名是门前决策项(迭代 08)
|
|
83
|
+
|
|
84
|
+
审批三级里 `BATCH_AT_GATE` 明确包含:域名、服务名、环境名、自停时长、窗口时长等**所有者以后要看见或念出来的名字与参数**。worker 顺手定的名字进不了台账;planner 在 intent 里列出并给推荐值,人一次批。
|
|
@@ -54,4 +54,25 @@ rm -rf .buildbeat/runtime/
|
|
|
54
54
|
|
|
55
55
|
## 诊断入口
|
|
56
56
|
|
|
57
|
-
`buildbeat-v2 doctor --config <run-config>`:配置可解析、workflow 无出口环、adapter env 姿态、digest
|
|
57
|
+
`buildbeat-v2 doctor --config <run-config>`:配置可解析、workflow 无出口环、adapter env 姿态、digest 可算、supersede 与 stall 阈值、通知通道与环境变量是否就位。`events`/`replay`/`metrics` 全部只读,可随时跑。
|
|
58
|
+
|
|
59
|
+
## "是不是卡住了"(迭代 08)
|
|
60
|
+
|
|
61
|
+
先看 `buildbeat-v2 status --repo . --run <RUN>`:在飞步骤有已用时间、同仓历史中位数、worker 命令、最后一次输出距今多久与末三行输出。无输出超过阈值(默认 15 分钟,`--stall-after <分钟>` 或 run 配置 `stallAfterMs`)标 `STALLED`——**只标不杀**。判断口径:
|
|
62
|
+
|
|
63
|
+
- 有输出在持续 → 等(对照 `typical` 看是否已远超中位数);
|
|
64
|
+
- STALLED 且 worker 是 Agent CLI → 多半在长推理或等一个永远不来的交互,`stop --reason` 后按崩溃恢复重跑(中断的步重跑自身);
|
|
65
|
+
- STALLED 且 worker 是脚本 → 看末三行,通常是等外部资源(端口、锁、网络)。
|
|
66
|
+
|
|
67
|
+
想不盯屏就订阅 `STALLED` 通知([Approval 指南](07-approval-guide.md))。`watch --repo . --run <RUN> --once true` 可手工探测一次。
|
|
68
|
+
|
|
69
|
+
## 打扫卫生:gc(迭代 08)
|
|
70
|
+
|
|
71
|
+
终态 Run 会留下工作树、`run/*` 分支和偶尔的锁。`buildbeat-v2 gc --repo .` 默认只出计划,`--apply true` 执行:
|
|
72
|
+
|
|
73
|
+
- 只动**终态且已压成 run-record** 的 Run(Git 面有账才动运行时面);
|
|
74
|
+
- 工作树可删(提交都在分支上);脏工作树不带 `--force true` 不动;
|
|
75
|
+
- 分支只在候选**已可从其他 ref 到达**(已合并 / 打 tag / 在远端)或 Run 未产出候选时删;否则明示"仅此分支可达,保留"——它是证据的最后一根线;
|
|
76
|
+
- 终态 Run 的残留 `locks/<RUN>.lock` 一并清;`active-run` 锁仍按上文人工处置。
|
|
77
|
+
|
|
78
|
+
gc 永不写台账(终态后只允许 `RUN_COMPACTED`),所以随时可跑、可重复。
|
package/docs/v2/guide/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
| # | 文档 | 一句话 |
|
|
6
6
|
|---|---|---|
|
|
7
|
+
| 0 | [怎么和会话说话](00-how-to-talk.md) | **给用户看的**:项目从未开始到换期,每个阶段你说什么、会话做什么、你得到什么 |
|
|
7
8
|
| 1 | [快速开始](01-quickstart.md) | 5 分钟:装 beta → 写 run 配置 → 跑到合并决定 |
|
|
8
9
|
| 2 | [Workflow 编写指南](02-workflow-guide.md) | 步序、显式转换、readonly、terminal |
|
|
9
10
|
| 3 | [Policy 指南](03-policy-guide.md) | 四类 Policy、8 算子、三值逻辑、强制等级 |
|
|
@@ -16,3 +17,5 @@
|
|
|
16
17
|
| 10 | [故障恢复手册](10-recovery.md) | 台账损坏、Run 中断、锁、runtime 全删重建 |
|
|
17
18
|
|
|
18
19
|
observe v0(探测→分层响应→Intent 草稿→人分诊)在 [快速开始 §5](01-quickstart.md) 与 [Evidence 指南](06-evidence-guide.md) 中覆盖;schema 冻结见 [`RFC-0003 §8`](../RFC-0003-workflow-policy.md)。
|
|
20
|
+
|
|
21
|
+
迭代 08 起:`SKILL.md` §0.5 是给 AI 会话读的 v2 驾驶手册(用户一句话 → 会话调什么),v2 项目的装载入口模板在 [`templates/v2/`](../../../templates/v2/AGENTS.md)。
|
package/lessons.md
CHANGED
|
@@ -125,3 +125,15 @@
|
|
|
125
125
|
**根因**:BuildBeat 规定了 `pm/status/{视角}.md` 的持久状态写法,却只笼统要求「一屏收尾」,没有给面向人的回复一个简单统一的出口。模型便按各自任务的局部叙事优化,交接信息结构自然漂移。
|
|
126
126
|
|
|
127
127
|
**解药**:每个 AI 视角面向人收口时统一用「已做 → 未做 → 下一步」。`已做`只写功能/业务结果,证据紧跟它支持的事项;多项共用才放一条共同证据。`未做`必须写原因,同时承载未验证边界。本域完成就说下一棒是谁、做什么;未完成就说需要谁提供或确认什么;自己还能继续就不伪求助。格式只约束收口,不约束中间探索;持久真相仍在 Git/status/证据文件。通用原则:**统一交接接口,不统一模型怎么思考。**
|
|
128
|
+
|
|
129
|
+
## 20. Worker 顺手起的名字,所有者要连问四轮
|
|
130
|
+
|
|
131
|
+
**症状**:一个非生产的健康聚合服务被 AI 会话按内部术语命名(形如 `<内部术语>-nonprod`),域名也照此申请;所有者在会话里连问「这是干啥的」「名字怎么起的」「非生产?后面还要建生产?」「不做会影响什么」,最后自己改成一个业务上听得懂的名字。同期「服务 4 小时自停」也是 worker 按契约自定,所有者见到才问「为什么需要 4 个小时」。
|
|
132
|
+
**根因**:名字、时长这类参数在实现视角里是"细节",在所有者视角里是"以后天天要念的东西";流程只把契约/范围/不可逆动作列为决策项,没把**可见命名**列进去,于是它们从 worker 手里直接落地。
|
|
133
|
+
**解药**:凡所有者以后要看见或念出来的名字与参数(域名、服务名、环境名、自停时长、窗口时长)默认 `BATCH_AT_GATE`——写进 intent 或门前决策卡,给推荐值和理由,人一次批;reviewer 清单把「引入未经批准的可见命名」记 P2(v2 AGENTS 模板第 ⑪ 条、Skill §0.5.2)。通用原则:**决策项的边界按"谁以后要面对它"划,不按"实现上是不是细节"划。**
|
|
134
|
+
|
|
135
|
+
## 21. Run 停下来了,但没人知道;人守着屏幕,但看不见时间
|
|
136
|
+
|
|
137
|
+
**症状**:两个子仓 58 个 Run 里 32 个被取消,多数是在等人批的状态挂满一天后被批量清掉,人批平均等 7~12 小时;另一边所有者守着一场部署战役时,在同一会话里问了十几次「半小时了正常吗」「十分钟了是卡住了吗」「现在到底是谁在干活?」。事后清理时又发现 16 个终态 Run 的工作树无人收拾。
|
|
138
|
+
**根因**:Run 的状态只存在于台账和 `inbox`,不主动出站——人只有开一个 AI 会话问才知道有东西等他;`status` 只有步骤和次数,worker 输出被同步 spawn 缓冲到步结束才可见,人手里没有任何时间读数;新 Run 起跑不作废旧等待,残留物也没有回收命令。
|
|
139
|
+
**解药**:`status` 带每步耗时、同仓历史中位数、最后一次输出距今与末几行,无输出超阈值标 `STALLED`(只标不杀);`.buildbeat/notify.yaml` 把 `HUMAN_REQUESTED / RUN_TERMINAL / STALLED` 出站到钉钉或 webhook(URL 只走环境变量,失败不影响 Run);同 Work 新 Run 自动作废旧等待;`gc` 按"终态且已压账、候选可从别处到达"的规则清工作树;`overview` 回答「到哪了、下一步该谁」。通用原则:**等待必须能找到人,时间必须能被读到;做不到这两条,人就会用反复追问来补,而追问本身就是流程债。**
|
package/package.json
CHANGED
package/src/v2/adapters/shell.js
CHANGED
|
@@ -2,8 +2,19 @@
|
|
|
2
2
|
// step's workspace. This is the vendor-neutral path to any CLI agent
|
|
3
3
|
// (claude -p, codex exec, plain scripts). Adapters never touch kernel state;
|
|
4
4
|
// they only return what actually happened — the orchestrator writes events.
|
|
5
|
+
//
|
|
6
|
+
// Live output (iteration 08): when the orchestrator hands over a liveDir the
|
|
7
|
+
// child's stdout/stderr stream straight into files there while it runs, and a
|
|
8
|
+
// live.json marker names the command and its start time. `status` reads
|
|
9
|
+
// those to answer "is it still doing something?" — the question the owner
|
|
10
|
+
// asked a dozen times during the deploy campaign while a buffered spawnSync
|
|
11
|
+
// showed nothing until the step ended. The marker and the streams are
|
|
12
|
+
// removed once the step returns; the evidence log is still written by the
|
|
13
|
+
// collector from what was read back.
|
|
5
14
|
|
|
6
15
|
import { spawnSync } from "node:child_process";
|
|
16
|
+
import { closeSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
7
18
|
|
|
8
19
|
export class AdapterConfigError extends Error {
|
|
9
20
|
constructor(message) {
|
|
@@ -13,10 +24,15 @@ export class AdapterConfigError extends Error {
|
|
|
13
24
|
}
|
|
14
25
|
|
|
15
26
|
function fillTemplate(text, context) {
|
|
16
|
-
|
|
27
|
+
let out = text
|
|
17
28
|
.replaceAll("{workspace}", context.workspacePath)
|
|
18
29
|
.replaceAll("{step}", context.step)
|
|
19
|
-
.replaceAll("{worker}", context.worker)
|
|
30
|
+
.replaceAll("{worker}", context.worker)
|
|
31
|
+
.replaceAll("{prompt}", context.promptPath ?? "");
|
|
32
|
+
for (const [key, value] of Object.entries(context.vars ?? {})) {
|
|
33
|
+
out = out.replaceAll(`{vars.${key}}`, String(value));
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
20
36
|
}
|
|
21
37
|
|
|
22
38
|
// Workers get a minimal environment by default: host credentials living in
|
|
@@ -24,6 +40,24 @@ function fillTemplate(text, context) {
|
|
|
24
40
|
// in with inheritEnv (which doctor reports as ADVISORY-only isolation).
|
|
25
41
|
const DEFAULT_ENV_KEYS = ["PATH", "HOME", "LANG", "LC_ALL", "TMPDIR", "TERM", "USER", "SHELL"];
|
|
26
42
|
|
|
43
|
+
export const LIVE_MARKER = "live.json";
|
|
44
|
+
|
|
45
|
+
export function liveStreamPaths(liveDir, step, attempt) {
|
|
46
|
+
return {
|
|
47
|
+
marker: join(liveDir, LIVE_MARKER),
|
|
48
|
+
stdout: join(liveDir, `${step}-${attempt}.stdout.live`),
|
|
49
|
+
stderr: join(liveDir, `${step}-${attempt}.stderr.live`),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function readLive(path) {
|
|
54
|
+
try {
|
|
55
|
+
return readFileSync(path, "utf8");
|
|
56
|
+
} catch {
|
|
57
|
+
return "";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
27
61
|
export function createShellAdapter(config) {
|
|
28
62
|
if (!config || typeof config.command !== "string" || config.command.length === 0) {
|
|
29
63
|
throw new AdapterConfigError("shell adapter requires a command");
|
|
@@ -33,9 +67,10 @@ export function createShellAdapter(config) {
|
|
|
33
67
|
return {
|
|
34
68
|
name,
|
|
35
69
|
envMode,
|
|
36
|
-
execute({ step, worker, workspacePath, input, timeoutMs, outputPath }) {
|
|
37
|
-
const context = { step, worker, workspacePath };
|
|
70
|
+
execute({ step, worker, workspacePath, input, timeoutMs, outputPath, liveDir, promptPath, vars }) {
|
|
71
|
+
const context = { step, worker, workspacePath, promptPath, vars };
|
|
38
72
|
const args = (config.args ?? []).map((arg) => fillTemplate(String(arg), context));
|
|
73
|
+
const command = [config.command, ...args].join(" ");
|
|
39
74
|
const startedAt = new Date().toISOString();
|
|
40
75
|
let env;
|
|
41
76
|
if (config.inheritEnv === true) {
|
|
@@ -53,21 +88,59 @@ export function createShellAdapter(config) {
|
|
|
53
88
|
if (outputPath) {
|
|
54
89
|
env.BUILDBEAT_OUTPUT = outputPath;
|
|
55
90
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
91
|
+
if (promptPath) {
|
|
92
|
+
env.BUILDBEAT_PROMPT = promptPath;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let live = null;
|
|
96
|
+
let stdio = "pipe";
|
|
97
|
+
if (liveDir) {
|
|
98
|
+
mkdirSync(liveDir, { recursive: true });
|
|
99
|
+
const paths = liveStreamPaths(liveDir, step, input?.attempt ?? 0);
|
|
100
|
+
const outFd = openSync(paths.stdout, "w");
|
|
101
|
+
const errFd = openSync(paths.stderr, "w");
|
|
102
|
+
writeFileSync(
|
|
103
|
+
paths.marker,
|
|
104
|
+
`${JSON.stringify({ step, attempt: input?.attempt ?? null, worker, command, startedAt, stdout: paths.stdout, stderr: paths.stderr })}\n`,
|
|
105
|
+
"utf8",
|
|
106
|
+
);
|
|
107
|
+
live = { paths, outFd, errFd };
|
|
108
|
+
stdio = ["ignore", outFd, errFd];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let result;
|
|
112
|
+
try {
|
|
113
|
+
result = spawnSync(config.command, args, {
|
|
114
|
+
cwd: workspacePath,
|
|
115
|
+
encoding: "utf8",
|
|
116
|
+
timeout: timeoutMs ?? config.timeoutMs,
|
|
117
|
+
env,
|
|
118
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
119
|
+
stdio,
|
|
120
|
+
});
|
|
121
|
+
} finally {
|
|
122
|
+
if (live) {
|
|
123
|
+
closeSync(live.outFd);
|
|
124
|
+
closeSync(live.errFd);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
63
127
|
const finishedAt = new Date().toISOString();
|
|
128
|
+
let stdout = result.stdout ?? "";
|
|
129
|
+
let stderr = result.stderr ?? "";
|
|
130
|
+
if (live) {
|
|
131
|
+
stdout = readLive(live.paths.stdout);
|
|
132
|
+
stderr = readLive(live.paths.stderr);
|
|
133
|
+
rmSync(live.paths.marker, { force: true });
|
|
134
|
+
rmSync(live.paths.stdout, { force: true });
|
|
135
|
+
rmSync(live.paths.stderr, { force: true });
|
|
136
|
+
}
|
|
64
137
|
return {
|
|
65
138
|
adapter: name,
|
|
66
|
-
command
|
|
139
|
+
command,
|
|
67
140
|
exitCode: result.status,
|
|
68
141
|
signal: result.signal ?? null,
|
|
69
|
-
stdout
|
|
70
|
-
stderr
|
|
142
|
+
stdout,
|
|
143
|
+
stderr,
|
|
71
144
|
timedOut: result.error?.code === "ETIMEDOUT",
|
|
72
145
|
spawnError: result.error && result.error.code !== "ETIMEDOUT" ? result.error.message : null,
|
|
73
146
|
startedAt,
|