feat(tools): 退役术语扫描工具 doc_term_audit.py(半自动)——①动机=机制换名/退役批后旧措辞残留肉眼难分(八型批实证:标空旗三选一、拔旗、行为环等 11+ 处漏网);②设计=退役词表 18 条(伏击/溢出未生效/七型/拔旗/蓄力行/行为环/道具窗口/雾格…各带退役日期+继任口径)×扫描域 design/{gdd,registry,ux}×上下文二分类——行级白名单信号(日期/退役/作废/换装/继任/留档/v0.x/台账)+版本历史节整段豁免+上一 2 行留档头注继承('下表留档作废'情形);③用法=报告模式默认 exit 0 / --strict 供门禁 exit 1 / --json 机器可读;词表扩充=改 RETIRED_TERMS 一处;④首跑读数=31 文件 225 命中、疑似现行 16 处(真残留约 11 处:五型×正负/拔旗交互行/标错反噬两处/行为环两处/道具窗口五处/已消耗未生效枚举/窗口锚行例举),留档类 209 处自动忽略
This commit is contained in:
1 parent
867e12da3b
commit
7f935c2a2e
1 file changed
+130
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env python3
|
||||
r"""doc_term_audit.py — GDD 退役术语扫描(半自动)
|
||||
|
||||
动机:机制换名/退役批(如 2026-09-20 伏击→警觉)之后,正文里容易残留旧措辞的
|
||||
"现行语义"引用——修订记录与退役注记里的旧词是合法历史,混在一起肉眼难分。
|
||||
本工具维护一份退役词表,全文档扫描命中并按上下文分两类:
|
||||
[疑似现行] 该行不含日期/退役/换装等白名单信号,也没落在版本历史节——人工判读
|
||||
[留档类] 行内带日期、退役/作废/换装/继任/留档/历史等信号,或位于修订记录节——默认忽略
|
||||
|
||||
用法:
|
||||
python tools/doc_term_audit.py # 扫默认范围,报告模式(exit 0)
|
||||
python tools/doc_term_audit.py --strict # 有疑似现行命中时 exit 1(供门禁/CI)
|
||||
python tools/doc_term_audit.py <file.md> ... # 只扫指定文件
|
||||
python tools/doc_term_audit.py --json # 机器可读
|
||||
|
||||
口径:只扫 design/gdd/ design/registry/ design/ux/——design/archive、design/research
|
||||
(历史提案)、docs/(样例)与 production/ 不在扫描域;词表扩充 = 改 RETIRED_TERMS。
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 退役词表:(正则词, 退役日期, 继任词/口径, 备注)。新增退役批时在此追加一行。
|
||||
RETIRED_TERMS = [
|
||||
("伏击", "2026-09-20", "警觉(blast 负向)", "伏击机制整体退役"),
|
||||
("ambush_double|ambush_layers|flat_attach", "2026-09-20", "enemy_alert / effects 投影", "伏击数据键退役"),
|
||||
("advantage_per_round_cap", "2026-09-20", "(已删字段——行动点天然封顶)", "单轮授予上限删除批"),
|
||||
("溢出未生效", "2026-09-20", "(行类型退役;满层拒收走奖励未生效行)", "行协议 10→9 类"),
|
||||
("m_amb|M_AMB", "2026-09-20", "(伏击暴露当量口径作废)", "挖掘风险不等式退役"),
|
||||
("七型|五型×正负|五型 × 正负", "2026-09-20", "八型(十六格)", "格面资源八型批"),
|
||||
("10 类行|行型封闭为 10 类|10 类封闭协议|行型 10 类", "2026-09-20", "9 类", "溢出未生效行下线"),
|
||||
("拔旗|撤旗", "2026-09-19", "(旗无移除手段——插旗不可逆)", "插旗不可逆批"),
|
||||
("三选一反噬|标错反噬|反噬三选一", "2026-09-15", "极性矩阵负向兑付", "格面资源批"),
|
||||
("defense_up", "2026-09-16", "ac_up(护甲强化)", "护甲等级掷点批换装"),
|
||||
("蓄力行|charge 行", "2026-09-16", "施放行(cast)", "敌人技能池批改型"),
|
||||
("行为环", "2026-09-16", "技能池(池模板+摇号)", "固定出招剧本退役"),
|
||||
("heavy_multiplier|重击倍率", "2026-09-16", "技能行 damage_mult", "行为环退役随批"),
|
||||
("多段命中", "2026-09-19", "(退役,无防御磨损体系)", "技能池新效应词汇批"),
|
||||
("充能", "2026-09-19", "(挖掘期/战斗段充能退役;蓄力 empower 仍在)", "充能退役批——注意误报"),
|
||||
("英雄卡|敌人卡", "2026-09-20", "站桩立绘", "敌人卡形态裁决批"),
|
||||
("道具窗口|可选类道具|已消耗未生效|使用被拒|窗口打开锚行|窗口跳过锚行", "2026-09-15", "(随可选类道具整体删除)", "格面资源批"),
|
||||
("雾格", "2026-09-14", "(雾格移除)", "不做清单"),
|
||||
]
|
||||
|
||||
DEFAULT_DIRS = ["design/gdd", "design/registry", "design/ux"]
|
||||
|
||||
# 行级白名单信号:命中任一即归"留档类"
|
||||
WHITELIST_RE = re.compile(
|
||||
r"2026-\d{2}-\d{2}"
|
||||
r"|退役|作废|换装|继任|留档|历史口径|已下线|随批|随伏击|不再|已删除"
|
||||
r"|\bv0\.\d+"
|
||||
r"|修订记录|版本历史|履行回执|台账"
|
||||
)
|
||||
# 节级白名单:版本历史/修订记录节内整段豁免
|
||||
SECTION_RE = re.compile(r"^#{1,6}\s*(版本历史|修订记录)")
|
||||
COMMENT_RE = re.compile(r"^\s*[;#]")
|
||||
|
||||
|
||||
def scan(paths):
|
||||
terms = [(re.compile(t), date, repl, note) for t, date, repl, note in RETIRED_TERMS]
|
||||
findings = [] # {term, file, line, text, klass, retired, replacement}
|
||||
for p in paths:
|
||||
text = Path(p).read_text(encoding="utf-8").replace("\r\n", "\n")
|
||||
in_version_section = False
|
||||
prev_lines = [] # 已处理行(供"上一 2 行留档标记继承"判读)
|
||||
for lineno, line in enumerate(text.split("\n"), 1):
|
||||
if SECTION_RE.match(line):
|
||||
in_version_section = True
|
||||
hits = [(m.group(0), t) for t in terms for m in [t[0].search(line)] if m]
|
||||
# 上一 2 行带留档/作废/历史标记(如"下表留档作废"头注)→ 本行视为留档
|
||||
prev = " ".join(prev_lines[-2:]) if prev_lines else ""
|
||||
prev_lines.append(line)
|
||||
if not hits:
|
||||
continue
|
||||
if in_version_section or WHITELIST_RE.search(line) or WHITELIST_RE.search(prev) or COMMENT_RE.match(line):
|
||||
klass = "留档类"
|
||||
else:
|
||||
klass = "疑似现行"
|
||||
findings.append({
|
||||
"term": "、".join(sorted({h[0] for h in hits})),
|
||||
"file": str(p).replace("\\", "/"),
|
||||
"line": lineno,
|
||||
"klass": klass,
|
||||
"text": line.strip()[:160],
|
||||
"retired": hits[0][1][1],
|
||||
"replacement": hits[0][1][2],
|
||||
})
|
||||
return findings
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="GDD 退役术语扫描(半自动)")
|
||||
ap.add_argument("files", nargs="*", help="指定文件;缺省扫默认目录")
|
||||
ap.add_argument("--strict", action="store_true", help="疑似现行命中时 exit 1")
|
||||
ap.add_argument("--json", action="store_true")
|
||||
args = ap.parse_args()
|
||||
|
||||
if args.files:
|
||||
paths = args.files
|
||||
else:
|
||||
paths = []
|
||||
for d in DEFAULT_DIRS:
|
||||
paths += sorted(Path(d).rglob("*.md"))
|
||||
paths += sorted(Path(d).rglob("*.yaml"))
|
||||
paths = [p for p in paths if Path(p).exists()]
|
||||
|
||||
findings = scan(paths)
|
||||
suspect = [f for f in findings if f["klass"] == "疑似现行"]
|
||||
kept = [f for f in findings if f["klass"] != "疑似现行"]
|
||||
|
||||
if args.json:
|
||||
print(json.dumps({"suspect": suspect, "archived_count": len(kept)}, ensure_ascii=False, indent=1))
|
||||
else:
|
||||
print(f"扫描 {len(paths)} 个文件 · 退役词命中 {len(findings)} 处 · 疑似现行 {len(suspect)} 处(留档类 {len(kept)} 处默认忽略)")
|
||||
by_term = {}
|
||||
for f in suspect:
|
||||
by_term.setdefault(f["term"], []).append(f)
|
||||
for term, items in sorted(by_term.items()):
|
||||
print(f"\n【{term}】×{len(items)}(退役 {items[0]['retired']} → {items[0]['replacement']})")
|
||||
for it in items[:8]:
|
||||
print(f" {it['file']}:{it['line']} {it['text']}")
|
||||
if len(items) > 8:
|
||||
print(f" …另有 {len(items) - 8} 处")
|
||||
return 1 if (args.strict and suspect) else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user