Files
CodeWhale BotandClaude Opus 5.5 4194b0e755 chore(preflight): add the Lint job's clippy step as an opt-in check
main has gone red on the Lint job three times in one night, each from a
different session's commit, and each a defect a local check could have caught:

1. runtime-contract budget, after #7046 grew a tool description (preflight
   --full already covers this one);
2. rustfmt, on the review-core refactor (preflight already covers this);
3. clippy::redundant_closure in the same refactor (nothing local covered it).

Only the third had no local equivalent. `scripts/preflight.sh` says in its own
header that it exists for the checks "that otherwise turn main red after a
green PR", and the Lint job's clippy step was the remaining hole in that set.

`--clippy` runs the job's exact command, workspace-wide with --all-targets and
--all-features, so a local pass means the CI step passes rather than a narrower
subset of it passing. It is a separate flag rather than part of --full because
a workspace clippy with every feature is far slower than everything else
preflight does, and folding it into --full would make --full unusable for the
quick pre-push case.

Verification: `bash -n scripts/preflight.sh` parses; `--help` renders the new
flag and the updated usage line; an unknown flag now prints the flag in its
usage. The step itself was not executed here, because a workspace clippy with
all features takes far longer than this slice's budget and another session is
building; it is the CI job's command verbatim, so CI remains the verifier.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-22 00:02:46 -07:00

140 lines
6.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# One local preflight for the generated files and ratchets that otherwise turn
# main red after a green PR: run it before you push.
#
# scripts/preflight.sh regenerate what can be regenerated, check the rest
# scripts/preflight.sh --check change nothing; fail if anything is stale
# scripts/preflight.sh --full also run the cargo-backed runtime-contract and
# persistence-backlog ratchets and the two
# version-stamped fixture tests (minutes, offline)
# scripts/preflight.sh --clippy also run the Lint job's clippy step exactly as
# CI runs it. Separate from --full because a
# workspace --all-targets --all-features clippy
# is far slower than everything else here, and it
# is the one Lint step nothing else covered.
# scripts/preflight.sh --base REF compare feature receipts against REF
# (default: merge base with origin/main)
#
# As a pre-push hook it runs in --check mode:
# ln -s ../../scripts/preflight.sh "$(git rev-parse --git-path hooks)/pre-push"
#
# What it covers, and the command that fixes each one:
# - rustfmt (the Lint job's first cargo fmt --all (written here)
# step, required on every PR)
# - crates/tui/CHANGELOG.md slice scripts/sync-changelog.sh (written here)
# - README locale stamps and links retranslate; check-readme-translations.py
# prints the new sha256 stamp to use
# - product lexicon (warn-only) python3 scripts/check-lexicon.py lists each hit
# - dead-code / blocking-calls each ratchet's --update, committed in the
# (and with --full runtime-contract, same PR with the reason in the PR body
# persistence-backlog)
# - feature release-note receipts add each feat commit's #issue to
# for this branch's own commits CHANGELOG.md in the same PR
# - clippy (with --clippy) the Lint job's exact command; fix the lint
set -uo pipefail
mode="write"
full=0
clippy=0
base=""
if [[ "$(basename "$0")" == "pre-push" ]]; then
# git passes <remote> <url>; the hook only ever checks.
mode="check"
set --
fi
while [[ "$#" -gt 0 ]]; do
case "$1" in
--check) mode="check"; shift ;;
--full) full=1; shift ;;
--clippy) clippy=1; shift ;;
--base) base="${2:?--base needs a ref}"; shift 2 ;;
-h|--help) sed -n '2,32p' "$0"; exit 0 ;;
*) echo "usage: $0 [--check] [--full] [--clippy] [--base REF]" >&2; exit 2 ;;
esac
done
root="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}" 2>/dev/null || echo "${BASH_SOURCE[0]}")")/.." && pwd)"
cd "${root}" || exit 2
failed=()
step() {
local label="$1" fix="$2"
shift 2
echo "== ${label}"
if "$@"; then
return 0
fi
failed+=("${label}: ${fix}")
}
if [[ "${mode}" == "write" ]]; then
step "rustfmt" "cargo fmt --all" cargo fmt --all
step "TUI changelog slice" "scripts/sync-changelog.sh" ./scripts/sync-changelog.sh
else
step "rustfmt" "cargo fmt --all" cargo fmt --all -- --check
step "TUI changelog slice" "scripts/sync-changelog.sh" ./scripts/sync-changelog.sh --check
fi
step "README translations in sync" \
"retranslate the changed README sections, then update each stamp to the sha256 printed above" \
python3 scripts/check-readme-translations.py
step "README locale link symmetry" "link every README.<locale>.md from README.md" \
bash scripts/check-readme-locales.sh
# Warn-only: lists retired product words and engineering notes in English
# copy; never fails the preflight.
step "product lexicon (warn-only)" "python3 scripts/check-lexicon.py" \
python3 scripts/check-lexicon.py --summary
step "dead-code budget" "python3 scripts/check-dead-code-budget.py --update" \
python3 scripts/check-dead-code-budget.py
step "blocking-calls budget" "python3 scripts/check-blocking-calls-budget.py --update" \
python3 scripts/check-blocking-calls-budget.py
if [[ "${full}" == "1" ]]; then
step "runtime-contract budget" \
"python3 scripts/check-runtime-contract-budget.py --update --allow-increase" \
python3 scripts/check-runtime-contract-budget.py
step "persistence-backlog budget" \
"python3 scripts/check-persistence-backlog-budget.py --update" \
python3 scripts/check-persistence-backlog-budget.py
step "version-stamped fixtures" \
"regenerate the stale fixture the failing test names, then commit it" \
scripts/dev-cargo.sh nextest run --locked --lib -p codewhale-config -p codewhale-tui \
-E 'test(golden_providers_export_matches_registry) | test(status_public_output_and_read_only_state_match_baseline)'
fi
if [[ "${clippy}" == "1" ]]; then
# Exactly the Lint job's clippy step. Two main-side failures in one night were
# a redundant closure and an unformatted test that this would have caught;
# the Lint job is what turns main red, so run its command, not a subset.
step "clippy (Lint job's command)" \
"fix the lint, or run the same command to see it in full: cargo clippy --workspace --all-targets --all-features --locked" \
cargo clippy --workspace --all-targets --all-features --locked -- \
-D warnings \
-A clippy::uninlined_format_args \
-A clippy::too_many_arguments \
-A clippy::unnecessary_map_or
fi
if [[ -z "${base}" ]]; then
base="$(git merge-base HEAD origin/main 2>/dev/null || true)"
fi
if [[ -n "${base}" ]]; then
step "feature release-note receipts (${base:0:12}..HEAD)" \
"add each feat commit's #issue to CHANGELOG.md in this branch" \
./scripts/release/check-feature-release-notes.sh "${base}" HEAD
else
echo "== feature release-note receipts: no base to compare against (no origin/main; pass --base REF)" >&2
failed+=("feature release-note receipts: no base to compare against; pass --base REF")
fi
echo
if [[ "${#failed[@]}" -gt 0 ]]; then
echo "preflight: ${#failed[@]} check(s) failed. Fix, commit the result, and re-run:" >&2
for line in "${failed[@]}"; do
echo " - ${line}" >&2
done
exit 1
fi
if [[ "${mode}" == "write" ]] && ! git diff --quiet -- crates/tui/CHANGELOG.md; then
echo "preflight: OK, and crates/tui/CHANGELOG.md was regenerated -- commit it."
else
echo "preflight: OK"
fi