stack.toml [ci] section with backend = "woodpecker" | "github".
gen_config.py dispatches to backends/woodpecker.py or
backends/github.py, both implementing emit(images, scans,
platform, ci_cfg) -> dict[str, str].
Woodpecker backend: extracted from monolithic gen_config.py,
all plugin images and docker socket now from [ci.woodpecker].
GitHub backend: generates 6 .github/workflows/*.yml using
actions/checkout, astral-sh/setup-uv, docker/build-push-action,
aquasecurity/trivy-action, softprops/action-gh-release. Matrix
strategy for parallel image builds. GHCR as default registry.
Backend switching: change ci.backend in stack.toml, commit.
gen_config.py emits to the active directory and cleans stale
files from the inactive backend directory.
Pre-commit hook triggers on changes to stack.toml,
gen_config.py, or backends/*.py — stages all generated files.