This file provides guidance to Claude Code (claude.ai/code) and other agents when working with code in this repository.
This is the Bupkis monorepo, containing the core assertion library and related packages.
Bupkis is a TypeScript assertion library that prioritizes type safety, extensibility, and natural language syntax. Unlike traditional chainable APIs (expect(value).toBeString()), Bupkis uses function calls with phrase arguments: expect(value, 'to be a string').
This is an npm workspaces monorepo. All packages live in packages/:
| Package | Path | Description |
|---|---|---|
bupkis |
packages/bupkis |
Core assertion library |
@bupkis/events |
packages/events |
EventEmitter and EventTarget assertions |
@bupkis/from-chai |
packages/from-chai |
Codemod to migrate Chai assertions |
@bupkis/from-jest |
packages/from-jest |
Codemod to migrate Jest/Vitest assertions |
@bupkis/msw |
packages/msw |
MSW request verification assertions |
@bupkis/property-testing |
packages/property-testing |
Property-based testing harness for assertions |
@bupkis/rxjs |
packages/rxjs |
RxJS Observable assertions |
@bupkis/sinon |
packages/sinon |
Sinon spy/stub/mock assertions |
@bupkis/http |
packages/http |
HTTP response assertions |
@bupkis/codemod-core |
packages/codemod-core |
Shared utilities for codemods |
@bupkis/from-assert |
packages/from-assert |
Codemod to migrate node:assert assertions |
Building and Testing (all packages):
npm run build- Build all packages (dual CJS/ESM via zshy)npm test- Run all tests across all packagesnpm run test:dev- Watch mode for all testsnpm run test:property- Property-based tests across packagesnpm run test:coverage- Run tests with coverage
Linting and Type Checking:
npm run lint- Run all linting (ESLint, types, markdown, spelling)npm run fix- Auto-fix all fixable issuesnpm run fix:eslint- Auto-fix ESLint issues onlynpm run lint:types- TypeScript type checking only
Documentation (bupkis package only):
npm run docs:build- Build documentationnpm run docs:dev- Watch mode documentation
Run commands for a specific package using --workspace:
npm run test --workspace=bupkis
npm run build --workspace=@bupkis/sinonOr cd into the package directory and run npm commands directly.
npm run bench- Run all benchmark suitesnpm run test:property- Property-based tests with fast-checknpm run test:snapshots- Run snapshot testsnpm run test:update-snapshots- Update snapshot filesnpm run test:types- Run type definition tests (tsd)
- Set
DEBUG=bupkis*for detailed logging npm run profile:test- Profile test execution (in bupkis package)npm run profile:benchmarks- Profile benchmark execution
All paths below are relative to packages/bupkis/.
Main Entry Points:
src/index.ts- Primary exports (expect,expectAsync,z,createAssertion)src/bootstrap.ts- Bootstraps expect functions with built-in assertionssrc/expect.ts- Core expect function implementations
Assertion Framework:
src/assertion/- Core assertion frameworkassertion-sync.ts/assertion-async.ts- Dual execution enginescreate.ts- Factory functions for custom assertionsassertion-types.ts- Complex TypeScript type systemimpl/- Built-in assertions by category:sync-basic.ts- Basic type assertionssync-collection.ts- Array/object assertionssync-parametric.ts- Parameterized assertionssync-esoteric.ts- Advanced assertionsasync.ts/async-parametric.ts- Async variants
Supporting Modules:
src/types.ts- Complex TypeScript inference systemsrc/schema.ts- Zod integration utilitiessrc/value-to-schema.ts- Runtime type introspectionsrc/error.ts- Custom error types extending Node.js AssertionErrorsrc/snapshot/- Snapshot testing support
Natural Language API:
// Instead of: expect(value).toBeString()
expect(value, 'to be a string');
expect(user, 'to satisfy', { name: expect.it('to be a string') });
expect(actual, 'not to be', expected);Zod-Centric Validation:
- Zod v4 schemas for both validation AND implementation
- Schema-based assertion creation:
createAssertion(['to be even'], (n) => n % 2 === 0) - Branded Zod types for compile-time validation
Type-Safe Parsing:
parseValues()converts natural language to typed tuples- Recursive conditional types for argument inference
- Heavy use of
type-festutilities
EventEmitter and EventTarget assertions for bupkis. Supports:
- Sync assertions for listener state (
to have listener for,to have listeners) - Async assertions for event emission (
to emit from,to dispatch from) - Duck-typed EventEmitter support (works with Node.js, eventemitter3, etc.)
- Symbol event names and timeout options
A codemod tool to migrate Chai assertions to bupkis. Supports:
- BDD style (
expect/should) and TDD style (assert) - Most common matchers and negation
- Plugins: chai-as-promised, chai-string, chai-subset
- Both CLI (
bupkis-from-chai) and programmatic API
A codemod tool to migrate Jest and Vitest assertions to bupkis. Supports:
- Jest 29+ and Vitest 1+
- Most common matchers (
toBe,toEqual,toBeTruthy, etc.) - Negation (
.not→'not to ...') - Both CLI (
bupkis-from-jest) and programmatic API
MSW (Mock Service Worker) request verification assertions for bupkis. Provides:
createTrackedServer()- Wraps MSW'ssetupServerwith request tracking- Path assertions (
to have handled request to,to have handled request matching) - Request options (method, body, headers, times, once)
- Body matching uses "to satisfy" semantics
- Header matching supports exact strings or RegExp patterns
Property-based testing harness using fast-check. Provides utilities for systematically testing assertions across four variants: valid, invalid, validNegated, invalidNegated.
Key exports:
createPropertyTestHarness()- Create test harness with expect functionsextractPhrases()- Extract phrase literals from assertionsgetVariants()- Extract test variants from configfilteredAnything/filteredObject- Safe generators for Zod
RxJS Observable assertions for bupkis. All assertions are asynchronous. Provides:
- Completion assertions (
to complete,to be empty) - Error assertions (
to emit error,to emit error satisfying) - Value assertions (
to emit values,to emit times,to emit once) - Completion value assertions (
to complete with value,to complete with value satisfying)
Sinon spy/stub/mock assertions for bupkis. Provides natural language assertions like:
expect(spy, 'was called')expect(spy, 'was called with', [arg1, arg2])expect(spy, 'was called before', otherSpy)expect(spy, 'to have calls satisfying', [...])
HTTP response assertions for bupkis. Works with supertest, superagent, fetch, axios, or any object with a status property. Provides:
- Status assertions (
to have status, with numeric codes or categories like'ok') - Header assertions (
to have header, with exact or regex matching) - Body assertions (
to have body,to have JSON body,to have JSON body satisfying) - Redirect assertions (
to redirect,to redirect to)
- Uses Node.js built-in
node:testwithdescribe/itstructure - Write tests in TypeScript with
tsxfor execution - BDD-style descriptions: "should do X when Y"
- Uses
fast-checkfor property tests inpackages/bupkis/test/property/ - Use
@bupkis/property-testingharness for new assertion tests - Coordinated generators for valid input combinations
- Avoid
fc.constant()- preferfc.func().map()for coverage - Use
getVariants()to extract variants; userunVariant()from the object returned bycreatePropertyTestHarness()
test/core/- Core functionality teststest/assertion/- Assertion implementation teststest/assertion-error/- Error formatting tests with snapshotstest/property/- Property-based teststest/integration/- Integration tests (CJS/ESM compatibility)
IMPORTANT:
- Comments should explain the intent of the code instead of the implementation.
- Always use ESM syntax (
import/export). - When completing a task, always run
npm run fix:eslintand then manually fix any outstanding issues. - If you encounter ESLint errors otherwise, always run
npm run fix:eslintfirst before attempting manual fixes. - ALWAYS use arrow functions unless you have a good reason not to (e.g., overloads, use of
this). ESLint will enforce this rule. - The docstrings of
constarrow functions must be tagged with@function(helps TypeDoc).
TypeScript Patterns:
- Use recursive conditional types for argument inference
- Consume
type-festtypes instead of hand-rolled equivalents - Type inference flow:
AssertionParts→AssertionSlots→ParsedValues
When returning AssertionFailure from an assertion, actual/expected feed into jest-diff:
- Include when both are the same type (enables useful diff)
- Omit when types differ or there's no meaningful comparison (just use
message)
Core (bupkis):
- Zod v4 - Validation library (peer dependency)
- tsx - TypeScript execution
- zshy - Dual CJS/ESM build system
Development (root):
- fast-check - Property-based testing
- debug - Structured logging (
bupkis:*namespace) - modestbench - Benchmarking
Assertion Parsing Issues:
- Check
parseValues()success/failure and reason
Async/Sync Confusion:
expect()throws if it finds a thenable; useexpectAsync()instead- Maintain clear sync/async separation in assertions.
- A function should be sync or async; not both (no Zalgo). Create parallel APIs, if necessary (e.g.,
expect()/expectAsync()).
Object Matching:
to satisfyanddeep equalboth usevalueToSchema(), but with different options.- When an assertion is said to use "'to satisfy' semantics", its implementation will need to call
valueToSchemasomewhere.
- Place temporary files in
.tmp/(Git-ignored) - Follow established module boundaries (
guards.ts,schema.ts,util.ts) - Package-specific code stays within its
packages/<name>/directory
This project uses Conventional Commits with package scopes. Each package is independently versioned, so commit types directly affect changelogs and version bumps.
Commit Format: <type>(<scope>): <description>
- Scopes correspond to package names:
bupkis,events,from-chai,from-jest,http,msw,property-testing,rxjs,sinon - Types follow a limited set of standard conventions:
feat,fix,chore,docs
Cross-Package Changes Require Careful Consideration:
When a change spans multiple packages, ask: "Is the commit type the same for all affected packages?"
- If yes (e.g.,
choreacross multiple packages), a single commit is fine - If no (e.g., adding a new export to one package is a
feat, but consuming it in another is achore), split into separate commits
Example of when to split:
- Adding
extractProperty()to@bupkis/property-testing→feat(property-testing): add extractProperty - Using that export to build fuzzing in
bupkis→chore(bupkis): add fuzzing infrastructure
Why this matters: feat commits trigger minor version bumps and appear in changelogs. Internal tooling or test changes (chore) should not trigger releases or clutter user-facing changelogs.