// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // The commands around a run, this binary as crafter-review: init (writes, // refreshes, keeps what it did not write, replaces the old link into the // submodule, refuses a foreign link, a subdirectory and the home directory // before it writes anything), doc, stop (a scratch dir, --hash on the // board), seats on a claude stand-in, and browser's usage. #include import std; import Crafter.Review; namespace fs = std::filesystem; using namespace Crafter::Review; namespace { std::int32_t Failures = 0; void Check(std::string_view name, bool cond, std::string_view detail = {}) { std::println("{}{}{}", cond ? " ok " : " FAIL ", name, !cond && !detail.empty() ? std::format(" ({})", detail) : std::string()); if (!cond) ++Failures; } void Write(const fs::path& path, std::string_view text) { fs::create_directories(path.parent_path()); std::ofstream(path, std::ios::binary) << text; } std::string Read(const fs::path& path) { return Platform::ReadFile(path).value_or(""); } // A claude that answers a seat probe as the CLI does: a rate_limit_event // with both windows, and on a Fable request the Fable window too. constexpr std::string_view FakeClaude = R"sh(#!/bin/sh model= prev= for a in "$@"; do [ "$prev" = --model ] && model=$a; prev=$a; done cat > /dev/null now=$(date +%s) fable= [ "$model" = claude-fable-5-1 ] && fable=",\"seven_day_overage_included\":{\"utilization\":0.3,\"resetsAt\":$((now+172800))}" echo '{"type":"system","subtype":"init","session_id":"0b6f3c56-1111-4222-8333-944455556666"}' echo "{\"type\":\"rate_limit_event\",\"rate_limit_info\":{\"status\":\"allowed\",\"rateLimitType\":\"five_hour\",\"resetsAt\":$((now+3600)),\"unifiedWindows\":{\"five_hour\":{\"utilization\":0.42,\"resetsAt\":$((now+3600))},\"seven_day\":{\"utilization\":0.13,\"resetsAt\":$((now+259200))}$fable}}}" echo '{"type":"result","subtype":"success","is_error":false,"result":"ok","total_cost_usd":0.0001}' )sh"; // The environment of every child: git with no user config, this // checkout as the home, nothing of the shell's review or seat setup. std::map Env; std::map With(std::map env, const std::map& changes) { for (const auto& [k, v] : changes) env[k] = v; return env; } Process::Captured Self(std::vector args, const fs::path& cwd, const std::map& env = {}) { args.insert(args.begin(), Platform::SelfExe().string()); return Process::Run(args, cwd, std::chrono::seconds(60), {}, With(Env, env)); } std::int32_t Checks() { std::println("commands: init, doc, stop, seats on a claude stand-in, browser's usage"); Platform::SetMode(Platform::Mode::Native); const fs::path checkout = fs::current_path(); SetHome(checkout); const fs::path base = fs::temp_directory_path() / std::format("crafter-review-commands-{}", Platform::PublishedPid()); fs::remove_all(base); for (const auto& [k, v] : Platform::Environment()) { const bool review = k.starts_with("REVIEW_GATES_") || k.starts_with("CRAFTER_REVIEW_") || k == "SEATS_ENV_FILE" || k == "CLAUDE_BIN"; const bool seat = k.starts_with("CLAUDE_CODE_OAUTH_TOKEN") || k.starts_with("ANTHROPIC_API_KEY") || k.starts_with("OPENROUTER_KEY"); if (!review && !seat) Env[k] = v; } Env = With(Env, {{"GIT_CONFIG_GLOBAL", "/dev/null"}, {"GIT_CONFIG_SYSTEM", "/dev/null"}, {"CRAFTER_REVIEW_HOME", checkout.string()}, {"REVIEW_GATES_BOARD", (base / "board.json").string()}, {"REVIEW_GATES_SCRATCH", (base / "scratch").string()}, {"TZ", "UTC"}}); auto repo = [&](std::string_view name) { const fs::path dir = base / name; fs::create_directories(dir); Process::Run({"git", "init", "-q", "-b", "main", dir.string()}, std::nullopt, std::chrono::seconds(60), {}, Env); return dir; }; const std::string shipped = Read(checkout / "process" / "skill" / "prepush" / "SKILL.md"); const std::string marker = "\nold text\n", marker)); const Process::Captured again = Self({"init"}, r); Check("init again: the config and an entry already ignored are kept, the missing one added once, the skill it wrote refreshed", again.returnCode == 0 && Read(r / ".review-gates" / "config.json") == R"({"base": "origin/dev"})" && Read(r / ".gitignore") == "node_modules/\n/.review-gates/push\n.review-gates/trees/\n" && Read(r / ".claude" / "skills" / "prepush" / "SKILL.md") == skill && again.out.contains("refreshed .claude/skills/prepush/SKILL.md"), again.out + again.err); Write(r / ".claude" / "skills" / "prepush" / "SKILL.md", "my own skill\n"); const Process::Captured kept = Self({"init"}, r); const std::string mine = Read(r / ".claude" / "skills" / "prepush" / "SKILL.md"); const Process::Captured forced = Self({"init", "--force"}, r); Check("init: a SKILL.md init did not write is kept; --force replaces it", kept.returnCode == 0 && mine == "my own skill\n" && kept.out.contains("--force replaces it") && forced.returnCode == 0 && Read(r / ".claude" / "skills" / "prepush" / "SKILL.md") == skill, kept.out + forced.out); // The old setup: the skill dir a link into the submodule. const fs::path old = repo("old"); Write(old / "review-gates" / "process" / "skill" / "prepush" / "SKILL.md", "the submodule's file\n"); fs::create_directories(old / ".claude" / "skills"); fs::create_directory_symlink("../../review-gates/process/skill/prepush", old / ".claude" / "skills" / "prepush"); const Process::Captured relinked = Self({"init"}, old); std::error_code error; Check("init: the old link into the submodule goes - a real directory with the skill in its place, the submodule's file untouched", relinked.returnCode == 0 && !fs::is_symlink(fs::symlink_status(old / ".claude" / "skills" / "prepush", error)) && Read(old / ".claude" / "skills" / "prepush" / "SKILL.md") == skill && Read(old / "review-gates" / "process" / "skill" / "prepush" / "SKILL.md") == "the submodule's file\n" && relinked.out.contains("removed the link .claude/skills/prepush -> ../../review-gates/process/skill/prepush"), relinked.out + relinked.err); // What init refuses, before it writes anything. const fs::path foreign = repo("foreign"); fs::create_directories(base / "elsewhere"); fs::create_directories(foreign / ".claude" / "skills"); fs::create_directory_symlink("../../../elsewhere", foreign / ".claude" / "skills" / "prepush"); const Process::Captured refused = Self({"init"}, foreign); fs::create_directories(r / "sub"); const Process::Captured sub = Self({"init"}, r / "sub"); const fs::path home = repo("home"); const Process::Captured atHome = Self({"init"}, home, {{"HOME", home.string()}}); const bool homeUntouched = !fs::exists(home / ".review-gates"); const Process::Captured atHomeForced = Self({"init", "--force"}, home, {{"HOME", home.string()}}); fs::create_directories(base / "plain"); const Process::Captured plain = Self({"init"}, base / "plain"); Check("init refuses, exit 3, writing nothing: a link that is not into the submodule (--force replaces it), a subdirectory, the home directory (--force goes on), no checkout", refused.returnCode == 3 && refused.err.contains(".claude/skills/prepush is a link to ../../../elsewhere") && !fs::exists(foreign / ".review-gates") && !fs::exists(foreign / ".gitignore") && sub.returnCode == 3 && sub.err.contains("from the repository's toplevel") && !fs::exists(r / "sub" / ".review-gates") && atHome.returnCode == 3 && atHome.err.contains("is your home directory") && homeUntouched && atHomeForced.returnCode == 0 && plain.returnCode == 3 && plain.err.contains("is not in a git checkout"), refused.err + sub.err + atHome.err + plain.err); // doc: the texts, and where they are. const Process::Captured prepush = Self({"doc", "prepush"}, base); const Process::Captured where = Self({"doc", "--path", "config"}, base); const Process::Captured missing = Self({"doc"}, base); const Process::Captured wrong = Self({"doc", "nope"}, base); Check("doc: the procedure byte for byte, a template's path, usage errors for no name and an unknown one (exit 3)", prepush.returnCode == 0 && prepush.out == Read(checkout / "process" / "prepush.md") && where.returnCode == 0 && where.out == std::format("{}\n", (checkout / "process" / "templates" / "config.json").string()) && missing.returnCode == 3 && missing.err.contains("the following arguments are required: name") && wrong.returnCode == 3 && wrong.err.contains("argument name: invalid choice: 'nope' (choose from 'prepush', 'manifest', 'config')"), prepush.err + where.out + missing.err + wrong.err); // stop: a live driver is this process; one that is gone, a sleep that // was reaped. const std::string me = Platform::Self()->Key(); const fs::path live = base / "scratch" / "abcd1234-run-1"; Write(live / ".driver", me); const std::int64_t gone = Process::Start({.argv = {"true"}}).pid; Process::Wait(gone); const fs::path dead = base / "scratch" / "abcd1234-run-2"; Write(dead / ".driver", std::format("{}:{}", gone, 1)); const Process::Captured byDir = Self({"stop", live.string()}, base); const bool wroteDir = fs::exists(live / "stop"); fs::remove(live / "stop"); const Process::Captured byDead = Self({"stop", dead.string()}, base); const Process::Captured byNone = Self({"stop", (base / "plain").string()}, base); const Process::Captured neither = Self({"stop"}, base); Check("stop : the stop file for the live driver its .driver names (exit 0); a driver that is gone, a dir no run holds, no target: nothing written", byDir.returnCode == 0 && wroteDir && byDir.out.contains(std::format("stop: driver {}", me)) && byDead.returnCode == 1 && byDead.err.contains("is no longer running") && !fs::exists(dead / "stop") && byNone.returnCode == 1 && byNone.err.contains("no .driver marker") && neither.returnCode == 3, byDir.out + byDir.err + byDead.err + byNone.err); Json drivers = Json::object(); drivers[me] = {{"run", "commits x @ abcd1234"}, {"scratch", live.string()}, {"since", Platform::Now()}, {"boot", Platform::BootId()}}; Write(base / "board.json", DumpFile({{"version", 1}, {"drivers", drivers}, {"seats", Json::object()}})); const std::string boardBefore = Read(base / "board.json"); const Process::Captured byHash = Self({"stop", "--hash", "abcd1234ffff"}, base); const bool wroteHash = fs::exists(live / "stop"); const Process::Captured otherHash = Self({"stop", "--hash", "00000000"}, base); Check("stop --hash: every live run of the hash on the board gets its stop file (the first 8 characters decide); another hash finds none (exit 1); the board is read, never written", byHash.returnCode == 0 && wroteHash && otherHash.returnCode == 1 && otherHash.err.contains("no live run of 00000000 on the board") && Read(base / "board.json") == boardBefore, byHash.out + byHash.err + otherHash.err); // seats on the stand-in: a token from the env file, an API key, the // CLI's own login. const fs::path fake = base / "claude"; Write(fake, FakeClaude); fs::permissions(fake, fs::perms::owner_all); Write(base / "seats" / ".env", "CLAUDE_CODE_OAUTH_TOKEN1=\"tok-one\"\nANTHROPIC_API_KEY1=sk-ant-api-one\n"); fs::remove(base / "board.json"); const std::map withFake = {{"CLAUDE_BIN", fake.string()}}; const Process::Captured report = Self({"seats", "--local", "--env-file", ".env"}, base / "seats", withFake); const Process::Captured json = Self({"seats", "--json", "--no-fable"}, base / "seats", withFake); Json parsed; try { parsed = Json::parse(json.out); } catch (const std::exception&) { } Check("seats: every seat probed - the CLI's own login, the env file's token, the API key - its windows read, the API seat beside the driver's ledger, the drivers on the board", report.returnCode == 0 && report.out.starts_with("seat pool: 3 seat(s) (local, 1, api1) from .env and your own login, probed ") && report.out.contains("| local | 42% |") && report.out.contains("| 1 | 42% |") && report.out.contains("| api1 | ok | $0.00 of $200") && report.out.contains("drivers: none running"), report.out + report.err); Check("seats --json: the probe results as JSON - the seats, the API seats, the eligible ones and the next placement; ./.env read by default; no token, no key printed", json.returnCode == 0 && parsed.is_object() && parsed.at("seats").size() == 1 && parsed.at("seats").at(0).at("probedWith") == "haiku" && parsed.at("apiSeats").size() == 1 && parsed.at("eligible") == Json::array({1}) && parsed.at("next").at("seat") == 1 && Dyn::Str(parsed.at("probedAt")).ends_with("Z") && !(report.out + json.out).contains("tok-one") && !(report.out + json.out).contains("sk-ant-api-one"), json.out.substr(0, 600) + json.err); const Process::Captured none = Self({"seats"}, base / "plain", withFake); const Process::Captured noFile = Self({"seats", "--env-file", "missing.env"}, base / "plain", withFake); Write(base / "plain" / "bad.env", std::string("\xff\n", 2)); const Process::Captured badFile = Self({"seats", "--env-file", "bad.env"}, base / "plain", withFake); Check("seats: no seat configured is exit 1 with what it looked for; a missing env file and one that is not UTF-8 are exit 1 with the read error", none.returnCode == 1 && none.out.starts_with("no seat configured (CLAUDE_CODE_OAUTH_TOKEN1..6 / ANTHROPIC_API_KEY1..6; --local probes your own login)") && noFile.returnCode == 1 && noFile.err.contains("FileNotFoundError: [Errno 2] No such file or directory: 'missing.env'") && badFile.returnCode == 1 && badFile.err.contains("UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"), none.out + noFile.err + badFile.err); // browser: its usage (an install fetches from npm). const Process::Captured noAction = Self({"browser"}, base); const Process::Captured badAction = Self({"browser", "remove"}, base); Check("browser: an action is required, and install is the one there is (exit 3)", noAction.returnCode == 3 && noAction.err.contains("the following arguments are required: action") && badAction.returnCode == 3 && badAction.err.contains("argument action: invalid choice: 'remove' (choose from 'install')"), noAction.err + badAction.err); fs::remove_all(base); return Failures ? 1 : 0; } } int main(int argc, char** argv) { const std::vector args(argv, argv + argc); // With arguments this binary is crafter-review itself, in native mode // whatever builds it. if (args.size() > 1) { Platform::SystemPaths paths = Platform::Paths(); paths.flatpakInfo = "/nonexistent/.flatpak-info"; Platform::SetPaths(paths); return Crafter::Review::Main(args); } try { return Checks(); } catch (const std::exception& error) { std::println(std::cerr, "commands: {}", error.what()); return 1; } }