- 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
143 lines
6.6 KiB
Bash
Executable File
143 lines
6.6 KiB
Bash
Executable File
#!/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 "M0–M10
|
||
# shipped" / "Implemented, M0–M8" drift). Only status-context lines are
|
||
# checked, so historical path lists ("M0–M2 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 M0–M$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"
|