ears-spec-authoring
Author acceptance criteria using EARS-lite shapes with stable AC-NNN IDs and explicit Q-NNN open questions. Use when drafting or editing `01-spec.md`, when transforming a tracker ticket into ACs, or w
SKILL.md.claude/skills/ears-spec-authoring/SKILL.md
GitHubEARS-lite spec authoring
The five shapes
Every AC is one of:
| Shape | Skeleton | Use when |
|---|---|---|
| Ubiquitous | The system shall <response>. |
Always-true invariant. |
| Event-driven | When <trigger>, the system shall <response>. |
An external event causes a behavior. |
| State-driven | While <state>, the system shall <response>. |
Behavior holds throughout a state. |
| Optional feature | Where <feature included>, the system shall <response>. |
Conditional on configuration. |
| Unwanted behavior | If <unwanted condition>, then the system shall <mitigation>. |
Error / failure handling. |
Rules (enforced by spec-review.md)
- One AC = one condition + one outcome. Split anything with "and".
- Stable IDs. Format
AC-NNN. Never renumber. Insertions get the next number, even if it breaks reading order. Write each AC as one bullet- AC-NNN: <EARS sentence>and each open question as- Q-NNN: <question>(seedocs/spec-format.md§ Canonical line format). Hooks andtraceability.shparse this shape. - No implementation. No class names, no DB columns, no library names, no defaults.
- No vague NFRs. "Fast" →
< 200 ms p95 under 50 RPSOR demote toQ-NNN. - No silent defaults. If the user did not say which behavior to pick, write a
Q-NNN. - Feature-flag the risky cutovers. For any cutover, migration, or behavior swap that is user-visible, raise a
Q-NNNasking whether a feature flag with rollback procedure is required. Default to "yes, use a feature flag" unless the user explicitly waives it.
The Q-NNN escape hatch
When you would otherwise invent something, write:
## Open Questions
- Q-001: Source ticket says "users get a discount" but does not specify whether unauthenticated users qualify. Need decision before AC-005 can be written.
Resolve via direct user answer, then move it to ## Resolved Questions with a - Status: resolved <YYYY-MM-DD> — <answer> sub-bullet. A question you explicitly defer may stay under ## Open Questions with - Status: deferred — <rationale>; it no longer blocks later phases.
Worked example
Source ticket:
Users should be able to apply a gift card during checkout. It should reduce the order total. If the card has been used, show an error.
Bad (what to avoid):
AC-001: The system uses a
GiftCardentity with ausedAttimestamp and returns 400 whenusedAt != null.
Why bad: invents a class name, invents a column, invents an HTTP status.
Good:
## Acceptance Criteria
- AC-001: When an authenticated buyer submits a gift card code with their order, the system shall reduce the order total by the card's remaining balance, capped at the order subtotal.
- AC-002: When an authenticated buyer submits a gift card code that has already been fully redeemed, the system shall reject the request and inform the buyer that the card has no remaining balance.
- AC-003: When an authenticated buyer submits a gift card code that does not exist, the system shall reject the request and inform the buyer that the code is unknown.
- AC-004: While a gift card has remaining balance, the system shall allow it to be applied to multiple orders until the balance reaches zero.
## Open Questions
- Q-001: Should unauthenticated/guest checkouts be allowed to use gift cards? Source ticket only mentions "users".
- Q-002: Maximum number of gift cards per order? Default? (No source guidance.)
- Q-003: What user-facing error format does the buyer see? Banner? Inline field? (UI not specified.)
Self-check before handing off to review
- Could a junior tester write a Given/When/Then for every AC without asking me a clarifying question?
- If I removed the AC, would something user-visible change?
- Does any AC contain "and" / "or" that should be two ACs?
- Are all invented details (defaults, error formats, limits) demoted to
Q-NNN?