@haiyangbg/buildbeat 2.0.1 → 3.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.
Files changed (127) hide show
  1. package/CHANGELOG.md +24 -301
  2. package/README.en.md +6 -19
  3. package/README.md +6 -19
  4. package/SKILL.md +168 -219
  5. package/bin/buildbeat.js +14 -2
  6. package/docs/CAPABILITY-MATRIX.md +13 -55
  7. package/docs/README.md +14 -14
  8. package/docs/RELEASING.md +8 -9
  9. package/docs/v2/RFC-0001-product-definition.md +2 -0
  10. package/docs/v2/RFC-0003-workflow-policy.md +2 -0
  11. package/docs/v2/guide/00-how-to-talk.md +3 -3
  12. package/docs/v2/guide/01-quickstart.md +12 -12
  13. package/docs/v2/guide/03-policy-guide.md +1 -1
  14. package/docs/v2/guide/06-evidence-guide.md +3 -3
  15. package/docs/v2/guide/07-approval-guide.md +10 -10
  16. package/docs/v2/guide/10-recovery.md +6 -6
  17. package/docs/v2/guide/11-session-handoff.en.md +2 -2
  18. package/docs/v2/guide/11-session-handoff.md +2 -2
  19. package/docs/v2/guide/README.md +0 -6
  20. package/lessons.md +52 -71
  21. package/package.json +16 -8
  22. package/src/v2/cli/run.js +33 -23
  23. package/src/v2/engine/risk-preset.js +1 -1
  24. package/src/v2/runtime/notify.js +5 -5
  25. package/src/v2/runtime/overview.js +7 -7
  26. package/templates/ARCHITECTURE.md +1 -1
  27. package/templates/contracts/PROTOCOL.md +2 -10
  28. package/templates/gitignore.template +0 -3
  29. package/templates/pm/adr/README.md +1 -1
  30. package/templates/pm/decisions.md +4 -5
  31. package/templates/standards/CODE.md +1 -1
  32. package/templates/standards/DESIGN.md +1 -1
  33. package/templates/standards/REVIEW.md +2 -2
  34. package/templates/standards/STACK.md +2 -8
  35. package/templates/v2/AGENTS.md +18 -18
  36. package/templates/v2/BUILDBEAT.md +2 -3
  37. package/templates/v2/CLAUDE.md +1 -1
  38. package/templates/v2/run-config.example.yaml +1 -1
  39. package/templates/v2//346/214/207/346/214/245/345/217/260.md +6 -6
  40. package/bin/buildbeat-v2.js +0 -18
  41. package/bin/solobaton.js +0 -6
  42. package/docs/BuildBeat v2/357/274/232AI /345/216/237/347/224/237/350/275/257/344/273/266/344/272/244/344/273/230/346/216/247/345/210/266/345/271/263/351/235/242.md" +0 -2053
  43. package/docs/CHECKS.md +0 -326
  44. package/docs/CLI-PILOT-2026-08-23.md +0 -25
  45. package/docs/CLI-STRATEGY-2026-08.md +0 -55
  46. package/docs/CLI.md +0 -244
  47. package/docs/EXECUTION-PLAN.md +0 -487
  48. package/docs/LEGACY-V1.16-MIGRATION.md +0 -54
  49. package/docs/PHASE1-PILOT-2026-08-24.md +0 -32
  50. package/docs/PHASE2-BUILDBEAT-PILOT-2026-08-25.md +0 -75
  51. package/docs/PHASE2-PILOT-2026-08-25.md +0 -88
  52. package/docs/PHASE2-PILOT-PREFLIGHT-2026-08-25.md +0 -42
  53. package/docs/PHASE4-STABILITY-AUDIT-2026-08-25.md +0 -35
  54. package/docs/PHASE4-V1.20-PILOT-2026-08-25.md +0 -56
  55. package/docs/ROADMAP.md +0 -875
  56. package/docs/V1.21-RELEASE-EVIDENCE-2026-08-25.md +0 -55
  57. package/docs/V2-D2-DECISION-CARD.md +0 -37
  58. package/docs/V2-DECISIONS.md +0 -11
  59. package/docs/V2-ITERATION-01.md +0 -60
  60. package/docs/V2-ITERATION-02.md +0 -32
  61. package/docs/V2-ITERATION-03.md +0 -30
  62. package/docs/V2-ITERATION-04.md +0 -29
  63. package/docs/V2-ITERATION-05.md +0 -20
  64. package/docs/V2-ITERATION-06.md +0 -18
  65. package/docs/V2-ITERATION-07.md +0 -36
  66. package/docs/V2-ITERATION-08.md +0 -62
  67. package/docs/V2-PLAN.md +0 -335
  68. package/docs/V2-PROPOSAL.md +0 -319
  69. package/docs/V2.0.0-BETA.1-RELEASE-EVIDENCE-2026-08-28.md +0 -41
  70. package/docs/V2.0.0-BETA.2-RELEASE-EVIDENCE-2026-08-28.md +0 -8
  71. package/docs/V2.0.0-BETA.3-RELEASE-EVIDENCE-2026-09-01.md +0 -8
  72. package/docs/V2.0.0-BETA.4-RELEASE-EVIDENCE-2026-09-03.md +0 -9
  73. package/docs/V2.0.0-BETA.5-RELEASE-EVIDENCE-2026-09-05.md +0 -10
  74. package/docs/V2.0.0-RELEASE-EVIDENCE-2026-09-05.md +0 -11
  75. package/docs/WP4.3-RELEASE-EVIDENCE-2026-08-25.md +0 -73
  76. package/docs/v2/M1-ACCEPTANCE-2026-08-28.md +0 -38
  77. package/docs/v2/M2-DOD-2026-08-28.md +0 -34
  78. package/docs/v2/M4-EXTERNAL-PILOT-2026-08-28.md +0 -46
  79. package/docs/v2/M4-PILOT-APP-2026-08-28.md +0 -44
  80. package/docs/v2/M4-SELFHOST-2026-08-28.md +0 -53
  81. package/docs/v2/guide/08-migration-v1.md +0 -72
  82. package/example/.buildbeat/manifest.json +0 -45
  83. package/example/AGENTS.md +0 -19
  84. package/example/ARCHITECTURE.md +0 -39
  85. package/example/BUILDBEAT.md +0 -17
  86. package/example/CLAUDE.md +0 -7
  87. package/example/README.md +0 -75
  88. package/example/contracts/PROTOCOL.md +0 -38
  89. package/example/pm/NOW.md +0 -22
  90. package/example/pm/adr/ADR-0001-local-first-sqlite.md +0 -25
  91. package/example/pm/adr/README.md +0 -7
  92. package/example/pm/archive//344/270/200/346/234/237/evidence/gate1.md +0 -5
  93. package/example/pm/archive//344/270/200/346/234/237/evidence/gate2.md +0 -5
  94. package/example/pm/archive//344/270/200/346/234/237/evidence/gate3.md +0 -5
  95. package/example/pm/archive//344/270/200/346/234/237/evidence/gate4.md +0 -5
  96. package/example/pm/archive//344/270/200/346/234/237/evidence/implementation.md +0 -5
  97. package/example/pm/decisions.md +0 -20
  98. package/example/pm/status//344/272/247/345/223/201.md +0 -20
  99. package/example/pm/status//345/205/250/346/240/210.md +0 -15
  100. package/example/pm/status//346/265/213/350/257/225.md +0 -15
  101. package/example/pm//344/270/200/346/234/237-/347/234/213/346/235/277.md +0 -97
  102. package/example/standards/CODE.md +0 -18
  103. package/example/standards/DESIGN.md +0 -34
  104. package/example/standards/REVIEW.md +0 -16
  105. package/example/standards/STACK.md +0 -31
  106. package/src/cli.js +0 -323
  107. package/src/constants.js +0 -202
  108. package/src/doctor.js +0 -267
  109. package/src/planner.js +0 -251
  110. package/src/project.js +0 -844
  111. package/src/upgrader.js +0 -1249
  112. package/src/v2/presets/risk/legacy-four-gates.yaml +0 -44
  113. package/src/writer.js +0 -534
  114. package/templates/.claude/agents/reviewer.md +0 -62
  115. package/templates/AGENTS.md +0 -85
  116. package/templates/BUILDBEAT.md +0 -13
  117. package/templates/CLAUDE.md +0 -7
  118. package/templates/pm/NOW.md +0 -26
  119. package/templates/pm/changes/README.md +0 -44
  120. package/templates/pm/status/README.md +0 -32
  121. package/templates/pm//345/275/223/346/234/237/347/234/213/346/235/277.md +0 -62
  122. package/templates/scripts/bus-check.sh +0 -1875
  123. package/templates/scripts/design-preview.sh +0 -44
  124. package/templates/scripts/drift-check.sh +0 -112
  125. package/templates/scripts/pre-commit.sh +0 -74
  126. package/templates/scripts/verify-status.sh +0 -105
  127. package/templates//346/214/207/346/214/245/345/217/260.md +0 -58
package/lessons.md CHANGED
@@ -1,36 +1,36 @@
1
1
  # 反模式与实战教训(全部真实发生,已脱敏通用化)
2
2
 
3
- > 读法:每条 = 症状 → 根因 → 总线里对应的解药。解药编号对应 SKILL.md §4 十条规则。
3
+ > 读法:每条 = 症状 → 根因 → 解药。解药指向项目根 `AGENTS.md` 的协作规则(模板 `templates/v2/AGENTS.md` §2,①–⑪)或运行时机制;编号对应 SKILL.md §4。
4
4
 
5
5
  ## 1. SSOT 腐烂(最隐蔽、最致命)
6
6
 
7
- **症状**:看板头部停在四周前的状态;"当前线上版本"在四份文档里出现三个互相矛盾的值;NOW 指针从 30 行薄文件长成 9 段流水账;新一期没建新看板,直接往上一期看板里塞嵌套引文"续命"。
7
+ **症状**:进度文件头部停在四周前的状态;"当前线上版本"在四份文档里出现三个互相矛盾的值;一个本该 30 行的指针文件长成 9 段流水账;新一期没建新记录,直接往上一期里塞嵌套引文"续命"。
8
8
  **根因**:协调文档天然 append-only,没有任何机制强制压缩/归档;每个事实(版本/决策/进度)被复写进 N 处,改的时候必漏。
9
- **解药**:规则⑨(单点事实)+ 规则①(看板名不写死)+ 换期压缩仪式。**会腐烂的事实(线上版本/在途数量/当前轨道)一律不写进文档,只放"怎么查"的指针。**
9
+ **解药**:规则⑨(单点事实)+ 规则①(唯一入口)。**会腐烂的事实(线上版本/在途数量/进度)一律不写进文档**——进度由内核从台账与 Git 回读(`overview` / `status`),线上版本只信 `observe status` 与部署平台实查,文档只放"怎么查"的指针。
10
10
 
11
11
  ## 2. 读过期 race(治了写冲突,漏了读 staleness)
12
12
 
13
- **症状**:A 会话开工时读了看板上的决策,干到一半用户改了主意并由 PM 更新了看板;A 会话不知道,按旧决策把活干完上线,只能事后拍板"不回滚"。
14
- **根因**:开工护栏只在会话开始跑一次;长会话中途不会重读总线。
15
- **解药**:规则④后半句——**部署/改契约/migration 等不可逆动作前必须重跑 bus-check**,bus-check 打印"最近拍板 3 条"正是给这一刻看的。
13
+ **症状**:A 会话开工时读了当时的决策,干到一半用户改了主意并更新了计划;A 会话不知道,按旧决策把活干完上线,只能事后拍板"不回滚"。
14
+ **根因**:开工护栏只在会话开始跑一次;长会话中途不会重读事实。
15
+ **解药**:规则④——**部署/改契约/migration 等不可逆动作前必须重跑 `overview`**;内核侧,批准绑定 intent/plan/候选/证据的 digest,对象改过即 `stale`,旧批准不复用。
16
16
 
17
17
  ## 3. 静态稿拍板 → 返工螺旋(单项目最大成本来源)
18
18
 
19
19
  **症状**:用户对着静态设计稿拍板通过;实现上线后用户见到真渲染,推翻自己,整层 UI 返工;某次改版存活仅 2 天就被下一期拍板回退;设计终签之后用户对着真机又推翻 4 条已"演进接受"的走查裁决。
20
- **根因**:人对静态 artboard 的判断不可靠,见到可点的真东西才表真态;流程却假设"Gate2 拍板是终态"。
21
- **解药**:规则⑩——Gate2 拍板对象必须是真渲染可点原型;终签也必须含真渲染走查(教训:spec 数值全对,渲染出来卡片仍然"偏平/缺卡/换行")。
20
+ **根因**:人对静态 artboard 的判断不可靠,见到可点的真东西才表真态;流程却假设"设计拍板是终态"。
21
+ **解药**:规则⑩——有 UI 的拍板对象必须是真渲染可点原型 + 截图 digest;上线前终签也必须含真渲染走查(教训:spec 数值全对,渲染出来卡片仍然"偏平/缺卡/换行")。
22
22
 
23
- ## 4. 域切得过细(按职能切,不按物理边界切)
23
+ ## 4. 视角切得过细(按职能切,不按物理边界切)
24
24
 
25
- **症状**:起步按人类公司职能切 6 个域(PM/前端/后端/设计/测试/运维);两个月内前端+后端合并、设计+测试合并,收敛到 4 个;期间契约 ping-pong、会话间信息差、人被编排成本压垮。
26
- **根因**:域的本质是"独立上下文+独立核查",不是"模拟一个公司组织架构图"。物理上同仓同镜像的"前后端"切成两个域,只制造交接;真正需要隔离的是"写者"与"审者"。
27
- **解药**:§2——3 域起步,切域只看物理边界与核查需要;合并丢掉的"天然独立核查"用 reviewer subagent + 测试域独立核两端补回。
25
+ **症状**:起步按人类公司职能切 6 个会话(PM/前端/后端/设计/测试/运维);两个月内前端+后端合并、设计+测试合并,收敛到 4 个;期间契约 ping-pong、会话间信息差、人被编排成本压垮。
26
+ **根因**:视角的本质是"独立上下文+独立核查",不是"模拟一个公司组织架构图"。物理上同仓同镜像的"前后端"切成两个视角,只制造交接;真正需要隔离的是"写者"与"审者"。
27
+ **解药**:SKILL §2——产品/全栈/测试三个视角起步,切视角只看物理边界与核查需要;"天然独立核查"由 Run 内置的 fresh-context 只读 reviewer + 测试视角独立核两端保证。
28
28
 
29
29
  ## 5. "当前线上 vX"声明漂移
30
30
 
31
- **症状**:运维 README 写"当前线上 v0.3.x",实际线上早已 v0.4.x;PM 文档表格写某服务 v0.2.18,bus-check 实查是 v0.2.20。
31
+ **症状**:运维 README 写"当前线上 v0.3.x",实际线上早已 v0.4.x;文档表格写某服务 v0.2.18,实查是 v0.2.20。
32
32
  **根因**:版本是变化最快的事实,写进文档的瞬间就开始腐烂。
33
- **解药**:规则⑨——线上版本唯一查询口是脚本实查(bus-check 调 live-status 钩子);文档要写版本必须带"写就时点"字样。
33
+ **解药**:规则⑨——线上版本唯一查询口是实查(`observe status`、`release-readback` 车道的上线前后回读);文档要写版本必须带"写就时点"字样。
34
34
 
35
35
  ## 6. 走查漏掉非主路径 UI 面
36
36
 
@@ -38,126 +38,107 @@
38
38
  **根因**:走查清单按"页面"组织,独立弹窗/二级浮层/空错态不在清单上。
39
39
  **解药**:走查范围 = **所有 UI 面**(独立弹窗也算);每个可见界面四态(加载/空/错误/移动)必处理、必走查;规则⑧带图对比让"漏走查"无处藏身。
40
40
 
41
- ## 7. 状态条目膨胀,下游读不动
42
-
43
- **症状**:单条状态 entry 长到一屏放不下,信息密度高但检索性差;下游会话开工要先啃几千字。
44
- **根因**:写状态的会话倾向把全部细节倒进去自证完成;没有长度纪律。
45
- **解药**:状态条目只写「做了什么 + hash + 证据指针」,长篇分析放报告文件挂链接;换期压缩仪式把历史截走。
46
-
47
- ## 8. 流程只管"怎么做对",不管"做的是不是对的事"
41
+ ## 7. 流程只管"怎么做对",不管"做的是不是对的事"
48
42
 
49
43
  **症状**:风险清单里 P1 质量问题(直接影响用户信任)挂了一个月;同期连续数期资源全部投给 UI 改版——因为人在回路,人挑了顺手有趣的活。
50
44
  **根因**:立项环节没有任何机制要求对照风险清单/路线图。
51
- **解药**:立项模板加一栏"对照风险清单/优先级建议,为什么先做这个";每期收尾加一条"上线 N 天数据回看"挂账。流程越精密,越要防"精密地做不重要的事"。
45
+ **解药**:`intent.md` 必须写"为什么做"与止损线,对照风险与优先级;`observe` 的 Intent 草稿由人分诊(fix_now / schedule / dismiss),把生产侧信号排进队列。流程越精密,越要防"精密地做不重要的事"。
52
46
 
53
- ## 9. 设计产物自相矛盾
47
+ ## 8. 设计产物自相矛盾
54
48
 
55
49
  **症状**:设计包终版 HTML 的**标题**写方案 A,**实际渲染**的是方案 B;实现会话按标题选了 A,返工。
56
50
  **根因**:设计稿没有 manifest,"终版"靠文件名约定,人没核对标题与渲染一致。
57
- **解药**:设计交付硬要求——标题/文件名必须与实渲染一致;Gate2 用渲染实物拍板天然兜底(规则⑩)。
51
+ **解药**:设计交付硬要求——标题/文件名必须与实渲染一致;拍板用渲染实物天然兜底(规则⑩)。
58
52
 
59
- ## 10. 自动化便利与安全红线打架
53
+ ## 9. 自动化便利与安全红线打架
60
54
 
61
55
  **症状**:Stop hook 自动 push 一切已 commit 内容,省心;但一旦误 commit 凭据,会在无人审查窗口下立刻出网。
62
56
  **根因**:自动化没有配套闸门。
63
- **解药**:装自动 push 前先装 secret 扫描;红线写成**可执行口径**("凭据不入 git、不出本机、600 权限"),而不是不可执行的口号("凭据绝不写进任何文件"——本地 .env 事实上必须存在,口径与实践不符的规则必被持续违反)。
64
-
65
- ## 11. 状态文档里的 commit hash 是幽灵
57
+ **解药**:装自动 push 前先装 secret 扫描(gitleaks pre-commit);Worker 默认 env 白名单,`env:` 只注入点名的变量,通知 URL 只走环境变量;红线写成**可执行口径**("凭据不入 git、不出本机、600 权限"),而不是不可执行的口号("凭据绝不写进任何文件"——本地 .env 事实上必须存在,口径与实践不符的规则必被持续违反)。
66
58
 
67
- **症状**:上游状态文件写着「code-complete,hash 9c3xxxx」;下游会话接手,一查——git 里**查无此 hash**,改动其实整个躺在工作树没提交,所谓 hash 是臆造/本地曾有后被重置的幽灵。
68
- **根因**:规则③只要求「状态行带 hash」,没要求 hash **可解析**;写状态的会话把"打算提交"当成"已提交",下游不核它就成了假事实。
69
- **解药**:证据制完成延伸到 hash 本身——**接手 code-complete 第一步 `git cat-file -t <hash>` 核 hash 真实存在**;查无此 hash 一律按"未完成"处理,回工作树找改动。规则②的"独立核查再信"不止适用契约,适用一切上游声明。(此检查已机器化:bus-check「幽灵 hash 核验」逐个核 status 里的 hash,`--strict` 下挂 pre-commit 直接拦。)
70
-
71
- ## 12. 多会话共用工作树 → 构建产物混入他人 WIP
59
+ ## 10. 多会话共用工作树 → 构建产物混入他人 WIP
72
60
 
73
61
  **症状**:部署构建直接 `tar` 打包工作树,把**另一个会话没写完的数据库 migration** 烤进了生产镜像,容器启动时自动执行了半成品 SQL。
74
62
  **根因**:多会话共编下工作树永远不干净——"我构建那一刻的目录内容" ≠ "我提交的代码";打包工作树等于把并行会话的 WIP 一起发布。
75
- **解药**:**构建/打包一律取 `git archive HEAD`**,产物只来自已提交内容;推镜像/发布前抽查产物文件清单(`tar tzf`)有无 stray 文件。这是红线"不 `git add -A`"在构建侧的镜像:提交只 stage 自己的,构建只取已提交的。
63
+ **解药**:每个 Run 在隔离 worktree 里跑,候选由 Git 回读;**构建/打包一律取 `git archive HEAD`**,产物只来自已提交内容;推镜像/发布前抽查产物文件清单(`tar tzf`)有无 stray 文件。这是红线"不 `git add -A`"在构建侧的镜像:提交只 stage 自己的,构建只取已提交的。
76
64
 
77
- ## 13. 平台侧配置漂移(git ≠ 生产的另一半)
65
+ ## 11. 平台侧配置漂移(git ≠ 生产的另一半)
78
66
 
79
67
  **症状**:某天第三方登录突然报凭据无效——部署平台控制台上的 env/secret 被改过,但**没重新部署**,容器还跑旧值;另一次实查发现线上镜像 tag 在 git 里找不到对应 tag,没人说得清线上跑的是哪份代码。
80
68
  **根因**:部署平台的 env/secret 与镜像配置**不在 git 里**,控制台一改就产生第二事实源;"改了配置"与"配置生效"之间还隔一次部署,没有机制把这个缺口暴露出来。
81
- **解药**:**漂移检测基线**(`templates/scripts/drift-check.sh`)——对平台侧配置做 env 指纹(🔴只存 sha256 指纹不存值)+ 镜像 tag↔git tag 锚定,基线存 `bus-baseline.json`;bus-check 每次开工自动比对,红字报「+新增 / -删除 / ≠值变 / git 无此 tag」;改配置或部署后跑 `--update-baseline`,把"确认已生效"变成显式动作。
69
+ **解药**:`observe` 预设里的 drift 类 Evidence Provider 对平台侧配置做指纹比对(🔴只存 sha256 指纹不存值)并把镜像 tag 锚定到 git;`release-readback` 车道把"做之前回读 → 人做 → 做之后回读 → 观察 → 关窗"记成 L4 证据,把"确认已生效"变成显式动作。
82
70
 
83
- ## 14. UI 元注释复发(模型爱给"做的人"写字)
71
+ ## 12. UI 元注释复发(模型爱给"做的人"写字)
84
72
 
85
- **症状**:新看板上线,用户在界面上抓到 6 处口径脚注/"数据来源:xxx"/示意标记之类**给开发者看的解释性文字**,返工清除;这类问题若只做一次性整改,下一个新页面必再长出来——模型出 UI 天然爱自证。
73
+ **症状**:新界面上线,用户在界面上抓到 6 处口径脚注/"数据来源:xxx"/示意标记之类**给开发者看的解释性文字**,返工清除;这类问题若只做一次性整改,下一个新页面必再长出来——模型出 UI 天然爱自证。
86
74
  **根因**:模型倾向把实现解释、数据口径、调试提示写进可见界面(训练里"解释自己"是美德,产品里是噪音);流程只在用户抓到时修一次,没有常设关卡。
87
- **解药**:升格为常设红线(§1.5「界面零元注释」)+ 写进 reviewer 审查清单(第 6 条,发现通常判 P1),每次上线核查门自动查;确需解释的信息走设计稿定的 hover/帮助入口。通用原则:**用户对同类问题给过一次纠正,第二次就不该靠用户抓——把"修一次"升格为"每次必查的关卡"(红线/清单/hook),让流程替人记住偏好。**
75
+ **解药**:升格为常设红线(AGENTS §1.5「界面零元注释」)+ 写进 reviewer prompt(发现通常判 P1),每次上线前必查;确需解释的信息走设计稿定的 hover/帮助入口。通用原则:**用户对同类问题给过一次纠正,第二次就不该靠用户抓——把"修一次"升格为"每次必查的关卡"(红线/清单/hook),让流程替人记住偏好。**
88
76
 
89
- ## 15. 上下文载体绑死单一厂商(且与标准名撞脸)
77
+ ## 13. 上下文载体绑死单一厂商(且与标准名撞脸)
90
78
 
91
- **症状**:装载入口只提供 `CLAUDE.md`,于是用 Codex CLI / Gemini CLI / Aider / Zed 开的会话**根本装载不到总线规则**——开工护栏(规则④)、状态分写(规则⑦)、红线在那些会话里静默失效,且失效时没有任何报错,人以为规则在跑。另一半症状在命名:根目录 `Agent.md`(全栈总图,按需读)与各子仓 `AGENTS.md`(开放标准,自动装载)仅差一个 S、语义相反,人和 AI 反复误判前者也会被自动装载。
79
+ **症状**:装载入口只提供 `CLAUDE.md`,于是用 Codex CLI / Gemini CLI / Aider / Zed 开的会话**根本装载不到协作规则**——开工护栏、写边界、红线在那些会话里静默失效,且失效时没有任何报错,人以为规则在跑。另一半症状在命名:根目录 `Agent.md`(全栈总图,按需读)与各子仓 `AGENTS.md`(开放标准,自动装载)仅差一个 S、语义相反,人和 AI 反复误判前者也会被自动装载。
92
80
 
93
- **根因**:把某个厂商的装载约定当成物理常量写进方法论(原 SKILL §3 原话:「`CLAUDE.md` 是工具装载约定,永远在项目根」),方法论因此绑死单一工具——而本方法论的核心场景恰恰是**并行多会话**,用户极可能一个会话用 A 工具、另一个用 B 工具,厂商锁在这里的杀伤面比单会话场景大一个量级。命名那半则是新造文件名时没去避让既有标准的近似名。
81
+ **根因**:把某个厂商的装载约定当成物理常量写进方法论,方法论因此绑死单一工具——而本方法论的核心场景恰恰是**并行多会话**,用户极可能一个会话用 A 工具、另一个用 B 工具,厂商锁在这里的杀伤面比单会话场景大一个量级。命名那半则是新造文件名时没去避让既有标准的近似名。
94
82
 
95
83
  **解药**:装载入口一律用开放标准 **`AGENTS.md`**——层叠语义由标准定义(向上收集沿途所有 `AGENTS.md` 合并、就近优先),「根写全局 / 子仓写局部」是白捡的,不用自己发明。厂商专属文件只留**一行指针**指过去,🔴 绝不复制内容(复制 = 自造第 1 条 SSOT 腐烂),🔴 也不用符号链接(Windows 上 git 默认 `core.symlinks=false`,clone 出来静默退化成内容是路径字符串的普通文件,装载照样失效且照样无声)。全栈总图改名 `ARCHITECTURE.md`,与标准名彻底拉开。**并明确拒绝** gitignore 的本地覆盖文件(`AGENTS.override.md` 之类):这份文件装的是红线与护栏,允许一份不进 git 的覆盖 = 给绕过护栏开后门,reviewer 与 pre-commit 都看不见它。通用原则:**方法论里凡是「某工具的约定」,都必须能被替换掉;替换不掉的地方就是厂商锁,迟早在换工具那天要债。**
96
84
 
97
- ## 16. 核查门按小任务重复全审,防线反过来吞掉交付
85
+ ## 14. 核查门按小任务重复全审,防线反过来吞掉交付
98
86
 
99
- **症状**:一个阶段被拆成规格、契约、键空间、设计输入、批准回写等多个小任务,每个任务收尾都生成一份完整 reviewer 报告,整改后再全文复核,合并前和阶段门又重新全审。同一条语义链被核三四次;报告体量开始超过真正交付物,主会话的大量时间花在复制 P0/P1/P2 原文、回写 status 和解释「本次通过不代表下一门通过」。
87
+ **症状**:一个阶段被拆成规格、契约、键空间、设计输入、批准回写等多个小任务,每个任务收尾都生成一份完整 reviewer 报告,整改后再全文复核,合并前和阶段门又重新全审。同一条语义链被核三四次;报告体量开始超过真正交付物,主会话的大量时间花在复制 P0/P1/P2 原文、回写状态和解释「本次通过不代表下一门通过」。
100
88
 
101
- **根因**:把三种本应分开的东西揉成一个开关:① 每次提交都该跑的机器检查;② 实现中突发的高风险语义 delta;③ 里程碑候选的完整四方一致性核查。再用「任务结束」「文件数」而不是**风险状态是否变化 / 候选 hash 是否变化**做触发条件,任务拆得越细,审查次数反而越多;P2 也被当成重新跑全流程的理由。流程于是优化了审查产量,没有优化风险发现率。
89
+ **根因**:把三种本应分开的东西揉成一个开关:① 每次提交都该跑的机器检查;② 实现中突发的高风险语义 delta;③ 里程碑候选的完整一致性核查。再用「任务结束」「文件数」而不是**风险状态是否变化 / 候选 hash 是否变化**做触发条件,任务拆得越细,审查次数反而越多;P2 也被当成重新跑全流程的理由。流程于是优化了审查产量,没有优化风险发现率。
102
90
 
103
- **解药**:规则⑥改成「机器闸常驻 + 高风险 delta 定向核 + 里程碑候选一次全核」。gitleaks/bus-check 等轻量机器闸每次提交照跑;受影响测试按变更批次跑,里程碑候选跑全量并留证据。只有冻结契约对外语义、鉴权/租户/Secret/fail-closed、持久化键空间或不可逆副作用变化才立即核 `base..candidate`;完整 reviewer 绑定精确候选 hash 集,同一 hash 与同一份绿证据在合并前直接复用。P0/P1 阻塞,P2 默认挂账;首轮问题原文只存一次,后续用 finding closure 表收口。实现期新自由度集中记在同一份「实现语义清单」,到里程碑一次消费。通用原则:**核查强度跟风险变化走,不跟任务数和文档行数走;写者≠审者保留,重复全审不是独立性。**
91
+ **解药**:review 只在 verify 通过后对固定 candidate 跑一次,每 Run 默认 2 轮封顶(`budgets.maxAttempts.review`),Work 级 `reviewRoundsPerWork` 跨 Run 累计;`cache.verify: tree` 让同树同命令的证据直接复用(标 `REUSED`);reviewer 输入带 `lastReviewed` 与历史裁决 `anchor`,只核 delta;P0/P1 阻断且经 `reviewTriage` 人分诊后才派 fixer,P2 默认挂账。通用原则:**核查强度跟风险变化走,不跟任务数和文档行数走;写者≠审者保留,重复全审不是独立性。**
104
92
 
105
- ## 17. 追踪项被当成任务边界,人不断说“继续”和“批准”
93
+ ## 15. 追踪项被当成任务边界,人不断说“继续”和“批准”
106
94
 
107
- **症状**:需求被拆成十几个可追踪条目后,会话每交一份文档、一个 commit、一次 reviewer 结论或一条 status 就结束,把接力棒交还给人;同一目标内明明还有安全工作,却要人反复说“继续”。另一边,十几个验收条件被原样呈现成十几个审批项,用户分轮回答后永久台账再留下“3/14、11/14、14/14”多条部分进度。形式上每一步都可审计,项目吞吐却被人工调度和确认请求吃掉。
95
+ **症状**:需求被拆成十几个可追踪条目后,会话每交一份文档、一个 commit、一次 reviewer 结论就结束,把接力棒交还给人;同一目标内明明还有安全工作,却要人反复说“继续”。另一边,十几个验收条件被原样呈现成十几个审批项,用户分轮回答后永久台账再留下“3/14、11/14、14/14”多条部分进度。形式上每一步都可审计,项目吞吐却被人工调度和确认请求吃掉。
108
96
 
109
- **根因**:把三种粒度混成一种:需求/验收项是**追踪粒度**,工作包是**执行粒度**,Gate/真实取舍是**审批粒度**。流程没有显式 `objective / in_scope / terminal_condition`,agent 就把“一个产物完成”当终止条件;又没有区分冻结前可逆草案与冻结后语义,于是任何草案选择都被当成必须立即人批。状态日志和决策台账反过来奖励“多收尾、多问、多记”,却不奖励用户级结果。
97
+ **根因**:把三种粒度混成一种:需求/验收项是**追踪粒度**,工作包是**执行粒度**,人批转换/真实取舍是**审批粒度**。流程没有显式 `objective / in_scope / terminal_condition`,agent 就把“一个产物完成”当终止条件;又没有区分冻结前可逆草案与冻结后语义,于是任何草案选择都被当成必须立即人批。台账反过来奖励“多收尾、多问、多记”,却不奖励用户级结果。
110
98
 
111
- **解药**:每轮只认领一个用户级工作包,通常覆盖多个 ID / 文档 / commit;范围内仍有安全可逆工作就继续,子产物只报中间进展。任务只因目标带证据完成、真实人类阻塞或明确检查点而结束。审批分 `STOP_NOW / BATCH_AT_GATE / NO_APPROVAL`:越 Gate/扩范围/改冻结契约/不可逆动作/风险接受立即停;冻结前可逆取舍进看板决策收件箱,到门前默认一次问 2–5 个(确实只有 1 个就单项);事实、推导约束、status/归档/P2 自主做。永久台账只记收敛后的真实决策包,status 只在工作包/里程碑/真实阻塞更新。通用原则:**细粒度用来追踪,不是用来制造更多任务终点和人批门。**
99
+ **解药**:每轮只认领一个用户级工作包(= Work),通常覆盖多个 ID / 文档 / commit;范围内仍有安全可逆工作就继续,子产物只报中间进展。任务只因目标带证据完成、真实人类阻塞或明确检查点而结束。审批分 `STOP_NOW / BATCH_AT_GATE / NO_APPROVAL`:越发布门/扩范围/改冻结契约/不可逆动作/风险接受立即停;冻结前可逆取舍进门前决策卡,到人批转换前默认一次问 2–5 个(确实只有 1 个就单项);事实、推导约束、归档、P2 自主做。永久台账只记收敛后的真实决策包。通用原则:**细粒度用来追踪,不是用来制造更多任务终点和人批门。**
112
100
 
113
- ## 18. 未稳定候选过早送审,一个 reviewer 变成连续状态风暴
101
+ ## 16. 未稳定候选过早送审,一个 reviewer 变成连续状态风暴
114
102
 
115
- **症状**:写者刚提交首个功能切片就宣称“候选已固定”并启动 reviewer;reviewer 还在跑,写者自己的边界自查又连续发现幂等恢复、429 退避、demo 模式与测试隔离等问题,几分钟内 candidate hash 连变数次、工作树重新变脏。主会话把这些自发现修补称为“只核 delta”,reviewer 状态卡与整改播报不断刷屏;旧候选尚无完整结论,却已经长出一条 milestone → delta → closure 的伪链。
103
+ **症状**:写者刚提交首个功能切片就宣称“候选已固定”并启动 reviewer;reviewer 还在跑,写者自己的边界自查又连续发现幂等恢复、429 退避、demo 模式与测试隔离等问题,几分钟内 candidate hash 连变数次、工作树重新变脏。主会话把这些自发现修补称为“只核 delta”,reviewer 状态卡与整改播报不断刷屏;旧候选尚无完整结论,却已经长出一条伪审查链。
116
104
 
117
- **根因**:规则只说“稳定候选”,没有把稳定变成可核前置;又把“高风险领域”误写成“实现中一碰就立即派 reviewer”。于是 reviewer 代替了写者应先完成的自查,而 `risk-delta` 被滥用于首次 milestone 前的普通候选收敛。subagent 还分批播报 findings/进度,视觉上一个 reviewer 像开了多轮审查。
105
+ **根因**:规则只说“稳定候选”,没有把稳定变成可核前置;reviewer 代替了写者应先完成的自查。subagent 还分批播报 findings/进度,视觉上一个 reviewer 像开了多轮审查。
118
106
 
119
- **解药**:引入 **review-ready** 四项硬前置:工作包实现与写者自查完成;所有候选仓 `HEAD=candidate` 且工作树干净;受影响/全量 L3 与真渲染证据绿;无已知待修或计划改 hash。首次 milestone 前的鉴权/Secret/fail-closed 等自发现问题先集中进实现语义清单并自行收敛,不送审;只有修改已冻结对外契约或不可逆副作用才提前 `STOP_NOW + risk-delta`。每工作包每 Gate 默认一次 milestone,P0/P1 合并修完后一次 closure,P2 不复核。reviewer 单次静默核完再返回;返回前 candidate 改变就标 `SUPERSEDED` 并停止,不得把连续修补包装成 delta 链。通用原则:**独立审查应消费稳定候选,不能成为写者边实现边找问题的后台 lint。**
107
+ **解药**:内核化——review 步只在 verify 通过之后、对 Git 回读固定的 candidate 启动;reviewer 是 fresh-context 只读 worker,工作树前后快照比对,写入即失败落账;Run 跑着时不改它的候选,要手修就等它停下,在 worktree 里改完提交后 `resume --adopt <sha>`(树必须干净、HEAD 必须是该 sha)。通用原则:**独立审查应消费稳定候选,不能成为写者边实现边找问题的后台 lint。**
120
108
 
121
- ## 19. 域回复各说各话,人还要自己拼交接结论
109
+ ## 17. 域回复各说各话,人还要自己拼交接结论
122
110
 
123
111
  **症状**:产品视角回一屏分析,全栈视角罗列文件和实现细节,测试视角只回一个通过/不通过。人要再追问「到底做了什么、还没做什么、该谁接」,证据又和业务结果分开,很难一眼对上。
124
112
 
125
- **根因**:BuildBeat 规定了 `pm/status/{视角}.md` 的持久状态写法,却只笼统要求「一屏收尾」,没有给面向人的回复一个简单统一的出口。模型便按各自任务的局部叙事优化,交接信息结构自然漂移。
113
+ **根因**:方法论规定了持久事实怎么写,却只笼统要求「一屏收尾」,没有给面向人的回复一个简单统一的出口。模型便按各自任务的局部叙事优化,交接信息结构自然漂移。
126
114
 
127
- **解药**:每个 AI 视角面向人收口时统一用「已做 → 未做 → 下一步」。`已做`只写功能/业务结果,证据紧跟它支持的事项;多项共用才放一条共同证据。`未做`必须写原因,同时承载未验证边界。本域完成就说下一棒是谁、做什么;未完成就说需要谁提供或确认什么;自己还能继续就不伪求助。格式只约束收口,不约束中间探索;持久真相仍在 Git/status/证据文件。通用原则:**统一交接接口,不统一模型怎么思考。**
115
+ **解药**:每个 AI 视角面向人收口时统一用「已做 → 未做 → 下一步」(SKILL §6.4)。`已做`只写功能/业务结果,证据紧跟它支持的事项;多项共用才放一条共同证据。`未做`必须写原因,同时承载未验证边界。本视角完成就说下一棒是谁、做什么;未完成就说需要谁提供或确认什么;自己还能继续就不伪求助。格式只约束收口,不约束中间探索;持久真相仍在 Git 与 Run 台账。通用原则:**统一交接接口,不统一模型怎么思考。**
128
116
 
129
- ## 20. Worker 顺手起的名字,所有者要连问四轮
117
+ ## 18. Worker 顺手起的名字,所有者要连问四轮
130
118
 
131
119
  **症状**:一个非生产的健康聚合服务被 AI 会话按内部术语命名(形如 `<内部术语>-nonprod`),域名也照此申请;所有者在会话里连问「这是干啥的」「名字怎么起的」「非生产?后面还要建生产?」「不做会影响什么」,最后自己改成一个业务上听得懂的名字。同期「服务 4 小时自停」也是 worker 按契约自定,所有者见到才问「为什么需要 4 个小时」。
132
120
  **根因**:名字、时长这类参数在实现视角里是"细节",在所有者视角里是"以后天天要念的东西";流程只把契约/范围/不可逆动作列为决策项,没把**可见命名**列进去,于是它们从 worker 手里直接落地。
133
- **解药**:凡所有者以后要看见或念出来的名字与参数(域名、服务名、环境名、自停时长、窗口时长)默认 `BATCH_AT_GATE`——写进 intent 或门前决策卡,给推荐值和理由,人一次批;reviewer 清单把「引入未经批准的可见命名」记 P2(v2 AGENTS 模板第 ⑪ 条、Skill §0.5.2)。通用原则:**决策项的边界按"谁以后要面对它"划,不按"实现上是不是细节"划。**
121
+ **解药**:凡所有者以后要看见或念出来的名字与参数(域名、服务名、环境名、自停时长、窗口时长)默认 `BATCH_AT_GATE`——写进 intent 或门前决策卡,给推荐值和理由,人一次批;reviewer 清单把「引入未经批准的可见命名」记 P2(AGENTS 模板第 ⑪ 条、Skill §0.5.2)。通用原则:**决策项的边界按"谁以后要面对它"划,不按"实现上是不是细节"划。**
134
122
 
135
- ## 21. Run 停下来了,但没人知道;人守着屏幕,但看不见时间
123
+ ## 19. Run 停下来了,但没人知道;人守着屏幕,但看不见时间
136
124
 
137
125
  **症状**:两个子仓 58 个 Run 里 32 个被取消,多数是在等人批的状态挂满一天后被批量清掉,人批平均等 7~12 小时;另一边所有者守着一场部署战役时,在同一会话里问了十几次「半小时了正常吗」「十分钟了是卡住了吗」「现在到底是谁在干活?」。事后清理时又发现 16 个终态 Run 的工作树无人收拾。
138
126
  **根因**:Run 的状态只存在于台账和 `inbox`,不主动出站——人只有开一个 AI 会话问才知道有东西等他;`status` 只有步骤和次数,worker 输出被同步 spawn 缓冲到步结束才可见,人手里没有任何时间读数;新 Run 起跑不作废旧等待,残留物也没有回收命令。
139
127
  **解药**:`status` 带每步耗时、同仓历史中位数、最后一次输出距今与末几行,无输出超阈值标 `STALLED`(只标不杀);`.buildbeat/notify.yaml` 把 `HUMAN_REQUESTED / RUN_TERMINAL / STALLED` 出站到钉钉或 webhook(URL 只走环境变量,失败不影响 Run);同 Work 新 Run 自动作废旧等待;`gc` 按"终态且已压账、候选可从别处到达"的规则清工作树;`overview` 回答「到哪了、下一步该谁」。通用原则:**等待必须能找到人,时间必须能被读到;做不到这两条,人就会用反复追问来补,而追问本身就是流程债。**
140
128
 
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"绕过
129
+ ## 20. 预算是刹车不是墙:人批了内核当没批,按 Run 计的预算又被"每轮一个新 Run"绕过
149
130
 
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 后才以"成本太高"砍掉,此前没有任何地方能看到花了多少。
131
+ **症状**: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 约一天,所有者在上线后才以"成本太高"砍掉,此前没有任何地方能看到花了多少。
151
132
  **根因**:预算只是一个数,不是一个可以被人延展的台账事实;预算的计数单位(Run)和人的决策单位(Work)不一致;成本只在事后复盘时才被算出来。
152
133
  **解药**:批准预算耗尽的 `resume-<step>` 即落 `BUDGET_EXTENDED`,该步上限 +1;run-config `budgets:` 覆盖预设;`budgets.reviewRoundsPerWork` 跨本 Work 所有 Run(含作废、含已压账)累计 review 轮数,达标在 review 起跑前停人;`overview` 每个 Work 一行 `cost:`(review 轮 / finding / 等人次 / worker 时长),intent 模板加止损线。通用原则:**预算的计数单位要和人做决定的单位一致,预算耗尽后的人批必须改变内核状态,否则人批等于没批。**
153
134
 
154
- ## 24. worker 基础设施故障被当成候选失败:杀 Run、派 fixer、烧预算
135
+ ## 21. worker 基础设施故障被当成候选失败:杀 Run、派 fixer、烧预算
155
136
 
156
137
  **症状**: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
138
  **根因**:step 状态词汇里已有 timeout / crashed / invalid-output,但路由把它们和"候选没过"一起压成 `failed`;verify 脚本没有办法告诉内核"是环境不行不是代码不行";没有边的失败一律终态。
158
139
  **解药**:timeout / crashed / invalid-output / 退出码 75(`EX_TEMPFAIL`,约定为"环境不可用")判 `infra`:不记失败指纹、不派 fixer、不扣预算(`infraAttempts` 抵回),停 `WAITING_HUMAN`(kind `infra`)等后端恢复;没有转移边的失败也停人而不终态,终态 FAILED 只剩 policy BLOCK。模板 worker 合同写清三条环境事实(沙箱不能监听端口、PATH 只认 POSIX、环境不满足 `exit 75`)。通用原则:**先问"这次失败说的是候选还是环境",再决定谁来修;答不上来的失败交给人,不交给 fixer。**
159
140
 
160
- ## 25. 台账说的和人看到的不是一回事:已上线显示为取消、已发布显示为"没东西可合"、doctor 过了 start 却被挡
141
+ ## 22. 台账说的和人看到的不是一回事:已上线显示为取消、已发布显示为"没东西可合"、doctor 过了 start 却被挡
161
142
 
162
143
  **症状**:候选已合入生产的 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
144
  **根因**:阶段判定只看最后一个 Run 的终态,不看候选是否已在主干;overview 不认识 release 车道;doctor 检查的是配置合法性,不是 start 第一道门会读的事实;内核只认 worker 产出的候选,没有"人供候选"的入口。
package/package.json CHANGED
@@ -1,17 +1,27 @@
1
1
  {
2
2
  "name": "@haiyangbg/buildbeat",
3
- "version": "2.0.1",
3
+ "version": "3.0.0",
4
4
  "description": "BuildBeat: Git-based AI delivery across models, tools, sessions, and people, with context in project files, build-test-review-fix loops, and human decisions.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "buildbeat": "bin/buildbeat.js",
8
- "buildbeat-v2": "bin/buildbeat-v2.js",
9
- "solobaton": "bin/solobaton.js"
7
+ "buildbeat": "bin/buildbeat.js"
10
8
  },
11
9
  "files": [
12
10
  "bin/",
13
11
  "docs/",
14
- "example/",
12
+ "!docs/*-RELEASE-EVIDENCE-*.md",
13
+ "!docs/V2-ITERATION-*.md",
14
+ "!docs/V2-PLAN.md",
15
+ "!docs/V2-PROPOSAL.md",
16
+ "!docs/V2-DECISIONS.md",
17
+ "!docs/V2-D2-DECISION-CARD.md",
18
+ "!docs/BuildBeat v2*.md",
19
+ "!docs/v2/M[124]-*.md",
20
+ "!docs/ROADMAP.md",
21
+ "!docs/EXECUTION-PLAN.md",
22
+ "!docs/PHASE*.md",
23
+ "!docs/CLI-STRATEGY-2026-08.md",
24
+ "!docs/CLI-PILOT-2026-08-23.md",
15
25
  "src/",
16
26
  "templates/",
17
27
  "CHANGELOG.md",
@@ -24,13 +34,11 @@
24
34
  "scripts": {
25
35
  "check:docs": "bash tests/check-docs.sh",
26
36
  "test": "node --test tests/*.test.js",
27
- "test:scripts": "bash tests/test-scripts.sh",
28
37
  "test:pilot": "bash tests/pilot-loop.test.sh",
29
- "test:skill-only": "bash tests/skill-only.test.sh",
30
38
  "test:plugin": "bash tests/plugin-marketplace.test.sh",
31
39
  "test:pack-firstrun": "bash tests/pack-firstrun.test.sh",
32
40
  "pack:check": "npm pack --dry-run",
33
- "prepublishOnly": "npm test && npm run test:scripts && npm run test:pilot && npm run test:skill-only && npm run test:plugin && npm run test:pack-firstrun && npm run check:docs && npm run pack:check"
41
+ "prepublishOnly": "npm test && npm run test:pilot && npm run test:plugin && npm run test:pack-firstrun && npm run check:docs && npm run pack:check"
34
42
  },
35
43
  "engines": {
36
44
  "node": ">=20"
package/src/v2/cli/run.js CHANGED
@@ -55,30 +55,31 @@ import { acquireLock, listHeldRunLocks, releaseLock } from "../workspace/workspa
55
55
 
56
56
  const KERNEL = { kind: "kernel", id: "cli" };
57
57
 
58
- const USAGE = `BuildBeat v2 runtime
58
+ const USAGE = `BuildBeat runtime
59
59
 
60
60
  Usage:
61
- run.js start --config <run-config.yaml> [--attempt new]
62
- run.js resume --config <run-config.yaml> [--adopt <sha> --by <name>] # --adopt: hand fix committed in the worktree; skip fix, resume at verify
63
- run.js status --repo <path> --run <RUN-ID> [--stall-after <minutes>]
64
- run.js inbox --repo <path>
65
- run.js overview --repo <path> [--work <WORK-ID>] [--json true]
66
- run.js approve --repo <path> --run <RUN-ID> --transition <t> [--by <name>] [--config <run-config.yaml>]
67
- run.js reject --repo <path> --run <RUN-ID> [--transition <t>] [--reason <text>] [--by <name>]
68
- run.js accept --repo <path> --work <WORK-ID> --artifact <plan|intent|spec> [--by <name>]
69
- run.js doctor --config <run-config.yaml>
70
- run.js events --repo <path> --run <RUN-ID>
71
- run.js replay --repo <path> --run <RUN-ID>
72
- run.js metrics --repo <path> [--json true]
73
- run.js stop --repo <path> --run <RUN-ID> --reason <text>
74
- run.js gc --repo <path> [--apply true] [--force true]
75
- run.js watch --repo <path> --run <RUN-ID> [--stall-after <minutes>] [--interval <seconds>] [--once true]
76
- run.js observe run --config <observe.yaml>
77
- run.js observe status --repo <path>
78
- run.js observe triage --repo <path> --intent <ref> --action <fix_now|schedule|dismiss> [--by <name>] [--note <text>]
79
- run.js preflight --config <run-config.yaml> --step <id>
80
- run.js findings list --repo <path> --work <WORK-ID>
81
- run.js findings adjudicate --repo <path> --work <WORK-ID> --fingerprint <fp> --action <accept|dismiss> [--by <name>] [--note <text>]
61
+ buildbeat --version
62
+ buildbeat start --config <run-config.yaml> [--attempt new]
63
+ buildbeat resume --config <run-config.yaml> [--adopt <sha> --by <name>] # --adopt: hand fix committed in the worktree; skip fix, resume at verify
64
+ buildbeat status --repo <path> --run <RUN-ID> [--stall-after <minutes>]
65
+ buildbeat inbox --repo <path>
66
+ buildbeat overview --repo <path> [--work <WORK-ID>] [--json true]
67
+ buildbeat approve --repo <path> --run <RUN-ID> --transition <t> [--by <name>] [--config <run-config.yaml>]
68
+ buildbeat reject --repo <path> --run <RUN-ID> [--transition <t>] [--reason <text>] [--by <name>]
69
+ buildbeat accept --repo <path> --work <WORK-ID> --artifact <plan|intent|spec> [--by <name>]
70
+ buildbeat doctor --config <run-config.yaml>
71
+ buildbeat events --repo <path> --run <RUN-ID>
72
+ buildbeat replay --repo <path> --run <RUN-ID>
73
+ buildbeat metrics --repo <path> [--json true]
74
+ buildbeat stop --repo <path> --run <RUN-ID> --reason <text>
75
+ buildbeat gc --repo <path> [--apply true] [--force true]
76
+ buildbeat watch --repo <path> --run <RUN-ID> [--stall-after <minutes>] [--interval <seconds>] [--once true]
77
+ buildbeat observe run --config <observe.yaml>
78
+ buildbeat observe status --repo <path>
79
+ buildbeat observe triage --repo <path> --intent <ref> --action <fix_now|schedule|dismiss> [--by <name>] [--note <text>]
80
+ buildbeat preflight --config <run-config.yaml> --step <id>
81
+ buildbeat findings list --repo <path> --work <WORK-ID>
82
+ buildbeat findings adjudicate --repo <path> --work <WORK-ID> --fingerprint <fp> --action <accept|dismiss> [--by <name>] [--note <text>]
82
83
  `;
83
84
 
84
85
  function parseFlags(argv) {
@@ -507,7 +508,7 @@ async function commandStart(flags) {
507
508
  summary = `${state.run?.work ?? "?"} ${state.run?.status ?? "?"} ${step}${since ? `, last event ${formatMs(Date.now() - Date.parse(since))} ago` : ""}`;
508
509
  }
509
510
  console.error(`blocked by ${holder}: ${summary}`);
510
- console.error(` watch it: buildbeat-v2 status --repo ${label} --run ${holder}`);
511
+ console.error(` watch it: buildbeat status --repo ${label} --run ${holder}`);
511
512
  }
512
513
  console.error("queue position: next after the holder(s) above stop or wait on a human (the repository allows one driving run at a time; worktrees are already isolated)");
513
514
  }
@@ -1174,8 +1175,17 @@ function commandObserve(rest) {
1174
1175
  }
1175
1176
  }
1176
1177
 
1178
+ function packageVersion() {
1179
+ const packageJson = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "package.json");
1180
+ return JSON.parse(readFileSync(packageJson, "utf8")).version;
1181
+ }
1182
+
1177
1183
  async function main() {
1178
1184
  const [command, ...rest] = process.argv.slice(2);
1185
+ if (command === "--version" || command === "-v" || command === "version") {
1186
+ process.stdout.write(`${packageVersion()}\n`);
1187
+ return;
1188
+ }
1179
1189
  if (command === "observe" || command === "findings") {
1180
1190
  try {
1181
1191
  (command === "observe" ? commandObserve : commandFindings)(rest);
@@ -1,5 +1,5 @@
1
1
  // Risk presets per docs/v2/RFC-0003-workflow-policy.md §7: fast / standard /
2
- // controlled plus the legacy-four-gates migration preset. A preset is data —
2
+ // controlled, plus release. A preset is data —
3
3
  // human boundaries (stopAt) and a policy set — never kernel-fixed gates.
4
4
 
5
5
  import { readFileSync } from "node:fs";
@@ -83,16 +83,16 @@ export function nextReply({ repoLabel, state }) {
83
83
  const runId = state.run.id;
84
84
  const lines = [];
85
85
  if (pending.kind === "finding-triage") {
86
- lines.push(`buildbeat-v2 findings list --repo ${repoLabel} --work ${state.run.work}`);
86
+ lines.push(`buildbeat findings list --repo ${repoLabel} --work ${state.run.work}`);
87
87
  lines.push(
88
- `buildbeat-v2 findings adjudicate --repo ${repoLabel} --work ${state.run.work} --fingerprint <fp> --action accept|dismiss --by <you>`,
88
+ `buildbeat findings adjudicate --repo ${repoLabel} --work ${state.run.work} --fingerprint <fp> --action accept|dismiss --by <you>`,
89
89
  );
90
90
  }
91
91
  lines.push(
92
- `buildbeat-v2 approve --repo ${repoLabel} --run ${runId} --transition ${pending.transition} --by <you>` +
92
+ `buildbeat approve --repo ${repoLabel} --run ${runId} --transition ${pending.transition} --by <you>` +
93
93
  (pending.kind === "final-decision" ? " # merge-ready; merge/push stay yours" : " # then: resume --config <run-config.yaml>"),
94
94
  );
95
- lines.push(`buildbeat-v2 reject --repo ${repoLabel} --run ${runId} --reason <why> --by <you>`);
95
+ lines.push(`buildbeat reject --repo ${repoLabel} --run ${runId} --reason <why> --by <you>`);
96
96
  return lines;
97
97
  }
98
98
 
@@ -135,7 +135,7 @@ export function buildNotification(kind, { repoLabel, state, detail = {} }) {
135
135
  `threshold ${detail.threshold ?? "?"}; the process is NOT killed — check status, then decide`,
136
136
  ],
137
137
  candidate: null,
138
- nextReply: [`buildbeat-v2 status --repo ${repoLabel} --run ${run.id}`],
138
+ nextReply: [`buildbeat status --repo ${repoLabel} --run ${run.id}`],
139
139
  };
140
140
  }
141
141
  throw new NotifyConfigError(`unknown notification kind: ${kind}`);
@@ -193,25 +193,25 @@ export function computeOverview(repoRoot, { work = null, repoLabel = "." } = {})
193
193
  stage = intent.accepted ? "INTENT_ACCEPTED" : "INTENT_DRAFT";
194
194
  next = intent.accepted
195
195
  ? `write delivery/work/${workId}/plan.md, then accept it`
196
- : `buildbeat-v2 accept --repo ${repoLabel} --work ${workId} --artifact intent --by <you>`;
196
+ : `buildbeat accept --repo ${repoLabel} --work ${workId} --artifact intent --by <you>`;
197
197
  } else if (!plan.accepted) {
198
198
  stage = plan.stale ? "PLAN_STALE" : "PLAN_DRAFT";
199
- next = `buildbeat-v2 accept --repo ${repoLabel} --work ${workId} --artifact plan --by <you>${plan.stale ? " # plan changed since acceptance" : ""}`;
199
+ next = `buildbeat accept --repo ${repoLabel} --work ${workId} --artifact plan --by <you>${plan.stale ? " # plan changed since acceptance" : ""}`;
200
200
  } else {
201
201
  stage = "READY_TO_RUN";
202
202
  const configs = readdirSync(workDir).filter((name) => /^run-config.*\.ya?ml$/.test(name));
203
203
  next =
204
204
  configs.length > 0
205
- ? `buildbeat-v2 start --config delivery/work/${workId}/${configs[0]} --attempt new`
205
+ ? `buildbeat start --config delivery/work/${workId}/${configs[0]} --attempt new`
206
206
  : `no run-config in delivery/work/${workId}: write one, or close it with a decisions.jsonl row {"transition":"close-work","decision":"closed","subject":{"result":"..."}} if it was doc-only`;
207
207
  }
208
208
  } else if (latest.status === "RUNNING") {
209
209
  stage = "RUNNING";
210
- next = `buildbeat-v2 status --repo ${repoLabel} --run ${latest.id}`;
210
+ next = `buildbeat status --repo ${repoLabel} --run ${latest.id}`;
211
211
  } else if (latest.status === "WAITING_HUMAN") {
212
212
  stage = latest.pendingHuman?.kind === "final-decision" ? "MERGE_DECISION" : "WAITING_HUMAN";
213
213
  const replies = latest.state ? nextReply({ repoLabel, state: latest.state }) : [];
214
- next = replies[0] ?? `buildbeat-v2 inbox --repo ${repoLabel}`;
214
+ next = replies[0] ?? `buildbeat inbox --repo ${repoLabel}`;
215
215
  } else if (latest.status === "SUCCEEDED" && isReleaseLane(latest)) {
216
216
  // A release-readback lane that reached wait-close and was approved is
217
217
  // a closed release window, not "nothing to merge".
@@ -220,7 +220,7 @@ export function computeOverview(repoRoot, { work = null, repoLabel = "." } = {})
220
220
  } else if (merged) {
221
221
  stage = "MERGED";
222
222
  next =
223
- `candidate ${mergedRun.candidate.slice(0, 7)} (${mergedRun.id}) is on ${mainRef}; release/deploy stays a human action; then buildbeat-v2 gc --repo ${repoLabel}` +
223
+ `candidate ${mergedRun.candidate.slice(0, 7)} (${mergedRun.id}) is on ${mainRef}; release/deploy stays a human action; then buildbeat gc --repo ${repoLabel}` +
224
224
  (latest.status !== "SUCCEEDED" ? ` # latest run ${latest.id} ended ${latest.status} after the merge` : "");
225
225
  } else if (latest.status === "SUCCEEDED") {
226
226
  stage = "MERGE_READY";
@@ -230,7 +230,7 @@ export function computeOverview(repoRoot, { work = null, repoLabel = "." } = {})
230
230
  } else {
231
231
  stage = `STOPPED_${latest.status}`;
232
232
  next = plan.accepted
233
- ? `decide: retry (buildbeat-v2 start ... --attempt new) or close the work`
233
+ ? `decide: retry (buildbeat start ... --attempt new) or close the work`
234
234
  : `plan not accepted (${plan.exists ? "draft" : "missing"}); fix that before another run`;
235
235
  }
236
236
  rows.push({
@@ -20,7 +20,7 @@
20
20
 
21
21
  ```
22
22
  <项目根>/
23
- ├── AGENTS.md / ARCHITECTURE.md / 指挥台.md / contracts/ / design/ / pm/ / scripts/
23
+ ├── AGENTS.md / ARCHITECTURE.md / 指挥台.md / BUILDBEAT.md / contracts/ / delivery/ / pm/decisions.md
24
24
  ├── <代码仓1>/ # ★ <说明>;详见其 AGENTS.md
25
25
  └── <代码仓2>/ # ★ <说明>
26
26
  ```
@@ -6,17 +6,9 @@
6
6
  > 同仓内部的"前后端"接口优先用**共享类型/schema 由编译器强制**,不进本文件;本文件只管跨服务、跨语言、跨部署单元的边界。
7
7
 
8
8
  **契约快照对应版本:`<vX.Y.Z>`**(<上线日期>)。
9
- > 🔴 **线上实况唯一查询口 = `bash scripts/bus-check.sh`**;本行只标「本快照写就时对应的版本」,其它文档一律不写「当前线上 vX」(规则⑨)。
9
+ > 🔴 **线上实况唯一查询口 = `buildbeat observe status --repo .` 与部署平台实查**;本行只标「本快照写就时对应的版本」,其它文档一律不写「当前线上 vX」(AGENTS ⑨)。
10
10
 
11
- > 多仓项目逐仓填写下面的显式来源 map;单仓项目删除整个 block。`contract` 指向含唯一「契约快照对应版本」行的仓库相对 Markdown,`deployment` 填 `scripts/bus-baseline.json` 的 app key;确认无部署填 `n/a`。不要靠目录名或自然语言猜版本关系。
12
-
13
- <!-- buildbeat-multirepo-map:v1
14
- repo=<代码子仓1>|contract=contracts/PROTOCOL.md|deployment=<bus-baseline.json app 名或 n/a>
15
- -->
16
- <!-- map 行格式:repo=<子仓路径>|contract=<contracts/*.md 或 n/a>|deployment=<bus-baseline.json app 名或 n/a>[|changelog=<该仓内模块 CHANGELOG 路径>]
17
- · changelog= 给多模块仓用(根下没有 CHANGELOG,由某个模块 CHANGELOG 承载契约版本);缺省 <repo>/CHANGELOG.md。
18
- · contract=n/a 表示该仓没有契约版本域(如只读存量前端、npm 包 semver 与契约版本不同域),只登记不核对;不得拿它掩盖真实的契约关系。
19
- · 被核对的 CHANGELOG 首个已发布 H2 须以契约快照版本开头,如 `## [v1.3 · Deployed 2026-09-05 · <sha> · <流水线>]`。 -->
11
+ > 多仓项目在 §1 按边界逐一列出参与的仓与部署单元;版本对齐由 `release-readback` 车道在上线前后回读,不靠目录名或自然语言猜。
20
12
 
21
13
  ---
22
14
 
@@ -11,9 +11,6 @@
11
11
  *.key
12
12
  *.pem
13
13
 
14
- # verify-status 的「上次全绿」标记(本地实查产物,不入 git)
15
- .last-green-*
16
-
17
14
  # BuildBeat v2 运行时面与隔离工作树(可随时整删重建;不入 git)
18
15
  # 同时让 rg / 尊重 .gitignore 的工具不再走进旧工作树;vitest / jest 等要另配 exclude,见 docs/v2/guide/02-workflow-guide.md
19
16
  .buildbeat/runtime/
@@ -1,6 +1,6 @@
1
1
  # ADR 使用约定
2
2
 
3
- ADR 只承载长期、难回退的技术决定;普通产品拍板、Gate 确认、短期可逆选择继续写在 `pm/decisions.md`。
3
+ ADR 只承载长期、难回退的技术决定;普通产品拍板、门前决策卡、短期可逆选择继续写在 `pm/decisions.md`。
4
4
 
5
5
  满足任一条件时建议从 `ADR-0000-template.md` 复制一份新文件:
6
6
 
@@ -1,11 +1,10 @@
1
1
  # decisions — 拍板台账(全工作区唯一决策单点)
2
2
 
3
- > **规则(总线⑨)**:一个真实决策包收敛后,**第一动作 = 在此追加一行**(倒序),然后才去回写受影响的 SSOT(契约/设计稿/看板/todo);「回写」列登记落点,没回写完 = 欠账可见。单个独立决定也可以是一项决策包。
4
- > 验收项先分成「人必须取舍的独立变量」与「由已选变量/现有契约推导的约束」:只记录前者;后者直接回写并随候选验收。`3/14 → 11/14 → 14/14` 之类部分进度只留在当期看板「决策收件箱」,不得制造三条永久拍板。
5
- > 决策单元沿用看板收件箱的包ID;用户分轮回答或要求解释时不换号,直到整包收敛后在本表出现一次。
6
- > 重轨变更照走 `changes/` delta 提案,此处一行指向提案,不重复正文。
3
+ > **规则(AGENTS ⑨ 单点事实)**:一个真实决策包收敛后,**第一动作 = 在此追加一行**(倒序),然后才去回写受影响的 SSOT(契约/设计稿/intent/plan);「回写」列登记落点,没回写完 = 欠账可见。单个独立决定也可以是一项决策包。Run 级批准不进这里,它们由内核落在各 Work 的 `decisions.jsonl`。
4
+ > 验收项先分成「人必须取舍的独立变量」与「由已选变量/现有契约推导的约束」:只记录前者;后者直接回写并随候选验收。`3/14 → 11/14 → 14/14` 之类部分进度只留在 Work 的 intent/plan 草稿或门前决策卡里,不得制造三条永久拍板。
5
+ > 决策单元沿用决策卡的包ID;用户分轮回答或要求解释时不换号,直到整包收敛后在本表出现一次。
7
6
  > 改变核心技术栈、跨仓架构、关键数据模型或长期难回退约束时,按 pm/adr/README.md 建独立 ADR;本表仍追加一行 ADR 索引与实际回写落点。未启用 ADR 目录时不因此报错。
8
- > 「拍板人」单人项目就固定写你的名/代号;多人或要审计时(Gate3 合并与 Gate4 上线未必同一人批),谁批的由此可查。
7
+ > 「拍板人」单人项目就固定写你的名/代号;多人或要审计时(合并决定与上线关窗未必同一人批),谁批的由此可查。
9
8
 
10
9
  | 日期 | 拍板人 | 决策包 | 回写(落点 → 状态) |
11
10
  |---|---|---|---|
@@ -20,4 +20,4 @@
20
20
 
21
21
  ## 偏离规则
22
22
 
23
- MUST 偏离必须在 Gate 前形成真实决策;SHOULD 偏离需在 Review 证据中写明理由;MAY 不构成阻断项。
23
+ MUST 偏离必须在人批的转换(plan / merge / release)前形成真实决策;SHOULD 偏离需在 Review 证据中写明理由;MAY 不构成阻断项。
@@ -33,4 +33,4 @@
33
33
 
34
34
  <项目特有例外>
35
35
 
36
- Gate2 与终签以真渲染可点结果为准;静态稿或规范数值不能替代实际走查。
36
+ 设计拍板与上线前终签以真渲染可点结果为准;静态稿或规范数值不能替代实际走查。
@@ -11,10 +11,10 @@
11
11
  - `REVIEW-MUST-003`: 核对受影响自动化测试、真渲染走查和 evidence;完成声明必须可追溯到候选与证据。
12
12
  - `REVIEW-MUST-004`: 核对 Secret、鉴权、租户、输入、持久化、依赖与不可逆副作用风险。
13
13
  - `REVIEW-SHOULD-001`: 识别不必要复杂度、重复抽象、不可维护分支和缺少回滚路径的设计。
14
- - `REVIEW-SHOULD-002`: 核对看板、status、decisions 与交付候选一致,不把旧报告复用于变化后的候选。
14
+ - `REVIEW-SHOULD-002`: 核对 intent / plan、decisions 与交付候选一致,不把旧报告复用于变化后的候选。
15
15
 
16
16
  ## 项目增量
17
17
 
18
18
  <项目特有 Review 条件>
19
19
 
20
- review-ready、milestone、risk-delta 与 closure 的触发节奏仍以 `AGENTS.md` 为准,本文件不新建第二套流程。
20
+ review 何时跑、跑几轮、哪些 finding 阻断,以 run 配置(`reviewTriage`、`budgets`)与 `AGENTS.md` 为准,本文件不新建第二套流程。