Local CI and PR gates
Run reproducible pull-request checks before pushing and inspect the remaining GitHub-only boundary.
quality can run a repository’s version-controlled Git hook as a timed local CI
gate. This catches deterministic failures before a push consumes a hosted
runner, while GitHub Actions remains authoritative for the exact pushed commit.
Inspect coverage
quality ci plan
quality ci plan --strict
quality ci plan --hook pre-commit --format json
The default plan compares pull-request workflows with hooks.pre-push in
quality.yml. Each workflow step is classified as:
- covered when its normalized
run:command and working directory exactly match a local hook step; - GitHub-only when it uses an action, expression, or conditional that needs GitHub context;
- uncovered when it is a plain command that could run locally but is absent from the selected hook.
The comparison is deliberately exact. It does not claim that a broad wrapper script covers a different workflow command, because doing so would turn an inference into false parity. Declare the relationship explicitly when a broader local wrapper genuinely includes the CI command:
hooks:
pre-push:
steps:
- name: Run full local validation
command: pnpm
args: [run, pre-push]
covers:
- pnpm run ci
covers affects the plan only; execution still runs the declared hook command.
Use --strict in adoption checks to reject uncovered commands.
Workflow- and job-level defaults.run.working-directory values are resolved
before comparison. A declared env or custom shell is not silently treated
as equivalent to a local hook: static context is uncovered, while expressions
that require GitHub context remain GitHub-only.
Jobs pinned to a different runner operating system are also GitHub-only; a
matching command on macOS does not claim coverage for Windows or Linux.
Run the gate
quality ci local
quality ci local --hook pre-commit
quality ci local --step 2
Steps run sequentially and stop after the first failure. The terminal report
shows each step’s wall time, the total wall time, the exit code and a concise
tail of bounded failure output, followed by a focused rerun command. --step
accepts a one-based number from the plan or prior report. When a step forwards
Git hook arguments, the rerun uses a <git-hook-args> placeholder instead of
persisting values such as remote URLs; replace it with the original hook
arguments.
Managed Git hooks use this same execution and reporting path. A typical policy
keeps pre-commit limited to staged-file checks and uses pre-push for affected
tests and builds:
hooks:
pre-commit:
steps:
- name: Check staged files
command: pnpm
args: [exec, lint-staged]
pre-push:
steps:
- name: Run affected validation
command: pnpm
args: [run, pre-push]
When a root package already declares pre-commit, precommit, pre-push, or
prepush, quality init and language-aware presets import that script as the
corresponding hook. Existing hook configuration is preserved.
Reports and privacy
The 20 most recent runs store metadata without command output under
.git/quality/local-ci/. The history stays outside the worktree and therefore
cannot be committed accidentally. A history write failure emits a warning but
does not change the gate result. Disable history with --no-history.
Write a complete versioned report explicitly when another local tool needs the captured output:
quality ci local --format json --report reports/local-ci.json
Combined output retention defaults to 1 MiB per step and can be changed with
--max-output-bytes. Explicit reports may contain anything printed by a check;
do not commit or share them when a command may expose sensitive information.
The contracts are published as the plan schema
and run-report schema.
What remains in GitHub
Local CI does not emulate hosted runner images or execute arbitrary uses:
actions. Secrets, GitHub permissions, service containers, CodeQL and dependency
review services, deployment, release publication, and platform-specific hosted
checks remain GitHub-only. Keep one protected GitHub check for the exact pushed
commit even after the local gate passes.