AI-augmented Engineering

Spec-driven development with AI: what it actually leaves behind

A practical look at spec-driven development with AI, from requirements and architectural constraints to story files, testing, reviews, and the artefacts left behind.

SC
Sameera Chathuranga
Aug 10, 2026 16 min read

We’ve all been there with “vibe coding.” It’s usually the first thing anyone tries: just keep prompting and tweaking until the code finally runs. It feels incredibly fast at first, but we know how that story ends. You end up with a codebase lacking a clear structure, a model that loses track as things get more complex, and a growing pile of technical debt. And your only “documentation” is buried in a chat history that no one can easily search.

Spec-driven development (SDD) is the logical response to this. The idea is simple: write the specification first. Treat it as your source of truth, and let the generated code follow a structure it could never have come up with on its own. It breaks down into five clear stages: requirements, architecture, tasks, implementation, and testing, where each step keeps the next one on the right track.

That sounds great in theory, but it’s easy to say when you’re looking at a blank screen. Most SDD tutorials are “greenfield” projects, like building a basic To-Do app in five minutes and walking away. The real friction starts later. What happens after the fourth major feature? How do you handle a code review that catches the same bug for the third time? What happens when you actually try to run the app on a physical device?

To find out, I built something modest but functional, followed the full workflow, and saved every single artefact along the way. This is a look at what was left when the dust settled, and a reality check on which parts of the process actually pulled their weight.

The setup

The project was “TodoList,” a mobile app designed to keep work and personal tasks strictly apart. I wanted two separate workspaces so that home chores wouldn’t clutter my workday. It needed a “Today” view for each workspace, overdue items that roll forward, local reminders, and no cloud accounts. It used pure on-device storage with JSON export/import for backups.

I used Expo SDK 56, React Native 0.85, and expo-sqlite. By the end, it was roughly 26,000 lines of TypeScript across 198 files. It was small by design, small enough that I could personally read every line the AI agents wrote. That turned out to be much more important than I expected.

For the Spec-driven development side, I went with the “BMad Method.” There are plenty of other frameworks out there, like Kiro or GitHub Spec Kit, but I chose BMad because it’s very specific about which documents you need to create. After finishing the project, I’ve realised the specific framework matters much less than the core principles. I’ll point out where something is just a BMad quirk versus a fundamental rule of SDD.

Where the specs live

Take a look at the repository layout. The most important detail is where the spec directory actually lives.

It’s a sibling to the src/ folder, not hidden away in a docs/ subdirectory. That might seem like a small detail, but it’s the first thing I’d recommend for any Spec-driven development setup.

We all know that documentation hidden in a docs/ folder eventually rots. I was sceptical that SDD could fix this, but the solution isn’t better discipline, but rather the workflow itself. These spec files are inputs. Because the next stage of the project literally reads these files to generate code, if the spec is wrong, the code comes out wrong immediately. You find the error in an hour, not a year later.

That’s the secret. Specs stay updated when something downstream depends on them, not because you promised to keep them current.

The organisation inside the folder matters, too. My “planning-artefacts” folder holds the permanent stuff, the PRD, UX design, and architecture. Meanwhile, “implementation-artefacts” handles the day-to-day across 32 story files, retrospectives, bug logs, and a small YAML file to track the project state.

To be totally transparent, this resulted in 68 markdown files and about 84,000 words. That is a massive amount of writing for a simple todo app. The real question is whether all that effort actually paid off.

Stage one: requirements that can be wrong

The first step isn’t actually a formal spec. It’s a massive brain dump of about 87 feature ideas, which I eventually trimmed down into a concise project brief.

What really caught me off guard, though, was the file sitting right next to it.

This file tracks every single judgment call, including the date, the reason, and who made it. “Sam” means it was my call. “Team” means the AI agents filled in a blank I missed. “Party-mode” means the agents had a debate and I had to break the tie.

People often say AI projects fail because there’s “no source of truth,” usually meaning a missing PRD. But I think it’s deeper than that. A PRD tells you what you decided, but this log tells you why. That “why” is what usually gets lost. I found myself constantly checking the “rejected” table at the bottom of this file. Decisions about cloud sync or UI features resurfaced weeks later, and having the original reasoning, with a date, saved me from repeating the same arguments.

If you take away just one practical tip from this, let it be the decision log. It was only fifteen rows long, but it saved me more headaches than any other document in the repo. It’s also the easiest thing for anyone to start doing today.

Next was the PRD. It contained 24 functional requirements (FRs). What made them actually useful for the AI wasn’t the quantity, but how they were phrased.

Each requirement was a single sentence, followed by a “Consequences” block. These are testable statements. A test should be able to clearly disagree with them.

Take FR-9 as an example: “Work Today never lists Personal Tasks and vice versa.” That simple sentence turned into a hard-coding constraint, an architectural rule, and eventually, a set of integration tests. It survived through every stage of development because it was written to be falsifiable.

Think about how requirements are usually written: “The system should provide clear separation between work and personal tasks.” You can’t test that. If you give that to an AI agent, you’ll get something that looks okay but is impossible to verify. You won’t realise it’s broken until you’re actually using the app.

This is my main critique of the usual Spec-driven development sales pitch. The value isn’t just in “writing specs first.” It’s in writing specs that can actually be proven wrong.

Stage two: architecture as prohibitions

The architecture doc is over 700 lines long, covering the usual suspects, stack choice, data models, and migrations. Most of it is standard stuff.

However, the most valuable part was a tiny list near the top, the seven “Architectural Invariants.” These were non-negotiable rules for the MVP.

  • Workspace isolation – every row, query, notification and export segment is workspace-scoped
  • Today correctness across timezone changes, DST, and multi-day absence
  • Transactional writes, crash mid-write must not corrupt data
  • Notification integrity – no orphan alarms after any lifecycle change
  • Versioned export envelope; replace-only import with confirmation
  • Foreground-first cold start
  • UX spine fidelity – the design docs win on conflict

Everyone worries about “architecture drift,” where AI edits slowly warp a design until the codebase becomes a mess. A massive 700-line document won’t stop that because no AI is going to re-read the whole thing before writing a single function.

But seven lines? Those can be pasted directly into every single story file. That’s the real trick to preventing drift. The constraints have to be short enough that you can drop them into every prompt without even thinking about it.

I’d also steal item seven for any project: “Spines win on conflict.” If a design document and a specific task file disagree on the layout, the design doc wins.

The traceability nobody reads until they need it

The epics.md file is another long one, totalling 763 lines. It mostly re-lists requirements and breaks them into smaller stories. Honestly, I found it tedious and barely looked at it at first.

But then I got to the mapping section at the end.

This maps every requirement to the specific epic that fulfils it. After ignoring it for a week, I started using it constantly. If I needed to know if FR-19 was actually built, I didn’t have to hunt through code, because I had a one-line answer.

This is the real solution to the “context window” problem. You don’t necessarily need a bigger window; you need a smart index that helps you pull in the right piece of context exactly when you need it.

Stage three: the story is the real artefact

If there’s one part of this process I’d fight to keep, it’s this. And it’s the part most people overlook.

It includes a clear “Given/When/Then” criterion and a breakdown of exactly which files to create and which tests will prove they work.

But the top half isn’t even the best part. Keep scrolling.

There’s a “golden test” spec with actual data, a file list for creates and updates, a history of previous stories, and an “anti-patterns” section telling the agent what not to do.

I want to highlight that “anti-patterns” block. After twelve days, I’m convinced it’s the most powerful idea in this entire approach, yet it almost never gets mentioned in summaries.

If you ask an AI to “make unfinished tasks roll over,” it will likely just update the due date at midnight. That’s the obvious way to code it, but it’s wrong for this app. It would erase the original due date and break the overdue view. In this app, “rollover” is just a way of querying data (due_date <= today). Nothing in the database should actually change. “Roll forward” is a UI concept, not a database operation.

You can’t just hope the AI figures that out from a requirement. You have to make that decision during architecture and then state it as a prohibition in the exact file the AI reads while it’s writing the code.

Stage four: implementation, then a second opinion

This changes how we think about specs. They aren’t just for describing what to build, as a prompt can do that. They are valuable because they block off the “plausible but wrong” paths before the AI can take them. Whether you call it a design, a constitution, or a spec, the goal is the same: defining the negative space.

The review found sixteen issues. Some were product questions the spec missed (like how to handle multiple task undos), some were immediate patches, and a few were deferred for later. 

It caught things I would have missed entirely, like a missing root view that would have silently broken swiping across the whole app. It also caught logic errors that a tired human might overlook, like dismissing a notification before the database was finished updating.

At first, I thought the coding agent was just being sloppy because there were so many findings. But the retrospective changed my mind. The issues weren’t about “bad code”, they were about gaps in the requirements that were only visible once we started implementation. Inconsistency in the code is almost always a sign of ambiguity in the plan.

So the fix wasn’t a better coding agent. It was a behaviour-matrix template forcing those decisions into the story before development starts, and it’s the honest shape of the inconsistent-code problem people blame on vibe coding. Inconsistency downstream is almost always ambiguity upstream.

One small note: I committed the whole project at the end rather than story-by-story. This meant the review agent was looking at the current files rather than a proper Git diff. If I did this again, I’d commit after every story to make the reviews even better.

The state is one small file

Despite all the documentation and structure, the actual “state” of the project is tracked in one tiny, simple file.

It’s just a flat list mapping story IDs to their status. That’s the only source of truth for what’s done and what’s next. Everything else is just supporting documents.

I also kept a running log of policy decisions. For example, I enforced a strict order for the epics, no jumping ahead until the current one was finished. I also defined “Done” for the first epic as the moment I could actually capture a real task in the second. It’s not done when the checklist is finished, but rather when it proves it works.

Retrospectives that grade the previous retrospective

After each of the four epics, we did a retrospective. I expected these to be generic AI “cheerleading,” but they actually read like real project reviews.

The metrics are fine, but the real value is the table tracking action items from the previous retro. It was brutally honest about what I hadn’t done yet. 

I’ve been in plenty of human meetings where we never look back at the last session’s goals. This AI system did it automatically, and it was a bit uncomfortable. By Epic 4, it was clear I had promised to do a QA checklist three times and never actually did it. That was on me.

We used a similar discipline for “deferred-work.md,” which acted as a home for every issue we decided to fix later.

Everything was grouped by the review that found it, with a reason for the delay, and crossed off when it was finally fixed. By the end, it was a 150-line history of the app’s imperfections.

This is how you stop documentation from rotting. I know exactly what’s currently wrong with the app and why I left it that way. Anyone else could pick this up and understand the state of the project instantly.

Where spec-driven development did not save me

Now, for the part that makes me actually trust this whole experiment.

By the end of the fourth epic, I had 446 tests across 81 files. I had everything covered: rollover logic, data imports, notifications, and the trash system.

Then I actually installed the app on a simulator, tried to export a backup, and… nothing. It just didn’t work.

It turns out the code had used a new API in a way that failed on a real device, even though it looked perfect in the editor. One of my “must-have” features was totally broken, yet the entire test suite was still green.

I found five other issues that were invisible to the tests. A decorative circle looked too much like a checkbox, so I kept trying to click it. A status display looked like a broken button. The priority levels weren’t even visible in the list view.

The retrospective was honest: “444 passing tests did not catch export error.” It noted that “Sam’s dogfooding is the real quality gate.” (The slight count error in the doc itself just shows how fast these files were being generated.)

This happened for three epics in a row. A human actually using the device found blockers that the automated system missed. And for three epics in a row, I ignored the QA checklist that would have caught those issues sooner.

I can’t blame Spec-driven development for this. The checklist was there the whole time. SDD can build you a safety net, but it can’t force you to walk across it. The process can’t replace the human step of actually checking the work.

What the repository supports, and what it doesn’t

Here is what Spec-driven development actually handled well, and why I’d use it over unstructured prompting.

The architecture actually held up. After twelve days of AI-generated code, the core rules, like workspace isolation and data boundaries, were still perfectly intact. Every bug I found was a visual or usability issue, not a fundamental structural failure. This is exactly where “vibe coding” usually falls apart, and SDD prevented it.

There was almost no “rework.” The plan I wrote on day one survived the entire project. We didn’t have to tear things down and start over because of a missed requirement.

Everything is documented. There are no mystery TODOs or hidden bugs that I just “promised” to fix later.

The second agent caught things I definitely would have shipped. That one swipe gesture fix alone saved me an entire evening of frustration.

Now, here’s what it didn’t do, despite what the marketing might tell you,

It isn’t a “10x” productivity multiplier. People claim one engineer can do the work of eight using this method, but there’s no data here to support that. It was one person building one small app. I believe it leads to better architecture, but I won’t claim it makes you eight times faster.

It’s not necessarily cheaper. Writing 84,000 words is a lot of work. On a bigger project, I probably wouldn’t have read every word, and a spec that no one reads is just a slower way to make mistakes.

I’m not sure the review process scales. Reading and deciding on sixteen findings per task is exhausting. This is the first place where a team under a deadline would start cutting corners.

When I wouldn’t use Spec-Driven Development

Formal specs are “technical debt” if the project won’t last long enough to make that overhead worth it. For hackathons, quick scripts, or experimental prototypes, just skip the ceremony and prompt away. 

The real test is to use Spec-driven development when an architectural mistake would be expensive to fix. In this app, the “rollover” logic was a great example. Getting that wrong would have corrupted the data history forever. That’s worth writing a spec for.

Most things in a typical sprint just aren’t that critical.

In Summary

The common advice is “stop vibe coding and start writing specs.” I agree with the first part, but the second part is too vague.

Requirements written as statements that can be proven false.

A tiny list of non-negotiable rules that you can paste into every prompt.

  • Recording why you made a decision, not just what it was.
  • Task docs that list anti-patterns, telling the AI what not to do.
  • An index to help you reload context when your own memory fails.
  • A human who actually tests the app on a real device.

Frameworks like BMad package these ideas together, but the packaging isn’t what matters.

If I had to sum up twelve days of work in one sentence, it is this: Spec-driven development is not just a delivery system for rules, but a robust, professional framework that transforms AI from a source of technical debt into a scalable engineering engine. The investment in discipline pays dividends in the form of significantly higher code quality, unwavering structural integrity, and long-term maintainability that vibe coding simply cannot match. It is the bridge between experimental prompting and professional software engineering.

SC
Written by
Sameera Chathuranga