There is a pattern that experienced developers have used long before AI arrived: write what a piece of software is supposed to do before writing any code. Call it a spec, a design doc, a technical brief — the name doesn't matter much. What matters is that it exists, and that it forces clarity before implementation begins.
AI makes this pattern both more powerful and more accessible. A spec written in Markdown is not just documentation. It becomes the context the model reasons from — directly, without any conversion or reformatting. Write the spec clearly, and the code you get back reflects that clarity.
The vague prompt problem
When you prompt an AI with something like "write a function that processes user data", you are asking it to make a series of decisions you haven't made yourself. What does "process" mean? What does the input look like? What should happen on error? What are the edge cases?
The model will answer these questions — it has to — but it answers them based on the most common patterns in its training data, not based on your specific situation. The result is code that might work for someone's problem. Often not yours.
Write a function that validates a user registration form.
## validateRegistration(input) **Input** - email: string (required, valid format) - password: string (min 8 chars, 1 digit) - displayName: string (optional, max 40 chars) **Returns** { valid: bool, errors: string[] } **Edge cases** - Email with + alias must pass - Password "12345678" fails (no letter) - Empty displayName → omit from output
The second version takes three minutes to write. It saves you considerably more than that in review, correction, and debugging cycles. And — crucially — it exists now as a document you can return to, share, and update.
What a spec actually is
A spec for AI-assisted development doesn't have to be a formal engineering document. It just needs to answer the questions the model would otherwise guess at:
One or two sentences. The purpose of the function, component, or module. What problem it solves and for whom.
Inputs with types and constraints. Outputs with shape and meaning. This is the contract. Everything else follows from it.
The things that are not obvious from the happy path. Empty inputs. Invalid values. Race conditions. The cases you know will arise because you know the domain.
External services, existing modules, libraries that must or must not be used. Context the model doesn't have unless you provide it.
That's it. No formal notation required. Plain language, organised with headings and lists, is sufficient — and Markdown is the natural format for exactly that.
Why Markdown specifically
This is where Markdown really shines. Not because of any particular feature, but because of its fundamental property: it is structured text that is equally readable by humans and AI, with no translation layer in between.
A ## heading in your spec becomes a section the model treats as a distinct concept. A bullet list communicates enumeration. A code block signals: this is literal, not approximate. You get semantic structure without XML, without JSON, without any format the model has to parse. The structure is already there, embedded in the text itself.
Claude, ChatGPT, Gemini — they all respond in Markdown. When your spec is in Markdown, the model's output can feed back into the same format without any friction. The spec can grow to include code examples the model generated, responses you accepted, and clarifications you added. It stays coherent as a document.
Markdown is plain text. Git diffs are readable. When your spec evolves — and it will — you can see exactly what changed, review the history, and understand why a particular decision was made. A Word document or a Notion page can hold this information, but they cannot give you a clean git diff.
A SPEC.md file in the same folder as the code it describes is accessible to every tool in your workflow. It can be referenced in a README, linked in a PR, reviewed by a colleague, or fed to an AI in a future session without any export or conversion step.
Markdown is the one format where the way it looks when you write it is precisely how AI reads it. That alignment is not incidental — it is the reason spec-driven development and Markdown belong together.
What a good spec looks like
A practical example. This is a spec for a small utility function — not a large system, just something representative of daily work:
## parseInvoiceDate(raw: string): Date | null Parses a date string from an imported invoice CSV. Returns a JavaScript Date if the input is recognisable, or null if it cannot be parsed reliably. ### Input formats observed in the wild - `2024-11-03` — ISO 8601 (most common) - `03/11/2024` — DD/MM/YYYY (Swedish suppliers) - `November 3, 2024` — long-form English (some UK invoices) - `3 nov 2024` — abbreviated Swedish month names ### What NOT to accept - US-style MM/DD/YYYY — ambiguous, never appears in our data - Two-digit years — treat as invalid - Relative strings like "yesterday" — return null ### Error handling - Return null (not throw) on unrecognised format - Log a warning to console with the raw input when returning null ### Dependencies - No external date libraries. Use built-in Date parsing with explicit format detection. - Must run in both Node and browser environments.
This takes perhaps five minutes to write. It eliminates an entire category of back-and-forth: the model won't guess that you want null instead of an exception, won't introduce a date library you didn't want, and won't conflate DD/MM and MM/DD. Every one of those decisions is already made.
The workflow in practice
The workflow is straightforward:
Open a .md file. Write what you know about the problem. Leave blanks where you're uncertain — those are exactly the places the model can help clarify before it writes a single line of code.
Paste the spec into the prompt. Append your instruction: "Implement this in TypeScript", or "Review this spec and flag any ambiguities before we write code." The spec is the context; the instruction is short.
When you discover a new edge case, add it to the spec. When a dependency changes, update the constraints. The spec becomes the single source of truth — more useful than inline comments, more durable than chat history.
Code review becomes mechanical in the best way: does the implementation match the spec? If yes, it's correct. If not, either the code is wrong or the spec needs updating. Either way, you have a clear decision to make.
The reusability bonus
A spec written in Markdown doesn't become obsolete after the first implementation. It is reusable context — in the sense that you can drop it into any future conversation, with any AI tool, and immediately pick up where you left off without re-explaining the domain.
This is closely related to a broader pattern: using Markdown files as lightweight, portable context for AI agents. You don't need a vector database or an embedding pipeline to give an AI tool meaningful background knowledge about your project. A well-written Markdown file will do. That idea is worth exploring on its own — and it's covered in Markdown as RAG: your own lightweight context layer.
Spec first, code second
Before asking AI for code, write a SPEC.md. Describe what the function or module is supposed to do. Specify inputs, outputs, and edge cases. Note dependencies and constraints.
Markdown is the right format for this because it structures your thinking without bureaucracy, renders in every tool, and is read by AI with exactly the fidelity you'd hope for. The five minutes you spend writing the spec are almost always recovered in the first round of review.
Start small. Pick one function you're about to implement. Write the spec before you write the prompt. See what comes back. The quality difference is usually clear enough on the first attempt.
Related reading: The full workflow — phases, slices and prompts · Markdown as RAG — your own lightweight context layer · Which Markdown flavor for AI? · Plain text formatted — what "AI-ready" really means
Further reading: Spec-driven development: the full workflow · Markdown as RAG — your own lightweight context layer · Build your own coding agent