Merge branch 'master' into qa-harness

# Conflicts:
#	.gitattributes
This commit is contained in:
DKChan committed 2026-11-06 05:24:31 +08:00
commit be0cab8356
6 files changed
+94 -20

No files matched your search

+17 -2
View File
@@ -1,4 +1,19 @@
# git 钩子文件必须保持 LF——autocrlf=true 检出会转 CRLF,bash 执行带 \r 的脚本即崩。
tools/hooks/* text eol=lf
# GDScript 一律 LF 检出——.gd 内嵌 r""" golden 字面量逐位对拍吃进 CRLF 即假红(Mac CI 检出 LF 为绿判环境;hooks 例外同款)。
*.gd text eol=lf
# 全仓文本一律 LF 入库与检出——Windows core.autocrlf=true 会把工作区检出成 CRLF,
# 行尾一旦进入逐字符比对的字符串基线数据面(如测试内嵌的多行 golden 常量),
# 运行时基线(\n)与源码基线(\r\n)逐位比对必红。本规则让一切平台检出落 LF。
* text=auto eol=lf
# 二进制面显式豁免(防 auto 误判转码损坏素材与基线图)
*.png binary
*.jpg binary
*.jpeg binary
*.webp binary
*.gif binary
*.ico binary
*.ogg binary
*.wav binary
*.mp3 binary
*.ttf binary
*.otf binary
*.zip binary
+2 -2
View File
@@ -16,7 +16,7 @@
## 运行与验证(工作目录 = 含 project.godot 的切片目录)
- **合回验证 = 远端 CI(2026-11-03 用户裁)**:git merge 合回 master 落地后,post-merge 钩子(`tools/hooks/post-merge`)自动 `git push origin master` 触发远端 CI(Gitea Actions,runner=Mac 本机 host 模式,环境自举件=`tools/ci/mac_setup.sh`),工作流(`.gitea/workflows/ci.yml`)按改动域分流(分流算法单点=`tools/ci/domain_route.sh`):游戏码批=两域全量含渲染闸,文档批=文档域秒级闸,纯闸件/管线批=零触发。远端结果**按需现查**:`tools/ci_check.sh <合并提交>` 读该提交任务的 run `status`/`conclusion`,只呈报、不落盘(2026-11-04 用户裁:合回不等待、不唤醒;判据=run 落定后的终局态,且绿须坐实跑闸步真跑过——零触发批呈报「未验」不作绿证,细则=process-conventions §1 第 3 条)。**合回落地即向用户呈报本批总结并止回合**,禁前台 sleep/轮询等 CI。CI 红态不回滚合并——红项在案、修复批合回即再推再验追绿;开工先跑现查件读 CI 状态,红未修不开新批。同一份字节禁止第二次人手跑。`tools/run_all_gates.sh` 仍是全部验证闸的**唯一入口与唯一清单,域划分与选闸算法只写在它这一个地方**,文档禁另列清单;`--code`/`--docs`/`--quick` 是开发期改完即测的选档入口(细则见 process-conventions §4),全量档不替代它。**runner 依赖**:Mac 需开机在线(关机=远端任务排队延后);换机/重装按 `tools/ci/mac_setup.sh` 自举
- **合回验证 = 前置检查 + 远端 CI(前置检查 2026-11-06 用户裁)**:主区 `git merge --no-ff` 落合并提交前,pre-merge-commit 钩子(`tools/hooks/pre-merge-commit`)自动跑 `tools/run_all_gates.sh --premerge`——冒烟测试+单元与集成测试全套件(4 分片),热导入缓存约 2 分钟;红=合并提交不落(修红后 git commit 续合,或 git merge --abort 放弃),快进合并不触发钩子,`--no-verify` 可跳过且仅属用户。git merge 合回 master 落地后,post-merge 钩子(`tools/hooks/post-merge`)自动 `git push origin master` 触发远端 CI(Gitea Actions,runner=Mac 本机 host 模式,环境自举件=`tools/ci/mac_setup.sh`),工作流(`.gitea/workflows/ci.yml`)按改动域分流(分流算法单点=`tools/ci/domain_route.sh`):游戏码批=两域全量含渲染闸,文档批=文档域秒级闸,纯闸件/管线批=零触发。远端结果**按需现查**:`tools/ci_check.sh <合并提交>` 读该提交任务的 run `status`/`conclusion`,只呈报、不落盘(2026-11-04 用户裁:合回不等待、不唤醒;判据=run 落定后的终局态,且绿须坐实跑闸步真跑过——零触发批呈报「未验」不作绿证,细则=process-conventions §1 第 3 条)。**合回落地即向用户呈报本批总结并止回合**,禁前台 sleep/轮询等 CI。CI 红态不回滚合并——红项在案、修复批合回即再推再验追绿;开工先跑现查件读 CI 状态,红未修不开新批。同一份字节禁止第二次人手跑。`tools/run_all_gates.sh` 仍是全部验证闸的**唯一入口与唯一清单,域划分与选闸算法只写在它这一个地方**,文档禁另列清单;`--code`/`--docs`/`--quick` 是开发期改完即测的选档入口(细则见 process-conventions §4),全量档不替代它。**runner 依赖**:Mac 需开机在线(关机=远端任务排队延后);换机/重装按 `tools/ci/mac_setup.sh` 自举
- 单件直跑(=改完即测的最窄确认,其读数就是那一步的凭据;跨改动面的漏网由合回后远端全量档兜):冒烟测试 `python tools/run_smoke.py`(`game/tests/smoke_boot.tscn` 极小启动场景)· 系统测试 `python tools/run_system_test.py`(宿主 `game/tests/system_test.gd`)· 场景探针 `…console.exe --headless --path . res://probe/ui_probe.tscn` · 单元与集成 `… --script res://tests/run_all.gd`
- 调参批量:`--script res://sim/adventure_monte_carlo.gd`(全冒险期望)· `res://sim/reward_expectation.gd`(奖励期望);关卡完成率/难度类测量一律走全冒险
- 数值验证:`python tools/balance_verifier.py`(采集端 `res://sim/verifier_batch.gd`,报告与原始数据落 `sim-data/`)
@@ -104,7 +104,7 @@
## 硬性红线(勿违反;操作细则在 process-conventions.md 与各规约)
- 车道铁律(2026-10-31 用户裁恢复)——主工作区只作合并落点,一切开发/设计改动走独立 worktree:`git worktree add ../g-plan2-<任务名> -b <任务分支>`(建区即跑 `tools/setup_worktree_userdir.sh`),收口=冻结→件核→并 master→重塑→主区 `git merge --no-ff`→删区(合回即删,2026-09-19 原裁),CI 红不回滚合并——新开修复 worktree(`fix-<任务名>` 分支,免重塑免数卡)修复合回追绿;会话单区(2026-11-04 用户裁)——同一时刻每会话至多一个 worktree 在役,同会话追加事项并入在役区、只做一次合入(一次 CI),合回后用户再发令=新回合另开区,迭代实现批独占回合(细则见 process-conventions §1 第 5 条);主区唯一合法写动作=合并,agent 侧由 worktree-guard 拦主区 Edit/Write 与非 merge 提交;豁免面仅 `.zcode/`、`.qoder/`(不入 git,主区直改);commit 点零钩子检查(pre-commit 直改闸已退役),全量验证归合回后远端 CI(post-merge 钩子推送触发,见「运行与验证」首条);`--no-verify` 仅属用户,agent 禁用(细则见 process-conventions §1)
- 车道铁律(2026-10-31 用户裁恢复)——主工作区只作合并落点,一切开发/设计改动走独立 worktree:`git worktree add ../g-plan2-<任务名> -b <任务分支>`(建区即跑 `tools/setup_worktree_userdir.sh`),收口=冻结→件核→并 master→重塑→主区 `git merge --no-ff`→删区(合回即删,2026-09-19 原裁),CI 红不回滚合并——新开修复 worktree(`fix-<任务名>` 分支,免重塑免数卡)修复合回追绿;会话单区(2026-11-04 用户裁)——同一时刻每会话至多一个 worktree 在役,同会话追加事项并入在役区、只做一次合入(一次 CI),合回后用户再发令=新回合另开区,迭代实现批独占回合(细则见 process-conventions §1 第 5 条);主区唯一合法写动作=合并,agent 侧由 worktree-guard 拦主区 Edit/Write 与非 merge 提交;豁免面仅 `.zcode/`、`.qoder/`(不入 git,主区直改);commit 点零钩子检查(pre-commit 直改闸已退役);合回 merge 点另有前置检查钩子(pre-merge-commit,2026-11-06 用户裁,见「运行与验证」首条);全量验证归合回后远端 CI(post-merge 钩子推送触发,见「运行与验证」首条);`--no-verify` 仅属用户,agent 禁用(细则见 process-conventions §1)
- **仓库根禁止设会话状态文件**(`active.md` 已废除)——现状看 roadmap「当前位」,待办看待办清单,过程看 git 提交信息,产出物看 `reports/`;任何文档禁止另设「当前状态快照」节,两处记同一件事必然漂移(细则见 doc-conventions 第 10 节)
- **改完即测铁规(2026-11-05 用户裁,取代原「测试执行两入口铁规」)**——任何会话里的任何改动,落笔之后立刻跑一遍覆盖该改动面的验证并跑绿、读数写进这一步的提交信息,这一步才算完成;验证载体一律按 `tools/run_all_gates.sh` 的域划分与该件自身的直跑方式,不扩出改动面、不请示、不等点名,未改动的面不测,合回后远端 CI 的全量跑批不替代本条。读数类采集(MC/verifier)不在该域划分内,不属本条义务
- **红账不清不开新批**——上一批远端跑批有真红未闭合不开新批;假红(导入缓存、user 目录未隔离那类在册环境坑)走异常分级处置,不算未闭合
+4 -4
View File
@@ -10,9 +10,9 @@
主工作区(master 检出)只作合并落点,一切开发/设计改动禁止直接发生在主工作区(2026-10-31 用户裁恢复;验证模型=批内按 §4 改完即测、合回后远端 CI 是唯一强制全量验证点,细则见本节第 3 条):
1. **worktree 车道(一切面)**:每回合先建独立 worktree(建区粒度=回合,会话单区细则见第 5 条):`git worktree add ../g-plan2-<任务名> -b <任务分支>`;建区后立刻跑 `tools/setup_worktree_userdir.sh`——给该区独立 Godot user 目录(测试夹具、试玩日志、截图产物不与其他区共用;主区不生成,真实档保持原位)。所有改动与提交都在 worktree 内自由进行——commit 点零钩子检查(`tools/hooks/pre-commit` 已删除)。豁免面仅 `.zcode/`(钩子与配置)与 `.qoder/`(本机 harness 配置)允许主工作区直改——两份实测均不入 git(走 worktree 反而带不进去,改动只对本机生效)
2. **收口顺序(固定)**:**冻结 → 件核 → 并 master → 重塑 → 主区合回 → 删区**——代码与断言改完即冻结,全仓件核 grep 查残留;master 已被别的批次推进时先在 worktree 内 `git merge master` 解冲突(必须在合回之前,合回后才并=CI 验的是中间态);**重塑**=迭代分支(iter<N> 形)合回前执行 `python tools/reshape_cards.py`,把分支线性史按卡末态快照压成一卡一笔(可选追加一笔纯文档收尾),master 台账由此每卡一行——工具自带「重塑前后树逐字节一致」断言与 backup 分支,不改任何文件内容;非迭代批免重塑;然后主工作区 `git merge --no-ff <任务分支>`——这是主区唯一合法写动作(合回守卫对迭代分支数卡校验,未归卡提交拦下)。**合回落地即 `git worktree remove`+删分支(合回即删,2026-09-19 原裁);post-merge 钩子已在合回落地时自动触发 CI,红态不回滚合并——新开修复 worktree(`fix-<任务名>` 分支,非 iter<N> 形免重塑免数卡)修复提交再合,落地复验追绿后删修复区**。批内的验证义务按 §4 改完即测铁规履行——改完即测自己改过的那部分,不请示、不等点名;收口顺序本身不设跑批关口。**卡片复核前置判据(2026-11-05 用户裁)**:需求卡片的 grilling 复核先于卡片提交、卡片提交先于合回——复核未跑完、缺陷清单未裁完,禁把卡片落 commit(落了也不算成立),`master` 永不落未成立卡;本条与 §2 段三出口铁律、doc-conventions §12.1「已写卡」凭据同判。
2. **收口顺序(固定)**:**冻结 → 件核 → 并 master → 重塑 → 主区合回 → 删区**——代码与断言改完即冻结,全仓件核 grep 查残留;master 已被别的批次推进时先在 worktree 内 `git merge master` 解冲突(必须在合回之前,合回后才并=CI 验的是中间态);**重塑**=迭代分支(iter<N> 形)合回前执行 `python tools/reshape_cards.py`,把分支线性史按卡末态快照压成一卡一笔(可选追加一笔纯文档收尾),master 台账由此每卡一行——工具自带「重塑前后树逐字节一致」断言与 backup 分支,不改任何文件内容;非迭代批免重塑;然后主工作区 `git merge --no-ff <任务分支>`(pre-merge-commit 钩子先跑前置检查——冒烟+单元与集成全套件约 2 分钟,红则停合并提交,细则见第 4 条 ③)——这是主区唯一合法写动作(合回守卫对迭代分支数卡校验,未归卡提交拦下)。**合回落地即 `git worktree remove`+删分支(合回即删,2026-09-19 原裁);post-merge 钩子已在合回落地时自动触发 CI,红态不回滚合并——新开修复 worktree(`fix-<任务名>` 分支,非 iter<N> 形免重塑免数卡)修复提交再合,落地复验追绿后删修复区**。批内的验证义务按 §4 改完即测铁规履行——改完即测自己改过的那部分,不请示、不等点名;收口顺序本身不设跑批关口。**卡片复核前置判据(2026-11-05 用户裁)**:需求卡片的 grilling 复核先于卡片提交、卡片提交先于合回——复核未跑完、缺陷清单未裁完,禁把卡片落 commit(落了也不算成立),`master` 永不落未成立卡;本条与 §2 段三出口铁律、doc-conventions §12.1「已写卡」凭据同判。
3. **合回后验证(远端 CI,2026-11-03 用户裁)**:git merge 合回 master 落地后 post-merge 钩子(`tools/hooks/post-merge`)自动 `git push origin master` 触发远端 CI(Gitea Actions,runner=Mac 本机 host 模式),工作流按改动域分流(分流算法单点=`tools/ci/domain_route.sh`:游戏码批=两域全量含渲染闸,文档批=文档域秒级闸,纯闸件/管线批=零触发)——唯一强制全量验证点。远端结果用 `tools/ci_check.sh <合并提交>` 按需现查(读该提交任务的 run 自身 `status`/`conclusion`,只呈报、不落盘),agent 开工先读 CI 状态的纪律不变,红未修不开新批。**绿须坐实**:零触发批的 run 照样建、照样 conclusion=success,但跑闸步是 `skipped`(run 55 实证)——一条闸都没跑不构成验证,现查件对此呈报「未验」(退出码 4),禁当绿证用;job 列表为空、取不到步级读数或工作流里没有跑闸步时呈报「不可判」,同样禁当绿证。CI 红态不回滚合并——修复走新开修复 worktree(`fix-<任务名>` 分支,非 iter<N> 形免重塑免数卡),修复合回落地即再推再验追绿(追绿同链)。导入缓存刷新(.godot 不入 git 的在册坑)由工作流「导入缓存刷新」步接管。**合回零等待纪律(2026-11-04 用户裁,取代 2026-11-02 回执等待纪律)**:合回落地即向用户呈报本批总结并止回合——不挂后台轮询、不等回执、不被唤醒;CI 结论什么时候要看就现查一次(`tools/ci_check.sh`,run 那时早已落定,秒级)。退役缘由=原轮询件把「commit status 离开 pending」当终局判据,会把 run 未落定期的尝试级失败写成终局红(run 37 实证:开跑 36 秒写红,该 run 三分半后 `conclusion=success`),且零触发批与 runner 排队必熬满超时再写红;同 commit 的多路轮询还重复覆写同一份判定。跑闸等一切分钟级跑批的等待纪律不变——后台运行,禁前台 sleep/轮询循环。
4. **执法分层(全机械,零自觉依赖)**:①`.zcode/hooks/worktree-guard.sh`(PreToolUse,本机件不入 git)——agent 侧:主工作区 Edit/Write 与主区非 merge `git commit` 拦截(豁免 `.zcode/`、`.qoder/`;worktree 内目标快速放行;合并收尾提交凭 MERGE_HEAD 豁免)、`git commit --no-verify` 拦截(该旗标仅属用户)、iter<N> 分支合回的一卡一提交数卡校验、规则闸(写 design//game/ 前核对会话规约读取记录)。②post-merge 钩子——远端 CI 触发(只推送,不挂等待;仅主工作区,任务 worktree 内的合并不触发)。③git 层不设车道钩子——用户手工改动不受限;ZCode 层只约束 agent(子代理不经守卫层)
4. **执法分层(全机械,零自觉依赖)**:①`.zcode/hooks/worktree-guard.sh`(PreToolUse,本机件不入 git)——agent 侧:主工作区 Edit/Write 与主区非 merge `git commit` 拦截(豁免 `.zcode/`、`.qoder/`;worktree 内目标快速放行;合并收尾提交凭 MERGE_HEAD 豁免)、`git commit --no-verify` 拦截(该旗标仅属用户)、iter<N> 分支合回的一卡一提交数卡校验、规则闸(写 design//game/ 前核对会话规约读取记录)。②post-merge 钩子——远端 CI 触发(只推送,不挂等待;仅主工作区,任务 worktree 内的合并不触发)。③git 层钩子=合回链两件:pre-merge-commit(合回前置检查,2026-11-06 用户裁:主区 merge 落合并提交前自动跑 `tools/run_all_gates.sh --premerge`=冒烟+单元与集成全套件 4 分片,热导入缓存约 2 分钟;exit≠0 拦合并提交不拦合并动作——红态仓库停 MERGE_HEAD 态,修红 git commit 续合或 git merge --abort 放弃;快进合并不触发;--no-verify 可跳过且仅属用户)+post-merge(远端 CI 触发,只推送,不挂等待);合回链之外 git 层不设车道钩子——用户手工改动不受限;ZCode 层只约束 agent(子代理不经守卫层)
5. **会话单区(2026-11-04 用户裁)**:建区粒度=回合——同一时刻每会话至多一个 worktree 在役;合入删区之前,会话内用户追加的事项一律并入在役区推进,禁为追加事项另开新区。区内全部事项完成后只做一次合入(一次 `git merge --no-ff`、post-merge 一次推送、远端 CI 只跑一次),追加事项在合入提交信息里分列;无改动需求的事项不建区。合回删区后用户再发令的事项=新回合,另开新区(区名取该回合主任务名)。CI 红的 `fix-<任务名>` 修复区属同一回合的追绿延续,不另算新区。迭代实现批(iter<N> 形分支)独占回合——其他类事项不得并入迭代区(合回守卫数卡校验本就拦未归卡提交),待其合回后按新回合另开。
## 2. 五段流水线
@@ -49,8 +49,8 @@
7. **验收改动先行声明**——验收变化(基线换代/探针增改/旧断言改判)预先写进需求卡片,禁事后补
**完成**
8. **合回后 CI 验证(唯一强制全量验证点)**——主工作区 `git merge --no-ff <任务分支>` 落地后 post-merge 钩子自动推送 origin master 触发远端 CI;远端按改动域分流(§1 第 3 条),全量档兜的是跨卡触面、探针与未申报套件的消费漏网(迭代 44 实证),判定与红项用 `tools/ci_check.sh <合并提交>` 按需现查(读 run 终局态,只呈报不落盘)。合回前独立验收审计通过(iter 技能第 5 步),未过不合回。`tools/run_all_gates.sh` 是全部验证闸的**唯一入口、唯一清单与唯一域划分(含选闸算法与套件族映射表)**,闸件增减、分域调整都只改它;**本文与其他文档禁另列闸件清单**(清单散落多处正是 iter27 漏跑 `probe_gate --full` 的成因)。开发期的验证义务与选档一律见 §4,CI 全量档不替代它。闸件(`tools/` 下机检脚本与闸壳)自身的改动不占 CI 全量——其质量义务=按 §4 把该件实跑一遍(最小参数全路径含产出落盘)并核对判据口径未变;闸真崩溃时远端 CI 判红在案(job 失败=verdict 红,fail-closed 语义保留)
9. **重跑语义(2026-10-30 用户裁)**——同一份字节禁止第二次人手跑;允许的重跑只剩两类:① CI 红后的修复合回(post-merge 钩子再推再验,每轮修复至多触发一轮);② 用户自发发起的全量回归。「跑都跑了再跑一遍保险」「顺手改点再补一跑」都不构成重跑理由。导入缓存刷新由远端工作流「导入缓存刷新」步接管,合并后无手工动作
8. **合回后 CI 验证(唯一强制全量验证点)**——主工作区 `git merge --no-ff <任务分支>` 落地后 post-merge 钩子自动推送 origin master 触发远端 CI;远端按改动域分流(§1 第 3 条),全量档兜的是跨卡触面、探针与未申报套件的消费漏网(迭代 44 实证),判定与红项用 `tools/ci_check.sh <合并提交>` 按需现查(读 run 终局态,只呈报不落盘)。合回前独立验收审计通过(iter 技能第 5 步),未过不合回。`tools/run_all_gates.sh` 是全部验证闸的**唯一入口、唯一清单与唯一域划分(含选闸算法与套件族映射表)**,闸件增减、分域调整都只改它;**本文与其他文档禁另列闸件清单**(清单散落多处正是 iter27 漏跑 `probe_gate --full` 的成因)。开发期的验证义务与选档一律见 §4,CI 全量档不替代它。闸件(`tools/` 下机检脚本与闸壳)自身的改动不占 CI 全量——其质量义务=按 §4 把该件实跑一遍(最小参数全路径含产出落盘)并核对判据口径未变;闸真崩溃时远端 CI 判红在案(job 失败=verdict 红,fail-closed 语义保留)。合回前的 pre-merge-commit 前置检查(§1 第 4 条 ③)是当场拦截面不是验证台账——不落回执不入基线,不改变本条「唯一强制全量验证点」口径
9. **重跑语义(2026-10-30 用户裁)**——同一份字节禁止第二次人手跑;允许的重跑只剩三类:① CI 红后的修复合回(post-merge 钩子再推再验,每轮修复至多触发一轮);② 用户自发发起的全量回归;③ 合回 pre-merge-commit 前置检查(§1 第 4 条 ③——与合回后远端跑批判面不同档〔拦截面 vs 台账〕,不算同一判面的二次人手跑)。「跑都跑了再跑一遍保险」「顺手改点再补一跑」都不构成重跑理由。导入缓存刷新由远端工作流「导入缓存刷新」步接管,合并后无手工动作
10. **批次完成后的登记义务**——合回 master 后按 doc-conventions §12.1 推进待办清单条目状态(做完整条删除),涉及迭代编排的在 roadmap 迭代总表与顶部「当前位」节换代——执行入口=项目技能 batch-closure(批后收尾与中间文档退役),跨会话教训写记忆库。批次过程与结论由 git 提交信息承载,禁止再写会话状态文件(见 doc-conventions 第 10 节)。`design/` 与根规则文件(`AGENTS.md`/`roadmap.md`/`待办清单.md`/`体验验证清单.md`)实现批禁写,见 §12
11. **文档同步闸(实现批禁写文档)**——实现批**禁写 `design/` 整个目录与根规则文件**,**另禁写 `game/tests/README.md`(能力清单,归写卡段维护,test-conventions §8.9)**,要往外交代的东西一律落 `reports/`:实现报告 `reports/iter<N>-实现报告-<日期>.md`(各卡状态+实机目检总清单+遗留读数义务+实现决定记录——超白名单/超卡片明列范围事项逐条记「决了什么/为什么/代价/撤销方式」,全部按卡执行时只写一行「零拍板」)、GDD 反哺点清单 `reports/iter<N>-反哺点-<日期>.md`(每条写清哪个文件哪一节、改成什么、依据是什么)。设计权威文档(GDD/art-bible)**任何批都不直改**:用户在会话中逐点裁决后,由工程/文档设计改动批(直改类)写入 `design/`(执行入口=doc-design 技能),并把反哺条目从 `reports/` 清掉(吸收即死;退役执行=batch-closure 技能)。机械面文档(roadmap 行/术语表/README 工具注)同样归工程/文档设计改动批在合并前更新。反哺点未落地期间相关 GDD 节视为待反哺,读前先查 `reports/` 与待办清单;需求卡片必列「关联文档同步面」按两层标注
12. **实机目检归用户,只登记不阻塞**——真人检测项登记进实现报告目检总清单与体验验证清单即完成批内义务;禁把等目检当停工理由或完成前置
+1 -1
View File
@@ -29,7 +29,7 @@
## 4. 验证时机(合回后远端 CI 制,2026-11-03 换代)
开发期义务=`process-conventions.md` §4 改完即测铁规:本批新增/改动的验收测试自己跑绿、读数写进该步提交信息才算完成(卡内只测自己改的东西)。全量验证归合回后的远端 CI:git merge 合回 master 落地后 post-merge 钩子推送 origin master,远端按改动域分流跑 `tools/run_all_gates.sh`(游戏码批=不带参数档两域全量、单元与集成测试全套件——跨卡触面与关联档选测的盲区由全量兜底;文档批=文档域秒级闸;分流算法单点=`tools/ci/domain_route.sh`),远端结果用 `tools/ci_check.sh <合并提交>` 按需现查(读 run 自身 `status`/`conclusion`,只呈报、不落盘——合回不等待、不唤醒,process-conventions §1 第 3 条);CI 红态下修复合回落地即再推再验追绿(修复走新开 `fix-<任务名>` 修复 worktree,process-conventions §1)。同一份字节禁止第二次人手跑。该脚本是全部验证闸的唯一入口与唯一清单,域划分与选闸算法只写在脚本里,本文禁另列闸件清单。
开发期义务=`process-conventions.md` §4 改完即测铁规:本批新增/改动的验收测试自己跑绿、读数写进该步提交信息才算完成(卡内只测自己改的东西)。合回 merge 提交落笔前另有 pre-merge-commit 前置检查(2026-11-06 用户裁:冒烟+单元与集成全套件 4 分片,约 2 分钟——当场拦截面,不落回执;细则见 process-conventions §1 第 4 条 ③)。全量验证归合回后的远端 CI:git merge 合回 master 落地后 post-merge 钩子推送 origin master,远端按改动域分流跑 `tools/run_all_gates.sh`(游戏码批=不带参数档两域全量、单元与集成测试全套件——跨卡触面与关联档选测的盲区由全量兜底;文档批=文档域秒级闸;分流算法单点=`tools/ci/domain_route.sh`),远端结果用 `tools/ci_check.sh <合并提交>` 按需现查(读 run 自身 `status`/`conclusion`,只呈报、不落盘——合回不等待、不唤醒,process-conventions §1 第 3 条);CI 红态下修复合回落地即再推再验追绿(修复走新开 `fix-<任务名>` 修复 worktree,process-conventions §1)。同一份字节禁止第二次人手跑。该脚本是全部验证闸的唯一入口与唯一清单,域划分与选闸算法只写在脚本里,本文禁另列闸件清单。
行为探针使用随机化玩家行为(封印/停手/刷新/无效点击)——自动玩家不做的动作就是测试盲区。
## 5. 测试函数怎么写——验收清单条目的落码规范
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# 合回前置检查钩子(git pre-merge-commit,2026-11-06 用户裁)——主工作区 git merge 落合并提交前
# 跑最小验证面:tools/run_all_gates.sh --premerge(冒烟测试+单元与集成全套件 4 分片,热导入缓存约 2 分钟)。
# 语义(git 2.45.2.windows.1 本机实测):本钩子拦的是「合并提交」不是「合并动作」——exit≠0 时合并结果
# 已进暂存区、仓库停 MERGE_HEAD 待提交态;处置二选一:修红后 git commit 续合,或 git merge --abort 放弃。
# 快进合并不触发本钩子(git 语义)——本仓合回铁规恒 --no-ff,漏写时远端 CI 兜底仍在。
# --no-verify 可整体跳过(实测)——仅属用户,agent 禁用(worktree-guard 在案)。
# 仅主工作区触发(worktree 内合并不触发,与 post-merge 同款 git-dir 判别)。
# 冷缓存注意:本机首次跑批前先刷导入($GODOT --headless --path game --editor --quit,约 20 秒),
# 否则冷导入可致套件内建 120 秒超时假红;主工作区 .godot 缓存常驻,日常免此步。
set -u
_GD="$(cd "$(git rev-parse --git-dir)" && pwd)"
_CD="$(cd "$(git rev-parse --git-common-dir)" && pwd)"
[ "$_GD" = "$_CD" ] || exit 0
REPO="$(git -C "$(dirname "$0")" rev-parse --show-toplevel 2>/dev/null || pwd)"
bash "$REPO/tools/run_all_gates.sh" --premerge
RC=$?
if [ "$RC" -ne 0 ]; then
echo "" >&2
echo "[pre-merge-commit] ✗ 前置检查红(读数如上)——合并提交未落,仓库停 MERGE_HEAD 待提交态。" >&2
echo " 处置二选一:① 修红后 git commit 续合本合并;② 放弃整次合并=git merge --abort。" >&2
exit 1
fi
echo "[pre-merge-commit] ✓ 前置检查绿——合并提交继续。"
exit 0
+45 -11
View File
@@ -9,7 +9,11 @@
# 只服务开发期按域跑批;--gates 档另为远端纯闸件/管线批的执行形态(domain_route.sh 判 gates → ci.yml 映射 --gates)。
# 跑闸时代码面带着未提交改动,回执会标 dirty_game(验的不是 HEAD 字节)。
#
# 用法:tools/run_all_gates.sh [--code|--docs|--gates|--quick]
# 用法:tools/run_all_gates.sh [--code|--docs|--gates|--premerge|--quick]
# --premerge 合回前置检查档(2026-11-06 用户裁):只跑冒烟测试+单元与集成测试全套件(4 分片,
# 热导入缓存约 2 分钟)——主工作区 git merge --no-ff 落合并提交前由 pre-merge-commit
# 钩子自动调用的拦截面。退出码聚合即判定(三硬闸口径),不落回执、不比基线——
# 验证台账归合回后远端 CI,本档只做当场拦截。快进合并不触发钩子;--no-verify 仅属用户。
# --code 只跑代码域闸(冒烟测试/系统测试/单元与集成测试/场景探针/
# 渲染闸/静态扫描+熵增闸)
# ——本批改动含 game/ 下非 .md 的行为载体(代码/测试脚本/探针/场景/数据表/素材)时用。
@@ -56,10 +60,10 @@ DOMAIN="full"
while [ $# -gt 0 ]; do
case "$1" in
--quick) QUICK=1 ;;
--code|--docs|--gates)
[ "$DOMAIN" = "full" ] || { echo "用法: --code/--docs/--gates 互斥"; exit 2; }
--code|--docs|--gates|--premerge)
[ "$DOMAIN" = "full" ] || { echo "用法: --code/--docs/--gates/--premerge 互斥"; exit 2; }
DOMAIN="${1#--}" ;;
*) echo "用法: tools/run_all_gates.sh [--code|--docs|--gates|--quick]"; exit 2 ;;
*) echo "用法: tools/run_all_gates.sh [--code|--docs|--gates|--premerge|--quick]"; exit 2 ;;
esac
shift
done
@@ -67,7 +71,7 @@ done
# 每个代码面 worktree 用独立 Godot user 目录(game/override.cfg 由 setup 脚本自愈生成;主区 no-op)。
# 并行批共用一套夹具、日志与截图目录会互相踩盘造成假红——隔离建不起来就拒跑(fail-closed)。
# 隔离只服务代码域(Godot 运行面);文档域与闸件自检档不启引擎,跳过省 0.8 秒。
if [ "$DOMAIN" = "code" ] || [ "$DOMAIN" = "full" ]; then
if [ "$DOMAIN" = "code" ] || [ "$DOMAIN" = "full" ] || [ "$DOMAIN" = "premerge" ]; then
"$REPO/tools/setup_worktree_userdir.sh" "$REPO" || {
echo "[gates] FAIL — user 目录隔离未建立,拒绝在共享目录上跑闸。"
exit 1
@@ -82,7 +86,8 @@ export PYTHONIOENCODING=utf-8
# 代码面(game/ 下非 .md 的行为载体)是否带着未提交改动(只算已跟踪文件——闸件自身产物都落在
# gitignore 面)。脏树跑出来的回执记的是 HEAD,验的却是别的字节,回执标 dirty_game 由 CI 落账留痕。
DIRTY_GAME=0
if [ -n "$(git -C "$REPO" -c core.quotepath=false diff HEAD --name-only -- game/ 2>/dev/null | grep -viE '\.md$')" ]; then
# premerge 档不查脏树:钩子跑在合并提交落笔前,合并内容本就未提交(diff HEAD=待合内容),无回执可比。
if [ "$DOMAIN" != "premerge" ] && [ -n "$(git -C "$REPO" -c core.quotepath=false diff HEAD --name-only -- game/ 2>/dev/null | grep -viE '\.md$')" ]; then
DIRTY_GAME=1
echo "[gates] 警告:代码面(game/ 下非 .md)有未提交改动——本次回执将标 dirty_game,不能用于合回(先提交再跑)。"
fi
@@ -130,7 +135,7 @@ in_domain() { # in_domain <code|docs|both>
# 所以 git worktree 检出的工程天生缺全部美术素材:main_theme.tres 解析失败 → 按钮五个样式盒全空
# → 冒烟 V1 成片假红;渲染闸又会因素材缺位把两段 golden 直接跳过(见 render_gate.sh)。
# 缺位即拒跑——禁带着空盘跑完,把「什么都没比」的回执当全绿合回。
if in_domain code; then
if in_domain code || [ "$DOMAIN" = "premerge" ]; then
_miss=""
for a in assets/art/ui/button_stone.png assets/fonts/ui_font_fusion_pixel_zh_hans.ttf \
assets/art/board/seal_mark.png assets/art/character/character_hero_idle.png \
@@ -405,8 +410,8 @@ gate_unit() { # run_all 是 --script 模式,退出码为唯一判据;分
# ══ 闸件自检段(gates 档专属)══
# 闸壳自检:bash -n 过全部 bash 闸件与管线件——闸件批改的就是这些件,语法坏=下一批真跑闸时才炸,
# 闸件批当场拦住。收件面动态收全:tools/ 全部 .sh+game/tests 顶层 .sh(静态扫描)+hooks/post-merge
# (无后缀件);新增 bash 闸件自动入检面,无需维护清单。
# 闸件批当场拦住。收件面动态收全:tools/ 全部 .sh+game/tests 顶层 .sh(静态扫描)+hooks/ 无后缀件
# (post-merge/pre-merge-commit);新增 bash 闸件自动入检面,无需维护清单。
gate_shell_selfcheck() {
local name="闸壳自检"
local rc=0 _n=0 _bad=0 _s
@@ -414,7 +419,7 @@ gate_shell_selfcheck() {
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
find "$REPO/tools/hooks" -maxdepth 1 -type f ! -name '*.sh' 2>/dev/null; } | sort -u
)
echo ""
echo "════════════════════════════════════════════"
@@ -442,6 +447,15 @@ gate_shell_selfcheck() {
return 0
}
# ══ 合回前置检查档(premerge,2026-11-06 用户裁)══
# 冒烟+单元与集成全套件(4 分片)——合回 merge 点的最小人工拦截面;不是全量、不替代远端 CI。
# 选闸推导整段跳过(本档不按 diff 选闸——合回是最后拦截点,恒全套件);退出码聚合即判定,
# 不落回执不比基线(回执与基线面归远端 CI,本档读数只进合并现场)。
if [ "$DOMAIN" = "premerge" ]; then
gate "冒烟测试" "smoke_boot.log" "$PY" "$REPO/tools/run_smoke.py"
gate_unit
fi
# ══ 代码域 ══
if in_domain code; then
# ── 冒烟测试(极小启动场景:真实开机路径存活检查,零逻辑判定,秒级)──
@@ -493,7 +507,10 @@ fi
# ── 静态扫描(代码域与闸件自检档)+ 熵增闸(各档都跑:它查顶层目录/根文件/图像白名单/退役路径,
# 文档批新增或挪动 design/ 下的文件同样会绊它;纯文本扫描,秒级)──
if [ $QUICK -eq 0 ]; then
# premerge 档不入此面:前置检查只盯冒烟+单元,静态扫描与熵增闸由合回后远端 CI 盯。
if [ "$DOMAIN" = "premerge" ]; then
echo ""; echo "[gates] 静态扫描/熵增闸 → 不入前置检查面(premerge;闸况归合回后远端 CI)"
elif [ $QUICK -eq 0 ]; then
if in_domain code || [ "$DOMAIN" = "gates" ]; then
gate "静态扫描" "static_scan.log" bash "$GAME/tests/static_scan.sh"
fi
@@ -506,6 +523,23 @@ else
echo ""; echo "[gates] 静态扫描(代码域)/ 熵增闸 → SKIP(--quick)"
fi
# ── premerge 档汇总:退出码聚合即判定(三硬闸口径),不落回执不比基线 ──
if [ "$DOMAIN" = "premerge" ]; then
RC=0
for _e in "${EXITS[@]}"; do
[ "$_e" -ne 0 ] && RC=1
done
echo ""
echo "════════════════════════════════════════════"
echo " 前置检查汇总(premerge——退出码即判定,不落回执)"
for _i in "${!NAMES[@]}"; do
_s="绿"; [ "${EXITS[$_i]}" -ne 0 ] && _s="红"
printf ' %s:%s(exit=%s)\n' "${NAMES[$_i]}" "$_s" "${EXITS[$_i]}"
done
echo "════════════════════════════════════════════"
exit $RC
fi
# ── 与存量基线比对 ──
"$PY" - "$BASELINE" "$RECEIPT" "$QUICK" "$DOMAIN" "$REPO" "$DIRTY_GAME" "$SCOPE" "$ONLY_TERMS" "${NAMES[@]}" -- "${EXITS[@]}" -- "${LOGS[@]}" <<'PYEOF'
import hashlib, json, os, re, subprocess, sys