@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.
@@ -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=[^|]+$'; then
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
- || ! printf '%s\n' "$multirepo_contract" | grep -Eq '^contracts/[^/].*\.md$' \
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
- printf '%s\t%s\t%s\n' "$multirepo_repo" "$multirepo_contract" "$multirepo_deployment" >> "$multirepo_records"
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 CHANGELOG.md"
561
- add_finding "sync.unverified" "unverified" "Repo=$multirepo_repo has no readable regular CHANGELOG.md version source." "$multirepo_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
- validate_reference "$multirepo_contract" "$multirepo_map_path" || multirepo_contract_rc=$?
576
- if [ "$multirepo_contract_rc" -ne 0 ]; then
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 多仓版本一致: CHANGELOG.md ↔ $multirepo_contract (deployment=n/a)"
700
+ echo " ✅ $multirepo_repo 多仓版本一致: $multirepo_changelog ↔ $multirepo_contract (deployment=n/a)"
676
701
  else
677
- echo " ✅ $multirepo_repo 多仓版本一致: CHANGELOG.md ↔ $multirepo_contract ↔ $multirepo_baseline#apps.$multirepo_deployment.imageTag"
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"
@@ -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