工程(合回闸关联档+闸机编排快速失败): 收口跑批按改动关联面选测+红树快速失败——①tools/run_all_gates.sh 关联档选闸(推广 2026-10-28 影响面选闸):00 冒烟/01 系统测试/09 静态扫描三件保险恒跑不降(测量面批不再免 00);03/04 探针与 05 渲染闸维持影响面口径;02 单元与集成改「选套件跑」——套件集=需求卡片申报∪diff 推导并集(申报=本批 diff 触及卡片∪roadmap 进行中行登记卡片的对照表「验收测试」列测试文件名;推导=脚本内套件族映射表按 game/ 代码面文件归族,映射不到一律横切),横切(公共件前缀 config_db*/rng_service*/save_manager*/meta_progression*/main*、project.godot autoload 注册面、game/data/tables、PARITY 触面表)→ 02 回落全部套件;PARITY 触面表上移单点定义(gate 11 与横切判定共用同一清单);回执加 scope(full|related)与 scope_terms 字段,worktree-guard 校验逻辑不动——②game/tests/run_all.gd 快速失败:连续 3 个套件超时(可调常量 CONSECUTIVE_TIMEOUT_ABORT 初值 3)即中止整轮、汇总已见失败与超时清单、退出码非 0,不连续超时照旧逐个回收;_run_suite 改返 {fail, timed_out};新增 --only-source=gates 来源标记(关联档选跑横幅,区别于开发期选跑免责句),无选跑参数时行为与现状完全一致——③规则同批同步(禁只改闸不改规则):AGENTS.md「交付前验收」句与硬性红线改按关联档选测口径(所选域绿即可合回,全量回归由用户自发、合入后无自动兜底,2026-10-29 用户裁)+规范导航行同步;process-conventions §3 第 8 条合回闸细则同口径;test-conventions §4 改「提交前验证」、code-conventions §3 主题行随改;game/tests/README.md 工具速查 run_all.gd 行(快速失败+闸机关联档选跑+manifest 实数 65)与挂死防护注、门禁/本地闸回执字段句同步——④待办清单「合回闸关联档」「闸机编排快速失败」两条目完成整条删除。孤立验证:推导沙箱七场景(行为面归族/触面横切/测量面批/main* 横切/project.godot 横切/申报并集/横切优先)+run_all 闸源选跑 9/65 绿+2s 截止连续超时第 3 个中止(exit 1 带汇总)

This commit is contained in:
DKChan committed 2026-10-29 04:36:26 +08:00
1 parent 6abaf67dd5
commit 9f94c9e100
8 files changed
+231 -77

No files matched your search

+155 -30
View File
@@ -14,8 +14,11 @@
# --code 只跑代码域闸(00 冒烟测试/01 系统测试/02 单元与集成测试/03 探针 --click/
# 04 探针 --full/05 渲染闸/09 静态扫描/11 回放确定性对拍+10 熵增闸)
# ——本批改动含 game/ 下非 .md 的行为载体(代码/测试脚本/探针/场景/数据表/素材)时用。
# 影响面选闸:本批 game/ 改动全在测量面(game/tests、game/sim)时,00/03/04/05 记
# SKIP(测量面不触开机路径/场景树/渲染输出;映射定义在脚本中部,判定保守)。
# 关联档选闸(2026-10-29 用户裁,推广自 2026-10-28 影响面选闸):00 冒烟/01 系统测试/
# 09 静态扫描三件保险恒跑不降;03/04 探针与 05 渲染闸维持影响面口径(本批 game/ 改动
# 全在测量面 game/tests、game/sim 时记 SKIP);02 单元与集成改「选套件跑」——套件集=
# 需求卡片申报 ∪ diff 推导并集(算法与套件族映射表见脚本中部),横切面(公共件/数据表/
# autoload/数值·存档·RNG·结算触面)回落全套件;回执 scope 字段记 full|related。
# --docs 只跑文档域闸(06 gdd_lint/07 doc_link/08 doc_term/08c 流水线闸/08d 引用可核/
# 08e 能力审计+10 熵增闸)
# ——本批改动只有文档件时用:game/ 内的 .md(README、生成清单)、design/、根规则文件、
@@ -154,12 +157,83 @@ fi
declare -a NAMES=() EXITS=() LOGS=()
FAILED=0
# ── 影响面选闸(2026-10-28 用户裁):本批 diff 相对 master 分叉点的 game/ 改动若全部落在
# 测量面(game/tests/**、game/sim/**——测试与模拟驱动件,不触开机路径、场景树与渲染
# 输出),则 00 冒烟/03/04 探针/05 渲染闸记 SKIP。判定保守取向:diff 含任一行为面
# 文件即全部实跑;01/02/09 恒跑(测试宿主与静态扫描正是测量面改动的验证载体)。
# 触发面映射只在本脚本一处定义;SKIP 与 --quick 同构,回执记 not_run,不作闸况变化。
# PARITY 触面表——gate 11 数值·存档·RNG·结算触面判定与关联档横切判定共用同一清单(单点定义,勿在别处复制)。
PARITY_TOUCH_FACES="
game/data/tables/
game/core/rng_service.gd
game/core/save_manager.gd
game/core/meta_progression.gd
game/core/settlement/
game/core/damage_math.gd
game/core/damage_controller.gd
game/core/battle_orchestrator.gd
game/sim/adventure_monte_carlo.gd
"
# ── 套件族映射表(关联档·推导半边的唯一事实源)──
# game/ 代码面文件 → tests/ 套件族词(--only 子串词,词=套件目录名)。机械近似:同族内
# 套件覆盖面仍可能宽于被测文件,保守方向=宁多勿漏。横切判定(公共件前缀/project.godot/
# PARITY 触面表)先于本表执行,故表中不收 damage_math/damage_controller/battle_orchestrator/
# settlement/** 等数值·存档·RNG·结算触面文件——它们恒走横切回落全量。映射不到的文件返回空=横切处理。
suite_terms_for() { # suite_terms_for <仓库相对路径> → stdout: 套件族词(空格分隔,可空)
case "$1" in
# 战斗结算域
game/core/action_economy.gd|game/core/battle_fx_host.gd|game/core/effect_context.gd|\
game/core/effect_engine.gd|game/core/gain_journal.gd|game/core/skill_lottery.gd)
echo "battle_settlement" ;;
game/core/reward_economy.gd|game/core/reward_grant.gd)
echo "battle_settlement core" ;;
game/core/status_container.gd)
echo "battle_settlement contracts" ;;
game/core/unit_container.gd)
echo "battle_settlement data_config" ;;
# 排雷/棋盘域(封印与极性行为跨战斗结算族)
game/core/board_data.gd|game/core/minesweeper_rules.gd)
echo "core battle_settlement" ;;
game/core/board_generator.gd|game/core/node_map_generator.gd|game/core/hero_stats.gd|\
game/core/curio_deck.gd|game/core/curio_stop.gd|game/core/refresh_bank.gd|\
game/core/treasure_items.gd|game/core/attune_loadout.gd|game/core/attunement.gd|\
game/core/meta_state_store.gd|game/core/slot_store.gd|game/core/gp_wallet.gd|\
game/core/shop_session.gd)
echo "core" ;;
# 冒险结构×存档断点×关卡推进(core 族+跨系统集成族)
game/core/adventure_runner.gd|game/core/level_runner.gd)
echo "core integration" ;;
# 配置域表类(autoload 本体与解析器/跨表校验器走横切,不入本表)
game/core/config_domain_*.gd)
echo "data_config core" ;;
# 数据 schema(data_config 族守卫+core 族消费侧校验与跨表校验)
game/data/schemas/*)
echo "data_config core" ;;
# 呈现域
game/presentation/*)
echo "presentation" ;;
# 素材(字体/地形/主题类断言在 presentation 族;探针与渲染闸另有行为面闸管)
game/assets/*)
echo "presentation" ;;
*)
echo "" ;;
esac
}
# ── 关联档选闸(2026-10-29 用户裁,推广自 2026-10-28 影响面选闸)──
# 三件保险恒跑不降:00 冒烟/01 系统测试/09 静态扫描(10 熵增闸照旧两域都跑,11 数值·存档·RNG·结算触面判定照旧)。
# 03/04 探针与 05 渲染闸维持影响面口径:本批 diff 相对 master 分叉点的 game/ 改动若全部落在
# 测量面(game/tests/**、game/sim/**——测试与模拟驱动件,不触场景树与渲染输出)则记 SKIP。
# 02 单元与集成改「选套件跑」:套件集=申报 ∪ 推导 取并集——
# 申报:在册需求卡片(本批 diff 触及的 reports/需求卡片*.md ∪ roadmap「进行中」迭代行登记的
# 卡片)对照表「验收测试」列提到的测试文件名,按文件名作为 --only 子串词归套件;
# 找不到在册卡片则申报集为空并输出注明。
# 推导:按下方套件族映射表把 game/ 代码面文件(非 .md、非测量面)归到套件族;
# 映射不到的文件一律按横切处理。
# 横切回落全量:diff 命中 game/data/tables/**、公共件前缀(config_db*/rng_service*/
# save_manager*/meta_progression*/main*——main* 覆盖组合根拆件与主场景)、
# autoload 注册面(game/project.godot)、或下方 PARITY 触面表任一 → 02 跑全部套件。
# 测量面批(game/ 改动全在 tests/sim):02 照旧全套件跑(不加过滤)。
# 回执 scope 字段:full|related(02 跑子集=related);映射表只在本脚本一处定义。
NONBEHAVIOR=0
SCOPE="full"
ONLY_TERMS=""
if in_domain code; then
_base="$(git -C "$REPO" merge-base master HEAD 2>/dev/null || true)"
_gfiles="$(git -C "$REPO" -c core.quotepath=false diff --name-only "${_base:-HEAD}" HEAD -- game/ 2>/dev/null || true)"
@@ -167,7 +241,62 @@ if in_domain code; then
_behavior="$(echo "$_gfiles" | grep -vE '^(game/tests/|game/sim/)' || true)"
if [ -n "$_gfiles" ] && [ -z "$_behavior" ]; then
NONBEHAVIOR=1
echo "[gates] 影响面选闸:game/ 改动全在测量面(tests/sim)——00 冒烟/03/04 探针/05 渲染闸 → SKIP"
echo "[gates] 影响面选闸:game/ 改动全在测量面(tests/sim)——03/04 探针/05 渲染闸 → SKIP(00/01/09 三件保险恒跑)"
fi
# ── 02 选套件推导(只对行为面代码文件做——测量面文件不进推导,其验证就是 02/09 自身)──
_codefiles="$(echo "$_gfiles" | grep -viE '\.md$' | grep -vE '^(game/tests/|game/sim/)' || true)"
if [ -n "$_codefiles" ]; then
CROSSCUT=0
_family_terms=""
# 逐文件判定:横切命中即整批回落全量;否则按映射表归族。
while IFS= read -r _f; do
[ -z "$_f" ] && continue
_b="$(basename "$_f")"
case "$_b" in
config_db*|rng_service*|save_manager*|meta_progression*|main*)
CROSSCUT=1; break ;;
esac
[ "$_f" = "game/project.godot" ] && { CROSSCUT=1; break; } # autoload 注册面
for _pat in $PARITY_TOUCH_FACES; do
case "$_f" in
"$_pat"*) CROSSCUT=1; break 2 ;;
esac
done
_t="$(suite_terms_for "$_f")"
[ -n "$_t" ] && _family_terms="$_family_terms $_t"
done <<<"$_codefiles"
# 申报半边:在册需求卡片对照表「验收测试」列的测试文件名(卡片=本批 diff 触及 ∪ roadmap 进行中行登记)。
_cards="$(git -C "$REPO" -c core.quotepath=false diff --name-only "${_base:-HEAD}" HEAD -- 'reports/' 2>/dev/null | grep '需求卡片.*\.md$' || true)"
_roadmap_cards="$(grep '| 进行中 |' "$REPO/roadmap.md" 2>/dev/null | grep -oE 'reports/需求卡片[^|]*\.md' || true)"
_declared_names=""
_card_seen=0
for _c in $_cards $_roadmap_cards; do
_card_seen=1
_cf="$REPO/$_c"
[ -f "$_cf" ] || continue
_names="$(grep -ohE '[a-z0-9_]+_test\.gd' "$_cf" 2>/dev/null | sort -u || true)"
[ -n "$_names" ] && _declared_names="$_declared_names $_names"
done
if [ "$CROSSCUT" -eq 1 ]; then
SCOPE="full"
echo "[gates] 关联档选闸:diff 命中横切面(公共件/数据表/autoload/数值·存档·RNG·结算触面)——02 回落全部套件"
else
# 并集去重:推导族词 ∪ 申报测试文件名;并集非空才过滤(映射表保证族词非空,此处双保险)。
_union="$(printf '%s\n' $_family_terms $_declared_names | sed '/^$/d' | sort -u | tr '\n' ' ' || true)"
_union="$(echo "$_union" | xargs 2>/dev/null || true)"
if [ -n "$_union" ]; then
SCOPE="related"
ONLY_TERMS="$_union"
else
SCOPE="full"
fi
fi
if [ "$_card_seen" -eq 0 ]; then
echo "[gates] 关联档选闸·申报:未找到在册需求卡片(diff 与 roadmap 进行中行均无),申报集为空——仅按 diff 推导"
fi
if [ "$SCOPE" = related ]; then
echo "[gates] 关联档选闸:02 选套件跑(--only=$ONLY_TERMS)"
fi
fi
fi
@@ -195,11 +324,20 @@ gate() { # gate <名称> <日志名> <命令...>
gate_unit() { # run_all 是 --script 模式,退出码为唯一判据
local name="02 单元与集成测试"
# 关联档选跑:把套件词列表经 --only= 传给 run_all(闸机来源标记 --only-source=gates,
# 入口据此打关联档横幅、不落「仅供开发期」免责句);全量态不传任何选跑参数。
local -a _user_args=()
if [ "$SCOPE" = related ] && [ -n "$ONLY_TERMS" ]; then
_user_args=(-- "--only=${ONLY_TERMS// /,}" --only-source=gates)
fi
echo ""
echo "════════════════════════════════════════════"
echo " $name(--script 模式,退出码为唯一判据)"
if [ "$SCOPE" = related ] && [ -n "$ONLY_TERMS" ]; then
echo " 关联档选跑:--only=$ONLY_TERMS"
fi
echo "════════════════════════════════════════════"
( cd "$REPO" && "$GODOT" --headless --path "$(cygpath -m "$GAME")" --script res://tests/run_all.gd ) \
( cd "$REPO" && "$GODOT" --headless --path "$(cygpath -m "$GAME")" --script res://tests/run_all.gd "${_user_args[@]}" ) \
>"$STAMP_DIR/unit.log" 2>&1
local rc=$?
# 三硬闸之二(脚本错误扫描):子套件的 SCRIPT ERROR/Parse Error 只写 stderr,入口内收不到,
@@ -216,12 +354,8 @@ gate_unit() { # run_all 是 --script 模式,退出码为唯一判据
# ══ 代码域(0–5)══
if in_domain code; then
# ── 0. 冒烟测试(极小启动场景:真实开机路径存活检查,零逻辑判定,秒级)──
if [ $NONBEHAVIOR -eq 1 ]; then
NAMES+=("00 冒烟测试"); EXITS+=("SKIP"); LOGS+=("")
echo ""; echo "[gates] 00 冒烟测试 → SKIP(影响面选闸:测量面改动不触开机路径)"
else
gate "00 冒烟测试" "smoke_boot.log" "$PY" "$REPO/tools/run_smoke.py"
fi
# 三件保险之一(00/01/09):恒跑不降——测量面批也不免(关联档选闸,2026-10-29 用户裁)。
gate "00 冒烟测试" "smoke_boot.log" "$PY" "$REPO/tools/run_smoke.py"
# ── 1. 系统测试(真实玩法路径断言面;宿主=game/tests/system_test.gd,缺省 --script 由 run_system_test.py 持有)──
gate "01 系统测试" "system_test.log" "$PY" "$REPO/tools/run_system_test.py"
# ── 2. 单元与集成测试 ──
@@ -260,21 +394,10 @@ gate "08e 能力审计" "capability.log" "$PY" "$REPO/tools/capability_audit.py"
fi
# ── 6c. 回放确定性对拍(条件闸件;属代码域)──
if in_domain code; then
# 触面判定:本批 diff 相对 master 分叉点,命中下列路径面之一即「触面」。
# 触面判定:本批 diff 相对 master 分叉点,命中 PARITY_TOUCH_FACES(脚本上部单点定义)之一即「触面」。
# 触面也**不自动跑**——对拍调的是 MC 脚本,属「MC/统计类跑批发起前须用户批准」红线管辖;
# 闸只负责每次跑批都把触面判定做一遍并显式要求批准,杜绝「该跑却没人记得」。
# 用户批准后设 PARITY_APPROVED=1 重跑本脚本即实跑三变体对拍。
PARITY_TOUCH_FACES="
game/data/tables/
game/core/rng_service.gd
game/core/save_manager.gd
game/core/meta_progression.gd
game/core/settlement/
game/core/damage_math.gd
game/core/damage_controller.gd
game/core/battle_orchestrator.gd
game/sim/adventure_monte_carlo.gd
"
_base="$(git -C "$REPO" merge-base master HEAD 2>/dev/null || true)"
_changed_files="$(git -C "$REPO" diff --name-only "${_base:-HEAD}" HEAD 2>/dev/null || true)"
_touch_hit=0
@@ -315,11 +438,11 @@ else
fi
# ── 与存量基线比对 ──
"$PY" - "$BASELINE" "$RECEIPT" "$QUICK" "$DOMAIN" "$REPO" "$DIRTY_GAME" "${NAMES[@]}" -- "${EXITS[@]}" -- "${LOGS[@]}" <<'PYEOF'
"$PY" - "$BASELINE" "$RECEIPT" "$QUICK" "$DOMAIN" "$REPO" "$DIRTY_GAME" "$SCOPE" "$ONLY_TERMS" "${NAMES[@]}" -- "${EXITS[@]}" -- "${LOGS[@]}" <<'PYEOF'
import hashlib, json, os, subprocess, sys
baseline_path, receipt_path, quick, domain, repo, dirty_game = sys.argv[1:7]
rest = sys.argv[7:]
baseline_path, receipt_path, quick, domain, repo, dirty_game, scope, only_terms = sys.argv[1:9]
rest = sys.argv[9:]
sep = rest.index("--")
names = rest[:sep]
tail = rest[sep + 1:]
@@ -414,6 +537,8 @@ with open(receipt_path, "w", encoding="utf-8") as f:
"timestamp": subprocess.run(["date", "-Iseconds"], capture_output=True, text=True).stdout.strip(),
"quick_mode": quick == "1",
"domain": domain,
"scope": scope,
"scope_terms": [t for t in only_terms.split() if t],
"dirty_game": dirty_game == "1",
"first_baseline_run": first_run,
"gates": [{"name": n, "exit": e, "fingerprint": now[n]["fp"]} for n, e in zip(names, exits)],