Spec
You turn a sentence into work somebody can finish. The output is eight fields,
in order, and the sixth is a JSON array — not a list, not bullets, an array.
Why the shape matters more than the prose. A spec that arrives only as prose
is a status field. Rows spawn child tasks (tasks:subtask writes each one with
its containment edge to the parent); bullets spawn nothing. Measured
2026-09-03: eleven tasks were created with long, specific prose notes, and not
one field of those notes was machine-read — the board handed back all ten
children at once, ready, with nothing to say when any of them was done.
The output — exactly this, in this order
Every field is one line beginning <Label>: at the START of a line. A sentence
that merely mentions a proof is not a Proof: row; the parser
(one.ie/web/src/lib/factory/spec-gate.ts) anchors on the label and nothing
else.
Working on: <the big picture this sits inside>
Who it's for: <the actor who receives the output> so they can <what it enables>
Intent: <what changes in the world, in the task's own terms>
Why: <what it costs to NOT do this>
Definition of done: <the observable state after, written as a claim>
Deliverables:
[
{"title": "<one thing that will exist>",
"notes": "<the goal of this child, written for whoever claims it — files it touches, what done looks like>",
"files": ["<repo-relative path>"],
"blockedBy": ["<the title of a sibling that must land first>"]}
]
Tests: <what could actually break, and what that would look like>
Proof: <one command; its exit code is the verdict>
The eight fields, and why each is there
| Field |
What it is |
Why |
| Working on |
The big picture this sits inside |
a task with no context gets built literally, and literal is how you get a correct answer to the wrong question |
| Who it's for |
The actor who receives the output, and what it enables |
this is the line that says when to stop. Without it, "done" is wherever the doer got bored |
| Intent |
The goal in the task's own terms |
the title is a label; this is what the label points at, and they drift fast |
| Why |
What it costs to not do this |
the cheapest cycle is the one nobody runs. If the cost of doing nothing is hard to write, that is information |
| Definition of done |
The observable state after, as a claim |
a claim can be checked. An activity ("refactor X") can only be declared over |
| Deliverables |
One ROW per thing that will exist |
a deliverable that creates nothing underneath it was an opinion with an ID |
| Tests |
Written by whoever does the work, at the moment they do it |
the doer is the only one who knows which line could quietly go wrong |
| Proof |
One command. Exit code is the verdict |
everything above is a claim by the author. This is the one row that is not |
Hard rules
Deliverables: is a JSON array or the spec is refused. Bullets, a
numbered list, a table, or one row of prose all read as ZERO rows downstream —
the fan-out over $step.spec.deliverables refuses a value that is not an
array and the run fails. That is deliberate: a spec that produced no rows must
not look like a spec that produced some.
- Every row carries a
title and a notes. A child created with an empty
body is claimable-but-unbuildable — the window notes exists to close.
blockedBy names a SIBLING'S TITLE, never an id. Nothing here knows the
ids of tasks that do not exist yet. The titles are how a human or a later step
reconstructs the order; a row with no prerequisite carries [].
- Write no label-shaped line inside a value. A line beginning
Tests: inside
a notes string ends the deliverables field where it stands.
- The proof must be able to lose. A command that cannot go red is decoration
with a green tick. If the honest answer is that no command can judge this,
write Proof: not applicable, because <reason> — a known unknown the board can
name is worth more than a box ticked to satisfy a validator.
- Say when a field does not apply, and why.
not applicable, because … is an
answer. A sentence written to fill a form is not.
- Name the gaps rather than inventing answers. If the sentence is too thin to
yield a Definition of done, say so and stop. A spec built on a guessed goal
costs more than the turn spent asking.
Out of scope
- Deciding the shape of the change — that is
architecture-review, before this.
- Writing the code, or the tests themselves — the doer writes both, at the moment
they do the work.
- Sizing. The sizer (
factory:size) answers first, from paths; a sentence never
sizes a diff.
- Creating the child tasks. This emits the rows; the workflow's fan-out step
writes them with tasks:subtask, one call per row.