- 1. Introduction
- 2. Design documents written by people rot without being updated
- 3. From the second version on, write only the diff
- 4. Materials are also copied per version
- 5. Carrying over is confirmation, not guesswork
- 6. The version number is decided by whether it has been released
- 7. git branches correspond to versions too
- 8. How we use this in maintenance
- 9. Summary
Keeping the spec as a diff per version
The twelfth article in the "Design of Hora Kit" series. Design documents written by people tend to end up one of two ways: they rot without being updated, or they are overwritten with the latest state and the past disappears. This article describes a mechanism that keeps the spec as a diff per version, so that "what we decided to build for 1.0.0" is still readable after 1.1.0 has been built.
1. Introduction
This article is the twelfth in a series explaining the design of Hora Kit, the AI development framework that we (Open Reach Tech) released as open source. The main article is here.
We open-sourced Hora Kit, an AI development framework for truly automated development
A note on terminology. There is only one command you type in Hora Kit:
/hora. Depending on the situation,/horacalls/hora-spec(writes the spec),/hora-setup(creates the implementation repositories),/hora-plan(fixes the version and writes the feature list and contracts),/hora-build(takes one feature through the 18 checkpoints), and/hora-accept(performs acceptance), in that order./hora-hotfixis the only one that/horadoes not call; a person types it directly. The docs call/hora-spec"the deciding side" and the rest "the building side", and this article uses those names too.
In the main article, we wrote that "what helps most in maintenance is that each version stays around together with its spec". This article describes that mechanism.
I suspect many of you have had the experience of a design document being overwritten with the latest state again and again, until nobody could tell which point in time it was describing. This is for you.
2. Design documents written by people rot without being updated
First, the problem this design solves.
A design document is correct at the moment it is written. After that, features are added, changed, and removed. The design document may or may not be updated. When you open it six months later, nobody can tell whether what it says is "the current product", "the original product", or "somewhere in between".
This is the biggest pain in maintenance. "What we decided to build for 1.0.0" can no longer be read once 1.1.0 has been built.
3. From the second version on, write only the diff
Hora Kit's specs are placed per release version.

specs/
1.0.0/spec.md The first version. Full text
1.1.0/spec.md Only the diff against 1.0.0
1.2.0/spec.md Only the diff against 1.1.0
From the second version on, spec.md is a diff against the previous version. What comes out of the dialogue is only that version's own document information and its new features; nothing else is written.
# <project name> design document
## 1. Document information ← Always rewritten. The product version changed
## 13. CSV export ← And then, only the sections this version changes
The spec.md of a past version is never touched. The 1.0.0 document remains as it was, as the record of the decisions made when 1.0.0 was built.
What /hora-plan reads is the "resolved document": the full text of 1.0.0 with the 1.1.0 diff layered on top. It is the same when a person reads it: if you want the complete spec of that version, resolve it; if you want to know what changed in that version, read the diff.
4. Materials are also copied per version
The materials placed in annex/ and sources/ also have their own copy per version.
The docs give the reason as follows. If one file is referenced from both versions, then the moment you edit it for 1.1.0, what 1.0.0 was written against silently changes. In other words, correctness of the record is chosen over disk space. An ER diagram that is still needed in 1.1.0 has its own copy in specs/1.1.0/annex/.
5. Carrying over is confirmation, not guesswork
The diff approach has one pitfall. "Unchanged from the previous version" and "nobody looked" cannot be distinguished from the text.
So at a stage that holds sections this version does not touch, /hora-spec presents the previous version's answers, gets confirmation, and passes with a carry-over note attached.
<!-- carried: 1.0.0's numbers, confirmed unchanged -->
Confirmation, not guesswork. A carry-over is the only kind of pass that is indistinguishable from a stage that never ran, so unless a record of the confirmation is left, the two cannot be told apart.
Stage 6 (security) and stage 7 (overall review) alone never carry over anything about what that version adds. Who can call a new operation is always stated in the version that introduced it. Stage 7 reads the resolved document, not the diff. A new operation that contradicts 1.0.0 is invisible in the diff and becomes visible once resolved.
6. The version number is decided by whether it has been released
The boundary for cutting a new version is not the size of the change. It is whether that version has been released. The evidence is the tags on the hora repository, which release.yml creates on merge into main.
git fetch --tags && git tag -l '1.0.0' # empty = unreleased
| Handling | |
|---|---|
| Unreleased | Additions, changes, and removals are all accepted, and the version number does not change. There are no users, so changing a contract breaks nobody |
| Released | Not touched. Done in the next version |
A spec change just before release is treated as entirely normal. It is rework, not a compatibility break. From the second version on, the number is judged against the diff of .hora/contracts/, not by feel. If fields and types were only added, it is minor; if nothing was added, it is patch.
7. git branches correspond to versions too

Nothing is committed directly to main. Work happens on release/<version>, and that version's spec.md is the working target. Each feature's implementation goes on feature/<feature-id> and is merged into release/<version> at gate boundaries. When the version-wide acceptance sweep passes, release/<version> is merged into main and tagged.
release/<version> is never rebased. Nothing that has been merged once is ever rolled back or rewritten. There is exactly one exception: when a hotfix/* has landed on main. /hora checks origin/main at every launch and immediately after each merge into release/<version>, and if it has moved, follows it. The follow-up is assembled on a throwaway temp branch, and release/<version> is moved only once, at the end.
8. How we use this in maintenance
As written in the main article, we maintain a system of nearly one million lines with this approach.
When tracing "when, why, and how did this feature come to have this spec", the file we open is specs/<that version>/spec.md. Only the sections that changed in that version are written there, so reading the diff tells us "what was decided in that version". The previous version's document has not changed, so "what was it like before that" can be traced the same way. With a single design document that keeps being overwritten with the latest state, this is impossible.
Hold the design document not as "the latest state" but as "the history of decisions". This is why we find this approach so valuable in maintenance.
9. Summary
Place the spec per version, and from the second version on, write only the diff. Never touch past versions, and copy materials per version too. Do not guess "unchanged"; confirm it and mark it. The boundary for cutting a version is not the size of the change but whether it has been released.
I hope this is useful to anyone struggling with how fresh their design documents are.
The original sources are here. https://github.com/openreachtech/hora-core/blob/main/docs/quick-start.ja.mdhttps://github.com/openreachtech/hora-core/blob/main/kit/skills/hora/references/spec-format.mdhttps://github.com/openreachtech/hora-core/blob/main/kit/skills/hora/references/commits.md