name: CI on: push: branches: [main, experiment/*] pull_request: branches: [main] jobs: test: name: Tests & Quality Checks runs-on: macos-15 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # Full history for CodeScene - name: Setup pnpm uses: pnpm/action-setup@v4 with: version: 10 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '22' cache: 'pnpm' - name: Setup Rust uses: dtolnay/rust-toolchain@stable with: components: rustfmt, clippy, llvm-tools-preview - name: Cache Rust dependencies uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git src-tauri/target key: ${{ runner.os }}-cargo-${{ hashFiles('src-tauri/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo- - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - name: Install dependencies run: pnpm install --frozen-lockfile # ── 0. Build check (catches type errors and bundler failures) ───────── - name: TypeScript type check run: pnpm exec tsc --noEmit - name: Vite build check run: pnpm build # ── 1. Tests ────────────────────────────────────────────────────────── - name: Run frontend tests run: pnpm test - name: Bundle MCP server resources (required by Tauri build) run: node scripts/bundle-mcp-server.mjs - name: Run Rust tests run: cargo test --manifest-path=src-tauri/Cargo.toml # ── 2. Coverage (enforced — fails build if thresholds not met) ──────── - name: Frontend coverage (≥70% lines/functions/branches/statements) run: pnpm test:coverage # Thresholds configured in vite.config.ts — exits non-zero if coverage drops - name: Rust coverage (≥85% lines) run: | cargo llvm-cov \ --manifest-path src-tauri/Cargo.toml \ --ignore-filename-regex 'lib\.rs|main\.rs|menu\.rs' \ --fail-under-lines 85 # cargo-llvm-cov exits non-zero if line coverage drops below 85% # lib.rs/main.rs/menu.rs are Tauri boilerplate -- not meaningfully unit-testable. # ── 3. Code Health (CodeScene — Hotspot Code Health gate) ──────────── # The webhook integration handles per-PR delta analysis (posts review # comments on PRs). This step enforces a minimum floor on the # project-wide Hotspot Code Health score (weighted avg of the most # frequently edited files — the ones that matter most). # Current baseline: 9.33 | Aspirational target: 9.5 - name: Hotspot Code Health gate (≥9.2) env: CODESCENE_PAT: ${{ secrets.CODESCENE_PAT }} CODESCENE_PROJECT_ID: ${{ secrets.CODESCENE_PROJECT_ID }} run: | THRESHOLD=9.2 SCORE=$(curl -sf \ -H "Authorization: Bearer $CODESCENE_PAT" \ -H "Accept: application/json" \ "https://api.codescene.io/v2/projects/$CODESCENE_PROJECT_ID" \ | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['analysis']['hotspot_code_health']['now'])") echo "Hotspot Code Health: $SCORE (threshold: $THRESHOLD)" python3 -c " score = float('$SCORE') threshold = float('$THRESHOLD') if score < threshold: print(f'❌ Hotspot Code Health {score:.2f} is below threshold {threshold}') exit(1) print(f'✅ Hotspot Code Health {score:.2f} ≥ {threshold}') " # ── 4. Documentation check (warning only — does not fail build) ─────── - name: Check docs are updated continue-on-error: true run: | if git log -1 --pretty=%B | grep -i '\[skip docs\]' > /dev/null; then echo "⏭️ Documentation check skipped" exit 0 fi if git diff --name-only origin/main | grep -E '^(src/|src-tauri/)' > /dev/null; then if ! git diff --name-only origin/main | grep -E '^docs/' > /dev/null; then echo "⚠️ Code files changed but docs/ not updated" git diff --name-only origin/main | grep -E '^(src/|src-tauri/)' echo "If this change affects architecture/abstractions/theme documented in docs/, update them." echo "To suppress: include [skip docs] in your commit message." fi fi echo "✅ Documentation check passed" # ── 5. Lint & format ────────────────────────────────────────────────── - name: Lint frontend run: pnpm lint - name: Clippy (Rust) run: cargo clippy --manifest-path=src-tauri/Cargo.toml -- -D warnings - name: Format check (Rust) run: cargo fmt --manifest-path=src-tauri/Cargo.toml -- --check