Coding Standards¶
This guide covers the coding conventions and standards used in HoloMUSH development.
Development Principles¶
Test-Driven Development¶
Write tests before implementation:
- Write a failing test
- Implement the minimum code to pass
- Refactor while keeping tests green
- Repeat
All tests must pass before any task is considered complete.
Spec-Driven Development¶
Work must be guided by specifications:
- Specs define requirements (
docs/specs/) - Plans define implementation approach (
docs/plans/) - Both use RFC2119 keywords for requirement levels
RFC2119 Keywords¶
Documentation uses these keywords to indicate requirement levels:
| Keyword | Meaning |
|---|---|
| MUST | Absolute requirement |
| MUST NOT | Absolute prohibition |
| SHOULD | Recommended, may ignore with justification |
| SHOULD NOT | Not recommended |
| MAY | Optional |
Go Conventions¶
Idiomatic Go¶
Follow standard Go idioms:
- Accept interfaces, return structs
- Errors are values - handle them explicitly
- Use context for cancellation and timeouts
- Prefer composition over inheritance
Error Handling¶
Use oops for structured errors with context:
// Wrap existing error with context
return oops.With("plugin", name).With("operation", "load").Wrap(err)
// Create new error
return oops.Errorf("validation failed").With("field", fieldName)
// At API boundaries, add error code
return oops.Code("PLUGIN_LOAD_FAILED").With("plugin", name).Wrap(err)
For logging oops errors, use pkg/errutil:
Logging¶
Use structured logging with slog:
- Log at appropriate levels (debug, info, warn, error)
- Include relevant context in log entries
- Use consistent key names across the codebase
logger.Info("plugin loaded",
"plugin", name,
"version", version,
"load_time_ms", loadTime.Milliseconds())
Naming¶
Use clear, descriptive names:
- Avoid abbreviations except well-known ones (ID, URL, HTTP)
- Package names are lowercase, single words when possible
- Use MixedCaps (PascalCase for exported, camelCase for unexported)
License Headers¶
All source files must include SPDX license headers:
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 HoloMUSH Contributors
// Package foo provides ...
package foo
Headers are auto-added by the pre-commit hook, or manually with:
Testing¶
Coverage Requirements¶
| Requirement | Threshold |
|---|---|
| Minimum per-package coverage | 80% |
| Target for core packages | 90%+ |
Verify coverage before completing work:
Test Organization¶
- Unit tests:
foo.go->foo_test.go(same directory) - Integration tests:
*_integration_test.gowith build tag
Table-Driven Tests¶
Use table-driven tests for comprehensive coverage:
func TestEventType_String(t *testing.T) {
tests := []struct {
name string
input EventType
expected string
}{
{"say event", EventTypeSay, "say"},
{"pose event", EventTypePose, "pose"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, tt.input.String())
})
}
}
Assertions¶
Use testify for assertions:
// Equality
assert.Equal(t, expected, got)
// Error checking
require.NoError(t, err)
assert.Error(t, err)
// Contains
assert.Contains(t, slice, element)
Mocking¶
Use mockery for generating mocks:
Use generated mocks in tests:
Integration Tests (BDD)¶
Integration tests use Ginkgo/Gomega for BDD-style specs:
//go:build integration
var _ = Describe("Feature Name", func() {
Describe("User story or capability", func() {
It("expected behavior in plain English", func() {
// Given/When/Then pattern
Expect(result).To(Equal(expected))
})
})
})
Run integration tests:
Build Commands¶
Use task for all build operations. Do not run Go commands directly:
task lint # Run all linters
task fmt # Format all files
task test # Run tests
task build # Build binary
task dev # Run dev server
These commands ensure consistent tooling and configuration.
Code Style¶
Simplicity¶
Prefer readable code over clever solutions:
- Write code that's easy to understand
- Avoid premature optimization
- Keep functions focused and small
Comments¶
- Do not add comments that restate what code does
- Comment the "why", not the "what"
- Use doc comments for exported functions/types
Consistency¶
Match existing project patterns:
- Follow the conventions in nearby code
- Use consistent formatting (enforced by
task fmt) - Match naming patterns used elsewhere
Directory Structure¶
api/ # Protocol definitions (protobuf)
cmd/holomush/ # Server entry point
docs/
plans/ # Implementation plans
specs/ # Specifications
reference/ # API documentation
internal/ # Private implementation
core/ # Event system, sessions, world engine
store/ # PostgreSQL implementations
wasm/ # Plugin host
pkg/ # Public plugin API
plugins/ # Core plugins
scripts/ # Build and utility scripts
test/integration/ # End-to-end test suites
Key Interfaces¶
Core interfaces that implementations must satisfy:
EventStore¶
type EventStore interface {
Append(ctx context.Context, event Event) error
Subscribe(ctx context.Context, stream string, afterID ulid.ULID) (<-chan Event, error)
Replay(ctx context.Context, stream string, afterID ulid.ULID, limit int) ([]Event, error)
LastEventID(ctx context.Context, stream string) (ulid.ULID, error)
}
AccessControl¶
type AccessControl interface {
Check(ctx context.Context, subject Subject, action string, resource Resource) (bool, error)
Grant(ctx context.Context, subject Subject, capability string) error
Revoke(ctx context.Context, subject Subject, capability string) error
}
Patterns¶
Event Sourcing¶
- All game actions produce events
- Events are immutable and ordered
- State is derived from event replay
Dependency Injection¶
- Accept interfaces in constructors
- Create implementations at composition root
- Enables testing with mocks
Context Propagation¶
- Pass context through call chains
- Use context for cancellation and timeouts
- Include tracing spans in context
Further Reading¶
- Pull Request Guide - Contribution workflow
- Architecture - System design overview
- Testing - Detailed testing guide