Lesson 1: The spec directory
What you'll learn: how to give your project a home for intent, so an AI agent, or future you, cannot quietly build the wrong feature. What you need: a text editor and the AI coding agent you already use. No new tools. Time: fifteen minutes to set up, ten per feature after. Cost: zero new dollars; the agent subscription you already pay for covers it.
The idea#
Before you ask an AI to build anything, create a directory next to your code that holds no code at all. It holds intent: one numbered file per feature, each stating what will be observably true when the work ships, the files the work must not touch, and one command that has to pass.
You do not write those files from a blank page. The directory carries a template, and your agent's first move on any new feature is to copy that template into the right folder, fill it in by interviewing you, and stop so you can correct the lines that matter. Then it builds, and you grade the result against the file instead of against your memory.
That directory is the smallest version of a spec system, and it is the foundation this entire series builds on.
Why start here#
I have built with AI daily for thirteen months: fifteen repositories, four public sites, two dozen scheduled jobs. The bottleneck was never producing software. It was remembering what I meant, because every new AI session starts with total amnesia about your project, and after enough projects, so do you. Nine months in I created a repository that holds no code at all, only written intent, and it now takes more commits than any code repository I own: 1,867 since January against 661 for my busiest code repo. The structure below is that repository reduced to its smallest useful shape.
The template#
Two artifacts this time, because the structure comes first. The skeleton:
specs/
README.md # the map: what this project is, what lives where
_template.md # every spec starts as a copy of this
app/ # one folder per area of your project
001-short-name.md
002-...
app/ is a placeholder; give each distinct area of your project its own folder the day it earns one, and number specs within each folder in creation order. Then _template.md, the file your agent copies and fills:
---
status: draft # draft -> ready -> in-progress -> completed
verify: npm test # one command that must exit 0
---
# <feature name>
## Acceptance criteria
- [ ] <an observable state: a URL that loads, a file that
exists, a number that appears somewhere it did not>
check: <the command and its expected output, or the
page and what it must show>
## Out of scope
- <paths, files, or systems this work must leave alone,
named precisely enough to paste into a request>
## Notes (guidance, not binding)
<links, patterns worth copying, context the builder
might want>
The rule that makes the file work: everything above Notes is binding, Notes is guidance. One line of standing instruction wires the two artifacts together. Your tools already read a standing file, CLAUDE.md or AGENTS.md, loaded into every session; feature intent pasted in there becomes noise the day after it ships, and both conventions warn that a bloated standing file gets ignored. What belongs there is this:
Before building anything new, copy specs/_template.md to
specs/<area>/<next-number>-<short-name>.md, fill it in from
what I have told you, and stop; only I move status from draft
to ready. Build only after that flip, and grade the finished
work against the file before telling me it is done.
Two surfaces, two jobs: the standing file says how to work here, the spec directory says what must be true, feature by feature.
Each part, and the mistake it prevents#
The README is for amnesia. Every fresh session, human or agent, starts by asking "where would I look." Three lines answer it: what this project is, where intent lives, how specs are numbered. Without it, an agent re-derives your project from the code, which tells it what you did and nothing about what you meant.
The template is what keeps the tenth spec readable. Ask an agent for a plan and you get a new format every time, each one plausible, none of them comparable. A template makes every spec gradable the same way: acceptance lines fused with their checks, so "done" cannot quietly mean "I stopped"; an Out of scope list in paths you can paste straight into a request, because an agent left undirected will refactor nearby code with infinite enthusiasm; one verify command named before the build starts, because if you cannot name it, you do not yet know what you want.
Numbered files are memory. A spec keeps its number and its status line for life: draft, ready, in-progress, completed. The draft-to-ready flip is the review step made visible; it records, where every future session can see it, that a human approved these binding lines. Completed specs stay where they are, and six months from now the directory reads as the history of what you meant, in order, which no commit log gives you.
The agent fills, you correct. The interview surfaces edge cases you had not considered; the editing keeps the intent yours. An agent that writes its own binding lines and then grades itself against them has audited nothing.
A worked example#
The real directory this series comes from, compressed:
specs/
README.md
_template.md
decision/ # empty until lesson 2
website/
014-blog-series.md # status: completed
016-learning-series.md # status: in-progress
The bottom slot in that tree is this lesson's own publication. Filled in from the template, it looked like this, and its status moved only after every check ran:
---
status: completed
verify: npm run check:content
---
# lesson-1-post
## Acceptance criteria
- [ ] the lesson page returns 200 on the live site
check: open the URL after deploy, template visible
in a code block
- [ ] the blog index lists it under the series title
check: open /blog, entry present
## Out of scope
- every other post in the blog content folder
- the blog index layout
Small on purpose. The structure survives because filling a slot costs ten minutes, not because it produces a document anyone admires.
From the field#
Earlier this month a proposal of mine died because of its own check, written early. I wanted to simplify a scoring system I run by removing one of its inputs, and I had a page of numbers agreeing with me. The acceptance line, written before those numbers existed, required the improvement to hold on data the analysis had never seen. It did not hold, and the change never shipped. The line was written by a version of me who did not yet want a particular answer. That is the entire mechanism: by the time results arrive you are a motivated reasoner, and the file is not.
Exercise#
- Create the skeleton: the folders above, a README.md with three lines (what this project is, where intent lives, how specs are numbered), and
_template.mdcopied from this page. - Add the standing instruction to the file your agent reads at session start.
- Pick the next feature you are about to ask an AI for. Let the agent instantiate the template and interview you; cap it at ten minutes; edit the binding lines yourself, then flip the status to ready.
- Build the feature the way you normally would, pasting the Out of scope lines into the request.
- Before you accept the work, grade the spec line by line: tick each acceptance line only after running its check, then run the verify command.
You're done when#
At least one line refuses to tick. There will be one; mine have ranged from a wrong chart default to a deploy that quietly did more than it was told. A failed check is not a failed build. It is the gap between what you meant and what you asked for, visible for the first time. Then run the second test: open a fresh agent session, point it at specs/ and nothing else, and ask what this project is building next. A right answer from a session with no memory is the structure doing its job.
Common mistakes#
- One growing SPECS.md instead of a directory. A single file scrolls; a structure routes, and an agent can be pointed at exactly one slot of it.
- Feature intent pasted into the standing file. Two surfaces, two jobs; the standing file stays short or stops being read.
- Letting the agent flip draft to ready itself. The binding lines are only binding because a human meant them.
- Writing activities instead of states in acceptance lines: "refactor the handler" instead of "the endpoint returns the new shape."
- Building the taxonomy first. Two folders are enough until a third earns itself.
- Writing the spec after the build. The mechanism only works because the lines predate the temptation.
Next lesson#
Why every new AI session relitigates decisions you already settled, and the one-page record that fills the empty decision/ folder above.
Keep reading
Watch the agent write
A polish agent drafts an essay against a pre-approved topic.
When your method repo and your product repo don't talk to each other
I built a method as a public repo and the product that runs it as two private ones, and none of them treated the others as a source of truth. The domain enum lived in four places. A persona drifted between its lens file and its API contract. Here is what that cost, and the one structural change that turned the whole class of bug into a failing test.
Context architecture beats documentation dumps
Dumping the whole corpus into an AI agent makes it worse, not better. The fix is architectural: each task loads a curated slice, not everything you have. Here is the method, and the same move at three different layers: specs, sensor data, and evaluation lenses.
SDD isn't about managing AI agents, it's about managing context
Spec-driven development reads like a methodology for controlling AI agents. It isn't. It's a methodology for managing context across stateless sessions. The spec is the persistent memory.
Spec-Driven Development and the Folder Architecture That Makes It Work
Why spec-driven development and structured folder architecture are the missing infrastructure for AI-assisted engineering: methodology, common mistakes, and where to start.
The schema is the product
SpecSelf looks like a set of features: coherence checks, persona rotation, review cadences, an audit trail. Every one of them was implied by ten frontmatter fields decided on day one. A life-OS is not a feature list. It is a schema decision, and the features are what fall out of making it correctly.
Follow the work
New tools and writing as they ship — pick a channel.