From a2d6e03df2c9e03626a8783981afac693843ccad Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Thu, 15 Oct 2026 04:45:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=A7=84=E7=BA=A6):=20=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=A4=B4=E2=89=A48=E8=A1=8C=E7=99=BD=E5=90=8D=E5=8D=95?= =?UTF-8?q?=E5=88=B6=E7=AC=AC=E4=BA=8C=E8=BD=AE=E7=AB=8B=E8=A7=84+?= =?UTF-8?q?=E5=A4=B4=E6=B3=A8=E6=9C=BA=E5=99=A8=E9=97=B8S10=EF=BC=882026-1?= =?UTF-8?q?0-15=20=E7=94=A8=E6=88=B7=E4=BB=A4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit code-conventions §1.4.1 换代:## 文档注释载体+8行上限+五类白名单+黑名单+写法权威参照(Google Python Style Guide §3.8 + PEP 257,GDScript 官方指南无文件头条款不自造标准);§2.1 信号契约改入发送方 ## 文档注释每信号一行、计入上限;CLAUDE.md 硬性红线同步换代;新建 tools/audit_file_headers.py(R1行数/R2日期/R3变更叙事,正例反例边界例盲测通过:恰好8行过/机制词结算批不误伤/无头注过),static_scan.sh 挂 S10。纯规则与工具零行为变更;实测存量 125 处约 90 文件,清偿批在册待发令 --- CLAUDE.md | 2 +- design/dev/code-conventions.md | 35 +++++++++++---- game/tests/static_scan.sh | 9 ++++ tools/audit_file_headers.py | 82 ++++++++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 9 deletions(-) create mode 100644 tools/audit_file_headers.py diff --git a/CLAUDE.md b/CLAUDE.md index 6bd7cea7..143b3ace 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -121,7 +121,7 @@ - **呈报六问铁规(2026-10-07 用户立规)**:向用户呈报一切事物——bug、需求、概念设计、提案——一律按六问结构顺序展开,确认后才动手实现:①**What 现状与差异**——现在是什么样(引用文件、行为等事实)+ 要做什么/改什么;②**Why 期望与动机**——目标态是什么样、为什么值得改,含「不做的代价」;③**How 怎么改**——修改路径,有备选案全部列出不私藏;④**Where 影响面**——改动波及哪些系统、文件、流程、既有裁决;⑤**怎么算改对了**——可判定的验收标准,不写模糊话;⑥**不确定的地方**——没把握、需要用户裁决的点,单列收尾不埋正文。**Who/When 不进呈报正文**——谁来执行、什么时候做由用户决定,由 roadmap 与需求卡片承接,呈报里最多一句话带过。**适用范围(2026-10-08 用户补立)**——六问结构只用于**首次描述需求、bug 缺陷或相关方案**的呈报;同一方案的后续对答(追问、讨论、细化、执行汇报)不再套用六问结构,除非用户显式要求 - **真人检测不阻塞(2026-10-12 用户立规)**:一切需要真人检测/实机目检/人工验收的事项一律「只登记、不等待」——登记进 07-体验验证清单(或当批实现报告目检总清单)即完成 agent 侧义务;自动化闸全绿即可合并收口,后续迭代照常开工,验收时间由用户自行安排。禁止把等真人检测当作任务阻塞点、停工理由或收口必要条件,也禁止因往期遗留真人检测项而拦截新迭代开工 -- **文件头零工作日志(2026-10-15 用户立规)**:代码文件(.gd 等)文件头注释只承载现在时——文件职责、接口契约、设计权威(GDD)锚;禁止写任何形式的变更日志/工作日志:带日期条目、批次号叙事段(「XX批/T几/迭代N 做了什么」)、迁移与退役经过叙述一律不得进文件头。变更叙事唯一归宿 = commit message 与迭代实现报告;「注释描述现在、历史归版本控制」是工程基线,不依赖本仓规范另行授权——规范未提及不构成许可。存量 17 文件已随本令同批清偿(2026-10-15) +- **文件头 = `##` 文档注释 ≤8 行白名单制(2026-10-15 用户立规,两轮)**:代码文件(.gd 等)文件头只许 `##` 文档注释、总行数 ≤8、内容限五类白名单(①一行职责摘要 ②用途/验证问题 ③GDD 锚行 ④硬使用约束每条一行 ⑤指路行);黑名单 = 日期、变更叙事(「XX批/T几/迭代N 做了什么」、迁移/改名/退役经过)、与 code-conventions/ADR/架构文档重复的段落、场景结构/生命周期等代码机制描述。写法权威 = Google Python Style Guide §3.8 + PEP 257(GDScript 官方风格指南对文件头零条款,不自造标准);变更叙事唯一归宿 = commit message 与迭代实现报告;规范与架构只指路不复制——"Never describe the code"。机器闸 = `tools/audit_file_headers.py`(static_scan S10),存量头注清偿批在册待发令 - **数值单一事实源**:玩法数值只存 `data/tables/`(.tres),经 `ConfigDB` autoload 类型化单点读取;禁止数值字面量与自建 `load()` 表路径 - **RNG 单点**:禁止 `randomize()` / 自建 RandomNumberGenerator——随机只经 `RngService` 统一流(单盐单计数器+用途标签+全事件日志,ADR-0007 裁丙 2026-09-27) - **Core 层零 UI 依赖**:完整战斗必须可 `--headless --script` 跑通 diff --git a/design/dev/code-conventions.md b/design/dev/code-conventions.md index 7b8588f9..d98d9f04 100644 --- a/design/dev/code-conventions.md +++ b/design/dev/code-conventions.md @@ -40,14 +40,32 @@ > **反面案例(2026-09-11,用户:"是没有代码规范吗")**:ID 前缀是跟随 GDD 文档名的事实约定, > 从未成文;同目录 `config_db`/`rng_service` 无前缀与 A/B/C 系列并存——两套命名即规范缺失的铁证。 -### 1.4.1 文件头零工作日志(2026-10-15 用户立规) -文件头注释只描述现在——职责、契约、GDD 锚。禁止任何变更叙事进文件头: -带日期条目、批次叙事段(「XX批」「T几」「迭代N」开头的"本批做了什么")、迁移/退役经过注记一律禁止; -改动历史查 `git log`/`git blame`,变更背景读对应迭代实现报告。 +### 1.4.1 文件头 = `##` 文档注释,≤8 行,白名单制(2026-10-15 第二轮立规) -> **反面案例(2026-10-15,用户令全仓清偿)**:main.gd 文件头曾累积 42 行——11+ 笔提交各自往头注 -> 追加一段「本批做了什么」,全部闸静默放行;save_manager.gd 曾达 49 行。同批 17 文件存量已清; -> 红线正文见 CLAUDE.md 硬性红线。 +文件头注释只用 `##` 文档注释(Godot 官方载体,进编辑器 F1 帮助),总行数 ≤8 行(不含空行, +从文件首个非空行到首个非注释代码行)。写法权威 = Google Python Style Guide §3.8 + PEP 257 +(GDScript 官方风格指南对文件头零条款,GDScript 语法近 Python,沿用其规范——不自造标准)。 + +白名单(只许写这五类,超出一类即违规): +1. 一行职责摘要——这是什么; +2. 用途/验证问题一行——它为什么存在; +3. GDD 锚行(§1.4 格式); +4. 硬使用约束——使用本文件前必须知道的禁令,每条一行; +5. 指路行——架构文档/ADR/契约文档的位置。 + +黑名单(出现即违规): +- 日期与变更叙事:任何日期、「XX批/T几/迭代N 做了什么」、迁移/改名/退役经过—— + 改动史查 `git log`/`git blame`,变更背景读对应迭代实现报告; +- 与 code-conventions、ADR、架构文档重复的段落——规范与架构各有唯一权威文档,文件头只指路不复制; +- 场景结构、生命周期等代码机制描述——"Never describe the code"(pyguide §3.8.5),读者读代码或架构文档。 + +机器闸:`tools/audit_file_headers.py`(static_scan S10:行数上限 + 黑名单机械可判子集; +信号契约成文位置见 §2.1)。存量头注按白名单清偿后挂入收口闸序列;此间新增/改动的文件头必须即合规。 + +> **反面案例(main.gd,两轮,2026-10-15)**:第一轮头注累积 42 行——11+ 笔提交各自追加「本批做了什么」, +> 全部闸静默放行;清偿删叙事后仍剩 31 行——约 2/3 是对 code-conventions §1.3 与 ADR-0001 的整段复制、 +> 场景结构/生命周期机制描述(对规约与代码零新信息),且第 4 行残留带日期批次叙事。第二轮立规后 +> 全仓头注按白名单重写,机制事实归 design/dev/architecture。 ### 1.5 禁止事实约定 任何"大家默认这么做"的惯例必须成文进本文件(或 CLAUDE.md)才算存在。 @@ -58,7 +76,8 @@ ## 2. 契约纪律——跨层/跨类调用的语义 ### 2.1 信号与事件契约成文 -每个跨类信号:参数语义、发送时点、消费方依赖,必须在发送方头注成文(参照 A2 三事件面先例)。 +每个跨类信号:参数语义、发送时点、消费方依赖,必须在发送方 `##` 文档注释成文,每信号一行 +(参照 A2 三事件面先例);计入 §1.4.1 的 8 行上限,塞不下时迁契约文档并在文件头留指路行。 状态机的每次迁移路径都必须发出对应事件——**缺一条路径的事件 = 消费方永久等待 = 软锁**。 > **反面案例(playtest 缺陷②)**:A2 旗动作花完末点 AP 后相位翻 Waiting 但不发 round_end, diff --git a/game/tests/static_scan.sh b/game/tests/static_scan.sh index 4b649534..c774f23b 100644 --- a/game/tests/static_scan.sh +++ b/game/tests/static_scan.sh @@ -21,6 +21,15 @@ else fail=1 fi +# S10 — 文件头闸(code-conventions §1.4.1 第二轮 2026-10-15:头注 ≤8 行+日期/变更叙事黑名单) +if python tools/audit_file_headers.py; then + echo "[static_scan] S10 ok" +else + echo "[static_scan] S10 FAIL: 文件头存量见上清单(白名单清偿批在册)" + fail=1 +fi + + # S1/S2 — 玩法代码禁 randomize()/自建 RandomNumberGenerator/全局随机函数(绕开 RngService 的口子) # 2026-09-24 首次实跑收敛(转正结构批):违规签名=裸 randomize/rand* 调用(实例方法 .randi() 排除) # + RngService 之外的 RandomNumberGenerator.new();接收服务注入流的类型标注不属违规(core 五文件 15 处实证) diff --git a/tools/audit_file_headers.py b/tools/audit_file_headers.py new file mode 100644 index 00000000..b680b183 --- /dev/null +++ b/tools/audit_file_headers.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""audit_file_headers.py — 文件头机器闸(code-conventions §1.4.1 第二轮立规 2026-10-15)。 + +文件头 = .gd 文件顶部连续注释区:连续的空行或 # 注释行,首个非注释代码行止。 +本脚本只实现机械可判子集(白名单五类的人判面见规范正文): + R1 行数上限:头注注释行(非空)> 8 行; + R2 日期:头注出现日期模式(2026-09-12 / 2026年9月 一类)——日期只属于 git log 与迭代报告; + R3 变更叙事词:「迭代N / 本迭代」、批次叙事复合词(本批/拆件批/改造批……)、「迁移到/改名为」 + 一类变更经过。清单从紧防误伤:「结算批」等玩法机制词不在黑名单; + 新失败模式先补本脚本规则再修码(static_scan 纪律)。 +扫描域 = game/core + game/data + game/presentation(tests/smoke 豁免,同 audit_type_hints 口径)。 +退出码:0=归零,1=有存量(逐条列 file:line)。用法:python tools/audit_file_headers.py +""" +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +SCAN_DIRS = ["game/core", "game/data", "game/presentation"] + +MAX_HEADER_LINES = 8 + +DATE_PAT = re.compile(r"\d{4}\s*[-/.年]\s*\d{1,2}") +NARRATIVE_PATS = [ + (re.compile(r"迭代\s*\d|本迭代"), "变更叙事「迭代N」"), + (re.compile( + r"本批|拆件批|改造批|重构批|实装批|清偿批|落地批|收口批|收尾批|转正批|对齐批|拆分批|换代批|升级批|随批"), + "变更叙事「XX批」"), + (re.compile(r"迁移到|迁至|改名为|更名为|原名为|曾用名"), "变更经过叙述"), +] + + +def header_lines(text): + """返回文件头注释区的注释行 list[(lineno, line)],首个非注释非空行止;空行不计数。""" + rows = [] + for lineno, line in enumerate(text.splitlines(), start=1): + stripped = line.lstrip("\ufeff").strip() + if stripped == "": + continue + if stripped.startswith("#"): + rows.append((lineno, line)) + else: + break + return rows + + +def audit_path(path): + hits = [] + text = path.read_text(encoding="utf-8", errors="replace") + rows = header_lines(text) + if len(rows) > MAX_HEADER_LINES: + hits.append((rows[0][0], "R1 头注 %d 行 > 上限 %d 行(区间 %d-%d 行)" + % (len(rows), MAX_HEADER_LINES, rows[0][0], rows[-1][0]))) + for lineno, line in rows: + if DATE_PAT.search(line): + hits.append((lineno, "R2 头注日期")) + for pat, label in NARRATIVE_PATS: + if pat.search(line): + hits.append((lineno, "R3 " + label)) + return hits + + +def main(): + 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 + if violations: + print("[audit_file_headers] FAIL: 文件头存量 %d 处(存量按 code-conventions §1.4.1 白名单清偿)" % violations) + return 1 + print("[audit_file_headers] ok: 头注 ≤8 行且零黑名单命中") + return 0 + + +if __name__ == "__main__": + sys.exit(main())