feat: the crafter-review skeleton, and its semantics for text, JSON and crypto
What the rest of crafter-review stands on.
- project.cpp: a static crafter.review-lib and the crafter-review
executable; Crafter.Network without QUIC, pinned, or with --local from
the checkout next to this one; Crafter.LintRules' lite rules, pinned;
the version from CRAFTER_REVIEW_VERSION, git describe or 0.0.0-dev.
- :Core and :Driver: the version, PromptVersion 1, --version.
- :Json: nlohmann's ordered_json, which keeps key order and unknown keys,
so the files every driver on a machine shares survive a rewrite; dumps
in the indented file layout, the spaced one-line form and the compact
sorted form the structured-output schema needs byte for byte.
- :Crypto: SHA-256/SHA-1/MD5, HMAC, PBKDF2 through libcrypto; base64 and
base32 written out, because what they refuse, and with which message,
is pinned.
- :Unicode, Unicode 16.0.0's tables: whitespace, line breaks, decimal
digits, printable characters, case mappings and case-insensitive folds.
- :Text: string functions on code points, number parsing, the shortest
float form and half-even rounding, printed values, shell patterns, URL
splitting and unquoting, POSIX paths, universal newlines and the
replacing UTF-8 decoder.
Tests: Crypto (published vectors), Text (293 reference vectors).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:29:18 +01:00
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
module ;
# include <stdio.h>
feat: the crafter-review skeleton, and its semantics for text, JSON and crypto
What the rest of crafter-review stands on.
- project.cpp: a static crafter.review-lib and the crafter-review
executable; Crafter.Network without QUIC, pinned, or with --local from
the checkout next to this one; Crafter.LintRules' lite rules, pinned;
the version from CRAFTER_REVIEW_VERSION, git describe or 0.0.0-dev.
- :Core and :Driver: the version, PromptVersion 1, --version.
- :Json: nlohmann's ordered_json, which keeps key order and unknown keys,
so the files every driver on a machine shares survive a rewrite; dumps
in the indented file layout, the spaced one-line form and the compact
sorted form the structured-output schema needs byte for byte.
- :Crypto: SHA-256/SHA-1/MD5, HMAC, PBKDF2 through libcrypto; base64 and
base32 written out, because what they refuse, and with which message,
is pinned.
- :Unicode, Unicode 16.0.0's tables: whitespace, line breaks, decimal
digits, printable characters, case mappings and case-insensitive folds.
- :Text: string functions on code points, number parsing, the shortest
float form and half-even rounding, printed values, shell patterns, URL
splitting and unquoting, POSIX paths, universal newlines and the
replacing UTF-8 decoder.
Tests: Crypto (published vectors), Text (293 reference vectors).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:29:18 +01:00
module Crafter . Review : Driver_impl ;
import : Driver ;
import : Core ;
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
import : Json ;
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
import : Text ;
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
import : Platform ;
import : Process ;
feat(pipeline): the pure pipeline, checked against its goldens
The modules that turn a frozen range into passes and passes into a
verdict: :Schema, :Tools (the DENY, git and base lists, a leaf, so the
config and the pool both read them), :Plan, :Artifact, :Inventory,
:Dispositions, :State, :Normalize, :Stream, :Prompt, :Config, and the URL
half of :PgWire. Units, jobs, results, the config and the claude stream
stay JSON from end to end, so every key keeps its place; :Text gives JSON
values truthiness, get, ==, membership and Str(), and throws Dyn::Error
under a name for each kind of unexpected data.
Exactness where the bytes are seen:
- the stream classifiers are hand-written scanners with case-insensitive
folds (the Kelvin sign is a k), Unicode \b and \s, and `.` stopping at
\n; the SQL call detection takes `<exe> sql` with any path before it;
- config problems carry the JSON scanner's and the UTF-8 decoder's own
texts, positions included;
- a float total is Neumaier's compensated sum, and the build passes
-ffp-contract=off: -march=native would fuse a*b+c;
- `crafter-review inventory` and `config` print what the goldens record,
byte for byte (the fixture and 14 configs, broken ones included); a
message names this binary as the command to run.
tests/Goldens: the recorded answers over the fixtures next to them (the
commits fixture, a rich versioned series with CRLF, a folded subject,
Markdown headings in and out of fences, the code-key rewrites); the suite
compares 2304 results as DumpFile writes them, so a key out of order fails
too. All agree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:48:55 +01:00
import : Inventory ;
import : Config ;
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
import : Freeze ;
feat(sql): the database helper and the TOTP helper, as subcommands
`crafter-review sql` hands the SQL to the driver's broker named in
REVIEW_GATES_DB; the broker runs it as one transaction, rolled back, and
streams the lines back with the exit code last. The client speaks protocol
3.0 over Crafter.Network's TCP client: cleartext, MD5 and SCRAM-SHA-256,
one Parse per statement, the transaction status as the backstop. The
splitter, the refusals, the table layout and every message work on code
points; connect and socket errors read "[Errno 111] Connection refused"
or "timed out". `crafter-review totp` prints "123456 (valid 17s)".
No sleep after the last connect attempt; TOTP reads the clock once; a
broker token that is no string gets the NO DATABASE answer and the exit
mark.
The goldens add the splitter, the comment stripping, the fingerprint, the
refusals, the first words, the headline, the cell, table, error and notice
layouts, the run's refusals and limits and 40 TOTP codes (2774 checks
now). tests/PgWire runs the helper over a Postgres stand-in and compares
its output with the goldens, line for line, plus the broker's protocol.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:20:00 +01:00
import : PgWire ;
import : Totp ;
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
import : Plan ;
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
import : Mcp ;
import : Seats ;
import : SeatPool ;
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
import : OpenCode ;
import : Run ;
feat: the crafter-review skeleton, and its semantics for text, JSON and crypto
What the rest of crafter-review stands on.
- project.cpp: a static crafter.review-lib and the crafter-review
executable; Crafter.Network without QUIC, pinned, or with --local from
the checkout next to this one; Crafter.LintRules' lite rules, pinned;
the version from CRAFTER_REVIEW_VERSION, git describe or 0.0.0-dev.
- :Core and :Driver: the version, PromptVersion 1, --version.
- :Json: nlohmann's ordered_json, which keeps key order and unknown keys,
so the files every driver on a machine shares survive a rewrite; dumps
in the indented file layout, the spaced one-line form and the compact
sorted form the structured-output schema needs byte for byte.
- :Crypto: SHA-256/SHA-1/MD5, HMAC, PBKDF2 through libcrypto; base64 and
base32 written out, because what they refuse, and with which message,
is pinned.
- :Unicode, Unicode 16.0.0's tables: whitespace, line breaks, decimal
digits, printable characters, case mappings and case-insensitive folds.
- :Text: string functions on code points, number parsing, the shortest
float form and half-even rounding, printed values, shell patterns, URL
splitting and unquoting, POSIX paths, universal newlines and the
replacing UTF-8 decoder.
Tests: Crypto (published vectors), Text (293 reference vectors).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:29:18 +01:00
import std ;
feat(pipeline): the pure pipeline, checked against its goldens
The modules that turn a frozen range into passes and passes into a
verdict: :Schema, :Tools (the DENY, git and base lists, a leaf, so the
config and the pool both read them), :Plan, :Artifact, :Inventory,
:Dispositions, :State, :Normalize, :Stream, :Prompt, :Config, and the URL
half of :PgWire. Units, jobs, results, the config and the claude stream
stay JSON from end to end, so every key keeps its place; :Text gives JSON
values truthiness, get, ==, membership and Str(), and throws Dyn::Error
under a name for each kind of unexpected data.
Exactness where the bytes are seen:
- the stream classifiers are hand-written scanners with case-insensitive
folds (the Kelvin sign is a k), Unicode \b and \s, and `.` stopping at
\n; the SQL call detection takes `<exe> sql` with any path before it;
- config problems carry the JSON scanner's and the UTF-8 decoder's own
texts, positions included;
- a float total is Neumaier's compensated sum, and the build passes
-ffp-contract=off: -march=native would fuse a*b+c;
- `crafter-review inventory` and `config` print what the goldens record,
byte for byte (the fixture and 14 configs, broken ones included); a
message names this binary as the command to run.
tests/Goldens: the recorded answers over the fixtures next to them (the
commits fixture, a rich versioned series with CRLF, a folded subject,
Markdown headings in and out of fences, the code-key rewrites); the suite
compares 2304 results as DumpFile writes them, so a key out of order fails
too. All agree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:48:55 +01:00
namespace fs = std : : filesystem ;
feat: the crafter-review skeleton, and its semantics for text, JSON and crypto
What the rest of crafter-review stands on.
- project.cpp: a static crafter.review-lib and the crafter-review
executable; Crafter.Network without QUIC, pinned, or with --local from
the checkout next to this one; Crafter.LintRules' lite rules, pinned;
the version from CRAFTER_REVIEW_VERSION, git describe or 0.0.0-dev.
- :Core and :Driver: the version, PromptVersion 1, --version.
- :Json: nlohmann's ordered_json, which keeps key order and unknown keys,
so the files every driver on a machine shares survive a rewrite; dumps
in the indented file layout, the spaced one-line form and the compact
sorted form the structured-output schema needs byte for byte.
- :Crypto: SHA-256/SHA-1/MD5, HMAC, PBKDF2 through libcrypto; base64 and
base32 written out, because what they refuse, and with which message,
is pinned.
- :Unicode, Unicode 16.0.0's tables: whitespace, line breaks, decimal
digits, printable characters, case mappings and case-insensitive folds.
- :Text: string functions on code points, number parsing, the shortest
float form and half-even rounding, printed values, shell patterns, URL
splitting and unquoting, POSIX paths, universal newlines and the
replacing UTF-8 decoder.
Tests: Crypto (published vectors), Text (293 reference vectors).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:29:18 +01:00
namespace Crafter : : Review {
namespace {
constexpr std : : string_view Usage =
R " (usage: crafter-review [--config FILE] {run,freeze,slots,config} ...
crafter - review { seats , inventory , sql , totp , init , doc , browser , stop } . . .
crafter - review - - version
The pre - push review pipeline : adversarial lenses , one claude process per
inventory unit , against a frozen commit range . ) " ;
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
std : : int32_t PrintVersion ( ) {
feat: the crafter-review skeleton, and its semantics for text, JSON and crypto
What the rest of crafter-review stands on.
- project.cpp: a static crafter.review-lib and the crafter-review
executable; Crafter.Network without QUIC, pinned, or with --local from
the checkout next to this one; Crafter.LintRules' lite rules, pinned;
the version from CRAFTER_REVIEW_VERSION, git describe or 0.0.0-dev.
- :Core and :Driver: the version, PromptVersion 1, --version.
- :Json: nlohmann's ordered_json, which keeps key order and unknown keys,
so the files every driver on a machine shares survive a rewrite; dumps
in the indented file layout, the spaced one-line form and the compact
sorted form the structured-output schema needs byte for byte.
- :Crypto: SHA-256/SHA-1/MD5, HMAC, PBKDF2 through libcrypto; base64 and
base32 written out, because what they refuse, and with which message,
is pinned.
- :Unicode, Unicode 16.0.0's tables: whitespace, line breaks, decimal
digits, printable characters, case mappings and case-insensitive folds.
- :Text: string functions on code points, number parsing, the shortest
float form and half-even rounding, printed values, shell patterns, URL
splitting and unquoting, POSIX paths, universal newlines and the
replacing UTF-8 decoder.
Tests: Crypto (published vectors), Text (293 reference vectors).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:29:18 +01:00
std : : println ( " {} " , DriverLine ( ) ) ;
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
try {
std : : println ( " home {} " , Home ( ) . string ( ) ) ;
for ( const auto & [ name , sha ] : CharterHashes ( ) ) std : : println ( " charter {} {} " , name , sha ) ;
} catch ( const std : : exception & e ) {
std : : println ( " home not found: {} " , e . what ( ) ) ;
}
feat: the crafter-review skeleton, and its semantics for text, JSON and crypto
What the rest of crafter-review stands on.
- project.cpp: a static crafter.review-lib and the crafter-review
executable; Crafter.Network without QUIC, pinned, or with --local from
the checkout next to this one; Crafter.LintRules' lite rules, pinned;
the version from CRAFTER_REVIEW_VERSION, git describe or 0.0.0-dev.
- :Core and :Driver: the version, PromptVersion 1, --version.
- :Json: nlohmann's ordered_json, which keeps key order and unknown keys,
so the files every driver on a machine shares survive a rewrite; dumps
in the indented file layout, the spaced one-line form and the compact
sorted form the structured-output schema needs byte for byte.
- :Crypto: SHA-256/SHA-1/MD5, HMAC, PBKDF2 through libcrypto; base64 and
base32 written out, because what they refuse, and with which message,
is pinned.
- :Unicode, Unicode 16.0.0's tables: whitespace, line breaks, decimal
digits, printable characters, case mappings and case-insensitive folds.
- :Text: string functions on code points, number parsing, the shortest
float form and half-even rounding, printed values, shell patterns, URL
splitting and unquoting, POSIX paths, universal newlines and the
replacing UTF-8 decoder.
Tests: Crypto (published vectors), Text (293 reference vectors).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:29:18 +01:00
return 0 ;
}
feat(pipeline): the pure pipeline, checked against its goldens
The modules that turn a frozen range into passes and passes into a
verdict: :Schema, :Tools (the DENY, git and base lists, a leaf, so the
config and the pool both read them), :Plan, :Artifact, :Inventory,
:Dispositions, :State, :Normalize, :Stream, :Prompt, :Config, and the URL
half of :PgWire. Units, jobs, results, the config and the claude stream
stay JSON from end to end, so every key keeps its place; :Text gives JSON
values truthiness, get, ==, membership and Str(), and throws Dyn::Error
under a name for each kind of unexpected data.
Exactness where the bytes are seen:
- the stream classifiers are hand-written scanners with case-insensitive
folds (the Kelvin sign is a k), Unicode \b and \s, and `.` stopping at
\n; the SQL call detection takes `<exe> sql` with any path before it;
- config problems carry the JSON scanner's and the UTF-8 decoder's own
texts, positions included;
- a float total is Neumaier's compensated sum, and the build passes
-ffp-contract=off: -march=native would fuse a*b+c;
- `crafter-review inventory` and `config` print what the goldens record,
byte for byte (the fixture and 14 configs, broken ones included); a
message names this binary as the command to run.
tests/Goldens: the recorded answers over the fixtures next to them (the
commits fixture, a rich versioned series with CRLF, a folded subject,
Markdown headings in and out of fences, the code-key rewrites); the suite
compares 2304 results as DumpFile writes them, so a key out of order fails
too. All agree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:48:55 +01:00
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
// A usage error: exit 3, the preflight's code, for every command that
// cannot start.
std : : int32_t UsageError ( std : : string_view usage , std : : string_view prog , std : : string_view message ) {
std : : println ( std : : cerr , " {} \n {}: error: {} " , usage , prog , message ) ;
feat(pipeline): the pure pipeline, checked against its goldens
The modules that turn a frozen range into passes and passes into a
verdict: :Schema, :Tools (the DENY, git and base lists, a leaf, so the
config and the pool both read them), :Plan, :Artifact, :Inventory,
:Dispositions, :State, :Normalize, :Stream, :Prompt, :Config, and the URL
half of :PgWire. Units, jobs, results, the config and the claude stream
stay JSON from end to end, so every key keeps its place; :Text gives JSON
values truthiness, get, ==, membership and Str(), and throws Dyn::Error
under a name for each kind of unexpected data.
Exactness where the bytes are seen:
- the stream classifiers are hand-written scanners with case-insensitive
folds (the Kelvin sign is a k), Unicode \b and \s, and `.` stopping at
\n; the SQL call detection takes `<exe> sql` with any path before it;
- config problems carry the JSON scanner's and the UTF-8 decoder's own
texts, positions included;
- a float total is Neumaier's compensated sum, and the build passes
-ffp-contract=off: -march=native would fuse a*b+c;
- `crafter-review inventory` and `config` print what the goldens record,
byte for byte (the fixture and 14 configs, broken ones included); a
message names this binary as the command to run.
tests/Goldens: the recorded answers over the fixtures next to them (the
commits fixture, a rich versioned series with CRLF, a folded subject,
Markdown headings in and out of fences, the code-key rewrites); the suite
compares 2304 results as DumpFile writes them, so a key out of order fails
too. All agree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:48:55 +01:00
return 3 ;
}
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
// die(): the preflight's message and exit 3.
std : : int32_t Die ( std : : string_view message ) {
std : : println ( std : : cerr , " preflight: {} " , message ) ;
return 3 ;
}
// ---- the option parser these commands use ---------------------------------
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
enum class Kind {
String ,
Int ,
Float ,
} ;
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
struct OptionSpec {
std : : string_view name ;
// A value follows (`--x V` or `--x=V`); else a store_true flag.
bool takesValue ;
std : : string_view metavar ;
std : : string_view help ;
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
// An integer or a float option: the value must parse as
// Dyn::ParseInt / Dyn::ParseFloat takes it.
Kind kind = Kind : : String ;
std : : span < const std : : string_view > choices = { } ;
bool required = false ;
// Left out of --help.
bool hidden = false ;
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
} ;
struct Parsed {
std : : map < std : : string , std : : string , std : : less < > > values ;
std : : set < std : : string , std : : less < > > flags ;
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
std : : vector < std : : string > positionals ;
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
bool help = false ;
std : : string error ;
std : : optional < std : : string > Value ( std : : string_view name ) const {
const auto found = values . find ( name ) ;
if ( found = = values . end ( ) ) return std : : nullopt ;
return found - > second ;
}
bool Flag ( std : : string_view name ) const {
return flags . contains ( name ) ;
}
} ;
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
// A positional as the usage line names it: its choices in braces, else
// its name.
std : : string PositionalName ( const OptionSpec & positional ) {
if ( positional . choices . empty ( ) ) return std : : string ( positional . name ) ;
const std : : vector < std : : string > names ( positional . choices . begin ( ) , positional . choices . end ( ) ) ;
return std : : format ( " {{{}}} " , Dyn : : Join ( names , " , " ) ) ;
}
std : : string UsageLine ( std : : string_view prog , std : : span < const OptionSpec > specs , std : : span < const OptionSpec > positionals = { } ) {
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
std : : string line = std : : format ( " usage: {} [-h] " , prog ) ;
for ( const OptionSpec & spec : specs ) {
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
if ( spec . hidden ) continue ;
const std : : string text = spec . takesValue ? std : : format ( " {} {} " , spec . name , spec . metavar ) : std : : string ( spec . name ) ;
line + = spec . required ? std : : format ( " {} " , text ) : std : : format ( " [{}] " , text ) ;
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
}
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
for ( const OptionSpec & positional : positionals ) {
line + = positional . required ? std : : format ( " {} " , PositionalName ( positional ) ) : std : : format ( " [{}] " , PositionalName ( positional ) ) ;
}
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
return line ;
}
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
std : : string HelpRow ( std : : string_view left , std : : string_view help ) {
return left . size ( ) < 20 ? std : : format ( " {:<20} {} \n " , left , help ) : std : : format ( " {} \n {} \n " , left , help ) ;
}
std : : string Help ( std : : string_view prog , std : : string_view description , std : : span < const OptionSpec > specs , std : : span < const OptionSpec > positionals = { } ) {
std : : string text = std : : format ( " {} \n \n {} \n \n " , UsageLine ( prog , specs , positionals ) , description ) ;
if ( ! positionals . empty ( ) ) {
text + = " positional arguments: \n " ;
for ( const OptionSpec & positional : positionals ) text + = HelpRow ( PositionalName ( positional ) , positional . help ) ;
text + = " \n " ;
}
text + = " options: \n -h, --help show this help message and exit \n " ;
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
for ( const OptionSpec & spec : specs ) {
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
if ( spec . hidden ) continue ;
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
text + = HelpRow ( spec . takesValue ? std : : format ( " {} {} " , spec . name , spec . metavar ) : std : : string ( spec . name ) , spec . help ) ;
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
}
return text ;
}
// `--opt value`, `--opt=value`, any unique prefix of a long option, the
// last of a repeated option, every unrecognized argument named at the
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
// end; positionals in their order, each one word, optional unless
// required.
Parsed ParseOptions ( std : : span < const std : : string_view > args , std : : span < const OptionSpec > specs , std : : span < const OptionSpec > positionals = { } ) {
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
Parsed parsed ;
std : : vector < std : : string > unrecognized ;
for ( std : : size_t i = 0 ; i < args . size ( ) ; + + i ) {
const std : : string_view arg = args [ i ] ;
if ( arg = = " -h " ) {
parsed . help = true ;
continue ;
}
if ( ! arg . starts_with ( " -- " ) | | arg = = " -- " ) {
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
if ( arg ! = " -- " & & ! ( arg . starts_with ( ' - ' ) & & arg . size ( ) > 1 ) & & parsed . positionals . size ( ) < positionals . size ( ) ) {
const OptionSpec & positional = positionals [ parsed . positionals . size ( ) ] ;
if ( ! positional . choices . empty ( ) & & ! std : : ranges : : contains ( positional . choices , arg ) ) {
std : : vector < std : : string > quoted ;
for ( const std : : string_view c : positional . choices ) quoted . push_back ( Dyn : : Repr ( c ) ) ;
parsed . error = std : : format ( " argument {}: invalid choice: {} (choose from {}) " , positional . name , Dyn : : Repr ( arg ) , Dyn : : Join ( quoted , " , " ) ) ;
return parsed ;
}
parsed . positionals . emplace_back ( arg ) ;
} else {
unrecognized . emplace_back ( arg ) ;
}
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
continue ;
}
const std : : size_t equals = arg . find ( ' = ' ) ;
const std : : string_view name = arg . substr ( 0 , equals ) ;
if ( name . size ( ) > 2 & & std : : string_view ( " --help " ) . starts_with ( name ) & & std : : ranges : : none_of ( specs , [ & ] ( const OptionSpec & s ) { return s . name . starts_with ( name ) ; } ) ) {
parsed . help = true ;
continue ;
}
std : : vector < const OptionSpec * > matches ;
for ( const OptionSpec & spec : specs ) {
if ( spec . name = = name ) {
matches = { & spec } ;
break ;
}
if ( spec . name . starts_with ( name ) ) matches . push_back ( & spec ) ;
}
if ( matches . empty ( ) ) {
unrecognized . emplace_back ( arg ) ;
continue ;
}
if ( matches . size ( ) > 1 ) {
std : : vector < std : : string > names ;
for ( const OptionSpec * spec : matches ) names . emplace_back ( spec - > name ) ;
parsed . error = std : : format ( " ambiguous option: {} could match {} " , name , Dyn : : Join ( names , " , " ) ) ;
return parsed ;
}
const OptionSpec & spec = * matches . front ( ) ;
const std : : string key ( spec . name . substr ( 2 ) ) ;
if ( ! spec . takesValue ) {
if ( equals ! = std : : string_view : : npos ) {
parsed . error = std : : format ( " argument {}: ignored explicit argument {} " , spec . name , Dyn : : Repr ( arg . substr ( equals + 1 ) ) ) ;
return parsed ;
}
parsed . flags . insert ( key ) ;
continue ;
}
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
std : : string value ;
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
if ( equals ! = std : : string_view : : npos ) {
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
value = std : : string ( arg . substr ( equals + 1 ) ) ;
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
} else if ( i + 1 < args . size ( ) & & ! ( args [ i + 1 ] . starts_with ( ' - ' ) & & args [ i + 1 ] . size ( ) > 1 ) ) {
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
value = std : : string ( args [ + + i ] ) ;
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
} else {
parsed . error = std : : format ( " argument {}: expected one argument " , spec . name ) ;
return parsed ;
}
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
if ( spec . kind = = Kind : : Int & & ! Dyn : : ParseInt ( value ) ) {
parsed . error = std : : format ( " argument {}: invalid int value: {} " , spec . name , Dyn : : Repr ( value ) ) ;
return parsed ;
}
if ( spec . kind = = Kind : : Float & & ! Dyn : : ParseFloat ( value ) ) {
parsed . error = std : : format ( " argument {}: invalid float value: {} " , spec . name , Dyn : : Repr ( value ) ) ;
return parsed ;
}
if ( ! spec . choices . empty ( ) & & ! std : : ranges : : contains ( spec . choices , value ) ) {
std : : vector < std : : string > quoted ;
for ( const std : : string_view c : spec . choices ) quoted . push_back ( Dyn : : Repr ( c ) ) ;
parsed . error = std : : format ( " argument {}: invalid choice: {} (choose from {}) " , spec . name , Dyn : : Repr ( value ) , Dyn : : Join ( quoted , " , " ) ) ;
return parsed ;
}
parsed . values [ key ] = std : : move ( value ) ;
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
}
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
if ( ! unrecognized . empty ( ) ) {
parsed . error = std : : format ( " unrecognized arguments: {} " , Dyn : : Join ( unrecognized , " " ) ) ;
return parsed ;
}
std : : vector < std : : string > missing ;
for ( const OptionSpec & spec : specs ) {
if ( spec . required & & ! parsed . values . contains ( spec . name . substr ( 2 ) ) ) missing . emplace_back ( spec . name ) ;
}
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
for ( std : : size_t i = parsed . positionals . size ( ) ; i < positionals . size ( ) ; + + i ) {
if ( positionals [ i ] . required ) missing . emplace_back ( positionals [ i ] . name ) ;
}
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
if ( ! missing . empty ( ) ) parsed . error = std : : format ( " the following arguments are required: {} " , Dyn : : Join ( missing , " , " ) ) ;
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
return parsed ;
}
// The consumer config, loaded before every command but `config`:
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
// warnings on stderr (`run` logs them with its other lines), a problem
// stops it.
std : : optional < Json > LoadConfig ( const std : : optional < std : : string > & configPath , bool warn = true ) {
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
Config : : Loaded loaded = Config : : Load ( fs : : current_path ( ) , configPath ? fs : : path ( * configPath ) : fs : : path ( ) ) ;
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
if ( warn ) {
for ( const Json & warning : Dyn : : Or ( Dyn : : Get ( loaded . config , " _warnings " ) , Dyn : : EmptyList ( ) ) ) std : : println ( std : : cerr , " config: warning: {} " , warning . get < std : : string > ( ) ) ;
}
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
if ( ! loaded . problems . empty ( ) ) {
Die ( std : : format ( " config: {} " , Dyn : : Join ( loaded . problems , " ; " ) ) ) ;
return std : : nullopt ;
}
return std : : move ( loaded . config ) ;
}
constexpr std : : array < OptionSpec , 9 > FreezeOptions = { {
{ " --repo " , true , " REPO " , " the repository (toplevel of a checkout; default: the working directory) " } ,
{ " --slug " , true , " SLUG " , " the artifact dir name: .review-gates/push/<slug>/ " } ,
{ " --out " , true , " OUT " , " the artifact dir (overrides --slug) " } ,
{ " --base " , true , " BASE " , " the range base; default @{upstream}, else merge-base with the configured base branch " } ,
{ " --replace " , false , " " , " re-freeze into an out dir that already holds patches (gate-state/ is kept) and supersede this repo's worktrees " } ,
{ " --worktree " , false , " " , " check out a detached worktree of HEAD under .review-gates/trees/ for the lenses (printed as --trees) " } ,
{ " --require-signed " , false , " " , " an unsigned commit fails instead of warning " } ,
{ " --date " , true , " DATE " , " the run date (default today) " } ,
{ " --verify " , false , " " , " push-time check: HEAD, a clean tree and a fresh export still match freeze.json " } ,
} } ;
feat(modes): the kernel modes - a patch series and a mailing-list reply
Beside the push gates, crafter-review runs the pre-send review of a kernel
contributor's workbench: --mode series over a format-patch export with its
cover letter, --mode reply over a mail draft. The inventory partitions a
cover letter, a plain mail and the camera review's shape; ten kernel lenses
join the table with their gates (structure, code, artifact truth) and the
persona gate - one maintainer-persona instance per --reviewers entry, its
whole pass gating its blocks and pruning the units that are not theirs. A
series' code units are keyed on the code like the push gates'; every text
unit on its text, the references it cites and the anchor when it is dated.
The references an artifact cites are prefetched through utilities/lore.py
into the gate-state, with a digest in the run layer, and a persona's
calibration map is its lore listing. The kernel schema carries the persona
vocabulary, the kernel note kinds, the predicted review and the not-mine
list; the matrix the persona holes, the thin and the modelled units; gates
clean without a persona pass is GATES CLEAN. A reply's gate-state sits
beside the draft and is read once; a kernel mode needs --subsystem,
defaults the effort to max and the trees to work/linux,work/milos-linux,
and excuses no unit from the holes.
The kernel lenses and the common text ship verbatim; the table lists them
after the push gates' lenses and the config template holds them off, so a
repository that never turns them on changes nothing. Prompts, keys and plans
were checked byte for byte against the pipeline these modes replace, on its
own artifacts: two replies and a series, every system prompt, user prompt,
job and reuse key identical.
2026-10-31 22:56:12 +01:00
constexpr std : : array < std : : string_view , 3 > Modes = { " reply " , " series " , " commits " } ;
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
constexpr std : : array < std : : string_view , 2 > Harnesses = { " claude " , " opencode " } ;
feat(modes): the kernel modes - a patch series and a mailing-list reply
Beside the push gates, crafter-review runs the pre-send review of a kernel
contributor's workbench: --mode series over a format-patch export with its
cover letter, --mode reply over a mail draft. The inventory partitions a
cover letter, a plain mail and the camera review's shape; ten kernel lenses
join the table with their gates (structure, code, artifact truth) and the
persona gate - one maintainer-persona instance per --reviewers entry, its
whole pass gating its blocks and pruning the units that are not theirs. A
series' code units are keyed on the code like the push gates'; every text
unit on its text, the references it cites and the anchor when it is dated.
The references an artifact cites are prefetched through utilities/lore.py
into the gate-state, with a digest in the run layer, and a persona's
calibration map is its lore listing. The kernel schema carries the persona
vocabulary, the kernel note kinds, the predicted review and the not-mine
list; the matrix the persona holes, the thin and the modelled units; gates
clean without a persona pass is GATES CLEAN. A reply's gate-state sits
beside the draft and is read once; a kernel mode needs --subsystem,
defaults the effort to max and the trees to work/linux,work/milos-linux,
and excuses no unit from the holes.
The kernel lenses and the common text ship verbatim; the table lists them
after the push gates' lenses and the config template holds them off, so a
repository that never turns them on changes nothing. Prompts, keys and plans
were checked byte for byte against the pipeline these modes replace, on its
own artifacts: two replies and a series, every system prompt, user prompt,
job and reuse key identical.
2026-10-31 22:56:12 +01:00
constexpr std : : array < std : : string_view , 2 > PersonaBlocks = { " pruned " , " all " } ;
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
const std : : vector < OptionSpec > & RunOptions ( ) {
static const std : : string effort = std : : format ( " reasoning effort of every lens process (default {}) " , Plan : : Effort ) ;
static const std : : string reads =
std : : format ( " clean reads a code unit of the static code lenses needs before its verdict is reused, one per round (default {}) " , Plan : : Reads ) ;
static const std : : string models = [ ] {
std : : vector < std : : string > keys ;
for ( const Plan : : ModelTier & m : Plan : : Models ( ) ) keys . push_back ( m . tier ) ;
return std : : format ( " override the lens model table, e.g. fable=claude-fable-5-1 (keys: {}) " , Dyn : : Join ( keys , " , " ) ) ;
} ( ) ;
static const std : : string variant = std : : format ( " opencode harness: the model's reasoning variant (default {}) " , OpenCode : : DefaultVariant ) ;
static const std : : vector < OptionSpec > specs = {
{ " --mode " , true , " MODE " , " " , Kind : : String , Modes , false , true } ,
feat(modes): the kernel modes - a patch series and a mailing-list reply
Beside the push gates, crafter-review runs the pre-send review of a kernel
contributor's workbench: --mode series over a format-patch export with its
cover letter, --mode reply over a mail draft. The inventory partitions a
cover letter, a plain mail and the camera review's shape; ten kernel lenses
join the table with their gates (structure, code, artifact truth) and the
persona gate - one maintainer-persona instance per --reviewers entry, its
whole pass gating its blocks and pruning the units that are not theirs. A
series' code units are keyed on the code like the push gates'; every text
unit on its text, the references it cites and the anchor when it is dated.
The references an artifact cites are prefetched through utilities/lore.py
into the gate-state, with a digest in the run layer, and a persona's
calibration map is its lore listing. The kernel schema carries the persona
vocabulary, the kernel note kinds, the predicted review and the not-mine
list; the matrix the persona holes, the thin and the modelled units; gates
clean without a persona pass is GATES CLEAN. A reply's gate-state sits
beside the draft and is read once; a kernel mode needs --subsystem,
defaults the effort to max and the trees to work/linux,work/milos-linux,
and excuses no unit from the holes.
The kernel lenses and the common text ship verbatim; the table lists them
after the push gates' lenses and the config template holds them off, so a
repository that never turns them on changes nothing. Prompts, keys and plans
were checked byte for byte against the pipeline these modes replace, on its
own artifacts: two replies and a series, every system prompt, user prompt,
job and reuse key identical.
2026-10-31 22:56:12 +01:00
{ " --subsystem " , true , " SUBSYSTEM " , " journal subsystem: required for reply and series; optional in commits mode (names the ledger and captures the lenses may read) " } ,
{ " --artifact " , true , " ARTIFACT " , " the frozen artifact: a dir of *.patch (.review-gates/push/<slug>/, a series), or the reply draft file " } ,
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
{ " --hash " , true , " HASH " , " " } ,
feat(modes): the kernel modes - a patch series and a mailing-list reply
Beside the push gates, crafter-review runs the pre-send review of a kernel
contributor's workbench: --mode series over a format-patch export with its
cover letter, --mode reply over a mail draft. The inventory partitions a
cover letter, a plain mail and the camera review's shape; ten kernel lenses
join the table with their gates (structure, code, artifact truth) and the
persona gate - one maintainer-persona instance per --reviewers entry, its
whole pass gating its blocks and pruning the units that are not theirs. A
series' code units are keyed on the code like the push gates'; every text
unit on its text, the references it cites and the anchor when it is dated.
The references an artifact cites are prefetched through utilities/lore.py
into the gate-state, with a digest in the run layer, and a persona's
calibration map is its lore listing. The kernel schema carries the persona
vocabulary, the kernel note kinds, the predicted review and the not-mine
list; the matrix the persona holes, the thin and the modelled units; gates
clean without a persona pass is GATES CLEAN. A reply's gate-state sits
beside the draft and is read once; a kernel mode needs --subsystem,
defaults the effort to max and the trees to work/linux,work/milos-linux,
and excuses no unit from the holes.
The kernel lenses and the common text ship verbatim; the table lists them
after the push gates' lenses and the config template holds them off, so a
repository that never turns them on changes nothing. Prompts, keys and plans
were checked byte for byte against the pipeline these modes replace, on its
own artifacts: two replies and a series, every system prompt, user prompt,
job and reuse key identical.
2026-10-31 22:56:12 +01:00
{ " --reviewers " , true , " REVIEWERS " , " reply and series: the reviewers a persona reads as, comma-separated (the persona gate runs after the others) " } ,
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
{ " --dispositions " , true , " DISPOSITIONS " , " " } ,
{ " --base " , true , " BASE " , " " } ,
{ " --date " , true , " DATE " , " " } ,
feat(modes): the kernel modes - a patch series and a mailing-list reply
Beside the push gates, crafter-review runs the pre-send review of a kernel
contributor's workbench: --mode series over a format-patch export with its
cover letter, --mode reply over a mail draft. The inventory partitions a
cover letter, a plain mail and the camera review's shape; ten kernel lenses
join the table with their gates (structure, code, artifact truth) and the
persona gate - one maintainer-persona instance per --reviewers entry, its
whole pass gating its blocks and pruning the units that are not theirs. A
series' code units are keyed on the code like the push gates'; every text
unit on its text, the references it cites and the anchor when it is dated.
The references an artifact cites are prefetched through utilities/lore.py
into the gate-state, with a digest in the run layer, and a persona's
calibration map is its lore listing. The kernel schema carries the persona
vocabulary, the kernel note kinds, the predicted review and the not-mine
list; the matrix the persona holes, the thin and the modelled units; gates
clean without a persona pass is GATES CLEAN. A reply's gate-state sits
beside the draft and is read once; a kernel mode needs --subsystem,
defaults the effort to max and the trees to work/linux,work/milos-linux,
and excuses no unit from the holes.
The kernel lenses and the common text ship verbatim; the table lists them
after the push gates' lenses and the config template holds them off, so a
repository that never turns them on changes nothing. Prompts, keys and plans
were checked byte for byte against the pipeline these modes replace, on its
own artifacts: two replies and a series, every system prompt, user prompt,
job and reuse key identical.
2026-10-31 22:56:12 +01:00
{ " --trees " , true , " TREES " , " the checkout the lenses read, at the range HEAD: the repository itself (`.`, default) or the frozen worktree; reply and series default work/linux,work/milos-linux " } ,
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
{ " --context-paths " , true , " CONTEXT_PATHS " , " comma-separated paths of material the artifact discusses (captures, evidence) - mechanical pointers, no narrative " } ,
{ " --only " , true , " ONLY " , " comma-separated lens files (test only) " } ,
{ " --without " , true , " WITHOUT " , " comma-separated lens files to leave out of this run by owner ruling (logged; the run stays a full run) " } ,
{ " --units " , true , " UNITS " , " comma-separated unit ids (test only) " } ,
{ " --warm " , false , " " , " test runs only: let one pass per prompt prefix go first, as a full run does " } ,
{ " --concurrency " , true , " CONCURRENCY " ,
2026-10-31 17:33:11 +01:00
" a cap on the lens processes in flight on this machine, every driver's counted (default none: each OAuth account takes at most 128 - "
" REVIEW_GATES_OAUTH_CONCURRENCY - and an API seat REVIEW_GATES_API_CONCURRENCY; the pool still places each pass by seat headroom, launches it only when it "
" fits in free memory at its environment's measured cost, and gives a pass with the browser a stack of its own) " ,
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
Kind : : Int } ,
{ " --effort " , true , " EFFORT " , effort } ,
{ " --reads " , true , " READS " , reads , Kind : : Int } ,
{ " --no-reuse " , false , " " , " " } ,
{ " --no-salvage " , false , " " , " do not reuse finished process outputs of an earlier run on this hash " } ,
feat(modes): the kernel modes - a patch series and a mailing-list reply
Beside the push gates, crafter-review runs the pre-send review of a kernel
contributor's workbench: --mode series over a format-patch export with its
cover letter, --mode reply over a mail draft. The inventory partitions a
cover letter, a plain mail and the camera review's shape; ten kernel lenses
join the table with their gates (structure, code, artifact truth) and the
persona gate - one maintainer-persona instance per --reviewers entry, its
whole pass gating its blocks and pruning the units that are not theirs. A
series' code units are keyed on the code like the push gates'; every text
unit on its text, the references it cites and the anchor when it is dated.
The references an artifact cites are prefetched through utilities/lore.py
into the gate-state, with a digest in the run layer, and a persona's
calibration map is its lore listing. The kernel schema carries the persona
vocabulary, the kernel note kinds, the predicted review and the not-mine
list; the matrix the persona holes, the thin and the modelled units; gates
clean without a persona pass is GATES CLEAN. A reply's gate-state sits
beside the draft and is read once; a kernel mode needs --subsystem,
defaults the effort to max and the trees to work/linux,work/milos-linux,
and excuses no unit from the holes.
The kernel lenses and the common text ship verbatim; the table lists them
after the push gates' lenses and the config template holds them off, so a
repository that never turns them on changes nothing. Prompts, keys and plans
were checked byte for byte against the pipeline these modes replace, on its
own artifacts: two replies and a series, every system prompt, user prompt,
job and reuse key identical.
2026-10-31 22:56:12 +01:00
{ " --persona-blocks " , true , " {pruned,all} " , " reply and series: pruned (default) skips the block passes of the units a persona's whole pass marked not-mine " , Kind : : String , PersonaBlocks } ,
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
{ " --dry-run " , false , " " , " " } ,
{ " --claude-bin " , true , " CLAUDE_BIN " , " " } ,
{ " --models " , true , " MODELS " , models } ,
{ " --scratch " , true , " SCRATCH " , " " } ,
{ " --seats " , true , " SEATS " , " the seats the lenses run on: auto (every Anthropic seat in the env file, else local), local, api, oauth, openrouter (never in auto), or a list like api1,1,2,local " } ,
{ " --env-file " , true , " ENV_FILE " , " dotenv file with CLAUDE_CODE_OAUTH_TOKEN1..6 / ANTHROPIC_API_KEY1..6 (default $SEATS_ENV_FILE, else the repository .env) " } ,
{ " --no-probe " , false , " " , " skip the one-request probe of every seat at the start " } ,
{ " --api-budget " , true , " API_BUDGET " , " USD an API-credit seat may spend per cycle (default REVIEW_GATES_API_BUDGET_USD or 200) " , Kind : : Float } ,
{ " --harness " , true , " {claude,opencode} " ,
" the agent harness of every lens process: claude (default) or opencode (OpenRouter seats only; no lens whose environment needs an MCP server) " , Kind : : String , Harnesses } ,
{ " --variant " , true , " VARIANT " , variant } ,
{ " --opencode-bin " , true , " OPENCODE_BIN " , " " } ,
} ;
return specs ;
}
std : : int32_t RunCommand ( std : : span < const std : : string_view > args , const std : : optional < std : : string > & configPath ) {
constexpr std : : string_view Prog = " crafter-review run " ;
const std : : vector < OptionSpec > & specs = RunOptions ( ) ;
const Parsed parsed = ParseOptions ( args , specs ) ;
if ( parsed . help ) {
std : : print ( " {} " , Help ( Prog , " one claude process per inventory unit per lens, the gates, the dump (process/prepush.md) " , specs ) ) ;
return 0 ;
}
if ( ! parsed . error . empty ( ) ) return UsageError ( UsageLine ( Prog , specs ) , Prog , parsed . error ) ;
const std : : optional < Json > config = LoadConfig ( configPath , false ) ;
if ( ! config ) return 3 ;
Run : : Arguments a ;
a . config = configPath ;
a . mode = parsed . Value ( " mode " ) . value_or ( " commits " ) ;
feat(modes): the kernel modes - a patch series and a mailing-list reply
Beside the push gates, crafter-review runs the pre-send review of a kernel
contributor's workbench: --mode series over a format-patch export with its
cover letter, --mode reply over a mail draft. The inventory partitions a
cover letter, a plain mail and the camera review's shape; ten kernel lenses
join the table with their gates (structure, code, artifact truth) and the
persona gate - one maintainer-persona instance per --reviewers entry, its
whole pass gating its blocks and pruning the units that are not theirs. A
series' code units are keyed on the code like the push gates'; every text
unit on its text, the references it cites and the anchor when it is dated.
The references an artifact cites are prefetched through utilities/lore.py
into the gate-state, with a digest in the run layer, and a persona's
calibration map is its lore listing. The kernel schema carries the persona
vocabulary, the kernel note kinds, the predicted review and the not-mine
list; the matrix the persona holes, the thin and the modelled units; gates
clean without a persona pass is GATES CLEAN. A reply's gate-state sits
beside the draft and is read once; a kernel mode needs --subsystem,
defaults the effort to max and the trees to work/linux,work/milos-linux,
and excuses no unit from the holes.
The kernel lenses and the common text ship verbatim; the table lists them
after the push gates' lenses and the config template holds them off, so a
repository that never turns them on changes nothing. Prompts, keys and plans
were checked byte for byte against the pipeline these modes replace, on its
own artifacts: two replies and a series, every system prompt, user prompt,
job and reuse key identical.
2026-10-31 22:56:12 +01:00
a . subsystem = parsed . Value ( " subsystem " ) ;
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
a . artifact = parsed . Value ( " artifact " ) . value_or ( " " ) ;
feat(modes): the kernel modes - a patch series and a mailing-list reply
Beside the push gates, crafter-review runs the pre-send review of a kernel
contributor's workbench: --mode series over a format-patch export with its
cover letter, --mode reply over a mail draft. The inventory partitions a
cover letter, a plain mail and the camera review's shape; ten kernel lenses
join the table with their gates (structure, code, artifact truth) and the
persona gate - one maintainer-persona instance per --reviewers entry, its
whole pass gating its blocks and pruning the units that are not theirs. A
series' code units are keyed on the code like the push gates'; every text
unit on its text, the references it cites and the anchor when it is dated.
The references an artifact cites are prefetched through utilities/lore.py
into the gate-state, with a digest in the run layer, and a persona's
calibration map is its lore listing. The kernel schema carries the persona
vocabulary, the kernel note kinds, the predicted review and the not-mine
list; the matrix the persona holes, the thin and the modelled units; gates
clean without a persona pass is GATES CLEAN. A reply's gate-state sits
beside the draft and is read once; a kernel mode needs --subsystem,
defaults the effort to max and the trees to work/linux,work/milos-linux,
and excuses no unit from the holes.
The kernel lenses and the common text ship verbatim; the table lists them
after the push gates' lenses and the config template holds them off, so a
repository that never turns them on changes nothing. Prompts, keys and plans
were checked byte for byte against the pipeline these modes replace, on its
own artifacts: two replies and a series, every system prompt, user prompt,
job and reuse key identical.
2026-10-31 22:56:12 +01:00
if ( a . artifact . empty ( ) ) return UsageError ( UsageLine ( Prog , specs ) , Prog , " the following arguments are required: --artifact " ) ;
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
a . hash = parsed . Value ( " hash " ) ;
feat(modes): the kernel modes - a patch series and a mailing-list reply
Beside the push gates, crafter-review runs the pre-send review of a kernel
contributor's workbench: --mode series over a format-patch export with its
cover letter, --mode reply over a mail draft. The inventory partitions a
cover letter, a plain mail and the camera review's shape; ten kernel lenses
join the table with their gates (structure, code, artifact truth) and the
persona gate - one maintainer-persona instance per --reviewers entry, its
whole pass gating its blocks and pruning the units that are not theirs. A
series' code units are keyed on the code like the push gates'; every text
unit on its text, the references it cites and the anchor when it is dated.
The references an artifact cites are prefetched through utilities/lore.py
into the gate-state, with a digest in the run layer, and a persona's
calibration map is its lore listing. The kernel schema carries the persona
vocabulary, the kernel note kinds, the predicted review and the not-mine
list; the matrix the persona holes, the thin and the modelled units; gates
clean without a persona pass is GATES CLEAN. A reply's gate-state sits
beside the draft and is read once; a kernel mode needs --subsystem,
defaults the effort to max and the trees to work/linux,work/milos-linux,
and excuses no unit from the holes.
The kernel lenses and the common text ship verbatim; the table lists them
after the push gates' lenses and the config template holds them off, so a
repository that never turns them on changes nothing. Prompts, keys and plans
were checked byte for byte against the pipeline these modes replace, on its
own artifacts: two replies and a series, every system prompt, user prompt,
job and reuse key identical.
2026-10-31 22:56:12 +01:00
a . reviewers = parsed . Value ( " reviewers " ) . value_or ( " " ) ;
a . personaBlocks = parsed . Value ( " persona-blocks " ) . value_or ( " pruned " ) ;
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
a . dispositions = parsed . Value ( " dispositions " ) ;
a . base = parsed . Value ( " base " ) . value_or ( " " ) ;
a . date = parsed . Value ( " date " ) . value_or ( Platform : : TodayIso ( ) ) ;
a . trees = parsed . Value ( " trees " ) ;
a . contextPaths = parsed . Value ( " context-paths " ) . value_or ( " " ) ;
a . only = parsed . Value ( " only " ) ;
a . without = parsed . Value ( " without " ) ;
if ( const std : : optional < std : : string > units = parsed . Value ( " units " ) ; units & & ! units - > empty ( ) ) {
// A trailing comma names no unit.
a . units = std : : set < std : : string > ( ) ;
for ( const std : : string & part : Dyn : : Split ( * units , " , " ) ) {
if ( std : : string id = Dyn : : Strip ( part ) ; ! id . empty ( ) ) a . units - > insert ( std : : move ( id ) ) ;
}
}
2026-10-31 16:45:53 +01:00
// Empty items are skipped, so a typo such as `--units ,` must not
// leave no filter at all: that is a full run.
auto namesNothing = [ ] ( const std : : optional < std : : string > & list ) {
return list & & ! list - > empty ( ) & & std : : ranges : : all_of ( Dyn : : Split ( * list , " , " ) , [ ] ( const std : : string & part ) { return Dyn : : Strip ( part ) . empty ( ) ; } ) ;
} ;
if ( namesNothing ( a . only ) ) return UsageError ( UsageLine ( Prog , specs ) , Prog , std : : format ( " argument --only: names no lens file ({}) " , Dyn : : Repr ( * a . only ) ) ) ;
if ( namesNothing ( parsed . Value ( " units " ) ) ) return UsageError ( UsageLine ( Prog , specs ) , Prog , std : : format ( " argument --units: names no unit id ({}) " , Dyn : : Repr ( * parsed . Value ( " units " ) ) ) ) ;
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
a . warm = parsed . Flag ( " warm " ) ;
2026-10-31 17:33:11 +01:00
if ( const std : : optional < std : : string > n = parsed . Value ( " concurrency " ) ) {
a . concurrency = * Dyn : : ParseInt ( * n ) ;
if ( * a . concurrency < 1 ) return UsageError ( UsageLine ( Prog , specs ) , Prog , " argument --concurrency: a number from 1 up (leave it out for no cap) " ) ;
}
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
a . effort = parsed . Value ( " effort " ) . value_or ( " " ) ;
feat(modes): the kernel modes - a patch series and a mailing-list reply
Beside the push gates, crafter-review runs the pre-send review of a kernel
contributor's workbench: --mode series over a format-patch export with its
cover letter, --mode reply over a mail draft. The inventory partitions a
cover letter, a plain mail and the camera review's shape; ten kernel lenses
join the table with their gates (structure, code, artifact truth) and the
persona gate - one maintainer-persona instance per --reviewers entry, its
whole pass gating its blocks and pruning the units that are not theirs. A
series' code units are keyed on the code like the push gates'; every text
unit on its text, the references it cites and the anchor when it is dated.
The references an artifact cites are prefetched through utilities/lore.py
into the gate-state, with a digest in the run layer, and a persona's
calibration map is its lore listing. The kernel schema carries the persona
vocabulary, the kernel note kinds, the predicted review and the not-mine
list; the matrix the persona holes, the thin and the modelled units; gates
clean without a persona pass is GATES CLEAN. A reply's gate-state sits
beside the draft and is read once; a kernel mode needs --subsystem,
defaults the effort to max and the trees to work/linux,work/milos-linux,
and excuses no unit from the holes.
The kernel lenses and the common text ship verbatim; the table lists them
after the push gates' lenses and the config template holds them off, so a
repository that never turns them on changes nothing. Prompts, keys and plans
were checked byte for byte against the pipeline these modes replace, on its
own artifacts: two replies and a series, every system prompt, user prompt,
job and reuse key identical.
2026-10-31 22:56:12 +01:00
if ( a . effort . empty ( ) ) a . effort = std : : string ( Plan : : DefaultEffort ( a . mode ) ) ;
if ( a . mode = = " commits " ) {
if ( ! a . reviewers . empty ( ) ) return Die ( " commits mode has no persona gate (the owner is the last gate): drop --reviewers " ) ;
} else {
if ( ! a . subsystem | | a . subsystem - > empty ( ) ) return Die ( std : : format ( " {} mode: --subsystem is required " , a . mode ) ) ;
if ( ! a . trees | | a . trees - > empty ( ) ) a . trees = " work/linux,work/milos-linux " ;
}
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
a . noReuse = parsed . Flag ( " no-reuse " ) ;
a . noSalvage = parsed . Flag ( " no-salvage " ) ;
a . dryRun = parsed . Flag ( " dry-run " ) ;
a . claudeBin = parsed . Value ( " claude-bin " ) ;
a . models = parsed . Value ( " models " ) . value_or ( " " ) ;
a . scratch = parsed . Value ( " scratch " ) ;
a . seats = parsed . Value ( " seats " ) ;
if ( ! a . seats ) a . seats = Platform : : GetEnv ( " REVIEW_GATES_SEATS " ) ;
a . envFile = parsed . Value ( " env-file " ) ;
a . noProbe = parsed . Flag ( " no-probe " ) ;
if ( const std : : optional < std : : string > budget = parsed . Value ( " api-budget " ) ) a . apiBudget = * Dyn : : ParseFloat ( * budget ) ;
a . harness = parsed . Value ( " harness " ) . value_or ( " claude " ) ;
a . variant = parsed . Value ( " variant " ) ;
if ( ! a . variant | | a . variant - > empty ( ) ) a . variant = a . harness = = " opencode " ? std : : optional < std : : string > ( std : : string ( OpenCode : : DefaultVariant ) ) : std : : nullopt ;
a . opencodeBin = parsed . Value ( " opencode-bin " ) ;
feat(modes): the kernel modes - a patch series and a mailing-list reply
Beside the push gates, crafter-review runs the pre-send review of a kernel
contributor's workbench: --mode series over a format-patch export with its
cover letter, --mode reply over a mail draft. The inventory partitions a
cover letter, a plain mail and the camera review's shape; ten kernel lenses
join the table with their gates (structure, code, artifact truth) and the
persona gate - one maintainer-persona instance per --reviewers entry, its
whole pass gating its blocks and pruning the units that are not theirs. A
series' code units are keyed on the code like the push gates'; every text
unit on its text, the references it cites and the anchor when it is dated.
The references an artifact cites are prefetched through utilities/lore.py
into the gate-state, with a digest in the run layer, and a persona's
calibration map is its lore listing. The kernel schema carries the persona
vocabulary, the kernel note kinds, the predicted review and the not-mine
list; the matrix the persona holes, the thin and the modelled units; gates
clean without a persona pass is GATES CLEAN. A reply's gate-state sits
beside the draft and is read once; a kernel mode needs --subsystem,
defaults the effort to max and the trees to work/linux,work/milos-linux,
and excuses no unit from the holes.
The kernel lenses and the common text ship verbatim; the table lists them
after the push gates' lenses and the config template holds them off, so a
repository that never turns them on changes nothing. Prompts, keys and plans
were checked byte for byte against the pipeline these modes replace, on its
own artifacts: two replies and a series, every system prompt, user prompt,
job and reuse key identical.
2026-10-31 22:56:12 +01:00
a . reads = Plan : : ReadsFor ( a . mode ) ;
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
if ( const std : : optional < std : : string > reads = parsed . Value ( " reads " ) ) {
const std : : int64_t n = * Dyn : : ParseInt ( * reads ) ;
feat(modes): the kernel modes - a patch series and a mailing-list reply
Beside the push gates, crafter-review runs the pre-send review of a kernel
contributor's workbench: --mode series over a format-patch export with its
cover letter, --mode reply over a mail draft. The inventory partitions a
cover letter, a plain mail and the camera review's shape; ten kernel lenses
join the table with their gates (structure, code, artifact truth) and the
persona gate - one maintainer-persona instance per --reviewers entry, its
whole pass gating its blocks and pruning the units that are not theirs. A
series' code units are keyed on the code like the push gates'; every text
unit on its text, the references it cites and the anchor when it is dated.
The references an artifact cites are prefetched through utilities/lore.py
into the gate-state, with a digest in the run layer, and a persona's
calibration map is its lore listing. The kernel schema carries the persona
vocabulary, the kernel note kinds, the predicted review and the not-mine
list; the matrix the persona holes, the thin and the modelled units; gates
clean without a persona pass is GATES CLEAN. A reply's gate-state sits
beside the draft and is read once; a kernel mode needs --subsystem,
defaults the effort to max and the trees to work/linux,work/milos-linux,
and excuses no unit from the holes.
The kernel lenses and the common text ship verbatim; the table lists them
after the push gates' lenses and the config template holds them off, so a
repository that never turns them on changes nothing. Prompts, keys and plans
were checked byte for byte against the pipeline these modes replace, on its
own artifacts: two replies and a series, every system prompt, user prompt,
job and reuse key identical.
2026-10-31 22:56:12 +01:00
if ( n < 1 | | ( a . mode = = " reply " & & n ! = 1 ) ) return Die ( " --reads: a number from 1 up; a reply has no code units and is read once " ) ;
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
a . reads = n ;
}
for ( const std : : string & part : Dyn : : Split ( a . models , " , " ) ) {
const std : : string kv = Dyn : : Strip ( part ) ;
if ( kv . empty ( ) ) continue ;
const std : : size_t equals = kv . find ( ' = ' ) ;
if ( equals = = std : : string : : npos ) throw Dyn : : Error ( " ValueError " , " not enough values to unpack (expected 2, got 1) " ) ;
const std : : string key = kv . substr ( 0 , equals ) ;
std : : vector < Plan : : ModelTier > & tiers = Plan : : Models ( ) ;
const auto tier = std : : ranges : : find_if ( tiers , [ & ] ( const Plan : : ModelTier & m ) { return m . tier = = key ; } ) ;
if ( tier = = tiers . end ( ) ) {
std : : vector < std : : string > known ;
for ( const Plan : : ModelTier & m : tiers ) known . push_back ( m . tier ) ;
std : : println ( std : : cerr , " --models: unknown key {} (known: {}) " , Dyn : : Repr ( key ) , Dyn : : Join ( known , " , " ) ) ;
return 1 ;
}
tier - > id = kv . substr ( equals + 1 ) ;
}
return Run : : Main ( std : : move ( a ) , * config ) ;
}
constexpr std : : array < OptionSpec , 1 > SlotsOptions = { {
{ " --artifact " , true , " ARTIFACT " , " a frozen artifact dir: no more slots than its browser passes can use at once " } ,
} } ;
std : : int32_t SlotsCommand ( std : : span < const std : : string_view > args , const std : : optional < std : : string > & configPath ) {
constexpr std : : string_view Prog = " crafter-review slots " ;
const Parsed parsed = ParseOptions ( args , SlotsOptions ) ;
if ( parsed . help ) {
std : : print ( " {} " , Help ( Prog , " how many stacks (slots.*) fit in free memory, and the commands that bring up the missing ones " , SlotsOptions ) ) ;
return 0 ;
}
if ( ! parsed . error . empty ( ) ) return UsageError ( UsageLine ( Prog , SlotsOptions ) , Prog , parsed . error ) ;
const std : : optional < Json > config = LoadConfig ( configPath ) ;
if ( ! config ) return 3 ;
const std : : optional < std : : string > artifact = parsed . Value ( " artifact " ) ;
try {
Run : : SlotsPlan ( * config , fs : : current_path ( ) , artifact ? std : : optional < fs : : path > ( * artifact ) : std : : nullopt ) ;
} catch ( const Inventory : : InvariantError & e ) {
return Die ( e . what ( ) ) ;
}
return 0 ;
}
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
std : : int32_t FreezeCommand ( std : : span < const std : : string_view > args , const std : : optional < std : : string > & configPath ) {
constexpr std : : string_view Prog = " crafter-review freeze " ;
const Parsed parsed = ParseOptions ( args , FreezeOptions ) ;
if ( parsed . help ) {
std : : print ( " {} " , Help ( Prog , " cut the frozen commit-range artifact for the push gates (process/prepush.md Gate 0) " , FreezeOptions ) ) ;
return 0 ;
}
if ( ! parsed . error . empty ( ) ) return UsageError ( UsageLine ( Prog , FreezeOptions ) , Prog , parsed . error ) ;
const std : : optional < Json > config = LoadConfig ( configPath ) ;
if ( ! config ) return 3 ;
Freeze : : Arguments arguments ;
arguments . repo = parsed . Value ( " repo " ) ;
arguments . slug = parsed . Value ( " slug " ) ;
arguments . out = parsed . Value ( " out " ) ;
arguments . base = parsed . Value ( " base " ) ;
arguments . replace = parsed . Flag ( " replace " ) ;
arguments . worktree = parsed . Flag ( " worktree " ) ;
arguments . requireSigned = parsed . Flag ( " require-signed " ) ;
arguments . date = parsed . Value ( " date " ) . value_or ( Platform : : TodayIso ( ) ) ;
arguments . verify = parsed . Flag ( " verify " ) ;
// `%G?` starts gpg, which can leave a daemon behind: as a subreaper
// this process inherits it, and takes it down on the way out.
Process : : BecomeSubreaper ( ) ;
2026-10-31 16:45:53 +01:00
struct Sweep {
~ Sweep ( ) {
Process : : KillDescendants ( ) ;
}
} const sweep ;
return Freeze : : Main ( arguments , * config ) ;
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
}
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
// ---- seats: the report, the ledger and the board beside it ------------------
constexpr std : : array < OptionSpec , 5 > SeatsOptions = { {
{ " --env-file " , true , " ENV_FILE " , " dotenv file holding the seat tokens (default: $SEATS_ENV_FILE, else ./.env when it exists) " } ,
{ " --json " , false , " " , " the probe results as JSON instead of the report " } ,
{ " --no-fable " , false , " " , " probe on Haiku instead of Fable (cheaper; the Fable weekly window stays unread) " } ,
{ " --local " , false , " " , " also probe the CLI's own login, shown as the seat `local` " } ,
{ " --timeout " , true , " TIMEOUT " , " seconds per probe (default 30) " , Kind : : Float } ,
} } ;
// datetime.fromtimestamp(t, timezone.utc).isoformat(timespec='milliseconds')
// with Z for +00:00.
std : : string IsoUtcMillis ( double epoch ) {
double whole = std : : floor ( epoch ) ;
auto micros = static_cast < std : : int64_t > ( std : : nearbyint ( ( epoch - whole ) * 1e6 ) ) ;
if ( micros > = 1'000'000 ) {
whole + = 1 ;
micros - = 1'000'000 ;
}
const std : : chrono : : sys_seconds at { std : : chrono : : seconds ( static_cast < std : : int64_t > ( whole ) ) } ;
return std : : format ( " {:%Y-%m-%dT%H:%M:%S}.{:03d}Z " , at , micros / 1000 ) ;
}
struct SeatJob {
std : : int32_t seat = 0 ;
std : : string token ;
bool local = false ;
} ;
// Every seat probed, eight at once at most; a probe that throws ends
// the command.
std : : vector < Json > ProbeAll ( const std : : vector < SeatJob > & todo , const Seats : : ProbeOptions & base ) {
std : : vector < Json > out ( todo . size ( ) ) ;
std : : atomic < std : : size_t > next = 0 ;
std : : mutex lock ;
std : : exception_ptr failed ;
std : : vector < std : : thread > workers ;
for ( std : : size_t w = 0 ; w < std : : min < std : : size_t > ( 8 , todo . size ( ) ) ; + + w ) {
workers . emplace_back ( [ & ] {
for ( std : : size_t i = next + + ; i < todo . size ( ) ; i = next + + ) {
try {
Seats : : ProbeOptions options = base ;
options . local = todo [ i ] . local ;
out [ i ] = Seats : : ProbeSeat ( todo [ i ] . seat , todo [ i ] . token , options ) ;
} catch ( const std : : exception & ) {
const std : : lock_guard guard ( lock ) ;
if ( ! failed ) failed = std : : current_exception ( ) ;
}
}
} ) ;
}
for ( std : : thread & worker : workers ) worker . join ( ) ;
if ( failed ) std : : rethrow_exception ( failed ) ;
return out ;
}
std : : int32_t SeatsCommand ( std : : span < const std : : string_view > args ) {
constexpr std : : string_view Prog = " crafter-review seats " ;
const Parsed parsed = ParseOptions ( args , SeatsOptions ) ;
if ( parsed . help ) {
std : : print ( " {} \n Seats: CLAUDE_CODE_OAUTH_TOKEN1..6 from the environment and/or the dotenv file. Never prints a token. \n " ,
Help ( Prog , " How much usage each Claude seat has left: every seat probed, both windows and their resets. " , SeatsOptions ) ) ;
return 0 ;
}
if ( ! parsed . error . empty ( ) ) return UsageError ( UsageLine ( Prog , SeatsOptions ) , Prog , parsed . error ) ;
const std : : optional < std : : string > timeoutText = parsed . Value ( " timeout " ) ;
const double timeout = timeoutText ? Dyn : : ParseFloat ( * timeoutText ) . value_or ( Seats : : ProbeTimeoutS ) : Seats : : ProbeTimeoutS ;
const std : : map < std : : string , std : : string > process = Platform : : Environment ( ) ;
const Json cfg = Seats : : ConfigFromEnv ( process ) ;
std : : string envFile = parsed . Value ( " env-file " ) . value_or ( " " ) ;
if ( envFile . empty ( ) ) envFile = Platform : : GetEnv ( " SEATS_ENV_FILE " ) . value_or ( " " ) ;
std : : error_code error ;
if ( envFile . empty ( ) & & fs : : exists ( " .env " , error ) ) envFile = " .env " ;
std : : map < std : : string , std : : string > env = process ;
if ( ! envFile . empty ( ) ) {
2026-10-31 16:35:26 +01:00
// The env file, read as text: one that cannot be read, or is not
// UTF-8, ends the command.
for ( const auto & [ name , value ] : Seats : : SeatTokensFromEnvFile ( Dyn : : ReadText ( envFile ) ) ) env [ name ] = value ;
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
}
std : : vector < SeatJob > jobs ;
for ( const auto & [ seat , token ] : Seats : : SeatTokensFromEnv ( env ) ) jobs . push_back ( { seat , token , false } ) ;
if ( parsed . Flag ( " local " ) ) jobs . insert ( jobs . begin ( ) , SeatJob { 0 , " " , true } ) ;
const std : : vector < std : : pair < std : : int32_t , std : : string > > apiJobs = Seats : : ApiKeysFromEnv ( env ) ;
if ( jobs . empty ( ) & & apiJobs . empty ( ) ) {
std : : println ( " no seat configured (CLAUDE_CODE_OAUTH_TOKEN1..6 / ANTHROPIC_API_KEY1..6{}; --local probes your own login) " , envFile . empty ( ) ? std : : string ( ) : std : : format ( " in {} " , envFile ) ) ;
return 1 ;
}
const std : : string claudeBin = Seats : : FindClaudeBin ( ) ;
const double now = Platform : : Now ( ) ;
Seats : : ProbeOptions haiku ;
haiku . timeoutS = timeout ;
haiku . claudeBin = claudeBin ;
std : : vector < Json > seats ;
if ( ! jobs . empty ( ) & & parsed . Flag ( " no-fable " ) ) {
seats = ProbeAll ( jobs , haiku ) ;
} else if ( ! jobs . empty ( ) ) {
// One Fable probe per seat carries all three windows and is the
// seat; only a seat whose Fable probe could not be read, or was
// refused for the Fable window alone, is probed again on Haiku
// for its own state.
Seats : : ProbeOptions fable = haiku ;
fable . model = std : : string ( Seats : : FableProbeModel ) ;
const std : : vector < Json > first = ProbeAll ( jobs , fable ) ;
std : : vector < SeatJob > again ;
for ( std : : size_t i = 0 ; i < jobs . size ( ) ; + + i ) {
if ( Seats : : NeedsHaiku ( first [ i ] ) ) again . push_back ( jobs [ i ] ) ;
}
std : : map < std : : int64_t , Json > standIns ;
const std : : vector < Json > got = again . empty ( ) ? std : : vector < Json > ( ) : ProbeAll ( again , haiku ) ;
for ( std : : size_t i = 0 ; i < again . size ( ) ; + + i ) standIns [ again [ i ] . seat ] = got [ i ] ;
for ( const Json & s : first ) {
const auto found = standIns . find ( s . at ( " seat " ) . get < std : : int64_t > ( ) ) ;
seats . push_back ( Seats : : MergeFableProbe ( found ! = standIns . end ( ) ? found - > second : s , s ) ) ;
}
}
const Json apiConfig = SeatPool : : ApiConfig ( process ) ;
std : : vector < Json > apiSeats ;
if ( ! apiJobs . empty ( ) ) {
std : : vector < SeatJob > todo ;
for ( const auto & [ n , key ] : apiJobs ) todo . push_back ( { n , key , false } ) ;
Seats : : ProbeOptions api = haiku ;
api . api = true ;
apiSeats = ProbeAll ( todo , api ) ;
// The driver's ledger: what it spent on each key this cycle.
SeatPool : : Ledger ledger = SeatPool : : Ledger : : Load ( SeatPool : : LedgerPath ( ) , apiConfig ) ;
for ( Json & s : apiSeats ) s [ " ledger " ] = ledger . Entry ( std : : format ( " api{} " , s . at ( " seat " ) . get < std : : int64_t > ( ) ) ) ;
}
// The board: the drivers running now and what they hold on each
// account - read, never written.
const Json board = SeatPool : : Board ( SeatPool : : BoardPath ( ) ) . View ( now ) ;
std : : map < std : : int32_t , std : : string > keys ;
for ( const SeatJob & j : jobs ) keys [ j . seat ] = SeatPool : : SeatKey ( j . local ? " local " : " oauth " , j . token ) ;
std : : map < std : : int32_t , std : : string > apiKeys ;
for ( const auto & [ n , key ] : apiJobs ) apiKeys [ n ] = SeatPool : : SeatKey ( " api " , key ) ;
const Seats : : Zone zone = Seats : : LocalZone ( ) ;
if ( parsed . Flag ( " json " ) ) {
Json eligible = Json : : array ( ) ;
for ( const Json & s : seats ) {
if ( Seats : : IsEligible ( s , cfg ) ) eligible . push_back ( s . at ( " seat " ) ) ;
}
const Json out = { { " probedAt " , IsoUtcMillis ( now ) } , { " config " , cfg } , { " seats " , seats } , { " apiSeats " , apiSeats } , { " eligible " , eligible } ,
{ " next " , seats . empty ( ) ? Json ( ) : Seats : : PickNext ( seats , cfg ) } , { " board " , board } } ;
std : : println ( " {} " , out . dump ( 2 , ' ' , false , Json : : error_handler_t : : replace ) ) ;
return 0 ;
}
const std : : string source = std : : format ( " {}{} " , envFile . empty ( ) ? std : : string ( " the environment " ) : envFile , parsed . Flag ( " local " ) ? " and your own login " : " " ) ;
std : : vector < std : : string > ids ;
for ( const Json & s : seats ) ids . push_back ( s . at ( " seat " ) = = 0 ? std : : string ( " local " ) : std : : to_string ( s . at ( " seat " ) . get < std : : int64_t > ( ) ) ) ;
for ( const Json & s : apiSeats ) ids . push_back ( std : : format ( " api{} " , s . at ( " seat " ) . get < std : : int64_t > ( ) ) ) ;
std : : println ( " seat pool: {} seat(s) ({}) from {}, probed {} {}, claude: {} \n " , ids . size ( ) , Dyn : : Join ( ids , " , " ) , source , Seats : : Clock ( now , zone ) , zone . label , claudeBin ) ;
if ( ! seats . empty ( ) ) std : : println ( " {} " , Seats : : RenderStatusReport ( seats , cfg , now , zone , board , keys ) ) ;
if ( ! apiSeats . empty ( ) ) std : : println ( " {}{} " , seats . empty ( ) ? " " : " \n " , Seats : : RenderApiReport ( apiSeats , apiConfig , board , apiKeys ) ) ;
std : : println ( " \n {} " , Seats : : RenderDrivers ( board , now , zone ) ) ;
// The pids above are no use from inside the sandbox: the way to
// stop a run instead.
if ( Platform : : InSandbox ( ) & & ! Dyn : : Get ( board , " drivers " ) . empty ( ) ) {
std : : println ( " stop a run with `{} stop --hash <hash8>` or `{} stop <its scratch dir>` " , Invocation ( ) , Invocation ( ) ) ;
}
return 0 ;
}
// ---- stop: the file a running driver looks for every 2 s ------------------------
constexpr std : : array < OptionSpec , 1 > StopOptions = { {
{ " --hash " , true , " HASH8 " , " every live run of this artifact hash on the board (its first 8 characters) " } ,
} } ;
constexpr std : : array < OptionSpec , 1 > StopPositionals = { {
{ " scratch " , false , " " , " the run's scratch dir (the last word of its gate-0 log) " } ,
} } ;
std : : int32_t StopCommand ( std : : span < const std : : string_view > args ) {
constexpr std : : string_view Prog = " crafter-review stop " ;
const Parsed parsed = ParseOptions ( args , StopOptions , StopPositionals ) ;
if ( parsed . help ) {
std : : print ( " {} " , Help ( Prog , " stop a running review as SIGTERM would - its running passes killed, no later gate started, the dump written - from any sandbox " , StopOptions ,
StopPositionals ) ) ;
return 0 ;
}
if ( ! parsed . error . empty ( ) ) return UsageError ( UsageLine ( Prog , StopOptions , StopPositionals ) , Prog , parsed . error ) ;
const std : : optional < std : : string > hash = parsed . Value ( " hash " ) ;
if ( hash . has_value ( ) = = ! parsed . positionals . empty ( ) ) return UsageError ( UsageLine ( Prog , StopOptions , StopPositionals ) , Prog , " a scratch dir or --hash, one of the two " ) ;
const fs : : path boardPath = SeatPool : : BoardPath ( ) ;
std : : vector < std : : pair < std : : string , fs : : path > > targets ;
if ( ! hash ) {
const fs : : path dir = fs : : absolute ( parsed . positionals . front ( ) ) ;
const std : : string key = Dyn : : Strip ( Platform : : ReadFile ( dir / " .driver " ) . value_or ( " " ) ) ;
if ( key . empty ( ) ) {
std : : println ( std : : cerr , " stop: {} is no run's scratch dir (it has no .driver marker) " , dir . string ( ) ) ;
return 1 ;
}
if ( ! SeatPool : : DriverAlive ( key , boardPath . parent_path ( ) , false ) ) {
std : : println ( std : : cerr , " stop: the driver of {} ({}) is no longer running " , dir . string ( ) , key ) ;
return 1 ;
}
targets . emplace_back ( key , dir ) ;
} else {
const std : : string h8 = hash - > substr ( 0 , 8 ) ;
const Json board = SeatPool : : Board ( boardPath ) . View ( Platform : : Now ( ) ) ;
const Json & drivers = Dyn : : Get ( board , " drivers " ) ;
for ( auto it = drivers . begin ( ) ; it ! = drivers . end ( ) ; + + it ) {
const Json & d = it . value ( ) ;
if ( Dyn : : StrOr ( Dyn : : Get ( d , " run " ) , " " ) . ends_with ( std : : format ( " @ {} " , h8 ) ) & & Dyn : : Truthy ( Dyn : : Get ( d , " scratch " ) ) ) targets . emplace_back ( it . key ( ) , Dyn : : Str ( d . at ( " scratch " ) ) ) ;
}
if ( targets . empty ( ) ) {
std : : println ( std : : cerr , " stop: no live run of {} on the board ({}) " , h8 , boardPath . string ( ) ) ;
return 1 ;
}
}
for ( const auto & [ key , dir ] : targets ) {
std : : ofstream file ( dir / " stop " , std : : ios : : binary ) ;
file < < std : : format ( " asked by `{} stop` at {} \n " , Invocation ( ) , Platform : : LocalTime ( " %Y-%m-%dT%H:%M:%S " ) ) ;
if ( ! file ) {
std : : println ( std : : cerr , " stop: cannot write {} " , ( dir / " stop " ) . string ( ) ) ;
return 1 ;
}
std : : println ( " stop: driver {} ({}) - asked to stop; a run stops within 2 s, else SIGTERM its pid " , key , dir . string ( ) ) ;
}
return 0 ;
}
// ---- doc: the texts the Flatpak ships -------------------------------------------------
constexpr std : : array < std : : string_view , 3 > DocNames = { " prepush " , " manifest " , " config " } ;
constexpr std : : array < OptionSpec , 1 > DocOptions = { {
{ " --path " , false , " " , " where the file is, as the host sees it, instead of the file (the procedure is 35 KB: the skill reads it from there) " } ,
} } ;
constexpr std : : array < OptionSpec , 1 > DocPositionals = { {
{ " name " , false , " " , " prepush: the procedure; manifest: the manifest template; config: the config template " , Kind : : String , DocNames , true } ,
} } ;
// Each text under process/.
std : : string_view DocFile ( std : : string_view name ) {
if ( name = = " manifest " ) return " templates/manifest.md " ;
if ( name = = " config " ) return " templates/config.json " ;
return " prepush.md " ;
}
// A file under the home as the host sees it. In the Flatpak that is
// under /.flatpak-info's app-path, with the deployment's `active` link
// in place of its commit, so the path outlives an update.
fs : : path HostPath ( const fs : : path & path ) {
if ( ! Platform : : InSandbox ( ) ) return path ;
const std : : string info = Platform : : ReadFile ( Platform : : Paths ( ) . flatpakInfo ) . value_or ( " " ) ;
for ( const std : : string & line : Dyn : : Split ( info , " \n " ) ) {
if ( ! line . starts_with ( " app-path= " ) ) continue ;
const fs : : path files ( line . substr ( 9 ) ) ;
std : : error_code error ;
const fs : : path inside = fs : : relative ( path , " /app " , error ) ;
if ( error | | inside . empty ( ) | | inside . string ( ) . starts_with ( " .. " ) ) return path ;
return files . parent_path ( ) . parent_path ( ) / " active " / files . filename ( ) / inside ;
}
return path ;
}
std : : int32_t DocCommand ( std : : span < const std : : string_view > args ) {
constexpr std : : string_view Prog = " crafter-review doc " ;
const Parsed parsed = ParseOptions ( args , DocOptions , DocPositionals ) ;
if ( parsed . help ) {
std : : print ( " {} " , Help ( Prog , " print a text the pipeline ships, or with --path where it is " , DocOptions , DocPositionals ) ) ;
return 0 ;
}
if ( ! parsed . error . empty ( ) ) return UsageError ( UsageLine ( Prog , DocOptions , DocPositionals ) , Prog , parsed . error ) ;
const fs : : path file = Home ( ) / " process " / DocFile ( parsed . positionals . front ( ) ) ;
if ( parsed . Flag ( " path " ) ) {
std : : println ( " {} " , HostPath ( file ) . string ( ) ) ;
return 0 ;
}
const std : : optional < std : : string > text = Platform : : ReadFile ( file ) ;
if ( ! text ) {
std : : println ( std : : cerr , " doc: cannot read {} " , file . string ( ) ) ;
return 1 ;
}
std : : print ( " {} " , * text ) ;
return 0 ;
}
// ---- browser install: the Chromium the MCP server drives ----------------------------
constexpr std : : array < std : : string_view , 1 > BrowserActions = { " install " } ;
constexpr std : : array < OptionSpec , 1 > BrowserPositionals = { {
{ " action " , false , " " , " install: the Chromium build the pinned Playwright MCP server drives, where it looks for it " , Kind : : String , BrowserActions , true } ,
} } ;
// Where Playwright keeps its browsers on Linux: PLAYWRIGHT_BROWSERS_PATH,
// else $XDG_CACHE_HOME/ms-playwright, else ~/.cache/ms-playwright. In
// the Flatpak XDG_CACHE_HOME is the host's again (Platform::Init).
fs : : path PlaywrightBrowsers ( ) {
const std : : string set = Platform : : GetEnv ( " PLAYWRIGHT_BROWSERS_PATH " ) . value_or ( " " ) ;
if ( ! set . empty ( ) & & set ! = " 0 " ) return set ;
const std : : string cache = Platform : : GetEnv ( " XDG_CACHE_HOME " ) . value_or ( " " ) ;
return fs : : path ( cache . empty ( ) ? std : : format ( " {}/.cache " , Platform : : Home ( ) ) : cache ) / " ms-playwright " ;
}
std : : int32_t BrowserCommand ( std : : span < const std : : string_view > args ) {
constexpr std : : string_view Prog = " crafter-review browser " ;
const Parsed parsed = ParseOptions ( args , { } , BrowserPositionals ) ;
if ( parsed . help ) {
std : : print ( " {} " , Help ( Prog , " the browser a lens with the browser environment drives " , { } , BrowserPositionals ) ) ;
return 0 ;
}
if ( ! parsed . error . empty ( ) ) return UsageError ( UsageLine ( Prog , { } , BrowserPositionals ) , Prog , parsed . error ) ;
const std : : vector < std : : string > argv = { " npx " , " -y " , std : : format ( " playwright@{} " , Mcp : : PlaywrightVersion ) , " install " , " chromium " } ;
std : : println ( " browser: {} (the Playwright of @playwright/mcp@{}) " , Dyn : : Join ( argv , " " ) , Mcp : : PlaywrightMcpVersion ) ;
std : : fflush ( stdout ) ;
std : : optional < std : : int32_t > code ;
try {
code = Process : : Wait ( Process : : Start ( { . argv = argv } ) . pid ) ;
} catch ( const std : : exception & e ) {
std : : println ( std : : cerr , " browser: {} " , e . what ( ) ) ;
return 1 ;
}
if ( code . value_or ( 1 ) ! = 0 ) {
std : : println ( std : : cerr , " browser: the install failed (exit {}) " , code . value_or ( 1 ) ) ;
return 1 ;
}
const fs : : path dir = PlaywrightBrowsers ( ) / std : : format ( " chromium-{} " , Mcp : : ChromiumRevision ) ;
std : : error_code error ;
if ( ! fs : : is_directory ( dir , error ) ) {
std : : println ( std : : cerr , " browser: installed, but {} is not there - the pinned Playwright and Chromium revision disagree " , dir . string ( ) ) ;
return 1 ;
}
std : : println ( " browser: chromium-{} in {} " , Mcp : : ChromiumRevision , dir . string ( ) ) ;
return 0 ;
}
// ---- init: a repository set up for the gates ---------------------------------------------
constexpr std : : array < OptionSpec , 1 > InitOptions = { {
{ " --force " , false , " " , " replace a SKILL.md or a link init did not make, and run from your home directory " } ,
} } ;
// After the skill's frontmatter: what tells a later `init` the file is
// its own to refresh.
constexpr std : : string_view InitMarker = " <!-- written by crafter-review init " ;
std : : string WithMarker ( const std : : string & skill ) {
const std : : string marker = std : : format ( " {} {}: `init` refreshes this file while this line is in it --> \n " , InitMarker , Version ( ) ) ;
if ( skill . starts_with ( " --- \n " ) ) {
const std : : size_t end = skill . find ( " \n --- \n " , 3 ) ;
if ( end ! = std : : string : : npos ) return std : : format ( " {}{}{} " , skill . substr ( 0 , end + 5 ) , marker , skill . substr ( end + 5 ) ) ;
}
return std : : format ( " {}{} " , marker , skill ) ;
}
std : : int32_t InitCommand ( std : : span < const std : : string_view > args ) {
constexpr std : : string_view Prog = " crafter-review init " ;
const Parsed parsed = ParseOptions ( args , InitOptions ) ;
if ( parsed . help ) {
std : : print ( " {} " , Help ( Prog , " set this repository up for the pre-push gates: .review-gates/config.json from the template, the artifact dirs ignored, the prepush skill " , InitOptions ) ) ;
return 0 ;
}
if ( ! parsed . error . empty ( ) ) return UsageError ( UsageLine ( Prog , InitOptions ) , Prog , parsed . error ) ;
const bool force = parsed . Flag ( " force " ) ;
auto refuse = [ ] ( const std : : string & message ) {
std : : println ( std : : cerr , " init: {} " , message ) ;
return 3 ;
} ;
// A directory the Flatpak cannot see becomes $HOME inside it, PWD
// included: the toplevel check and the home check catch that.
const fs : : path cwd = fs : : current_path ( ) ;
const Process : : Captured top = Process : : Run ( { " git " , " rev-parse " , " --show-toplevel " } ) ;
if ( top . returnCode ! = 0 ) return refuse ( std : : format ( " {} is not in a git checkout: run init from the repository's toplevel " , cwd . string ( ) ) ) ;
std : : error_code error ;
const fs : : path toplevel = Dyn : : Strip ( top . out ) ;
if ( ! fs : : equivalent ( toplevel , cwd , error ) ) return refuse ( std : : format ( " run it from the repository's toplevel, {}, not {} " , toplevel . string ( ) , cwd . string ( ) ) ) ;
if ( ! force & & fs : : equivalent ( cwd , Platform : : Home ( ) , error ) ) {
return refuse ( std : : format ( " {} is your home directory, which is where the Flatpak lands in a directory it cannot see: cd into the repository (--force if it is the repository) " , cwd.string())) ;
}
// The skill's place. The old setup linked .claude/skills/prepush
// into the review-gates submodule: the link goes, never written
// through, which would change the submodule's tracked file and
// fail the next freeze on a dirty tree. Any other link stays
// unless --force - checked before anything is written.
const fs : : path skillDir = cwd / " .claude " / " skills " / " prepush " ;
const fs : : path skill = skillDir / " SKILL.md " ;
std : : vector < std : : pair < fs : : path , fs : : path > > links ;
for ( const fs : : path & link : { skillDir , skill } ) {
if ( ! fs : : is_symlink ( fs : : symlink_status ( link , error ) ) ) continue ;
const fs : : path target = fs : : read_symlink ( link , error ) ;
if ( ! force & & ! target . string ( ) . contains ( " review-gates " ) ) {
return refuse ( std : : format ( " {} is a link to {}, not into the review-gates submodule: --force replaces it " , link . lexically_relative ( cwd ) . string ( ) , target . string ( ) ) ) ;
}
links . emplace_back ( link , target ) ;
}
std : : println ( " init: {} " , cwd . string ( ) ) ;
// The config, from the template, only when there is none. Written
// fresh, not copied: the Flatpak's files are read-only.
const fs : : path config = cwd / " .review-gates " / " config.json " ;
if ( fs : : exists ( config , error ) ) {
std : : println ( " kept .review-gates/config.json " ) ;
} else {
fs : : create_directories ( config . parent_path ( ) ) ;
std : : ofstream ( config , std : : ios : : binary ) < < Platform : : ReadFile ( Home ( ) / " process " / " templates " / " config.json " ) . value_or ( " {} \n " ) ;
2026-10-31 20:09:07 +01:00
std : : println ( " wrote .review-gates/config.json (the template: every lens off - turn on the ones this repository runs, set base, then what their environments need) " ) ;
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
}
// The artifacts and the worktrees stay out of git.
const fs : : path ignore = cwd / " .gitignore " ;
const std : : string ignored = Platform : : ReadFile ( ignore ) . value_or ( " " ) ;
std : : vector < std : : string > present ;
for ( const std : : string & line : Dyn : : Split ( ignored , " \n " ) ) present . push_back ( Dyn : : Strip ( Dyn : : Strip ( line ) , " / " ) ) ;
std : : vector < std : : string > added ;
for ( const std : : string_view entry : { std : : string_view ( " .review-gates/push/ " ) , std : : string_view ( " .review-gates/trees/ " ) } ) {
if ( ! std : : ranges : : contains ( present , Dyn : : Strip ( entry , " / " ) ) & & ! std : : ranges : : contains ( present , std : : string ( " .review-gates " ) ) ) added . emplace_back ( entry ) ;
}
if ( added . empty ( ) ) {
std : : println ( " kept .gitignore " ) ;
} else {
std : : string text = ignored ;
if ( ! text . empty ( ) & & ! text . ends_with ( ' \n ' ) ) text + = ' \n ' ;
for ( const std : : string & entry : added ) text + = std : : format ( " {} \n " , entry ) ;
std : : ofstream ( ignore , std : : ios : : binary ) < < text ;
std : : println ( " added {} to .gitignore " , Dyn : : Join ( added , " and " ) ) ;
}
// The skill.
for ( const auto & [ link , target ] : links ) {
fs : : remove ( link , error ) ;
std : : println ( " removed the link {} -> {} " , link . lexically_relative ( cwd ) . string ( ) , target . string ( ) ) ;
}
fs : : create_directories ( skillDir ) ;
const std : : optional < std : : string > shipped = Platform : : ReadFile ( Home ( ) / " process " / " skill " / " prepush " / " SKILL.md " ) ;
if ( ! shipped ) return refuse ( std : : format ( " cannot read the skill under {} " , Home ( ) . string ( ) ) ) ;
const std : : optional < std : : string > existing = Platform : : ReadFile ( skill ) ;
if ( existing & & ! existing - > contains ( InitMarker ) & & ! force ) {
std : : println ( " kept .claude/skills/prepush/SKILL.md (init did not write it: --force replaces it) " ) ;
} else {
std : : ofstream ( skill , std : : ios : : binary ) < < WithMarker ( * shipped ) ;
std : : println ( " {} .claude/skills/prepush/SKILL.md " , existing ? " refreshed " : " wrote " ) ;
}
// What comes next.
const Config : : Loaded loaded = Config : : Load ( cwd ) ;
const std : : vector < std : : string > off = Config : : LensesOff ( loaded . config ) ;
const Json environments = Config : : Environments ( loaded . config ) ;
bool browser = false ;
for ( auto it = environments . begin ( ) ; it ! = environments . end ( ) ; + + it ) {
const Plan : : Environment * environment = Plan : : FindEnvironment ( Dyn : : Str ( it . value ( ) ) ) ;
if ( ! std : : ranges : : contains ( off , it . key ( ) ) & & environment & & ! environment - > mcp . empty ( ) ) browser = true ;
}
std : : println ( " \n next: \n {} config - until it reports no problem " , Invocation ( ) ) ;
2026-10-31 20:09:07 +01:00
if ( off . size ( ) = = Plan : : Lenses . size ( ) ) std : : println ( " turn lenses on in .review-gates/config.json (lenses.<name>.run = true) - every lens is off until the repository says which run " ) ;
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
if ( browser ) std : : println ( " {} browser install - a lens drives the application in Chromium " , Invocation ( ) ) ;
return 0 ;
}
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
}
std : : int32_t Main ( std : : span < const std : : string_view > args ) {
const std : : span < const std : : string_view > rest = args . empty ( ) ? args : args . subspan ( 1 ) ;
// The supervisor first and alone: it is single-threaded and has to
// stay so, and it runs between the driver and claude, not as a
// command anyone types.
if ( ! rest . empty ( ) & & rest . front ( ) = = Process : : SuperviseVerb ) return Process : : SuperviseMain ( rest . subspan ( 1 ) ) ;
Platform : : Init ( ) ;
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
// Before any other thread: SIGINT, SIGTERM and SIGHUP go to a thread of
// their own, which stops a run once it is under way and otherwise ends
// the process.
Process : : InstallSignalThread ( Run : : OnSignal ) ;
feat(pipeline): the pure pipeline, checked against its goldens
The modules that turn a frozen range into passes and passes into a
verdict: :Schema, :Tools (the DENY, git and base lists, a leaf, so the
config and the pool both read them), :Plan, :Artifact, :Inventory,
:Dispositions, :State, :Normalize, :Stream, :Prompt, :Config, and the URL
half of :PgWire. Units, jobs, results, the config and the claude stream
stay JSON from end to end, so every key keeps its place; :Text gives JSON
values truthiness, get, ==, membership and Str(), and throws Dyn::Error
under a name for each kind of unexpected data.
Exactness where the bytes are seen:
- the stream classifiers are hand-written scanners with case-insensitive
folds (the Kelvin sign is a k), Unicode \b and \s, and `.` stopping at
\n; the SQL call detection takes `<exe> sql` with any path before it;
- config problems carry the JSON scanner's and the UTF-8 decoder's own
texts, positions included;
- a float total is Neumaier's compensated sum, and the build passes
-ffp-contract=off: -march=native would fuse a*b+c;
- `crafter-review inventory` and `config` print what the goldens record,
byte for byte (the fixture and 14 configs, broken ones included); a
message names this binary as the command to run.
tests/Goldens: the recorded answers over the fixtures next to them (the
commits fixture, a rich versioned series with CRLF, a folded subject,
Markdown headings in and out of fences, the code-key rewrites); the suite
compares 2304 results as DumpFile writes them, so a key out of order fails
too. All agree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:48:55 +01:00
// The options before the command: --config FILE or --config=FILE, and
// any unique prefix of a long option.
std : : optional < std : : string > configPath ;
std : : size_t at = 0 ;
while ( at < rest . size ( ) & & rest [ at ] . starts_with ( " -- " ) & & rest [ at ] ! = " -- " ) {
const std : : string_view arg = rest [ at ] ;
const std : : size_t equals = arg . find ( ' = ' ) ;
const std : : string_view name = arg . substr ( 0 , equals ) ;
if ( name . size ( ) > 2 & & std : : string_view ( " --config " ) . starts_with ( name ) ) {
if ( equals ! = std : : string_view : : npos ) {
configPath = std : : string ( arg . substr ( equals + 1 ) ) ;
} else if ( at + 1 < rest . size ( ) ) {
configPath = std : : string ( rest [ + + at ] ) ;
} else {
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
return UsageError ( Usage , " crafter-review " , " argument --config: expected one argument " ) ;
feat(pipeline): the pure pipeline, checked against its goldens
The modules that turn a frozen range into passes and passes into a
verdict: :Schema, :Tools (the DENY, git and base lists, a leaf, so the
config and the pool both read them), :Plan, :Artifact, :Inventory,
:Dispositions, :State, :Normalize, :Stream, :Prompt, :Config, and the URL
half of :PgWire. Units, jobs, results, the config and the claude stream
stay JSON from end to end, so every key keeps its place; :Text gives JSON
values truthiness, get, ==, membership and Str(), and throws Dyn::Error
under a name for each kind of unexpected data.
Exactness where the bytes are seen:
- the stream classifiers are hand-written scanners with case-insensitive
folds (the Kelvin sign is a k), Unicode \b and \s, and `.` stopping at
\n; the SQL call detection takes `<exe> sql` with any path before it;
- config problems carry the JSON scanner's and the UTF-8 decoder's own
texts, positions included;
- a float total is Neumaier's compensated sum, and the build passes
-ffp-contract=off: -march=native would fuse a*b+c;
- `crafter-review inventory` and `config` print what the goldens record,
byte for byte (the fixture and 14 configs, broken ones included); a
message names this binary as the command to run.
tests/Goldens: the recorded answers over the fixtures next to them (the
commits fixture, a rich versioned series with CRLF, a folded subject,
Markdown headings in and out of fences, the code-key rewrites); the suite
compares 2304 results as DumpFile writes them, so a key out of order fails
too. All agree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:48:55 +01:00
}
} else if ( name . size ( ) > 2 & & std : : string_view ( " --version " ) . starts_with ( name ) ) {
return PrintVersion ( ) ;
} else if ( name . size ( ) > 2 & & std : : string_view ( " --help " ) . starts_with ( name ) ) {
std : : println ( " {} " , Usage ) ;
return 0 ;
} else {
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
return UsageError ( Usage , " crafter-review " , std : : format ( " unrecognized arguments: {} " , arg ) ) ;
feat(pipeline): the pure pipeline, checked against its goldens
The modules that turn a frozen range into passes and passes into a
verdict: :Schema, :Tools (the DENY, git and base lists, a leaf, so the
config and the pool both read them), :Plan, :Artifact, :Inventory,
:Dispositions, :State, :Normalize, :Stream, :Prompt, :Config, and the URL
half of :PgWire. Units, jobs, results, the config and the claude stream
stay JSON from end to end, so every key keeps its place; :Text gives JSON
values truthiness, get, ==, membership and Str(), and throws Dyn::Error
under a name for each kind of unexpected data.
Exactness where the bytes are seen:
- the stream classifiers are hand-written scanners with case-insensitive
folds (the Kelvin sign is a k), Unicode \b and \s, and `.` stopping at
\n; the SQL call detection takes `<exe> sql` with any path before it;
- config problems carry the JSON scanner's and the UTF-8 decoder's own
texts, positions included;
- a float total is Neumaier's compensated sum, and the build passes
-ffp-contract=off: -march=native would fuse a*b+c;
- `crafter-review inventory` and `config` print what the goldens record,
byte for byte (the fixture and 14 configs, broken ones included); a
message names this binary as the command to run.
tests/Goldens: the recorded answers over the fixtures next to them (the
commits fixture, a rich versioned series with CRLF, a folded subject,
Markdown headings in and out of fences, the code-key rewrites); the suite
compares 2304 results as DumpFile writes them, so a key out of order fails
too. All agree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:48:55 +01:00
}
+ + at ;
}
if ( at < rest . size ( ) & & rest [ at ] = = " -h " ) {
feat: the crafter-review skeleton, and its semantics for text, JSON and crypto
What the rest of crafter-review stands on.
- project.cpp: a static crafter.review-lib and the crafter-review
executable; Crafter.Network without QUIC, pinned, or with --local from
the checkout next to this one; Crafter.LintRules' lite rules, pinned;
the version from CRAFTER_REVIEW_VERSION, git describe or 0.0.0-dev.
- :Core and :Driver: the version, PromptVersion 1, --version.
- :Json: nlohmann's ordered_json, which keeps key order and unknown keys,
so the files every driver on a machine shares survive a rewrite; dumps
in the indented file layout, the spaced one-line form and the compact
sorted form the structured-output schema needs byte for byte.
- :Crypto: SHA-256/SHA-1/MD5, HMAC, PBKDF2 through libcrypto; base64 and
base32 written out, because what they refuse, and with which message,
is pinned.
- :Unicode, Unicode 16.0.0's tables: whitespace, line breaks, decimal
digits, printable characters, case mappings and case-insensitive folds.
- :Text: string functions on code points, number parsing, the shortest
float form and half-even rounding, printed values, shell patterns, URL
splitting and unquoting, POSIX paths, universal newlines and the
replacing UTF-8 decoder.
Tests: Crypto (published vectors), Text (293 reference vectors).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:29:18 +01:00
std : : println ( " {} " , Usage ) ;
return 0 ;
}
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
if ( at > = rest . size ( ) ) return UsageError ( Usage , " crafter-review " , " a command is required " ) ;
feat(pipeline): the pure pipeline, checked against its goldens
The modules that turn a frozen range into passes and passes into a
verdict: :Schema, :Tools (the DENY, git and base lists, a leaf, so the
config and the pool both read them), :Plan, :Artifact, :Inventory,
:Dispositions, :State, :Normalize, :Stream, :Prompt, :Config, and the URL
half of :PgWire. Units, jobs, results, the config and the claude stream
stay JSON from end to end, so every key keeps its place; :Text gives JSON
values truthiness, get, ==, membership and Str(), and throws Dyn::Error
under a name for each kind of unexpected data.
Exactness where the bytes are seen:
- the stream classifiers are hand-written scanners with case-insensitive
folds (the Kelvin sign is a k), Unicode \b and \s, and `.` stopping at
\n; the SQL call detection takes `<exe> sql` with any path before it;
- config problems carry the JSON scanner's and the UTF-8 decoder's own
texts, positions included;
- a float total is Neumaier's compensated sum, and the build passes
-ffp-contract=off: -march=native would fuse a*b+c;
- `crafter-review inventory` and `config` print what the goldens record,
byte for byte (the fixture and 14 configs, broken ones included); a
message names this binary as the command to run.
tests/Goldens: the recorded answers over the fixtures next to them (the
commits fixture, a rich versioned series with CRLF, a folded subject,
Markdown headings in and out of fences, the code-key rewrites); the suite
compares 2304 results as DumpFile writes them, so a key out of order fails
too. All agree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:48:55 +01:00
const std : : string_view command = rest [ at ] ;
const std : : span < const std : : string_view > after = rest . subspan ( at + 1 ) ;
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
try {
feat(sql): the database helper and the TOTP helper, as subcommands
`crafter-review sql` hands the SQL to the driver's broker named in
REVIEW_GATES_DB; the broker runs it as one transaction, rolled back, and
streams the lines back with the exit code last. The client speaks protocol
3.0 over Crafter.Network's TCP client: cleartext, MD5 and SCRAM-SHA-256,
one Parse per statement, the transaction status as the backstop. The
splitter, the refusals, the table layout and every message work on code
points; connect and socket errors read "[Errno 111] Connection refused"
or "timed out". `crafter-review totp` prints "123456 (valid 17s)".
No sleep after the last connect attempt; TOTP reads the clock once; a
broker token that is no string gets the NO DATABASE answer and the exit
mark.
The goldens add the splitter, the comment stripping, the fingerprint, the
refusals, the first words, the headline, the cell, table, error and notice
layouts, the run's refusals and limits and 40 TOTP codes (2774 checks
now). tests/PgWire runs the helper over a Postgres stand-in and compares
its output with the goldens, line for line, plus the broker's protocol.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:20:00 +01:00
// The helpers a lens runs: they read neither the home nor the
// config, only their arguments, stdin and REVIEW_GATES_DB.
if ( command = = " sql " ) return PgWire : : SqlMain ( after ) ;
if ( command = = " totp " ) return Totp : : Main ( after ) ;
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
if ( command = = " inventory " & & ! configPath ) return Inventory : : Main ( after ) ;
if ( command = = " config " ) {
2026-10-31 16:45:53 +01:00
const Parsed parsed = ParseOptions ( after , { } ) ;
if ( parsed . help ) {
std : : print ( " {} " , Help ( " crafter-review config " , " print the merged consumer config and its problems " , { } ) ) ;
return 0 ;
}
if ( ! parsed . error . empty ( ) ) return UsageError ( Usage , " crafter-review " , parsed . error ) ;
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
return Config : : Main ( fs : : current_path ( ) , configPath ? fs : : path ( * configPath ) : fs : : path ( ) ) ;
}
if ( command = = " freeze " ) return FreezeCommand ( after , configPath ) ;
feat(run): `crafter-review run` and `slots`, the driver of the gates
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +01:00
if ( command = = " run " ) return RunCommand ( after , configPath ) ;
if ( command = = " slots " ) return SlotsCommand ( after , configPath ) ;
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
if ( command = = " seats " ) return SeatsCommand ( after ) ;
if ( command = = " stop " ) return StopCommand ( after ) ;
if ( command = = " doc " ) return DocCommand ( after ) ;
if ( command = = " browser " ) return BrowserCommand ( after ) ;
if ( command = = " init " ) return InitCommand ( after ) ;
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
} catch ( const Dyn : : Error & error ) {
// An error no command catches: its message, exit 1.
std : : println ( std : : cerr , " crafter-review: {}: {} " , error . Kind ( ) , error . what ( ) ) ;
return 1 ;
2026-10-31 16:45:53 +01:00
} catch ( const std : : exception & error ) {
// Anything else that escapes a command, as a traceback would.
std : : println ( std : : cerr , " crafter-review: {} " , error . what ( ) ) ;
return 1 ;
feat(pipeline): the pure pipeline, checked against its goldens
The modules that turn a frozen range into passes and passes into a
verdict: :Schema, :Tools (the DENY, git and base lists, a leaf, so the
config and the pool both read them), :Plan, :Artifact, :Inventory,
:Dispositions, :State, :Normalize, :Stream, :Prompt, :Config, and the URL
half of :PgWire. Units, jobs, results, the config and the claude stream
stay JSON from end to end, so every key keeps its place; :Text gives JSON
values truthiness, get, ==, membership and Str(), and throws Dyn::Error
under a name for each kind of unexpected data.
Exactness where the bytes are seen:
- the stream classifiers are hand-written scanners with case-insensitive
folds (the Kelvin sign is a k), Unicode \b and \s, and `.` stopping at
\n; the SQL call detection takes `<exe> sql` with any path before it;
- config problems carry the JSON scanner's and the UTF-8 decoder's own
texts, positions included;
- a float total is Neumaier's compensated sum, and the build passes
-ffp-contract=off: -march=native would fuse a*b+c;
- `crafter-review inventory` and `config` print what the goldens record,
byte for byte (the fixture and 14 configs, broken ones included); a
message names this binary as the command to run.
tests/Goldens: the recorded answers over the fixtures next to them (the
commits fixture, a rich versioned series with CRLF, a folded subject,
Markdown headings in and out of fences, the code-key rewrites); the suite
compares 2304 results as DumpFile writes them, so a key out of order fails
too. All agree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:48:55 +01:00
}
feat(freeze): `crafter-review freeze`, and --verify that names a git upgrade
Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.
freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.
tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:03:48 +01:00
return UsageError ( Usage , " crafter-review " , std : : format ( " argument cmd: invalid choice: '{}' " , command ) ) ;
feat: the crafter-review skeleton, and its semantics for text, JSON and crypto
What the rest of crafter-review stands on.
- project.cpp: a static crafter.review-lib and the crafter-review
executable; Crafter.Network without QUIC, pinned, or with --local from
the checkout next to this one; Crafter.LintRules' lite rules, pinned;
the version from CRAFTER_REVIEW_VERSION, git describe or 0.0.0-dev.
- :Core and :Driver: the version, PromptVersion 1, --version.
- :Json: nlohmann's ordered_json, which keeps key order and unknown keys,
so the files every driver on a machine shares survive a rewrite; dumps
in the indented file layout, the spaced one-line form and the compact
sorted form the structured-output schema needs byte for byte.
- :Crypto: SHA-256/SHA-1/MD5, HMAC, PBKDF2 through libcrypto; base64 and
base32 written out, because what they refuse, and with which message,
is pinned.
- :Unicode, Unicode 16.0.0's tables: whitespace, line breaks, decimal
digits, printable characters, case mappings and case-insensitive folds.
- :Text: string functions on code points, number parsing, the shortest
float form and half-even rounding, printed values, shell patterns, URL
splitting and unquoting, POSIX paths, universal newlines and the
replacing UTF-8 decoder.
Tests: Crypto (published vectors), Text (293 reference vectors).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:29:18 +01:00
}
}