skills/golang-conventions/SKILL.md
Go coding standards for Go 1.21+, including idioms, error handling, testing patterns, concurrency, and golangci-lint configuration. Use when writing, reviewing, or refactoring Go code, working with goroutines, or setting up Go projects.
npx skillsauth add rory-data/copilot golang-conventionsInstall 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.
gofmt and goimports (automatic via gopls)golangci-lint with comprehensive linters enabledt.Run()errors.Is() and errors.As()// Package names: lowercase, single word, no underscores
package user
// Exported identifiers: PascalCase
type UserService struct {}
func NewUserService() *UserService {}
// Unexported identifiers: camelCase
type userData struct {}
func validateEmail(email string) error {}
// Interface names: single method interfaces end with -er
type Reader interface { Read(p []byte) (n int, err error) }
type UserRepository interface { /* multiple methods */ }
// Acronyms: consistent case (HTTP, ID, URL, not Http, Id, Url)
type HTTPServer struct {}
var userID int64
// 1. Package declaration
package user
// 2. Import statements (grouped: stdlib, external, internal)
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
"myapp/internal/database"
)
// 3. Constants
const (
MaxRetries = 3
DefaultTimeout = 30 * time.Second
)
// 4. Variables (avoid package-level vars, prefer const or functions)
var (
ErrUserNotFound = errors.New("user not found")
)
// 5. Types
type User struct {
ID uuid.UUID
Email string
CreatedAt time.Time
}
// 6. Functions and methods (exported first, then unexported)
// Generics for type-safe collections
func Map[T, U any](slice []T, fn func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
// min/max built-ins
func clamp(val, minVal, maxVal int) int {
return max(minVal, min(val, maxVal))
}
// clear() built-in for maps and slices
func resetCache(cache map[string]any) {
clear(cache)
}
// slices package for common operations
import "slices"
sorted := slices.Clone(original)
slices.Sort(sorted)
// Good: Clear error handling
func GetUser(ctx context.Context, id uuid.UUID) (*User, error) {
user, err := db.FindUserByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to get user %s: %w", id, err)
}
return user, nil
}
// Use errors.Is() and errors.As() for sentinel errors
if errors.Is(err, ErrUserNotFound) {
return handleNotFound()
}
var validationErr *ValidationError
if errors.As(err, &validationErr) {
return handleValidation(validationErr)
}
// Custom error types for rich error information
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
// Wrap errors to preserve context
func processUser(ctx context.Context, id uuid.UUID) error {
user, err := GetUser(ctx, id)
if err != nil {
return fmt.Errorf("process user failed: %w", err)
}
if err := validateUser(user); err != nil {
return fmt.Errorf("validation failed for user %s: %w", id, err)
}
return nil
}
func TestValidateEmail(t *testing.T) {
tests := []struct {
name string
email string
wantErr bool
}{
{
name: "valid email",
email: "[email protected]",
wantErr: false,
},
{
name: "missing at sign",
email: "userexample.com",
wantErr: true,
},
{
name: "empty email",
email: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateEmail(tt.email)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateEmail() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
// testdata/ directory for fixtures
// helper_test.go for test utilities
func setupTestDB(t *testing.T) *sql.DB {
t.Helper()
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("failed to open test db: %v", err)
}
t.Cleanup(func() {
db.Close()
})
return db
}
// Use testify for assertions (optional but popular)
import "github.com/stretchr/testify/assert"
func TestUserCreation(t *testing.T) {
user := NewUser("[email protected]")
assert.NotNil(t, user.ID)
assert.Equal(t, "[email protected]", user.Email)
assert.False(t, user.CreatedAt.IsZero())
}
// Benchmark functions
func BenchmarkValidateEmail(b *testing.B) {
email := "[email protected]"
b.ResetTimer()
for i := 0; i < b.N; i++ {
ValidateEmail(email)
}
}
// Example functions (appear in godoc)
func ExampleValidateEmail() {
err := ValidateEmail("[email protected]")
fmt.Println(err == nil)
// Output: true
}
myproject/
├── cmd/ # Main applications
│ └── server/
│ └── main.go
├── internal/ # Private application code
│ ├── user/
│ │ ├── user.go
│ │ ├── user_test.go
│ │ ├── repository.go
│ │ └── service.go
│ └── database/
│ └── postgres.go
├── pkg/ # Public libraries (optional)
│ └── utils/
├── api/ # API definitions (OpenAPI, protobuf)
├── web/ # Web assets (if applicable)
├── scripts/ # Build and automation scripts
├── deployments/ # Deployment configs (Docker, k8s)
├── test/ # Additional test data
├── go.mod
├── go.sum
├── Makefile # Common tasks
└── README.md
// internal/user/user.go - Domain types
package user
type User struct {
ID uuid.UUID
Email string
}
// internal/user/repository.go - Data access
package user
type Repository interface {
Create(ctx context.Context, user *User) error
FindByID(ctx context.Context, id uuid.UUID) (*User, error)
}
// internal/user/service.go - Business logic
package user
type Service struct {
repo Repository
}
func NewService(repo Repository) *Service {
return &Service{repo: repo}
}
# Initialise module
go mod init github.com/username/project
# Add dependencies (automatically)
go get github.com/google/uuid@latest
# Tidy up (remove unused, add missing)
go mod tidy
# Verify integrity
go mod verify
# Vendor dependencies (optional)
go mod vendor
// go.mod with specific versions
module github.com/username/project
go 1.21
require (
github.com/google/uuid v1.5.0
github.com/lib/pq v1.10.9
)
require (
// Indirect dependencies
golang.org/x/crypto v0.17.0 // indirect
)
Use goroutines with context cancellation, sync.WaitGroup for coordination, sync.Once for singletons, and sync.Pool for buffer reuse. See references/concurrency.md for full patterns.
Preallocate slices when size is known, use strings.Builder for concatenation, and profile with pprof. See references/performance.md.
Use golangci-lint with the recommended linter set. See references/linting.md for the full .golangci.yml configuration.
Constructor pattern, functional options, interface segregation, and middleware chaining. See references/patterns.md.
Avoid: panicking in library code, ignoring errors, naked returns in long functions, global mutable state. See references/patterns.md for annotated examples.
Makefile targets, build tags for integration tests, essential tools (goimports, golangci-lint, air), and VS Code settings. See references/build-tools.md.
// Use crypto/rand for random values
import "crypto/rand"
func generateToken() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}
// Validate and sanitise inputs
func sanitiseUserInput(input string) string {
return html.EscapeString(strings.TrimSpace(input))
}
// Use prepared statements for SQL
stmt, err := db.PrepareContext(ctx, "SELECT * FROM users WHERE id = $1")
defer stmt.Close()
// Set timeouts on contexts
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
references/concurrency.md — Goroutines, channels, sync primitivesreferences/performance.md — Memory optimisation, profilingreferences/linting.md — Full .golangci.yml configurationreferences/patterns.md — Constructor, options, middleware, anti-patternsreferences/build-tools.md — Makefile, build tags, essential tools, IDE configtools
Queries, manages, and troubleshoots Apache Airflow using the af CLI. Covers listing DAGs, triggering runs, reading task logs, diagnosing failures, debugging DAG import errors, checking connections, variables, pools, and monitoring health. Also routes to sub-skills for writing DAGs, debugging, deploying, and migrating Airflow 2 to 3. Use when user mentions "Airflow", "DAG", "DAG run", "task log", "import error", "parse error", "broken DAG", or asks to "trigger a pipeline", "debug import errors", "check Airflow health", "list connections", "retry a run", or any Airflow operation. Do NOT use for warehouse/SQL analytics on Airflow metadata tables — use analyzing-data instead.
tools
Build Airflow 3.1+ plugins that embed FastAPI apps, custom UI pages, React components, middleware, macros, and operator links directly into the Airflow UI. Use this skill whenever the user wants to create an Airflow plugin, add a custom UI page or nav entry to Airflow, build FastAPI-backed endpoints inside Airflow, serve static assets from a plugin, embed a React app in the Airflow UI, add middleware to the Airflow API server, create custom operator extra links, or call the Airflow REST API from inside a plugin. Also trigger when the user mentions AirflowPlugin, fastapi_apps, external_views, react_apps, plugin registration, or embedding a web app in Airflow 3.1+. If someone is building anything custom inside Airflow 3.1+ that involves Python and a browser-facing interface, this skill almost certainly applies.
data-ai
Use when the user needs human-in-the-loop workflows in Airflow (approval/reject, form input, or human-driven branching). Covers ApprovalOperator, HITLOperator, HITLBranchOperator, HITLEntryOperator, HITLTrigger. Requires Airflow 3.1+. Does not cover AI/LLM calls (see airflow-ai).
development
Detects and fixes common code smells during review or refactoring. Invoke whenever reviewing code for quality issues, before merging a PR, when refactoring legacy code, or when the user asks about code quality, anti-patterns, or technical debt. Detects: over-abstraction, complex inheritance, large functions, tight coupling, hidden dependencies, magic numbers, boolean traps, swallowed exceptions, global state, and duplicate code. Provides specific fixes with before/after examples. Also invoke when someone says "review this code", "is this clean?", "can I improve this?", "this feels messy", or "find problems in my code".