Files
gplan-2/tools/audit_file_headers.py

84 lines
3.7 KiB
Python
Raw Permalink 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)。
文件头 = .gd 文件顶部连续注释区:连续的空行或 # 注释行,首个非注释代码行止。
本脚本只实现机械可判子集(2026-10-20 缩窄:行数上限 R1 与类别白名单已停用):
R0 载体:头注非空时首行必须以 `##` 文档注释开头;
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"]
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 头注必须以 ## 文档注释开头"))
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():
# 扫描面空转自检:目录缺失或零个 .gd 一律判红——原实现 `if not is_dir(): continue`
# 会让路径写错静默零命中换绿,违背 test-conventions §7「零命中自检为辅证」。
missing = [d for d in SCAN_DIRS if not (ROOT / d).is_dir()]
face = [p for d in SCAN_DIRS for p in sorted((ROOT / d).rglob("*.gd"))]
if missing or not face:
print("[audit_file_headers] FAIL——扫描面为空(缺目录:%s;面清单:%s)"
% ("、".join(missing) if missing else "无", "、".join(SCAN_DIRS)))
return 1
violations = 0
for path in face:
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: 头注零黑名单命中(R0 载体/R2 日期/R3 变更叙事;扫描面 %d 个 .gd)" % len(face))
return 0
if __name__ == "__main__":
sys.exit(main())