Development guide
May 20, 2026 · View on GitHub
Audience: contributors. Pair with AGENTS.md for the project rules.
Toolchain
- Go: latest stable (>= 1.26).
- protoc: any recent release that supports the protobuf descriptor APIs used
by
protoc-gen-validatev1.3.x andlyft/protoc-gen-star/v2. - golangci-lint: any v2.x.
make lintuses the system binary. - govulncheck: any v1.x.
make vulnuses the system binary.
One-time setup after clone
make proto/validate # fetches validate.proto from the protoc-gen-validate release tarball
proto/validate/ is .gitignored on purpose — the validate proto definitions
come from an external release tagged by PROTOC_GEN_VALIDATE in the Makefile
(currently 1.3.3). Bump that variable when you want a newer release.
Build
make build # produces bin/protoc-gen-checker
make install # go install . into $GOBIN
protoc finds the plugin via $PATH, so make install is enough for a
host-wide invocation. For local invocation pass --plugin=protoc-gen-checker=./bin/protoc-gen-checker.
Regenerate the checker proto
checker/checker.pb.go is generated from checker/checker.proto. Do not
hand-edit it. To regenerate:
make bin/protoc-gen-go # fetched once
make checker/checker.pb.go
The first target installs google.golang.org/protobuf/cmd/protoc-gen-go into
the local bin/. The second invokes protoc with the import-path remappings
declared in GO_IMPORT_SPACES.
Run unit tests
make unit # go test ./... -count=1 -race
These cover pure-Go helpers in checker.go (getNoValidationReason). Visitor
methods are exercised by the integration fixture below.
Run fixture integration test
make test
This command is expected to print failures. It runs the plugin against
tests/*.proto in strict mode; those fixtures intentionally illustrate every
misuse the plugin reports. The banner repeats the warning:
/////////////////////////////////////////////////////////////////////////////////////////////
This test is supposed to FAIL. It illustrate the various good/wrong ways of using this plugin.
/////////////////////////////////////////////////////////////////////////////////////////////
A real regression looks like: the failure list changing, or the plugin crashing
instead of reporting findings. Use git diff against a known-good run to spot
real changes.
Lint
make lint # golangci-lint run ./...
Config is .golangci.yml (Standard preset). ireturn and nilnil are disabled
because the PGS visitor API contractually returns pgs.Visitor and signals
"stop descending" with nil, nil.
Vulnerability scan
make vuln # govulncheck ./...
Reachable findings (Your code is affected by N vulnerabilities) must be
zero. Non-reachable findings in imported packages or transitive modules are
tracked but do not block — they get cleared by dep refreshes.
Reachable Go stdlib findings clear after upgrading the system go toolchain.
Release flow
- Bump dep versions if needed (
go get …@latest;go mod tidy). make lint && make vuln && make unit && make test— last one expected to FAIL with the standard banner.- Tag:
git tag -a v<MAJOR>.<MINOR>.<PATCH> -m "Release v<...>" && git push origin v<...>. - The
.github/workflows/release.ymlworkflow fires onv*tag push and runs goreleaser. It cross-compiles binaries forlinux/{amd64,arm64}anddarwin/{amd64,arm64}, produces.tar.gzarchives plus raw binaries, generates a CycloneDX SBOM per archive via syft, writes aSHA256SUMSfile, signs it with cosign (keyless OIDC via the workflow'sid-token: writepermission), and uploads everything to the GitHub release.
To dry-run the release locally without pushing a tag:
make release-snapshot # runs `goreleaser release --snapshot --clean`
make release-check # validates .goreleaser.yaml
These require goreleaser, cosign, and syft on $PATH. Snapshot mode
skips the cosign signing step.
Common pitfalls
proto/validate/missing after fresh clone. Runmake proto/validate.make distcleanwipes everything generated, includingchecker/checker.pb.goand the fetched validate proto. Useful when bumpingPROTOC_GEN_VALIDATEin theMakefile.- Editing
checker/checker.pb.go. Never. It is generated. - Lint complains about generated code.
.golangci.ymlexcludes*.pb.go,vendor/, andthird_party/. If a new generator is added, append its output path regex to bothlinters.exclusions.pathsandformatters.exclusions.paths. - Vendor mode is carved out (see
AGENTS.md). Do not rungo mod vendorunless reinstating the carve-out —vendor/is in.gitignore.