Files
gplan-2/tools/audit_file_headers.py
T
DKChan a2d6e03df2 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 文件,清偿批在册待发令
2026-10-15 04:45:00 +08:00

83 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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())