MHRN-Space / scripts /check_doc_consistency.py
ThomasHeisig's picture
Sync GitHub main 4645f4137666463023fa501984a14336dd2fc666 (part 11)
31226fd verified
Raw History Blame Contribute Delete
4.99 kB
"""Validate documentation links and canonical baseline claims."""
from __future__ import annotations
import argparse
import json
import re
import subprocess
import sys
from pathlib import Path
from urllib.parse import unquote
REPO_ROOT = Path(__file__).resolve().parents[1]
DOCS_ROOT = REPO_ROOT / "docs"
ROADMAP_DOCS = (
DOCS_ROOT / "08-roadmap" / "TODO.md",
DOCS_ROOT / "08-roadmap" / "ROADMAP.md",
)
MARKDOWN_LINK = re.compile(r"(?<!!)\[[^\]]+\]\(([^)]+)\)")
BASELINE_PATTERN = re.compile(
r"\*\*Baseline:\*\*\s+`?(?:brain5d-core|mhrn-core)\s+([^`\s]+)`?"
)
COLLECTED_PATTERN = re.compile(r"(\d+)\s+tests collected")
def markdown_files() -> tuple[Path, ...]:
"""Return tracked documentation files in stable order."""
return tuple(sorted(DOCS_ROOT.rglob("*.md")))
def check_markdown_links(files: tuple[Path, ...], root: Path = REPO_ROOT) -> list[str]:
"""Return one error for every missing local Markdown link target."""
root = root.resolve()
errors: list[str] = []
for source in files:
source = source.resolve()
text = source.read_text(encoding="utf-8")
for match in MARKDOWN_LINK.finditer(text):
target = match.group(1).strip().strip("<>")
target = target.split("#", 1)[0].strip()
if not target or target.startswith(
("http://", "https://", "mailto:", "data:")
):
continue
candidate = (source.parent / unquote(target)).resolve()
try:
candidate.relative_to(root)
except ValueError:
errors.append(f"{source}: link escapes repository: {target}")
continue
if not candidate.exists():
errors.append(f"{source}: missing link target: {target}")
return errors
def check_baseline_version() -> list[str]:
"""Ensure the canonical roadmap baseline matches ``pyproject.toml``."""
pyproject = (REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8")
version_match = re.search(r'^version\s*=\s*"([^"]+)"', pyproject, re.MULTILINE)
if version_match is None:
return ["pyproject.toml: canonical project version is missing"]
expected = version_match.group(1)
errors: list[str] = []
for path in ROADMAP_DOCS:
text = path.read_text(encoding="utf-8")
match = BASELINE_PATTERN.search(text)
if match is None:
errors.append(f"{path.relative_to(REPO_ROOT)}: baseline claim is missing")
elif match.group(1) != expected:
errors.append(
f"{path.relative_to(REPO_ROOT)}: baseline {match.group(1)} "
f"does not match {expected}"
)
return errors
def check_collected_test_count() -> list[str]:
"""Reject test-collection regressions relative to the canonical baseline.
``tests/test_baseline.json`` records a verified historical run. Adding new
tests must not make documentation consistency fail merely because that
immutable snapshot is older than the current tree. A lower current count,
however, remains a regression and fails the gate.
"""
result = subprocess.run(
[sys.executable, "-m", "pytest", "--collect-only", "-q"],
cwd=REPO_ROOT,
capture_output=True,
text=True,
check=False,
)
output = f"{result.stdout}\n{result.stderr}"
actual_match = COLLECTED_PATTERN.search(output)
if actual_match is None:
return ["pytest collection did not report a collected test count"]
if result.returncode != 0:
return ["pytest collection failed; a partial count is not a valid baseline"]
baseline_path = REPO_ROOT / "tests" / "test_baseline.json"
try:
baseline = json.loads(baseline_path.read_text(encoding="utf-8"))
declared = int(baseline["full_collection"]["collected"])
except (OSError, ValueError, KeyError, TypeError):
return ["tests/test_baseline.json: canonical collected-test snapshot missing"]
actual = int(actual_match.group(1))
if actual < declared:
return [
"tests/test_baseline.json: test collection regressed "
f"from baseline {declared} to {actual}"
]
return []
def main() -> int:
"""Run documentation checks and return a shell-friendly status code."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--check-tests",
action="store_true",
help="also ensure pytest collection has not regressed below the baseline",
)
args = parser.parse_args()
errors = check_markdown_links(markdown_files())
errors.extend(check_baseline_version())
if args.check_tests:
errors.extend(check_collected_test_count())
if errors:
for error in errors:
print(f"ERROR: {error}")
return 1
print("Documentation consistency checks passed.")
return 0
if __name__ == "__main__":
raise SystemExit(main())