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 —
.valueto read,.produce(draft => ...)to mutate (usesmutative)..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 }.registrydefaults tocreateGameCommandRegistry()if omitted. - Command system: CLI-style parsing with schema validation via
inline-schema.CommandRunner.run()usesthisbound to aCommandRunnerContext. - Prompt system:
game.prompt(def, validator, currentPlayer?)— validator throws a string to reject, returns a value to accept.PromptEvent.tryCommit(Command | string)returnsnullon success, error string on failure.promptEndevent 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;partMapmaps position keys to part IDs. - Barrel exports:
src/index.tsis 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 tosrc/*(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;
functionkeyword for methods needingthis(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:
createormutable—createGameHost,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)→ returnsnull| error string - No mocking — real implementations. Define inline helpers (
createTestContext(),waitForPrompt()) per file. - Narrow result types:
if (result.success)before accessingresult.result