From 85dce2c77de49f2bc831802e7315070d2a9be06b Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Tue, 20 Oct 2026 20:35:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(T1):=20S11=20=E4=BB=A3=E7=A0=81=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=E7=A6=81=E7=94=A8=E8=AF=8D=E8=A7=84=E5=88=99=E8=90=BD?= =?UTF-8?q?=E9=97=B8=E2=80=94=E2=80=94=E5=85=88=E8=90=BD=E8=A7=84=E5=88=99?= =?UTF-8?q?=E5=86=8D=E8=A7=81=E7=BA=A2=EF=BC=8889=20=E5=A4=84=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按契约 6 先写 S11 规则并跑出红,为 T2–T6 批量换代提供回归网。 词表从术语表「退役词对照」节解析+显式追加完美排雷、排除巢穴/旗。 三层豁免(闸门自指/显式留档 4 处/非注释面天然不取)+两道自检。 --- game/tests/README.md | 6 +- game/tests/static_scan.sh | 117 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 2 deletions(-) diff --git a/game/tests/README.md b/game/tests/README.md index 77577154..4d2cf913 100644 --- a/game/tests/README.md +++ b/game/tests/README.md @@ -15,7 +15,7 @@ tests/ smoke/ # critical-paths.md——人工 gate 清单(/smoke-check 读取) evidence/ # 截图与人工签核记录 run_all.gd # 单元套件统一入口(套件 manifest 协议,D2 AC-2b) - static_scan.sh # 静态扫描域 S1–S8 可执行子集 + static_scan.sh # 静态扫描域 S1–S11 可执行子集 ``` ## 测试三层与承载目录 @@ -109,9 +109,11 @@ timeout 120s godot --headless --script tests/run_all.gd ## Static Scan(提交前本地跑) -`tests/static_scan.sh` 实现静态扫描域 S1–S8 的可执行子集;规则全集与动机见 +`tests/static_scan.sh` 实现静态扫描域 S1–S11 的可执行子集;规则全集与动机见 `design/engineering/architecture/architecture.md`「测试与质量域」节。新违规案例必须**先补扫描规则再修码**。 +S11 代码注释禁用词:扫描 game/ 全域 .gd/.tres/.md/.json/.sh 的注释面,词表从 `design/gdd/术语表.md`「退役词对照」解析(显式追加「完美排雷」,排除「巢穴」「旗」);三层豁免(闸门自指/显式留档 4 处/非注释面天然不取);两道自检(扫描面文件数非空+词表解析非空)。 + ## 本地闸(唯一验收门禁) 本仓库无远端、无 CI——**验收只在本地发生**,跑 `tools/run_all_gates.sh` 是收口唯一入口,按本批改动域选档(`--code`/`--docs`/跨两域或改闸件本身=不带参数全量), diff --git a/game/tests/static_scan.sh b/game/tests/static_scan.sh index 4cf5a2ce..0716a83f 100644 --- a/game/tests/static_scan.sh +++ b/game/tests/static_scan.sh @@ -93,4 +93,121 @@ fi # S7 B2/B3/B4 API 符号命中文件 ⊆ B1 文件集(编排唯一性) # S8 结算路径 await 零命中(B1 结算/编排文件集) +# ── S11 — 代码注释禁用词 ── +# 禁「板级」「门票」「正主」「落籍」「完美排雷」等退役词进注释面(完整词表见术语表解析) +# 权威出处:design/gdd/术语表.md「退役词对照」节+「对外命名纪律」节 +# 扫描面:game/ 全域 .gd/.tres/.md/.json/.sh(.txt 不收=第三方许可证非本仓库文本) +# 词表来源:解析「退役词对照」第一列(去括号注、斜杠拆分)+显式追加「完美排雷」;显式排除「巢穴」(玩家专名/风味层,术语表自带除外注)、「旗」(单字误报率高,doc_term_audit 判例) +# 注释文本提取:.gd/.sh 取 # 后文本;.tres 取 ; 后文本;.md 取整行;.json 无注释语法(保留扩展名但不产生注释段) +# 三层豁免依序:①闸门自指(行内含检查/禁令形态即跳过)②显式留档豁免单 4 处 ③非注释面天然不取 + +if [ -d game/core ]; then +S11_TABLE="design/gdd/术语表.md" +S11_FAIL_LOCAL=0 + +if [ ! -f "$S11_TABLE" ]; then + echo "[static_scan] S11 FAIL: 术语表文件不存在($S11_TABLE)" + S11_FAIL_LOCAL=1 +else + # 解析词表:「退役词对照」第一列→去括号注→斜杠拆分 + S11_WFILE=$(mktemp) + awk '/^## 退役词对照/{f=1;next} f && /^## /{exit} f && /^\|/ && !/^\|---/ && !/退役词/{ + sub(/^\| */, ""); sub(/ *\|.*/, "") + gsub(/([^)]*)/, ""); gsub(/\([^)]*\)/, "") + gsub(/^ *| *$/, "") + n = split($0, parts, " / ") + for (i=1;i<=n;i++){gsub(/^ *| *$/,"",parts[i]); if(parts[i]!="") print parts[i]} + }' "$S11_TABLE" > "$S11_WFILE" + # 显式追加+排除 + printf '完美排雷\n' >> "$S11_WFILE" + grep -vx '巢穴' "$S11_WFILE" | grep -vx '旗' | grep -vx 'flag_\*' | sort -u > "${S11_WFILE}.x" && mv "${S11_WFILE}.x" "$S11_WFILE" + + # 自检二:词表解析非空 + S11_WC=$(grep -c . "$S11_WFILE" 2>/dev/null || echo 0) + if [ "$S11_WC" -eq 0 ]; then + echo "[static_scan] S11 FAIL: 词表解析为空——术语表结构可能已变" + S11_FAIL_LOCAL=1 + fi + + # 自检一:扫描面非空 + S11_FC=$(find game/ -type f \( -name '*.gd' -o -name '*.tres' -o -name '*.md' -o -name '*.json' -o -name '*.sh' \) 2>/dev/null | wc -l) + if [ "$S11_FC" -eq 0 ]; then + echo "[static_scan] S11 FAIL: 扫描面为空——路径写错导致的静默空转" + S11_FAIL_LOCAL=1 + fi + + if [ "$S11_FAIL_LOCAL" -eq 0 ]; then + # 扫描三个文件类型并合并结果 + S11_ALL_HITS=$( + # .gd/.sh:取 # 后注释 + find game/ -type f \( -name '*.gd' -o -name '*.sh' \) -print0 2>/dev/null \ + | xargs -0 awk -v wf="$S11_WFILE" ' + BEGIN{nw=0;while((getline w0)words[++nw]=w;close(wf)} + /#/{ + cs=index($0,"#"); cmt=substr($0,cs+1) + for(i=1;i<=nw;i++){ + if(index(cmt,words[i])>0){ + if($0~/contains\(|find\(|grep|has_char\(|禁「|禁『|零「|零『/)next + if(FILENAME~"enemy.tres"&&NR==653)next + if(FILENAME~"level_runner"&&NR==832)next + if(FILENAME~"adventure_runner"&&NR==403)next + if(FILENAME~"damage_math_test"&&NR==36)next + print FILENAME":"NR":"words[i] + next + } + } + } + ' 2>/dev/null + # .tres:取 ; 后注释 + find game/ -type f -name '*.tres' -print0 2>/dev/null \ + | xargs -0 awk -v wf="$S11_WFILE" ' + BEGIN{nw=0;while((getline w0)words[++nw]=w;close(wf)} + /;/{ + cs=index($0,";"); cmt=substr($0,cs+1) + for(i=1;i<=nw;i++){ + if(index(cmt,words[i])>0){ + if($0~/contains\(|find\(|grep|has_char\(|禁「|禁『|零「|零『/)next + if(FILENAME~"enemy.tres"&&NR==653)next + print FILENAME":"NR":"words[i] + next + } + } + } + ' 2>/dev/null + # .md:整行即注释面 + find game/ -type f -name '*.md' -print0 2>/dev/null \ + | xargs -0 awk -v wf="$S11_WFILE" ' + BEGIN{nw=0;while((getline w0)words[++nw]=w;close(wf)} + { + for(i=1;i<=nw;i++){ + if(index($0,words[i])>0){ + if($0~/contains\(|find\(|grep|has_char\(|禁「|禁『|零「|零『/)next + print FILENAME":"NR":"words[i] + next + } + } + } + ' 2>/dev/null + ) + S11_HITS=0 + if [ -n "$S11_ALL_HITS" ]; then + S11_HITS=$(echo "$S11_ALL_HITS" | grep -c .) + fi + + if [ "$S11_HITS" -gt 0 ]; then + echo "[static_scan] S11 FAIL: 代码注释禁用词命中 $S11_HITS 处" + echo "$S11_ALL_HITS" | head -50 + if [ "$S11_HITS" -gt 50 ]; then echo " ...(截断,共 $S11_HITS 处)"; fi + fail=1 + else + echo "[static_scan] S11 ok(扫描面 $S11_FC 个文件,词表 $S11_WC 词)" + fi + else + fail=1 + fi + + rm -f "$S11_WFILE" +fi +fi + exit $fail