Files
familienarchiv/SPEC_DRIVEN_DEVELOPMENT.md
Marcel 40c1bba113 refactor(sdd): make the feature spec issue-only (no committed spec.md)
The Gitea issue body is the single source of truth for a spec; the only
per-feature artifact in git is the RTM row (REQ-ID -> issue # -> test). Drops
per-feature spec.md/tasks.md/checklist files from the workflow (the _example
stays as a template/reference). Updates the guide, ADR-041, AGENTS.md, CLAUDE.md,
templates, the RTM (adds an Issue column), the implement/review-pr skills, and
replaces the file-spec CI jobs with an rtm-check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 12:14:32 +02:00

10 KiB
Raw Blame History

Spec-Driven Development (SDD)

How we turn a feature idea into merged, traceable code in this repo. SDD layers a uniform, machine-readable front-end onto the workflow we already run (Gitea issues → branch/PR → multi-persona review → red/green TDD). It does not replace any of that — see ADR-041 for the why.


1. The workflow in 8 steps

# Step Who Artifacts created / touched
1 Idea → Gitea issue using the Feature template author Gitea issue (labels spec-required, needs-review) from .gitea/ISSUE_TEMPLATE/feature.md
2 Write the spec in the issue body — Context, User Journey, EARS REQ-NNN requirements, measurable acceptance criteria, Out of Scope author the Gitea issue body is the spec (single source of truth — no committed spec.md)
3 Capture durable design decisions as needed author a docs/adr/ ADR for any project-wide/irreversible decision; an OpenAPI contract and a STRIDE threat model inline in the issue (use the .specify/templates/ as the writing aid)
4 Persona spec review — the six checklists gate the spec RE, Developer, Security, DevOps, UI/UX, Architect /review-issue posts each persona's checklist verdict as a Gitea comment; findings folded into the issue body
5 Resolve Open Questions & blocking FAILs — spec does not proceed while any remain author issue body updated; Open Questions emptied
6 Seed the RTM — one row per REQ-NNN, pointing at the issue author rows added to .specify/rtm.md (Issue: #n, Status: Planned) — committed with the feature branch
7 Implement in a worktree, TDD per task (failing test → green → refactor → commit); agent reads AGENTS.md + the issue body (the spec) implementer (often an AI agent) code + tests; npm run generate:api after backend changes; RTM StatusDone
8 PR → multi-persona PR review → merge reviewers PR (Closes #n); the closed issue is the archived spec, the RTM rows record what shipped

The personas at step 4 review the spec (the issue); the same personas at step 8 (via the existing review-pr / deliver-issue skills) review the code. Step 4 catches at spec time what used to surface only at step 8.

Skills that drive this: /draft-spec (requirements engineer authors steps 12 → creates the issue) → /review-issue (step 4 gate) → /implement (steps 67) → /review-pr (step 8). /deliver-issue runs review → discuss → implement → review-loop end-to-end.

Why issue-only? The Gitea issue body is the single source of truth for a spec — there is no committed per-feature spec.md to drift out of sync with it. The only SDD artifact that lives in git per feature is the RTM row (REQ-ID → issue # → test). The worked example under .specify/features/_example/ is a template/reference, not a live feature — it shows the full artifact set in one place; real features keep the spec in the issue.

2. How a Gitea issue becomes a spec

Before (free-form issue):

Title: Add profile pictures Users should be able to upload a picture for their profile. Make sure it's not too big and only admins can remove other people's. Show initials if there's no picture.

Ambiguous: how big? which formats? what status code on rejection? what about unauthenticated callers? No identifiers to trace, no measurable criteria.

After (SDD-structured issue — excerpt):

Title: As a user I want to upload a profile picture so other family members recognise me

## Requirements

  • REQ-002 (Event-driven) — When an authenticated user sends POST /api/users/me/avatar with a valid image, the user service shall store it and return a profile view with a non-null avatarUrl.
  • REQ-008 (Unwanted-behavior) — If the uploaded file exceeds 2 MB, then the user service shall return 400 ErrorCode.AVATAR_TOO_LARGE and store nothing.
  • REQ-009 (Unwanted-behavior) — If a caller without Permission.ADMIN_USER targets another user's avatar, then the system shall return 403 ErrorCode.FORBIDDEN.

## Acceptance Criteria

  • REQ-008 — a 2.1 MB PNG returns 400 AVATAR_TOO_LARGE; bucket object count unchanged.

Every behavior is now a uniquely-identified, testable, EARS-formed requirement with a measurable acceptance criterion. See the full version in .specify/features/_example/spec.md.

3. How to run a persona review

Each persona reads the spec, walks its checklist in .specify/personas/<persona>.md, and posts a Gitea comment with PASS / FAIL / QUESTION per item and a verdict. A FAIL from Security or Architect is a hard block. Concrete example:

Security — Spec Review

# Item Status Note
1 All mutating endpoints have authn + authz If clauses PASS REQ-006 (401), REQ-009 (403)
3 Audit fields server-set, forbidden in body FAIL avatarObjectKey is bound from the request body → mass-assignment (CWE-639). Make it server-set in UserService.
6 Upload type allow-list + size PASS REQ-007 / REQ-008
9 threat-model.md present & STRIDE-complete QUESTION Is the avatar URL public or proxied? If public S3, that's information disclosure.

Verdict: CHANGES REQUESTED — blocking FAIL: #3. Resolve #9 in the threat model.

The author folds the fix into the spec (here: server-set key + authenticated proxy URL), empties the finding, and the persona re-reviews until APPROVE. This mirrors the existing review-issue skill — the persona checklists just make the spec pass/fail explicit.

4. How the AI agent uses the spec

Once the spec is APPROVEd and tasks are seeded, the implementer points the agent at the artifacts. Example prompt:

Implement Gitea issue #142 (profile picture upload). Read .specify/AGENTS.md and obey the constitution it references. The contract is the issue body — its EARS requirements REQ-001…REQ-009 and acceptance criteria. Build a red/green task list from them, write the failing test for each REQ first, confirm it fails, then make it pass. After backend model changes run npm run generate:api. Do not mark a REQ done until its test is green; flip its row in .specify/rtm.md to Done as you go.

The agent now has: the rules (AGENTS.md → constitution) and the exact requirements with ids from the issue — so its output is bounded and verifiable. (The /implement skill fetches the issue body for you via the Gitea API.)

5. Maintenance rules

  • Constitution (.specify/constitution.md) — change it only when a project-wide rule genuinely changes. Bump the semantic version (MAJOR = rule removed/weakened, MINOR = rule added/tightened, PATCH = wording), run the §6 Sync Impact review, and let the constitution-diff CI job list the files to reconcile. Record the bump in ADR-041's revision log (or a superseding ADR for MAJOR).
  • AGENTS.md — keep it under 200 lines. It cross-references the constitution; it must never duplicate or contradict it.
  • ADRs — project-wide/irreversible decisions go in docs/adr/ (next free NNN, verify on disk). Immutable once Accepted; supersede, don't edit.
  • Feature specs — the spec is the Gitea issue body; there is no committed spec.md. "Archiving" is just closing the issue (Closes #n on merge). The closed issue + the RTM rows are the record of what shipped.
  • RTM (.specify/rtm.md) — append one row per REQ-NNN when a spec is approved, each pointing at its issue (#n); flip Status as tests go green; never delete a shipped requirement's row.
  • Personas — update .specify/personas/*.md checklists when a recurring blind spot appears; keep them aligned with the richer .claude/personas/.

6. Quick-start cheatsheet

EARS patterns (every requirement is one of these + a REQ-NNN id):

Pattern Shape
Ubiquitous The <system> shall <behavior>.
Event-driven When <trigger>, the <system> shall <behavior>.
State-driven While <state>, the <system> shall <behavior>.
Optional-feature Where <feature/permission present>, the <system> shall <behavior>.
Unwanted-behavior If <undesired condition>, then the <system> shall <response>.

File locations:

What Where
Non-negotiable rules .specify/constitution.md
Agent rules (read every time) .specify/AGENTS.md
Templates (writing aids) .specify/templates/{feature-spec,adr,threat-model,api-contract-stub}.md
Persona checklists .specify/personas/*.md
In-flight feature spec the Gitea issue body (not a committed file)
Worked example (template/reference) .specify/features/_example/
Traceability matrix .specify/rtm.md (REQ-ID → issue # → test)
ADR archive docs/adr/NNN-*.md
Issue templates .gitea/ISSUE_TEMPLATE/{feature,bug}.md
CI gate .gitea/workflows/sdd-gate.yml

Before you mark a feature done: every REQ-NNN has a green test, the RTM Status is Done, all six personas APPROVE, npm run lint and the targeted tests pass, and npm run generate:api has been run if the backend model changed.

Commands:

# validate an OpenAPI contract locally (if you drafted one — same as CI)
npx @stoplight/spectral-cli lint <your-contract>.yaml

# regenerate the TS client after a backend model/endpoint change
cd frontend && npm run generate:api   # backend must run with --spring.profiles.active=dev