Driver for extensive AI code reviews
  • C++ 99.5%
  • Python 0.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Jorijn van der Graaf e215ad986e
feat(seats): the env file's settings count, and a machine's own seats file
ApiConfig and PoolConfig read the seat order, the budgets and the
concurrency from the process environment alone, so a `.env` that said
REVIEW_GATES_SEAT_ORDER=oauth-first ordered nothing: the Python driver
reads those lines from the file under the process environment
(config_env, 2026-10-17: a setting lives beside the seats it orders).
SeatPool::SettingsOf does the same - the file's placement settings
under the process environment, in a map handed to the placement readers
alone (ApiConfig, PoolConfig, Seats::ConfigFromEnv) - and leaves the
process environment untouched, so no other reader sees a line of the
file: a path's or a binary's line reaches nothing, and two drivers
reading one .env keep one board and one ledger. The placement settings
are one list, SeatPool::PlacementSettings - the fifteen names those
readers read - for the map, the run's log and what makes a working
directory's .env a seats file; a REVIEW_GATES_* line of any other name
stays out of the map and is named, by the run's log and on `seats`'s
stderr. One dotenv grammar serves the tokens and the settings
(Seats::DotenvLines).

`run` and `seats` resolve the file from their own --env-file
(SeatPool::ResolveSeatsFile): the --env-file, else $SEATS_ENV_FILE, else
the working directory's .env - the operator's, as the Python driver and
`seats` read it, not the repository under review's - when it is a file
holding a seat or a placement setting, each with a value, or one that
cannot be read as UTF-8 text (its error is then the command's) - a
readable application .env, its Vite or Supabase lines, one of blank
placeholder lines or one that is no file is passed over, and the run
says so - else the machine's own
$XDG_CONFIG_HOME/crafter-review/env, else ~/.config/crafter-review/env:
one file for every repository on a machine, a repository's seats .env
winning over it. Each file is read once and the settings and the tokens
are taken from that read (SeatsFile::Text), so a file that can be read
only once - a pipe, `--env-file <(gpg -d seats.env.gpg)`, a FIFO - gives
its settings and its seats alike, and a failed read raises its own error
where the tokens are laid over the environment: SeatPool::WithFileTokens,
for `run` and `seats` alike, and LoadSeats takes the merged environment
and no longer reads a file by path. An empty `seats --env-file` means no file
now, as it did for `run`, and the `seats` report names the side that
runs first (it said "api seats run first" whatever the order).

Runs, Commands and KernelModes, the tests that run `run` and `seats`,
point XDG_CONFIG_HOME, and HOST_XDG_CONFIG_HOME for the Flatpak, at a
path of their own that nothing creates, so a global file stays out of
them; the grammar, the settings and their precedence, the lookup and the
one read have their tests, a run with `--env` (a prefix) and with a
repeated --env-file, and a `seats` with `--env`, take tokens and
settings from the one file, an application's .env and a venv are passed
over, a FIFO is read once, no line of the file reaches `stop`, every
listed placement setting moves a reader, another REVIEW_GATES_* line is
named and a .env of blank lines passed over, and the
README, the environment table and the procedure say the new rule.
2026-11-02 00:30:52 +01:00
.forgejo/workflows feat(config): modes - a mode is an artifact kind, the lenses it runs and its effort 2026-11-02 00:29:00 +01:00
implementations feat(seats): the env file's settings count, and a machine's own seats file 2026-11-02 00:30:52 +01:00
interfaces feat(seats): the env file's settings count, and a machine's own seats file 2026-11-02 00:30:52 +01:00
lib chore: vendor nlohmann/json 3.12.0, and the licence text 2026-10-31 05:29:18 +01:00
packaging/flatpak feat(seats): the env file's settings count, and a machine's own seats file 2026-11-02 00:30:52 +01:00
process feat(seats): the env file's settings count, and a machine's own seats file 2026-11-02 00:30:52 +01:00
tests feat(seats): the env file's settings count, and a machine's own seats file 2026-11-02 00:30:52 +01:00
.gitignore build(flatpak): net.catcrafts.CrafterReview on the Crafter.Build base 2026-10-31 06:01:19 +01:00
AGENTS.md feat(seats): the env file's settings count, and a machine's own seats file 2026-11-02 00:30:52 +01:00
LICENSE chore: vendor nlohmann/json 3.12.0, and the licence text 2026-10-31 05:29:18 +01:00
project.cpp feat(modes): the kernel modes - a patch series and a mailing-list reply 2026-10-31 22:56:12 +01:00
README.md feat(seats): the env file's settings count, and a machine's own seats file 2026-11-02 00:30:52 +01:00

Crafter Review

The pre-push review pipeline: adversarial lenses review a frozen commit range, one claude --print process per inventory unit, before the range is pushed. Five lenses in a fixed order: the approach, the range exercised in the running application through the Playwright MCP server, side by side the static bug proof and the security review, and last the commit truth of the messages and the documentation. Every lens is off until the repository's config turns it on, and the config says what each gets: the browser, the local Supabase database, both, or neither.

Gate Lens Question
1 repo-better-solution (a whole pass gating code blocks) the right mechanism, layer and commit shape? compatible with what users have?
2 repo-functional (a whole pass gating code blocks; the browser or browser+supabase environment) in the running application, does the range do what its code and messages say, and does it leave the flows next to it working?
3 repo-bug-prover (code blocks, static; with the database a proof may also run there) a demonstrated defect in the range's code
3 repo-security (code blocks, static, beside the bug-prover) a demonstrated gap in a protection the repository promises
4 repo-facts (message blocks, documentation blocks and the whole:order / whole:absent slots, static) every claim in every commit message, and in the documentation the range adds or changes, proven or gone
5 the owner approves the exact frozen hash, then the push happens

crafter-review's prompts, reuse keys, exit codes and shared files are pinned byte for byte by its goldens (tests/Goldens): a review one driver started, another resumes and reuses, and every driver on a machine sees the others' passes on its seat board.

Install

crafter-review ships as a Flatpak with its own git, Node (for the Playwright MCP server) and ripgrep, so every machine reviews with the same tools:

flatpak remote-add --if-not-exists catcrafts https://flatpak.catcrafts.net/catcrafts.flatpakrepo
flatpak install catcrafts net.catcrafts.CrafterReview
alias crafter-review='flatpak run net.catcrafts.CrafterReview'

master builds are the beta branch (net.catcrafts.CrafterReview//beta). The alias is for people: the /prepush skill spells every command as flatpak run net.catcrafts.CrafterReview …, because Claude's Bash tool sees no aliases.

What the machine provides:

  • The Claude Code CLI. It is proprietary and not bundled. crafter-review looks for it in this order: CLAUDE_BIN or REVIEW_GATES_CLAUDE_BIN, the PATH, the editor extensions' bundled binary (newest first), then ~/.claude/local and ~/.local/bin. It runs inside the sandbox, so every pass is measured and every pass's process tree can be killed.
  • Seats. The lens processes run on the seats of a dotenv file (CLAUDE_CODE_OAUTH_TOKEN1..6, ANTHROPIC_API_KEY1..6): --env-file, else $SEATS_ENV_FILE, else the working directory's .env if it is a file holding a seat or a placement setting, or one that cannot be read as UTF-8 text (its read error then stops run and seats) - a readable application .env is passed over, and the run says so - else the machine's own ~/.config/crafter-review/env ($XDG_CONFIG_HOME/crafter-review/env: one file for every repository, a repository's seats .env winning over it). Its seat lines go over the process environment's; with no seat in either, this machine's own login. The placement settings among its lines - the seat order, the budgets, the concurrency, the utilisation ceilings - are read with it, under the process environment; any other REVIEW_GATES_* line (a path, a binary) is not, and run and seats name it. crafter-review seats probes and reports them. It never prints a token. An account whose 5-hour limit Anthropic has removed shows no limit in that column and is placed by its weekly window. Every account takes at most 128 passes at once, every driver's counted, and there is no machine-wide cap beyond that and free memory (run --concurrency N sets one).
  • Chromium, for a lens with the browser. Run crafter-review browser install once. It installs the Chromium build the pinned @playwright/mcp@0.0.83 drives into ~/.cache/ms-playwright, where the MCP server looks. It runs Playwright's own installer, which also removes the browser builds no installed Playwright on the machine refers to.
  • A local Supabase stack, for a lens with the database (supabase start, or the repository's own bring-up). The sql helper speaks the Postgres protocol itself: no psql and no driver to install. Every call is one transaction, rolled back.
  • gpg, if your commits are signed. freeze reads each commit's signature through gnupg's keyboxd socket, which is all the sandbox is given. The agent, which signs, stays out of reach.

Setting a repository up

From the repository's toplevel:

crafter-review init
crafter-review config        # edit .review-gates/config.json until it prints no problem

init does four things:

  • it writes .review-gates/config.json from the template when there is none - the three artifact kinds as modes with every lens of their kind: trim each to what this repository runs;
  • it adds .review-gates/push/ and .review-gates/trees/ to .gitignore;
  • it writes the /prepush skill to .claude/skills/prepush/SKILL.md, which a later init refreshes (the line after its frontmatter marks it as init's);
  • it prints the next steps.

It writes nothing in a subdirectory, in your home directory (--force when that is the repository) or over a SKILL.md it did not write.

.review-gates/config.json is committed. It says what the pipeline must know about the repository:

  • who may author its commits, and which branch a range is measured against;
  • its modes (modes.<name>): each an artifact kind (reply, series, commits; a kind is a mode of its own name), the lenses it runs (every lens is off until a mode lists it; they run in their kind's gate order - the table above for commits, the kernel modes' gates below for a series or a reply) and the effort it runs at; run --mode names one;
  • the environment each lens gets (none, browser, supabase or browser+supabase);
  • each lens's own section (lenses.<name>.section), a file in the repository appended to that lens's shipped charter, for the calibration entries and conventions that are this repository's rather than the lens's (the reuse keys carry the combined text's hash); a lesson general to the lens goes into the shipped charter;
  • how its application is started and reached: one stack, or several side by side (slots);
  • where its local database answers;
  • which paths carry no runtime behaviour.

crafter-review doc config prints the template; crafter-review doc prepush prints the procedure, process/prepush.md.

Moving a repository off the review-gates submodule

  1. Re-freeze every review in flight with crafter-review. The Flatpak's git may be another version than the one that froze it, and a different git version gives a different hash. The old run-line.txt also points into the submodule.
  2. git submodule deinit -f review-gates && git rm review-gates
  3. crafter-review init. If .claude/skills/prepush is the old link into review-gates/, it removes the link, never writing through it, and makes a real directory with the skill.

An existing config.json needs one change since 2026-11-01: lenses.<name>.run is retired, so list the lenses in modes.commits.lenses (config names what is missing, and freeze, run and slots refuse until it is there). The reuse keys keep working: a range reviewed before the move is reused, not bought again.

Running a review

crafter-review freeze --slug <branch>          # Gate 0: checks, export, hash, run line
# start the application / the stack from the checkout at HEAD, when a lens has the browser / the database
crafter-review run --artifact .review-gates/push/<branch>/ --hash <sha256> \
    --base <sha> --trees . --dispositions .review-gates/push/<branch>/gate-state/dispositions.json --date <date>

freeze prints the exact run line and writes it to run-line.txt; launch from there in a detached shell (setsid nohup … &). The exit codes:

  • 0: SEND-READY (push-ready);
  • 1: FIX FIRST;
  • 2: INCOMPLETE, PARTIAL or CONFIRMING (run the same line again);
  • 3: preflight.

The dump with every finding, note and coverage entry is gate-state/commits-<hash8>-run-<n>.json.

Reviewing another repository

A directory can review repositories other than itself: a workbench whose config, .env and records serve several. From it, crafter-review freeze --repo <path> --out <dir> --worktree, then the printed run line. The run reads the repository under review from the artifact's freeze.json: the prompt names it, the calibration is kept under it, and --trees defaults to the recorded worktree. The config and the slots are the working directory's, the seats file is looked up from it (Seats, above), the lenses run in it, and --context-paths are relative to it.

The kernel modes: a patch series, a mailing-list reply

Beside the push gates, crafter-review runs the pre-send review a kernel contributor's workbench keeps: --mode series over a git format-patch export with its cover letter, and --mode reply over a mail draft (a reply's gate-state sits beside the draft). Both take --subsystem <name>, whose journal - journal/<name>/ledger.md, captures/, captures/threads/ - is the lenses' evidence, and the trees --trees names (default work/linux,work/milos-linux; history only in the full clone). lore.kernel.org is reached through python3 utilities/lore.py in the working directory, which also prefetches every reference the artifact cites into gate-state/refs/ before the gates run. The series gates are structure (better-solution), code (code-quality, minimalism, bug-prover) and artifact truth (facts, consistency, process-docs, etiquette, conciseness); a reply runs an artifact truth of its own alone (facts, etiquette, consistency, conciseness). --reviewers "A B,C D" adds the persona gate after them: one instance of the maintainer-persona lens per reviewer, calibrated on their own lore mail, its whole pass gating its blocks and pruning the units that are not theirs (--persona-blocks all keeps them). Gates clean without a persona pass is GATES CLEAN, not SEND-READY. These lenses are off until the config turns them on, like every other; the effort defaults to max, and a reply's units are read once.

To stop a run, run crafter-review stop <its scratch dir>, or crafter-review stop --hash <hash8> for every live run of a hash. Its running passes are killed, no later gate starts and the dump is written. A run takes its passes with it however it ends, SIGKILL included: each pass runs under a supervisor that kills its whole tree, the MCP server and Chromium included. flatpak kill net.catcrafts.CrafterReview is the hard stop.

Command What it does
freeze Gate 0: the checks, the export, the hash, the run line; --verify at push time
run the gates of the mode (--mode: commits, series, reply or one of the repository's own, as the config defines it): the lenses, the dump; --dry-run launches nothing
slots how many stacks fit in free memory, and the commands for the missing ones
config the merged config and its problems
seats every seat probed: both windows, their resets, the drivers running
init, doc, browser install, stop the setup, the shipped texts, the Chromium, a stop
sql, totp, inventory the helpers a lens runs, and the partition of an export, a series or a reply

Inside the Flatpak

The sandbox gets the network, the whole host filesystem (--filesystem=host), the host's /tmp and gnupg's keyboxd socket. --filesystem=host is a convenience for a developer tool, not an isolation boundary: a lens's tool allowlist is what keeps a pass to its job, as it was outside the Flatpak.

The driver hands its children the host's XDG directories. git then reads ~/.config/git/config, Playwright finds ~/.cache/ms-playwright, and claude and npx behave as on the host. A pass's shell is bash. A few things do differ from a native run:

  • a lens reading /usr sees the Sdk's;
  • git hooks run with the Sdk's tools;
  • FLATPAK_ID reaches every child;
  • a runtime update can change git between freeze and --verify, which the MISMATCH message then names.

Several drivers on one machine. Every driver registers its passes on one board, ~/.cache/review-gates/seat-board.json, and holds a lifetime lock beside it while it runs. A driver in the Flatpak publishes the host pid of its sandbox, so a driver outside it sees it alive exactly as long as it runs and sweeps it the moment it is gone.

Building from source

crafter-build --local test          # the suite; --local takes ../Crafter.Network instead of the pinned commit
crafter-build --local lint
crafter-build run flatpak           # the Flatpak on the stable Crafter.Build base (flatpak-builder or org.flatpak.Builder)
crafter-build run install           # install that build as net.catcrafts.CrafterReview//local

run flatpak --base=<branch> builds on another branch of the base app, and --deps=local takes Crafter.Network and Crafter.Thread from the checkouts next to this one, for commits not yet on forgejo. AGENTS.md is the guide for changing the pipeline.

License

crafter-review is licensed under the GNU Affero General Public License v3.0 only (LICENSE). It vendors nlohmann/json 3.12.0 (lib/json.hpp, MIT License, © 2013-2025 Niels Lohmann).

Copyright (C) 2026 Catcrafts®