#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" # shellcheck disable=SC1091 source "$SCRIPT_DIR/lib/common.sh" REPO_ROOT="$(resolve_repo_root)" cd "$REPO_ROOT" QUICK_MODE=false while [[ $# -gt 0 ]]; do case $1 in --quick) QUICK_MODE=true shift ;; --help|-h) cat << 'EOF' Docs Link-Validierung USAGE: ./.ai/scripts/validate-links.sh [--quick] BESCHREIBUNG: Prüft Markdown-Links in `docs/**/*.md` auf gebrochene relative Pfade. Ignoriert externe Links (http/https/mailto) sowie reine Anchors (#...). OPTIONEN: --quick Führt nur eine Teilmenge der Prüfungen durch (aktuell nicht implementiert). EOF exit 0 ;; *) echo "[ERROR] Unbekannter Parameter: $1" >&2 exit 2 ;; esac done python3 - <<'PY' import re import sys from pathlib import Path from urllib.parse import unquote root = Path.cwd() docs_dir = root / "docs" if not docs_dir.is_dir(): print(f"[ERROR] docs-Verzeichnis nicht gefunden: {docs_dir}", file=sys.stderr) sys.exit(2) # Veraltete Pfad-Prüfungen wurden entfernt; Fokus auf Link-Integrität. FORBIDDEN_SUBSTRINGS = [] md_files = sorted(docs_dir.rglob("*.md")) link_pattern = re.compile(r"\]\(([^)]+)\)") errors = 0 def is_external(target: str) -> bool: t = target.lower() return t.startswith("http://") or t.startswith("https://") or t.startswith("mailto:") def strip_fragment_and_query(target: str) -> str: target = target.split("#", 1)[0] target = target.split("?", 1)[0] return target for f in md_files: text = f.read_text(encoding="utf-8", errors="replace") for forbidden in FORBIDDEN_SUBSTRINGS: if forbidden in text: print(f"[ERROR] Veralteter Pfad '{forbidden}' in {f}") errors += 1 for match in link_pattern.finditer(text): target = match.group(1).strip() if not target: continue if is_external(target): continue if target.startswith("#"): continue if target.startswith("<") and target.endswith(">"): target = target[1:-1] target = unquote(strip_fragment_and_query(target)) if target.startswith("/"): continue if ":" in target.split("/", 1)[0]: # z.B. "vscode:..." continue resolved = (f.parent / target).resolve() try: resolved.relative_to(root.resolve()) except ValueError: print(f"[ERROR] Link zeigt außerhalb des Repos: {f} -> {target}") errors += 1 continue if resolved.is_dir(): if not (resolved / "README.md").is_file(): print(f"[ERROR] Verlinktes Verzeichnis ohne README.md: {f} -> {target}") errors += 1 continue if not resolved.exists(): print(f"[ERROR] Broken link: {f} -> {target}") errors += 1 if errors: print(f"[ERROR] Link-Validierung fehlgeschlagen: {errors} Fehler") sys.exit(1) print(f"[OK] Link-Validierung erfolgreich: {len(md_files)} Markdown-Dateien geprüft") PY