# Lean CI

> Guide for writing and modifying GitHub Actions workflows in this repository. Use when creating CI/CD pipelines, adding workflow jobs, modifying build steps, or debugging CI failures. Enforces the project's lean CI philosophy.

- Skill: `ansible/lean-ci` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ansible/lean-ci`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ansible/lean-ci/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra, Coding & Dev Tools
- Author: ansible (https://skillmd.com/u/ansible)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ansible/lean-ci

---


# Lean CI

This project follows a strict "CI as thin wrapper" philosophy. GitHub Actions
workflows must never contain substantive build logic. All logic lives in
locally-runnable tox environments; CI just calls them.

## Principles

1. **Every CI step must be reproducible locally.** A developer should be able to
   run the exact same command on their laptop. If a step only works inside
   GitHub Actions, it violates this rule.

2. **Workflows call tox environments, not inline shell.** Build and test logic
   belongs in `tox.ini` environments -- never in multi-line YAML `run:` blocks.
   CI runs `uvx --with tox-uv tox -e <env>`.

3. **No scattered version pinning.** Python version is in `pyproject.toml`
   (`requires-python`). Node is in `frontend/package.json` (`engines.node`).
   Tool versions are managed in `.pre-commit-config.yaml`
   (ruff, mypy) and `pyproject.toml` (deps). Not in workflow YAML.
   Jobs that need Node use `actions/setup-node` with
   `node-version-file: frontend/package.json`.

4. **Minimal setup actions.** `astral-sh/setup-uv` and `actions/checkout` only.
   No `actions/setup-python` (uv handles it). No other setup actions without
   explicit justification.
   **Exception:** `test.yml` jobs `ui` and `ui-workflow-pack` use
   `actions/setup-node` because `tox -e ui` / `ui-workflow-pack` need `npm`.

5. **Pin actions to commit SHAs.** Mutable tags (`@v4`) allow upstream changes
   to affect CI without review. Always pin to a full commit SHA with a comment
   noting the tag (ADR-015).

## tox as orchestration layer (ADR-047)

tox is the sole developer orchestration tool. Every CI step maps to a tox
environment that developers run locally.

| tox environment | What it does | CI workflow |
|-----------------|-------------|-------------|
| `tox -e lint` | Lint, format, type check (prek: ruff + mypy + pydoclint) | `prek.yml` |
| `tox -e unit` | Unit tests with coverage (`--cov-fail-under=36`) | `test.yml` |
| `tox -e integration` | Integration tests (requires OPA binary) | `test.yml` |
| `tox -e ai` | AI extra tests (abbenay) | `test.yml` |
| `tox -e ui` | Playwright UI tests | `test.yml` |
| `tox -e ui-workflow-pack` | Pack `@apme/ui-workflow` release tarball | `test.yml` |
| `tox -e grpc` | Regenerate gRPC stubs | manual |
| `tox -e helm` | Lint + package Helm chart | `helm-charts.yml` |
| `tox -e build` | Build container images | `container-images.yml` (GHCR) |
| `tox -e up` | Start the APME pod | manual |
| `tox -e down` | Stop the APME pod | manual |
| `tox -e pm` | Build + start + open browser | manual |

Install: `uv tool install tox --with tox-uv`

## Workflow structure

CI has six workflows in `.github/workflows/`:

- **prek.yml**: Runs `prek` (ruff lint, ruff format, mypy strict, pydoclint,
  uv-lock). Quality gate for code style and type safety.
- **test.yml**: Runs `tox -e unit`, `tox -e integration`, `tox -e ui`,
  `tox -e ai`, and `tox -e ui-workflow-pack` as separate jobs. Quality gate
  for correctness. Coverage threshold is enforced via `--cov-fail-under` in
  `tox.ini`.
- **container-images.yml**: Builds and pushes multi-arch container images
  (`linux/amd64` + `linux/arm64`, ADR-063) to GHCR on `main`, version tags, and
  `workflow_dispatch`, then merges manifests via
  `containers/ci/merge-manifests.sh` (image list: `containers/ci/images.txt`).
- **helm-charts.yml**: Lints/packages the Helm chart (`tox -e helm`) and
  publishes to GitHub Pages via chart-releaser when `deploy/helm/apme/**`
  changes on `main`. Also publishes values profiles
  (`tox -e helm-pages-profiles`) to `https://ansible.github.io/apme/`.
- **deprecation-scrape.yml**: Monthly cron scraping ansible-core for deprecation
  gaps.
- **pr-feedback.yml**: Labels PRs with failing checks or merge conflicts.

`prek.yml` and `test.yml` trigger on `pull_request` targeting `main` and use
`concurrency` groups with `cancel-in-progress` to avoid stacking runs on rapid
pushes.

## Rules for modifications

When adding or modifying CI:

- **DO** add new build logic as a tox environment in `tox.ini`, then call it
  from the workflow with `uvx --with tox-uv tox -e <env>`.
- **DO** use SHA-pinned actions with a tag comment (e.g.,
  `actions/checkout@de0fac2e...  # v6`).
- **DO** set `FORCE_COLOR: 1` and `PY_COLORS: 1` as workflow-level env vars
  for readable CI logs.
- **DO** use `ubuntu-24.04` explicitly rather than `ubuntu-latest`.
- **DO NOT** put multi-line shell scripts in `run:` blocks. If it needs more
  than one command, it belongs in a tox environment or a script in `scripts/`.
  The git dirty check is the one exception -- it is a CI-only guard with no
  local equivalent.
- **DO NOT** add `actions/setup-python` or other setup actions. `setup-uv`
  handles the Python toolchain.
- **DO NOT** hardcode tool versions in YAML. Versions belong in
  `.pre-commit-config.yaml` or `pyproject.toml`.
- **DO NOT** add secrets or publishing steps without explicit approval.

