---
name: user-story-conversation
description: Guide users through Ron Jeffries' Card, Conversation, Confirmation workflow using Example Mapping, Allium specs, and fast-check properties.
disable-model-invocation: true
license: MIT
metadata:
  methodology: example-mapping
  references:
    - https://www.examplerelated.com/
    - https://www.ronjeffries.com/
---

# User Story Conversation

A skill for turning user stories into formal specifications and executable properties, following Ron Jeffries' **Card, Conversation, Confirmation** mantra enhanced with Matt Wynne's **Example Mapping**.

## The Six Steps

```
Card          →  User story (the "what")
Conversation  →  Example Mapping (the "understanding")
              →  Allium Spec (the "formal model")
Confirmation  →  fast-check Properties (the "executable verification")
              →  TypeScript ADTs (the "type-safe implementation")
```

## When to Use

- Starting a new feature or user story
- Clarifying ambiguous requirements
- Breaking down a complex user story into testable properties
- Aligning the team on domain understanding before implementation

## How to Run

1. **Start with the Card** — Ask the user for the user story (or use an existing one from `user-stories.md`)
2. **Example Map** — Work through the story together, identifying Rules, Examples, Questions, and Answers
3. **Allium Spec** — Formalize the rules and answers into a `.allium` spec
4. **fast-check Properties** — Translate invariants into property-based tests
5. **TypeScript ADTs** — Implement with value objects and discriminated unions
6. **Confirmation** — Verify the implementation satisfies all properties

## Step 1: Card

Present the user story as a card. Use the format:

```
As a [role]
I want [feature]
So that [benefit]
```

If the user already has a user story (e.g., from `user-stories.md`), use that. Otherwise, help them write one.

## Step 2: Conversation — Example Mapping

Work through the user story using Example Mapping. For each rule, identify:

### Rules (yellow) — Domain concepts

- Nouns and concepts in the domain
- What the system manages
- Example: "Health", "Damage", "Character", "Level", "Faction"

### Examples (blue) — Concrete scenarios

- Specific instances of rules
- Should be testable
- Example: "Character with 500 health takes 200 damage → 300 health"

### Questions (pink) — Ambiguities

- Things we don't know or aren't sure about
- Example: "What happens when damage exceeds health?"

### Answers (green) — Resolved questions

- Write directly on the pink sticky
- Example: "Health becomes 0, character dies"

### Example Mapping Output Format

Create a structured output like this:

```markdown
## Example Map: [User Story Title]

### Rules

1. [Rule description]
2. [Rule description]

### Examples

- [Example 1]
- [Example 2]

### Questions

- [Question 1]
- [Question 2]

### Answers

- [Answer to Question 1]
- [Answer to Question 2]
```

## Step 3: Allium Spec

Formalize the rules, examples, and answers into an Allium spec. Use the skills in this repository:

- `/skill:elicit` — if you need to explore requirements further
- `/skill:tend` — if you need to evolve an existing spec
- `/skill:allium` — for language reference

### Spec Structure

Create a `.allium` file with:

```allium
-- allium: 3

-- allium: [module-name]

------------------------------------------------------------
-- Entities and Variants
------------------------------------------------------------

entity Character {
    name: String
    health: Health
    status: alive | dead
    level: Level
    factions: Set<Faction>
}

------------------------------------------------------------
-- Rules
------------------------------------------------------------

rule DamageReducesHealth {
    when: CharacterTakesDamage(character, damage)
    ensures: character.health = character.health - damage
}

------------------------------------------------------------
-- Invariants
------------------------------------------------------------

invariant HealthNonNegative {
    for c in Characters:
        c.health >= 0
}

invariant DeathAtZeroHealth {
    for c in Characters:
        c.health = 0 implies c.status = dead
}
```

### Key Principles

- **Entities** represent domain objects with identity
- **Rules** describe state transitions and behaviour
- **Invariants** capture properties that must always hold
- **Enums** and **value types** for constrained data
- **Rules** should be testable — each one maps to a property

## Step 4: fast-check Properties

Translate Allium invariants and rules into fast-check properties.

### Property Patterns

#### Invariant Properties

```typescript
import fc from 'fast-check';

// "Health is never negative"
fc.property(fc.integer({ min: 0, max: 10000 }), (health) => {
  const c = new Character({ health: Health.create(health) });
  return c.health.value >= 0;
});
```

#### Rule Properties (State Transitions)

```typescript
// "Damage reduces health, capped at 0"
fc.property(
  fc.integer({ min: 0, max: 10000 }),
  fc.integer({ min: 0, max: 10000 }),
  (health, damage) => {
    const c = new Character({ health: Health.create(health) });
    c.takeDamage(Damage.create(damage));
    return c.health.value === Math.max(0, health - damage);
  },
);
```

#### Edge Case Properties

```typescript
// "Zero damage changes nothing"
fc.property(fc.integer({ min: 0, max: 10000 }), (health) => {
  const c = new Character({ health: Health.create(health) });
  c.takeDamage(Damage.create(0));
  return c.health.value === health;
});
```

### Property Naming

Name properties after the rule or invariant they verify:

```typescript
describe('DamageReducesHealth', () => {
  it('property: health is reduced by damage amount', () => { ... });
  it('property: health never goes below zero', () => { ... });
  it('property: character dies when health reaches zero', () => { ... });
});
```

## Step 5: TypeScript ADTs

Implement with "I can't believe it's not Haskell" principles:

### Discriminated Unions (ADTs)

```typescript
type Status = { kind: 'alive' } | { kind: 'dead' };

type FactionMember = { kind: 'member'; faction: Faction };
type FactionNeutral = { kind: 'neutral' };
type FactionMembership = FactionMember | FactionNeutral;
```

### Value Objects

```typescript
class Health {
  private constructor(private readonly value: number) {}

  static create(n: number): Health {
    if (n < 0) throw new Error('Health cannot be negative');
    return new Health(n);
  }

  static maxForLevel(level: number): number {
    return level >= 6 ? 1500 : 1000;
  }

  get value(): number {
    return this.value;
  }

  add(amount: number): Health {
    return Health.create(Math.min(this.value + amount, Health.maxForLevel(this.level.value)));
  }

  sub(amount: number): Health {
    return Health.create(Math.max(0, this.value - amount));
  }
}
```

### Immutable Entities

```typescript
class Character {
  constructor(
    readonly name: string,
    readonly health: Health,
    readonly status: Status,
    readonly level: Level,
    readonly factions: ReadonlySet<Faction>,
  ) {}

  // Pure functions — no mutation
  takeDamage(damage: Damage): Character {
    const newHealth = this.health.sub(damage.value);
    const newStatus = newHealth.value === 0 ? { kind: 'dead' as const } : this.status;
    return new Character(this.name, newHealth, newStatus, this.level, this.factions);
  }
}
```

### Key Principles

- **ADTs over classes** — discriminated unions for state/variants
- **Value objects over primitives** — `Health`, `Damage`, `Level` instead of `number`
- **Immutability** — return new instances, never mutate
- **Invariants at construction** — invalid states are unrepresentable
- **Pure functions** — domain logic has no side effects

## Step 6: Confirmation

Verify that the implementation satisfies all properties:

```bash
npm test
```

All properties should pass. If any fail, the property reveals a gap in understanding — revisit the Example Map or Allium spec.

## Output Artifacts

After completing the workflow, produce:

1. **Example Map** — Markdown file with structured domain knowledge
2. **Allium Spec** — `.allium` file with formal behavioural specification
3. **fast-check Properties** — Test files with property-based tests
4. **TypeScript Implementation** — ADTs, value objects, and pure functions

## Example: Full Workflow

Here's a complete example walking through "Characters can Deal Damage":

### Card

> Characters can Deal Damage to Characters.

### Example Map

**Rules:**

1. Damage is subtracted from Health
2. When damage exceeds health, health becomes 0 and character dies
3. A Character cannot Deal Damage to itself

**Examples:**

- Character with 1000 health takes 200 damage → 800 health
- Character with 100 health takes 200 damage → 0 health, dead
- Character tries to deal damage to self → no effect

**Questions:**

- Can a dead character take damage?
- Does damage stack across multiple attacks?

**Answers:**

- Dead characters cannot take damage (they're already dead)
- Yes, damage stacks (each attack reduces health further)

### Allium Spec

```allium
entity Character {
    name: String
    health: Health
    status: alive | dead
}

rule DamageReducesHealth {
    when: CharacterTakesDamage(character, damage)
    requires: character.status = alive
    ensures: character.health = character.health - damage
    ensures:
        if character.health <= 0:
            character.status = dead
}

invariant HealthNonNegative {
    for c in Characters:
        c.health >= 0
}

invariant SelfDamageForbidden {
    for a in Characters, t in Characters:
        a.dealDamage(t, _) implies a.name != t.name
}
```

### fast-check Properties

```typescript
describe('DamageReducesHealth', () => {
  it('property: health is reduced by damage', () => { ... });
  it('property: health never goes below zero', () => { ... });
  it('property: character dies when health reaches zero', () => { ... });
  it('property: dead characters cannot take damage', () => { ... });
  it('property: self-damage is forbidden', () => { ... });
});
```

### TypeScript Implementation

```typescript
type Status = { kind: 'alive' } | { kind: 'dead' };

class Health {
  static create(n: number): Health { ... }
  sub(amount: number): Health { ... }
}

class Character {
  constructor(
    readonly name: string,
    readonly health: Health,
    readonly status: Status,
  ) {}

  dealDamage(target: Character, damage: Damage): void {
    if (this.name === target.name) return; // self-damage forbidden
    if (target.status.kind === 'dead') return; // dead can't take damage
    const newHealth = target.health.sub(damage.value);
    const newStatus = newHealth.value === 0 ? { kind: 'dead' as const } : target.status;
    // ... update target
  }
}
```

## Integration with Allium Skills

This skill works alongside the Allium skills:

- **Before starting** — Use `/skill:allium` to understand the language
- **During spec writing** — Use `/skill:tend` to evolve specs, `/skill:propagate` to generate test obligations
- **After implementation** — Use `/skill:weed` to check spec-code alignment
- **When clarifying requirements** — Use `/skill:elicit` to explore with stakeholders

## Tips

- **Start small** — Map one user story at a time
- **Examples first** — Concrete examples help surface ambiguities
- **Properties before implementation** — Write fast-check properties before code
- **Let properties guide design** — If a property is hard to write, the design needs work
- **Iterate** — Example Mapping is a conversation, not a document. Revise as understanding deepens
- **Use the Allium CLI** — Run `allium check` to validate specs
- **Run fast-check with many iterations** — Use `fc.property(...).check({ numRuns: 1000 })` for thorough testing
