Chapter 23: Repository Architecture for SDD
Learning Objectives
By the end of this chapter, you will be able to:
- Describe the canonical SDD repository structure and the purpose of each directory
- Explain why separating intent (specs/) from implementation (src/) matters
- Organize specifications into global, feature, constraint, and template categories
- Use the memory/ directory for constitution, ADRs, and context files
- Apply feature branch workflow with specs/[branch-name]/ structure
- Version control specifications alongside code effectively
- Set up a complete SDD repository from scratch through a hands-on tutorial
- Handle monorepo considerations: shared specs and package-level constraints
- Migrate an existing project to SDD structure using a step-by-step guide
The SDD Repository Structure
A Spec-Driven Development repository is not a conventional codebase with specs bolted on. It is a specification-first structure where intent drives implementation. The layout reflects this hierarchy: specifications are the source of truth; code is derived from them.
The Canonical Layout
repo/
├── specs/ # Specifications — the source of truth
│ ├── global/ # Project-wide specs
│ ├── constraints/ # Constraint documents
│ ├── templates/ # Specification templates
│ └── features/ # Feature specifications (or specs/[branch-name]/)
├── agents/ # AGENTS.md, skills, subagents
├── .cursor/rules/ # Editor configuration
├── src/ # Generated/implementation code
├── tests/ # Test suites
└── memory/ # Constitution, ADRs, context files
Each directory has a distinct role. Understanding them is the first step to building a maintainable SDD project.
Separating Intent from Implementation
The fundamental principle of SDD repository architecture is separation of intent from implementation.
Intent (specs/)
Intent lives in specs/. It answers WHAT and WHY:
- What does the system do? (user stories, requirements, acceptance criteria)
- Why does it do it? (problem statement, goals, non-goals)
- What does it NOT do? (edge cases, constraints, boundaries)
Intent is stable. It changes when requirements change, not when implementation details change. A specification for "user registration" remains valid whether you implement it with PostgreSQL or MongoDB, REST or GraphQL.
Implementation (src/)
Implementation lives in src/. It answers HOW:
- How is the feature built? (code, data structures, algorithms)
- How does it integrate? (APIs, dependencies, deployment)
Implementation is derived from intent. When you refactor code, the specification does not change. When you change the specification, the code must change to match.
Why Separation Matters
Without separation:
- Requirements drift into code comments
- "Why did we do it this way?" becomes unanswerable
- Refactoring risks breaking undocumented assumptions
- AI and humans lack a single source of truth
With separation:
- Specifications are the contract. Code is the fulfillment.
- Traceability is explicit: every requirement maps to acceptance criteria, every acceptance criterion maps to tests, every test maps to code.
- AI can generate correct code without guessing—it reads the spec.
- Onboarding and handoffs are faster: read the spec first.
The specs/ Directory Organization
The specs/ directory is the heart of the repository. It contains multiple subdirectories, each serving a purpose.
Global Specs (specs/global/)
Project-wide specifications that apply across all features:
| File | Purpose |
|---|---|
project-overview.md | High-level vision, goals, scope |
user-personas.md | Target users and their needs |
glossary.md | Domain terms and definitions |
non-functional-requirements.md | Performance, security, compliance (global) |
Example: specs/global/project-overview.md
# Project Overview: TaskFlow
## Vision
TaskFlow is a productivity app for teams who need lightweight task management
without the overhead of enterprise tools.
## Goals
- Deliver core task CRUD in under 2 seconds
- Support 10,000 concurrent users on a single instance
- Mobile-first design (60% of users on mobile)
## Non-Goals (Out of Scope)
- Gantt charts and resource planning
- Custom workflows beyond status transitions
- Third-party integrations (Phase 1)
Constraint Documents (specs/constraints/)
Constraints that constrain all implementation:
| File | Purpose |
|---|---|
security.md | Authentication, authorization, data protection |
performance.md | Latency, throughput, resource limits |
api-conventions.md | REST/GraphQL patterns, error formats |
data-integrity.md | Validation rules, constraints, invariants |
Example: specs/constraints/security.md
# Security Constraints
## Authentication
- All API endpoints require JWT (except /health, /login)
- Tokens: RS256, 15 min access, 7 day refresh
- No passwords in logs or error messages
## Authorization
- RBAC: admin, member, viewer
- Resource-level: user can only access own resources unless admin
## Data
- PII: encrypt at rest (AES-256)
- Passwords: bcrypt cost 12, no plaintext storage
Templates (specs/templates/)
Reusable specification templates for new features:
| File | Purpose |
|---|---|
feature-spec-template.md | Template for feature specifications |
plan-template.md | Template for implementation plans |
api-contract-template.yaml | OpenAPI/contract template |
Example: specs/templates/feature-spec-template.md
# Feature: {{FEATURE_NAME}}
## Problem
[What problem does this solve?]
## User Stories
- As a [role], I want [goal] so that [benefit]
## Requirements
- FR-001: [Requirement]
- FR-002: [Requirement]
## Acceptance Criteria
- AC-001: [Testable criterion]
- AC-002: [Testable criterion]
## Edge Cases
- [List edge cases]
## Non-Goals
- [What we explicitly NOT do]
## Completeness Checklist
- [ ] All [NEEDS CLARIFICATION] resolved
- [ ] Every requirement has acceptance criteria
Feature Specs (specs/features/ or specs/[branch-name]/)
Feature-specific specifications. Two naming conventions:
Option A: specs/features/
specs/features/
├── 001-user-registration/
│ ├── spec.md
│ ├── plan.md
│ ├── data-model.md
│ ├── contracts/
│ └── tasks.md
├── 002-login/
│ └── ...
└── 003-password-reset/
└── ...
Option B: specs/[branch-name]/ (feature branch workflow)
specs/
├── 001-user-registration/
│ ├── spec.md
│ ├── plan.md
│ └── ...
├── 002-login/
│ └── ...
└── 004-real-time-chat/
└── ...
The branch-name convention aligns with Git: git checkout -b 004-real-time-chat creates a branch; specs/004-real-time-chat/ holds that feature's specs. When merging, specs merge with code.
The memory/ Directory
The memory/ directory stores project identity and institutional knowledge. It is the "long-term memory" of the project.
constitution.md
The project constitution defines principles that AI and humans must follow. It is always loaded when relevant.
Example: memory/constitution.md
# Project Constitution: TaskFlow
## Principles
1. **Simplicity First**: Prefer the simplest solution. No premature abstraction.
2. **Test-Driven**: Every feature has tests. Tests before implementation.
3. **Fail Fast**: Validate inputs at boundaries. Fail early with clear errors.
4. **No Secrets in Code**: No API keys, passwords, or tokens in source.
5. **Convention Over Configuration**: Use project conventions unless there's a reason not to.
## Architecture
- Layered: API → Service → Repository → Entity
- Database: PostgreSQL, migrations via Flyway
- API: REST, JSON, OpenAPI 3.0
## Constraints
- No external API calls in repositories (use services)
- No business logic in controllers (use services)
- All errors must be logged with context
Architectural Decision Records (ADRs)
ADRs capture why decisions were made. They live in memory/adr/.
Example: memory/adr/0001-use-postgresql.md
# ADR 0001: Use PostgreSQL for Primary Storage
## Status
Accepted
## Context
We need a relational database for task management. Options: PostgreSQL, MySQL, SQLite.
## Decision
Use PostgreSQL.
## Rationale
- JSONB support for flexible schema evolution
- Strong ACID guarantees
- Excellent ecosystem (pg, Prisma, etc.)
- Team familiarity
## Consequences
- Requires PostgreSQL in deployment
- Migrations via Flyway
Context Files
Context files are checkpoint documents, cross-session summaries, or other artifacts that help restore context:
memory/checkpoints/— Session checkpointsmemory/context/— Domain summaries, integration notesmemory/phr/— Prompt History Records (effective prompts)
Feature Branch Workflow: specs/[branch-name]/
The feature branch workflow aligns specifications with Git branches.
How It Works
-
Create branch:
git checkout -b 004-real-time-chat -
Create spec directory:
specs/004-real-time-chat/(orspecs/features/004-real-time-chat/) -
Run Spec Kit:
/speckit.specify→spec.md -
Run
/speckit.plan→plan.md,data-model.md,contracts/ -
Run
/speckit.tasks→tasks.md -
Implement: Execute tasks, commit code and specs together
-
Merge: Merge branch; specs merge with code
Benefits
- Traceability: Specs and code live in the same branch
- Review: PRs include both spec changes and implementation
- Isolation: No spec pollution from other features
- Rollback: Revert branch = revert spec + code
Directory Layout per Feature
specs/004-real-time-chat/
├── spec.md
├── plan.md
├── data-model.md
├── contracts/
│ ├── chat-api.yaml
│ └── websocket-events.yaml
├── research.md
├── quickstart.md
└── tasks.md
Version Controlling Specifications
Specifications should be version controlled alongside code.
What to Commit
- All spec files:
spec.md,plan.md,data-model.md,contracts/,tasks.md - Templates:
specs/templates/ - Global specs:
specs/global/ - Constraints:
specs/constraints/ - Memory:
memory/constitution.md,memory/adr/
What to Ignore (Optional)
memory/checkpoints/— Session-specific; may be ephemeralresearch.mddrafts — If they contain sensitive or temporary notes
Commit Message Conventions
feat(004): add spec for real-time chat
feat(004): add implementation plan
feat(004): T-001 ChatRoom entity
fix(004): correct message schema in contract
Branching Strategy
- main: Production-ready code + specs
- feature branches: specs/[branch-name]/ + implementation
- Spec changes: Commit with code; avoid spec-only branches unless doing design review
Tutorial: Set Up a Complete SDD Repository from Scratch
This tutorial walks you through creating an SDD repository from an empty directory.
Prerequisites
- Git installed
- A code editor (Cursor or VS Code)
- Basic familiarity with spec-driven workflow
Step 1: Initialize Project
mkdir taskflow-app
cd taskflow-app
git init
Step 2: Create Directory Structure
mkdir -p specs/global specs/constraints specs/templates specs/features
mkdir -p agents
mkdir -p .cursor/rules
mkdir -p src tests
mkdir -p memory/adr memory/checkpoints memory/context
Step 3: Create Global Specs
Create specs/global/project-overview.md:
# Project Overview: TaskFlow
## Vision
TaskFlow is a lightweight task management app for small teams.
## Goals
- Simple task CRUD
- Real-time updates
- Mobile-first UI
## Non-Goals
- Enterprise SSO
- Gantt charts
Create specs/global/glossary.md:
# Glossary
- **Task**: A unit of work with title, description, status, assignee
- **Board**: A collection of tasks (e.g., "Sprint 1")
- **Status**: todo | in-progress | done
Step 4: Create Constraints
Create specs/constraints/security.md:
# Security Constraints
- JWT for all API auth
- No passwords in logs
- HTTPS only in production
Create specs/constraints/api-conventions.md:
# API Conventions
- REST, JSON
- Errors: { "error": { "code": "ERR_001", "message": "..." } }
- Pagination: ?limit=20&offset=0
Step 5: Create Feature Spec Template
Create specs/templates/feature-spec-template.md (use the template from earlier in this chapter).
Step 6: Create Constitution
Create memory/constitution.md:
# Constitution: TaskFlow
## Principles
1. Simplicity first
2. No secrets in code
3. Test-driven
## Stack
- Node.js + Express
- PostgreSQL
- REST API
Step 7: Create First Feature Spec
mkdir -p specs/001-task-crud
Create specs/001-task-crud/spec.md:
# Feature: Task CRUD
## Problem
Users need to create, read, update, and delete tasks.
## User Stories
- As a user, I want to create a task so I can track work
- As a user, I want to list my tasks so I can see what's pending
## Requirements
- FR-001: Create task with title, description, status
- FR-002: List tasks with pagination
## Acceptance Criteria
- AC-001: POST /tasks returns 201 with task
- AC-002: GET /tasks returns paginated list
Step 8: Create .cursor/rules
Create .cursor/rules/sdd.mdc:
---
description: SDD project rules
globs: ["**/*.ts", "**/*.js"]
---
- Follow specs in specs/ for all implementation
- Use constitution in memory/constitution.md
- Write tests for every feature
Step 9: Create AGENTS.md
Create agents/AGENTS.md:
# TaskFlow Agents
## Default Agent
- Follows specs/ and memory/constitution.md
- Implements tasks from tasks.md
- Uses .cursor/rules for coding standards
Step 10: Verify Structure
tree -L 3 -I node_modules
Expected output:
.
├── agents
│ └── AGENTS.md
├── memory
│ ├── adr
│ ├── checkpoints
│ ├── context
│ └── constitution.md
├── specs
│ ├── constraints
│ │ ├── api-conventions.md
│ │ └── security.md
│ ├── features
│ │ └── 001-task-crud
│ │ └── spec.md
│ ├── global
│ │ ├── glossary.md
│ │ └── project-overview.md
│ └── templates
│ └── feature-spec-template.md
├── src
├── tests
└── .cursor
└── rules
└── sdd.mdc
Step 11: Commit
git add .
git commit -m "chore: initialize SDD repository structure"
Monorepo Considerations
In a monorepo (multiple packages), SDD structure adapts.
Option A: Shared Specs at Root
monorepo/
├── specs/
│ ├── global/
│ ├── constraints/
│ └── features/
├── packages/
│ ├── api/
│ │ ├── src/
│ │ └── tests/
│ ├── web/
│ │ ├── src/
│ │ └── tests/
│ └── shared/
│ └── src/
└── memory/
Shared specs apply to all packages. Feature specs may reference which package(s) they affect.
Option B: Package-Level Specs
monorepo/
├── packages/
│ ├── api/
│ │ ├── specs/
│ │ ├── src/
│ │ └── tests/
│ └── web/
│ ├── specs/
│ ├── src/
│ └── tests/
└── specs/
└── global/
Shared specs at root; package-specific specs in each package. Use when packages have distinct domains.
Package-Level Constraints
Create packages/api/specs/constraints/ for API-specific constraints. Reference global constraints in package constraints:
# API Package Constraints
- Inherit: ../specs/global/security.md
- Inherit: ../specs/global/api-conventions.md
- Package-specific: Use Express; no NestJS
Migration Guide: Restructuring an Existing Project for SDD
Migrating an existing project to SDD structure is incremental.
Phase 1: Add Structure (No Code Changes)
- Create
specs/,memory/,agents/directories - Create
memory/constitution.md— extract principles from existing codebase - Create
specs/global/project-overview.md— document current vision - Create
specs/constraints/— add security, API conventions from existing patterns - Add
.cursor/rules/with basic rules
Commit: "chore: add SDD directory structure"
Phase 2: Document One Feature
- Choose a well-understood feature (e.g., user login)
- Create
specs/features/001-login/spec.md— write spec from existing behavior - Create
specs/features/001-login/plan.md— document current architecture - Add
memory/adr/for key decisions (e.g., "Why JWT?")
Commit: "docs: add spec for login feature"
Phase 3: New Features Use SDD
- For all new features, use specs-first workflow
- Create spec before implementation
- Run Spec Kit commands if available
- Keep existing features as-is until they need changes
Phase 4: Retrofit Existing Features (Optional)
- When touching an existing feature, add specs first
- Refactor to match spec
- Gradually bring all features under spec coverage
Migration Checklist
- Directory structure created
- Constitution written
- At least one spec documented
- .cursor/rules configured
- AGENTS.md created (if using agents)
- Team trained on new workflow
Anti-Patterns to Avoid
Anti-Pattern 1: Specs in Code Comments
Bad: Requirements scattered in JSDoc or README
Good: Requirements in specs/; code references spec IDs
Anti-Pattern 2: No Separation
Bad: Implementation details in spec ("Use PostgreSQL with connection pool of 10")
Good: Spec says "persistent storage"; plan says "PostgreSQL"
Anti-Pattern 3: Orphan Specs
Bad: Specs that don't match code; no traceability
Good: Every spec has corresponding implementation; tasks link to both
Anti-Pattern 4: Flat specs/
Bad: All specs in one folder with no organization
Good: global/, constraints/, templates/, features/ (or branch-name/)
Anti-Pattern 5: Memory in Code
Bad: "Why we did this" in code comments
Good: ADRs in memory/adr/
Try With AI
Prompt 1: Structure Audit
"I have a project at [path]. Analyze the current structure and list what's missing for SDD: (1) specs directory organization, (2) memory directory, (3) separation of intent vs implementation. Propose a concrete migration plan."
Prompt 2: Constitution from Codebase
"Review my codebase at [path]. Extract the implicit principles, conventions, and constraints. Draft a memory/constitution.md that captures what the codebase already follows. I'll refine it."
Prompt 3: Spec from Existing Feature
"I have an existing feature [describe]. I need a spec that documents current behavior. Create specs/features/[name]/spec.md with problem, user stories, requirements, acceptance criteria. Base it on the actual code."
Prompt 4: Migration Checklist
"I'm migrating [project] to SDD. Create a step-by-step migration checklist tailored to my project structure. Include: directory creation, file templates, and order of operations. No code changes yet—structure only."
Practice Exercises
Exercise 1: Create SDD Structure from Scratch
Create a new directory. Set up the full SDD structure (specs, memory, agents, .cursor/rules). Add a minimal project-overview.md, constitution.md, and one feature spec (e.g., "user registration"). Verify the structure with a tree command. Document what you created.
Expected outcome: A complete SDD skeleton with at least one feature spec.
Exercise 2: Migrate an Existing Project
Take a small existing project (or a subset of your current project). Add Phase 1 and Phase 2 from the migration guide: add structure, add constitution, document one feature. Do not change any code. Write a brief report: what was easy, what was hard, what would you do differently?
Expected outcome: Migration report and updated project structure.
Exercise 3: Monorepo Layout
Design an SDD structure for a monorepo with three packages: api, web, shared. Decide: shared specs at root or package-level? What goes in global vs package constraints? Draw the directory tree and write a one-page rationale for your choices.
Expected outcome: Monorepo layout diagram and rationale document.
Key Takeaways
-
Canonical SDD structure: specs/ (global, constraints, templates, features), agents/, .cursor/rules/, src/, tests/, memory/ (constitution, ADRs, context). Each directory has a distinct role.
-
Separation of intent and implementation: specs/ holds WHAT and WHY; src/ holds HOW. Intent is stable; implementation is derived. This enables traceability and AI clarity.
-
specs/ organization: global/ for project-wide specs, constraints/ for cross-cutting rules, templates/ for reuse, features/ or [branch-name]/ for feature specs.
-
memory/ for identity: constitution.md, ADRs, and context files capture project principles and decisions. They are the long-term memory that guides AI and humans.
-
Feature branch workflow: specs/[branch-name]/ aligns specs with Git branches. Specs and code merge together. PRs include both.
-
Migration is incremental: Add structure first, document one feature, use SDD for new features, retrofit existing features when touched. No big-bang rewrite.
Chapter Quiz
-
What are the six main directories in the canonical SDD repository structure, and what is the purpose of each?
-
Why does separating intent (specs/) from implementation (src/) matter? Give two concrete benefits.
-
What goes in specs/global/ vs specs/constraints/ vs specs/features/?
-
What is the purpose of memory/constitution.md? What should it contain?
-
How does the feature branch workflow (specs/[branch-name]/) align with Git? What are two benefits?
-
What should you commit to version control? What might you optionally ignore?
-
In a monorepo, what are two options for organizing specs? When would you use each?
-
What are the four phases of the migration guide for restructuring an existing project?