What this is
roadkeep owns the writes to a project’s ROADMAP.md, CHANGELOG.md, IMPROVEMENTS.md
and STRATEGY.md. A task line is inserted by a command that validates it, not typed by
whoever is holding the file — so the format is a schema at the moment the text is created,
and drift is refused before it is written rather than reported after.
The author it constrains is usually an agent. It ships as a Claude Code plugin, and the same engine answers on the command line, in a hook, in CI and over MCP.
The six laws
Section titled “The six laws”Every design question in this project is settled by one of these. They are rendered from
agents.md, which is where they live and where the gate holds a budget over them — a copy
typed into this page would be the accretion the tool exists to refuse, and the copy nobody is
looking at is the one that drifts.
- L1
- Schema enforced where the text is created;
lintis only the backstop. - L2
- The store is the repository — Markdown, greppable, no database, no service.
- L3
- Round-trip or don't write — parse → render → byte-identical, or refuse the file.
- L4
- The tool never writes prose — it validates and renders.
- L5
- Query instead of read — every question is a command, so answering costs no context.
- L6
- Configuration, not convention — prefix, paths, markers, limits are per-project.
What is deliberately not built
Section titled “What is deliberately not built”The other half of a scope, and the half most projects never write down. These come from
roadkeep non-goal list, which is the verb that owns them: they are bullets in this
repository’s own roadmap, printed with every task briefing so a suggestion that violates one is
refused before it is worked on.
- No web UI and no server.
- No model and no prompts.
- No enforced id scheme beyond
<prefix><n>. - No dates or quarters.
- No backlog in an issue tracker
- No multi-line task line.
- No supported Python API.
- No effort or size field.
What is here, and what is elsewhere
Section titled “What is here, and what is elsewhere”This area is for the reads that decide an adoption, which are the ones an installed copy
would otherwise gate. Everything narrower than the whole README used to live inside a
checkout: --help is a terminal read, explain answers a finding code from an installed
copy, config describes roadkeep.toml from a build, and the skill is written for an
agent that already has the plugin.
Reference pages here are generated from the package, not written beside it. A page that retyped a flag, a finding code or a config key would be wrong at the first rename and would report nothing; the build derives them from the same declarations the tool enforces, and fails where the two disagree.
For everything else, the repository is the source and this area does not hold a second copy of it:
- The README — the measured problem, the six laws, the installation routes and the non-goals.
- The backlog — what is open, in the format the tool enforces on itself.
- The ledger — what shipped, indexed by block.
The shortest thing that shows the point
Section titled “The shortest thing that shows the point”uvx roadkeep init # scaffold the files and roadkeep.tomluvx roadkeep add --block A --symptom "…" --why "…"uvx roadkeep lint # exits 1 on anything that driftedadd refuses a line that would not validate, so the question “what would I cut?” never
arrives — the ceiling was known before a sentence was composed to fill it. lint is the
backstop for whatever bypassed it, and its exit code is the whole difference between a
gate and advice.
Nothing is installed to run those three. The package is on
PyPI and takes no runtime dependencies —
argparse and tomllib, both stdlib — so uvx resolves a single name and there is no
tree to solve behind it. pip install roadkeep puts the same console script in an
environment you keep, and requires-python in the published metadata is the only floor.
A checkout is the exception now rather than the route:
uvx --from git+https://github.com/alegauss/roadkeep roadkeep lint is how you reach a fix
that has not been released yet.