---
name: purpose-driven-development
description: Purpose-Driven Development (PDD) for non-trivial sf changes. Use when implementing or reviewing behaviour changes, bug fixes, or refactors where purpose, consumer, contract, failure boundary, and evidence should be explicit before any code is written. Companion to spec-first-tdd — PDD scopes the change; TDD pins it with a test.
---

# Purpose-Driven Development

Use this skill for non-trivial work where implementation can drift away from user-visible behaviour. PDD makes the work explicit before code so that the test, the implementation, and the evidence all answer the same question.

This is the lightweight scoping companion to [`spec-first-tdd`](../spec-first-tdd/SKILL.md). PDD answers *what is this change?*; spec-first-tdd answers *how do we prove it?*. Use PDD first when scope is unclear; jump straight to spec-first-tdd when the contract is already obvious.

## What PDD Means

Before coding, name each of the following explicitly. If any one is missing, the change is underspecified — go back.

| Field | Question it answers |
|---|---|
| **Purpose** | What outcome must become true? |
| **Consumer** | Who depends on that outcome? (production caller, not a test) |
| **Contract** | What observable behaviour proves success? |
| **Failure boundary** | What does *correct failure* look like if the purpose can't be fulfilled? |
| **Evidence** | What test, metric, or repro proves the contract? Each criterion must be machine-executable (a named test file, a queryable metric, a runnable command) OR explicitly tagged `[MANUAL: reviewer + scenario]` when no automated check is feasible. Prose-only evidence is unfalsifiable and forbidden. |
| **Non-goals** | What is this change *not* solving? |
| **Invariants** | What must remain true while making the change? Split into safety ("X never happens") + liveness ("Y eventually happens") when the change touches async, queues, timers, or state machines. Pure synchronous code can use safety-only invariants. |
| **Assumptions** | What conditions about the world MUST be true for this spec to even be valid? (locking protocols, API stability, caller invariants, deployment context, data shape) |

These are the same fields the **Purpose Gate** in [`docs/SPEC_FIRST_TDD.md`](../../../../../../docs/SPEC_FIRST_TDD.md) requires of every artefact. PDD is how you fill them in before you start.

## Workflow

1. State the **purpose** in one or two sentences.
2. Name the **consumer** precisely — file, function, route, command. If you can't, stop.
3. Define the observable **contract** — what the consumer receives, not what the implementation does internally.
4. Define the **failure boundary** — degradation is not crash; surface, don't swallow.
5. State **non-goals** and **invariants** (split safety/liveness when the change touches async).
6. State **assumptions** — what must be true about the world for this spec to apply at all.
7. Choose the **evidence** before changing code — each criterion must be machine-executable or tagged `[MANUAL: reviewer + scenario]`.
8. Write or identify the failing behaviour test or repro for non-trivial work (hand off to `spec-first-tdd`).
9. Implement the *minimum* change that satisfies the contract.
10. Verify using the evidence chosen in step 7 — not a different one chosen post-hoc.

## Rules

- **Behaviour is the spec; mechanics are secondary.** A test that asserts how the implementation works internally is a rubber stamp.
- **Do not write tests that mirror the current implementation.** They lock in bugs and break on refactor.
- **Prefer observable outcomes over call counts and internal wiring assertions.** `mockFn.calls.length === 2` is mechanical, not purposeful.
- **If a bug blocks the purpose, fix the bug — do not route around it.** A workaround that leaves the bug in place is an anti-pattern.
- **If the purpose is unclear, stop and clarify it before coding** (use `clarify-spec`).
- **Failure boundary is part of the contract.** A function that "works in the happy path" but corrupts state on error has not satisfied PDD.

## Good Contracts

Observable, consumer-facing, falsifiable:

- "Startup stays in the normal flow when the SOPS secret file is reachable; degrades to env-var-only mode (with a single warning) when it isn't."
- "Cancelling a follow-up removes the scheduled task from `.sf/runtime/tasks.json` and emits one cancellation event."
- "Background task completion emits exactly one user-visible notification."
- "`sf headless --output-format json` writes a single newline-terminated JSON object to stdout — never partial JSON, never multiple objects."

## Weak Contracts

Mechanical, internal-wiring, refactor-fragile — rewrite these:

- `validateCredentials()` should call `fetch()` twice.
- The cache layer should set `Map.size === 3` after warm-up.
- `claim()` should invoke `db.update()` with `where.id === unitId` exactly once.

These can be useful as labelled implementation guards — but they are not the primary contract.

## Failure-Boundary Examples

| Purpose | Failure boundary |
|---|---|
| Native engine handles grep at full speed. | If `forge_engine.node` fails to load, fall back to JS implementation, log degraded mode once, do not crash. |
| Provider lookup returns a model. | If no provider in `allowed_providers` has a working API key, raise `ErrModelUnavailable` with the list of providers tried — never silently route to a disallowed model. |
| Auto-loop dispatches the next eligible unit. | If no unit is eligible AND no unit is waiting on a recoverable blocker, terminate the loop with verdict `idle` — never spin. |

A function without a stated failure boundary either doesn't fail (very rare) or fails in undefined ways (very common).

## When to Skip PDD

Skip this skill for trivial changes:

- Typo fixes.
- Lint/format-only changes.
- Dependency bumps that don't change behaviour.
- Renames that don't cross module boundaries.

For everything else — feature work, bug fixes, refactors that touch behaviour, anything user-visible — PDD takes 5 minutes and prevents 50 minutes of wrong implementation.

## Output

Persist the PDD packet to the unit's artefacts so the next phase (TDD) and the reviewer (`requesting-code-review`) start from the same frame:

```markdown
## PDD — <slice or change name>

- **Purpose**:
- **Consumer**:
- **Contract**:
- **Failure boundary**:
- **Evidence**:
- **Non-goals**:
- **Invariants**:
- **Assumptions**:
```

Save to `.sf/active/{unit-id}/pdd.md` (or inline at the top of the slice plan). When the slice completes, this packet feeds the `Evidence` block of `requesting-code-review`.

## What's new in v2

Three research findings drove the v1 → v2 update:

**Assumptions field (Jackson & Zave, 1997).** "Four Dark Corners of Requirements Engineering" (ACM TOSEM) formalised the world-machine framework: a specification S is only valid relative to domain assumptions A. The Failure Boundary field catches machine-side failures; the new Assumptions field catches world-side failures where the spec itself becomes invalid even when the code is correct. Violated world assumptions are the most expensive failure class because they are invisible to internal tests.

**Safety/liveness split for Invariants (Alpern & Schneider, 1985/1987).** "Defining Liveness" and "Recognizing Safety and Liveness" showed that every property is the intersection of a safety property ("X never happens") and a liveness property ("Y eventually happens"), and that the two require different verification techniques. Conflating them produces proof gaps in code that touches async, queues, timers, or state machines. Pure synchronous code can continue using safety-only invariants.

**Machine-executable Evidence requirement (SWE-bench Pro arXiv:2509.16941, 2025; MetaGPT ICLR 2024; Agentic Rubrics Scale AI 2024).** Empirical benchmarking across agentic coding systems found that prose-only evidence enables self-deception ("I think this is right") while machine-executable evidence enables self-correction loops. SWE-bench Pro identified under-specified evidence as the primary cause of agent failure, not model capability. Evidence criteria are now required to be either machine-executable (named test, queryable metric, runnable command) or explicitly tagged `[MANUAL: reviewer + scenario]`.
