diff --git a/.husky/pre-push b/.husky/pre-push index 5f44d835..9e211251 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -2,49 +2,96 @@ # Pre-push: full CI checks run locally before any push. # This replaces remote CI for normal task pushes. # DO NOT skip with --no-verify (Claude Code is configured to never do this). +# +# ── Optimizations (Feb 2026) ───────────────────────────────────────────── +# +# 1. --no-clean on cargo llvm-cov: reuses previous instrumented build +# artifacts for incremental compilation. Reduces Rust coverage from +# ~8 min (full recompile) to ~30-60s (incremental rebuild). +# +# 2. Change detection: skips Rust checks entirely when no files under +# src-tauri/ changed. Saves ~1-2 min on frontend-only pushes. +# +# 3. Merged redundant test runs: frontend coverage (step 2) already runs +# all tests, so the separate test step was removed. +# +# 4. Fast-fail ordering: within Rust checks, fast lints (fmt ~2s, clippy +# ~15s) run before slow coverage (~30-60s) for quicker feedback. +# +# 5. Force full coverage: set LAPUTA_FULL_COVERAGE=1 to run cargo llvm-cov +# without --no-clean (clean rebuild, accurate baseline). +# +# Expected times (warm cache, incremental): +# Frontend only: ~1 min +# Frontend+Rust: ~2-3 min +# Full coverage: ~9-10 min (with LAPUTA_FULL_COVERAGE=1) +# ───────────────────────────────────────────────────────────────────────── set -e +START_TIME=$(date +%s) + echo "" echo "🚀 Pre-push checks (replaces CI — do not skip)" echo "================================================" -# ── 0. Build check ──────────────────────────────────────────────────────── +# ── Detect what changed ───────────────────────────────────────────────── +PUSH_TARGET=$(git rev-parse @{push} 2>/dev/null || echo "") +RUST_CHANGED=true + +if [ -n "$PUSH_TARGET" ]; then + CHANGED=$(git diff --name-only "$PUSH_TARGET"..HEAD) + if ! echo "$CHANGED" | grep -qE '^(src-tauri/|Cargo)'; then + RUST_CHANGED=false + fi +fi + +# ── 0. TypeScript + Vite build ────────────────────────────────────────── echo "" -echo "📦 [0/5] TypeScript + Vite build..." +echo "📦 [0/4] TypeScript + Vite build..." pnpm exec tsc --noEmit pnpm build echo " ✅ Build OK" -# ── 1. Frontend tests ───────────────────────────────────────────────────── +# ── 1. Frontend coverage (≥70%) — includes all unit tests ─────────────── echo "" -echo "🧪 [1/5] Frontend tests..." -pnpm test --run --silent -echo " ✅ Frontend tests OK" - -# ── 2. Frontend coverage (≥70%) ─────────────────────────────────────────── -echo "" -echo "📊 [2/5] Frontend coverage (≥70%)..." +echo "📊 [1/4] Frontend tests + coverage (≥70%)..." pnpm test:coverage --silent echo " ✅ Frontend coverage OK" -# ── 3. Rust tests + coverage (≥85% lines) ──────────────────────────────── +# ── 2. Rust lint (clippy + fmt) — fast, run before coverage ───────────── echo "" -echo "🦀 [3/5] Rust tests + coverage (≥85% lines)..." -cargo llvm-cov \ - --manifest-path src-tauri/Cargo.toml \ - --fail-under-lines 85 -echo " ✅ Rust coverage OK" +if [ "$RUST_CHANGED" = true ]; then + echo "🔧 [2/4] Clippy + rustfmt..." + cargo clippy --manifest-path=src-tauri/Cargo.toml -- -D warnings + cargo fmt --manifest-path=src-tauri/Cargo.toml -- --check + echo " ✅ Rust lint OK" +else + echo "⏭️ [2/4] Rust lint — skipped (no src-tauri/ changes)" +fi -# ── 4. Clippy + rustfmt ─────────────────────────────────────────────────── +# ── 3. Rust coverage (≥85% lines) ────────────────────────────────────── echo "" -echo "🔧 [4/5] Clippy + rustfmt..." -cargo clippy --manifest-path=src-tauri/Cargo.toml -- -D warnings -cargo fmt --manifest-path=src-tauri/Cargo.toml -- --check -echo " ✅ Rust lint OK" +if [ "$RUST_CHANGED" = true ]; then + LLVM_COV_FLAGS="--no-clean" + if [ "${LAPUTA_FULL_COVERAGE:-0}" = "1" ]; then + LLVM_COV_FLAGS="" + echo "🦀 [3/4] Rust coverage — FULL (LAPUTA_FULL_COVERAGE=1)..." + else + echo "🦀 [3/4] Rust coverage (≥85%, incremental)..." + fi + # shellcheck disable=SC2086 + cargo llvm-cov \ + --manifest-path src-tauri/Cargo.toml \ + $LLVM_COV_FLAGS \ + --fail-under-lines 85 + echo " ✅ Rust coverage OK" +else + echo "⏭️ [3/4] Rust coverage — skipped (no src-tauri/ changes)" +fi -# ── 5. CodeScene code health gate (≥9.2) ───────────────────────────────── +# ── 4. CodeScene code health gate (≥9.2) ──────────────────────────────── echo "" -echo "🏥 [5/5] CodeScene code health gate (≥9.2)..." +echo "🏥 [4/4] CodeScene code health gate (≥9.2)..." if [ -z "$CODESCENE_PAT" ] || [ -z "$CODESCENE_PROJECT_ID" ]; then echo " ⚠️ CODESCENE_PAT or CODESCENE_PROJECT_ID not set — skipping" else @@ -65,7 +112,12 @@ print(f' ✅ Code Health {score:.2f} ≥ {threshold}') " fi +END_TIME=$(date +%s) +ELAPSED=$((END_TIME - START_TIME)) +MINUTES=$((ELAPSED / 60)) +SECONDS=$((ELAPSED % 60)) + echo "" echo "================================================" -echo "✅ All checks passed — pushing" +echo "✅ All checks passed — pushing (${MINUTES}m ${SECONDS}s)" echo "" diff --git a/CLAUDE.md b/CLAUDE.md index 2d0bb825..459e95e9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,7 +9,7 @@ pnpm lint && npx tsc --noEmit # lint + types pnpm test # unit tests pnpm test:coverage # frontend ≥70% coverage cargo test # Rust tests -cargo llvm-cov --manifest-path src-tauri/Cargo.toml --fail-under-lines 85 +cargo llvm-cov --manifest-path src-tauri/Cargo.toml --no-clean --fail-under-lines 85 pre_commit_code_health_safeguard # CodeScene ≥9.2 — if it fails, fix structurally (see below) ```