This document defines the local verification workflow for contributors and maintainers. It complements hosted GitHub Actions and gives pull requests a reproducible validation record.
Why This Exists
Pull requests need one documented local workflow so review does not depend on ad hoc command selection. Hosted CI remains authoritative for merge readiness, but local validation catches most issues before review.
Commands
Before running full, release, or make coverage, install the development
and LSP extras in the active virtual environment:
python3 -m pip install -e ".[dev,lsp]"
These commands enforce 80% coverage of production code, including the LSP
server. Without the LSP extra, its tests skip and the coverage gate can fail.
The local CI wrapper uses the active environment and does not install extras.
Use python instead of python3 on Windows. For a release environment matching
publishing, follow the hash-locked setup in the release runbook.
The entrypoint is:
python3 scripts/local_ci.py <mode> [options]
Supported modes:
targeted: scoped validation for touched files and explicit test targetsfull: repo-wide validationoptional: opt-in fuzz and property checks that mirror hosted optional commandsrelease:fullplus release-gate scripts for packaging, docs/spec, target metadata, self-host parity, benchmark, and templates
Examples:
# Typical subsystem PR
python3 scripts/local_ci.py targeted \
--paths geno/lsp_server.py geno/project_resolution.py geno/tests/test_lsp.py \
--tests geno/tests/test_lsp.py geno/tests/test_api.py
# Broad refactor or merge-readiness sweep
python3 scripts/local_ci.py full
# Optional fuzz/property confidence sweep
python3 scripts/local_ci.py optional
# Release-sensitive changes
python3 scripts/local_ci.py release
make local-ci runs full, and make local-ci-release runs release.
Local CI stops after the first hard failure so a cheap lint or type error does
not leave an expensive test suite running. Pass --keep-going before or after
the mode when a complete diagnostic sweep is more useful than fast feedback.
Expectations
Minimum expectations:
- Most PRs must run
targetedagainst the touched subsystem and relevant tests. - Broad cross-cutting work should run
full. - Parser, typechecker, compiler, or runtime changes should consider
optionalwhen fuzz/property coverage is relevant. - Release, scaffolding, benchmark, or packaging changes should run
release.
targeted covers the scoped Python quality gates that are practical for changed
files:
ruff checkruff format --check- file-scoped
mypywith--follow-imports=skip compileallruffsecurity lint forgeno/paths- explicit
pytesttargets
full covers the repo-wide local gates:
- repo-wide
ruff check - repo-wide
ruff format --check - repo-wide
ruffsecurity lint AnyTyperecovery and CI/DX debt ratchets- repo-wide
pytestwith coverage and timeout settings - top-level example type checks
release adds release-sensitive guardrails in make release-check order, with
the full gate's example type checks kept after pytest:
- version alignment and dependency/install validation
- init template, VS Code package, and example app gates
- builtin parity, language spec, supported target, self-host parity, and benchmark validators
optional covers the hosted optional/fuzz command shapes without adding them to
every local full run. It is command-level parity; local developer environments
may already include dependencies that the hosted collection job intentionally
omits.
- collect backend parity, fuzzing, property, and differential fuzzing tests
- run property, fuzzing, and differential fuzzing tests with short tracebacks and the hosted timeout setting
Deep fuzzing remains a manual or scheduled check via GENO_FUZZ_DEEP=1.
The production mypy profile in pyproject.toml intentionally excludes
geno/tests/. The staged test-typing profile lives in mypy-tests.ini so test
typing can tighten independently without weakening the production gate.
CI/DX Ratchets
scripts/check_ci_dx_ratchets.py keeps broad CI hardening work executable
without making every PR pay for a new heavy job. The ratchet currently tracks:
- global Ruff ignores and per-file Ruff ignores
- non-test
# type: ignorecomments - non-test functions missing parameter or return annotations
- non-test bare or
Exceptionhandlers - presence of Windows, optional-runtime, LSP, VS Code, and sandbox-regression coverage in the hosted/local CI surfaces
The budgets are ceilings. Reducing any count should lower the matching budget in the same change. Increasing a count requires a deliberate budget update and a reason in review.
Function-length and complexity ratchets are intentionally deferred until the
first hot-path refactor lands behind tests. When that happens, add the measured
baseline to scripts/check_ci_dx_ratchets.py instead of introducing another
untracked checklist item.
PR Traceability
Every PR should include a Local Validation section with:
- the exact commands that were run
- the outcome of those commands
- any intentionally skipped checks
- any expected environmental skips or local constraints that affected validation
The pull request template includes this section so the workflow stays visible in review.
Rendered from docs/operations/local-ci.md in the Geno repository.