把 AGENTS.md(545 行)中的项目知识按性质拆开,只留下对代理的要求: - AGENTS.md(176 行)只写规则:读文档指引、提交许可、等待规则、TDD、 测试规则(模块边界/三段结构/功能覆盖优先)、注释规范、目标运行环境、 PowerShell 规则、文档维护规则; - 项目信息新建 docs/ 专题:architecture、node-protocol、workflows、 configuration、operations、testing、decisions; - README 改为项目索引(定位、快速开始、文档导航、目录、工作流、结论摘要); - 修正旧文档错误:README 的"详细约定见 AGENTS.md"与"100% 行覆盖率" (pytest 已移除该门槛);环境变量表补齐 WOV_AUTO_VAD 等 3 项。 新增 scripts/check_doc_links.py 校验相对链接与锚点,当前 14 个文档全部可达。
68 lines
2.6 KiB
Python
68 lines
2.6 KiB
Python
#!/usr/bin/env python3
|
|
"""校验 vrsub 文档中的相对 Markdown 链接是否都指向真实文件。
|
|
|
|
用法:uv run python scripts/check_doc_links.py(在 vrsub 目录下执行)
|
|
职责:扫描 README.md、AGENTS.md 与 docs/*.md 中的所有相对链接
|
|
(含 #锚点),确认目标文件存在、锚点能在目标文件中找到对应标题。
|
|
退出码非 0 表示存在断链,供 CI 或提交前检查使用。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Markdown 链接语法 [文本](目标),目标不含协议头即为仓库内相对链接
|
|
LINK_RE = re.compile(r"\[[^\]]*\]\(([^)]+)\)")
|
|
|
|
# GitHub 风格锚点生成:小写、去反引号与标点、空格转连字符(此处按中文文档习惯保留中文)
|
|
_ANCHOR_STRIP = re.compile(r"[^\w\u4e00-\u9fff\s-]")
|
|
|
|
|
|
def slug(title: str) -> str:
|
|
"""把 Markdown 标题转成锚点 id,规则与 GitHub/Gitea 一致。"""
|
|
text = title.strip().lower()
|
|
text = _ANCHOR_STRIP.sub("", text)
|
|
return text.replace(" ", "-")
|
|
|
|
|
|
def collect_anchors(path: Path) -> set[str]:
|
|
"""收集文件中全部标题的锚点,用于校验 #片段。"""
|
|
anchors: set[str] = set()
|
|
for line in path.read_text(encoding="utf-8").splitlines():
|
|
if line.startswith("#"):
|
|
anchors.add(slug(line.lstrip("#").strip()))
|
|
return anchors
|
|
|
|
|
|
def main() -> int:
|
|
root = Path(__file__).resolve().parent.parent
|
|
files = [root / "README.md", root / "AGENTS.md", *sorted((root / "docs").glob("*.md"))]
|
|
problems: list[str] = []
|
|
for md in files:
|
|
if not md.is_file():
|
|
problems.append(f"缺少文档:{md.relative_to(root)}")
|
|
continue
|
|
for target in LINK_RE.findall(md.read_text(encoding="utf-8")):
|
|
if target.startswith(("http://", "https://", "mailto:")):
|
|
continue
|
|
path_part, _, anchor = target.partition("#")
|
|
resolved = (md.parent / path_part).resolve() if path_part else md.resolve()
|
|
# 目录链接(如 ./docs/)视为可达,只要求路径存在
|
|
if path_part and not resolved.exists():
|
|
problems.append(f"{md.relative_to(root)} -> 文件不存在:{target}")
|
|
continue
|
|
if anchor and resolved.suffix == ".md" and anchor not in collect_anchors(resolved):
|
|
problems.append(f"{md.relative_to(root)} -> 锚点不存在:{target}")
|
|
if problems:
|
|
print("\n".join(problems))
|
|
print(f"\n共 {len(problems)} 处断链")
|
|
return 1
|
|
print(f"检查 {len(files)} 个文档,全部链接与锚点可达")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|