#!/usr/bin/env bash # scripts/quality/codespell.sh # # Run codespell across the repo to catch typos in: # * doc/**/*.md (reviewer-facing material) # * code/include/**/*.{h,hpp} (Doxygen comments are user-facing) # * code/src/, code/tests/ (test names, error messages) # * scripts/**/*.{sh,py} (CI messages reach contributors) # * README.md, CHANGELOG.md, CLAUDE.md # # Skips vendored deps, build dirs, generated Doxygen output (see # `.codespellrc`). # # Local-only. CI promotion once the existing tree is 0-typo. # # Usage: # bash scripts/quality/codespell.sh # dry-run, exit 1 on any hit # bash scripts/quality/codespell.sh --fix # interactively apply suggestions # # Exit codes: # 0 no typos # 1 at least one typo found (no --fix), or --fix completed # 2 prerequisite missing set -uo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" cd "$ROOT" || exit 2 command -v codespell >/dev/null 2>&1 || { echo "FAIL: codespell not in PATH." >&2 echo " macOS: brew install codespell" >&2 echo " Linux: sudo apt install codespell OR pip3 install codespell" >&2 exit 2 } FIX=0 for arg in "$@"; do case "$arg" in --fix) FIX=1 ;; *) echo "Unknown arg: $arg" >&2; exit 2 ;; esac done # Targets: everything except deps + build + generated. TARGETS=( "doc" "code/include" "code/src" "code/tests" "scripts" "README.md" "CHANGELOG.md" "CLAUDE.md" "CITATION.cff" "CONTRIBUTING.md" ) # Filter to existing entries (CONTRIBUTING.md / CHANGELOG.md may not exist # on every branch). EXISTING=() for t in "${TARGETS[@]}"; do [ -e "$t" ] && EXISTING+=("$t") done echo "codespell ($(codespell --version 2>&1 | head -1))" echo "Targets: ${EXISTING[*]}" echo if [ "$FIX" -eq 1 ]; then codespell --write-changes "${EXISTING[@]}" rc=$? else codespell "${EXISTING[@]}" rc=$? fi if [ "$rc" -ne 0 ]; then echo echo "Recovery:" echo " bash scripts/quality/codespell.sh --fix # apply" echo " # or add false-positives to .codespellrc → ignore-words-list" exit 1 fi echo echo "OK: no typos found." exit 0