A look at spec driven development

Spec-Driven DevelopmentAI AgentsWorkflowTooling

I have a folder convention I’ve been using for a while, and it’s simple enough to write out in full:

./features
  notes.md
  favourites.md
  tags.md

./tasks
  1-implement-database-orm.md
  2-initiate-notes-crud.md
  ...

Everything is organised around the feature. Tasks are separate, numbered globally, and one task might cross several features. The ORM work underpins notes, tags and favourites all at once, so it doesn’t belong to any of them.

I went looking at the current spec-driven development frameworks to see which one matched. Most of them disagree with me, and they disagree with each other about why.

I’ve settled on OpenSpec, and I’m moving my projects over to it. It’s the only one of the four that keeps the feature separate from the work that implements it. openspec/specs/notes/spec.md says what notes is for and stays put; the changes that build notes fold their deltas into it and then archive. The other three make a folder per increment and call that a spec, so the answer to “what is notes supposed to do” ends up spread across however many increments have touched it, with nothing to reconcile them.

Does, and should

Current behaviour is derivable. It’s in the code. If I want to know what notes does today I can read src/ or run the app. A markdown file describing current behaviour is a second copy of something I already have, and it goes quietly wrong the first time someone merges a fix without updating it.

What isn’t derivable is what the system is supposed to do.

Code tells you what a system does. Only a document tells you what it should do.

The obvious objection is that the tasks already give me this. Fold the log and you get the system, like replaying a state machine. That works, up to a point: replaying every task tells you how the thing got where it is. Three things don’t survive the fold:

  • Prohibitions. “Notes are never hard-deleted, only archived” constrains code nobody has written yet. Its absence from src/ is indistinguishable from nobody having got to it.
  • Rationale. Why archive rather than delete. A log records what was done, never what was rejected on the way.
  • The gap. The distance between should and does is the work. A document that only ever described current behaviour has a gap of zero by construction, so it can’t generate work.

That last one is why I split features from tasks. ./features/notes.md says where notes ought to be, the numbered tasks close the distance, and when the two disagree it’s the code that has the bug. That only holds if the file describes intent rather than reporting behaviour.

Telling them apart

Spec Kit’s own docs name the axis: spec persistence. A spec-first framework writes the spec, builds from it and discards it. A spec-anchored one keeps the spec and edits it for the next change. My convention is spec-anchored, and I’d been using it without knowing there was a term for it.

The crude version, the one you can answer by opening a repo, is to ask what survives the implementation. Not which files are still on disk, since all four keep everything. Which files you’d open and edit six months later, rather than read as history.

It’s a proxy and it leaks. It catches agent-context files like CLAUDE.md, which are durable without being specs. But it sorts the four quickly, and it’s what the explorer below is built on.

Explore

Explore all four

Every file in these four repos has a fate: durable and worth coming back to edit, a frozen record of one increment nobody reconciles, scratch that gets executed once and ignored, or the framework’s own machinery.

Switch between the trees, click any file to see what’s actually inside it, and tick only what survives to strip each repo down to the files still worth editing six months later.

Procedural. Phase gates between numbered, branch-bound features.

Unit of work: A numbered feature, tied 1:1 to a git branchSurvives the implementation: constitution.md, and CLAUDE.md if you count agent context

An append-only log of increments. Nothing in the repo says what notes is for, only what feature 001 changed.

Click any file or folder to see what it holds, and what happens to it once the work ships.

Spec Kit

Spec Kit is very procedural. We do this, then we do that, then we do another thing: /speckit.constitution, /speckit.specify, /speckit.plan, /speckit.tasks, /speckit.implement, with gates between the phases.

Its unit of work is a numbered feature tied to a git branch:

specs/
  001-capture-notes/
    spec.md  plan.md  research.md  data-model.md  contracts/  tasks.md
  002-due-dates-reminders/
    ...

The numbering is global rather than per-feature, so 002 is the second feature in the project, not the second attempt at due dates. Folder names are derived from your description by stripping stop words and taking roughly the first three surviving words, which is why you’ll want --short-name most of the time. The branch name and the folder name have to match, because the commands locate the active feature from your current branch.

To be fair to it, the workflow is the best documented of the four and the phase gates are real. If you work in genuinely independent slices the append-only log is fine. My projects aren’t like that. A notes app evolves.

Superpowers

Superpowers is the odd one out. It isn’t a spec framework. It ships an engineering culture as a folder of skills: brainstorming, worktree isolation, TDD where code written before its test gets deleted, review by a fresh agent, and a verification step that refuses to accept a green test suite as proof of completion.

Its entire artifact surface is one flat directory:

docs/plans/
  2026-03-14-notes-capture-design.md
  2026-03-14-notes-capture.md

Those plans are scratch. They’re written to be executable by an agent with no context: 2-to-5-minute steps, exact file paths, literal test commands, “run it and watch it fail” as a real step. Then the branch merges and nobody reads them again. There’s an open request to make the location configurable on the grounds that plans are personal working documents rather than project artifacts, which tells you how its own users read them.

BMAD

BMAD simulates an agile team. Analyst, PM, architect, scrum master, dev, test architect, each with workflows, thirty-odd of them in the dev module alone. Artifacts land in _bmad-output/:

_bmad-output/
  project-context.md
  planning-artifacts/
    prd.md  architecture.md  epics.md  epics/
  implementation-artifacts/
    sprint-status.yaml
    stories/
      1.1.quick-capture-field.md
      1.2.persist-on-blur.md

Its unit is the story, nested under an epic, and epics are planning slices. So it lands where Spec Kit does: epics/epic-1-notes-capture.md describes a chunk of work rather than what notes is meant to be. Epic 4 will also change how notes behave, and neither file is the spec for notes, because there isn’t one.

It’s also heavy for a small project, the output paths aren’t fully reconciled between workflows (some write to docs/, others read from _bmad-output/), and the tree has been restructured across recent releases.

OpenSpec

OpenSpec is the one that matched, and it matched almost exactly. It splits the thing the others conflate:

openspec/
  specs/                        # the layer I was missing
    notes/spec.md
    todos/spec.md
    tags/spec.md
  changes/
    add-note-pinning/
      proposal.md  design.md  tasks.md
      specs/notes/spec.md       # delta only
    archive/
      2026-03-14-add-notes-capture/

specs/ is organised by domain, one folder per capability, which is my ./features with a different name. changes/ holds proposals, and a change’s spec files are deltas instead of copies: sections headed ## ADDED Requirements, ## MODIFIED Requirements, ## REMOVED Requirements. On archive, ADDED appends to the real spec, MODIFIED replaces the requirement, REMOVED deletes it, and the change folder moves into changes/archive/ with a date prefix.

So notes/spec.md is one file that accumulates. Increments fold in and then get out of the way, still readable in the archive but no longer the record.

One quibble with how it’s sold. OpenSpec’s docs describe specs/ as how your system currently works, which is the indicative reading, and it undersells the thing. I use that file for the optative half: notes/spec.md says notes are never hard-deleted, and that’s a decision, binding on code that doesn’t exist yet. Read it as current behaviour and it’s a duplicate of src/. Read it as intent and it’s the only copy.

Two bits of friction

Why specs/notes/spec.md and not specs/notes.md?

The folder-per-spec thing looked like ceremony to me at first. The answer is in the archive code: it enumerates capabilities by reading directories, so “every subdirectory” is an unambiguous rule where “every .md file except the ones that aren’t capabilities” would need an ignore list. It also means both trees, real specs and change deltas, resolve through one path function. And a directory can gain children later where a file can’t, which is why the open proposal for nested capability paths is even possible.

I still think nine editor tabs all named spec.md is worse than nine named notes.md. Turn on your editor’s unique-path-segment setting and move on.

Where do BDD journeys live?

I wanted specs/notes/journeys.md for Gherkin-style journeys. That doesn’t work. The merge only ever targets spec.md, and there’s no delta format for anything else, so the file would be inert and would drift.

The supported answer is fenced Gherkin inside the ### Requirement: / #### Scenario: structure, which stays mergeable and can be extracted into real .feature files at test time. Cross-capability journeys, the ones that span notes and tags and todos, go in the test tree as hand-written .feature files, because no single capability spec can own them.

What happens to ./tasks

An OpenSpec change is not scoped to one capability. proposal.md names the affected specs, and the delta folder carries one file per capability the change touches. So 1-implement-database-orm.md isn’t homeless here: it’s a change called add-database-orm whose proposal names notes, tags and favourites, with its own tasks.md covering all three. Deltas are scoped to individual requirements, so two changes in flight can both edit notes/spec.md as long as they touch different requirements.

That’s most of what I wanted from a global task list. A unit of work that serves three features without belonging to any of them.

What’s missing is ordering rather than scope. Each change carries its own tasks.md, so with three changes in flight there’s no one place showing all outstanding work, and nothing expresses that one change should land before another. openspec list covers the first half at the command line. The sequencing my numbering implied has nowhere to live.

Spec Kit is the one that genuinely can’t express it. specs/001-capture-notes/tasks.md belongs to one feature on one branch, so shared ORM work has to be adopted by whichever slice needs it first. BMAD’s sprint-status.yaml is the only file in any of these four holding ordered state across the whole project, and it comes attached to a whole agile simulation.

The specs half of my convention maps onto OpenSpec cleanly. The tasks half maps onto it too, apart from the ordering, and I’ve stopped trying to make a folder do a tracker’s job.


Appendix: which layers each one has

Six layers, four frameworks. Only one column has a durable per-capability spec, and only one has anywhere to put a task that spans three features.

LayerSpec KitOpenSpecSuperpowersBMAD
Durable per-feature specone file per capability, edited as it evolvesnonespecs/<cap>/spec.mdnonenone
Frozen increment recordwhat one slice changed, and whyspecs/NNN-*/changes/archive/docs/plans/ (scratch)planning-artifacts/
Merge back into truthincrements accumulate instead of scatteringmanualdelta specsmanualmanual
Cross-feature task stateone place showing outstanding work across the whole projectnoneopenspec list (CLI)nonesprint-status.yaml
Enforced agent behaviourTDD, review gates, worktree isolationphase orderzone rules14 skillsagent roles
Branch couplingmust the folder name match your git branch?requiredfreeworktree per taskfree
has it properly friction: manual work, or a rule you have to obey present, but a different shape nothing there