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®
|
|
|
|
|
|
|
|
|
|
module Crafter.Review:Driver_impl;
|
|
|
|
|
import :Driver;
|
|
|
|
|
import :Core;
|
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: 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 ---------------------------------
|
|
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct Parsed {
|
|
|
|
|
std::map<std::string, std::string, std::less<>> values;
|
|
|
|
|
std::set<std::string, std::less<>> flags;
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::string UsageLine(std::string_view prog, std::span<const OptionSpec> specs) {
|
|
|
|
|
std::string line = std::format("usage: {} [-h]", prog);
|
|
|
|
|
for (const OptionSpec& spec : specs) {
|
|
|
|
|
line += spec.takesValue ? std::format(" [{} {}]", spec.name, spec.metavar) : std::format(" [{}]", spec.name);
|
|
|
|
|
}
|
|
|
|
|
return line;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string Help(std::string_view prog, std::string_view description, std::span<const OptionSpec> specs) {
|
|
|
|
|
std::string text = std::format("{}\n\n{}\n\noptions:\n -h, --help show this help message and exit\n", UsageLine(prog, specs), description);
|
|
|
|
|
for (const OptionSpec& spec : specs) {
|
|
|
|
|
const std::string left = spec.takesValue ? std::format("{} {}", spec.name, spec.metavar) : std::string(spec.name);
|
|
|
|
|
text += left.size() < 20 ? std::format(" {:<20} {}\n", left, spec.help) : std::format(" {}\n {}\n", left, spec.help);
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
// end.
|
|
|
|
|
Parsed ParseOptions(std::span<const std::string_view> args, std::span<const OptionSpec> specs) {
|
|
|
|
|
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 == "--") {
|
|
|
|
|
unrecognized.emplace_back(arg);
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
if (equals != std::string_view::npos) {
|
|
|
|
|
parsed.values[key] = std::string(arg.substr(equals + 1));
|
|
|
|
|
} else if (i + 1 < args.size() && !(args[i + 1].starts_with('-') && args[i + 1].size() > 1)) {
|
|
|
|
|
parsed.values[key] = std::string(args[++i]);
|
|
|
|
|
} else {
|
|
|
|
|
parsed.error = std::format("argument {}: expected one argument", spec.name);
|
|
|
|
|
return parsed;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!unrecognized.empty()) parsed.error = std::format("unrecognized arguments: {}", Dyn::Join(unrecognized, " "));
|
|
|
|
|
return parsed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The consumer config, loaded before every command but `config`:
|
|
|
|
|
// warnings on stderr, a problem stops it.
|
|
|
|
|
std::optional<Json> LoadConfig(const std::optional<std::string>& configPath) {
|
|
|
|
|
Config::Loaded loaded = Config::Load(fs::current_path(), configPath ? fs::path(*configPath) : fs::path());
|
|
|
|
|
for (const Json& warning : Dyn::Or(Dyn::Get(loaded.config, "_warnings"), Dyn::EmptyList())) std::println(std::cerr, "config: warning: {}", warning.get<std::string>());
|
|
|
|
|
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"},
|
|
|
|
|
}};
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
const std::int32_t code = Freeze::Main(arguments, *config);
|
|
|
|
|
Process::KillDescendants();
|
|
|
|
|
return code;
|
|
|
|
|
}
|
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(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") {
|
|
|
|
|
if (!after.empty()) return UsageError(Usage, "crafter-review", std::format("unrecognized arguments: {}", after.front()));
|
|
|
|
|
return Config::Main(fs::current_path(), configPath ? fs::path(*configPath) : fs::path());
|
|
|
|
|
}
|
|
|
|
|
if (command == "freeze") return FreezeCommand(after, configPath);
|
|
|
|
|
} 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;
|
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
|
|
|
}
|
|
|
|
|
}
|