chore(acm2-75): 补文档校验盲区并接入 CI 门禁
This commit is contained in:
+56
-2
@@ -4,9 +4,12 @@
|
||||
校验五件事:
|
||||
① docs 顶层 Markdown 固定为六个文件;
|
||||
② 稳定 ID 定义唯一性与语法:每个 ID 恰好定义一次,且全仓引用均有定义;
|
||||
③ 旧文件名、章节号引用与已闭合 G 标记零命中(docs/legacy/ 外);
|
||||
③ 旧文件名、章节号引用、已闭合 G 标记与字母后缀编号零命中(docs/legacy/ 外);
|
||||
④ 仓库内 Markdown 链接有效。
|
||||
⑤ 给出 Git 基线时,持久 ID 集合与活跃 G 集合保持不变。
|
||||
⑥ `application.yml` 的 `msgx.*` 叶子键逐个登记在 reference.md:`msgx.*` 是逐键登记的参数族,
|
||||
光看文档查不出「代码有键、注册表没登记」的漂移;`mailbox.*`、`datasources.*`、`kafka.*`
|
||||
按 reference.md 的约定成组登记,不做逐键检查。
|
||||
|
||||
用法:scripts/check-docs.py [仓库根目录] [Git 基线]
|
||||
"""
|
||||
@@ -56,6 +59,34 @@ USE_PATTERNS = {
|
||||
|
||||
FIRST_COL = re.compile(r"^\|\s*`?([^|`]+?)`?\s*\|")
|
||||
|
||||
# 字母后缀编号(编号数字后面直接接小写字母)不在 ID 语法内。裸 `\bINV-\d+\b` 匹配不到它,
|
||||
# 于是这类 ID 的定义与引用会一起从校验里消失:曾有一批这样的引用只出现在正文、没有定义,
|
||||
# 校验器却全数漏过。
|
||||
SUFFIXED_ID = re.compile(r"\b(?:US|OPS|C|PRE|INV|CLM|Q)-\d+[a-z]\b")
|
||||
|
||||
# application.yml 叶子键:有值(非空、非纯注释)的键才算叶子,父节点不算。
|
||||
YML_KEY_LINE = re.compile(r"^(\s*)([A-Za-z0-9_-]+):\s*(.*)$")
|
||||
|
||||
|
||||
def yml_leaf_keys(path: Path, prefix: str) -> list[tuple[str, int]]:
|
||||
"""按缩进还原 `application.yml` 的叶子键全路径,只保留指定前缀。"""
|
||||
keys: list[tuple[str, int]] = []
|
||||
stack: list[tuple[int, str]] = []
|
||||
for lineno, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
|
||||
if not line.strip() or line.lstrip().startswith("#"):
|
||||
continue
|
||||
m = YML_KEY_LINE.match(line)
|
||||
if not m:
|
||||
continue
|
||||
indent, key, value = len(m.group(1)), m.group(2), m.group(3).strip()
|
||||
stack = [item for item in stack if item[0] < indent]
|
||||
stack.append((indent, key))
|
||||
if value and not value.startswith("#"):
|
||||
full = ".".join(item[1] for item in stack)
|
||||
if full.startswith(prefix):
|
||||
keys.append((full, lineno))
|
||||
return keys
|
||||
|
||||
|
||||
def is_placeholder(kind: str, ident: str) -> bool:
|
||||
if ident in PLACEHOLDER_WORDS:
|
||||
@@ -234,13 +265,28 @@ def main() -> int:
|
||||
if not failures:
|
||||
print(f"OK ID 注册表:{totals} 个定义各一处且位于所属注册表")
|
||||
|
||||
# ⑥ 参数键登记:代码里有、注册表里没有的 `msgx.*` 键,是文档查不出的那类漂移
|
||||
app_yml = ROOT / "src/main/resources/application.yml"
|
||||
if app_yml.exists():
|
||||
ref_text = (DOCS / "reference.md").read_text(encoding="utf-8")
|
||||
unregistered = [
|
||||
f"src/main/resources/application.yml:{lineno} -> {key}"
|
||||
for key, lineno in yml_leaf_keys(app_yml, "msgx.")
|
||||
if key not in ref_text
|
||||
]
|
||||
if unregistered:
|
||||
failures.append("application.yml 的 msgx.* 参数未登记在 reference.md")
|
||||
failures += unregistered
|
||||
else:
|
||||
print("OK application.yml 的 msgx.* 参数均已登记在 reference.md")
|
||||
|
||||
# ③ 旧文件名、章节号引用与已闭合 G 标记零命中(legacy 外)
|
||||
stale_name = re.compile(
|
||||
r"(design|invariants|contracts|flight-state|user-stories|spec-boundary-closure"
|
||||
r"|message-lifecycle|runbooks)\.md")
|
||||
section_ref = re.compile(r"(?:§\s*\d|(?:第\s*)?\d+(?:\.\d+)*\s*节)")
|
||||
closed_g = re.compile(r"G-[A-Z][A-Z0-9-]*.*(?:✓|已闭合|已关闭)")
|
||||
stale_hits, section_hits, closed_g_hits = [], [], []
|
||||
stale_hits, section_hits, closed_g_hits, suffixed_hits = [], [], [], []
|
||||
for f in all_repo_files:
|
||||
try:
|
||||
text = f.read_text(encoding="utf-8")
|
||||
@@ -253,6 +299,9 @@ def main() -> int:
|
||||
section_hits.append(f"{f.relative_to(ROOT)}:{i}")
|
||||
if "legacy" not in f.parts and closed_g.search(line):
|
||||
closed_g_hits.append(f"{f.relative_to(ROOT)}:{i}")
|
||||
if "legacy" not in f.parts:
|
||||
for ident in SUFFIXED_ID.findall(line):
|
||||
suffixed_hits.append(f"{f.relative_to(ROOT)}:{i} -> {ident}")
|
||||
if stale_hits:
|
||||
failures.append("存在指向已删除文档的文件名引用")
|
||||
failures += stale_hits
|
||||
@@ -268,6 +317,11 @@ def main() -> int:
|
||||
failures += closed_g_hits
|
||||
else:
|
||||
print("OK 已闭合 G 标记零命中")
|
||||
if suffixed_hits:
|
||||
failures.append("存在字母后缀编号(ID 不含字母后缀,同一编号只有一条定义)")
|
||||
failures += suffixed_hits
|
||||
else:
|
||||
print("OK 字母后缀编号零命中")
|
||||
|
||||
# ④ 仓库内 Markdown 链接
|
||||
link_files = list(DOCS.rglob("*.md")) + [ROOT / "README.md", ROOT / "AGENTS.md"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# 文档体系机械校验入口:顶层结构、ID 注册表与全仓引用、陈旧引用、活跃 G、Markdown 链接。
|
||||
# 文档体系机械校验入口:顶层结构、ID 注册表与全仓引用、application.yml 的 msgx.* 参数登记、
|
||||
# 陈旧引用、活跃 G、字母后缀编号、Markdown 链接。
|
||||
# 用法:scripts/check-docs.sh [Git 基线] (在仓库根目录执行)
|
||||
set -uo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
Reference in New Issue
Block a user