Quality CI
Instructions
Generate a GitHub Actions workflow that enforces code quality and test coverage on every pull request: $ARGUMENTS. $ARGUMENTS may specify the branch, workflow name, or special requirements.
Nexa Rules Gate
Read and follow ${CLAUDE_PLUGIN_ROOT}/shared/readiness/NEXA_RULES_GATE.md.
Prerequisites
- The project must have a
.golangci.ymlat the root withgocycloenabled andrun.build-tagscoveringintegrationande2e(use/code-qualityfirst if needed) templmust be pinned as a tool ingo.mod(go tool templ versionworks)- The project must have
sqlc.yamland committed generated code (internal/db/,*_templ.go) - Integration tests must use the
//go:build integrationtag (use/integration-testfirst if needed)
Workflow
Read the project context:
- Verify
.golangci.ymlexists in the project root and enablesgocyclo - Verify
go.modhas agodirective and atool github.com/a-h/templ/cmd/templentry - Verify
sqlc.yamlexists - Read the local tool versions:
golangci-lint version(major.minor, e.g.v2.5) andsqlc version(e.g.1.29.0) - Check for any existing GitHub Actions workflows in
.github/workflows/ - Ask the user for: workflow trigger branch (default:
main), whether to also trigger on pull requests (default: yes)
- Verify
Create the GitHub Actions workflow at
.github/workflows/quality.ymlusing the template.Fill in the template placeholders:
Placeholder Description Default {{BRANCH}}Branch that triggers the workflow main{{GOLANGCI_LINT_VERSION}}golangci-lint version used locally output of golangci-lint version, asv2.X{{SQLC_VERSION}}sqlc version used locally output of sqlc version, without thevThe Go version is not a placeholder —
actions/setup-goreads it fromgo.modviago-version-file, and caches modules and the build cache by default.Ensure the following are present in the generated workflow:
- Concurrency: The
concurrencykey must cancel in-progress runs for the same branch/PR to prevent redundant CI billing - Generated-code drift check:
go tool templ generate && sqlc generatefollowed bygit diff --exit-code - Coverage report upload: The coverage job must use
actions/upload-artifactto save the filtered profile and the HTML report
- Concurrency: The
Verify the workflow file:
- Validate YAML syntax by reading the file back
- Ensure no placeholder
{{...}}markers remain (GitHub expressions${{ ... }}are expected) - Ensure the workflow file is under 80 lines
Output a summary with:
- What was created and where
- How the workflow triggers (push, PR, or both)
- What each job checks (lint job: golangci-lint + formatting + generated-code drift, coverage job:
go test -tags=integrationwith coverage) - Reminder: the coverage job fails if total coverage of
./internal/..., excluding generated code, drops below 80%
Reference Templates
- GitHub Actions workflow: templates/github-actions/quality.yml
Workflow Template Placeholders
| Placeholder | Description | Example |
|---|---|---|
{{BRANCH}} |
Branch that triggers the workflow on push | main |
{{GOLANGCI_LINT_VERSION}} |
golangci-lint version | v2.5 |
{{SQLC_VERSION}} |
sqlc version | 1.29.0 |
Design Decisions
Two separate jobs: lint and coverage
The lint job runs golangci-lint, the formatters, and the generated-code drift check — these are
fast and have no external dependencies. The coverage job runs the integration tests — these need
Docker for Testcontainers and take longer. Splitting them gives faster feedback: a formatting issue
fails in under a minute instead of waiting for the full test suite.
Why pin the tool versions
golangci-lint releases add linters and change findings; sqlc output changes between versions. An unpinned version makes CI disagree with the developer's machine — a lint failure nobody can reproduce, or a drift failure caused by a newer generator rather than a stale commit.
Why generated code is committed and checked
Committed sqlc and templ output means go build ./... works without the generators. The drift
check guarantees the committed output matches the sources, so a forgotten generate fails CI
instead of shipping stale queries or views.
Coverage threshold fails the CI job
go test has no native threshold, so the workflow reads the total: line from
go tool cover -func and exits non-zero below 80%. Generated code (internal/db/, *_templ.go)
and the test helper internal/testdb/ are filtered out of the profile first — it would otherwise inflate or deflate the number without
reflecting test quality. Testcontainers works on ubuntu-latest because Docker is preinstalled.
Update CLAUDE.md
After creating the workflow, append a ## Quality CI section to the target project's
CLAUDE.md so that future sessions know CI is configured.
- If
CLAUDE.mddoes not exist, create it - If a
## Quality CIsection already exists (check for<!-- NEXA_QUALITY_CI_CONFIGURED -->), ask the user whether to overwrite or skip - Append the following section (fill in the actual values from the setup):
## Quality CI
<!-- NEXA_QUALITY_CI_CONFIGURED -->
- Workflow: `.github/workflows/quality.yml`
- Triggers: push to `[branch]`, pull requests to `[branch]`
- Lint job: `golangci-lint` [version] (gocyclo max 10, formatters reported as issues) + `templ fmt -fail` + generated-code drift (sqlc [version], templ)
- Coverage job: `go test -tags=integration` with `-coverpkg=./internal/...`, generated code excluded, 80% total threshold
- Artifacts: coverage profile and HTML report uploaded on every run
Do not remove or modify any other content in CLAUDE.md.
DO NOT
- Combine lint and coverage into a single job — they have different performance profiles and dependencies
- Hard-code the Go version —
actions/setup-goreads it fromgo.mod - Use
latestfor golangci-lint or sqlc — pin the versions the project uses locally - Omit the
concurrencykey — we must prevent redundant CI billing on rapid pushes - Disable the
actions/setup-gocache — speed is a priority for the Nexa ecosystem - Add matrix strategies for multiple Go versions or OS — keep the workflow single-purpose
- Add deployment steps or notifications — this workflow is only for quality gates
- Overwrite an existing
.github/workflows/quality.ymlwithout reading it first and asking the user - Add separate
go vet,staticcheck,gofmt, orgolangci-lint fmt --diffsteps —golangci-lint runalready runs the linters and reports the enabled formatters