Thanks for your interest in diffah. This document describes the
development workflow, the conventions the codebase follows, and the
quality bar a pull request needs to clear before merge.
By participating in this project you agree to abide by the Code of Conduct.
- Bugs: open a bug report.
Include the failing command, the diffah version (
diffah version), and — when possible — a minimal reproduction archive. - Feature requests: open a feature request. Describe the workflow you can't accomplish today and why an existing flag combination isn't enough.
- Security vulnerabilities: see
SECURITY.md. Do not open a public issue.
| Requirement | Why |
|---|---|
| Go ≥ 1.25.9 | matches the version in .tool-versions and CI |
zstd ≥ 1.5 on $PATH |
required by integration tests for intra-layer patching |
golangci-lint v2.11+ |
matches the version pinned in CI |
make, git |
standard developer tooling |
We use asdf to pin the Go version locally; the
file is .tool-versions. Other version managers work the same way.
git clone https://github.com/leosocy/diffah.git
cd diffah
make build # → ./bin/diffah
make test # unit tests with -race -cover
make test-integration # adds the `integration` build tag
make lint # golangci-lint
make fixtures # rebuild test image fixtures (occasionally needed)- Fork and branch. Branch from
master; use a short topical name likefeat/registry-tokenorfix/sidecar-version-check. We ship pre-v1.0.0, so master is the integration branch. - Write a failing test first. Every change to behavior needs a test that fails before the change and passes after. Bug fixes include a regression test that reproduces the bug.
- Implement. Keep the change focused. If a refactor is needed, land it as its own PR before the feature change.
- Run the full quality gate locally (formatter + linter + tests + integration tests if you touched I/O). All four must pass.
- Update the changelog and docs. Add a bullet under the relevant
[Unreleased]section inCHANGELOG.md. UpdateREADME.md,docs/compat.md, ordocs/performance.mdwhen behavior the README claims changes. - Open a PR. Title and body should explain the why; reviewers can read the diff for the what. Reference any linked issues.
We follow Conventional Commits:
<type>(<optional scope>): <imperative-mood description>
<optional body — explain WHY, not just WHAT>
<optional footer — refs / breaking-change notes>
Allowed types: feat, fix, refactor, perf, test, docs,
style, chore, build, ci. The body should describe the
motivation; the diff already shows the code change.
Examples (drawn from this repo's history):
feat(exporter): top-K candidate selection with deterministic tie-break
style(imageio): scope gosec G703 nolint on fileExists stat call
We do not require DCO sign-off and do not use a CLA. Please don't
add Co-authored-by: Claude or other AI-attribution trailers — the
project tracks human contributors only.
- Run
make fmtbefore committing. It appliesgofmt -sandgoimports -local github.com/leosocy/diffah. - The project follows Effective Go and the Go Code Review Comments.
- Public packages live under
pkg/...; internal helpers underinternal/.... Thecmdpackage is the CLI shim and is allowed to reach intointernal/.... - Errors at the user-facing surface use the
pkg/diff/errscategory helpers so theExecute()exit-code classifier maps them correctly. Adding a new error path? Pick the righterrs.Category(user / environment / content / internal). - Prefer composition over interface inheritance. Keep functions short — the reviewer should be able to hold the whole function in working memory.
| Layer | Convention |
|---|---|
| Unit tests | *_test.go next to the package; table-driven preferred. |
| Integration | gated by the integration build tag; live in cmd/*_integration_test.go. |
| Fixtures | regenerated by scripts/build_fixtures (make fixtures). |
| Determinism | byte-equality assertions on archive output where applicable. |
Mock at boundaries only (filesystem, registry, signer key material) — do not mock the unit under test. If a test needs more than 3-4 mocks, the production code should usually be split.
The integration build tag is required for any test that exercises
the CLI binary, the in-process registry harness, or zstd itself.
- Tests added (or updated) and passing locally.
-
make lintis clean. -
make testis clean (andmake test-integrationif touched). -
CHANGELOG.mdupdated under the relevant[Unreleased]section. -
README.md/docs/compat.md/docs/performance.mdupdated when behavior the docs claim changes. - Commit messages follow Conventional Commits.
Releases are cut by tagging vX.Y.Z on master. The
release.yml workflow builds
cross-platform binaries via goreleaser,
emits SBOMs, signs the artifacts with cosign (keyless / Sigstore),
and publishes a multi-arch container image to
ghcr.io/leosocy/diffah.
If you are not a maintainer you do not need to do anything for a
release — your [Unreleased] CHANGELOG entry is enough.
The full design history (per-phase specs and ADRs) is under
docs/superpowers/. Stable user-facing contracts
(exit codes, sidecar schema, transports, JSON envelope) are in
docs/compat.md.