# -*- coding: utf-8 -*- """M2 实施基线文档一致性校验(issue #1 辅助脚本)。 校验项: 1. 必备章节齐全(目标/现状盘点/改造清单/里程碑与风险/验收对照/关联); 2. 引用的配置资产路径在仓库中存在(防文档指向缺失文件); 3. 关键验收关键词存在(配置驱动/模板配置台/缓冲/RISK 风险标注)。 """ from __future__ import annotations import os import re import sys REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DOC = os.path.join(REPO_ROOT, "docs", "M2内核平台化改造_实施基线.md") REQUIRED_SECTIONS = [ "目标与范围", "现状盘点", "改造清单", "里程碑与风险", "验收对照", "关联", ] KEYWORDS = ["配置驱动", "Template Console", "模板配置台", "预留 2 周缓冲", "iAOP-Core 可用"] # 文档中应引用的内核配置资产(以仓库内路径出现,支持 {a,b} 集合写法) EXPECTED_ASSETS = [ "core/edge-gateway/config/gateway.example.yaml", "core/data-bus/config/{kafka,postgres,retention,minio}.template.yaml", "core/llm-gateway/config/{router,prompts}.template.yaml", "core/rag-kb/config/kb.template.yaml", "core/inference-backend/config/backends.template.yaml", "deploy/k8s/helm/iaop", "templates/resin/dashboard/cockpit.resin.yaml", ] def expand_braces(path: str) -> list: """展开 {a,b} 集合写法:a/{b,c}.yaml -> [a/b.yaml, a/c.yaml]。""" m = re.search(r"\{(.+?)\}", path) if not m: return [path] parts = m.group(1).split(",") prefix, suffix = path.split(m.group(0), 1) return [prefix + p + suffix for p in parts] def main() -> int: with open(DOC, encoding="utf-8") as f: text = f.read() errors = [] # 1. 必备章节 for sec in REQUIRED_SECTIONS: if sec not in text: errors.append("缺少必备章节: %s" % sec) # 2. 配置资产引用存在(支持 {a,b} 集合写法:原始形式或展开后任一命中) for asset in EXPECTED_ASSETS: candidates = expand_braces(asset) if asset in text or any(a in text for a in candidates): for a in candidates: if not os.path.exists(os.path.join(REPO_ROOT, a)): errors.append("引用资产不存在: %s" % a) else: errors.append("未引用核心资产: %s" % asset) # 3. 验收关键词 for kw in KEYWORDS: if kw not in text: errors.append("缺少验收关键词: %s" % kw) if errors: print("FAIL") for e in errors: print(" - %s" % e) return 1 print("PASS — 章节 %d 项、资产引用 %d 项、关键词 %d 项全部通过" % (len(REQUIRED_SECTIONS), len(EXPECTED_ASSETS), len(KEYWORDS))) return 0 if __name__ == "__main__": sys.exit(main())