Contributing to Portwing

August 17, 2026 · View on GitHub

Thanks for your interest in contributing! Whether it is a bug fix, new feature, documentation improvement, or something else — all contributions are welcome.

Questions or help? Open an issue.

Getting started

  1. Fork the repository and clone your fork.

  2. Install Go 1.26+:

    go version  # should be 1.26+
    
  3. Download dependencies:

    go mod download
    
  4. Create a branch from the active integration branch (currently dev/v0.9). Portwing uses an integration-branch flow: bug fixes and features branch from and merge back into the current dev/* branch, which is promoted to main immediately before each release cut — main itself only ever advances by those promotions.

Development loop

go build ./...                  # Build
go test -race ./...             # All tests with race detector
gofmt -l .                      # List unformatted files (must be empty)
golangci-lint run               # Lint
go vet ./...                    # Vet

Running integration tests

Integration tests require a running Docker daemon:

go test -tags=integration -race -timeout=10m ./internal/integration/

Drydock compatibility

A compatibility verification script is included:

# Verify protocol compatibility with the Drydock platform
./scripts/drydock-compat-check.sh

Fuzz testing (Tier 0 smoke)

Each fuzz target runs for 5 seconds locally:

go test -run=^$ -fuzz=^FuzzParsePHC$           -fuzztime=5s ./internal/server/
go test -run=^$ -fuzz=^FuzzParseTrustedProxies$ -fuzztime=5s ./internal/server/
go test -run=^$ -fuzz=^FuzzParseImageRef$       -fuzztime=5s ./internal/adapter/
go test -run=^$ -fuzz=^FuzzParseLabels$         -fuzztime=5s ./internal/adapter/drydock/
go test -run=^$ -fuzz=^FuzzMCPHandler$          -fuzztime=5s ./internal/mcp/
go test -run=^$ -fuzz=^FuzzEnvelope$            -fuzztime=5s ./internal/protocol/

Code style

  • Formatter: gofmt (enforced by CI)
  • Linter: golangci-lint
  • Follow Effective Go conventions
  • Line length: no hard limit; use judgment
  • Zero new dependencies: stdlib + golang.org/x/crypto + github.com/google/uuid + github.com/gorilla/websocket. PRs adding deps require a strong justification.

Commit convention

We use Conventional Commits — no emoji:

<type>(<scope>): <description>

Scope is optional. A ! before the colon marks a breaking change (feat(api)!: drop v1 tokens). A BREAKING CHANGE: footer is valid Conventional Commit syntax, but release versioning currently reads only the subject-line ! for major bumps.

TypeUse
featNew feature
fixBug fix
docsDocumentation
styleFormatting only
refactorRefactor (no feature/fix)
perfPerformance improvement
testTests
buildBuild system / dependencies (e.g. build(deps): bump gorilla/websocket)
ciCI / deployment config (e.g. ci(deploy): add release-cut concurrency guard)
choreEverything else / tooling / config (e.g. chore(config): tune lefthook timeouts)
revertReverting a previous commit

Example: feat(auth): add Ed25519 enrollment

Multi-change commits: lead type on first line, bulleted sub-changes in body. Reference Linear issues in footer as Fixes: LIN-XXX.

Pull request guidelines

  • Target the active integration branch (currently dev/v0.9) for all PRs, bug fixes and features alike. Never target main directly; it advances only through release promotions.
  • Keep PRs focused — one feature or fix per PR.
  • Include tests for non-trivial changes.
  • Update CHANGELOG.md under [Unreleased] for user-visible changes.
  • All CI checks must pass before merge.

Security

Please report security vulnerabilities privately via GitHub Security Advisoriesdo NOT open a public issue. See SECURITY.md for the full policy.