I wanted my specs checked both ways, so I built spec-sync
Leif ·
Spec-driven development is not new. The version I kept running into was one-way: you write a spec, something generates the code, and from then on the spec is a museum piece. Written once, rotting the moment the code moves. I wanted the opposite. My own SDD: a CLI tool where the spec and the code are checked against each other in both directions, and CI fails when they drift.
That is the whole idea of spec-sync, and it really is that simple. Spec and code can each drift from the other. So you check both ways.
A folder of companion files
A spec-sync spec is not one big document in a wiki. Each module gets its own folder of companion files: the contract itself (the .spec.md), plus requirements, tasks, context, a testing plan, and an opt-in design file. They live together under specs/, keyed back to the source they describe, so opening a module opens everything about it at once. No tab-hunting in Confluence, no doc nobody remembers writing.
Only the spec is checked against the code. The rest is structured context, for me and for an agent. Twelve languages, one Markdown contract, one Rust binary.
The format bends, the checking does not
The spec format is deliberately generic. It is normal software stuff (purpose, public API, invariants, behavior, change log), and you shape the required sections however your project needs. I did not want a rigid schema people fight against. The format bends. The part that does not bend is the checking.
It runs both ways
Here is the part I actually like. Ship code your spec does not mention, and you get a warning: the spec is incomplete. Write a spec for code that is not there, and you get an error that fails the build: the spec is lying. Same tool, both directions, and the asymmetry is on purpose. A lying spec is worse than an incomplete one, so it is the one that stops the merge.
Does it hold up?
Does any of this actually hold up in real work? Honestly, yes, and not because I babysit it. I do not hand-maintain these specs anymore. The build does. spec-sync checks itself at 100% spec coverage on every run, so a public symbol with no spec is a failed build, not a polite reminder. The rest of the flock is the same. In Merlin, Quill, and fledge, somewhere between a fifth and nearly half of recent commits change a spec and its code together. In augur and attest, every spec change so far shipped in a commit that also changed code. There is nothing to chase, because drift does not survive the gate.
Agent and human first
This runs through everything I build. spec-sync, fledge, augur, and attest are not human tools with an agent mode bolted on, and they are not agent tools that forget a person is in the loop. They are agent and human first, both at once.
The drift check is the clearest example. When I write code that drifts from the contract, spec-sync flags it. When an agent writes code that drifts from the contract, spec-sync flags it the exact same way. The contract does not care who is typing. The same spec, the same gate, the same red build, for the person and the agent.
If you are letting agents write code, that matters more every month. A contract they cannot quietly break beats docs that look nice. spec-sync is the one I wanted, so I built it.