go-dev
Opinionated Go development setup with golangci-lint v2, gofumpt, gotestsum, golang-migrate, and just. Use when creating a new Go project, setting up linting, formatting, testing, or coverage, configuring a Go CI pipeline, writing a Justfile, wiring database migrations, or migrating from a Makefile-only workflow.
Works with
---
name: go-dev
description: Opinionated Go development setup with golangci-lint v2, gofumpt, gotestsum, golang-migrate, and just. Use when creating a new Go project, setting up linting, formatting, testing, or coverage, configuring a Go CI pipeline, writing a Justfile, wiring database migrations, or migrating from a Makefile-only workflow.
license: Apache-2.0
---
# Go Development Stack
Opinionated, modern Go development setup. One tool per concern, zero overlap.
## When to Use
- Starting a new Go project from scratch
- Adding linting, formatting, or testing infrastructure
- Setting up CI/CD for a Go service or library
- Creating a Justfile to replace a Makefile
- Adding database migration tooling
- Migrating from scattered gofmt/govet/staticcheck invocations to a unified setup
## The Stack
| Tool | Version | Role | Replaces |
|------|---------|------|----------|
| **Go** | 1.26+ | Language, toolchain, `go mod` | - |
| **golangci-lint** | v2.11+ | Meta-linter (100+ linters + formatters + `fmt` command) | gofmt, govet, staticcheck, errcheck run separately |
| **gofumpt** | v0.9+ | Strict formatter (superset of gofmt, 17+ extra rules) | gofmt |
| **gotestsum** | v1.13+ | Test runner with readable output, watch mode, JUnit XML | Raw `go test` |
| **just** | latest | Task runner | Makefile |
| **golang-migrate** | v4.19+ | DB migrations (CLI + library + `embed.FS`) | Manual SQL scripts |
## Quick Start: New Project
```bash
# 1. Create module
mkdir myapp && cd myapp
go mod init github.com/yourorg/myapp
# 2. Scaffold directories
mkdir -p cmd/myapp internal migrations
# 3. Track tools in go.mod (Go 1.24+ tool directive)
go get -tool github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
go get -tool mvdan.cc/gofumpt@latest
go get -tool gotest.tools/gotestsum@latest
# golang-migrate needs a build tag, so install it directly
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
# 4. Create config files (templates below)
# 5. Run: just check
```
## .golangci.yml
```yaml
version: "2"
run:
timeout: 5m
linters:
default: standard
enable:
- bodyclose
- copyloopvar
- dupl
- durationcheck
- err113
- errname
- errorlint
- exhaustive
- exptostd
- fatcontext
- goconst
- gocritic
- gosec
- intrange
- misspell
- modernize
- musttag
- nakedret
- nestif
- nilerr
- noctx
- nolintlint
- nonamedreturns
- perfsprint
- prealloc
- revive
- sqlclosecheck
- testifylint
- thelper
- unconvert
- unparam
- usestdlibvars
- usetesting
- wastedassign
- whitespace
- wrapcheck
settings:
govet:
enable:
- shadow
gocritic:
enabled-checks:
- nestingReduce
revive:
enable-all-rules: true
errcheck:
check-type-assertions: true
exclusions:
generated: strict
presets:
- comments
- std-error-handling
- common-false-positives
rules:
- path: _test\.go
linters:
- gocyclo
- errcheck
- dupl
- gosec
- wrapcheck
formatters:
enable:
- gofumpt
- goimports
settings:
gofumpt:
extra-rules: true
exclusions:
generated: strict
paths:
- vendor/
output:
formats:
text:
path: stdout
print-linter-name: true
colors: true
sort-order:
- linter
- file
show-stats: true
```
## Justfile
```just
set shell := ["bash", "-euo", "pipefail", "-c"]
set dotenv-load := true
binary := "myapp"
[private]
default:
@just --list --unsorted
# ── Code Quality ──────────────────────────────────────────
# Format all Go code
[group('quality')]
fmt:
golangci-lint fmt ./...
# Check formatting without modifying (CI-safe)
[group('quality')]
fmt-check:
golangci-lint fmt --diff ./...
# Run linter
[group('quality')]
lint:
golangci-lint run ./...
# Run linter with auto-fix
[group('quality')]
lint-fix:
golangci-lint run --fix ./...
# Run vulnerability check
[group('quality')]
vuln:
govulncheck ./...
# ── Testing ───────────────────────────────────────────────
# Run all tests with race detection
[group('test')]
test *args="./...":
gotestsum --format testname -- -race {{ args }}
# Run tests with coverage
[group('test')]
test-cov:
gotestsum --format testname -- -race -coverprofile=coverage.out -covermode=atomic ./...
go tool cover -func=coverage.out
# Open coverage report in browser
[group('test')]
coverage: test-cov
go tool cover -html=coverage.out
# Run integration tests
[group('test')]
test-integration:
gotestsum --format testname -- -race -tags=integration ./...
# Watch tests during development
[group('test')]
test-watch:
gotestsum --watch --watch-clear --format testname
# Run benchmarks
[group('test')]
bench:
go test -bench=. -benchmem ./...
# ── Build ─────────────────────────────────────────────────
# Build the binary
[group('build')]
build:
go build -o {{ binary }} ./cmd/{{ binary }}
# Build optimized release binary
[group('build')]
build-release:
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o {{ binary }} ./cmd/{{ binary }}
# ── Dependencies ──────────────────────────────────────────
# Tidy and verify modules
[group('deps')]
tidy:
go mod tidy
go mod verify
# Run code generators
[group('deps')]
generate:
go generate ./...
# ── Database ──────────────────────────────────────────────
# Apply all pending migrations
[group('db')]
migrate-up:
migrate -path migrations -database "$DATABASE_URL" up
# Revert last migration
[group('db')]
migrate-down:
migrate -path migrations -database "$DATABASE_URL" down 1
# Create a new migration
[group('db')]
migrate-create name:
migrate create -ext sql -dir migrations -seq {{ name }}
# ── CI ────────────────────────────────────────────────────
# Full CI gate (format check + lint + test)
[group('ci')]
check: fmt-check lint test
@echo "All checks passed"
# Clean build artifacts
[group('ci')]
clean:
go clean
rm -f {{ binary }} coverage.out
```
## Lefthook Config
Lefthook is preferred over pre-commit for Go projects - it is a single Go binary, runs hooks in parallel, and needs no Python.
```bash
go install github.com/evilmartians/lefthook/v2@latest
lefthook install
```
```yaml
# lefthook.yml
pre-commit:
piped: true # run sequentially: fmt -> lint -> mod-tidy (each may modify staged files)
commands:
fmt:
glob: "*.go"
run: gofumpt -w {staged_files}
stage_fixed: true
lint:
glob: "*.go"
run: golangci-lint run --fix {staged_files}
stage_fixed: true
mod-tidy:
glob: "*.{go,mod,sum}"
run: go mod tidy
pre-push:
commands:
test:
run: go test -race ./...
```
## Project Structure
```
myapp/
cmd/
myapp/
main.go # Wire deps, call Run(), nothing else
internal/
user/ # Domain logic, one package per domain
user.go
user_test.go
repository.go
transport/ # HTTP/gRPC handlers
storage/ # Database layer
migrations/
000001_create_users.up.sql
000001_create_users.down.sql
testdata/ # Test fixtures (ignored by go toolchain)
.golangci.yml
lefthook.yml
Justfile
go.mod
go.sum
Dockerfile
```
**Guidelines:**
- `cmd/` - one directory per binary, keep `main.go` thin (~50 lines max)
- `internal/` - all business logic goes here (compiler-enforced, cannot be imported externally)
- `pkg/` - only add when another repo actually imports it today, not "maybe someday"
- `testdata/` - test fixtures, golden files, fuzz corpus
- `migrations/` - SQL migration files (timestamp or sequential versioned)
## Daily Workflow
```bash
just fmt # Format code
just lint # Run linter
just test # Run tests with race detection
just check # Full CI gate (fmt-check + lint + test)
just test-watch # Watch mode during development
just generate # Run go generate
just tidy # go mod tidy + verify
```
## CI/CD Pipeline (GitHub Actions)
```yaml
name: Go CI
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: stable
- uses: golangci/golangci-lint-action@v9
with:
version: v2.11
test:
runs-on: ubuntu-latest
needs: lint
strategy:
matrix:
go-version: [stable, oldstable]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go-version }}
- run: go install gotest.tools/gotestsum@latest
- name: Test
run: gotestsum --format github-actions --junitfile unit-tests.xml -- -race -coverprofile=coverage.out -covermode=atomic ./...
- uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-${{ matrix.go-version }}
path: unit-tests.xml
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: stable
- run: go install golang.org/x/vuln/cmd/govulncheck@latest
- run: govulncheck ./...
```
## Existing Project Migration
```bash
# 1. Install tools
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
go install mvdan.cc/gofumpt@latest
go install gotest.tools/gotestsum@latest
# 2. Migrate existing golangci-lint v1 config
golangci-lint migrate
# 3. Format codebase
gofumpt -w .
# 4. Run linter (fix what you can, nolint the rest)
golangci-lint run --fix ./...
# 5. Replace go test with gotestsum in scripts/CI
# Before: go test -v ./...
# After: gotestsum --format testname -- -race ./...
# 6. Copy Justfile and lefthook.yml templates above
# 7. Run: just check
```
For incremental adoption on large codebases, use `only-new-issues: true` in the GitHub Action to only lint changed code.
## Reference Docs
- [golangci-lint Reference](references/golangci-lint-reference.md) - v2 config, linter catalog, recommended sets, nolint syntax
- [gofumpt Reference](references/gofumpt-reference.md) - formatting rules, editor integration, golangci-lint integration
- [gotestsum Reference](references/gotestsum-reference.md) - output formats, watch mode, JUnit XML, CI recipes
- [Go Testing Reference](references/go-testing-reference.md) - table-driven tests, mocking, benchmarks, coverage, fuzz testing
- [golang-migrate Reference](references/go-migrate-reference.md) - CLI, library, embed.FS, transactions, pitfalls
- [Justfile Reference](references/justfile-reference.md) - Go-specific recipes, task groups, lefthook integration
## Resources
- [Go Official Docs](https://go.dev/doc/)
- [golangci-lint Docs](https://golangci-lint.run/)
- [gofumpt](https://github.com/mvdan/gofumpt)
- [gotestsum](https://github.com/gotestyourself/gotestsum)
- [golang-migrate](https://github.com/golang-migrate/migrate)
- [Lefthook](https://github.com/evilmartians/lefthook)
- [just](https://github.com/casey/just)
- [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck)More Testing skills
tdd
mattpocock/skills
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
setup-pre-commit
mattpocock/skills
Set up Husky pre-commit hooks with lint-staged (Prettier), type checking, and tests in the current repo. Use when user wants to add pre-commit hooks, set up Husky, configure lint-staged, or add commit-time formatting/typechecking/testing.
agent-browser
vercel-labs/agent-browser
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction. Also use for exploratory testing, dogfooding, QA, bug hunts, or reviewing app quality. Also use for automating Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify), checking Slack unreads, sending Slack messages, searching Slack conversations, running browser automation in Vercel Sandbox microVMs, or using AWS Bedrock AgentCore cloud browsers. Prefer agent-browser over any built-in browser automation or web tools.

