合并(CI 读数与迭代51连带面·工程+文档): ①现查件堵假绿——绿须坐实跑闸步真跑过,零触发批呈报未验(退出码4)、读数不可信呈报不可判(2),run 55 实证跑闸步 skipped 而 conclusion=success;②迭代 51 四红项随迁——forge_table 词表计数 22→21(卡①删 charge_full)、system_contracts 补 facts 第五参、save_manager_domain 改公开名 migrate_legacy_save、breakpoint_store 参数补类型(09 R3),件核全仓零残留同类点;③口径同步 AGENTS+process-conventions §1/§8+test-conventions §8.7+技能两镜像。本机验证三套件 exit0+类型闸归零+--docs 零新增红;master 停在 5d7579e7 的 run 56 红由本批修复追绿。遗留: settlement_wiring_snapshot golden 平台绑定(本机红/远端绿)待登记
ci / gates (push) Failing after 8m2s

This commit is contained in:
DKChan committed 2026-11-04 07:51:06 +08:00
commit 7046cbaf83
8 files changed
+44 -25

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@
## 运行与验证(工作目录 = 含 project.godot 的切片目录)
- **合回验证 = 远端 CI(2026-11-03 用户裁,取代 2026-10-30 本地 CI)**: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 §6),不构成验收;全量回归由用户自发发起。**runner 依赖**:Mac 需开机在线(关机=远端任务排队延后);换机/重装按 `tools/ci/mac_setup.sh` 自举
- **合回验证 = 远端 CI(2026-11-03 用户裁,取代 2026-10-30 本地 CI)**: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 §6),不构成验收;全量回归由用户自发发起。**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/`)
+1 -1
View File
@@ -11,7 +11,7 @@
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> 形免重塑免数卡)修复提交再合,落地复验追绿后删修复区**。游戏类测试(冒烟/单测/MC/探针)批内默认零执行(两入口铁规,见 §4),用户点名或当场问才跑
3. **合回后验证(远端 CI,2026-11-03 用户裁取代本地 CI)**: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 状态的纪律不变,红未修不开新批。CI 红态不回滚合并——修复走新开修复 worktree(`fix-<任务名>` 分支,非 iter<N> 形免重塑免数卡),修复合回落地即再推再验追绿(追绿同链;本地跑批件 `ci_runner.sh` 与本地追绿钩子 post-commit 已退役)。导入缓存刷新(.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/轮询循环。
3. **合回后验证(远端 CI,2026-11-03 用户裁取代本地 CI)**: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> 形免重塑免数卡),修复合回落地即再推再验追绿(追绿同链;本地跑批件 `ci_runner.sh` 与本地追绿钩子 post-commit 已退役)。导入缓存刷新(.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 层不设车道钩子(pre-commit 直改闸已退役)——用户手工改动不受限;ZCode 层只约束 agent(子代理不经守卫层)
5. **会话单区(2026-11-04 用户裁)**:建区粒度=回合——同一时刻每会话至多一个 worktree 在役;合入删区之前,会话内用户追加的事项一律并入在役区推进,禁为追加事项另开新区。区内全部事项完成后只做一次合入(一次 `git merge --no-ff`、post-merge 一次推送、远端 CI 只跑一次),追加事项在合入提交信息里分列;无改动需求的事项不建区。合回删区后用户再发令的事项=新回合,另开新区(区名取该回合主任务名)。CI 红的 `fix-<任务名>` 修复区属同一回合的追绿延续,不另算新区。迭代实现批(iter<N> 形分支)独占回合——其他类事项不得并入迭代区(合回守卫数卡校验本就拦未归卡提交),待其合回后按新回合另开。
+1 -1
View File
@@ -128,7 +128,7 @@
3. 检查测试削弱:删除断言、放宽阈值、缩小扫描面、换下层直调,逐处要卡片申报或用户批准。
4. 核对内部字段直查的受控例外:直查行是否已在对照表注明理由(该行为无公开可观测面可钉)。
**审计深度分级(2026-11-04 立)**:审计按卡内申报的验收档(§8.3)逐卡定深度;梯度只落在昂贵的判断件上,机械且堵造假向量的两件(削弱检查、例外核对)三档全保留不抽。中/低档省略的测试复跑以远端 CI 读数为执行证据——`tools/ci_check.sh <合并提交>` 的现查输出(commit+run 号+conclusion)是机器产物,run 号在远端永久可复核,审计报告照抄该行即可(2026-11-04 用户裁:回执等待件退役,证据由落盘文件改为现查读数;「不采信实现者自述已全绿」的口径不变):
**审计深度分级(2026-11-04 立)**:审计按卡内申报的验收档(§8.3)逐卡定深度;梯度只落在昂贵的判断件上,机械且堵造假向量的两件(削弱检查、例外核对)三档全保留不抽。中/低档省略的测试复跑以远端 CI 读数为执行证据——`tools/ci_check.sh <合并提交>` 的现查输出(commit+run 号+conclusion)是机器产物,run 号在远端永久可复核,审计报告照抄该行即可;照抄前先核对读数必须是「绿」——「未验」(零触发批跑闸步全跳过)与「不可判」不作证据,那种批的回补办法是等一次含代码面的合回或用户自发全量回归(2026-11-04 用户裁:回执等待件退役,证据由落盘文件改为现查读数;「不采信实现者自述已全绿」的口径不变):
| 审计动作 | 高 | 中 | 低 |
|---|---|---|---|
+1 -1
View File
@@ -222,7 +222,7 @@ func _bp_read_int(key: String, bp: Dictionary, fallback: int, params: Dictionary
# ---- 断点域六成员公开读写面(5.1 封装修复——宿主私取清零:方法体一律一行转发自有成员,零逻辑增量)----
# 断点承载注入(宿主构造后接线——save_manager 后绑)
func bind_save_manager(sm) -> void:
func bind_save_manager(sm: SaveManagerCore) -> void:
_save_manager = sm
@@ -13,6 +13,7 @@ const RngServiceScript := preload("res://core/rng_service.gd")
const StatusContainerScript := preload("res://core/status_container.gd")
const UnitContainerScript := preload("res://core/unit_container.gd")
const DamageControllerScript := preload("res://core/damage_controller.gd")
const BattleFactsScript := preload("res://core/battle_facts.gd")
const Kit := preload("res://tests/unit/test_kit.gd") # T6 收口:_check/汇总退出样板单点
@@ -98,13 +99,13 @@ func _test_ticket_nine_columns() -> void:
func _bare_controller():
var db = ConfigDBScript.new()
# 只取 open_ticket 纯构造面——四个依赖一律传真实实例,不用空桩:
# DamageController 的 db/rng/units/effects 全是定型标注,传无类型的空桩会触发
# 只取 open_ticket 纯构造面——五个依赖一律传真实实例,不用空桩:
# DamageController 的 db/rng/units/effects/facts 全是定型标注,传无类型的空桩会触发
# 「Trying to assign value of type '' to a variable of type '…'」脚本错误——该错误不改退出码,
# 却让赋值静默失败(成员留 null),脚本错误扫描落地后必红。三者均可零消费构造。
var units = UnitContainerScript.new(db)
var effects = StatusContainerScript.new(db)
return DamageControllerScript.new(db, RngServiceScript.new(), units, effects)
return DamageControllerScript.new(db, RngServiceScript.new(), units, effects, BattleFactsScript.new())
# ---- B4→B3:增益授予布尔受理口径(受理/拒绝二元面 + 投影一致性) ----
@@ -478,7 +478,7 @@ func _smoke_save_slots() -> void:
fsrc.store_string(JSON.stringify({"version": 7, "meta": {"keystone_balance": 1234}}))
fsrc.close()
DirAccess.remove_absolute(slot_tpl % 1)
ml.slots._migrate_legacy_save(legacy_src, slot_tpl % 1, legacy_src + ".migrated.bak")
ml.slots.migrate_legacy_save(legacy_src, slot_tpl % 1, legacy_src + ".migrated.bak")
var migrated: Dictionary = JSON.parse_string(FileAccess.get_file_as_string(slot_tpl % 1))
Kit.check("sv12 旧档字节迁入槽 1(逐域相等——余额 1234)〔卡① sv12〕",
int((migrated.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234)
@@ -489,7 +489,7 @@ func _smoke_save_slots() -> void:
var fsrc2 := FileAccess.open(legacy_src, FileAccess.WRITE)
fsrc2.store_string(JSON.stringify({"version": 7, "meta": {"keystone_balance": 777}}))
fsrc2.close()
ml.slots._migrate_legacy_save(legacy_src, slot_tpl % 1, legacy_src + ".migrated.bak")
ml.slots.migrate_legacy_save(legacy_src, slot_tpl % 1, legacy_src + ".migrated.bak")
var slot1_now: Dictionary = JSON.parse_string(FileAccess.get_file_as_string(slot_tpl % 1))
Kit.check("sv12 幂等:槽 1 已存在不重迁(预置 777 零覆盖、源保留)〔卡① sv12〕",
int((slot1_now.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234
@@ -216,5 +216,5 @@ func _test_negative_cases() -> void:
func _test_effect_kinds_closed() -> void:
Kit.check("效果引擎效果类型闭集 32 种(甲案词表退役——overflow/attack_segments 摘名)〔锻造工坊批 V11〕",
EffectEngineScript.EFFECT_KINDS.size() == 32)
Kit.check("效果引擎事件词表在册 22 种(迭代 46 换图系扩签 refresh_request/board_refreshed 换代)〔锻造工坊批 V11〕",
EffectEngineScript.EVENTS.size() == 22)
Kit.check("效果引擎事件词表在册 21 种(charge_full 位摘名——蓄力层删键批)〔锻造工坊批 V11〕",
EffectEngineScript.EVENTS.size() == 21)
+32 -14
View File
@@ -1,11 +1,12 @@
#!/usr/bin/env bash
# CI 现查件——按需读远端 run 的终局态,只呈报、不落盘(2026-11-04 用户裁:回执等待件与本地缓存判定退役)。
# 用法: tools/ci_check.sh [<提交hash>](缺省=当前 HEAD)
# 判据:Gitea commit status 只用来取本次任务的 run 链接,绿红一律认 run 自身的 status/conclusion——
# run 未 completed(排队/在跑/尝试级中间态)与零触发批(纯闸件/管线批不建任务)一律呈报「未落定」,不写红。
# 判据:Gitea commit status 只用来取本次任务的 run 链接,绿红一律认 run 自身的 status/conclusion,
# 且绿必须坐实「跑闸步真跑过」——零触发批的 run 照样建、照样 success,但跑闸步是 skipped,
# 一条闸都没跑,读成绿就是假绿(2026-11-04 run 55 实证)。run 未 completed(排队/在跑/尝试级中间态)呈报未落定。
# 调用面:开工前读态(红未修不开新批)、合回后想知道结果时手动查——合回流程不等待、不唤醒,呈报即回合止。
# 授权:GITEA_TOKEN 环境变量优先,否则读 git 凭据库(host=git.kuaimeo.com)。
# 退出码: 0=绿 1=红 2=未落定/零触发/查不到 3=参数或令牌错误
# 退出码: 0=绿(跑闸步真跑过) 1=红 2=未落定/不可判/查不到 3=参数或令牌错误 4=未验(零触发批,跑闸步全跳过)
set -u
COMMON="$(git -C "$(dirname "$0")" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" \
|| { echo "不在 git 仓库内"; exit 3; }
@@ -49,6 +50,11 @@ def undetermined(msg):
sys.exit(2)
def unknown(msg):
print("CI现查: 不可判 | commit %s | %s" % (short(base), msg))
sys.exit(2)
try:
s = get(api + "/commits/%s/status" % base)
except urllib.error.HTTPError as e:
@@ -59,7 +65,7 @@ except Exception as e:
statuses = s.get("statuses") or []
target = next((x.get("target_url", "") for x in statuses if x.get("target_url")), "")
if not statuses:
undetermined("远端无该提交的任务——零触发批(纯闸件/管线批不建任务),或推送未触发 CI")
undetermined("远端无该提交的 status——该批没推上去(或推的不是 master);零触发批有 run,走 run 侧读数")
m = re.search(r"/actions/runs/(\d+)", target or "")
if not m:
@@ -75,20 +81,32 @@ rstatus, rconclusion = run.get("status"), run.get("conclusion")
if rstatus != "completed":
undetermined("run %s status=%s(runner 需开机在线;队列与日志见 %s/actions)" % (run_id, rstatus, web))
try:
jobs = get(api + "/actions/runs/%s/jobs" % run_id).get("jobs", []) or []
except Exception as e:
unknown("查 run %s 的 job 明细失败:%s——取不到步级读数就不下绿" % (run_id, e))
gate_steps = [x.get("conclusion") for j in jobs for x in (j.get("steps") or []) if "跑闸" in (x.get("name") or "")]
if rconclusion == "success":
print("CI现查: 绿 | commit %s | run %s conclusion=success" % (short(base), run_id))
if not jobs:
unknown("run %s conclusion=success 但 job 列表为空,无法确认闸是否真跑" % run_id)
if not gate_steps:
unknown("run %s 里没有「跑闸」步——工作流结构已改,本件的步级判据需随动(查 %s/actions/runs/%s)" % (run_id, web, run_id))
if all(c == "skipped" for c in gate_steps):
print("CI现查: 未验 | commit %s | run %s conclusion=success 但跑闸步全为 skipped——零触发批,远端一条闸都没跑" % (short(base), run_id))
sys.exit(4)
print("CI现查: 绿 | commit %s | run %s conclusion=success,跑闸步 %d 条真跑过(闸况明细查 job 日志)" % (short(base), run_id, len(gate_steps)))
sys.exit(0)
red_jobs = []
try:
jobs = get(api + "/actions/runs/%s/jobs" % run_id)
for j in jobs.get("jobs", []):
if j.get("conclusion") in ("success", "skipped", None):
continue
bad_steps = [x.get("name") for x in (j.get("steps") or []) if x.get("conclusion") not in ("success", "skipped")]
red_jobs.append((j.get("name"), bad_steps))
except Exception as e:
red_jobs.append(("<job明细获取失败:%s>" % e, []))
for j in jobs:
if j.get("conclusion") in ("success", "skipped", None):
continue
bad_steps = [x.get("name") for x in (j.get("steps") or []) if x.get("conclusion") not in ("success", "skipped")]
red_jobs.append((j.get("name"), bad_steps))
if not red_jobs:
red_jobs.append(("<job 全为成功或跳过——conclusion=%s 与步级读数矛盾,取日志核对>" % rconclusion, []))
print("CI现查: 红 | commit %s | run %s conclusion=%s" % (short(base), run_id, rconclusion))
for name, steps in red_jobs: