Skip to content

Quality hardening: real bug fixes + SOLID/DRY refactors + honest coverage + tooling spine #233

Quality hardening: real bug fixes + SOLID/DRY refactors + honest coverage + tooling spine

Quality hardening: real bug fixes + SOLID/DRY refactors + honest coverage + tooling spine #233

Workflow file for this run

# PR + main verification: typecheck (core + pro), lint, and the full vitest suite
# (which includes the pro/ tests when the pro repo is checked out alongside).
#
# Cross-repo note: pro/ is a separate private repo (paid features). Set the repo
# secret CI_CROSS_REPO_TOKEN (a PAT with read access to off-grid-ai/desktop-pro)
# so the pro tests + pro typecheck run here too. Without it, core is still fully
# verified and the pro steps are skipped gracefully.
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
verify:
runs-on: ubuntu-latest
timeout-minutes: 25 # backstop: a hung step fails fast instead of running for hours
steps:
- uses: actions/checkout@v4
# Check out pro on the branch that MATCHES this PR/push (so a coordinated
# core+pro change is tested together). If pro has no such branch (most PRs),
# this step's checkout fails quietly and the fallback below pulls pro `main`.
# Without this, CI silently tested the core branch against pro `main`, so
# pro's new test files were absent while pro source was still measured →
# coverage cratered (60% vs 97% local). See PR #46.
- name: Check out pro (matching branch) into ./pro
id: pro_branch
uses: actions/checkout@v4
continue-on-error: true
with:
repository: off-grid-ai/desktop-pro
token: ${{ secrets.CI_CROSS_REPO_TOKEN }}
path: pro
ref: ${{ github.head_ref || github.ref_name }}
# Don't leave the cross-repo PAT in ./pro/.git/config where a later
# PR-controlled step could reuse it — we only need it for the clone.
persist-credentials: false
- name: Fall back to pro main if no matching branch
if: ${{ steps.pro_branch.outcome != 'success' || hashFiles('pro/tsconfig.json') == '' }}
continue-on-error: true
uses: actions/checkout@v4
with:
repository: off-grid-ai/desktop-pro
token: ${{ secrets.CI_CROSS_REPO_TOKEN }}
path: pro
persist-credentials: false
# pro MUST be present: without it the guarded typecheck/coverage path is skipped and
# CI would pass core-only with cratered/misleading coverage (the failure the
# matching-branch checkout above was added to fix). Fail loudly instead of silently.
- name: Require pro checkout to have succeeded
run: |
if [ ! -f pro/tsconfig.json ]; then
echo "::error::pro (desktop-pro) was not checked out — cannot validate the open-core build. Check CI_CROSS_REPO_TOKEN / repo access."
exit 1
fi
- uses: actions/setup-node@v4
with:
node-version: '24' # node:sqlite (used by integration tests) is available unflagged
- run: npm ci
# Hard gates: types + the full test suite.
- name: Typecheck (core)
timeout-minutes: 6
run: npm run typecheck
- name: Typecheck (pro)
if: ${{ hashFiles('pro/tsconfig.json') != '' }}
timeout-minutes: 6
run: cd pro && npx tsc --noEmit -p tsconfig.json
# Runs the full vitest suite AND enforces the coverage ratchet floor
# (vitest.config.ts `thresholds`) — the same gate as the pre-push hook, so a
# coverage regression fails CI, not just local pushes. The pro/** threshold
# group applies only when pro was checked out above (guarded in the config).
- name: Test + coverage thresholds
timeout-minutes: 10
run: npm run test:coverage
# Architecture-boundary gate (hygiene §A/§H): walks the import graph and fails
# on a forbidden edge — core->pro (open-core), a pure-logic module gaining an
# IO import, renderer->main, circular deps. Fast (graph-only, ~2s), so unlike
# eslint it can BLOCK without wedging CI. Rules verified clean on the tree.
- name: Dependency boundaries
timeout-minutes: 4
run: npm run depcruise
# NOTE: SonarCloud runs via Automatic Analysis (SonarCloud clones this repo and
# analyzes core on each push/PR — no CI step, token, or coverage upload here).
# Advisory: lint is enforced locally; bounded + non-blocking here so an
# eslint slowdown over the full tree (incl. checked-out pro/) can't wedge CI.
- name: Lint
timeout-minutes: 8
continue-on-error: true
run: npm run lint