@haiyangbg/buildbeat 0.0.0 → 1.21.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 (80) hide show
  1. package/CHANGELOG.md +304 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +290 -0
  4. package/README.md +285 -4
  5. package/SKILL.md +334 -0
  6. package/bin/buildbeat.js +5 -0
  7. package/bin/solobaton.js +6 -0
  8. package/docs/CAPABILITY-MATRIX.md +50 -0
  9. package/docs/CHECKS.md +326 -0
  10. package/docs/CLI-PILOT-2026-08-23.md +25 -0
  11. package/docs/CLI-STRATEGY-2026-08.md +55 -0
  12. package/docs/CLI.md +233 -0
  13. package/docs/EXECUTION-PLAN.md +487 -0
  14. package/docs/LEGACY-V1.16-MIGRATION.md +54 -0
  15. package/docs/PHASE1-PILOT-2026-08-24.md +32 -0
  16. package/docs/PHASE2-BUILDBEAT-PILOT-2026-08-25.md +75 -0
  17. package/docs/PHASE2-PILOT-2026-08-25.md +88 -0
  18. package/docs/PHASE2-PILOT-PREFLIGHT-2026-08-25.md +42 -0
  19. package/docs/PHASE4-STABILITY-AUDIT-2026-08-25.md +35 -0
  20. package/docs/PHASE4-V1.20-PILOT-2026-08-25.md +56 -0
  21. package/docs/RELEASING.md +117 -0
  22. package/docs/ROADMAP.md +872 -0
  23. package/docs/WP4.3-RELEASE-EVIDENCE-2026-08-25.md +73 -0
  24. package/example/.buildbeat/manifest.json +45 -0
  25. package/example/AGENTS.md +19 -0
  26. package/example/ARCHITECTURE.md +39 -0
  27. package/example/BUILDBEAT.md +17 -0
  28. package/example/CLAUDE.md +7 -0
  29. package/example/README.md +75 -0
  30. package/example/contracts/PROTOCOL.md +38 -0
  31. package/example/pm/NOW.md +22 -0
  32. package/example/pm/adr/ADR-0001-local-first-sqlite.md +25 -0
  33. package/example/pm/adr/README.md +7 -0
  34. package/example/pm/archive//344/270/200/346/234/237/evidence/gate1.md +5 -0
  35. package/example/pm/archive//344/270/200/346/234/237/evidence/gate2.md +5 -0
  36. package/example/pm/archive//344/270/200/346/234/237/evidence/gate3.md +5 -0
  37. package/example/pm/archive//344/270/200/346/234/237/evidence/gate4.md +5 -0
  38. package/example/pm/archive//344/270/200/346/234/237/evidence/implementation.md +5 -0
  39. package/example/pm/decisions.md +20 -0
  40. package/example/pm/status//344/272/247/345/223/201.md +20 -0
  41. package/example/pm/status//345/205/250/346/240/210.md +15 -0
  42. package/example/pm/status//346/265/213/350/257/225.md +15 -0
  43. package/example/pm//344/270/200/346/234/237-/347/234/213/346/235/277.md +97 -0
  44. package/example/standards/CODE.md +18 -0
  45. package/example/standards/DESIGN.md +34 -0
  46. package/example/standards/REVIEW.md +16 -0
  47. package/example/standards/STACK.md +31 -0
  48. package/lessons.md +127 -0
  49. package/package.json +48 -7
  50. package/src/cli.js +323 -0
  51. package/src/constants.js +199 -0
  52. package/src/doctor.js +267 -0
  53. package/src/planner.js +251 -0
  54. package/src/project.js +839 -0
  55. package/src/upgrader.js +1249 -0
  56. package/src/writer.js +534 -0
  57. package/templates/.claude/agents/reviewer.md +62 -0
  58. package/templates/AGENTS.md +85 -0
  59. package/templates/ARCHITECTURE.md +50 -0
  60. package/templates/BUILDBEAT.md +13 -0
  61. package/templates/CLAUDE.md +7 -0
  62. package/templates/contracts/PROTOCOL.md +32 -0
  63. package/templates/gitignore.template +19 -0
  64. package/templates/pm/NOW.md +26 -0
  65. package/templates/pm/adr/ADR-0000-template.md +25 -0
  66. package/templates/pm/adr/README.md +15 -0
  67. package/templates/pm/changes/README.md +44 -0
  68. package/templates/pm/decisions.md +12 -0
  69. package/templates/pm/status/README.md +32 -0
  70. package/templates/pm//345/275/223/346/234/237/347/234/213/346/235/277.md +62 -0
  71. package/templates/scripts/bus-check.sh +1850 -0
  72. package/templates/scripts/design-preview.sh +44 -0
  73. package/templates/scripts/drift-check.sh +112 -0
  74. package/templates/scripts/pre-commit.sh +74 -0
  75. package/templates/scripts/verify-status.sh +105 -0
  76. package/templates/standards/CODE.md +23 -0
  77. package/templates/standards/DESIGN.md +36 -0
  78. package/templates/standards/REVIEW.md +20 -0
  79. package/templates/standards/STACK.md +37 -0
  80. package/templates//346/214/207/346/214/245/345/217/260.md +58 -0
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bash
2
+ # design-preview.sh —— Gate2 真渲染拍板(总线规则⑩)。
3
+ # 把 design/design_<N>期/ 起成本地静态服务,用户在浏览器点过关键流之后再拍板;
4
+ # 静态稿 / 截图不充当拍板对象。设计走查复核时同样可用。
5
+ # 本脚本按自身位置定位协调层根,放 <根>/scripts/ 或 <根>/pm/scripts/ 均可(紧凑布局见 SKILL §3);下文命令示例按默认布局写。
6
+ # 用法:
7
+ # bash scripts/design-preview.sh 1 # 渲染 design/design_1期 → http://localhost:8799
8
+ # bash scripts/design-preview.sh 2 8801 # 指定端口
9
+ # bash scripts/design-preview.sh design_2期 # 也接受完整目录名
10
+ set -uo pipefail
11
+ # 协调层根 = 向上最近的含 pm/NOW.md 的目录(设计稿在 <根>/design/);SDIR 只用于把用法提示打成实际路径。见 bus-check.sh 同段注释。
12
+ _sd="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ ROOT="$_sd"; for _ in 1 2 3 4; do [ -f "$ROOT/pm/NOW.md" ] && break; ROOT="$(dirname "$ROOT")"; done
14
+ [ -f "$ROOT/pm/NOW.md" ] || ROOT="$(cd "$_sd/.." && pwd)"
15
+ SDIR="${_sd#"$ROOT"/}"; [ "$SDIR" = "$_sd" ] && SDIR="."
16
+
17
+ N="${1:?用法: bash $SDIR/design-preview.sh <期号|目录名> [端口,默认 8799]}"
18
+ PORT="${2:-8799}"
19
+
20
+ DIR="$ROOT/design/design_${N}期"
21
+ [ -d "$DIR" ] || DIR="$ROOT/design/$N"
22
+ if [ ! -d "$DIR" ]; then
23
+ echo "✗ 找不到设计目录: design/design_${N}期 或 design/$N" >&2
24
+ echo " 现有:"
25
+ existing_n=0
26
+ for existing in "$ROOT"/design/design_*; do
27
+ [ -d "$existing" ] || continue
28
+ printf ' - %s\n' "${existing#"$ROOT"/}"
29
+ existing_n=$((existing_n + 1))
30
+ done
31
+ [ "$existing_n" -gt 0 ] || echo " (无)"
32
+ exit 1
33
+ fi
34
+
35
+ ENTRY=""
36
+ for candidate in "$DIR"/*.html; do
37
+ [ -f "$candidate" ] || continue
38
+ ENTRY="$candidate"
39
+ break
40
+ done
41
+ echo "▸ 渲染 ${DIR#"$ROOT"/} → http://localhost:$PORT/"
42
+ [ -n "${ENTRY:-}" ] && echo "▸ 入口: http://localhost:$PORT/$(basename "$ENTRY" | sed 's/ /%20/g')"
43
+ echo "▸ Gate2 拍板前:浏览器里把关键流点一遍(规则⑩)。Ctrl-C 停止。"
44
+ exec python3 -m http.server "$PORT" --directory "$DIR"
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env bash
2
+ # drift-check.sh —— 生产漂移检测(治「git ≠ 生产」与平台侧 env/secret 漂移,BuildBeat lessons.md 第 13 条)
3
+ # 比对「部署平台当前配置(env 指纹 + 镜像/版本 tag)」vs 基线快照 bus-baseline.json(与本脚本同目录)。
4
+ # 本脚本按自身位置定位协调层根,放 <根>/scripts/ 或 <根>/pm/scripts/ 均可(紧凑布局见 SKILL §3);下文命令示例按默认布局写。
5
+ # ⚠ 能力边界:检测「平台配置 vs 基线」——把"配置被改"暴露出来,逼"改完即确认部署 + 刷基线";
6
+ # 不检测「running 容器 vs 平台配置」(配置改了没重新部署、容器跑旧值),后者需平台运行时 API,各项目自行增强。
7
+ # 🔴 红线:env value 只在内存进 sha256,**绝不落盘 / 绝不打印**;基线与输出只有 key 名 + 指纹。
8
+ # 诚实边界:高熵 secret 的指纹不可逆;低熵值(true/false/端口/开关类)key 名已知,可被字典猜出——
9
+ # 基线 bus-baseline.json 按半敏感文件对待:入私仓可以,勿公开传播。
10
+ #
11
+ # 项目接入点(必需):live-config.sh <app名>(与本脚本同目录)—— 自行调用部署平台 CLI,stdout 按行输出:
12
+ # tag <镜像tag或版本号> (可选,一行)
13
+ # env <KEY>=<VALUE> (每个环境变量一行;VALUE 只经内存管道,本脚本只留指纹)
14
+ # 查询失败请返回非 0,别输出半截结果。
15
+ # 用法:
16
+ # bash scripts/drift-check.sh # 检测(与 bus-check 同目录时被自动调用)
17
+ # bash scripts/drift-check.sh --update-baseline # 刷新基线(改 env/secret 或部署后跑 = 「确认已部署」动作)
18
+ # BUS_CHECK_NO_LIVE=1 ... # 跳过线上查询
19
+ # 退出码:0 = 无漂移/跳过/无法判定;1 = --update-baseline 失败(基线保护);2 = 确凿检出漂移(bus-check --strict 据此拦)
20
+ set -uo pipefail
21
+ # 协调层根 = 向上最近的含 pm/NOW.md 的目录;SDIR = 本脚本目录(相对根)。见 bus-check.sh 同段注释。
22
+ _sd="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23
+ ROOT="$_sd"; for _ in 1 2 3 4; do [ -f "$ROOT/pm/NOW.md" ] && break; ROOT="$(dirname "$ROOT")"; done
24
+ [ -f "$ROOT/pm/NOW.md" ] || ROOT="$(cd "$_sd/.." && pwd)"
25
+ SDIR="${_sd#"$ROOT"/}"; [ "$SDIR" = "$_sd" ] && SDIR="."
26
+ cd "$ROOT" || exit 1
27
+ BASELINE="$SDIR/bus-baseline.json"
28
+ UPDATE=0; [ "${1:-}" = "--update-baseline" ] && UPDATE=1
29
+
30
+ # 监控应用:app名|代码子仓(镜像tag↔git tag 锚定:在子仓 git 里查 v<tag> 是否存在;无 tag 规范填 - 跳过)
31
+ APPS=(
32
+ "<应用1>|<代码子仓1>"
33
+ "<应用2>|-"
34
+ )
35
+
36
+ if command -v sha256sum >/dev/null 2>&1; then SHACMD="sha256sum"; else SHACMD="shasum -a 256"; fi
37
+
38
+ # 拉某 app 当前快照 {imageTag, perKeyFp};value 只进 sha256 前 10 位;失败返回非 0
39
+ _snapshot(){
40
+ local name="$1" out imgtag perkey
41
+ out=$(bash "$SDIR/live-config.sh" "$name" 2>/dev/null) || return 1
42
+ [ -z "$out" ] && return 1
43
+ imgtag=$(printf '%s\n' "$out" | awk '$1=="tag"{print $2; exit}')
44
+ perkey=$(printf '%s\n' "$out" | grep '^env ' | sed 's/^env //' \
45
+ | while IFS='=' read -r k v; do printf '%s\t%s\n' "$k" "$(printf '%s' "$k=$v" | $SHACMD | cut -c1-10)"; done \
46
+ | jq -R -s -c 'split("\n")|map(select(length>0)|split("\t")|{(.[0]):.[1]})|add // {}')
47
+ [ -z "$perkey" ] && perkey='{}' # env 空/解析失败→兜底{},不误报"查询失败"
48
+ jq -cn --arg t "${imgtag:-}" --argjson pk "$perkey" '{imageTag:$t, perKeyFp:$pk}'
49
+ }
50
+
51
+ if [ "${BUS_CHECK_NO_LIVE:-0}" = "1" ] || [ ! -f "$SDIR/live-config.sh" ] || ! command -v jq >/dev/null 2>&1; then
52
+ echo " (漂移检测跳过:需 $SDIR/live-config.sh + jq + 不设 BUS_CHECK_NO_LIVE)"; exit 0
53
+ fi
54
+
55
+ # ── 刷新基线 ──
56
+ if [ "$UPDATE" = "1" ]; then
57
+ acc="{}"; fail=0
58
+ for pair in "${APPS[@]:-}"; do
59
+ [ -n "$pair" ] || continue
60
+ name="${pair%%|*}"
61
+ snap=$(_snapshot "$name") || { echo " ⚠️ $name 查询失败"; fail=1; continue; }
62
+ acc=$(printf '%s' "$acc" | jq -c --arg n "$name" --argjson s "$snap" '. + {($n):$s}')
63
+ done
64
+ if [ "$fail" = "1" ]; then
65
+ echo " ✗ 有应用查询失败 —— 基线未写入(保留旧基线);修好平台 CLI/凭据后重跑 --update-baseline"
66
+ exit 1
67
+ fi
68
+ printf '%s' "$acc" | jq --arg d "$(date +%Y-%m-%d)" --arg s "$SDIR" \
69
+ '{updated:$d, note:("🔴只存指纹不存value;改env/secret或部署后跑 " + $s + "/drift-check.sh --update-baseline"), apps:.}' \
70
+ > "$BASELINE"
71
+ echo " ✅ 基线已刷新 → $BASELINE"
72
+ exit 0
73
+ fi
74
+
75
+ # ── 检测 ──
76
+ if [ ! -f "$BASELINE" ]; then
77
+ echo " ⚠️ 无基线 → 先跑: bash $SDIR/drift-check.sh --update-baseline"; exit 0
78
+ fi
79
+
80
+ drift=0; checked=0
81
+ for pair in "${APPS[@]:-}"; do
82
+ [ -n "$pair" ] || continue
83
+ name="${pair%%|*}"; subrepo="${pair##*|}"
84
+ snap=$(_snapshot "$name") || { printf " %-16s (查询失败,跳过)\n" "$name"; continue; }
85
+ checked=1
86
+ imgtag=$(printf '%s' "$snap" | jq -r '.imageTag')
87
+ base_app=$(jq -c --arg n "$name" '.apps[$n] // empty' "$BASELINE" 2>/dev/null)
88
+ if [ -z "$base_app" ]; then printf " ⚠️ %-16s 基线无此应用 → --update-baseline\n" "$name"; drift=1; continue; fi
89
+ base_tag=$(printf '%s' "$base_app" | jq -r '.imageTag // ""')
90
+ # env 漂移:并集 key 分类 +新增 -删除 ≠值变(只报 key 名,不报 value)
91
+ changed=$(jq -rn \
92
+ --argjson b "$(printf '%s' "$base_app" | jq '.perKeyFp')" \
93
+ --argjson c "$(printf '%s' "$snap" | jq '.perKeyFp')" \
94
+ '($b+$c|keys) | map(. as $k |
95
+ if $b[$k]==null then "+"+$k
96
+ elif $c[$k]==null then "-"+$k
97
+ elif $b[$k]!=$c[$k] then "≠"+$k
98
+ else empty end) | join(" ")')
99
+ msg=""
100
+ [ -n "$imgtag" ] && [ "$imgtag" != "$base_tag" ] && msg="$msg 镜像 $base_tag→$imgtag(新部署?)"
101
+ [ -n "$changed" ] && msg="$msg env:[$changed]"
102
+ gt=""
103
+ if [ "$subrepo" != "-" ] && [ -d "$subrepo/.git" ] && [ -n "$imgtag" ]; then
104
+ # 锚定假设「镜像 tag X ↔ git tag vX」;你的 tag 规范不同就改这一行
105
+ git -C "$subrepo" rev-parse "v$imgtag" >/dev/null 2>&1 || gt=" 🏷git 无 v$imgtag(git≠生产?)"
106
+ fi
107
+ if [ -n "$msg" ] || [ -n "$gt" ]; then printf " ⚠️ %-16s%s%s\n" "$name" "$msg" "$gt"; drift=1
108
+ else printf " ✅ %-16s 配置/镜像==基线 (tag %s)\n" "$name" "${imgtag:-?}"; fi
109
+ done
110
+ if [ "$checked" = "0" ]; then echo " ⚠️ 没有成功查到任何应用(APPS 为空或全部查询失败)—— 无法判定漂移,检查 APPS 配置/平台 CLI/凭据"; exit 0
111
+ elif [ "$drift" = "0" ]; then echo " —— 无漂移"; exit 0
112
+ else echo " ⚠️ 有漂移 → 配置改了是否已重新部署?新部署是否打 tag + 跑 --update-baseline?"; exit 2; fi
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env bash
2
+ # pre-commit.sh —— 红线机器闸。规则不能只靠自觉:每条规则问一句「违反了会怎样」,答案得是「拦下来」。
3
+ # 闸①:凭据不入 git(红线1)—— gitleaks 扫暂存区,报警即拦;未装 gitleaks 只警告不拦(装:brew install gitleaks)。
4
+ # 闸②:协调层可信 —— bus-check --strict 检出任一 conflict/error finding 即拦(离线模式,不查线上;仅 meta 仓生效,子仓自动跳过)。
5
+ # 闸③:状态分写(规则⑦)—— 一次 commit 暂存 ≥2 个域的 status 文件即拦(单会话=单域,不该同时写别人的;
6
+ # 换期压缩仪式例外:连同 pm/archive/ 一起提交即放行,或 BUS_RITUAL=1 git commit)。
7
+ # 闸④:不批量 stage(红线2)—— 暂存文件数 > BUS_MAX_STAGED(默认 40)即拦,像 `git add -A` 的手笔;
8
+ # 确属大重构:BUS_ALLOW_BULK=1 git commit 放行一次。
9
+ # 闸⑤:契约先落盘(规则②,仅提醒不拦)—— 暂存文件名疑似接口边界(route/controller/api/schema/proto)时,
10
+ # 提醒检查 PROTOCOL.md 是否同步;契约在 meta 仓、代码在子仓,跨仓无法原子核验,故不拦以免误伤。
11
+ # 装法(meta 仓 + 各代码子仓,每仓各装一次;子仓从 meta 仓拷同一份即可):
12
+ # cp scripts/pre-commit.sh .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
13
+ # (紧凑布局下脚本在 pm/scripts/,拷贝源路径随之改;闸②两种布局都能自己找到 bus-check)
14
+ # (钩子想进版本控制可改用:git config core.hooksPath <钩子目录>,克隆后一次 config 即带)
15
+ # 配套:红线3 已禁 --no-verify,这道闸绕不过去;bus-check 会自检「闸装了没」。
16
+ set -uo pipefail
17
+ cd "$(git rev-parse --show-toplevel)" || exit 1
18
+
19
+ # ── 闸①:gitleaks(v8.19+ 用 `gitleaks git`,旧版退回 `protect`)──
20
+ if command -v gitleaks >/dev/null 2>&1; then
21
+ if gitleaks git -h >/dev/null 2>&1; then
22
+ gitleaks git --staged --no-banner --redact >/dev/null 2>&1
23
+ else
24
+ gitleaks protect --staged --no-banner --redact >/dev/null 2>&1
25
+ fi || { echo "⛔ gitleaks 报警:疑似凭据进入暂存区,已拦截。看细节重跑:gitleaks git --staged(确认误报则登记 .gitleaksignore)"; exit 1; }
26
+ else
27
+ echo "⚠️ 未装 gitleaks —— 红线1(凭据不入 git)当前没有机器闸,只剩自觉。装:brew install gitleaks"
28
+ fi
29
+
30
+ # ── 闸②:bus-check --strict(只有协调层仓同时有 pm/NOW.md 与 bus-check.sh;两种布局都认,见 SKILL §3)──
31
+ BC=""; for p in scripts/bus-check.sh pm/scripts/bus-check.sh; do [ -f "$p" ] && { BC="$p"; break; }; done
32
+ if [ -f pm/NOW.md ] && [ -n "$BC" ]; then
33
+ out=$(BUS_CHECK_NO_FETCH=1 BUS_CHECK_NO_LIVE=1 bash "$BC" --strict 2>&1) || {
34
+ printf '%s\n' "$out" | grep -E "⚠️|⛔"
35
+ echo "⛔ bus-check --strict 未过 —— 全量输出看:bash $BC"
36
+ exit 1
37
+ }
38
+ fi
39
+
40
+ STAGED=$(git -c core.quotepath=false diff --cached --name-only) # quotepath=false:中文文件名按原样输出,否则被引号+八进制转义,规则匹配不上
41
+
42
+ # ── 闸③:状态分写(规则⑦)—— 一次 commit 只该动一个域的 status ──
43
+ st_count=$(printf '%s\n' "$STAGED" | grep -c '^pm/status/.*\.md$' 2>/dev/null | tr -d ' ')
44
+ st_count=${st_count:-0}
45
+ if [ "$st_count" -ge 2 ] && [ "${BUS_RITUAL:-0}" != "1" ]; then
46
+ # 排除 status/README.md 自身;换期压缩仪式(连同 archive/ 提交)放行
47
+ real=$(printf '%s\n' "$STAGED" | awk '/^pm\/status\/.*\.md$/ && $0 !~ /README\.md$/ {n++} END {print n+0}')
48
+ has_archive=$(printf '%s\n' "$STAGED" | grep -c '^pm/archive/' 2>/dev/null | tr -d ' ')
49
+ if [ "${real:-0}" -ge 2 ] && [ "${has_archive:-0}" = "0" ]; then
50
+ echo "⛔ 一次 commit 暂存了 $real 个域的 status(规则⑦:各域只写自己的)—— 分开提交;换期压缩仪式请连同 pm/archive/ 一起提交(或 BUS_RITUAL=1 git commit)"
51
+ exit 1
52
+ fi
53
+ fi
54
+
55
+ # ── 闸④:不批量 stage(红线2)——像 git add -A 的手笔即拦 ──
56
+ staged_n=$(printf '%s\n' "$STAGED" | grep -c . | tr -d ' ')
57
+ MAXN="${BUS_MAX_STAGED:-40}"
58
+ if [ "${staged_n:-0}" -gt "$MAXN" ] && [ "${BUS_ALLOW_BULK:-0}" != "1" ]; then
59
+ echo "⛔ 暂存了 $staged_n 个文件(>$MAXN)—— 像 \`git add -A\`(红线2:只 stage 自己域的具体文件,多仓分别提交);确属大重构:BUS_ALLOW_BULK=1 git commit"
60
+ exit 1
61
+ fi
62
+
63
+ # ── 闸⑤:契约先落盘(规则②,仅提醒)——只看**提供方**(Controller/路由/schema 定义) ──
64
+ # 消费方(src/api/ 等客户端调用层、前端页面 router)是「跟随契约」不是「改契约」,不提醒——
65
+ # 宽匹配在真实仓回放中 59% 提交误响,常驻红字=没有红字(评估 D2)。按项目调下面两个 env;
66
+ # 更精的收法(自行升级):只匹配 diff 内容里新增/删除的路由定义与 DTO 字段,或只在重轨提醒。
67
+ CONTRACT_HINT="${BUS_CONTRACT_HINT:-controller|endpoint|schema|\.proto|routes?/|router\.(go|py|rb|php|java|kt)}"
68
+ CONTRACT_SKIP="${BUS_CONTRACT_SKIP:-(^|/)api(s)?/|(^|/)src/router/|/client(s)?/|request}"
69
+ hint_hits=$(printf '%s\n' "$STAGED" | grep -iE "$CONTRACT_HINT" | grep -viE "$CONTRACT_SKIP" || true)
70
+ if [ -n "$hint_hits" ] && ! printf '%s\n' "$STAGED" | grep -q 'PROTOCOL\.md'; then
71
+ echo "⚠️ 暂存文件疑似动了接口**提供方**($(printf '%s\n' "$hint_hits" | head -1) 等)—— 跨边界行为变了的话,先改 contracts/PROTOCOL.md 再动代码(规则②;此为提醒不拦截)"
72
+ fi
73
+
74
+ exit 0
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env bash
2
+ # verify-status.sh —— 工程层验证能力(证据分级 L3「自动化测试过」的兜底;bus-check §2.7 自动调用)
3
+ # 本脚本按自身位置定位协调层根,放 <根>/scripts/ 或 <根>/pm/scripts/ 均可(紧凑布局见 SKILL §3);下文命令示例按默认布局写。
4
+ # 三种用法:
5
+ # bash scripts/verify-status.sh # 打印各套件状态:套件 | 命令 | 上次全绿时间(bus-check 调的是这个)
6
+ # bash scripts/verify-status.sh --run # 逐套件真跑;全绿的套件记录「上次全绿时间」到标记文件
7
+ # bash scripts/verify-status.sh --format=machine
8
+ # # 给 bus-check 返回稳定 TSV finding,不打印人类文案
9
+ # 接入:改 SUITES 数组即可(名称|命令;命令里自己 cd 进子仓)。标记文件 .last-green-<套件>(与本脚本同目录)
10
+ # 是本地实查产物,不入 git(gitignore.template 已排除)——新机器上显示"从未全绿"是诚实的:你确实没在这台机器跑过。
11
+ set -uo pipefail
12
+ # 协调层根 = 向上最近的含 pm/NOW.md 的目录;SDIR = 本脚本目录(相对根)。见 bus-check.sh 同段注释。
13
+ _sd="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ ROOT="$_sd"; for _ in 1 2 3 4; do [ -f "$ROOT/pm/NOW.md" ] && break; ROOT="$(dirname "$ROOT")"; done
15
+ [ -f "$ROOT/pm/NOW.md" ] || ROOT="$(cd "$_sd/.." && pwd)"
16
+ SDIR="${_sd#"$ROOT"/}"; [ "$SDIR" = "$_sd" ] && SDIR="."
17
+ cd "$ROOT" || exit 1
18
+
19
+ # 套件表:名称|命令(示例:npm / mvn 各一,按项目替换;没有测试就留占位符,bus-check 会如实红字)
20
+ SUITES=(
21
+ "<套件1>|cd <代码子仓1> && npm test --silent"
22
+ "<套件2>|cd <代码子仓2> && mvn -q test"
23
+ )
24
+
25
+ RUN=0; FORMAT="human"
26
+ for arg in "$@"; do
27
+ case "$arg" in
28
+ --run) RUN=1 ;;
29
+ --format=machine) FORMAT="machine" ;;
30
+ *) echo "verify-status: unknown argument: $arg" >&2; exit 2 ;;
31
+ esac
32
+ done
33
+ [ "$RUN" = 0 ] || [ "$FORMAT" = "human" ] || {
34
+ echo "verify-status: --run cannot be combined with --format=machine" >&2
35
+ exit 2
36
+ }
37
+
38
+ emit_machine_finding() {
39
+ code="$1"; level="$2"; message="$3"; path="$4"
40
+ message="$(printf '%s' "$message" | tr '\t\r\n' ' ')"
41
+ path="$(printf '%s' "$path" | tr '\t\r\n' ' ')"
42
+ printf 'FINDING\t%s\t%s\t%s\t%s\n' "$code" "$level" "$message" "$path"
43
+ }
44
+
45
+ mark_epoch() {
46
+ value="$1"
47
+ if date -j -f '%Y-%m-%d %H:%M' "$value" '+%s' >/dev/null 2>&1; then
48
+ date -j -f '%Y-%m-%d %H:%M' "$value" '+%s'
49
+ elif date -d "$value" '+%s' >/dev/null 2>&1; then
50
+ date -d "$value" '+%s'
51
+ else
52
+ return 1
53
+ fi
54
+ }
55
+
56
+ n=0; ph_warned=0; failed=0
57
+ for pair in "${SUITES[@]:-}"; do
58
+ [ -n "$pair" ] || continue
59
+ case "$pair" in *"<"*)
60
+ if [ "$ph_warned" = 0 ] && [ "$FORMAT" = "human" ]; then
61
+ echo "⚠️ SUITES 还是占位符 —— 填入真实测试命令,L3 级证据才有兜底(没有测试?先补,见 SKILL §8.5 第 3 步)"
62
+ fi
63
+ ph_warned=1; continue;; esac
64
+ n=$((n+1))
65
+ name="${pair%%|*}"; cmd="${pair#*|}"
66
+ mark="$SDIR/.last-green-$name"
67
+ if [ "$RUN" = 1 ]; then
68
+ echo "▸ 跑 $name:$cmd"
69
+ if bash -c "$cmd"; then
70
+ date '+%Y-%m-%d %H:%M' > "$mark"; echo "✅ $name 全绿 → 已记 $(cat "$mark")"
71
+ else
72
+ echo "❌ $name 未过 —— 修完再跑 --run 刷新「上次全绿」"
73
+ failed=$((failed + 1))
74
+ fi
75
+ elif [ "$FORMAT" = "human" ]; then
76
+ last="从未全绿(或没在本机跑过)"; [ -f "$mark" ] && last="$(cat "$mark")"
77
+ printf '%s | %s | 上次全绿:%s\n' "$name" "$cmd" "$last"
78
+ else
79
+ max_age_days="${BUS_L3_MAX_AGE_DAYS:-7}"
80
+ case "$max_age_days" in ''|*[!0-9]*) max_age_days=7 ;; esac
81
+ if [ ! -f "$mark" ]; then
82
+ emit_machine_finding "sync.l3_stale" "warning" "Configured L3 suite '$name' has no local all-green marker." "$mark"
83
+ continue
84
+ fi
85
+ last="$(head -1 "$mark" 2>/dev/null || true)"
86
+ if ! last_epoch="$(mark_epoch "$last")"; then
87
+ emit_machine_finding "sync.l3_stale" "warning" "Configured L3 suite '$name' has an unreadable all-green marker." "$mark"
88
+ continue
89
+ fi
90
+ now_epoch="$(date '+%s')"
91
+ age_days=$(( (now_epoch - last_epoch) / 86400 ))
92
+ if [ "$age_days" -gt "$max_age_days" ]; then
93
+ emit_machine_finding "sync.l3_stale" "warning" "Configured L3 suite '$name' is stale ($age_days days > $max_age_days days)." "$mark"
94
+ fi
95
+ fi
96
+ done
97
+ if [ "$n" = 0 ]; then
98
+ if [ "$FORMAT" = "machine" ]; then
99
+ emit_machine_finding "sync.l3_unconfigured" "unverified" "No real L3 suite is configured." "$SDIR/verify-status.sh"
100
+ elif [ "$RUN" = 1 ]; then
101
+ echo "(没有可跑的套件)"
102
+ fi
103
+ fi
104
+ [ "$failed" -eq 0 ] || exit 1
105
+ exit 0
@@ -0,0 +1,23 @@
1
+ # CODE.md — <项目名> 代码与安全规范
2
+
3
+ > **Optional**: 本文件由项目拥有;缺失时 BuildBeat 直接跳过,不作为告警或错误。
4
+ > **AI write boundary**: 默认只读;普通需求、修构建或装依赖不得顺手改规范,只有用户明确要求或批准规范变更时才可修改。
5
+ > **Status**: Draft
6
+
7
+ ## 项目约定
8
+
9
+ - 代码组织与命名:<代码组织与命名约定>
10
+ - 项目特有禁止事项:<项目特有禁止事项>
11
+
12
+ ## Rules
13
+
14
+ - `CODE-MUST-001`: Secret、访问令牌、真实身份数据和生产配置值不得进入 Git、日志、测试夹具或交付证据。
15
+ - `CODE-MUST-002`: 鉴权、租户隔离、输入校验、持久化和不可逆副作用必须 fail-closed,并有对应测试或显式未验证边界。
16
+ - `CODE-MUST-003`: 公共接口、数据模型或兼容性语义变化必须先同步契约;不得用实现细节偷偷改写已冻结行为。
17
+ - `CODE-MUST-004`: 新依赖必须核对来源、许可证、锁文件和已知安全风险;不得绕过项目既有供应链检查。
18
+ - `CODE-SHOULD-001`: 错误处理应保留可定位上下文,但不得回显 Secret、个人数据或内部凭据值。
19
+ - `CODE-MAY-001`: 不改变外部语义的局部风格改进可随工作包完成,但不得制造与现有代码库不一致的新体系。
20
+
21
+ ## 偏离规则
22
+
23
+ MUST 偏离必须在 Gate 前形成真实决策;SHOULD 偏离需在 Review 证据中写明理由;MAY 不构成阻断项。
@@ -0,0 +1,36 @@
1
+ # DESIGN.md — <项目名> UI / 视觉 / 交互规范
2
+
3
+ > **Optional**: 仅有 UI、视觉或交互交付的项目按需创建;缺失时 BuildBeat 直接跳过,不作为告警或错误。
4
+ > **AI write boundary**: 默认只读;普通页面实现不得顺手重写设计系统,只有用户明确批准设计语言变化或项目首次确认本规范时才可修改。
5
+ > **Status**: Draft
6
+
7
+ ## Principles
8
+
9
+ <设计原则>
10
+
11
+ ## Tokens
12
+
13
+ <排版 / 色彩 / 间距 token 来源>
14
+
15
+ ## Components
16
+
17
+ <核心组件与复用边界>
18
+
19
+ ## Interaction Patterns
20
+
21
+ - `DESIGN-MUST-001`: 关键操作提供即时、明确且可访问的反馈;不得只靠颜色区分状态。
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
+ <项目特有例外>
35
+
36
+ Gate2 与终签以真渲染可点结果为准;静态稿或规范数值不能替代实际走查。
@@ -0,0 +1,20 @@
1
+ # REVIEW.md — <项目名> Review 规范
2
+
3
+ > **Optional**: 本文件由项目拥有;缺失时 BuildBeat 直接跳过,不作为告警或错误。
4
+ > **AI write boundary**: 默认只读;只在项目 Review 口径被明确改变时修改,不用它记录成员、岗位、响应 SLA 或审批人。
5
+ > **Status**: Draft
6
+
7
+ ## 最小检查维度
8
+
9
+ - `REVIEW-MUST-001`: 核对规格、非目标、验收条件与实现行为,不以“测试绿”替代需求覆盖。
10
+ - `REVIEW-MUST-002`: 核对契约、架构、数据与兼容性变化是否同步,并明确不可外推的未验证范围。
11
+ - `REVIEW-MUST-003`: 核对受影响自动化测试、真渲染走查和 evidence;完成声明必须可追溯到候选与证据。
12
+ - `REVIEW-MUST-004`: 核对 Secret、鉴权、租户、输入、持久化、依赖与不可逆副作用风险。
13
+ - `REVIEW-SHOULD-001`: 识别不必要复杂度、重复抽象、不可维护分支和缺少回滚路径的设计。
14
+ - `REVIEW-SHOULD-002`: 核对看板、status、decisions 与交付候选一致,不把旧报告复用于变化后的候选。
15
+
16
+ ## 项目增量
17
+
18
+ <项目特有 Review 条件>
19
+
20
+ review-ready、milestone、risk-delta 与 closure 的触发节奏仍以 `AGENTS.md` 为准,本文件不新建第二套流程。
@@ -0,0 +1,37 @@
1
+ # STACK.md — <项目名> 技术栈约束
2
+
3
+ > **Optional**: 本文件由项目拥有;缺失时 BuildBeat 直接跳过,不作为告警或错误。
4
+ > **AI write boundary**: 默认只读;首次 Bootstrap 只能依据可观测事实起草,用户确认或明确要求技术栈变化后才可修改已确认内容。
5
+ > **Status**: Draft
6
+
7
+ ## 声明
8
+
9
+ | 维度 | 项目约束 | 事实来源 |
10
+ |---|---|---|
11
+ | Runtime | <运行时及版本约束> | 版本文件 / 包清单 / CI |
12
+ | 包管理器 | <包管理器及 lockfile> | lockfile / 包清单 |
13
+ | 语言与框架 | <主要语言与框架> | 依赖与源码入口 |
14
+ | 数据设施 | <数据库 / 缓存 / 消息设施> | 配置 schema / 部署配置 |
15
+ | 部署 | <部署平台 / 容器基线> | Dockerfile / 平台配置 |
16
+ | CI 与测试 | <CI 与测试命令> | CI workflow / 测试配置 |
17
+ | 供应链 | <许可证 / 供应链约束> | LICENSE / lockfile / 安全策略 |
18
+
19
+ ## 可核对基线(bus-check v1)
20
+
21
+ 下面的注释块是仓库事实的精确比对入口,不从上表自然语言猜值。每个键至少一行;有多个已观测值时重复该键;确认不适用时将唯一值填为 n/a。Node 值逐字对应 .nvmrc 或 package.json 的 engines.node;lockfile 填文件名;容器填 Dockerfile FROM 的镜像令牌。
22
+
23
+ <!-- buildbeat-stack-baseline:v1
24
+ nodeConstraint=<.nvmrc / engines.node 的精确值;多值重复本行;无则 n/a>
25
+ lockfileKind=<lockfile 文件名;多类重复本行;无则 n/a>
26
+ dockerFromImage=<Dockerfile FROM 镜像;多值重复本行;无则 n/a>
27
+ -->
28
+
29
+ ## Rules
30
+
31
+ - `STACK-MUST-001`: 声明状态与 package、lockfile、版本文件、容器和部署配置等可观测状态冲突时,只报告漂移,不自动改代码或本文件。
32
+ - `STACK-MUST-002`: 更换核心运行时、框架、数据库、包管理器或部署平台前,必须建立 ADR,并同步 `contracts/PROTOCOL.md` 中受影响的跨边界事实。
33
+ - `STACK-SHOULD-001`: 版本约束应尽量指向一个可重复核对的仓库事实,无法确认的内容保留为 Draft,不靠猜测补齐。
34
+
35
+ ## 变更记录
36
+
37
+ 确认后将头部 Status 改为 `Confirmed`;后续变化在 `pm/decisions.md` 留索引,满足 ADR 判据时同时新增 ADR。
@@ -0,0 +1,58 @@
1
+ # 指挥台 — Builder 怎么驱动工作包内的 AI 视角
2
+
3
+ > 忘了怎么开场就看这页。先从看板认领一个端到端工作包;同一个 Builder 对它从判断、实现、测试一直负责到合并/发布证据。每个 session 一开就自动读 `AGENTS.md` + `pm/NOW.md`,你基本只需说「**开工 / 接着做 / 当前工作包该你这个视角了**」,剩下它自驱。
4
+ > **每个 session 第一次对话只设定 AI 视角**:「你是当前工作包的产品/全栈/测试视角,负责……(照 `AGENTS.md` §1 表说)」;这不是人类岗位或成员分配。
5
+
6
+ | AI 视角 Session | 在哪开(cwd) | 你的一句话 | 它在当前工作包内自动做啥 |
7
+ |---|---|---|---|
8
+ | **产品** | `pm/` | 「把需求X拆工单」/「验收X」 | 定一个覆盖多个子项的工作包、维护决策收件箱、更看板、按包记 decisions、收敛候选 hash 集;review-ready 后派一次 reviewer |
9
+ | **全栈** | 工作区根 | 「看板上需求X该你了,开工」 | 读工作包+契约 → 在范围内持续实现 → 每提交过机器闸 → 只记实现语义 delta → 工作包/里程碑时状态带 hash + CHANGELOG |
10
+ | **测试** | `<被测仓>/` | 「跑X的E2E + 走查设计」 | 黑盒 E2E + 带图设计走查 → 直接提 bug → 给里程碑核查提供证据 |
11
+
12
+ ## 工作包闭环
13
+ 同一个 Builder 对这一整条负责:1. **产品视角**收敛规格 + 人拍板 **⛔Gate1** → 2. 产品视角写设计 brief(要求单 HTML 可渲染)→ 设计工具出稿 → **真渲染后人拍板 ⛔Gate2** → 3. **全栈视角**实现(先改 PROTOCOL、每提交过机器闸、写者自发现问题先收敛;只有改冻结对外语义/不可逆副作用才提前 `risk-delta`)→ 4. **测试视角** E2E + 带图走查 → review-ready 后 milestone reviewer 静默全核**一次** → P0/P1 合并修完后 closure **一次** → **⛔Gate3 人确认合并** → 5. 部署候选 → **⛔Gate4 人确认上线** → 工作包收尾。多个 Builder 并行时各走自己的闭环,不把这条链拆成人类岗位流水线。
14
+
15
+ > **会话不按子任务交还接力棒**:每轮先认领 `objective / in_scope / terminal_condition`;文档、commit、reviewer、status 都可能只是包内步骤。范围内仍有安全工作就继续,不等你再说“继续”。
16
+ > **审批只分三档**:跨 Gate/扩范围/改冻结契约/不可逆外部动作/风险接受才 `STOP_NOW`;冻结前可逆选择进看板收件箱,到 Gate 默认一次批 2–5 个(确实只有 1 个就单项);事实、推导约束、归档/status/P2 等无需批。
17
+ > **人批预算**:每个工作包、每道 Gate 默认只发 1 个批量请求。你只答一部分或要解释时,会话保持同一决策包继续说明,不会重新包装成“第二次审批”;不阻塞的工作照常继续。
18
+ > 决策包收敛后,当前工作包的产品视角先落 `pm/decisions.md` 一行再回写各处;部分回答只更新收件箱,不为 `3/14 → 11/14 → 14/14` 制造三条台账。**线上是什么版本别问会话、别信文档,跑 `bash scripts/bus-check.sh` 看实况**。
19
+ > **三轨制**:小改快轨 / 单功能标准轨 / 契约变更重轨(`pm/changes/` 提案)。本期走哪轨看 `pm/NOW.md`。
20
+ > **核查门不按任务数收费**:首次 milestone 前不启动 reviewer;review-ready 后每工作包每 Gate 默认 1 次 milestone,P0/P1 合并修完后 1 次 closure,P2 不复核。reviewer 单次静默核完再返回,不播报中间 findings。同一 candidate 复用结论;返回前 hash 变化就标 `SUPERSEDED` 并停止,不得边改边 delta。
21
+ > **4 个 Gate 不可自动跨过**;reviewer 批准 ≠ 你合并。你只是节拍器,信息全在 repo 里流动,不用你搬运。
22
+
23
+ ## 域回复怎么写
24
+
25
+ 每个 AI 视角面向人收口或交接时,统一按「已做 → 未做 → 下一步」回复:
26
+
27
+ ```md
28
+ ## 〔当前域〕|✅ 已完成 / 🔄 未完成
29
+
30
+ ### 已做
31
+ 1. 〔功能或业务结果〕
32
+ - 证据:〔commit、测试结果或报告〕
33
+
34
+ ### 未做
35
+ 1. 〔还没完成或没验证什么〕
36
+ - 原因:〔具体原因〕
37
+
38
+ ### 下一步
39
+ - **本域已完成:** 下一棒是〔哪个域 / AI 视角〕,负责〔业务级目标〕。
40
+ - **本域未完成:** 需要〔谁〕提供或确认〔什么〕。
41
+ - **无需协助:** 我继续做,暂不交棒。
42
+ ```
43
+
44
+ `已做`只写功能/业务结果,证据紧跟对应事项;多项共用时在列表末写「共同证据」。`未做`要写原因,没有就写「无」。`下一步`只保留符合当前状态的一条;工作包已完成就写「下一棒:无」。没有真实阻塞就继续做,不把自己能解决的事包装成求助。完整口径见 `AGENTS.md` 的「域回复格式」。
45
+
46
+ ## 检查结果怎么读
47
+
48
+ 先跑 `bash scripts/bus-check.sh --strict`,再按级别读摘要;exit 0 只表示没有 `conflict/error`,不表示所有范围都核过。
49
+
50
+ | Level | 含义 | 怎么办 |
51
+ |---|---|---|
52
+ | `confirmed` | 一条事实已直接观测 | 只复用该事实,不外推全绿 |
53
+ | `warning` | 有风险/追溯弱点,但尚无确定矛盾 | 按 `path` 定位,受影响 Gate 前处理或明确挂账 |
54
+ | `unverified` | 工具没可靠核到这段范围 | 补证据/工具/权限后重跑,收尾必须列出 |
55
+ | `conflict` | 声明与事实矛盾或必需证据缺失 | 先修再重跑;strict 阻断 |
56
+ | `error` | 协议结构或检查过程不可信 | 先修格式/唯一性/检查器;strict 阻断 |
57
+
58
+ 常见处置:`sync.scan_truncated` 看 `reason=limit|symlink|permission`——先确认省略范围,不要为消警盲抬阈值;symlink 目标单独核或落成根内 regular file;权限只补最小读取/目录搜索能力。`sync.l3_*` 配真实套件或刷新证据;`gate.* / evidence.* / ref.*` 修 canonical 行与证据引用;`stack.drift / sync.multirepo_drift` 回权威来源逐项对齐,脚本不代改。JSON 中 `coverage.complete=false` 时,结论只能写“已覆盖部分 + 未验证边界”。