Spec-Driven Development

source-of-truth

A self-maintaining docs/ catalog the AI must read before touching code and update after shipping. The catalog owns what & why; code owns how.

πŸ—’οΈ Plain English: it's a shared notebook for your project. Any AI session β€” today's or next month's β€” opens the notebook first, so it never rebuilds, breaks, or forgets what's already there.
READ β†’ before coding SYNC β†’ after shipping BOOTSTRAP β†’ run once

The whole idea in one picture

Every change goes through the same loop

You ask "add feature X" READ check docs/ first STOP on conflict Write code only after checks pass SYNC update docs/ from the diff you shipped docs/ stays true β†’ next session trusts it BOOTSTRAP run once β€” creates docs/

Problem

Four ways AI quietly wrecks a codebase

Without a reference the AI trusts, long-running projects hit these four failures. Each one is blocked by a specific part of the catalog.

Failure 1Rebuilds what already exists

Session #12 doesn't remember session #3 β€” so it writes a second, slightly different version.

Blocked by READ β†’ "Already exists?"
Failure 2Silently deletes a feature

A refactor drops a capability nobody intended to remove β€” noticed weeks later.

Blocked by specs + changelog
Failure 3Breaks a contract

Changes behavior without knowing what callers depend on ("improves" a 404 into a 200).

Blocked by Invariants in each spec
Failure 4Drifts off-track

Builds something that violates project principles or was never planned.

Blocked by constitution + roadmap gates

Operation Β· interactive

Three modes, selected automatically

β–Έ Click a situation to see which mode fires:

No mode picker, no flags β€” the skill infers the mode from what's on disk and what you just said.

Run once

BOOTSTRAP

docs/overview.md not yet present
  1. Auto-detect stack, test runner, design system (no prompts)
  2. One interview pass: quality rules, perf budget, mission
  3. Confirm β†’ write catalog + update CLAUDE.md
Before editing code

READ

write / modify / delete β€” bug fixes included
  1. Read overview β†’ constitution β†’ roadmap β†’ spec + check the record folders
  2. Print the "Catalog check" for you to review
  3. STOP on conflict; proceed only after you approve
After shipping

SYNC

"done" Β· "merge" Β· "shipped" Β· before commit
  1. Read the diff (not the whole repo) β†’ classify changes
  2. Update specs, remove shipped entries from roadmap
  3. Record removals / renames / contract changes as changelog entry files + decision/debugging records when triggered
RE-BOOTSTRAP: catalog exists but constitution.md / mission.md missing β†’ rebuild that file before any code edit. Updating CLAUDE.md during BOOTSTRAP is the highest-leverage step: every future session then reads the catalog automatically.

Mechanism Β· interactive

Gate chain: each layer guards the one below

β–Έ Click any layer in the diagram to see what it does:

Gate chain Five stacked layers β€” constitution, mission, roadmap, specs, code β€” where each layer gates the one below. Click a layer for its explanation. READ TOP-DOWN Β· WHY a line of code exists READ BOTTOM-UP Β· HOW FAR a change reaches gates ↓ gates ↓ gates ↓ gates ↓ Constitution principles Β· stack Β· testing Β· performance constitution.md Mission problem Β· users Β· value mission.md Roadmap Now / Next / Later roadmap.md Specs Plan Β· Requirement Β· Validation spec-<feature>.md Code the only place that says β€œhow” src/…

constitution.md β€” the rules of the house

Answers: β€œAre we allowed to build it this way?”

Example: constitution says "stateless API tier" β†’ a session-based login plan is rejected at the gate; JWT gets chosen instead. Changing this file requires your explicit approval + a changelog entry + a decision record.

mission.md β€” why the project exists

Answers: β€œDoes this actually serve our users?”

Example: problem, target users, value proposition, success metrics. A feature idea that serves nobody in Users shouldn't reach the roadmap. Content comes from you β€” never invented by the AI.

roadmap.md β€” what we're doing, in order

Answers: β€œIs this planned β€” Now, Next, or Later?”

Example: work not on the roadmap β†’ NOT TRACKED β†’ STOP, add a one-line entry first (takes seconds). Shipped entries leave the roadmap β€” it shrinks as you ship.

spec-<feature>.md β€” the contract of each feature

Answers: β€œWhat must not break when we touch this?”

Example: "wrong password β†’ 401 with empty body". Any change that breaks a documented invariant stops for your confirmation and gets a changelog entry.

src/ β€” code, the only place that says β€œhow”

Answers: β€œHow is it implemented?”

Example: algorithms, libraries, file structure live here only. If a spec starts describing how (bcrypt, Redis, class names), that content is in the wrong layer β€” it belongs in code or spec Notes.

principles gate plans β†’ mission gates roadmap β†’ roadmap gates specs β†’ specs gate code.

Live demo Β· interactive

Watch READ mode stop a duplicate feature

A real session, step by step. You ask for something that already exists β€” see how the catalog catches it before a single line is written.

claude β€” READ modedemo session
$ you: Add an endpoint to search users by their email address. claude: Before touching code I'll check the catalog. (READ mode fires β€” automatic, code edit requested)
claude: Reading the catalog… βœ“ docs/overview.md β†’ index lists a feature: email-search βœ“ docs/constitution.md β†’ stack: NestJS + PostgreSQL, no conflicts βœ“ docs/roadmap.md β†’ not in Now/Next/Later… because it already shipped
claude: Reading the matching spec… βœ“ docs/specs/spec-email-search.md Surface: GET /api/users/search?email= Invariants: case-insensitive match Β· soft-deleted users excluded Β· 404 when not found (never 200 + null)
claude: Catalog check: - Constitution: no conflict - Roadmap status: shipped (off-roadmap) - Invariants: 3 documented contracts - Already exists? YES β†’ spec-email-search STOP. Feature already exists at docs/specs/spec-email-search.md. Modify it, or is this genuinely different?
$ you: Oh right β€” just add fuzzy matching to the existing one. claude: Adding email-search-fuzzy to roadmap `Now`, then extending the existing endpoint. All 3 invariants preserved.
Outcome: zero duplicate code written, existing contracts intact, roadmap still true. Without the catalog you'd now have two search endpoints slowly drifting apart.

The 5 STOP rules of READ mode

SituationResponse
Feature already existsSTOP. "Already exists at <path>. Modify it, or is this genuinely different?"
Breaks a documented invariant"Breaks invariant X. Confirm (goes in the changelog) or rethink?"
Violates the constitution"Conflicts with <principle>. Update the constitution first, or change approach."
Not on the roadmapSTOP. Add a roadmap entry (Now/Next) first β€” unless you explicitly override.
Spec points to a missing file"Spec X references a missing file β€” sync first?" Never silently self-corrects.

Structure Β· interactive

Four docs, four folders. That's the whole system.

β–Έ Click any file in the tree to preview its exact format:

docs/click a file
  • docs/
  • β”œβ”€ # index β€” points to everything
  • β”œβ”€ # principles & tech stack
  • β”œβ”€ # why the project exists
  • β”œβ”€ # Now / Next / Later
  • β”œβ”€ changelog/ # one file per entry β€” no conflicts
  • β”‚   β””─ # removals Β· renames Β· contracts
  • β”œβ”€ decisions/ # why we chose it β€” created on first entry
  • β”œβ”€ debugging/ # how we found that bug β€” created on first entry
  • └─ specs/
  •    β””─ # one per feature
docs/overview.mdpreview
# Spec catalog β€” my-project

## Project docs
- [Constitution](constitution.md) β€” principles, stack
- [Mission](mission.md) β€” problem, users, value
- [Roadmap](roadmap.md) β€” delivery plan
- [Changelog](changelog/) β€” removals, renames
- [Decisions](decisions/) β€” why, across features
- [Debugging](debugging/) β€” root causes worth keeping

## Feature specs
- [jwt-authentication](specs/…) β€” JWT + refresh
- [email-search](specs/…) β€” search by email

# summaries only β€” never a detail dump.
# touched only when a feature is added /
# removed / renamed, or a record folder's
# first entry.
# Constitution
_Changes require user approval._

## Tech Stack
- Language: TypeScript on Node 22
- Framework: NestJS 10 Β· DB: PostgreSQL 16
## Code Quality
- No `any` types in new code
## Testing Standards
- β‰₯80% coverage on src/, blocking
## UX Consistency  Β·  ## Performance
- _TBD: p95 latency budget?_ ← unknown =
  marked, never invented
# Mission

## Problem
Ops teams juggle 6 dashboards to answer
one question.
## Users
- On-call engineer: needs answers in <1 min
## Value proposition
One query instead of six dashboards.
## Success metrics
- Time-to-answer < 60s

# comes from YOU in the bootstrap
# interview. AI never fabricates this.
# Roadmap
_Forward plan only β€” shipped work leaves._

## Now   (≀3 in flight, each has a spec)
- [ ] email-search-fuzzy β€” fuzzy match
      β€” [spec](…) β€” _started 2026-07-10_
## Next  (spec'd, queued)
- [ ] rate-limiting β€” per-key limits β€” [spec](…)
## Later (idea pool, no spec yet)
- [ ] sso-login β€” SAML for enterprise

# one line per entry. detail β†’ the spec.
# one flat file per (date, feature) β€”
# parallel sessions never merge-conflict.
# no title / date heading: the filename
# carries both.

### Contract changed
- email-search β€” Old: 200+null on miss.
  New: 404. Migration: handle 404.

# sibling: 2026-07-08-csv-export.md (### Removed)
# answers ONE question: "was this removed
# on purpose, or did the AI forget it?"
### Feature: email-search
- Status: active Β· Roadmap: shipped
- Source files: src/users/search.ts
- Last verified: 2026-07-08 @ 8cb70b0

## Plan       # why + chosen approach
## Requirement # surface + invariants
- Invariants: case-insensitive Β·
  soft-deleted excluded Β· 404 on miss
## Validation # checkable by reading
- Given unknown email β†’ 404.

# full anatomy in the next section ↓

Heart of the catalog

Anatomy of a spec

One spec-<feature>.md per feature: header + 3 sections (+ optional Notes). Goal: someone who has never seen the code understands why it exists and which contracts must not break.

docs/specs/spec-jwt-authentication.md
### Feature: jwt-authentication
- Status: active   Roadmap: shipped
- Last verified: 2026-04-30 @ a1b2c3d

## Plan          # why + approach
JWT chosen over sessions β€” constitution requires
a stateless API tier. Non-goal: SSO.

## Requirement
- Surface: POST /api/auth/login Β· /refresh
- Invariants:
  - Wrong password β†’ 401 empty body
  - Email comparison is case-insensitive
  - WHEN /refresh with a valid token
    THEN the old token is revoked
    AND a new token is issued in the same transaction

## Validation      # verifiable by reading alone
- Given wrong password β†’ 401 empty body.
- Given refresh token reused β†’ 401.

## Notes         # gotchas code doesn't document
Do not cache decoded JWTs β€” revocation requires
a DB lookup on every request.

Quiz: Invariant, or implementation detail?

Invariants = only what a caller can observe without reading source. This is the #1 thing people get wrong β€” try it:

Question 1 / 6Score 0

Litmus test: if rewriting the code in another language would change the bullet β†’ it's implementation detail, move it to Notes. And if you can't prove it from code or tests, don't write it.
Progressive rigor: a short spec is right for ~80% of features. Expand to WHEN/THEN/AND only when: a past regression Β· race/ordering concern Β· compliance Β· β‰₯2 clients Β· an AI misread it before. Length is not quality. Every Validation criterion must trace to an Invariants bullet.

SYNC mode

After shipping: reconcile the diff β†’ update the catalog

SYNC reads the diff (never the whole repo), classifies each change, and shows you the catalog diff before writing anything.

Change typeCatalog action
New featureCreate spec-<name>.md + remove from roadmap + add to overview
Feature updateUpdate spec; Requirements changed β†’ changelog entry
Remove featureStatus: removed + changelog ### Removed
Rename featureRename spec file + roadmap + changelog ### Renamed
Internal refactorUpdate Source files if files moved β€” don't touch overview/roadmap, unless it embodies a cross-cutting decision β†’ decision record (can touch overview via its first-entry link)
Tech stack changeSurface to you β†’ constitution + changelog only after you confirm
scripts/sync_helpers.shbash
# changed files + commit hash + today's date
bash scripts/sync_helpers.sh diff
# pre-formatted "Last verified" line for a spec
bash scripts/sync_helpers.sh stamp
# find specs pointing to missing files
bash scripts/sync_helpers.sh stale
Commit gate: asking to commit is itself a SYNC trigger β€” SYNC completes before the commit so code and catalog land together. Automatic, not a question.
The changelog (one file per entry) records only 4 event types: Removed Β· Renamed Β· Contract changed Β· Constitution change. It answers one question: "was this removed intentionally, or did the AI forget it?" New features and safe refactors don't need an entry.

Hard rule

Iron-rule: every unit of work gets a roadmap entry

Every change β€” feature, refactor, and bug fix β€” gets a one-line roadmap.md entry before code is written. It exists to stop the AI from rationalizing "too small to record" β€” untracked small changes are exactly how the roadmap drifts and the gates decay. It is not a way to overrule you: you own the project.

βœ— Violation
  • AI skips it unilaterally because "it seems minor"
  • Silently shipping something not on the roadmap
  • Treating urgency as permission to skip
βœ“ Valid
  • Add the entry (takes seconds), then move fast
  • You, knowing the rule, explicitly override (e.g. live hotfix)
  • β†’ proceed + retroactive roadmap/changelog entry after

Discipline

No fabrication Β· No full-repo reads Β· No silent edits

An AI-maintained catalog is only trustworthy if it refuses to fill in blanks. These are the red flags the skill self-enforces.

Reading the entire codebase. READ reads catalog files; SYNC reads the diff; BOOTSTRAP caps at ~15 files (per package in a monorepo).
Fabricating invariants. Not visible in code or tests β†’ don't assert it. Tests are the best source of invariants.
Inventing constitution/mission content. Must come from you; _TBD:_ is fine, fabrication is not.
Updating overview for every change. It's an index β€” touch it only on add / remove / rename, or a record folder's first entry.
Silently editing the constitution. Stack changes need your confirmation; principle changes need an explicit request.
Skipping READ for a "simple" bug fix. Bug fixes break invariants more often than features do.
Slug naming: lowercase kebab-case verb-noun (jwt-authentication, email-search) Β· flat namespace Β· no abstract names (util, core) Β· no bare nouns (user β†’ user-creation).

Where it fits

Works well with superpowers & spec-kit

source-of-truth is a catalog, not a workflow engine. The methodology produces the work; the catalog keeps the source of truth honest across sessions.

superpowersDrives how you work β€” brainstorming β†’ planning β†’ TDD β†’ review. Each cycle READs the catalog before coding and SYNCs after shipping.
spec-kitGenerates specs & plans up front. source-of-truth turns those one-off specs into a living catalog that stays in sync as the project evolves.