149 KiB
TypeScript Rete-Based Rules Engine + Custom-Rules Chess Demo
TL;DR
Quick Summary: Build
@paratype/rete(a Doorenbos-style Rete II rules engine in TypeScript with Immer-backed time-travel) and@paratype/chess(a browser chess game where every rule — including FIDE rules and 15 preset custom rules — is expressed as Rete productions) served by a@paratype/chess-serverauthoritative Bun WebSocket server for multiplayer.Deliverables:
packages/rete: Rete engine with alpha/beta nodes, joins, negation, NCC, existential, aggregation, derived facts, cycle detection, event log, N-tick Immer snapshots, TS builder API, JSON serialization (handler-registry pattern)packages/chess: Browser chess game with FIDE rules expressed as Rete productions, 15 preset custom rules (toggle between games), React/Vite UI, localStorage persistence, JSON ruleset/game export-importpackages/chess-server: Bun WebSocket authoritative server with rooms, reconnection, move validation, deterministic broadcastdocs/PHASES.md,packages/rete/SPEC.md,packages/chess/RULES.md,packages/server/PROTOCOL.md(Phase 0 specification locks)- Full CI (typecheck, lint, Vitest with coverage, Playwright E2E, bundle-size, security audit)
Estimated Effort: XL (5-phase plan, 70+ tasks) Parallel Execution: YES — heavy parallelism within waves, strict sequencing between phases Critical Path: Phase 0 specs → monorepo scaffold → Phase 1 alpha/beta network → Phase 2 advanced nodes → Phase 3 chess engine-as-rules → Phase 4 multiplayer server → Final QA
Context
Original Request
Build a TypeScript Rete-based rules engine for browser games (inspired by paranim/pararules in Nim), with immutable data structures enabling time-rewind and debugging. Use it to power a browser chess game with user-customizable rules at runtime (inspired by chess.dougdoug.com).
Interview Summary
Key Decisions (all confirmed by user):
- Repo: Monorepo with
packages/rete(engine),packages/chess(browser game),packages/server(WebSocket server) - Rule authoring: Typed TS builder API + JSON serialization via handler-registry pattern (no
eval, no function-to-string) - Fact model: Strict EAV
(id, attr, value)— pararules parity - Time-travel: Event log + Immer snapshots every N ticks
- Chess integration: Chess rules ARE Rete productions (no chess.js)
- Feature scope: Full Doorenbos-style Rete II (alpha, beta, joins, negation, NCC, existential, aggregation, derived facts, cycle detection)
- Tooling: Bun workspaces + Vitest + tsc --noEmit + tsup (engine) + Vite (chess demo)
- Testing: TDD for engine core; tests-after + Playwright for chess demo; Playwright QA for all tasks
- Chess v1: 15 preset rules with toggle UI (between-games toggle only in v1)
- Persistence: localStorage auto-save + JSON export/import
- Play mode: Networked multiplayer via WebSocket
- Networking: Authoritative server — engine on server, clients send intents, server broadcasts events
- Packaging:
@paratype/rete,@paratype/chess,@paratype/chess-server, MIT license
Research Findings (condensed):
- Pararules uses strict EAV, alpha+beta networks, joins, conditions, derived facts via
thenFinally, cycle detection via recursion limit — no native negation/aggregation/NCC; emulated via derived facts. User chose to extend to full Rete II. - No production-ready TS Rete engine exists — Nools is dead (2019), Rete.js is a node editor (not a rules engine), node-rules is not true Rete. Must build greenfield.
- Immer is the best immutable fit — structural sharing, draft-based mutations, minimal API overhead.
- Chess.js hardcodes FIDE rules — no hook system; validates the "chess-as-rules" architectural choice.
- Runtime rule injection is THE feature for chess.dougdoug.com-style play.
Metis Review (Gaps Addressed)
Metis identified 36 ambiguity points and classified combined risk as non-linear. Key resolutions now baked into this plan:
- "Rete II" is fixed: Doorenbos 1995 thesis (unlinking, right/left activation, enumerated node types: alpha, beta, join, negation, NCC, existential, aggregation). No other interpretation accepted.
- Phasing is mandatory: 5 phases (0: specs → 1: pararules parity → 2: Rete II + local chess → 3: time-travel + presets + UI → 4: multiplayer). Phase N+1 tasks do not start until Phase N acceptance gate is green.
- RHS serialization = handler-registry pattern: JSON rules store
{conditions: [...], handler: "registeredName", args: [...]}. Handlers are registered TS functions in a per-package registry. Zeroeval, zero function-to-string, zero arbitrary JS in saved JSON. - Fact ID authority = server-minted in multiplayer; client only references positions/piece-ids opaquely; deterministic across clients.
- Conflict resolution = deterministic:
salience desc → specificity desc → insertion order asc. Documented in SPEC.md. - Match refraction = once per unique match (CLIPS-style). Re-fires only when fact identity or bound variables change.
- RHS purity contract: No
Date.now(),Math.random(), or I/O in rule RHS. Enforced via ESLint rule + dev-mode runtime guard that wraps globals in engine package. - Rule set immutability during active game — in v1, rules cannot be added/removed mid-game; toggle only between games (simplifies time-travel + multiplayer determinism).
- Preset rules are dev-authored TS in v1 — no user JS upload (would be RCE vector on server).
- Protocol versioning: Every WebSocket message includes
v: 1; mismatch = hard disconnect.
Work Objectives
Core Objective
Deliver a production-quality Rete-based rules engine in TypeScript (with time-travel via Immer) and a fully functional browser chess demo (networked multiplayer, 15 custom rule presets) that proves out the engine as a game-logic substrate.
Concrete Deliverables
Packages:
packages/rete/— Published as@paratype/rete(ESM + CJS + .d.ts via tsup)packages/chess/— Published as@paratype/chess(chess UI, Vite dev server, bundled static)packages/server/— Published as@paratype/chess-server(Bun HTTP+WebSocket server)
Specifications (Phase 0 locks):
packages/rete/SPEC.md— Engine semantics (fact shape, ID authority, conflict resolution, refraction, iteration order, truth maintenance, cycle limit, RHS purity, JSON schema, named Rete II reference)docs/PHASES.md— 5-phase plan with gates, non-goals, perf budgets, demo scenariospackages/chess/RULES.md— 15 concrete preset rules with compat matrix and test scenariospackages/server/PROTOCOL.md— WebSocket message schemas, reconnection flow, rate limits
Infrastructure:
.github/workflows/ci.yml— Typecheck, lint, test+coverage, Playwright, bundle-size,bun audit- Root
bunfig.toml,tsconfig.base.json,eslint.config.js,vitest.workspace.ts,playwright.config.ts - Pre-commit hook enforcing
bun run check(lefthook or simple-git-hooks)
Acceptance Gates (per phase): executable verification commands (see each phase's wave).
Definition of Done
Run from repo root:
bun install # → 0 errors
bun run check # tsc --noEmit + eslint + vitest → all green
bun run test:coverage # engine ≥90% line, chess ≥70%, server ≥80%
bun run playwright test # all E2E scenarios pass
bun run build # all three packages build → dist/ populated
bun run size-limit # engine < 50KB min+gz, chess < 200KB min+gz
bun run replay-determinism # hash of replayed state == recorded hash, 100% match
bun run server & sleep 1 && bun run test:integration # real WebSocket handshake, move exchange
Must Have
- Doorenbos-style Rete II: alpha network, beta network, join nodes, negation nodes, NCC nodes, existential nodes, aggregation nodes, derived-fact production with
thenFinally-equivalent semantics, cycle detection with configurable recursion limit (default 64) - Strict EAV fact model with typed attributes; type-safe TS builder API with autocompleted attr names
- JSON serialization of all rules via handler-registry pattern (round-trip equivalence tested per rule)
- Deterministic tick execution: documented conflict resolution (salience → specificity → insertion-order); iteration of Set/Map replaced with sorted arrays everywhere; no
Date.now/Math.random/I/O in RHS - Immer-backed working memory snapshots at configurable interval N (default 30 ticks); append-only event log with monotonic sequence numbers; replay produces byte-identical state (verified via state hash)
- Full FIDE chess rules expressed as Rete productions in
@paratype/chess: piece placement, legal move generation per piece, turn order, captures, check detection, castling, en passant, promotion, checkmate, stalemate, 50-move rule, threefold repetition, insufficient material - 15 concrete preset custom rules in
@paratype/chesswith compatibility matrix; toggleable between games; each with unit tests and at least one Playwright scenario - Chess UI (React + Vite): 8×8 board with drag-drop moves, legal-move highlighting, rule-toggle screen, save/load UI, JSON export/import, undo via time-travel (to previous turn boundary)
- localStorage auto-save (per tick end) with schema-versioned payload; restore on page load; JSON export-import with validation
- Bun WebSocket server with: room create/join/leave (6-char room codes, 60s reconnect window), authoritative move validation, fact-delta broadcast, protocol versioning (
vfield), rate limit (100 msg/sec/client), 64KB message cap, origin allow-list, structured logging (pino) - CI green on ubuntu-latest with Bun latest; bundle-size enforced;
bun auditgreen - ≥90% line coverage for
@paratype/rete; ≥70% for@paratype/chess; ≥80% for@paratype/chess-server - Conventional Commits; phase boundaries tagged (
v0.1.0-phase1, etc.); pre-commit hook runsbun run check
Must NOT Have (Guardrails)
Scope exclusions (v1):
- NO chess AI, puzzles, tutorials, opening books, ELO, matchmaking, tournaments, leaderboards
- NO social features: chat, emotes, friends, profiles, avatars
- NO rule marketplace, remote rule sharing, user-authored JS rule upload
- NO mid-game rule toggle (toggle only between games in v1)
- NO spectators in v1 (2-player rooms only)
- NO server-side game persistence across restart (in-memory rooms only)
- NO mobile-native clients (responsive web only)
- NO accounts, OAuth, email, password, analytics, telemetry, i18n
- NO additional games on top of the engine in this plan
- NO visual rule editor / node graph editor (toggle-only UI in v1)
- NO pararules' Nim macro equivalents via runtime code-gen or
eval - NO external TS Rete library dependency (greenfield build)
- NO chess.js dependency (chess rules ARE Rete productions)
- NO Stockfish or other chess engines
- NO persistent user data beyond localStorage
Code-quality exclusions:
- NO
as any,as unknown as X,@ts-ignore,@ts-expect-errorin engine package (ESLint-enforced) - NO
Date.now(),Math.random(),performance.now(),setTimeout,setInterval,fetch,console.loginside engine RHS code paths (ESLint override on engine package) - NO raw
Set<object>orMap<object, …>iteration in engine hot paths (must sort to array first) - NO circular package dependencies (
@paratype/chessmay import@paratype/rete; reverse forbidden) - NO internal JSDoc (public API only); NO over-validation inside module boundaries
- NO premature abstraction / "framework" layer between engine and chess
- NO generic names in engine code:
data,result,item,temp,obj,foo
Verification Strategy (MANDATORY)
ZERO HUMAN INTERVENTION — ALL verification is agent-executed. No exceptions.
Test Decision
- Infrastructure exists: NO (fresh repo; infrastructure built in Phase 0 scaffold)
- Automated tests: YES (TDD for engine, tests-after for chess/server)
- Framework: Vitest (unit/integration), Playwright (E2E browser), custom Bun scripts (WebSocket integration, replay-determinism)
- TDD workflow: For engine tasks, each task follows RED (failing Vitest) → GREEN (minimal impl) → REFACTOR (clean up while tests remain green)
QA Policy
Every task MUST include agent-executable QA scenarios. Evidence saved to .sisyphus/evidence/task-{N}-{slug}.{ext}.
- Engine unit tests:
bun test <path> -t "<name>"with exact expected PASS/FAIL line; evidence = stdout log - Chess UI: Playwright (playwright skill) — specific
[data-square="e2"],[data-piece="white-pawn"]selectors; evidence = screenshot + trace - Server integration: scripted Bun WebSocket client against running server process; evidence = transcript JSON
- Determinism:
bun run scripts/hash-state.ts <log>produces sha256; evidence = hash file - Bundle size:
bun run size-limit; evidence = stdout showing kb count - Build:
bun run build→ inspectpackages/*/dist/; evidence =ls -laoutput - CI:
gh run list --limit 1 --json conclusion→ "success"; evidence = run URL
Mandatory QA Scenario Requirements
Every task MUST have:
- At least 1 happy-path scenario with exact commands, inputs, and assertions
- At least 1 failure/edge-case scenario (invalid input, missing dep, rejected move, protocol mismatch, etc.)
- Evidence path:
.sisyphus/evidence/task-{N}-{scenario-slug}.{ext} - Specific selectors/data, not vague descriptions
- Binary pass/fail result (no "looks correct")
Execution Strategy
Phase Structure (Metis-directed)
5 phases, strictly sequential. Phase N+1 cannot begin until Phase N acceptance gate (see each phase's final wave) is green.
- Phase 0 — Specification Lock (Wave P0.1 parallel spec authoring, Wave P0.2 scaffold)
- Phase 1 — Engine Parity with Pararules (alpha/beta, joins, conditions, derived facts, cycle detection, builder API, JSON handler-registry, basic Immer state)
- Phase 2 — Rete II Extensions + Chess Engine (negation, NCC, existential, aggregation; full FIDE chess as Rete productions; local 2-player via hot-seat for internal validation only)
- Phase 3 — Time-Travel + Presets + UI (event log + snapshots; replay determinism; 15 preset custom rules; React UI; localStorage; JSON import/export)
- Phase 4 — Authoritative Multiplayer Server (WebSocket server, rooms, reconnection, protocol v1; client networking layer; end-to-end multiplayer scenarios)
- Final Wave — 4 parallel review agents (plan compliance, code quality, manual QA, scope fidelity) → user okay → DONE
Parallel Execution Waves
Phase 0 — Specification Lock
Wave P0.1 (parallel spec authoring — 4 tasks):
├── P0.1 SPEC.md (engine semantics) [deep]
├── P0.2 PHASES.md (phase gates) [writing]
├── P0.3 RULES.md (15 preset custom rules) [deep]
└── P0.4 PROTOCOL.md (WS protocol v1) [deep]
Wave P0.2 (after P0.1, sequential foundation):
├── P0.5 Monorepo scaffold (bun workspaces, tsconfig, eslint, vitest, playwright) [unspecified-high]
└── P0.6 CI pipeline + pre-commit hook [unspecified-high]
GATE: SPEC/PHASES/RULES/PROTOCOL reviewed; bun install + bun run check green; CI green
Phase 1 — Engine Pararules Parity (TDD)
Wave P1.1 (parallel engine primitives — 6 tasks):
├── P1.1 Schema + Fact type with typed attrs [deep]
├── P1.2 Working memory (WM) storage + retrieval [deep]
├── P1.3 Alpha network (fact indexing by (id,attr))[deep]
├── P1.4 Session + lifecycle (init, add, fireRules)[deep]
├── P1.5 TS builder API + handler registry [deep]
└── P1.6 JSON serialization (round-trip) [deep]
Wave P1.2 (parallel join mechanics — 4 tasks):
├── P1.7 Beta network (memory + token propagation) [deep]
├── P1.8 Join nodes with variable binding [deep]
├── P1.9 Condition filters (`cond` analog) [deep]
└── P1.10 Query API (query / queryAll) [deep]
Wave P1.3 (parallel advanced parity — 3 tasks):
├── P1.11 Derived facts (thenFinally equivalent) [deep]
├── P1.12 Cycle detection (recursion limit) [deep]
└── P1.13 Deterministic conflict resolution [deep]
Wave P1.4 (parity validation):
└── P1.14 Pararules golden-file test port [unspecified-high]
GATE: Engine v0.1.0-phase1 tag; 90% coverage; all golden tests green; `bun run check` green
Phase 2 — Rete II Extensions + Chess Engine
Wave P2.1 (parallel Rete II nodes — 4 tasks):
├── P2.1 Negation nodes (NOT) [deep]
├── P2.2 Existential nodes (EXISTS) [deep]
├── P2.3 NCC nodes (not-count-condition) [deep]
└── P2.4 Aggregation nodes (count/sum/collect/min/max) [deep]
Wave P2.2 (chess foundation — parallel 4 tasks):
├── P2.5 Chess attribute schema & piece fact shape [deep]
├── P2.6 Starting-position fact generator [quick]
├── P2.7 Square coordinate & color helpers [quick]
└── P2.8 Piece movement primitive rules (directions/steps) [deep]
Wave P2.3 (chess legal-move rules — parallel 6 tasks):
├── P2.9 Pawn move/capture rules [deep]
├── P2.10 Knight move rules [deep]
├── P2.11 Bishop/Rook/Queen sliding rules [deep]
├── P2.12 King move rules [deep]
├── P2.13 Turn order + move legality integration [deep]
└── P2.14 Capture resolution rules [deep]
Wave P2.4 (chess special rules — parallel 4 tasks):
├── P2.15 Castling (kingside + queenside with history flags) [deep]
├── P2.16 En passant (single-tick capture window) [deep]
├── P2.17 Promotion (to Q/R/B/N) [deep]
└── P2.18 Check detection rule [deep]
Wave P2.5 (chess endgames — parallel 4 tasks):
├── P2.19 Checkmate detection [deep]
├── P2.20 Stalemate detection [deep]
├── P2.21 50-move rule + threefold repetition (aggregation-based) [deep]
└── P2.22 Insufficient material draw [deep]
Wave P2.6 (integration):
└── P2.23 End-to-end FIDE game replay test [unspecified-high]
GATE: Engine v0.2.0-phase2 tag; full FIDE game playable via rules only; `bun run check` green
Phase 3 — Time-Travel + Presets + UI
Wave P3.1 (time-travel — parallel 3 tasks):
├── P3.1 Event log (append-only, monotonic seq) [deep]
├── P3.2 Immer snapshot every N ticks [deep]
└── P3.3 Replay engine + determinism hash verifier [deep]
Wave P3.2 (15 preset rules — parallel 5 tasks x 3 rules each):
├── P3.4 Presets 1-3 (pawn-focused variants) [deep]
├── P3.5 Presets 4-6 (knight/bishop variants) [deep]
├── P3.6 Presets 7-9 (rook/queen/king variants) [deep]
├── P3.7 Presets 10-12 (board/geometry variants) [deep]
└── P3.8 Presets 13-15 (meta rules: HP/heal/immune) [deep]
Wave P3.3 (UI — parallel 5 tasks):
├── P3.9 React + Vite scaffold for chess app [visual-engineering]
├── P3.10 Chessboard component (drag-drop, highlights) [visual-engineering]
├── P3.11 Rule-toggle screen (list with compat warnings) [visual-engineering]
├── P3.12 Save/Load panel + undo via time-travel [visual-engineering]
└── P3.13 JSON export/import + validation [visual-engineering]
Wave P3.4 (persistence + integration):
├── P3.14 localStorage auto-save + restore [unspecified-high]
└── P3.15 End-to-end UI scenario (play game, toggle rule, save, restore) [unspecified-high]
GATE: Engine v0.3.0-phase3 tag; chess UI fully playable locally with presets; `bun run check` green
Phase 4 — Authoritative Multiplayer
Wave P4.1 (server core — parallel 4 tasks):
├── P4.1 Bun HTTP+WS server scaffold + config [unspecified-high]
├── P4.2 Message schemas + validation [deep]
├── P4.3 Room model (create/join/leave, 6-char codes) [deep]
└── P4.4 Rate limiting + origin allow-list + 64KB cap [unspecified-high]
Wave P4.2 (server game logic — parallel 4 tasks):
├── P4.5 Authoritative session per room [deep]
├── P4.6 Move-intent validation + fact-delta broadcast [deep]
├── P4.7 Reconnection flow (60s window, snapshot resume) [deep]
└── P4.8 Structured logging (pino) + metrics [unspecified-high]
Wave P4.3 (client networking — parallel 3 tasks):
├── P4.9 WebSocket client with reconnect + seq ack [deep]
├── P4.10 Client prediction + server reconciliation [deep]
└── P4.11 Room lobby UI (create/join screens) [visual-engineering]
Wave P4.4 (integration):
└── P4.12 E2E multiplayer scenario (two Playwright contexts play a full game) [unspecified-high]
GATE: Engine v0.4.0-phase4 tag; two-browser multiplayer working end-to-end; `bun run check` green
Final Verification Wave (4 parallel reviews)
├── F1 Plan compliance audit (oracle)
├── F2 Code quality review (unspecified-high)
├── F3 Real manual QA via Playwright + scripted WS client (unspecified-high)
└── F4 Scope fidelity check (deep)
→ Present results → Wait for explicit user okay → Tag v1.0.0
Dependency Matrix (abbreviated — full matrix embedded in each task's "Blocked By")
- Phase 0 tasks: No external deps; P0.5 blocks ALL Phase 1+ tasks; P0.6 depends on P0.5
- P1.1-P1.6: parallel within Wave P1.1, block P1.7-P1.10
- P1.7-P1.10: parallel within Wave P1.2, block P1.11-P1.13
- P1.11-P1.13: parallel within Wave P1.3, block P1.14
- P1.14: Phase 1 gate; blocks all Phase 2
- P2.1-P2.4: Rete II nodes, parallel, block P2.21 (aggregation-dependent)
- P2.5-P2.8: chess foundation, parallel, block P2.9-P2.14
- P2.9-P2.14: legal-move rules, parallel, block P2.15-P2.18
- P2.15-P2.18: special rules, parallel, block P2.19-P2.22
- P2.19-P2.22: endgames, parallel, block P2.23
- P2.23: Phase 2 gate; blocks all Phase 3
- P3.1-P3.3: time-travel, parallel, block P3.14 (restore requires replay)
- P3.4-P3.8: presets, parallel, block P3.11 (UI needs presets listed)
- P3.9-P3.13: UI tasks, mostly parallel (P3.10 depends on P3.9; others parallel with P3.10)
- P3.14: localStorage, depends on P3.3 + P3.12
- P3.15: Phase 3 gate; blocks all Phase 4
- P4.1-P4.4: server core, parallel, block P4.5-P4.8
- P4.5-P4.8: server game logic, parallel, block P4.9-P4.11
- P4.9-P4.11: client networking, parallel, block P4.12
- P4.12: Phase 4 gate; blocks Final Wave
- F1-F4: parallel; all must APPROVE before user-okay
Agent Dispatch Summary
- Phase 0 (6): P0.1-P0.4 →
deep+writing; P0.5-P0.6 →unspecified-high - Phase 1 (14): All
deep(TDD engine work); P1.14 →unspecified-high - Phase 2 (23): All
deep; P2.23 →unspecified-high - Phase 3 (15): P3.1-P3.8 →
deep; P3.9-P3.13 →visual-engineering; P3.14-P3.15 →unspecified-high - Phase 4 (12): P4.1 →
unspecified-high; P4.2-P4.3 →deep; P4.4 →unspecified-high; P4.5-P4.7 →deep; P4.8 →unspecified-high; P4.9-P4.10 →deep; P4.11 →visual-engineering; P4.12 →unspecified-high - Final (4): F1 →
oracle; F2 →unspecified-high; F3 →unspecified-high; F4 →deep
TODOs
Implementation + Test = ONE Task. Never separate. EVERY task has: Recommended Agent Profile + Parallelization info + QA Scenarios. A task WITHOUT QA Scenarios is INCOMPLETE. No exceptions.
Phase 0 — Specification Lock
-
P0.1. Author
packages/rete/SPEC.md— engine semantics specificationWhat to do:
- Create directory
packages/rete/ - Write
packages/rete/SPEC.mdwith sections (exactly these,##headings):## Fact Model— strict EAV (id, attr, value); id minted by Session (auto-increment), opaque to users; attr is branded string literal type; value is typed per attr via schema## ID Authority— Session owns counter; in multiplayer, only server increments; clients receive facts with server-assigned ids## Conflict Resolution— deterministic order: salience desc → specificity (# of conditions) desc → rule insertion order asc## Match Refraction— each unique match fires once; re-fires only on fact change affecting bindings## Iteration Order— all Session iteration uses sorted arrays (sort keys documented per structure); no rawSet<object>iteration in hot paths## Truth Maintenance— derived facts (thenFinally) retract when any supporting fact retracts; logical dependency tracked per derived fact## Cycle Detection— configurable recursion limit (default 64); exceeded →RecursionLimitExceededErrorwith cycle trace## RHS Purity Contract— RHS may NOT call Date.now, Math.random, performance.now, setTimeout, setInterval, fetch, or any I/O; enforced via ESLint ruleno-impure-rhs(custom rule) + dev-mode runtime global wrapping## JSON Rule Schema— handler-registry pattern:{name, salience, conditions: [...], handler: "registeredName", args: JsonValue[]}; NO function-to-string, NO eval, NO arbitrary JS## Rete II Reference Target— Doorenbos 1995 thesis; enumerate node types in scope: AlphaNode, BetaMemory, JoinNode, NegationNode, NccNode, ExistentialNode, AggregationNode, DerivedFactProduction
Must NOT do:
- Do NOT include implementation code in SPEC.md
- Do NOT reference specific library versions
- Do NOT leave any section as TBD
Recommended Agent Profile:
- Category:
deep— Requires careful semantic reasoning about Rete and distributed determinism - Skills: [
context7,web-search]context7: Look up canonical Rete references (Forgy 1982, Doorenbos 1995)web-search: Find CLIPS/Drools/Jess documentation for conflict resolution conventions
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave P0.1 (with P0.2, P0.3, P0.4)
- Blocks: P0.5, ALL engine implementation tasks
- Blocked By: None — start immediately
References:
Pattern References:
- Pararules README semantics: https://github.com/paranim/pararules#overview
API/Type References:
- Will be source of truth — no prior file
External References:
- Doorenbos 1995: "Production Matching for Large Learning Systems" — canonical Rete II thesis
- CLIPS reference manual — conflict resolution strategies
- Pararules source: https://github.com/paranim/pararules/blob/master/src/pararules/engine.nim
WHY Each Reference Matters:
- Doorenbos is the only authoritative source for "Rete II"; without it, scope ambiguity persists
- CLIPS refraction/salience is the de facto industry standard
- Pararules defines our baseline behavior to match
Acceptance Criteria:
- File
packages/rete/SPEC.mdexists [ "$(grep -c '^## ' packages/rete/SPEC.md)" -ge "10" ]→ true (exactly 10##sections)grep -q 'Doorenbos' packages/rete/SPEC.md→ 0 exitgrep -q 'handler-registry' packages/rete/SPEC.md→ 0 exitgrep -q 'no-impure-rhs' packages/rete/SPEC.md→ 0 exit
QA Scenarios:
Scenario: SPEC.md exists with required structure Tool: Bash Preconditions: clean repo Steps: 1. Run: test -f packages/rete/SPEC.md 2. Run: grep -c '^## ' packages/rete/SPEC.md 3. Run: for term in "Fact Model" "ID Authority" "Conflict Resolution" "Match Refraction" "Iteration Order" "Truth Maintenance" "Cycle Detection" "RHS Purity Contract" "JSON Rule Schema" "Rete II Reference Target"; do grep -q "^## $term" packages/rete/SPEC.md || echo "MISSING: $term"; done Expected Result: Step 1 exit 0; Step 2 outputs exactly 10; Step 3 outputs nothing (no MISSING lines) Failure Indicators: missing file, section count != 10, any MISSING line Evidence: .sisyphus/evidence/task-P0.1-spec-exists.log Scenario: SPEC.md forbids eval in JSON schema section Tool: Bash Preconditions: SPEC.md written Steps: 1. Run: awk '/^## JSON Rule Schema/,/^## /' packages/rete/SPEC.md | grep -qiE 'eval|function-to-string|arbitrary JS' && echo "OK" || echo "MISSING_FORBID_EVAL" Expected Result: stdout "OK" Evidence: .sisyphus/evidence/task-P0.1-json-forbid.logCommit: YES
- Message:
docs(rete): author engine specification (SPEC.md) - Files:
packages/rete/SPEC.md - Pre-commit: none (doc-only commit; hook runs
bun run checkwhich no-ops on empty repo)
- Create directory
-
P0.2. Author
docs/PHASES.md— phase gates + non-goals + perf budgetsWhat to do:
- Create
docs/PHASES.mdwith sections (exact headings):## Phase 0 — Specification Lock## Phase 1 — Pararules Parity## Phase 2 — Rete II + Chess Engine## Phase 3 — Time-Travel + Presets + UI## Phase 4 — Authoritative Multiplayer## Non-Goals (v1)## Performance Budgets## Demo Scenarios
- Each phase section: bullet-listed in-scope deliverables + executable acceptance-gate commands + explicit Must-NOT-Have exclusions
- Non-Goals: copy the plan's "Must NOT Have" list
- Performance Budgets:
insert(fact)< 0.5ms @ 10k facts;fireRules()< 5ms for chess ruleset; replay 1000 events < 500ms; engine bundle < 50KB min+gz; chess bundle < 200KB min+gz; server tick broadcast < 50ms p99 - Demo Scenarios: one per phase, each a scripted flow (e.g., "Phase 1 demo: run
bun test packages/rete— all pararules golden tests pass")
Must NOT do:
- Do NOT duplicate SPEC.md content; link to it
- Do NOT set unrealistic budgets (these are contractual)
Recommended Agent Profile:
- Category:
writing— Documentation authoring, prose-heavy - Skills: [
web-search]web-search: Reference typical WebSocket server perf budgets and bundle-size norms
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave P0.1 (with P0.1, P0.3, P0.4)
- Blocks: All subsequent task enumeration validation
- Blocked By: None
References:
Pattern References: None (new doc)
External References:
- bundlephobia.com for bundle size norms
- Pino docs for server-logging perf norms
Acceptance Criteria:
- File
docs/PHASES.mdexists [ "$(grep -c '^## Phase' docs/PHASES.md)" -eq "5" ]→ true (Phase 0..4)grep -q 'Non-Goals' docs/PHASES.md→ 0 exitgrep -q 'Performance Budgets' docs/PHASES.md→ 0 exitgrep -q '< 50KB' docs/PHASES.md→ 0 exit
QA Scenarios:
Scenario: PHASES.md has all required sections Tool: Bash Preconditions: none Steps: 1. Run: test -f docs/PHASES.md 2. Run: for h in "Phase 0 — Specification Lock" "Phase 1 — Pararules Parity" "Phase 2 — Rete II + Chess Engine" "Phase 3 — Time-Travel + Presets + UI" "Phase 4 — Authoritative Multiplayer" "Non-Goals (v1)" "Performance Budgets" "Demo Scenarios"; do grep -qF "## $h" docs/PHASES.md || echo "MISSING: $h"; done Expected Result: step 1 exit 0; step 2 outputs nothing Evidence: .sisyphus/evidence/task-P0.2-sections.log Scenario: Perf budgets are numeric and concrete (failure path) Tool: Bash Preconditions: PHASES.md written Steps: 1. Run: awk '/^## Performance Budgets/,/^## /' docs/PHASES.md | grep -E '(TBD|TODO|FIXME)' && echo "FAIL: placeholder found" || echo "OK" Expected Result: stdout "OK" Evidence: .sisyphus/evidence/task-P0.2-budgets.logCommit: YES
- Message:
docs(root): author PHASES.md with phase gates and perf budgets - Files:
docs/PHASES.md - Pre-commit: none
- Create
-
P0.3. Author
packages/chess/RULES.md— 15 concrete preset custom rulesWhat to do:
- Create directory
packages/chess/ - Write
packages/chess/RULES.mdlisting exactly 15 preset custom rules - Each rule has
### {rule-name}heading, plus bullet subsections:**ID**,**Description**,**Base Rule Affected**(which FIDE production it modifies, or "additive"),**Mode**(additive | override),**Incompatible With**(list of other rule IDs),**Test Scenarios**(≥3 concrete scenarios describing input board state + expected behavior),**Edge Cases**(interaction with en passant, castling, promotion as relevant) - Propose 15 concrete rules; include at least 3 from each category: movement-modifier (e.g., "Pawns may move backward"), piece-ability (e.g., "King heals +1HP when not in check"), win-condition (e.g., "Capture any piece to win"), board-geometry (e.g., "Board wraps horizontally"), meta-state (e.g., "Pieces have 3 HP; captures deal 1 damage")
Must NOT do:
- Do NOT leave any rule as "TBD" or "example rule"
- Do NOT allow two rules to be mutually required (circular dependency)
- Do NOT define rules requiring user-authored JS (v1 preset-only constraint)
Recommended Agent Profile:
- Category:
deep— Game design + rule-interaction reasoning - Skills: [
web-search]web-search: Survey chess variants (Fairy chess, Pocket chess, Really Bad Chess) for rule inspiration
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave P0.1 (with P0.1, P0.2, P0.4)
- Blocks: P3.4-P3.8 (preset implementations)
- Blocked By: None
References:
- Fairy chess variants: https://en.wikipedia.org/wiki/Fairy_chess_piece
- Chess.dougdoug.com (concept inspiration)
Acceptance Criteria:
- File
packages/chess/RULES.mdexists [ "$(grep -c '^### ' packages/chess/RULES.md)" -eq "15" ]→ true (exactly 15 rule headings)grep -c '\*\*ID\*\*:' packages/chess/RULES.md== 15grep -c '\*\*Incompatible With\*\*:' packages/chess/RULES.md== 15- Rule IDs unique:
grep -oE '\*\*ID\*\*: [a-z-]+' packages/chess/RULES.md | sort -u | wc -l== 15
QA Scenarios:
Scenario: Exactly 15 unique preset rules defined Tool: Bash Steps: 1. Run: test -f packages/chess/RULES.md 2. Run: grep -c '^### ' packages/chess/RULES.md 3. Run: grep -oE '\*\*ID\*\*: [a-z0-9-]+' packages/chess/RULES.md | sort -u | wc -l Expected Result: step 1 exit 0; step 2 outputs 15; step 3 outputs 15 Evidence: .sisyphus/evidence/task-P0.3-rules-count.log Scenario: No TBD placeholders (failure path) Tool: Bash Steps: 1. Run: grep -E '(TBD|TODO|FIXME|example rule|placeholder)' packages/chess/RULES.md && echo "FAIL" || echo "OK" Expected Result: stdout "OK" Evidence: .sisyphus/evidence/task-P0.3-no-placeholders.logCommit: YES
- Message:
docs(chess): author RULES.md with 15 concrete preset custom rules - Files:
packages/chess/RULES.md - Pre-commit: none
- Create directory
-
P0.4. Author
packages/server/PROTOCOL.md— WebSocket protocol v1What to do:
- Create directory
packages/server/ - Write
packages/server/PROTOCOL.mddefining WebSocket protocol v1 - Include
## Overviewexplaining: all messages have top-levelv: 1; all includeseq: number(monotonic); all includets: number(unix ms); mismatchedv→ hard disconnect; max message 64KB; rate limit 100 msg/sec/client; origin allow-list - Enumerate at least 8 message types, each as
### Message: {name}with subsections:**Direction**(C→S | S→C | bidir),**Purpose**,**JSON Schema**(fenced zod-like pseudo-schema or JSON example),**Example**(fenced json),**Error Cases**(listed) - Required message types:
room.create,room.join,room.leave,game.move(C→S intent),game.state(S→C full snapshot on join/reconnect),game.delta(S→C fact changes per tick),game.end,error - Include
## Reconnection Flow— client disconnects, 60s window, reconnect with last seenseq, server replays deltas since that seq - Include
## Auth— room code 6 chars [A-Z0-9]; optional room token (UUID v4) returned on create; every subsequent message includes token - Include
## Rate Limiting— token bucket per connection, 100 msg/sec, burst 20; over-limit → disconnect witherrorcodeRATE_LIMIT
Must NOT do:
- Do NOT define message types requiring session persistence across server restart (v1 in-memory only)
- Do NOT define spectator-related messages (v1 2-player only)
- Do NOT define rule-mutation-during-game messages (v1 between-games only)
Recommended Agent Profile:
- Category:
deep— Protocol design requires precision and failure-mode reasoning - Skills: [
web-search,code-search]web-search: Look at lichess/chess.com WebSocket patternscode-search: Find battle-tested WebSocket protocols (e.g., y-websocket, automerge)
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave P0.1 (with P0.1, P0.2, P0.3)
- Blocks: Phase 4 (all server tasks)
- Blocked By: None
References:
- y-websocket protocol docs (well-designed minimal WS protocol)
- RFC 6455 (WebSocket) for base protocol
Acceptance Criteria:
- File
packages/server/PROTOCOL.mdexists [ "$(grep -c '^### Message: ' packages/server/PROTOCOL.md)" -ge "8" ]→ truegrep -q 'Reconnection Flow' packages/server/PROTOCOL.md→ 0 exitgrep -q 'Rate Limiting' packages/server/PROTOCOL.md→ 0 exitgrep -q 'v: 1' packages/server/PROTOCOL.md→ 0 exit
QA Scenarios:
Scenario: Protocol defines all 8+ required message types Tool: Bash Steps: 1. Run: test -f packages/server/PROTOCOL.md 2. Run: grep -c '^### Message: ' packages/server/PROTOCOL.md 3. Run: for m in "room.create" "room.join" "room.leave" "game.move" "game.state" "game.delta" "game.end" "error"; do grep -qF "### Message: $m" packages/server/PROTOCOL.md || echo "MISSING: $m"; done Expected Result: step 1 exit 0; step 2 ≥ 8; step 3 outputs nothing Evidence: .sisyphus/evidence/task-P0.4-messages.log Scenario: Rate-limit and auth sections present (failure path) Tool: Bash Steps: 1. Run: grep -qc 'Rate Limiting' packages/server/PROTOCOL.md && grep -qc 'Auth' packages/server/PROTOCOL.md && echo "OK" || echo "FAIL" Expected Result: stdout "OK" Evidence: .sisyphus/evidence/task-P0.4-sections.logCommit: YES
- Message:
docs(server): author PROTOCOL.md defining WebSocket protocol v1 - Files:
packages/server/PROTOCOL.md - Pre-commit: none
- Create directory
-
P0.5. Scaffold monorepo skeleton (Bun workspaces + tsconfig + eslint + vitest + playwright)
What to do:
- Root:
package.jsonwith"workspaces": ["packages/*"],"private": true,"packageManager": "bun@latest" - Root scripts:
check(runs typecheck + lint + test),typecheck(bun x tsc -b),lint(bun x eslint),test(bun x vitest run),test:coverage(vitest run --coverage),build(bun run --filter '*' build),size-limit(placeholder) - Root
tsconfig.base.json: target ES2022, module ESNext, moduleResolution Bundler, strict: true, noImplicitAny, exactOptionalPropertyTypes, noUncheckedIndexedAccess, verbatimModuleSyntax - Root
tsconfig.json: references to all packages - Root
eslint.config.js(flat): typescript-eslint strict preset;no-restricted-globalsbanDate,Math.random,performance,setTimeout,setInterval,fetchwithinpackages/rete/src/**/rhs/**and engine RHS paths (override-based);@typescript-eslint/no-explicit-anyerror - Root
vitest.workspace.tslisting all packages - Root
playwright.config.tswith chess app base URL (http://localhost:5173) - Packages:
packages/rete/package.json("name": "@paratype/rete", type module, main dist/index.js, types dist/index.d.ts),tsconfig.jsonextending base, emptysrc/index.tswithexport {},README.md(one-line description) - Same skeleton for
packages/chess("name": "@paratype/chess") andpackages/server("name": "@paratype/chess-server") - Add
.gitignore:node_modules/,dist/,.sisyphus/evidence/,*.log,.DS_Store,coverage/,playwright-report/,test-results/ - Add
LICENSE(MIT) with paratype org name - Add root
README.md: project overview, link to SPEC/PHASES/RULES/PROTOCOL
Must NOT do:
- Do NOT install production dependencies beyond what's needed for scaffolding (TypeScript, Vitest, ESLint, Playwright, tsup)
- Do NOT add Immer/React/Vite yet (Phase 3 concern)
- Do NOT add WebSocket / pino yet (Phase 4 concern)
- Do NOT write any engine/chess/server source code beyond
export {}
Recommended Agent Profile:
- Category:
unspecified-high— Tooling setup with many moving parts - Skills: [
context7]context7: Look up Bun workspace, Vitest workspace, Playwright, ESLint flat config docs
Parallelization:
- Can Run In Parallel: NO (sole foundation task)
- Parallel Group: Wave P0.2 (sequential)
- Blocks: P0.6 and ALL implementation tasks
- Blocked By: P0.1, P0.2 (need SPEC to know package boundaries)
References:
Pattern References: None (greenfield)
External References:
- Bun workspaces: https://bun.sh/docs/install/workspaces
- Vitest workspace: https://vitest.dev/guide/workspace
- Playwright config: https://playwright.dev/docs/test-configuration
- typescript-eslint flat config: https://typescript-eslint.io/packages/typescript-eslint/#flat-config
Acceptance Criteria:
bun installexits 0bun run checkexits 0 (zero tests OK; zero lint errors)bun run buildexits 0 (emits dist/ for each package OR exits 0 with skip — depends on tsup wiring; at minimumtsc -bpasses)- Files exist:
package.json,tsconfig.base.json,tsconfig.json,eslint.config.js,vitest.workspace.ts,playwright.config.ts,.gitignore,LICENSE,README.md - Directory tree:
packages/rete/{package.json,tsconfig.json,src/index.ts,README.md}, same forchessandserver
QA Scenarios:
Scenario: Fresh clone installs and checks clean Tool: Bash Preconditions: repo on fresh checkout; Bun installed Steps: 1. Run: bun install 2>&1 | tee /tmp/p05-install.log 2. Run: bun run check 2>&1 | tee /tmp/p05-check.log 3. Run: bun run build 2>&1 | tee /tmp/p05-build.log Expected Result: step 1 exits 0; step 2 exits 0; step 3 exits 0; no errors in logs Failure Indicators: any non-zero exit, "error" token in logs Evidence: .sisyphus/evidence/task-P0.5-install-check-build.log Scenario: ESLint rejects Math.random in engine RHS path (failure path validating config correctness) Tool: Bash Preconditions: scaffold complete Steps: 1. Create temp file: mkdir -p packages/rete/src/rhs && printf 'export const x = () => Math.random();\n' > packages/rete/src/rhs/_temp.ts 2. Run: bun run lint 2>&1 | tee /tmp/p05-lint-fail.log 3. Capture exit: echo "exit=$?" 4. Cleanup: rm packages/rete/src/rhs/_temp.ts Expected Result: step 2 outputs ESLint error referencing Math.random and exits non-zero Evidence: .sisyphus/evidence/task-P0.5-lint-rejects-random.log Scenario: Workspace package names are correct Tool: Bash Steps: 1. Run: jq -r .name packages/rete/package.json 2. Run: jq -r .name packages/chess/package.json 3. Run: jq -r .name packages/server/package.json Expected Result: outputs "@paratype/rete", "@paratype/chess", "@paratype/chess-server" respectively Evidence: .sisyphus/evidence/task-P0.5-pkg-names.logCommit: YES
- Message:
chore(root): scaffold monorepo with Bun workspaces, TypeScript, Vitest, ESLint, Playwright - Files:
package.json,tsconfig.base.json,tsconfig.json,eslint.config.js,vitest.workspace.ts,playwright.config.ts,.gitignore,LICENSE,README.md,packages/*/package.json,packages/*/tsconfig.json,packages/*/src/index.ts,packages/*/README.md,bun.lockb - Pre-commit:
bun run check(hook installed next task)
- Root:
-
P0.6. CI pipeline (
.github/workflows/ci.yml) + pre-commit hook (lefthook)What to do:
- Create
.github/workflows/ci.yml:- Trigger: pull_request, push to main
- Jobs:
check(typecheck, lint, test with coverage upload),build(build all packages, upload dist artifacts),e2e(Playwright headless),size(bundle size check),audit(bun audit) - All on ubuntu-latest with
oven-sh/setup-bun@v1pinning to stable - Cache:
~/.bun/install/cache - Upload Playwright traces on failure
- Create
lefthook.ymlat root with pre-commit hook runningbun run check(fast — typecheck + lint + unit tests only, not Playwright) - Install lefthook as dev dep; add
postinstallscript runningbunx lefthook install - Add
.github/workflows/README.mdexplaining CI status badges - Add size-limit config to root
package.json(size-limit dev dep; initial budget: engine 50KB, chess 200KB — both placeholders until dist exists; the CI job passes when empty)
Must NOT do:
- Do NOT add Node.js matrix (Bun only, per decision)
- Do NOT add deployment workflows (out of scope)
- Do NOT skip
bun audit(security requirement)
Recommended Agent Profile:
- Category:
unspecified-high - Skills: [
context7,code-search]context7: Look up currentoven-sh/setup-bunaction optionscode-search: Find production CI workflows for Bun monorepos on grep.app
Parallelization:
- Can Run In Parallel: NO
- Parallel Group: Wave P0.2 (after P0.5)
- Blocks: All subsequent commits (CI becomes a required status check)
- Blocked By: P0.5
References:
- setup-bun action: https://github.com/oven-sh/setup-bun
- lefthook: https://github.com/evilmartians/lefthook
- size-limit: https://github.com/ai/size-limit
Acceptance Criteria:
.github/workflows/ci.ymlexists and passesactionlint(bun x @action-validator/cli action-validator .github/workflows/ci.ymlORgh workflow viewafter push)lefthook.ymlexists at rootbun run checkis wired as pre-commit (runningbunx lefthook run pre-commitexecutes check)- First push triggers CI; all jobs green
gh run list --limit 1 --json conclusion -q '.[0].conclusion'returns"success"
QA Scenarios:
Scenario: CI green on first push Tool: Bash Preconditions: remote configured; push enabled Steps: 1. Run: git add -A && git commit -m "ci: verify pipeline" --allow-empty 2. Run: git push 3. Wait: sleep 120 (or poll with gh run watch) 4. Run: gh run list --limit 1 --json conclusion,databaseId,url -q '.[0]' Expected Result: stdout contains `"conclusion":"success"` and a URL Evidence: .sisyphus/evidence/task-P0.6-ci-success.json Scenario: Pre-commit hook blocks bad commit (failure path) Tool: Bash Preconditions: hook installed Steps: 1. Run: echo 'const x: any = 1;' > packages/rete/src/_bad.ts 2. Run: git add packages/rete/src/_bad.ts 3. Run: git commit -m "bad" 2>&1 | tee /tmp/p06-hook.log; echo "exit=$?" 4. Cleanup: git reset HEAD && rm packages/rete/src/_bad.ts Expected Result: commit fails; log shows ESLint "no-explicit-any" error Evidence: .sisyphus/evidence/task-P0.6-hook-blocks.log Scenario: actionlint accepts workflow Tool: Bash Steps: 1. Run: bun x @action-validator/cli action-validator .github/workflows/ci.yml Expected Result: exit 0 Evidence: .sisyphus/evidence/task-P0.6-actionlint.logCommit: YES
- Message:
ci(root): add GitHub Actions pipeline and lefthook pre-commit hook - Files:
.github/workflows/ci.yml,.github/workflows/README.md,lefthook.yml,package.json(size-limit config + lefthook dep),bun.lockb - Pre-commit:
bun run check
- Create
Phase 1 — Engine Pararules Parity (TDD)
-
P1.1. Schema + Fact type with typed attributes (TDD)
What to do:
- RED: In
packages/rete/src/schema.test.ts, write failing tests:defineSchema({ Health: 'number', Position: 'Vec2' })returns object with keyed attrs typed correctly- Attempting to create a
Factwith wrong value type for an attr produces a TypeScript type error (type-level test via@ts-expect-errorcomments in a.type-test.tsfile) - Runtime fact creation:
fact(id, attr, value)returns{ id, attr, value }with branded types
- GREEN: Implement in
packages/rete/src/schema.ts:export function defineSchema<S extends Record<string, unknown>>(defs: S)returning typed schema objectexport type Fact<S>as tagged union discriminated byattrkeyexport function fact<S, K extends keyof S>(id: EntityId, attr: K, value: S[K]): Fact<S>EntityIdas brandednumberviatype EntityId = number & { readonly __brand: 'EntityId' }
- REFACTOR: Extract type utilities to
schema.types.tsif file exceeds 150 LOC; add JSDoc on public exports only - Export from
packages/rete/src/index.ts
Must NOT do:
- Do NOT use
anyorunknown as Xcasts - Do NOT expose Immer (Phase 3 concern)
- Do NOT allow runtime attr name collisions silently — error-throw on duplicate
Recommended Agent Profile:
- Category:
deep - Skills: [
context7]context7: Look up TypeScript branded types and discriminated unions best practices
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave P1.1 (with P1.2, P1.3, P1.4, P1.5, P1.6)
- Blocks: P1.7-P1.13 (beta network, derived facts all depend on Fact type)
- Blocked By: P0.5, P0.6 (scaffold + CI); P0.1 (SPEC.md defines fact shape)
References:
Pattern References:
packages/rete/SPEC.md§Fact Model — canonical fact shape
External References:
- Branded types: https://egghead.io/blog/using-branded-types-in-typescript
- Discriminated unions: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions
Acceptance Criteria:
bun test packages/rete/src/schema.test.ts→ all greenbun x tsc --noEmit -p packages/rete/tsconfig.json→ 0 errors- Type-level tests in
schema.type-test.tscompile (failures are intentional via@ts-expect-error) - Coverage of
schema.ts≥ 95% line
QA Scenarios:
Scenario: Schema + fact round-trip with correct types Tool: Bash Steps: 1. Run: bun test packages/rete/src/schema.test.ts 2>&1 | tee /tmp/p11-test.log 2. Run: grep -E '(PASS|FAIL|Tests )' /tmp/p11-test.log Expected Result: output contains "PASS" and final line "Tests {N} passed" with 0 failures Evidence: .sisyphus/evidence/task-P1.1-schema-tests.log Scenario: Type-level rejection of invalid value (failure path) Tool: Bash Steps: 1. Run: bun x tsc --noEmit -p packages/rete/tsconfig.json 2>&1 | tee /tmp/p11-tsc.log 2. Run: grep -c 'error TS' /tmp/p11-tsc.log Expected Result: step 1 exits 0; step 2 outputs 0 (all @ts-expect-error annotations consumed cleanly) Evidence: .sisyphus/evidence/task-P1.1-tsc.logCommit: YES
- Message:
feat(rete): add schema and typed Fact primitives (P1.1) - Files:
packages/rete/src/schema.ts,packages/rete/src/schema.types.ts,packages/rete/src/schema.test.ts,packages/rete/src/schema.type-test.ts,packages/rete/src/index.ts - Pre-commit:
bun run check
- RED: In
-
P1.2. Working-memory (WM) storage + retrieval (TDD)
What to do:
- RED:
packages/rete/src/wm.test.ts— failing tests:WM.insert(id, attr, value)stores fact; duplicate(id, attr)replaces value (update semantics per SPEC)WM.retract(id, attr)removes fact; returns true if existed, false if notWM.contains(id, attr)returns booleanWM.get(id, attr)returns value or undefinedWM.allFacts()returns sorted stable array (sort key:[id, attr]) — iteration determinism per SPEC §Iteration Order
- GREEN:
packages/rete/src/wm.ts—class WorkingMemory<S>usingMap<EntityId, Map<AttrKey, FactValue>>;allFacts()flattens and sorts - REFACTOR: Add internal change-subscription hook (array of listener callbacks) called on every insert/retract — used later by alpha network. Document the subscription API in JSDoc.
Must NOT do:
- Do NOT emit events during iteration (mutation-during-iteration = undefined behavior)
- Do NOT expose raw Map objects (encapsulation)
Recommended Agent Profile:
- Category:
deep - Skills: []
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave P1.1 (with P1.1, P1.3-P1.6)
- Blocks: P1.3 (alpha consumes WM events), P1.7 (beta), P1.10 (query)
- Blocked By: P0.5, P0.6, P0.1
References:
packages/rete/SPEC.md§Fact Model, §Iteration Order
Acceptance Criteria:
bun test packages/rete/src/wm.test.tsall green- Coverage ≥ 95%
- No raw Map/Set exposed in public API (
grep -E 'export (const|function|class).*(Map|Set)' packages/rete/src/wm.tsempty)
QA Scenarios:
Scenario: WM insert/get/retract/contains semantics Tool: Bash Steps: 1. Run: bun test packages/rete/src/wm.test.ts 2>&1 | tee /tmp/p12.log Expected Result: "Tests {N} passed, 0 failed" Evidence: .sisyphus/evidence/task-P1.2-wm.log Scenario: allFacts() returns deterministic order (failure path for non-determinism) Tool: Bash Steps: 1. Run: bun test packages/rete/src/wm.test.ts -t "allFacts deterministic order" 2>&1 | tee /tmp/p12-order.log Expected Result: test named "allFacts deterministic order" passes; verifies same order across multiple invocations with Map insertion-order permutation Evidence: .sisyphus/evidence/task-P1.2-wm-order.logCommit: YES
- Message:
feat(rete): add WorkingMemory with deterministic iteration (P1.2) - Files:
packages/rete/src/wm.ts,packages/rete/src/wm.test.ts,packages/rete/src/index.ts - Pre-commit:
bun run check
- RED:
-
P1.3. Alpha network: fact indexing by (id, attr) pattern (TDD)
What to do:
- RED:
packages/rete/src/alpha.test.ts— failing tests:- AlphaNode matches facts by optional id-wildcard + required attr key; stores matched facts in AlphaMemory
- Inserting a fact dispatches it to all matching AlphaNodes
- Retracting a fact removes it from AlphaMemories
- A condition like
(Player, X, ?x)creates one alpha node indexed by(attr=X, id=Player);(?id, X, ?x)indexed by(attr=X)
- GREEN:
packages/rete/src/alpha.ts:class AlphaNetworksubscribes toWorkingMemoryeventsclass AlphaNodewithcondition: { id?: EntityId, attr: AttrKey }class AlphaMemoryholdsFact[]sorted by (id, attr)AlphaNetwork.buildNode(cond)— memoized: same condition → same node (sharing)- Emits change events (
activate(fact),deactivate(fact)) to downstream (beta) subscribers
- REFACTOR: Extract indexing (attr → AlphaNode[]) as inverted index; ensure O(1) dispatch per fact
Must NOT do:
- Do NOT scan all alpha nodes per fact (must use index)
- Do NOT retain references to retracted facts
Recommended Agent Profile:
- Category:
deep - Skills: [
context7]context7: Rete alpha network implementation patterns
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave P1.1
- Blocks: P1.7 (beta network), P1.8 (joins)
- Blocked By: P0.5, P0.6, P0.1, P1.2 (WM events)
References:
packages/rete/SPEC.md§Fact Model- Doorenbos thesis §2.2 (Alpha Network)
Acceptance Criteria:
bun test packages/rete/src/alpha.test.tsall green- Coverage ≥ 90%
- Dispatch is O(1) per fact: benchmark test asserting 10k inserts in <50ms
QA Scenarios:
Scenario: Alpha network dispatches to matching nodes only Tool: Bash Steps: 1. Run: bun test packages/rete/src/alpha.test.ts 2>&1 | tee /tmp/p13.log Expected Result: "Tests {N} passed, 0 failed" Evidence: .sisyphus/evidence/task-P1.3-alpha.log Scenario: Alpha dispatch performance (failure path if slow) Tool: Bash Steps: 1. Run: bun test packages/rete/src/alpha.test.ts -t "dispatch 10000 facts in under 50ms" 2>&1 | tee /tmp/p13-perf.log Expected Result: test passes; log includes timing assertion under 50ms Evidence: .sisyphus/evidence/task-P1.3-alpha-perf.logCommit: YES
- Message:
feat(rete): add AlphaNetwork with inverted-index dispatch (P1.3) - Files:
packages/rete/src/alpha.ts,packages/rete/src/alpha.test.ts,packages/rete/src/index.ts - Pre-commit:
bun run check
- RED:
-
P1.4. Session lifecycle: init, add rule, fire (TDD)
What to do:
- RED:
packages/rete/src/session.test.ts— failing tests:const session = new Session(schema, { autoFire: false })creates sessionsession.add(rule)registers a rule (rule definition opaque for now; covered by P1.5)session.insert(id, attr, value)/session.retract(id, attr)delegate to WMsession.fireRules()returns number of rules that fired- With
autoFire: true, insert/retract auto-calls fireRules session.fireRules({ recursionLimit: 64 })— cycle detection (covered by P1.12, stub throws)
- GREEN:
packages/rete/src/session.ts:class Session<S>holdingWorkingMemory<S>,AlphaNetwork,ProductionNode[], config{ autoFire, recursionLimit }- Public API:
add(prod),insert,retract,fireRules,contains,get,allFacts - Fire: iterate pending activations in deterministic order (per SPEC conflict resolution), call RHS, repeat until fixed-point or recursion limit
Must NOT do:
- Do NOT leak internal AlphaNetwork / beta / production types to public API
- Do NOT implement conflict resolution yet (P1.13) — stub with insertion-order
Recommended Agent Profile:
- Category:
deep - Skills: []
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave P1.1
- Blocks: P1.7-P1.14 (all downstream engine tasks need Session)
- Blocked By: P1.2, P1.3 (WM + Alpha ready)
References:
packages/rete/SPEC.md§Conflict Resolution (stub per insertion-order), §RHS Purity Contract
Acceptance Criteria:
bun test packages/rete/src/session.test.tsall green- Public API surface locked via
typeexport;tsdorexpect-typeverifies noanyleaks - Coverage ≥ 90%
QA Scenarios:
Scenario: Session lifecycle (insert, fire, retract) Tool: Bash Steps: 1. Run: bun test packages/rete/src/session.test.ts 2>&1 | tee /tmp/p14.log Expected Result: "Tests {N} passed, 0 failed" Evidence: .sisyphus/evidence/task-P1.4-session.log Scenario: autoFire flag controls behavior (failure path) Tool: Bash Steps: 1. Run: bun test packages/rete/src/session.test.ts -t "autoFire=false does not fire on insert" 2>&1 | tee /tmp/p14-af.log Expected Result: named test passes Evidence: .sisyphus/evidence/task-P1.4-session-autofire.logCommit: YES
- Message:
feat(rete): add Session lifecycle (P1.4) - Files:
packages/rete/src/session.ts,packages/rete/src/session.test.ts,packages/rete/src/index.ts - Pre-commit:
bun run check
- RED:
-
P1.5. Typed TS builder API + handler registry (TDD)
What to do:
- RED:
packages/rete/src/builder.test.ts— failing tests:rule('name').what((Player, X, v('x'))).what((Player, Y, v('y'))).then('moveHandler', ['x', 'y'])produces aRuleDefinitionobject with conditions andhandlerrefHandlerRegistry.register('moveHandler', (session, match) => { ... })stores the function- Attempting to build a rule referencing an unregistered handler throws (at build time, not fire time)
- Variable bindings use
v('name')helper; unbound variables cause type error
- GREEN:
packages/rete/src/builder.ts— fluent builder returningRuleDefinition - GREEN:
packages/rete/src/registry.ts—HandlerRegistry(Map-backed, withregister,get,has,verify) - Session.add validates all referenced handlers exist via
registry.verify(rule)
Must NOT do:
- Do NOT allow function references directly in conditions (must be via registry name) — this enforces JSON serializability from day 1
- Do NOT use
evalornew Function
Recommended Agent Profile:
- Category:
deep - Skills: [
context7]context7: TypeScript builder-pattern type inference
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave P1.1
- Blocks: P1.6 (JSON serialization needs builder output), P1.7+ (all rule tests use builder)
- Blocked By: P1.1 (schema types)
References:
packages/rete/SPEC.md§JSON Rule Schema (handler-registry pattern)
Acceptance Criteria:
bun test packages/rete/src/builder.test.tsgreenbun test packages/rete/src/registry.test.tsgreen- Coverage ≥ 90%
grep -r "new Function\|eval(" packages/rete/src→ empty
QA Scenarios:
Scenario: Builder produces serializable rule definitions Tool: Bash Steps: 1. Run: bun test packages/rete/src/builder.test.ts 2>&1 | tee /tmp/p15.log Expected Result: all pass Evidence: .sisyphus/evidence/task-P1.5-builder.log Scenario: No eval/Function anywhere in engine src (failure path) Tool: Bash Steps: 1. Run: grep -rE "new Function|eval\(" packages/rete/src 2>&1 | tee /tmp/p15-grep.log; echo "exit=$?" Expected Result: grep exits 1 (no matches); log empty Evidence: .sisyphus/evidence/task-P1.5-no-eval.logCommit: YES
- Message:
feat(rete): add typed rule builder + handler registry (P1.5) - Files:
packages/rete/src/builder.ts,packages/rete/src/registry.ts,packages/rete/src/builder.test.ts,packages/rete/src/registry.test.ts,packages/rete/src/index.ts - Pre-commit:
bun run check
- RED:
-
P1.6. JSON serialization round-trip (TDD)
What to do:
- RED:
packages/rete/src/serialize.test.ts— failing tests:serialize(rule)produces JSON conforming to SPEC §JSON Rule Schemadeserialize(json, registry)produces aRuleDefinitionequivalent (deep-equal after normalization)- Round-trip:
deserialize(serialize(rule)) ≡ rulefor every shape (conditions, variables, handler refs, salience) - Deserialization with unknown handler throws
UnknownHandlerError - Schema validation (zod or hand-rolled) rejects malformed JSON
- GREEN:
packages/rete/src/serialize.tswithserialize,deserialize, exported JSON schema (asRULE_SCHEMA_V1constant)
Must NOT do:
- Do NOT support "v0" or back-compat (there is no prior version)
- Do NOT serialize runtime function references
Recommended Agent Profile:
- Category:
deep - Skills: []
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave P1.1
- Blocks: P3.13 (JSON import/export UI), P4.2 (server protocol)
- Blocked By: P1.5 (builder types)
References:
packages/rete/SPEC.md§JSON Rule Schema
Acceptance Criteria:
bun test packages/rete/src/serialize.test.tsgreen- Round-trip test covers ≥10 distinct rule shapes
- Coverage ≥ 95%
QA Scenarios:
Scenario: Round-trip 10 distinct rule shapes Tool: Bash Steps: 1. Run: bun test packages/rete/src/serialize.test.ts 2>&1 | tee /tmp/p16.log 2. Run: grep -c "round-trip shape" /tmp/p16.log Expected Result: all tests pass; step 2 outputs ≥ 10 Evidence: .sisyphus/evidence/task-P1.6-roundtrip.log Scenario: Malformed JSON rejected (failure path) Tool: Bash Steps: 1. Run: bun test packages/rete/src/serialize.test.ts -t "malformed JSON throws" 2>&1 | tee /tmp/p16-bad.log Expected Result: named test passes Evidence: .sisyphus/evidence/task-P1.6-bad-json.logCommit: YES
- Message:
feat(rete): add JSON serialize/deserialize round-trip (P1.6) - Files:
packages/rete/src/serialize.ts,packages/rete/src/serialize.test.ts,packages/rete/src/index.ts - Pre-commit:
bun run check
- RED:
-
P1.7. Beta network: memory + token propagation (TDD)
What to do:
- RED:
packages/rete/src/beta.test.ts— failing tests covering single-condition rule (beta reduces to alpha), two-condition rule (one join), three-condition chain - GREEN:
packages/rete/src/beta.ts—BetaMemory,Token(parent + fact chain), activation/deactivation propagation; each production node accumulates full matches
Must NOT do:
- Do NOT allocate new Tokens on every fact change if shared chains unchanged (reuse via parent reference)
Recommended Agent Profile:
- Category:
deep - Skills: [
context7]
Parallelization:
- Can Run In Parallel: YES (Wave P1.2 with P1.8, P1.9, P1.10)
- Blocks: P1.11-P1.14, P2.*
- Blocked By: P1.3 (alpha), P1.4 (session)
References:
packages/rete/SPEC.md§Iteration Order; Doorenbos §2.4Acceptance Criteria:
bun test packages/rete/src/beta.test.tsgreen- Coverage ≥ 90%
QA Scenarios:
Scenario: Multi-condition rule produces join matches Tool: Bash Steps: 1. Run: bun test packages/rete/src/beta.test.ts 2>&1 | tee /tmp/p17.log Expected Result: all pass Evidence: .sisyphus/evidence/task-P1.7-beta.log Scenario: Retraction removes join matches (failure path) Tool: Bash Steps: 1. Run: bun test packages/rete/src/beta.test.ts -t "retraction removes dependent tokens" 2>&1 | tee /tmp/p17-ret.log Expected Result: pass Evidence: .sisyphus/evidence/task-P1.7-retract.logCommit: YES
- Message:
feat(rete): add BetaMemory + Token propagation (P1.7) - Files:
packages/rete/src/beta.ts,packages/rete/src/beta.test.ts,packages/rete/src/index.ts - Pre-commit:
bun run check
- RED:
-
P1.8. Join nodes with variable binding (TDD)
What to do:
- RED:
packages/rete/src/join.test.ts— join on shared variable?id(e.g.,(?id, X, ?x)∧(?id, Y, ?y)must match when id is the same), numeric equality tests - GREEN:
packages/rete/src/join.ts— JoinNode with tests[] (equality constraints between left token's binding and right fact's field) - Handle many-to-many, many-to-one, and cross-product cases
Must NOT do:
- Do NOT implement inequality tests yet (those go in P1.9 conditions)
Recommended Agent Profile:
- Category:
deep
Parallelization: YES — Wave P1.2
- Blocks: P1.11-P1.14, P2.*
- Blocked By: P1.7
References: Doorenbos §2.5
Acceptance Criteria:
bun test packages/rete/src/join.test.tsgreen- Coverage ≥ 90%
- Benchmark: 100 entities × 3-condition join < 10ms
QA Scenarios:
Scenario: Multi-variable join matches entities consistently Tool: Bash Steps: 1. Run: bun test packages/rete/src/join.test.ts 2>&1 | tee /tmp/p18.log Expected Result: all pass Evidence: .sisyphus/evidence/task-P1.8-join.log Scenario: Join perf benchmark (failure path) Tool: Bash Steps: 1. Run: bun test packages/rete/src/join.test.ts -t "3-condition join on 100 entities under 10ms" 2>&1 | tee /tmp/p18-perf.log Expected Result: pass Evidence: .sisyphus/evidence/task-P1.8-join-perf.logCommit: YES
- Message:
feat(rete): add JoinNode with variable-binding equality tests (P1.8) - Files:
packages/rete/src/join.ts,packages/rete/src/join.test.ts,packages/rete/src/index.ts - Pre-commit:
bun run check
- RED:
-
P1.9. Condition filters (cond equivalent) (TDD)
What to do:
- RED:
packages/rete/src/condition.test.ts— filter predicates applied after join; predicates are registered (via registry, for JSON serializability) - GREEN:
packages/rete/src/condition.ts—FilterNodeholdingpredicate: string(registry key) +args: JsonValue[]; applies to incoming tokens
Must NOT do:
- Do NOT allow inline arrow functions in conditions (must use registry)
Recommended Agent Profile:
deepParallelization: YES — Wave P1.2
- Blocks: P1.11-P1.14, P2.*
- Blocked By: P1.8 (join produces tokens to filter)
References:
packages/rete/SPEC.md§JSON Rule SchemaAcceptance Criteria:
bun test packages/rete/src/condition.test.tsgreen- Coverage ≥ 90%
QA Scenarios:
Scenario: Filter predicate correctly rejects tokens Tool: Bash Steps: 1. Run: bun test packages/rete/src/condition.test.ts 2>&1 | tee /tmp/p19.log Expected Result: all pass Evidence: .sisyphus/evidence/task-P1.9-cond.log Scenario: Unregistered predicate throws (failure path) Tool: Bash Steps: 1. Run: bun test packages/rete/src/condition.test.ts -t "unknown predicate throws" 2>&1 Expected Result: pass Evidence: .sisyphus/evidence/task-P1.9-unknown.logCommit: YES
- Message:
feat(rete): add FilterNode with registered predicates (P1.9) - Files:
packages/rete/src/condition.ts,packages/rete/src/condition.test.ts,packages/rete/src/index.ts - Pre-commit:
bun run check
- RED:
-
P1.10. Query API: query / queryAll (TDD)
What to do:
- RED:
packages/rete/src/query.test.ts—session.query(rule)returns first match or throws;session.queryAll(rule)returns all;session.query(rule, { bindings })filters by binding value - GREEN:
packages/rete/src/query.ts— wraps production node's accumulated matches; deterministic order per SPEC
Must NOT do:
- Do NOT allow query on rules without registered production
Recommended Agent Profile:
deepParallelization: YES — Wave P1.2
- Blocks: P2.*
- Blocked By: P1.8 (beta produces tokens)
References:
packages/rete/SPEC.md§Iteration OrderAcceptance Criteria:
bun test packages/rete/src/query.test.tsgreen- Coverage ≥ 95%
QA Scenarios:
Scenario: query returns deterministic ordering Tool: Bash Steps: 1. Run: bun test packages/rete/src/query.test.ts 2>&1 | tee /tmp/p110.log Expected Result: all pass Evidence: .sisyphus/evidence/task-P1.10-query.log Scenario: query on missing rule throws (failure path) Tool: Bash Steps: 1. Run: bun test packages/rete/src/query.test.ts -t "query on unknown rule throws" 2>&1 Expected Result: pass Evidence: .sisyphus/evidence/task-P1.10-unknown.logCommit: YES
- Message:
feat(rete): add query/queryAll API (P1.10) - Files:
packages/rete/src/query.ts,packages/rete/src/query.test.ts,packages/rete/src/index.ts - Pre-commit:
bun run check
- RED:
-
P1.11. Derived facts via thenFinally-equivalent (TDD)
What to do:
- RED:
packages/rete/src/derived.test.ts—rule.thenFinally('aggregateHandler', [])fires after all activations of a tick; derived facts auto-retract when supporting matches disappear (truth maintenance) - GREEN:
packages/rete/src/derived.ts—ProductionNode.thenFinallyhandler; tracks derived facts per match chain; on match removal, retracts corresponding derived fact
Must NOT do:
- Do NOT allow derived fact id collision with user facts (derived facts use negative EntityIds)
Recommended Agent Profile:
deepParallelization: YES — Wave P1.3
- Blocks: P1.14, P2.21 (repetition detection uses derived facts)
- Blocked By: P1.7-P1.10
References:
packages/rete/SPEC.md§Truth MaintenanceAcceptance Criteria:
bun test packages/rete/src/derived.test.tsgreen- Coverage ≥ 90%
QA Scenarios:
Scenario: thenFinally aggregates after tick Tool: Bash Steps: 1. Run: bun test packages/rete/src/derived.test.ts 2>&1 | tee /tmp/p111.log Expected Result: all pass Evidence: .sisyphus/evidence/task-P1.11-derived.log Scenario: Derived fact retracts when support retracts (failure path) Tool: Bash Steps: 1. Run: bun test packages/rete/src/derived.test.ts -t "derived retracts on support loss" 2>&1 Expected Result: pass Evidence: .sisyphus/evidence/task-P1.11-retract.logCommit: YES
- Message:
feat(rete): add derived facts with thenFinally + truth maintenance (P1.11) - Files:
packages/rete/src/derived.ts,packages/rete/src/derived.test.ts,packages/rete/src/index.ts - Pre-commit:
bun run check
- RED:
-
P1.12. Cycle detection with recursion limit (TDD)
What to do:
- RED:
packages/rete/src/cycle.test.ts— rule A inserts fact triggering rule B inserting fact triggering A (cycle);fireRules({ recursionLimit: 4 })throwsRecursionLimitExceededErrorwith cycle trace;recursionLimit: 0disables (for advanced use) - GREEN: wire recursion counter into Session.fireRules; build cycle trace (last N activations); error includes rule names
Must NOT do:
- Do NOT silently skip cycles (error must be loud)
Recommended Agent Profile:
deepParallelization: YES — Wave P1.3
- Blocks: P1.14
- Blocked By: P1.4 (session)
References:
packages/rete/SPEC.md§Cycle DetectionAcceptance Criteria:
bun test packages/rete/src/cycle.test.tsgreen- Coverage ≥ 95%
QA Scenarios:
Scenario: Cycle exceeding limit throws with trace Tool: Bash Steps: 1. Run: bun test packages/rete/src/cycle.test.ts 2>&1 | tee /tmp/p112.log Expected Result: all pass; trace includes rule names Evidence: .sisyphus/evidence/task-P1.12-cycle.log Scenario: recursionLimit 0 allows unlimited (failure path for infinite loop detection) Tool: Bash Steps: 1. Run: timeout 5 bun test packages/rete/src/cycle.test.ts -t "recursionLimit 0 runs to natural fixpoint" 2>&1 Expected Result: pass within 5s (natural fixpoint reached) Evidence: .sisyphus/evidence/task-P1.12-unlimited.logCommit: YES
- Message:
feat(rete): add cycle detection with recursionLimit (P1.12) - Files:
packages/rete/src/cycle.ts,packages/rete/src/cycle.test.ts,packages/rete/src/session.ts - Pre-commit:
bun run check
- RED:
-
P1.13. Deterministic conflict resolution (TDD)
What to do:
- RED:
packages/rete/src/conflict.test.ts— given N matching activations, firing order is: salience desc → specificity (# conditions) desc → insertion order asc; deterministic across runs - GREEN:
packages/rete/src/conflict.ts—orderActivations(activations)pure function; integrate into Session.fireRules
Must NOT do:
- Do NOT use
Math.randomfor tiebreaking - Do NOT sort by rule name lexicographically (that hides bugs via alphabetization)
Recommended Agent Profile:
deepParallelization: YES — Wave P1.3
- Blocks: P1.14
- Blocked By: P1.4
References:
packages/rete/SPEC.md§Conflict ResolutionAcceptance Criteria:
bun test packages/rete/src/conflict.test.tsgreen- Fuzz test: 100 random rule sets, 2 identical runs → identical fire order
- Coverage ≥ 95%
QA Scenarios:
Scenario: Fire order matches spec for mixed salience Tool: Bash Steps: 1. Run: bun test packages/rete/src/conflict.test.ts 2>&1 | tee /tmp/p113.log Expected Result: all pass Evidence: .sisyphus/evidence/task-P1.13-conflict.log Scenario: Determinism fuzz (failure path for non-det) Tool: Bash Steps: 1. Run: bun test packages/rete/src/conflict.test.ts -t "fuzz 100 rule sets yield identical fire order" 2>&1 Expected Result: pass Evidence: .sisyphus/evidence/task-P1.13-fuzz.logCommit: YES
- Message:
feat(rete): add deterministic conflict resolution (P1.13) - Files:
packages/rete/src/conflict.ts,packages/rete/src/conflict.test.ts,packages/rete/src/session.ts - Pre-commit:
bun run check
- RED:
-
P1.14. Pararules golden-file test port
What to do:
- Port 5-10 representative pararules tests from
paranim/pararules/tests/*.nimto TS/Vitest underpackages/rete/tests/golden/ - Each test = fixture (fact insertion script + rule definitions) + expected query results snapshot
- Add Vitest snapshots for derived-fact cases
- Document each golden's pararules-origin line reference in a
GOLDEN-MAP.md
Must NOT do:
- Do NOT skip tests that exercise derived facts / multi-condition joins
Recommended Agent Profile:
unspecified-high- Skills: [
repo-analysis]repo-analysis: Retrieve pararules tests from GitHub
Parallelization: NO — Wave P1.4 (parity gate)
- Blocks: Phase 2 start
- Blocked By: P1.1-P1.13
References:
- https://github.com/paranim/pararules/blob/master/tests/test1.nim
- https://github.com/paranim/pararules/blob/master/tests/test2.nim
- https://github.com/paranim/pararules/blob/master/tests/test3.nim
Acceptance Criteria:
bun test packages/rete/tests/golden→ all greenpackages/rete/tests/golden/GOLDEN-MAP.mdlists each ported test with origin line- Coverage of engine src ≥ 90%
- Tag
v0.1.0-phase1
QA Scenarios:
Scenario: Golden suite passes end-to-end Tool: Bash Steps: 1. Run: bun test packages/rete/tests/golden 2>&1 | tee /tmp/p114.log 2. Run: bun run test:coverage -- --coverage.reporter=text packages/rete 2>&1 | tee /tmp/p114-cov.log 3. Run: grep -oE 'All files.*[0-9.]+' /tmp/p114-cov.log | head -1 Expected Result: all tests pass; line coverage ≥ 90% Evidence: .sisyphus/evidence/task-P1.14-golden.log, .sisyphus/evidence/task-P1.14-cov.log Scenario: Phase 1 tag exists Tool: Bash Steps: 1. Run: git tag v0.1.0-phase1 2. Run: git tag | grep v0.1.0-phase1 Expected Result: tag output present Evidence: .sisyphus/evidence/task-P1.14-tag.logCommit: YES
- Message:
test(rete): port pararules golden tests; tag Phase 1 parity (P1.14) - Files:
packages/rete/tests/golden/*.test.ts,packages/rete/tests/golden/GOLDEN-MAP.md - Pre-commit:
bun run check - Post-commit:
git tag v0.1.0-phase1
- Port 5-10 representative pararules tests from
Phase 2 — Rete II Extensions + Chess Engine
-
P2.1. Negation nodes (NOT) (TDD)
What to do:
- RED:
packages/rete/src/negation.test.ts—rule.whatNot((Player, Dead, v(true)))matches only when no fact satisfies the negated pattern; activation toggles when blocking fact inserted/retracted - GREEN:
packages/rete/src/negation.ts—NegationNodeper Doorenbos §2.6; counts matching facts; token passes iff count is zero
Must NOT do: implement unsafe NOT (unbound vars in NOT) — reject at build time
Recommended Agent Profile:
deep; Skills: [context7] Parallelization: YES — Wave P2.1 (with P2.2, P2.3, P2.4) Blocks: P2.13, P2.18 (check detection uses NOT) Blocked By: P1.14 (Phase 1 gate)References: Doorenbos §2.6.1
Acceptance Criteria:
bun test packages/rete/src/negation.test.tsgreen- Coverage ≥ 90%
QA Scenarios:
Scenario: NOT fires when pattern absent; retracts when inserted Tool: Bash Steps: 1. Run: bun test packages/rete/src/negation.test.ts 2>&1 | tee /tmp/p21.log Expected Result: all pass Evidence: .sisyphus/evidence/task-P2.1-not.log Scenario: Unsafe NOT (unbound var) rejected at build (failure path) Tool: Bash Steps: 1. Run: bun test packages/rete/src/negation.test.ts -t "unsafe NOT rejected" 2>&1 Expected Result: pass Evidence: .sisyphus/evidence/task-P2.1-unsafe.logCommit: YES —
feat(rete): add negation nodes (NOT) (P2.1)— files:packages/rete/src/negation.ts,packages/rete/src/negation.test.ts - RED:
-
P2.2. Existential nodes (EXISTS) (TDD)
What to do:
rule.whatExists((Attacker, AttacksSquare, v('sq')))— EXISTS is negation-of-negation; propagate token if ≥1 matching fact. GREEN:packages/rete/src/existential.tsMust NOT do: double-count (increment on same fact twice) Recommended Agent Profile:
deepParallelization: YES — Wave P2.1 Blocks: P2.18, P2.19 Blocked By: P1.14 References: Doorenbos §2.6.2Acceptance Criteria:
bun test packages/rete/src/existential.test.tsgreen- Coverage ≥ 90%
QA Scenarios:
Scenario: EXISTS toggles correctly Tool: Bash Steps: 1. Run: bun test packages/rete/src/existential.test.ts 2>&1 | tee /tmp/p22.log Expected: all pass Evidence: .sisyphus/evidence/task-P2.2-exists.log Scenario: Multiple supporting facts do not re-activate (failure path) Tool: Bash Steps: 1. Run: bun test packages/rete/src/existential.test.ts -t "single activation despite multiple supports" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.2-single.logCommit: YES —
feat(rete): add existential nodes (EXISTS) (P2.2) -
P2.3. NCC nodes: not-count-condition (TDD)
What to do: Subconjunction negation — "no matching combination of N conditions exists". GREEN:
packages/rete/src/ncc.ts. Per Doorenbos §2.6.3, NCC is a sub-network whose top-level production feeds a negation partner.Must NOT do: collapse NCC into NOT (NCC is strictly more powerful) Recommended Agent Profile:
deep; Skills: [context7] Parallelization: YES — Wave P2.1 Blocks: P2.19 Blocked By: P1.14, P2.1 (reuses negation machinery) References: Doorenbos §2.6.3Acceptance Criteria:
bun test packages/rete/src/ncc.test.tsgreen- Coverage ≥ 90%
QA Scenarios:
Scenario: NCC rejects when combination exists Tool: Bash Steps: 1. Run: bun test packages/rete/src/ncc.test.ts 2>&1 | tee /tmp/p23.log Expected: all pass Evidence: .sisyphus/evidence/task-P2.3-ncc.log Scenario: NCC partner cleanup on retract (failure path) Tool: Bash Steps: 1. Run: bun test packages/rete/src/ncc.test.ts -t "NCC partner cleans up on retract" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.3-retract.logCommit: YES —
feat(rete): add NCC nodes (P2.3) -
P2.4. Aggregation nodes: count/sum/collect/min/max (TDD)
What to do:
rule.whatAggregate(count, (?id, Health, v('h')))returns count bound to variable. Supportcount,sum,min,max,collect(array). Incremental update: maintain running total rather than full recompute. GREEN:packages/rete/src/aggregate.tsMust NOT do: full-recompute on every change (performance); operate on raw Set iteration Recommended Agent Profile:
deep; Skills: [context7] Parallelization: YES — Wave P2.1 Blocks: P2.21 (50-move + threefold use aggregation) Blocked By: P1.14References: Drools aggregation patterns
Acceptance Criteria:
bun test packages/rete/src/aggregate.test.tsgreen- Benchmark: 1000 facts × 5 aggregators < 20ms per full re-run
- Coverage ≥ 90%
QA Scenarios:
Scenario: All 5 aggregators produce correct values Tool: Bash Steps: 1. Run: bun test packages/rete/src/aggregate.test.ts 2>&1 | tee /tmp/p24.log Expected: all pass Evidence: .sisyphus/evidence/task-P2.4-agg.log Scenario: Incremental sum on retract (failure path for full recompute) Tool: Bash Steps: 1. Run: bun test packages/rete/src/aggregate.test.ts -t "sum updates incrementally on retract" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.4-incr.logCommit: YES —
feat(rete): add aggregation nodes (count/sum/collect/min/max) (P2.4) -
P2.5. Chess attribute schema + piece fact shape
What to do:
packages/chess/src/schema.ts— define attrs:PieceType(pawn|knight|bishop|rook|queen|king),Color(white|black),Square(a1..h8 as number 0..63),Position(id→Square),HasMoved(bool for castling),Turn(color),HalfmoveClock(number),FullmoveNumber(number),EnPassantTarget(Square?); piece entity convention (each piece = one entity with multiple attrs)- TDD the schema types (compile-time only test via
expect-type)
Must NOT do: use strings for squares (numeric 0..63 for perf) Recommended Agent Profile:
deepParallelization: YES — Wave P2.2 Blocks: P2.8-P2.22 Blocked By: P1.14References:
packages/chess/RULES.md,packages/rete/SPEC.mdAcceptance Criteria:
packages/chess/src/schema.tsexports typed schemabun x tsc -b packages/chess→ 0 errorsbun test packages/chess/src/schema.test.tsgreen
QA Scenarios:
Scenario: Chess schema compiles with strict types Tool: Bash Steps: 1. Run: bun x tsc -b packages/chess 2>&1 | tee /tmp/p25.log 2. Run: bun test packages/chess/src/schema.test.ts 2>&1 | tee /tmp/p25-test.log Expected: step 1 exit 0; step 2 all pass Evidence: .sisyphus/evidence/task-P2.5-schema.log Scenario: Square is numeric 0..63 (failure path for string squares) Tool: Bash Steps: 1. Run: grep -E "type Square = .*0..63|type Square = .*number" packages/chess/src/schema.ts Expected: match present Evidence: .sisyphus/evidence/task-P2.5-square.logCommit: YES —
feat(chess): add attribute schema and piece fact shape (P2.5) - TDD the schema types (compile-time only test via
-
P2.6. Starting-position fact generator
What to do:
packages/chess/src/starting-position.ts—generateStartingPosition(session)inserts 32 piece facts for FIDE start. TDD via snapshot ofsession.allFacts()sorted output.Must NOT do: hardcode as JSON fixture (must be generated deterministically) Recommended Agent Profile:
quickParallelization: YES — Wave P2.2 Blocks: P2.8+ Blocked By: P2.5Acceptance Criteria:
bun test packages/chess/src/starting-position.test.tsgreen- Facts match FIDE snapshot
QA Scenarios:
Scenario: Starting position snapshot matches FIDE Tool: Bash Steps: 1. Run: bun test packages/chess/src/starting-position.test.ts 2>&1 | tee /tmp/p26.log Expected: all pass; snapshot file `__snapshots__/starting-position.test.ts.snap` exists Evidence: .sisyphus/evidence/task-P2.6-start.log Scenario: Exactly 32 pieces (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/starting-position.test.ts -t "inserts exactly 32 piece entities" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.6-count.logCommit: YES —
feat(chess): add starting-position fact generator (P2.6) -
P2.7. Square + color helpers
What to do:
packages/chess/src/coord.ts— pure functions:fileOf(square),rankOf(square),squareFromFileRank(f, r),colorOf(square)(light/dark),oppositeColor(c),isOnBoard(f, r); TDD eachMust NOT do: use string representations internally Recommended Agent Profile:
quickParallelization: YES — Wave P2.2 Blocks: P2.9-P2.12 Blocked By: P2.5Acceptance Criteria:
bun test packages/chess/src/coord.test.tsgreen- Coverage ≥ 100%
QA Scenarios:
Scenario: Coord helpers pure + total Tool: Bash Steps: 1. Run: bun test packages/chess/src/coord.test.ts 2>&1 | tee /tmp/p27.log Expected: all pass; 100% line coverage Evidence: .sisyphus/evidence/task-P2.7-coord.log Scenario: Off-board rejection (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/coord.test.ts -t "isOnBoard rejects out-of-range" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.7-off.logCommit: YES —
feat(chess): add coordinate + color helpers (P2.7) -
P2.8. Piece movement primitive rules (directions + steps)
What to do:
packages/chess/src/rules/primitives.ts— rule-level primitives that legal-move rules build on:StraightLineMoves,DiagonalMoves,SingleStepMoves,KnightOffsets,PawnSingleAdvance,PawnDoubleAdvance,PawnDiagonalCapture. Each primitive is one Rete production generating candidate moves as derived facts (e.g.,CandidateMove(pieceId, targetSquare)).Must NOT do: embed legality checks (check/pin/etc) in primitives (those layer in P2.13+) Recommended Agent Profile:
deepParallelization: YES — Wave P2.2 Blocks: P2.9-P2.14 Blocked By: P2.5, P2.7Acceptance Criteria:
bun test packages/chess/src/rules/primitives.test.tsgreen- Each primitive is a registered rule (listed in a primitives manifest)
QA Scenarios:
Scenario: Primitives generate candidate moves Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/primitives.test.ts 2>&1 | tee /tmp/p28.log Expected: all pass Evidence: .sisyphus/evidence/task-P2.8-prims.log Scenario: Primitives do NOT generate captures (separation of concerns, failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/primitives.test.ts -t "primitives produce only non-capture candidates" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.8-sep.logCommit: YES —
feat(chess): add movement primitive rules (P2.8) -
P2.9. Pawn move/capture rules
What to do:
packages/chess/src/rules/pawn.ts— productions:PawnSingleMove,PawnDoubleMoveFromHome,PawnDiagonalCapture. Use primitives + filters. Color-aware (white advances +rank, black -rank). EmitLegalMove(pieceId, from, to)derived facts. TDD each case including blocked paths.Must NOT do: handle en passant yet (P2.16) Recommended Agent Profile:
deepParallelization: YES — Wave P2.3 (with P2.10-P2.14) Blocks: P2.13, P2.16, P2.17 Blocked By: P2.8Acceptance Criteria:
bun test packages/chess/src/rules/pawn.test.tsgreen- Tests cover: single move, double from home, blocked by own piece, blocked by enemy, diagonal capture, no diagonal without capture
QA Scenarios:
Scenario: All pawn movement and capture cases Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/pawn.test.ts 2>&1 | tee /tmp/p29.log Expected: all pass Evidence: .sisyphus/evidence/task-P2.9-pawn.log Scenario: Pawn cannot move diagonally without capture (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/pawn.test.ts -t "pawn diagonal without capture rejected" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.9-diag.logCommit: YES —
feat(chess): add pawn move/capture rules (P2.9) -
P2.10. Knight move rules
What to do:
packages/chess/src/rules/knight.ts— 8 L-offsets; leap over other pieces;LegalMoveemissionMust NOT do: filter path squares (knight leaps) Recommended Agent Profile:
deepParallelization: YES — Wave P2.3 Blocks: P2.13 Blocked By: P2.8Acceptance Criteria:
bun test packages/chess/src/rules/knight.test.tsgreen
QA Scenarios:
Scenario: Knight L-moves from all positions Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/knight.test.ts 2>&1 | tee /tmp/p210.log Expected: all pass Evidence: .sisyphus/evidence/task-P2.10-knight.log Scenario: Knight leaps over pieces (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/knight.test.ts -t "knight ignores intervening pieces" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.10-leap.logCommit: YES —
feat(chess): add knight move rules (P2.10) -
P2.11. Bishop/Rook/Queen sliding rules
What to do:
packages/chess/src/rules/sliding.ts—SlidingMoveproduction parameterized by directions (diagonal, orthogonal, both); uses aggregation or sequential tokens to stop at first blocker (own = stop before; enemy = capture then stop)Must NOT do: generate moves beyond blocker Recommended Agent Profile:
deepParallelization: YES — Wave P2.3 Blocks: P2.13, P2.15 (castling reads rook moves) Blocked By: P2.8Acceptance Criteria:
bun test packages/chess/src/rules/sliding.test.tsgreen- Tests cover all three pieces × blocker scenarios
QA Scenarios:
Scenario: Sliding moves stop correctly Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/sliding.test.ts 2>&1 | tee /tmp/p211.log Expected: all pass Evidence: .sisyphus/evidence/task-P2.11-sliding.log Scenario: Sliding piece cannot jump (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/sliding.test.ts -t "bishop stops at blocker" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.11-stop.logCommit: YES —
feat(chess): add bishop/rook/queen sliding rules (P2.11) -
P2.12. King move rules (basic)
What to do:
packages/chess/src/rules/king.ts— 8 adjacent squares; excludes squares occupied by own piece. Castling deferred to P2.15; check-aware rejection deferred to P2.13.Recommended Agent Profile:
deepParallelization: YES — Wave P2.3 Blocks: P2.13, P2.15, P2.18, P2.19 Blocked By: P2.8Acceptance Criteria:
bun test packages/chess/src/rules/king.test.tsgreen
QA Scenarios:
Scenario: King single-step moves Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/king.test.ts 2>&1 | tee /tmp/p212.log Expected: all pass Evidence: .sisyphus/evidence/task-P2.12-king.log Scenario: King blocked by own piece (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/king.test.ts -t "king blocked by own piece" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.12-block.logCommit: YES —
feat(chess): add king basic move rules (P2.12) -
P2.13. Turn order + move legality integration
What to do:
packages/chess/src/rules/turn.ts— only pieces of current turn's color generate legal moves; after move, turn flips; move-intent fact (AttemptedMove) validated vsLegalMoveset; on success, update piece positions + retract oldLegalMovefacts. Uses negation to reject intents with no matching LegalMove.Must NOT do: allow movement into check (that's P2.18, but at least queue the integration point here) Recommended Agent Profile:
deepParallelization: YES — Wave P2.3 Blocks: P2.14, all further rules Blocked By: P2.9, P2.10, P2.11, P2.12, P2.1 (negation)Acceptance Criteria:
bun test packages/chess/src/rules/turn.test.tsgreen- Full single move validated and applied
QA Scenarios:
Scenario: Legal move applied; turn switches Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/turn.test.ts 2>&1 | tee /tmp/p213.log Expected: all pass Evidence: .sisyphus/evidence/task-P2.13-turn.log Scenario: Illegal move rejected, turn unchanged (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/turn.test.ts -t "illegal move rejected" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.13-illegal.logCommit: YES —
feat(chess): add turn order + move integration (P2.13) -
P2.14. Capture resolution rules
What to do:
packages/chess/src/rules/capture.ts— when a LegalMove targets an enemy-occupied square, applying the move retracts the captured piece's facts (Position, PieceType, Color) via the RHS handler.Must NOT do: modify captured piece's facts (they retract entirely in FIDE; other presets may vary — handled in presets) Recommended Agent Profile:
deepParallelization: YES — Wave P2.3 Blocks: P2.16, P2.19-P2.22 Blocked By: P2.13Acceptance Criteria:
bun test packages/chess/src/rules/capture.test.tsgreen
QA Scenarios:
Scenario: Capture removes enemy piece Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/capture.test.ts 2>&1 | tee /tmp/p214.log Expected: all pass Evidence: .sisyphus/evidence/task-P2.14-cap.log Scenario: Cannot capture own piece (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/capture.test.ts -t "cannot capture own piece" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.14-own.logCommit: YES —
feat(chess): add capture resolution (P2.14) -
P2.15. Castling (kingside + queenside)
What to do:
packages/chess/src/rules/castling.ts— productions requiring: King has not moved (HasMoved=false), relevant Rook has not moved, no pieces between, king not in check, transit squares not attacked. Two-piece move: king + rook positions updated atomically.Must NOT do: allow castling through check Recommended Agent Profile:
deepParallelization: YES — Wave P2.4 (with P2.16-P2.18) Blocks: P2.23 (integration test) Blocked By: P2.11, P2.12, P2.18 (check detection for transit squares)References: FIDE §3.8.2
Acceptance Criteria:
bun test packages/chess/src/rules/castling.test.tsgreen- Tests: kingside, queenside, rejected after king moves, rejected through check
QA Scenarios:
Scenario: Both castling directions + rejection cases Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/castling.test.ts 2>&1 | tee /tmp/p215.log Expected: all pass Evidence: .sisyphus/evidence/task-P2.15-castle.log Scenario: Castling rejected through check (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/castling.test.ts -t "castling rejected when king passes attacked square" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.15-through.logCommit: YES —
feat(chess): add castling rules (P2.15) -
P2.16. En passant (single-tick capture window)
What to do:
packages/chess/src/rules/enpassant.ts— after a pawn's double-advance, setEnPassantTarget(turn, square)fact for one turn; eligible-pawn rule emits LegalMove that captures via adjacent target; target fact retracts on next turn.Must NOT do: allow en passant beyond one turn window Recommended Agent Profile:
deepParallelization: YES — Wave P2.4 Blocks: P2.23 Blocked By: P2.9, P2.14References: FIDE §3.7.3
Acceptance Criteria:
bun test packages/chess/src/rules/enpassant.test.tsgreen
QA Scenarios:
Scenario: En passant capture works within 1-turn window Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/enpassant.test.ts 2>&1 | tee /tmp/p216.log Expected: all pass Evidence: .sisyphus/evidence/task-P2.16-ep.log Scenario: En passant disallowed after window closes (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/enpassant.test.ts -t "en passant window closes after one turn" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.16-window.logCommit: YES —
feat(chess): add en passant rule (P2.16) -
P2.17. Promotion
What to do:
packages/chess/src/rules/promotion.ts— when a pawn reaches final rank, retract pawn PieceType fact and insert new PieceType (Q/R/B/N). The choice is specified in theAttemptedMovefact viapromoteTofield; default to Q if missing.Recommended Agent Profile:
deepParallelization: YES — Wave P2.4 Blocks: P2.23 Blocked By: P2.9References: FIDE §3.7.5
Acceptance Criteria:
bun test packages/chess/src/rules/promotion.test.tsgreen- Tests: promotion to Q/R/B/N, default-to-queen
QA Scenarios:
Scenario: Pawn promotion to each valid piece Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/promotion.test.ts 2>&1 | tee /tmp/p217.log Expected: all pass Evidence: .sisyphus/evidence/task-P2.17-promo.log Scenario: Invalid promotion target rejected (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/promotion.test.ts -t "promotion to king rejected" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.17-invalid.logCommit: YES —
feat(chess): add pawn promotion rule (P2.17) -
P2.18. Check detection
What to do:
packages/chess/src/rules/check.ts— derived factInCheck(color)when any enemy piece has a LegalMove targeting that color's king. Uses EXISTS node. Rules that would leave own king in check are filtered out of LegalMove (self-check filter).Recommended Agent Profile:
deepParallelization: YES — Wave P2.4 Blocks: P2.15 (castling through check), P2.19, P2.23 Blocked By: P2.2 (exists), P2.9-P2.14Acceptance Criteria:
bun test packages/chess/src/rules/check.test.tsgreen
QA Scenarios:
Scenario: Check detected; self-check prevented Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/check.test.ts 2>&1 | tee /tmp/p218.log Expected: all pass Evidence: .sisyphus/evidence/task-P2.18-check.log Scenario: Move leaving own king in check rejected (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/check.test.ts -t "move exposing own king rejected" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.18-self.logCommit: YES —
feat(chess): add check detection + self-check filter (P2.18) -
P2.19. Checkmate detection
What to do:
packages/chess/src/rules/checkmate.ts— derived factGameOver(result, reason)when:InCheck(turn)AND no LegalMove exists for any piece ofturn. Uses NCC.Recommended Agent Profile:
deepParallelization: YES — Wave P2.5 (with P2.20-P2.22) Blocks: P2.23 Blocked By: P2.3 (NCC), P2.18Acceptance Criteria:
bun test packages/chess/src/rules/checkmate.test.tsgreen- Tests: Fool's Mate, Scholar's Mate, back-rank mate
QA Scenarios:
Scenario: Checkmate positions detected Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/checkmate.test.ts 2>&1 | tee /tmp/p219.log Expected: all pass Evidence: .sisyphus/evidence/task-P2.19-mate.log Scenario: Check without mate is not mate (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/checkmate.test.ts -t "check with escape is not mate" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.19-noesc.logCommit: YES —
feat(chess): add checkmate detection (P2.19) -
P2.20. Stalemate detection
What to do:
packages/chess/src/rules/stalemate.ts—GameOver('draw', 'stalemate')when: NOTInCheck(turn)AND no LegalMove exists forturn.Recommended Agent Profile:
deepParallelization: YES — Wave P2.5 Blocks: P2.23 Blocked By: P2.3 (NCC), P2.18Acceptance Criteria:
bun test packages/chess/src/rules/stalemate.test.tsgreen
QA Scenarios:
Scenario: Stalemate detected Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/stalemate.test.ts 2>&1 | tee /tmp/p220.log Expected: all pass Evidence: .sisyphus/evidence/task-P2.20-stale.log Scenario: Checkmate not mistaken for stalemate (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/stalemate.test.ts -t "checkmate distinguished from stalemate" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.20-dist.logCommit: YES —
feat(chess): add stalemate detection (P2.20) -
P2.21. 50-move rule + threefold repetition (aggregation-based)
What to do:
packages/chess/src/rules/draws.ts— track halfmove clock (resets on pawn move or capture) via a rule; 50-move rule fires at 100 halfmoves. For threefold, maintain aPositionHashfact per tick; aggregation counts occurrences of each hash; threshold of 3 → draw claim available.Must NOT do: auto-claim (threefold is claimable, but plan keeps it auto-triggered on 3rd occurrence for simplicity; documented) Recommended Agent Profile:
deepParallelization: YES — Wave P2.5 Blocks: P2.23 Blocked By: P2.4 (aggregation), P2.14References: FIDE §5.2.2, §5.2.3
Acceptance Criteria:
bun test packages/chess/src/rules/draws.test.tsgreen
QA Scenarios:
Scenario: 50-move + threefold detected Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/draws.test.ts 2>&1 | tee /tmp/p221.log Expected: all pass Evidence: .sisyphus/evidence/task-P2.21-draws.log Scenario: Clock reset on capture (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/draws.test.ts -t "halfmove clock resets on capture" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.21-reset.logCommit: YES —
feat(chess): add 50-move and threefold repetition rules (P2.21) -
P2.22. Insufficient material draw
What to do:
packages/chess/src/rules/insufficient.ts— draw when material sets are: KvK, KvK+N, KvK+B, K+BvK+B (same color bishop). Uses aggregation count over piece types.Recommended Agent Profile:
deepParallelization: YES — Wave P2.5 Blocks: P2.23 Blocked By: P2.4 (aggregation)Acceptance Criteria:
bun test packages/chess/src/rules/insufficient.test.tsgreen
QA Scenarios:
Scenario: All 4 insufficient-material configurations detected Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/insufficient.test.ts 2>&1 | tee /tmp/p222.log Expected: all pass Evidence: .sisyphus/evidence/task-P2.22-insuf.log Scenario: Bishops on opposite colors NOT draw (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/rules/insufficient.test.ts -t "opposite-color bishops is not insufficient" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P2.22-opp.logCommit: YES —
feat(chess): add insufficient material draw (P2.22) -
P2.23. End-to-end FIDE game replay integration test
What to do:
packages/chess/tests/fide-games/: include 5 famous games as PGN fixtures (Immortal, Opera, Evergreen, Kasparov vs Topalov 1999, Deep Blue vs Kasparov G6 1997). Write a runner that parses PGN, drives moves through the engine, asserts each move accepted, asserts terminal state (mate/draw/resign). Resigns are not a chess rule — handled as UI-only terminal state for now; filter those from fixtures.Must NOT do: add a PGN parser dependency (write minimal hand-rolled parser for SAN within
packages/chess/src/pgn.ts) Recommended Agent Profile:unspecified-highParallelization: NO — Wave P2.6 (gate) Blocks: Phase 3 Blocked By: P2.1-P2.22Acceptance Criteria:
bun test packages/chess/tests/fide-games→ all 5 games replay to completion- Phase 2 tag:
git tag v0.2.0-phase2
QA Scenarios:
Scenario: 5 classic games replay end-to-end Tool: Bash Steps: 1. Run: bun test packages/chess/tests/fide-games 2>&1 | tee /tmp/p223.log 2. Run: grep -c 'PASS.*\.pgn' /tmp/p223.log Expected: all tests pass; grep >= 5 Evidence: .sisyphus/evidence/task-P2.23-games.log Scenario: Phase 2 tag created Tool: Bash Steps: 1. Run: git tag v0.2.0-phase2 && git tag | grep v0.2.0-phase2 Expected: tag present Evidence: .sisyphus/evidence/task-P2.23-tag.logCommit: YES —
test(chess): replay 5 classic FIDE games; tag Phase 2 (P2.23); post-commit:git tag v0.2.0-phase2
Phase 3 — Time-Travel + Presets + UI
-
P3.1. Event log: append-only, monotonic sequence numbers (TDD)
What to do:
packages/rete/src/eventlog.ts—class EventLogrecords everyinsert(id, attr, value),retract(id, attr), and rule-fire as{ seq, ts, kind, payload }. Append-only;getSince(seq)returns entries after seq. Session integrates: every state-mutating call appends to log (if log attached). Tests cover monotonic seq, replay-safe encoding, payload determinism.Must NOT do: allow out-of-order writes Recommended Agent Profile:
deepParallelization: YES — Wave P3.1 (with P3.2, P3.3) Blocks: P3.3, P3.14, P4.7 Blocked By: P2.23Acceptance Criteria:
bun test packages/rete/src/eventlog.test.tsgreen- Coverage ≥ 95%
QA Scenarios:
Scenario: Log records every mutation monotonically Tool: Bash Steps: 1. Run: bun test packages/rete/src/eventlog.test.ts 2>&1 | tee /tmp/p31.log Expected: all pass Evidence: .sisyphus/evidence/task-P3.1-eventlog.log Scenario: Out-of-order append rejected (failure path) Tool: Bash Steps: 1. Run: bun test packages/rete/src/eventlog.test.ts -t "out-of-order append throws" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P3.1-order.logCommit: YES —
feat(rete): add append-only event log with monotonic sequence (P3.1) -
P3.2. Immer snapshot every N ticks (TDD)
What to do:
packages/rete/src/snapshot.ts— on every NthfireRules()call (configurable, default N=30), capture full WM state via Immer'sproduce. Structural sharing minimizes copies.getSnapshotAt(seq)returns nearest snapshot ≤ seq. AddSessionoptionsnapshotInterval: number.Must NOT do: snapshot mid-tick (must be at tick boundary only) Recommended Agent Profile:
deep; Skills: [context7] Parallelization: YES — Wave P3.1 Blocks: P3.3, P3.14 Blocked By: P2.23References: Immer docs
Acceptance Criteria:
bun test packages/rete/src/snapshot.test.tsgreen- Memory test: 1000 ticks with N=30 produces ~33 snapshots, total memory < 10MB for chess-sized WM
- Coverage ≥ 90%
QA Scenarios:
Scenario: Snapshots captured at expected interval Tool: Bash Steps: 1. Run: bun test packages/rete/src/snapshot.test.ts 2>&1 | tee /tmp/p32.log Expected: all pass Evidence: .sisyphus/evidence/task-P3.2-snap.log Scenario: Memory bound with structural sharing (failure path if Immer misused) Tool: Bash Steps: 1. Run: bun test packages/rete/src/snapshot.test.ts -t "1000 ticks under 10MB" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P3.2-mem.logCommit: YES —
feat(rete): add Immer snapshots at tick boundaries (P3.2) -
P3.3. Replay engine + determinism hash verifier (TDD)
What to do:
packages/rete/src/replay.ts—replayFromLog(log, schema, handlers): Sessionreconstructs WM by replaying events on a fresh session.stateHash(session): stringproduces sha256 over sorted facts. Determinism test: recording a random fact/rule sequence, replaying, comparing hashes — must match byte-for-byte. Addscripts/replay-determinism.tsrunner for CI.Must NOT do: depend on Map/Set iteration order (sort before hashing) Recommended Agent Profile:
deepParallelization: YES — Wave P3.1 Blocks: P3.14, P4.7 Blocked By: P3.1, P3.2Acceptance Criteria:
bun test packages/rete/src/replay.test.tsgreenbun run scripts/replay-determinism.ts packages/chess/tests/fide-games/*.pgn→ 5/5 hash match- Coverage ≥ 95%
QA Scenarios:
Scenario: Replay hash matches recording hash across 5 games Tool: Bash Steps: 1. Run: bun test packages/rete/src/replay.test.ts 2>&1 | tee /tmp/p33.log 2. Run: bun run scripts/replay-determinism.ts 2>&1 | tee /tmp/p33-run.log 3. Run: grep -c 'MATCH' /tmp/p33-run.log Expected: step 1 pass; step 3 >= 5 Evidence: .sisyphus/evidence/task-P3.3-replay.log, .sisyphus/evidence/task-P3.3-hashes.log Scenario: Injected non-determinism detected (failure path) Tool: Bash Steps: 1. Run: bun test packages/rete/src/replay.test.ts -t "non-deterministic RHS produces MISMATCH" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P3.3-mismatch.logCommit: YES —
feat(rete): add replay engine + state-hash determinism verifier (P3.3) -
P3.4. Preset rules 1-3 (pawn-focused variants)
What to do: Implement 3 of the 15 presets from
packages/chess/RULES.md(assume first 3 are pawn-focused: e.g.,pawns-move-backward,pawns-diagonal-no-capture,double-advance-any-turn). Each preset = one or more rule definitions inpackages/chess/src/presets/{id}.ts, a registered toggle inpackages/chess/src/presets/registry.ts, unit tests, compatibility declarations.Must NOT do: implement presets outside the first 3 Recommended Agent Profile:
deepParallelization: YES — Wave P3.2 (with P3.5-P3.8) Blocks: P3.11 (UI needs presets registered) Blocked By: P2.23, P0.3 (RULES.md)Acceptance Criteria:
bun test packages/chess/src/presets/{preset-1,2,3}.test.tsgreen- 3 presets registered; registry has 3 entries in this task
QA Scenarios:
Scenario: Presets 1-3 toggle on/off correctly Tool: Bash Steps: 1. Run: bun test packages/chess/src/presets 2>&1 | tee /tmp/p34.log Expected: all pass Evidence: .sisyphus/evidence/task-P3.4-presets.log Scenario: Incompatible presets flag conflict (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/presets -t "incompatible pair flagged" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P3.4-incompat.logCommit: YES —
feat(chess): add preset rules 1-3 (P3.4) -
P3.5. Preset rules 4-6 (knight/bishop variants)
What to do: Implement presets 4-6 from RULES.md. Same structure as P3.4.
Recommended Agent Profile:
deepParallelization: YES — Wave P3.2 Blocks: P3.11 Blocked By: P2.23, P0.3Acceptance Criteria:
bun test packages/chess/src/presetsincludes 6 preset files green; registry has 6 entries
QA Scenarios:
Scenario: Presets 4-6 functional Tool: Bash Steps: 1. Run: bun test packages/chess/src/presets/knight*.test.ts packages/chess/src/presets/bishop*.test.ts 2>&1 | tee /tmp/p35.log Expected: all pass Evidence: .sisyphus/evidence/task-P3.5-presets.log Scenario: Registry expanded to 6 (failure path) Tool: Bash Steps: 1. Run: bun -e "import {REGISTRY} from './packages/chess/src/presets/registry.ts'; console.log(Object.keys(REGISTRY).length)" Expected: stdout "6" Evidence: .sisyphus/evidence/task-P3.5-count.logCommit: YES —
feat(chess): add preset rules 4-6 (P3.5) -
P3.6. Preset rules 7-9 (rook/queen/king variants)
What to do: Implement presets 7-9 from RULES.md. Recommended Agent Profile:
deepParallelization: YES — Wave P3.2 Blocks: P3.11 Blocked By: P2.23, P0.3Acceptance Criteria: registry has 9 entries; all tests green
QA Scenarios:
Scenario: Presets 7-9 functional Tool: Bash Steps: 1. Run: bun test packages/chess/src/presets/rook*.test.ts packages/chess/src/presets/queen*.test.ts packages/chess/src/presets/king*.test.ts 2>&1 | tee /tmp/p36.log Expected: all pass Evidence: .sisyphus/evidence/task-P3.6.log Scenario: Registry has 9 entries (failure path) Tool: Bash Steps: 1. Run: bun -e "import {REGISTRY} from './packages/chess/src/presets/registry.ts'; console.log(Object.keys(REGISTRY).length)" Expected: stdout "9" Evidence: .sisyphus/evidence/task-P3.6-count.logCommit: YES —
feat(chess): add preset rules 7-9 (P3.6) -
P3.7. Preset rules 10-12 (board/geometry variants)
What to do: Implement presets 10-12 from RULES.md — board-geometry changes (e.g., horizontal wrap). These modify coord helpers via override or interception rule. Recommended Agent Profile:
deepParallelization: YES — Wave P3.2 Blocks: P3.11 Blocked By: P2.23, P0.3Acceptance Criteria: registry has 12 entries; all tests green
QA Scenarios:
Scenario: Geometry presets alter legal moves correctly Tool: Bash Steps: 1. Run: bun test packages/chess/src/presets/wrap*.test.ts packages/chess/src/presets/geometry*.test.ts 2>&1 | tee /tmp/p37.log Expected: all pass Evidence: .sisyphus/evidence/task-P3.7.log Scenario: Wrap preset enables horizontal movement across board edge (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/presets/wrap-horizontal.test.ts -t "rook crosses file-a to file-h" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P3.7-wrap.logCommit: YES —
feat(chess): add preset rules 10-12 (P3.7) -
P3.8. Preset rules 13-15 (meta rules: HP/heal/immunity)
What to do: Implement presets 13-15 from RULES.md — introduce HP/cooldown/immunity attributes in chess schema extensions (within chess package only, not engine). These require adding extended attrs to chess schema (via
extendChessSchemahelper), supporting facts (HP defaults to 1 for FIDE).Must NOT do: leak chess-schema extensions into engine core Recommended Agent Profile:
deepParallelization: YES — Wave P3.2 Blocks: P3.11 Blocked By: P2.23, P0.3Acceptance Criteria: registry has 15 entries; HP-aware rules tested
QA Scenarios:
Scenario: HP preset: captures deal 1 damage; piece with 2 HP survives first hit Tool: Bash Steps: 1. Run: bun test packages/chess/src/presets/hp*.test.ts packages/chess/src/presets/heal*.test.ts packages/chess/src/presets/immune*.test.ts 2>&1 | tee /tmp/p38.log Expected: all pass Evidence: .sisyphus/evidence/task-P3.8.log Scenario: Full registry has 15 entries (failure path) Tool: Bash Steps: 1. Run: bun -e "import {REGISTRY} from './packages/chess/src/presets/registry.ts'; console.log(Object.keys(REGISTRY).length)" Expected: stdout "15" Evidence: .sisyphus/evidence/task-P3.8-count.logCommit: YES —
feat(chess): add preset rules 13-15 (P3.8) -
P3.9. React + Vite scaffold for chess app
What to do: Wire up Vite + React 19 (or latest) in
packages/chess/:index.html,src/app/main.tsx,src/app/App.tsx(root, routes: Home, Game, Rules, Save), Vite config with base URL, Tailwind for styling (or CSS modules if Tailwind explicitly disliked by user; default Tailwind). Bundle size placeholder — checked by size-limit later.Must NOT do: install UI libraries beyond React, Tailwind, minimal dnd (react-dnd) if needed — no Material UI, no Ant Design Recommended Agent Profile:
visual-engineering; Skills: [interface-design,context7] Parallelization: YES — Wave P3.3 (with P3.10-P3.13 — P3.10 depends on P3.9) Blocks: P3.10-P3.13 Blocked By: P2.23References: Vite React guide; Tailwind setup
Acceptance Criteria:
cd packages/chess && bun run devstarts at localhost:5173bun run buildproduces static dist/- Playwright can open the home route and see an app root element
QA Scenarios:
Scenario: Dev server starts; home page renders root Tool: Playwright Preconditions: bun run dev started in background on port 5173 Steps: 1. Navigate to http://localhost:5173/ 2. Wait for selector '[data-testid="app-root"]' 3. Screenshot Expected: root element visible Evidence: .sisyphus/evidence/task-P3.9-home.png Scenario: Build produces static bundle (failure path for missing build script) Tool: Bash Steps: 1. Run: cd packages/chess && bun run build && ls -la dist/ Expected: dist/ contains index.html Evidence: .sisyphus/evidence/task-P3.9-build.logCommit: YES —
feat(chess): scaffold Vite + React app (P3.9) -
P3.10. Chessboard component with drag-drop + legal-move highlights
What to do:
packages/chess/src/ui/Board.tsx— 8×8 grid, piece SVG icons (inline or public/), drag-drop via HTML5 DnD or react-dnd; on drag-start, query engine for that piece's LegalMoves and highlight target squares; on drop, dispatch AttemptedMove fact. UsesuseSession()hook providing reactive fact subscriptions (implemented via tick-subscription observer on session).Must NOT do: load piece images from external CDN (bundle locally) Recommended Agent Profile:
visual-engineering; Skills: [interface-design] Parallelization: NO (depends on P3.9) — Wave P3.3 Blocks: P3.15 Blocked By: P3.9Acceptance Criteria:
- Board renders with 32 pieces in starting position
- Drag pawn e2→e4: piece moves on board; engine fact updated
- Illegal move: piece snaps back; no fact change
QA Scenarios:
Scenario: Legal drag-drop move applied Tool: Playwright Steps: 1. Navigate to http://localhost:5173/game 2. Locator '[data-square="e2"]' → dragTo '[data-square="e4"]' 3. Wait selector '[data-square="e4"] [data-piece="white-pawn"]' 4. Screenshot Expected: pawn on e4 Evidence: .sisyphus/evidence/task-P3.10-e2e4.png Scenario: Illegal move rejected (failure path) Tool: Playwright Steps: 1. Navigate to /game 2. Locator '[data-square="e2"]' dragTo '[data-square="e5"]' (illegal double-plus) 3. Wait selector '[data-square="e2"] [data-piece="white-pawn"]' (pawn still home) 4. Screenshot Expected: pawn returned Evidence: .sisyphus/evidence/task-P3.10-reject.pngCommit: YES —
feat(chess): add interactive Chessboard with drag-drop (P3.10) -
P3.11. Rule-toggle screen (preset list + compatibility warnings)
What to do:
packages/chess/src/ui/Rules.tsx— list all 15 presets with description, toggle switch, compat-warning banner when incompatibility detected; "Apply and start new game" button; toggles only between games (disabled during active game — grayed state).Recommended Agent Profile:
visual-engineeringParallelization: YES — Wave P3.3 Blocks: P3.15 Blocked By: P3.4-P3.8, P3.9Acceptance Criteria:
- 15 toggle rows render; enabling two incompatibles shows warning
- Starting new game applies enabled presets
QA Scenarios:
Scenario: Toggle preset, start new game, effect observable Tool: Playwright Steps: 1. Navigate to /rules 2. Click '[data-preset="pawns-move-backward"] [data-role="toggle"]' 3. Click '[data-action="start-new-game"]' 4. Navigate to /game 5. Locator '[data-square="e2"]' dragTo '[data-square="e1"]' (backward move; normally illegal) 6. Wait selector '[data-square="e1"] [data-piece="white-pawn"]' Expected: pawn moved backward Evidence: .sisyphus/evidence/task-P3.11-back.png Scenario: Incompatible presets show warning (failure path) Tool: Playwright Steps: 1. Navigate to /rules 2. Enable two presets listed as incompatible in RULES.md 3. Expect '[data-testid="compat-warning"]' visible Expected: warning shown Evidence: .sisyphus/evidence/task-P3.11-warn.pngCommit: YES —
feat(chess): add rule-toggle UI with compatibility warnings (P3.11) -
P3.12. Save/Load panel + undo via time-travel
What to do:
packages/chess/src/ui/SavePanel.tsx+ undo button in Game view; undo uses time-travel to rewind to previousTurn-changed fact boundary (one full move back); save panel lists slots from localStorage (schema-versioned JSON).Recommended Agent Profile:
visual-engineeringParallelization: YES — Wave P3.3 Blocks: P3.14, P3.15 Blocked By: P3.3, P3.9Acceptance Criteria:
- Undo rewinds one full move
- Save to slot, reload page, load — same position
QA Scenarios:
Scenario: Undo reverts one move Tool: Playwright Steps: 1. Navigate to /game 2. Drag e2→e4; drag e7→e5 3. Click '[data-action="undo"]' 4. Assert '[data-square="e5"] [data-piece]' is NOT black-pawn (reverted) 5. Assert turn indicator shows 'black' Expected: state reverted Evidence: .sisyphus/evidence/task-P3.12-undo.png Scenario: Save/load round-trip (failure path) Tool: Playwright Steps: 1. Play 4 moves 2. Click '[data-action="save"]' into slot "test" 3. page.reload() 4. Click '[data-action="load"]' slot "test" 5. Assert board state matches pre-reload Evidence: .sisyphus/evidence/task-P3.12-saveload.pngCommit: YES —
feat(chess): add Save/Load panel + time-travel undo (P3.12) -
P3.13. JSON export/import + validation
What to do:
packages/chess/src/ui/ImportExport.tsx+packages/chess/src/persist/io.ts— export button produces a downloadable JSON file (schema:{ version: 1, rules: [...], facts: [...] }); import button accepts file, validates against schema (via@paratype/rete's exported schema + chess extension schema), applies rules + facts.Must NOT do: allow importing from untrusted URL (file-upload only) Recommended Agent Profile:
visual-engineeringParallelization: YES — Wave P3.3 Blocks: P3.15 Blocked By: P1.6, P3.9Acceptance Criteria:
- Export downloads valid JSON parseable by the importer
- Invalid JSON shows user-facing error, no crash
QA Scenarios:
Scenario: Export then re-import round-trip Tool: Playwright Steps: 1. Navigate to /game; make 3 moves 2. Click '[data-action="export"]'; Playwright captures download as /tmp/export.json 3. Click '[data-action="import"]'; upload /tmp/export.json 4. Assert board state matches pre-import Evidence: .sisyphus/evidence/task-P3.13-export.json, .sisyphus/evidence/task-P3.13-import.png Scenario: Malformed JSON rejected with user message (failure path) Tool: Playwright Steps: 1. Click '[data-action="import"]'; upload fixture with `{"bad":"data"}` 2. Assert '[data-testid="import-error"]' visible with descriptive message Evidence: .sisyphus/evidence/task-P3.13-bad.pngCommit: YES —
feat(chess): add JSON export/import with validation (P3.13) -
P3.14. localStorage auto-save + restore
What to do:
packages/chess/src/persist/autosave.ts— subscribe to session tick end; on every turn boundary, write serialized state + event log to localStorage keyparatype-chess:v1:autosave. On app load, if key present, restore viareplayFromLog. Include schema version in payload.Must NOT do: write on every tick (too noisy); write to sessionStorage (lost on close) Recommended Agent Profile:
unspecified-highParallelization: YES — Wave P3.4 (with P3.15) Blocks: P3.15 Blocked By: P3.3, P3.12Acceptance Criteria:
- After 3 moves, localStorage has
paratype-chess:v1:autosave - Reload page → game resumes in same position
QA Scenarios:
Scenario: Autosave persists across reload Tool: Playwright Steps: 1. Navigate to /game; play 5 moves 2. localStorage.getItem('paratype-chess:v1:autosave') not null 3. Reload 4. Assert board state matches Evidence: .sisyphus/evidence/task-P3.14-autosave.png Scenario: Schema version mismatch discards silently (failure path) Tool: Playwright Steps: 1. Set localStorage to stale payload with version 0 2. Reload 3. Assert new game started (no crash) Evidence: .sisyphus/evidence/task-P3.14-stale.pngCommit: YES —
feat(chess): add localStorage auto-save and restore (P3.14) - After 3 moves, localStorage has
-
P3.15. End-to-end UI scenario (gate)
What to do: Playwright scenario at
packages/chess/e2e/full-flow.spec.ts— open app → toggle 2 presets → start game → play 5 moves → save → reload → game restored → export → import in fresh context → play 3 more moves → undo → play until checkmate (scripted sequence) → assert Game Over banner.Must NOT do: use timing-based waits (
waitForTimeoutis banned; use selector waits) Recommended Agent Profile:unspecified-high; Skills: [playwright] Parallelization: NO — Wave P3.4 (gate) Blocks: Phase 4 Blocked By: P3.1-P3.14Acceptance Criteria:
bun x playwright test packages/chess/e2e/full-flow.spec.tsgreen- Video + trace artifacts captured
- Phase 3 tag:
git tag v0.3.0-phase3
QA Scenarios:
Scenario: Full flow end-to-end Tool: Playwright Preconditions: bun run dev serving packages/chess Steps: (executed by the spec file; evidence is trace + video) Expected Result: spec passes; video shows full flow Evidence: .sisyphus/evidence/task-P3.15-full-flow.webm, .sisyphus/evidence/task-P3.15-trace.zip Scenario: Phase 3 tag present Tool: Bash Steps: 1. Run: git tag v0.3.0-phase3 && git tag | grep v0.3.0-phase3 Expected: present Evidence: .sisyphus/evidence/task-P3.15-tag.logCommit: YES —
test(chess): e2e full-flow scenario; tag Phase 3 (P3.15); post-commit:git tag v0.3.0-phase3
Phase 4 — Authoritative Multiplayer
-
P4.1. Bun HTTP+WS server scaffold + config
What to do:
packages/server/src/index.ts—Bun.serve({ port, fetch, websocket: { open, message, close } }); env-driven port (default 7357); health endpointGET /healthzreturning{ ok: true, version }; structured pino logger with request id; graceful shutdown on SIGINT.Recommended Agent Profile:
unspecified-high; Skills: [context7] Parallelization: YES — Wave P4.1 (with P4.2-P4.4) Blocks: P4.5-P4.11 Blocked By: P3.15References: Bun.serve docs, pino
Acceptance Criteria:
bun run packages/server/src/index.tsstarts;curl localhost:7357/healthzreturns 200- Logs emit JSON lines
QA Scenarios:
Scenario: Server responds to health check Tool: Bash Steps: 1. Run: bun run packages/server/src/index.ts & 2. Sleep 2 3. Run: curl -sS -o /tmp/p41.json -w "%{http_code}" http://localhost:7357/healthz 4. Kill %1 Expected: status 200; body has {"ok":true} Evidence: .sisyphus/evidence/task-P4.1-health.log Scenario: SIGINT shuts down gracefully (failure path) Tool: Bash Steps: 1. Run: bun run packages/server/src/index.ts & 2. SIGINT; wait; echo $? Expected: exit 0 Evidence: .sisyphus/evidence/task-P4.1-shutdown.logCommit: YES —
feat(server): scaffold Bun HTTP+WS server with health + logging (P4.1) -
P4.2. Message schemas + validation (TDD)
What to do:
packages/server/src/protocol.ts— zod schemas per PROTOCOL.md message type;validateMessage(raw): Result; top-levelvversion check; round-trip tested.Must NOT do: use JSON.parse without validation Recommended Agent Profile:
deepParallelization: YES — Wave P4.1 Blocks: P4.5, P4.6 Blocked By: P0.4 (PROTOCOL.md), P3.15Acceptance Criteria:
bun test packages/server/src/protocol.test.tsgreen- Coverage ≥ 95%
QA Scenarios:
Scenario: All 8+ message types round-trip Tool: Bash Steps: 1. Run: bun test packages/server/src/protocol.test.ts 2>&1 | tee /tmp/p42.log Expected: all pass Evidence: .sisyphus/evidence/task-P4.2-proto.log Scenario: Malformed message rejected (failure path) Tool: Bash Steps: 1. Run: bun test packages/server/src/protocol.test.ts -t "invalid v rejected" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P4.2-bad.logCommit: YES —
feat(server): add protocol schemas + validation (P4.2) -
P4.3. Room model (create/join/leave, 6-char codes)
What to do:
packages/server/src/rooms.ts—class RoomRegistrywithcreateRoom()→ 6-char [A-Z0-9] code + uuid-v4 token;joinRoom(code, token); 2-player max; token-authenticated per message; TDD.Must NOT do: persist across restart (v1 constraint) Recommended Agent Profile:
deepParallelization: YES — Wave P4.1 Blocks: P4.5 Blocked By: P3.15Acceptance Criteria:
bun test packages/server/src/rooms.test.tsgreen- Code generation uniqueness fuzz (1000 codes, 0 collisions expected)
QA Scenarios:
Scenario: Room create, join, duplicate-join-rejected Tool: Bash Steps: 1. Run: bun test packages/server/src/rooms.test.ts 2>&1 | tee /tmp/p43.log Expected: all pass Evidence: .sisyphus/evidence/task-P4.3-rooms.log Scenario: Third player rejected (failure path) Tool: Bash Steps: 1. Run: bun test packages/server/src/rooms.test.ts -t "third join rejected" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P4.3-third.logCommit: YES —
feat(server): add room registry with codes + tokens (P4.3) -
P4.4. Rate limiting + origin allow-list + 64KB cap
What to do:
packages/server/src/middleware.ts— per-connection token bucket (100 msg/sec, burst 20); WebSocket upgrade rejects non-allow-list origins (configurable via envALLOWED_ORIGINS); reject payloads > 64KB with disconnect.Recommended Agent Profile:
unspecified-highParallelization: YES — Wave P4.1 Blocks: P4.12 Blocked By: P3.15Acceptance Criteria:
bun test packages/server/src/middleware.test.tsgreen- Stress test: 200 msg/sec triggers RATE_LIMIT disconnect
QA Scenarios:
Scenario: Rate-limit trips on over-limit Tool: Bash Steps: 1. Run: bun test packages/server/src/middleware.test.ts 2>&1 | tee /tmp/p44.log Expected: all pass Evidence: .sisyphus/evidence/task-P4.4-rl.log Scenario: Origin disallowed rejected (failure path) Tool: Bash Steps: 1. Run: bun test packages/server/src/middleware.test.ts -t "origin not in allow-list rejected" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P4.4-origin.logCommit: YES —
feat(server): add rate-limit, origin allow-list, message-size cap (P4.4) -
P4.5. Authoritative session per room
What to do:
packages/server/src/game-session.ts— each room holds aSessionfrom@paratype/rete+ chess rules; server is the only one that callsinsert/retract/fireRules. Fact IDs minted here only.Recommended Agent Profile:
deepParallelization: YES — Wave P4.2 Blocks: P4.6, P4.12 Blocked By: P4.1, P4.2, P4.3Acceptance Criteria:
bun test packages/server/src/game-session.test.tsgreen
QA Scenarios:
Scenario: Each room has isolated session state Tool: Bash Steps: 1. Run: bun test packages/server/src/game-session.test.ts 2>&1 | tee /tmp/p45.log Expected: all pass Evidence: .sisyphus/evidence/task-P4.5-sess.log Scenario: Fact IDs do not collide across rooms (failure path) Tool: Bash Steps: 1. Run: bun test packages/server/src/game-session.test.ts -t "room fact ids distinct" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P4.5-ids.logCommit: YES —
feat(server): add authoritative game session per room (P4.5) -
P4.6. Move-intent validation + fact-delta broadcast
What to do:
packages/server/src/broadcast.ts— ongame.moveintent: insertAttemptedMovefact; fire rules; diff pre/post WM; broadcastgame.deltawith added/removed facts to both clients. Assignedseqper delta for reconnection.Recommended Agent Profile:
deepParallelization: YES — Wave P4.2 Blocks: P4.12 Blocked By: P4.5Acceptance Criteria:
- Integration test: send legal move → both clients receive delta with updated Position fact
- Illegal move →
errormessage; no broadcast
QA Scenarios:
Scenario: Legal move broadcast to both clients Tool: Bash (WS client script) Steps: 1. Launch server 2. Run: bun run scripts/ws-client.ts --script fixtures/two-client-legal-move.json Expected: both clients receive matching game.delta with Position change Evidence: .sisyphus/evidence/task-P4.6-delta.json Scenario: Illegal move rejected; no broadcast (failure path) Tool: Bash Steps: 1. Run: bun run scripts/ws-client.ts --script fixtures/illegal-move.json Expected: error to sender; zero delta messages Evidence: .sisyphus/evidence/task-P4.6-illegal.jsonCommit: YES —
feat(server): add move validation + fact-delta broadcast (P4.6) -
P4.7. Reconnection flow (60s window, snapshot resume)
What to do:
packages/server/src/reconnect.ts— on disconnect, start 60s timer; during grace, incoming (code, token) matches → resume and sendgame.state(full snapshot) + all deltas since client's lastseq. After 60s, room aborts withgame.endbroadcast to remaining client.Recommended Agent Profile:
deepParallelization: YES — Wave P4.2 Blocks: P4.12 Blocked By: P4.5, P4.6, P3.3 (replay for determinism)Acceptance Criteria:
- Integration test: disconnect, reconnect within 30s, resume state exactly
QA Scenarios:
Scenario: Reconnect within grace resumes game Tool: Bash Steps: 1. Run: bun run scripts/ws-client.ts --script fixtures/reconnect-within-grace.json Expected: client B reconnects, receives state, continues game Evidence: .sisyphus/evidence/task-P4.7-reconnect.json Scenario: Reconnect after grace fails with game.end (failure path) Tool: Bash Steps: 1. Run: bun run scripts/ws-client.ts --script fixtures/reconnect-after-grace.json Expected: rejected; remaining client received game.end Evidence: .sisyphus/evidence/task-P4.7-expired.jsonCommit: YES —
feat(server): add reconnection with 60s grace + snapshot resume (P4.7) -
P4.8. Structured logging + metrics
What to do:
packages/server/src/logging.ts— pino logger with request-scopedroomId,clientId,seq; per-tick duration metric;/metricsendpoint (Prometheus text format) with counters:rooms_active,messages_received_total,moves_validated_total{result}, tick duration histogram.Recommended Agent Profile:
unspecified-highParallelization: YES — Wave P4.2 Blocks: P4.12 Blocked By: P4.1Acceptance Criteria:
curl localhost:7357/metricsreturns text/plain with expected series
QA Scenarios:
Scenario: Metrics endpoint exposes required series Tool: Bash Steps: 1. Run: bun run packages/server/src/index.ts & 2. Sleep 2 3. Run: curl -sS http://localhost:7357/metrics | grep -E 'rooms_active|messages_received_total|moves_validated_total|tick_duration' 4. Kill %1 Expected: all 4 series present Evidence: .sisyphus/evidence/task-P4.8-metrics.log Scenario: Log lines are valid JSON (failure path) Tool: Bash Steps: 1. Run: bun run packages/server/src/index.ts 2>&1 | head -20 | jq -e . Expected: exit 0 for each line (jq parses) Evidence: .sisyphus/evidence/task-P4.8-logs.logCommit: YES —
feat(server): add pino logging and Prometheus metrics (P4.8) -
P4.9. WebSocket client library with reconnect + seq ack
What to do:
packages/chess/src/net/client.ts—class GameClientwithconnect(code, token), exponential-backoff reconnect, sequence-ack tracking, event emitter forgame.state,game.delta,error. Client owns a local engine session but only applies deltas received from server (no self-validation of moves).Recommended Agent Profile:
deepParallelization: YES — Wave P4.3 (with P4.10, P4.11) Blocks: P4.12 Blocked By: P4.2 (protocol schemas)Acceptance Criteria:
bun test packages/chess/src/net/client.test.tsgreen- Reconnect after drop succeeds within 30s
QA Scenarios:
Scenario: Client handshake + delta application Tool: Bash Steps: 1. Run: bun test packages/chess/src/net/client.test.ts 2>&1 | tee /tmp/p49.log Expected: all pass Evidence: .sisyphus/evidence/task-P4.9-client.log Scenario: Reconnect after forced disconnect (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/net/client.test.ts -t "reconnect restores state" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P4.9-recon.logCommit: YES —
feat(chess): add WebSocket client library with reconnect (P4.9) -
P4.10. Client prediction + server reconciliation
What to do:
packages/chess/src/net/prediction.ts— on user drag-drop, client locally applies move optimistically to engine session; sends intent to server; ongame.delta, reconciles (replaces predicted state with authoritative state). Onerrorresponse, rolls back.Must NOT do: drift — always re-hash local state against server snapshot on receipt; mismatch → resync from server full state Recommended Agent Profile:
deepParallelization: YES — Wave P4.3 Blocks: P4.12 Blocked By: P4.9Acceptance Criteria:
bun test packages/chess/src/net/prediction.test.tsgreen- Simulated latency (100ms artificial delay) doesn't cause desync
QA Scenarios:
Scenario: Optimistic prediction matches authoritative result Tool: Bash Steps: 1. Run: bun test packages/chess/src/net/prediction.test.ts 2>&1 | tee /tmp/p410.log Expected: all pass Evidence: .sisyphus/evidence/task-P4.10-pred.log Scenario: Rejected prediction rolls back (failure path) Tool: Bash Steps: 1. Run: bun test packages/chess/src/net/prediction.test.ts -t "rejected intent rolls back" 2>&1 Expected: pass Evidence: .sisyphus/evidence/task-P4.10-rollback.logCommit: YES —
feat(chess): add client prediction + server reconciliation (P4.10) -
P4.11. Room lobby UI (create/join screens)
What to do:
packages/chess/src/ui/Lobby.tsx— home route with two buttons: "Create Room" (shows generated code, share link) and "Join Room" (input for code). After join, redirect to/gamewith active session.Recommended Agent Profile:
visual-engineering; Skills: [interface-design] Parallelization: YES — Wave P4.3 Blocks: P4.12 Blocked By: P4.9Acceptance Criteria:
- Playwright: create room in ctx A, join in ctx B, both see game
- Invalid code shows error
QA Scenarios:
Scenario: Two contexts join same room Tool: Playwright Steps: 1. Context A navigates /; clicks [data-action="create-room"]; notes [data-testid="room-code"] value (CODE) 2. Context B navigates /; types CODE in [data-testid="room-code-input"]; clicks [data-action="join-room"] 3. Both reach /game; both see starting position Expected: both boards render Evidence: .sisyphus/evidence/task-P4.11-create.png, .sisyphus/evidence/task-P4.11-join.png Scenario: Invalid code errors (failure path) Tool: Playwright Steps: 1. Navigate /; type "XXXXXX"; click join 2. Assert [data-testid="lobby-error"] visible Evidence: .sisyphus/evidence/task-P4.11-bad.pngCommit: YES —
feat(chess): add lobby UI for create/join rooms (P4.11) -
P4.12. E2E multiplayer scenario (Phase 4 gate)
What to do:
packages/chess/e2e/multiplayer.spec.ts— launches server + client (via Playwright webServer config); two contexts create/join room, play 10-move game alternating sides; ctx A disconnects at move 6, reconnects at move 7; game completes to checkmate; assert both clients see identical final state.Must NOT do: use fixed sleeps; use selector waits Recommended Agent Profile:
unspecified-high; Skills: [playwright] Parallelization: NO — Wave P4.4 (gate) Blocks: Final Wave Blocked By: P4.1-P4.11Acceptance Criteria:
bun x playwright test packages/chess/e2e/multiplayer.spec.tsgreen- Phase 4 tag:
git tag v0.4.0-phase4
QA Scenarios:
Scenario: Two-browser full multiplayer game with mid-game reconnect Tool: Playwright (see spec) Expected: spec passes; video captured Evidence: .sisyphus/evidence/task-P4.12-mp.webm, .sisyphus/evidence/task-P4.12-trace.zip Scenario: Phase 4 tag present Tool: Bash Steps: 1. Run: git tag v0.4.0-phase4 && git tag | grep v0.4.0-phase4 Expected: present Evidence: .sisyphus/evidence/task-P4.12-tag.logCommit: YES —
test(root): E2E multiplayer with reconnect; tag Phase 4 (P4.12); post-commit:git tag v0.4.0-phase4
Final Verification Wave (MANDATORY — after ALL implementation tasks)
4 review agents run in PARALLEL. ALL must APPROVE. Present consolidated results to user and get explicit "okay" before marking work complete. Do NOT auto-proceed after verification. Wait for user's explicit approval. Never mark F1-F4 as checked before getting user's okay. Rejection or user feedback → fix → re-run → present again → wait for okay.
-
F1. Plan Compliance Audit —
oracleRead this plan end-to-end. For each "Must Have": verify implementation exists (read file, run command, inspect built artifact). For each "Must NOT Have": search codebase for forbidden patterns (e.g.,grep -r "as any" packages/rete/src), reject with file:line if found. Check evidence files exist in.sisyphus/evidence/. Verify all 5 phase tags exist (git tag | grep phase). Compare deliverables against plan. Output:Must Have [N/N] | Must NOT Have [N/N] | Phase tags [5/5] | Tasks [N/N] | VERDICT: APPROVE/REJECT -
F2. Code Quality Review —
unspecified-highRunbun run typecheck+bun run lint+bun run test:coverage+bun run size-limit. Review all changed files for:as any/@ts-ignore/@ts-expect-error, empty catches,console.login prod code, commented-out code, unused imports,Date.now()/Math.random()in engine RHS paths, rawSet<object>iteration in engine hot paths. Check AI slop: excessive comments, over-abstraction, generic names (data/result/item/temp/obj). Audit bundle sizes against budgets (engine < 50KB min+gz, chess < 200KB min+gz). Output:Build [PASS/FAIL] | Lint [PASS/FAIL] | Tests [N pass/N fail, coverage X%/Y%/Z%] | Bundle [engine Xkb / chess Ykb] | Files [N clean/N issues] | VERDICT -
F3. Real Manual QA via Playwright + Scripted Clients —
unspecified-high(+playwrightskill) Start from clean state:rm -rf node_modules && bun install && bun run build. Launch chess server. Execute EVERY QA scenario from EVERY task — follow exact steps, capture evidence. Test cross-task integration: play a full FIDE game; toggle 3 presets between games; play a custom-rules game; save via localStorage; reload browser; verify state persisted; export JSON; import into fresh browser; play a multiplayer game across two browser contexts with reconnect mid-game. Test edge cases: illegal move rejected, rate-limit trip, protocol version mismatch hard-disconnect, 60s reconnect boundary. Save to.sisyphus/evidence/final-qa/. Output:Scenarios [N/N pass] | Integration [N/N] | Edge Cases [N tested] | VERDICT -
F4. Scope Fidelity Check —
deepFor each task: read "What to do", read actual diff (git log/git diffon that task's commits). Verify 1:1 — everything in spec was built (no missing), nothing beyond spec was built (no creep). Check "Must NOT do" compliance in diff. Detect cross-task contamination: Task N touching Task M's files. Flag unaccounted changes. Verify commit messages follow Conventional Commits with scope (feat(rete):,feat(chess):,feat(server):). Output:Tasks [N/N compliant] | Contamination [CLEAN/N issues] | Unaccounted [CLEAN/N files] | Commit format [N/N] | VERDICT
Commit Strategy
- Conventional Commits enforced:
type(scope): descriptionwherescope ∈ {rete, chess, server, root} - Types:
feat,fix,test,refactor,chore,docs,perf,build,ci - Atomic commits: one logical change per commit. TDD tasks commit test+impl together.
- Every commit: passes
bun run check(tsc + eslint + vitest) — enforced via pre-commit hook AND CI required-status-check - Phase boundaries tagged:
v0.1.0-phase1,v0.2.0-phase2,v0.3.0-phase3,v0.4.0-phase4,v1.0.0(final) - No WIP commits on main; feature work in feature branches (if branching used) or linearly via rebase on main
- No squash-merge across phases; each phase is a merge train
Per-task commit details live in each TODO's Commit: block.
Success Criteria
Verification Commands (run from repo root)
bun install # → 0 errors
bun run typecheck # → 0 errors
bun run lint # → 0 errors
bun run test # → all green
bun run test:coverage # → engine ≥90%, chess ≥70%, server ≥80%
bun run build # → dist/ populated in all 3 packages
bun run size-limit # → engine < 50KB, chess < 200KB
bun run playwright test # → all E2E pass
bun run scripts/replay-determinism.ts fixtures/game-*.log # → hashes match for every fixture
bun run start:server & # server up
sleep 2
bun run test:integration # WebSocket handshake, move exchange, reconnect
kill %1
gh run list --limit 1 --json conclusion -q '.[0].conclusion' # → "success"
git tag --list # → contains v0.1.0-phase1 … v1.0.0
Final Checklist
- All "Must Have" present (verified by F1)
- All "Must NOT Have" absent (verified by F1 and F2)
- All phase tags present (v0.1.0-phase1 … v1.0.0)
- Engine coverage ≥90% / chess ≥70% / server ≥80%
- Bundle sizes within budget (engine <50KB, chess <200KB)
- Playwright scenarios all green
- Server integration tests all green
- Replay-determinism hash match 100%
- CI green on latest commit
- User has given explicit approval after F1-F4 presentation