# The Spec That Shrinks: How We Document as We Build

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

**Authors:** [Vadim Zolotokrylin](/c/people/vadim-zolotokrylin)

---

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

<!-- ref: https://github.com/holdex/developers/pull/108
     Process change: specs move from Google Docs to markdown files
     in docs/specs/, versioned with the codebase. -->

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.

```text
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

<!-- ref: https://github.com/holdex/developers/pull/108
     This constraint was surfaced during planning: trunk-based
     development means no single PR represents "feature complete."
     The spec had to graduate incrementally, not all at once. -->

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

<!-- ref: https://github.com/holdex/developers/pull/108
     An alternative using inline callout markers (> [!NOTE] Planned)
     was explored and rejected. The result was that docs/ became
     a mix of current reality and future vision. -->

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

<!-- ref: https://github.com/holdex/developers/pull/108
     Figma links previously lived in a standalone DESIGN.md file.
     The decision to move them into the spec came from the observation
     that design IS part of the spec — it describes intended
     visual behavior, not current state. -->

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.
