update agent skill with YAGNI

This commit is contained in:
Willem van den Ende 2026-06-12 22:08:10 +01:00
parent e267c35e55
commit 8450f90e89

View File

@ -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