diff --git a/skills/feature-spec/SKILL.md b/skills/feature-spec/SKILL.md index 09d9fda..29c5628 100644 --- a/skills/feature-spec/SKILL.md +++ b/skills/feature-spec/SKILL.md @@ -32,6 +32,90 @@ A feature spec is the implementation-level contract for one feature or component 2. **An "Out of scope" section is mandatory.** If you do not draw the boundary, the implementer (you, future-you, or another agent) will guess wrong. 3. **Every technical decision in "Technical approach" must have a rationale.** A decision is any choice where a reasonable alternative existed and was not taken. "Use Postgres" is not a decision; "use Postgres because the rest of the stack uses it and we need transactions across user + invoice" is. +## EARS-lite acceptance criteria + +Measurable criteria answer "is it done?". They do not always answer "what should it do +when X happens?" — and behavioural requirements written as prose are where ambiguity +survives review. For any criterion that is conditional or event-driven, use EARS-lite +instead of prose: + +``` +WHEN THE SYSTEM SHALL +WHILE THE SYSTEM SHALL +IF THEN THE SYSTEM SHALL +``` + +There is a fourth, unconditional form. Use it for invariants — the properties that must +hold with no trigger at all, which are usually the ones that matter most: + +``` +THE SYSTEM SHALL +``` + +Worked, deliberately from a domain unlike whatever you are specifying: + +``` +THE SYSTEM SHALL reject a firmware image whose signature does not verify +WHEN a sensor reports a temperature above the shutdown limit + THE SYSTEM SHALL open the contactor within 200ms +WHILE the contactor is open + THE SYSTEM SHALL refuse every start command +IF a sensor reading is missing or stale by more than one interval + THEN THE SYSTEM SHALL treat it as above the limit +``` + +The example is off-domain on purpose. An on-domain example gets copied: a spec written +against an invoice-flavoured example inherited a requirement about extraction confidence +that its feature request never mentioned, and the author only noticed on review. Read +the shapes, not the content. + +Each conditional line names a trigger and one observable response, which usually maps +onto a single test. The `WHILE` and unconditional forms are universally quantified — +"every start command", "a firmware image" — so they map onto a property or a set of +cases, not one assertion. That is still worth writing: it closes the requirements half +of the audit trail, which is otherwise issue-body prose. + +**This is an option, not a replacement.** Threshold criteria ("p99 < 200ms under 100 +RPS") stay as they are — EARS phrasing adds nothing to a number. Use it for conditional +behaviour, error handling, and state-dependent rules. + +## Context snapshot + +The executor of this spec will not see the conversation that produced it, and often +cannot see the architecture document either — a different agent, a fresh session, a +different machine. A spec that says "see the architecture doc" hands them a lookup they +may not be able to perform. + +So inline the specific facts this spec depends on, not the whole document: + +```markdown +## Context snapshot +Taken from @ . Re-check before implementing if stale. + +- The mailbox poller writes rows to `invoices` with `state = 'received'`. +- The payment-file writer reads `invoices` and emits one file per batch. +- Money is int64 minor units plus an ISO-4217 code; float is banned repo-wide. +``` + +Three rules keep this from becoming a second source of truth, which is the real +objection to inlining and a good one: + +1. **Snapshot what the implementer cannot look up and cannot proceed without.** That is + wider than "facts this spec's decisions rest on" — a table name may underpin no + decision here and still be the thing that blocks writing the migration. It is still + narrower than the whole document: if a fact changes nothing and blocks nothing, leave + it out, and say what you left out so the omission reads as deliberate. +2. **Always cite source and version.** A snapshot without provenance cannot be + revalidated, and an unrevalidatable copy is exactly the drift everyone fears. +3. **If you could not read the source either, say so in those words.** Writing a spec + against a document you cannot open is common and is not a reason to skip the section + — it is a reason to mark the whole block `UNVERIFIED — reconstructed, not read`, and + to make confirming it the implementer's first task, before the first failing test. + An unmarked guess is the only genuinely dangerous version. +4. **The snapshot is evidence, not authority.** Open the section with one line to that + effect — "where this disagrees with the source, the source wins and this spec is + stale" — rather than annotating each fact. + ## Spec Template ```markdown @@ -42,12 +126,21 @@ A feature spec is the implementation-level contract for one feature or component What problem does this feature solve? For whom? Why now? (2-4 sentences. If you cannot answer "why now," ask before writing the rest.) +## Context snapshot +[Facts the implementer cannot look up, with source and version. See "Context snapshot" above.] + ## Success Criteria - [ ] Criterion 1 — measurable and verifiable - [ ] Criterion 2 — measurable and verifiable - [ ] Criterion 3 — measurable and verifiable +### Behavioural criteria (EARS-lite) + +[Conditional and event-driven behaviour, one trigger and one observable response per +line. Not checkboxes — these are properties, and several need more than one test each. +Omit the subsection entirely if the feature has no conditional behaviour.] + ## Constraints Non-negotiable requirements the solution must satisfy. diff --git a/skills/spec-driven-dev/SKILL.md b/skills/spec-driven-dev/SKILL.md index 0d8ce57..39e9dba 100644 --- a/skills/spec-driven-dev/SKILL.md +++ b/skills/spec-driven-dev/SKILL.md @@ -200,6 +200,56 @@ Dev: [full command] [Anything unresolved that needs input before implementation begins] ``` +## EARS-lite acceptance criteria + +Measurable criteria answer "is it done?". They do not always answer "what should it do +when X happens?" — and behavioural requirements written as prose are where ambiguity +survives review. For any criterion that is conditional or event-driven, use EARS-lite +instead of prose: + +``` +WHEN THE SYSTEM SHALL +WHILE THE SYSTEM SHALL +IF THEN THE SYSTEM SHALL +``` + +There is a fourth, unconditional form. Use it for invariants — the properties that must +hold with no trigger at all, which are usually the ones that matter most: + +``` +THE SYSTEM SHALL +``` + +Worked, deliberately from a domain unlike whatever you are specifying: + +``` +THE SYSTEM SHALL reject a firmware image whose signature does not verify +WHEN a sensor reports a temperature above the shutdown limit + THE SYSTEM SHALL open the contactor within 200ms +WHILE the contactor is open + THE SYSTEM SHALL refuse every start command +IF a sensor reading is missing or stale by more than one interval + THEN THE SYSTEM SHALL treat it as above the limit +``` + +The example is off-domain on purpose. An on-domain example gets copied: a spec written +against an invoice-flavoured example inherited a requirement about extraction confidence +that its feature request never mentioned, and the author only noticed on review. Read +the shapes, not the content. + +Each conditional line names a trigger and one observable response, which usually maps +onto a single test. The `WHILE` and unconditional forms are universally quantified — +"every start command", "a firmware image" — so they map onto a property or a set of +cases, not one assertion. That is still worth writing: it closes the requirements half +of the audit trail, which is otherwise issue-body prose. + +**This is an option, not a replacement.** Threshold criteria ("p99 < 200ms under 100 +RPS") stay as they are — EARS phrasing adds nothing to a number. Use it for conditional +behaviour, error handling, and state-dependent rules. + +For the executor-facing handoff, `feature-spec` also carries a **Context snapshot** +section — inline the architecture facts the implementer cannot look up. + ## Reframing Vague Requirements When you receive a vague requirement, translate it into specific success criteria before writing any spec content: