mirror of
https://github.com/Dicklesworthstone/coding_agent_session_search.git
synced 2026-09-22 11:59:25 +08:00
5f05938484
Correction to 1f642a24's message: the golden leak was not caused by the golden harness's own stale-on-read refresh — verify7 reproduced it with that opt-out in place. The writer is tests/cli_robot.rs: 52 call sites use tests/fixtures/search_demo_data IN PLACE through a base_cmd() that never disabled auto-refresh, and on a fleet worker the checkout is not under the OS temp dir, so a robot-CLI search spawned `cass index --background` into the committed fixture and ingested that worker's real sessions before the golden tests copied it. base_cmd() now sets CASS_AUTO_REFRESH=0. Proof: verify9 ran the in-place robot tests before golden regeneration in one job and search_robot/stats_full_payload came back byte-identical to HEAD. pages key (ctigq): `cass pages key list|add-password|add-recovery|revoke| rotate --archive <bundle>` over the complete key_management engine (3,915 lines, zero callers until now). Passwords via prompt or --password-stdin (current on line 1, new on line 2), never argv; --json; exit 1 engine refused, 2 usage, 3 not a bundle, 6 password required. Four unit tests on a real encrypted bundle plus a CLI suite (tests/pages_key_cli.rs). docs/RECOVERY.md rewritten to the real surface; README row. WAL hygiene (z2uon): forget --apply, dedup --apply and analytics validate --fix close through the index run's checkpointing close; doctor `archive_wal` (pass ≤ 64 MiB; warn with size and remedy; warn-and-defer under an index run; --fix checkpoints and reports pass / blocked / failed truthfully) with a sparse-WAL e2e in tests/cli_doctor.rs. status/health e2e for db_bytes / wal_bytes / shm_present, now on the `status` surface too (q2vn9). Segment pressure (wpe6s, GH #441): quill_bridge::segment_file_count (metadata-only upper bound), status/health `index.segment_files`, doctor `index_segments` (warn above 8× the merge threshold, `cass index --full` remedy), CASS_TEST_SKIP_POST_RUN_LEXICAL_MAINTENANCE hook and an e2e that builds a 40-segment generation, sees the warning, and proves a plain `cass index` consolidates it. GH #439 (vg2e1) and GH #440 (tcrxi) end-to-end proofs: FTS-repair hooks (sleep per page after the heartbeat; park before the first page) — a slow repair survives a 20 s abort window, a parked one exits 70 with the `index-stalled` envelope; a hook parks a force-rebuild between the staged engine commit and the checkpoint write, records the gap, and a plain `cass index` resumes with exit 0 and no duplicate identities. sources (vdyxd, G.1): setup no longer marks the final sync complete without running it — interactive setup runs `cass sources sync` afterwards and records it only on success; --json setup defers it and reports sync.status=pending. Remote install walks candidate_methods() (binstall → verified prebuilt → cargo install → bootstrap) and falls through on failure instead of stopping at the first choice. `cass sources sync --all` exists (jwox6): doctor, robot-docs and the fleet rehearsal recommended it while clap rejected it. installer (apriu, G.2a): install.sh probes host glibc and falls back to build-from-source below 2.38; proven on ubuntu:22.04 (2.35 → source route) and ubuntu:24.04 (2.39 → prebuilt). docs validator (xwuon, A.9): scripts/validate_docs.sh --keys/--flags/--env/ --truth check README key bindings against the TUI key map (123/123, planted fake key fails), every `cass … --flag` usage against `cass … --help`, and every README env var against the code; found and fixed `sources sync --all`, `timeline --days`, the `<mark>` highlight claim and a dead CASS_UI_METRICS row. gate.sh gains an opt-in docs-truth stage. gate (c2xxq): scripts/gate.sh had a false green — verify8 was cut by the fleet's 30-minute SSH ceiling after fmt+clippy and the script still said GREEN. Now a non-zero rch exit is RED, every expected stage (plus a terminal job-complete marker) must report, the receipt captures both streams and survives a RED run, --lib-only/--no-goldens split runs under the ceiling, the lib stage runs under `timeout`, and the fleet target dir no longer inherits the shell's CARGO_TARGET_DIR. contract (hr9rn): introspect/capabilities has_json_output looks through the subcommand tree (11 commands flip to true and gain the robot-format arg). TUI: the unreachable `Alt+W => swarm cockpit` arm removed. CHANGELOG [Unreleased], AGENTS swarm/issue rules, plan §7b progress log. Verification (fleet, scripts/gate.sh STAGE receipts, RUST_MIN_STACK=16777216): - verify9 (2026-09-02 00:35-00:58): fmt 0, clippy 0, goldens-regen 0, goldens 0 with search_robot/stats_full_payload byte-identical to HEAD after the in-place cli_robot tests ran first (leak fix proven); cli_index/cli_robot/cli_status 101 (Codex fixture names, thresholds, health nesting) fixed in tree and re-run below. - verify10 (01:04-01:19): `cargo test --lib` to completion, 6,847 passed / 6 failed / 38 ignored, no wedge; the six (bookmarks missing from CANONICAL_TOP_LEVEL_COMMANDS, four sqlite3-CLI tests on a worker without sqlite3, daemon socket path over sun_path on a long TMPDIR) fixed in tree and re-run below. - verify11 (05:08-05:23): fmt 0, clippy 0, lib-tests 0 (canonical commands, daemon, sqlite, sources install/setup, pages key, quill_bridge, stall diagnostics, fingerprint cache), pages_key_cli 0, cli_robot 0, cli_doctor 0, tui_flows 0, bookmarks_cli 0, goldens-regen 0, goldens 0; cli_index 101 (gh439 reads the last stdout JSON line now) and cli_status 101 (health nests state.database) fixed in tree. - verify12 (05:41-05:47, full lib suite on this tree): fmt 0, clippy 0, lib-tests 0 — 6,855 passed / 0 failed / 38 ignored in 144 s. - verify13 (05:47-05:55, --regen-goldens): fmt 0, clippy 0, lib-tests 0 (quill_bridge, stall diagnostics), cli_status 0, cli_doctor 0, pages_key_cli 0, goldens-regen 0, goldens 0; cli_index 101 because the gh439 slow-repair test parsed the last LINE of a pretty-printed summary (harness bug) - it now stream-parses the last JSON document. - verify14 (05:57-06:00, cli_index gh439 after that fix): fmt 0, clippy 0, test-cli_index 0 (gate: GREEN). Goldens: 12 reviewed diffs (segment_files / db_bytes / wal_bytes / shm_present fields, has_json_output flips, pages key and sources sync --all in the command trees, doctor archive_wal / index_segments checks); the four that differ only by the regeneration writer's dropped final newline were left at HEAD. Measurement (owner's archive: 10.3 GB db, 200 MB WAL, 267 Quill segment files; `search "borrow checker lifetimes" --robot --robot-meta --limit 5`, five warm runs, CASS_AUTO_REFRESH=0): v0.7.1 median wall 15.3 s (search_ms 6.3-7.5 s, other_ms 1.0-1.9 s); this tree (release build) median wall 1.9 s (search_ms 337-406 ms, other_ms 754-810 ms after a first-run 2.6 s sidecar miss). hits=1 on both. Not yet isolated: the engine-time drop happened with the segment count unchanged, so the per-open seal in the old Quill config is the likelier cause than fragmentation on this archive. Consolidation of the owner's 267 segments was NOT achieved: the documented path (`cass index`, and `cass doctor --fix` for the WAL) never leaves the frankensqlite writable open on that archive - one core, 6.7 GB RSS, ~30k 24-byte header reads/s on the 200 MB WAL, no progress in 7 min - while read-only opens take 1.5 s. On a COPY whose WAL C SQLite checkpointed losslessly (1,012 conversations / 538,807 messages before and after), the same `cass index` gets through the open and indexes, so the WAL is the trigger; C SQLite's quick_check on that copy also reports a doubly- referenced freelist page and free-space corruption on ~35 pages that `cass doctor` calls healthy. The archive's last completed index is 2026-08-14. Recorded as a new P0 in the plan (section 7b); the live archive was not modified. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
678 lines
23 KiB
Bash
Executable File
678 lines
23 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Documentation validation script for cass.
|
|
#
|
|
# Validates:
|
|
# - Link validity in markdown files
|
|
# - Required sections in README
|
|
# - CLI help text consistency
|
|
# - Example code validity
|
|
#
|
|
# Usage:
|
|
# ./scripts/validate_docs.sh # Run all validations
|
|
# ./scripts/validate_docs.sh --links # Only check links
|
|
# ./scripts/validate_docs.sh --readme # Only check README
|
|
# ./scripts/validate_docs.sh --help # Only check CLI help
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
RCH_BIN="${RCH_BIN:-rch}"
|
|
RCH_TARGET_DIR="${RCH_TARGET_DIR:-${TMPDIR:-/tmp}/rch_target_cass_validate_docs}"
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Counters
|
|
ERRORS=0
|
|
WARNINGS=0
|
|
CHECKS=0
|
|
|
|
# =============================================================================
|
|
# Helper Functions
|
|
# =============================================================================
|
|
|
|
log_pass() {
|
|
((CHECKS += 1))
|
|
echo -e "${GREEN}✓${NC} $1"
|
|
}
|
|
|
|
log_fail() {
|
|
((CHECKS += 1))
|
|
((ERRORS += 1))
|
|
echo -e "${RED}✗${NC} $1"
|
|
}
|
|
|
|
log_warn() {
|
|
((WARNINGS += 1))
|
|
echo -e "${YELLOW}!${NC} $1"
|
|
}
|
|
|
|
log_info() {
|
|
echo -e " $1"
|
|
}
|
|
|
|
ensure_rch() {
|
|
if ! command -v "$RCH_BIN" &> /dev/null; then
|
|
log_fail "rch binary not found; validate_docs cargo work must be offloaded"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
run_cargo() {
|
|
"$RCH_BIN" exec -- env CARGO_TARGET_DIR="$RCH_TARGET_DIR" cargo "$@"
|
|
}
|
|
|
|
section() {
|
|
echo ""
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo " $1"
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
}
|
|
|
|
# =============================================================================
|
|
# Link Validation
|
|
# =============================================================================
|
|
|
|
check_links() {
|
|
section "Link Validation"
|
|
|
|
local md_files
|
|
md_files=$(find . -name "*.md" -not -path "./target/*" -not -path "./.git/*" 2>/dev/null || true)
|
|
|
|
if [[ -z "$md_files" ]]; then
|
|
log_warn "No markdown files found"
|
|
return
|
|
fi
|
|
|
|
local file
|
|
for file in $md_files; do
|
|
log_info "Checking $file..."
|
|
|
|
# Check for broken internal links (relative paths)
|
|
local links
|
|
links=$(grep -oE '\[([^]]+)\]\(([^)]+)\)' "$file" 2>/dev/null | grep -v 'http' | grep -v 'mailto' || true)
|
|
|
|
while IFS= read -r link; do
|
|
[[ -z "$link" ]] && continue
|
|
|
|
# Extract the path from the link
|
|
local path
|
|
path=$(echo "$link" | sed -E 's/.*\]\(([^)#]+).*/\1/')
|
|
|
|
# Skip anchors and empty paths
|
|
[[ -z "$path" || "$path" == "#"* ]] && continue
|
|
|
|
# Resolve relative to file directory
|
|
local dir
|
|
dir=$(dirname "$file")
|
|
local full_path="$dir/$path"
|
|
|
|
if [[ ! -e "$full_path" && ! -e "$path" ]]; then
|
|
log_fail "Broken link in $file: $path"
|
|
fi
|
|
done <<< "$links"
|
|
|
|
# Check for valid URL patterns in external links
|
|
local urls
|
|
urls=$(grep -oE 'https?://[^)"\s>]+' "$file" 2>/dev/null || true)
|
|
|
|
while IFS= read -r url; do
|
|
[[ -z "$url" ]] && continue
|
|
|
|
# Basic URL format validation
|
|
if ! echo "$url" | grep -qE '^https?://[a-zA-Z0-9]'; then
|
|
log_fail "Malformed URL in $file: $url"
|
|
fi
|
|
done <<< "$urls"
|
|
done
|
|
|
|
log_pass "Link validation complete"
|
|
}
|
|
|
|
# =============================================================================
|
|
# README Validation
|
|
# =============================================================================
|
|
|
|
check_readme() {
|
|
section "README Validation"
|
|
|
|
local readme="README.md"
|
|
|
|
if [[ ! -f "$readme" ]]; then
|
|
log_fail "README.md not found"
|
|
return
|
|
fi
|
|
|
|
log_info "Checking required sections..."
|
|
|
|
# Check for key sections
|
|
local sections=("installation" "usage" "features" "license")
|
|
|
|
for sec in "${sections[@]}"; do
|
|
if grep -qi "## .*$sec\|# .*$sec" "$readme"; then
|
|
log_pass "README has $sec section"
|
|
else
|
|
log_warn "README may be missing $sec section"
|
|
fi
|
|
done
|
|
|
|
# Check for examples
|
|
if grep -q '```' "$readme"; then
|
|
log_pass "README contains code examples"
|
|
else
|
|
log_warn "README has no code examples"
|
|
fi
|
|
|
|
# Check for badges (optional)
|
|
if grep -qE '!\[.*\]\(https?://' "$readme"; then
|
|
log_pass "README has badges/images"
|
|
else
|
|
log_info "README has no badges (optional)"
|
|
fi
|
|
|
|
# Check file isn't empty or too short
|
|
local lines
|
|
lines=$(wc -l < "$readme")
|
|
if [[ "$lines" -lt 20 ]]; then
|
|
log_warn "README seems short ($lines lines)"
|
|
else
|
|
log_pass "README has adequate content ($lines lines)"
|
|
fi
|
|
}
|
|
|
|
# =============================================================================
|
|
# CLI Help Validation
|
|
# =============================================================================
|
|
|
|
check_help() {
|
|
section "CLI Help Validation"
|
|
|
|
# Check if binary exists
|
|
local binary="$RCH_TARGET_DIR/release/cass"
|
|
if [[ ! -x "$binary" ]]; then
|
|
binary="$RCH_TARGET_DIR/debug/cass"
|
|
fi
|
|
if [[ ! -x "$binary" ]]; then
|
|
binary="target/release/cass"
|
|
fi
|
|
if [[ ! -x "$binary" ]]; then
|
|
binary="target/debug/cass"
|
|
fi
|
|
|
|
if [[ ! -x "$binary" ]]; then
|
|
log_warn "cass binary not found, building through rch..."
|
|
ensure_rch || return
|
|
run_cargo build --quiet --bin cass 2>/dev/null || {
|
|
log_fail "Could not build cass binary"
|
|
return
|
|
}
|
|
binary="$RCH_TARGET_DIR/debug/cass"
|
|
if [[ ! -x "$binary" ]]; then
|
|
log_fail "Built cass binary not found at $binary"
|
|
return
|
|
fi
|
|
fi
|
|
|
|
log_info "Using binary: $binary"
|
|
|
|
# Test --help
|
|
if "$binary" --help &>/dev/null; then
|
|
log_pass "--help flag works"
|
|
else
|
|
log_fail "--help flag failed"
|
|
fi
|
|
|
|
# Test -h
|
|
if "$binary" -h &>/dev/null; then
|
|
log_pass "-h flag works"
|
|
else
|
|
log_fail "-h flag failed"
|
|
fi
|
|
|
|
# Test --version
|
|
local version_output
|
|
version_output=$("$binary" --version 2>&1 || true)
|
|
if echo "$version_output" | grep -qE '[0-9]+\.[0-9]+\.[0-9]+'; then
|
|
log_pass "--version shows version number"
|
|
else
|
|
log_fail "--version doesn't show version number"
|
|
fi
|
|
|
|
# Test subcommand help
|
|
local subcommands=("search" "index" "export" "tui" "health")
|
|
for cmd in "${subcommands[@]}"; do
|
|
if "$binary" "$cmd" --help &>/dev/null; then
|
|
log_pass "Subcommand '$cmd' has help"
|
|
else
|
|
log_warn "Subcommand '$cmd' help unavailable"
|
|
fi
|
|
done
|
|
|
|
# Check help mentions key features
|
|
local help_output
|
|
help_output=$("$binary" --help 2>&1 || true)
|
|
|
|
if echo "$help_output" | grep -qi "search"; then
|
|
log_pass "Help mentions search"
|
|
else
|
|
log_warn "Help doesn't mention search"
|
|
fi
|
|
|
|
if echo "$help_output" | grep -qi "index"; then
|
|
log_pass "Help mentions index"
|
|
else
|
|
log_warn "Help doesn't mention index"
|
|
fi
|
|
}
|
|
|
|
# =============================================================================
|
|
# Security Doc Validation
|
|
# =============================================================================
|
|
|
|
check_security() {
|
|
section "Security Documentation"
|
|
|
|
local security="SECURITY.md"
|
|
|
|
if [[ ! -f "$security" ]]; then
|
|
log_warn "SECURITY.md not found (may be generated at publish time)"
|
|
return
|
|
fi
|
|
|
|
log_info "Checking security documentation..."
|
|
|
|
# Check for key security concepts
|
|
local concepts=("encrypt" "argon" "aes" "password" "key")
|
|
|
|
for concept in "${concepts[@]}"; do
|
|
if grep -qi "$concept" "$security"; then
|
|
log_pass "Security doc mentions $concept"
|
|
else
|
|
log_warn "Security doc may not cover $concept"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# =============================================================================
|
|
# Example Code Validation
|
|
# =============================================================================
|
|
|
|
# =============================================================================
|
|
# README ↔ code truth checks (reality check 2026-09-01, WS-A.9)
|
|
#
|
|
# The README is the vision document; these checks make its concrete claims
|
|
# executable: every key binding in a README key table must have a matching arm
|
|
# in the TUI key map, every `cass` flag the README shows must exist in
|
|
# `cass introspect --json`, and every env var in the README env table must be
|
|
# one `cass robot-docs env` knows. `README_PATH` overrides the file under test
|
|
# (used to plant a negative); `CASS_BIN` overrides the binary.
|
|
# =============================================================================
|
|
|
|
resolve_cass_binary() {
|
|
local candidate
|
|
for candidate in "${CASS_BIN:-}" "$RCH_TARGET_DIR/release/cass" "$RCH_TARGET_DIR/debug/cass" \
|
|
"target/release/cass" "target/debug/cass"; do
|
|
if [[ -n "$candidate" && -x "$candidate" ]]; then
|
|
echo "$candidate"
|
|
return 0
|
|
fi
|
|
done
|
|
if command -v cass &> /dev/null; then
|
|
command -v cass
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# Translate one README key token (`Alt+Shift+W`, `F4`, `Ctrl+Del`, `Esc`, `?`)
|
|
# into an extended regex over the TUI key map source, or print nothing when the
|
|
# token is not a key (prose in the key column).
|
|
key_token_pattern() {
|
|
local token="$1" mods="" base
|
|
local ctrl=0 alt=0 shift=0
|
|
base="$token"
|
|
while [[ "$base" == *+* && ${#base} -gt 1 ]]; do
|
|
case "${base%%+*}" in
|
|
Ctrl|ctrl) ctrl=1 ;;
|
|
Alt|alt|Meta|Opt|Option) alt=1 ;;
|
|
Shift|shift) shift=1 ;;
|
|
*) break ;;
|
|
esac
|
|
base="${base#*+}"
|
|
done
|
|
local code=""
|
|
case "$base" in
|
|
F[0-9]|F1[0-2]) code="KeyCode::F\\(${base#F}\\)" ;;
|
|
Esc|Escape) code="KeyCode::Esc" ;;
|
|
Enter|Return) code="KeyCode::Enter" ;;
|
|
Tab) if [[ $shift == 1 ]]; then code="KeyCode::BackTab"; shift=0; else code="KeyCode::Tab"; fi ;;
|
|
BackTab) code="KeyCode::BackTab" ;;
|
|
Del|Delete) code="KeyCode::Delete" ;;
|
|
Backspace) code="KeyCode::Backspace" ;;
|
|
Up|Down|Left|Right|Home|End) code="KeyCode::${base}" ;;
|
|
PageUp|PgUp) code="KeyCode::PageUp" ;;
|
|
PageDown|PgDn) code="KeyCode::PageDown" ;;
|
|
Space) code="Char\\(' '\\)" ;;
|
|
[1-9]) # README writes digit ranges as `Alt+1`..`Alt+9`; the map has one
|
|
# range arm: `KeyCode::Char(c @ '1'..='9') if <modifier>`.
|
|
code="(Char\\('${base}'\\)|Char\\(c @ '1'\\.\\.='9'\\))" ;;
|
|
?) # single printable character: either case is accepted, and either
|
|
# of the two forms the TUI uses — a `KeyCode::Char('x')` arm, or a
|
|
# typed-character re-dispatch `text == "x"` (the detail pane).
|
|
local lower upper escaped
|
|
lower=$(printf '%s' "$base" | tr '[:upper:]' '[:lower:]')
|
|
upper=$(printf '%s' "$base" | tr '[:lower:]' '[:upper:]')
|
|
case "$base" in
|
|
[A-Za-z]) code="(Char\\('(${lower}|${upper})'\\)|text == \"(${lower}|${upper})\")" ;;
|
|
"'") code="Char\\('\\\\''\\)" ;;
|
|
*)
|
|
escaped=$(printf '%s' "$base" | sed 's/[][\\.*^$/?+(){}|]/\\&/g')
|
|
code="(Char\\('${escaped}'\\)|text == \"${escaped}\")"
|
|
;;
|
|
esac
|
|
;;
|
|
*) return 0 ;;
|
|
esac
|
|
[[ $ctrl == 1 ]] && mods="${mods}(?=.*ctrl)"
|
|
[[ $alt == 1 ]] && mods="${mods}(?=.*alt)"
|
|
[[ $shift == 1 ]] && mods="${mods}(?=.*shift)"
|
|
# PCRE: the arm line must mention the code and every modifier guard.
|
|
echo "^(?=.*${code})${mods}"
|
|
}
|
|
|
|
check_keys() {
|
|
section "README key bindings ↔ TUI key map"
|
|
local readme="${README_PATH:-README.md}" keymap="src/ui/app.rs"
|
|
local missing=0 checked=0 token pattern cell
|
|
while IFS= read -r cell; do
|
|
# Split multi-key cells: `F3` / `Alt+G`
|
|
while IFS= read -r token; do
|
|
token="${token#\`}"; token="${token%\`}"
|
|
[[ -z "$token" ]] && continue
|
|
pattern=$(key_token_pattern "$token")
|
|
[[ -z "$pattern" ]] && continue
|
|
checked=$((checked + 1))
|
|
if ! grep -P -q "$pattern" "$keymap"; then
|
|
log_fail "README key \`$token\` has no matching arm in $keymap"
|
|
missing=$((missing + 1))
|
|
fi
|
|
done < <(printf '%s\n' "$cell" | grep -o -E '`[^`]+`')
|
|
done < <(grep -E '^\| `[^|]+` *\|' "$readme" | sed -E 's/^\| ([^|]*)\|.*/\1/' | grep -E '`(F[0-9]+|Esc|Enter|Tab|Del|Delete|Backspace|Up|Down|Left|Right|Home|End|Page(Up|Down)|Space|Ctrl|Alt|Shift|.)`|\+' )
|
|
if [[ $missing -eq 0 ]]; then
|
|
log_pass "all $checked README key bindings resolve to a TUI key-map arm"
|
|
fi
|
|
}
|
|
|
|
check_flags() {
|
|
section "README cass flags ↔ cass introspect"
|
|
local readme="${README_PATH:-README.md}" binary known
|
|
if ! binary=$(resolve_cass_binary); then
|
|
log_warn "cass binary not found (set CASS_BIN); skipping flag check"
|
|
return
|
|
fi
|
|
# `cass introspect --json` lists top-level commands only (nested
|
|
# subcommands such as `sources sync` are not described there — a contract
|
|
# gap noted in the reality check), so the truth source for a README
|
|
# invocation is clap itself: `cass <command path> --help`. Help output is
|
|
# collected once per command path.
|
|
local global_flags
|
|
global_flags=$("$binary" --help 2>/dev/null | grep -o -E -- '--[a-z][a-z0-9-]+' | sort -u)
|
|
if [[ -z "$global_flags" ]]; then
|
|
log_fail "cass --help printed no flags from $binary"
|
|
return
|
|
fi
|
|
declare -A help_cache=()
|
|
flags_for_path() {
|
|
local path="$1"
|
|
if [[ -z "${help_cache[$path]+x}" ]]; then
|
|
# shellcheck disable=SC2086
|
|
help_cache[$path]=$("$binary" $path --help 2>/dev/null | grep -o -E -- '--[a-z][a-z0-9-]+' | sort -u)
|
|
fi
|
|
printf '%s\n' "${help_cache[$path]}"
|
|
}
|
|
local missing=0 checked=0 line path token flag
|
|
# README lines that deliberately show WRONG spellings (the auto-correction
|
|
# tables) are not claims about the contract. A line may hold several
|
|
# invocations; each `cass …` segment is judged on its own.
|
|
while IFS= read -r line; do
|
|
path=""
|
|
local first=1
|
|
for token in $(printf '%s' "$line"); do
|
|
if [[ $first == 1 ]]; then first=0; continue; fi # the literal `cass`
|
|
case "$token" in
|
|
-*|\"*|\'*|\`*|\||'#'*) break ;;
|
|
*)
|
|
if [[ "$token" =~ ^[a-z][a-z0-9-]*$ ]]; then
|
|
path="${path:+$path }$token"
|
|
else
|
|
break
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
while IFS= read -r flag; do
|
|
[[ -z "$flag" ]] && continue
|
|
case "$flag" in --help|--version) continue ;; esac
|
|
checked=$((checked + 1))
|
|
if printf '%s\n' "$global_flags" | grep -q -x -- "$flag"; then
|
|
continue
|
|
fi
|
|
if [[ -z "$path" ]]; then
|
|
# `cass --robot` in prose names a flag without its command;
|
|
# not checkable against one --help, so it is a warning.
|
|
log_warn "README mentions \`cass $flag\` without a command; cannot check it against --help"
|
|
continue
|
|
fi
|
|
if flags_for_path "$path" | grep -q -x -- "$flag"; then
|
|
continue
|
|
fi
|
|
log_fail "README shows \`cass $path $flag\` but \`cass $path --help\` does not list $flag"
|
|
missing=$((missing + 1))
|
|
done < <(printf '%s\n' "$line" | grep -o -E -- '--[a-z][a-z0-9-]+' | sort -u)
|
|
done < <(grep -E '(^|[^a-z])cass ' "$readme" \
|
|
| grep -v -i -E 'typo|alias|corrected|auto-correct|Levenshtein|wrong|converted|become|normalized|promoted|route' \
|
|
| sed -E 's/(^|[^a-z])cass /\ncass /g' | grep -E '^cass ' \
|
|
| sed -E 's/`.*$//') # an inline code span ends at the next backtick
|
|
if [[ $missing -eq 0 ]]; then
|
|
log_pass "all $checked README cass flag usages are accepted by the matching \`cass … --help\`"
|
|
fi
|
|
}
|
|
|
|
check_env() {
|
|
section "README env vars ↔ code"
|
|
local readme="${README_PATH:-README.md}" binary robot_known=""
|
|
# Truth source: the variable must be read somewhere in src/ (dotenvy::var,
|
|
# env::var, or a named constant). `cass robot-docs env` is a curated subset
|
|
# for agents, so a README variable it omits is only a warning.
|
|
if binary=$(resolve_cass_binary); then
|
|
robot_known=$("$binary" robot-docs env 2>/dev/null | grep -o -E '^\s+[A-Z][A-Z0-9_]+' | tr -d ' ' | sort -u)
|
|
fi
|
|
# Connector root overrides (`CASS_<AGENT>_DATA_ROOT`) are read by the
|
|
# franken_agent_detection crate, not by src/; look in its checkout or the
|
|
# cargo registry copy when present.
|
|
local -a env_roots=(src)
|
|
local fad fad_found=0
|
|
for fad in /data/projects/franken_agent_detection/src \
|
|
"${CARGO_HOME:-$HOME/.cargo}"/registry/src/*/franken_agent_detection-*/src; do
|
|
if [[ -d "$fad" ]]; then
|
|
env_roots+=("$fad")
|
|
fad_found=1
|
|
fi
|
|
done
|
|
local missing=0 checked=0 unlisted=0 var
|
|
while IFS= read -r var; do
|
|
checked=$((checked + 1))
|
|
if ! grep -r -q -F -- "\"$var\"" "${env_roots[@]}" --include='*.rs'; then
|
|
if [[ $fad_found == 0 && "$var" == CASS_*_DATA_ROOT ]]; then
|
|
# Connector root overrides live in the detection crate; without
|
|
# its source on this host the claim cannot be checked.
|
|
log_warn "README env var \`$var\` is a connector root override; the agent-detection crate source is not available here to confirm it"
|
|
continue
|
|
fi
|
|
log_fail "README env table documents \`$var\` but nothing in src/ (or the agent-detection crate) reads \"$var\""
|
|
missing=$((missing + 1))
|
|
elif [[ -n "$robot_known" ]] && ! printf '%s\n' "$robot_known" | grep -q -x -- "$var"; then
|
|
unlisted=$((unlisted + 1))
|
|
fi
|
|
done < <(grep -E '^\| `CASS_[A-Z0-9_]+`' "$readme" | grep -o -E 'CASS_[A-Z0-9_]+' | sort -u)
|
|
if [[ $missing -eq 0 ]]; then
|
|
log_pass "all $checked README env vars are read by the code"
|
|
fi
|
|
if [[ $unlisted -gt 0 ]]; then
|
|
log_warn "$unlisted README env vars are not in the curated \`cass robot-docs env\` list"
|
|
fi
|
|
}
|
|
|
|
check_examples() {
|
|
section "Example Code Validation"
|
|
|
|
# Extract code blocks from README
|
|
local readme="README.md"
|
|
|
|
if [[ ! -f "$readme" ]]; then
|
|
log_warn "README.md not found"
|
|
return
|
|
fi
|
|
|
|
# Check for shell examples
|
|
if grep -qE '```(bash|sh|shell)' "$readme"; then
|
|
log_pass "README has shell examples"
|
|
else
|
|
log_info "No shell examples in README"
|
|
fi
|
|
|
|
# Check for Rust examples
|
|
if grep -qE '```rust' "$readme"; then
|
|
log_pass "README has Rust examples"
|
|
else
|
|
log_info "No Rust examples in README"
|
|
fi
|
|
|
|
# Validate cargo commands mentioned work
|
|
local cargo_cmds
|
|
cargo_cmds=$(grep -oE 'cargo (build|test|run|install|bench)[^`]*' "$readme" 2>/dev/null | head -5 || true)
|
|
|
|
if [[ -n "$cargo_cmds" ]]; then
|
|
log_info "Found cargo commands in README"
|
|
while IFS= read -r cmd; do
|
|
[[ -z "$cmd" ]] && continue
|
|
log_info " - $cmd"
|
|
done <<< "$cargo_cmds"
|
|
fi
|
|
}
|
|
|
|
# =============================================================================
|
|
# Cargo Doc Validation
|
|
# =============================================================================
|
|
|
|
check_cargo_docs() {
|
|
section "Cargo Documentation"
|
|
|
|
log_info "Building documentation..."
|
|
|
|
ensure_rch || return
|
|
|
|
if run_cargo doc --no-deps --quiet 2>/dev/null; then
|
|
log_pass "cargo doc builds successfully"
|
|
else
|
|
log_fail "cargo doc has errors"
|
|
fi
|
|
|
|
# Check for documentation warnings
|
|
local doc_output
|
|
doc_output=$(run_cargo doc --no-deps 2>&1 || true)
|
|
|
|
local missing_docs
|
|
missing_docs=$(echo "$doc_output" | grep -c "missing documentation" || true)
|
|
|
|
if [[ "$missing_docs" -gt 0 ]]; then
|
|
log_warn "$missing_docs items missing documentation"
|
|
else
|
|
log_pass "No missing documentation warnings"
|
|
fi
|
|
}
|
|
|
|
# =============================================================================
|
|
# Main
|
|
# =============================================================================
|
|
|
|
main() {
|
|
echo "╔═══════════════════════════════════════════════════════════════╗"
|
|
echo "║ CASS Documentation Validation ║"
|
|
echo "╚═══════════════════════════════════════════════════════════════╝"
|
|
|
|
cd "$PROJECT_ROOT"
|
|
|
|
case "${1:-all}" in
|
|
--links)
|
|
check_links
|
|
;;
|
|
--readme)
|
|
check_readme
|
|
;;
|
|
--help)
|
|
check_help
|
|
;;
|
|
--security)
|
|
check_security
|
|
;;
|
|
--examples)
|
|
check_examples
|
|
;;
|
|
--cargo)
|
|
check_cargo_docs
|
|
;;
|
|
--keys)
|
|
check_keys
|
|
;;
|
|
--flags)
|
|
check_flags
|
|
;;
|
|
--env)
|
|
check_env
|
|
;;
|
|
--truth)
|
|
check_keys
|
|
check_flags
|
|
check_env
|
|
;;
|
|
all|*)
|
|
check_readme
|
|
check_links
|
|
check_help
|
|
check_security
|
|
check_examples
|
|
check_cargo_docs
|
|
check_keys
|
|
check_flags
|
|
check_env
|
|
;;
|
|
esac
|
|
|
|
# Summary
|
|
section "Summary"
|
|
echo ""
|
|
echo " Checks: $CHECKS"
|
|
echo " Passed: $((CHECKS - ERRORS))"
|
|
echo " Errors: $ERRORS"
|
|
echo " Warnings: $WARNINGS"
|
|
echo ""
|
|
|
|
if [[ "$ERRORS" -gt 0 ]]; then
|
|
echo -e "${RED}Documentation validation failed with $ERRORS error(s)${NC}"
|
|
exit 1
|
|
elif [[ "$WARNINGS" -gt 0 ]]; then
|
|
echo -e "${YELLOW}Documentation validation passed with $WARNINGS warning(s)${NC}"
|
|
exit 0
|
|
else
|
|
echo -e "${GREEN}Documentation validation passed!${NC}"
|
|
exit 0
|
|
fi
|
|
}
|
|
|
|
main "$@"
|