We have all heard some version of it by now. The agents write the code, and our job is to review it. And for a small change, that really is how it goes. But the more I used agents on real features, the less the question was whether they could write code, and the more it was whether I could trust the code they wrote. Would it handle the edge case I would have handled? Would it reuse the helper that already exists, or write a third copy of it? Would the tests actually test anything, or just pass?
After nearly a decade of writing software, I have a fairly clear picture of what good code looks like to me, and most of it is not in the code itself, it is in how the code gets made. A spec before anything gets written. Small slices that each work end to end. A failing test before the fix. Commits that are always green. None of that is new, it is just how I work. The agent did not know any of it, and telling it once at the top of a conversation does not last.
There is a prompt that has turned into a meme by now, a one-line request for an entire app with "make no mistakes" tacked on the end, something like this:
An illustration, not a real conversation. The tests are the funny part.
It is funny because most of us have been tempted to write some version of it. But think about what that line actually does. The model was never planning to make mistakes, it has no idea which ones we mean, and nothing in the line tells it what our version of correct looks like, or checks whether it got there. Andrew Yager makes the same case in "Make No Mistakes" and Other Things That Don't Work, the phrase adds nothing about the task, and what helps is context, an explicit standard to meet, and a fresh conversation to review the work, which is more or less the rest of this article. A rule that nobody checks is a wish.
I ran into a version of this on a larger project, where the work was spread across Slack threads, Jira tickets and a handful of other places, and keeping track of all of it was hard enough for me, let alone for the agents. On long-running threads the context gets truncated, the details from earlier in the conversation get lost, and without something concrete to come back to, the agents had a hard time staying on course. This is where Obsidian became the power for me, one place that kept track of the different moving parts, written down in files that both I and the agent could go back to.
That project changed how I thought about the problem. We cannot make a model trustworthy by asking it nicely, or sternly. What we can do is write our own methodology down, as specs, rules, tests and reviews, and build the agents around it, so the output either meets the bar or fails somewhere we can see it.
That was roughly six months ago. Since then I have run every non-trivial piece of work through the same three steps: write a spec, break it into phases, let the build run. More than 100 different projects and features have gone through it, and it has changed a lot along the way. Each time something went sideways, a rule got added, and each time a rule turned out to be dead weight, it got cut. Recently I packaged the whole thing as a Claude Code plugin called spec-workflow, so that anyone can install it in a couple of commands.
This article walks through how the workflow turns a methodology into something the agents are handed and then checked against: the spec, the rules, the tests and the reviews, then where the ideas came from and the other plugins it leans on under the hood.
1. The Spec Comes Before Any Code
The spec is the first place we tell the agent what "the way we want it" actually means, and it is the step I spend the most time on. /spec-workflow:spec takes a plan name and whatever context exists:
/spec-workflow:spec checkout-redesign --jira DEV-1234 --figma https://figma.com/design/...
From there it fetches the Jira ticket, the Confluence page, and the Figma design in parallel, whichever of them exist (a plain description of the feature works too), while an Explore subagent (a read-only search agent built into Claude Code) searches the codebase for existing components, test patterns and API conventions. All of that research lands in a findings.md, and the synthesis lands in a spec.md with a fixed shape: an objective, three to five measurable success criteria, boundaries, open questions, dependencies, and a table of key decisions.
The boundaries are the part I would least want to give up. Every spec has three lists: Always Do, Needs Approval, and Never Do, a three-tier idea borrowed from Addy Osmani's spec-driven development skill. Here is a trimmed version from a real one, written in May for a bug in my wife's dance studio app where class times showed up an hour off around daylight saving:
## Success Criteria
2. A class created for Nov 15 at 9:00 AM displays as 9:00 AM on Nov 15,
regardless of current DST offset.
...
4. Opening an existing class in the edit form and saving without changes
does not shift the stored time.
## Boundaries
**Always Do:**
- Interpret all form date+time inputs as `America/Los_Angeles` wall-clock time.
**Never Do:**
- Migrate or modify existing stored UTC values in the database — the storage is correct.
- Change the DB schema.That "Never Do" line is the whole bug in one sentence. The research had found that Postgres was storing the times correctly, the problem was the frontend encoding form input in the browser's time zone and not the studio's. Without that line, the easy-looking fix is a data migration, which would have made things worse. This is the kind of call an experienced engineer makes without thinking about it, and the kind an agent will get wrong unless it is written down.
Note: the spec step runs on Opus, on purpose. It is where the actual thinking happens, and in my experience a better spec means a simpler build with a lot less rework, which is worth more to me than the tokens. Granted, the workflow as a whole is not light on usage. Between Opus on the spec and two of the reviewers, the scoring passes and the retries, a big feature can run for a long time, so it fits better with a plan that has room in its usage limits than with a tight one.
2. Open Questions Get Answered One at a Time
Every open question is a place where the agent will otherwise guess, and a guess is exactly what we cannot trust. Earlier versions of the workflow left them all in one list at the bottom of the spec, and a list of eight questions is easy to skim, answer badly, or not answer at all.
Now the skill asks them one by one, ordered by impact, so the questions that change scope or architecture come first and the cosmetic ones come last. Each question comes with a sentence or two on why it matters, two to four concrete options, and a recommended one marked first. After each answer the spec gets updated right away: the question moves into the key decisions table, and the answer gets pushed into the success criteria and boundaries it affects. If an answer makes a later question moot, that question is dropped. If we really do not know yet, "decide later" is an answer too, and it gets marked as deferred.
Before I ever see the spec, it also goes through a scoring pass. Six axes, from objective clarity to whether a downstream step could plan from it without redoing the research, scored 0 to 2 each. Anything under 10 out of 12, or any axis at zero, gets revised, up to two times. Only then does it come to me for approval.
Ten minutes of questions up front saves hours of rework later.
3. Phases Are Vertical Slices, Not Layers
Once the spec is approved, /spec-workflow:decompose breaks it into phases in a task_plan.md. The rule here is vertical slicing, so each phase delivers something complete and testable, rather than "do the whole database layer, then the whole API layer, then the UI." A layer-by-layer plan looks tidy on paper, but nothing works end to end until the last phase, and that is exactly where the surprises show up. A slice that works end to end is something we can check, and checking is how trust gets earned.
Decompose also turns the spec into rules for the build. Success criteria become acceptance criteria for phases. Key decisions are treated as fixed. Boundaries become constraints on every phase. And any question I deferred during the spec gets decided here, with best judgment, and written down as a judgment call so I can see what was chosen and why.
There is one rule in decompose I think matters more than it looks: no phase is allowed to contain a task like "confirm with user" or "get approval." The next step runs on its own, so every phase has to be something it can finish without me.
4. The Methodology Lives in the Agents, Not the Prompt
This is the part that answers the question at the top. Rules typed into a conversation fade as the conversation gets longer, the same context loss I watched happen on that larger project. So the rules live in files instead. The build agent is a markdown file that ships with the plugin, and it is, more or less, how I would want a teammate to work. Here is a trimmed piece of it:
### 1. Reuse Check (BEFORE writing any code)
1. **Search for existing implementations.** Glob and Grep the codebase for
similar names, functionality, or patterns.
2. **If a match exists** — reuse it. Import and adapt rather than creating
a new file.
3. **If no match but the code will be used in 2+ places** — extract to a
shared location from the start.
### Code Quality Rules
- **Cognitive complexity budget**: New functions MUST stay under 15.
- **No magic numbers or strings.** Extract repeated values into named constants.Every slice of every phase is handed to a fresh build agent, so every slice starts from a clean context with the same rules at the top, rather than at the bottom of a long thread where they have been half forgotten. The plan itself lives on disk too, the pattern Manus describes in their post on context engineering, and before every prompt a small hook puts the active phase and the latest progress back in front of the model (more on that in section 8).
Granted, a rule file is still a prompt. Having the rules in front of the agent every time makes it consistent, it does not make it obedient. That is the job of everything after it: the build journal shows what the agent did, a separate grading pass scores each slice against these same standards, three reviewers read the result, and the tests either pass or they do not. The files set the bar, and the rest of the pipeline checks it.
What this means, is that the rules get better the same way a team's conventions do: when something goes wrong, a rule gets added, and the next run follows it. Most of the rules in the build agent today got there because something went sideways once.
5. The Build Runs Without Me
/spec-workflow:implement-plan asks me exactly one question, where to do the work: the current branch, a new branch, or a separate git worktree. After that it does not stop to ask anything, it runs every remaining phase back to back.
It commits as it goes, on the branch or worktree picked at the start, but it never pushes and never opens the PR, that part stays with me. To run without stopping on every command, Claude Code needs a permission mode that does not prompt for each one, like auto mode, which is one more reason to give it a branch or a worktree and keep it off main.
For each phase, it splits the work into slices of one to five tasks and under about 150 lines each, then hands each slice to the build agent, running on Sonnet. The orchestrator is the main session, the one handing out slices and keeping the plan files up to date. When the build agent finishes, it returns a structured journal that the orchestrator parses:
<!-- BUILD_JOURNAL_START -->
## Build Journal: {slice name}
### Files
- **Created:** ...
- **Modified:** ...
### Tests
- **Added:** ...
- **Results:** PASS | FAIL
### Reuse Decisions
{what was searched, what was found, reused vs created}
### Status
{complete | failed_after_3_strikes}
<!-- BUILD_JOURNAL_END -->
The journal is the agent showing its work. Instead of trusting that it reused what was there, we can read what it searched for and what it found.
If a slice fails three times in a row, each time trying a different approach, the agent stops and reports, and the orchestrator hands the failure to a Ralph loop, which re-runs the same fix-it prompt over and over until the tests pass or it runs out of attempts (ten by default). A loop that only wants green tests could cheat its way there, by weakening an assertion or deleting a test, so the orchestrator checks that the fix is sound and not a hack before moving on, and the grading pass and the reviewers still read it afterwards. It is the same check I would do in a code review.
After the slices, there is a cleanup pass, and then three reviewers run in parallel, each on one axis only: correctness and performance on Opus, architecture and readability on Sonnet, and security on Opus. Their findings get filtered for nitpicks, the real issues get fixed, and the tests run again. If there is a Figma link, it also screenshots the implementation and compares it against the design.
So what happens when something comes up mid-build that would normally need me? The skill picks the option most consistent with the spec and the existing code, and when two options are equally good, the smaller and more reversible one. Every one of those decisions goes into progress.md under Judgment Calls, and the final summary lists them all, so I review the decisions after the fact rather than babysitting the run.
Note: on a Mac, it also runs caffeinate for the length of the run, with a 12 hour cap, so a long build does not die because the laptop went to sleep. It is a small thing, but a build that dies halfway through the night is a lot of time to lose.
nohup caffeinate -dims -t 43200 >/dev/null 2>&1 &6. Tests Come First, So We Can Trust the Output
Speed was never the main goal here. Code we cannot trust is not worth shipping fast, so the output has to be strong, resilient, and something we can stand behind in a code review. A test written first is the strongest check we have that the code does what we asked, which is why the build agent follows test-driven development strictly, with no exceptions for "simple" files.
For every acceptance criterion in a slice, it goes through the same three steps:
- RED: write a test for the behavior first, run it, and confirm it fails for the right reason. A test that fails because of a typo or a bad import proves nothing, it has to fail because the behavior is not there yet.
- GREEN: write the minimum code that makes the test pass. No more.
- REFACTOR: clean up names, pull out helpers, remove duplication, while the tests stay green.
The goal is that every line of new code exists because a test asked for it. And because the success criteria in the spec are written to be measurable, they translate almost directly into tests. "Opening an existing class in the edit form and saving without changes does not shift the stored time" is already most of a test name.
A few rules in the build agent go beyond the textbook version, and I think they are what make the tests worth having:
- Bug fixes start with a reproduction test, one that proves the bug exists before anything gets fixed.
- UI components and config get tests too. A component with no branching logic still gets a render test, and a config file gets a test on its shape.
- Tests assert outcomes, not implementation details, and use real dependencies wherever possible. Mocks are only for external boundaries, like a payment provider or a third-party API.
- Test names describe behavior, like
"returns 404 when app does not exist", not"test error case". - Every commit is green. The test and the code it covers are staged together, specific files only, never
git add -A.
Resilience comes from the layers around the tests. On top of the retries and the Ralph loop from the build step, each slice gets its own grading pass, separate from the agent that wrote it, the generator and evaluator split from Anthropic's post on harness design, scored 0 to 2 on correctness, test coverage, architecture, security, scope control and maintainability, and it gets reworked if it comes in under 10 out of 12. The three reviewers only ever read code whose tests already pass. By the time a phase is marked complete, it has gone through tests, a scoring pass, a cleanup pass and three separate reviews.
Note: none of this replaces my own review of the PR. What it changes is what I am reviewing, a set of decisions and a green test suite, instead of a pile of code I have to check line by line for whether it even runs.
7. Where the Ideas Came From
Very little of this is original, and I think that is a strength. The workflow is mostly good ideas from other people, wired together around how I already like to work, and tested against a lot of real work.
- Spec-driven development from Addy Osmani's agent-skills. The three-tier boundaries come from his spec skill, where they are called Always / Ask First / Never. Here the middle tier is called "Needs Approval."
- Planning with files, the pattern Manus described in their post on context engineering, keeping the plan, the findings and the progress in markdown files on disk instead of in the context window, packaged as planning-with-files. This is what lets a plan survive a new session, a
/clear, or a week away from the project. - The Ralph loop, Geoffrey Huntley's technique of feeding an agent the same prompt in a loop until the work is done, used here as the recovery step when a slice keeps failing.
- Generator and evaluator, from Anthropic's engineering post on harness design for long-running apps. The idea is to separate the thing that builds from the thing that grades, and turn vague judgments into concrete criteria. That is where the 0 to 2 scoring rubrics on the spec, the phases and each slice come from.
- Keep or discard experiments, from Andrej Karpathy's autoresearch. When a phase has a number to improve, like bundle size or query count, the build runs one experiment at a time and keeps it only if the metric moves.
8. Many Plugins Under the Hood
The pieces in this section are mostly about keeping the agents on track and leaving a record we can check later, every spec, decision and phase written down. spec-workflow does not try to do everything itself. A lot of the power comes from handing each job to the tool that is already good at it:
- planning-with-files supplies the templates for
findings.md,task_plan.mdandprogress.md, and decompose hands the phase breakdown to its planning skill, with the approved spec as the input. - ralph-loop handles recovery during the build, and the optional
run-qapass before a PR. - Subagents do the heavy lifting in parallel: an Explore agent for codebase research, the build agent for each slice, and three reviewers at once.
- Model routing, as covered above, keeps the most capable model on the thinking and the faster one on the typing.
- MCP servers for Atlassian, Figma, Playwright and
obsidian-plan-ragare each used when they are installed and skipped when they are not. Nothing blocks on a missing one. /simplifyand a review skill of our choice,pr-review-toolkitby default, run in a loop with/spec-workflow:run-qa. It cleans the code up, reviews it, fixes what the review finds, and repeats three times, as a last pass before opening a PR.
obsidian-plan-rag deserves a mention of its own. It is an optional MCP server that indexes old plans for search. After more than 100 projects and features, the old plans are worth something, so when it is installed, a new spec searches past plans for similar work and pulls in the gotchas and test patterns from them, with the file and line they came from. Still, the current Jira ticket and the current code win whenever they disagree with an old plan.
Hooks hold it together during a long session. Claude Code hooks are scripts that run on events like a new prompt. Before every prompt, a small script finds the open plan for the current repo and puts the active phase and the latest progress back in front of the model:
echo "[spec-workflow] Plan: $PLAN_DIR — Phase $PHASE active"
sed -n "/### Phase $PHASE/,/^### Phase/p" "$PLAN" | head -25
echo '=== recent progress ==='
tail -15 "$PLAN_DIR/progress.md" 2>/dev/nullBecause a plan is matched by its repo, rather than by a single "active plan" pointer, several plans can run at once in different repos, or in the same repo using worktrees.
All the plan files live in one folder we choose, outside the repo. Mine is my Obsidian vault, which means every spec, every judgment call, and every phase is something I can browse and search later. When a PR merges, /spec-workflow:audit-plans checks it against GitHub, writes up what was delivered, and archives the plan.
One folder per plan, and the frontmatter shows up as properties at the top of each file.
Note: yes, I use light mode, despite most engineers preferring dark, because I am not a vampire.
Installing It
Two commands to install, and one to set up:
/plugin marketplace add Phongtlam/spec-workflow
/plugin install spec-workflow@phongtlam
/spec-workflow:setup
Setup asks for a folder to keep plans in, installs the plugins it needs (planning-with-files and ralph-loop, plus pr-review-toolkit if we keep the default review skill), checks which optional MCP servers are around, and asks before changing anything. Beyond that it only needs a recent Claude Code with plugin support and git, plus the GitHub CLI for audit-plans. The wake lock is Mac-only, everything else runs wherever Claude Code does.
After restarting Claude Code, the whole flow is three commands, with an optional fourth before the PR:
/spec-workflow:spec <plan-name>
/spec-workflow:decompose
/spec-workflow:implement-plan
/spec-workflow:run-qa
A small, well-understood feature makes a good first run, something with two or three phases, to get a feel for the plan files before trusting it with something bigger. And since the rules are just files, the build agent and the skills are a good place to start writing down our own way of working.
The source is on GitHub under MIT.
The plugin on GitHub. The CLAUDE.md in there is the same idea again, the rules for working on the plugin, written down for the agent.
Conclusion
AI agents are very good at writing code. Whether we can trust that code depends on the process we put around them, and the best process to start from is the one we already trust ourselves.
- Write the spec first, with research behind it and boundaries that say what not to touch.
- Answer the open questions one at a time, so nothing is left for the agent to guess.
- Plan in vertical slices that each work end to end, so every step can be checked.
- Put the methodology in the agents' files, not the prompt, so it holds up on long runs.
- Write the test first, every time, so the output is something we can trust.
- Borrow ideas that have already been proven elsewhere, and let each plugin do the job it is best at.
Six months and more than a hundred projects and features later, I would not go back to prompting straight into code for anything bigger than a small fix.
Build the process we trust, then let the agent work inside it.
If anyone tries it and something breaks, or there is a step that should not be there, feel free to open an issue or reach out :).
This is a personal project, and the views here are my own. The examples and screenshots all come from my personal projects.