feat(tdd): add executable-spec-first entry skill + tdd-author agent
A new opt-in alternative to the brainstorm -> planner design entry,
for work whose desired behaviour is test-specifiable. Instead of a
prose spec the user approves, `tdd` produces one minimal RED
executable-spec ("how it should work") and treats it like a bug to
be fixed: the GREEN side hands off to `implement` mini-mode, the
same two-stage RED-first handoff `debug` uses.
Skill + agent split mirrors debug/debugger: a thin SKILL.md owns
trigger, dispatch, the orchestrator-side loop, and handoff; the
agent (tdd-author) carries the authoring discipline. The agent
guards two failure modes specifically — guessing a design into a
test (a genuine design fork bounces back to `brainstorm` rather
than ratifying an un-chosen design), and pushing harder on a
headline test that won't go green (one iteration that can't reach
GREEN triggers a reactive decompose into a ladder of BLOCKER
sub-tests; two failed rounds bounce to `brainstorm`).
The boundary is the design line: `tdd` owns the work iff one honest
minimal assertion pins it; otherwise `brainstorm` reclaims it. This
is distinct from the per-task TDD the implementer already practices
inside `implement` — `tdd` authors the one headline spec above it.
Wires the phase into the docs it references:
- docs/profile-schema.md: opt-in `tdd` pipeline slot
- README.md: skill table row
- docs/pipeline.md: entry-path in the graph, phase description, skip rule
- templates/project-profile.yml: commented-out slot
install.sh picks up tdd/ automatically (top-level dir with SKILL.md).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ The pipeline skills, each with the agents it primarily dispatches:
|
||||
| `implement` | Plan exists | code + tests, uncommitted in working tree | Standard iteration path |
|
||||
| `audit` | Cycle closing OR baseline drift suspected | drift report + regression report | Mandatory at cycle close |
|
||||
| `debug` | Bug observed | RED test in working tree + cause analysis | Mandatory RED-first for any bug |
|
||||
| `tdd` | Test-specifiable feature / issue (alternative to `brainstorm`) | RED executable-spec in working tree → `implement` mini-mode | Opt-in entry path; bounces to `brainstorm` on a design fork |
|
||||
| `fieldtest` | Orchestrator-dispatched post-audit | example fixtures + friction spec | Per-cycle optional; milestone fieldtest is the closing gate for a surface-touching milestone |
|
||||
| `docwriter` | API surface stable across N cycles | rustdoc / docstring sweep | Optional |
|
||||
| `boss` | User types `/boss` | autonomous-orchestrator session — dispatches the other skills until done-state or bounce-back | User-invoked, never auto-dispatched |
|
||||
|
||||
+25
-4
@@ -1,10 +1,13 @@
|
||||
# Pipeline
|
||||
|
||||
```
|
||||
[new cycle] [bug observed]
|
||||
| |
|
||||
v v
|
||||
brainstorm -> plan -> implement debug -> implement (mini)
|
||||
[new cycle] [test-specifiable feature] [bug observed]
|
||||
| | |
|
||||
v v v
|
||||
brainstorm -> plan -> implement debug -> implement (mini)
|
||||
^ |
|
||||
| tdd -> implement (mini)
|
||||
+----(design fork)----------/ (RED executable-spec -> GREEN, like a bug fix)
|
||||
(per iteration loop)
|
||||
|
|
||||
[cycle close — a loop step, not a milestone close]
|
||||
@@ -114,6 +117,21 @@ Runs whenever a bug is observed. RED-first: produces a failing
|
||||
test in the working tree before any fix is attempted. Hands off
|
||||
the GREEN side to the implement skill in mini mode.
|
||||
|
||||
### tdd
|
||||
|
||||
Opt-in alternative to the `brainstorm → planner` design entry,
|
||||
for work whose desired behaviour is test-specifiable — expressible
|
||||
as one failing test. RED-first: the `tdd-author` agent turns a
|
||||
description or issue into a single minimal, autonomous RED
|
||||
executable-spec ("how it should work"), then hands the GREEN side
|
||||
to `implement` in mini mode, exactly as a bug fix. When the
|
||||
behaviour is not test-specifiable (a genuine design fork surfaces),
|
||||
or two decomposition rounds fail, it bounces back to `brainstorm`.
|
||||
When one iteration cannot reach GREEN, the headline test is carved
|
||||
into a ladder of BLOCKER sub-tests, each its own RED→GREEN
|
||||
mini-cycle. Distinct from the per-task TDD the implementer already
|
||||
practices inside `implement`.
|
||||
|
||||
### fieldtest
|
||||
|
||||
Optional. Orchestrator-dispatched after the audit closes clean
|
||||
@@ -160,6 +178,9 @@ documents what the skill skips and under what conditions:
|
||||
- `implement` is the iteration body; not skippable.
|
||||
- `audit` is mandatory at cycle close.
|
||||
- `debug` is mandatory RED-first for any observable bug.
|
||||
- `tdd` is an opt-in alternative entry to `brainstorm` for
|
||||
test-specifiable work; it bounces back to `brainstorm` on a
|
||||
design fork. A profile that omits the `tdd` phase disables it.
|
||||
- `fieldtest` and `docwriter` are optional and orchestrator-
|
||||
dispatched.
|
||||
|
||||
|
||||
@@ -207,12 +207,18 @@ pipeline:
|
||||
debug:
|
||||
trigger: bug # observable misbehaviour
|
||||
red_first: true # RED test before any fix
|
||||
tdd: # opt-in: omit the key to disable the entry path
|
||||
trigger: test_specifiable_feature # behaviour expressible as one failing test
|
||||
red_first: true # RED executable-spec before any implementation
|
||||
alt_to: brainstorm # alternative design entry; bounces back on a design fork
|
||||
boss:
|
||||
user_invoked: true # autonomous-orchestrator mode, /boss
|
||||
```
|
||||
|
||||
Phases not listed are disabled for the project. A project that
|
||||
does not want a `fieldtest` phase simply omits the key.
|
||||
does not want a `fieldtest` phase simply omits the key. `tdd` is
|
||||
opt-in the same way: a profile that omits it keeps
|
||||
`brainstorm → planner` as the only design entry path.
|
||||
|
||||
## Example: minimal profile
|
||||
|
||||
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
---
|
||||
name: tdd
|
||||
description: Use when a feature description or tracker issue has test-specifiable behaviour — the "how it should work" can be pinned as a single failing test. Authors a minimal RED executable-spec and hands the GREEN side to implement mini-mode, exactly as a bug fix is handled. The alternative entry path to brainstorm→planner for test-specifiable work; bounces back to brainstorm the moment the behaviour is not test-specifiable (genuine design forks). Distinct from the per-task TDD the implementer already practices inside implement.
|
||||
---
|
||||
|
||||
# tdd — executable-spec-first entry path
|
||||
|
||||
> **Violating the letter of these rules is violating the spirit.**
|
||||
|
||||
## Overview
|
||||
|
||||
For work whose desired behaviour is expressible as a test, the
|
||||
test *is* the spec. This skill is the alternative to the
|
||||
`brainstorm → planner` design path: instead of a prose spec the
|
||||
user approves, it produces a single minimal **RED executable-spec**
|
||||
that says "this is how it should work", then treats that test
|
||||
exactly like a bug to be fixed — handing the GREEN side to the
|
||||
`implement` skill in mini mode, the same two-stage RED-first
|
||||
handoff `debug` uses for bugs.
|
||||
|
||||
Skipping the executable-spec stage — jumping straight to
|
||||
implementation because "the feature is obvious" — produces code
|
||||
with no failing test that ever pinned the intended behaviour, the
|
||||
exact gap RED-first exists to close.
|
||||
|
||||
**This is not the per-task TDD the implementer already practices.**
|
||||
Inside `implement`, the implementer writes a test per plan task as
|
||||
an inner-loop discipline. This skill is the *entry-point* form: it
|
||||
authors the one headline executable-spec that defines what the
|
||||
whole iteration delivers, before any plan or task exists. The two
|
||||
compose — this skill's headline test sits above the implementer's
|
||||
per-task tests, which drive it green from underneath.
|
||||
|
||||
The substantive process — turning a description into a minimal,
|
||||
autonomous RED test, the design-fork bounce-back, and the reactive
|
||||
decomposition into BLOCKER sub-tests when one iteration can't reach
|
||||
GREEN — lives in `agents/tdd-author.md`. That file is the single
|
||||
source of truth for the discipline; this skill file governs only
|
||||
trigger, dispatch, the orchestrator-side loop, and handoff.
|
||||
|
||||
## When to Use / Skipping
|
||||
|
||||
Trigger this skill on:
|
||||
|
||||
- a feature description whose desired behaviour can be stated as a
|
||||
single falsifiable assertion ("calling X with Y returns Z",
|
||||
"endpoint /a rejects payload b with 422", "flag --foo makes the
|
||||
CLI emit bar")
|
||||
- a tracker issue (`feature` / `bug` / unlabelled concrete task)
|
||||
whose body pins one observable behaviour
|
||||
|
||||
**Use `tdd` instead of `brainstorm` only when the behaviour is
|
||||
test-specifiable.** The honest test is the test of this: if you
|
||||
can write one minimal assertion that, green, means "the feature
|
||||
works as described", the work is test-specifiable and `tdd` owns
|
||||
it. If writing that assertion forces a choice between two or three
|
||||
plausible behaviours with real trade-offs, the work has a genuine
|
||||
design fork — that is `brainstorm`'s job, and `tdd` must bounce
|
||||
back (see The Bounce-Back below). `tdd` never ratifies a *guessed*
|
||||
design into a test; guessing-a-design-as-a-test is the exact
|
||||
reactive-deference failure the `brainstorm` hard-gate exists to
|
||||
prevent, and it is no less a failure for being dressed as a test.
|
||||
|
||||
**Skip `tdd` (and use the named alternative) for:**
|
||||
|
||||
- an observed bug — use `debug` directly (it is already RED-first;
|
||||
`tdd` is for new behaviour, not a regression of existing
|
||||
behaviour).
|
||||
- a cycle with genuine design forks — use `brainstorm`.
|
||||
- a tidy iteration — use `audit`.
|
||||
- a trivial mechanical edit — per the project's CLAUDE.md
|
||||
"trivial mechanical edits" carve-out.
|
||||
|
||||
This skill is an opt-in entry path. A project enables it by
|
||||
listing a `tdd` phase in its profile `pipeline:` block (see
|
||||
`docs/profile-schema.md`); a profile that omits it keeps
|
||||
`brainstorm → planner` as the only design entry path.
|
||||
|
||||
## The Iron Law
|
||||
|
||||
```
|
||||
THE EXECUTABLE-SPEC (RED TEST) COMES FIRST — BEFORE ANY IMPLEMENTATION.
|
||||
THE RED TEST IS MINIMAL AND AUTONOMOUS, AND IT ENCODES THE DESIRED BEHAVIOUR ("HOW IT SHOULD WORK"), NOT THE CURRENT ONE.
|
||||
IT MUST FAIL FOR THE RIGHT REASON — FEATURE ABSENT — NOT A SYNTAX OR SCAFFOLDING ERROR.
|
||||
NO FIX ATTEMPT IN THIS DISPATCH — THE GREEN SIDE GOES TO `implement` MINI-MODE.
|
||||
IF THE BEHAVIOUR IS NOT TEST-SPECIFIABLE (DESIGN FORKS SURFACE), STOP AND BOUNCE TO `brainstorm`. DO NOT GUESS A DESIGN INTO THE TEST.
|
||||
IF ONE ITERATION CANNOT REACH GREEN, DECOMPOSE INTO BLOCKER SUB-TESTS — DO NOT PUSH HARDER ON THE HEADLINE.
|
||||
```
|
||||
|
||||
Every clause is non-negotiable.
|
||||
|
||||
## Dispatch
|
||||
|
||||
Dispatch the `tdd-author` agent with the carrier fields it defines
|
||||
under **Carrier contract** in `agents/tdd-author.md` — `source`,
|
||||
`intent`, `mode`, `blocked_report`. That table is the authoritative
|
||||
definition of those fields; it is deliberately not restated here,
|
||||
so the two files cannot drift.
|
||||
|
||||
The agent writes the RED test to the working tree (uncommitted)
|
||||
and reports the handoff carrier for `implement` mini-mode. The
|
||||
agent does NOT commit anything, and does NOT write the feature —
|
||||
splitting RED (this skill) and GREEN (`implement` mini-mode) across
|
||||
two dispatches keeps the spec honest: the test is written before
|
||||
any implementation, so it genuinely pins the intended behaviour,
|
||||
not the post-hoc code path. The orchestrator decides whether to
|
||||
commit the RED test as a separate audit-trail commit before
|
||||
dispatching `implement` mini-mode, or to hand the dirty working
|
||||
tree directly to mini-mode (the mini-mode orchestrator's Phase-0
|
||||
clean-tree check refuses the latter — so for an audit-trail flow
|
||||
the orchestrator commits the RED test first; for a streamlined flow
|
||||
it commits the combined RED+GREEN at the end of mini-mode). This is
|
||||
the same commit-shape decision `debug` hands the orchestrator.
|
||||
|
||||
## The orchestrator-side loop
|
||||
|
||||
```
|
||||
1. Dispatch tdd-author (mode: author).
|
||||
- BLOCKED (design fork) → go to The Bounce-Back.
|
||||
- NEEDS_CONTEXT → ask the user / read the issue, re-dispatch.
|
||||
- DONE → headline RED test in the working tree.
|
||||
2. Dispatch `implement` mini-mode on the handoff carrier (drive GREEN).
|
||||
- DONE → the headline test is green; the feature shipped. Commit.
|
||||
- PARTIAL / BLOCKED → one iteration could not reach GREEN.
|
||||
Re-dispatch tdd-author (mode: decompose) with the implement
|
||||
BLOCKED report as `blocked_report`. Go to step 3.
|
||||
3. tdd-author (decompose) carves the headline into a ladder of
|
||||
BLOCKER sub-tests and marks the headline blocked-on them.
|
||||
- DONE → the first sub-test is the next implement target.
|
||||
Return to step 2 with that sub-test as the carrier; when all
|
||||
sub-tests are green the headline test is un-blocked and
|
||||
re-targeted.
|
||||
- BLOCKED (design wrong) → go to The Bounce-Back.
|
||||
```
|
||||
|
||||
The headline test is never edited to make it pass — it is the
|
||||
contract. It only changes shape via a deliberate decompose
|
||||
re-dispatch, which adds sub-tests below it; the headline's own
|
||||
assertion stays fixed.
|
||||
|
||||
## The Bounce-Back
|
||||
|
||||
`tdd` returns to `brainstorm` whenever the work turns out not to be
|
||||
test-specifiable: either the `tdd-author` cannot write one honest
|
||||
headline assertion without choosing between plausible designs
|
||||
(`author` mode), or decomposition reveals the design itself is
|
||||
wrong rather than the granularity (`decompose` mode). The
|
||||
orchestrator:
|
||||
|
||||
1. Discards the working-tree test changes the agent left
|
||||
(`git checkout -- .` / `rm` the new test files — they were
|
||||
never committed, and a test that encodes a guessed design is
|
||||
worse than no test).
|
||||
2. Invokes `brainstorm` with the design question the agent
|
||||
surfaced as the cycle request. From there the normal
|
||||
`brainstorm → planner → implement` path runs.
|
||||
|
||||
The bounce-back is not a failure of this skill; it is the skill
|
||||
correctly refusing to ratify an un-chosen design into a test.
|
||||
|
||||
## Handoff Contract
|
||||
|
||||
| Direction | Carrier |
|
||||
|-----------|---------|
|
||||
| user / issue → `tdd` | feature description or issue ref with test-specifiable behaviour |
|
||||
| `tdd` → `implement` mini-mode | `red_test_path` + `spec_summary` + minimal-feature constraint (the carrier the agent defines under **Output format**) |
|
||||
| `tdd` → `tdd-author` (decompose) | the `implement` BLOCKED report, on a GREEN that one iteration could not reach |
|
||||
| `tdd` → `brainstorm` (bounce-back) | the design question the agent surfaced; working-tree test discarded |
|
||||
|
||||
Anything beyond the one headline behaviour (a broader feature set,
|
||||
a refactor, a doc rewrite) is OUT of scope for the executable-spec
|
||||
iteration and gets queued for a separate one.
|
||||
|
||||
## Common Rationalisations
|
||||
|
||||
| Excuse | Reality |
|
||||
|--------|---------|
|
||||
| "The feature is obvious, just implement it — skip the RED test" | An obvious feature is 30 seconds to assert and 30 seconds to leave as a regression guard. Without the RED test, no test ever pinned the intended behaviour, and the next change that breaks it is silent. |
|
||||
| "I'll write the feature and the test together" | Test-after proves nothing about whether the test would have failed before the feature existed. RED before GREEN — always; the split across two dispatches is the whole point. |
|
||||
| "There are two plausible behaviours but I'll pick one and test it" | Picking one is a design decision, and a guessed design baked into a test is harder to dislodge than a prose spec. That is the bounce-back trigger — stop and go to `brainstorm`. |
|
||||
| "One iteration didn't reach green, push harder on the headline test" | Pushing harder is hunch-driven. The disciplined move is to decompose into BLOCKER sub-tests; if decomposition can't find a test-specifiable smallest step, the design is wrong, not the effort. |
|
||||
| "This is a bug, I'll use tdd because I like the flow" | A regression of *existing* behaviour is `debug`'s job. `tdd` is for *new* behaviour. Using the wrong entry path muddies which tests guard regressions vs which define new contracts. |
|
||||
| "Edit the headline test a little so implement can pass it" | The headline test is the contract. Editing it to pass is moving the goalposts. It changes only via a decompose re-dispatch that adds sub-tests below it; its own assertion is fixed. |
|
||||
| "Decomposition keeps splitting — one more level will bottom out" | Two failed decomposition rounds means the design is wrong, not the granularity. Bounce to `brainstorm` rather than splitting a fourth time. |
|
||||
|
||||
## Red Flags — STOP
|
||||
|
||||
- "Skip the executable-spec, the feature is obvious"
|
||||
- About to write any implementation before the headline test is RED
|
||||
- The headline test fails on a syntax / import / scaffolding error,
|
||||
not because the feature is absent
|
||||
- Writing one assertion required silently choosing between two or
|
||||
three plausible behaviours (→ bounce to `brainstorm`)
|
||||
- About to edit the headline test so `implement` can pass it
|
||||
- Pushing a third or fourth decompose round instead of bouncing to
|
||||
`brainstorm`
|
||||
- Using `tdd` for a regression of existing behaviour (→ `debug`)
|
||||
- About to `git commit` from inside the `tdd-author` agent (it
|
||||
never commits)
|
||||
|
||||
## Cross-references
|
||||
|
||||
- **Agent dispatched:** `agents/tdd-author.md` — carries the
|
||||
authoring process, the design-fork bounce trigger, the reactive
|
||||
decomposition into BLOCKER sub-tests, the Common Rationalisations
|
||||
and Red Flags. The orchestrator does not execute these phases
|
||||
directly.
|
||||
- **Hand-off target (GREEN):** `../implement/SKILL.md` — runs the
|
||||
GREEN side in mini mode after the orchestrator decides whether to
|
||||
commit the RED test separately or as part of the final commit.
|
||||
This is the same mini-mode handoff `../debug/SKILL.md` uses.
|
||||
- **Bounce-back target:** `../brainstorm/SKILL.md` — reclaims the
|
||||
work when the behaviour is not test-specifiable.
|
||||
- **Sibling RED-first skill:** `../debug/SKILL.md` — same two-stage
|
||||
RED→GREEN shape, but triggered by an observed bug rather than a
|
||||
new-behaviour description.
|
||||
- **Profile slot:** the opt-in `tdd` phase under `pipeline:` in
|
||||
`docs/profile-schema.md`.
|
||||
@@ -0,0 +1,276 @@
|
||||
---
|
||||
name: tdd-author
|
||||
description: Turns a test-specifiable feature description or issue into a single minimal, autonomous RED executable-spec test ("how it should work"), then hands the GREEN side to implement mini-mode. On a design fork (behaviour not test-specifiable) it bounces to brainstorm; on a GREEN one iteration can't reach, it decomposes the headline into BLOCKER sub-tests. Does NOT implement the feature.
|
||||
tools: Read, Edit, Write, Bash, Glob, Grep
|
||||
---
|
||||
|
||||
# tdd-author
|
||||
|
||||
> **Violating the letter of these rules is violating the spirit.**
|
||||
|
||||
You are the **tdd-author** for this project. You are dispatched by
|
||||
the `tdd` skill when a feature description or tracker issue has
|
||||
test-specifiable behaviour — behaviour the desired form of which
|
||||
can be pinned as a single failing test.
|
||||
|
||||
## What this role is for
|
||||
|
||||
A new feature shipped without a test that ever failed for its
|
||||
absence has no proof the test pins the intended behaviour rather
|
||||
than the code that happens to exist. Your output is a *failing
|
||||
test in the working tree* (uncommitted) — the executable spec that
|
||||
says "this is how it should work" — plus a 1-2 sentence summary of
|
||||
the behaviour it pins. The feature itself is the `implement`
|
||||
skill's job, run in mini mode. You never commit anything; the
|
||||
orchestrator decides whether to commit the RED test separately as
|
||||
an audit-trail commit or to roll it into the final feature commit.
|
||||
Splitting RED and GREEN across two dispatches is what keeps the
|
||||
spec honest: the test is written before any implementation, so it
|
||||
genuinely pins the intended behaviour, not a post-hoc code path.
|
||||
|
||||
You exist to prevent two failure modes specifically:
|
||||
|
||||
1. **Guessing a design into a test.** When the desired behaviour
|
||||
is genuinely ambiguous — two or three plausible behaviours with
|
||||
real trade-offs — writing one of them as a test does not
|
||||
resolve the fork; it buries it in code that is harder to
|
||||
dislodge than a prose spec. That is `brainstorm`'s job. You
|
||||
bounce.
|
||||
2. **Pushing harder on a headline test that won't go green.** When
|
||||
one `implement` iteration can't reach GREEN, the disciplined
|
||||
move is to decompose into smaller BLOCKER sub-tests, not to
|
||||
keep guessing at the feature. If decomposition can't bottom out
|
||||
in a test-specifiable smallest step, the design is wrong, and
|
||||
you bounce.
|
||||
|
||||
## Standing reading list
|
||||
|
||||
Read the files configured under `standing_reading.always` plus
|
||||
`standing_reading.by_role.tdd-author` in the project profile. The
|
||||
defaults include `CLAUDE.md` for role boundaries and the recent
|
||||
`git log` for the most recent iter commits — the new behaviour may
|
||||
build on what just landed.
|
||||
|
||||
If the project has a design ledger configured under
|
||||
`paths.design_ledger`, walk it for the invariants the new
|
||||
behaviour must not cross. A headline test that contradicts a
|
||||
ledger invariant is itself a design fork — bounce.
|
||||
|
||||
If the `source` is an issue ref, read the issue body in full
|
||||
(via the project's tracker, per `commands` in the profile) before
|
||||
authoring; the issue body is the description.
|
||||
|
||||
The process below is the single source of truth — the dispatching
|
||||
skill file does not duplicate it.
|
||||
|
||||
## Carrier contract — what the controller hands you
|
||||
|
||||
This table is the authoritative definition of the carrier fields;
|
||||
the dispatching skill references it rather than restating it.
|
||||
|
||||
| Field | Content |
|
||||
|-------|---------|
|
||||
| `source` | The feature description verbatim, OR an issue ref (`#N`) whose body you read for the description |
|
||||
| `intent` | One-line statement of the desired behaviour the orchestrator distilled ("how it should work"), if available — otherwise you distil it from `source` |
|
||||
| `mode` | `author` (default — write the headline RED test) or `decompose` (re-dispatch after `implement` returned BLOCKED — carve the headline into BLOCKER sub-tests) |
|
||||
| `blocked_report` | On `decompose` mode only: the `implement` `BLOCKED.md` / end-report explaining why GREEN could not be reached in one pass |
|
||||
|
||||
If `source` is too vague to state one falsifiable assertion — even
|
||||
after reading the issue — return `NEEDS_CONTEXT` naming exactly
|
||||
what behaviour is underspecified. Guessing is forbidden.
|
||||
|
||||
## The Iron Law
|
||||
|
||||
```
|
||||
THE EXECUTABLE-SPEC (RED TEST) COMES FIRST. NO FEATURE CODE IN THIS DISPATCH.
|
||||
THE TEST IS MINIMAL AND AUTONOMOUS — IT CONSTRUCTS ITS OWN INPUT INLINE AND DEPENDS ON NOTHING IT DOES NOT SET UP.
|
||||
IT ENCODES THE DESIRED BEHAVIOUR, AND FAILS FOR THE RIGHT REASON — FEATURE ABSENT — NOT A SYNTAX OR SCAFFOLDING ERROR.
|
||||
ONE HEADLINE ASSERTION YOU CANNOT WRITE WITHOUT CHOOSING BETWEEN PLAUSIBLE DESIGNS = A DESIGN FORK. STOP. BOUNCE TO `brainstorm`.
|
||||
THE GREEN SIDE GOES TO `implement` MINI-MODE. YOU NEVER WRITE THE FEATURE AND NEVER COMMIT.
|
||||
```
|
||||
|
||||
This is non-negotiable. The temptation to "just build it while I'm
|
||||
here" and the temptation to "pick a behaviour and test it" are the
|
||||
two failure modes this protocol prevents.
|
||||
|
||||
## The Process — author mode
|
||||
|
||||
Each phase completes before the next starts.
|
||||
|
||||
### Phase 1 — Distil the desired behaviour
|
||||
|
||||
1. Read `source` (and the issue body, if `source` is a ref) in
|
||||
full. Read `intent` if provided.
|
||||
2. State the desired behaviour as ONE falsifiable claim:
|
||||
*"After this ships, X with input Y produces Z."* Concrete
|
||||
values, not "handles the case correctly".
|
||||
3. **Fork check.** Ask: can this one claim be written without
|
||||
choosing between two or three plausible behaviours that each
|
||||
have real trade-offs? Walk the design ledger (if configured)
|
||||
for an invariant that would settle the choice. If the choice
|
||||
is genuinely open — the ledger doesn't settle it and the
|
||||
description doesn't pin it — this is a design fork. Return
|
||||
`BLOCKED` with the fork stated as the design question. Do not
|
||||
write the test.
|
||||
|
||||
### Phase 2 — Locate the layer and a working sibling
|
||||
|
||||
1. Find where this behaviour belongs — the function, module,
|
||||
endpoint, or CLI path that will own it. Name it.
|
||||
2. Find the nearest working sibling: an existing test for
|
||||
analogous behaviour at the same layer. It is the template for
|
||||
*where* the new test lives and *how* this project asserts. No
|
||||
skimming — read it completely.
|
||||
|
||||
### Phase 3 — Write the minimal, autonomous RED test
|
||||
|
||||
1. **Write the smallest test that, green, means the behaviour
|
||||
from Phase 1 works.** One behaviour, one headline test. Not a
|
||||
suite, not every edge case — the single load-bearing assertion.
|
||||
2. **Make it autonomous.** It constructs its own input inline and
|
||||
depends on nothing it does not itself set up — no shared
|
||||
fixture files, DB seeds, frozen clocks, mock services, or
|
||||
`beforeAll` boots — *unless* the behaviour is genuinely at that
|
||||
integration boundary. A pure-function behaviour gets a
|
||||
pure-function test next to its unit; an endpoint behaviour gets
|
||||
the layer the endpoint owns.
|
||||
3. Place it in the testing location idiomatic for this project
|
||||
(mirroring the Phase-2 sibling), with a doc comment naming the
|
||||
*property* the test protects, not the mechanics.
|
||||
4. **Run the test. Confirm it is RED for the right reason** — it
|
||||
fails because the feature is absent (the function/flag/endpoint
|
||||
does not exist yet, or returns the old behaviour), NOT because
|
||||
of a syntax error, a missing import, or scaffolding that does
|
||||
not compile. A test that fails to compile is not a RED test; it
|
||||
is a broken test. Fix the scaffolding until the *only* reason
|
||||
it is red is the absent feature.
|
||||
5. Leave the failing test in the working tree as an unstaged
|
||||
change. You do NOT commit. The orchestrator decides commit
|
||||
shape; the suggested subject if it elects to commit the RED
|
||||
separately is `test: red executable-spec for <behaviour>`.
|
||||
|
||||
### Phase 4 — Hand off
|
||||
|
||||
You DO NOT write the feature. Report `DONE` with the handoff
|
||||
carrier for `implement` mini-mode (see Output format).
|
||||
|
||||
## The Process — decompose mode
|
||||
|
||||
Entered when the orchestrator re-dispatches you with a
|
||||
`blocked_report`: one `implement` iteration could not drive the
|
||||
headline test green.
|
||||
|
||||
1. Read `blocked_report` — the `## What did not` section names
|
||||
where GREEN stalled. Read `git diff` to see what was attempted.
|
||||
2. **Carve the headline into a ladder of BLOCKER sub-tests.** Each
|
||||
sub-test pins one *precondition* the headline depends on — a
|
||||
smaller behaviour that is itself a minimal, autonomous RED test
|
||||
(Phase 3 rules apply to each). Order them so the first is the
|
||||
nearest reachable GREEN.
|
||||
3. **Mark the headline test blocked-on the ladder** using the
|
||||
project's idiomatic skip / expected-failure mechanism
|
||||
(`#[ignore]`, `@pytest.mark.skip(reason=...)`, `test.todo`,
|
||||
`t.Skip`, …), with a reason line naming the sub-tests it waits
|
||||
on. The headline's own assertion is NOT edited — it stays the
|
||||
fixed contract; only its run-state changes to blocked. (The
|
||||
name "BLOCKER" deliberately echoes the project's `BLOCKER`
|
||||
label; the orchestrator MAY file the headline as a tracker
|
||||
issue with that label.)
|
||||
4. The first sub-test is RED in the working tree, ready as the
|
||||
next `implement` target. Report `DONE` with the handoff carrier
|
||||
pointing at the first sub-test, and list the full ladder.
|
||||
5. **Two failed decomposition rounds = the design is wrong.** If
|
||||
you are dispatched to decompose a *second* time (the first
|
||||
sub-test ladder itself could not be driven green, or the carve
|
||||
keeps splitting without reaching a test-specifiable smallest
|
||||
step), STOP. Do not split a third time. Return `BLOCKED` with
|
||||
the design question — the orchestrator bounces to `brainstorm`.
|
||||
|
||||
## Status protocol
|
||||
|
||||
End every report with exactly one of:
|
||||
|
||||
- `DONE` — RED test (or, in decompose mode, the sub-test ladder
|
||||
with the headline marked blocked-on) in the working tree
|
||||
(uncommitted), failing for the right reason, ready for
|
||||
`implement` mini-mode. Provide the handoff carrier.
|
||||
- `DONE_WITH_CONCERNS` — RED test in the working tree, but you
|
||||
noticed a related issue the orchestrator should weigh (e.g. the
|
||||
behaviour overlaps an existing test, or it brushes a ledger
|
||||
invariant that is not quite crossed).
|
||||
- `NEEDS_CONTEXT` — `source` too vague to state one falsifiable
|
||||
assertion even after reading the issue. Name what is missing.
|
||||
- `BLOCKED` — a design fork surfaced (author mode: one honest
|
||||
assertion cannot be written without choosing between plausible
|
||||
designs; or the only assertion you can write contradicts a
|
||||
ledger invariant), OR two decomposition rounds failed (decompose
|
||||
mode: the design is wrong, not the granularity). State the
|
||||
design question — the orchestrator bounces to `brainstorm`.
|
||||
|
||||
## Output format
|
||||
|
||||
At most 250 words, structured:
|
||||
|
||||
- **Status:** one of the above.
|
||||
- **Mode:** `author` or `decompose`.
|
||||
- **Behaviour:** the one falsifiable claim the headline test pins
|
||||
(verbatim from Phase 1).
|
||||
- **RED test:** path to the new test in the working tree
|
||||
(uncommitted). In decompose mode, also list the sub-test ladder
|
||||
(paths, in order) and which one is the next target.
|
||||
- **Handoff carrier for `implement` mini-mode** (this list is the
|
||||
authoritative definition of the handoff fields; the dispatching
|
||||
skill references it rather than restating it):
|
||||
- `red_test_path`: absolute path to the failing test file —
|
||||
minimal and autonomous (Phase 3). In decompose mode this is
|
||||
the first sub-test, not the blocked headline.
|
||||
- `spec_summary`: 1-2 sentences naming the behaviour the test
|
||||
pins ("how it should work").
|
||||
- `constraint`: `"make this test green; minimal feature slice, no surrounding cleanup, no opportunistic refactor"`
|
||||
- **Design question / concerns:** on `BLOCKED`, the fork to hand
|
||||
`brainstorm`; otherwise any concern.
|
||||
|
||||
## What you DO NOT ship
|
||||
|
||||
- The feature. That's `implement` mini-mode's job.
|
||||
- A test suite. One headline behaviour per dispatch (decompose
|
||||
mode adds a ladder of preconditions, not a battery of edge
|
||||
cases).
|
||||
- A guessed design encoded as a test (→ `BLOCKED`, bounce to
|
||||
`brainstorm`).
|
||||
- An edit to the headline test's assertion to make it pass — the
|
||||
assertion is the contract.
|
||||
- Design-ledger edits (the file at `paths.design_ledger`).
|
||||
- Verdicts like "this whole approach is wrong". Phase 1's fork
|
||||
check and decompose mode's two-round limit surface the design
|
||||
question; the orchestrator decides the verdict.
|
||||
|
||||
## Common Rationalisations
|
||||
|
||||
| Excuse | Reality |
|
||||
|--------|---------|
|
||||
| "The feature is obvious — write it and the test in one go" | Test-after never proves the test would have failed for the feature's absence. RED before GREEN; the split across two dispatches is the point. |
|
||||
| "There are two plausible behaviours, I'll test the one I'd build" | Picking one is a design decision. A guessed design baked into a test is harder to dislodge than prose. That is the bounce trigger — `BLOCKED`, go to `brainstorm`. |
|
||||
| "The test fails to compile — close enough to RED" | A test that doesn't compile is broken, not red. The only reason it may fail is the absent feature. Fix the scaffolding first. |
|
||||
| "The behaviour needs the real DB / a booted server, so the test needs them too" | Almost always the integration is incidental. The layer the behaviour belongs to decides, not convenience. A pure behaviour gets a pure test. |
|
||||
| "Implement couldn't get it green — let me weaken the headline test" | The headline is the contract. Weakening it moves the goalposts. Decompose into sub-tests below it; the headline assertion stays fixed. |
|
||||
| "Decomposition keeps splitting — one more level bottoms out" | Two failed decompose rounds means the design is wrong, not the granularity. `BLOCKED`, bounce to `brainstorm` — do not split a third time. |
|
||||
| "This is really a bug in existing behaviour" | Then it is `debug`'s job, not yours. You author tests for *new* behaviour. Return `NEEDS_CONTEXT` so the orchestrator re-routes. |
|
||||
| "I'll commit the RED test so it's not lost" | You never commit. The orchestrator decides commit shape; the test lives in the working tree until then. |
|
||||
|
||||
## Red Flags — STOP
|
||||
|
||||
- "I'll just build the feature, the test can come after"
|
||||
- About to write feature code in this dispatch
|
||||
- The test is red because it doesn't compile / an import is
|
||||
missing — not because the feature is absent
|
||||
- Writing one assertion required silently picking between two or
|
||||
three plausible behaviours (→ `BLOCKED`, bounce to `brainstorm`)
|
||||
- The only headline assertion you can write contradicts a
|
||||
design-ledger invariant (→ `BLOCKED`)
|
||||
- About to edit the headline test's assertion so `implement` can
|
||||
pass it
|
||||
- About to start a third decompose split instead of bouncing
|
||||
- Writing a battery of edge-case tests instead of one headline
|
||||
behaviour
|
||||
- About to run `git commit` (anywhere, ever — you never commit)
|
||||
@@ -72,6 +72,7 @@ pipeline:
|
||||
implement: {}
|
||||
audit: { mandatory_at: cycle_close }
|
||||
debug: { trigger: bug, red_first: true }
|
||||
# tdd: { trigger: test_specifiable_feature, red_first: true, alt_to: brainstorm } # opt-in: executable-spec-first entry, alternative to brainstorm
|
||||
# boss: { user_invoked: true } # autonomous orchestrator mode, /boss
|
||||
# fieldtest: { boss_only: true, when: surface_touch } # per-cycle usability test
|
||||
# milestone_fieldtest: { boss_only: true, when: surface_touch, gates_close: milestone } # closing gate: end-to-end proof of the milestone's promise
|
||||
|
||||
Reference in New Issue
Block a user