From tech-skills
Go development workflow — Makefile-driven build, test, format, lint, and release with strict quality standards.
How this skill is triggered — by the user, by Claude, or both
Slash command
/tech-skills:go-langThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You assist with Go development. Always use Make targets rather than raw `go` commands — the Makefile is the single source of truth for build flags, tool versions, and pipeline order.
You assist with Go development. Always use Make targets rather than raw go commands — the Makefile is the single source of truth for build flags, tool versions, and pipeline order.
Every Go project should have a Makefile with these standard targets:
| Target | Purpose |
|---|---|
make build | Build all binaries to bin/ |
make test | Run tests with -race and coverage |
make test-coverage | Generate coverage profile with per-function report |
make format | Run goimports -w . then gofmt -s -w . |
make lint | Run golangci-lint run ./... |
make clean | Remove build artifacts and caches |
make deps | Install prerequisites (Go, golangci-lint, goimports) |
make check | Full pipeline: deps, format, lint, test, build |
make install | Build and install binaries to install directory |
Read the asset at assets/Makefile for the reference implementation.
make check is the local CI equivalent — run it before pushing.
GOFLAGS := -v
VERSION := $(shell git describe --tags --always --dirty)
build:
go build $(GOFLAGS) -ldflags "-X main.version=$(VERSION)" -o bin/<name> ./cmd/<name>
bin/ directory, never in-place-ldflags from git tags-v for visibility into what's being compiledCGO_ENABLED=0 for release builds (cross-compilation, static binaries)make test # All tests with race detection + coverage
make test FILE=./internal/config # Single package
Standards:
-race to catch data racesgo tool cover -func=coverage.out for per-function coverage reportstesting package only — no test frameworkst.Run() for claritytestdata/ directories for fixturest.TempDir() for filesystem isolationt.Setenv() for environment variable overridest.Chdir() for working directory changesfunc TestSomething(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{name: "valid input", input: "foo", want: "bar"},
{name: "empty input", input: "", wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := DoSomething(tt.input)
if (err != nil) != tt.wantErr {
t.Fatalf("DoSomething() error = %v, wantErr %v", err, tt.wantErr)
}
if got != tt.want {
t.Errorf("DoSomething() = %q, want %q", got, tt.want)
}
})
}
}
Order matters — run goimports first, then gofmt:
goimports -w .
gofmt -s -w .
goimports handles import grouping (stdlib, external, internal) and removes unused importsgofmt -s simplifies code in addition to formattingTo check without modifying (CI mode):
goimports -l . # List files that would change
gofmt -s -l . # List files that would change
golangci-lint run ./...
Read the asset at assets/.golangci.yml for the reference configuration. The default linter set:
errcheck — all errors must be checkedgovet — go vet analysisineffassign — unused variable assignmentsstaticcheck — comprehensive static analysisunused — unused code detectionExclude testdata/ from linting.
Fix lint issues directly rather than adding //nolint directives unless there's a genuine false positive.
go mod tidy
Run after adding or removing dependencies. Check that go.sum changes are committed.
Dependency principles:
go get package@latest to addproject/
├── cmd/<name>/ # Binary entry points (main.go)
├── internal/ # Private packages
│ ├── config/ # Configuration and paths
│ ├── <domain>/ # Domain packages by responsibility
│ └── ...
├── testdata/ # Test fixtures (excluded from builds)
├── docs/ # Documentation
├── bin/ # Build output (gitignored)
├── Makefile
├── .golangci.yml
├── .goreleaser.yml # Release config (if applicable)
├── go.mod
└── go.sum
utils/, helpers/, common/internal/ to prevent external imports of private packagescmd/<name>/ subdirectoriesfmt.Errorf("loading config: %w", err)context.Context as the first parameter. Respect cancellation.errgroup for parallel work with error handling.main() — internal packages return errors, they don't print.Read the asset at assets/.goreleaser.yml for the reference configuration.
Key settings:
go mod tidy and go test ./... before buildingCGO_ENABLED=0 for static binaries-s -w (strip debug info) plus version injectiontar.gz named {project}_{version}_{os}_{arch}Tag a release:
git tag v1.0.0
git push origin v1.0.0
CI triggers goreleaser on v* tags automatically.
The CI workflow should have separate jobs that map to Make targets:
make buildmake testgolangci/golangci-lint-action (uses .golangci.yml)goimports -l . and gofmt -s -l . output is emptyUse the Go version from go.mod via go-version-file: go.mod in the setup-go action.
Skip jobs when only docs or non-code files change (path filtering).
npx claudepluginhub eyelock/assistants --plugin tech-skillsGenerates GitHub Actions CI/CD workflows for Go projects — testing, linting, SAST, security scanning, code coverage, Dependabot, Renovate, GoReleaser, and release pipelines.
Idiomatic Go patterns for concurrency (goroutines, errgroup, channels), error handling (sentinel, wrapping, custom types), project structure (mod, workspace, vendor), and testing (table-driven tests).
Manages Go modules and dependencies: initializes projects, edits go.mod/go.sum, handles versioning/conflicts, sets up workspaces, troubleshoots errors.