feat(wiki-lint): check #4 — top-level docs dangling links
Some checks are pending
CI / rust (pull_request) Waiting to run
CI / markdown (pull_request) Waiting to run
CI / port (pull_request) Waiting to run
CI / agent-jail-pkgs (pull_request) Waiting to run

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)
This commit is contained in:
Sam & Claude 2026-06-26 21:49:56 +02:00
parent 9552b79d0c
commit 8ffbf09f12

View file

@ -1,11 +1,13 @@
#!/bin/sh #!/bin/sh
# wiki-lint — validate the docs/wiki/ knowledge base against the codebase. # 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. # 1. Dangling references: every path/line cited in wiki pages must exist.
# 2. Resurrected old names: "Shipped" renames from naming-decisions.md # 2. Resurrected old names: "Shipped" renames from naming-decisions.md
# must not reappear in code (outside the wiki). # must not reappear in code (outside the wiki).
# 3. Orphan pages: every docs/wiki/*.md must be linked from index.md. # 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. # 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)" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
WIKI_DIR="$REPO_ROOT/docs/wiki" WIKI_DIR="$REPO_ROOT/docs/wiki"
TOP_DOCS_DIR="$REPO_ROOT/docs"
FAIL=0 FAIL=0
PASS=0 PASS=0
STRICT=0 STRICT=0
@ -174,6 +177,52 @@ done
echo "" 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 ──────────────────────────────────────────────────────────── # ── report ────────────────────────────────────────────────────────────
printf "=== PASS: %d FAIL: %d ===\n" "$PASS" "$FAIL" printf "=== PASS: %d FAIL: %d ===\n" "$PASS" "$FAIL"