@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
@@ -1,15 +0,0 @@
1
- # 状态 · 全栈
2
-
3
- > 只此域写,别人只读。倒序。hash 均为示意值(真项目必须可 `git cat-file` 核验)。
4
-
5
- ## 当前基线
6
-
7
- - 两仓:jz-web(React + Vite)/ jz-api(Node + SQLite);部署见 ARCHITECTURE.md §2
8
- - 线上版本:查 bus-check(不写在这里)
9
-
10
- ## 倒序日志
11
-
12
- - **2026-06-20** 部署 v0.2.0(两仓)+ 更新两仓 CHANGELOG · commit `jz-api a3f21c9` / `jz-web 7be04d2` · 证据:bus-check 线上实况 v0.2.0 · ✅
13
- - **2026-06-19** 修走查 P1:月报空月除零白屏 · commit `jz-web c91e77a` · 证据:`npx vitest run reports`(12/12)· ✅
14
- - **2026-06-18** 月度报表 API + 前端页,**契约先行**改 PROTOCOL v0.2.0 再动代码(规则②)· commit `jz-api 5d8b310` / `jz-web 2fa9c44` · 证据:PROTOCOL §3 v0.2.0 行 + 两端测试 · ✅
15
- - **2026-06-13** 记一笔 / 列表 两端点 + Bearer 鉴权(契约 v0.1.0) · commit `jz-api 9c07e12` / `jz-web e51ab08` · 证据:`curl` 实测 201/200 + 单测 · ✅
@@ -1,15 +0,0 @@
1
- # 状态 · 测试
2
-
3
- > 只此域写,别人只读。倒序。
4
-
5
- ## 当前基线
6
-
7
- - E2E:Playwright,12 例;证据产物:`pm/archive/一期/evidence/`(生成时即写归档位,换期零搬运;沙盘略)
8
- - 契约核查:两端独立核(不信全栈自述,规则②)
9
-
10
- ## 倒序日志
11
-
12
- - **2026-06-19** 复验 P1 修复 + 全量回归:E2E 12/12 绿;一期候选 milestone 核查一次,P0/P1=0 · 证据:`npx playwright test` 报告 + 空月空态截图 · ✅
13
- - **2026-06-19** 月报走查发现 **P1:空月份页面白屏**(分类占比除零),带图提 bug · 证据:实现⟷设计稿并排截图(规则⑧,沙盘略) · ✅
14
- - **2026-06-18** 独立核契约两端:实测空月 `curl` 返回 200 空结构、读 Report 页无 404 分支,登记 PROTOCOL §3「独立核查」列 · ✅
15
- - **2026-06-15** Gate2 前真渲染走查:提折线图改柱状、移动端两列改单列,均被拍板采纳 · 证据:decisions.md 06-15 行 · ✅
@@ -1,97 +0,0 @@
1
- # 一期(记账主流程 + 月度报表) · 协调看板
2
-
3
- > 谁做什么、什么顺序、卡点在哪。工作包完成口径 = **用户级结果 + commit hash + 可核验证据**(核查门);子任务完成不等于会话结束。
4
- > 线上实况:`bash scripts/bus-check.sh`。拍板记 `decisions.md`。
5
-
6
- ## 工作包快照(沙盘展示一期全程;live 看板只需保留在途 + 最近完成)
7
-
8
- > 简账由同一 Builder 端到端拥有一期工作包;产品/全栈/测试是该 Builder 调用的 AI 专业视角,不是三个人类岗位接力。
9
-
10
- ### WP-1 · Gate2 候选
11
-
12
- - **objective**:把一期三条需求、契约和真渲染原型收敛到可拍板状态
13
- - **AI视角**:产品 / 测试(设计稿由外部工具交付)
14
- - **in_scope**:规格、契约、设计 brief、原型走查、Gate1/Gate2 决策包
15
- - **terminal_condition**:Gate2 真渲染拍板完成并留证据;或出现范围冲突
16
- - **状态**:✅完成(06-15)
17
- - **证据**:`pm/archive/一期/evidence/gate2.md`
18
-
19
- ### WP-2 · 实现候选
20
-
21
- - **objective**:形成可测试的 jz-web + jz-api + 契约 v0.2.0 候选
22
- - **AI视角**:全栈
23
- - **in_scope**:一期三条需求实现、实现语义清单、受影响测试、两仓 CHANGELOG
24
- - **terminal_condition**:候选 hash 集和机器证据齐备;或出现冻结契约 delta
25
- - **状态**:✅完成(06-19)
26
- - **证据**:`pm/archive/一期/evidence/implementation.md`
27
-
28
- ### WP-3 · Gate3 候选
29
-
30
- - **objective**:把实现候选验证到可合并状态
31
- - **AI视角**:测试 / 产品
32
- - **in_scope**:E2E 12 例、带图走查、review-ready 自检、一次 milestone reviewer、P0/P1 合并 closure
33
- - **terminal_condition**:两仓 `HEAD=candidate`、工作树干净、L3/渲染证据绿且无待修后一次核查,P0/P1 清零再提交 Gate3;或发现需修改冻结语义的真实阻塞
34
- - **状态**:✅完成(06-19)
35
- - **证据**:`pm/archive/一期/evidence/gate3.md`
36
-
37
- ### WP-4 · 上线收尾
38
-
39
- - **objective**:完成受控上线、L4 复核与一期记账
40
- - **AI视角**:全栈 / 测试 / 产品
41
- - **in_scope**:Gate3 合并、部署、Gate4 上线、线上复核、挂账移交
42
- - **terminal_condition**:Gate4 后 L4 证据与状态/看板收尾完成;或部署前真实阻塞
43
- - **状态**:✅完成(06-20)
44
- - **证据**:`pm/archive/一期/evidence/gate4.md`
45
-
46
- > 每个工作包都覆盖多个需求 ID、文档、commit 或 reviewer 事件;子项完成只报中间进展,到用户级结果/Gate 候选才交接,没有让人反复说“继续”。
47
-
48
- ## 决策收件箱
49
-
50
- | 包ID | 变量ID | 真实取舍 | 推荐值 + 理由 | 另一选择的后果 | 截止 Gate | 状态 |
51
- |---|---|---|---|---|---|---|
52
- | WP1-G1 | D1 | 月度统计周期 | 自然月,与账期一致 | 滚动 30 天更实时但难对账 | Gate1 | ✅ 06-12 已收敛 |
53
- | WP1-G2 | D1 | 报表图形 | 柱状图,真渲染下类别对比更清楚 | 折线更适合连续趋势,不适合本页类别比较 | Gate2 | ✅ 06-15 同包收敛 |
54
- | WP1-G2 | D2 | 移动端布局 | 单列,小屏信息不拥挤 | 双列信息密度高但卡片过窄 | Gate2 | ✅ 06-15 同包收敛 |
55
-
56
- > 验收清单的其余条目均由上述选择与契约推导,没有逐项要求人批。收敛结论只在 `decisions.md` 各记一次。
57
-
58
- ## 阶段门
59
-
60
- - Gate1: passed | 决策: `pm/decisions.md:19` | 证据: `pm/archive/一期/evidence/gate1.md`
61
- - Gate2: passed | 决策: `pm/decisions.md:16` | 证据: `pm/archive/一期/evidence/gate2.md`
62
- - Gate3: passed | 决策: `pm/decisions.md:15` | 证据: `pm/archive/一期/evidence/gate3.md`
63
- - Gate4: passed | 决策: `pm/decisions.md:14` | 证据: `pm/archive/一期/evidence/gate4.md`
64
-
65
- > 四行机器令牌是 Gate 状态权威;下表是给人的阶段摘要。
66
-
67
- | 阶段 | 产出 | 负责 | 状态 |
68
- |---|---|---|---|
69
- | ① 需求(⛔Gate1 人批) | 一期规格:记一笔 / 列表 / 月度报表;多币种不做 | 产品 | ✅ 06-11 |
70
- | ② 设计(⛔Gate2 人对**真渲染原型**批) | `design/design_1期/`(沙盘略) | 外部设计工具 | ✅ 06-15(改 2 处:柱状图、移动端单列) |
71
- | ③ 实现 | jz-web + jz-api + 契约 v0.2.0 + 实现语义清单;机器闸每提交 | 全栈 | ✅ 06-19 |
72
- | ④ 验证 + 核查门 | review-ready 候选 hash 集 + E2E 12 例 + 带图走查 + milestone 一次 + P1 合并 closure 一次 | 测试 + 产品 | ✅ 06-19(P1 空月除零已修复复验) |
73
- | ⑤ 上线(⛔Gate3 合并 / ⛔Gate4 上线,人批) | 部署 + 两仓 CHANGELOG | 全栈 | ✅ 06-20 |
74
-
75
- ## 能力视角表(需求 → 调用哪些 AI 视角)
76
-
77
- | 需求 | 全栈 | 测试 | 备注 |
78
- |---|---|---|---|
79
- | 记一笔 + 列表 | ★ | E2E+走查 | 契约 v0.1.0 |
80
- | 月度报表 | ★ | E2E+走查 | 按自然月(拍板 06-12) |
81
-
82
- ## 🔴 关键对齐点(开工先定,别各做各的)
83
-
84
- 1. 金额单位 = 分(整数)→ `contracts/PROTOCOL.md` §2
85
- 2. 空月份返回空结构不 404 → PROTOCOL §1
86
-
87
- ## 挂账(实时;工作包认领后各 AI 视角写回自己的 status,此处只改 ☐/✅)
88
-
89
- | # | 项 | 域 | 状态 | 依据/出处 |
90
- |---|---|---|---|---|
91
- | 1 | 报表导出 CSV → 移交二期 | 产品 | ✅(已入二期候选) | 拍板 06-20 |
92
- | 2 | SQLite 每日备份 | 全栈 | ☐ | ARCHITECTURE.md §2 |
93
- | 3 | 上线 7 天数据回看 | 产品 | ☐ | BuildBeat lessons 第 8 条 |
94
-
95
- ## 验收口径
96
-
97
- 对照一期规格三条 + 设计稿逐屏;两仓 `HEAD=candidate` 且干净;E2E 全绿;无已知待修后 milestone reviewer 无 P0/P1;真渲染走查含移动端与空月空态。同一候选 hash 在 Gate3 前复用该结论;reviewer 返回前 hash 变化则旧审查 `SUPERSEDED`,重新 review-ready 后再核。
@@ -1,18 +0,0 @@
1
- # CODE.md — 简账代码与安全规范
2
-
3
- > **Optional**: 本文件由项目拥有;缺失时 BuildBeat 直接跳过,不作为告警或错误。
4
- > **AI write boundary**: 默认只读;普通需求、修构建或装依赖不得顺手改规范。
5
- > **Status**: Confirmed
6
-
7
- ## Rules
8
-
9
- - `CODE-MUST-001`: Secret、真实账本、身份数据和生产配置值不得进入 Git、日志、测试夹具或证据。
10
- - `CODE-MUST-002`: API 输入先做 schema 校验;账目写入失败必须回滚,不返回伪成功。
11
- - `CODE-MUST-003`: 前后端共享字段变化先同步 `contracts/PROTOCOL.md`,再分别实现。
12
- - `CODE-MUST-004`: 新依赖必须提交 lockfile,并通过许可证与安全检查。
13
- - `CODE-SHOULD-001`: 金额在 API 与存储层使用整数分,界面边界才格式化为元。
14
- - `CODE-MAY-001`: 局部重构可随工作包完成,但不得趁机更换框架或持久化方案。
15
-
16
- ## 项目禁止事项
17
-
18
- 不得用浮点数持久化金额;不得在前端日志输出完整账目内容。
@@ -1,34 +0,0 @@
1
- # DESIGN.md — 简账 UI / 视觉 / 交互规范
2
-
3
- > **Optional**: 仅有 UI、视觉或交互交付的项目按需创建;缺失时 BuildBeat 直接跳过,不作为告警或错误。
4
- > **AI write boundary**: 默认只读;普通页面实现不得顺手重写设计系统。
5
- > **Status**: Confirmed
6
-
7
- ## Principles
8
-
9
- 清晰、克制、账目优先;不用装饰性渐变掩盖信息层级。
10
-
11
- ## Tokens
12
-
13
- 字体、颜色、间距与圆角统一来自 jz-web 的 CSS variables;页面不得自建平行 token。
14
-
15
- ## Components
16
-
17
- 金额输入、账目行、月份选择器和空态组件优先复用;差异通过 props 或明确变体表达。
18
-
19
- ## Interaction Patterns
20
-
21
- - `DESIGN-MUST-001`: 新增账目在 100ms 内给出按压或 loading 反馈,保存结果提供可访问确认。
22
- - `DESIGN-MUST-002`: 上线界面不得出现调试信息、实现说明、mock 标记或开发者元注释。
23
-
24
- ## States
25
-
26
- - `DESIGN-MUST-003`: 账目列表和报表必须覆盖 loading、empty、error、disabled 与移动端状态。
27
-
28
- ## Accessibility
29
-
30
- - `DESIGN-MUST-004`: 表单标签、键盘路径、焦点、图表替代文本和颜色对比进入真渲染走查。
31
-
32
- ## Project-specific exceptions
33
-
34
- 月度报表移动端使用单列柱状图;不复用桌面双列布局。
@@ -1,16 +0,0 @@
1
- # REVIEW.md — 简账 Review 规范
2
-
3
- > **Optional**: 本文件由项目拥有;缺失时 BuildBeat 直接跳过,不作为告警或错误。
4
- > **AI write boundary**: 默认只读;只在项目 Review 口径被明确改变时修改。
5
- > **Status**: Confirmed
6
-
7
- ## Rules
8
-
9
- - `REVIEW-MUST-001`: 逐项核对一期范围、非目标、错误态与金额口径,不以测试绿替代需求覆盖。
10
- - `REVIEW-MUST-002`: 核对跨仓契约、SQLite migration 与向后兼容边界。
11
- - `REVIEW-MUST-003`: 核对单元、API 集成、主流程 E2E、真渲染和 evidence 路径。
12
- - `REVIEW-MUST-004`: 核对输入校验、日志脱敏、账目持久化和回滚风险。
13
- - `REVIEW-SHOULD-001`: 拒绝无需求支撑的抽象、依赖或复杂状态层。
14
- - `REVIEW-SHOULD-002`: 确认看板、status、decisions 与同一候选一致。
15
-
16
- review-ready 与 reviewer 调用节奏仍以 `AGENTS.md` 为准。
@@ -1,31 +0,0 @@
1
- # STACK.md — 简账技术栈约束
2
-
3
- > **Optional**: 本文件由项目拥有;缺失时 BuildBeat 直接跳过,不作为告警或错误。
4
- > **AI write boundary**: 默认只读;用户确认或明确要求技术栈变化后才可修改。
5
- > **Status**: Confirmed
6
-
7
- ## 声明
8
-
9
- | 维度 | 项目约束 | 事实来源 |
10
- |---|---|---|
11
- | Runtime | Node.js 22 LTS | 两仓 package engines 与 CI |
12
- | 包管理器 | npm;各仓提交 package-lock.json | 两仓 lockfile |
13
- | 语言与框架 | React + TypeScript;Node.js API | 两仓 package.json 与源码入口 |
14
- | 数据设施 | SQLite 单机账本 | API schema 与 migration |
15
- | 部署 | Web/API 两个独立 PaaS 服务 | 部署配置与 `ARCHITECTURE.md` |
16
- | CI 与测试 | 单元、API 集成、主流程 E2E | 两仓 CI 与 verify-status suites |
17
- | 供应链 | MIT;依赖必须锁定并过安全检查 | LICENSE、lockfile、CI |
18
-
19
- ## 可核对基线(bus-check v1)
20
-
21
- <!-- buildbeat-stack-baseline:v1
22
- nodeConstraint=22
23
- lockfileKind=package-lock.json
24
- dockerFromImage=n/a
25
- -->
26
-
27
- ## Rules
28
-
29
- - `STACK-MUST-001`: 声明与 package、lockfile、容器或部署配置冲突时只报告漂移,不自动选择一方。
30
- - `STACK-MUST-002`: 更换 Node、React、SQLite、包管理器或部署平台前必须建立 ADR,并同步 `contracts/PROTOCOL.md`。
31
- - `STACK-SHOULD-001`: 版本结论必须能回到仓库配置或运行平台证据。
package/src/cli.js DELETED
@@ -1,323 +0,0 @@
1
- import { createInterface } from "node:readline/promises";
2
-
3
- import { CLI_VERSION, OUTPUT_SCHEMA_VERSION } from "./constants.js";
4
- import { formatDoctor, runDoctor } from "./doctor.js";
5
- import { buildPlan, formatPlan } from "./planner.js";
6
- import { applyUpgrade, buildUpgradePlan, formatUpgradePlan } from "./upgrader.js";
7
- import { applyScaffold, WriteError } from "./writer.js";
8
-
9
- const HELP = `BuildBeat CLI v${CLI_VERSION} (Wave 2 source candidate)
10
-
11
- Usage:
12
- buildbeat doctor [path] [--json]
13
- buildbeat init [path] [--dry-run] [--layout default|compact] [--json] [--yes]
14
- buildbeat adopt [path] [--dry-run] [--layout default|compact] [--json] [--yes]
15
- buildbeat upgrade [path] [--dry-run] [--json] [--force] [--major]
16
- buildbeat version
17
-
18
- init/adopt write only after a blocker-free plan and confirmation; --yes skips
19
- only that prompt. upgrade is schema-2-only and requires a clean target-root Git
20
- worktree. diff and uninstall remain unavailable.
21
-
22
- Legacy compatibility: the published npm package and solobaton executable alias
23
- remain available during the BuildBeat namespace migration.`;
24
-
25
- class UsageError extends Error {
26
- constructor(message) {
27
- super(message);
28
- this.name = "UsageError";
29
- }
30
- }
31
-
32
- function parse(args) {
33
- if (args.length === 0) {
34
- return {
35
- command: "help",
36
- target: ".",
37
- json: false,
38
- dryRun: false,
39
- layout: null,
40
- yes: false,
41
- force: false,
42
- major: false,
43
- targetProvided: false,
44
- };
45
- }
46
- const options = {
47
- command: args[0],
48
- target: ".",
49
- json: false,
50
- dryRun: false,
51
- yes: false,
52
- force: false,
53
- major: false,
54
- layout: null,
55
- help: false,
56
- targetProvided: false,
57
- };
58
- let targetSeen = false;
59
- for (let index = 1; index < args.length; index += 1) {
60
- const token = args[index];
61
- if (token === "--json") {
62
- options.json = true;
63
- } else if (token === "--dry-run") {
64
- options.dryRun = true;
65
- } else if (token === "--yes") {
66
- options.yes = true;
67
- } else if (token === "--force") {
68
- options.force = true;
69
- } else if (token === "--major") {
70
- options.major = true;
71
- } else if (token === "--help" || token === "-h") {
72
- options.help = true;
73
- } else if (token === "--layout") {
74
- index += 1;
75
- if (index >= args.length) {
76
- throw new UsageError("--layout requires default or compact.");
77
- }
78
- options.layout = args[index];
79
- } else if (token.startsWith("--layout=")) {
80
- options.layout = token.slice("--layout=".length);
81
- } else if (token.startsWith("-")) {
82
- throw new UsageError(`Unknown option: ${token}`);
83
- } else if (!targetSeen) {
84
- options.target = token;
85
- targetSeen = true;
86
- options.targetProvided = true;
87
- } else {
88
- throw new UsageError(`Unexpected argument: ${token}`);
89
- }
90
- }
91
- if (options.layout && !["default", "compact"].includes(options.layout)) {
92
- throw new UsageError("--layout must be default or compact.");
93
- }
94
- return options;
95
- }
96
-
97
- async function confirmPlan(io, plan) {
98
- if (typeof io.confirm === "function") {
99
- return Boolean(await io.confirm(plan));
100
- }
101
- if (!io.stdin?.isTTY || !io.stderr) {
102
- return null;
103
- }
104
- const readline = createInterface({ input: io.stdin, output: io.stderr });
105
- try {
106
- const answer = await readline.question("Apply this BuildBeat plan? [y/N] ");
107
- return /^(?:y|yes)$/i.test(answer.trim());
108
- } finally {
109
- readline.close();
110
- }
111
- }
112
-
113
- function outputJson(io, value) {
114
- io.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
115
- }
116
-
117
- function commandLabel(value) {
118
- if (typeof value !== "string" || value.length === 0) {
119
- return null;
120
- }
121
- return value
122
- .replace(/[\u0000-\u001f\u007f]+/g, " ")
123
- .replace(/\s+/g, " ")
124
- .trim()
125
- .slice(0, 80) || null;
126
- }
127
-
128
- function writeError(io, message, { json = false, code = "usage", command = null } = {}) {
129
- if (json) {
130
- outputJson(io, {
131
- schemaVersion: OUTPUT_SCHEMA_VERSION,
132
- command: commandLabel(command),
133
- cliVersion: CLI_VERSION,
134
- ok: false,
135
- error: { code, message },
136
- });
137
- } else {
138
- io.stderr.write(`Error: ${message}\n`);
139
- }
140
- }
141
-
142
- export async function run(
143
- args,
144
- io = { stdin: process.stdin, stdout: process.stdout, stderr: process.stderr },
145
- ) {
146
- let options;
147
- try {
148
- options = parse(args);
149
- } catch (error) {
150
- writeError(io, error.message, {
151
- json: args.includes("--json"),
152
- command: args[0] || null,
153
- });
154
- return 2;
155
- }
156
-
157
- if (options.help || options.command === "help" || options.command === "--help") {
158
- io.stdout.write(`${HELP}\n`);
159
- return 0;
160
- }
161
- if (options.command === "version" || options.command === "--version" || options.command === "-v") {
162
- if (
163
- options.targetProvided ||
164
- options.json ||
165
- options.dryRun ||
166
- options.yes ||
167
- options.force ||
168
- options.major ||
169
- options.layout
170
- ) {
171
- writeError(io, "version does not accept a path or options.", {
172
- json: options.json,
173
- command: options.command,
174
- });
175
- return 2;
176
- }
177
- io.stdout.write(`${CLI_VERSION}\n`);
178
- return 0;
179
- }
180
-
181
- try {
182
- if (options.command === "doctor") {
183
- if (options.layout || options.dryRun || options.yes || options.force || options.major) {
184
- throw new UsageError("doctor accepts only [path] and --json.");
185
- }
186
- const report = runDoctor(options.target);
187
- if (options.json) {
188
- outputJson(io, report);
189
- } else {
190
- io.stdout.write(`${formatDoctor(report)}\n`);
191
- }
192
- return report.ok ? 0 : 1;
193
- }
194
-
195
- if (options.command === "init" || options.command === "adopt") {
196
- if (options.force || options.major) {
197
- throw new UsageError(`${options.command} does not accept --force or --major.`);
198
- }
199
- if (options.dryRun && options.yes) {
200
- throw new UsageError("--yes cannot be combined with --dry-run.");
201
- }
202
- const layout = options.layout || (options.command === "adopt" ? "compact" : "default");
203
- const now = new Date();
204
- const plan = buildPlan({
205
- mode: options.command,
206
- target: options.target,
207
- layout,
208
- preview: options.dryRun,
209
- now,
210
- });
211
-
212
- if (options.dryRun || !plan.ready) {
213
- if (options.json) {
214
- outputJson(io, plan);
215
- } else {
216
- io.stdout.write(`${formatPlan(plan)}\n`);
217
- }
218
- return plan.ready ? 0 : 1;
219
- }
220
-
221
- if (!options.json) {
222
- io.stdout.write(`${formatPlan(plan)}\n`);
223
- } else if (options.yes || io.stdin?.isTTY) {
224
- io.stderr.write(`${formatPlan(plan)}\n`);
225
- }
226
-
227
- if (!options.yes) {
228
- const confirmed = await confirmPlan(io, plan);
229
- if (confirmed === null) {
230
- writeError(
231
- io,
232
- "Interactive confirmation is unavailable. Re-run with --yes only after reviewing --dry-run output.",
233
- {
234
- json: options.json,
235
- code: "confirmation_required",
236
- command: options.command,
237
- },
238
- );
239
- return 2;
240
- }
241
- if (!confirmed) {
242
- if (options.json) {
243
- outputJson(io, { ...plan, cancelled: true });
244
- } else {
245
- io.stdout.write("\nCancelled. No files changed.\n");
246
- }
247
- return 0;
248
- }
249
- }
250
-
251
- const result = applyScaffold(plan, { now });
252
- if (options.json) {
253
- outputJson(io, result);
254
- } else {
255
- io.stdout.write(`\n${formatPlan(result)}\n`);
256
- }
257
- return 0;
258
- }
259
-
260
- if (options.command === "upgrade") {
261
- if (options.layout || options.yes) {
262
- throw new UsageError("upgrade accepts only [path], --dry-run, --json, --force, and --major.");
263
- }
264
- const now = new Date();
265
- const plan = buildUpgradePlan({
266
- target: options.target,
267
- preview: options.dryRun,
268
- force: options.force,
269
- major: options.major,
270
- now,
271
- });
272
- if (options.dryRun || !plan.ready || plan.upToDate) {
273
- if (options.json) {
274
- outputJson(io, plan);
275
- } else {
276
- io.stdout.write(`${formatUpgradePlan(plan)}\n`);
277
- }
278
- return plan.ready ? 0 : 1;
279
- }
280
-
281
- if (options.json) {
282
- io.stderr.write(`${formatUpgradePlan(plan)}\n`);
283
- } else {
284
- io.stdout.write(`${formatUpgradePlan(plan)}\n`);
285
- }
286
- const result = applyUpgrade(plan, { now });
287
- if (options.json) {
288
- outputJson(io, result);
289
- } else {
290
- io.stdout.write(`\n${formatUpgradePlan(result)}\n`);
291
- }
292
- return result.doctor?.ok === false ? 1 : 0;
293
- }
294
-
295
- if (["uninstall", "diff"].includes(options.command)) {
296
- writeError(
297
- io,
298
- `${options.command} is reserved by the lifecycle contract but is not enabled in this CLI build.`,
299
- {
300
- json: options.json,
301
- code: "command_not_available",
302
- command: options.command,
303
- },
304
- );
305
- return 2;
306
- }
307
-
308
- throw new UsageError(`Unknown command: ${options.command}`);
309
- } catch (error) {
310
- if (error instanceof UsageError) {
311
- writeError(io, error.message, { json: options.json, command: options.command });
312
- return 2;
313
- }
314
- writeError(io, error.message, {
315
- json: options.json,
316
- code: error instanceof WriteError ? error.code : "runtime_error",
317
- command: options.command,
318
- });
319
- return 1;
320
- }
321
- }
322
-
323
- export { HELP };