#!/usr/bin/env bash # Checks the documentation set: every markdown file links to every other one, and every relative link resolves. # Usage: scripts/check-docs.sh (exit 1 on any problem, lists them all) set -u cd "$(dirname "$0")/.." DOCS=(README.md SPEC.md COMPARISON.md AGENTS.md CHANGELOG.md docs/ARCHITECTURE.md docs/RESEARCH.md docs/CI.md) bad=0 fail() { echo "docs: $*"; bad=$((bad+1)); } # no markdown file may exist outside the checked set (so a new doc cannot dodge the rule) while IFS= read -r f; do f=${f#./} printf '%s\n' "${DOCS[@]}" | grep -qxF "$f" || fail "$f is not in the DOCS list of scripts/check-docs.sh" done < <(find . -name '*.md' -not -path './build/*' -not -path './.git/*' -not -path './prompt.md' -not -path './audit/*' -not -path './review.md' -not -path './review2.md' -not -path './valgrind/*') # prompt.md: the original task statement, not documentation. # audit/*, valgrind/*: separate, self-contained doc sets (one README per CVE check, one for the # valgrind setup); linked from the main docs but not forced into their full cross-link mesh. # review.md, review2.md: standalone one-off reviews, not part of the maintained doc set. for f in "${DOCS[@]}"; do [ -f "$f" ] || { fail "$f is missing"; continue; } dir=$(dirname "$f") # every other document must be linked for g in "${DOCS[@]}"; do [ "$f" = "$g" ] && continue rel=$(realpath --relative-to="$dir" "$g") grep -qE "\]\(${rel//./\\.}(#[^)]*)?\)" "$f" || fail "$f does not link to $g (expected ]($rel))" done # every relative link must point at something that exists while IFS= read -r target; do case "$target" in http://*|https://*|mailto:*|\#*) continue ;; esac path=${target%%#*} [ -z "$path" ] && continue [ -e "$dir/$path" ] || fail "$f links to missing $target" done < <(grep -oE '\]\([^)]+\)' "$f" | sed -E 's/^\]\(//; s/\)$//') done [ "$bad" -eq 0 ] && echo "docs: ${#DOCS[@]} documents, all cross-linked, all links resolve" || { echo "docs: $bad problem(s)"; exit 1; }