diff --git a/AGENTS.md b/AGENTS.md index 4d3a58d..3512b76 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,13 +16,14 @@ An implementation of the RPG Combat rules engine. There are six user stories des This project combines three practices: -1. **Allium** (`.allium` specs) — formal behavioural specifications that capture *what* the system does +1. **Allium** (`.allium` specs) — formal behavioural specifications that capture _what_ the system does 2. **fast-check** — property-based testing that verifies those properties hold across thousands of random inputs 3. **"I can't believe it's not Haskell"** — TypeScript with ADTs, value objects, and immutability ### Step 1: Spec with Allium Use the Allium skills to formalize user stories into `.allium` specs: + - `/skill:elicit` — explore requirements with stakeholders - `/skill:distill` — extract specs from existing code - `/skill:tend` — evolve specs as understanding deepens @@ -38,13 +39,10 @@ import fc from 'fast-check'; import { Character } from './domain'; // Invariant: "A character's health is never negative" -fc.property( - fc.integer({ min: 0, max: 1000 }), - (initialHealth) => { - const c = new Character({ name: 'hero', health: initialHealth }); - return c.health >= 0; - } -); +fc.property(fc.integer({ min: 0, max: 1000 }), (initialHealth) => { + const c = new Character({ name: 'hero', health: initialHealth }); + return c.health >= 0; +}); // Property: "Dealing damage reduces health, capped at 0" fc.property( @@ -58,13 +56,13 @@ fc.property( const t = new Character({ name: target, health: 1000 }); a.dealDamage(t, damage); return t.health === Math.max(0, 1000 - damage); - } + }, ); ``` ### Step 3: "I can't believe it's not Haskell" -Use TypeScript's type system to encode domain constraints: +Use TypeScript's type system to encode domain constraints. Add only code necessary to make the property pass, no more: ```typescript // ADTs via discriminated unions @@ -77,9 +75,15 @@ class Health { if (n < 0) throw new Error('Health cannot be negative'); return new Health(n); } - get value() { return this.value; } - add(n: number) { return Health.create(this.value + n); } - sub(n: number) { return Health.create(Math.max(0, this.value - n)); } + get value() { + return this.value; + } + add(n: number) { + return Health.create(this.value + n); + } + sub(n: number) { + return Health.create(Math.max(0, this.value - n)); + } } // Immutable entities @@ -98,16 +102,28 @@ class Character { } ``` + + **Key principles:** + - **ADTs over classes** — use discriminated unions for state/variants - **Value objects over primitives** — `Health`, `Damage`, `Level` instead of `number` - **Immutability** — no `this.health = ...`, return new instances - **Invariants at boundaries** — constructors enforce invariants, not getters/setters - **Pure functions** — domain logic has no side effects, testable in isolation +- **YAGNI** Write the minimum code necessary to make the current property pass. + +Before writing a method, ask: + + 1. Does a story property require this? If no → don't write it. + 2. Does this method touch a concept from a different story? If yes → it's scope creep. + 3. Am I implementing something because it feels useful, not because a property forces it? If yes → stop. + + ### Example: Damage Property -From user story: *"When damage received exceeds current Health, Health becomes 0 and the character dies"* +From user story: _"When damage received exceeds current Health, Health becomes 0 and the character dies"_ ```typescript // Allium invariant (in .allium spec) @@ -122,13 +138,14 @@ fc.property( const c = new Character({ name: 'goblin', health: Health.create(health) }); c.takeDamage(Damage.create(damage)); return c.health.value === Math.max(0, health - damage); - } + }, ).check(/* ... */); ``` ## Skill Invocation Allium skills are available in this project: + - `/skill:allium` — entry point and language reference - `/skill:elicit` — explore requirements - `/skill:distill` — extract specs from code @@ -137,4 +154,5 @@ Allium skills are available in this project: - `/skill:weed` — check spec-code alignment Domain workflow skill: + - `/skill:user-story-conversation` — Card, Conversation, Confirmation workflow with Example Mapping, Allium specs, and fast-check properties