切换外观
scripts/check.py
仓库开发文件的只读副本。网站不执行此文件;运行方式见工作环境。
python
"""Check the startup files and local links in maintained Markdown documents."""
from pathlib import Path
import re
import sys
from urllib.parse import unquote, urlsplit
ROOT = Path(__file__).resolve().parents[1]
REQUIRED = (
"README.md",
"AGENTS.md",
"docs/product.md",
"docs/visual-guide.md",
"docs/assets/illustrations/README.md",
"docs/user-stories.md",
"docs/formal-model.md",
"docs/information-contract.md",
"docs/skill-design.md",
"docs/architecture.md",
"docs/decisions.md",
"docs/development.md",
"docs/reference/design-v0.3.md",
"conformance/cases/01-us01-roundtrip.yaml",
"conformance/cases/02-us03-overflow-reject.yaml",
"conformance/cases/03-batch-receipts-and-checks.yaml",
"conformance/cases/04-patch-and-subtree.yaml",
"conformance/cases/05-operation-boundaries.yaml",
"conformance/cases/06-state-overview.yaml",
"Makefile",
"scripts/check.py",
".editorconfig",
".gitattributes",
".gitignore",
)
LINK = re.compile(r"!?\[[^\]\n]*\]\(([^)\n]+)\)")
FENCE = re.compile(r"^\s*(`{3,}|~{3,})")
def check_document(path: Path) -> list[str]:
errors: list[str] = []
name = path.relative_to(ROOT)
content = path.read_text(encoding="utf-8")
if not content.endswith("\n"):
errors.append(f"{name}: 文件末尾缺少换行")
fence = ""
for number, line in enumerate(content.splitlines(), start=1):
location = f"{name}:{number}"
if line.rstrip() != line:
errors.append(f"{location}: 行尾有空白")
marker = FENCE.match(line)
if marker:
token = marker.group(1)
if not fence:
fence = token
elif token[0] == fence[0] and len(token) >= len(fence):
fence = ""
continue
if fence:
continue
for match in LINK.finditer(line):
target = match.group(1).strip().removeprefix("<").removesuffix(">")
url = urlsplit(target)
if url.scheme or url.netloc or not url.path:
continue
link_path = Path(unquote(url.path))
if link_path.is_absolute():
errors.append(f"{location}: 请使用仓库相对路径:{target}")
continue
destination = (path.parent / link_path).resolve()
if not destination.is_relative_to(ROOT):
errors.append(f"{location}: 链接超出仓库:{target}")
elif not destination.exists():
errors.append(f"{location}: 链接目标不存在:{target}")
return errors
def main() -> int:
if sys.version_info < (3, 11):
print("需要 Python 3.11 或更高版本。", file=sys.stderr)
return 1
errors = [f"缺少启动文件:{name}" for name in REQUIRED if not (ROOT / name).is_file()]
documents = [ROOT / "README.md", ROOT / "AGENTS.md"]
documents.extend(sorted((ROOT / "docs").rglob("*.md")))
maintained = [
path for path in documents
if path.is_file() and not path.is_relative_to(ROOT / "docs" / "reference")
]
for path in maintained:
errors.extend(check_document(path))
if errors:
print("启动检查未通过:", file=sys.stderr)
for error in errors:
print(f"- {error}", file=sys.stderr)
return 1
print(f"启动检查通过:{len(REQUIRED)} 个文件齐全,{len(maintained)} 份维护中文档链接与格式有效。")
print("原始草案按原件保留;当前没有产品功能测试。")
return 0
if __name__ == "__main__":
raise SystemExit(main())