Build with /ssd. Watch the orchestrator do the work.
One natural-language prompt — /ssd create a simple web-based multimedia address book — and Claude Code, driving the SSD skills, scaffolds the project, picks the stack, writes the code, runs a code review, and ships a running app to localhost:5000. Your job is to read along and accept proposals. This tutorial walks you through every step and shows what was happening behind the scenes so you can drive /ssd on your own next time.
Overview
This tutorial has two layers running in parallel:
- The happy path. What a beginner sees, types, and gets back. Mostly natural language.
- Behind the scenes. Which sub-skill
/ssdinvoked, which artifact got written under.ssd/features/.../, and what the explicit command equivalent would have been if you'd typed it directly.
By the end you will have built a real Flask + SQLite address book with photos, and you will know how to drive /ssd on your own projects — both via natural-language one-liners and via explicit phase commands.
/ssd enforces — you'll see it in action.
Why This Tutorial Was Rewritten
An earlier version of this page taught beginners to hand-write JavaScript using sql.js. It was a fine little app, but it taught the opposite lesson from what this site is about: SSD is about getting the orchestrator to do the work for you, not about writing every line by hand.
If you came here to learn raw JS + SQLite-in-the-browser, that page no longer exists — but sql.js is well documented if you want it. This page teaches something more valuable: how to use AI orchestration to build real software fast and well.
What You'll Build
By the end you'll have a folder called address-book/ containing a working Python + Flask + SQLite app that:
- Stores contacts (name, phone, email, photo, notes) in a real SQLite database
- Lets you add, view, and delete contacts via a browser UI
- Stores photos as BLOBs and serves them from a route
- Has a feature flag system so half-built features can ship hidden
- Has a one-line dev-server start (
python app.py) and a clear path to production
And — more importantly — you'll have an .ssd/ directory with the full provenance of how the app was built: every architect spec, every code review, every deploy log.
Prerequisites
- Claude Code installed — the CLI from Anthropic. Install instructions: claude.ai/code.
- Python 3 — already on most Macs and Linux machines. Type
python3 --versionto check. Windows users: install from python.org. - A terminal you're comfortable opening. No prior Python or Flask experience needed.
auto mode after they trust the workflow.Step 0: Install the SSD Skills
The SSD skills are a set of Markdown files Claude Code loads when you type /ssd. Install them once into your Claude Code skills directory:
$ git clone https://github.com/AlexHorovitz/skills ~/.claude/skills
$ cat ~/.claude/skills/VERSION
1.14.0
That's it. Open Claude Code in any folder and you can now type /ssd-init, /ssd, /architect, /coder, etc.
~/.claude/skills/ on startup. Each subdirectory with a SKILL.md becomes a slash command. The version line you just printed (1.14.0) should match the version stamp at the top of the Guide. If it doesn't, you have an out-of-sync install — git pull in ~/.claude/skills fixes it.Step 1: Make a Project, Run /ssd-init
Make an empty folder, open Claude Code in it, run init.
Your transcript will look slightly different from what's below — ssd-init only asks for fields it can't auto-detect, and your environment may already have some of them.
$ mkdir address-book && cd address-book
$ git init
$ claude # opens Claude Code in this folder
> /ssd-init
I'll set up SSD for this project. A few questions:
1. Platform target? [web | headless | ios | android | macos]
2. Distribution channel? (URL, TestFlight, container registry, "TBD")
3. Developer profile? [novice | standard | expert] (default: standard)
You: web ; TBD ; novice
Detected: no language yet (empty repo). Will re-detect after first commit.
Created .ssd/ tree, .ssd/project.yml, .ssd/current.yml (v2),
.ssd/current.notes.yml, docs/decisions/, docs/runbooks/, docs/architecture/.
Added .ssd/ to .gitignore.
Prerequisite checks:
❌ CI/CD pipeline — missing (BLOCKER for /ssd start)
❌ Test harness — missing (BLOCKER for /ssd start)
⚠ Linter — missing (MAJOR)
❌ Feature flags — missing (BLOCKER for /ssd feature)
❌ Hello-world deploy — missing
⚠ README — missing (MAJOR)
Recommended next step: /ssd start — this is a greenfield project.
Walking Skeleton will address the BLOCKERs above as part of foundation.
The skill noticed that you have nothing yet. That's fine — it's recommending the right next move.
• Skill invoked:
ssd-init (v1.5.0)
• Artifacts written:
.ssd/project.yml, .ssd/current.yml, .ssd/current.notes.yml, .ssd/init-log.md, .ssd/README.md
• Explicit equivalent: there's only one way to invoke this —
/ssd-init. It's a prerequisite to every other /ssd command.
• Why: SSD doctrine says "Production parity from day one." The init skill tells you on day zero what you don't have — and what you'll need before you can ship.
Step 2: The One-Liner
Now the magic line. You're back at the Claude Code prompt:
> /ssd create a simple web-based multimedia address book
I see no active workstreams in .ssd/current.yml. With your one-line brief
and an empty repo, this is a Walking Skeleton job.
I propose: /ssd start address-book
This will:
1. Run /architect to design the stack, models, API contract, and ADRs.
2. Run /systems-designer for the day-1 deploy + observability plan.
3. Help you stand up a "Hello, world" deploy.
4. Then we'll iterate features one by one with /ssd feature.
Proceed? [y/N] You: y
Under the hood: I called auto-detect because you didn't type a phase. With
no active workstream, my decision tree proposed /ssd start. Teaching mode
is on for the first 5 invocations — you'll see one of these "under the
hood" lines after every step.
• Skill invoked:
ssd (v1.10.0) — no-arg auto-detect, then /ssd start.
• Artifact written:
.ssd/features/address-book/00-brief.md capturing your one-liner.
• Explicit equivalent:
/ssd start address-book — same effect, no auto-detect proposal step.
• Why: auto-detect lets a beginner stay in natural language. The orchestrator never silently advances — it always proposes; you accept or redirect. That's the rails in action.
Step 3: Walking Skeleton
Now the orchestrator runs the design pass. You'll see two artifacts get written, and you'll be asked to confirm each one before the orchestrator moves on.
Invoking /architect with brief: "simple web-based multimedia address book"
[architect output — full file at .ssd/features/address-book/01-architect.md]
Stack pick:
Language: Python 3.11+
Framework: Flask 3.x
Database: SQLite via stdlib sqlite3 (no install needed)
Templates: Jinja2 (comes with Flask)
Photos: stored as BLOB in the contacts table
Flag system: simple Python dict in flags.py
Data model (schema.sql):
CREATE TABLE contacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
phone TEXT,
email TEXT,
notes TEXT,
photo BLOB,
photo_mime TEXT,
created INTEGER NOT NULL
);
ADR-0001 (docs/decisions/ADR-0001-stack.md): why Flask + stdlib sqlite3
over Django, FastAPI, or sql.js. (Beginner ergonomics; smallest install
footprint; one-file app shape.)
API contract:
GET / — list contacts
GET /contact/<id> — detail page
POST /contact — create (form-encoded)
POST /contact/<id>/delete — delete
GET /photo/<id> — serve BLOB
Approve architect spec? [y/N] You: y
Invoking /systems-designer with the architect output as input.
[systems-designer output at .ssd/features/address-book/02-systems-designer.md]
Day-1 deploy plan:
Local dev: flask --app app run --debug (port 5000)
Production: gunicorn app:app behind nginx; SQLite on disk; SQLite-WAL
mode for concurrent reads.
Observability: standard Flask logging to stdout; structured JSON in prod.
Health: GET /health returns {"status":"ok"}
Failure modes considered:
- Photo BLOB grows DB beyond 1 GB → mitigation: cap upload at 5 MB.
- SQLite write contention → WAL mode + single-writer assumption.
- File-upload XSS → use Content-Disposition: attachment for /photo.
- CSRF on delete → Flask-WTF with secret key.
Approve readiness checklist? [y/N] You: y
Foundation phase complete. Now exiting to the Walking Skeleton's exit
gate: a Hello-World deploy. (Right after this, we'll go straight into
the first end-to-end slice in Step 4.)
$ python3 -m venv .venv
$ source .venv/bin/activate
(.venv) $ pip install flask flask-wtf gunicorn
(.venv) $ cat > app.py <<'EOF'
from flask import Flask
app = Flask(__name__)
@app.get("/")
def index(): return "Hello, address book."
@app.get("/health")
def health(): return {"status": "ok"}
EOF
(.venv) $ flask --app app run
* Running on http://127.0.0.1:5000
Open http://localhost:5000 in your browser.
Walking Skeleton complete. Phase: code → review → deploy in next iteration.
Open the browser. There's nothing useful yet — but there's a real Flask app running. You shipped Hello World on day one. That's the most important moment in any project.
• Skills invoked, in order:
architect → systems-designer → coder (for the Hello-World scaffold).
• Artifacts written:
01-architect.md, 02-systems-designer.md, 03-coder-status.md, plus committed docs/decisions/ADR-0001-stack.md.
• Explicit equivalents:
/ssd design address-book would have run architect + systems-designer back-to-back without the Hello-World scaffold step.
• Why: SSD's first principle is Constant Production Parity. Deploy "Hello, world" before any business logic exists, so deployment is never "the hard part" later.
Step 4: First Feature — Add a Contact
Your one-liner from Step 2 was the brief for the whole address book. The orchestrator broke it into features. The first one is "user can add a contact." Continue:
> (just press enter — auto-detect resumes)
Active workstream: address-book (phase: design, last touched 2 min ago).
Walking Skeleton is up. Next slice: "user can add a contact."
Proposed: /ssd feature add-contact
Proceed? [y/N] You: y
Running /ssd feature add-contact:
1. /architect — adds form spec, INSERT route, schema migration
2. /systems-designer — confirms migration is reversible, flag plan
3. /coder — writes routes/templates/migration
4. /code-reviewer — gate
5. Deploy to local
[architect spec excerpt — full at iterations/add-contact/01-architect.md]
Route: POST /contact
Form: name (required), phone, email, notes, photo (file)
Schema: already created by Walking Skeleton; nothing new
Flag: flags.ENABLE_ADD_CONTACT (default: True for dev, False for prod)
[coder writes app.py, templates/add.html, templates/_form.html.
Output at iterations/add-contact/03-coder-status.md.]
[code-reviewer runs on the diff. Output at .../04-code-review.md]
finding_counts: { blocker: 0, major: 0, minor: 1, suggestion: 2 }
gate_pass: true
MINOR-1: name field has no maxlength; consider 200 to match the column.
Gate PASSED. Deploying locally.
(.venv) $ flask --app app run
Running on http://127.0.0.1:5000 — open it.
You can now add a contact. The MINOR finding is logged but doesn't block.
Address it in the next pass or carry it forward.
The form works. Add yourself as the first contact. Shippable state, again.
• Phase loop:
architect → systems-designer → coder → code-reviewer → deploy.
• Artifacts written: five files under
.ssd/features/address-book/iterations/add-contact/.
• Explicit equivalents you could have run instead:
/ssd design address-book#add-contact, then /ssd code address-book#add-contact, then /ssd gate address-book#add-contact.
• Why: the feature loop is the daily rhythm of SSD. Each feature ships end-to-end before the next starts. The MINOR is captured but doesn't gate — only BLOCKER and MAJOR do.
Step 5: List + Detail + Delete
Same loop, faster, because the architect spec and stack are settled. You can use one prompt to roll up several small features:
> /ssd feature list-detail-delete: show contacts on /, click for detail,
detail page has a delete button.
Running feature loop. Output to
.ssd/features/address-book/iterations/list-detail-delete/.
[architect — adds GET /, GET /contact/<id>, POST /contact/<id>/delete]
[systems-designer — flags CSRF on delete; produces a follow-up note]
[coder — writes index.html, detail.html, delete handler]
[code-reviewer — gate]
finding_counts: { blocker: 0, major: 0, minor: 0 }
gate_pass: true
Refresh http://localhost:5000.
Now you have a real address book — minus photos. The MINOR from Step 4 is still in the carry-over ledger; we'll close it in Step 6.
• Multiple small UI features bundled into one workstream-iteration is fine. The orchestrator doesn't enforce one-feature-per-iteration; it does enforce a clean gate on whatever lands.
• Look in
.ssd/current.yml now — you'll see elapsed_hours ticking up and gate_rounds: 1 per iteration.
Step 6: Photos (Multimedia)
Now the multimedia part. Note: the database schema already has photo BLOB and photo_mime TEXT from the architect spec in Step 3. So this iteration is wiring the form, the BLOB INSERT, and the /photo/<id> serve route.
> /ssd feature photos: accept <input type="file"> on the add-contact
form, store as BLOB, serve at /photo/<id>, render <img> on detail page.
Cap uploads at 5 MB per the systems-designer plan.
[architect — confirms BLOB strategy from ADR-0001; specifies
werkzeug.utils.secure_filename; specifies max-content-length config]
[coder — adds upload handler, /photo route, max-bytes setting]
[code-reviewer — gate]
finding_counts: { blocker: 0, major: 0, minor: 1 }
gate_pass: true
MINOR-1: serve /photo with Content-Disposition: inline (current) is
correct for <img> rendering, but consider X-Content-Type-Options:
nosniff to prevent MIME-confusion XSS.
Carry-over ledger updated:
closed: name-maxlength (from add-contact iteration)
open: X-Content-Type-Options on /photo (this iteration's MINOR)
Refresh.
Your address book has photos. You built a multimedia app. The carry-over ledger keeps you honest about MINORs — they don't block ship, but they don't disappear either.
Step 7: A Round-2 Gate (When the Reviewer Says No)
So far every gate has passed. They won't always. Here's what happens when the code-reviewer finds something serious. Suppose you'd added the delete route without a CSRF token:
[code-reviewer round 1 — output at .ssd/features/address-book/
iterations/list-detail-delete/code-review/round-1.md]
finding_counts: { blocker: 1, major: 0, minor: 0 }
gate_pass: false
BLOCKER-1: POST /contact/<id>/delete has no CSRF protection.
A malicious page can submit this form on behalf of a logged-in user.
Doctrine: methodology/core.md §2 (deployable / no broken features).
Fix: wrap the form in {{ csrf_token() }} from Flask-WTF (already
in deps from systems-designer's day-1 plan).
Gate FAILED. Returning to coder.
[coder reads the BLOCKER, fixes the template, regenerates.]
> /ssd gate
[code-reviewer round 2 — auto-numbered to round-2.md, increments
current.yml.gate_rounds to 2.]
round: 2
closed_from_previous_round: [BLOCKER-1]
finding_counts: { blocker: 0, major: 0, minor: 0 }
gate_pass: true
Gate PASSED on round 2. Proceeding to deploy.
Two things to notice:
- The orchestrator refuses to merge or deploy until BLOCKERs are closed. Hard Rule #1 from the methodology.
- Round-2 is a separate file (
round-2.md) that explicitly lists which findings from round 1 closed. The reviewer verifies closure against the code itself, not against the coder's claim.current.yml.gate_roundstracks how many rounds it took — useful budget signal. If you hitgate_rounds: 3+on a small feature, the orchestrator will suggest scope cut.
closed_from_previous_round, the closure is auditable.Step 8: /ssd ship — Production Readiness
Local dev works. Now: what would it take to put this in front of real users? Run:
> /ssd ship
Invoking /systems-designer for production-readiness checklist.
[output at .ssd/features/address-book/iterations/.../05-deploy.md]
Production checklist:
✅ App runs locally
✅ Health endpoint /health responding
✅ Migration is reversible (just delete the .sqlite file in dev)
❌ Deployed to a public URL — not yet (out of scope for this tutorial)
❌ HTTPS termination — not yet
❌ Backup strategy for the SQLite file — not yet
❌ Production secrets management — not yet (Flask SECRET_KEY in env)
⚠ Error monitoring (Sentry) — recommended for production
Ready to ship to production? Not quite — the ❌ items are real work.
For local-only use, you're done. For real production deploy, see the
runbook at docs/runbooks/address-book-production.md (just generated).
That runbook covers fly.io, Railway, and self-hosted on a VPS.
This is where the tutorial honestly stops. Going from localhost:5000 to a public URL is real work — DNS, hosting, HTTPS, backup strategy. The orchestrator generated a runbook to help you, but the actual deploy is yours to do (and yours to pay for).
/ssd doesn't do: register domains, pay for hosting, manage your DNS, or guarantee uptime. It can scaffold the deploy, write the runbook, and tell you what's missing — but production is still a human decision.Step 9: /ssd milestone — A Glimpse Forward
You don't need this yet — milestones run after 4-8 weeks or 10+ features land. But here's what it looks like, so you know when to reach for it:
> /ssd milestone
Snapshot:
git SHA → .ssd/milestones/YYYY-MM-DD-q2/sha-before
Coverage / metrics → metrics-before.yml
Invoking /codebase-skeptic — full architectural critique across 10 expert
voices. Output: .ssd/milestones/YYYY-MM-DD-q2/skeptic-before.md
Voices activated: Fowler (refactoring), Uncle Bob (clean code), Evans
(DDD), Beck (TDD), Humble (CD), Hickey (simplicity), Knuth, Norman
(UX), Wozniak (engineering elegance), Jobs (product clarity).
Findings: 12 architectural concerns; 4 high-priority for refactor.
Invoking /refactor with skeptic-before.md as input.
Output: .ssd/milestones/YYYY-MM-DD-q2/refactor-plan.md
Each item cites a finding ID. No cite → not in scope.
After refactor PRs ship and merge:
/ssd verify (mandatory)
Re-runs codebase-skeptic to confirm findings closed.
Output: skeptic-after.md, verification.md.
Milestone is complete only when verification passes.
Milestones are not for everyday work. They're the consolidation pass that keeps an SSD project from drifting into spaghetti over months. Run them on a calendar, not a feature trigger.
Command Map
Every /ssd phase command, what it does, and when auto-detect would propose it for you. You almost never need to type these explicitly — but knowing they exist helps you read the orchestrator's "Behind the scenes" lines.
| Command | What it does | Auto-detect would propose when… |
|---|---|---|
/ssd-init | One-time project bootstrap | Always — it's a prerequisite, not auto-proposed |
/ssd (no args) | Read state, propose next action | Default. Use this most of the time. |
/ssd start <slug> | Walking Skeleton for new project / major feature | Empty repo, no active workstreams |
/ssd feature <slug> | Full feature loop end-to-end | Active workstream, phase=brief or design |
/ssd design <slug> | Architect + systems-designer only | You want a spec without committing to build yet |
/ssd code <slug> | Coder only, from existing spec | phase=design, spec approved |
/ssd gate <slug> | Code-reviewer + methodology rules | phase=code, ready to verify |
/ssd ship <slug> | Production-readiness checklist | Gate passed, ready to deploy |
/ssd milestone | Deep audit + refactor planning | 4-8 weeks since last; never auto-proposed mid-feature |
/ssd verify | Re-audit after milestone refactor | Mandatory after a milestone refactor lands |
/ssd audit | Adversarial comparative review | Never auto-proposed; nuclear option you reach for |
Append #<iter-id> to any command to operate on a specific iteration of a multi-iteration feature: /ssd code address-book#3a.
What Got Created
After this tutorial your project looks roughly like:
address-book/
├── .git/
├── .gitignore # contains .ssd/ and .venv/
├── .venv/ # Python virtualenv (gitignored)
├── .ssd/ # SSD working dir (gitignored)
│ ├── project.yml
│ ├── current.yml
│ ├── current.notes.yml
│ ├── init-log.md
│ └── features/
│ └── address-book/
│ ├── 00-brief.md
│ ├── 01-architect.md
│ ├── 02-systems-designer.md
│ ├── 03-coder-status.md
│ ├── 04-code-review.md
│ ├── 05-deploy.md
│ └── iterations/
│ ├── add-contact/ # step 4
│ │ ├── brief.md
│ │ ├── 01-architect.md
│ │ ├── 03-coder-status.md
│ │ ├── 04-code-review.md
│ │ └── 05-deploy.md
│ ├── list-detail-delete/ # step 5 (with round-2 gate)
│ │ ├── brief.md
│ │ ├── 03-coder-status.md
│ │ ├── code-review/
│ │ │ ├── round-1.md # gate_pass: false
│ │ │ └── round-2.md # gate_pass: true
│ │ └── 05-deploy.md
│ └── photos/ # step 6
│ └── …
├── docs/ # committed decision records
│ ├── decisions/
│ │ └── ADR-0001-stack.md
│ ├── runbooks/
│ │ └── address-book-production.md
│ └── architecture/
├── app.py # the actual Flask app
├── templates/
├── static/
├── flags.py # feature-flag dict
├── schema.sql
└── requirements.txt
Two important things about that tree:
- Everything under
.ssd/is gitignored. It's working state, not source. Your repo stays clean. - Everything under
docs/is committed. ADRs, runbooks, architecture diagrams — durable decisions that future you (or future teammates) need.
Where to Go Next
- Try it on a real project. Open Claude Code in any folder, run
/ssd-init, type a one-liner. The orchestrator will figure out the stack from what's already there. - Read the Guide. Every doctrine principle this tutorial showed in action — Production Parity, Walking Skeleton, multi-round gates, the rails — is documented in full there.
- Customize the rails. If your team's canonical sequence differs from the default eight steps, fork
~/.claude/skills/ssd/rails.mdand point.ssd/project.ymlat the fork. See ADR-0003 for why. - Promote yourself. Once you're comfortable, change
developer_profilefromnovicetostandardin.ssd/project.yml. The orchestrator stops asking before every shell command and gets terser.expertturns off teaching mode entirely. - Read the SSD methodology page. The discipline behind the tooling — without it, the orchestrator is just a code generator.