feat(规约): 文件头≤8行白名单制第二轮立规+头注机器闸S10(2026-10-15 用户令)
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 文件,清偿批在册待发令
This commit is contained in:
1 parent
58432701df
commit
a2d6e03df2
4 files changed
+119
-9
No files matched your search
@@ -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` 跑通
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 处实证)
|
||||
|
||||
@@ -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())
|
||||
Reference in new issue
Block a user