Go Development Guidelines
March 14, 2026 · View on GitHub
This guide outlines the coding standards for the floop project.
1. Project Structure
cmd/floop/— CLI entry pointinternal/— All application packages. Runls internal/for current list.docs/— Documentation.floop/— Learned behaviors (JSONL + manifest tracked; DB + audit.jsonl gitignored).beads/— Issue tracking (version controlled)
2. Code Style
Formatting
- Always use
gofmtorgoimports - Run
go fmt ./...before committing
Naming
CamelCasefor exported identifierscamelCasefor unexported identifiers- Short but descriptive names (
ctxfor context,bfor behavior in loops) - Package names: short, lowercase, singular (
models,store,learning)
Error Handling
- Return errors as the last return value
- Check errors immediately after function calls
- Wrap errors with context using
fmt.Errorf("doing X: %w", err) - Never panic except for truly unrecoverable initialization errors
// Good
result, err := doSomething()
if err != nil {
return fmt.Errorf("failed to do something: %w", err)
}
// Bad
result, _ := doSomething() // Don't ignore errors
3. Testing
Requirements
- All packages must have
*_test.gofiles - Use table-driven tests for functions with multiple input cases
- Test both success and error paths
Running Tests
go test ./... # Run all tests
go test ./internal/models/... # Run specific package
go test -v -cover ./... # Verbose with coverage
Test File Structure
func TestFunctionName(t *testing.T) {
tests := []struct {
name string
input InputType
want OutputType
wantErr bool
}{
{"valid input", validInput, expectedOutput, false},
{"invalid input", invalidInput, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := FunctionName(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("got %v, want %v", got, tt.want)
}
})
}
}
4. Interfaces
Design Principles
- Define interfaces where they're used, not where they're implemented
- Keep interfaces small (1-3 methods when possible)
- Use interfaces for testability (e.g.,
GraphStoreinterface enablesInMemoryGraphStorefor tests)
// Good: interface defined in consumer package
type Store interface {
GetNode(ctx context.Context, id string) (*Node, error)
}
// Implementation in store package
type InMemoryGraphStore struct { ... }
func (s *InMemoryGraphStore) GetNode(ctx context.Context, id string) (*Node, error) { ... }
5. Context Usage
- Pass
context.Contextas the first parameter - Use context for cancellation and timeouts, not for passing data
- Never store context in structs
// Good
func (s *Store) GetNode(ctx context.Context, id string) (*Node, error)
// Bad
func (s *Store) GetNode(id string) (*Node, error) // Missing context
6. JSON/YAML Struct Tags
- All serializable structs must have explicit tags
- Use
omitemptyfor optional fields - Match JSON field names to the canonical API format
type Behavior struct {
ID string `json:"id" yaml:"id"`
Name string `json:"name" yaml:"name"`
When map[string]any `json:"when,omitempty" yaml:"when,omitempty"`
Confidence float64 `json:"confidence" yaml:"confidence"`
}
7. Documentation
- Add comments to all exported functions and types
- Use complete sentences starting with the identifier name
- Document non-obvious behavior, not what's obvious from the code
// Behavior represents a unit of agent behavior that can be
// activated based on context conditions.
type Behavior struct { ... }
// Matches checks if this context matches a 'when' predicate.
// It supports exact matching, array membership, and glob patterns.
func (c *ContextSnapshot) Matches(predicate map[string]any) bool { ... }
8. Dependencies
- Use
go modfor dependency management - Pin specific versions in
go.mod - Minimize external dependencies
- Current dependencies:
github.com/spf13/cobra- CLI frameworkgopkg.in/yaml.v3- YAML parsinggithub.com/lancedb/lancedb-go- LanceDB embedded vector database (requires CGO for Rust bindings)
9. CLI Patterns (Cobra)
Command Structure
func newXxxCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "xxx",
Short: "One line description",
Long: `Longer description with examples.`,
RunE: func(cmd *cobra.Command, args []string) error {
// Implementation
return nil
},
}
cmd.Flags().String("flag", "", "description")
return cmd
}
JSON Output
All commands must support --json flag for agent consumption:
jsonOut, _ := cmd.Flags().GetBool("json")
if jsonOut {
json.NewEncoder(os.Stdout).Encode(result)
} else {
// Human-readable output
}
10. Concurrency
- Use channels for goroutine communication
- Use
sync.Mutexfor protecting shared state - Avoid global mutable state
- Document goroutine safety in struct comments