boardgame-core/AGENTS.md

4.4 KiB

AGENTS.md - boardgame-core

Commands

Shell environment: git bash (bash.exe) on Windows.

npm run build           # ESM bundle + declarations to dist/ (tsup)
npm run build:samples   # Build samples separately (tsup.samples.config.ts)
npm run test            # vitest in watch mode
npm run test:run        # vitest once (no watch)
npm run typecheck       # tsc --noEmit

Run a single test file: npx vitest run tests/core/game.test.ts Run tests matching a name: npx vitest run -t "should create"

npm run prepare runs npm run build on install — if inline-schema isn't available, installs will fail.

Local Dependency

inline-schema is a local peer dependency at file:../inline-schema — must exist as a sibling directory before npm install. Both vitest and tsup load custom plugins from it (inline-schema/csv-loader/rollup and inline-schema/csv-loader/esbuild). yarn-spinner-loader is also a local dependency at file:../yarn-spinner-loader — it can be changed and published if needed. It provides vitest and tsup/esbuild plugins for .yarnproject dialogue files.

Architecture

  • MutableSignal<T>: extends Preact Signal — .value to read, .produce(draft => ...) to mutate (uses mutative). .produceAsync() awaits interruption promises first, then mutates.
  • GameHost: lifecycle manager — start(seed?), tryInput(string), tryAnswerPrompt(def, ...args), dispose(). Reactive signals: state, status, activePromptSchema, activePromptPlayer, activePromptHint.
  • GameModule: { registry?, createInitialState, start }. registry defaults to createGameCommandRegistry() if omitted.
  • Command system: CLI-style parsing with schema validation via inline-schema. CommandRunner.run() uses this bound to a CommandRunnerContext.
  • Prompt system: game.prompt(def, validator, currentPlayer?) — validator throws a string to reject, returns a value to accept. PromptEvent.tryCommit(Command | string) returns null on success, error string on failure. promptEnd event fires after resolve or cancel.
  • Part collections: Record<string, Part<TMeta>> keyed by ID. Object.values() for iteration, Object.keys() for count/IDs.
  • Region system: createRegion() factory; partMap maps position keys to part IDs.
  • Barrel exports: src/index.ts is the single public API surface.

Project Structure

src/
  core/              # game.ts, game-host.ts, part.ts, part-factory.ts, region.ts
  samples/           # tic-tac-toe.ts, boop/, onitama/, regicide/, slay-the-spire-like/
  utils/             # mutable-signal.ts, rng.ts, async-queue.ts, command/ (8 files)
  index.ts           # barrel export
  global.d.ts        # *.yarnproject module declaration
tests/               # mirrors src/ with *.test.ts

src/utils/command/ is a directory: types.ts, command-parse.ts, command-registry.ts, command-runner.ts, command-validate.ts, command-apply.ts, schema-parse.ts, index.ts.

Samples Build

tsup.samples.config.ts auto-discovers entries from src/samples/ (directories → index.ts, files → direct .ts). It rewrites @/core/*, @/utils/*, and @/index imports to external boardgame-core.

Code Style

  • Double quotes for local imports, single quotes for npm packages
  • @/* alias maps to src/* (configured in tsconfig, vitest, and tsup)
  • Group imports: npm packages first, then local @/ imports, separated by blank line
  • 4-space indentation, semicolons, trailing commas in multi-line
  • Arrow functions for callbacks; function keyword for methods needing this (e.g. command handlers)
  • Strict TypeScript — no any; type aliases for object shapes (not interfaces)
  • Discriminated unions for results: { success: true; result: T } | { success: false; error: string }
  • Derive state types via ReturnType<typeof createInitialState>
  • Factory function prefix: create or mutablecreateGameHost, mutableSignal
  • Validation error messages in Chinese (e.g. "参数不足")

Testing

  • Vitest with globals enabled, but test files import explicitly: import { describe, it, expect } from 'vitest'
  • Use @/ imports in test files (vitest resolve alias)
  • Command/prompt test pattern: waitForPrompt(ctx)promptEvent.tryCommit(Command) → returns null | error string
  • No mocking — real implementations. Define inline helpers (createTestContext(), waitForPrompt()) per file.
  • Narrow result types: if (result.success) before accessing result.result