A prompt is a wish. A spec is a contract.
Here is a prompt: “Add a way for users to save their favourite items.”
It sounds complete. It is not. It leaves at least eight decisions unmade, and the model is going to make every one of them without telling you, because a model that stopped to ask eight questions would be an annoying model and it has been trained not to be one.
- Can a signed-out user save favourites? Where would they go?
- Are favourites private, or can other people see them?
- Do they survive a logout? A new device?
- Can you favourite the same item twice?
- What happens when the underlying item gets deleted?
- Is there a limit? What happens at the limit?
- Does the button show state immediately, or after the server confirms?
- What does it do when the network call fails?
The model will answer all eight, instantly, invisibly, and reasonably. “Reasonably” is the trap: the answers will be defensible and they will not be yours. Two weeks later, you will find favourites stored in localStorage, vanishing when the user switches to their phone, and you will have no memory of ever agreeing to that.
The anatomy of a spec that works
Four parts. You do not need ceremony or a template file. You need these four things present, in whatever form.
1. The user-visible outcome
What can a person do at the end that they could not do before? Written as behaviour, not implementation. “A signed-in user can click a star on any item, and that item appears in their Saved page, and it is still there tomorrow on a different device.” Notice that this sentence is checkable. You can perform it. That is the bar.
2. The rules that are not negotiable
The constraints you would be upset to discover were violated. Who is allowed to do this. Where the data lives. What must never happen. This is where you pre-empt the entire security section of this manual:
Rules for this feature:
- Only the owner can read their own saved items. Enforce in the database policy,
not in the UI. Assume a hostile user calling the API directly.
- Saved items live in Postgres, not localStorage. They must survive a device change.
- The star renders optimistically, but rolls back visibly if the server rejects it.
- No new npm packages without asking me first.Every line there is a bug you are not going to have. The last one is worth its weight in gold: unprompted, models install dependencies with real enthusiasm.
3. The context it cannot guess
The model can read your code. It cannot read your intentions, your previous decisions, or the reason that one weird function exists. If there is a pattern in the codebase you want followed, say which file to copy. “Follow the same pattern as app/api/notes/route.ts” is worth three paragraphs of description, because it replaces your description of the pattern with the pattern itself.
4. How you will know it worked
State the check before the work starts. “When this is done, I should be able to sign in as user A, star an item, sign out, sign in as user B, and not see it.” Now the model has a target it can aim at, and you have a test you cannot talk yourself out of running.
Context beats cleverness, every time
There is a persistent belief that prompting is about magic phrasing: the right incantation, the correct persona, threatening the model with consequences. It is mostly not. The dominant variable, by a very large margin, is whether the relevant information is in the window.
A model with the right context and a blunt instruction beats a model with beautiful phrasing and no context, and it is not close. Practically:
- Point at files, not memories. If the change touches three files, make sure the model can see all three. Most doom loops start with the model confidently editing a file while blind to the one that actually calls it.
- Paste the real error. All of it. The stack trace, the line numbers, the surrounding log lines. Not your summary of it. Your summary has already discarded the part that mattered, because you did not know it mattered.
- Paste the real docs when you are on a library the model keeps getting wrong. This single move fixes the version trap from the last chapter outright.
- Keep a project rules file so the standing context (stack, conventions, hard rules) is present on every request without you retyping it.
Scope so it can finish
The size of the request should be the size of a thing that can be verified in one sitting. Ask for a whole app and you get a plausible skeleton with nine stubs in it, and, critically, you will not be able to tell which nine, because they look exactly like the working parts. That is the origin story of most of the demo-to-production gap.
Ask for one end-to-end slice and you get something you can actually exercise. Then the next. The rhythm to aim for:
1. "Plan this feature. List the files you'd touch and what changes in each.
Don't write code yet." -> you catch the bad idea before it costs anything
2. "Good. Do step 1 only. The database table and its RLS policy."
-> one thing, verifiable
3. [you check it. you actually check it.]
4. "Now the API route. Follow the pattern in app/api/notes/route.ts."
5. [you check it] -> git commit -> next sliceStep 1 is the highest-value prompt in that list, and it costs nothing. Asking for a plan before code is where you find out that the model misunderstood you, while the misunderstanding is still one sentence long, rather than four hundred lines of code and a broken migration.
Four prompts that reliably go wrong
| What you said | What it heard | Say this instead |
|---|---|---|
| “Make it work” | Change things until the error stops. Deleting the check that produced the error counts. | “This throws X on line Y. Explain why before changing anything.” |
| “Fix the security issues” | Add some input validation, feel good, leave the database wide open. | “Show me the RLS policy on every table. Which ones allow anonymous reads?” |
| “Also add…” (fifth time, same thread) | Five half-features, each subtly breaking the last, none verified. | One feature. Verify. Commit. Then the next. |
| “Is this production ready?” | It is optimistic and agreeable by construction. It will say yes. | Nothing. Do not ask the author to grade its own work. Go verify it from the outside. |
That last row is not a joke, and it is the reason this product exists. A model asked to review code it just wrote is being asked to find a flaw in its own reasoning using the same reasoning that produced the flaw. It is agreeable, it wants to be helpful, and it has no independent evidence. It will tell you it looks good.