@haiyangbg/buildbeat 2.0.0-beta.4 → 2.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -1
- package/SKILL.md +14 -4
- package/docs/CLI.md +1 -1
- package/docs/RELEASING.md +1 -1
- package/docs/V2.0.0-BETA.4-RELEASE-EVIDENCE-2026-09-03.md +9 -0
- package/docs/v2/SPEC-0001-events-v1.md +5 -4
- package/docs/v2/guide/02-workflow-guide.md +34 -1
- package/docs/v2/guide/07-approval-guide.md +14 -0
- package/docs/v2/guide/10-recovery.md +3 -2
- package/example/.buildbeat/manifest.json +1 -1
- package/lessons.md +25 -0
- package/package.json +1 -1
- package/src/v2/adapters/mock.js +9 -2
- package/src/v2/cli/run.js +147 -6
- package/src/v2/domain/event-registry.js +1 -0
- package/src/v2/engine/reducer.js +27 -1
- package/src/v2/runtime/decisions.js +80 -0
- package/src/v2/runtime/orchestrator.js +122 -11
- package/src/v2/runtime/overview.js +56 -19
- package/src/v2/runtime/run-record.js +3 -0
- package/src/v2/runtime/work-cost.js +147 -0
- package/src/v2/workspace/workspace-manager.js +14 -1
- package/templates/contracts/PROTOCOL.md +4 -0
- package/templates/gitignore.template +5 -0
- package/templates/scripts/bus-check.sh +37 -12
- package/templates/v2/AGENTS.md +3 -2
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// deleted here — the pinned candidate must stay reachable for evidence.
|
|
5
5
|
|
|
6
6
|
import { execFileSync } from "node:child_process";
|
|
7
|
-
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
7
|
+
import { existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
|
|
10
10
|
export class WorkspaceError extends Error {
|
|
@@ -44,6 +44,19 @@ export function acquireLock(repoRoot, runId) {
|
|
|
44
44
|
return lockPath;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// Run ids currently holding a lock in this repository (the repository-wide
|
|
48
|
+
// active-run marker excluded): who a blocked `start` is queued behind.
|
|
49
|
+
export function listHeldRunLocks(repoRoot) {
|
|
50
|
+
const lockDir = join(repoRoot, ".buildbeat", "runtime", "locks");
|
|
51
|
+
if (!existsSync(lockDir)) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
return readdirSync(lockDir)
|
|
55
|
+
.filter((entry) => entry.endsWith(".lock") && entry !== "active-run.lock")
|
|
56
|
+
.map((entry) => entry.slice(0, -".lock".length))
|
|
57
|
+
.sort();
|
|
58
|
+
}
|
|
59
|
+
|
|
47
60
|
export function releaseLock(repoRoot, runId) {
|
|
48
61
|
const lockPath = join(repoRoot, ".buildbeat", "runtime", "locks", `${runId}.lock`);
|
|
49
62
|
rmSync(lockPath, { recursive: true, force: true });
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
<!-- buildbeat-multirepo-map:v1
|
|
14
14
|
repo=<代码子仓1>|contract=contracts/PROTOCOL.md|deployment=<bus-baseline.json app 名或 n/a>
|
|
15
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> · <流水线>]`。 -->
|
|
16
20
|
|
|
17
21
|
---
|
|
18
22
|
|
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
# verify-status 的「上次全绿」标记(本地实查产物,不入 git)
|
|
15
15
|
.last-green-*
|
|
16
16
|
|
|
17
|
+
# BuildBeat v2 运行时面与隔离工作树(可随时整删重建;不入 git)
|
|
18
|
+
# 同时让 rg / 尊重 .gitignore 的工具不再走进旧工作树;vitest / jest 等要另配 exclude,见 docs/v2/guide/02-workflow-guide.md
|
|
19
|
+
.buildbeat/runtime/
|
|
20
|
+
.buildbeat/worktrees/
|
|
21
|
+
|
|
17
22
|
# 代码子仓(各自独立 git,meta 仓不跟踪;按实际仓名替换)
|
|
18
23
|
/<代码仓1>/
|
|
19
24
|
/<代码仓2>/
|
|
@@ -438,14 +438,18 @@ check_multirepo_drift() {
|
|
|
438
438
|
multirepo_line="${multirepo_line%$'\r'}"
|
|
439
439
|
[ -n "$multirepo_line" ] || continue
|
|
440
440
|
if ! printf '%s\n' "$multirepo_line" \
|
|
441
|
-
| grep -Eq '^repo=[^|]+\|contract=[^|]+\|deployment=[^|]
|
|
441
|
+
| grep -Eq '^repo=[^|]+\|contract=[^|]+\|deployment=[^|]+(\|changelog=[^|]+)?$'; then
|
|
442
442
|
multirepo_map_invalid=1
|
|
443
443
|
continue
|
|
444
444
|
fi
|
|
445
|
-
IFS='|' read -r multirepo_repo_field multirepo_contract_field multirepo_deployment_field <<< "$multirepo_line"
|
|
445
|
+
IFS='|' read -r multirepo_repo_field multirepo_contract_field multirepo_deployment_field multirepo_changelog_field <<< "$multirepo_line"
|
|
446
446
|
multirepo_repo="${multirepo_repo_field#repo=}"
|
|
447
447
|
multirepo_contract="${multirepo_contract_field#contract=}"
|
|
448
448
|
multirepo_deployment="${multirepo_deployment_field#deployment=}"
|
|
449
|
+
# 可选第 4 字段 changelog=<repo 内的 CHANGELOG 路径>:多模块仓没有根 CHANGELOG 时,
|
|
450
|
+
# 由 map 显式指定承载契约版本的模块 CHANGELOG;缺省仍为 <repo>/CHANGELOG.md。
|
|
451
|
+
multirepo_changelog_override="${multirepo_changelog_field#changelog=}"
|
|
452
|
+
[ -n "$multirepo_changelog_field" ] || multirepo_changelog_override=""
|
|
449
453
|
multirepo_repo_trimmed="$(printf '%s' "$multirepo_repo" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')"
|
|
450
454
|
multirepo_contract_trimmed="$(printf '%s' "$multirepo_contract" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')"
|
|
451
455
|
multirepo_deployment_trimmed="$(printf '%s' "$multirepo_deployment" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')"
|
|
@@ -454,12 +458,23 @@ check_multirepo_drift() {
|
|
|
454
458
|
|| [ "$multirepo_deployment" != "$multirepo_deployment_trimmed" ] \
|
|
455
459
|
|| ! multirepo_repo_path_safe "$multirepo_repo" \
|
|
456
460
|
|| ! multirepo_map_value_safe "$multirepo_contract" 240 \
|
|
457
|
-
|| !
|
|
461
|
+
|| ! { [ "$multirepo_contract" = "n/a" ] \
|
|
462
|
+
|| printf '%s\n' "$multirepo_contract" | grep -Eq '^contracts/[^/].*\.md$'; } \
|
|
458
463
|
|| ! multirepo_map_value_safe "$multirepo_deployment" 100; then
|
|
459
464
|
multirepo_map_invalid=1
|
|
460
465
|
continue
|
|
461
466
|
fi
|
|
462
|
-
|
|
467
|
+
# contract=n/a 表示该仓没有契约版本域(如只读存量前端、npm 包 semver 与契约版本不同域),
|
|
468
|
+
# 只登记入 inventory、不做版本核对;不得用它掩盖真实存在的契约版本关系。
|
|
469
|
+
if [ -n "$multirepo_changelog_override" ]; then
|
|
470
|
+
if ! multirepo_repo_path_safe "$multirepo_changelog_override" \
|
|
471
|
+
|| [ "${#multirepo_changelog_override}" -gt 240 ] \
|
|
472
|
+
|| ! printf '%s\n' "$multirepo_changelog_override" | grep -Eq "^$(printf '%s' "$multirepo_repo" | sed 's/[.[\*^$]/\\&/g')/.+/CHANGELOG\.md$"; then
|
|
473
|
+
multirepo_map_invalid=1
|
|
474
|
+
continue
|
|
475
|
+
fi
|
|
476
|
+
fi
|
|
477
|
+
printf '%s\t%s\t%s\t%s\n' "$multirepo_repo" "$multirepo_contract" "$multirepo_deployment" "$multirepo_changelog_override" >> "$multirepo_records"
|
|
463
478
|
printf '%s\n' "$multirepo_repo" >> "$multirepo_expected"
|
|
464
479
|
done < "$multirepo_map_raw"
|
|
465
480
|
|
|
@@ -485,9 +500,9 @@ check_multirepo_drift() {
|
|
|
485
500
|
add_finding "sync.unverified" "unverified" "Discovered repo=$multirepo_repo is absent from buildbeat-multirepo-map:v1." "$multirepo_repo"
|
|
486
501
|
done < "$multirepo_discovered"
|
|
487
502
|
|
|
488
|
-
while IFS=$'\t' read -r multirepo_repo multirepo_contract multirepo_deployment; do
|
|
503
|
+
while IFS=$'\t' read -r multirepo_repo multirepo_contract multirepo_deployment multirepo_changelog_override; do
|
|
489
504
|
[ -n "$multirepo_repo" ] || continue
|
|
490
|
-
multirepo_changelog="$multirepo_repo/CHANGELOG.md"
|
|
505
|
+
multirepo_changelog="${multirepo_changelog_override:-$multirepo_repo/CHANGELOG.md}"
|
|
491
506
|
multirepo_issue=0
|
|
492
507
|
multirepo_drift=0
|
|
493
508
|
multirepo_changelog_ok=0
|
|
@@ -544,6 +559,10 @@ check_multirepo_drift() {
|
|
|
544
559
|
;;
|
|
545
560
|
esac
|
|
546
561
|
|
|
562
|
+
if [ "$multirepo_contract" = "n/a" ] && [ "$multirepo_deployment" = "n/a" ]; then
|
|
563
|
+
echo " · $multirepo_repo 已登记,无契约/部署版本域(contract=n/a, deployment=n/a),不做版本核对"
|
|
564
|
+
continue
|
|
565
|
+
fi
|
|
547
566
|
if [ -L "$multirepo_changelog" ] \
|
|
548
567
|
|| { [ -e "$multirepo_changelog" ] \
|
|
549
568
|
&& path_uses_symlink_component "$ROOT_PHYS/$multirepo_changelog"; }; then
|
|
@@ -557,8 +576,8 @@ check_multirepo_drift() {
|
|
|
557
576
|
"the mapped CHANGELOG version source was not readable"
|
|
558
577
|
multirepo_issue=1
|
|
559
578
|
elif [ ! -f "$multirepo_changelog" ]; then
|
|
560
|
-
echo " ⚠️ $multirepo_repo 缺少可读 regular
|
|
561
|
-
add_finding "sync.unverified" "unverified" "Repo=$multirepo_repo has no readable regular CHANGELOG
|
|
579
|
+
echo " ⚠️ $multirepo_repo 缺少可读 regular $multirepo_changelog"
|
|
580
|
+
add_finding "sync.unverified" "unverified" "Repo=$multirepo_repo has no readable regular CHANGELOG version source: $multirepo_changelog." "$multirepo_changelog"
|
|
562
581
|
multirepo_issue=1
|
|
563
582
|
else
|
|
564
583
|
multirepo_changelog_version="$(read_changelog_head_version "$multirepo_changelog" || true)"
|
|
@@ -572,8 +591,14 @@ check_multirepo_drift() {
|
|
|
572
591
|
fi
|
|
573
592
|
|
|
574
593
|
multirepo_contract_rc=0
|
|
575
|
-
|
|
576
|
-
|
|
594
|
+
if [ "$multirepo_contract" = "n/a" ]; then
|
|
595
|
+
multirepo_contract_rc=0
|
|
596
|
+
else
|
|
597
|
+
validate_reference "$multirepo_contract" "$multirepo_map_path" || multirepo_contract_rc=$?
|
|
598
|
+
fi
|
|
599
|
+
if [ "$multirepo_contract" = "n/a" ]; then
|
|
600
|
+
:
|
|
601
|
+
elif [ "$multirepo_contract_rc" -ne 0 ]; then
|
|
577
602
|
echo " ⚠️ $multirepo_repo 的契约版本来源不可读:$multirepo_contract"
|
|
578
603
|
if [ "$multirepo_contract_rc" -eq 4 ]; then
|
|
579
604
|
add_scan_boundary "symlink" "$multirepo_contract" \
|
|
@@ -672,9 +697,9 @@ check_multirepo_drift() {
|
|
|
672
697
|
add_finding "sync.multirepo_drift" "conflict" "Version sources disagree for repo=$multirepo_repo: $multirepo_changelog=$multirepo_changelog_fact; $multirepo_contract=$multirepo_contract_fact; $multirepo_deployment_fact." "$multirepo_changelog"
|
|
673
698
|
elif [ "$multirepo_issue" -eq 0 ]; then
|
|
674
699
|
if [ "$multirepo_deployment" = "n/a" ]; then
|
|
675
|
-
echo " ✅ $multirepo_repo 多仓版本一致:
|
|
700
|
+
echo " ✅ $multirepo_repo 多仓版本一致: $multirepo_changelog ↔ $multirepo_contract (deployment=n/a)"
|
|
676
701
|
else
|
|
677
|
-
echo " ✅ $multirepo_repo 多仓版本一致:
|
|
702
|
+
echo " ✅ $multirepo_repo 多仓版本一致: $multirepo_changelog ↔ $multirepo_contract ↔ $multirepo_baseline#apps.$multirepo_deployment.imageTag"
|
|
678
703
|
fi
|
|
679
704
|
fi
|
|
680
705
|
done < "$multirepo_records"
|
package/templates/v2/AGENTS.md
CHANGED
|
@@ -7,14 +7,15 @@
|
|
|
7
7
|
|
|
8
8
|
## 0. v2 下工作怎么发生(一页流程)
|
|
9
9
|
|
|
10
|
-
1. **工作项**:每件事一个 `delivery/work/<WORK-ID>/`(`intent.md` 为什么做 + `plan.md` 怎么做,可选 `env-facts.md` 记踩出来的环境事实);被 digest 绑定接受(`buildbeat-v2 accept
|
|
10
|
+
1. **工作项**:每件事一个 `delivery/work/<WORK-ID>/`(`intent.md` 为什么做 + **止损线**(最多几个 Run / 几轮 review / 几小时,越线先问所有者"继续还是砍")+ `plan.md` 怎么做,可选 `env-facts.md` 记踩出来的环境事实);被 digest 绑定接受(`buildbeat-v2 accept`)前只是草稿、不产生义务。`overview` 的 `cost:` 行就是止损线的读数。
|
|
11
11
|
2. **代码工作跑 Run**:`buildbeat-v2 start --config <run-config.yaml> --attempt new` → 隔离 worktree 内 Build→Verify→Fix→Review 自动闭环 → **停在合并决定**。push、合并、部署永远是人批之后的人类动作。
|
|
12
12
|
3. **人怎么知道该做什么**:`buildbeat-v2 overview --repo .` 回答「每件事走到哪、下一步该谁」;`inbox` 只列等人批的 Run,每条后面附可复制的下一句命令;`status --run <RUN>` 回答「还在动吗、动了多久、卡没卡」。
|
|
13
13
|
4. **上线**:生产动作是人的;`release-readback` 预设 + `release` 风险预设把「做之前回读 → 人做 → 做之后回读 → 观察 → 人关窗」记成 L4 证据,任一步失败即停人批。
|
|
14
14
|
5. **observe 盯生产**:`buildbeat-v2 observe run --config .buildbeat/observe.yaml` 一次=一轮只读体检;异常分层(落账→只读诊断→intent 草稿入队 `delivery/observe/intents/`),草稿**绝不自动执行**,人用 `observe triage` 分诊。
|
|
15
15
|
6. **拍板台账**:平台级真实决策包一行进 `pm/decisions.md`;Run 级批准落各 Work 的 `decisions.jsonl`;finding 裁决落 `review-findings.jsonl`。契约在 `contracts/`。
|
|
16
16
|
7. **通知**:`.buildbeat/notify.yaml` 配一条通道(URL 只能来自环境变量),Run 停在人批 / 终态 / 疑似卡住会来找人。
|
|
17
|
-
8. **打扫**:终态 Run 留下的工作树用 `buildbeat-v2 gc --repo .`
|
|
17
|
+
8. **打扫**:终态 Run 留下的工作树用 `buildbeat-v2 gc --repo .` 清(默认只出计划)。工作树在仓内 `.buildbeat/worktrees/`:`.gitignore` 排除 `.buildbeat/runtime/` 与 `.buildbeat/worktrees/`,测试框架的收集范围也要排除 `**/.buildbeat/**`(vitest `exclude`、jest `testPathIgnorePatterns`、pytest `norecursedirs`),否则主干测试会把旧候选的用例一起跑。
|
|
18
|
+
9. **worker 环境事实(写进 worker prompt / 信封)**:worker 的沙箱通常**不能监听端口**,需要起服务或绑定 loopback 的集成测试交给 verify 步,worker 只跑单测与静态检查,不要反复尝试;PATH 只认 POSIX 工具(`grep -E` 不用 `rg`,`find` 不用 `fd`)或在 `requires:` 里声明;verify / 包装脚本发现环境不满足(命令不在 PATH、端口被占、后端 404)就 `exit 75`,内核会当基础设施故障停人、不派 fixer、不扣预算。
|
|
18
19
|
|
|
19
20
|
## 1. 工作包路由 —— Builder 端到端负责,会话按 AI 视角隔离
|
|
20
21
|
|