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: 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 {
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
}
|