docs: AGENTS.md 只保留代理规则,项目信息迁入 README 与 docs/
把 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 个文档全部可达。
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user