Ashiedu's picture
Sync unified workbench
0490201 verified

A newer version of the Gradio SDK is available: 6.14.0

Upgrade

T-008: GitHub Actions CI β€” Windows Runner, Check/Lint/Test, Python Enforcement

Type: Task
Phase: 0 β€” Foundation
Autonomy: agent:review-required β€” CI config has security implications (permissions, secrets scope). Human reviews before merge.
Stack: stack:infra
Version: v0.1
Iteration: iter-1
Effort: S (half-day)


⚠️ Agent Scope: Replace the T-001 CI stub at .github/workflows/ci.yml with a full workflow. Add missing package.json scripts if absent. Do not add a full cargo tauri build step β€” that is T-015's scope. This workflow is check/lint/test only: fast feedback on every PR.


Context

Every PR to main must be gated by: compile check, clippy lint, unit tests, TypeScript typecheck, format check, and the Python-in-src enforcement rule. Without CI these checks are optional and will be skipped. With CI they are required status checks β€” the PR cannot merge if any fails.

Why Windows runner: The project is Windows-first. WASAPI, DirectML, and the VST3/CLAP bundler all have Windows-specific behaviour. Running CI on ubuntu-latest would miss platform-specific compile errors that only appear with the MSVC toolchain.

Scope boundary: This workflow runs checks only. The full cargo tauri build (which requires WebView2 and takes ~10 minutes) is gated behind T-015 which adds a separate build job. Keeping check/lint/test fast means PR feedback in under 5 minutes.


Prerequisites

  • T-001 merged β€” .github/workflows/ci.yml stub exists, package.json exists
  • T-007 merged β€” Taskfile.yml with task ci:python-check exists

Acceptance Criteria

Workflow

  • Workflow triggers on push to main and on all pull requests targeting main
  • All steps run on windows-latest
  • Rust nightly toolchain is installed with clippy and rustfmt components
  • Rust build cache (~/.cargo/registry, ~/.cargo/git, target/) is cached keyed on Cargo.lock
  • Node.js 20 LTS and pnpm 9 are installed and frontend deps cached
  • go-task is installed so task ci:python-check can run

Jobs β€” check (fast feedback, ~3–5 min)

  • cargo check --workspace passes
  • cargo clippy --workspace -- -D warnings passes
  • cargo fmt --all --check passes (fails if unformatted Rust code is committed)
  • pnpm typecheck passes
  • pnpm format:check passes
  • task ci:python-check passes (zero Python subprocess calls in src/ or kansas/)

Jobs β€” test (depends on check)

  • cargo test --workspace passes
  • pnpm test passes (or skips gracefully if no test suite exists yet)

package.json scripts

  • typecheck script exists: tsc --noEmit
  • format:check script exists: prettier --check "src/**/*.{ts,css,html}"
  • format script exists: prettier --write "src/**/*.{ts,css,html}"
  • lint script exists: eslint src/ --ext .ts
  • test script exists: vitest run (or echo 'No tests yet' && exit 0 stub if vitest not configured)

Security

  • Workflow has permissions: read-all at the top level (deny-by-default principle)
  • No GITHUB_TOKEN used beyond the default checkout scope
  • No secrets accessed in this workflow (secrets are for Day 10 release workflow)

package.json Scripts to Add

If these are missing from the package.json created by T-001, add them:

{
  "scripts": {
    "dev":          "vite",
    "build":        "tsc && vite build",
    "preview":      "vite preview",
    "typecheck":    "tsc --noEmit",
    "lint":         "eslint src/ --ext .ts --max-warnings 0",
    "format":       "prettier --write \"src/**/*.{ts,css,html}\"",
    "format:check": "prettier --check \"src/**/*.{ts,css,html}\"",
    "test":         "echo '[test] No frontend tests yet β€” see T-130' && exit 0"
  }
}

Add prettier and eslint as dev deps if not already present:

pnpm add -D prettier eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

Add .prettierrc at repo root:

{
  "semi": true,
  "singleQuote": true,
  "trailingComma": "es5",
  "printWidth": 100,
  "tabWidth": 2
}

Add .eslintrc.json at repo root:

{
  "root": true,
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint"],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "rules": {
    "@typescript-eslint/no-explicit-any": "error",
    "@typescript-eslint/no-unused-vars": "error"
  },
  "ignorePatterns": ["dist/", "node_modules/"]
}

.github/workflows/ci.yml

name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

# Deny all permissions by default; grant only what each job needs
permissions: read-all

env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: 1

jobs:
  # ────────────────────────────────────────────────────────────────────────────
  # Job 1: Check, lint, typecheck, format, Python enforcement
  # Target time: < 5 minutes
  # ────────────────────────────────────────────────────────────────────────────
  check:
    name: Check & Lint
    runs-on: windows-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      # ── Rust ────────────────────────────────────────────────────────────────
      - name: Install Rust nightly
        uses: dtolnay/rust-toolchain@nightly
        with:
          components: clippy, rustfmt

      - name: Cache Rust build artifacts
        uses: Swatinem/rust-cache@v2
        with:
          # Key on Cargo.lock so wildcard deps get fresh cache on cargo update
          key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
          cache-on-failure: true

      # ── Node / pnpm ─────────────────────────────────────────────────────────
      - name: Install Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9
          run_install: false

      - name: Get pnpm store path
        id: pnpm-cache
        shell: bash
        run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT

      - name: Cache pnpm store
        uses: actions/cache@v4
        with:
          path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
          restore-keys: ${{ runner.os }}-pnpm-

      - name: Install frontend dependencies
        run: pnpm install --frozen-lockfile

      # ── go-task ─────────────────────────────────────────────────────────────
      - name: Install go-task
        uses: arduino/setup-task@v2
        with:
          version: 3.x
          repo-token: ${{ secrets.GITHUB_TOKEN }}

      # ── Rust checks ─────────────────────────────────────────────────────────
      - name: cargo check
        run: cargo check --workspace

      - name: cargo clippy
        run: cargo clippy --workspace -- -D warnings

      - name: cargo fmt check
        run: cargo fmt --all --check

      # ── TypeScript checks ───────────────────────────────────────────────────
      - name: TypeScript typecheck
        run: pnpm typecheck

      - name: Prettier format check
        run: pnpm format:check

      - name: ESLint
        run: pnpm lint

      # ── Policy enforcement ──────────────────────────────────────────────────
      - name: Python-in-src check
        run: task ci:python-check
        shell: bash

  # ────────────────────────────────────────────────────────────────────────────
  # Job 2: Unit tests (runs after check passes)
  # ────────────────────────────────────────────────────────────────────────────
  test:
    name: Test
    runs-on: windows-latest
    needs: check

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install Rust nightly
        uses: dtolnay/rust-toolchain@nightly

      - name: Cache Rust build artifacts
        uses: Swatinem/rust-cache@v2
        with:
          key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
          cache-on-failure: true

      - name: Install Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9
          run_install: false

      - name: Install frontend dependencies
        run: pnpm install --frozen-lockfile

      - name: cargo test
        run: cargo test --workspace

      - name: Frontend test
        run: pnpm test

  # ────────────────────────────────────────────────────────────────────────────
  # Job 3: Plugin validation stub (T-143 wires in pluginval)
  # ────────────────────────────────────────────────────────────────────────────
  pluginval:
    name: Plugin Validation (stub)
    runs-on: windows-latest
    needs: check
    # T-143 replaces this stub with real pluginval execution
    # and removes the `if: false` guard
    if: false

    steps:
      - name: pluginval stub
        run: echo "pluginval CI β€” see T-143"

Implementation Notes

Why dtolnay/rust-toolchain@nightly not actions-rs/toolchain

actions-rs/toolchain is unmaintained. dtolnay/rust-toolchain is the community-maintained replacement β€” it handles nightly, components, and targets correctly on all platforms.

Why Swatinem/rust-cache@v2

It caches ~/.cargo/registry, ~/.cargo/git, and target/ in a single step. On Windows runners the target/ directory can be 2–4 GB β€” without caching the Rust build step takes 8–12 minutes on every run. With a warm cache it drops to 1–3 minutes.

pnpm install --frozen-lockfile

This fails the CI job if pnpm-lock.yaml is out of sync with package.json. It prevents "works on my machine" dependency drift. Developers must commit an updated lockfile when adding deps.

Python check via task ci:python-check

Calls the Taskfile target defined in T-007. The target uses grep -rn and exits non-zero if any match is found. The shell: bash directive is required on Windows runners to use bash syntax in the grep command.

The pluginval job with if: false

This is a placeholder job that never runs (if: false prevents execution). T-143 replaces it with real pluginval steps and removes the guard. Having it here means:

  • T-143 agents have a named job to fill in β€” no new job creation needed
  • The job name appears in the GitHub Actions UI under "skipped" β€” visible reminder

Branch protection (manual step β€” not in YAML)

After merging T-008, go to GitHub β†’ Settings β†’ Branches β†’ Add rule for main:

  • βœ… Require status checks to pass before merging
  • Required checks: Check & Lint, Test
  • βœ… Require branches to be up to date before merging

This is a repo settings action, not something the agent can do via YAML.


Testing

Local simulation

# These are the same commands the CI runs β€” all should pass locally first
cargo check --workspace
cargo clippy --workspace -- -D warnings
cargo fmt --all --check
pnpm typecheck
pnpm format:check
pnpm lint
task ci:python-check
cargo test --workspace
pnpm test

After pushing

  • Open a draft PR against main
  • Verify both Check & Lint and Test jobs appear and pass
  • Introduce a deliberate clippy warning (e.g. unused variable) β€” verify CI fails on that job
  • Revert the deliberate warning β€” verify CI passes again

GitHub CLI

gh issue create \
  --title "T-008: GitHub Actions CI β€” Windows runner, check/lint/test, Python enforcement" \
  --label "type:task,stack:infra,agent:review-required,priority:critical,status:ready,day:1" \
  --body-file T-008.md

Parent: GENESIS
Blocks: T-015 (adds build job to this workflow), T-074 (adds timing test step), T-136 (expands Python check to cover more patterns), T-143 (fills in pluginval job), T-147 (adds perf regression job)
Blocked By: T-001, T-007
Version: v0.1 Β· Iteration: iter-1 Β· Effort: S (half-day)