3215 lines
149 KiB
Markdown
3215 lines
149 KiB
Markdown
# 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-server` authoritative 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-import
|
||
> - `packages/chess-server`: Bun WebSocket authoritative server with rooms, reconnection, move validation, deterministic broadcast
|
||
> - `docs/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](https://github.com/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](https://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. Zero `eval`, 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 scenarios
|
||
- `packages/chess/RULES.md` — 15 concrete preset rules with compat matrix and test scenarios
|
||
- `packages/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:
|
||
```bash
|
||
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/chess` with 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 (`v` field), 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 audit` green
|
||
- ≥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 runs `bun 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-error` in engine package (ESLint-enforced)
|
||
- NO `Date.now()`, `Math.random()`, `performance.now()`, `setTimeout`, `setInterval`, `fetch`, `console.log` inside engine RHS code paths (ESLint override on engine package)
|
||
- NO raw `Set<object>` or `Map<object, …>` iteration in engine hot paths (must sort to array first)
|
||
- NO circular package dependencies (`@paratype/chess` may 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` → inspect `packages/*/dist/`; evidence = `ls -la` output
|
||
- **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
|
||
|
||
- [x] P0.1. **Author `packages/rete/SPEC.md` — engine semantics specification**
|
||
|
||
**What to do**:
|
||
- Create directory `packages/rete/`
|
||
- Write `packages/rete/SPEC.md` with sections (exactly these, `## ` headings):
|
||
1. `## 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
|
||
2. `## ID Authority` — Session owns counter; in multiplayer, only server increments; clients receive facts with server-assigned ids
|
||
3. `## Conflict Resolution` — deterministic order: salience desc → specificity (# of conditions) desc → rule insertion order asc
|
||
4. `## Match Refraction` — each unique match fires once; re-fires only on fact change affecting bindings
|
||
5. `## Iteration Order` — all Session iteration uses sorted arrays (sort keys documented per structure); no raw `Set<object>` iteration in hot paths
|
||
6. `## Truth Maintenance` — derived facts (thenFinally) retract when any supporting fact retracts; logical dependency tracked per derived fact
|
||
7. `## Cycle Detection` — configurable recursion limit (default 64); exceeded → `RecursionLimitExceededError` with cycle trace
|
||
8. `## RHS Purity Contract` — RHS may NOT call Date.now, Math.random, performance.now, setTimeout, setInterval, fetch, or any I/O; enforced via ESLint rule `no-impure-rhs` (custom rule) + dev-mode runtime global wrapping
|
||
9. `## JSON Rule Schema` — handler-registry pattern: `{name, salience, conditions: [...], handler: "registeredName", args: JsonValue[]}`; NO function-to-string, NO eval, NO arbitrary JS
|
||
10. `## 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.md` exists
|
||
- [ ] `[ "$(grep -c '^## ' packages/rete/SPEC.md)" -ge "10" ]` → true (exactly 10 `## ` sections)
|
||
- [ ] `grep -q 'Doorenbos' packages/rete/SPEC.md` → 0 exit
|
||
- [ ] `grep -q 'handler-registry' packages/rete/SPEC.md` → 0 exit
|
||
- [ ] `grep -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.log
|
||
```
|
||
|
||
**Commit**: YES
|
||
- Message: `docs(rete): author engine specification (SPEC.md)`
|
||
- Files: `packages/rete/SPEC.md`
|
||
- Pre-commit: none (doc-only commit; hook runs `bun run check` which no-ops on empty repo)
|
||
|
||
- [x] P0.2. **Author `docs/PHASES.md` — phase gates + non-goals + perf budgets**
|
||
|
||
**What to do**:
|
||
- Create `docs/PHASES.md` with 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.md` exists
|
||
- [ ] `[ "$(grep -c '^## Phase' docs/PHASES.md)" -eq "5" ]` → true (Phase 0..4)
|
||
- [ ] `grep -q 'Non-Goals' docs/PHASES.md` → 0 exit
|
||
- [ ] `grep -q 'Performance Budgets' docs/PHASES.md` → 0 exit
|
||
- [ ] `grep -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.log
|
||
```
|
||
|
||
**Commit**: YES
|
||
- Message: `docs(root): author PHASES.md with phase gates and perf budgets`
|
||
- Files: `docs/PHASES.md`
|
||
- Pre-commit: none
|
||
|
||
- [x] P0.3. **Author `packages/chess/RULES.md` — 15 concrete preset custom rules**
|
||
|
||
**What to do**:
|
||
- Create directory `packages/chess/`
|
||
- Write `packages/chess/RULES.md` listing 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.md` exists
|
||
- [ ] `[ "$(grep -c '^### ' packages/chess/RULES.md)" -eq "15" ]` → true (exactly 15 rule headings)
|
||
- [ ] `grep -c '\*\*ID\*\*:' packages/chess/RULES.md` == 15
|
||
- [ ] `grep -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.log
|
||
```
|
||
|
||
**Commit**: YES
|
||
- Message: `docs(chess): author RULES.md with 15 concrete preset custom rules`
|
||
- Files: `packages/chess/RULES.md`
|
||
- Pre-commit: none
|
||
|
||
- [x] P0.4. **Author `packages/server/PROTOCOL.md` — WebSocket protocol v1**
|
||
|
||
**What to do**:
|
||
- Create directory `packages/server/`
|
||
- Write `packages/server/PROTOCOL.md` defining WebSocket protocol v1
|
||
- Include `## Overview` explaining: all messages have top-level `v: 1`; all include `seq: number` (monotonic); all include `ts: number` (unix ms); mismatched `v` → 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 seen `seq`, 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 with `error` code `RATE_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 patterns
|
||
- `code-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.md` exists
|
||
- [ ] `[ "$(grep -c '^### Message: ' packages/server/PROTOCOL.md)" -ge "8" ]` → true
|
||
- [ ] `grep -q 'Reconnection Flow' packages/server/PROTOCOL.md` → 0 exit
|
||
- [ ] `grep -q 'Rate Limiting' packages/server/PROTOCOL.md` → 0 exit
|
||
- [ ] `grep -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.log
|
||
```
|
||
|
||
**Commit**: YES
|
||
- Message: `docs(server): author PROTOCOL.md defining WebSocket protocol v1`
|
||
- Files: `packages/server/PROTOCOL.md`
|
||
- Pre-commit: none
|
||
|
||
- [x] P0.5. **Scaffold monorepo skeleton (Bun workspaces + tsconfig + eslint + vitest + playwright)**
|
||
|
||
**What to do**:
|
||
- Root: `package.json` with `"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-globals` ban `Date`, `Math.random`, `performance`, `setTimeout`, `setInterval`, `fetch` within `packages/rete/src/**/rhs/**` and engine RHS paths (override-based); `@typescript-eslint/no-explicit-any` error
|
||
- Root `vitest.workspace.ts` listing all packages
|
||
- Root `playwright.config.ts` with 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.json` extending base, empty `src/index.ts` with `export {}`, `README.md` (one-line description)
|
||
- Same skeleton for `packages/chess` (`"name": "@paratype/chess"`) and `packages/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 install` exits 0
|
||
- [ ] `bun run check` exits 0 (zero tests OK; zero lint errors)
|
||
- [ ] `bun run build` exits 0 (emits dist/ for each package OR exits 0 with skip — depends on tsup wiring; at minimum `tsc -b` passes)
|
||
- [ ] 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 for `chess` and `server`
|
||
|
||
**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.log
|
||
```
|
||
|
||
**Commit**: 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)
|
||
|
||
- [x] 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@v1` pinning to stable
|
||
- Cache: `~/.bun/install/cache`
|
||
- Upload Playwright traces on failure
|
||
- Create `lefthook.yml` at root with pre-commit hook running `bun run check` (fast — typecheck + lint + unit tests only, not Playwright)
|
||
- Install lefthook as dev dep; add `postinstall` script running `bunx lefthook install`
|
||
- Add `.github/workflows/README.md` explaining 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 current `oven-sh/setup-bun` action options
|
||
- `code-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.yml` exists and passes `actionlint` (`bun x @action-validator/cli action-validator .github/workflows/ci.yml` OR `gh workflow view` after push)
|
||
- [ ] `lefthook.yml` exists at root
|
||
- [ ] `bun run check` is wired as pre-commit (running `bunx lefthook run pre-commit` executes 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.log
|
||
```
|
||
|
||
**Commit**: 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`
|
||
|
||
### Phase 1 — Engine Pararules Parity (TDD)
|
||
|
||
- [x] 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 `Fact` with wrong value type for an attr produces a TypeScript type error (type-level test via `@ts-expect-error` comments in a `.type-test.ts` file)
|
||
- 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 object
|
||
- `export type Fact<S>` as tagged union discriminated by `attr` key
|
||
- `export function fact<S, K extends keyof S>(id: EntityId, attr: K, value: S[K]): Fact<S>`
|
||
- `EntityId` as branded `number` via `type EntityId = number & { readonly __brand: 'EntityId' }`
|
||
- REFACTOR: Extract type utilities to `schema.types.ts` if file exceeds 150 LOC; add JSDoc on public exports only
|
||
- Export from `packages/rete/src/index.ts`
|
||
|
||
**Must NOT do**:
|
||
- Do NOT use `any` or `unknown as X` casts
|
||
- 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 green
|
||
- [ ] `bun x tsc --noEmit -p packages/rete/tsconfig.json` → 0 errors
|
||
- [ ] Type-level tests in `schema.type-test.ts` compile (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.log
|
||
```
|
||
|
||
**Commit**: 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`
|
||
|
||
- [x] 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 not
|
||
- `WM.contains(id, attr)` returns boolean
|
||
- `WM.get(id, attr)` returns value or undefined
|
||
- `WM.allFacts()` returns sorted stable array (sort key: `[id, attr]`) — iteration determinism per SPEC §Iteration Order
|
||
- GREEN: `packages/rete/src/wm.ts` — `class WorkingMemory<S>` using `Map<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.ts` all green
|
||
- [ ] Coverage ≥ 95%
|
||
- [ ] No raw Map/Set exposed in public API (`grep -E 'export (const|function|class).*(Map|Set)' packages/rete/src/wm.ts` empty)
|
||
|
||
**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.log
|
||
```
|
||
|
||
**Commit**: 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`
|
||
|
||
- [x] 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 AlphaNetwork` subscribes to `WorkingMemory` events
|
||
- `class AlphaNode` with `condition: { id?: EntityId, attr: AttrKey }`
|
||
- `class AlphaMemory` holds `Fact[]` 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.ts` all 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.log
|
||
```
|
||
|
||
**Commit**: 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`
|
||
|
||
- [x] 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 session
|
||
- `session.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 WM
|
||
- `session.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>` holding `WorkingMemory<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.ts` all green
|
||
- [ ] Public API surface locked via `type` export; `tsd` or `expect-type` verifies no `any` leaks
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: 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`
|
||
|
||
- [x] 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 a `RuleDefinition` object with conditions and `handler` ref
|
||
- `HandlerRegistry.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 returning `RuleDefinition`
|
||
- GREEN: `packages/rete/src/registry.ts` — `HandlerRegistry` (Map-backed, with `register`, `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 `eval` or `new 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.ts` green
|
||
- [ ] `bun test packages/rete/src/registry.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: 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`
|
||
|
||
- [x] 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 Schema
|
||
- `deserialize(json, registry)` produces a `RuleDefinition` equivalent (deep-equal after normalization)
|
||
- Round-trip: `deserialize(serialize(rule)) ≡ rule` for 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.ts` with `serialize`, `deserialize`, exported JSON schema (as `RULE_SCHEMA_V1` constant)
|
||
|
||
**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.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: 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`
|
||
|
||
- [x] 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.4
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/rete/src/beta.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: 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`
|
||
|
||
- [x] 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.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: 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`
|
||
|
||
- [x] 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` — `FilterNode` holding `predicate: 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**: `deep`
|
||
|
||
**Parallelization**: 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 Schema
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/rete/src/condition.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: 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`
|
||
|
||
- [x] 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**: `deep`
|
||
|
||
**Parallelization**: YES — Wave P1.2
|
||
- **Blocks**: P2.*
|
||
- **Blocked By**: P1.8 (beta produces tokens)
|
||
|
||
**References**: `packages/rete/SPEC.md` §Iteration Order
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/rete/src/query.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: 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`
|
||
|
||
- [x] 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.thenFinally` handler; 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**: `deep`
|
||
|
||
**Parallelization**: 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 Maintenance
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/rete/src/derived.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: 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`
|
||
|
||
- [x] 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 })` throws `RecursionLimitExceededError` with cycle trace; `recursionLimit: 0` disables (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**: `deep`
|
||
|
||
**Parallelization**: YES — Wave P1.3
|
||
- **Blocks**: P1.14
|
||
- **Blocked By**: P1.4 (session)
|
||
|
||
**References**: `packages/rete/SPEC.md` §Cycle Detection
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/rete/src/cycle.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: 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`
|
||
|
||
- [x] 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.random` for tiebreaking
|
||
- Do NOT sort by rule name lexicographically (that hides bugs via alphabetization)
|
||
|
||
**Recommended Agent Profile**: `deep`
|
||
|
||
**Parallelization**: YES — Wave P1.3
|
||
- **Blocks**: P1.14
|
||
- **Blocked By**: P1.4
|
||
|
||
**References**: `packages/rete/SPEC.md` §Conflict Resolution
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/rete/src/conflict.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: 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`
|
||
|
||
- [x] P1.14. **Pararules golden-file test port**
|
||
|
||
**What to do**:
|
||
- Port 5-10 representative pararules tests from `paranim/pararules/tests/*.nim` to TS/Vitest under `packages/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 green
|
||
- [ ] `packages/rete/tests/golden/GOLDEN-MAP.md` lists 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.log
|
||
```
|
||
|
||
**Commit**: 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`
|
||
|
||
### Phase 2 — Rete II Extensions + Chess Engine
|
||
|
||
- [x] 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` — `NegationNode` per 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.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(rete): add negation nodes (NOT) (P2.1)` — files: `packages/rete/src/negation.ts`, `packages/rete/src/negation.test.ts`
|
||
|
||
- [x] 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.ts`
|
||
|
||
**Must NOT do**: double-count (increment on same fact twice)
|
||
**Recommended Agent Profile**: `deep`
|
||
**Parallelization**: YES — Wave P2.1
|
||
**Blocks**: P2.18, P2.19
|
||
**Blocked By**: P1.14
|
||
**References**: Doorenbos §2.6.2
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/rete/src/existential.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(rete): add existential nodes (EXISTS) (P2.2)`
|
||
|
||
- [x] 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.3
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/rete/src/ncc.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(rete): add NCC nodes (P2.3)`
|
||
|
||
- [x] 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. Support `count`, `sum`, `min`, `max`, `collect` (array). Incremental update: maintain running total rather than full recompute. GREEN: `packages/rete/src/aggregate.ts`
|
||
|
||
**Must 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.14
|
||
|
||
**References**: Drools aggregation patterns
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/rete/src/aggregate.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(rete): add aggregation nodes (count/sum/collect/min/max) (P2.4)`
|
||
|
||
- [x] 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**: `deep`
|
||
**Parallelization**: YES — Wave P2.2
|
||
**Blocks**: P2.8-P2.22
|
||
**Blocked By**: P1.14
|
||
|
||
**References**: `packages/chess/RULES.md`, `packages/rete/SPEC.md`
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `packages/chess/src/schema.ts` exports typed schema
|
||
- [ ] `bun x tsc -b packages/chess` → 0 errors
|
||
- [ ] `bun test packages/chess/src/schema.test.ts` green
|
||
|
||
**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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add attribute schema and piece fact shape (P2.5)`
|
||
|
||
- [x] 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 of `session.allFacts()` sorted output.
|
||
|
||
**Must NOT do**: hardcode as JSON fixture (must be generated deterministically)
|
||
**Recommended Agent Profile**: `quick`
|
||
**Parallelization**: YES — Wave P2.2
|
||
**Blocks**: P2.8+
|
||
**Blocked By**: P2.5
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/chess/src/starting-position.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add starting-position fact generator (P2.6)`
|
||
|
||
- [x] 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 each
|
||
|
||
**Must NOT do**: use string representations internally
|
||
**Recommended Agent Profile**: `quick`
|
||
**Parallelization**: YES — Wave P2.2
|
||
**Blocks**: P2.9-P2.12
|
||
**Blocked By**: P2.5
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/chess/src/coord.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add coordinate + color helpers (P2.7)`
|
||
|
||
- [x] 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**: `deep`
|
||
**Parallelization**: YES — Wave P2.2
|
||
**Blocks**: P2.9-P2.14
|
||
**Blocked By**: P2.5, P2.7
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/chess/src/rules/primitives.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add movement primitive rules (P2.8)`
|
||
|
||
- [x] 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). Emit `LegalMove(pieceId, from, to)` derived facts. TDD each case including blocked paths.
|
||
|
||
**Must NOT do**: handle en passant yet (P2.16)
|
||
**Recommended Agent Profile**: `deep`
|
||
**Parallelization**: YES — Wave P2.3 (with P2.10-P2.14)
|
||
**Blocks**: P2.13, P2.16, P2.17
|
||
**Blocked By**: P2.8
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/chess/src/rules/pawn.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add pawn move/capture rules (P2.9)`
|
||
|
||
- [x] P2.10. **Knight move rules**
|
||
|
||
**What to do**: `packages/chess/src/rules/knight.ts` — 8 L-offsets; leap over other pieces; `LegalMove` emission
|
||
|
||
**Must NOT do**: filter path squares (knight leaps)
|
||
**Recommended Agent Profile**: `deep`
|
||
**Parallelization**: YES — Wave P2.3
|
||
**Blocks**: P2.13
|
||
**Blocked By**: P2.8
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/chess/src/rules/knight.test.ts` green
|
||
|
||
**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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add knight move rules (P2.10)`
|
||
|
||
- [x] P2.11. **Bishop/Rook/Queen sliding rules**
|
||
|
||
**What to do**: `packages/chess/src/rules/sliding.ts` — `SlidingMove` production 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**: `deep`
|
||
**Parallelization**: YES — Wave P2.3
|
||
**Blocks**: P2.13, P2.15 (castling reads rook moves)
|
||
**Blocked By**: P2.8
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/chess/src/rules/sliding.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add bishop/rook/queen sliding rules (P2.11)`
|
||
|
||
- [x] 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**: `deep`
|
||
**Parallelization**: YES — Wave P2.3
|
||
**Blocks**: P2.13, P2.15, P2.18, P2.19
|
||
**Blocked By**: P2.8
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/chess/src/rules/king.test.ts` green
|
||
|
||
**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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add king basic move rules (P2.12)`
|
||
|
||
- [x] 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 vs `LegalMove` set; on success, update piece positions + retract old `LegalMove` facts. 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**: `deep`
|
||
**Parallelization**: 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.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add turn order + move integration (P2.13)`
|
||
|
||
- [x] 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**: `deep`
|
||
**Parallelization**: YES — Wave P2.3
|
||
**Blocks**: P2.16, P2.19-P2.22
|
||
**Blocked By**: P2.13
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/chess/src/rules/capture.test.ts` green
|
||
|
||
**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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add capture resolution (P2.14)`
|
||
|
||
- [x] 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**: `deep`
|
||
**Parallelization**: 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.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add castling rules (P2.15)`
|
||
|
||
- [x] P2.16. **En passant (single-tick capture window)**
|
||
|
||
**What to do**: `packages/chess/src/rules/enpassant.ts` — after a pawn's double-advance, set `EnPassantTarget(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**: `deep`
|
||
**Parallelization**: YES — Wave P2.4
|
||
**Blocks**: P2.23
|
||
**Blocked By**: P2.9, P2.14
|
||
|
||
**References**: FIDE §3.7.3
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/chess/src/rules/enpassant.test.ts` green
|
||
|
||
**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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add en passant rule (P2.16)`
|
||
|
||
- [x] 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 the `AttemptedMove` fact via `promoteTo` field; default to Q if missing.
|
||
|
||
**Recommended Agent Profile**: `deep`
|
||
**Parallelization**: YES — Wave P2.4
|
||
**Blocks**: P2.23
|
||
**Blocked By**: P2.9
|
||
|
||
**References**: FIDE §3.7.5
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/chess/src/rules/promotion.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add pawn promotion rule (P2.17)`
|
||
|
||
- [x] P2.18. **Check detection**
|
||
|
||
**What to do**: `packages/chess/src/rules/check.ts` — derived fact `InCheck(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**: `deep`
|
||
**Parallelization**: YES — Wave P2.4
|
||
**Blocks**: P2.15 (castling through check), P2.19, P2.23
|
||
**Blocked By**: P2.2 (exists), P2.9-P2.14
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/chess/src/rules/check.test.ts` green
|
||
|
||
**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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add check detection + self-check filter (P2.18)`
|
||
|
||
- [x] P2.19. **Checkmate detection**
|
||
|
||
**What to do**: `packages/chess/src/rules/checkmate.ts` — derived fact `GameOver(result, reason)` when: `InCheck(turn)` AND no LegalMove exists for any piece of `turn`. Uses NCC.
|
||
|
||
**Recommended Agent Profile**: `deep`
|
||
**Parallelization**: YES — Wave P2.5 (with P2.20-P2.22)
|
||
**Blocks**: P2.23
|
||
**Blocked By**: P2.3 (NCC), P2.18
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/chess/src/rules/checkmate.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add checkmate detection (P2.19)`
|
||
|
||
- [x] P2.20. **Stalemate detection**
|
||
|
||
**What to do**: `packages/chess/src/rules/stalemate.ts` — `GameOver('draw', 'stalemate')` when: NOT `InCheck(turn)` AND no LegalMove exists for `turn`.
|
||
|
||
**Recommended Agent Profile**: `deep`
|
||
**Parallelization**: YES — Wave P2.5
|
||
**Blocks**: P2.23
|
||
**Blocked By**: P2.3 (NCC), P2.18
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/chess/src/rules/stalemate.test.ts` green
|
||
|
||
**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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add stalemate detection (P2.20)`
|
||
|
||
- [x] 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 a `PositionHash` fact 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**: `deep`
|
||
**Parallelization**: YES — Wave P2.5
|
||
**Blocks**: P2.23
|
||
**Blocked By**: P2.4 (aggregation), P2.14
|
||
|
||
**References**: FIDE §5.2.2, §5.2.3
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/chess/src/rules/draws.test.ts` green
|
||
|
||
**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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add 50-move and threefold repetition rules (P2.21)`
|
||
|
||
- [x] 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**: `deep`
|
||
**Parallelization**: YES — Wave P2.5
|
||
**Blocks**: P2.23
|
||
**Blocked By**: P2.4 (aggregation)
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/chess/src/rules/insufficient.test.ts` green
|
||
|
||
**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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add insufficient material draw (P2.22)`
|
||
|
||
- [x] 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-high`
|
||
**Parallelization**: NO — Wave P2.6 (gate)
|
||
**Blocks**: Phase 3
|
||
**Blocked By**: P2.1-P2.22
|
||
|
||
**Acceptance 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.log
|
||
```
|
||
|
||
**Commit**: 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
|
||
|
||
- [x] P3.1. **Event log: append-only, monotonic sequence numbers (TDD)**
|
||
|
||
**What to do**: `packages/rete/src/eventlog.ts` — `class EventLog` records every `insert(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**: `deep`
|
||
**Parallelization**: YES — Wave P3.1 (with P3.2, P3.3)
|
||
**Blocks**: P3.3, P3.14, P4.7
|
||
**Blocked By**: P2.23
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/rete/src/eventlog.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(rete): add append-only event log with monotonic sequence (P3.1)`
|
||
|
||
- [x] P3.2. **Immer snapshot every N ticks (TDD)**
|
||
|
||
**What to do**: `packages/rete/src/snapshot.ts` — on every Nth `fireRules()` call (configurable, default N=30), capture full WM state via Immer's `produce`. Structural sharing minimizes copies. `getSnapshotAt(seq)` returns nearest snapshot ≤ seq. Add `Session` option `snapshotInterval: 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.23
|
||
|
||
**References**: Immer docs
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/rete/src/snapshot.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(rete): add Immer snapshots at tick boundaries (P3.2)`
|
||
|
||
- [x] P3.3. **Replay engine + determinism hash verifier (TDD)**
|
||
|
||
**What to do**: `packages/rete/src/replay.ts` — `replayFromLog(log, schema, handlers): Session` reconstructs WM by replaying events on a fresh session. `stateHash(session): string` produces sha256 over sorted facts. Determinism test: recording a random fact/rule sequence, replaying, comparing hashes — must match byte-for-byte. Add `scripts/replay-determinism.ts` runner for CI.
|
||
|
||
**Must NOT do**: depend on Map/Set iteration order (sort before hashing)
|
||
**Recommended Agent Profile**: `deep`
|
||
**Parallelization**: YES — Wave P3.1
|
||
**Blocks**: P3.14, P4.7
|
||
**Blocked By**: P3.1, P3.2
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/rete/src/replay.test.ts` green
|
||
- [ ] `bun 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(rete): add replay engine + state-hash determinism verifier (P3.3)`
|
||
|
||
- [x] 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 in `packages/chess/src/presets/{id}.ts`, a registered toggle in `packages/chess/src/presets/registry.ts`, unit tests, compatibility declarations.
|
||
|
||
**Must NOT do**: implement presets outside the first 3
|
||
**Recommended Agent Profile**: `deep`
|
||
**Parallelization**: 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.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add preset rules 1-3 (P3.4)`
|
||
|
||
- [x] 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**: `deep`
|
||
**Parallelization**: YES — Wave P3.2
|
||
**Blocks**: P3.11
|
||
**Blocked By**: P2.23, P0.3
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/chess/src/presets` includes 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add preset rules 4-6 (P3.5)`
|
||
|
||
- [x] P3.6. **Preset rules 7-9 (rook/queen/king variants)**
|
||
|
||
**What to do**: Implement presets 7-9 from RULES.md.
|
||
**Recommended Agent Profile**: `deep`
|
||
**Parallelization**: YES — Wave P3.2
|
||
**Blocks**: P3.11
|
||
**Blocked By**: P2.23, P0.3
|
||
|
||
**Acceptance 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add preset rules 7-9 (P3.6)`
|
||
|
||
- [x] 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**: `deep`
|
||
**Parallelization**: YES — Wave P3.2
|
||
**Blocks**: P3.11
|
||
**Blocked By**: P2.23, P0.3
|
||
|
||
**Acceptance 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add preset rules 10-12 (P3.7)`
|
||
|
||
- [x] 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 `extendChessSchema` helper), supporting facts (HP defaults to 1 for FIDE).
|
||
|
||
**Must NOT do**: leak chess-schema extensions into engine core
|
||
**Recommended Agent Profile**: `deep`
|
||
**Parallelization**: YES — Wave P3.2
|
||
**Blocks**: P3.11
|
||
**Blocked By**: P2.23, P0.3
|
||
|
||
**Acceptance 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add preset rules 13-15 (P3.8)`
|
||
|
||
- [x] 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.23
|
||
|
||
**References**: Vite React guide; Tailwind setup
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `cd packages/chess && bun run dev` starts at localhost:5173
|
||
- [ ] `bun run build` produces 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): scaffold Vite + React app (P3.9)`
|
||
|
||
- [x] 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. Uses `useSession()` 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.9
|
||
|
||
**Acceptance 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.png
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add interactive Chessboard with drag-drop (P3.10)`
|
||
|
||
- [x] 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-engineering`
|
||
**Parallelization**: YES — Wave P3.3
|
||
**Blocks**: P3.15
|
||
**Blocked By**: P3.4-P3.8, P3.9
|
||
|
||
**Acceptance 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.png
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add rule-toggle UI with compatibility warnings (P3.11)`
|
||
|
||
- [x] 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 previous `Turn`-changed fact boundary (one full move back); save panel lists slots from localStorage (schema-versioned JSON).
|
||
|
||
**Recommended Agent Profile**: `visual-engineering`
|
||
**Parallelization**: YES — Wave P3.3
|
||
**Blocks**: P3.14, P3.15
|
||
**Blocked By**: P3.3, P3.9
|
||
|
||
**Acceptance 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.png
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add Save/Load panel + time-travel undo (P3.12)`
|
||
|
||
- [x] 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-engineering`
|
||
**Parallelization**: YES — Wave P3.3
|
||
**Blocks**: P3.15
|
||
**Blocked By**: P1.6, P3.9
|
||
|
||
**Acceptance 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.png
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add JSON export/import with validation (P3.13)`
|
||
|
||
- [x] 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 key `paratype-chess:v1:autosave`. On app load, if key present, restore via `replayFromLog`. Include schema version in payload.
|
||
|
||
**Must NOT do**: write on every tick (too noisy); write to sessionStorage (lost on close)
|
||
**Recommended Agent Profile**: `unspecified-high`
|
||
**Parallelization**: YES — Wave P3.4 (with P3.15)
|
||
**Blocks**: P3.15
|
||
**Blocked By**: P3.3, P3.12
|
||
|
||
**Acceptance 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.png
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add localStorage auto-save and restore (P3.14)`
|
||
|
||
- [x] 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 (`waitForTimeout` is 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.14
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun x playwright test packages/chess/e2e/full-flow.spec.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `test(chess): e2e full-flow scenario; tag Phase 3 (P3.15)`; post-commit: `git tag v0.3.0-phase3`
|
||
|
||
### Phase 4 — Authoritative Multiplayer
|
||
|
||
- [x] 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 endpoint `GET /healthz` returning `{ 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.15
|
||
|
||
**References**: Bun.serve docs, pino
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun run packages/server/src/index.ts` starts; `curl localhost:7357/healthz` returns 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(server): scaffold Bun HTTP+WS server with health + logging (P4.1)`
|
||
|
||
- [x] 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-level `v` version check; round-trip tested.
|
||
|
||
**Must NOT do**: use JSON.parse without validation
|
||
**Recommended Agent Profile**: `deep`
|
||
**Parallelization**: YES — Wave P4.1
|
||
**Blocks**: P4.5, P4.6
|
||
**Blocked By**: P0.4 (PROTOCOL.md), P3.15
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/server/src/protocol.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(server): add protocol schemas + validation (P4.2)`
|
||
|
||
- [x] P4.3. **Room model (create/join/leave, 6-char codes)**
|
||
|
||
**What to do**: `packages/server/src/rooms.ts` — `class RoomRegistry` with `createRoom()` → 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**: `deep`
|
||
**Parallelization**: YES — Wave P4.1
|
||
**Blocks**: P4.5
|
||
**Blocked By**: P3.15
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/server/src/rooms.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(server): add room registry with codes + tokens (P4.3)`
|
||
|
||
- [x] 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 env `ALLOWED_ORIGINS`); reject payloads > 64KB with disconnect.
|
||
|
||
**Recommended Agent Profile**: `unspecified-high`
|
||
**Parallelization**: YES — Wave P4.1
|
||
**Blocks**: P4.12
|
||
**Blocked By**: P3.15
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/server/src/middleware.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(server): add rate-limit, origin allow-list, message-size cap (P4.4)`
|
||
|
||
- [x] P4.5. **Authoritative session per room**
|
||
|
||
**What to do**: `packages/server/src/game-session.ts` — each room holds a `Session` from `@paratype/rete` + chess rules; server is the only one that calls `insert/retract/fireRules`. Fact IDs minted here only.
|
||
|
||
**Recommended Agent Profile**: `deep`
|
||
**Parallelization**: YES — Wave P4.2
|
||
**Blocks**: P4.6, P4.12
|
||
**Blocked By**: P4.1, P4.2, P4.3
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/server/src/game-session.test.ts` green
|
||
|
||
**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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(server): add authoritative game session per room (P4.5)`
|
||
|
||
- [x] P4.6. **Move-intent validation + fact-delta broadcast**
|
||
|
||
**What to do**: `packages/server/src/broadcast.ts` — on `game.move` intent: insert `AttemptedMove` fact; fire rules; diff pre/post WM; broadcast `game.delta` with added/removed facts to both clients. Assigned `seq` per delta for reconnection.
|
||
|
||
**Recommended Agent Profile**: `deep`
|
||
**Parallelization**: YES — Wave P4.2
|
||
**Blocks**: P4.12
|
||
**Blocked By**: P4.5
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] Integration test: send legal move → both clients receive delta with updated Position fact
|
||
- [ ] Illegal move → `error` message; 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.json
|
||
```
|
||
|
||
**Commit**: YES — `feat(server): add move validation + fact-delta broadcast (P4.6)`
|
||
|
||
- [x] 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 send `game.state` (full snapshot) + all deltas since client's last `seq`. After 60s, room aborts with `game.end` broadcast to remaining client.
|
||
|
||
**Recommended Agent Profile**: `deep`
|
||
**Parallelization**: 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.json
|
||
```
|
||
|
||
**Commit**: YES — `feat(server): add reconnection with 60s grace + snapshot resume (P4.7)`
|
||
|
||
- [x] P4.8. **Structured logging + metrics**
|
||
|
||
**What to do**: `packages/server/src/logging.ts` — pino logger with request-scoped `roomId`, `clientId`, `seq`; per-tick duration metric; `/metrics` endpoint (Prometheus text format) with counters: `rooms_active`, `messages_received_total`, `moves_validated_total{result}`, tick duration histogram.
|
||
|
||
**Recommended Agent Profile**: `unspecified-high`
|
||
**Parallelization**: YES — Wave P4.2
|
||
**Blocks**: P4.12
|
||
**Blocked By**: P4.1
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `curl localhost:7357/metrics` returns 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(server): add pino logging and Prometheus metrics (P4.8)`
|
||
|
||
- [x] P4.9. **WebSocket client library with reconnect + seq ack**
|
||
|
||
**What to do**: `packages/chess/src/net/client.ts` — `class GameClient` with `connect(code, token)`, exponential-backoff reconnect, sequence-ack tracking, event emitter for `game.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**: `deep`
|
||
**Parallelization**: 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.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add WebSocket client library with reconnect (P4.9)`
|
||
|
||
- [x] 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; on `game.delta`, reconciles (replaces predicted state with authoritative state). On `error` response, 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**: `deep`
|
||
**Parallelization**: YES — Wave P4.3
|
||
**Blocks**: P4.12
|
||
**Blocked By**: P4.9
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun test packages/chess/src/net/prediction.test.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add client prediction + server reconciliation (P4.10)`
|
||
|
||
- [x] 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 `/game` with active session.
|
||
|
||
**Recommended Agent Profile**: `visual-engineering`; Skills: [`interface-design`]
|
||
**Parallelization**: YES — Wave P4.3
|
||
**Blocks**: P4.12
|
||
**Blocked By**: P4.9
|
||
|
||
**Acceptance 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.png
|
||
```
|
||
|
||
**Commit**: YES — `feat(chess): add lobby UI for create/join rooms (P4.11)`
|
||
|
||
- [x] 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.11
|
||
|
||
**Acceptance Criteria**:
|
||
- [ ] `bun x playwright test packages/chess/e2e/multiplayer.spec.ts` green
|
||
- [ ] 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.log
|
||
```
|
||
|
||
**Commit**: 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.
|
||
|
||
- [x] F1. **Plan Compliance Audit** — `oracle`
|
||
Read 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`
|
||
|
||
- [x] F2. **Code Quality Review** — `unspecified-high`
|
||
Run `bun 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.log` in prod code, commented-out code, unused imports, `Date.now()`/`Math.random()` in engine RHS paths, raw `Set<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`
|
||
|
||
- [x] F3. **Real Manual QA via Playwright + Scripted Clients** — `unspecified-high` (+ `playwright` skill)
|
||
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`
|
||
|
||
- [x] F4. **Scope Fidelity Check** — `deep`
|
||
For each task: read "What to do", read actual diff (`git log` / `git diff` on 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): description` where `scope ∈ {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)
|
||
|
||
```bash
|
||
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
|