合并(闸件自检档·工程批): 纯闸件/管线批不再零触发——domain_route只含tools/.gitea批判gates(空diff仍skip,full/docs不动)+run_all_gates新增--gates档(文档域python机检件+熵增+静态扫描+闸壳bash -n自检12件,不启引擎,判据口径原样)+ci.yml档位映射三值化(full→空/docs→--docs/gates→--gates)与导入缓存步排除gates档; 闸壳自检入基线4行(用户认可)+并origin/master(c16a2bf7)零冲突后重出--gates绿(exit0,回执86d266a2); 本机全量档12绿+1红(settlement_wiring_snapshot golden平台绑定=在册7046cbaf遗留,非本批引入,远端Mac绿)+3闸影响面SKIP; 分流四例直跑full/docs/gates/skip全对; post-merge钩子推送触发远端CI(diff含baseline.json→全量档)
ci / gates (push) Successful in 5m22s

This commit is contained in:
DKChan committed 2026-11-05 04:34:04 +08:00
commit 7e7f737c7e
4 files changed
+82 -21

No files matched your search

+9 -4
View File
@@ -2,7 +2,8 @@
# 触发:master 收到 push(post-merge 钩子合回自动推,或人手推 master)——分支推送不触发
# (开发分支不烧远端引擎时长;分支级验证=开发期手动跑批档)。
# 跑什么由 tools/ci/domain_route.sh 按改动域分流(乙案)——
# 游戏码批=两域全量(含渲染闸);文档批=文档域秒级闸;纯闸件/管线批=零触发。
# 游戏码批=两域全量(含渲染闸);文档批=文档域秒级闸;纯闸件/管线批=闸件自检档
# (run_all_gates.sh --gates:不启引擎的机检面+闸壳自检)。
# 运行环境:Mac host 模式(tools/ci/mac_setup.sh 自举),GODOT_BIN/GDSTYLE_BIN 由自举件安装到位。
# 检出用纯 shell 克隆:host 模式执行 JS 系动作(actions/checkout)需要 node 运行时,
# runner 机器无 node——首跑实证「Cannot find: node in PATH」即败(run 4),本仓禁用 JS 系动作,
@@ -32,8 +33,8 @@ jobs:
cd "$GITHUB_WORKSPACE/repo"
bash tools/ci/domain_route.sh "${{ github.event.before }}" "${{ github.sha }}"
- name: 导入缓存刷新(闸前置——.godot 不入 git,冷检出一付)
if: steps.route.outputs.mode != 'skip'
- name: 导入缓存刷新(闸前置——.godot 不入 git,冷检出一付;gates 档不启引擎,跳过)
if: steps.route.outputs.mode != 'skip' && steps.route.outputs.mode != 'gates'
run: |
cd "$GITHUB_WORKSPACE/repo"
/usr/local/bin/godot --headless --path game --editor --quit
@@ -46,6 +47,10 @@ jobs:
run: |
cd "$GITHUB_WORKSPACE/repo"
MODE="${{ steps.route.outputs.mode }}"
if [ "$MODE" = "full" ]; then ARGS=""; else ARGS="--docs"; fi
case "$MODE" in
full) ARGS="" ;;
gates) ARGS="--gates" ;;
*) ARGS="--docs" ;;
esac
echo "[ci] 域=$MODE 跑闸参数=$ARGS"
bash tools/run_all_gates.sh $ARGS
+4
View File
@@ -60,6 +60,10 @@
"单元与集成测试": {
"exit": "0",
"fp": "green"
},
"闸壳自检": {
"exit": "0",
"fp": "green"
}
}
}
+5 -4
View File
@@ -1,13 +1,14 @@
#!/usr/bin/env bash
# 乙案域分流——push 触发的远端 CI 按改动域选闸(2026-11-03 用户裁)。
# 用法: tools/ci/domain_route.sh <base_sha> <head_sha>
# 输出: stdout 打印 full|docs|skip;GITHUB_OUTPUT 在位时另写 mode= 行(Actions 步骤输出)。
# 输出: stdout 打印 full|docs|gates|skip;GITHUB_OUTPUT 在位时另写 mode= 行(Actions 步骤输出)。
# 规则(唯一事实源,乙案语义):
# 1. diff 含 game/ 下非 .md 文件(行为载体:代码/场景/数据表/素材)→ full(两域全量,渲染闸随 code 域)
# 2. 否则 diff 非空(design/ 文档、reports/、根 md、game/ 下 .md 等一切非代码面)→ docs
# (乙案:文档批只跑文档域秒级闸——gdd_lint/doc_link/doc_term/pipeline/doc_ref/capability+熵增)
# 例外:diff 只含 tools/ 与 .gitea/(闸件与管线自身改动)→ skip——闸件改动不占合回全量,
# 质量义务=落批人工确认可跑通(run_all_gates.sh 头注既有口径,不在远端重复烧引擎)。
# 例外:diff 只含 tools/ 与 .gitea/(闸件与管线自身改动)→ gates——闸件自检档:跑不启引擎的
# 机检面(文档域 python 机检件+熵增闸+静态扫描+闸壳 bash -n 自检,见 run_all_gates.sh
# --gates),闸件改动不再零触发,也不在远端重复烧引擎。
# 3. diff 空 → skip。
# 判定面说明:game/tests/** 与 game/sim/** 属 game/ 行为载体(.md 除外)→ full,
# 与 run_all_gates.sh 关联档口径一致(测量面批单元与集成测试照跑全套件)。
@@ -28,7 +29,7 @@ else
mode="full"
else
non_tools="$(echo "$files" | grep -vE '^(tools/|\.gitea/)' || true)"
if [ -n "$non_tools" ]; then mode="docs"; else mode="skip"; fi
if [ -n "$non_tools" ]; then mode="docs"; else mode="gates"; fi
fi
fi
if [ -n "${GITHUB_OUTPUT:-}" ]; then printf 'mode=%s\n' "$mode" >> "$GITHUB_OUTPUT"; fi
+64 -13
View File
@@ -5,10 +5,11 @@
#
# 跑完写回执 game/tests/.gates/receipt.json(commit+内容树哈希+各闸退出码+存量基线比对+时间戳);
# 强制验证点=合回 master 后 post-merge 钩子推送触发的远端跑批;远端结果用 tools/ci_check.sh 现查(只呈报、
# 不落盘——2026-11-04 用户裁去掉回执等待件与本地判定缓存)。本脚本手动调用(--code/--docs/全量档)只服务开发期按域跑批。
# 不落盘——2026-11-04 用户裁去掉回执等待件与本地判定缓存)。本脚本手动调用(--code/--docs/--gates/全量档)
# 只服务开发期按域跑批;--gates 档另为远端纯闸件/管线批的执行形态(domain_route.sh 判 gates → ci.yml 映射 --gates)。
# 跑闸时代码面带着未提交改动,回执会标 dirty_game(验的不是 HEAD 字节)。
#
# 用法:tools/run_all_gates.sh [--code|--docs|--quick]
# 用法:tools/run_all_gates.sh [--code|--docs|--gates|--quick]
# --code 只跑代码域闸(冒烟测试/系统测试/单元与集成测试/场景探针/
# 渲染闸/静态扫描+熵增闸)
# ——本批改动含 game/ 下非 .md 的行为载体(代码/测试脚本/探针/场景/数据表/素材)时用。
@@ -20,11 +21,15 @@
# --docs 只跑文档域闸(gdd_lint/doc_link/doc_term/流水线闸/引用可核/
# 能力审计+熵增闸)
# ——本批改动只有文档件时用:game/ 内的 .md(README、生成清单)、design/、根规则文件、
# tools/ 工具脚本、.zcode/ 配置等一切非代码面改动。
# .zcode/ 配置等一切非代码面改动(tools/ 与 .gitea/ 只含批走 --gates 档,见下)。
# --gates 闸件自检档(2026-11-05 用户裁):只跑不启引擎的机检面——文档域 python 机检件
# (gdd_lint/变更记录节/doc_link/doc_term/流水线闸/引用可核/能力审计)+熵增闸
# +静态扫描+闸壳自检(bash -n 过全部 bash 闸件与管线件)。各机检件的命令与判据口径
# 与原档一致,不另造判据。本批改动只含 tools/ 与 .gitea/(闸件与管线自身)时用——
# 纯闸件/管线批合回后远端照跑本档(domain_route.sh 判 gates),闸件改动不再零验证。
# --quick 全量档的中段自检形态:跳过渲染闸(窗口化,需开显示器)与静态扫描;完成出库禁用。
# 不带参数 = 全量档(两域全跑),触发条件=同批既有代码面改动又有文档件改动。闸件(tools/ 下机检脚本
# 与闸壳)自身的改动不占合回全量——其质量义务=落批时人工确认闸可跑通且判据口径未变;
# 闸真崩溃时合回闸 fail-closed(跑不出回执即拦),不以全量为兜底。
# 不带参数 = 全量档(两域全跑),触发条件=同批既有代码面改动又有文档件改动。闸真崩溃时
# 合回闸 fail-closed(跑不出回执即拦),不以全量为兜底。
# **域划分只在本脚本里定义一处**——文档只写「按改动域跑本脚本」,禁另列闸清单(清单散落多处
# 正是既往漏跑场景探针扩展段的成因)。
#
@@ -51,17 +56,17 @@ DOMAIN="full"
while [ $# -gt 0 ]; do
case "$1" in
--quick) QUICK=1 ;;
--code|--docs)
[ "$DOMAIN" = "full" ] || { echo "用法: --code 与 --docs 互斥"; exit 2; }
--code|--docs|--gates)
[ "$DOMAIN" = "full" ] || { echo "用法: --code/--docs/--gates 互斥"; exit 2; }
DOMAIN="${1#--}" ;;
*) echo "用法: tools/run_all_gates.sh [--code|--docs|--quick]"; exit 2 ;;
*) echo "用法: tools/run_all_gates.sh [--code|--docs|--gates|--quick]"; exit 2 ;;
esac
shift
done
# 每个代码面 worktree 用独立 Godot user 目录(game/override.cfg 由 setup 脚本自愈生成;主区 no-op)。
# 并行批共用一套夹具、日志与截图目录会互相踩盘造成假红——隔离建不起来就拒跑(fail-closed)。
# 隔离只服务代码域(Godot 运行面);文档域不启引擎,跳过省 0.8 秒。
# 隔离只服务代码域(Godot 运行面);文档域与闸件自检档不启引擎,跳过省 0.8 秒。
if [ "$DOMAIN" = "code" ] || [ "$DOMAIN" = "full" ]; then
"$REPO/tools/setup_worktree_userdir.sh" "$REPO" || {
echo "[gates] FAIL — user 目录隔离未建立,拒绝在共享目录上跑闸。"
@@ -115,6 +120,8 @@ in_domain() { # in_domain <code|docs|both>
[ "$dom" = "both" ] && return 0
[ "$DOMAIN" = "full" ] && return 0
[ "$DOMAIN" = "$dom" ] && return 0
# gates 档跑文档域机检件——同一命令同一判据,闸件批不启引擎、机检面照跑。
[ "$DOMAIN" = "gates" ] && [ "$dom" = "docs" ] && return 0
return 1
}
@@ -396,6 +403,45 @@ gate_unit() { # run_all 是 --script 模式,退出码为唯一判据;分
return 0
}
# ══ 闸件自检段(gates 档专属)══
# 闸壳自检:bash -n 过全部 bash 闸件与管线件——闸件批改的就是这些件,语法坏=下一批真跑闸时才炸,
# 闸件批当场拦住。收件面动态收全:tools/ 全部 .sh+game/tests 顶层 .sh(静态扫描)+hooks/post-merge
# (无后缀件);新增 bash 闸件自动入检面,无需维护清单。
gate_shell_selfcheck() {
local name="闸壳自检"
local rc=0 _n=0 _bad=0 _s
local -a _shells=()
while IFS= read -r _s; do [ -n "$_s" ] && _shells+=("$_s"); done < <(
{ find "$REPO/tools" -name '*.sh' 2>/dev/null
find "$REPO/game/tests" -maxdepth 1 -name '*.sh' 2>/dev/null
echo "$REPO/tools/hooks/post-merge"; } | sort -u
)
echo ""
echo "════════════════════════════════════════════"
echo " $name(bash -n 全部 bash 闸件与管线件)"
echo "════════════════════════════════════════════"
: >"$STAMP_DIR/shell_selfcheck.log"
for _s in "${_shells[@]}"; do
[ -f "$_s" ] || continue
_n=$((_n + 1))
if bash -n "$_s" 2>>"$STAMP_DIR/shell_selfcheck.log"; then
echo "ok: ${_s#"$REPO"/}" >>"$STAMP_DIR/shell_selfcheck.log"
else
echo "FAIL: bash -n ${_s#"$REPO"/} 解析失败" >>"$STAMP_DIR/shell_selfcheck.log"
_bad=$((_bad + 1))
fi
done
if [ "$_bad" -eq 0 ]; then
echo "bash -n × $_n 件全过" >>"$STAMP_DIR/shell_selfcheck.log"
else
rc=1
grep -E "FAIL" "$STAMP_DIR/shell_selfcheck.log" | sed 's/^/ | /'
fi
echo "[gates] $name:bash -n × $_n 件,失败 $_bad(日志 shell_selfcheck.log)"
NAMES+=("$name"); EXITS+=("$rc"); LOGS+=("shell_selfcheck.log")
return 0
}
# ══ 代码域 ══
if in_domain code; then
# ── 冒烟测试(极小启动场景:真实开机路径存活检查,零逻辑判定,秒级)──
@@ -440,15 +486,20 @@ if in_domain docs; then
gate "能力审计" "capability.log" "$PY" "$REPO/tools/capability_audit.py" --strict
fi
# ── 静态扫描(代码域)+ 熵增闸(两域都跑:它查顶层目录/根文件/图像白名单/退役路径,
# ── 闸件自检段(gates 档专属)──
if [ "$DOMAIN" = "gates" ]; then
gate_shell_selfcheck
fi
# ── 静态扫描(代码域与闸件自检档)+ 熵增闸(各档都跑:它查顶层目录/根文件/图像白名单/退役路径,
# 文档批新增或挪动 design/ 下的文件同样会绊它;纯文本扫描,秒级)──
if [ $QUICK -eq 0 ]; then
if in_domain code; then
if in_domain code || [ "$DOMAIN" = "gates" ]; then
gate "静态扫描" "static_scan.log" bash "$GAME/tests/static_scan.sh"
fi
gate "熵增闸" "entropy.log" "$PY" "$REPO/tools/repo_structure_audit.py"
else
if in_domain code; then
if in_domain code || [ "$DOMAIN" = "gates" ]; then
NAMES+=("静态扫描"); EXITS+=("SKIP"); LOGS+=("")
fi
NAMES+=("熵增闸"); EXITS+=("SKIP"); LOGS+=("")