Encoding the release, part 1 of 8.
We are a small distributed team shipping open source to npm, with strict TDD and trunk-based development. Like every team, we have operations we repeat: releasing, keeping docs in step with the code, setting up a new package. And like every team, we felt the pull to automate them immediately.
We have a rule that resists that pull: every repeated operation gets written down first, run by hand until it is boring, and only then encoded.
Releasing EkoLite is the first operation to make the whole journey. It started as a
document, RELEASING.md: branch release/vX.Y.Z off main, bump the version, write
the changelog entry, open a PR, let CI go green, merge; cut a GitHub Release; approve
the deployment gate; let the workflow publish to npm with OIDC trusted publishing, so no
token ever leaves anyone's machine. Two human decisions, and the machines do the rest.
We ran that document by hand for several releases. That period was not wasted time — it was the requirements phase. Running it by hand told us which steps needed judgement (writing the changelog entry, approving the gate) and which were pure rail (everything else). It told us where mistakes actually happen: a mirror registry quietly rewriting lockfile URLs, a changelog entry forgotten until the Release notes were due. You cannot design good checks for a process you have not felt fail.
When the document was boring, we encoded it: ekohacks release <version>, a command in
our operations CLI. The name of the game is that the document is the spec. The
command does not do anything the manual process does not do. Its preflight is the
document's "Before cutting" checklist, line for line. When we discover the process needs
a new step — and part 6 of this series is about the day we did — the rule is: the
process changes in RELEASING.md first, by hand, then in the tool. The document leads;
the command mirrors it.
This ordering sounds bureaucratic and is the opposite. Automation that grows ahead of the process it automates becomes the process, silently, and nobody can say why it does what it does. Automation that trails a living document can always answer the question "why does the tool insist on this?" with a sentence a human wrote down after a bad afternoon.
The rest of this series is the story of building that command: how a failing test became our review artefact, how you test "wait for CI, then merge" without a CI in sight, which one decision we refused to automate — and what the first real release taught us that no test could have.


