How to Adopt a Documentation-First Workflow
Documentation-driven development (DDD) is the practice of writing project documentation before writing any implementation code. The spec comes first. The code comes second. This guide walks through the process step by step, from the first Markdown file to a project that's ready to build.
What You'll Accomplish
By the end of this guide, you'll have a documentation scaffold for a software project that includes a product requirements specification, a design specification, a test plan outline, and a contributing guide. More importantly, you'll have validated your idea by forcing it through a structured writing process that exposes gaps, ambiguities, and bad assumptions before a single line of code exists.
Prerequisites
You need a text editor and a willingness to write Markdown before you write code. That's it. No build tools, no dependencies, no framework. If your project will eventually involve code, you'll want a Git repository initialized, but even that can wait.
This guide uses Markdown throughout. If you're unfamiliar with Markdown syntax, the Markdown Guide covers everything you'll need.
Why This Works
Writing a clear specification for a bad idea is remarkably difficult. The spec fights back. It asks questions you weren't ready for and exposes assumptions you were hoping to sneak past yourself. By the time the documentation is solid enough to stand on its own, you know two things: whether the project is worth building, and exactly what you're building.
This isn't a theoretical claim. Every project documented on this site, from LlmsTxtKit to Rune & Rust, started as a Markdown file that got increasingly detailed until there was no choice but to build the thing. The documentation phase has killed more bad ideas than any linter or test suite ever could.
Step 1: Write the Problem Statement
Create a file called PROPOSAL.md (or README.md if you prefer) and start with the problem. Not the solution. The problem. Write 2–3 paragraphs that answer:
- What specific problem does this project solve?
- Who has this problem? (Be specific: "developers" is too broad; "C#/.NET developers building AI tools who need to parse llms.txt files" is useful.)
- What do people currently do instead? What's wrong with the existing approach?
If you can't write a compelling problem statement, stop here. You don't have a project yet; you have a vague idea. That's fine. Come back when the problem crystallizes.
What to watch for: If your problem statement keeps drifting into describing your solution, you haven't separated the problem from the approach yet. Rewrite until the problem stands on its own, independent of how you plan to solve it.
Step 2: Define Success Criteria
Add a section to your proposal that lists 5–10 specific, measurable criteria for what "done" looks like. These should be concrete enough that someone else could look at your finished project and verify each one independently.
Good success criteria are binary (met or not met), specific (not "the tool is fast" but "parsing a 50KB llms.txt file completes in under 100ms"), and scoped (they describe what the first version does, not what you hope to build eventually).
Example from LlmsTxtKit's PRS:
- The parser handles all well-formed llms.txt files per the published spec
- The fetcher returns structured status codes (Success, NotFound, Blocked, RateLimited, Timeout) instead of throwing exceptions for expected failure modes
- The library has zero required third-party dependencies
Step 3: List Your Non-Goals
This is the step most people skip, and it's arguably the most valuable. Write down everything your project explicitly will not do in its first version. Non-goals prevent scope creep by making the boundaries visible before you start building.
Every feature you exclude is a decision you won't have to make during implementation. Every "that's out of scope for v1" conversation you'd otherwise have is already documented and resolved.
Example non-goals from LlmsTxtKit:
- Generating llms.txt files (this is a consumer library, not an authoring tool)
- Supporting .NET versions older than 8.0
- Bypassing WAF protections (the library detects and reports blocks; it doesn't try to circumvent them)
- Model-specific token counting (uses an approximate heuristic instead)
Step 4: Write the Design Specification
Create a specs/design-spec.md file. This is where you describe how the project will work at an architectural level. The design spec should cover:
Component breakdown: What are the major pieces of the system? How do they relate to each other? A simple ASCII diagram or a list of components with one-paragraph descriptions is sufficient. You're not writing API documentation yet; you're establishing the shape of the system.
Data flow: What happens when a user (or another system) interacts with your project? Trace the path from input to output. "The user provides a domain name. The fetcher retrieves /llms.txt from that domain. The parser converts the raw Markdown into a structured document object. The validator checks the document against the spec and returns a report." This kind of narrative data flow catches integration issues that component-level thinking misses.
Key design decisions: For every non-obvious architectural choice, document the decision and the reasoning. "We use structured result types instead of exceptions for expected failure modes because HTTP 403 (blocked by WAF) is a normal outcome, not an exceptional one." Future-you will thank present-you for these notes.
Error handling strategy: How does the system handle failure? What failures are expected (and should be handled gracefully) versus unexpected (and should surface as exceptions or errors)?
What to watch for: If your design spec is longer than 5–6 pages, you might be designing too much upfront. The goal is to establish architecture, not to write pseudocode for every method. If you find yourself specifying implementation details, you've gone too far. Pull back to the architectural level and save the details for the code.
Step 5: Create the Test Plan Outline
Create specs/test-plan.md. You don't need to write individual test cases yet, but you should identify the testing categories and the scenarios that matter most. This typically includes:
- Unit testing boundaries: Which components have logic complex enough to warrant unit tests? What are the key input/output scenarios?
- Edge cases: What inputs are unusual, malformed, or adversarial? How should the system behave?
- Integration points: Where do components interact with each other or with external systems? These boundaries are where bugs live.
- Test data requirements: What sample data do you need? Where will it come from? Are there real-world examples you can use?
Writing the test plan before the code ensures you're building something testable. If a component is hard to describe test scenarios for, it might be poorly designed. The test plan catches this before you've committed to the implementation.
Step 6: Write the Contributing Guide
Create CONTRIBUTING.md. Even if you're the only contributor, this document forces you to articulate your development standards: coding conventions, branching strategy, commit message format, PR process, and documentation expectations.
The contributing guide also serves as a promise to your future self. When you come back to the project after three months working on something else, the contributing guide tells you how you work on this project, not how you work on whatever you were doing yesterday.
Step 7: Scaffold the Repository
Now, and only now, create the project structure. The directory layout should mirror the architecture described in your design spec. Create the folders, add .gitkeep files to empty directories, and set up the build configuration.
At this point your repository contains:
README.md (or PROPOSAL.md, the problem statement)
CONTRIBUTING.md
CHANGELOG.md (start with "## [Unreleased]", it'll fill in later)
specs/
design-spec.md
test-plan.md
src/
ComponentA/ (.gitkeep placeholder)
ComponentB/ (.gitkeep placeholder)
tests/
ComponentA.Tests/ (.gitkeep placeholder)
TestData/ (.gitkeep placeholder)
The source directories are empty. The specs are full. This is exactly how it should look.
Step 8: Start Implementing (Finally)
Begin with the component that has the fewest dependencies on other components. In most projects, this is the parser, the data model, or the core domain logic. Write the code, write the tests, and update the design spec if the implementation reveals something the spec got wrong.
The documentation isn't a contract you're bound to; it's a living plan that evolves alongside the code. When the code diverges from the spec, update the spec. When the spec describes something you haven't built yet, that's your roadmap.
Expected Outcome
At the end of this process, you have a project with three properties that most projects lack:
- A clear rationale. Anyone (including future-you) can read the proposal and understand why this exists, who it's for, and what problem it solves.
- Validated architecture. The design was reviewed through writing before it was tested through code. Structural issues were caught early.
- Built-in documentation. The project has useful documentation from day one, not as an afterthought bolted on before release.
Troubleshooting
"I can't write the problem statement without thinking about the solution." This is normal. Write the solution-contaminated version first, then rewrite the problem section to stand on its own. It usually takes 2–3 passes.
"My design spec keeps growing." You're over-specifying. The design spec describes architecture and key decisions, not implementation details. If you're writing pseudocode, stop and pull back to the "what" rather than the "how."
"This feels slow." It is slow at the start. The time you spend on documentation is time you're not spending building the wrong thing, debugging an architecture that doesn't work, or rewriting code because you didn't understand the requirements. The payoff compounds as the project grows.
"Nobody else does this." Some do. Readme-driven development is the closest mainstream equivalent. The principle is the same: write the documentation a user would need before the software they'd use it for exists. If the documentation is useful, the software probably will be too.
Further Reading
- I Write the Docs Before the Code, and Yes, I Know That's Weird: The blog post that tells the story behind this methodology
- The Menagerie: Overview of all 13 projects built using this approach
- LlmsTxtKit: A concrete example of docs-first in practice, with the full spec chain from PRS to design spec to implementation