.agents/skills/go-developer/SKILL.md
Expert guidance for Go development, including code quality standards, testing practices, service configuration, and HTTP routing patterns.
npx skillsauth add em-jones/staccato-toolkit go-developerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill provides comprehensive guidance for Go developers working in this repository. It consolidates best practices, standards, and tooling for writing high-quality, maintainable Go code.
Go development in this repository is governed by clear standards and automated enforcement through tools and usage rules. This skill integrates those standards and provides expert guidance for common development tasks.
Key capabilities:
Key integrations:
All Go development happens within a devbox shell to ensure consistent environments:
devbox shell
This provides:
devbox.json)go version
golangci-lint version
Code quality is enforced through golangci-lint, a fast parallel Go linter that aggregates results from multiple linters.
Check code quality:
golangci-lint run ./...
Fix auto-fixable issues:
golangci-lint run --fix ./...
Lint a specific package:
golangci-lint run ./cmd/myapp/...
The linter is configured via .golangci.yml at repository root. Key settings:
[bugs, performance, style, unused] — run standard linters in parallelerrcheck)errcheck — detects unchecked error returnsineffassign — identifies unused assignmentsmisspell — catches common spelling mistakesvet — Go's built-in static analysisgolint — Go code style issuesError: "error returned but not checked"
_ = functionCall()Warning: "ineffectual assignment"
Issue: "This comparison of a constant type is always true/false"
Go testing follows Go testing standards with emphasis on clarity, coverage, and maintainability.
File placement (Go convention):
*_test.go (e.g., handler_test.go for handler.go)src/services/user/
├── handler.go
├── handler_test.go
├── repository.go
└── repository_test.go
Run all tests:
go test ./...
Run tests in a specific package:
go test ./cmd/myapp/...
Run with coverage:
go test -cover ./...
Run with detailed output:
go test -v ./...
Run tests matching a pattern:
go test -run TestHandlerLogin ./...
Table-driven test pattern (recommended):
func TestUserValidation(t *testing.T) {
tests := []struct {
name string
input string
wantErr bool
}{
{"valid email", "[email protected]", false},
{"invalid email", "not-an-email", true},
{"empty string", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateEmail(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateEmail() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
What to test:
What NOT to test:
Aim for meaningful coverage:
# Generate coverage report
go test -coverprofile=coverage.out ./...
# View coverage in browser
go tool cover -html=coverage.out
Use interfaces for testability:
// Define an interface for external dependencies
type UserRepository interface {
GetUser(ctx context.Context, id string) (*User, error)
SaveUser(ctx context.Context, user *User) error
}
// Implement a mock for testing
type MockUserRepository struct {
GetUserFunc func(ctx context.Context, id string) (*User, error)
}
func (m *MockUserRepository) GetUser(ctx context.Context, id string) (*User, error) {
if m.GetUserFunc != nil {
return m.GetUserFunc(ctx, id)
}
return nil, ErrNotFound
}
// Use the mock in tests
func TestUserService(t *testing.T) {
repo := &MockUserRepository{
GetUserFunc: func(ctx context.Context, id string) (*User, error) {
return &User{ID: id, Name: "Test"}, nil
},
}
service := NewUserService(repo)
// Test service behavior...
}
For tests that require external services:
Use build tags to separate integration tests:
//go:build integration
package mypackage
func TestDatabaseIntegration(t *testing.T) {
// Requires real database
}
Run integration tests explicitly:
go test -tags integration ./...
Ensure integration tests are skipped in fast test runs:
go test ./... # Only unit tests
New Go services use the Service Defaults package for unified observability setup (logging, tracing, metrics).
Initialize a new service with observability:
import (
"context"
"log/slog"
"os"
"github.com/staccato-toolkit/core/pkg/servicedefaults"
)
func main() {
ctx := context.Background()
// Initialize all observability signals (traces, metrics, logs)
shutdown, err := servicedefaults.Configure(ctx, "my-service")
if err != nil {
slog.Error("failed to initialize service defaults", "error", err)
os.Exit(1)
}
defer shutdown(ctx)
// Use slog.Default() for logging (no global logger variable)
slog.Info("service started", "port", 8080)
// Your service logic...
}
✓ Structured logging via log/slog
✓ Distributed tracing (OpenTelemetry)
✓ Metrics collection (Prometheus)
✓ HTTP client defaults with tracing instrumentation
✓ Graceful shutdown for all signals
✓ Non-blocking OTLP dial (service starts even if Collector unreachable)
✓ Environment-aware behavior (OTEL_SDK_DISABLED=true for dev/test)
Use log/slog exclusively (no global logger variables):
slog.Info("user created", "user_id", id, "email", email)
slog.Warn("high memory usage", "percent", 95)
slog.Error("database connection failed", "error", err)
Structured logging with key-value pairs enables filtering and correlation in observability dashboards.
Always use the HTTP client provided by servicedefaults:
client := servicedefaults.NewHTTPClient()
resp, err := client.Get(ctx, "https://api.example.com/users")
This client automatically includes:
The Chi framework is used for HTTP routing and middleware in this repository.
import "github.com/go-chi/chi/v5"
func main() {
r := chi.NewRouter()
// Global middleware
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
// Routes
r.Get("/", handleHome)
r.Post("/users", handleCreateUser)
r.Get("/users/{id}", handleGetUser)
http.ListenAndServe(":8080", r)
}
Group related routes using Chi's Group or Route methods:
r.Route("/api/v1", func(r chi.Router) {
r.Use(authMiddleware)
r.Route("/users", func(r chi.Router) {
r.Get("/", listUsers)
r.Post("/", createUser)
r.Get("/{id}", getUser)
r.Put("/{id}", updateUser)
r.Delete("/{id}", deleteUser)
})
r.Route("/posts", func(r chi.Router) {
r.Get("/", listPosts)
r.Post("/", createPost)
})
})
Create composable middleware for cross-cutting concerns:
// Custom middleware
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "missing authorization", http.StatusUnauthorized)
return
}
// Validate token...
next.ServeHTTP(w, r)
})
}
// Apply to routes
r.Route("/api", func(r chi.Router) {
r.Use(authMiddleware)
r.Get("/protected", handleProtected)
})
Extract parameters from URL paths:
r.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
user, err := getUser(id)
// Handle response...
})
Standard patterns for JSON APIs:
type UserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
type UserResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
r.Post("/users", func(w http.ResponseWriter, r *http.Request) {
var req UserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid request", http.StatusBadRequest)
return
}
user := createUser(req.Name, req.Email)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(UserResponse{
ID: user.ID,
Name: user.Name,
Email: user.Email,
})
})
Good structure:
src/services/user/
├── handler.go # HTTP handlers
├── service.go # Business logic
├── repository.go # Data access
├── entity.go # Domain models
└── user_test.go # Tests
Avoid:
src/
├── handlers/ # All handlers
├── services/ # All services
├── repositories/ # All repositories
user, auth, storage)User, Handler, Repository)DefaultTimeout)userName, isActive)Go emphasizes explicit error handling:
// DO: Always check errors
file, err := os.Open("config.yaml")
if err != nil {
return fmt.Errorf("failed to open config: %w", err)
}
defer file.Close()
// DON'T: Ignore errors
file, _ := os.Open("config.yaml")
defer file.Close()
Define errors for specific failure modes:
var (
ErrNotFound = errors.New("resource not found")
ErrUnauthorized = errors.New("unauthorized")
ErrInvalidInput = errors.New("invalid input")
)
// Use errors.Is for checking
if errors.Is(err, ErrNotFound) {
http.Error(w, "not found", http.StatusNotFound)
}
Use fmt.Errorf() with %w to preserve error chains:
user, err := repository.GetUser(ctx, id)
if err != nil {
return nil, fmt.Errorf("get user: %w", err)
}
This allows errors.Is() and errors.As() to traverse the chain.
Use structured logging with context:
slog.Debug("processing user request",
"user_id", userID,
"action", "update",
"duration_ms", elapsed,
)
For interactive debugging:
# Run with debugger
dlv debug ./cmd/myapp
# Set breakpoints
(dlv) break main.main
(dlv) continue
golangci-lint run ./...golangci-lint run --fix ./...go test -v ./...go test -cover ./...go test -v -run TestName ./...dlv test ./...go test -v ./...golangci-lint run ./...go test -cover ./...The staccato-toolkit exposes three user-facing interfaces, each implemented as a separate Go module:
| Interface | Module | Library | Pattern |
| ----------- | -------------------------- | ------------------------------------------------------ | ----------------------------------------- |
| Terminal UI | src/staccato-toolkit/tui | Bubble Tea v2 (charm.land/bubbletea/v2) | Elm Architecture (Model/Init/Update/View) |
| CLI | src/staccato-toolkit/cli | cobra (github.com/spf13/cobra) | Command tree with RunE |
| Web/PWA | src/staccato-toolkit/web | go-app v10 (github.com/maxence-charriere/go-app/v10) | Go+WASM component model |
When adding a feature to any of these three interfaces, evaluate whether the same feature should also be implemented in the other two.
This ensures a consistent user experience regardless of how users interact with the toolkit. Apply this rule whenever:
Checklist for any interface feature:
See Bubble Tea usage rules for full guidance.
Quick reference:
model structInit() tea.Cmd — return nil for no startup commandUpdate(tea.Msg) (tea.Model, tea.Cmd) — handle messages, return updated modelView() tea.View — return tea.NewView(content) for renderingtea.NewProgram(m).Run() to avoid stdout corruption:slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, nil)))
shutdown, err := servicedefaults.Configure(ctx, "Tui")
See cobra usage rules for full guidance.
Quick reference:
&cobra.Command{Use: "staccato", ...}rootCmd.AddCommand(newHelloCmd())cmd.Println (not fmt.Println) in RunE for testabilityrootCmd.SetOut(buf) and asserting buf.String()See go-app usage rules for full guidance.
Quick reference:
app.Compo and implement Render() app.UIctx.Dispatch(func(ctx app.Context) { h.field++ })go build -o server . (native) and GOARCH=wasm GOOS=js go build -o web/app.wasm . (WASM)app.RunWhenOnBrowser() is a no-op on the server; blocks in WASMProblem: "package not found" error
go mod tidy to sync go.mod and go.sumProblem: Circular import error
Problem: Tests pass locally but fail in CI
go test -race ./...), timezone assumptions, or non-deterministic behaviorProblem: "undefined: functionName" in tests
Problem: "go: updates to go.mod needed" during build
go mod tidy and commit changesProblem: Module version conflict
go mod graph to visualize dependencies and go get -u ./... to updatetools
<!--VITE PLUS START--> # Using Vite+, the Unified Toolchain for the Web This project is using Vite+, a unified toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Vite+ wraps runtime management, package management, and frontend tooling in a single global CLI called `vp`. Vite+ is distinct from Vite, but it invokes Vite through `vp dev` and `vp build`. ## Vite+ Workflow `vp` is a global binary that handles the full development lifecycle. Run `vp help` to pr
development
Guide for building performant data tables. Uses tanstack-table for table logic (sorting, filtering, pagination) and tanstack-virtual for rendering large datasets efficiently.
development
Expert guidance for building observable, expressive, and fault-tolerant TypeScript applications using the effect-ts/effect ecosystem. Covers Effect<A, E, R> type, error management, dependency injection via Layers, observability (logging, metrics, tracing), concurrency with Fibers, retry/scheduling, Schema validation, Streams, and Sinks.
tools
Complete E2E (end-to-end) and integration testing skill for TypeScript/NestJS projects using Jest, real infrastructure via Docker, and GWT pattern. ALWAYS use this skill when user needs to: **SETUP** - Initialize or configure E2E testing infrastructure: - Set up E2E testing for a new project - Configure docker-compose for testing (Kafka, PostgreSQL, MongoDB, Redis) - Create jest-e2e.config.ts or E2E Jest configuration - Set up test helpers for database, Kafka, or Redis - Configure .env.e2e environment variables - Create test/e2e directory structure **WRITE** - Create or add E2E/integration tests: - Write, create, add, or generate e2e tests or integration tests - Test API endpoints, workflows, or complete features end-to-end - Test with real databases, message brokers, or external services - Test Kafka consumers/producers, event-driven workflows - Working on any file ending in .e2e-spec.ts or in test/e2e/ directory - Use GWT (Given-When-Then) pattern for tests **REVIEW** - Audit or evaluate E2E tests: - Review existing E2E tests for quality - Check test isolation and cleanup patterns - Audit GWT pattern compliance - Evaluate assertion quality and specificity - Check for anti-patterns (multiple WHEN actions, conditional assertions) **RUN** - Execute or analyze E2E test results: - Run E2E tests - Start/stop Docker infrastructure for testing - Analyze E2E test results - Verify Docker services are healthy - Interpret test output and failures **DEBUG** - Fix failing or flaky E2E tests: - Fix failing E2E tests - Debug flaky tests or test isolation issues - Troubleshoot connection errors (database, Kafka, Redis) - Fix timeout issues or async operation failures - Diagnose race conditions or state leakage - Debug Kafka message consumption issues **OPTIMIZE** - Improve E2E test performance: - Speed up slow E2E tests - Optimize Docker infrastructure startup - Replace fixed waits with smart polling - Reduce beforeEach cleanup time - Improve test parallelization where safe Keywords: e2e, end-to-end, integration test, e2e-spec.ts, test/e2e, Jest, supertest, NestJS, Kafka, Redpanda, PostgreSQL, MongoDB, Redis, docker-compose, GWT pattern, Given-When-Then, real infrastructure, test isolation, flaky test, MSW, nock, waitForMessages, fix e2e, debug e2e, run e2e, review e2e, optimize e2e, setup e2e