Prompt file imported from lukasz-lobocki/step-badger (
.github/prompts/go-release-readiness.prompt.md). Copyright stays with the author.
Context
Goal: bring the codebase to a shippable, reproducible, secure, observable and well-documented release state, then produce everything needed to cut the tag.
Prime directive: do not change runtime behaviour unless it fixes a defect you identify and document. Every behavioural change must be called out explicitly in the PR description with rationale and blast radius.
Phase 0 — Reconnaissance (do this first, report before changing anything)
Produce a short written assessment covering:
- Repository layout: packages, entrypoints (
cmd/), internal vs public API surface (internal/,pkg/). - Build system in use: plain
go build,Makefile,Taskfile,mage, GoReleaser, Bazel. - Existing CI: which workflows exist, what they run, which are currently failing.
- Existing release process: tags, changelog, artifacts published previously.
- Test posture: number of test files, presence of integration/e2e tests, fixtures, golden files.
- Delta since last release:
git log [vA.B.C]..HEAD --onelineand merged PRs — categorise them. - A prioritised list of blockers vs. nice-to-haves for this release.
Stop and summarise. Then proceed through the phases below.
Phase 1 — Correctness & code health
Module hygiene
go mod tidy—go.mod/go.summust be clean and committed.go mod verifypasses.- Confirm the
godirective matches the true minimum supported version; do not bump it casually (it is a breaking change for library consumers). - Review
replace,excludeandretractdirectives — remove local/devreplacelines. - Audit direct dependencies: for each, note current vs latest version, whether the bump is patch/minor/major, whether it is still maintained, and whether it can be dropped in favour of stdlib.
- Apply safe upgrades (
go get -u=patch ./...then targeted minors). Defer major bumps and list them explicitly. - Check for duplicate/overlapping dependencies (two YAML libs, two logging libs, etc.).
- Check the dependency tree depth and any unexpectedly heavy transitive deps (
go mod graph,go mod why).
Static analysis
go build ./...— zero errors, including with-tags '[buildtags]'if build tags are used.go vet ./...— zero findings.gofmt -l .(orgofumpt -l .) — empty output.goimportsgrouping consistent (stdlib / external / internal).golangci-lint runwith a committed.golangci.yml. Recommended linter set:errcheck, govet, staticcheck, unused, gosimple, ineffassign, revive, gocritic, gosec, bodyclose, rowserrcheck, sqlclosecheck, noctx, errorlint, wrapcheck (scoped), copyloopvar, nilerr, misspell, unconvert, dupl (high threshold), prealloc, exhaustive (for enums), contextcheck, containedctx.- Every remaining
//nolint:...must have an explanatory comment. go test ./... -run XXX -vet=allsanity pass.
Code review sweep
- Error handling: all errors wrapped with
%wwhere the caller may neederrors.Is/As; sentinel errors exported where appropriate; noerr != nil { return err }that loses context; no swallowed errors. context.Contextpropagated through all I/O paths; nocontext.TODO()in production paths; no context stored in structs; timeouts/deadlines set on every outbound call.- Concurrency: every goroutine has a clear owner and termination path; no leaked goroutines
(consider
go.uber.org/goleakin tests); channels closed by the sender;sync.WaitGroup/errgroupused correctly; no data races; mutexes not copied;atomictypes used instead of raw ints where relevant. - Resource management: every
Close()deferred and its error handled; HTTP response bodies drained and closed; file handles, DB rows, tickers (defer ticker.Stop()) cleaned up. - Nil safety: no dereference of possibly-nil pointers/maps/interfaces; typed-nil-in-interface traps checked.
- Slices/maps: no unintended aliasing of caller-owned slices; defensive copies where the API contract implies it.
- Panics: none in library code paths;
recover()only at well-defined boundaries (HTTP middleware, worker loops) and always logged. - Time: use
time.Time/time.Durationcorrectly; no wall-clock arithmetic where monotonic is needed; clock injectable for tests. - Determinism: no map-iteration-order dependence in output.
- Remove dead code, unused exports, debug
fmt.Println, commented-out blocks, staleTODO/FIXME(either fix, or convert to a tracked issue and reference it). - Ensure
internal/is used to keep non-public helpers out of the public API.
Phase 2 — Testing
go test ./... -race -count=1passes cleanly.go test ./... -count=5to catch flakiness; also run with-shuffle=on.- Coverage:
go test ./... -coverprofile=coverage.out -covermode=atomic; report total and per-package coverage. Add tests for uncovered exported behaviour that is release-critical. State a coverage floor and enforce it in CI. - Table-driven tests for public API edge cases: empty input, nil, zero values, max sizes, unicode/multibyte strings, negative numbers, timezone boundaries, cancelled contexts.
- Golden-file tests for any rendered output (CLI help text, templates, serialised formats) — with an
-updateflag. - Fuzz targets (
FuzzXxx) for parsers, decoders, and anything handling untrusted input; rungo test -fuzz=Fuzz -fuzztime=[60s]and commit any crashers found intestdata/fuzz. - Benchmarks (
BenchmarkXxx) for hot paths; compare against the previous release withbenchstatand report regressions >[5]%. - Integration tests gated behind a build tag or
testing.Short(); document how to run them. - Tests must be hermetic: no real network, no reliance on host timezone/locale, no
time.Sleepfor synchronisation, temp dirs viat.TempDir(), parallel-safe (t.Parallel()where sound). go test ./... -raceunder the oldest and newest supported Go version.- Example tests (
ExampleXxx) for the main public API — these double as documentation on pkg.go.dev.
Phase 3 — Build, versioning & reproducibility
- Version information embedded at build time:
or derived from-ldflags "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"runtime/debug.ReadBuildInfo()(preferred — works withgo install). - A
--version/versionsubcommand printing version, commit, build date, Go version, OS/arch. - Reproducible builds:
CGO_ENABLED=0where possible,-trimpath, pinned toolchain,SOURCE_DATE_EPOCHhonoured. - Cross-compilation verified for every target platform in the matrix.
- Static vs dynamic linking decision documented (especially for CGO /
net/os/userresolver behaviour). - Binary size checked and reported; strip symbols in release builds.
.goreleaser.yaml:- builds matrix,
ignoreentries for unsupported combos - archives (
tar.gzfor unix,zipfor windows) with README/LICENSE included checksums.txtwith SHA-256- changelog generation from conventional commits, with
groupsandfilters.exclude release.draft: truefor review before publish,prerelease: auto- optional: Homebrew tap, Scoop bucket, nfpm (deb/rpm/apk), Docker manifests, SBOM, signing
- validate with
goreleaser checkandgoreleaser release --snapshot --clean, then inspectdist/.
- builds matrix,
- Container image (if applicable): multi-stage build,
distroless/alpine/scratchbase, non-rootUSER,HEALTHCHECKwhere meaningful, OCI labels (org.opencontainers.image.*), multi-arch manifest,.dockerignorepresent, image scanned with Trivy/Grype. Makefile/Taskfiletargets:build,test,lint,fmt,cover,snapshot,release,clean,tools.- Tool dependencies pinned via
tools.go+go.mod, or atools/go.mod, so contributors get identical versions.
Phase 4 — CI/CD
- PR workflow (
.github/workflows/ci.yml): matrix over[os] × [go-version]; steps: checkout, setup-go with module+build cache,go mod download, build, vet,golangci-lint,go test -race -coverprofile, upload coverage. Concurrency group cancels superseded runs. Least-privilegepermissions:block. - Release workflow (
.github/workflows/release.yml): triggered onpush: tags: ['v*'];permissions: contents: write, packages: write, id-token: write; fullfetch-depth: 0for changelog; runs GoReleaser; publishes artifacts, checksums, SBOM, signatures and container images. - Security workflows: CodeQL (Go),
govulncheck, dependency review on PRs, secret scanning enabled. - Dependabot (
.github/dependabot.yml) forgomod,github-actions, anddocker, grouped updates. - All GitHub Actions pinned to a full commit SHA, not a floating tag.
- Branch protection: required status checks, required review, linear history, no force-push to
main. - Optional: release-please or changesets if you want automated version bumps + changelog PRs.
- Verify the workflows actually pass — do not hand back untested YAML.
Phase 5 — Security & supply chain
govulncheck ./...— every finding either fixed or documented with justification and tracking issue.gosec ./...— review findings, especially file permissions, command execution, TLS config, weak crypto, path traversal.- SBOM generated (CycloneDX or SPDX via Syft/GoReleaser) and attached to the release.
- Artifact signing:
cosign sign-blob/ keyless OIDC signing for binaries and container images; document the verification command for users. - SLSA provenance attestation if targeting a higher assurance level.
- Secrets audit:
gitleaks detectover full history; no tokens, keys, internal hostnames, or customer data committed. - Input validation on all external boundaries; size limits on request bodies and file reads; no unbounded allocations.
- TLS: minimum version 1.2+, no
InsecureSkipVerifyoutside explicitly opt-in debug flags. - Dependency licence audit (
go-licenses report) — confirm all licences are compatible with the project's licence; produce aTHIRD_PARTY_NOTICESfile if required. LICENSEpresent and correct; SPDX headers consistent if the project uses them.SECURITY.mdwith a vulnerability disclosure policy and contact.
Phase 6 — Operability (for services/daemons)
- Structured logging (
log/slog), configurable level, no secrets logged, consistent field names. - Metrics (Prometheus) and/or OpenTelemetry traces on key paths; documented metric names and labels.
- Health/readiness endpoints.
- Graceful shutdown on
SIGINT/SIGTERMwith a bounded drain timeout; in-flight work completed or checkpointed. - Configuration precedence documented (flags > env > file > defaults); config validated at startup with clear errors.
- Sensible resource limits and backpressure; retries with exponential backoff and jitter; circuit breaking where relevant.
- Exit codes documented and meaningful.
Phase 7 — Documentation
README.md: badges, one-paragraph description, feature list, installation for every distribution channel, quick start with copy-pasteable commands, full flags/env/config reference table, worked examples, troubleshooting/FAQ, compatibility & support policy, links to CHANGELOG/CONTRIBUTING/SECURITY.- Doc comments on every exported identifier, starting with the identifier name; package-level
doc.gofor each non-trivial package. Verify the rendering withgo doc -all ./.... - Runnable
Examplefunctions so pkg.go.dev shows usage. CHANGELOG.mdfor[vX.Y.Z]in Keep a Changelog format:Added / Changed / Deprecated / Removed / Fixed / Security, with PR and issue links, and an explicit statement of whether the release is breaking under SemVer.- Migration guide for any breaking change: before/after code, deprecation timeline, automated migration if feasible.
- Deprecation notices use the
// Deprecated: ...convention so tooling picks them up. CONTRIBUTING.md,CODE_OF_CONDUCT.md, issue/PR templates present and current.- If the CLI supports it, regenerate shell completions and man pages as part of the release.
- Architecture notes / ADRs updated if the design changed.
Phase 8 — Release readiness checklist
Produce this as a filled-in checklist in the PR description:
- Version number chosen per SemVer and justified
- No breaking changes, or breaking changes documented + major bump
-
go build,go vet, lint,go test -raceall green on the full matrix - Coverage ≥
[N]%, no new uncovered critical paths -
govulncheckclean or all findings documented -
goreleaser release --snapshot --cleanproduces correct artifacts for all platforms - CHANGELOG updated, README accurate, docs regenerated
- CI workflows pass on the release branch
- Upgrade path from
[vA.B.C]verified (config compatibility, data/schema migration, API compatibility) - Rollback plan documented
- Post-release verification steps listed (install from each channel and smoke test)
Deliverables
- A pull request with all changes, split into logical, reviewable commits
(e.g.
chore(deps):,fix(lint):,test:,ci:,docs:,build:), following Conventional Commits. - A PR description containing:
- Summary table: area → what changed → risk level
- Full list of commands executed, with their output/results
- Behavioural changes, if any, flagged prominently
- Findings deliberately not fixed, each with reasoning and a suggested follow-up issue
- Before/after metrics: binary size, test count, coverage, benchmark deltas, dependency count
- The exact release commands:
git checkout main && git pull git tag -a [vX.Y.Z] -m "[vX.Y.Z]" git push origin [vX.Y.Z] # then verify the release workflow and publish the draft release
- A draft
CHANGELOG.mdentry ready to publish as the GitHub Release notes. - A list of suggested follow-up issues for anything deferred.
Constraints & rules of engagement
- Do not modify the public API surface without flagging it prominently in the PR description.
- Do not commit generated artifacts (
dist/, binaries,coverage.out,*.test); ensure.gitignorecovers them. - Do not blanket-disable linters to make CI pass; fix or narrowly scope the exclusion with a comment.
- Do not perform major dependency upgrades as part of a patch release.
- Do not silently skip a step — if something cannot be completed (missing credentials, no network, platform unavailable), state it explicitly and explain what would be needed.
- Prefer the standard library over new dependencies; justify any dependency you add.
- Keep changes minimal and surgical; a release-prep PR is not the place for refactors — split those out.