@haiyangbg/buildbeat 3.3.1 → 4.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 (100) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.en.md +20 -124
  3. package/README.md +20 -124
  4. package/SKILL.md +73 -84
  5. package/docs/CAPABILITY-MATRIX.md +30 -39
  6. package/docs/MIGRATION.md +93 -0
  7. package/docs/README.md +8 -37
  8. package/docs/RELEASING.md +4 -3
  9. package/docs/v2/RFC-0001-product-definition.md +3 -0
  10. package/docs/v2/RFC-0002-domain-model.md +3 -0
  11. package/docs/v2/RFC-0003-workflow-policy.md +3 -0
  12. package/docs/v2/SPEC-0001-events-v1.md +3 -0
  13. package/docs/v2/guide/00-how-to-talk.en.md +12 -58
  14. package/docs/v2/guide/00-how-to-talk.md +12 -58
  15. package/docs/v2/guide/01-quickstart.en.md +41 -112
  16. package/docs/v2/guide/01-quickstart.md +41 -112
  17. package/docs/v2/guide/02-workflow-guide.en.md +13 -123
  18. package/docs/v2/guide/02-workflow-guide.md +14 -122
  19. package/docs/v2/guide/03-policy-guide.en.md +6 -50
  20. package/docs/v2/guide/03-policy-guide.md +6 -50
  21. package/docs/v2/guide/04-adapter-guide.en.md +7 -60
  22. package/docs/v2/guide/04-adapter-guide.md +7 -60
  23. package/docs/v2/guide/05-worker-contract.en.md +6 -53
  24. package/docs/v2/guide/05-worker-contract.md +7 -52
  25. package/docs/v2/guide/06-evidence-guide.en.md +8 -44
  26. package/docs/v2/guide/06-evidence-guide.md +9 -43
  27. package/docs/v2/guide/07-approval-guide.en.md +8 -110
  28. package/docs/v2/guide/07-approval-guide.md +8 -121
  29. package/docs/v2/guide/09-security-boundaries.en.md +13 -37
  30. package/docs/v2/guide/09-security-boundaries.md +13 -37
  31. package/docs/v2/guide/10-recovery.en.md +12 -96
  32. package/docs/v2/guide/10-recovery.md +12 -96
  33. package/docs/v2/guide/11-session-handoff.en.md +7 -78
  34. package/docs/v2/guide/11-session-handoff.md +7 -78
  35. package/docs/v2/guide/12-without-runtime.en.md +46 -0
  36. package/docs/v2/guide/12-without-runtime.md +46 -0
  37. package/docs/v2/guide/README.en.md +16 -36
  38. package/docs/v2/guide/README.md +16 -36
  39. package/example/AGENTS.md +15 -69
  40. package/example/README.md +8 -23
  41. package/example/delivery/envelope/prompts/builder.md +3 -1
  42. package/example/delivery/envelope/prompts/fixer.md +3 -1
  43. package/example/delivery/envelope/prompts/reviewer.md +3 -1
  44. package/example//346/214/207/346/214/245/345/217/260.md +8 -40
  45. package/package.json +2 -1
  46. package/src/v2/adapters/notification-payload.js +40 -0
  47. package/src/v2/adapters/shell.js +3 -1
  48. package/src/v2/cli/run-config-check.js +129 -45
  49. package/src/v2/cli/run.js +821 -252
  50. package/src/v2/engine/reducer.js +2 -0
  51. package/src/v2/engine/risk-preset.js +14 -47
  52. package/src/v2/engine/workflow.js +177 -185
  53. package/src/v2/evidence/collector.js +12 -1
  54. package/src/v2/evidence/image.js +346 -0
  55. package/src/v2/policy/policy.js +219 -286
  56. package/src/v2/presets/software-delivery.yaml +1 -16
  57. package/src/v2/runtime/cache.js +22 -1
  58. package/src/v2/runtime/decisions.js +185 -31
  59. package/src/v2/runtime/findings.js +4 -1
  60. package/src/v2/runtime/notify.js +42 -36
  61. package/src/v2/runtime/orchestrator.js +811 -297
  62. package/src/v2/runtime/overview.js +163 -34
  63. package/src/v2/runtime/release.js +233 -0
  64. package/src/v2/runtime/run-record.js +1 -0
  65. package/src/v2/workspace/workspace-manager.js +8 -0
  66. package/templates/v2/AGENTS.md +15 -69
  67. package/templates/v2/BUILDBEAT.md +2 -13
  68. package/templates/v2/envelope/prompts/builder.md +3 -1
  69. package/templates/v2/envelope/prompts/fixer.md +3 -1
  70. package/templates/v2/envelope/prompts/reviewer.md +3 -1
  71. package/templates/v2/run-config.example.yaml +7 -5
  72. package/templates/v2/work.example.md +16 -0
  73. package/templates/v2//346/214/207/346/214/245/345/217/260.md +8 -40
  74. package/docs/v2/skill/01-principles.md +0 -26
  75. package/docs/v2/skill/02-project-layout.md +0 -31
  76. package/docs/v2/skill/03-collaboration-rules.md +0 -45
  77. package/docs/v2/skill/04-rhythm-and-rituals.md +0 -104
  78. package/docs/v2/skill/05-red-lines.md +0 -13
  79. package/docs/v2/skill/06-bootstrap-and-takeover.md +0 -84
  80. package/docs/v2/skill/07-templates-and-lessons.md +0 -24
  81. package/example/.buildbeat/observe.yaml +0 -31
  82. package/src/v2/observe/observe-config.js +0 -194
  83. package/src/v2/observe/observe-reducer.js +0 -117
  84. package/src/v2/observe/observe.js +0 -427
  85. package/src/v2/presets/observe.yaml +0 -45
  86. package/src/v2/presets/policies/ui-render-gate.yaml +0 -13
  87. package/src/v2/presets/release-readback.yaml +0 -36
  88. package/src/v2/presets/risk/controlled.yaml +0 -39
  89. package/src/v2/presets/risk/fast.yaml +0 -19
  90. package/src/v2/presets/risk/release.yaml +0 -21
  91. package/src/v2/presets/risk/standard.yaml +0 -28
  92. package/templates/ARCHITECTURE.md +0 -50
  93. package/templates/contracts/PROTOCOL.md +0 -28
  94. package/templates/pm/adr/ADR-0000-template.md +0 -25
  95. package/templates/pm/adr/README.md +0 -15
  96. package/templates/pm/decisions.md +0 -11
  97. package/templates/standards/CODE.md +0 -23
  98. package/templates/standards/DESIGN.md +0 -36
  99. package/templates/standards/REVIEW.md +0 -20
  100. package/templates/standards/STACK.md +0 -31
@@ -1,24 +0,0 @@
1
- # BuildBeat 方法论 · 模板索引与实战教训(原 §9–§10)
2
-
3
- > 从 [`SKILL.md`](../../../SKILL.md) 移出的正文,原文与原节号不变,只调整了相对链接;入口、驾驶手册与红线摘要仍在 `SKILL.md`。
4
-
5
- ## 9. 模板索引(templates/,直接拷贝后改占位符)
6
-
7
- | 模板 | 用途 |
8
- |---|---|
9
- | [templates/v2/AGENTS.md](../../../templates/v2/AGENTS.md) / [templates/v2/指挥台.md](../../../templates/v2/指挥台.md) | **项目装载入口**:一页流程 + 视角路由 + 十一条规则(含可见命名进决策卡)+ 红线;指挥台是"用户一句话 → 会话调什么"的操作卡 |
10
- | [templates/v2/CLAUDE.md](../../../templates/v2/CLAUDE.md) / [templates/v2/BUILDBEAT.md](../../../templates/v2/BUILDBEAT.md) | 一行指针与版本标记(运行时版本、装载方式、升级 = 升级 CLI、回灌通道) |
11
- | [templates/v2/run-config.example.yaml](../../../templates/v2/run-config.example.yaml) | 可原样解析的 run 配置样板(含 fixer、reviewTriage、budgets、cache、envelope、redact);机器验证见 `tests/v2-templates-firstrun.test.js` |
12
- | [templates/v2/envelope/worker.sh](../../../templates/v2/envelope/worker.sh) + [prompts/](../../../templates/v2/envelope/prompts/builder.md) | worker 包装(工具缺失 exit 75、喂 prompt、写入步机械 commit、只读步落信封)与 builder / reviewer / fixer 三份 prompt;拷到仓级 `delivery/envelope/` |
13
- | [templates/pm/decisions.md](../../../templates/pm/decisions.md) | 平台级决策包台账(全工作区唯一决策单点;Run 级批准由内核落 `decisions.jsonl`) |
14
- | [templates/pm/adr/README.md](../../../templates/pm/adr/README.md) / [ADR 模板](../../../templates/pm/adr/ADR-0000-template.md) | 可选 ADR 判据、四态 Status 与替代链;默认不生成 |
15
- | [templates/contracts/PROTOCOL.md](../../../templates/contracts/PROTOCOL.md) | 跨边界契约唯一入口骨架(多仓项目) |
16
- | [templates/ARCHITECTURE.md](../../../templates/ARCHITECTURE.md) | 全栈总图骨架(架构/基础设施/凭据位置/子项目索引;多仓项目) |
17
- | [templates/standards/STACK.md](../../../templates/standards/STACK.md) / [CODE](../../../templates/standards/CODE.md) / [REVIEW](../../../templates/standards/REVIEW.md) / [DESIGN](../../../templates/standards/DESIGN.md) | 可选 project-owned 规范(Policy 输入工件);缺失跳过,Draft 显式待确认,DESIGN 仅 UI 项目 |
18
- | [templates/gitignore.template](../../../templates/gitignore.template) | 工作区 .gitignore 模板(排除子仓、*.env、`.buildbeat/runtime/`、`.buildbeat/worktrees/`;拷入后改名) |
19
-
20
- > 运行时命令面(`accept / start / resume / status / inbox / overview / approve / reject / findings / doctor / preflight / gc / metrics / observe / watch`)见 [docs/v2/guide/README.md](../guide/README.md);Skill-only 手工路径 / 运行时 / Claude 插件各自的可用面见 [docs/CAPABILITY-MATRIX.md](../../CAPABILITY-MATRIX.md)。
21
-
22
- ## 10. 反模式与实战教训
23
-
24
- 血泪清单(每条都真实发生过)见 [lessons.md](../../../lessons.md)——SSOT 腐烂、读过期 race、静态稿拍板返工螺旋、视角过细收敛史、"当前版本"声明漂移、走查漏独立弹窗、构建产物混入他人 WIP、平台侧配置漂移、UI 元注释复发、核查门吞掉交付、追踪项当任务边界、等待找不到人、预算是刹车不是墙、基础设施故障当候选失败、台账说的和人看到的不是一回事等。**搭完骨架后建议通读一遍,大部分零件就是为这些坑而生。**
@@ -1,31 +0,0 @@
1
- # observe 配置样例:一次 `buildbeat observe run --config .buildbeat/observe.yaml` = 一轮只读体检。
2
- # 简账没有线上环境,这里只挂一个"测试还绿吗"的探针,示范 provider 的形状;真实项目换成部署平台的只读查询。
3
- kind: workflow
4
- version: 1
5
- name: observe
6
- providers:
7
- - id: tests-green
8
- command: bash
9
- args:
10
- - -lc
11
- - npm test
12
- schedule: manual
13
- evidence:
14
- kind: health
15
- subject: jianzhang-tests
16
- severity:
17
- failed: error
18
- unverified: warn
19
- bands:
20
- - level: log
21
- when: "severity >= info"
22
- - level: diagnose
23
- when: "severity >= error"
24
- - level: intent
25
- when: "severity >= error"
26
- triage:
27
- actions:
28
- - fix_now
29
- - schedule
30
- - dismiss
31
- dismissFeedback: bands
@@ -1,194 +0,0 @@
1
- // observe.yaml loader per docs/v2/RFC-0003-workflow-policy.md §8 (frozen
2
- // shape, block form — the strict YAML subset rejects inline {}/[]). Fail
3
- // closed: any shape violation is an error, never a default.
4
-
5
- import { createHash } from "node:crypto";
6
- import { readFileSync } from "node:fs";
7
- import { basename, dirname, resolve } from "node:path";
8
-
9
- import {
10
- OBSERVE_BAND_LEVELS,
11
- OBSERVE_SEVERITIES,
12
- TRIAGE_ACTIONS,
13
- } from "../domain/model.js";
14
- import { parseYamlSubset } from "../engine/yaml-subset.js";
15
-
16
- export class ObserveConfigError extends Error {
17
- constructor(message) {
18
- super(message);
19
- this.name = "ObserveConfigError";
20
- }
21
- }
22
-
23
- const WHEN_PATTERN = /^severity\s*>=\s*(info|warn|error|critical)$/;
24
-
25
- export function severityRank(severity) {
26
- const rank = OBSERVE_SEVERITIES.indexOf(severity);
27
- if (rank === -1) {
28
- throw new ObserveConfigError(`unknown severity: ${severity}`);
29
- }
30
- return rank;
31
- }
32
-
33
- function requireString(value, label) {
34
- if (typeof value !== "string" || value.length === 0) {
35
- throw new ObserveConfigError(`${label} must be a non-empty string`);
36
- }
37
- return value;
38
- }
39
-
40
- function parseSeverityMap(raw, providerId) {
41
- const map = { failed: "error", unverified: "warn" };
42
- if (raw === undefined) {
43
- return map;
44
- }
45
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
46
- throw new ObserveConfigError(`provider ${providerId}: severity must be a map`);
47
- }
48
- for (const [key, value] of Object.entries(raw)) {
49
- if (key !== "failed" && key !== "unverified") {
50
- throw new ObserveConfigError(`provider ${providerId}: severity.${key} is not a known key`);
51
- }
52
- if (!OBSERVE_SEVERITIES.includes(value)) {
53
- throw new ObserveConfigError(
54
- `provider ${providerId}: severity.${key} must be one of ${OBSERVE_SEVERITIES.join("|")}`,
55
- );
56
- }
57
- map[key] = value;
58
- }
59
- return map;
60
- }
61
-
62
- function parseProvider(raw, index) {
63
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
64
- throw new ObserveConfigError(`providers[${index}] must be a map`);
65
- }
66
- const id = requireString(raw.id, `providers[${index}].id`);
67
- const command = requireString(raw.command, `provider ${id}: command`);
68
- const args = raw.args ?? [];
69
- if (!Array.isArray(args) || args.some((arg) => typeof arg !== "string" && typeof arg !== "number")) {
70
- throw new ObserveConfigError(`provider ${id}: args must be a list of scalars`);
71
- }
72
- if (raw.evidence === null || typeof raw.evidence !== "object" || Array.isArray(raw.evidence)) {
73
- throw new ObserveConfigError(`provider ${id}: evidence must be a map with kind and subject`);
74
- }
75
- const evidence = {
76
- kind: requireString(raw.evidence.kind, `provider ${id}: evidence.kind`),
77
- subject: requireString(raw.evidence.subject, `provider ${id}: evidence.subject`),
78
- };
79
- let diagnose = null;
80
- if (raw.diagnose !== undefined) {
81
- if (raw.diagnose === null || typeof raw.diagnose !== "object" || Array.isArray(raw.diagnose)) {
82
- throw new ObserveConfigError(`provider ${id}: diagnose must be a map with command`);
83
- }
84
- const diagnoseArgs = raw.diagnose.args ?? [];
85
- if (!Array.isArray(diagnoseArgs)) {
86
- throw new ObserveConfigError(`provider ${id}: diagnose.args must be a list`);
87
- }
88
- diagnose = {
89
- command: requireString(raw.diagnose.command, `provider ${id}: diagnose.command`),
90
- args: diagnoseArgs.map((arg) => String(arg)),
91
- timeoutMs: raw.diagnose.timeoutMs,
92
- };
93
- }
94
- return {
95
- id,
96
- command,
97
- args: args.map((arg) => String(arg)),
98
- schedule: raw.schedule === undefined ? "manual" : String(raw.schedule),
99
- evidence,
100
- severity: parseSeverityMap(raw.severity, id),
101
- diagnose,
102
- timeoutMs: raw.timeoutMs,
103
- };
104
- }
105
-
106
- function parseBands(raw) {
107
- if (!Array.isArray(raw) || raw.length !== OBSERVE_BAND_LEVELS.length) {
108
- throw new ObserveConfigError(
109
- `bands must list exactly the three levels ${OBSERVE_BAND_LEVELS.join(", ")} in order`,
110
- );
111
- }
112
- return raw.map((band, index) => {
113
- const expectedLevel = OBSERVE_BAND_LEVELS[index];
114
- if (band === null || typeof band !== "object" || Array.isArray(band)) {
115
- throw new ObserveConfigError(`bands[${index}] must be a map`);
116
- }
117
- if (band.level !== expectedLevel) {
118
- throw new ObserveConfigError(
119
- `bands[${index}].level must be ${expectedLevel} (levels are fixed), got: ${band.level}`,
120
- );
121
- }
122
- const when = requireString(band.when, `band ${expectedLevel}: when`);
123
- const match = WHEN_PATTERN.exec(when.trim());
124
- if (!match) {
125
- throw new ObserveConfigError(
126
- `band ${expectedLevel}: when must match "severity >= <level>", got: ${when}`,
127
- );
128
- }
129
- return { level: expectedLevel, minSeverity: match[1] };
130
- });
131
- }
132
-
133
- function parseTriage(raw) {
134
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
135
- throw new ObserveConfigError("triage must be a map");
136
- }
137
- const actions = raw.actions;
138
- if (
139
- !Array.isArray(actions) ||
140
- actions.length !== TRIAGE_ACTIONS.length ||
141
- TRIAGE_ACTIONS.some((action, index) => actions[index] !== action)
142
- ) {
143
- throw new ObserveConfigError(
144
- `triage.actions must be exactly [${TRIAGE_ACTIONS.join(", ")}] (closed set)`,
145
- );
146
- }
147
- if (raw.dismissFeedback !== "bands") {
148
- throw new ObserveConfigError('triage.dismissFeedback must be "bands"');
149
- }
150
- return { actions: [...TRIAGE_ACTIONS], dismissFeedback: "bands" };
151
- }
152
-
153
- export function loadObserveConfig(configPath) {
154
- const absolutePath = resolve(configPath);
155
- const text = readFileSync(absolutePath, "utf8");
156
- const raw = parseYamlSubset(text);
157
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
158
- throw new ObserveConfigError("observe config must be a YAML map");
159
- }
160
- if (raw.kind !== "workflow") {
161
- throw new ObserveConfigError(`kind must be workflow, got: ${raw.kind}`);
162
- }
163
- if (raw.version !== 1) {
164
- throw new ObserveConfigError(`unsupported observe config version: ${raw.version}`);
165
- }
166
- if (raw.name !== "observe") {
167
- throw new ObserveConfigError(`name must be observe, got: ${raw.name}`);
168
- }
169
- if (!Array.isArray(raw.providers) || raw.providers.length === 0) {
170
- throw new ObserveConfigError("providers must be a non-empty list");
171
- }
172
- const providers = raw.providers.map((provider, index) => parseProvider(provider, index));
173
- const ids = new Set();
174
- for (const provider of providers) {
175
- if (ids.has(provider.id)) {
176
- throw new ObserveConfigError(`duplicate provider id: ${provider.id}`);
177
- }
178
- ids.add(provider.id);
179
- }
180
- const configDir = dirname(absolutePath);
181
- const repoRoot = raw.repo
182
- ? resolve(configDir, String(raw.repo))
183
- : basename(configDir) === ".buildbeat"
184
- ? dirname(configDir)
185
- : configDir;
186
- return {
187
- configPath: absolutePath,
188
- configDigest: `sha256:${createHash("sha256").update(text, "utf8").digest("hex")}`,
189
- repoRoot,
190
- providers,
191
- bands: parseBands(raw.bands),
192
- triage: parseTriage(raw.triage),
193
- };
194
- }
@@ -1,117 +0,0 @@
1
- // Deterministic reducer for the observe ledger (RFC-0003 §8, implemented M5).
2
- // Shares envelope/chain rules with the run ledger via EventLedger's pluggable
3
- // reducer; state here is a runtime view only — triage terminal state lives in
4
- // the Git-plane intent draft files (invariant 23: runtime is deletable).
5
-
6
- export class IllegalObserveEventError extends Error {
7
- constructor(message) {
8
- super(message);
9
- this.name = "IllegalObserveEventError";
10
- }
11
- }
12
-
13
- export function observeInitialState() {
14
- return {
15
- seq: 0,
16
- cycles: 0,
17
- activeCycle: null,
18
- providers: {},
19
- evidence: [],
20
- bands: [],
21
- intents: {},
22
- triage: [],
23
- };
24
- }
25
-
26
- export function applyObserveEvent(state, event) {
27
- if (event.seq !== state.seq + 1) {
28
- throw new IllegalObserveEventError(`expected seq ${state.seq + 1}, got ${event.seq}`);
29
- }
30
- const { type, data } = event;
31
- const next = structuredClone(state);
32
- next.seq = event.seq;
33
-
34
- switch (type) {
35
- case "OBSERVE_CYCLE_STARTED": {
36
- if (state.activeCycle !== null) {
37
- // A crashed cycle never finished; the new cycle supersedes it. Facts
38
- // already appended stand; nothing is repaired.
39
- next.activeCycle = null;
40
- }
41
- if (data.cycle !== state.cycles + 1) {
42
- throw new IllegalObserveEventError(
43
- `expected cycle ${state.cycles + 1}, got ${data.cycle}`,
44
- );
45
- }
46
- next.activeCycle = { cycle: data.cycle, configDigest: data.configDigest, ts: event.ts };
47
- next.cycles = data.cycle;
48
- break;
49
- }
50
- case "OBSERVE_CYCLE_FINISHED": {
51
- if (!state.activeCycle || state.activeCycle.cycle !== data.cycle) {
52
- throw new IllegalObserveEventError(`no active cycle ${data.cycle} to finish`);
53
- }
54
- next.activeCycle = null;
55
- break;
56
- }
57
- case "EVIDENCE_RECORDED": {
58
- next.evidence.push({
59
- ref: data.evidenceRef,
60
- kind: data.kind,
61
- subject: data.subject,
62
- digest: data.digest,
63
- status: data.status,
64
- grade: data.grade,
65
- ...(data.provider ? { provider: data.provider } : {}),
66
- ...(data.cycle !== undefined ? { cycle: data.cycle } : {}),
67
- });
68
- if (data.provider) {
69
- next.providers[data.provider] = {
70
- lastStatus: data.status,
71
- lastKind: data.kind,
72
- lastSubject: data.subject,
73
- lastEvidenceRef: data.evidenceRef,
74
- lastTs: event.ts,
75
- };
76
- }
77
- break;
78
- }
79
- case "BAND_TRIGGERED": {
80
- next.bands.push({
81
- provider: data.provider,
82
- band: data.band,
83
- severity: data.severity,
84
- fingerprint: data.fingerprint,
85
- ts: event.ts,
86
- });
87
- break;
88
- }
89
- case "INTENT_DRAFTED": {
90
- next.intents[data.fingerprint] = {
91
- intentRef: data.intentRef,
92
- provider: data.provider,
93
- severity: data.severity,
94
- ts: event.ts,
95
- };
96
- break;
97
- }
98
- case "TRIAGE_RECORDED": {
99
- next.triage.push({
100
- intentRef: data.intentRef,
101
- action: data.action,
102
- fingerprint: data.fingerprint,
103
- ts: event.ts,
104
- });
105
- break;
106
- }
107
- default:
108
- // Unknown type written by a newer version: preserved, skipped.
109
- break;
110
- }
111
- return next;
112
- }
113
-
114
- export const OBSERVE_REDUCER = {
115
- initialState: observeInitialState,
116
- applyEvent: applyObserveEvent,
117
- };