Thanks for considering a contribution. This document explains how to build, test, and submit changes.
- Go ≥ 1.23
golangci-lintv2.x (https://golangci-lint.run/usage/install/)make- (optional)
dockerfor the release image - (optional)
goreleaserfor cutting a release locally
Check your toolchain:
go version && golangci-lint --version && make --version | head -1cmd/qctx/ # CLI entrypoint
internal/cli/ # cobra commands (fetch, snapshot, version, root)
internal/bundle/ # concurrent source gather + adapters
internal/sonar/ # SonarQube REST client
internal/gitlab/ # GitLab REST client + MR URL parser + auto-discovery
internal/nexus/ # Nexus IQ JSON reader
internal/model/ # normalized output types + JSON / JSONL emitters
internal/config/ # config loader (flag > env > file)
internal/httpclient/ # shared retrying HTTP with custom CA + proxy + headers
internal/logging/ # zerolog bootstrap
internal/version/ # build-time version vars
test/e2e/ # end-to-end test (build-tag e2e) against mock servers
test/fixtures/ # canned API payloads + expected JSONL types
docs/ # design spec, plan, enterprise/examples
make test # fast inner loop
make lint # lint everything
make ci # full CI run (tidy + fmt + lint + cover + cover-check + build)
make e2e # end-to-end test against mock servers- TDD encouraged. Each feature has a RED test before implementation.
- Table-driven for parsing/filtering.
- Mock servers (
net/http/httptest) for API client tests — never hit a real Sonar / GitLab. - No flaky timing. No
time.Sleepin tests; use channels ort.Cleanup. - Coverage gate.
make cover-checkenforces ≥80% per logic package. Push that bar up when you can.
- Conventional Commits:
feat:,fix:,chore:,test:,docs:,refactor:,style:,ci:. - Atomic — one logical change per commit.
- Imperative mood (
add foo, notadded foo). - First line ≤ 72 chars.
qctx must remain enterprise-first. Do not introduce hardcoded defaults for sonarcloud.io, gitlab.com, or cloud.sonatype.com. Every API client constructor takes a BaseURL and errors when it is empty. A CI grep enforces this:
! grep -rIE 'sonarcloud\.io|(^|[^.])gitlab\.com|cloud\.sonatype\.com' --include='*.go' .The type-discriminated JSONL schema in internal/model/jsonl.go is part of the project's contract with downstream agents. Within a 0.x minor series:
- Adding new fields to a record type is allowed.
- Adding new record types is allowed.
- Renaming, removing, or changing the type of an existing field is a breaking change and requires a minor-version bump plus a CHANGELOG entry under
### Changedor### Removed.
- Tokens live inside the
Secrettype (internal/config).Secret.String()returns***redacted***; do not log viaReveal(). gosecruns in CI. New// #nosecannotations must explain why (// #nosec G304 -- reason).- The
--insecureflag emits a stderr warning every run — keep that warning.
Tags drive GoReleaser. To cut a release locally for testing:
goreleaser release --clean --snapshotA real tag (vX.Y.Z) on the default branch triggers the release stage in .gitlab-ci.yml.
Before requesting review:
-
make ciis green locally (tidy, fmt, lint, test, cover, cover-check, build). - If you touched the JSONL schema, the change is additive or you have CHANGELOG notes.
- If you touched a Sonar / GitLab endpoint, you added or updated an httptest-based test.
- No public-cloud URL literals were introduced.
- Any new flag is documented in
README.md, the relevantbindCommonFlagsblock, and (if user-facing)docs/enterprise.md.
Before changing core behavior, check the relevant ADR in docs/adr/:
- 0001 — JSONL schema with
typediscriminator. - 0002 — no public-cloud defaults.
- 0003 — partial-success and
--strict. - 0004 —
Secrettype for tokens. - 0005 — cobra as CLI framework.
- 0006 — JSONL writer's byte-stitching approach.
- 0007 — HTTP retry library choice.
If your change supersedes an ADR, write a new ADR that references the old one.
By contributing you agree your contributions are licensed under the project's Apache-2.0 license.