Development record
One writer per file
The rule. A committed record survives parallel programmes only when it has one writer per merge window, reached by exactly one of two shapes: a multi-writer source is atomicised into per-record files — and the family is complete only when its id mint is guarded by an armed uniqueness detector — or a single file is kept only where a single writer is guaranteed by construction (a derived aggregate with one publishing path, or a per-worktree local file). A multi-writer single file is a merge hotspot regardless of how disciplined its appenders are.
Why. The 2026-07-22 two-programme merge drew the line empirically. Across
roughly thirty record files landing from two concurrent programmes, the only
textual conflict anywhere was the single decision log (since retired for one
record per decision in .abcd/work/decisions/) — the one shared record that
was a multi-writer append-only file. Every atomicised family
(issues, specs, intents) merged file-clean, but collided on minted ids
exactly where no detector was armed: the issue collisions (iss-110, iss-111)
were caught mechanically by issue_id_unique, while the spec collisions
(spc-10, spc-11) were caught only by a human reading a merge diff — the gap
iss-115 records. Atomicisation without a detector trades a visible textual
conflict for a silent identity collision, which is strictly worse. The
changelog programme is the pattern's clearest positive instance: release
lines are atomicised into impact-carrying issue records (detector-guarded),
and the CHANGELOG.md that remains has exactly one writer — the ship path
on main that ADR-37 names.
Bounds.
- The two shapes are exhaustive for committed records. Per-worktree local
files (
.abcd/.work.local/) satisfy single-writer by construction and are out of scope. - "Armed" means a blocker at a gate that runs on the merged tree (push gate or CI), not an advisory warning: the skew class only exists in the union of branches, so a branch-local check cannot see it.
- The rule names the shape, not the remedy. For an existing hotspot the
smallest compliant fix may be a
merge=unionattribute (legitimate for an append-only ledger whose entries never need identity) rather than full atomicisation; the choice is a design call per record. The attribute holds for a localgit mergeonly: the forge computes a pull request's mergeability and the merge queue's merge without it, so two open pull requests appending to the same file still conflict there. Counting that cost is what makes one file per entry the remedy that removes the conflict everywhere (adr-2609151138420062).
Promotion. The detector half is already discipline-shaped for issues and
intents (issue_id_unique, intent_lifecycle via the shared
validateIDUnique primitive); extending it to spec and ADR ids (iss-115)
and adding a lint that flags multi-writer single files under .abcd/work/
would promote the whole principle.
.abcd/development/principles/one-writer-per-file.md
open on GitHub ↗ · commit history ↗
mentions7
- adr-37Releases are changelog-driven — rolling `[Unreleased]` is the release decision, and automation tags exactly that commit
- adr-2609151138420062The decisions log is a folder of minted records, not one appended file
- iss-110agents/CHANGELOG.md and agents/README.md are plain docs (the itd-5 prompt-version log; a readme) but the plugin agent-loader globs agents/*.md, so both are mis-registered as agents (abcd:CHANGELOG, abcd:README appear in the harness agent list). They have no agent frontmatter and are not invokable workers. Fix: either move these docs out of agents/ (e.g. to .abcd/development/ or a docs path) or make the loader skip non-agent files (require agent frontmatter). Surfaced by the derived-changelog plan adversarial review, which had assumed the abcd:CHANGELOG slot was free for a new composer agent.
- iss-111The open-questions marker pattern admits a bare uppercase word followed by whitespace, so prose that merely mentions a marker matches. Measured against this repository the adapter reports 42 markers across 11 files at medium confidence, and every one is documentation about markers rather than a work marker. The precision cost lands on repos that document their own conventions. Options: require the trailing colon or parenthesis, or drop the bare-word alternative for the ambiguous markers only. spc-12 fixes the current pattern, so this is a design revisit.
- iss-115There is no uniqueness lint for spec ids, so two concurrent branches always mint the same spc-N. abcd mints the next free id from the local tree, so any two branches cut from the same base collide silently: the itd-73/itd-67 programme and the itd-95/itd-96 programme both minted spc-10 and spc-11 on 2026-07-21. The collision is invisible on each branch and only appears on merge, where it breaks the bidirectional intent-spec link in BOTH directions at once — 'itd-95 names spc-10, but spc-10 claims itd-73' and 'itd-67 names spc-11, but spc-11 claims itd-96' — because whichever spec wins the store lookup leaves the other intent's link dangling. iss-74 added an issue-id uniqueness lint; specs need the same guard, and record-lint is the natural home so CI catches it on the PR rather than a human catching it at merge. Consider also whether minting should reserve ids in a way concurrent branches cannot duplicate.
- spc-10derived-versioning
- spc-11installable-versioned-plugin