roadkeep owns the writes to your ROADMAP.md, CHANGELOG.md, IMPROVEMENTS.md and STRATEGY.md — so the format is enforced at the point of insertion instead of remembered by whoever is typing.
Shipped as a Claude Code plugin, because the author to constrain is usually an agent.
It started as three readings from a real production repository, where all three files declared a format and none of them enforced it.
The finding that decided the design: six of the eight worst lines were written in the session that then diagnosed the problem.
This is not inattention. An author — human or model — who has the whole design in working memory will write it down where the reader is. An instruction to be terse does not survive the moment its author knows more than the line allows.
“The saving is the analysis,
not the characters.”
A linter reports after the prose exists. By then the tokens are spent, and the
author is being asked to delete work they just did. A field with
maxLength: 200 refuses before a sentence is composed to fill it —
the same rule, two orders of magnitude cheaper. It turns an analytical act
(“is this too long, and what would I cut?”) into a procedural one
(“call add”).
So the honest comparison is a narrow one.
| Tool | What it does well | Why roadkeep is not it |
|---|---|---|
| markdownlint | Structure and style of Markdown | Explicitly not prose — it will never tell you a sentence is too long |
| Vale | Prose rules and style guides | A linter: it reports after the text exists, which is the cost being avoided |
| Backlog.md, taskmd, the markdown-task family |
Mature task management, kanban, MCP | One .md file per task, with acceptance criteria and DoD — more room, and more room invites more prose |
| ADR / MADR | Rationale that survives; superseded is never deleted | An ADR set grows monotonically — that curve is the 539 KB above |
The refusal happens before the prose exists. Everything else in this space reports afterwards, at review time.
One line in your existing ROADMAP.md — not a new store you migrate into. A repository with a hand-written backlog is the target, not the obstacle, so it will measure yours and tell you what adopting would cost before you commit to it.
Parse → render → byte-identical, or the tool declines to write the file at all. A tool that owns writes to a hand-edited file has to prove it cannot corrupt one.
Every question a maintainer asks the file is a command, so answering it costs no context. Reading a backlog end-to-end to find one ready task cost ~5k tokens in this very repository.
L4 is the one people try to relax first. A generator that writes the symptom for you would reintroduce exactly the drift this exists to stop.
The format is a schema, enforced where the text is created; lint is only the backstop.
The store is the repository — Markdown, greppable, diffable. No database, no service.
Round-trip or don't write — parse → render → byte-identical.
The tool never writes prose — it validates and renders.
Query instead of read — every question is a command.
Configuration, not convention — prefix, paths, markers and limits are per project.
What is left is the layer that makes an agent unable to route around any of it.
Next ready RK21 — A standard adopted by one project is a preference
A whole task is four calls. Not a manual to learn — a shape: ask what to work on, write the line, ship it, prove nothing drifted.
Around those sit the rest — status, sections, the dependency walk, the git-derived
history, the projection that keeps this page honest. Every one takes
--json carrying which file and which line the answer came from, because
an answer an agent cannot audit gets verified by reading the file,
which is the cost the command existed to remove. The full surface lives in
--help and the repository; a landing page is the wrong place for it.
Python ≥3.11, zero runtime dependencies — which is what makes the first line below possible: there is nothing to resolve.
uvx roadkeep lint
No install and no checkout. Or pip install roadkeep to keep it around.
Worth reading before proposing work.
No web UI and no server. Files and a CLI.
No issue-tracker sync. A backlog that lives in a service is one an agent cannot grep.
No model and no prompts inside the tool. It validates and renders; it never writes the symptom or the rationale.
No dates, quarters or estimates. A marker is maturity, not a schedule.
roadkeep lint must pass on its own docs/. The format is
proven by the artefact rather than asserted in a README — including that one. A limit
that cannot express these lines is the wrong limit, not a set of wrong lines,
and the test suite asserts it against docs/ROADMAP.md under this
repository's own roadkeep.toml.