-
Notifications
You must be signed in to change notification settings - Fork 2
Hands On Walkthrough
One complete SDD cycle on a toy project, in about 45 minutes. Works in Claude Code and in GitHub Copilot — the commands are identical.
This is a workshop exercise: work in pairs. One person is the Product Owner (PO), one is the Developer (Dev). You sit at the same machine and take turns deliberately. In PO phases only the PO types; in Dev phases only the Dev types. That discipline is the lesson.
Doing it alone? Then switch hats consciously at each phase boundary and notice how different the two roles feel.
A tiny HTTP service that splits a restaurant bill fairly across several people. Inputs: bill amount, tip percentage, number of people. Outputs: the amount per person and the total tip. No database, no frontend, no login — just enough for the whole SDD cycle to run through visibly once.
Create an empty project from the template and open that folder in your tool — not a parent folder, or the slash commands will not appear.
git clone https://github.com/GregorBiswanger/featherspec.git tip-splitter
cd tip-splitter
rm -rf .git
git init(On Windows PowerShell: Remove-Item -Recurse -Force .git. With Node.js installed,
npx degit GregorBiswanger/featherspec tip-splitter replaces the clone and the two cleanup
lines.)
Claude Code: run claude in the folder. Type / and check that the sdd- commands appear.
GitHub Copilot: open the folder in VS Code, open Copilot Chat
(Ctrl/Cmd + Alt + I), switch to Agent mode, pick
the SpecDrivenAgent persona. Type / and check that sdd-setup, sdd-specify, sdd-plan,
sdd-compile and sdd-lifecycle appear. Missing? Restart VS Code completely — not just a
window reload.
Once per project. Creates the Memory Bank, fills the architecture snapshot, and pins down the working agreements.
/sdd-setup
Answer the language question with whatever your team documents in — English, Deutsch, … —
and then the short wizard. Two of its steps are agreements, not data entry: the quality gate
(the wizard proposes exact lint / type-check / test commands from the detected stack, to run
after every implementation step until zero warnings and errors) and the TDD working mode (a
strict default is presented — test first, failing through a Not implemented stub, and a stop
after every new or changed test for your confirmation — you say whether it fits, no quiz). If
the repo has no commit yet, the wizard ends by proposing a baseline commit; later plan baselines
and safe file moves depend on it.
Learning point: open
.memory-bank/. That is the long-term memory. Everything decided here survives closing the tool, switching to the other assistant, and next Monday. That is what separates SDD from a throwaway chat.
The PO writes the specification from the user's point of view. No tech stack. Not one word about Node.js.
/sdd-specify I want a small service that splits a restaurant bill fairly across several
people. A user gives three things: the bill amount, a tip percentage and the number of
people. The service returns what each person pays in total (including tip) and how large
the total tip is.
Goal: a group at the table knows within seconds what everyone owes, without doing mental
arithmetic.
Success criteria:
- The per-person result is always rounded to two decimal places.
- The individual amounts add back up to the total (no cents lost to rounding).
- A response arrives in under a second.
Out of scope: no login, no persistence, no currency conversion, no splitting by what each
person ordered.
The assistant now runs its interview — one question per message — about users, roles, edge cases and acceptance criteria. Answer as the PO. Short answers are fine.
Learning point: pause and notice what is not in there. No "build a REST API", no "use Express", no endpoint paths. Only the problem and what success means. The how comes from the Dev in a minute.
As a follow-up message in the same chat:
Add these rules to the spec:
- 0 or fewer people returns a clear error message, not a calculation.
- A negative tip percentage is not allowed and returns an error. 0 percent is allowed.
- A bill amount of 0 is allowed and yields 0 per person.
- Leftover rounding cents are added to the first person, so the sum comes out exactly.
Learning point: the rounding-cents rule is a genuine product decision. The AI cannot guess it and the Dev is not allowed to invent it. That is exactly why the PO is in the room.
/sdd-clarify
This reads the spec as a stranger — no memory of the conversation you just had — and comes back with four lists: contradictions, terms used in two senses, criteria nothing can decide, and failure modes nobody named. It does not fix them. It ends with one question.
Learning point: read the output together, then ask the room: how many of these could we have caught ourselves? Usually one or two. The rest were invisible from inside the conversation that produced them — which is the entire argument for a second pass. The context that created an ambiguity is structurally the worst placed to find it.
Decide per item: fix it in the spec now, or move it to Open points and accept the risk knowingly. Both are legitimate; silently ignoring it is not.
The PO reads the generated spec in .specs/backlog/, checks it, and says out loud:
"Spec approved." Then hand the keyboard to the Dev.
You should now have:
.specs/backlog/0001-tip-splitter.md
with **Status:** Draft and a list of AC-001, AC-002, … criteria.
Now only the Dev types. The PO watches and does not intervene.
/sdd-plan We build this as a minimal HTTP service with Node.js and the built-in http module,
without external frameworks. A single POST endpoint /split takes JSON with the fields amount,
tipPercent and people, and returns JSON with perPerson and totalTip. Input validation happens
in the service. The calculation lives in its own testable module. We write unit tests with
the built-in node:test runner. No database, no build step.
This writes .specs/backlog/0001-tip-splitter.plan.md next to the spec: numbered baby steps
(T-001, T-002, …), each with a Verify: line and an empty Verified: field, plus a research
section, a traceability table and a session-handoff block. Then it stops and hands the plan
back — planning does not touch code.
Now actually read it. Both of you, out loud, for five minutes. This is the exercise most worth doing properly, and the one every team skips in real life.
You are not hunting for bugs. You are checking that you agree on the why and the ordering — does step three really need step two first, is the risky part early enough, is anything missing. A wrong step produces hundreds of wrong lines; a wrong line produces one. Two hundred lines of plan versus two thousand lines of diff is not a close call.
Say which steps look wrong before a single line is written.
Learning point: the Dev just made a dozen decisions the PO never saw — the
httpmodule instead of Express, a separate calculation module,node:test. The PO has no business in any of them. And conversely: the Dev should never have invented the rounding-cents rule.
Approving the plan is not the start of the work. Before implementation, the pair — spec and plan
together — moves from .specs/backlog/ to .specs/active/ and the spec status becomes
In Progress. Implementation happens in active/, not in backlog/.
You do not have to type /sdd-lifecycle for it: an explicit start signal like Implement T-001.
or start T-001 covers the still-pending move — the assistant performs it per /sdd-lifecycle's
procedure and says so.
Implement T-001.
Then T-002, and so on — or ask for the whole plan and review each focused change as it lands.
After every step the assistant runs the Verify: command, writes what came back into
Verified:, then ticks the checkbox, fills in Notes, writes the real file paths and the
deciding test into the traceability table, and moves Current step — all in the same change set
as the code.
Open the plan file and look at a finished step. Verified: should read something like
2026-08-03 · node --test · 7 passed. If it is empty and the box is ticked anyway, you have
caught the failure mode this field exists for: an agent optimises for reporting completion, and
a checkbox costs nothing to tick.
Try this: close the session halfway through and start a new one with /sdd-plan. It reads the
plan, checks it against git status, and tells you in three lines what is done, what is next and
what blocks it.
In the terminal:
node --testnode server.jsAnd in a second terminal:
curl -X POST http://localhost:3000/split -H "Content-Type: application/json" -d "{\"amount\": 100, \"tipPercent\": 10, \"people\": 3}"Expect roughly 36.67 / 36.67 / 36.66 per person — the leftover cent goes to person one — and a
total tip of 10.
/sdd-compile
You get the readiness brief. It opens with a verdict — READY, NOT READY, or
NOT READY — unverified — then every acceptance criterion marked satisfied or pending with
evidence, open plan steps, docs-sync status, and the next three actions.
Look hard at the evidence column, because that word has a narrow definition here: a test name
and its output, or a command and its output. Not a step ID. Not a sentence explaining what the
code does — that is the code describing itself through the thing that wrote it. If a criterion
shows prose instead of an artifact, it should say pending, and if it says satisfied anyway,
you have found something worth discussing in the retro.
The Dev says out loud: "Implementation done, tests green." Keyboard back to the PO.
The PO does not review the code. The PO checks every acceptance criterion they wrote themselves — with exactly the edge cases from Part 2.
Normal case:
curl -X POST http://localhost:3000/split -H "Content-Type: application/json" -d "{\"amount\": 50, \"tipPercent\": 0, \"people\": 2}"Edge case "0 people" — must return an error, not a calculation:
curl -X POST http://localhost:3000/split -H "Content-Type: application/json" -d "{\"amount\": 50, \"tipPercent\": 10, \"people\": 0}"Edge case "rounding adds up" — the three amounts must total exactly 100:
curl -X POST http://localhost:3000/split -H "Content-Type: application/json" -d "{\"amount\": 100, \"tipPercent\": 0, \"people\": 3}"The most important learning point of the day: you check against your own criteria from the spec, not against a gut feeling. If "actually I'd also like X" occurs to you now — that is not the Dev's mistake. It was not in the spec. That is the next iteration, a new
/sdd-specify.
/sdd-lifecycle
Here spec and plan part ways. The spec moves to .specs/done/ and its status becomes
Implemented. The plan is archived frozen into .specs/plan-archive/ under a dated name —
the date is the completion day — with one closing edit (status Done, handoff closed, a
**Spec:** backlink) and is never edited or deleted after that. The spec keeps hold of it: a
**Plan:** line pointing into plan-archive/, plus a dated entry in ## Plan history — one
line per iteration, append-only. .memory-bank/activeContext.md is reset to its skeleton with
Current phase: idle.
The hand-off ends with a warning — take it seriously in the workshop too: close any still-open
editor tabs or pending review buffers of the moved files now, because a later "save all"
resurrects them at the old path. Then run /sdd-overview once; its duplicate warning is the
re-check /sdd-lifecycle cannot perform after it ends.
You should end with exactly this:
.specs/done/0001-tip-splitter.md ← Status: Implemented, links the plan
.specs/plan-archive/0001-tip-splitter.2026-08-03.plan.md ← frozen, Status: Done
.specs/active/ ← empty again
.memory-bank/activeContext.md ← skeleton, Current phase: idle
The spec stays as a versioned living document — the basis for the next iteration.
| Phase | Who | Command | What happens |
|---|---|---|---|
| Setup | PO + Dev | /sdd-setup |
Memory Bank, architecture snapshot, working agreements |
| Specify | PO | /sdd-specify … |
what and why, success criteria, scope |
| Sharpen | PO | follow-up message | edge cases and product rules |
| Clarify | PO + Dev | /sdd-clarify |
a cold second pass finds what the conversation hid |
| Sign off | PO | — | "Spec approved", swap seats |
| Plan | Dev | /sdd-plan … |
tech stack, module structure, baby steps |
| Read the plan | PO + Dev | — | the cheapest review in the cycle — do not skip it |
| Start | Dev | /sdd-lifecycle |
pair → active/, spec In Progress — the start signal covers it |
| Implement | Dev | Implement T-001. |
code step by step, Verified: filled, plan kept current |
| Verify | Dev | node --test |
tests green, app runs |
| Compile | Dev | /sdd-compile |
verdict + evidence per criterion, swap seats |
| Accept | PO |
curl against the criteria |
check against your own acceptance criteria |
| Close | PO | /sdd-lifecycle |
spec → done/, plan frozen into plan-archive/, iteration finished |
Two rows are not commands. That is deliberate: the two moments with the highest leverage in this whole flow are a human reading something, not a tool producing something.
The PO now also wants each person's amount rounded up to the next whole euro.
Run /sdd-specify again against the existing spec, then /sdd-plan. Watch what happens in
Mode C: the spec moves back to active/ — alone — and before writing anything, the archived
plan's traceability table is read in reverse to tell you which steps and which files that
one changed rule reaches. Then a fresh plan lands beside the spec; the archived one stays
frozen. One immutable plan per iteration, all of them listed in ## Plan history.
The second lap is noticeably faster — the Memory Bank already knows the context, and the spec is the starting point rather than a blank page. That is the payoff of writing it down.
One last note, not a step: when the persistent markdown grows noisy over many iterations,
/sdd-clean dedupes and compacts it safely and reports the tokens saved. A fresh workshop
project gives it nothing to do — it is a fallback tidy-up, not part of the cycle.
Adapted from the Spec-Driven Development hands-on workshop for Product Owners by Gregor Biswanger.
Start here
Reference
Design & interop
- Interop Matrix
- Design Decisions
- Specify Method
- Committing to One Tool
- Migrating from the predecessor
- Releasing
Help