From a20ca0df0b62e607bbbc78f445fdea2bd4cfd633 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Thu, 5 Nov 2026 03:47:52 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E9=97=B8=E4=BB=B6?= =?UTF-8?q?=E8=87=AA=E6=A3=80=E6=A1=A3):=20=E7=BA=AF=E9=97=B8=E4=BB=B6/?= =?UTF-8?q?=E7=AE=A1=E7=BA=BF=E6=89=B9=E4=B8=8D=E5=86=8D=E9=9B=B6=E8=A7=A6?= =?UTF-8?q?=E5=8F=91=E2=80=94=E2=80=94domain=5Froute=E5=8F=AA=E5=90=ABtool?= =?UTF-8?q?s/.gitea=E5=88=A4gates(=E7=A9=BAdiff=E4=BB=8Dskip)=20+=20run=5F?= =?UTF-8?q?all=5Fgates=E6=96=B0=E5=A2=9E--gates=E6=A1=A3(=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E5=9F=9Fpython=E6=9C=BA=E6=A3=80=E4=BB=B6+=E7=86=B5?= =?UTF-8?q?=E5=A2=9E+=E9=9D=99=E6=80=81=E6=89=AB=E6=8F=8F+=E9=97=B8?= =?UTF-8?q?=E5=A3=B3bash=20-n=E8=87=AA=E6=A3=80,=E4=B8=8D=E5=90=AF?= =?UTF-8?q?=E5=BC=95=E6=93=8E,=E5=88=A4=E6=8D=AE=E5=8F=A3=E5=BE=84?= =?UTF-8?q?=E5=8E=9F=E6=A0=B7)=20+=20ci.yml=E6=A1=A3=E4=BD=8D=E6=98=A0?= =?UTF-8?q?=E5=B0=84=E4=B8=89=E5=80=BC=E5=8C=96(full/docs/gates)=E5=B9=B6?= =?UTF-8?q?=E6=8E=92=E9=99=A4gates=E6=A1=A3=E5=AF=BC=E5=85=A5=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E5=88=B7=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci.yml | 13 ++++--- tools/ci/domain_route.sh | 9 ++--- tools/run_all_gates.sh | 77 +++++++++++++++++++++++++++++++++------- 3 files changed, 78 insertions(+), 21 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 7237235d..65325e22 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -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 diff --git a/tools/ci/domain_route.sh b/tools/ci/domain_route.sh index b30a878a..e2e02a89 100644 --- a/tools/ci/domain_route.sh +++ b/tools/ci/domain_route.sh @@ -1,13 +1,14 @@ #!/usr/bin/env bash # 乙案域分流——push 触发的远端 CI 按改动域选闸(2026-11-03 用户裁)。 # 用法: tools/ci/domain_route.sh -# 输出: 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 diff --git a/tools/run_all_gates.sh b/tools/run_all_gates.sh index 2b2474cd..b64714e2 100755 --- a/tools/run_all_gates.sh +++ b/tools/run_all_gates.sh @@ -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 [ "$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+=("") From 1477404294f21f8fbe3b648f5a03d1e74d72880b Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Thu, 5 Nov 2026 03:51:27 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E9=97=B8=E4=BB=B6?= =?UTF-8?q?=E8=87=AA=E6=A3=80=E6=A1=A3=C2=B7=E5=9F=BA=E7=BA=BF=E7=99=BB?= =?UTF-8?q?=E8=AE=B0):=20=E9=97=B8=E5=A3=B3=E8=87=AA=E6=A3=80=E5=85=A5?= =?UTF-8?q?=E5=AD=98=E9=87=8F=E5=9F=BA=E7=BA=BF(=E7=BB=BF,12=E4=BB=B6bash?= =?UTF-8?q?=E9=97=B8=E4=BB=B6=E4=B8=8E=E7=AE=A1=E7=BA=BF=E4=BB=B60?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=A4=B1=E8=B4=A5,--gates=E6=A1=A3=E9=A6=96?= =?UTF-8?q?=E8=B7=91=E5=AE=9E=E8=AF=81)=E2=80=94=E2=80=94=E9=98=B2?= =?UTF-8?q?=E7=BA=AF=E9=97=B8=E4=BB=B6=E6=89=B9=E8=BF=9C=E7=AB=AF=E9=A6=96?= =?UTF-8?q?=E8=B7=91=E5=88=A4=E6=97=A0=E5=9F=BA=E7=BA=BF=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E7=BA=A2;=20=E5=85=B6=E4=BD=99=E5=90=84=E6=A1=A3=E9=97=B8?= =?UTF-8?q?=E6=9D=A1=E7=9B=AE=E5=8E=9F=E6=A0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/tests/.gates/baseline.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/game/tests/.gates/baseline.json b/game/tests/.gates/baseline.json index 0b6694a2..1b5747bc 100644 --- a/game/tests/.gates/baseline.json +++ b/game/tests/.gates/baseline.json @@ -60,6 +60,10 @@ "单元与集成测试": { "exit": "0", "fp": "green" + }, + "闸壳自检": { + "exit": "0", + "fp": "green" } } }