Ashiedu's picture
Sync unified workbench
0490201 verified
# 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:
```json
{
"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:
```bash
pnpm add -D prettier eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
```
Add `.prettierrc` at repo root:
```json
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"tabWidth": 2
}
```
Add `.eslintrc.json` at repo root:
```json
{
"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`
```yaml
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
```bash
# 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
```bash
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)