4.1 KiB
4.1 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 (mutable-signal, 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
@/indexwhen 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;
functionkeyword for methods needingthis(e.g. command handlers) - No trailing whitespace
- Trailing commas in multi-line objects/arrays
Naming Conventions
- Types/Interfaces:
PascalCase—Part,Region,Command,IGameContext - Classes:
PascalCase—MutableSignal,AsyncQueue,Mulberry32RNG - Functions:
camelCase, verb-first —createGameContext,parseCommand,isValidMove - Variables:
camelCase - Constants:
UPPER_SNAKE_CASE—BOARD_SIZE,WINNING_LINES - Test files:
*.test.tsmirroringsrc/structure undertests/ - Factory functions: prefix with
createormutable—createGameContext,mutableSignal
Types
- Strict TypeScript is enabled — no
any - Use generics heavily:
MutableSignal<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
unknownfor untyped values, narrow with type guards oras - 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 useerror.message - Use
throw new Error(...)only for truly exceptional cases - Validation error messages are in Chinese (e.g.
"参数不足") - Prompt validators return
nullfor valid input,stringerror message for invalid
Testing
- Vitest with globals enabled (
describe,it,expectavailable without import) - Use
async/awaitfor async tests - Narrow result types with
if (result.success)before accessingresult.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:
MutableSignal<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
PromptEventwithresolve/reject;tryCommitacceptsCommand | stringand validates against schema before custom validator - Barrel exports:
src/index.tsis the single public API surface - Game modules: export
registryandcreateInitialStatefor use withcreateGameContextFromModule - Region system: plain
Regiontype withcreateRegion()factory;partsare a separate record keyed by ID - Mutative: used for immutable state updates inside
MutableSignal.produce()