← Back to Blog

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#

You ask your agent for a feature and get back something plausible. A week later it is wrong in a way nobody can quite name, because what you actually asked for lives in a chat session that no longer exists. Every new session starts from amnesia, re-reads your code, and learns what you did, never what you meant.

The fix is a directory next to your code that holds no code at all, only intent: one numbered file per feature stating what will be observably true when the work ships, what stays out of bounds, and one command that has to pass. It runs on one loop:

  1. Your agent copies a blank form into the right folder.
  2. It fills the form in by interviewing you.
  3. You correct the lines that matter and flip the status from draft to ready.
  4. The agent builds.
  5. You grade the work against the form, line by line, 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.

Four boxes in two lanes show the spec loop: the agent fills the blank form by interviewing you, you approve it by flipping its status from draft to ready, the agent builds, and you grade the result line by line against the file rather than your memory.

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, and after enough projects the amnesia was mine as much as the machine's. 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, where intent lives
  _template.md       # the blank form: every spec starts as a copy of it
  app/               # one folder per area of your project
    001-short-name.md
    002-...          # the record: what you meant, in order

app/ is a placeholder; give each area of your project its own folder and number specs within it in creation order. The growth rule is the same at every size: a folder earns its place the day two kinds of intent compete for one home. When api specs and page specs scroll past each other, split them. Each split buys routing, because a folder is a place you can point an agent at, so a session reads one slot instead of all of them. And the shape never changes as it grows: areas become products, products get their own specs directory, and eventually intent earns a repository of its own. The platform this series comes from grew exactly that way, one earned folder at a time.

Then the blank form itself, _template.md:

---
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 map is for amnesia. Every fresh session asks "where would I look." Three README lines answer it: what this project is, where intent lives, how specs are numbered. Without a map, the agent re-derives your project from the code. Code says what you did. It says nothing about what you meant.

The blank form is what keeps the tenth spec readable. Ask an agent for a plan and you get a new format every time. Each one is plausible. None of them are comparable. The form makes every spec gradable the same way. Each acceptance line is a state fused with its check, so "done" cannot quietly mean "I stopped." Out of scope is a list of paths you can paste straight into a request, because an agent left undirected will refactor nearby code with infinite enthusiasm. The verify command is named before the build starts, because if you cannot name it, you do not yet know what you want.

The record is memory. A spec keeps its number and status for life: draft, ready, in-progress, completed. The draft-to-ready flip is your approval, recorded where every future session can see it. Completed specs stay put. Six months from now the record 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#

  1. 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.md copied from this page.
  2. Add the standing instruction to the file your agent reads at session start.
  3. 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.
  4. Build the feature the way you normally would, pasting the Out of scope lines into the request.
  5. 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.
  • 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

Demo

Watch the agent write

A polish agent drafts an essay against a pre-approved topic.

Read
Post

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.

Read
Post

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.

Read
Post

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.

Read
Post

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.

Read
Post

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.

Read

Follow the work

New tools and writing as they ship — pick a channel.

Written by Eric Caskey. I build AI tools you can actually use. Explore the Tools or see the case studies.