Spec.md
The spec.md file is the single most important document produced as part of the speckit’s SDD workflow. It is the first piece of documentation produced, setting the direction for the remaining documentation. It is the most durable artifact of the documentation. The spec.md file is the only place that captures what the program is supposed to accomplish, and why this behavior is being implemented. It is information dense, and durable. Since it describes what the program should do, without getting into the details of how, it is most likely to remain relevant over time.
In this blog post, we’ll look at what parts make up the spec.md file, how to generate a great one, and how to evaluate/code review a spec.md file.

Crafting a great spec.md
A spec.md file should define all of the observable behavior you care about. You should be willing to give your spec.md to someone else, and no matter how they choose to implement it, as long as it meets the desired behavior, you would be happy with it. This doesn’t mean everything needs to be lawyered to death, though. It means focusing on the aspects you care about, leaving flexibility for the parts where different solutions might exist.
A spec.md is not the place to define the internal behavior or structure that you might care about. It’s someone else’s job (aka the plan stage) to combine the user stories & requirements of a spec with our technical knowledge to guide any internal considerations.
Spec.md files make PM’s & testers happy.
Plan documents make engineers happy.
User stories frame the objectives, write them with intention
Outcomes are not user stories. Engineers especially have a tendency to think “I need my REST endpoints to have these parameters, this type of validation etc”. Stop, for just a second. All of those details are important, and we’ll get to them, but ask yourself why am I doing these things?
- Define your user. Is it a customer on your SaaS platform? Sometimes our users are developers who are trying to consume our SDK or API. Sometimes our users are ourselves, in order to simplify or improve development of the codebase
- What is the goal the user is trying to accomplish? Not the steps, the goal.
- What benefits does the user get when they accomplish their goal? Why is this important?
There are lots of articles written about “How to write a user story”, and will help to understand the correct framing. Take the patterns to heart, and don’t just add “As a user, I want to ABC because XYZ” madlibs style. Importantly, AI is really bad at writing user stories, so you really need to understand the process, and you need to bring to the table strong opinions about what is to be accomplished.
Here’s a list of some of the ways AI tends to mess up the user stories:
- “Why is this important: This adds foundational capabilities to blah blah” – This is AI-speak for I don’t know why we’re working on this feature. That’s okay, because it’s your job to provide the context for where this is going and why something is needed
- “A user needs to call the GET /abc/endpoint to get a list of blah blahs” – This needs to go up a level into _why_ we are doing these things
- “Error formats need to be in XYZ structure”, or similarly: “Output is in JSON format” – These are not user stories at all, these are acceptance criteria of user stories
Acceptance criteria – not too hot, not too cold, just right
A sign of a well-written user story is that the acceptance criteria naturally flows from it. The acceptance criteria should be written from the perspective of the test team. The test team is looking for:
- What are the scenarios that I care about? What is supposed to happen when circumstances happen?
- What are the known failure modes, and how should the system handle them?
- What side effects exist? If the scenarios are run multiple times, or interleaved with other operations, what happens?
- What is not covered? Is it because we don’t care about it, or is it because we forgot to consider a scenario?
In this framing good acceptance criteria is:
- High-level (assume that there is an expert tester who can read the context of the user stories and generally knows about the codebase)
- Comprehensive about the scenarios covered, while being high level. Describe the various permutations, or side effects that you care about.
- Only as specific about the outcome behavior as necessary. “The document is successfully created” can be a valid acceptance criteria. Other times you might need “The error_type matches the upstream error, and the status code is 400”
A good way of thinking about acceptance criteria – if the PR author said in their comments “I manually tested all of these scenarios and they work” would you be happy with knowing the code does what you want it to?
Functional requirements
If the acceptance criteria is for the test team / the PR author / anyone manually validating correctness, the functional requirements are for the LLM. Functional requirements are a prioritized list of detailed behavior the user stories must implement. A good FR should be testable. If you can imagine writing a unit test to validate the requirement is met, then this is probably a good functional requirement.
It is easy to bleed functional requirements into technical requirements. If you are describing the behavior or functionality of the system, this is a functional requirement. If you are describing the data model, architecture, or other implementation detail, this is a technical requirement, and is out of scope for this section
Rubric for evaluating a spec.md file
- Overall size requirements
- spec.md file < 500 lines
- 3 or less user stories
- 15 or less functional requirements (FR’s)
- User stories
- Does it describe behavior the user actually cares about?
- Is the user story really just an acceptance criteria or functional requirement of a different user story?
- Does the user story define the user & their role, their goal, and the benefit they get from achieving that goal?
- Acceptance criteria
- Can the acceptance criteria be manually verified?
- Is the success criteria defined specifically enough to unambiguously determine if the code passes or fails?
- Consider the dumbest, but earnest implementation of the spec. Would the acceptance criteria catch all of the desired, observable behaviors?
- For anything not listed, are you comfortable with any behavior (e.g. for errors, performance, concurrency, etc). Any other edge cases you care about?
- Functional requirements
- Can you write a unit or functional test to verify the behavior described?
- Can you combine any of the functional requirements together, while still describing the same behavior?
- Does the requirement describe what should happen (good), or how it should be accomplished (bad)?
How to quickly and efficiently review a spec.md file
So, you used /speckit.specify to generate your spec, or perhaps you’re looking at the spec during code review time. What’s the best way to review the file?
- Read just the user stories first. Not the acceptance criteria or other requirements. Follow the rubric for user stories, make sure they are actual user stories, and that they are correct for what you’re trying to solve for.
- If a user story is superfluous, (should be rolled into another story, or made into acceptance criteria), ask the author to do so, but continue reviewing
- Otherwise, if the user stories need further editing, stop, and ask for revisions before continuing
- Take a extra quick look at the “Why this matters” section, and ensure it captures the intent behind the work. Imagine if you were an AI reading this file in the future. Would you be able to understand what the original intention was when trying to make future modifications?
- Add any comments about “Why this matters”, but continue reviewing since this is not likely to affect the remainder of the spec
- Look at the acceptance criteria for all the user stories from a testing perspective. Could you actually test these? Are there any edge cases you can think of from your experience that should be considered? If you ONLY verified these scenarios and no others, would you be satisfied this feature works?
- Stop, and iterate on feedback until you’re happy with these sections before continuing to the functional requirements
- For the functional requirements, make sure they are all testable (with minor exceptions), that they are precise and detailed for what the requirements are, and that they are not duplicitous with other functional requirements. Use your judgement as an engineer to specify detailed behavioral aspects you are interested in.
- Do a final pass throughout the entire spec. Consider reading it aloud to yourself. This is the one file where details matter, so consider the wording, how the sections fit in with each other, and whether there are ambiguities, conflicting requirements, under-specification, or duplication between the sections. Since this should be a relatively small document (500 words or less), it should be quick to do a final pass, once the detailed feedback has been addressed