update agent skill with YAGNI
This commit is contained in:
parent
e267c35e55
commit
8450f90e89
44
AGENTS.md
44
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) => {
|
||||
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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user