Contributing to Quill¶
Thanks for your interest in Quill. This guide covers the build, the checks a change must pass, and the conventions the codebase follows.
Development environment¶
The library's minimum is Go 1.23 (the go line in go.mod, kept conservative
for broad compatibility). Development uses a newer Go: the test and benchmark
suites rely on b.Loop (Go 1.24+), so pin a recent toolchain locally (for
example with goenv). The engine itself is standard-library only; the linters
and scanners below are dev tooling.
Quill uses go-task as its build tool:
Install the dev tools (golangci-lint, govulncheck, actionlint, go-licenses)
once with task install:tools.
Build, test, and lint¶
The common targets:
task build # build all packages and the cmd/quill binary
task test # go test ./...
task test:unit # tests with the race detector and a coverage profile
task check:all # gofmt, go vet, and go mod tidy checks
task lint:all # golangci-lint + actionlint
task ci # the full pipeline: lint, checks, tests, and security scans
Run task ci before opening a pull request; it is the same pipeline CI runs. A
thin Makefile forwards make build/make test/make check to the equivalent
task targets.
The gates a change must keep green:
go build ./...andgo vet ./...succeed.gofmt -l .prints nothing (the tree is formatted).golangci-lint runreports zero issues.go test ./...passes, including anyExampleXxxfunctions.govulncheck ./...is clean.
Conventions¶
- Dependency-free runtime. The engine depends on nothing outside the Go standard library. Do not add a runtime dependency.
- Tests for new behavior. Add tests for anything you add or change. The
conformance suite under
testdata/conformanceis the behavioral contract: each case is atemplate.ql+data.json+expected.outtriple, and a table test renders each and diffs the bytes. Rendered output is byte-exact, so golden fixtures must match to the byte. - Coverage. Keep meaningful coverage on new code;
task test:unitproduces a profile you can inspect withtask test:cover. - Doc comments on exported API. Every exported symbol carries a doc comment
that is a full sentence ending in a period;
golangci-lint(reviveexported+godot) enforces this. Package-level prose lives indoc.go, and runnableExampleXxxfunctions double as tests and populate the Examples tab on pkg.go.dev. - Documentation surfaces. User-facing docs live in
docs/(the guide site) and in theREADME.md; the canonical API reference is pkg.go.dev, generated from the doc comments. Keep behavior and docs in sync in the same change.
Commit messages and pull requests¶
- Use a conventional prefix (
feat:,fix:,refactor:,docs:,chore:,ci:), an imperative subject under 72 characters, and a body explaining what changed and why. - Keep a pull request focused on one logical change. Note any user-visible change
in
CHANGELOG.mdunder the unreleased section.
Reporting security issues¶
Do not open a public issue for a security vulnerability. See SECURITY.md for the private reporting process.