From 8ffbf09f12de70534b3d568f6de120776c860812 Mon Sep 17 00:00:00 2001 From: Sam & Claude Date: Fri, 26 Jun 2026 21:49:56 +0200 Subject: [PATCH] =?UTF-8?q?feat(wiki-lint):=20check=20#4=20=E2=80=94=20top?= =?UTF-8?q?-level=20docs=20dangling=20links?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #224 fixed two stale references to removed docs by hand. The root cause: wiki-lint only validated docs/wiki/, never the top-level docs/*.md — so a doc could link to a removed sibling forever with nothing to catch it. Add check #4: scan docs/*.md for two doc-reference patterns and verify they resolve (relative to docs/ or repo root): a) markdown links [label](local.md) — the exact #224 bug class b) backtick SHOUTING-CASE .md refs (e.g. `FOO-BAR.md`) Scoped to doc-to-doc references deliberately. External URLs, anchors, and cross-repo paths are skipped, and bare lowercase source filenames (env.sh, build.sh — often runtime/contextual) are out of scope, so the check has zero false positives on current main (171 pass) and fail-closes under --strict (which CI already runs). Calibrated by injecting fake removed-doc links: both the markdown link and the backtick doc-name ref are detected and exit non-zero. (Sam & Claude) --- scripts/wiki-lint | 51 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/scripts/wiki-lint b/scripts/wiki-lint index 69685af..4f6239d 100755 --- a/scripts/wiki-lint +++ b/scripts/wiki-lint @@ -1,11 +1,13 @@ #!/bin/sh # wiki-lint — validate the docs/wiki/ knowledge base against the codebase. # -# Three deterministic checks (no LLM, CI-friendly): +# Four deterministic checks (no LLM, CI-friendly): # 1. Dangling references: every path/line cited in wiki pages must exist. # 2. Resurrected old names: "Shipped" renames from naming-decisions.md # must not reappear in code (outside the wiki). # 3. Orphan pages: every docs/wiki/*.md must be linked from index.md. +# 4. Top-level docs dangling links: markdown links + doc-name refs in +# docs/*.md must resolve (catches links to removed docs, e.g. PR #224). # # Output: PASS count or FAIL report. Non-zero exit on failure in --strict. # @@ -17,6 +19,7 @@ set -eu SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" WIKI_DIR="$REPO_ROOT/docs/wiki" +TOP_DOCS_DIR="$REPO_ROOT/docs" FAIL=0 PASS=0 STRICT=0 @@ -174,6 +177,52 @@ done echo "" +# ── 4. top-level docs dangling links ────────────────────────────────── +# +# Catches links to removed docs (the PR #224 bug class). Two patterns: +# a) markdown links [label](local.md) — resolve relative to docs/ or root +# b) backtick SHOUTING-CASE .md refs — doc-name references (e.g. FOO-BAR.md) +# External URLs, anchors, and cross-repo paths are skipped. Bare lowercase +# source filenames (env.sh, build.sh) are intentionally out of scope — those +# are often runtime/contextual, not committed doc references. + +echo "=== 4. top-level docs dangling links ===" + +for doc_file in "$TOP_DOCS_DIR"/*.md; do + base="$(basename "$doc_file")" + # index.md (README) and the wiki-split plan doc are meta; still check them. + + # a) markdown links to local .md files + _tmp_links=$(mktemp) + grep -oE '\]\([^)]+\.md\)' "$doc_file" \ + | sed 's/^\](//; s/)$//' > "$_tmp_links" + while IFS= read -r link; do + case "$link" in + http*|\#|mailto:*) continue ;; + esac + if [ -e "${TOP_DOCS_DIR}/$link" ] || [ -e "$REPO_ROOT/$link" ]; then + pass + else + fail "docs/$base → markdown link '$link' (target not found)" + fi + done < "$_tmp_links" + rm -f "$_tmp_links" + + # b) backtick SHOUTING-CASE doc-name references (e.g. `COLIBRI-SKILLS.md`) + _tmp_docrefs=$(mktemp) + grep -o '`[A-Z][A-Z0-9_-]*\.md`' "$doc_file" | tr -d '`' > "$_tmp_docrefs" + while IFS= read -r docref; do + if [ -e "${TOP_DOCS_DIR}/$docref" ] || [ -e "$REPO_ROOT/$docref" ]; then + pass + else + fail "docs/$base → doc reference '$docref' (not found)" + fi + done < "$_tmp_docrefs" + rm -f "$_tmp_docrefs" +done + +echo "" + # ── report ──────────────────────────────────────────────────────────── printf "=== PASS: %d FAIL: %d ===\n" "$PASS" "$FAIL"