Skip to content

Writing a line

This is where the whole design lives. A task line is inserted by add, which validates it against the project’s schema at the moment the text is created — so a symptom that is too long is refused before it is written, and the question “what would I cut?” never arrives.

That is the difference between this and a linter. A linter reports after the prose exists and asks the author to delete work they have already done; a limit enforced at insertion is one nobody composes a sentence against. Everything else in the tool is downstream of that choice: lint is the backstop for what bypassed these verbs, not the primary gate.

The rest of the family moves a line that is already open. status changes its marker, amend corrects the reasoning, restate corrects the symptom while keeping the id, and renumber moves the number itself. defer and resume are the door out that is not terminal — a line set aside without being closed, on a project that declares somewhere to put it.

Ids are never chosen by hand and never reused. next-id answers with one past the highest anywhere, rather than the first unused number: a retired id filled back in would make two tasks share it in the history, and the history is what origin reads.

next-id

Reads. Served to an agent as next_id.

Print the next id. Never the first unused number: a retired id is never reused, so filling its hole would make two tasks share it in the history.

ArgumentNotesWhat it is
--prefix FAMILYcount in this track (default: the first declared) — two tracks sharing a counter are two tracks that renumber each other
--jsondefault falseinclude where the highest id was found, so the answer can be audited

add

Writes. Served to an agent as add.

Compose, validate and insert one task line. Nothing is written unless every field passes: a limit reported after the prose exists is a limit discovered too late to save the tokens it was meant to save. With --section the rationale the line points at is written in the same transaction; without it, the follow-up the pointer needs is named.

ArgumentNotesWhat it is
--block BLOCKrequiredthe block label, e.g. B
--symptom SYMPTOMrequiredwhat does not work — a phrase, never a fix
--why WHYrequiredone sentence, ending in a stop; '-' reads stdin, which is how an apostrophe or a backtick survives a shell
--dep DEPrepeatable; default []a dep, repeatable: an id, 'Block X', a range, or work outside the backlog
--requires REQUIREMENTrepeatable; default []what must be present to finish this, repeatable and declared in `[requirements]`: not a dep — `pick` offers it only to a caller that has one
--status, --marker STATUSthe status marker (default: the first marker roadkeep.toml declares)
--id TASK_IDthe id (default: derived, one past the highest anywhere)
--prefix FAMILYwhich track the derived id counts in (default: the first declared); only a backlog that numbers by track has a second one to name
--ref REFthe rationale anchor, for ref_scheme = 'outline' only; otherwise derived — <prefix>:<x.y> for a prose file [refs] gives a namespace
--section TITLEwrite the rationale under this heading, in the same transaction: the pointer every line carries resolves to nothing until a section exists
--section-body SECTION_BODYthe rationale prose; omitted or '-' reads stdin. Read only with --section
--section-body-file PATHread the rationale from this file instead — a refusal on a short field then costs the corrected field alone and never the paragraph again
--capture PATHthe kept capture this line files, stamped with the id this call mints (RK1141) — `report` prints this flag already filled in, so the row `stats` counts is cleared by the act that closes it and never by a second step somebody remembers
--jsondefault falsethe line, with the file and line it landed on

status

Writes. Served to an agent as status.

Write one task's status marker. Refused if a sibling file already carries one for that id: two files that both express status will eventually express different status, and nothing says which is right.

ArgumentNotesWhat it is
idrequiredthe task, e.g. RK7
markerrequiredthe new marker, from the open set this project declares
--jsondefault falsemachine-readable form

amend

Writes. Served to an agent as amend.

Correct the three fields an adopting project has to be able to fix: a pointer it never had, a dep naming an id in neither file, and a `why` written before the limit existed. Validated at input exactly as `add` validates it, or nothing is written. The `symptom` is not amendable — it is the claim the line is — and `restate` is the door where that claim turned out false.

ArgumentNotesWhat it is
idrequiredthe task, e.g. RK7
--why WHYthe sentence, re-validated against the limit; '-' reads stdin, which is how an apostrophe or a backtick survives a shell
--dep DEPrepeatablea dep, repeatable: given at all, it replaces the whole group
--requires REQUIREMENTrepeatablea requirement, repeatable: given at all, it replaces the whole group
--ref REFthe rationale anchor, for ref_scheme = 'outline'
--lines LINEShow many lines this correction replaces; required where the line wraps, which on a roadmap only an adopted backlog can be
--jsondefault falsemachine-readable form

restate

Writes. Served to an agent as restate.

The one field `amend` does not reach, at a door of its own. A different symptom is normally a different task, which is why that verb excludes it — and a premise that turns out false is not a different task, it is this file asserting something untrue in the field a reader sees first. `retire` plus `add` is the exit that was designed for it, and it spends an id, deletes a section that was already right and records a departure that never happened. This keeps all three. A verb rather than a flag, so the act has a name a reviewer can see.

ArgumentNotesWhat it is
idrequiredthe task, e.g. RK7
--symptom SYMPTOMrequiredwhat does not work — re-validated against the limit, exactly as `add` does; '-' reads stdin, which is how an apostrophe or a backtick survives a shell
--lines LINEShow many lines this restatement replaces; required where the line wraps
--typodefault falsea slip of the pen rather than a false premise: the claim is the one intended and a word in it was wrong, so the answer and the payload say so
--jsondefault falsemachine-readable form

renumber

Writes. Served to an agent as renumber.

Change one line's address, without a departure. The line, the section its pointer resolves to and every dep naming it move in one transaction; the ledger is never opened, because an id it records is a decision and not an address. This is the repair a merge that allocated one id twice needs, and the door `ship`, `retire` and `amend` all deliberately refuse to be.

ArgumentNotesWhat it is
idrequiredthe line to move, e.g. RK90
--to TOthe new id (default: derived, one past the highest in the line's family)
--jsondefault falseevery edit, as data

defer

Writes. Served to an agent as defer, on a project that declares deferred.

A pause spelled as a retirement is terminal — the id cannot come back, the resolver reads the dep as never, and the rationale is deleted. This moves the line to the deferred store instead, keeping every slot and the section.

ArgumentNotesWhat it is
idrequiredthe task being set aside, e.g. RK33
--reason, --why REASONrequiredone sentence, the author's own: it wraps the why and a resume unwraps it; '-' reads stdin, which is how an apostrophe or a backtick survives a shell
--jsondefault falseevery edit, as data

resume

Writes. Served to an agent as resume, on a project that declares deferred.

The store is revivable, which is what separates it from the two terminal doors: the same id, the same deps, the same section, back under the block the line left. The open marker is the one thing the store could not keep.

ArgumentNotesWhat it is
idrequiredthe task coming back, e.g. RK33
--marker, --status MARKERthe open marker it returns with; omitted, the first this project declares — the store holds one marker, so which one it was is not a fact any file kept
--jsondefault falseevery edit, as data