#!/usr/bin/env python3 """Check internal cross-references between topic files. Scans all .md files for See Also links to other topics (backtick-quoted names) and verifies that matching files exist under topics/. Exit codes: 0 — all links valid 1 — broken links found """ import re import sys from pathlib import Path TOPICS_DIR = Path("topics") DOCS_DIR = Path("docs") SEARCH_DIRS = [Path("."), TOPICS_DIR, DOCS_DIR] EXCLUDE = {"docs/TEMPLATE.md"} def find_md_files() -> list[Path]: files = [] for d in SEARCH_DIRS: if d.exists(): files.extend(d.glob("*.md")) return sorted(set(files)) def extract_topic_refs(path: Path) -> list[tuple[int, str]]: """Extract backtick-quoted topic references from See Also sections.""" refs = [] in_see_also = False for lineno, line in enumerate(path.read_text().splitlines(), start=1): if re.match(r"^##\s+See Also", line, re.IGNORECASE): in_see_also = True continue if in_see_also and re.match(r"^##\s+", line): break if in_see_also: for match in re.finditer(r"`([a-z][a-z0-9-]*)`", line): refs.append((lineno, match.group(1))) return refs def check_links() -> int: md_files = find_md_files() topic_names = {p.stem for p in TOPICS_DIR.glob("*.md")} if TOPICS_DIR.exists() else set() errors = 0 for path in [p for p in md_files if str(p) not in EXCLUDE]: for lineno, ref in extract_topic_refs(path): if ref not in topic_names: print(f" {path}:{lineno} broken ref `{ref}` — no topics/{ref}.md") errors += 1 if errors: print(f"\n {errors} broken link(s) found") return 1 print(f" {len(md_files)} files checked, all internal links valid") return 0 if __name__ == "__main__": sys.exit(check_links())