4 min read Emadideen Ghannam
Why I write specs in the repo, not Confluence
Confluence is where decisions go to die. The repo is where decisions live next to the code they describe.
Every team I have joined had a Confluence space. Most of them had a Notion workspace. A few had both. Each had thousands of pages, half of them stale, none of them indexed by the engineers who needed them.
I write specs in the repo now. Markdown. tasks/specs/YYYY-MM-DD-<topic>.md. Committed alongside the PR that implements them.
Three reasons. Each one obvious in retrospect.
1. The spec lives next to the code
When the spec lives in Confluence and the code lives in GitHub, they drift. The reader of the code does not see the spec unless they know to look for it. The reader of the spec doesn’t see the code unless they know to look for it. Two sources, no link.
When the spec lives in the repo, the code reader sees it in the directory listing. The blame on a file points to a commit. The commit references the spec. The spec references the file. The graph is dense.
This is not a small thing. Six months after a feature ships, the engineer who has to extend it usually doesn’t remember it exists. They open the file, they see a comment pointing to tasks/specs/2025-09-12-attendance-rounding.md, they read the spec, they understand the trade-offs that were made. Without that link they make assumptions, they get them wrong, they ship a bug.
2. The spec is searchable by the same tool you use for code
Engineers grep the codebase fifty times a week. They Cmd+P for filenames a hundred times a week. They go to Confluence twice a year, when somebody nudges them to.
If the spec is in the repo, it shows up in code search. git grep "tenant isolation" finds the spec and the code. Cmd+P "tenant" finds them both. The cognitive cost of locating documentation drops to zero.
Confluence has search. It is bad. It is bad enough that engineers don’t use it. The right metric isn’t “is the search functional”; it’s “do engineers reach for it.” For Confluence the answer is no. For git grep the answer is yes.
3. The spec is versioned with the code it describes
The spec is wrong by Q3. Always. Reality drifts. The spec said “we use BullMQ for retries”; six months later we replaced BullMQ with a custom worker because of clock-skew issues. Did anybody update the Confluence page? No. The Confluence page now lies.
When the spec is in the repo, the lie shows up in code review. A PR removes the BullMQ dependency. The reviewer notices the spec still mentions BullMQ. They ask: “should we update the spec or write a new one?” The answer is usually “write a new one and supersede the old.”
Specs accumulate. They are dated. The team’s history is a folder of dated decisions. Reading them in order tells you what the team used to think and why they changed. Confluence pages are edited in place; the history is invisible.
The format I use
The spec template I converged on, in 2026:
# <topic>
Date: 2026-03-02
Status: accepted (or: proposed | superseded by ...)
## Context
What problem we're solving. Why it matters now.
Who's affected. What changed in the world to make this come up.
## Options
Two or three real choices. For each: cost, what it absorbs,
what it makes worse.
## Decision
The choice we made. Why we made it.
## Consequences
What we'll have to build. What we'll have to monitor.
What we're committing to that future-us has to live with.
## What could go wrong
Three to five things. Each with a mitigation.
Five sections. Two pages. No more.
I do not write Architecture Decision Records (ADRs) by the formal Michael Nygard template. I read his original blog post in 2014 and absorbed the spirit. The format above is the spirit, in fewer words.
What about wider docs?
Specs cover decisions. They don’t cover:
- Onboarding instructions (“how to clone, how to install, how to run”). Those go in
README.mdandCONTRIBUTING.md. - API reference. That is generated from the code, not written by hand.
- Marketing-facing product docs. Those belong in a customer-facing system. Not the repo.
The split is: anything an engineer needs to know to make a decision goes in the repo. Anything a customer or external party needs goes elsewhere.
What I lost by leaving Confluence
Two real things:
- Tables and rich embeds. Confluence’s table editor is good. Markdown tables are okay. For the occasional “compare four options across six dimensions,” I sometimes paste a Mermaid diagram or a CSV. It is fine, not great.
- Comments threaded on a paragraph. Confluence lets a reader leave a comment on a specific line. Markdown doesn’t. We use PR comments instead, which work, but only at PR time.
For these losses I gain everything in the three reasons above. Net positive every team.