| State | Accepted |
| Architectural Significance | MEDIUM |
| Domain | Developer Tooling |
| Document version | 1.0 |
| Related | ADR-0004 · ADR-0021 · ADR-0022 |
Affected code: crates/rto-spec/src/layer.rs#authored_docs,
crates/rto-spec/src/adr.rs#declares_adr,
crates/rto-spec/src/check.rs#run_layer, crates/rto-render/src/okf/conform.rs#lint_report.
adr_home, which answers where a new document belongs, is in flight as #747 and
is deliberately not linked yet: roteiro check refuses a link to a symbol the
graph does not hold, which is the rule working rather than an inconvenience.
Roteiro can read an authored document — classify it, parse it, check its links, gate its version rules — and can generate one from a scaffold. Between those two it can do nothing: every edit to an ADR or blueprint is done by hand, and every rule the gate enforces is a rule a person has to satisfy unaided.
This records the decision to add authoring commands, and — more importantly —
where the line falls. They act on the authored sources under version
control, never on the OKF bundle, which is a build output that the next
render okf overwrites. And each command is admitted only where its rule is
decidable without judgement: a command that guesses at prose would put
Roteiro's name on a change it cannot defend.
The set is new, fmt, fix, mv, rm, split, merge and index, mirroring
W4G1/okf's CLI so the vocabulary is one a reader
already has. Three of them are admitted narrowly, and the narrowing is the
decision.
ADR-0004 made authoring a pillar and
delivered its first tier: roteiro spec scaffold emits a house-style,
graph-grounded skeleton. ADR-0021 made
the graph's shareable form an OKF bundle, and ADRs and blueprints are concepts in
it. #744 made an ADR something a document declares rather than somewhere it
sits, and #747 made writing one follow the repository's own layout.
So the reading half is complete and portable. The writing half is a scaffold and nothing else.
Three forces make that gap worth closing now:
roteiro check
reports a broken [[…]] after the fact. Nothing offers to fix it, so the
safest edit to an ADR is not to move it.crates/rto-render/src/okf/conform.rs#lint_report
reports L1–L12 and our R1, and every one is remedied by hand.Upstream's set is the wrong shape applied to the bundle, and the right shape
applied to the sources. okf new/mv/rm/split/merge/fmt/fix/index mutate a
bundle. Ours is derived, so mutating it is writing to a cache: correct for a
tool whose bundle is the source of truth, pointless for one whose bundle is a
projection. Pointed at docs/ the same verbs are exactly what is missing.
Add roteiro docs <command> over the authored sources, with each command
admitted only as far as its rule is decidable.
Rejected. The bundle is regenerated by render okf, so every edit is lost on the
next render. It would also break the one-way guarantee
ADR-0021 rests on: the graph is the
source and the bundle is its projection.
Rejected, and this is the one worth arguing. split and merge over prose are
not decidable: a tool cannot know which paragraphs belong to which decision, and
one that guesses produces a document a person must then verify line by line —
more work than doing it by hand, with a machine's name on the result. fix
applied to every lint has the same defect: some of L1–L12 are remedied by
writing better prose, not by editing punctuation.
Each command is added, and its scope is stated where the rule stops:
| command | what it does | admitted because |
|---|---|---|
new | scaffold a document into the repository's own ADR home | already exists as spec scaffold; this is its name in the family, and it writes rather than printing |
fmt | single-space table cells, ISO date normalisation | purely syntactic; the document's meaning cannot change. Frontmatter key order was in this list and was removed — see v1.0 |
fix | apply the lint remedies that are mechanical, and list the rest | the mechanical set is decidable; the remainder is reported, never guessed |
mv | move or rename, and repair every inbound [[…]] | the link graph is already computed, so the repair is derived, not inferred |
rm | delete, and report every inbound link that would break | deletion is decidable; whether to accept the breakage is the author's call |
index | regenerate the ADR index table | derived from the documents themselves |
split | carve at explicit markers only | a person marks the seam; the tool does the mechanical part |
merge | concatenate, preserving both histories, and leave a conflict list | joining is mechanical; reconciling two decision records is not |
split and merge are the narrowed pair. They are admitted because the
mechanical half — creating files, allocating ids, moving sections, rewriting
links — is real work worth automating, and refused the judgement half: split
acts on a marker a person placed, and merge produces a document that says, in
the text, what a human still has to reconcile.
Every command is a no-op without --write. The default is a diff on stdout,
because a tool that rewrites documents under version control by default is one
people run once.
A new command surface to keep honest. Eight commands whose help text can drift from their behaviour — a failure this project has hit three times in one pull request. Each gets a test that runs it rather than reads it.
fmt will churn once. The first run over docs/ restates every table in
the canonical form. That is a single reviewable commit, and it is the point.
Amended at v1.0: this bullet said the churn would be reordered frontmatter in every ADR. Measurement found all 26 already shared one key order, so there was no churn to have — and the reordering built to produce it was removed after three content-destroying defects. See the v1.0 row.
A worked example arrived while this ADR was in review: the summary table's
first row is **State** in twenty of twenty-three ADRs and **Status** in the
other three, and scaffold_adr emits **State**. Neither spelling is wrong;
having both is. Settling it by hand means editing twenty documents and the
generator without missing one, which is the work fmt exists to do — and a
reviewer citing either group as "the convention" would be right in both cases,
which is what makes the drift worth a command rather than a style note.
fix invites over-trust. Its output must name what it did not fix as
prominently as what it did, or it reads as "the lints are clean".
Portability is inherited, not added. These act through
crates/rto-spec/src/layer.rs#authored_docs, so they work on any repository
#744 already made legible — this ADR adds no new layout assumption.
Phased, so each lands reviewable:
fmt — the canonical form, and the test that it is idempotent.fix — over the mechanical lint subset, reporting the remainder.mv/rm — link repair and breakage reporting, over the existing link graph.index — the ADR table, and the gate that it matches the documents.new — spec scaffold given its family name and a --write path.split/merge — the narrowed pair, last, because they are the ones whose
scope most needs the earlier commands to exist first.The prompt to add these came from the observation that Roteiro is meant to run on
other people's repositories, and that everything it offered them was read-only.
The narrowing of split and merge is a departure from upstream's set, taken
because a tool that guesses at prose and signs the result is worse than one that
declines.
| Version | Date | Notes |
|---|---|---|
| 0.1 | 2026-09-02 | Draft scaffold. Records the decision to add roteiro docs over the authored sources rather than the bundle, and the rule that admits each command only as far as it is decidable. split and merge are narrowed to their mechanical half. |
| 1.0 | 2026-09-11 | Step 1 implemented: roteiro docs fmt. rto_spec::fmt::canonical is the form and roteiro docs fmt [PATHS] is the surface; without --write it prints a unified diff and exits non-zero, so it is usable as a check the way cargo fmt --check is. Measurement moved two of the three stated jobs and narrowed the third. Measured over this repository's 26 ADRs before anything was written: the frontmatter key order is already one order in 26 of 26, and last-modified is already ISO in 26 of 26 — so those survive as idempotence guarantees rather than cleanups, holding a line rather than moving one. "Table alignment" had to be narrowed, because the obvious reading of it is destructive here: an ADR history table carries prose paragraphs in single cells — 59 of them over 1,000 characters, the widest 5,789 — so padding each cell to its column's widest would blow every sibling row out to match. The canonical form is therefore one space either side of every cell, which is the ordinary markdown convention and a form in which a long cell pads nothing. Three rules the draft did not anticipate, each of which a naive implementation gets wrong. (a) A pipe inside inline code is not a column boundary. Found by running fmt over the real docs/ rather than a fixture: ADR-0006 documents the chat-template marker `` `< |