boardgame-core/AGENTS.md

3.9 KiB

AGENTS.md - boardgame-core

Commands

Prefer bash.exe(C:\Users\Administrator\AppData\Local\Microsoft\WindowsApps\bash.exe) over Powershell.

npm run build        # Build ESM bundle + declarations to dist/ (via tsup)
npm run test         # Run vitest in watch mode
npm run test:run     # Run vitest once (no watch)
npm run typecheck    # Type-check without emitting (tsc --noEmit)

Running a single test file

npx vitest run tests/samples/tic-tac-toe.test.ts

Running a single test by name

npx vitest run -t "should detect horizontal win for X"

Project Structure

src/
  core/        # Core game primitives (game, part, region)
  samples/     # Example games (tic-tac-toe, boop)
  utils/       # Shared utilities (entity, command, rng, async-queue)
  index.ts     # Single public API barrel export
tests/         # Mirrors src/ structure with *.test.ts files

Code Style

Imports

  • Use double quotes for local imports, single quotes for npm packages
  • Use @/**/* for ./src/**/* import alias (configured in tsconfig.json)
  • Use @/index when importing from the public API surface in samples
  • Group imports: npm packages first, then local @/ imports, separated by blank line

Formatting

  • 4-space indentation (no tabs)
  • Semicolons at statement ends
  • Arrow functions for callbacks; function keyword for methods needing this (e.g. command handlers)
  • No trailing whitespace
  • Trailing commas in multi-line objects/arrays

Naming Conventions

  • Types/Interfaces: PascalCasePart, Region, Command, IGameContext
  • Classes: PascalCaseEntity, AsyncQueue, Mulberry32RNG
  • Functions: camelCase, verb-first — createGameContext, parseCommand, isValidMove
  • Variables: camelCase
  • Constants: UPPER_SNAKE_CASEBOARD_SIZE, WINNING_LINES
  • Test files: *.test.ts mirroring src/ structure under tests/
  • Factory functions: prefix with createcreateEntity, createTestContext

Types

  • Strict TypeScript is enabled — no any
  • Use generics heavily: Entity<T>, CommandRunner<TContext, TResult>
  • Use type aliases for object shapes (not interfaces)
  • Use discriminated unions for results: { success: true; result: T } | { success: false; error: string }
  • Use unknown for untyped values, narrow with type guards or as
  • Derive state types via ReturnType<typeof createInitialState>

Error Handling

  • Prefer result objects over throwing: return { success, result/error }
  • When catching: const error = e as Error; then use error.message
  • Use throw new Error(...) only for truly exceptional cases
  • Validation error messages are in Chinese (e.g. "参数不足")
  • Prompt validators return null for valid input, string error message for invalid

Testing

  • Vitest with globals enabled (describe, it, expect available without import)
  • Use async/await for async tests
  • Narrow result types with if (result.success) before accessing result.result
  • Define inline helper functions in test files when needed (e.g. createTestContext())
  • No mocking — use real implementations
  • For command tests: use waitForPrompt() + promptEvent.tryCommit() pattern
  • Test helpers: createTestContext(), createTestRegion()

Architecture Notes

  • Reactivity: Entity<T> extends Preact Signal — access state via .value, mutate via .produce(draft => ...)
  • Command system: CLI-style parsing with schema validation via inline-schema
  • Prompt system: Commands prompt for input via PromptEvent with resolve/reject
  • Barrel exports: src/index.ts is the single public API surface
  • Game modules: export registry and createInitialState for use with createGameContextFromModule
  • RegionEntity: manages spatial board state with axis-based positioning and child entities
  • Mutative: used for immutable state updates inside Entity.produce()