| name | spec-writer |
| description | Use this skill when the user asks to "write a spec", "create a spec", "spec out", "plan the implementation", "draft an implementation plan", "create a feature spec", "document how to implement", or otherwise needs a detailed implementation specification for a feature, integration, refactor, library, CLI, UI flow, data pipeline, or any other piece of work. Produces project-grounded specs with explicit goals and non-goals, decisions paired with rejected alternatives, testable acceptance criteria, and a verifiable definition of done. Auto-sizes spec depth to the scope of the work. Works for any language, framework, or domain. |
| version | 2.1.0 |
| license | MIT |
spec-writer
Produce implementation specs grounded in the actual project — real names, real paths, real constraints — so an implementing agent (or human) can build the feature without mid-task clarifying questions.
This skill does not write code, review code, or resolve undecided architectural questions on its own. When a question is genuinely open, mark it [NEEDS CLARIFICATION: ...] and surface it; do not silently invent the answer.
This file is a single Markdown document so any AI coding tool can use it: Claude Code (reads the frontmatter for routing), Cursor (drop into .cursor/rules/), Windsurf, GitHub Copilot, Aider (--read SKILL.md), Codex, Continue, and similar tools that load instruction files. The body avoids tool-specific commands.
Output
Default location: specs/<feature-name>/SPEC.md — lowercase, hyphenated, one spec per directory. Create specs/ if it does not exist. If the user specifies a different path, use it.
If specs/README.md does not exist, create it as an index: name | status (draft / ready / in progress / done) | dependencies on other specs | primary files touched.
Process
Five phases. Phases 1–3 are where most specs are won or lost; phase 5 catches the rest. None can be skipped.
Phase 1 — Investigate the project
First, size the work. The scope tier determines spec depth — a fixed-depth pipeline over-engineers small work and under-specifies large work:
| Tier | Typical scope | Spec depth |
|---|
| Small | ≤3 files, one obvious change | Compact spec: Summary, Goals/Non-goals, Open questions, Requirements, Tasks, Definition of Done. Omitted sections need no N/A line. Skip the phase-2 sweep. |
| Medium | One component area, clear feature | All required sections plus only the conditional sections that clearly apply. Sweep only the dimensions obviously present. |
| Large | Multi-component feature | Full template with conditional sections and full dimensions sweep. |
| Complex | Ambiguous scope, new domain, cross-cutting concerns | Full template, full sweep, and a decomposition plan (see "Working with multiple specs") considered before drafting. |
Record the tier in the spec header. Re-evaluate after investigating: if the code reveals more moving parts than the request implied, size up — never size down to save effort.
The single most common failure mode is generating a generic spec because the codebase was never opened. Read first:
- The project's primary agent context (in priority order — first match wins):
AGENTS.md, CLAUDE.md, .cursorrules, .cursor/rules/, CONVENTIONS.md, README.md. If one exists, it is the source of truth — read it in full.
- Existing specs (under
specs/, docs/, rfcs/, or wherever convention places them). Match their style and depth.
- The dependency manifest for whichever ecosystem applies (
go.mod, package.json, Cargo.toml, pyproject.toml, requirements.txt, pom.xml, build.gradle, Gemfile, *.csproj, pubspec.yaml, etc.).
- The entry point(s) — wiring / bootstrap / main / app composition root.
- Any environment or configuration contract (
.env.example, config.*, settings schema).
- How the project runs its checks — test/lint/build commands from the project's own manifests and CI (
package.json scripts, Makefile, pyproject.toml, .github/workflows/, etc.), and where existing tests live. Commands quoted in the spec must be discovered here, never invented from ecosystem habit.
- Infrastructure manifests if the work touches them (
docker-compose.*, k8s/, terraform/).
- The directory containing the area being modified — at least three representative files so the spec can match local conventions.
While reading, flag concerns in the code the feature touches — fragile coupling, tech debt, security gaps, performance traps, untested paths the feature will depend on. They go into the spec's "Risks and concerns" section (7), each with a mitigation.
Then detect the feature class before drafting — it determines which sections of the template apply:
| Class | Examples | Sections that usually apply | Sections often omitted |
|---|
| Backend service / API | endpoints, jobs, integrations | wiring, infra, config, observability, rollout | UX |
| Frontend / UI feature | pages, components, flows | components, state, routing, accessibility, analytics | infra |
| Library / SDK | public package, internal lib | public API, semver impact, examples, deprecations | wiring, infra |
| CLI / script / automation | commands, tooling | command surface, exit codes, IO contract | UX |
| Data / pipeline / ML | ETL, training, inference | schema, lineage, idempotency, backfill | UX |
| Refactor / migration | restructuring without new behavior | invariants, equivalence proof, rollout, rollback | new behavior |
The spec uses real names from this project. Placeholder identifiers (SomeService, MyRepo, generic Handler) are a sign the codebase was not read; rewrite before delivering.
Phase 2 — Clarify before drafting
Identify what is decided, what is assumed, and what is unknown. Ask the user about the unknowns instead of inventing answers — but ask in one batch, not one question at a time. Strong cues that something needs clarification:
- The choice has security, durability, or money consequences.
- Two reasonable engineers would pick differently with the information you have.
- The user's constraints contradict something you found in the codebase.
- A non-goal is unclear (often the actual scope question).
Before closing this phase, run an implicit-requirements sweep. These dimensions hide the requirements nobody writes down until production finds them:
| Dimension | What to cover |
|---|
| Input validation & bounds | limits, formats, sanitization |
| Failure / partial failure | timeouts, partial writes, rollback |
| Idempotency / retries / duplicates | safe retries, dedup keys |
| Auth boundaries & rate limits | who can call what; throttle rules |
| Concurrency / ordering | races, ordering guarantees |
| Data lifecycle | TTL, archival, deletion |
| Observability | logs, metrics, traces for the new paths |
| External-dependency failure | fallback, circuit breaking, fail-fast |
| State-transition integrity | valid transitions, guards |
Sweep depth follows the tier from phase 1. Large/Complex: every dimension resolves to a requirement or an explicit N/A — <reason>; no blanks. Medium: cover only the dimensions obviously present; collapse the rest into a single "remaining dimensions N/A for this scope" line. Small: skip the sweep. Record the outcome at the end of the Requirements section.
The N/A — <reason> escape is mandatory where it applies — it prevents inventing requirements to fill a checklist. The sweep is bounded by the feature's boundary: it clarifies scope, it never expands it.
Not every unknown blocks the spec. Split what remains unresolved at draft time:
-
Blocking — it meets any of the cues above. Mark it inline and collect it under "Open questions" so it cannot be missed:
[NEEDS CLARIFICATION: token-bucket per-IP, per-user, or both?]
-
Assumable — a reasonable default exists and being wrong is cheap to correct. Choose the default, log it in the Assumptions table (section 3) with the rationale and Confirmed? no, and proceed.
A spec is not "ready" while any [NEEDS CLARIFICATION] remains. Unconfirmed assumptions do not block readiness — they are visible, signed defaults the user can veto. The invariant: nothing is left silently unresolved. Every unknown ends up resolved with the user, marked [NEEDS CLARIFICATION], or logged as an assumption.
Two more rules while clarifying:
- Scope guardrail. Clarification refines how within the stated boundary; it never adds capabilities. When the user — or your own investigation — surfaces a new capability ("should we also…"), record it under "Deferred ideas" in section 2 and return to the current scope. Captured, not built.
- Delegated decisions. When batching questions, present concrete options with a recommendation, and accept "you decide" as an answer. A delegated decision goes into the Assumptions table with your chosen default and
Confirmed? yes (delegated).
Phase 3 — Justify the approach
For each material decision (technology, pattern, integration boundary, abstraction shape), answer two questions before documenting the implementation:
- Why this, for this project? Reference an existing constraint, pattern, or decision in the codebase. Generic praise of the technology is rejected.
- Why not the obvious alternative? Name at least one alternative and explain the trade-off in one sentence grounded in a real constraint — not "it's more complex".
If you cannot answer both questions specifically, the decision is not ready to spec. Read more code or ask the user.
Generic, rejected:
Redis is widely adopted with excellent client support.
Project-grounded, accepted:
Cache-aside puts invalidation in the application — grep-able and consistent with the explicit-over-magic rule already established in AGENTS.md. Write-through would coordinate cache writes with the existing transactional outbox in Service.Register(), putting two responsibilities in one transaction.
The same grounding applies to external facts. When a decision or code example depends on a library API, configuration flag, or protocol behavior, verify it in this order: an existing call site in the codebase → the project's own docs → the dependency's official documentation. If none confirms it, do not present it as fact — mark it [NEEDS CLARIFICATION: verify <X> against <source>]. A fabricated API in a spec propagates into implementation and fails late; a flagged uncertainty fails now, cheaply.
Phase 4 — Draft the spec
Use the template below. Include only sections that apply to the feature class and the tier from phase 1. For each omitted optional section, leave a one-line N/A — <reason> so reviewers know it was considered — except in small-tier specs, which drop omitted sections without ceremony.
Write requirements as testable statements. EARS patterns are useful for that:
- Ubiquitous —
The <system> shall <response>.
- Event —
When <trigger>, the <system> shall <response>.
- State —
While <state>, the <system> shall <response>.
- Unwanted —
If <unwanted condition>, then the <system> shall <response>.
- Optional —
Where <feature included>, the <system> shall <response>.
Use RFC 2119 keywords (MUST / SHOULD / MAY) only when distinguishing a hard requirement from a preference matters. Don't pepper the spec with all-caps directives — overuse drains their meaning.
Each requirement gets an ID (R1, R2, …) and a priority: P1 (MVP — the feature is not shippable without it), P2 (should have), P3 (nice to have). The P1 set must form a coherent, independently demonstrable slice — if P1 alone cannot be demoed, the split is wrong. Priorities give the implementer a task order and a natural cut line when the appetite shrinks.
Each task in the implementation plan back-references the requirement(s) it satisfies, so traceability is one grep away.
Phase 5 — Self-check before saving
Verify each item. Anything that fails must be addressed before delivering.
- Did I read the project's primary context document and the area being modified?
- Are all identifiers in code blocks real names from this project?
- Is every material decision paired with at least one named, dismissed alternative?
- Are non-goals explicit — not just implied by the goal?
- Are acceptance criteria testable? Could a different person verify each one without asking me?
- Does each Definition-of-Done item have an objective verification — a command, a file existence check, an observable behavior — not a feeling?
- Is every unknown accounted for — blocking ones under "Open questions", assumable ones in the Assumptions table with a chosen default and rationale — rather than silently resolved?
- For any external dependency: is the failure-mode behavior specified (degrade, retry, fail-fast)?
- For any feature flag or staged rollout: is the rollback path and the flag-removal trigger documented?
- Is the spec the right size for the tier chosen in phase 1? A 50-page monolith should be split into a graph; a 5-line stub usually skipped phases 1–3.
- Trace every requirement ID: does each R appear in at least one task, one test scenario, and one Definition-of-Done item? An orphaned requirement is either dead scope or a missing task — fix it before delivering.
Spec template
Sections marked required must appear. Sections marked conditional appear only when the feature class needs them; otherwise leave a one-line N/A — <reason>. Small-tier specs use only the compact subset from the phase-1 sizing table and skip the N/A lines.
# <Feature Name> — Spec
**Status:** draft | ready | in progress | done
**Tier:** small | medium | large | complex
**Owner:** <single name>
**Last updated:** <ISO date>
**Related:** <ADRs, issues, prior specs, PRs>
## 1. Summary (required)
Two to four sentences. What this adds, who benefits, what it explicitly is **not**, and the most common over-engineering trap for this kind of feature.
## 2. Goals and Non-goals (required)
### Goals
- G1 — ...
- G2 — ...
### Non-goals
- N1 — This spec does **not** cover ...
- N2 — ...
Non-goals do more work than goals — they bound the scope. Be specific.
### Deferred ideas (optional)
Capabilities that surfaced during clarification but belong to another spec. Captured so they are not lost; explicitly not built here.
- ...
## 3. Open questions and assumptions (required while non-empty)
### Open questions (blocking — spec is not ready while any remains)
- [NEEDS CLARIFICATION: ...]
Remove this subsection once empty.
### Assumptions (non-blocking — chosen defaults, visible and vetoable)
| Assumption | Chosen default | Rationale | Confirmed? |
|---|---|---|---|
| <ambiguity> | <what the spec proceeds with> | <why this default> | no |
Flip `Confirmed?` to `yes` when the user signs off. Keep confirmed rows — they are the decision record for choices too small for section 5.
## 4. Requirements (required)
Numbered, testable, prioritized. EARS patterns where useful.
- R1 (P1) — When <trigger>, the system shall <response>.
- R2 (P1) — The system shall <ubiquitous requirement>.
- R3 (P2) — If <unwanted condition>, then the system shall <response>.
P1 = MVP, P2 = should have, P3 = nice to have. End the section with the sweep outcome: dimensions from the phase-2 sweep that produced no requirement, each as `N/A — <reason>`.
## 5. Decisions and Alternatives Considered (required)
For each material decision:
### D1 — <decision>
- **Chosen:** ...
- **Why this for this project:** reference real constraint or existing pattern.
- **Alternatives considered:**
- <alt 1> — rejected because ...
- <alt 2> — rejected because ...
- **Consequences:** positive, negative, neutral.
## 6. Design (required)
A description at one consistent level of detail. Include only what's new or changed:
- Component / module boundaries — use real names.
- Data model deltas — schemas, types, ownership.
- Public API or interface surface.
- Sequence of operations for the main flow(s) — ASCII diagram or numbered steps.
- Error model — how failures propagate and what the caller sees.
Place abstractions at the **consumer** layer, not the implementation layer. Dependencies must flow inward toward the domain.
## 7. Risks and concerns (conditional — required when investigation surfaced any)
Concerns found in the existing code this feature touches:
| Concern | Location | Impact | Mitigation |
|---|---|---|---|
| <fragile coupling / tech debt / security gap / performance trap / test gap> | `path/file.ext:42` | what breaks or degrades | how this spec — or a named follow-up — addresses it |
Every row gets a mitigation. "None found" is a valid section body.
## 8. Dependencies (conditional — required when introducing any)
Exact install commands grouped by purpose (runtime / dev / tooling). Include version constraints where they matter. Otherwise: "No new dependencies required."
## 9. Configuration (conditional — required for new env vars or config keys)
Table: variable | config key | default | required | description.
Each new optional capability has an `_ENABLED`-style toggle defaulting to off. When off, no infrastructure is initialized and no connections are opened.
## 10. File structure (required)
A tree showing only:
- new files (one-line description)
- modified files (`← Modified: <reason>`)
Do not list files unaffected by this work.
## 11. Implementation outline (required)
Pattern-critical code, not full implementations. For non-trivial points, show:
- The interface or contract.
- The concrete logic that is not obvious.
- The integration site in existing code — file, function, exact insertion point.
- Comments only on the lines whose **why** is non-obvious.
Skip boilerplate that follows the project's established pattern; write `// follows the standard <pattern> pattern` and move on.
## 12. Failure modes and degradation (conditional — required for any external dependency)
For each external dependency:
- Behavior at startup if unavailable.
- Behavior at runtime if it fails.
- Whether the user-facing surface degrades or fails loudly.
- If the dependency is critical (failure = system cannot serve requests), state so and justify.
## 13. Wiring / Bootstrap (conditional — backend / service code)
Position in the existing startup sequence and the reason. Shutdown ordering and the reason. Reference the project's documented wiring sequence if one exists.
## 14. Infrastructure (conditional — new external services or manifests)
Configuration must include healthchecks or readiness criteria, resource limits, restart policy, and secrets handling. State exactly which files are added or modified (compose, k8s, terraform).
## 15. UX / Accessibility (conditional — user-facing UI changes)
- Component placement, all states (loading / empty / error / success).
- Keyboard, screen-reader, and contrast behavior.
- Telemetry on user-visible interactions.
## 16. Public API / SDK impact (conditional — libraries, SDKs, public APIs)
- Backward compatibility, semver impact (MAJOR / MINOR / PATCH).
- Deprecations and timelines.
- Migration guide for callers.
## 17. Data and migrations (conditional — schema or data changes)
- Schema delta, migration script location, reversibility.
- Backfill plan: idempotency, batch size, throttling.
- Read/write compatibility window during the migration.
## 18. Security and privacy (conditional — auth, PII, secrets, external input)
- New attack surface introduced.
- AuthN / AuthZ changes.
- Data classification, retention, redaction.
- Input validation boundary — where untrusted data becomes trusted.
## 19. Observability (conditional — recommended for production code)
- Logs: events, levels, structured fields.
- Metrics: names, units, labels.
- Traces: spans of interest.
- Alerts and ownership.
## 20. Performance (conditional — when latency, throughput, or cost matters)
- Targets: p50 / p95 / p99 latency, throughput, memory, cost.
- Load characteristics and capacity assumptions.
## 21. Testing (required)
A scenario table with at least five rows. Cover: happy path, dependency unavailable, idempotency / repeat input, invalid input, cancellation / timeout, and any feature-specific concern.
| Scenario | Setup | Expected behavior | Requirement |
|---|---|---|---|
| Happy path | ... | ... | R1 |
State unit / integration / contract test placement, what is mocked, what runs against real infrastructure, and the command to run them. Every command must come from the project's own manifests or CI config — name the source (e.g. "`make test`, from `Makefile`"). If the project has no test setup, say so and mark `[NEEDS CLARIFICATION: test runner and command?]` instead of inventing one. State any coverage target the project has.
## 22. Rollout and rollback (conditional — staged or risky changes)
- Flag name and default.
- Phased rollout plan (cohorts, % traffic).
- Rollback procedure — concrete: a command, a flag flip, a redeploy.
- Removal trigger for the flag and its target date.
## 23. Tasks (required)
Numbered, dependency-ordered. Mark groups that can run in parallel. Each task back-references one or more requirement IDs. Order tasks so the P1 requirements complete first — the P1 slice should be demonstrable before P2/P3 work starts.
- T1 [parallel-A] [R1] — ...
- T2 [parallel-A] [R2] — ...
- T3 [after T1, T2] [R3] — ...
Coverage: every requirement ID appears in at least one task. List any unmapped R-IDs here with ⚠️ and resolve them before the spec is `ready`.
## 24. Definition of Done (required)
A checklist where every item is independently verifiable — a command passes, a file exists, an observable behavior is met. Group by layer (contracts, implementation, integration, tests, observability) when useful.
- [ ] R1 verified by <test / command / observation>
- [ ] R2 ...
Quality bar for code examples in specs
- Real names from the project. No
SomeService, MyRepo, generic handler. If you cannot fill in real names, you didn't read the codebase.
- Pattern, not full implementation. Show the 15 lines that contain the pattern, not the 60 with boilerplate. Write "remainder follows the standard pattern" for the rest.
- Comment the why, not the what. A future reader can read the code; they need the constraint, the trade-off, the reason.
- Match the project's error-handling convention. Read two or three existing call sites before drafting examples.
- Show both failure modes when degradation matters: a wrong example (propagates failure to the caller) and a correct one (degrades gracefully).
Patterns to avoid in specs
These produce specs that read well but don't ship:
- Collaborative voice (
we will use X) instead of imperative (use X).
- "Can be extended later" without a concrete trigger or owner.
- "See library docs" as the only guidance for a non-trivial decision.
- Abstractions defined at the implementation layer instead of the consumer.
- A test plan whose only scenario is the happy path.
- A configuration flag without a documented default.
- An external service without a healthcheck or readiness criterion.
- TODO comments without a specific trigger or owner.
- Errors silently swallowed without a documented reason.
- Definition-of-Done items that cannot be objectively verified.
- Placeholder identifiers (
SomeService, MyRepository, generic Handler).
- Strawman alternatives — listed only to be dismissed; not real options anyone would have considered.
- Library APIs, flags, or protocol behaviors recalled from memory instead of verified against the codebase or official docs.
Working with multiple specs
A large feature usually wants a graph of focused specs rather than one monolith. Decomposition rule: two specs can be drafted in parallel if and only if their File structure sections do not share modified files.
When the feature is broad, before drafting any spec, produce a decomposition plan:
- List sub-components, each as a candidate spec.
- For each, list the files it modifies.
- Group specs that share zero modified files (parallel).
- Order the rest by dependency.
Then draft parallel specs concurrently, dependent specs once their inputs are accepted.