The Spec That Shrinks: How We Document as We Build

A spec that shrinks with every PR is documentation that never goes stale.

Table of Contents

Most engineering teams have a documentation problem they don't realize is actually a process problem.

You write a spec. You start building. Six weeks later, the spec describes a product that no longer exists. Nobody updates it — by the time you'd need to, you're three problems ahead.

We spent time thinking about how to fix this without adding overhead. The answer was simpler than expected: make the spec a file that lives in the repository, and give it one rule — whatever lives in it has not shipped yet.

The old approach

Our previous flow: write a Google Doc, share with stakeholders, ship. The Google Doc was the source of truth at the start. By month two, it was archaeology.

No one updated it. No one had time. And worse — new contributors would read the spec and confidently build something the team had already decided to change.

The root cause wasn't laziness. It was that the spec and the code lived in different worlds. The code evolved with every PR. The spec sat still.

Specs as code

Each Goal now opens with a spec file at docs/specs/<feature>.md. It describes intended behavior — not what exists today, but what we're building toward.

The file has one rule: whatever lives in it has not shipped yet.

Every PR that delivers part of the Goal must move the corresponding sections from docs/specs/<feature>.md into docs/. When the spec file has nothing left, delete it — the Goal is fully documented.

The spec can also grow. When scope is added mid-Goal, a PR adds new sections to the spec file. Those sections then follow the same path — implemented and graduated to docs/, or removed if the scope is cut. What the file can never hold is behavior that has already shipped.

docs/specs/<feature>.md   ← only what isn't built yet
docs/<feature>.md         ← only what is currently shipped

Two directories. No mixing. A reader always knows exactly what they're looking at.

Handling weeks of small PRs

One constraint shaped the design: we use trunk-based development with small PRs merged daily into main. A Goal can span weeks of work. No single PR represents "the feature is done."

A naive approach — graduate the whole spec when the Goal ships — would mean the spec sits untouched for weeks then disappears in one big commit. That's just delayed staleness.

The solution: the spec graduates incrementally. Each implementing PR moves only the sections it delivers. The file shrinks with every merge. Progress is visible in the git history, and docs/ stays strictly current-state throughout.

Why two directories matter

We explored an alternative: keep everything in docs/, but mark unimplemented sections with a callout:

Planned — not yet implemented.

It didn't work. docs/ became a mix of current reality and future vision. Readers couldn't trust the documentation. The signal-to-noise ratio degraded immediately.

Clean separation is the constraint that makes both directories trustworthy. docs/specs/ is explicitly a "not yet real" zone. docs/ is the source of truth for what exists today.

Design belongs in the spec

Figma links used to live in a standalone DESIGN.md. We moved them into the spec under a ## Design section.

When a feature ships, the design links graduate with the behavioral content. The final documentation includes both: the written description of what users can do, and the Figma reference for how it looks.

No separate question of where the design lives. It lives where the spec lives.

Google Docs as input, not substitute

Some Goals start with external stakeholders who aren't on GitHub. For early ideation and discussion, a Google Document works well.

But before the Goal is opened, that content must graduate to a markdown spec. The Google Doc is an input — a collaboration surface that feeds into the spec. A Goal description never links a Google Document as its Spec.

If a Google Document was used, the link lives in the Goal issue description — not in the spec. The spec contains only unimplemented behavior, nothing else.

The result

The pattern has one visible outcome: documentation that never goes stale, because it's updated by the same PR that ships the behavior.

Writing a spec is no longer a one-time act before the real work starts. It's the starting point of a file that transforms into documentation as you build — section by section, PR by PR.

When it's empty, you're done.

Have a suggestion?Edit this page