Local CI concordance¶
A green laptop is not automatically a green GitHub Actions badge. This page defines tiers so agents and humans know what "passed" means, and points at the single runner that keeps command strings aligned with CI.
| Artifact | Role |
|---|---|
scripts/ci_local.sh |
Source of truth for local gate commands and extras lists |
Makefile targets ci-fast / ci-core / sync-ci-type / sync-ci-test / type-check-ci |
Thin wrappers |
.github/workflows/ci.yml |
Server-side jobs |
.github/actions/setup-dazzle |
Frozen uv sync + extras |
When you change a CI command or extras list, update scripts/ci_local.sh in the
same change (constants at the top of the script cite the CI jobs).
Tiers¶
Preflight — make preflight-surface (scripts/ci_local.sh preflight-surface)¶
Mandatory first step of every ship path (also invoked automatically by Tier 0 and Tier 1). Exists to kill the pattern where feature unit tests pass locally while GitHub stays red on unpaid structural/artifact debt.
| What it runs | CI failure class it blocks |
|---|---|
scripts/preflight_surface.py → curated gate modules |
See table below |
| Module | Debt class | Typical fix |
|---|---|---|
test_api_surface_drift |
MCP/IR/public API baselines | dazzle inspect api <lens> --write |
test_docs_drift |
AGENTS MCP table, cli.md groups, generated refs | edit docs + dazzle docs generate |
test_deferred_imports_ratchet_1438 |
function-level dazzle.* import growth |
hoist import or raise baseline with justification |
test_import_contracts |
layer edges (core ↛ page/api_kb) |
relocate code |
test_no_bare_except_pass |
silent except Exception |
log with exc_info=True or narrow type |
test_ux_catalogue |
stale catalogue CSS / render crash in previews | python scripts/gen_ux_catalogue.py |
test_complexity_ratchet |
cyclomatic/MI regressions | refactor or dazzle fitness code --write-baseline |
test_clone_ratchet (tree regression nodeid) |
new/grown clone clusters (URL helpers, etc.) | reuse existing fn or dazzle fitness clones --write-baseline |
test_hm_package_suite_gate |
HM gallery stale-dist | cd packages/hatchi-maxchi && python site/build_site.py |
Non-zero exit → do not ship. The script prints a remediation playbook.
Standalone use mid-change:
Tier 0.5 — make ship-surface (scripts/ci_local.sh ship-surface)¶
Recurrent badge-red classes that used to slip past Tier 0 and only fail
after a full ci.yml matrix. Script: scripts/ship_surface.py.
| Step | CI failure class |
|---|---|
bandit -r src/ --severity-level medium |
lint job B3xx (e.g. B324 hashlib) |
test_example_spec_bar |
example DSL without SPECIFICATION.md regen |
test_spec_narrative_brief_snapshot |
simple_task brief golden |
pattern_count meta tests |
patterns.toml [meta].pattern_count |
test_no_new_ir_field_orphans |
IR reader baseline |
| golden IR snapshot | test_simple_dsl_to_ir_snapshot |
| viewport DRAWER freshness | shell toggle selector rot (browser-free) |
gen_surface_check |
catalogue md/css + CONTRACT_SURFACE.md vs generators |
Also runs automatically as the second step of Tier 0 (after preflight).
Standalone for mid-edit: make ship-surface.
After a cimonitor repair, new recurrent classes must be promoted into this pack or preflight-surface — fix-only is incomplete.
Gen-surface — make gen-surface-check (scripts/gen_surface_check.py)¶
Fast check that committed UX catalogue + HM CONTRACT_SURFACE.md match
what the generators would emit (no write). Also runs inside ship-surface.
| Stale artifact | Regen |
|---|---|
docs/reference/ux-catalogue.md, docs/assets/dazzle-catalogue.css |
python scripts/gen_ux_catalogue.py |
packages/hatchi-maxchi/CONTRACT_SURFACE.md |
python packages/hatchi-maxchi/tools/contract_surface.py --write |
Rule: regenerate, commit the outputs, then re-run. Do not push with dirty gen files after HM / render / component work.
Path-aware — make ci-changed (scripts/ci_local.sh changed)¶
scripts/ci_changed.py selects packs from the git diff (origin/main...HEAD
plus worktree dirt):
| Path touch | Pack |
|---|---|
examples/**/dsl/** |
example SPEC bar |
src/dazzle/mcp/semantics_kb/** |
pattern_count |
src/dazzle/core/**, IR snapshots |
golden IR + IR orphans |
| shell / viewport | viewport unit + topbar |
packages/hatchi-maxchi/** |
hm-surface (contract + catalogue + package suite; note sibling visual) |
src/dazzle/render/**, kanban/components |
render-catalogue |
src/dazzle/http/** |
http-ratchets (deferred import + clone + action URL) |
src/** |
bandit medium on src/ |
Use mid-edit for a fast loop; does not replace Tier 0 for ship.
Push gate — make push-gate (scripts/push_gate.py)¶
Before every git push to main. Machine-enforced process control from the
2026-07-28 CI autopsy (cancel storm + skipped gates + HM visual plane):
| Check | Blocks when |
|---|---|
| Stamp | No/stale stamp, or worktree fingerprint ≠ stamp (gates not re-run after edit) |
| Min tier | Stamp tier below required (make push-gate defaults to tier 0 = ci-fast) |
| Throttle | ≥4 commits on origin/main in the last hour, or <8m since newest main commit |
| CI tip | Latest main ci.yml is still in_progress / queued |
| HM plane | Diff touches gallery CSS/JS/baselines and gen-surface is dirty; optional --require-hm-green |
| Improve commit | HEAD is improve: cycle N and fails oral #127 (scripts/improve_commit_contract.py: clerk-visible subject, Before/After/Live, leftover-token cadence) |
Stamp is written automatically at the end of ci_local.sh tier0 / tier1.
Manual:
python scripts/push_gate.py record --tier surface # after preflight+ship-surface only
python scripts/push_gate.py record --tier 0 # after ci-fast
python scripts/push_gate.py check --min-tier 0
python scripts/push_gate.py check --repair # cimonitor only
python scripts/push_gate.py check --require-hm-green
python scripts/push_gate.py status
/ship and /improve land steps must run this and refuse push on non-zero.
Tier 0 — make ci-fast (scripts/ci_local.sh tier0)¶
Default for /ship. Budget ~3–4 minutes, no Postgres. Always runs
preflight-surface then ship-surface first.
| Step | Mirrors |
|---|---|
preflight-surface |
Structural debt cluster (see above) |
ship-surface |
Recurrent badge-red pack (bandit + SPEC/IR/viewport) |
ruff check --fix + ruff format |
CI lint (local mutates; CI is check-only) |
mypy src/dazzle |
CI type-check command (extras may still differ) |
pytest tests/unit -m gate |
Full gate suite (superset of preflight modules) |
mkdocs build --strict |
docs.yml build |
Does not include full unit matrix, multi-version Python, Postgres, Playwright walks, or service-backed jobs.
Tier 1 — make ci-core (scripts/ci_local.sh tier1)¶
Required before release tags (/ship minor, /ship major, or any bump of
pyproject.toml version). Closer to GitHub core jobs. Always runs
preflight-surface first (after sync, before the long unit matrix).
| Step | Mirrors |
|---|---|
uv sync --frozen with CI python-tests then type-check extras, Python 3.12 |
setup-dazzle |
preflight-surface |
Structural debt cluster |
python scripts/build_dist.py |
CI builds gitignored dist/ before tests |
ruff check + ruff format --check |
lint |
mypy src/dazzle after type extras |
type-check |
CSS clip + raw-ramp + dazzle coverage --fail-on-uncovered |
lint extras |
bandit (medium, all of src/) + pip-audit hard-fail |
lint + security-tests |
| JWT fuzz + shapes RBAC matrix | security-tests unique gates |
pytest -n auto --dist loadgroup -m "not e2e" |
python-tests (single local version) |
mkdocs build --strict |
docs.yml |
Environment knobs:
CI_LOCAL_SKIP_SYNC=1— skip frozen sync (uses current.venv; concordance not guaranteed)CI_LOCAL_COVERAGE=1— enable coverage on the unit stepCI_LOCAL_PYTHON=3.12— override sync Python (default 3.12)
Tier 2 — still GitHub-only (not in ci_local.sh yet)¶
These routinely fail after a Tier-0-only ship:
- Python matrix 3.12 / 3.13 / 3.14
- postgres-tests (service container)
- e2e-runtime, e2e-smoke (example validate loop is partially make-able)
- interaction-walks / viewport (Playwright + Postgres)
- guide-walks (12-app matrix)
- contracts-gate (
ux verify --contracts --managedon support_tickets) - homebrew-validation (macOS)
See ci.yml job names for the authoritative list.
Extras cheatsheet (must match CI)¶
Copied into scripts/ci_local.sh — do not invent a third list.
| Job | Extras |
|---|---|
| type-check | dev,llm,mcp,mobile,postgres,pitch,i18n,viewport,perf,lsp |
| python-tests | dev,llm,mcp,mobile,postgres,perf,saml,lsp,test-full |
| lint | dev,llm,mcp,postgres,perf |
| security-tests | dev,mobile,postgres,perf |
| setup-dazzle default | dev,llm,mcp,mobile,postgres,perf,saml,lsp |
Install type-check env only:
Agent skills¶
| Skill | Default tier |
|---|---|
/ship |
Tier 0 (make ci-fast = preflight + ship-surface + ruff/mypy/gate/docs); Tier 1 for minor/major or version bumps; optional make ci-changed mid-edit |
/cimonitor |
Repair badge; close the loop by promoting new failure classes into ship-surface/preflight |
/check |
Opportunistic per changed files (make ci-changed); prefer full unit when Python changed; make ci-core when asked for release-grade |
Why concordance breaks¶
- Extras — thin local venv → mypy lies (
warn_return_any/ unused ignores). - Tier 0 is intentional subset — gate suite ≠ full
python-tests. - No Postgres / Playwright locally unless you add services.
- OS — macOS vs
ubuntu-latest. - Three legacy "CI" paths — prefer
ci-fast/ci-coreover oldmake ci(soft pip-audit, no frozen extras). - Unpaid surface debt — MCP/CLI/IR changes without regenerating baselines
and docs. This is what
preflight-surfaceexists to catch before push. Stacking feature commits on a red tip multiplies the same failures across every matrix cell.
Quick recipes¶
# Mid-change: only structural debt (fast fail)
make preflight-surface
# Everyday push confidence (includes preflight)
make ci-fast
# Before tagging a release
make ci-core
# Only re-type after deps change
make type-check-ci
# Skip sync when debugging a single test (you accept drift)
CI_LOCAL_SKIP_SYNC=1 bash scripts/ci_local.sh tier1