crew-code-review
crew-code-review is a CLI binary on $PATH. Default output is NDJSON:
one JSON record per line on stdout (-f json|yaml wrap a list as one
{"data": [...]} document; run and score recompute|refetch end with an {"@summary": {...}} line).
Errors go to stderr as one JSON line
{"error": "...", "fixable_by": "agent"|"human"|"retry", "hint": "..."} with a
non-zero exit.
It maintains a DuckDB-backed queue of candidate PRs and reviews them with a
pluggable engine (Codex or Claude Code; default: Codex). Configuration lives at
~/.config/app.paulie.crew-code-review/config.json: repos, the author groups, age
thresholds, schedule, and the review prompt + rules.
Inspect the queue
crew-code-review queue ls # all pending candidates, NDJSON
crew-code-review queue ls --repo owner/name
The queue holds only pending work, FIFO by first discovery; a row with
claimed_at set is being reviewed right now, and a row carries a holds map
of name to expiry (settling = the PR was pushed or edited within
candidates.quiet_period; cooldown = we reviewed it within
candidates.rereview_cooldown; editing = its author has the dashboard's
steering editor open, for candidates.steering_hold). It is dispatched once
every one of them is past, keeping its queue position and being stepped over
until then. Completed
outcomes live in history (see the dashboard's History page).
Manage candidates
crew-code-review queue add owner/name 1234 # add a PR (fetches live metadata; rejects closed/merged; no holds)
crew-code-review queue promote owner/name 1234 # review NOW: top of queue, clears any hold, treated as manual
crew-code-review queue skip owner/name 1234 # record SKIPPED and drop (re-eligible on new commits)
crew-code-review queue rm owner/name 1234 # remove, recording nothing
crew-code-review queue prune --dry-run # recheck the whole queue now: skip stale discovered PRs, warn about merged/closed manual adds
crew-code-review queue log owner/name 1234 -f # stream the review agent's log (live or postmortem)
Manage the author roster (which group each author is in, per repo, in DuckDB)
crew-code-review authors set owner/name alice core --name "Alice" --slack-id U01
crew-code-review authors set '*' bob outsider # that group on every repo
crew-code-review authors ls --repo owner/name # rows + the policy each resolves to
crew-code-review authors groups # the cohorts and what each grants
crew-code-review authors who alice --repo owner/name
crew-code-review authors rm owner/name alice
We are the reviewer. An author belongs to ONE group per repo, and the group
(defined in config under authors.groups) is a complete review policy: the
review level (ignore = never discovered, though a manual queue add still
reviews; comment = reviewed but never approved; approve = approvable), plus
the engine, model, effort, and an extra prompt fragment. authors.overrides
narrows any of that per handle.
Resolution: the roster row for this repo, else the row for *, else
authors.unlisted[repo], else authors.unlisted["*"]; then every matching
override patches it field by field. authors who names the deciding layer per
field, which is how to answer "why did that PR get approved / ignored". Only
this PR's own resolved policy reaches the engine, never the roster.
Author scores and the leaderboard
Every completed review earns the PR's author points, from the diff's size,
the verdict, whether the codebase grew or shrank, and how many revisions it
took. Generated and vendored files are excluded, read from the repo's own
.gitattributes.
crew-code-review score leaderboard # standings, highest first
crew-code-review score leaderboard --days 30 # a window
crew-code-review score show owner/repo 123 # every scored review of one PR
crew-code-review score ls --missing # rows that were never scored
A score has two parts. SIZE is a curve over added-plus-removed lines, peaking
in points per line at piece_lines (50 changed lines), and paying at most
size_points (100) for one PR before verdict and decay, so
the same solve in fewer lines is worth more and a stack of well-sized PRs beats
one enormous one; it is quadratic near zero, so atomising work into one-line
PRs earns nothing at the defaults. REMOVAL is removal_points_per_100 (20) for every hundred
NET removed lines, paid on top. Deleting more never earns less at the defaults;
tuned size rewards can decline faster than the removal component grows. Linear
removal is split-neutral before rounding only across net-deleting pieces with
the same verdict and revision multiplier.
+100/-100 scores 100, +1000/-1000 scores 29, +100/-1000 scores 227, +1000/-100 scores 47, and -2000 scores 429.
Scores are frozen when a review completes, alongside a hash of the rules that produced them, so retuning config changes what FUTURE reviews earn and nobody loses points they already have.
crew-code-review score ls --stale # scored under older rules
crew-code-review score recompute --stale --dry-run # what would change
crew-code-review score recompute --stale # apply it
crew-code-review score refetch --missing # re-measure from GitHub
crew-code-review score set owner/repo 123 0 --note "duplicate of #120"
recompute is offline: it re-derives from each row's stored measurement, so
changing which paths are excluded needs no network. refetch asks GitHub again
and is the only repair for a row whose size was never measured; it needs the PR
to still be at the head that was reviewed.
Both move points people already have, so both refuse to touch all of history
without --all, both take --dry-run, and a score set by hand is left alone
unless --include-manual is passed.
Run reviews
crew-code-review run # drain the queue, then exit
crew-code-review serve --http :8330 # daemon + dashboard
crew-code-review serve --http :8330 --tailscale serve # + expose on tailnet
Configuration
crew-code-review config path # where the config lives
crew-code-review config show # current config (NDJSON)
See config.example.json in the repo for the full shape. The CLI never
hardcodes repos or GitHub handles; everything is config.
Notes
- Requires
gh(authenticated), theduckdbCLI, and the configured review engine (codexby default, orclaude) on$PATH, already authenticated. - Candidate rules: NEW (never reviewed, ≤14d) and REFRESHED (head SHA changed since our last review, ≤21d). Processed FIFO by first discovery, up to 4 in parallel. Already-approved PRs are skipped, and any recorded outcome at the current head SHA suppresses re-enqueueing.
- Discovered candidates wait out eligibility holds (quiet period default 15m,
re-review cooldown default 90m,
0sdisables) so the agent doesn't review mid-push or instantly re-review;queue promoteor a manualqueue addbypasses them. Manual rows also skip the pre-review candidacy recheck. - Most config edits reload live within ~30s (cadence, parallelism, usage
floors, repos, prompts); only the loop switches and dashboard/Tailscale
settings need a daemon restart. A candidate is HELD when the engine that
would review it drops below
<engine>.usage_floor.{5h,1w}_percentremaining. Both the meter and the floor are per engine, so one engine being out of headroom does not hold candidates bound for the other; a cohort can set its own underauthors.groups.<name>.usage_floor.<engine>. A held candidate is never claimed or recorded, so it runs when the window refills. - The agent does the actual review and GitHub actions, then reports back what
it did (APPROVED|COMMENTED|REQUESTED_CHANGES|SKIPPED). The assembled prompt
carries a built-in approval directive that defaults to comment-only; approval
needs an
approve-level group AND a non-self-authored PR (the self-review veto sits above the group cascade and no group can lift it). Post-outcome behaviour comes from review.on_approve/on_comment/on_reject in config. - Manage watched repos with
repos ls|add|rm, prompts withprompts show|set|unset|preview, and scalar dials withconfig list|get|set|unset(all persisted to config.json). - Scoring can be switched off globally with
config set scoring.mode:enabled(measure and score),leaderboard-only(stop measuring, which removes the per-review GitHub call scoring adds, while still showing the points already earned), ordisabled(also hides the leaderboard in the dashboard). It can be narrowed per repo underscoring.repos. - A second review at the same commit (somebody replying to the bot) scores 0: it is discussion, not new work. The leaderboard pays once per revision.
- Every command group has a
usagesubcommand with full docs and examples.