Rewelo CLI
Prioritisation tool using the relative weight method. Stores data in SQLite.
Installation
npm install
npm run build
The CLI is available as rw (via npm link) or directly with node dist/index.js.
Global Options
| Option | Description |
|---|---|
--db <path> | Path to SQLite file, must end in .db (default: ./relative-weight.db) |
--json | Output as JSON |
--csv | Output as CSV (where supported) |
--quiet | Minimal output: commands that change data print nothing (create prints the new UUID), lists print one item per line |
--no-color | Accepted for compatibility; rw never prints colour |
--version | Show version |
An option that takes one value may be given once; --title A --title B is an error. Options marked repeatable, such as --tag, collect every value.
Only project create and import json create the database file; other commands report a missing database instead of creating an empty one. The database uses SQLite's write-ahead log, so commands can read while another rw process writes; <name>.db-wal and <name>.db-shm files appear next to the database while it is in use. Back up with rw export json, or copy the .db file while no rw process runs. A database from an older rewelo is upgraded when it is first opened. Titles and project names that older versions accepted but today's rules reject (such as ., line breaks, invisible characters or My Project) get the nearest accepted name then; a renamed ticket keeps its old title in its history, and a .rewelo.json naming a renamed project needs the new name.
Default project
Every --project <name> below may be left out when a .rewelo.json names the project. rw looks for it in the current directory, then in each parent directory, and uses the first it finds:
{ "project": "Acme" }
project is its only key; a malformed file is an error rather than a reason to look further up. --project given on the command line wins over the file.
Commands
project
rw project create <name>
rw project list
rw project delete <name> [--force]
rw project history --project <name> [--since <timestamp>] [--limit <n>] [--offset <n>]
rw project diff --project <name> --since <timestamp>
delete prompts for confirmation unless --force is passed. history lists the revisions of all tickets, newest first; with --since, the revisions right after it, oldest first. --limit and --offset page through them. diff compares the project now with its state at --since: new, updated and deleted tickets and net tag changes. Timestamps are ISO dates or date-times; without an offset they are read as UTC.
ticket
rw ticket create --project <name> --title <title> [--description <text>] \
[--benefit <n>] [--penalty <n>] [--estimate <n>] [--risk <n>]
rw ticket list --project <name> [--tag <prefix:value>...] \
[--exclude-tag <prefix:value>...] [--search <text>] \
[--sort <field>] [--limit <n>] [--offset <n>] \
[--min-priority <n>] [--min-value <n>] [--max-cost <n>]
rw ticket update --project <name> --title <title> [--new-title <title>] \
[--description <text>] [--benefit <n>] [--penalty <n>] \
[--estimate <n>] [--risk <n>]
rw ticket delete --project <name> --title <title>
rw ticket history --project <name> --title <title> [--limit <n>] [--offset <n>]
rw ticket upsert --project <name> --title <title> [--description <text>] \
[--benefit <n>] [--penalty <n>] [--estimate <n>] [--risk <n>]
upsert creates the ticket if no ticket has that title, and updates it otherwise.
Scores use the Fibonacci scale: 1, 2, 3, 5, 8, 13, 21.
- benefit / penalty form the value side (why do it)
- estimate / risk form the cost side (what it takes)
- priority = value / cost
Sort fields: priority, value, cost, benefit, penalty, estimate, risk.
priority here, in report summary and on the dashboard is always the unweighted value / cost. The project's weights (config weights) apply only to the weighted priority that rw calc priority shows next to it.
ticket list supports filtering, search, pagination, and score thresholds:
# multiple tag filters (intersection)
rw ticket list --project Acme --tag state:backlog --tag team:backend
# exclude done items
rw ticket list --project Acme --exclude-tag state:done
# search by title
rw ticket list --project Acme --search "login"
# pagination
rw ticket list --project Acme --sort priority --limit 20
rw ticket list --project Acme --sort priority --limit 20 --offset 20
# score thresholds
rw ticket list --project Acme --min-priority 1.5
rw ticket list --project Acme --max-cost 5 # quick wins
rw ticket list --project Acme --min-value 10 # high-value items
--min-priority (like report health --threshold) compares the exact value / cost, not the two decimals shown: a ticket shown with 1.62 (21/13 = 1.615…) is below --min-priority 1.62.
JSON output includes { total, offset, items } for pagination. ticket list also supports --csv output. ticket history shows the revision log for a ticket, oldest first; --limit and --offset page through it.
tag
Tags use a prefix:value format (e.g. state:wip, team:backend). A prefix works like a field: a ticket holds one value per prefix. Assigning state:done to a ticket tagged state:wip replaces it (the output says replaced "state:wip"), and asking for two values of one prefix in one command, e.g. feature:auth feature:login, is an error. Use different prefixes for independent classifications.
rw tag create <prefix:value> --project <name>
rw tag assign <tags...> --project <name> --ticket <title> [--ticket <title>...]
rw tag remove <prefix:value> --project <name> --ticket <title>
rw tag list --project <name>
rw tag delete <prefix:value> --project <name> # only a tag no ticket holds
rw tag rename --project <name> --prefix <prefix> --old <value> --new <value>
rw tag log --project <name> --ticket <title>
tag assign auto-creates tags if they don't exist. It accepts multiple tags and multiple --ticket flags, assigning every tag to every ticket:
# multiple tags on one ticket
rw tag assign state:backlog priority:p1 --project Acme --ticket "Login page"
# one tag on multiple tickets
rw tag assign state:done --project Acme --ticket "Login page" --ticket "Signup flow"
# both combined (cross product)
rw tag assign state:wip team:backend --project Acme --ticket "Login page" --ticket "Signup flow"
tag log shows the audit trail of tag changes on a ticket.
config
rw config weights --project <name> # view
rw config weights --project <name> --set --w1 2 --w3 1 # set (omitted weights keep current value)
rw config weights --project <name> --reset # reset to defaults (all 1.5)
Weights w1-w4 control how much each factor contributes to weighted priority:
- w1 = benefit weight
- w2 = penalty weight
- w3 = estimate weight
- w4 = risk weight
calc
rw calc weights --project <name> [--tag <prefix:value>...]
rw calc priority --project <name> [--tag <prefix:value>...] [--w1 <n>] [--w2 <n>] [--w3 <n>] [--w4 <n>]
calc weightsshows each ticket's scores as a share of the total across all tickets, as a fraction between 0 and 1 (e.g.0.25= 25%).calc priorityshows both standard priority and weighted priority, sorted descending. Inline--w1..--w4override the stored config for that run.
export
rw export csv --project <name> [--output <path>] [--with-calculations]
rw export json --project <name> [--output <path>] [--with-history]
Without --output, data is written to stdout. export json contains the tickets with their tags, the project's tags, its relations (by ticket title) and its weight configuration.
import
rw import csv <file> --project <name>
rw import json <file> --project <name>
import csv reads the columns written by export csv: title (required), description, benefit, penalty, estimate, risk and tags; value, cost and priority are ignored. Any other column is an error.
import json creates the project if it does not exist yet, and restores relations and weights when the file has them (weights replace the project's current ones). A file written with --with-history also restores each ticket's creation and last update time, revisions and tag changes, and the project's deleted tickets, so lead and cycle times, the event log and project diff survive a backup and restore. A relation of the file that the others contradict (the reverse of one, one in the opposite order, or one closing a cycle, as older versions could store) is left out, and the output names it with the reason; everything else is imported. Keys the JSON format doesn't have are refused, so a misspelt key (benfit) isn't dropped silently. Imports are all-or-nothing: if any row fails, nothing is imported.
relation
rw relation create --project <name> --source <title> --type <type> --target <title>
rw relation remove --project <name> --source <title> --type <type> --target <title>
rw relation list --project <name> --ticket <title>
rw relation list-all --project <name>
Relation types: blocks, depends-on, relates-to, duplicates, supersedes, precedes, tests, implements, addresses, splits-into, informs, see-also. Each asymmetric type has an inverse name, which listings show from the other ticket's side and which is accepted as well: is-blocked-by, is-depended-on-by, is-duplicated-by, is-superseded-by, follows, is-tested-by, is-implemented-by, is-addressed-by, is-split-from, is-informed-by. A is-blocked-by B is B blocks A.
A relation can't contradict an existing one: B blocks A is refused when A blocks B exists, and so are A depends-on B and B precedes A, which put the two tickets in the opposite order. Nor can the ordering relations (blocks, precedes, depends-on) close a cycle through other tickets: with A blocks B and B blocks C, C blocks A is refused, as no ticket in it could be started first.
report
rw report summary --project <name> [--top <n>]
rw report group --project <name> --prefix <prefix>
rw report distribution --project <name>
rw report health --project <name> [--threshold <n>]
rw report times --project <name>
rw report dashboard --project <name> --output <path> [--limit <n>]
rw report event-log --project <name> [--since <timestamp>] [--after <sequence>] [--limit <n>]
| Report | Description |
|---|---|
summary | Total tickets, breakdown by state: tag, top-N open tickets (not state:done) by priority (default 5) |
group | Group tickets by a tag prefix and show average priority per group |
distribution | Histogram of Fibonacci scores across benefit, penalty, estimate, risk |
health | High/low priority ratio, total backlog cost, done vs open counts |
times | Lead time (created → done) and cycle time (wip → done) per ticket |
dashboard | Self-contained static HTML dashboard: tickets by priority, score distribution, backlog health and a table of relations; the ticket and relation tables show 500 rows unless --limit says otherwise |
event-log | Ticket creations, updates, deletions and tag changes: the newest first (default 50), or after --since/--after, oldest first. --after takes an event's sequence (in --json) and polls without missing events |
times requires state:wip and state:done tags to be assigned to tickets. States are recognised by name: work starts the first time a ticket gets a tag called state:wip at that moment, and a ticket is done while it holds the tag now called state:done (report health counts done tickets the same way). Renaming a state tag changes its meaning: after state:done becomes state:cancelled, those tickets are no longer done.
serve (MCP)
rw serve [--db <path>] [--channel]
Starts the MCP server over stdio. The database path can also be set via the RW_DB_PATH environment variable. --channel pushes changes made outside the session into Claude Code as channel messages; see Live events.
Docker
# Build (APP_VERSION labels the image; rw --version works without it)
docker build --build-arg APP_VERSION="$(node -p "require('./package.json').version")" -t rw .
# Run CLI commands
docker run --rm -v rw-data:/data rw project list
# Run MCP server
docker run --rm -i -v rw-data:/data rw serve
The container stores the database at /data/relative-weight.db. Use a named volume or bind mount to persist data between runs; without one, rw warns on stderr that the database is lost when the container is removed.