Skip to content

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:

make preflight-surface

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 step
  • CI_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 --managed on 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:

make sync-ci-type
make type-check-ci

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

  1. Extras — thin local venv → mypy lies (warn_return_any / unused ignores).
  2. Tier 0 is intentional subset — gate suite ≠ full python-tests.
  3. No Postgres / Playwright locally unless you add services.
  4. OS — macOS vs ubuntu-latest.
  5. Three legacy "CI" paths — prefer ci-fast / ci-core over old make ci (soft pip-audit, no frozen extras).
  6. Unpaid surface debt — MCP/CLI/IR changes without regenerating baselines and docs. This is what preflight-surface exists 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