Skip to content

Prose, blocks and the other bullets

A task line is one sentence, and one sentence is not where a design goes. What it carries instead is a pointer to a section in the rationale file, and this family owns both halves: section add writes the prose, and the line that points at it is refused by the gate if the section is not there.

That split is deliberate. The line is what a reader scans and what a query returns; the section is what somebody opens once, when they are about to do the work. Keeping the argument out of the line is what stops a backlog from becoming a document — the failure this tool was written after measuring, and the reason the model treats the line and its section as two halves of one write.

The rest of the family writes the other things a file holds. Blocks are the grouping every line belongs to, declared rather than invented by a write. Non-goals are the roadmap’s other bullet: a constraint, not a task, printed with every brief so a suggestion that violates one is refused before it is worked on. Criteria are the positive twin — what must be true for a block to be finished. Priority is the one ordering a project declares rather than derives, and it outranks the id order that pick otherwise follows.

section find goes the direction a pointer does not: an id resolves to a section, and this resolves a sentence back to the id that carries it — which for an agent is the difference between one call and a file printed into a context window per guess.

section

A group of the verbs under it.

The prose files are paragraphs, not lines, so their unit is a section: an anchor a pointer can resolve, a word budget, and a place derived from the task's block. `ship` calls `drop` for the first of its three edits.

section add

Writes. Served to an agent as section_add.

Write one rationale section: the prose in `--body`, within the word budget, filled to the configured width, and placed under the task's own block or beneath the section it extends. A table or a list is inserted as written.

ArgumentNotesWhat it is
anchorrequiredthe anchor, e.g. RK9 or S:VIII.2 (no §)
--title TITLErequiredthe heading text
--body BODYthe prose; omitted or '-' reads stdin, which is how a paragraph gets in
--body-file PATHread the prose from this file instead — a refusal on a short field then costs the corrected field alone and never the paragraph again
--role ROLEdefault "improvements"which prose file (default: improvements)
--level LEVELheading depth; derived where omitted — a subsection at 3, and a new top level at the depth this file already writes one at
--jsondefault falsemachine-readable form

section amend

Writes. Served to an agent as section_amend.

Rewrite one section's own prose, or its heading text, without deleting it. The door that was missing: `drop` refuses while an open line points at the anchor, `add` refuses the duplicate and the guard denies the hand edit, so a design was write-once until it shipped — which is the opposite of when it changes. The subtree is not touched: a subsection is amended by its own anchor. Neither is the anchor itself: that is `section move` under an outline, and `renumber` where the address is the task's id. Nor is the heading line, unless --title asks for it: a body-only amend leaves those bytes alone. An address that is not an anchor is read as a heading text, which is how the two regions carrying no anchor — the file's opening, and a table of contents — are reached; neither is charged the section word limit, which is a rationale's.

ArgumentNotesWhat it is
anchorrequiredthe anchor, e.g. RK9 (no §), or an unanchored heading's own text
--title TITLEreplace the heading text
--body BODYthe replacement prose; '-' reads stdin, and stdin is the default unless --title is the only thing being changed
--body-file PATHread the replacement prose from this file instead — a refusal on a short field then costs the corrected field alone and never the paragraph again
--replace OLDthe text to correct, edited in the prose already on disk — refused unless it occurs exactly once, so the edit's reach is visible in the call
--with NEWwhat --replace becomes; the empty string deletes it
--role ROLEdefault "improvements"which prose file (default: improvements)
--jsondefault falsemachine-readable form

section move

Writes. Served to an agent as section_move.

Move one section to a free address, keeping its prose exactly where it is. The verb an outline had none of: `renumber` moves an id and leaves the pointer as typed under any other scheme, so a doubled address — what `lint` calls `section.ambiguous` and `add` refuses to create — was repairable only by the hand edit the guard denies. The heading, every nested anchor that extends it and the `→ §<anchor>` on every line naming one of them move together, or none of them do. The destination takes every refusal `add` computes, and stays under the parent the address already had: this write changes the address, not the place.

ArgumentNotesWhat it is
anchorrequiredthe anchor to move, e.g. I.2 (no §)
--to TOrequiredthe free address to move it to — `anchors` names one
--role ROLEdefault "improvements"which prose file (default: improvements)
--jsondefault falsemachine-readable form

section show

Reads.

Print one section whole, with the word count the budget is measured in. An address that is not an anchor is read as a heading text, so the file's opening and a table of contents answer too. `--own` prints what `section amend --body` takes, so the two extents are one on the section that has children. Reads; never writes.

ArgumentNotesWhat it is
anchorrequiredthe anchor, e.g. RK9, or an unanchored heading's own text
--owndefault falseprint this section's own prose alone, which is the extent `section amend --body` replaces — the round-trip on a section that has subsections
--role ROLEdefault "improvements"which prose file
--jsondefault falsemachine-readable form

section find

Reads. Served to an agent as section_find.

Report the anchors whose own prose holds a string, with a count each, so a `section amend --replace` is addressed rather than guessed. The count is what decides the next call: one is what `--replace` accepts, and more is the ambiguity it refuses. The prose itself is `section show`'s. Reads; never writes.

ArgumentNotesWhat it is
textrequiredthe string to look for, exactly as the prose spells it
--role ROLEone of improvements, strategy, decisionsone prose file (default: every one this project declares)
--jsondefault falsemachine-readable form

section drop

Writes. Served to an agent as section_drop.

Delete one section and everything under it. Subsections included, because one left behind is orphaned prose under the next task's heading — which reads as that task's design and is the outcome worse than deleting too much. Refused when an open line points at the anchor or at anything under it: the section a live pointer names is `ship`'s to remove, and this verb's job is the orphan.

ArgumentNotesWhat it is
anchorrequiredthe anchor, e.g. RK9
--role ROLEdefault "improvements"which prose file
--jsondefault falsemachine-readable form

block

A group of the verbs under it.

A block is declared by a heading and by nothing else, so every write refuses an undeclared one — and the guard denies the hand-edit that would declare it. Both refusals are right and the pair is a deadlock; this is the key. `list` is the read the other four assume has happened: which labels exist to file under.

block list

Reads. Served to an agent as block_list.

Where a task may go, at the moment one is placed. `add --block <x>` is the first flag on the first write of any new task and nothing said what `<x>` could be: `stats` prints letters and counts and never a title, `list --block` and `delivered <block>` both demand the letter they cannot enumerate. In file order, because a reader takes the sequence for the shape of the plan. A label the roadmap has lost keeps its ledger heading and is named as such — that is the row an `add` still refuses, and `block add` re-declares it.

ArgumentNotesWhat it is
--jsondefault falsethe rows, with every file each label is in

block add

Writes. Served to an agent as block_add.

The label and the title are yours; everything else is derived per file. It goes after the last block's subtree — never at the end, where the roadmap's Non-goals live — or after the block `--after` names, which is a neighbour and not an index, so each file places it after its own copy of that heading. It is spelled at the level and with the separator that file's own first block heading uses. All of the files, or none of them.

ArgumentNotesWhat it is
labelrequiredthe block label, e.g. G
--title TITLErequiredwhat the block is for
--after AFTERopen it after this block instead of last, e.g. C; refused where a file that wants the heading declares no such neighbour
--organise ROLErepeatable; default []also write the first block heading into this file, e.g. changelog; a file organised by nothing is skipped without it, and every ship there refuses
--jsondefault falsemachine-readable form

block amend

Writes. Served to an agent as block_amend.

The words on a heading the other three cannot change. `drop` plus `add` was the repair and it is refused the moment anything is filed under the label, so a title was write-once from the first `add` on. Narrow: the label is the identity and does not move, the subtree is untouched, and each file keeps its own level and separator. Every file that declares the label or none — a title corrected in one and left in another is the defect this closes.

ArgumentNotesWhat it is
labelrequiredthe block label, e.g. G
--title TITLErequiredthe words it should read
--jsondefault falsemachine-readable form

block drop

Writes. Served to an agent as block_drop.

The inverse, and narrow in the one way that matters: a heading over work is not an empty heading. It is removed only where its whole subtree is blank, and refused by name where anything is filed under the label — open lines, paused ones, rationale sections. The ledger is the exception, left alone and said so, because history keeps the heading it was filed under.

ArgumentNotesWhat it is
labelrequiredthe block label, e.g. G
--prosedefault falsetake the heading's note with it — loose prose only, never work
--jsondefault falsemachine-readable form

block merge

Writes. Served to an agent as block_merge.

The key RK391 named. Two headings under one label is a state the gate reports and every write refuses with `merge the two regions by hand` — which the guard denies. This is that merge, done by the tool: the first heading stays, every later one's entries move under it, and the emptied duplicates go. The ledger is included, not skipped, because history stays under a heading of the same label. All of the files, or none of them. A nested section is `section move`'s to place and refused here; loose prose is dropped only under --prose.

ArgumentNotesWhat it is
labelrequiredthe block label, e.g. B
--prosedefault falsedrop a duplicate heading's loose prose as it is folded — never an entry
--jsondefault falsemachine-readable form

non-goal

A group of the verbs under it.

The one content of the roadmap that is not a task line, and until RK70 the one thing nothing governed: `Edit` denied and offered five commands that all write task lines, `lint` said nothing because a bullet with no marker is prose, and `sed` through `Bash` was the route left. Opt in with `[non_goals]`.

non-goal add

Writes. Served to an agent as non_goal_add.

Compose, validate and insert one non-goal. Addressed by its lead — unique and checked — because an id would buy a lifecycle for a list of eight lines that changes once a year. No marker, no dep and no pointer: a constraint has no status to state.

ArgumentNotesWhat it is
--lead LEADrequiredwhat is not built — the bolded head a brief prints and a duplicate is judged on
--why WHYrequiredthe reason it is not, in this file's own limit; '-' reads stdin, which is how an apostrophe or a backtick survives a shell
--jsondefault falsethe bullet, with the file and line it landed on

non-goal amend

Writes. Served to an agent as non_goal_amend.

Correct a constraint's reason in place, keeping the bullet's position. The door `record amend` and `section amend` already are one grammar over: without it the only route was drop-and-re-add, and `add` inserts after the last bullet — so a constraint that sat fifth of eight moved to eighth, and a reviewer read a deletion where a word changed. The lead is not a field: it is the address, so a changed one is a `drop` and an `add`.

ArgumentNotesWhat it is
leadrequiredthe lead, as the file reads it; the trailing stop and case do not matter
--why WHYrequiredthe corrected reason, in this file's own limit; '-' reads stdin, which is how an apostrophe or a backtick survives a shell
--jsondefault falsemachine-readable form

non-goal list

Reads. Served to an agent as non_goal_list.

The list that binds an `add`, at the moment one is *proposed* rather than the moment a task starts: until RK69 only `brief <id>` printed it, so the rule was carried by a sentence in a file. Presence, not enforcement — whether a proposal violates a constraint is a judgement about meaning, and this tool has no model (L4). Reading is never refused, so an ungoverned list prints and says so.

ArgumentNotesWhat it is
--jsondefault falsethe leads, with the file and what was left

non-goal drop

Writes. Served to an agent as non_goal_drop.

Delete one non-goal whole. The half a correction needs: a lead is the address, so a constraint whose lead changes is one retired and one written rather than an edit to an address. Where two bullets carry one lead the later goes, which makes this the door for `lint`'s non-goal.duplicate as well.

ArgumentNotesWhat it is
leadrequiredthe lead, as the file reads it; the trailing stop and case do not matter
--jsondefault falsemachine-readable form

criterion

A group of the verbs under it.

The roadmap's third list. A non-goal says what is not built; nothing said what would make a block done, so the only test left was a line count reaching zero — measured where a block was declared closed and reopened six times. One list per block, addressed by its lead, under a `## Done when — Block X` heading `ship` never touches. Opt in with `[criteria]`.

criterion add

Writes. Served to an agent as criterion_add.

Compose, validate and insert one criterion, addressed by its lead within its block. It **opens the block's list** where there is none, as `priority add` writes its own heading — but never the block: a label the roadmap does not declare is refused, so a typo opens nothing.

ArgumentNotesWhat it is
--block BLOCKthe block label this finishes, e.g. B
--task TASKthe id this finishes, e.g. RK42
--lead LEADrequiredwhat must be true — the bolded head a brief prints, unique in its list
--why WHYrequiredhow it is checked, in this file's own limit; '-' reads stdin, which is how an apostrophe or a backtick survives a shell
--jsondefault falsethe bullet, with the file and line it landed on

criterion amend

Writes. Served to an agent as criterion_amend.

Correct a criterion's reason where it sits. `add` appends, so drop-and-re-add moves a line in a list read as the shape of what finishing means. The lead is not a field — it is the address, so a changed one is a `drop` and an `add`.

ArgumentNotesWhat it is
leadrequiredthe lead, as the file reads it; the trailing stop and case do not matter
--block BLOCKwhich block's list it is in (default: the one that carries the lead)
--task TASKwhich task's list it is in, by id
--why WHYrequiredthe corrected reason, in this file's own limit; '-' reads stdin, which is how an apostrophe or a backtick survives a shell
--jsondefault falsemachine-readable form

criterion list

Reads. Served to an agent as criterion_list.

Print what a block has to satisfy. Presence, not enforcement — whether the work satisfies a criterion is a judgement this tool has no model for (L4). Never refused, and it says which empty it found: ungoverned, unasked, or all dropped.

ArgumentNotesWhat it is
--block BLOCKone block's list (default: everything that declares one)
--task TASKone task's list, by id
--jsondefault falsethe criteria, with the file and their lines

criterion drop

Writes. Served to an agent as criterion_drop.

Delete one criterion whole — the half a changed lead needs, the lead being the address. The heading stays: a block whose criteria all went is one somebody asked the question about, which is not a block nobody asked.

ArgumentNotesWhat it is
leadrequiredthe lead, as the file reads it; the trailing stop and case do not matter
--block BLOCKwhich block's list it is in (default: the one that carries the lead)
--task TASKwhich task's list it is in, by id
--jsondefault falsemachine-readable form

priority

A group of the verbs under it.

The one tier of `pick` a project declares rather than derives, moved out of `roadkeep.toml` and into a `## Priority` section of the roadmap (RK325). Every token in it names work, and work leaves — so unlike the prefix, the paths and the limits, the queue stops being true by itself, and the config was the one file nothing governs. The section wins wherever both are declared.

priority add

Writes. Served to an agent as priority_add.

Insert one entry under the priority heading. Appended by default, because a queue grows at the end and 'everything new is most urgent' is the order nobody meant; --first and --after are the two places that are not the end, and moving work up the order is the act the config file made unavailable.

ArgumentNotesWhat it is
tokenrequiredan id of this project, or 'Block X'
--firstdefault falseahead of everything already queued
--after TOKENdirectly after this entry, which must be queued
--jsondefault falsethe entry, its place in the order, and the line

priority list

Reads. Served to an agent as priority_list.

The queue `pick` applies, in order, with the file it came from — because a project that wrote a section and is still being ordered by its config has a fact to learn and no other way to learn it. Reading is never refused.

ArgumentNotesWhat it is
--jsondefault falsemachine-readable form

priority drop

Writes. Served to an agent as priority_drop.

Delete one entry. There is no correction verb between the two: an entry carries a token and nothing else, so a token that changed is a different entry and a move is a drop and an insert.

ArgumentNotesWhat it is
tokenrequiredthe entry to remove, as the file spells it
--jsondefault falsemachine-readable form

priority migrate

Writes. Served to an agent as priority_migrate.

RK325 moved the queue into the roadmap and the gate still reads the old declaration, which is right — a project that has not migrated has a real order. This is the door between them. The config line is left alone, because nothing here writes `roadkeep.toml`: the section wins from the moment this returns, and `lint` names the leftover as `priority.config`.

ArgumentNotesWhat it is
--jsondefault falsemachine-readable form

refs

Writes.

Write `[refs] <role>` and re-address that file's own citations in the same transaction (RK1168). Declaring the key alone re-addresses every heading at once and carries none of the prose citing them: measured on one adoption, 7 citations dangled and 21 kept resolving into the other prose file's section of the same address, where nothing reports them. Both writes land or neither does. Only a declaration: a role that already has a namespace is a re-addressing, whose citations carry the old prefix and whose answer is a different transaction.

ArgumentNotesWhat it is
rolerequiredthe prose role, e.g. strategy
--as NSrequiredthe namespace, e.g. S — the letters before the colon of an address like S:I.2
--jsondefault falsemachine-readable form