From c40db898aaa5d268c6dcea9297f3fd811634aa42 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Tue, 3 Nov 2026 20:37:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E6=9E=B6=E6=9E=84=E5=AE=A1?= =?UTF-8?q?=E6=9F=A5P1=E4=BD=99=E9=A1=B9=C2=B7=E9=97=B8=E4=BB=B6=E8=90=BD?= =?UTF-8?q?=E5=9C=B0):=20=C2=A7=E5=9B=9B=E5=BE=85=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E4=B8=89=E9=A1=B9=E5=90=8C=E6=89=B9=E5=81=9A=E5=AE=8C=E5=B9=B6?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E6=96=87=E6=A1=A3=E2=80=94=E2=80=94=E2=91=A0?= =?UTF-8?q?static=5Fscan.sh=20=E7=9A=84=20S8=20=E5=8A=A0=20settlement/=20?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E5=9F=9F=E5=8F=8D=E5=90=91=E5=AF=B9=E8=B4=A6?= =?UTF-8?q?(=E5=AE=9E=E6=9C=89=20.gd=20=E6=9C=AA=E7=99=BB=E8=AE=B0?= =?UTF-8?q?=E8=BF=9B=E6=B8=85=E5=8D=95=E5=8D=B3=E7=BA=A2,=E5=8F=AA?= =?UTF-8?q?=E8=A6=86=E7=9B=96=E8=AF=A5=E7=9B=AE=E5=BD=95=E5=B9=B6=E5=86=99?= =?UTF-8?q?=E6=98=8E=E6=A0=B9=E4=B8=8B=E5=9B=9B=E4=BB=B6=E7=BB=93=E7=AE=97?= =?UTF-8?q?=E7=A0=81=E4=B8=8D=E5=9C=A8=E7=9B=AE=E5=BD=95=E5=9F=9F=E5=86=85?= =?UTF-8?q?=E3=80=81=E4=BB=8D=E5=BD=92=E3=80=8C=E6=A8=A1=E5=9D=97=E5=BD=92?= =?UTF-8?q?=E5=B1=9E=E3=80=8DB1=20=E7=99=BB=E8=AE=B0=E9=9D=A2),S7=20?= =?UTF-8?q?=E7=9A=84=20S7=5FB1=5FSET=20=E4=B8=8E=20S8=5FFILES=20=E4=B8=A4?= =?UTF-8?q?=E4=BB=BD14=E6=96=87=E4=BB=B6=E6=8A=84=E6=9C=AC=E5=90=88?= =?UTF-8?q?=E4=B8=80=E4=B8=BA=E5=8D=95=E7=82=B9=20B1=5FSET(=E4=B8=A4?= =?UTF-8?q?=E4=BB=BD=E9=9D=A0=E4=BA=BA=E5=B7=A5=E5=90=8C=E6=AD=A5=E5=BF=85?= =?UTF-8?q?=E6=BC=82),=E7=BB=BF=E6=96=87=E6=A1=88=E3=80=8C10=20=E4=B8=AA?= =?UTF-8?q?=20.gd=E3=80=8D=E6=94=B9=E6=B4=BE=E7=94=9F=E8=AE=A1=E6=95=B0?= =?UTF-8?q?=E5=B9=B6=E7=94=B3=E6=8A=A5=E5=8F=8D=E5=90=91=E5=AF=B9=E8=B4=A6?= =?UTF-8?q?=E5=B7=B2=E5=9C=A8=E6=A1=88=20=E2=91=A1audit=5Ftype=5Fhints.py?= =?UTF-8?q?=EF=BC=8Faudit=5Ffile=5Fheaders.py=20=E5=90=84=E8=A1=A5?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=E9=9D=A2=E7=A9=BA=E8=BD=AC=E8=87=AA=E6=A3=80?= =?UTF-8?q?(=E7=BC=BA=E7=9B=AE=E5=BD=95=E6=88=96=E9=9B=B6=E4=B8=AA=20.gd?= =?UTF-8?q?=20=E5=8D=B3=E7=BA=A2=E3=80=81=E7=BB=BF=E6=97=B6=E6=89=93?= =?UTF-8?q?=E5=8D=B0=E9=9D=A2=E4=BB=B6=E6=95=B0;=E5=8E=9F=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=20rglob=20=E7=A9=BA=E9=9B=86=E4=B8=8E=20is=5Fdir=20co?= =?UTF-8?q?ntinue=20=E4=B8=A4=E5=A4=84=E9=9D=99=E9=BB=98=E7=BB=BF,?= =?UTF-8?q?=E8=BF=9D=E8=83=8C=20test-conventions=20=C2=A77=20=E9=9B=B6?= =?UTF-8?q?=E5=91=BD=E4=B8=AD=E8=87=AA=E6=A3=80=E8=BE=85=E8=AF=81=E4=B8=8E?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=E5=89=8D=E8=A8=80=E8=87=AA=E7=AB=8B=E7=BA=AA?= =?UTF-8?q?=E5=BE=8B)=20=E2=91=A2architecture=20S=20=E8=A1=A8=20S8=20?= =?UTF-8?q?=E8=A1=8C=E7=99=BB=E8=AE=B0=E5=8D=95=E7=82=B9=E4=B8=8E=E5=8F=8D?= =?UTF-8?q?=E5=90=91=E5=AF=B9=E8=B4=A6=E5=88=A4=E6=8D=AE=E3=80=81S9/S10=20?= =?UTF-8?q?=E5=81=8F=E5=B7=AE=E5=8F=A5=E6=94=B9=E5=88=A4=E5=B7=B2=E6=B8=85?= =?UTF-8?q?=E5=81=BF=E3=80=81=E7=89=88=E6=9C=AC1.33=E2=86=921.34=20?= =?UTF-8?q?=E2=91=A3=E5=AE=A1=E8=AE=A1=E6=8A=A5=E5=91=8A=20=C2=A74.2=20?= =?UTF-8?q?=E5=BE=85=E6=89=A7=E8=A1=8C=E6=B8=85=E5=8D=95=E6=94=B9=E5=88=A4?= =?UTF-8?q?=E5=B7=B2=E8=90=BD=E5=9C=B0=E5=B9=B6=E8=AE=B0=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E5=8F=A3=E5=BE=84=E3=80=82=E9=AA=8C=E8=AF=81:static=5Fscan=20?= =?UTF-8?q?=E5=85=A8=E9=87=8F13=E6=9D=A1=E8=A7=84=E5=88=99=E7=BB=BF?= =?UTF-8?q?=E9=80=80=E5=87=BA=E7=A0=810;=E4=B8=A4=E4=BB=B6=E8=B4=9F?= =?UTF-8?q?=E8=AF=81(=E9=9D=A2=E6=B8=85=E5=8D=95=E6=94=B9=E6=88=90?= =?UTF-8?q?=E4=B8=8D=E5=AD=98=E5=9C=A8=E7=9B=AE=E5=BD=95=E5=90=84=E8=BF=94?= =?UTF-8?q?=E5=9B=9E1)=E6=AD=A3=E8=AF=81(=E7=9C=9F=E5=AE=9E=E9=9D=A2158?= =?UTF-8?q?=E4=B8=AA=20.gd=20=E5=BD=92=E9=9B=B6PASS)=E3=80=82=C2=A7?= =?UTF-8?q?=E5=9B=9B=20P1=20=E5=9B=9B=E6=9D=A1=E8=87=B3=E6=AD=A4=E5=85=A8?= =?UTF-8?q?=E9=83=A8=E9=97=AD=E5=90=88;=E9=9B=B6=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E5=8D=A1=E7=89=87(=E7=BA=AF=E9=97=B8=E4=BB=B6=E5=B1=9E?= =?UTF-8?q?=E5=B7=A5=E7=A8=8B=E6=94=B9=E5=8A=A8),=E5=BE=85=E5=8A=9E?= =?UTF-8?q?=E6=B8=85=E5=8D=95=E4=B8=8E=E7=AC=AC=E4=BA=94=E8=8A=82P2?= =?UTF-8?q?=E6=8C=89=E7=94=A8=E6=88=B7=E4=BB=A4=E6=9C=AA=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../engineering/architecture/architecture.md | 8 +++---- game/tests/static_scan.sh | 23 +++++++++++++++---- reports/审计-全库架构审查-2026-10-29.md | 9 ++++---- tools/audit_file_headers.py | 22 ++++++++++-------- tools/audit_type_hints.py | 19 ++++++++++----- 5 files changed, 53 insertions(+), 28 deletions(-) diff --git a/design/engineering/architecture/architecture.md b/design/engineering/architecture/architecture.md index 77eb8852..6f0634c5 100644 --- a/design/engineering/architecture/architecture.md +++ b/design/engineering/architecture/architecture.md @@ -2,8 +2,8 @@ ## 文档状态 -- 版本:1.33 -- 最近更新:2026-11-03(架构审查 P1 余项裁决落文——静态扫描域标题补齐至 S13 并登记 S11/S12/S13 三行、S1/S2 与 S3 行加适用域与维持 warning 的裁定注、S9/S10 缺空面自检登记为已知偏差;上一版 1.32=迭代 50 批后收尾随动(preview_reveal 只读预览口登记、天赋树 tree_rules 参数字典 API 换代);沿革查 git) +- 版本:1.34 +- 最近更新:2026-11-03(架构审查 P1 余项·工程改动批随动——S8 行登记清单单点 `B1_SET` 与 settlement/ 目录域反向对账、S9/S10 空面自检查偏差清偿;沿革查 git) - 引擎:Godot 4.7.2-stable(GDScript 单语言;2026-09-07 锁定) - 评审模式:solo(`review-mode.txt`,2026-09-12 用户拍板——TD/LP 门禁按 solo 跳过,用户授权架构产出自主推进、迭代期回调) - 覆盖 GDD:现行全集(根索引=`design/gdd/02-系统清单.md`,文档树构成以根索引为准) @@ -292,11 +292,11 @@ | S5 | core/ 禁裸引用 autoload 全局标识符(`ConfigDB` / `RngService` 直接写 = --script 模式 parse error)——构造注入 only(specialist 升格) | **在位**(硬失败;形态=标识符后紧跟 `.` 的成员访问,排除注释行与字符串字面量) | | S6 | core↔core 信号连接只许方法 Callable(禁 lambda 捕获 self);发射者禁持有订阅者反向引用 | **在位**(2026-10-25 落闸:内联 lambda 禁令机器闸——同行与跨行两形态、豁免注记 `s6-signal-exempt`、盘面零存量;「发射者禁反向引用」句静态判据不成熟,归人工评审面承接、不造弱判据) | | S7 | B1 编排唯一性:B2/B3/B4 API 符号命中文件 ⊆ B1 文件集;A2/B3/B4 零相互直调(B1 AC-43) | **在位**(2026-10-25 落闸:符号清单×B1 域文件集,族内自调用豁免、豁免注记 `s7-b1-exempt`、缺位自检;全符号形态盘面零存量) | -| S8 | 结算路径 `await` 零命中(B1 结算/编排文件集);字面量扫描 = warning 级 + 人工三要素复核(D2 AC-14 口径) | **在位**(2026-10-25 落闸:B1 域文件集 await 零命中,文件集缺位自检、排除注释与字符串;盘面零存量。本行后半句「字面量扫描=warning 级+人工三要素复核」为独立子项、仍归人工面) | +| S8 | 结算路径 `await` 零命中(B1 结算/编排文件集);字面量扫描 = warning 级 + 人工三要素复核(D2 AC-14 口径) | **在位**(2026-10-25 落闸:B1 域文件集 await 零命中,排除注释与字符串;盘面零存量。清单单点=`static_scan.sh` 的 `B1_SET`,S7 的豁免名单由它派生(原两份 14 文件抄本已并点,2026-11-03 架构审查 P1 裁)。自检两件:①清单内文件缺位即红;②**目录域反向对账**——`game/core/settlement/` 实有 `.gd` 未登记进 `B1_SET` 即红(新段类忘登记=await 面静默失效,2026-11-03 补)。反向对账只覆盖 settlement/:装板/授予/效果端口/掷格四件结算码住 `game/core` 根下,纯目录域覆盖不住,根下新增仍由本行「模块归属」B1 登记面+写卡段申报承接。本行后半句「字面量扫描=warning 级+人工三要素复核」为独立子项、仍归人工面 | 域外扩充五件(S11/S12/S13 自 2026-09-29 起陆续落闸,本表原先只登记到 S10,2026-11-03 补齐登记面):**S9** 类型闸(`tools/audit_type_hints.py`,迭代 27 T5——产品码无类型容器/有值返回缺类型/参数缺类型;扫描面=产品码三目录);**S10** 文件头闸(`tools/audit_file_headers.py`——头注 `##` 载体+日期/变更叙事黑名单;扫描面=产品码三目录);**S11** 代码注释禁用词闸(扫描面=`game/` 全域 `.gd`/`.tres`/`.md`/`.json`/`.sh`,词表单点取 `design/gdd/术语表.md`);**S12** 效果引擎无状态化旧件零残留闸(扫描面=`game/` 全域 `.gd`/`.tres`,旧符号与旧 id 字面清单为脚本字面值);**S13** 第三方质量规则闸(`tools/gdstyle_gate.py`,钉 gdstyle v0.4.0;扫描面=产品码三目录,存量以 `tools/gdstyle_baseline.txt` 欠账簿登记,判定=基线外新键红、同键数值恶化红)。 -S11/S12 扫描面覆盖全域,故 sim/probe/tests 的术语与旧件面**在册**;其余十一闸(S1–S10、S13)扫描面止于产品码,非产品码三目录不受约束——分档理由与各目录的运行期承接通道见 code-conventions §4 第 13 条。**防空转自检现行状态**(本文「三层承载与判定原则」要求扫描面为空即判红):S1–S8 与 S11/S12/S13 各自带空面或缺位自检;**S9/S10 现行缺该自检**(`tools/audit_type_hints.py` 与 `tools/audit_file_headers.py` 遇目录不存在时静默零命中并 PASS),属已知偏差,修复归工程改动批、登记在 `reports/审计-全库架构审查-2026-10-29.md` §4.2 标注处。 +S11/S12 扫描面覆盖全域,故 sim/probe/tests 的术语与旧件面**在册**;其余十一闸(S1–S10、S13)扫描面止于产品码,非产品码三目录不受约束——分档理由与各目录的运行期承接通道见 code-conventions §4 第 13 条。**防空转自检现行状态**(本文「三层承载与判定原则」要求扫描面为空即判红):S1–S8 与 S11/S12/S13 各自带空面或缺位自检;**S9/S10 两件已补齐同款自检**(`tools/audit_type_hints.py` 与 `tools/audit_file_headers.py` 缺目录或扫描面零个 `.gd` 一律判红,并绿时打印扫描面件数供核;2026-11-03 架构审查 P1 余项的工程改动批落地,本表此前登记的「静默零命中即 PASS」偏差已清偿)。 ### 三层承载与判定原则 diff --git a/game/tests/static_scan.sh b/game/tests/static_scan.sh index 1be94af6..b8f9c873 100755 --- a/game/tests/static_scan.sh +++ b/game/tests/static_scan.sh @@ -318,13 +318,15 @@ fi # 可回放纯逻辑步骤流(同种子同输入逐位复现),await 会打断确定性——「模块归属」B1 行已写明 # 「禁 await——切片 88 冒烟实证」。本闸为防回归防线(2026-10-24 人工 grep 盘面干净)。 # 扫描面:B1 域文件集(与 architecture.md「模块归属」B1 行同口径)=battle_orchestrator.gd -# + battle_fx_host.gd + reward_grant.gd + board_installer.gd 三拆件 + settlement/ 五段段类 + damage_controller.gd,共 14 文件,显式清单见 S8_FILES。 +# + battle_fx_host.gd + reward_grant.gd + board_installer.gd 三拆件 + settlement/ 五段段类 + damage_controller.gd,清单单点=B1_SET。 # 执行侧字面值豁免:脚本必须带字面值才能工作;文件集与架构文档冲突时以 B1 域文件集为准。 # 判据:文件集内出现 await 关键字即红(词边界 \bawait\b,任何形态——await 信号/协程/ # get_tree());排除注释行(行首 #/##)与字符串字面量(双引号包裹段内的 await 非代码), # 口径仿 S1/S2 的 grep 排除手法。 -# 自检:清单内文件缺位(路径写错/被改名)即判红——防扫面为空静默绿。 -S8_FILES="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/board_installer.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/settlement/round_cycle.gd game/core/damage_controller.gd game/core/settlement/battle_refresh_flow.gd game/core/effect_port.gd game/core/reward_roller.gd" +# 自检:①清单内文件缺位(路径写错/被改名)即判红——防扫面为空静默绿;②settlement/ 目录域反向对账——新段类未登记即判红(见下)。 +# B1 域文件集=单点清单(S8 await 禁扫面与 S7 豁免名单共用;改成员先改 architecture.md「模块归属」B1 行再改此处,2026-11-03 架构审查 P1 裁并点) +B1_SET="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/board_installer.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/settlement/round_cycle.gd game/core/damage_controller.gd game/core/settlement/battle_refresh_flow.gd game/core/effect_port.gd game/core/reward_roller.gd" +S8_FILES="$B1_SET" S8_MISS=0 for f in $S8_FILES; do if [ ! -f "$f" ]; then @@ -332,6 +334,17 @@ for f in $S8_FILES; do S8_MISS=1 fi done +# 自检二(2026-11-03 架构审查 P1 裁):目录域反向对账——settlement/ 实有 .gd 未登记进 B1_SET=该扫的没扫,判红。 +# 只反向对 settlement/ 一个目录域:装板/授予/效果端口/掷格四件结算码住 game/core 根下,纯目录域覆盖不住, +# 根下新增仍由「模块归属」B1 行+写卡段申报面承接(禁把目录判据当成全域判据)。 +S8_UNREG="" +for f in $(find game/core/settlement -name '*.gd' 2>/dev/null | sort); do + case " $B1_SET " in *" $f "*) ;; *) S8_UNREG="$S8_UNREG $f" ;; esac +done +if [ -n "$S8_UNREG" ]; then + echo "[static_scan] S8 FAIL: settlement/ 有结算段类未登记进 B1 域文件集——await 禁扫面对它静默失效:$S8_UNREG(先改 architecture.md「模块归属」B1 行,再补 B1_SET)" + S8_MISS=1 +fi if [ "$S8_MISS" -eq 1 ]; then fail=1 else @@ -344,7 +357,7 @@ else grep -rnE '\bawait\b' $S8_FILES 2>/dev/null | grep -vE '^[^:]+:[0-9]+:[[:space:]]*#' | grep -vE '"[^"]*\bawait\b[^"]*"' fail=1 else - echo "[static_scan] S8 ok(扫描面 B1 文件集 10 个 .gd,await 零命中)" + echo "[static_scan] S8 ok(扫描面 B1 文件集 $(echo $S8_FILES | wc -w) 个 .gd+settlement/ 目录域已反向对账,await 零命中)" fi fi @@ -368,7 +381,7 @@ fi if require_scan_face "S7" "$DIR_CORE"; then # B1 域文件集(迭代 46 卡A 增补 battle_refresh_flow——换图刷新流程件=C13 拆分边界的 B1 域拆件, # 承载旧资源退役/装板编排调用;architecture.md「模块归属」BattleRefreshFlow 行随反哺点落文) -S7_B1_SET="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/board_installer.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/settlement/round_cycle.gd game/core/damage_controller.gd game/core/settlement/battle_refresh_flow.gd game/core/effect_port.gd game/core/reward_roller.gd" +S7_B1_SET="$B1_SET" # 与 S8 同一单点清单(原第二份抄本,2026-11-03 并点——两份靠人工同步必漂) S7_MISS=0 for f in $S7_B1_SET game/core/unit_container.gd game/core/status_container.gd game/core/reward_economy.gd; do if [ ! -f "$f" ]; then diff --git a/reports/审计-全库架构审查-2026-10-29.md b/reports/审计-全库架构审查-2026-10-29.md index 55db487b..5423a184 100644 --- a/reports/审计-全库架构审查-2026-10-29.md +++ b/reports/审计-全库架构审查-2026-10-29.md @@ -136,10 +136,11 @@ core 内干净,绕过点全在 core 之外: - **S9/S10/S13 不扩到三层**(裁决)。2026-11-03 只读数实跑(三个静态扫描脚本对三层,未修文件、未写基线)给出现存总量 **≈2162 处**:S9 类型 1070(sim 158/probe 130/tests 782,其中无类型容器 859)、S10 文件头 117、S13 质量 975(其中「禁调试打印」274+「循环内 await」70——这两类在测试与探针码里是功能需要,不是债)。产品码面 S9/S10 现行**归零**,扩面即把「归零」这一执法前提换成 1187 项存量红;为三层另建欠账基线会把账从 1 份变 3 份。落文=code-conventions §4 第 13 条写明适用域止于产品码并给出三目录的运行期承接通道。 - **读数是**:S8 现行 **14 文件**显式清单(`static_scan.sh:317`),非本节所记 9 文件;同口径 14 文件清单在 S7 另抄一份(`:361`),两份靠人工同步;绿文案仍报「B1 文件集 10 个 .gd」(`:337`)与头注 `:311` 的「共 14 文件」自相矛盾;S8 自检只防「清单内文件缺位」(`:319-324`)不防「该进的没进」——纯目录域方案已证伪:装板/授予/效果端口/掷格四件结算码在 `game/core` 根下而非 `settlement/` 目录内。 - **本轮新查两处**(审计未列):①S9/S10 **无扫描面空转自检**(`tools/audit_type_hints.py:82-83`、`tools/audit_file_headers.py:64-65` 目录不存在即静默 PASS),违背本文「三层承载与判定原则」的零命中自检要求与脚本前言自立的防空转纪律(`static_scan.sh:18-19`);②S3 是半闸(命中只打 WARNING、不置 fail,`static_scan.sh:110`)——**但这条不是洞**:架构文档 S 表 S3 行早已登记「warning 级——class_name 直引误报需人工复核」,属已在册状态。2026-11-03 裁**维持 warning 不升硬**(升硬前提是先有可靠的直引误报排除判据,该判据未立)。 -- **待执行清单(登记处=本节;车道=工程改动批,用户发令走独立 worktree 直改+合回 CI,零需求卡片;估 <0.5 工作日)**: - 1. `game/tests/static_scan.sh`:S8 新增「`game/core/settlement/` 实有文件 ⊆ 清单」反向对账(新段类未登记即红),并把 S7 的 `S7_B1_SET` 与 `S8_FILES` 两份抄本合一为单点变量,订正 `:337` 的「10 个 .gd」文案; - 2. `tools/audit_type_hints.py`/`tools/audit_file_headers.py`:补扫描面空转自检(目录缺失或 `.gd` 数为零即红,口径仿 `static_scan.sh:27-36`); - 3. 上述两条落地后,架构文档 S 表 S8 行与「S9/S10 缺空面自检」的偏差登记句随同批改写(禁文档跑在码前,本轮只登记现状)。 +- **待执行清单(2026-11-03 同日执行完毕,凭据见下)**: + 1. `game/tests/static_scan.sh`:S8 新增「`game/core/settlement/` 实有 `.gd` ⊆ 清单」反向对账(新段类未登记即红)+S7 的 `S7_B1_SET` 与 `S8_FILES` 两份 14 文件抄本合一为单点 `B1_SET`+绿文案「10 个 .gd」改为派生计数(现打「14 个 .gd+settlement/ 目录域已反向对账」)——**已落地**; + 2. `tools/audit_type_hints.py`/`tools/audit_file_headers.py`:补扫描面空转自检(缺目录或零个 `.gd` 即红,绿时打印扫描面件数供核)——**已落地**(负证:把面清单改成不存在目录,两件各返回退出码 1;正证:真实面 158 个 `.gd` 归零 PASS); + 3. 架构文档 S 表同步——S8 行登记单点与反向对账判据、原「S9/S10 缺自检」偏差句改判为已清偿——**已落地**(凭据=`design/engineering/architecture/architecture.md` v1.34)。 +- **验证口径**:`bash game/tests/static_scan.sh` 全量 13 条规则绿、退出码 0;车道=会话内独立 worktree 直改+合回远端 CI(零需求卡片,纯闸件面属工程改动)。§四 P1 至此四条全部闭合。 ### 4.3 S13 豁免常态化 diff --git a/tools/audit_file_headers.py b/tools/audit_file_headers.py index 5d8b1072..433d0ac7 100644 --- a/tools/audit_file_headers.py +++ b/tools/audit_file_headers.py @@ -59,19 +59,23 @@ def audit_path(path): def main(): + # 扫描面空转自检:目录缺失或零个 .gd 一律判红——原实现 `if not is_dir(): continue` + # 会让路径写错静默零命中换绿,违背 test-conventions §7「零命中自检为辅证」。 + missing = [d for d in SCAN_DIRS if not (ROOT / d).is_dir()] + face = [p for d in SCAN_DIRS for p in sorted((ROOT / d).rglob("*.gd"))] + if missing or not face: + print("[audit_file_headers] FAIL——扫描面为空(缺目录:%s;面清单:%s)" + % ("、".join(missing) if missing else "无", "、".join(SCAN_DIRS))) + return 1 violations = 0 - for d in SCAN_DIRS: - base = ROOT / d - if not base.is_dir(): - continue - for path in sorted(base.rglob("*.gd")): - for lineno, msg in audit_path(path): - print("%s:%d: %s" % (path.relative_to(ROOT), lineno, msg)) - violations += 1 + for path in face: + for lineno, msg in audit_path(path): + print("%s:%d: %s" % (path.relative_to(ROOT), lineno, msg)) + violations += 1 if violations: print("[audit_file_headers] FAIL: 文件头存量 %d 处(存量按 code-conventions §1.4.1 白名单清偿)" % violations) return 1 - print("[audit_file_headers] ok: 头注零黑名单命中(R0 载体/R2 日期/R3 变更叙事)") + print("[audit_file_headers] ok: 头注零黑名单命中(R0 载体/R2 日期/R3 变更叙事;扫描面 %d 个 .gd)" % len(face)) return 0 diff --git a/tools/audit_type_hints.py b/tools/audit_type_hints.py index 968c1d4b..91384022 100644 --- a/tools/audit_type_hints.py +++ b/tools/audit_type_hints.py @@ -78,16 +78,23 @@ def audit_file(path): def main(): if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(encoding="utf-8", errors="replace") + # 扫描面空转自检:目录缺失或零个 .gd 一律判红——路径写错不得静默零命中换绿 + # (test-conventions §7「零命中自检为辅证」;game/tests/static_scan.sh 前言同款纪律)。 + missing = [d for d in SCAN_DIRS if not (ROOT / d).is_dir()] + face = [p for d in SCAN_DIRS for p in sorted((ROOT / d).rglob("*.gd"))] + if missing or not face: + print("[audit_type_hints] FAIL——扫描面为空(缺目录:%s;面清单:%s)" + % ("、".join(missing) if missing else "无", "、".join(SCAN_DIRS))) + return 1 total = 0 - for d in SCAN_DIRS: - for p in sorted((ROOT / d).rglob("*.gd")): - for line, rule, text in audit_file(p): - print("[%s] %s:%d: %s" % (rule, p.relative_to(ROOT).as_posix(), line, text)) - total += 1 + for p in face: + for line, rule, text in audit_file(p): + print("[%s] %s:%d: %s" % (rule, p.relative_to(ROOT).as_posix(), line, text)) + total += 1 if total: print("[audit_type_hints] FAIL——存量 %d 处(R1 无类型容器/R2 有值返回缺类型/R3 参数缺类型)" % total) return 1 - print("[audit_type_hints] PASS——产品码类型闸归零(R1/R2/R3)") + print("[audit_type_hints] PASS——产品码类型闸归零(R1/R2/R3;扫描面 %d 个 .gd)" % len(face)) return 0