diff --git a/.github/CI.md b/.github/CI.md new file mode 100644 index 0000000000..a5b6401b7e --- /dev/null +++ b/.github/CI.md @@ -0,0 +1,88 @@ +# CI — Win-CodexBar + +Win-CodexBar runs one hosted workflow: the **PR check** on Blacksmith Windows. +Release stays **local**. See `CONTEXT.md` for the shared CI budget glossary +and `docs/adr/0001` and `docs/adr/0002` for the decisions. + +## Workflows + +### PR check — `.github/workflows/pr-check.yml` + +Runs on `pull_request`, on `push` to `main`/`master`, and on +`workflow_dispatch`. Runner: `blacksmith-4vcpu-windows-2025` +(Windows Server 2025; VS Build Tools available per Blacksmith docs). + +Exact commands run, in order: + +```powershell +cargo fmt --all --check +cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings +cargo clippy --manifest-path apps/desktop-tauri/src-tauri/Cargo.toml --all-targets -- -D warnings +cargo test --manifest-path rust/Cargo.toml +cargo test --manifest-path apps/desktop-tauri/src-tauri/Cargo.toml +pnpm --dir apps/desktop-tauri test +pnpm --dir apps/desktop-tauri run build +``` + +This is the **local-check slice** from `scripts/local-check.ps1` only. It does +**not** run `tauri:build` release, installer packaging, smoke install, or +upload. Those stay on the local release path. + +`concurrency.cancel-in-progress` is on, keyed by ref, so superseded pushes +cancel the in-flight run. + +### Interaction guard — `.github/workflows/interaction-guard.yml` + +Unchanged except for the budget gate (below). Runner stays +`blacksmith-2vcpu-ubuntu-2404`. Permissions are unchanged +(`contents: read`, `issues: write`, `pull-requests: write`). + +## CI budget mode + +Both workflows carry the gate `if: vars.CI_BUDGET_MODE != 'off'`, so they run +when the variable is unset (`normal`), `normal`, or `thin`, and **skip only +when `off`**. + +Set `CI_BUDGET_MODE` in **Settings → Secrets and variables → Actions → +Variables**. Do not hard-code it in a workflow. + +| Mode | PR check | Interaction guard | Release | +|--------|----------|--------------------|---------| +| normal | runs | runs | local | +| thin | runs | runs | local | +| off | skip | skip | local | + +## Intent share (60/30/10) + +The Blacksmith Pool minutes intent is divided: roughly **60% Win-CodexBar**, +**30% linear-cli**, **10% buffer**. This is an intent allocation of the +pool's minutes, not a measure of time spent in `normal`/`thin`/`off` modes. +Win-CodexBar's single Blacksmith Windows PR check is the only recurring +Windows job. If you add a second recurring job, reassess the 60% share before +merging. + +## Blacksmith billing — Windows bills 2x + +On the Blacksmith **free tier**, **Windows minutes bill at 2x**: one Windows +minute consumes two free-tier minutes. `blacksmith-4vcpu-windows-2025` is +Windows Server 2025. Because the PR check is a thin slice (fmt + clippy + +test) and never runs release builds, this is the only recurring Windows +cost. Keep it that way. + +## $0 spend alert + +Treat any non-zero monthly Blacksmith bill as an alert. If spend appears: + +1. Set `CI_BUDGET_MODE=off` to stop both workflows immediately. +2. Investigate which run(s) consumed minutes (Actions tab → billed runs). +3. Trim or fix, then restore `CI_BUDGET_MODE=normal` (or `thin`). + +The default posture is **$0 spend**. The PR check should stay within free-tier +minutes; if it does not, prefer reducing the check slice before paying. + +## Release + +Release is **local only**. There is no hosted release workflow. Follow +`docs/BUILDING.md` and `docs/release/ci-cd.md`: tag, run release-doctor, run +`windows-release-build.ps1 -SmokeInstall`, then upload assets to GitHub +Releases manually. diff --git a/.github/workflows/interaction-guard.yml b/.github/workflows/interaction-guard.yml index eeafc08ecb..d44b264f9c 100644 --- a/.github/workflows/interaction-guard.yml +++ b/.github/workflows/interaction-guard.yml @@ -13,6 +13,7 @@ permissions: jobs: guard: + if: vars.CI_BUDGET_MODE != 'off' runs-on: blacksmith-2vcpu-ubuntu-2404 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml new file mode 100644 index 0000000000..e4ed5ec3e7 --- /dev/null +++ b/.github/workflows/pr-check.yml @@ -0,0 +1,82 @@ +name: PR check + +on: + push: + branches: [main, master] + paths-ignore: + - 'docs/**' + - '**/*.md' + - 'CONTEXT.md' + - '.github/CI.md' + pull_request: + paths-ignore: + - 'docs/**' + - '**/*.md' + - 'CONTEXT.md' + - '.github/CI.md' + workflow_dispatch: + +concurrency: + group: pr-check-${{ github.ref }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + +jobs: + pr-check: + name: Local check + if: vars.CI_BUDGET_MODE != 'off' + runs-on: blacksmith-4vcpu-windows-2025 + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-pc-windows-msvc + components: rustfmt, clippy + + - name: Cache cargo + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + rust + apps/desktop-tauri/src-tauri + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + cache-dependency-path: apps/desktop-tauri/pnpm-lock.yaml + + - name: Install frontend deps + run: pnpm --dir apps/desktop-tauri install --frozen-lockfile + + - name: Rust format check + run: cargo fmt --all --check + + - name: Shared Rust clippy + run: cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings + + - name: Tauri Rust clippy + run: cargo clippy --manifest-path apps/desktop-tauri/src-tauri/Cargo.toml --all-targets -- -D warnings + + - name: Shared Rust tests + run: cargo test --manifest-path rust/Cargo.toml + + - name: Tauri Rust tests + run: cargo test --manifest-path apps/desktop-tauri/src-tauri/Cargo.toml + + - name: Frontend tests + run: pnpm --dir apps/desktop-tauri test + + - name: Frontend type check / build + run: pnpm --dir apps/desktop-tauri run build diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000000..ad0f0e7881 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,69 @@ +# Shared CI Budget glossary + +This glossary is shared with the sister repo (`linear-cli`) so operators can +reason about CI spend across both projects with one vocabulary. + +## Blacksmith Pool + +The shared Blacksmith free-tier minute pool that both Win-CodexBar and the +sister repo (`linear-cli`) draw from. All `blacksmith-*` runners bill against +this one pool. The intent-share (60%) and $0 spend alert are defined against +the combined draw on this pool, not per repo. + +## CI budget mode + +A repository variable `CI_BUDGET_MODE` controls how much CI runs per change. +It is intentionally coarse: `normal`, `thin`, or `off`. + +| Mode | PR check | Interaction guard | Release | +|--------|----------|--------------------|----------------------------------| +| normal | runs | runs | local (Win-CodexBar only) | +| thin | runs | runs | local (Win-CodexBar only) | +| off | **skip** | **skip** | local (Win-CodexBar only) | + +> Note: Win-CodexBar's Release is **always local** (no release workflow on +> Blacksmith). The sister repo `linear-cli` has a Blacksmith dispatch-only +> release workflow, so its Release column differs by design. Budget mode does +> not gate Win-CodexBar's local release — it is operator-driven regardless of +> mode. + +- `normal` — default. Unset is treated as `normal`. +- `thin` — Win-CodexBar's PR check survives `thin`: it is the single Windows + Blacksmith job and stays within the 60% intent share, so it still runs. The + sister repo (`linear-cli`) skips its PR check entirely under `thin` + (`if: mode != 'off' && mode != 'thin'`); its default PR check is already a + single Linux-only job, so there is no matrix to trim — `thin` simply drops + the whole job. +- `off` — emergency stop. Both the PR check and the interaction guard skip. + Use this when the bill approaches the `$0 spend` alert threshold or when a + runaway workflow is burning minutes. + +Set it in **Settings → Secrets and variables → Actions → Variables**, not in +code. The workflows read it as `vars.CI_BUDGET_MODE`. + +## Intent share (60/30/10) + +The Blacksmith Pool minutes intent is divided: roughly **60% Win-CodexBar**, +**30% linear-cli**, and **10% buffer**. This is an intent allocation of the +pool's minutes, not a measure of time spent in `normal`/`thin`/`off` modes. +Win-CodexBar's single Blacksmith Windows PR check is the only recurring +Windows job; release builds stay local. If you add a second recurring job +here, reassess the 60% share before merging. + +## Blacksmith billing note + +On the Blacksmith free tier, **Windows minutes bill at 2x** (one Windows +minute consumes two free-tier minutes). `blacksmith-4vcpu-windows-2025` is a +Windows Server 2025 runner with VS Build Tools available, so Rust + Tauri +builds work without extra setup. The PR check is a thin slice (fmt + clippy + +test) and never runs `tauri:build` release, installer packaging, smoke +install, or upload. + +## Local check slice + +The PR check mirrors `scripts/local-check.ps1`'s default slice only: +`cargo fmt --check`, `cargo clippy -D warnings` on both crates, `cargo test` +on both crates, and the frontend `pnpm test` / `tsc --noEmit` (via +`pnpm run build`). It deliberately excludes `tauri:build` release, the +installer, smoke install, and release upload — those stay on the local +Windows release path. diff --git a/docs/BUILDING.md b/docs/BUILDING.md index df89da4aa3..98d918c058 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -71,9 +71,10 @@ Useful release flags: .\scripts\release-doctor.ps1 -Version 0.27.5 ``` -There is no hosted CI/CD for this repository right now. Run local checks before -PRs; the Windows release script is the primary path for installer and portable -artifacts. +A hosted **PR check** runs on Blacksmith Windows for pull requests and pushes +to `main`/`master`; see `.github/CI.md` for its scope and budget modes. The +Windows release script remains the primary path for installer and portable +artifacts; there is no hosted release workflow. ## macOS Windows Cross Build diff --git a/docs/adr/0001-pr-check-blacksmith-local-release.md b/docs/adr/0001-pr-check-blacksmith-local-release.md new file mode 100644 index 0000000000..875cb7f9ae --- /dev/null +++ b/docs/adr/0001-pr-check-blacksmith-local-release.md @@ -0,0 +1,36 @@ +# ADR 0001: PR check on Blacksmith Windows, release stays local + +Date: 2026-07-24 +Status: Accepted + +## Context + +Win-CodexBar had no hosted CI. Contributors ran `scripts/local-check.ps1` +before PRs, and the canonical release path was `scripts/windows-release-build.ps1` +on a local Windows server. We want hosted feedback on PRs without paying for +release-grade CI. + +## Decision + +Add a single PR check workflow (`.github/workflows/pr-check.yml`) that runs on +**Blacksmith Windows** (`blacksmith-4vcpu-windows-2025`). It mirrors only the +local-check slice: + +- `cargo fmt --all --check` (workspace) +- `cargo clippy --all-targets -- -D warnings` on both `rust` and + `apps/desktop-tauri/src-tauri` +- `cargo test` on both crates +- `pnpm test` and `pnpm run build` (which runs `tsc --noEmit`) in + `apps/desktop-tauri` + +Release stays **LOCAL**: no release workflow on Blacksmith. `tauri:build` +release, installer packaging, smoke install, and upload to GitHub Releases +continue to use `scripts/windows-release-build.ps1` on the operator's Windows +server, exactly as documented in `docs/BUILDING.md`. + +## Consequences + +- PRs get hosted fmt/clippy/test feedback on the real Windows target. +- No release CI spend. Release remains a deliberate, operator-driven step. +- The Blacksmith Windows runner has VS Build Tools available, so the Rust + + Tauri toolchain works without extra provisioning. diff --git a/docs/adr/0002-ci-budget-modes.md b/docs/adr/0002-ci-budget-modes.md new file mode 100644 index 0000000000..e1d7b0ae55 --- /dev/null +++ b/docs/adr/0002-ci-budget-modes.md @@ -0,0 +1,33 @@ +# ADR 0002: CI budget modes + +Date: 2026-07-24 +Status: Accepted + +## Context + +Win-CodexBar and its sister repo (`linear-cli`) share Blacksmith CI minute +budget. We need a single, coarse control to keep spend within the 60% +intent share and to stop runaway spend fast. Per-workflow toggles are too +fine-grained for an emergency stop. + +## Decision + +Introduce a repository variable `CI_BUDGET_MODE` with three modes: +`normal`, `thin`, and `off`. Unset is treated as `normal`. + +- **PR check** (`pr-check.yml`): runs when mode is unset, `normal`, or `thin`; + skips only when `off`. + Gate: `if: vars.CI_BUDGET_MODE != 'off'` +- **Interaction guard** (`interaction-guard.yml`): same gate. Keeps its + existing `blacksmith-2vcpu-ubuntu-2404` runner. +- **Release**: unaffected — release stays local regardless of mode. + +`thin` is a no-op for Win-CodexBar's PR check because it is already a single +job (no cross-OS matrix to trim). The mode exists for parity with the sister +repo and as a future dial if a second job is added. + +## Consequences + +- One variable controls both workflows; `off` is an immediate, full stop. +- Operators set `CI_BUDGET_MODE` in repo Settings → Variables, not in code. +- The glossary in `CONTEXT.md` is the shared definition across both repos.