Files
games/AGENTS.md
Ryan Hamamura 331c4c8759
All checks were successful
CI / Deploy / test (push) Successful in 15s
CI / Deploy / lint (push) Successful in 28s
CI / Deploy / deploy (push) Successful in 1m28s
docs: add AGENTS.md with coding guidelines for AI agents
Includes build/test commands, code style guidelines, naming conventions,
error handling patterns, and Go/templ/Datastar patterns used in this repo.
2026-03-03 10:53:14 -10:00

5.9 KiB

AGENTS.md

Instructions for AI coding agents working in this repository.

Quick Reference

# Development
task live              # Hot-reload dev server (templ + tailwind + air)
task build             # Production build to bin/games
task run               # Build and run server

# Quality
task test              # Run all tests: go test ./...
task lint              # Run linter: golangci-lint run

# Single test
go test -run TestName ./path/to/package

# Code generation
task build:templ       # Compile .templ files
task build:styles      # Build TailwindCSS
go generate ./...      # Run sqlc for DB queries

Workflow Rules

  • Never merge PRs without explicit user approval. Create the PR, push changes, then wait.
  • Always use PRs via tea CLI - never push directly to main.
  • Write semantic commit messages focusing on "why" not "what".

Project Structure

games/
├── connect4/, snake/       # Game logic packages (pure Go)
├── features/               # Feature modules (handlers, routes, templates)
│   ├── auth/               # Login/register
│   ├── c4game/             # Connect 4 UI
│   ├── snakegame/          # Snake UI
│   ├── lobby/              # Game lobby
│   └── common/             # Shared components, layouts
├── chat/                   # Reusable chat room (NATS + persistence)
├── db/                     # SQLite, migrations, sqlc queries
├── assets/                 # Static files (embedded)
└── config/, logging/, nats/, sessions/, router/  # Infrastructure

Code Style

Imports

Organize in three groups: stdlib, third-party, local. The linter enforces this.

import (
    "context"
    "fmt"
    "net/http"

    "github.com/go-chi/chi/v5"
    "github.com/rs/zerolog/log"

    "github.com/ryanhamamura/games/connect4"
    "github.com/ryanhamamura/games/db/repository"
)

Naming Conventions

Type Convention Examples
Files lowercase, underscores config_dev.go, handlers.go
HTTP handlers Handle prefix HandleGamePage, HandleLogin
Constructors New prefix NewStore, NewRoom
Getters Get prefix GetPlayerID, GetGame
Setup functions Setup prefix SetupRoutes, SetupLogger
Types PascalCase Game, Player, Instance
Status enums Status prefix StatusWaitingForPlayer, StatusInProgress
Session keys Key prefix KeyPlayerID, KeyUserID

Error Handling

  1. Wrap errors with context:

    return fmt.Errorf("loading game %s: %w", id, err)
    
  2. Return (result, error) tuples:

    func loadGame(queries *repository.Queries, id string) (*Game, error)
    
  3. Best-effort operations - use nolint comment:

    nc.Publish(subject, nil) //nolint:errcheck // best-effort notification
    
  4. HTTP errors:

    http.Error(w, "game not found", http.StatusNotFound)
    http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
    

Comments

  • Focus on why, not how. Avoid superfluous comments.
  • Package comments at top of primary file:
    // Package connect4 implements Connect 4 game logic, state management, and persistence.
    package connect4
    
  • Function comments for exported functions:
    // DropPiece attempts to drop a piece in the given column.
    // Returns (row placed, success).
    func (g *Game) DropPiece(col, playerColor int) (int, bool)
    

Go Patterns

Dependency Injection via Closures

Handlers receive dependencies and return http.HandlerFunc:

func HandleGamePage(store *connect4.Store, sm *scs.SessionManager) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // use store, sm here
    }
}

Mutex for Concurrent Access

type Store struct {
    games   map[string]*Instance
    gamesMu sync.RWMutex
}

func (s *Store) Get(id string) (*Instance, bool) {
    s.gamesMu.RLock()
    defer s.gamesMu.RUnlock()
    inst, ok := s.games[id]
    return inst, ok
}

Build Tags for Environment

//go:build dev

//go:build !dev

Embedded Filesystems

//go:embed assets
var assets embed.FS

//go:embed migrations/*.sql
var MigrationFS embed.FS

Graceful Shutdown

eg, egctx := errgroup.WithContext(ctx)
eg.Go(func() error { return server.ListenAndServe() })
eg.Go(func() error {
    <-egctx.Done()
    return server.Shutdown(context.Background())
})
return eg.Wait()

Templ + Datastar Patterns

SSE Connection with Disabled Cancellation

Datastar cancels SSE on user interaction by default. Disable for persistent connections:

data-init={ fmt.Sprintf("@get('/games/%s/events',{requestCancellation:'disabled'})", g.ID) }

Prevent Script Duplication on SSE Patches

Use templ.NewOnceHandle() for scripts in components that get patched:

var scriptHandle = templ.NewOnceHandle()

templ MyComponent() {
    <div id="my-component">...</div>
    @scriptHandle.Once() {
        @myScript()
    }
}

Conditional Classes with templ.KV

class={
    "status status-sm",
    templ.KV("status-success", isConnected),
    templ.KV("status-error", !isConnected),
}

Datastar SSE Responses

sse := datastar.NewSSE(w, r)
sse.MergeFragmentTempl(components.GameBoard(game))

Tech Stack

Layer Technology
Templates templ (type-safe HTML)
Reactivity Datastar (SSE-driven)
CSS TailwindCSS v4 + daisyUI
Router chi/v5
Sessions scs/v2
Database SQLite (modernc.org/sqlite)
Migrations goose
SQL codegen sqlc
Pub/sub Embedded NATS
Logging zerolog

Testing

# All tests
task test

# Single test
go test -run TestDropPiece ./connect4

# With verbose output
go test -v -run TestDropPiece ./connect4

# Test a package
go test ./connect4/...

Use testutil.SetupTestDB() for tests requiring database access.