Files
buzz/lefthook.yml
Duncan 937f1ea192 chore(hooks): scope rust-tests pre-push lane to touched crates
The rust-tests pre-push lane ran `just test-unit` (11 nextest
invocations, ~460s) on every push that touched any crate. For
single-crate branches this was unnecessary; the full suite routinely
exceeded the 10-minute MCP shell cap, leaving agents unable to push.

Replace the bare `just test-unit` with scripts/rust-tests-selective.sh
(Variant A: path→crate mapping, no dependency graph). The script:

- Maps changed paths under crates/<name>/ or examples/<name>/ to
  their package names and runs only those packages' test suites.
- Falls back to the full `just test-unit` battery for any full-battery
  trigger (Cargo.lock, root Cargo.toml, rust-toolchain.toml, deny.toml,
  Justfile, scripts/run-tests.sh, migrations/**, schema/**), any
  unmapped path, a missing cargo-nextest, or any script error.
- Preserves per-package invocation shapes exactly: buzz-auth runs
  nextest --lib plus cargo test --doc; buzz-conformance and buzz-cli
  run all targets; buzz-relay uses the scoped api::admin filter.
- Enforces a drift guard: PACKAGE_TABLE keys must exactly match the
  CANONICAL_PACKAGES list; a mismatch falls back to the full battery.

The desktop-tauri-checks lane is intentionally left coarse-grained this
round — scoping it requires the tauri workspace transitive path-dep
closure, which depends on the dependency-graph machinery deferred per
Will's direction. This is noted in lefthook.yml.

CI (dorny/paths-filter + cargo clippy --workspace --all-targets) remains
the authoritative gate; the local lane is a fast pre-filter only.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-09-01 12:59:13 -04:00

139 lines
7.2 KiB
YAML

# Glob patterns below mirror the `changes` job's dorny/paths-filter groups in
# .github/workflows/ci.yml — keep the two in sync. Each globbed pre-push lane
# also sets `files: git diff --name-only origin/main...HEAD` so its file set is
# the branch's merge-base diff against origin/main — the same semantics as CI's
# dorny/paths-filter and `scripts/check-file-sizes-core.mjs` `resolveBaseRef`.
# Without it lefthook derives files from `git diff HEAD @{push}`, which after a
# rebase or `git merge origin/main` includes everything main changed since the
# branch's last push (firing mobile/desktop/rust lanes on unrelated pushes), and
# on a brand-new branch falls back to a stale remote's tip (a full-suite run).
# The three-dot diff scopes every lane to what this branch actually changed.
# Both the three-dot diff and lefthook's stock discovery inspect the checked-out
# HEAD, so pushing a non-HEAD ref (explicit refspec, `--all`) validates HEAD's
# tree, not the pushed commit — the always-on `push-head-scope` lane warns when
# they diverge and CI remains the authoritative gate for that ref.
# Deliberate deviations:
# - The `.github/workflows/ci.yml` path CI adds to its `rust`/`mobile` filters
# is omitted; a CI-workflow-only edit doesn't need a local test run.
# - `desktop-check`/`desktop-typecheck`/`desktop-test` don't trigger on `rust`
# changes, though CI's Desktop Core job does. Those commands are pure TS
# (biome + tsc + node:test) with no Rust dependency, so the extra trigger
# would be spurious locally.
# - The repository-wide `file-size-check` is deliberately unfiltered. Its own
# merge-base diff is the path filter; duplicating its governed roots here is
# the coverage drift this gate is intended to prevent.
# - Deletion-only surface changes do not trigger local hooks: lefthook 2.1.x
# drops deleted paths from push-file discovery (`extractFiles` existence
# check, repository.go). CI's dorny/paths-filter catches deletions.
# Deliberate — accepted, not worked around.
# - `commit-msg` has no glob: it rewrites the commit message, not files. Note
# Git only runs it for `git commit` and `git merge` — other flows need their own
# flag: `git rebase --signoff`, `git cherry-pick -s`.
# - `rust-tests` uses scripts/rust-tests-selective.sh instead of `just test-unit`
# directly. The script maps changed paths to crates (directory-prefix only, no
# dependency graph) and runs only affected packages' shaped suites. Full-battery
# triggers (Cargo.lock, root Cargo.toml, Justfile, migrations/, schema/, etc.)
# and any unmapped path fall back to `just test-unit` unchanged. Per-package
# invocation shapes (buzz-auth doctests, buzz-conformance all-targets, etc.)
# live in the script's PACKAGE_TABLE with a drift guard. The local lane is a
# fast pre-filter; CI's full `cargo clippy --workspace --all-targets` and
# dorny/paths-filter matrix remain the authoritative gate.
# - `desktop-tauri-checks` is intentionally coarse-grained: scoping it per-crate
# requires the tauri workspace's transitive path-dep closure (dependency-graph
# machinery deferred). It fires on all crate changes. CI remains authoritative
# for this lane.
#
# `rc` points `lefthook install` at `bin/.lefthookrc`, which the generated
# `.git/hooks/*` dispatchers source before their `$LEFTHOOK_BIN`-first lookup. It
# (1) sets `LEFTHOOK_BIN` to the Hermit-pinned `bin/lefthook` (2.1.3) so a push
# from any worktree runs the pinned binary even when a different lefthook is on
# PATH, and (2) prepends the repo's Hermit `bin/` to `PATH` so every lane
# subprocess (`just mobile-check` -> flutter/dart, etc.) resolves the pinned
# toolchain regardless of the invoking shell — the hook self-pins, so a Homebrew
# flutter/dart ahead on PATH can no longer cause false lane failures.
rc: bin/.lefthookrc
pre-commit:
parallel: true
commands:
rust-fmt:
glob: ["crates/**", "examples/countdown-bot/**"]
run: just fmt
stage_fixed: true
desktop-tauri-fmt:
glob: ["desktop/src-tauri/**"]
run: just desktop-tauri-fmt
stage_fixed: true
desktop-fix:
# A lockfile-only change has no desktop source for Biome to rewrite.
glob: ["desktop/**"]
exclude: ["desktop/src-tauri/**"]
run: just desktop-fix
stage_fixed: true
web-fix:
# A lockfile-only change has no web source for Biome to rewrite.
glob: ["web/**"]
run: just web-fix
stage_fixed: true
mobile-fmt:
glob: ["mobile/**"]
run: just mobile-fmt
stage_fixed: true
# Appends the DCO Signed-off-by trailer the required "DCO Check" enforces.
# `--if-exists doNothing` makes it idempotent and preserves an existing
# sign-off (including `git commit -s` and a different signer's trailer).
commit-msg:
commands:
signoff:
run: 'git interpret-trailers --if-exists doNothing --trailer "Signed-off-by: $(git var GIT_COMMITTER_IDENT | sed ''s/ [0-9]* [+-][0-9]*$//'')" --in-place {1}'
pre-push:
parallel: true
commands:
branch-skew:
run: ./scripts/check-branch-skew.sh
push-head-scope:
# Warn-only: the scoped lanes below validate the checked-out HEAD, so a
# non-HEAD ref push (explicit refspec, --all) relies on CI for its
# path-scoped checks. This lane reads the pushed refs from stdin and
# warns when any pushed commit is not HEAD. It never fails the push.
use_stdin: true
run: ./scripts/check-push-head-scope.sh
file-size-check:
# The ratchet computes its own merge-base diff, so path filtering here
# would only duplicate policy and create another place for coverage drift.
run: just file-size-check
rust-tests:
files: git diff --name-only origin/main...HEAD
glob: ["crates/**", "migrations/**", "schema/**", "Cargo.toml", "Cargo.lock", "rust-toolchain.toml", "deny.toml", "scripts/run-tests.sh", "Justfile"]
run: ./scripts/rust-tests-selective.sh
desktop-check:
files: git diff --name-only origin/main...HEAD
glob: ["desktop/**", "pnpm-lock.yaml"]
exclude: ["desktop/src-tauri/**"]
run: just desktop-check
desktop-typecheck:
files: git diff --name-only origin/main...HEAD
glob: ["desktop/**", "pnpm-lock.yaml"]
exclude: ["desktop/src-tauri/**"]
run: just desktop-typecheck
desktop-test:
files: git diff --name-only origin/main...HEAD
glob: ["desktop/**", "pnpm-lock.yaml"]
exclude: ["desktop/src-tauri/**"]
run: just desktop-test
desktop-tauri-checks:
# Keep local lint parity with Desktop Core CI for every path that can
# affect the Tauri crate or its path dependencies. Run clippy and tests
# serially so parallel pre-push hooks do not contend for Cargo's lock.
files: git diff --name-only origin/main...HEAD
glob: ["desktop/src-tauri/**", "crates/**", "migrations/**", "schema/**", "Cargo.toml", "Cargo.lock", "rust-toolchain.toml", "deny.toml", "scripts/run-tests.sh", "Justfile"]
run: just desktop-tauri-clippy && just desktop-tauri-test
mobile-checks:
# Run analysis and tests serially so parallel pre-push hooks do not
# contend for shared Flutter state (.dart_tool, shader bundle).
files: git diff --name-only origin/main...HEAD
glob: ["mobile/**"]
run: just mobile-check && just mobile-test