CI & Hooks¶
GitHub Actions¶
The CI workflow is intentionally thin: GitHub Actions installs tools and then delegates validation to mise task entrypoints.
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
check:
name: Quality Gate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install mise
uses: jdx/mise-action@v2
- name: Setup
run: mise run setup
- name: Check
run: mise run ci
sync-check:
name: Sync Contract
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install mise
uses: jdx/mise-action@v2
- name: Setup
run: mise run setup
- name: Sync check
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
mise run sync-check -- --changed-plans "origin/${{ github.base_ref }}...HEAD"
else
mise run sync-check
fi
mise-action installs mise and runs mise install automatically, pulling tool versions from .mise.toml.
Quality gate logic lives in mise run ci → mise run check. Handoff contract
logic lives in mise run sync-check. Local runs validate the active plan. Pull
request CI calls mise run sync-check -- --changed-plans origin/<base>...HEAD
so changed plans must be marked complete and their artifacts are validated. The
repository CI also runs generated-project smoke tests across the supported
stacks so Python, Go, Rust, and Web scaffolds prove they can initialize and pass
mise run check.
Pre-commit hooks¶
Pre-commit hooks mirror CI exactly — every hook calls a mise run task:
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: fmt
entry: mise run fmt
language: system
always_run: true
pass_filenames: false
- id: lint
entry: mise run lint
# ...
- id: typecheck
entry: mise run typecheck
# ...
- id: test
entry: mise run test
# ...
This guarantees CI parity for the fast quality gate: if pre-commit passes,
the check job should pass. sync-check remains the explicit handoff gate for
slice evidence and review completeness, with PR CI using changed-plan mode.
Installing hooks¶
mise run setup # installs hooks automatically if pre-commit is available
# or manually:
pre-commit install
Running hooks manually¶
Design rationale¶
Why keep CI YAML thin? GitHub Actions chooses the CI context, such as
whether a run is a pull request or a main-branch push. The validation logic
still lives in mise tasks: CI calls mise run ci, mise run sync-check, or
mise run sync-check -- --changed-plans ..., and pre-commit calls
mise run <task>.
Why always_run: true? The tasks (ruff, ty, pytest) are fast enough that running them unconditionally is cheaper than filtering by changed files. It also prevents edge cases where a change to a config file doesn't trigger re-checking source files.