@haiyangbg/buildbeat 3.3.1 → 4.1.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 (102) hide show
  1. package/CHANGELOG.md +26 -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 -56
  14. package/docs/v2/guide/00-how-to-talk.md +12 -56
  15. package/docs/v2/guide/01-quickstart.en.md +42 -111
  16. package/docs/v2/guide/01-quickstart.md +42 -111
  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 +10 -75
  34. package/docs/v2/guide/11-session-handoff.md +10 -75
  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/delivery/work/WORK-EXPORT-DATE-FILTER/run-config.yaml +1 -1
  45. package/example//346/214/207/346/214/245/345/217/260.md +8 -40
  46. package/package.json +2 -1
  47. package/src/v2/adapters/notification-payload.js +40 -0
  48. package/src/v2/adapters/shell.js +3 -1
  49. package/src/v2/cli/run-config-check.js +129 -45
  50. package/src/v2/cli/run.js +853 -254
  51. package/src/v2/engine/reducer.js +2 -0
  52. package/src/v2/engine/risk-preset.js +14 -47
  53. package/src/v2/engine/workflow.js +177 -185
  54. package/src/v2/evidence/collector.js +12 -1
  55. package/src/v2/evidence/image.js +346 -0
  56. package/src/v2/policy/policy.js +219 -286
  57. package/src/v2/presets/software-delivery.yaml +1 -16
  58. package/src/v2/runtime/cache.js +22 -1
  59. package/src/v2/runtime/decisions.js +185 -31
  60. package/src/v2/runtime/findings.js +4 -1
  61. package/src/v2/runtime/notify.js +42 -36
  62. package/src/v2/runtime/orchestrator.js +811 -297
  63. package/src/v2/runtime/overview-repos.js +79 -0
  64. package/src/v2/runtime/overview.js +279 -47
  65. package/src/v2/runtime/release.js +233 -0
  66. package/src/v2/runtime/run-record.js +1 -0
  67. package/src/v2/workspace/workspace-manager.js +8 -0
  68. package/templates/v2/AGENTS.md +20 -73
  69. package/templates/v2/BUILDBEAT.md +2 -13
  70. package/templates/v2/envelope/prompts/builder.md +3 -1
  71. package/templates/v2/envelope/prompts/fixer.md +3 -1
  72. package/templates/v2/envelope/prompts/reviewer.md +3 -1
  73. package/templates/v2/run-config.example.yaml +9 -7
  74. package/templates/v2/work.example.md +16 -0
  75. package/templates/v2//346/214/207/346/214/245/345/217/260.md +8 -40
  76. package/docs/v2/skill/01-principles.md +0 -26
  77. package/docs/v2/skill/02-project-layout.md +0 -31
  78. package/docs/v2/skill/03-collaboration-rules.md +0 -45
  79. package/docs/v2/skill/04-rhythm-and-rituals.md +0 -104
  80. package/docs/v2/skill/05-red-lines.md +0 -13
  81. package/docs/v2/skill/06-bootstrap-and-takeover.md +0 -84
  82. package/docs/v2/skill/07-templates-and-lessons.md +0 -24
  83. package/example/.buildbeat/observe.yaml +0 -31
  84. package/src/v2/observe/observe-config.js +0 -194
  85. package/src/v2/observe/observe-reducer.js +0 -117
  86. package/src/v2/observe/observe.js +0 -427
  87. package/src/v2/presets/observe.yaml +0 -45
  88. package/src/v2/presets/policies/ui-render-gate.yaml +0 -13
  89. package/src/v2/presets/release-readback.yaml +0 -36
  90. package/src/v2/presets/risk/controlled.yaml +0 -39
  91. package/src/v2/presets/risk/fast.yaml +0 -19
  92. package/src/v2/presets/risk/release.yaml +0 -21
  93. package/src/v2/presets/risk/standard.yaml +0 -28
  94. package/templates/ARCHITECTURE.md +0 -50
  95. package/templates/contracts/PROTOCOL.md +0 -28
  96. package/templates/pm/adr/ADR-0000-template.md +0 -25
  97. package/templates/pm/adr/README.md +0 -15
  98. package/templates/pm/decisions.md +0 -11
  99. package/templates/standards/CODE.md +0 -23
  100. package/templates/standards/DESIGN.md +0 -36
  101. package/templates/standards/REVIEW.md +0 -20
  102. package/templates/standards/STACK.md +0 -31
@@ -0,0 +1,233 @@
1
+ // Release readback and closeout (4.0; replaces the retired release-readback
2
+ // lane, owner decision 2026-10-04). BuildBeat still performs no deployment
3
+ // (invariant 20): after a person releases a merged candidate, the project's
4
+ // own read-only command proves the release took effect. Every readback is a
5
+ // line in the Work's releases.jsonl, next to its decisions, and the window
6
+ // closes only on a passing readback. Real cost behind it: one pilot go-live
7
+ // was recorded by hand as forty "step N readback" commits.
8
+
9
+ import { execFileSync } from "node:child_process";
10
+ import { createHash, randomUUID } from "node:crypto";
11
+ import {
12
+ appendFileSync,
13
+ existsSync,
14
+ mkdirSync,
15
+ readFileSync,
16
+ writeFileSync,
17
+ } from "node:fs";
18
+ import { join } from "node:path";
19
+
20
+ import { createShellAdapter } from "../adapters/shell.js";
21
+ import { toRepoRef } from "./repo-ref.js";
22
+
23
+ const TAIL_LINES = 20;
24
+
25
+ export class ReleaseError extends Error {
26
+ constructor(message) {
27
+ super(message);
28
+ this.name = "ReleaseError";
29
+ }
30
+ }
31
+
32
+ function workDir(repoRoot, workId) {
33
+ return join(repoRoot, "delivery", "work", workId);
34
+ }
35
+
36
+ function readRows(path) {
37
+ if (!existsSync(path)) {
38
+ return [];
39
+ }
40
+ return readFileSync(path, "utf8")
41
+ .split("\n")
42
+ .filter(Boolean)
43
+ .map((line) => {
44
+ try {
45
+ return JSON.parse(line);
46
+ } catch {
47
+ return null;
48
+ }
49
+ })
50
+ .filter(Boolean);
51
+ }
52
+
53
+ export function readReleases(repoRoot, workId) {
54
+ return readRows(join(workDir(repoRoot, workId), "releases.jsonl"));
55
+ }
56
+
57
+ function isAncestor(repoRoot, sha, commit) {
58
+ try {
59
+ execFileSync(
60
+ "git",
61
+ ["-C", repoRoot, "merge-base", "--is-ancestor", sha, commit],
62
+ { stdio: "ignore" },
63
+ );
64
+ return true;
65
+ } catch {
66
+ return false;
67
+ }
68
+ }
69
+
70
+ // Runs the configured readback in the main checkout once and records it.
71
+ // `runs` are the Work's runs (overview's runsFor); the newest SUCCEEDED one
72
+ // whose candidate the release ref contains is what this readback is about.
73
+ export function runReadback({
74
+ repoRoot,
75
+ workId,
76
+ spec,
77
+ redact = [],
78
+ runs,
79
+ ref = "HEAD",
80
+ note = null,
81
+ ts = new Date().toISOString(),
82
+ }) {
83
+ const resolveCommit = (name) =>
84
+ execFileSync(
85
+ "git",
86
+ ["-C", repoRoot, "rev-parse", "--verify", `${name}^{commit}`],
87
+ { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
88
+ ).trim();
89
+ let commit;
90
+ try {
91
+ commit = resolveCommit(ref);
92
+ } catch {
93
+ throw new ReleaseError(`--ref ${ref} does not name a commit`);
94
+ }
95
+ // The command runs in the main checkout, which may sit elsewhere than the
96
+ // released ref; both are recorded so neither is claimed for the other.
97
+ const checkout = resolveCommit("HEAD");
98
+ const succeeded = runs
99
+ .filter((run) => run.status === "SUCCEEDED" && run.candidate)
100
+ .sort((a, b) => String(a.lastAt).localeCompare(String(b.lastAt)));
101
+ if (succeeded.length === 0) {
102
+ throw new ReleaseError(
103
+ `${workId} has no succeeded run; approve its merge decision first`,
104
+ );
105
+ }
106
+ const released = [...succeeded]
107
+ .reverse()
108
+ .find((run) => isAncestor(repoRoot, run.candidate, commit));
109
+ if (!released) {
110
+ const latest = succeeded.at(-1);
111
+ throw new ReleaseError(
112
+ `candidate ${latest.candidate.slice(0, 7)} (${latest.id}) is not contained in ${ref} (${commit.slice(0, 7)}); merge it first`,
113
+ );
114
+ }
115
+
116
+ const adapter = createShellAdapter({
117
+ name: "shell:release",
118
+ command: spec.command,
119
+ args: spec.args ?? [],
120
+ timeoutMs: spec.timeoutMs,
121
+ inheritEnv: spec.inheritEnv === true,
122
+ env: spec.env ?? {},
123
+ });
124
+ const exec = adapter.execute({
125
+ step: "release",
126
+ worker: "release",
127
+ workspacePath: repoRoot,
128
+ input: { workId, run: released.id, candidate: released.candidate, ref, commit },
129
+ });
130
+ const scrub = (text) => {
131
+ let out = String(text ?? "");
132
+ for (const pattern of redact) {
133
+ out = out.replace(pattern, "<REDACTED>");
134
+ }
135
+ return out;
136
+ };
137
+ const output = [scrub(exec.stdout), scrub(exec.stderr)]
138
+ .filter((text) => text.length > 0)
139
+ .join("\n");
140
+ const body = [
141
+ `command: ${scrub(exec.command)}`,
142
+ `exitCode: ${exec.exitCode}`,
143
+ `signal: ${exec.signal}`,
144
+ `timedOut: ${exec.timedOut}`,
145
+ `spawnError: ${exec.spawnError}`,
146
+ "--- stdout ---",
147
+ scrub(exec.stdout),
148
+ "--- stderr ---",
149
+ scrub(exec.stderr),
150
+ ].join("\n");
151
+ const logDir = join(repoRoot, ".buildbeat", "runtime", "releases", workId);
152
+ mkdirSync(logDir, { recursive: true });
153
+ // Unique per readback: concurrent readbacks never share a log file.
154
+ const logPath = join(
155
+ logDir,
156
+ `release-${ts.replace(/[^0-9A-Za-z]/g, "")}-${randomUUID().slice(0, 8)}.log`,
157
+ );
158
+ writeFileSync(logPath, body, { encoding: "utf8", flag: "wx" });
159
+ const passed =
160
+ exec.exitCode === 0 && !exec.timedOut && !exec.signal && !exec.spawnError;
161
+ const row = {
162
+ ts,
163
+ work: workId,
164
+ run: released.id,
165
+ candidate: released.candidate,
166
+ ref,
167
+ commit,
168
+ checkout,
169
+ ...(note ? { note } : {}),
170
+ command: scrub(exec.command),
171
+ exitCode: exec.exitCode,
172
+ status: passed ? "passed" : "failed",
173
+ grade: "L4",
174
+ digest: `sha256:${createHash("sha256").update(body, "utf8").digest("hex")}`,
175
+ log: toRepoRef(repoRoot, logPath),
176
+ tail: output.split("\n").filter(Boolean).slice(-TAIL_LINES),
177
+ };
178
+ mkdirSync(workDir(repoRoot, workId), { recursive: true });
179
+ appendFileSync(
180
+ join(workDir(repoRoot, workId), "releases.jsonl"),
181
+ `${JSON.stringify(row)}\n`,
182
+ "utf8",
183
+ );
184
+ return row;
185
+ }
186
+
187
+ // Closes the release window: a close-work decision bound to the latest
188
+ // readback, which must have passed.
189
+ export function closeWork({
190
+ repoRoot,
191
+ workId,
192
+ result,
193
+ by = "human",
194
+ ts = new Date().toISOString(),
195
+ }) {
196
+ if (typeof result !== "string" || result.trim() === "") {
197
+ throw new ReleaseError("closing a work needs --result <text>");
198
+ }
199
+ const decisionsPath = join(workDir(repoRoot, workId), "decisions.jsonl");
200
+ const decisions = readRows(decisionsPath);
201
+ if (decisions.some((row) => row.transition === "close-work")) {
202
+ throw new ReleaseError(`${workId} is already closed`);
203
+ }
204
+ const latest = readReleases(repoRoot, workId).at(-1);
205
+ if (!latest) {
206
+ throw new ReleaseError(
207
+ `${workId} has no readback; run buildbeat release --config <run-config.yaml> after the release first`,
208
+ );
209
+ }
210
+ if (latest.status !== "passed") {
211
+ throw new ReleaseError(
212
+ `the latest readback failed (exit ${latest.exitCode} at ${latest.ts}); fix the release and run buildbeat release again before closing`,
213
+ );
214
+ }
215
+ const count = existsSync(decisionsPath)
216
+ ? readFileSync(decisionsPath, "utf8").split("\n").filter(Boolean).length
217
+ : 0;
218
+ const row = {
219
+ ts,
220
+ decisionRef: `C-${workId}-${count + 1}`,
221
+ decision: "closed",
222
+ transition: "close-work",
223
+ subject: {
224
+ result: result.trim(),
225
+ readback: latest.digest,
226
+ commit: latest.commit,
227
+ candidate: latest.candidate,
228
+ },
229
+ by,
230
+ };
231
+ appendFileSync(decisionsPath, `${JSON.stringify(row)}\n`, "utf8");
232
+ return row;
233
+ }
@@ -42,6 +42,7 @@ export function writeRunRecord({ repoRoot, ledger, ts }) {
42
42
  run: first.run,
43
43
  work: first.work,
44
44
  workflow: state.run?.workflowRef ?? null,
45
+ ...(state.run?.deliveryChecks ? { deliveryChecks: state.run.deliveryChecks } : {}),
45
46
  terminal: state.terminal,
46
47
  events: { from: first.seq, to: last.seq, lastDigest: last.digest },
47
48
  startedAt: first.ts,
@@ -374,6 +374,14 @@ export function pinCandidate(workspace) {
374
374
  return head;
375
375
  }
376
376
 
377
+ // Undoes createWorkspace for a run that never started (nothing was written
378
+ // to its ledger): the worktree and its branch both go, so the same run id
379
+ // can be created again.
380
+ export function discardWorkspace(workspace) {
381
+ git(workspace.repoRoot, ["worktree", "remove", "--force", workspace.worktreePath]);
382
+ git(workspace.repoRoot, ["branch", "-D", workspace.branch]);
383
+ }
384
+
377
385
  export function removeWorkspace(workspace, { force = false } = {}) {
378
386
  const { dirty } = readback(workspace.worktreePath);
379
387
  if (dirty && !force) {
@@ -1,73 +1,20 @@
1
- # AGENTS.md — <项目名> 工作区 · BuildBeat 协作契约
2
-
3
- > 本文件走开放标准 `AGENTS.md`,由工作区下的会话按各工具自己的方式装载(Claude Code / Codex / Cursor / Gemini CLI / Aider / Zed 等多数会自动读根目录 `AGENTS.md` 或 `CLAUDE.md`;不自动读的工具由人开场贴给会话——用哪个工具就按它的文档核对一次,不要假设)。目的:每个会话开工即知道「当前工作在哪 / 我是什么视角 / 读哪 / 写哪 / 该调哪条命令」,不靠人转述上下文。
4
- > **层叠规则**(标准语义):会话从被编辑文件所在目录向上收集沿途所有 `AGENTS.md` 合并,**离得越近优先级越高**。本文件只写全局的(路由 / 协作规则 / 红线),各代码子仓的局部细节写进**该仓自己的 `AGENTS.md`**。
5
- > 根目录 `CLAUDE.md` 只是一行指针(兼容只认该文件名的工具),内容单点在本文件。全栈总图见 `./ARCHITECTURE.md`,按需读。
6
- > **本仓运行 BuildBeat**(运行时 `@haiyangbg/buildbeat@<版本>`,`buildbeat` 由会话调用,人不必手敲)。
7
-
8
- ## 0. 工作怎么发生(一页流程)
9
-
10
- 1. **工作项**:每件事一个 `delivery/work/<WORK-ID>/`(`intent.md` 为什么做 + **止损线**(最多几个 Run / 几轮 review / 几小时,越线先问所有者"继续还是砍")+ `plan.md` 怎么做,可选 `env-facts.md` 记踩出来的环境事实);被 digest 绑定接受(`buildbeat accept`)前只是草稿、不产生义务。`overview` 的 `cost:` 行就是止损线的读数。
11
- 2. **代码工作跑 Run**:`buildbeat start --config <run-config.yaml> --attempt new` → 隔离 worktree 内 Build→Verify→Fix→Review 自动闭环 → **停在合并决定**。push、合并、部署永远是人批之后的人类动作。
12
- 3. **人怎么知道该做什么**:`buildbeat overview --repo .` 回答「每件事走到哪、下一步该谁」;`inbox` 只列等人批的 Run,每条后面附可复制的下一句命令;`status --run <RUN>` 回答「还在动吗、动了多久、卡没卡」。
13
- 4. **上线**:生产动作是人的;`release-readback` 预设 + `release` 风险预设把「做之前回读 → 人做 → 做之后回读 → 观察 → 人关窗」记成 L4 证据,任一步失败即停人批。
14
- 5. **observe 盯生产**:`buildbeat observe run --config .buildbeat/observe.yaml` 一次=一轮只读体检;异常分层(落账→只读诊断→intent 草稿入队 `delivery/observe/intents/`),草稿**绝不自动执行**,人用 `observe triage` 分诊。
15
- 6. **拍板台账**:平台级真实决策包一行进 `pm/decisions.md`(从 `templates/pm/decisions.md` 拷,`pm/` 下只有这一个文件与可选的 `adr/`);Run 级批准落各 Work 的 `decisions.jsonl`;finding 裁决落 `review-findings.jsonl`。跨仓契约在 `contracts/`(单仓项目可无)。
16
- 7. **通知**:`.buildbeat/notify.yaml` 配一条通道(URL 只能来自环境变量),Run 停在人批 / 终态 / 疑似卡住会来找人。
17
- 8. **打扫**:终态 Run 留下的工作树用 `buildbeat gc --repo .` 清(默认只出计划)。工作树在仓内 `.buildbeat/worktrees/`:`.gitignore` 排除 `.buildbeat/runtime/` 与 `.buildbeat/worktrees/`,测试框架的收集范围也要排除 `**/.buildbeat/**`(vitest `exclude`、jest `testPathIgnorePatterns`、pytest `norecursedirs`),否则主干测试会把旧候选的用例一起跑。
18
- 9. **worker 信封**:`delivery/envelope/`(从 `templates/v2/envelope/` 拷)放 `worker.sh` 与 builder / reviewer / fixer 的 prompt,run 配置 `envelope.prompts` 指向它;换工具只改 run 配置里 `--` 后的命令。**worker 环境事实(写进 prompt)**:worker 的沙箱通常**不能监听端口**,需要起服务或绑定 loopback 的集成测试交给 verify 步,worker 只跑单测与静态检查,不要反复尝试;PATH 只认 POSIX 工具(`grep -E` 不用 `rg`,`find` 不用 `fd`)或在 `requires:` 里声明;verify / 包装脚本发现环境不满足(命令不在 PATH、端口被占、后端 404)就 `exit 75`,内核会当基础设施故障停人、不派 fixer、不扣预算。
19
-
20
- ## 1. 工作包路由 —— Builder 端到端负责,会话按 AI 视角隔离
21
-
22
- > 协作单元是需求/功能工作包(= Work)。一个 Builder 对工作包的产品判断、实现、测试、合并与发布证据端到端负责;下表是可调用的 AI 专业视角和文件写边界,不是人类岗位或固定交接流水线。共享事实走 Git(`delivery/` 与 Run 台账)。
23
-
24
- | AI 视角 | cwd | 可写(拥有) | 只读 | 开工先读 |
25
- |---|---|---|---|---|
26
- | **产品**(规格/编排) | 工作区根 | `delivery/**`、`pm/decisions.md`、根规划文档、`contracts/**` | 全仓 | `buildbeat overview --repo .` |
27
- | **全栈**(实现,含运维) | `<代码仓>/` | `<代码仓>/**`(Run 内受 `allowedPaths` 机器约束) | `delivery/*`、契约 | 所属 Work 的 intent/plan + `run-config.yaml` |
28
- | **测试**(契约验证·E2E) | 工作区根 | `tests/**`、独立核验报告(落所属 Work 目录) | 实现 + 规格 + 契约 | 所属 Work + 契约 |
29
-
30
- > 🔴 **边界(按项目填写)**:<新地盘 / 老地盘 / 只读模块 / 不得借道写入的目录>。
31
- > 🔴 **写者≠审者的机器化**:Run 内置 fresh-context 只读 reviewer(快照强制,写入即失败落账);merge 门绑定 candidate + plan + 证据 digest,过期即 stale。
32
- > **开工/收工护栏**:任意会话开工先各仓 `git pull`,再 `buildbeat overview --repo .`(活动 Work、等人的 Run、成本);收工前再跑一次 `overview` 并把 warning / unverified 原样写进收口。生产状态问 `observe status`,不猜。
33
-
34
- ## 1.5 UI 规范摘要(非 UI 项目可删)
35
-
36
- - 延用既定设计语言与 token;每个可见界面处理加载 / 空 / 错误 / 移动端四态。
37
- - **界面零元注释**:上线的可见界面不得出现给"做的人"看的文字;每次上线核查门必查。
38
- - UI 交付的拍板对象必须含可渲染证据(真渲染入口 + 截图 digest);静态描述不构成拍板对象。
39
-
40
- ## 2. 协作规则
41
-
42
- **① 唯一入口** —— 活动工作看 `delivery/`(`overview` / `inbox`);不另建进度文件、状态文件或看板,进度由内核从台账与 Git 回读。
43
- **② 契约落盘不喊话(双向)** —— 跨边界接口先改 `contracts/` 再动代码;收到协议声明独立核查再信。反向流:实现中发现契约不够用 → 不得就地消化,停下记契约缺口交产品域裁决。
44
- **③ 交接靠 candidate hash + 台账** —— Run 停在合并决定时 candidate 已由 Git 回读固定;跨会话接力读 `delivery/work/<id>/` 即知全部事实,hash 不得编造。
45
- **④ 护栏与不可逆动作** —— 开工 `overview`;部署/改契约/migration 等不可逆动作前再核一次并走人批;exit 0 不消除 `warning/unverified`。
46
- **⑤ 风险分轨** —— Risk Preset:`fast`(仅 merge 人批)/ `standard`(plan+merge,默认)/ `controlled`(intent+plan+merge+release)/ `release`(上线回读车道)。
47
- **⑥ 核查门** —— Run 内 reviewer 只读、结构化 findings;`reviewTriage: required` 时 P0/P1 先过人分诊再派 fixer;review 不收敛(修过的 finding 又出现,或阻断数多于上一轮)即停人,每 Work 默认 6 轮兜底(`reviewRoundsPerWork`)。**完成 = hash + 可核验证据**;标准轨最低 L3,上线必须 L4。`UNVERIFIED` 永不当作通过。
48
- **⑦ 状态单点** —— 事实进 Run 证据与 Work 记录;进度看 `overview`,度量看 `metrics`(本地只读)。
49
- **⑧ 视觉问题带图对比** —— 提 UI bug 必附『实现截图 ⟷ 设计稿截图』并排 + 标注差异点。
50
- **⑨ 单点事实** —— 线上版本只信实查(`observe status` / 部署平台);任何文档不写「当前线上 vX」;每个收敛后的真实决策包只在 `pm/decisions.md` 记一行;历史台账不回改。
51
- **⑩ 真渲染拍板** —— 有 UI 的拍板对象必须是真渲染证据。
52
- **⑪ 所有者可见命名进决策卡** —— 域名、服务名、环境名、自停时长、窗口时长等**所有者以后要看见或要念出来的名字与参数**,不由 worker 顺手定:进 intent 或门前决策卡(`BATCH_AT_GATE`),给推荐值和理由(用业务上听得懂的名字,不用内部术语)。
53
-
54
- ## 2.5 任务包与人批节奏
55
-
56
- **任务包信封** —— 开工时从用户目标与活动 Work 明确 `objective / in_scope / terminal_condition`(即 Work 的 intent/plan)。默认一个工作包覆盖多个子项;每个会话同时只认领一个。只要仍有安全、可逆、在范围内且能推进目标的工作,会话就继续做,不因子产物完成交还接力棒。
57
-
58
- **域回复格式** —— 面向用户收口、交接或回复明确检查点时,统一写「已做 → 未做 → 下一步」;已做/未做默认各总结成一句,不习惯性 1234 分条;只有事项性质差异大时才列举,每条仍是"结果+证据"。
59
-
60
- **审批三级**:`STOP_NOW`(跨发布门 / 扩范围 / 改冻结契约 / 不可逆外部动作 / 接受风险);`BATCH_AT_GATE`(冻结前可逆取舍、默认值、阈值、可见命名——攒到门前一次批 2~5 个);`NO_APPROVAL`(能实查的事实、派生约束、文档归档、普通 P2)。
61
-
62
- **人批预算** —— 每个工作包、每道门默认 1 个 `BATCH_AT_GATE` 请求;待批项必须带 findings 摘要与风险声明。
63
-
64
- > **元原则:能实查的不问人** —— 查代码 / 配置 / 部署平台 / `overview` / `status` / `observe status` 能得到的事实,不拿去问用户、不信文档、不信上游转述。
65
-
66
- ## 3. 红线(每个会话受约束)
67
-
68
- 1. **凭据不入 git、不出本机**:文档只标位置不写值;本地 .env gitignore + 600;机器闸 = gitleaks pre-commit;Worker 默认 env 白名单;通知 URL 只能来自环境变量。
69
- 2. **不 `git add -A`**:只 stage 当前工作包拥有的具体文件;各仓分别提交。
70
- 3. **不未授权部署**、不 force-push、不 `--amend` 已推送历史、不 `--no-verify`。Run 的合并决定只表示候选具备合并条件(`SUCCEEDED` ≠ 已合并),合并/push/发布是其后的人类动作、逐项授权。
71
- 4. **每次部署完必更对应仓 `CHANGELOG.md`**;部署后 `observe run` 一轮。
72
- 5. **写者≠审者**:Run 内置只读 reviewer 机器强制;写者转述不构成证据。
73
- 6. **事实分层**:代码已确认事实 / 运行时待核事实 / 拟议需求 / 已实现行为,四类严格分开;未实查一律写「待核」。
1
+ # Project delivery instructions
2
+
3
+ Load BuildBeat's Skill according to the host tool's rules. Read project-owned
4
+ constraints, delivery/work/<ID>/work.md (or legacy intent/plan), Git and status.
5
+ One Work owns one user-visible result. Continue safe authorized work to its
6
+ acceptance condition; intermediate commits are progress, not completion.
7
+
8
+ Use accept once the user has accepted the scope, run to execute/continue, status
9
+ to inspect (add --all-repos in a main repository to include its code repositories),
10
+ decide for a real human decision, and check for diagnostics. Keep
11
+ candidate/evidence/approval binding, allowedPaths, read-only review, and failure
12
+ budgets. Do not impersonate a human approval or silently weaken requirements.
13
+
14
+ Respect the host sandbox and environment contract. Missing tools or unavailable
15
+ backends are infrastructure failures (exit 75). Never print or commit secrets.
16
+ Stage only owned files. Merge, push, publication and deployment require their
17
+ own authorization. Existing project standards stay under project ownership.
18
+
19
+ Preserve active runtime files and worktrees when switching sessions. Candidates
20
+ and terminal records must remain reachable before cleanup.
@@ -1,14 +1,3 @@
1
- # BUILDBEAT.md — 本项目的 BuildBeat 标记
1
+ # BuildBeat
2
2
 
3
- **本项目运行 BuildBeat**:运行时 `@haiyangbg/buildbeat@<X.Y.Z>`(<yyyy-mm-dd> 首次接入;查看本机版本 `npm ls -g @haiyangbg/buildbeat`,查看最新 `npm view @haiyangbg/buildbeat@latest version`)
4
- **装载方式**:会话读根目录 `AGENTS.md`(`CLAUDE.md` 是一行指针);驾驶手册在 BuildBeat Skill `SKILL.md` §0.5
5
- **活动工作**:`delivery/work/<WORK-ID>/`(intent / plan / run-config / decisions.jsonl / runs/);信封与 worker 包装在 `delivery/envelope/`
6
- 来源:<https://github.com/HaiYangBG1/BuildBeat>
7
-
8
- ## 升级
9
-
10
- 运行时升级只是 `npm install --global @haiyangbg/buildbeat@latest`,对项目文件零改动;升级后更新上面的版本行。模板(`AGENTS.md` / `指挥台.md` / `delivery/envelope/`)对照上游 [CHANGELOG.md](https://github.com/HaiYangBG1/BuildBeat/blob/main/CHANGELOG.md) 中「模板」条目手工同步,拿不准就让会话对比上游 `templates/v2/` 与本项目对应文件。事件 schema 只增不改,旧 Run 台账不需要迁移。
11
-
12
- ## 回灌(比升级更重要)
13
-
14
- 本项目踩到 BuildBeat **没覆盖的新坑**(新反模式 / 机制漏洞)→ 回上游 `lessons.md` 登记一条(症状 → 根因 → 解药),提 issue / PR 或直接改上游仓。只回灌真实事故,不回灌猜想。
3
+ This project uses the fixed delivery runtime and work.md. Record the installed runtime version and project-owned constraints when adopting it. See status for current work.
@@ -1,8 +1,10 @@
1
1
  你是本 Run 的 builder,在一个隔离的 git worktree 里工作(当前目录)。
2
2
 
3
- 1. 读环境变量 `BUILDBEAT_INPUT`(JSON):`workId` / `runId` / `step` / `attempt`。目标与计划在 `delivery/work/<workId>/intent.md` 与 `plan.md`——只做 plan 里写的,plan 没写的记到 `delivery/work/<workId>/notes.md` 交给人,不要顺手做。
3
+ 1. 读环境变量 `BUILDBEAT_INPUT`(JSON):`workId` / `runId` / `step` / `attempt`。优先读取 `workArtifact.ref` 指定的已确认工作说明;新工作是 `delivery/work/<workId>/work.md`,其中包含目标、范围、验收和实施计划。没有这个输入字段时,先检查 work.md;只有 work.md 不存在时才回退到旧 intent.md 与 plan.md。只做确认范围内的事,范围外想法交给人,不要为满足旧路径而另造一套计划。
4
4
  2. 只改 run 配置 `allowedPaths` 列出的目录(通常是 `src` 与 `tests`);越界改动不会成为候选。
5
5
  3. 不要 `git commit`、不要 `git push`、不要改分支:提交由包装脚本机械完成,push 已被封禁。
6
6
  4. 沙箱通常不能监听端口:需要起服务的集成测试交给 verify 步,你只跑单测与静态检查;PATH 只认 POSIX 工具(`grep -E` 不用 `rg`)。
7
7
  5. 所有者以后要看见或念出来的名字与参数(域名、服务名、环境名、时长)不由你定:写进 `delivery/work/<workId>/notes.md` 给推荐值与理由,等人批。
8
8
  6. 改完自检一次能跑的测试;把做了什么、没做什么、下一步各一句写到 stdout 末尾。
9
+
10
+ 若 workArtifact.ref 指向旧 plan.md,同时读取同目录的 intent.md(如存在);work.md 存在时不要拼接旧文件覆盖新范围。
@@ -1,8 +1,10 @@
1
1
  你是本 Run 的 fixer,在隔离的 git worktree 里工作(当前目录)。你只修被指出的问题,不做别的。
2
2
 
3
- 1. 读环境变量 `BUILDBEAT_INPUT`(JSON):
3
+ 1. 读环境变量 `BUILDBEAT_INPUT`(JSON),对照 workArtifact.ref 指定的 work.md(缺失字段时优先 work.md,否则回退旧 intent.md/plan.md)确认范围:
4
4
  - 有 `findings[]`:这是上一轮 review 的结论及裁决状态,**只修 `adjudication` 为 `accepted` 或 `open` 的条目**,`dismissed` 的不碰;
5
5
  - 没有 `findings`:说明是 verify 失败进来的。失败日志在主仓 `.buildbeat/runtime/runs/<runId>/logs/verify-<n>.log`(`<n>` 是最近一次 verify 的 attempt;从 worktree 找主仓:`cd "$(git rev-parse --git-common-dir)/.."`),里面有命令、退出码、stdout / stderr。先读日志,再改代码。
6
6
  2. 只改 run 配置 `allowedPaths` 列出的目录;不要 `git commit` / `git push`(提交由包装脚本完成,push 已封禁)。
7
7
  3. 修不了或问题不在代码(环境缺工具、端口被占、后端 404):不要绕,`exit 75`,内核会当基础设施故障停人。
8
8
  4. 改完跑一次相关测试;把修了哪条、没修哪条及原因各一句写到 stdout 末尾。
9
+
10
+ 若 workArtifact.ref 指向旧 plan.md,同时读取同目录的 intent.md(如存在);work.md 存在时不要拼接旧文件覆盖新范围。
@@ -2,7 +2,7 @@
2
2
 
3
3
  1. 读环境变量 `BUILDBEAT_INPUT`(JSON)。若有 `lastReviewed.range`,只审 `git diff <range>` 的增量;否则审本分支相对 base 的全部改动(`git log --oneline` 与 `git diff HEAD~1` 起步,按需扩大)。
4
4
  2. `BUILDBEAT_INPUT.anchor` 是历史 finding 与人的裁决:**已裁决(accepted / dismissed)的结论不得翻案**;同一问题不要换措辞重提。
5
- 3. 对照 `delivery/work/<workId>/plan.md`:做了 plan 没写的事、引入了未经批准的可见命名(域名 / 服务名 / 环境名)→ 记 P2。
5
+ 3. 对照 `BUILDBEAT_INPUT.workArtifact.ref` 指定的已确认工作说明。新工作使用 `delivery/work/<workId>/work.md`;没有该输入字段时优先 work.md,只有它不存在才回退到旧 intent.md 与 plan.md。检查目标、范围、验收与实施计划,不以缺失的旧文件判断新工作。范围外改动或未经批准的可见命名记为具体 finding。
6
6
  4. 严重度:P0 数据丢失 / 安全 / 不可逆;P1 功能错误或测试未覆盖计划要求的行为;P2 可维护性、命名、边界;P3 建议。只有 P0 / P1 会阻断。
7
7
  5. **最后只输出一个 JSON 对象,不要别的文字**(可以裹一层 ```json 代码栏):
8
8
 
@@ -11,3 +11,5 @@
11
11
  ```
12
12
 
13
13
  没有问题就 `"findings": []`。每条必须有 `severity`(P0–P3)和 `summary`;缺字段或不是 JSON 会被当成 worker 故障停人,而不是候选缺陷。
14
+
15
+ 若 workArtifact.ref 指向旧 plan.md,同时读取同目录的 intent.md(如存在);work.md 存在时不要拼接旧文件覆盖新范围。
@@ -1,15 +1,10 @@
1
1
  # BuildBeat v2 run 配置样板。拷到 delivery/work/<WORK-ID>/run-config.yaml 后改 work / run / allowedPaths / workers。
2
2
  # 路径相对本文件解析。严格 YAML 子集:只有块列表与块映射(列表项可与键同缩进),行内只允许空的 [] / {},无锚点,注释必须独占一行。
3
- # 起跑前:buildbeat doctor --config <本文件>;起跑:buildbeat start --config <本文件> --attempt new
3
+ # 起跑前:buildbeat check --config <本文件>;起跑:buildbeat run --config <本文件>
4
4
  repo: ../../..
5
5
  work: WORK-X
6
- # 家族名;--attempt new 自动编成 RUN-X-01/02…
6
+ # 家族名;run 自动编成 RUN-X-01/02…,--new 开新一轮
7
7
  run: RUN-X
8
- # 从 $(npm root -g)/@haiyangbg/buildbeat/src/v2/presets/software-delivery.yaml 复制到本目录(digest 随项目进 Git)
9
- workflow: workflow.yaml
10
- # fast | standard | controlled | release(配 release-readback 预设)
11
- riskPreset: standard
12
- entry: build
13
8
  # builder / fixer 只能改这些目录;越界改动不成为候选
14
9
  allowedPaths:
15
10
  - src
@@ -34,6 +29,13 @@ envelope:
34
29
  # worker 输出落成证据前按这些 JS 正则脱敏(不支持 (?i) 这类内联标志)
35
30
  redact:
36
31
  - "(token|secret|password|TOKEN|SECRET|PASSWORD)=\\S+"
32
+ # 有 UI 的交付:verify 把真渲染截图以 PNG 写进 $BUILDBEAT_SCREENSHOT_DIR(只收可解码的 PNG),合并检查要求当前候选有截图
33
+ # requireScreenshot: true
34
+ # 合并并上线后由 buildbeat release 在主检出运行的只读回读命令(形状同 worker);回读通过后才能关窗
35
+ # release:
36
+ # command: bash
37
+ # args:
38
+ # - scripts/readback.sh
37
39
  # worker 以隔离 worktree 为 cwd 运行;delivery/envelope/worker.sh 已随仓库进 worktree。
38
40
  # 换工具只改 `--` 后面的命令:codex exec … / claude -p … / 任意脚本。
39
41
  workers:
@@ -0,0 +1,16 @@
1
+ # Work title
2
+
3
+ ## Goal
4
+ The user-visible result and why it matters.
5
+
6
+ ## Scope
7
+ Allowed changes, exclusions, and existing project constraints.
8
+
9
+ ## Acceptance
10
+ Observable outcomes and the real verification command.
11
+
12
+ ## Implementation plan
13
+ The smallest sequence that reaches the acceptance criteria.
14
+
15
+ ## Stop conditions
16
+ Scope changes need a new decision. Set a review-round budget in run-config.yaml.
@@ -1,40 +1,8 @@
1
- # 指挥台 — Builder 怎么驱动工作(忘了怎么开场就看这页)
2
-
3
- > 本仓运行 BuildBeat。会话按所用工具的方式装载 `AGENTS.md`(多数工具自动读根目录 `AGENTS.md` / `CLAUDE.md`;不自动读的,开场把它贴给会话);活动工作在 `delivery/`。你基本只需说下面这几句话,命令由会话调、不用你手敲。
4
-
5
- ## 日常六句话
6
-
7
- | 你想干什么 | 说什么 | 会话背后调什么 |
8
- |---|---|---|
9
- | 看现在到哪了、该谁动 | 「当前进度」/「待办是什么」 | `buildbeat overview --repo .`(每个 Work 的阶段 + 下一步)+ `observe status` |
10
- | 看有什么等我批 | 「有什么要我拍板」 | `buildbeat inbox --repo .`(每条附可复制的下一句) |
11
- | 立一件新事 | 「开个 Work:〔一句话目标〕」→ 看完说「接受」 | 建 `delivery/work/<ID>/intent.md + plan.md` → `accept`(digest 绑定) |
12
- | 让它干活 | 「开工」/「再来一轮」 | 先 `buildbeat doctor --config …`(配置、intent/plan 接受状态、env 姿态、预算),再 `start --config … --attempt new`(自动编号、自动作废旧等待,停在合并决定) |
13
- | 它是不是卡了 | 「怎么样了」/「卡住了吗」 | `buildbeat status --repo . --run <RUN>`(耗时、历史中位数、最后输出、STALLED) |
14
- | 拍板 | 「批准 / 拒绝〔RUN-ID〕」;分诊时「这条接受,那条不算」 | `approve` / `reject` / `findings adjudicate`;会话说清批的是哪一步(放行 fixer / 再跑一次 / 合并决定),非终态批准后 `resume` 续跑;合并决定只表示候选够格合并,合并/push/部署逐项另说 |
15
-
16
- 其他:生产报警看 `delivery/observe/intents/` 草稿 → 「fix_now / schedule / dismiss」;上线用 `release-readback` 预设开一个 Run,「我做完了」就是批准 `enter-apply-readback`;「打扫卫生」= `gc --repo .`(先看计划再 `--apply true`)。
17
-
18
- ## 新开一个 Work 时会话要动的文件
19
-
20
- `delivery/work/<WORK-ID>/`:`intent.md`(为什么 + 止损线)、`plan.md`(怎么做)、`run-config.yaml`(从 `templates/v2/run-config.example.yaml` 改 work / run / allowedPaths / workers)、`workflow.yaml`(从 `$(npm root -g)/@haiyangbg/buildbeat/src/v2/presets/software-delivery.yaml` 复制)。信封与 worker 包装在仓级 `delivery/envelope/`,不用每个 Work 重写。
21
-
22
- ## 视角开场白
23
-
24
- | AI 视角 | 在哪开(cwd) | 你的一句话 |
25
- |---|---|---|
26
- | **产品** | 工作区根 | 「把当前目标收成一个 Work」/「验收」——定 intent/plan、维护决策收件箱、按决策包记 `pm/decisions.md` |
27
- | **全栈** | `<代码仓>/` | 「〔WORK-ID〕该你了,开工」——授权范围内实现,Run 停在合并决定回人批 |
28
- | **测试** | 工作区根 | 「验〔RUN-ID〕候选」——对精确 candidate 独立核验,报告落所属 Work 目录 |
29
-
30
- ## 域回复怎么写
31
-
32
- 面向你收口统一「已做 → 未做 → 下一步」,各一句;证据紧跟事项。下一步只写一条:下一棒是谁、需要你确认什么、或「我继续做,暂不交棒」。
33
-
34
- ## 按项目阶段的完整问法
35
-
36
- 未开始 → 立项定方案 → 准备执行 → 执行推进 → 验收合并 → 上线 → 完结换期复盘,每阶段说什么、会话做什么、你得到什么:见 BuildBeat 指南第 0 篇 `docs/v2/guide/00-how-to-talk.md`(Skill 装载目录下同路径)。
37
-
38
- ## 红线、写边界、审批三级、可见命名进决策卡
39
-
40
- 全在 `AGENTS.md`,本页不复制。
1
+ # Daily work
2
+
3
+ - Prepare the goal, scope, acceptance and plan in work.md.
4
+ - Accept once the owner has decided.
5
+ - Continue with run; inspect with status.
6
+ - Resolve the exact pending decision; resume afterward when nonterminal.
7
+ - Diagnose with check; check --step executes a real command outside the ledger.
8
+ - Preserve context and active runs before handoff. Cleanup plans precede apply.
@@ -1,26 +0,0 @@
1
- # BuildBeat 方法论 · 原则与分工(原 §1–§2)
2
-
3
- > 从 [`SKILL.md`](../../../SKILL.md) 移出的正文,原文与原节号不变,只调整了相对链接;入口、驾驶手册与红线摘要仍在 `SKILL.md`。
4
-
5
- ## 1. 四根支柱(命根子,所有零件都为它们服务)
6
-
7
- 1. **端到端工作包**:一个 Builder 对一个需求/功能工作包(= Work)的产品判断、实现、测试、合并与发布证据负责;需要隔离时再调用独立 AI 视角(独立 cwd / 上下文 / 写边界)。
8
- 2. **人在决定点**:接受 intent/plan、合并决定、上线关窗必须人拍板,**不可自动跨过**;Run 内的 Build→Verify→Review→Fix 由内核自动闭环。人只当"节拍器 + 拍板者",不当信息搬运工。
9
- 3. **上下文在项目文件**:会话间不靠人转述,长期事实全部走 Git(`AGENTS.md` → `delivery/work/<ID>/` → 契约 → 决策台账),运行态由内核落台账;开工自取,进度由内核回读而不是会话自述。
10
- 4. **证据制完成**:任何工作包声明"完成"必须带 ① Git 回读的候选 commit ② 可核验证据(真实命令的 verify 结果 / 只读 reviewer 的 findings / 线上回读 / 截图)。**无证据 = 没完成。**
11
-
12
- > 需求编号可以细,但**执行边界不能跟着编号碎掉**:一个 Builder 同时认领一个可验收的用户级工作包,通常覆盖多个任务 ID / 文档 / commit;同一工作包可调用多个 AI 视角按各自写边界协作。多个 Builder 默认按项目/需求工作包切分,不按人类产品→研发→测试岗位接力。子产物提交、reviewer 返回、Run 停下都只是工作包内事件,不是自动结束会话的理由。
13
-
14
- ## 2. 工作包所有权与 AI 专业视角
15
-
16
- **人类责任按工作包端到端闭环。** 下表是同一 Builder 可调用的默认 AI 视角,用于上下文和写边界隔离;它不是成员目录、岗位分工或审批链。多个 Builder 协作时各自拥有不同工作包,共享契约冲突线下收敛后只落最终事实。
17
-
18
- | AI 视角 | 在当前工作包内做什么 | 典型写入边界 |
19
- |---|---|---|
20
- | **产品**(规格/编排) | 拆需求、写 intent/plan、定契约要点、维护决策台账、分诊 finding | `delivery/**`、`pm/decisions.md`、`contracts/` |
21
- | **全栈**(实现,含运维) | 实现 + 改契约 + 部署;可同持多仓但**按仓分别 stage**;Run 内受 `allowedPaths` 机器约束 | 代码仓 |
22
- | **测试**(E2E·走查) | 对精确 candidate 独立核验、视觉回归、设计走查,不合格直接提带图 bug;报告落所属 Work 目录 | `tests/**` + 所属 Work 目录 |
23
-
24
- - **审查不是会话视角**:reviewer 是 Run 内置的 fresh-context 只读 worker,输入固定 candidate,输出结构化 findings;任何工作树写入都会被前后快照抓住并按失败落账。会话不另开"审查会话"。
25
- - **设计生成 = 外部工具**(可选):当前工作包的产品视角写 brief → 人喂设计工具 → 稿落 `design/design_N期/`;走查归测试视角。
26
- - **拆 AI 视角的依据是"物理边界(仓/部署单元)+ 是否需要独立核查",不是人类公司职能表。** 实践教训:按职能切出 6 个会话,两个月内被迫合并回 4 个(前端+后端合并、设计+测试合并)——每多一个上下文,编排成本和信息差面积都扩大。合并视角会丢"天然独立核查"防线,由 Run 内置 reviewer + 测试视角独立核两端补回。
@@ -1,31 +0,0 @@
1
- # BuildBeat 方法论 · 项目文件布局(原 §3)
2
-
3
- > 从 [`SKILL.md`](../../../SKILL.md) 移出的正文,原文与原节号不变,只调整了相对链接;入口、驾驶手册与红线摘要仍在 `SKILL.md`。
4
-
5
- ## 3. 项目文件布局
6
-
7
- ```
8
- <项目根>/ ← 工作区(单仓项目就是代码仓本身;多仓项目是协调层 meta 仓)
9
- ├── AGENTS.md # 会话路由 + 协作规则 + 红线(开放标准,按工具装载)
10
- ├── CLAUDE.md # 一行指针 → AGENTS.md(兼容只认此名的工具;🔴 不复制内容)
11
- ├── 指挥台.md # 给人看的一页:日常六句话、视角开场白
12
- ├── BUILDBEAT.md # 运行时版本标记 + 升级/回灌说明
13
- ├── ARCHITECTURE.md # 全栈总图(多仓项目;按需读,不自动装载)
14
- ├── contracts/PROTOCOL.md # 跨边界契约唯一入口(多仓项目;单仓可无)
15
- ├── standards/ # 可选:STACK / CODE / REVIEW / DESIGN(Policy 输入工件,默认不生成)
16
- ├── pm/decisions.md # 🔴 平台级拍板台账(全工作区决策单点);可选 pm/adr/
17
- ├── delivery/
18
- │ ├── envelope/ # worker.sh + builder / reviewer / fixer prompt(仓级,进 Git)
19
- │ ├── work/<WORK-ID>/ # intent.md / plan.md / run-config.yaml / workflow.yaml / decisions.jsonl
20
- │ │ └── runs/<RUN-ID>/ # run-record.json(终态记录,进 Git)
21
- │ └── observe/intents/ # observe 的 Intent 草稿(人分诊,绝不自动执行)
22
- ├── .buildbeat/
23
- │ ├── notify.yaml / observe.yaml # 通知通道(URL 只走环境变量)/ 生产体检配置
24
- │ ├── runtime/ # 🔴 事件台账、锁、日志(本机,不进 Git)
25
- │ └── worktrees/ # 🔴 每个 Run 的隔离工作树(本机,不进 Git;gc 清)
26
- └── <代码子仓们>/ # 多仓项目:各自独立 git + 该仓自己的 AGENTS.md(只写本仓局部细节)
27
- ```
28
-
29
- > 🔴 **装载入口走开放标准 `AGENTS.md`,不绑厂商**(lessons.md「上下文载体绑死单一厂商」)。标准语义 = 会话从被编辑文件所在目录**向上收集沿途所有 `AGENTS.md` 合并、离得最近的优先**,所以「根写全局、子仓写局部」是白捡的层叠能力,不用自己发明。只认 `CLAUDE.md` 的工具靠根上一份**一行指针**兼容(内容单点在 `AGENTS.md`,复制过去 = 自造 SSOT 腐烂;也别用符号链接,Windows 上 git 默认 `core.symlinks=false` 会静默退化成文本文件)。同理**不要**引入 gitignore 的本地覆盖文件(如 `AGENTS.override.md`):本文件装的是红线与护栏,允许不进 git 的本地覆盖 = 给绕过护栏开后门,reviewer 与 pre-commit 都看不见。
30
- >
31
- > **不建进度文件、状态文件或看板**:进度由内核从台账与 Git 回读(`overview` / `status`),写进文档的进度从写下那一刻开始腐烂(lessons.md「SSOT 腐烂」)。`.gitignore` 排除 `.buildbeat/runtime/` 与 `.buildbeat/worktrees/`;有 vitest / jest / pytest 的仓另配 exclude `**/.buildbeat/**`,否则主干测试会把旧候选的用例一起跑。