This document outlines the Go coding standards and conventions used in the Pocket project. These standards are automatically enforced through our formatting tools and Claude Code hooks.
The Pocket project uses automatic Go formatting that runs whenever files are created or modified through Claude Code. This ensures consistent code style without manual intervention.
- Code formatting - Standard Go formatting via
gofmt - Import organization - Imports are grouped and sorted via
goimports - Comment punctuation - Single-line comments starting with a capital letter automatically get periods added
- Linting fixes - Various auto-fixable issues are resolved via
golangci-lint
When you save or modify a Go file:
gofmtformats the code structuregoimportsorganizes imports with local packages grouped separately- Comments are checked and periods added where appropriate
- Auto-fixable linting issues are resolved
package builtin
import (
// Standard library imports
"context"
"fmt"
"time"
// Third-party imports
"github.com/some/external/package"
// Local imports (automatically grouped)
"github.com/agentstation/pocket"
"github.com/agentstation/pocket/yaml"
)- Package declaration
- Import statements (automatically organized)
- Constants
- Types
- Variables
- Functions (exported first, then unexported)
- Use meaningful, descriptive names
- Avoid abbreviations unless widely understood (e.g.,
ctxfor context) - Prefer clarity over brevity
// Interfaces - "er" suffix for single-method interfaces
type NodeBuilder interface { ... }
// Structs - Noun or noun phrase
type HTTPNodeBuilder struct { ... }
// Functions - Verb or verb phrase
func ValidateNodeConfig() error { ... }
// Constants - MixedCaps or mixedCaps
const DefaultTimeout = 30 * time.Second
const maxRetries = 3Every package should have a package comment:
// Package builtin provides the built-in node implementations for Pocket.
package builtinAll exported types and functions must have comments starting with the name:
// NodeBuilder creates nodes and provides metadata.
type NodeBuilder interface { ... }
// Build creates a node from a definition.
func (b *HTTPNodeBuilder) Build(def *yaml.NodeDefinition) (pocket.Node, error) { ... }- Comments are automatically formatted to end with periods
- Use complete sentences for doc comments
- For inline comments, be concise but clear
// ValidateNodeConfig validates a node configuration against its schema.
func ValidateNodeConfig(meta *NodeMetadata, config map[string]interface{}) error {
// No schema defined, skip validation
if len(meta.ConfigSchema) == 0 {
return nil
}
// ... rest of function
}- Error messages should be lowercase
- Don't end with punctuation
- Include context when wrapping errors
if err != nil {
return fmt.Errorf("failed to parse template: %w", err)
}Always check errors immediately:
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read file: %w", err)
}Test files should be named *_test.go and placed in the same package:
// builders_test.go
package builtin
func TestHTTPNodeBuilder(t *testing.T) { ... }- Test the happy path first
- Test edge cases
- Test error conditions
- Use table-driven tests for multiple scenarios
func TestValidateNodeConfig(t *testing.T) {
t.Run("valid config", func(t *testing.T) {
// Happy path test
})
t.Run("missing required field", func(t *testing.T) {
// Error condition test
})
}Always pass context as the first parameter:
func (b *HTTPNodeBuilder) Build(ctx context.Context, def *yaml.NodeDefinition) (pocket.Node, error) {
// Use context for cancellation and timeouts
req, err := http.NewRequestWithContext(ctx, method, url, body)
// ...
}- Always ensure goroutines can be cancelled
- Use sync.WaitGroup or channels for coordination
- Handle panics in goroutines
When the size is known, preallocate slices:
// Good
conditions := make([]condition, 0, len(conditionsRaw))
// Avoid
var conditions []conditionUse bytes.Buffer or strings.Builder for string concatenation:
var sb strings.Builder
sb.WriteString("prefix")
sb.WriteString(value)
result := sb.String()In rare cases where automatic formatting needs to be disabled:
Use //nolint directives sparingly and with explanations:
//nolint:gocyclo // Complex configuration parsing requires many conditions
func ComplexFunction() { ... }There's no way to disable gofmt, but you can use build tags to exclude files:
//go:build ignore
package mainWhile formatting happens automatically, you can also run it manually:
# Basic formatting
make fmt
# Comprehensive formatting with all tools
make fmt-all
# Check formatting without making changes
make fmt-checkOur CI pipeline enforces these standards by:
- Running
make fmt-checkto ensure code is formatted - Running
make lintto check for linting issues - Failing the build if formatting is needed
When contributing to Pocket:
- Let the automatic formatting handle style
- Focus on writing clear, idiomatic Go code
- Add meaningful comments and documentation
- Write comprehensive tests
- Follow the patterns established in the codebase
The automatic formatting ensures consistency, so you can focus on the logic and functionality of your code rather than formatting details.