From db62b6c9f2bb68b690d24efe22a9776c0ffcf2a9 Mon Sep 17 00:00:00 2001 From: hyperpolymath-recon Date: Sat, 19 Sep 2026 19:30:51 +0000 Subject: [PATCH] =?UTF-8?q?feat(scripts):=20link-rot=20guard=20=E2=80=94?= =?UTF-8?q?=20internal-pointer=20integrity=20as=20a=20standing=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The estate has repeatedly shipped docs pointing at paths a reorg moved (2026-09-19 first census: 677 broken internal links in this repo, 194 outside the vendored dir and proof artefacts — including inside RSR-SPEC-v2.adoc itself: dead ../../a2ml/ pointer, off-by-one TEMPLATE-APPLICABILITY-POLICY pointer; and 14 in rsr-template-repo). The deed-pointer sweep (f5ba975) proved the fix pattern works by hand; this makes it a standing, CI-runnable check. Checks real link syntax only (asciidoc link:/image:, markdown [text](path)); relative targets; http/mailto/absolute/fragment-stripped excluded; code-quoted paths deliberately not checked (false-positive class). Exit 1 on any BROKEN finding. --- Justfile | 8 +++++ scripts/link-rot-guard.sh | 71 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100755 scripts/link-rot-guard.sh diff --git a/Justfile b/Justfile index 3cc9aa386..475a94f89 100644 --- a/Justfile +++ b/Justfile @@ -53,6 +53,14 @@ set-allowlist repository posture="selected": actions-policy-test: @bash scripts/tests/actions-policy-486-test.sh +# Internal-pointer integrity: every asciidoc link:/image: and markdown +# relative link in the repo must resolve. The September reorg moved files +# without updating relative links (first measured census 2026-09-19: 194 +# live-canon findings, incl. inside RSR-SPEC-v2.adoc itself); this makes +# that class a standing check. Usage: just link-rot-guard [ROOT] +link-rot-guard root="." : + @bash scripts/link-rot-guard.sh "{{root}}" + # Wave-0 anti-false-green regression: proves each fixed validator CAN fail false-green-test: @bash scripts/tests/wave0-false-green-test.sh diff --git a/scripts/link-rot-guard.sh b/scripts/link-rot-guard.sh new file mode 100755 index 000000000..d5519ade0 --- /dev/null +++ b/scripts/link-rot-guard.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# link-rot-guard.sh — internal-pointer integrity check for the canon and +# the spine (and any repo). +# +# Rationale: the estate has repeatedly shipped docs that point at paths a +# reorg moved (ADR-0003 and archetypes/README.adoc cite +# rhodium-standard-repositories/spec/SCAFFOLD-LIFECYCLE.adoc; the spec now +# lives at 0-canon/rsr/). The deed-pointer sweep (standards f5ba975) proved +# the pattern works when done by hand; this makes it a standing check. +# +# Checks real link syntax only (not code-quoted paths — too noisy): +# 1. AsciiDoc: link:target[label] and image:target[alt] +# 2. Markdown: [text](target) +# Relative targets only; http(s)/mailto/absolute/fragment-stripped. +# +# Usage: link-rot-guard.sh [REPO_ROOT] + +set -uo pipefail +ROOT="${1:-.}" +FAIL=0 + +# Archive exclusion: rhodium-standard-repositories/ is the LAST COPY of a +# dead upstream (8 of 10 satellites have no external home; standards-map +# note: "ARCHIVE, do not delete"). Its internal links are historical and +# deliberately unmaintained; the guard polices live content. +EXCLUDES=() +case "$(basename "$(cd "$ROOT" && pwd)")" in + standards) EXCLUDES+=("-path" "$ROOT/rhodium-standard-repositories" -prune -o) ;; +esac + +check_target() { + local src="$1" target="$2" + target="${target%%#*}" + target="${target%%\?*}" + [ -z "$target" ] && return 0 + case "$target" in + http*|https*|mailto:*|/*) return 0 ;; + esac + local dir + dir="$(dirname "$src")" + if [ -e "$dir/$target" ] || [ -e "$ROOT/$target" ]; then + return 0 + fi + echo "BROKEN $src -> $target" + FAIL=1 + return 0 +} + +# 1. AsciiDoc links and local images +while IFS= read -r -d '' f; do + while IFS= read -r target; do + check_target "$f" "$target" + done < <(grep -oE '(link|image):[^][]+\[' "$f" 2>/dev/null | sed -E 's/^(link|image)://; s/\[$//') +done < <(find "$ROOT" "${EXCLUDES[@]:-}" -path '*/.git' -prune -o -type f -name '*.adoc' -print0) + +# 2. Markdown relative links +while IFS= read -r -d '' f; do + while IFS= read -r target; do + check_target "$f" "$target" + done < <(grep -oE '\]\([^)]+\)' "$f" 2>/dev/null | sed -E 's/^\]\(//; s/\)$//' | grep -vE '^(http|mailto|#|/)') +done < <(find "$ROOT" "${EXCLUDES[@]:-}" -path '*/.git' -prune -o -type f -name '*.md' -print0) + +echo "----" +if [ "$FAIL" -ne 0 ]; then + echo "link rot: findings above (exit 1)" + exit 1 +else + echo "all internal links resolve" +fi