tidaldb/scripts/check-docs.sh
jx12n 4051077cff docs(m12): refresh API, specs, ops, and roadmap to the shipped M12 reality
- API.md: document `similar_to`/`region`/`unavailable_shards` on /feed and
  /search, the new POST /vector_search k-NN probe, and the cluster-node-only
  routes (/cluster/*, /sharded/*, /hardnegs)
- CHANGELOG.md: M12 entries — multi-vector preference + ANN candidate-gen,
  idle-readiness + TLS scale-up (m12p5/p6), sharded ingestion (m12p4)
- ROADMAP.md: mark M11 + M12 COMPLETE; restate the v1.0 bar (30-day-green
  nightly calendar + Ref-A/k3s throughput re-runs)
- prometheus-alerts.yaml: add ship-stall, quorum-lag, divergence-quarantine,
  reseed-pending, and snapshot-pin-force-drop cluster alerts
- check-docs.sh: self-updating milestone-status freshness guard derived from
  ROADMAP's latest COMPLETE milestone
- refresh specs (00-14), ai-lookup, guides, and runbooks to M0-M12
2026-06-23 21:39:55 -06:00

143 lines
6.6 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
#
# Documentation consolidation guard.
#
# tidalDB documentation has exactly TWO canonical homes: the repository-root *.md
# files and docs/. This script prevents the doc set from drifting back into the
# per-crate mirror that once sprawled to 240+ duplicated files (tidal/docs/,
# tidal/ai-lookup/, tidal/site/, .ai/, .agents/skills/).
#
# Hard failures (exit 1): a mirror tree reappears; CLAUDE.md's Repository
# Structure stops listing a real workspace crate; a core-doc relative link breaks.
# Warnings (exit 0): planning-archive link breakage and ROADMAP phase-dir gaps.
#
# Run standalone (`scripts/check-docs.sh`) or via the pre-commit hook.
set -uo pipefail
cd "$(git rev-parse --show-toplevel)" || exit 2
fail=0
err() { printf ' \033[31m✗\033[0m %s\n' "$*" >&2; fail=1; }
ok() { printf ' \033[32m✓\033[0m %s\n' "$*"; }
warn() { printf ' \033[33m!\033[0m %s\n' "$*"; }
echo "doc-guard: checking documentation consolidation invariants"
# ---------------------------------------------------------------------------
# 1. No doc-mirror tree may reappear.
# ---------------------------------------------------------------------------
mirror_hit=0
for p in tidal/docs tidal/ai-lookup tidal/site .ai .agents/skills; do
if [ -e "$p" ] || git ls-files --error-unmatch "$p" >/dev/null 2>&1; then
err "doc mirror reappeared: '$p' — docs live only at repo root + docs/ (see CLAUDE.md § Critical Rules)"
mirror_hit=1
fi
done
[ "$mirror_hit" -eq 0 ] && ok "no doc-mirror trees present"
# ---------------------------------------------------------------------------
# 1b. No per-crate docker mirror. Container images live only at the repo-root
# docker/ (what .woodpecker.yaml builds); tidal/docker/ was a drifting
# duplicate and was consolidated away.
# ---------------------------------------------------------------------------
if [ -e "tidal/docker" ] || git ls-files --error-unmatch "tidal/docker" >/dev/null 2>&1; then
err "docker mirror reappeared: 'tidal/docker' — Dockerfiles live only at repo-root docker/ (build from the repo root so COPY . . sees the workspace)"
else
ok "no docker-mirror tree present"
fi
# ---------------------------------------------------------------------------
# 2. CLAUDE.md Repository Structure must list every sibling workspace crate.
# ---------------------------------------------------------------------------
struct_hit=0
for crate in tidal-net tidal-server tidalctl; do
if grep -q "^\s*\"$crate\"" Cargo.toml; then
grep -q "$crate" CLAUDE.md || { err "CLAUDE.md does not mention workspace crate '$crate' (Cargo.toml/CLAUDE.md drift)"; struct_hit=1; }
fi
done
[ "$struct_hit" -eq 0 ] && ok "CLAUDE.md structure lists all sibling crates"
# ---------------------------------------------------------------------------
# 3. Core-doc relative links resolve (hard). Planning archive is warn-only.
# ---------------------------------------------------------------------------
check_links() { # $1 = file, $2 = "hard"|"warn"
local f="$1" mode="$2" dir broken=0
dir=$(dirname "$f")
# extract ](target) link targets
while IFS= read -r target; do
[ -z "$target" ] && continue
case "$target" in
http://*|https://*|mailto:*|\#*) continue ;; # external / anchors
*\{*\}*) continue ;; # {template} placeholders
esac
target=${target%%#*} # strip #anchor
[ -z "$target" ] && continue
if [ ! -e "$dir/$target" ]; then
if [ "$mode" = hard ]; then err "broken link in $f -> $target"; else warn "broken link in $f -> $target"; fi
broken=1
fi
done < <(grep -oE '\]\([^)]+\)' "$f" 2>/dev/null | sed -E 's/^\]\(//; s/\)$//')
return $broken
}
core_docs=(README.md CLAUDE.md AGENTS.md CONTRIBUTING.md CHANGELOG.md \
VISION.md USE_CASES.md SEQUENCE.md ARCHITECTURE.md API.md \
QUICKSTART.md CODING_GUIDELINES.md thoughts.md docs/README.md)
core_ok=1
for f in "${core_docs[@]}"; do [ -f "$f" ] && { check_links "$f" hard || core_ok=0; }; done
[ "$core_ok" -eq 1 ] && [ "$fail" -eq 0 ] && ok "core-doc links resolve"
# specs cross-links (hard)
for f in docs/specs/*.md; do [ -f "$f" ] && check_links "$f" hard >/dev/null || true; done
# planning archive (warn-only — historical docs)
for f in $(git ls-files 'docs/planning/*.md' 2>/dev/null); do check_links "$f" warn >/dev/null || true; done
# ---------------------------------------------------------------------------
# 4. ROADMAP COMPLETE milestones should have an on-disk planning dir (warn).
# ---------------------------------------------------------------------------
if [ -f docs/planning/ROADMAP.md ]; then
for n in $(grep -oE 'M[0-9]+' docs/planning/ROADMAP.md | sort -u | sed 's/M//'); do
if grep -qiE "\| *\*?\*?M$n\b.*(COMPLETE|✅)" docs/planning/ROADMAP.md 2>/dev/null; then
[ -d "docs/planning/milestone-$n" ] || warn "ROADMAP marks M$n complete but docs/planning/milestone-$n/ is missing (backlog: backfill phase/task docs)"
fi
done
fi
# ---------------------------------------------------------------------------
# 5. Milestone-status freshness (hard). A core doc must not advertise a status
# bound behind ROADMAP's latest COMPLETE milestone (catches the "M0M10
# shipped" / "Implemented, M0M8" drift). Only status-context lines are
# checked, so historical path lists ("M0M2 Embed & prove primitives")
# never trip it. Self-updating: the bound is derived from ROADMAP, not pinned.
# ---------------------------------------------------------------------------
stale_ms=0
latest=0
if [ -f docs/planning/ROADMAP.md ]; then
for n in $(grep -oE 'M[0-9]+' docs/planning/ROADMAP.md | sed 's/M//' | sort -un); do
if grep -qiE "\| *\*?\*?M$n\b.*(COMPLETE|✅)" docs/planning/ROADMAP.md 2>/dev/null; then
[ "$n" -gt "$latest" ] && latest=$n
fi
done
if [ "$latest" -gt 0 ]; then
for f in CLAUDE.md README.md docs/README.md; do
[ -f "$f" ] || continue
while IFS=: read -r ln text; do
echo "$text" | grep -qiE 'status|shipped|implemented|milestones?' || continue
k=$(echo "$text" | grep -oE 'M0(|-)M[0-9]+' | grep -oE '[0-9]+$' | head -1)
[ -z "$k" ] && continue
if [ "$k" -lt "$latest" ]; then
err "$f:$ln advertises status bound M0M$k but ROADMAP's latest COMPLETE milestone is M$latest — update the status line"
stale_ms=1
fi
done < <(grep -nE 'M0(|-)M[0-9]+' "$f")
done
fi
fi
[ "$stale_ms" -eq 0 ] && ok "core-doc milestone-status lines current (latest COMPLETE = M$latest)"
if [ "$fail" -ne 0 ]; then
echo "doc-guard: FAILED — fix the ✗ items above." >&2
exit 1
fi
echo "doc-guard: OK"