mirror of
https://github.com/wshobson/agents.git
synced 2026-03-18 17:47:16 +00:00
fix(conductor): move plugin to plugins/ directory for proper discovery
Conductor plugin was at root level instead of plugins/ directory, causing slash commands to not be recognized by Claude Code.
This commit is contained in:
562
plugins/conductor/templates/code_styleguides/go.md
Normal file
562
plugins/conductor/templates/code_styleguides/go.md
Normal file
@@ -0,0 +1,562 @@
|
||||
# Go Style Guide
|
||||
|
||||
Go idioms and conventions for clean, maintainable code.
|
||||
|
||||
## gofmt and Standard Formatting
|
||||
|
||||
### Always Use gofmt
|
||||
|
||||
```bash
|
||||
# Format a single file
|
||||
gofmt -w file.go
|
||||
|
||||
# Format entire project
|
||||
gofmt -w .
|
||||
|
||||
# Use goimports for imports management
|
||||
goimports -w .
|
||||
```
|
||||
|
||||
### Formatting Rules (Enforced by gofmt)
|
||||
|
||||
- Tabs for indentation
|
||||
- No trailing whitespace
|
||||
- Consistent brace placement
|
||||
- Standardized spacing
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Explicit Error Checking
|
||||
|
||||
```go
|
||||
// Always check errors explicitly
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening file %s: %w", filename, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Don't ignore errors with _
|
||||
// Bad
|
||||
data, _ := json.Marshal(obj)
|
||||
|
||||
// Good
|
||||
data, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshaling object: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
### Error Wrapping
|
||||
|
||||
```go
|
||||
// Use %w to wrap errors for unwrapping later
|
||||
func processFile(path string) error {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading file %s: %w", path, err)
|
||||
}
|
||||
|
||||
if err := validate(data); err != nil {
|
||||
return fmt.Errorf("validating data: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check wrapped errors
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
// Handle file not found
|
||||
}
|
||||
|
||||
var validationErr *ValidationError
|
||||
if errors.As(err, &validationErr) {
|
||||
// Handle validation error
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Error Types
|
||||
|
||||
```go
|
||||
// Sentinel errors for expected conditions
|
||||
var (
|
||||
ErrNotFound = errors.New("resource not found")
|
||||
ErrUnauthorized = errors.New("unauthorized access")
|
||||
ErrInvalidInput = errors.New("invalid input")
|
||||
)
|
||||
|
||||
// Custom error type with additional context
|
||||
type ValidationError struct {
|
||||
Field string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *ValidationError) Error() string {
|
||||
return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
|
||||
}
|
||||
|
||||
// Error constructor
|
||||
func NewValidationError(field, message string) error {
|
||||
return &ValidationError{Field: field, Message: message}
|
||||
}
|
||||
```
|
||||
|
||||
## Interfaces
|
||||
|
||||
### Small, Focused Interfaces
|
||||
|
||||
```go
|
||||
// Good: Single-method interface
|
||||
type Reader interface {
|
||||
Read(p []byte) (n int, err error)
|
||||
}
|
||||
|
||||
type Writer interface {
|
||||
Write(p []byte) (n int, err error)
|
||||
}
|
||||
|
||||
// Compose interfaces
|
||||
type ReadWriter interface {
|
||||
Reader
|
||||
Writer
|
||||
}
|
||||
|
||||
// Bad: Large interfaces
|
||||
type Repository interface {
|
||||
Find(id string) (*User, error)
|
||||
FindAll() ([]*User, error)
|
||||
Create(user *User) error
|
||||
Update(user *User) error
|
||||
Delete(id string) error
|
||||
FindByEmail(email string) (*User, error)
|
||||
// Too many methods - hard to implement and test
|
||||
}
|
||||
```
|
||||
|
||||
### Accept Interfaces, Return Structs
|
||||
|
||||
```go
|
||||
// Good: Accept interface, return concrete type
|
||||
func NewUserService(repo UserRepository) *UserService {
|
||||
return &UserService{repo: repo}
|
||||
}
|
||||
|
||||
// Interface defined by consumer
|
||||
type UserRepository interface {
|
||||
Find(ctx context.Context, id string) (*User, error)
|
||||
Save(ctx context.Context, user *User) error
|
||||
}
|
||||
|
||||
// Concrete implementation
|
||||
type PostgresUserRepo struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (r *PostgresUserRepo) Find(ctx context.Context, id string) (*User, error) {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
### Interface Naming
|
||||
|
||||
```go
|
||||
// Single-method interfaces: method name + "er"
|
||||
type Reader interface { Read(p []byte) (n int, err error) }
|
||||
type Writer interface { Write(p []byte) (n int, err error) }
|
||||
type Closer interface { Close() error }
|
||||
type Stringer interface { String() string }
|
||||
|
||||
// Multi-method interfaces: descriptive name
|
||||
type UserStore interface {
|
||||
Get(ctx context.Context, id string) (*User, error)
|
||||
Put(ctx context.Context, user *User) error
|
||||
}
|
||||
```
|
||||
|
||||
## Package Structure
|
||||
|
||||
### Standard Layout
|
||||
|
||||
```
|
||||
myproject/
|
||||
├── cmd/
|
||||
│ └── myapp/
|
||||
│ └── main.go # Application entry point
|
||||
├── internal/
|
||||
│ ├── auth/
|
||||
│ │ ├── auth.go
|
||||
│ │ └── auth_test.go
|
||||
│ ├── user/
|
||||
│ │ ├── user.go
|
||||
│ │ ├── repository.go
|
||||
│ │ └── service.go
|
||||
│ └── config/
|
||||
│ └── config.go
|
||||
├── pkg/ # Public packages (optional)
|
||||
│ └── api/
|
||||
│ └── client.go
|
||||
├── go.mod
|
||||
├── go.sum
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### Package Guidelines
|
||||
|
||||
```go
|
||||
// Package names: short, lowercase, no underscores
|
||||
package user // Good
|
||||
package userService // Bad
|
||||
package user_service // Bad
|
||||
|
||||
// Package comment at top of primary file
|
||||
// Package user provides user management functionality.
|
||||
package user
|
||||
|
||||
// Group imports: stdlib, external, internal
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/lib/pq"
|
||||
|
||||
"myproject/internal/config"
|
||||
)
|
||||
```
|
||||
|
||||
### Internal Packages
|
||||
|
||||
```go
|
||||
// internal/ packages cannot be imported from outside the module
|
||||
// Use for implementation details you don't want to expose
|
||||
|
||||
// myproject/internal/cache/cache.go
|
||||
package cache
|
||||
|
||||
// This can only be imported by code in myproject/
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Test File Organization
|
||||
|
||||
```go
|
||||
// user_test.go - same package
|
||||
package user
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUserValidation(t *testing.T) {
|
||||
// Test implementation details
|
||||
}
|
||||
|
||||
// user_integration_test.go - external test package
|
||||
package user_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"myproject/internal/user"
|
||||
)
|
||||
|
||||
func TestUserService(t *testing.T) {
|
||||
// Test public API
|
||||
}
|
||||
```
|
||||
|
||||
### Table-Driven Tests
|
||||
|
||||
```go
|
||||
func TestAdd(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
a, b int
|
||||
expected int
|
||||
}{
|
||||
{"positive numbers", 2, 3, 5},
|
||||
{"negative numbers", -1, -1, -2},
|
||||
{"mixed numbers", -1, 5, 4},
|
||||
{"zeros", 0, 0, 0},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := Add(tt.a, tt.b)
|
||||
if result != tt.expected {
|
||||
t.Errorf("Add(%d, %d) = %d; want %d",
|
||||
tt.a, tt.b, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Test Helpers
|
||||
|
||||
```go
|
||||
// Helper functions should call t.Helper()
|
||||
func newTestUser(t *testing.T) *User {
|
||||
t.Helper()
|
||||
return &User{
|
||||
ID: uuid.New().String(),
|
||||
Name: "Test User",
|
||||
Email: "test@example.com",
|
||||
}
|
||||
}
|
||||
|
||||
func assertNoError(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertEqual[T comparable](t *testing.T, got, want T) {
|
||||
t.Helper()
|
||||
if got != want {
|
||||
t.Errorf("got %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Mocking with Interfaces
|
||||
|
||||
```go
|
||||
// Define interface for dependency
|
||||
type UserRepository interface {
|
||||
Find(ctx context.Context, id string) (*User, error)
|
||||
Save(ctx context.Context, user *User) error
|
||||
}
|
||||
|
||||
// Mock implementation for testing
|
||||
type mockUserRepo struct {
|
||||
users map[string]*User
|
||||
}
|
||||
|
||||
func newMockUserRepo() *mockUserRepo {
|
||||
return &mockUserRepo{users: make(map[string]*User)}
|
||||
}
|
||||
|
||||
func (m *mockUserRepo) Find(ctx context.Context, id string) (*User, error) {
|
||||
user, ok := m.users[id]
|
||||
if !ok {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (m *mockUserRepo) Save(ctx context.Context, user *User) error {
|
||||
m.users[user.ID] = user
|
||||
return nil
|
||||
}
|
||||
|
||||
// Test using mock
|
||||
func TestUserService_GetUser(t *testing.T) {
|
||||
repo := newMockUserRepo()
|
||||
repo.users["123"] = &User{ID: "123", Name: "Test"}
|
||||
|
||||
service := NewUserService(repo)
|
||||
user, err := service.GetUser(context.Background(), "123")
|
||||
|
||||
assertNoError(t, err)
|
||||
assertEqual(t, user.Name, "Test")
|
||||
}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Options Pattern
|
||||
|
||||
```go
|
||||
// Option function type
|
||||
type ServerOption func(*Server)
|
||||
|
||||
// Option functions
|
||||
func WithPort(port int) ServerOption {
|
||||
return func(s *Server) {
|
||||
s.port = port
|
||||
}
|
||||
}
|
||||
|
||||
func WithTimeout(timeout time.Duration) ServerOption {
|
||||
return func(s *Server) {
|
||||
s.timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
func WithLogger(logger *slog.Logger) ServerOption {
|
||||
return func(s *Server) {
|
||||
s.logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor using options
|
||||
func NewServer(opts ...ServerOption) *Server {
|
||||
s := &Server{
|
||||
port: 8080, // defaults
|
||||
timeout: 30 * time.Second,
|
||||
logger: slog.Default(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(s)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Usage
|
||||
server := NewServer(
|
||||
WithPort(9000),
|
||||
WithTimeout(time.Minute),
|
||||
)
|
||||
```
|
||||
|
||||
### Context Usage
|
||||
|
||||
```go
|
||||
// Always pass context as first parameter
|
||||
func (s *Service) ProcessRequest(ctx context.Context, req *Request) (*Response, error) {
|
||||
// Check for cancellation
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
// Use context for timeouts
|
||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result, err := s.repo.Find(ctx, req.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("finding item: %w", err)
|
||||
}
|
||||
|
||||
return &Response{Data: result}, nil
|
||||
}
|
||||
```
|
||||
|
||||
### Defer for Cleanup
|
||||
|
||||
```go
|
||||
func processFile(path string) error {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close() // Always executed on return
|
||||
|
||||
// Process file...
|
||||
return nil
|
||||
}
|
||||
|
||||
// Multiple defers execute in LIFO order
|
||||
func transaction(db *sql.DB) error {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback() // Safe: no-op if committed
|
||||
|
||||
// Do work...
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
```
|
||||
|
||||
### Concurrency Patterns
|
||||
|
||||
```go
|
||||
// Worker pool
|
||||
func processItems(items []Item, workers int) []Result {
|
||||
jobs := make(chan Item, len(items))
|
||||
results := make(chan Result, len(items))
|
||||
|
||||
// Start workers
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < workers; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for item := range jobs {
|
||||
results <- process(item)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Send jobs
|
||||
for _, item := range items {
|
||||
jobs <- item
|
||||
}
|
||||
close(jobs)
|
||||
|
||||
// Wait and collect
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(results)
|
||||
}()
|
||||
|
||||
var out []Result
|
||||
for r := range results {
|
||||
out = append(out, r)
|
||||
}
|
||||
return out
|
||||
}
|
||||
```
|
||||
|
||||
## Code Quality
|
||||
|
||||
### Linting with golangci-lint
|
||||
|
||||
```yaml
|
||||
# .golangci.yml
|
||||
linters:
|
||||
enable:
|
||||
- errcheck
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- unused
|
||||
- gosimple
|
||||
- gocritic
|
||||
- gofmt
|
||||
- goimports
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
errcheck:
|
||||
check-type-assertions: true
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- errcheck
|
||||
```
|
||||
|
||||
### Common Commands
|
||||
|
||||
```bash
|
||||
# Format code
|
||||
go fmt ./...
|
||||
|
||||
# Run linter
|
||||
golangci-lint run
|
||||
|
||||
# Run tests
|
||||
go test ./...
|
||||
|
||||
# Run tests with coverage
|
||||
go test -coverprofile=coverage.out ./...
|
||||
go tool cover -html=coverage.out
|
||||
|
||||
# Check for race conditions
|
||||
go test -race ./...
|
||||
|
||||
# Build
|
||||
go build ./...
|
||||
```
|
||||
Reference in New Issue
Block a user