You’ll receive an email confirming your submission.
Our team will contact you within 24–72 hours, depending on the complexity of your request.
By submitting, you agree to our [Privacy Policy] and consent to receive updates or consultation support from Open Reach Tech.
Please select the privacy consent checkbox.

components..title

components..description

components..title

components..description

You’ll receive an email confirming your submission.
Our team will contact you within 24–72 hours, depending on the complexity of your request.
By submitting, you agree to our [Privacy Policy] and consent to receive updates or consultation support from Open Reach Tech.
Please select the privacy consent checkbox.

Keeping the spec as a diff per version

Portrait of Jiro Yamamoto
Jiro YamamotoBackend Developer

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.

Banner of Keeping the spec as a diff per version

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, /hora calls /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-hotfix is the only one that /hora does 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.

The spec accumulates as a diff per 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
UnreleasedAdditions, changes, and removals are all accepted, and the version number does not change. There are no users, so changing a contract breaks nobody
ReleasedNot 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

The git model

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