Spec-driven development flips the usual prompt-and-pray loop: you write a precise spec — requirements, user stories, acceptance criteria — and the coding agent generates the plan, the tasks, and the code from that document. The spec, not the chat history, is what you review, and it stays in the repo after the code ships. OpenSpec and GitHub Spec Kit are the two open-source toolkits that wire this workflow into Claude Code, Cursor, and other agents through slash commands, and this lab walks both end to end on a small feature: rate limiting an API endpoint.
What spec-driven development actually changes
In a normal agent session you type an instruction, the agent edits files, and you review the resulting diff. Everything it assumed about scope, edge cases, and error handling stayed implicit in the prompt, and you only find out it was wrong by reading code.
Spec-driven development (SDD) inserts a document between the instruction and the code: a spec stating what the feature does and a list of scenarios that must hold true (acceptance criteria). You review that document first; only then does the agent turn it into a plan, a task list, and code. The review gate moves from “is this diff correct” to “is this spec correct,” and a spec is far shorter to check than a diff touching a dozen files.
Both toolkits below implement this shape — propose/specify, plan, implement, verify — as slash commands the agent runs in its own chat. The CLI only installs the tool and the command files; the agent runs every other step.
OpenSpec: install and the propose → apply → archive loop
OpenSpec ships as a single binary with no Node.js dependency. Pick one install path:
# macOS and Linux
curl -fsSL https://raw.githubusercontent.com/Fission-AI/OpenSpec/main/scripts/binary/install.sh | sh
# Windows PowerShell
irm https://raw.githubusercontent.com/Fission-AI/OpenSpec/main/scripts/binary/install.ps1 | iex
# Alternative: npm (needs Node.js 20.19.0+) or Homebrew
npm install -g @fission-ai/openspec@latest
brew install openspec
Initialize inside your project:
cd your-project
openspec init # interactive tool picker
openspec init --tools claude,cursor # set up specific tools, no prompts
openspec init writes an openspec/ folder and prints which slash-command spelling your chosen tools use — the canonical name is /opsx:propose, but Cursor and Copilot read it as /opsx-propose, Amazon Q as @opsx-propose, Codex as $openspec-propose. The default profile gives three commands: /opsx:explore (a no-stakes thinking pass before committing to a plan), /opsx:propose <change-name> (creates openspec/changes/<change-name>/ with proposal.md, a specs/ folder, design.md, and tasks.md), /opsx:apply (implements the tasks), and /opsx:archive (moves the folder to openspec/changes/archive/<date>-<change-name>/ and merges its spec deltas into the project’s openspec/specs/ folder).
An expanded profile adds /opsx:new, /opsx:continue, /opsx:ff, /opsx:verify, /opsx:bulk-archive, and /opsx:onboard via openspec config profile plus openspec update, which also refreshes the installed command files after a CLI upgrade.
The same actions exist as terminal commands, useful for scripting or checking a change without opening the agent:
openspec list # active changes
openspec list --specs # specs with requirement counts
openspec show add-rate-limit --diff # requirement diffs for one change
openspec validate add-rate-limit # check one change's artifacts
openspec validate --all # check everything
openspec archive add-rate-limit -y # CLI equivalent of /opsx:archive
GitHub Spec Kit: constitution, specify, plan, tasks, implement, converge
Spec Kit is a Python CLI, installed with uv:
uv tool install specify-cli
specify init my-project --integration claude
cd my-project
--integration claude targets Claude Code; swap it for copilot or another supported agent key. For Claude Code, the install writes one skill file per command under .claude/skills/speckit-<name>/SKILL.md, invoked as /speckit-specify, /speckit-plan, and so on — the hyphenated form, not the dotted /speckit.specify Spec Kit’s docs use as neutral notation. Codex uses $speckit-specify; Kimi uses /skill:speckit-specify.
Run these one at a time in the agent’s chat, reading each result before the next:
/speckit-constitution Create principles focused on code quality, testing, and maintainability.
/speckit-specify Limit requests on POST /api/orders to protect the order service from bursts.
/speckit-plan Node.js with an in-memory token bucket per API key, no new infrastructure.
/speckit-tasks
/speckit-implement
/speckit-converge
/speckit-constitution runs once per project and records the principles every later step is checked against. /speckit-specify turns a natural-language description into spec.md — requirements and user stories, not tech stack. /speckit-plan generates plan.md, where the tech stack belongs. /speckit-tasks generates a dependency-ordered tasks.md, split into Setup, Foundational, one phase per user story, then Polish. /speckit-implement executes tasks.md in order and is the only command that writes application code. /speckit-converge checks the result against all three artifacts; it is append-only, its sole possible write is adding tasks under a “Convergence” section if it finds gaps, and it reports Converged when nothing is left.
Three optional quality gates slot into the full sequence (constitution → specify → clarify → plan → checklist → tasks → analyze → implement → converge): /speckit-clarify asks up to five targeted questions about underspecified areas and writes the answers back into spec.md; /speckit-checklist generates a reviewer-owned checklist testing the spec itself for completeness; /speckit-analyze is a read-only pass reporting conflicts across the three artifacts before you implement anything.
Spec Kit tracks the “active” feature through .specify/feature.json, not your git branch. Two bundled extensions follow the same style: specify extension add bug adds /speckit-bug-assess, /speckit-bug-fix, and /speckit-bug-test, with reports under .specify/bugs/<slug>/; specify extension add assess adds a five-step idea-assessment flow under .specify/assessments/<slug>/.
OpenSpec vs Spec Kit at a glance
| OpenSpec | GitHub Spec Kit | |
|---|---|---|
| Runtime | Single binary, or npm/Homebrew | Python 3.11+ via uv |
| Setup command | openspec init |
specify init <project> --integration <agent> |
| Start a unit of work | /opsx:propose <name> |
/speckit-specify "..." |
| Build it | /opsx:apply |
/speckit-implement |
| Close it out | /opsx:archive |
/speckit-converge (gap-check, not archival) |
| Consistency check | openspec validate |
/speckit-analyze (read-only) |
| Where specs live | openspec/changes/<name>/ then merged into openspec/specs/ |
spec.md/plan.md/tasks.md in the active feature directory |
| Process shape | Iterate on any artifact anytime | Mostly-ordered phases with optional gates |
Pick OpenSpec for a light loop with the freedom to jump back into a proposal mid-implementation. Pick Spec Kit for a stricter, checked sequence with a written constitution and an automated consistency check before code gets written.
Lab: spec a rate limit, generate the plan, implement, review
The feature: limit POST /api/orders to 20 requests per minute per API key, returning 429 with a Retry-After header when the caller goes over.
Step 1 — write the acceptance criteria first, independent of either tool
Before touching a slash command, write the user story and acceptance criteria in plain language — this is the document you will actually review, and the input both tools need.
## User story
As an API consumer, I need predictable rate limiting on order creation
so a retrying client cannot overload the order service, and so I know
exactly when to retry.
## Acceptance criteria
### Scenario: under the limit
- GIVEN an API key with fewer than 20 requests in the current minute
- WHEN it sends POST /api/orders
- THEN the request is processed normally
### Scenario: over the limit
- GIVEN an API key with 20 requests already counted in the current minute
- WHEN it sends one more POST /api/orders
- THEN the response is 429 with a Retry-After header in seconds
### Scenario: window resets
- GIVEN an API key that was rate-limited in the previous minute
- WHEN a new minute window starts
- THEN its next request is processed normally
Step 2 — turn it into the tool’s spec format
With OpenSpec, run /opsx:propose add-rate-limit and the agent drafts openspec/changes/add-rate-limit/ for you. Replace the generated specs/ draft with your own scenarios, translated into OpenSpec’s requirement grammar:
## ADDED Requirements
### Requirement: Order creation rate limit
The orders API SHALL limit POST /api/orders to 20 requests per minute
per API key and SHALL reject requests over that limit with 429.
#### Scenario: Over the limit
- **WHEN** an API key sends a 21st request within the same minute
- **THEN** the API responds 429 and includes a Retry-After header in seconds
#### Scenario: Window resets
- **WHEN** a new minute window starts for a previously limited key
- **THEN** that key's next request is processed normally
With Spec Kit, run /speckit-specify and paste the user story and scenarios as the argument. If a scenario is ambiguous to the agent — per API key or per IP? — run /speckit-clarify before moving on; that is exactly the ambiguity it is built to catch.
Step 3 — plan, implement, review against the spec
OpenSpec: edit design.md if you have a technical preference, then /opsx:apply to implement. Spec Kit: /speckit-plan with your technical constraints, then /speckit-tasks, then /speckit-implement.
Then review the spec file next to the diff, scenario by scenario: does the 21st request get a 429? Does Retry-After contain a number of seconds? Does a key reset after the window passes? Keep tests in the loop — see the TDD workflow with an AI agent. With Spec Kit, run /speckit-converge and expect Converged; appended tasks mean a missed scenario, not a tool bug. With OpenSpec, run openspec validate add-rate-limit before /opsx:archive so the archived spec and the shipped code match. Only then archive or converge the change — the same slash-command mechanism both toolkits use is covered in the custom commands lab.
Pitfalls
Vague specs produce vague code. “Add rate limiting” is not a spec — no number, no window, no response contract. Without those, the agent invents them, and your review has nothing concrete to check against.
Spec drift: the agent edits the spec to match its own code. If a run that was supposed to write code also silently rewrote a requirement in spec.md, that requirement now describes what the agent built, not what you asked for. Diff the spec files after every /opsx:apply or /speckit-implement run, not just the application code.
A green /speckit-converge or a clean /opsx:apply is not the review. /speckit-converge only checks the artifacts it was given; it will say Converged on a bad spec that was consistently implemented. The scenario-by-scenario read in Step 3 above is the part no automated check replaces.
Running both toolkits in the same repo without picking one as the source of truth. OpenSpec writes to openspec/; Spec Kit writes to .specify/. Your agent has no way to know which one to honor for a given change — decide per project, not per session.
Skipping /speckit-clarify or openspec validate because the feature looks small. A 20-requests-per-minute limiter has unstated decisions — per key or per IP, fixed window or sliding — that are cheap to resolve before planning and expensive to find after /speckit-implement has already picked an answer.
FAQ
What is spec-driven development with AI coding agents?
It is a workflow where you write a spec first — requirements, user stories, and acceptance criteria as concrete scenarios — and the coding agent generates the plan, the task list, and the code from that document. You review the spec before any code exists, instead of reviewing a diff after the agent has already guessed at your intent.
How is OpenSpec different from GitHub Spec Kit?
OpenSpec ships as a single binary with a light default loop: propose, apply, archive. GitHub Spec Kit is a Python CLI (installed with uv) with a longer, phase-gated flow — constitution, specify, plan, tasks, implement, converge — plus optional clarify, checklist, and analyze quality gates. OpenSpec favors iterating on any artifact at any time; Spec Kit favors a stricter, checked sequence.
What is the exact OpenSpec command to start a new change?
In the agent chat, type /opsx:propose <change-name> (Cursor and GitHub Copilot spell it /opsx-propose, Amazon Q uses @opsx-propose, Codex uses $openspec-propose). From a terminal instead of the agent, openspec new change <change-name> creates the same folder without going through the agent.
Which Spec Kit command actually writes the code?
/speckit-implement is the only command that executes tasks.md and produces code, running each phase in dependency order. /speckit-specify, /speckit-plan, and /speckit-tasks only produce Markdown artifacts (spec.md, plan.md, tasks.md); /speckit-converge checks the result afterward and only ever appends new tasks, it never edits code.
Do I need Python for OpenSpec or Spec Kit?
GitHub Spec Kit needs Python 3.11+ and the uv tool, installed with uv tool install specify-cli. OpenSpec’s standalone installer is a single binary with no Node.js requirement; its npm install path (npm install -g @fission-ai/openspec@latest) needs Node.js 20.19.0 or higher, and a Homebrew formula is also available.
How this guide was verified
Written by hand from the official documentation, every command re-checked, and reviewed a second time before publishing. Last reviewed on .
Official sources read for this article

