# Spec Writing

> Use when you need to define precise behavior, boundaries, and testable success criteria for a feature before writing implementation plans

- Skill: `jsarif/spec-writing` (Agent Skill)
- Install (CLI): `npx skillmds@latest add jsarif/spec-writing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jsarif/spec-writing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: JSarif (https://skillmd.com/u/jsarif)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/jsarif/spec-writing

---


# Spec Writing

## Overview

Turn a design or feature request into a **behavioral specification** -- what the feature does, what it doesn't, how it fails, and how you know it's done. The spec defines the *contract* before anyone writes code or an implementation plan.

**Core principle:** Wrong details in the spec cascade into bad implementation. A vague spec produces vague code. The spec is the highest-leverage document because it defines "done."

**Announce at start:** "I'm using the spec-writing skill to define the behavioral spec."

## When to Use

- After brainstorming, when you have an approved design but no precise behavioral definition
- When a feature request needs boundaries and success criteria before implementation planning
- When requirements are ambiguous and you need to surface decisions

**Don't use for:**
- Changing a button color or config value (just do it)
- Pure research or exploration (that's the researching skill)
- Implementation steps and code (that's writing-plans)

## The Spec Is Not an Implementation Plan

<HARD-GATE>
A spec defines WHAT the feature does. It does NOT define HOW to build it.

If you find yourself writing React hooks, SQL queries, data model schemas, localStorage strategies, or code snippets -- STOP. You've crossed from spec into implementation. Pull back to behavior.

**The test:** Could a team rewrite the entire tech stack and still use this spec unchanged? If not, you've leaked implementation into the spec.

Acceptable: "Search results return within 500ms for datasets under 10k rows."
Not acceptable: "Use a GIN trigram index on the artists.name column."

Acceptable: "User's theme preference persists across browser sessions."
Not acceptable: "Store preference in localStorage under key 'theme-preference'."
</HARD-GATE>

## Process

```dot
digraph spec_writing {
    "Input: design or feature request" [shape=doublecircle];
    "Define behavior from user perspective" [shape=box];
    "Draw non-goals boundary" [shape=box];
    "Define constraints" [shape=box];
    "Specify error and edge case behavior" [shape=box];
    "Write testable success criteria" [shape=box];
    "Every criterion verifiable?" [shape=diamond];
    "Rewrite vague criteria" [shape=box];
    "Flag open questions" [shape=box];
    "Present to human for review" [shape=box];
    "Human approves?" [shape=diamond];
    "Revise spec" [shape=box];
    "Handoff to writing-plans" [shape=doublecircle];

    "Input: design or feature request" -> "Define behavior from user perspective";
    "Define behavior from user perspective" -> "Draw non-goals boundary";
    "Draw non-goals boundary" -> "Define constraints";
    "Define constraints" -> "Specify error and edge case behavior";
    "Specify error and edge case behavior" -> "Write testable success criteria";
    "Write testable success criteria" -> "Every criterion verifiable?";
    "Every criterion verifiable?" -> "Rewrite vague criteria" [label="no"];
    "Rewrite vague criteria" -> "Every criterion verifiable?";
    "Every criterion verifiable?" -> "Flag open questions" [label="yes"];
    "Flag open questions" -> "Present to human for review";
    "Present to human for review" -> "Human approves?";
    "Human approves?" -> "Handoff to writing-plans" [label="yes"];
    "Human approves?" -> "Revise spec" [label="no"];
    "Revise spec" -> "Present to human for review";
}
```

### 1. Define behavior from the user's perspective

Describe what the user sees and does. Use concrete scenarios, not abstract descriptions.

Bad: "Users can search for events."
Good: "User enters an artist name. Results show matching upcoming events sorted by date. Each result displays: artist name, venue, date, and a link to tickets."

### 2. Draw the non-goals boundary

Non-goals are not an afterthought. They are the primary defense against scope creep. For every behavior you define, ask: "What's the adjacent thing someone might assume is included?"

Bad non-goals: "Full-text search is out of scope."
Good non-goals: "Full-text relevance ranking -- results are filtered by substring match, not ranked by relevance. Geolocation ('near me') -- not included; users search by venue name. Saved searches -- users cannot save or subscribe to search queries."

### 3. Define constraints

Every feature has limits. Name them.

- **Performance:** Response time targets, dataset size assumptions
- **Capacity:** Maximum items, rate limits, storage bounds
- **Compatibility:** Browser support, device requirements
- **Data:** Freshness guarantees, accuracy expectations

### 4. Specify error and edge case behavior

For each behavior, ask: "What happens when this goes wrong?"

- Empty states (no results, no data yet)
- Invalid input (bad dates, empty queries, XSS attempts)
- System failures (API down, database timeout, partial data)
- Boundary conditions (first use, maximum capacity, concurrent access)

### 5. Write testable success criteria

<HARD-GATE>
Every success criterion must be **concretely verifiable**. A tester with no context should be able to read the criterion and determine pass/fail unambiguously.

**The litmus test:** Can you write a test case (manual or automated) directly from this criterion? If the answer is "it depends" or "you'd need to use judgment," the criterion is too vague.

Bad: "Search works correctly."
Bad: "Sensible empty state."
Bad: "No critical text becomes unreadable."

Good: "Searching for 'nirvana' returns only events where the artist name contains 'nirvana' (case-insensitive)."
Good: "When search returns 0 results, the page displays 'No upcoming shows match your search' with a link to clear filters."
Good: "In dark mode, all text meets WCAG AA contrast ratio (4.5:1 for body text, 3:1 for large text)."
</HARD-GATE>

### 6. Flag open questions

Separate what you **decided** from what you **assumed**. Assumptions that need human verification go in an explicit "Open Questions" section -- not buried in the body as "locked" decisions.

Bad: "Authentication: email/password (locked for MVP)."
Good: "OPEN: Authentication method -- this spec assumes email/password, but magic link or OAuth may be preferred. Needs product decision."

## Spec Template

Save specs to `docs/specs/YYYY-MM-DD-<feature>.md`.

```markdown
# Spec: [Feature Name]

Date: YYYY-MM-DD
Status: Draft | Approved

## Summary
One paragraph: what this feature does and why it matters.

## Behavior
What the user sees and does, described as concrete scenarios.

## Non-Goals
What this feature explicitly does NOT do. Adjacent functionality excluded and why.

## Constraints
Performance, capacity, compatibility, and data limits.

## Error and Edge Case Behavior
What happens when things go wrong or hit boundaries.

## Success Criteria
Numbered list of concretely verifiable conditions for "done."
Each criterion is pass/fail with no ambiguity.

## Open Questions
Assumptions that need human verification before implementation.
```

Adapt sections to fit the feature. A simple feature may need only a few sentences per section. A complex feature may expand sections. The structure should serve the content.

## Scaling Guidance

| Feature Complexity | What to Do |
|---|---|
| Trivial (config change, copy tweak) | No spec needed |
| Small (single behavior, clear scope) | Lightweight spec -- a few sentences per section |
| Medium (multiple behaviors, some ambiguity) | Full spec with all sections |
| Complex (many behaviors, cross-cutting concerns) | Full spec, possibly split into sub-specs per component |

## After the Spec

Present the spec to your human partner for review:

"Please review this spec -- especially the Non-Goals, Success Criteria, and Open Questions. Let me know if anything looks wrong or missing before we move to implementation planning."

After approval, invoke writing-plans to create the implementation plan with the spec as input.

## Common Mistakes

**Leaking implementation into the spec.** If you're writing code, database schemas, or naming React components, you've left spec territory. Pull back to behavior.

**Vague success criteria.** "Works correctly" and "handles errors gracefully" are not criteria. Every criterion must be pass/fail verifiable by someone with no context.

**Treating assumptions as decisions.** When requirements are ambiguous, flag the ambiguity in Open Questions rather than silently choosing. Wrong assumptions cascade into wrong implementation.

**Thin non-goals.** "X is out of scope" with no explanation invites debate. Good non-goals explain WHY something is excluded so the boundary sticks.

**Skipping error behavior.** Happy path specs produce happy path code. Edge cases discovered during implementation are 10x more expensive to handle than edge cases defined in the spec.

## Rationalizations

| Excuse | Reality |
|--------|---------|
| "We can refine the spec later" | Vague specs produce vague code. Refinement after implementation is rework. |
| "The team is blocked, just write something quick" | A quick bad spec blocks the team longer than a thorough spec takes to write. |
| "This feature is too simple for all sections" | Simple features have hidden complexity. The spec can be short, but every section gets at least a sentence. |
| "I'll add implementation details so the team knows HOW" | That's what writing-plans is for. The spec defines the contract; the plan defines the build. |
| "I flagged the open question inline, that's enough" | Open questions must be collected in the Open Questions section. Inline flags get lost. |
| "Non-goals are obvious, I don't need to list them" | If they're obvious, writing them takes 30 seconds. If they're not, you just prevented scope creep. |

## Red Flags -- STOP and Reconsider

- You're writing code snippets or database schemas in the spec
- Success criteria use words like "appropriate," "sensible," "correct," or "graceful"
- There's no Open Questions section despite ambiguous requirements
- Non-goals section has fewer than 3 items for a medium+ feature
- You can't imagine writing a test case directly from a success criterion
- You flagged an open question inline but didn't add it to the Open Questions section

