#!/usr/bin/env python3 # -*- coding: utf-8 -*- """audit_file_headers.py — 文件头机器闸(code-conventions §1.4.1)。 文件头 = .gd 文件顶部连续注释区:连续的空行或 # 注释行,首个非注释代码行止。 本脚本只实现机械可判子集(两类白名单的人判面见规范正文): R0 载体:头注非空时首行必须以 `##` 文档注释开头(清偿批落地起生效); 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 rows and not rows[0][1].lstrip().startswith("##"): hits.append((rows[0][0], "R0 头注必须以 ## 文档注释开头")) 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())