Agent guidance for the @echecs/uci repository — a TypeScript wrapper around
UCI chess engine processes, providing a typed event-emitter API.
See also: REFERENCES.md |
COMPARISON.md | SPEC.md
Backlog: tracked in GitHub Issues.
@echecs/uci wraps a UCI chess engine subprocess and exposes it as a typed
Emittery-based event emitter. The public API is a single default export: the
UCI class. Runtime dependencies: emittery (event emitter) and zod (option
validation).
Key source files:
| File | Role |
|---|---|
src/index.ts |
UCI class — public API and engine lifecycle |
src/types.ts |
Exported types: ID, InfoCommand, Option, Score |
src/options.ts |
Options class — engine option store with Zod validation |
src/process.ts |
Process class — child process wrapper with line emitter |
src/parser/index.ts |
Parser dispatcher — routes UCI output tokens to handlers |
src/parser/bestmove.ts |
Parses bestmove responses |
src/parser/info.ts |
Parses info lines into typed InfoCommand objects |
src/parser/identity.ts |
Parses id responses |
src/parser/extract.ts |
Utility: extracts named fields from UCI token strings |
src/parser/noop.ts |
Pass-through for commands with no payload |
src/__tests__/index.spec.ts |
Unit tests for the UCI class |
src/__tests__/parser.spec.ts |
Unit tests for the parser module |
src/__tests__/integration.spec.ts |
Integration tests (skipped without a real engine binary) |
Use pnpm exclusively (no npm/yarn).
pnpm build # bundle TypeScript → dist/ via tsdownpnpm test # run all tests once (vitest run)
pnpm test:watch # watch mode
pnpm test:coverage # with v8 coverage report
# Run a single test file
pnpm test src/__tests__/parser.spec.ts
# Run tests matching a name substring
pnpm test -- --reporter=verbose -t "bestmove"Note:
integration.spec.tstests are skipped whenUCI_ENGINE_PATHis not set. Set it to a UCI engine path to enable them. 6 skipped tests is expected and normal in CI.
pnpm lint # ESLint + tsc type-check (auto-fixes style issues)
pnpm lint:ci # strict — zero warnings allowed, no auto-fix
pnpm lint:style # ESLint only (auto-fixes)
pnpm lint:types # tsc --noEmit type-check only
pnpm format # Prettier (writes changes)
pnpm format:ci # Prettier check only (no writes)pnpm lint && pnpm test && pnpm buildInput validation is mostly provided by TypeScript's strict type system at
compile time. The exception in this package is engine option validation, which
uses zod for runtime schema validation (since engine options arrive as untyped
strings from the UCI protocol). Do not add additional runtime type-checking
guards elsewhere unless there is an explicit trust boundary.
- ESM-only — the package ships only ESM. Do not add a CJS build.
UCIdoes not extendEmittery— it holds a private#emitterfield and exposes onlyon(),off(), andonce(). All engine output is surfaced as typed events (bestmove,info,id,option,error, etc.).- Engine communication flows:
Process(child process + line reader) →UCI#ingest(parser dispatch) → typedemitcalls. Optionsvalidates and stores engine options using Zod schemas.src/types.tsholds all public types as named exports — do not use global ambient namespaces.- Propagate errors via
this.#emitter.emit('error', ...)— do not swallow them silently. - Runtime dependencies (
emittery,zod) are intentional; do not remove them.
ready() and execute() are error-absorbing: they catch all rejections
internally and route them to this.#emitter.emit('error', ...), then return a
resolved promise. They never reject.
This means adding a .catch() to a chain like
this.ready().then(() => this.execute(...)) is dead code — neither call
will reject, so the .catch() can never fire. Do not propose "add a .catch()
to propagate errors" as a fix anywhere in src/index.ts; it will not work
without first refactoring ready() or execute() to throw instead of absorb.
Note: ready() does not permanently cache errors. A failed isready
handshake does not prevent subsequent ready() calls from retrying — each call
performs a fresh handshake.
Step-by-step process for releasing a new version. CI auto-publishes to npm when
version in package.json changes on main.
-
Verify the package is clean:
pnpm lint && pnpm test && pnpm build
Do not proceed if any step fails.
-
Decide the semver level:
patch— bug fixes, internal refactors with no API changeminor— new features, new exports, non-breaking additionsmajor— breaking changes to the public API
-
Update
CHANGELOG.mdfollowing Keep a Changelog format:## [x.y.z] - YYYY-MM-DD ### Added - … ### Changed - … ### Fixed - … ### Removed - …
Include only sections that apply. Use past tense.
-
Update
README.mdif the release introduces new public API, changes usage examples, or deprecates/removes existing features. -
Bump the version:
npm version <major|minor|patch> --no-git-tag-version
-
Open a release PR:
git checkout -b release/x.y.z git add package.json CHANGELOG.md README.md git commit -m "release: @echecs/uci@x.y.z" git push -u origin release/x.y.z gh pr create --title "release: @echecs/uci@x.y.z" --body "<description>"
Wait for CI (format, lint, test) to pass on the PR before merging.
-
Merge the PR: Once CI is green, merge (squash) into
main. The release workflow detects the version bump, publishes to npm, and creates a GitHub Release with a git tag.
Do not manually publish with npm publish. Do not create git tags manually —
the release workflow handles tagging.