From 331c4c8759ee16f248a8ccf70d088fba58b82b09 Mon Sep 17 00:00:00 2001 From: Ryan Hamamura <58859899+ryanhamamura@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:53:14 -1000 Subject: [PATCH] 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. --- .gitignore | 1 + AGENTS.md | 253 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 AGENTS.md diff --git a/.gitignore b/.gitignore index 90c8dd5..26b0262 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ !.env.example !LICENSE +!AGENTS.md !assets/**/* diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..46f375d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,253 @@ +# AGENTS.md + +Instructions for AI coding agents working in this repository. + +## Quick Reference + +```bash +# 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. + +```go +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:** + ```go + return fmt.Errorf("loading game %s: %w", id, err) + ``` + +2. **Return (result, error) tuples:** + ```go + func loadGame(queries *repository.Queries, id string) (*Game, error) + ``` + +3. **Best-effort operations** - use nolint comment: + ```go + nc.Publish(subject, nil) //nolint:errcheck // best-effort notification + ``` + +4. **HTTP errors:** + ```go + 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: + ```go + // Package connect4 implements Connect 4 game logic, state management, and persistence. + package connect4 + ``` +- Function comments for exported functions: + ```go + // 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`: + +```go +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 + +```go +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 +//go:build dev + +//go:build !dev +``` + +### Embedded Filesystems + +```go +//go:embed assets +var assets embed.FS + +//go:embed migrations/*.sql +var MigrationFS embed.FS +``` + +### Graceful Shutdown + +```go +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: + +```go +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: + +```go +var scriptHandle = templ.NewOnceHandle() + +templ MyComponent() { +
...
+ @scriptHandle.Once() { + @myScript() + } +} +``` + +### Conditional Classes with templ.KV + +```go +class={ + "status status-sm", + templ.KV("status-success", isConnected), + templ.KV("status-error", !isConnected), +} +``` + +### Datastar SSE Responses + +```go +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 + +```bash +# 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.