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>
This commit is contained in:
parent
b02de3ceff
commit
741e3a656d
70 changed files with 30565 additions and 21 deletions
36
interfaces/Crafter.Review-Artifact.cppm
Normal file
36
interfaces/Crafter.Review-Artifact.cppm
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
export module Crafter.Review:Artifact;
|
||||
import std;
|
||||
|
||||
// The frozen artifact and the tree it was cut from: the hash formula, the
|
||||
// HEAD a format-patch export names, the tree checks.
|
||||
export namespace Crafter::Review::Artifact {
|
||||
std::string Sha256File(const std::filesystem::path& path);
|
||||
// reply: the sha256 of the draft file; series and commits: the sha256 of
|
||||
// the sorted *.patch files concatenated (`cat $(ls *.patch | sort) |
|
||||
// sha256sum`).
|
||||
std::string ArtifactHash(std::string_view mode, const std::filesystem::path& path);
|
||||
// The *.patch names in the directory, sorted by code point.
|
||||
std::vector<std::string> PatchFiles(const std::filesystem::path& path);
|
||||
// K of a format-patch file name, ^(?:v\d+-)?(\d{4})-: 1 for
|
||||
// 0001-x.patch and v2-0001-x.patch; nullopt for a name without one.
|
||||
std::optional<std::int64_t> PatchNumber(std::string_view name);
|
||||
// {K: text} of the export, each patch read as Dyn::ReadText reads a file
|
||||
// (strict UTF-8, universal newlines). Dyn::Error (AttributeError) for a
|
||||
// patch name without a number.
|
||||
std::map<std::int64_t, std::string> PatchTexts(const std::filesystem::path& path);
|
||||
// The commit the last patch was cut from (its `From <sha>` line): the
|
||||
// range HEAD.
|
||||
std::optional<std::string> PatchHead(const std::filesystem::path& path);
|
||||
|
||||
// `git -C <tree> <args>`'s stdout; Dyn::Error (RuntimeError) with git's
|
||||
// message when it fails.
|
||||
std::string Git(const std::filesystem::path& tree, std::span<const std::string> args);
|
||||
bool IsToplevel(const std::filesystem::path& tree);
|
||||
std::string TreeHead(const std::filesystem::path& tree);
|
||||
// Tracked changes only (untracked files never reach a commit); "" when
|
||||
// clean.
|
||||
std::string TreeDirty(const std::filesystem::path& tree);
|
||||
}
|
||||
69
interfaces/Crafter.Review-Config.cppm
Normal file
69
interfaces/Crafter.Review-Config.cppm
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
export module Crafter.Review:Config;
|
||||
import std;
|
||||
import :Json;
|
||||
|
||||
// The consumer's configuration: `.review-gates/config.json` at the
|
||||
// repository root (`--config` names another file). It holds what the
|
||||
// pipeline must know about THIS repository — who may author its commits,
|
||||
// which branch a range is measured against, which lenses run and the
|
||||
// environment each gets, how its application is started and reached, where
|
||||
// its local database answers, which paths carry no runtime behaviour — and
|
||||
// nothing about any one review. A missing file is the defaults; an unknown
|
||||
// key or a malformed value is a preflight failure, and a retired key a
|
||||
// warning. The config stays the JSON it was read as, keys in their order.
|
||||
export namespace Crafter::Review::Config {
|
||||
constexpr std::string_view ConfigDir = ".review-gates";
|
||||
constexpr std::string_view ConfigFile = ".review-gates/config.json";
|
||||
constexpr std::string_view SlotFile = "slot.json";
|
||||
|
||||
// DEFAULTS, a fresh copy each call.
|
||||
Json Defaults();
|
||||
|
||||
struct Loaded {
|
||||
Json config;
|
||||
std::vector<std::string> problems;
|
||||
};
|
||||
// The defaults, the file's values merged over them, and every problem
|
||||
// Validate finds. A retired key is no problem: it is dropped, and
|
||||
// config["_warnings"] says so and what replaced it — a key the pipeline
|
||||
// stopped reading must not stop a consumer's freeze or run (2026-10-22).
|
||||
// `path` empty: <repo>/.review-gates/config.json.
|
||||
Loaded Load(const std::filesystem::path& repo, const std::filesystem::path& path = {});
|
||||
std::vector<std::string> Validate(const Json& config);
|
||||
// The config for a dump: the database URL with its password masked, no
|
||||
// `_` bookkeeping keys.
|
||||
Json Redacted(const Json& config);
|
||||
// The emails the freeze accepts: the configured list, else the
|
||||
// repository's git identity.
|
||||
std::vector<std::string> Authors(const Json& config, const std::filesystem::path& repo);
|
||||
std::string GitUserEmail(const std::filesystem::path& repo);
|
||||
// The lens files the config turns off (lenses.<name>.run = false), in gate
|
||||
// order.
|
||||
std::vector<std::string> LensesOff(const Json& config);
|
||||
// {lens file: environment} for every lens of the table, as the config
|
||||
// holds it (validated elsewhere).
|
||||
Json Environments(const Json& config);
|
||||
|
||||
struct Slots {
|
||||
// pool.Stacks entries {id, appUrl, apiUrls, dbUrl, login, slot: true},
|
||||
// in slot order.
|
||||
Json stacks = Json::array();
|
||||
std::vector<std::string> problems;
|
||||
};
|
||||
// The slots that are up: slots.dir/<n>/slot.json.
|
||||
Slots SlotStacks(const Json& config, const std::filesystem::path& repo);
|
||||
// slots.start or slots.stop for the slot numbers given, {n} filled.
|
||||
std::vector<std::string> SlotCommands(const Json& config, std::string_view which, std::span<const std::int64_t> slots);
|
||||
// The first login fixture that exists, relative to the repository root.
|
||||
std::optional<std::string> LoginFixture(const Json& config, const std::filesystem::path& repo);
|
||||
// The example a consumer starts from (process/templates/config.json is
|
||||
// this, rendered).
|
||||
Json Example();
|
||||
|
||||
// `crafter-review [--config F] config`: the merged config, its warnings
|
||||
// and its problems; exit 1 when there is a problem.
|
||||
std::int32_t Main(const std::filesystem::path& repo, const std::filesystem::path& path);
|
||||
}
|
||||
|
|
@ -20,6 +20,11 @@ export namespace Crafter::Review {
|
|||
// the first log line of a run and `--version`.
|
||||
std::string DriverLine();
|
||||
|
||||
// How a person runs this binary, for the command lines it prints: `flatpak
|
||||
// run net.catcrafts.CrafterReview` in the Flatpak, the binary's absolute
|
||||
// path otherwise.
|
||||
std::string Invocation();
|
||||
|
||||
// Where the lens texts, the procedure and the templates are: the
|
||||
// directory that holds process/. In this order:
|
||||
// - CRAFTER_REVIEW_HOME, natively only: a host setting must not swap the
|
||||
|
|
|
|||
21
interfaces/Crafter.Review-Dispositions.cppm
Normal file
21
interfaces/Crafter.Review-Dispositions.cppm
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
export module Crafter.Review:Dispositions;
|
||||
import std;
|
||||
import :Json;
|
||||
|
||||
// The disposition register: which entries are in force (a hash match),
|
||||
// carried (every anchor text still occurs its recorded number of times) or
|
||||
// lapsed, and the DISPOSITIONS IN FORCE prompt block. Entries are the
|
||||
// owner's JSON, kept as it is.
|
||||
export namespace Crafter::Review::Dispositions {
|
||||
// The register's entries with an id; empty `path`: none.
|
||||
std::vector<Json> Load(std::string_view path);
|
||||
// [{id, status, detail}], statuses in-force | carried | lapsed.
|
||||
std::vector<Json> Classify(std::span<const Json> entries, std::string_view hash, const std::filesystem::path& artifact);
|
||||
// The entries the rows say are in force or carried.
|
||||
std::vector<Json> InForce(std::span<const Json> entries, std::span<const Json> rows);
|
||||
// The DISPOSITIONS IN FORCE block for one lens.
|
||||
std::string Block(std::span<const Json> inForce, std::span<const Json> rows, std::string_view lensFile, std::string_view hash);
|
||||
}
|
||||
72
interfaces/Crafter.Review-Inventory.cppm
Normal file
72
interfaces/Crafter.Review-Inventory.cppm
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
export module Crafter.Review:Inventory;
|
||||
import std;
|
||||
import :Json;
|
||||
|
||||
// The partition of a frozen commit range — a `git format-patch` export — into
|
||||
// review units for the coverage contract. It is MECHANICAL: no agent decides
|
||||
// what the units are. Every non-blank line of the artifact belongs to
|
||||
// exactly one unit, and the inventory fails when that does not hold. Fixed
|
||||
// slots give what is NOT in the text a row of the coverage matrix too.
|
||||
//
|
||||
// Units (ids):
|
||||
// pK:subject commit K's subject
|
||||
// pK:msg commit K's message body (before the trailers), when not empty
|
||||
// pK:trailers commit K's trailer block, when not empty
|
||||
// pK:diffstat commit K's diffstat
|
||||
// pK:<file>#hdr the diff header of <file> in commit K (mode, rename, blobs)
|
||||
// pK:<file>#H hunk H of <file> in commit K; a hunk of a documentation file
|
||||
// that holds more than one heading-delimited part is cut into
|
||||
// #H.1, #H.2, ... (DocParts)
|
||||
// pK:outside fixed slot: the in-repository users and callers of what
|
||||
// commit K changes, outside its diff
|
||||
// pK:absent fixed slot: what a reviewer expects in commit K and does not
|
||||
// find
|
||||
// whole:* fixed slots (WholeCommits)
|
||||
export namespace Crafter::Review::Inventory {
|
||||
struct WholeSlot {
|
||||
std::string_view id;
|
||||
std::string_view description;
|
||||
};
|
||||
constexpr std::array<WholeSlot, 3> WholeCommits = {{
|
||||
{"whole:order", "commit ordering and bisectability — each commit a self-contained step that leaves the repository consistent (no later commit in the range repairs an earlier one, nothing is used before the commit that adds it), the declared base"},
|
||||
{"whole:absent", "what a reviewer of this repository expects the range to contain and it does not — a test, a version/changelog bump, a doc or packaging update, a migration for existing installs, a message for a body-less commit"},
|
||||
{"whole:behaviour", "the user-visible behaviour of the range, exercised end to end in the running application — every behaviour the code and the messages promise, as a user sees it; the flows the range touches and the adjacent flows that share what it changes; what a user expects the range to deliver and does not get"},
|
||||
}};
|
||||
|
||||
// [a, b) is one hunk of a Markdown file, lines[a] its @@ line: its
|
||||
// partition at the headings on the new side of the diff, outside fenced
|
||||
// code blocks, once the part before holds anything but headings.
|
||||
std::vector<std::pair<std::size_t, std::size_t>> DocParts(std::span<const std::string> lines, std::size_t a, std::size_t b);
|
||||
// The first non-blank line, stripped and cut to 90 code points.
|
||||
std::string Excerpt(std::span<const std::string> lines);
|
||||
|
||||
struct Series {
|
||||
// Every patch's lines, one after the other: what unit lines number.
|
||||
std::vector<std::string> lines;
|
||||
// Units ({id, lines, excerpt}), the whole slots not yet among them.
|
||||
Json units;
|
||||
};
|
||||
Series PartitionSeries(const std::filesystem::path& dir, bool splitDocs = true);
|
||||
|
||||
// A partition that broke the invariant.
|
||||
class InvariantError : public std::runtime_error {
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
struct Built {
|
||||
// What `inventory commits <dir>` prints: {mode, artifact, hash8,
|
||||
// units, invariant, count}.
|
||||
Json out;
|
||||
std::vector<std::string> lines;
|
||||
};
|
||||
// `artifact` is kept as it is spelled: the inventory records it so.
|
||||
Built Build(std::string_view mode, std::string_view artifact);
|
||||
|
||||
// `crafter-review inventory commits <dir>`: the partition as indented
|
||||
// JSON on stdout. Exit 1 on a broken invariant or a bad command line.
|
||||
std::int32_t Main(std::span<const std::string_view> args);
|
||||
}
|
||||
|
|
@ -37,6 +37,56 @@ export namespace Crafter::Review {
|
|||
return sorted.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace);
|
||||
}
|
||||
|
||||
// What another program wrote — a line of the claude stream, a shared
|
||||
// file — read leniently: an escaped lone surrogate ("\ud800" with no low
|
||||
// half after it), which nlohmann refuses, is read as U+FFFD.
|
||||
inline Json ParseLoose(std::string_view text) {
|
||||
try {
|
||||
return Json::parse(text);
|
||||
} catch (const nlohmann::json::parse_error&) {
|
||||
auto hexAt = [&](std::size_t at) -> std::int32_t {
|
||||
if (at + 4 > text.size()) return -1;
|
||||
std::int32_t value = 0;
|
||||
for (std::size_t i = at; i < at + 4; ++i) {
|
||||
const char c = text[i];
|
||||
const std::int32_t digit = c >= '0' && c <= '9' ? c - '0' : (c >= 'a' && c <= 'f' ? c - 'a' + 10 : (c >= 'A' && c <= 'F' ? c - 'A' + 10 : -1));
|
||||
if (digit < 0) return -1;
|
||||
value = value * 16 + digit;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
std::string fixed;
|
||||
bool changed = false;
|
||||
for (std::size_t at = 0; at < text.size();) {
|
||||
if (text[at] != '\\' || at + 1 >= text.size()) {
|
||||
fixed.push_back(text[at++]);
|
||||
continue;
|
||||
}
|
||||
if (text[at + 1] != 'u') {
|
||||
fixed.append(text.substr(at, 2));
|
||||
at += 2;
|
||||
continue;
|
||||
}
|
||||
const std::int32_t unit = hexAt(at + 2);
|
||||
const bool high = unit >= 0xD800 && unit <= 0xDBFF;
|
||||
const bool low = unit >= 0xDC00 && unit <= 0xDFFF;
|
||||
if (high && at + 12 <= text.size() && text[at + 6] == '\\' && text[at + 7] == 'u' && hexAt(at + 8) >= 0xDC00 && hexAt(at + 8) <= 0xDFFF) {
|
||||
fixed.append(text.substr(at, 12));
|
||||
at += 12;
|
||||
} else if (high || low) {
|
||||
fixed += "\\ufffd";
|
||||
changed = true;
|
||||
at += 6;
|
||||
} else {
|
||||
fixed.append(text.substr(at, 2));
|
||||
at += 2;
|
||||
}
|
||||
}
|
||||
if (!changed) throw;
|
||||
return Json::parse(fixed);
|
||||
}
|
||||
}
|
||||
|
||||
// On one line, with ", " and ": " between items: where a reader matches
|
||||
// on the bytes, such as the first line of an opencode output, `{"type":
|
||||
// "review_gates_meta", …`, which salvage looks for.
|
||||
|
|
|
|||
41
interfaces/Crafter.Review-Normalize.cppm
Normal file
41
interfaces/Crafter.Review-Normalize.cppm
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
export module Crafter.Review:Normalize;
|
||||
import std;
|
||||
import :Json;
|
||||
|
||||
// From the raw structured output of a pass to its discharge, its coverage
|
||||
// contract and its derived verdict; the merge of a lens's parts with the
|
||||
// cross-unit rule; the coverage matrix. The structured output is the model's
|
||||
// JSON and stays JSON, with every field the model wrote.
|
||||
export namespace Crafter::Review::Normalize {
|
||||
// An exact id, else the one id of `universe` it is a prefix of, else
|
||||
// itself.
|
||||
Json ResolveId(const Json& id, std::span<const std::string> universe);
|
||||
|
||||
// v: the raw structured output ({verdict, findings, notes, coverage?}).
|
||||
// Its notes get their items resolved in place, and the result holds them.
|
||||
// inventory: the units THIS pass must cover (null: no coverage contract);
|
||||
// allIds: every inventory id, for prefix resolution.
|
||||
Json NormalizeOutput(Json& v, std::span<const Json> inForce, std::string_view lensFile, std::string_view hash, const Json& inventory,
|
||||
std::span<const std::string> allIds);
|
||||
|
||||
// The distinct values of `items`, first occurrence kept, equal when their
|
||||
// canonical JSON is.
|
||||
std::vector<Json> Dedupe(std::span<const Json> items);
|
||||
|
||||
// parts: normalized outputs (each with partLabel, expectedIds);
|
||||
// missingUnits: unit ids whose process never returned. Coverage: one entry
|
||||
// per unit, blocking > immaterial > the rest, a tie to the later part
|
||||
// (the block, the zoomed view). The cross-unit rule: a unit cited by an
|
||||
// undischarged finding whose merged verdict is not blocking is
|
||||
// overwritten with the lens's blocking verdict.
|
||||
Json MergeParts(std::string_view lens, std::string_view lensFile, std::span<const Json> parts, std::span<const std::string> missingUnits,
|
||||
const Json& stoppedEarly = nullptr);
|
||||
|
||||
// Units x every lens that returned coverage; holes (a reviewed-kind unit
|
||||
// no lens owned) and defects. notReviewed: the unit ids no lens that runs
|
||||
// reads, listed, never holes.
|
||||
Json Matrix(const Json& inventory, std::span<const Json> results, std::span<const std::string> notReviewed = {});
|
||||
}
|
||||
67
interfaces/Crafter.Review-PgWire.cppm
Normal file
67
interfaces/Crafter.Review-PgWire.cppm
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
export module Crafter.Review:PgWire;
|
||||
import std;
|
||||
import :Json;
|
||||
|
||||
// The database under test: the SQL a lens with the `supabase` environment
|
||||
// runs, and the driver's probe before that lens's gate. The lens runs
|
||||
// `crafter-review sql`, which hands the SQL to the driver's broker: the
|
||||
// database URL, its password included, stays in the driver, and the pass's
|
||||
// process knows the broker's address and its stack's token alone.
|
||||
//
|
||||
// Every call of the helper is ONE transaction that is rolled back at its end,
|
||||
// so the passes of a run share one local database without seeing each
|
||||
// other, and the database must be on this machine (127.0.0.1, localhost,
|
||||
// ::1): the helper never reaches a hosted project.
|
||||
export namespace Crafter::Review::PgWire {
|
||||
// Printed once the database answered; the pool looks for it.
|
||||
constexpr std::string_view Answered = "review-gates sql: connected";
|
||||
// Printed when it did not (exit 2).
|
||||
constexpr std::string_view NoDatabase = "review-gates sql: NO DATABASE";
|
||||
// Retired 2026-10-22: the URL stays in the driver; dropped from every
|
||||
// pass's environment.
|
||||
constexpr std::string_view UrlEnv = "REVIEW_GATES_DB_URL";
|
||||
// `<host>:<port>#<token>`: the driver's broker and the pass's database,
|
||||
// never a URL.
|
||||
constexpr std::string_view BrokerEnv = "REVIEW_GATES_DB";
|
||||
// The broker's last line: the call's exit code. Its NUL is part of it.
|
||||
constexpr std::string_view ExitMark("\x00review-gates-exit ", 19);
|
||||
constexpr std::array<std::string_view, 3> Loopback = {"127.0.0.1", "localhost", "::1"};
|
||||
constexpr std::int32_t StatementTimeoutS = 20;
|
||||
constexpr std::int32_t LockTimeoutS = 3;
|
||||
// Rows printed per statement (all are counted).
|
||||
constexpr std::int32_t MaxRows = 100;
|
||||
// Characters printed per value.
|
||||
constexpr std::int32_t MaxCell = 300;
|
||||
// Characters printed per call; past it, statements still run and print
|
||||
// their tags only.
|
||||
constexpr std::int32_t MaxOutput = 40000;
|
||||
constexpr std::int32_t MaxSql = 200000;
|
||||
constexpr std::int32_t MaxStatements = 200;
|
||||
constexpr std::int32_t ExitOk = 0;
|
||||
constexpr std::int32_t ExitSql = 1;
|
||||
constexpr std::int32_t ExitNoDb = 2;
|
||||
constexpr std::int32_t ExitUsage = 64;
|
||||
|
||||
struct Url {
|
||||
std::string scheme;
|
||||
std::string host;
|
||||
std::int32_t port = 5432;
|
||||
std::string user;
|
||||
std::string password;
|
||||
std::string database;
|
||||
};
|
||||
// The URL's parts with the defaults: port 5432, user postgres, database
|
||||
// postgres. std::invalid_argument with a ValueError text for a URL
|
||||
// Dyn::UrlSplit refuses.
|
||||
Url ParseUrl(std::string_view url);
|
||||
// nullopt when the URL names a Postgres database on this machine, else the
|
||||
// problem. Any JSON value: a config may hold anything.
|
||||
std::optional<std::string> CheckUrl(const Json& url);
|
||||
// The URL with its password replaced, for a log line or a dump.
|
||||
std::string Redact(std::string_view url);
|
||||
// "<database> on <host>:<port> as <user>"
|
||||
std::string Where(std::string_view url);
|
||||
}
|
||||
260
interfaces/Crafter.Review-Plan.cppm
Normal file
260
interfaces/Crafter.Review-Plan.cppm
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
export module Crafter.Review:Plan;
|
||||
import std;
|
||||
import :Json;
|
||||
|
||||
// The lens table, the unit kinds, pass planning, the mechanical n/a, the
|
||||
// process budgets, the reuse keys and the cost estimate. A unit is the
|
||||
// inventory's object ({id, lines, excerpt}) and a job the object PlanLens
|
||||
// builds, both kept as JSON with their keys in a fixed order: the dump,
|
||||
// progress.json and the prompts are made from them.
|
||||
export namespace Crafter::Review::Plan {
|
||||
constexpr std::string_view FunctionalLens = "repo-functional.md";
|
||||
// The reasoning effort when --effort is not given (2026-10-02: the push
|
||||
// gates run at xhigh).
|
||||
constexpr std::string_view Effort = "xhigh";
|
||||
// Clean reads a code unit of a lens marked `reads` needs before its
|
||||
// verdict is reused, one per round (--reads). 2026-10-06: a static lens
|
||||
// is not deterministic — a second and third read of unchanged code still
|
||||
// found defects; "3 is fine".
|
||||
constexpr std::int32_t Reads = 3;
|
||||
// The key's last slot held the run's effort from 2026-09-22 to
|
||||
// 2026-10-02; it is frozen at the value every stored verdict was built
|
||||
// with, so a clean verdict bought at one effort is reused at any other
|
||||
// (Jorijn, 2026-10-02).
|
||||
constexpr std::string_view ReuseKeyEffortSlot = "max";
|
||||
|
||||
// The model of each lens tier; every lens on Opus 5.5 (2026-09-25), and
|
||||
// `--models fable=claude-fable-5-1` splits them. In table order: opus,
|
||||
// fable.
|
||||
struct ModelTier {
|
||||
std::string tier;
|
||||
std::string id;
|
||||
};
|
||||
std::vector<ModelTier>& Models();
|
||||
// MODELS[tier]; Dyn::Error (KeyError) for an unknown tier.
|
||||
const std::string& ModelFor(std::string_view tier);
|
||||
|
||||
constexpr std::array<std::string_view, 2> ApproachSlots = {"whole:order", "whole:absent"};
|
||||
constexpr std::array<std::string_view, 1> BehaviourSlots = {"whole:behaviour"};
|
||||
constexpr std::array<std::string_view, 1> CodeBlocks = {"code"};
|
||||
|
||||
// One row of LENSES. whole: "" (none), "first-repo" (the approach) or
|
||||
// "functional" (the range exercised in the running application); every
|
||||
// whole pass gates the lens's block passes and covers the slots named in
|
||||
// `slots`. block: the unit kinds the lens zooms into. reuse: per-unit reuse
|
||||
// across hashes; wholeReuse: the clean whole pass carries while the code
|
||||
// of the range is unchanged. key: what a block is keyed on — "code" (its
|
||||
// own hunk and the base; 2026-10-06) or "functional" (its hunk AND the
|
||||
// code of the whole range: what the browser shows for a hunk depends on
|
||||
// every other hunk). reads: the lens's code units need Reads clean reads.
|
||||
// empirical: the lens's evidence is observation, so it needs an
|
||||
// environment with an application under test, and a whole pass that never
|
||||
// used the instrument is not evidence. perCommitSlots false: the lens
|
||||
// judges the per-commit slots in its whole pass. env: the default
|
||||
// environment; a verdict bought in any other carries it in its key.
|
||||
struct Lens {
|
||||
std::string_view file;
|
||||
std::int32_t gate;
|
||||
std::string_view whole;
|
||||
std::span<const std::string_view> slots;
|
||||
std::span<const std::string_view> block;
|
||||
std::string_view model;
|
||||
bool reuse;
|
||||
bool wholeReuse;
|
||||
bool reads;
|
||||
std::string_view key;
|
||||
bool empirical;
|
||||
bool perCommitSlots;
|
||||
std::string_view env;
|
||||
};
|
||||
// In the order the dump and the prompts list them.
|
||||
constexpr std::array<Lens, 4> Lenses = {{
|
||||
{"repo-better-solution.md", 1, "first-repo", ApproachSlots, CodeBlocks, "fable", true, true, true, "code", false, true, "none"},
|
||||
{FunctionalLens, 2, "functional", BehaviourSlots, CodeBlocks, "opus", true, true, false, "functional", true, false, "browser"},
|
||||
{"repo-bug-prover.md", 3, "", {}, CodeBlocks, "opus", true, false, true, "code", false, true, "none"},
|
||||
// The security lens (2026-10-22): does the range's code keep the
|
||||
// protections the repository promises? The bug-prover's shape beside
|
||||
// it in Gate 3: one gate stop for both proofs.
|
||||
{"repo-security.md", 3, "", {}, CodeBlocks, "opus", true, false, true, "code", false, true, "none"},
|
||||
}};
|
||||
bool IsLens(std::string_view file);
|
||||
// LENSES[file]; Dyn::Error (KeyError) for a file that is no lens.
|
||||
const Lens& LensOf(std::string_view file);
|
||||
|
||||
struct Gate {
|
||||
std::string_view title;
|
||||
std::vector<std::string_view> lenses;
|
||||
};
|
||||
const std::vector<Gate>& Gates();
|
||||
|
||||
// What a lens's processes get beyond the read-only tools, by the
|
||||
// consumer's lenses.<name>.environment: mcp, the server rendered into
|
||||
// --mcp-config ("" for none); tools, allowlist entries; app, the lens
|
||||
// drives the application under test; db, the lens runs SQL in the
|
||||
// database under test; memMb, what a pass is admitted at before one is
|
||||
// measured. A new entry must first bring seven things (the environment in
|
||||
// the reuse keys, its prompt texts, its own fail-closed check, validation,
|
||||
// DENY, the opencode refusal, a firing proof); the tests hold the table at
|
||||
// these four until then.
|
||||
//
|
||||
// memMb: the highest peak PSS of a pass's own process tree measured
|
||||
// 2026-10-22 on the first consumer — the claude process alone 158-208 MB;
|
||||
// with the browser 385-840 MB (Chromium 554-568, claude 208, the Playwright
|
||||
// server 112-118, the SQL helper 5-9).
|
||||
constexpr std::int32_t MemNone = 210;
|
||||
constexpr std::int32_t MemBrowser = 840;
|
||||
constexpr std::array<std::string_view, 1> BrowserTools = {"Bash(curl:*)"};
|
||||
struct Environment {
|
||||
std::string_view name;
|
||||
std::string_view mcp;
|
||||
std::span<const std::string_view> tools;
|
||||
bool app;
|
||||
bool db;
|
||||
std::int32_t memMb;
|
||||
};
|
||||
constexpr std::array<Environment, 4> Environments = {{
|
||||
{"none", "", {}, false, false, MemNone},
|
||||
{"browser", "playwright", BrowserTools, true, false, MemBrowser},
|
||||
{"supabase", "", {}, false, true, MemNone},
|
||||
{"browser+supabase", "playwright", BrowserTools, true, true, MemBrowser},
|
||||
}};
|
||||
// nullptr for a name that is no environment.
|
||||
const Environment* FindEnvironment(std::string_view name);
|
||||
|
||||
// The environment in a reuse key: evidence bought in one environment is
|
||||
// never reused in another. The lens's default adds nothing, so every
|
||||
// verdict stored before 2026-10-22 keeps its key. Empty `environment`:
|
||||
// the default.
|
||||
std::vector<std::string> EnvDeps(std::string_view lensFile, std::string_view environment = {});
|
||||
|
||||
// (max_turns, timeout_min) of a pass. Caps are safety nets, never retried
|
||||
// (2026-09-17: a max-turns retry re-buys the whole context). A functional
|
||||
// pass spends turns on the browser and gets more.
|
||||
struct Budget {
|
||||
std::int32_t maxTurns;
|
||||
std::int32_t timeoutMin;
|
||||
};
|
||||
Budget BudgetFor(std::string_view kind, std::string_view unitKind, std::string_view lensFile);
|
||||
|
||||
std::string_view DefaultEffort();
|
||||
// "slot", "code" or "text".
|
||||
std::string_view UnitKind(const Json& unit);
|
||||
std::string_view UnitKindOf(std::string_view unitId);
|
||||
// A hunk, a part of a hunk or the file header of a documentation file.
|
||||
bool IsDocUnit(std::string_view unitId);
|
||||
// `\.md$`, case-insensitively: a documentation file (plan.DOC_PATH and
|
||||
// inventory.DOC_FILE, the same pattern).
|
||||
bool IsDocPath(std::string_view path);
|
||||
// The unit's kind for a lens: a documentation file's units are "doc".
|
||||
std::string_view KindFor(std::string_view lensFile, const Json& unit);
|
||||
// The file a hunk or file-header unit belongs to; nullopt for a slot or a
|
||||
// text unit.
|
||||
std::optional<std::string> UnitPath(std::string_view unitId);
|
||||
// The units no lens of `lensFiles` (the lenses that run) plans a whole or
|
||||
// block pass on. A mechanical n/a is not a reading.
|
||||
std::vector<std::string> NotReviewed(const Json& units, std::span<const std::string> lines, std::span<const std::string> lensFiles,
|
||||
std::span<const std::string> exclude = {});
|
||||
// The first glob of functional.exclude the path matches (`**/` prefixes
|
||||
// match at the root too).
|
||||
std::optional<std::string> ExcludedBy(std::string_view path, std::span<const std::string> globs);
|
||||
std::string UnitText(std::span<const std::string> lines, const Json& unit);
|
||||
// sha256 of the text's UTF-8, in hex.
|
||||
std::string Sha(std::string_view text);
|
||||
// lens_file.replace('.md', '')
|
||||
std::string LensName(std::string_view lensFile);
|
||||
// Every lens of the gates.
|
||||
std::vector<std::string> ModeLenses();
|
||||
// The gates with their lens files. off: the lenses the config turns off;
|
||||
// only keeps (test runs), without drops (an owner ruling); an empty span
|
||||
// filters nothing, and a gate left empty disappears.
|
||||
std::vector<Gate> GatesFor(std::span<const std::string> only = {}, std::span<const std::string> without = {},
|
||||
std::span<const std::string> off = {});
|
||||
|
||||
// The whole job (null when the lens has none) and the block jobs.
|
||||
struct LensPlan {
|
||||
Json whole;
|
||||
std::vector<Json> blocks;
|
||||
};
|
||||
// unitsFilter: --units (empty: every unit); exclude: the functional
|
||||
// lens's path globs.
|
||||
LensPlan PlanLens(std::string_view lensFile, const Json& units, std::span<const std::string> lines,
|
||||
std::span<const std::string> unitsFilter = {}, std::span<const std::string> exclude = {});
|
||||
|
||||
// The model is part of the key (a verdict bought from one model is not
|
||||
// evidence for another); the effort is not. ident replaces the unit id for
|
||||
// a unit that is keyed on its content. nullopt: the job is not reusable.
|
||||
std::optional<std::string> ReuseKey(const Json& job, std::string_view lensFile, std::string_view unitText, std::vector<std::string> deps,
|
||||
std::vector<std::string> inForceIds, std::string_view charterSha, std::int32_t promptVersion,
|
||||
std::string_view ident = {});
|
||||
|
||||
// A code unit's text without what a rewrite of the range changes while
|
||||
// the code stays: the blob ids of an `index` line, the line numbers of a
|
||||
// hunk header, and the tail `git format-patch` appends (base-commit, the
|
||||
// `-- ` signature, the git version), which the inventory hands to a file's
|
||||
// last hunk.
|
||||
std::string CodeNorm(std::string_view text);
|
||||
|
||||
// One format-patch file, cut where the inventory cuts it: whether the
|
||||
// commit has a body, its code (every file section from the first
|
||||
// `diff --git` on, normalised, without documentation files), the base,
|
||||
// the diff headers of the documentation files, the message.
|
||||
struct PatchParts {
|
||||
bool hasBody = false;
|
||||
std::string code;
|
||||
std::string base;
|
||||
std::vector<std::string> docs;
|
||||
std::string message;
|
||||
};
|
||||
PatchParts PartsOf(std::string_view patchText);
|
||||
|
||||
// What the code keys of a range are built from, by commit number K.
|
||||
struct CommitCode {
|
||||
std::map<std::int64_t, bool> body;
|
||||
std::map<std::int64_t, std::string> code;
|
||||
std::map<std::int64_t, std::vector<std::string>> docs;
|
||||
std::map<std::int64_t, std::string> message;
|
||||
std::string range;
|
||||
std::map<std::int64_t, std::string> upto;
|
||||
std::string base;
|
||||
};
|
||||
CommitCode CommitCodeOf(const std::map<std::int64_t, std::string>& patches);
|
||||
|
||||
// (ident, deps) of a code unit: a hunk or a file header is keyed on its
|
||||
// file and its own normalised text, the two per-commit slots on that
|
||||
// commit's position and code, the code of the whole range, its
|
||||
// documentation files, and for `absent` whether it has a body. Every code
|
||||
// unit depends on the base.
|
||||
std::pair<std::string, std::vector<std::string>> CodeKeyParts(std::string_view unitId, const CommitCode& cc);
|
||||
std::optional<std::string> CodeReuseKey(const Json& job, std::string_view lensFile, std::string_view unitText, const CommitCode& cc,
|
||||
std::span<const std::string> inForceIds, std::string_view charterSha, std::int32_t promptVersion,
|
||||
std::string_view environment = {});
|
||||
std::optional<std::string> FunctionalReuseKey(const Json& job, std::string_view lensFile, std::string_view unitText, const CommitCode& cc,
|
||||
std::span<const std::string> inForceIds, std::string_view charterSha, std::int32_t promptVersion,
|
||||
std::string_view environment = {});
|
||||
// By the lens's `key` kind.
|
||||
std::optional<std::string> BlockReuseKey(const Json& job, std::string_view lensFile, std::string_view unitText, const CommitCode& cc,
|
||||
std::span<const std::string> inForceIds, std::string_view charterSha, std::int32_t promptVersion,
|
||||
std::string_view environment = {});
|
||||
// A whole pass that judges the code of the range: the code of every
|
||||
// commit in order, the base, which commits have a body and which
|
||||
// documentation files each touches. No message text and no commit id.
|
||||
std::optional<std::string> WholeCodeKey(const Json& job, std::string_view lensFile, const CommitCode& cc, std::span<const std::string> inForceIds,
|
||||
std::string_view charterSha, std::int32_t promptVersion, std::string_view environment = {});
|
||||
|
||||
// How many clean reads a stored result stands for: one per round that
|
||||
// read its key clean.
|
||||
std::int64_t ReadsOf(const Json& entry);
|
||||
// Was this key already read clean in that round on that hash?
|
||||
bool ReadIn(const Json& entry, std::string_view hash8, std::int64_t round);
|
||||
// A stored result under the id its unit has in THIS artifact.
|
||||
Json Rebind(const Json& entry, std::string_view unitId);
|
||||
|
||||
// Weighted cost-units (input 1, cache write 1.25, cache read 0.1, output
|
||||
// 5) and list USD at opus rates (5 / 6.25 / 0.5 / 25 per M), calibrated on
|
||||
// the 2026-09-17 smoke. lensLayerBytes: {lens name: bytes}.
|
||||
Json Estimate(std::span<const Json> jobs, std::int64_t prefixBytes, const std::map<std::string, std::int64_t>& lensLayerBytes,
|
||||
std::int64_t tailBytes = 1800, double tpb = 3.7, std::int64_t sysTokens = 32000, std::int64_t outPerTurn = 1200);
|
||||
}
|
||||
|
|
@ -76,7 +76,8 @@ export namespace Crafter::Review::Platform {
|
|||
|
||||
// An exclusive flock(2) on `lockPath`, held while the object lives. flock,
|
||||
// not fcntl or OFD locks: every driver on the machine takes flock on the
|
||||
// same side files, and on Linux the two kinds do not exclude each other.
|
||||
// same side files, and on Linux the two kinds do not exclude each other. A
|
||||
// missing lock file is created 0666 less the umask.
|
||||
class FileLock {
|
||||
public:
|
||||
explicit FileLock(const std::filesystem::path& lockPath);
|
||||
|
|
|
|||
73
interfaces/Crafter.Review-Prompt.cppm
Normal file
73
interfaces/Crafter.Review-Prompt.cppm
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
export module Crafter.Review:Prompt;
|
||||
import std;
|
||||
import :Json;
|
||||
|
||||
// The system text and the layered prompt: the run layer, shared by every
|
||||
// process of the run, then the lens layer, shared per lens, then the tail,
|
||||
// this pass. Byte-identical prefixes are what the shared prompt cache keys
|
||||
// on, so every literal here is fixed to the byte (the goldens hold them).
|
||||
export namespace Crafter::Review::Prompt {
|
||||
constexpr std::string_view SepLens = "\n\n==================== LENS ====================\n\n";
|
||||
constexpr std::string_view SepTail = "\n\n==================== SCOPE OF THIS PASS ====================\n\n";
|
||||
constexpr std::string_view SepRun = "\n\n==================== THIS RUN ====================\n\n";
|
||||
|
||||
extern const std::string_view GenericVocab;
|
||||
// WHOLE_TEXT['first-repo'] and WHOLE_TEXT['functional'].
|
||||
extern const std::string_view WholeFirstRepo;
|
||||
extern const std::string_view WholeFunctional;
|
||||
extern const std::string_view FunctionalBlockHint;
|
||||
extern const std::string_view ShotRules;
|
||||
// KIND_HINTS_REPO
|
||||
extern const std::string_view HintOutside;
|
||||
extern const std::string_view HintAbsent;
|
||||
|
||||
// _common-repo.md with the artifact kind filled in.
|
||||
std::string SystemText();
|
||||
// The lens's charter, read with universal newlines.
|
||||
std::string Charter(std::string_view lensFile);
|
||||
|
||||
// INPUTS, the artifact with line numbers, the inventory. trees: as they
|
||||
// are spelled to the lens; contextPaths may be empty.
|
||||
std::string RunLayer(std::string_view artifactRel, std::string_view base, std::string_view date, std::span<const std::string> trees, std::string_view hash,
|
||||
std::span<const std::string> lines, const Json& units, std::span<const std::string> contextPaths = {},
|
||||
std::string_view repoName = "the repository");
|
||||
|
||||
// APPLICATION UNDER TEST: what the processes of a lens with the browser
|
||||
// environment are told about the running application — from the
|
||||
// consumer's functional config, the login fixture the driver found and the
|
||||
// TOTP helper's exact command. slots: the stacks are the bring-up's
|
||||
// slots, one per pass, and this block, shared by every pass of the lens,
|
||||
// names none of them; the pass's own is in its user message (StackText).
|
||||
// A nullopt fixture or command prints as None.
|
||||
std::string AppBlock(const Json& functional, const std::optional<std::string>& loginFixture = std::nullopt,
|
||||
const std::optional<std::string>& totpCommand = std::nullopt, bool slots = false);
|
||||
// STACK FOR THIS PASS: the slot a pass holds, in its user message, so
|
||||
// the system prompt every pass of the lens shares stays byte-identical.
|
||||
std::string StackText(const Json& stack, const std::optional<std::string>& totpCommand);
|
||||
// DATABASE UNDER TEST: the exact helper command (the URL stays in the
|
||||
// driver's broker, never here), what the driver checked before the gate
|
||||
// (dbUser: the role of the stacks' connections, nullopt when they differ;
|
||||
// bypass: whether that role bypasses row-level security, nullopt says
|
||||
// nothing), what one call is, how to act as an API caller, the limits,
|
||||
// the evidence, the owner's notes.
|
||||
std::string DbBlock(const Json& supabase, std::string_view sqlCommand, const std::optional<std::string>& dbUser, bool withApp = false,
|
||||
const std::optional<std::string>& checked = std::nullopt, std::optional<bool> bypass = std::nullopt);
|
||||
// The charter, the application and database blocks when there are
|
||||
// any, the dispositions, the whole pass's summary.
|
||||
std::string LensLayer(std::string_view lensFile, std::string_view charterText, std::string_view dispositionsBlock, std::string_view app = {},
|
||||
std::string_view wholeSummary = {}, std::string_view db = {});
|
||||
// The scope of one pass: a whole pass's units, a slot's question, or a
|
||||
// block's unit text.
|
||||
std::string Tail(const Json& job, const Json& units, std::span<const std::string> lines, std::string_view wholeSummary = {});
|
||||
std::string BudgetLine(const Json& job);
|
||||
// The appended system prompt of a pass: the common charter text, the run
|
||||
// layer and the lens layer, byte-identical for every pass of a lens in a
|
||||
// run.
|
||||
std::string SystemPrompt(std::string_view systemText, std::string_view run, std::string_view lens);
|
||||
// The stdin user message of a pass: only what differs between passes. A
|
||||
// null job adds no budget line.
|
||||
std::string UserPrompt(std::string_view tail, const Json& job = nullptr, std::string_view stack = {});
|
||||
}
|
||||
32
interfaces/Crafter.Review-Schema.cppm
Normal file
32
interfaces/Crafter.Review-Schema.cppm
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
export module Crafter.Review:Schema;
|
||||
import std;
|
||||
import :Json;
|
||||
|
||||
// The structured output a pass returns: FINDING, NOTE, COVERAGE and the
|
||||
// verdict. One block schema and one whole schema per run, the union of every
|
||||
// lens's vocabulary, so the tool block ahead of the user message is
|
||||
// byte-identical across lenses and the prompt cache is shared. The bytes are
|
||||
// fixed: SchemaJson() is the schema in DumpCompactSorted's form, and the
|
||||
// goldens hold the descriptions.
|
||||
export namespace Crafter::Review::Schema {
|
||||
constexpr std::array<std::string_view, 10> NoteKinds = {
|
||||
"reserve-answer", "invariant", "pre-existing", "residual-risk", "hold", "info", "immaterial",
|
||||
"send-time-check", "refutation-verified", "unverified",
|
||||
};
|
||||
constexpr std::array<std::string_view, 4> CoverageVerdicts = {"clear", "defect", "immaterial", "n/a"};
|
||||
constexpr std::array<std::string_view, 1> CoverageBlocking = {"defect"};
|
||||
constexpr std::array<std::string_view, 2> CoverageUnowned = {"n/a", "MISSING"};
|
||||
constexpr std::array<std::string_view, 2> VerdictEnum = {"FIX FIRST", "SEND-READY"};
|
||||
|
||||
Json Finding();
|
||||
Json Note();
|
||||
Json Coverage();
|
||||
Json BlockSchema();
|
||||
Json WholeSchema();
|
||||
// The schema of `kind` ("block", anything else the whole one) as it goes
|
||||
// to claude.
|
||||
std::string SchemaJson(std::string_view kind);
|
||||
}
|
||||
73
interfaces/Crafter.Review-State.cppm
Normal file
73
interfaces/Crafter.Review-State.cppm
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
export module Crafter.Review:State;
|
||||
import std;
|
||||
import :Json;
|
||||
|
||||
// unit-state.json (per-unit reuse), the run lock, the usage totals and the
|
||||
// stdout summary. unit-state.json is merged on write, because drivers share
|
||||
// one gate-state dir, and one full run per hash holds the hash's lock: every
|
||||
// driver on the machine reads and writes these, so the files, the locks and
|
||||
// the layout do not change.
|
||||
export namespace Crafter::Review::State {
|
||||
// {version: 1, entries: {}, personaPass: null}
|
||||
Json EmptyState();
|
||||
// The file as JSON, whatever its shape, or the empty state when it is
|
||||
// missing or does not parse.
|
||||
Json LoadState(const std::filesystem::path& path);
|
||||
// This run's stores and drops applied to the state on disk — pure. Drops
|
||||
// win over sets; an entry of `sets` that is not a whole pass gets its
|
||||
// clean-read count from the FILE's previous entry (or the one-read
|
||||
// synthesis of an entry stored before the count was kept) plus `read`
|
||||
// unless that (hash, round) is counted already. Garbage on disk is the
|
||||
// empty state. `sets` is an object; a null `read` counts nothing.
|
||||
Json MergeState(const Json& disk, const Json& sets, std::span<const std::string> drops = {}, const Json& read = nullptr);
|
||||
// The whole state written as it is, through `<path>.tmp`.
|
||||
void SaveWhole(const std::filesystem::path& path, const Json& state);
|
||||
// Merge-on-write: the file re-read under `<path>.lock`, this run's deltas
|
||||
// applied (MergeState), written atomically; returns the merged state. Two
|
||||
// drivers on one gate-state dir would otherwise overwrite each other's
|
||||
// stores, and a --no-reuse run would wipe the file from its empty object.
|
||||
Json SaveState(const std::filesystem::path& path, const Json& sets, std::span<const std::string> drops = {}, const Json& read = nullptr);
|
||||
|
||||
// ---- one full run per hash -------------------------------------------------
|
||||
// Another full run holds this hash's lock; what() names the holder.
|
||||
class RunLocked : public std::runtime_error {
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
std::filesystem::path LockPath(const std::filesystem::path& gateDir, std::string_view mode, std::string_view hash8);
|
||||
// The holder's JSON, {} when there is none or it does not parse.
|
||||
Json ReadLockInfo(const std::filesystem::path& path);
|
||||
std::string Describe(const Json& info);
|
||||
|
||||
// An exclusive, non-blocking flock on gate-state/.run-<mode>-<hash8>.lock,
|
||||
// kept for the life of the process: the kernel releases it when the
|
||||
// holder dies, and no child inherits it. The file is never unlinked: an
|
||||
// unlink and re-create would hand two drivers one hash.
|
||||
class RunLock {
|
||||
public:
|
||||
// Throws RunLocked naming the holder.
|
||||
RunLock(const std::filesystem::path& gateDir, std::string_view mode, std::string_view hash8);
|
||||
~RunLock();
|
||||
RunLock(const RunLock&) = delete;
|
||||
RunLock& operator=(const RunLock&) = delete;
|
||||
// The holder's identity, once it knows its run number: one JSON
|
||||
// document, truncated and rewritten.
|
||||
void WriteInfo(const Json& info);
|
||||
|
||||
private:
|
||||
std::int32_t fd = -1;
|
||||
};
|
||||
// Who holds this hash's run lock ({} while the holder has not written
|
||||
// itself yet), nullopt when nobody does: a test or dry run takes no lock
|
||||
// but numbers its scratch dir past a live holder.
|
||||
std::optional<Json> LockHolder(const std::filesystem::path& gateDir, std::string_view mode, std::string_view hash8);
|
||||
std::int64_t NextRunNumber(const std::filesystem::path& gateDir, std::string_view mode, std::string_view hash8);
|
||||
|
||||
// The usage of a set of pass records, summed in the order they come.
|
||||
Json UsageTotals(std::span<const Json> records);
|
||||
// The stdout summary of a run's dump.
|
||||
std::string Summary(const Json& dump);
|
||||
}
|
||||
40
interfaces/Crafter.Review-Stream.cppm
Normal file
40
interfaces/Crafter.Review-Stream.cppm
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
export module Crafter.Review:Stream;
|
||||
import std;
|
||||
import :Json;
|
||||
|
||||
// The claude CLI's stream-json, parsed, and a pass's status classified from
|
||||
// it. The patterns are hand-written scanners, case-insensitive with Unicode
|
||||
// folding (the Kelvin sign is a k) and Unicode-aware where \b and \s are.
|
||||
//
|
||||
// The stream is split into lines at \n only: the CLI writes U+2028 and \x1c
|
||||
// raw inside a JSON string, and a line cut there would be lost.
|
||||
export namespace Crafter::Review::Stream {
|
||||
// A Bash call of the database helper, alone or in a compound command:
|
||||
// this binary's `<exe> sql`, with any path before it.
|
||||
bool IsSqlCall(std::string_view command);
|
||||
// The helper's ANSWERED line at the start of a line of the result.
|
||||
bool HasAnsweredLine(std::string_view text);
|
||||
// LIMIT_RE, AUTH_RE, CREDIT_RE: a rate limit, an authentication failure,
|
||||
// an organization out of API credit.
|
||||
bool LimitText(std::string_view text);
|
||||
bool AuthText(std::string_view text);
|
||||
bool CreditText(std::string_view text);
|
||||
|
||||
// One pass over the NDJSON: the last result line, the last
|
||||
// rate_limit_event, the first session_id, usage totals, the first turn's
|
||||
// usage, tool-call forensics, system/init, each under its own key.
|
||||
Json ParseStream(std::string_view text);
|
||||
// structured_output, else the result text parsed as JSON, a
|
||||
// double-encoded string decoded; nullopt without a findings array.
|
||||
std::optional<Json> StructuredOutput(const Json& result);
|
||||
// ok | limit | credits | auth | timeout | error, and why.
|
||||
std::pair<std::string, std::string> Classify(const Json& parsed, std::optional<std::int32_t> exitCode, std::string_view stderrText,
|
||||
bool timedOut = false);
|
||||
// {window: {utilization, resetsAt}} from a rate_limit_info: the CLI
|
||||
// reports a fraction (2026-09-17: five_hour 0.2), the pool thresholds are
|
||||
// percents.
|
||||
Json Windows(const Json& rate);
|
||||
}
|
||||
|
|
@ -33,6 +33,19 @@ export namespace Crafter::Review::Dyn {
|
|||
// The code points from start to end: a negative index counts from the
|
||||
// end, and both are clipped to the text.
|
||||
std::string Slice(std::string_view text, std::int64_t start, std::optional<std::int64_t> end = std::nullopt);
|
||||
// The code point at byte `at` and the bytes it takes: U+FFFD and the
|
||||
// length of the invalid sequence where the bytes are no UTF-8.
|
||||
std::pair<char32_t, std::size_t> CodePointAt(std::string_view text, std::size_t at);
|
||||
// [start, end) of a slice of `size` items: a negative index counts from
|
||||
// the end, and both are clipped to the sequence.
|
||||
std::pair<std::size_t, std::size_t> SliceBounds(std::size_t size, std::int64_t start, std::optional<std::int64_t> end = std::nullopt);
|
||||
|
||||
// ---- regex classes, for the hand-written scanners -----------------------
|
||||
// The bytes of the run at `at` of regex \d (Unicode decimal digits), \s
|
||||
// (Unicode whitespace) or \S characters.
|
||||
std::size_t DecimalRun(std::string_view text, std::size_t at);
|
||||
std::size_t SpaceRun(std::string_view text, std::size_t at);
|
||||
std::size_t NonSpaceRun(std::string_view text, std::size_t at);
|
||||
|
||||
// ---- characters ----------------------------------------------------------
|
||||
bool IsSpace(char32_t c);
|
||||
|
|
@ -41,6 +54,8 @@ export namespace Crafter::Review::Dyn {
|
|||
// The digit's value, -1 for a character that is not a decimal digit.
|
||||
std::int32_t DecimalValue(char32_t c);
|
||||
bool IsPrintable(char32_t c);
|
||||
// regex \w: what \b takes for a word character.
|
||||
bool IsWord(char32_t c);
|
||||
// The lowercase ASCII letter `c` matches case-insensitively, or '\0':
|
||||
// ASCII letters, and the four code points outside ASCII that fold onto
|
||||
// one (İ ı → i, ſ → s, K → k).
|
||||
|
|
@ -70,14 +85,27 @@ export namespace Crafter::Review::Dyn {
|
|||
std::size_t Count(std::string_view text, std::string_view sub);
|
||||
std::string Replace(std::string_view text, std::string_view from, std::string_view to);
|
||||
std::string Join(std::span<const std::string> parts, std::string_view separator);
|
||||
// f'{text:<width}' / text.ljust(width): padded with spaces to `width`
|
||||
// code points, where std::format would count display columns.
|
||||
std::string LJust(std::string_view text, std::size_t width);
|
||||
|
||||
// ---- reading text files ----------------------------------------------------
|
||||
// A text file, read whole: strict UTF-8 — an invalid byte throws
|
||||
// std::runtime_error naming the file and the offset — and universal
|
||||
// newlines.
|
||||
// A text file, read whole: strict UTF-8 — invalid bytes throw Error
|
||||
// ("UnicodeDecodeError") with Utf8DecodeError()'s message — and universal
|
||||
// newlines. A file that cannot be opened throws Error ("FileNotFoundError").
|
||||
std::string ReadText(const std::filesystem::path& path);
|
||||
// \r\n and a lone \r become \n, as text mode reads them.
|
||||
std::string UniversalNewlines(std::string_view text);
|
||||
// The message for bytes that are not UTF-8 — "'utf-8' codec can't decode
|
||||
// byte 0xff in position 3: invalid start byte" — nullopt for valid UTF-8.
|
||||
std::optional<std::string> Utf8DecodeError(std::string_view bytes);
|
||||
// The message for text that is no JSON document, with the scanner's
|
||||
// reason and position ("Expecting ',' delimiter: line 3 column 5 (char
|
||||
// 21)"); nullopt for a document LoadJsonFile() takes.
|
||||
std::optional<std::string> JsonDecodeError(std::string_view text);
|
||||
// A JSON file: read as text, then parsed. Throws Error with the message a
|
||||
// "not JSON (...)" problem carries: the decode error, or the JSON error.
|
||||
Json LoadJsonFile(const std::filesystem::path& path);
|
||||
|
||||
// ---- numbers ------------------------------------------------------------------
|
||||
// A float as its shortest digits that round-trip; an exponent below 1e-4
|
||||
|
|
@ -127,6 +155,9 @@ export namespace Crafter::Review::Dyn {
|
|||
// std::invalid_argument with a ValueError message for unbalanced or
|
||||
// invalid brackets.
|
||||
SplitUrl UrlSplit(std::string_view url);
|
||||
// The five parts joined back into a URL: an empty query or fragment gets
|
||||
// no '?' or '#'.
|
||||
std::string UrlUnsplit(std::string_view scheme, std::string_view netloc, std::string_view path, std::string_view query, std::string_view fragment);
|
||||
// unquote(text): %XX sequences decoded as UTF-8, bad bytes replaced.
|
||||
std::string Unquote(std::string_view text);
|
||||
|
||||
|
|
@ -152,4 +183,63 @@ export namespace Crafter::Review::Dyn {
|
|||
// ---- errno texts -------------------------------------------------------------------------
|
||||
// "[Errno 111] Connection refused": an errno as a message prints it.
|
||||
std::string ErrnoText(std::int32_t code);
|
||||
|
||||
// ---- exceptions ------------------------------------------------------------------------------
|
||||
// Data the code does not expect — a key that is absent, a value of the
|
||||
// wrong type, a lookup in something that is no object — throws Error
|
||||
// named for the case (KeyError, TypeError, AttributeError): a caller
|
||||
// catches the names it expects, and anything else fails loudly.
|
||||
class Error : public std::runtime_error {
|
||||
public:
|
||||
Error(std::string_view kind, const std::string& message);
|
||||
const std::string& Kind() const;
|
||||
|
||||
private:
|
||||
std::string kind;
|
||||
};
|
||||
|
||||
// ---- JSON values ---------------------------------------------------------------------------------
|
||||
// The driver's data — units, jobs, results, dispositions, the config, the
|
||||
// claude stream — stays JSON from end to end, so every key keeps its place
|
||||
// and its unknown neighbours. These are the operations on it.
|
||||
//
|
||||
// Truthiness: null, false, 0, 0.0, "", [] and {} are false.
|
||||
bool Truthy(const Json& value);
|
||||
// `value or fallback`. A reference to one of the two, so neither may be a
|
||||
// temporary: name the fallback, or use EmptyDict()/EmptyList().
|
||||
const Json& Or(const Json& value, const Json& fallback);
|
||||
const Json& Or(Json&& value, const Json& fallback) = delete;
|
||||
const Json& Or(const Json& value, Json&& fallback) = delete;
|
||||
// The shared {} and [] that `x or {}` and `x or []` fall back to.
|
||||
const Json& EmptyDict();
|
||||
const Json& EmptyList();
|
||||
// Whether an object has the key; false for anything that is no object.
|
||||
bool Has(const Json& dict, std::string_view key);
|
||||
// The key's value, null when it is absent; Error (AttributeError) on
|
||||
// something that is no object.
|
||||
const Json& Get(const Json& dict, std::string_view key);
|
||||
// The key's value; Error (KeyError) when it is absent.
|
||||
const Json& At(const Json& dict, std::string_view key);
|
||||
// The type name messages use: dict, list, str, int, float, bool, NoneType.
|
||||
std::string_view TypeName(const Json& value);
|
||||
// An integer, a bool included.
|
||||
bool IsInt(const Json& value);
|
||||
// An integer or a float, a bool included.
|
||||
bool IsNumber(const Json& value);
|
||||
// A string as it is, anything else as ReprValue() prints it.
|
||||
std::string Str(const Json& value);
|
||||
// Str() of the value, or of the fallback when the value is falsy.
|
||||
std::string StrOr(const Json& value, std::string_view fallback);
|
||||
// a == b: numbers by value (True == 1 == 1.0), dicts whatever their key
|
||||
// order, a str never equal to a number.
|
||||
bool Equal(const Json& a, const Json& b);
|
||||
// Membership: an element of a list, a key of an object, a substring of a
|
||||
// string (Error when the item is no string there).
|
||||
bool In(const Json& item, const Json& container);
|
||||
// a + b on numbers: int + int stays an int, a float makes a float, a bool
|
||||
// counts as 0 or 1; Error (TypeError) for anything else.
|
||||
Json Add(const Json& a, const Json& b);
|
||||
// A total of floats by Neumaier's compensated summation, so it comes out
|
||||
// to the same last bit as the goldens record it.
|
||||
double Sum(std::span<const double> values);
|
||||
}
|
||||
|
|
|
|||
44
interfaces/Crafter.Review-Tools.cppm
Normal file
44
interfaces/Crafter.Review-Tools.cppm
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
export module Crafter.Review:Tools;
|
||||
import std;
|
||||
|
||||
// The tool lists every pass is started with: what is denied whatever the
|
||||
// allowlist says, the read-only git verbs, the base allowlist, and the
|
||||
// Playwright MCP server's tools by name. A leaf of its own: the config checks
|
||||
// its extra tools against DENY and the pool builds every command line from
|
||||
// these, so with the lists in either of them the two would import each
|
||||
// other.
|
||||
export namespace Crafter::Review::Tools {
|
||||
// The Playwright MCP server, as --mcp-config names it.
|
||||
constexpr std::string_view McpServer = "playwright";
|
||||
constexpr std::array<std::string_view, 24> McpTools = {
|
||||
"browser_navigate", "browser_navigate_back", "browser_snapshot", "browser_click", "browser_type", "browser_fill_form",
|
||||
"browser_select_option", "browser_press_key", "browser_hover", "browser_drag", "browser_drop", "browser_handle_dialog",
|
||||
"browser_wait_for", "browser_take_screenshot", "browser_console_messages", "browser_network_requests", "browser_network_request",
|
||||
"browser_evaluate", "browser_tabs", "browser_resize", "browser_close", "browser_find", "browser_emulate_media", "browser_file_upload",
|
||||
};
|
||||
// browser_run_code_unsafe runs arbitrary Node code in the server's
|
||||
// process.
|
||||
constexpr std::array<std::string_view, 1> McpDenied = {"browser_run_code_unsafe"};
|
||||
|
||||
// mcp__playwright__<tool> for each.
|
||||
std::vector<std::string> McpToolNames();
|
||||
std::vector<std::string> McpDeniedToolNames();
|
||||
|
||||
// Never, whatever the allowlist says: writes, the network beyond the
|
||||
// browser, the agent tools, package managers, stacks, a database client
|
||||
// that commits (psql: the supabase environment's helper is the one way
|
||||
// in, every call rolled back).
|
||||
const std::vector<std::string>& Deny();
|
||||
constexpr std::array<std::string_view, 22> GitReadOnly = {
|
||||
"diff", "log", "show", "grep", "blame", "ls-tree", "ls-files", "cat-file", "rev-parse", "rev-list", "hash-object", "describe",
|
||||
"merge-base", "name-rev", "shortlog", "for-each-ref", "branch", "tag", "status", "worktree list", "remote", "count-objects",
|
||||
};
|
||||
const std::vector<std::string>& BaseAllow();
|
||||
|
||||
// Does Deny() override this allowlist entry? `Bash(psql -h x:*)` falls
|
||||
// under `Bash(psql:*)`, `Bash(psqlx:*)` does not.
|
||||
bool Denied(std::string_view tool);
|
||||
}
|
||||
|
|
@ -6,7 +6,8 @@
|
|||
//
|
||||
// The parts of the Unicode Character Database the driver's output depends
|
||||
// on: whitespace, line breaks, decimal digits, lowercase and uppercase
|
||||
// mappings outside ASCII, and which other code points match an ASCII letter
|
||||
// mappings outside ASCII, what a regular expression's \w counts as a word
|
||||
// character, and which other code points match an ASCII letter
|
||||
// case-insensitively. :Text builds its string functions on these, so a
|
||||
// unit, an excerpt or a key cut from text with an NBSP, a U+2028 or a Kelvin
|
||||
// sign in it always comes out the same.
|
||||
|
|
@ -3878,6 +3879,782 @@ export namespace Crafter::Review::Unicode {
|
|||
{0xe0100, 0xe01ef},
|
||||
}};
|
||||
|
||||
// Word characters (regex \w: alphanumeric or _): what \b takes for a
|
||||
// word character, as runs.
|
||||
constexpr std::array<Run, 771> Word = {{
|
||||
{0x30, 0x39},
|
||||
{0x41, 0x5a},
|
||||
{0x5f, 0x5f},
|
||||
{0x61, 0x7a},
|
||||
{0xaa, 0xaa},
|
||||
{0xb2, 0xb3},
|
||||
{0xb5, 0xb5},
|
||||
{0xb9, 0xba},
|
||||
{0xbc, 0xbe},
|
||||
{0xc0, 0xd6},
|
||||
{0xd8, 0xf6},
|
||||
{0xf8, 0x2c1},
|
||||
{0x2c6, 0x2d1},
|
||||
{0x2e0, 0x2e4},
|
||||
{0x2ec, 0x2ec},
|
||||
{0x2ee, 0x2ee},
|
||||
{0x370, 0x374},
|
||||
{0x376, 0x377},
|
||||
{0x37a, 0x37d},
|
||||
{0x37f, 0x37f},
|
||||
{0x386, 0x386},
|
||||
{0x388, 0x38a},
|
||||
{0x38c, 0x38c},
|
||||
{0x38e, 0x3a1},
|
||||
{0x3a3, 0x3f5},
|
||||
{0x3f7, 0x481},
|
||||
{0x48a, 0x52f},
|
||||
{0x531, 0x556},
|
||||
{0x559, 0x559},
|
||||
{0x560, 0x588},
|
||||
{0x5d0, 0x5ea},
|
||||
{0x5ef, 0x5f2},
|
||||
{0x620, 0x64a},
|
||||
{0x660, 0x669},
|
||||
{0x66e, 0x66f},
|
||||
{0x671, 0x6d3},
|
||||
{0x6d5, 0x6d5},
|
||||
{0x6e5, 0x6e6},
|
||||
{0x6ee, 0x6fc},
|
||||
{0x6ff, 0x6ff},
|
||||
{0x710, 0x710},
|
||||
{0x712, 0x72f},
|
||||
{0x74d, 0x7a5},
|
||||
{0x7b1, 0x7b1},
|
||||
{0x7c0, 0x7ea},
|
||||
{0x7f4, 0x7f5},
|
||||
{0x7fa, 0x7fa},
|
||||
{0x800, 0x815},
|
||||
{0x81a, 0x81a},
|
||||
{0x824, 0x824},
|
||||
{0x828, 0x828},
|
||||
{0x840, 0x858},
|
||||
{0x860, 0x86a},
|
||||
{0x870, 0x887},
|
||||
{0x889, 0x88e},
|
||||
{0x8a0, 0x8c9},
|
||||
{0x904, 0x939},
|
||||
{0x93d, 0x93d},
|
||||
{0x950, 0x950},
|
||||
{0x958, 0x961},
|
||||
{0x966, 0x96f},
|
||||
{0x971, 0x980},
|
||||
{0x985, 0x98c},
|
||||
{0x98f, 0x990},
|
||||
{0x993, 0x9a8},
|
||||
{0x9aa, 0x9b0},
|
||||
{0x9b2, 0x9b2},
|
||||
{0x9b6, 0x9b9},
|
||||
{0x9bd, 0x9bd},
|
||||
{0x9ce, 0x9ce},
|
||||
{0x9dc, 0x9dd},
|
||||
{0x9df, 0x9e1},
|
||||
{0x9e6, 0x9f1},
|
||||
{0x9f4, 0x9f9},
|
||||
{0x9fc, 0x9fc},
|
||||
{0xa05, 0xa0a},
|
||||
{0xa0f, 0xa10},
|
||||
{0xa13, 0xa28},
|
||||
{0xa2a, 0xa30},
|
||||
{0xa32, 0xa33},
|
||||
{0xa35, 0xa36},
|
||||
{0xa38, 0xa39},
|
||||
{0xa59, 0xa5c},
|
||||
{0xa5e, 0xa5e},
|
||||
{0xa66, 0xa6f},
|
||||
{0xa72, 0xa74},
|
||||
{0xa85, 0xa8d},
|
||||
{0xa8f, 0xa91},
|
||||
{0xa93, 0xaa8},
|
||||
{0xaaa, 0xab0},
|
||||
{0xab2, 0xab3},
|
||||
{0xab5, 0xab9},
|
||||
{0xabd, 0xabd},
|
||||
{0xad0, 0xad0},
|
||||
{0xae0, 0xae1},
|
||||
{0xae6, 0xaef},
|
||||
{0xaf9, 0xaf9},
|
||||
{0xb05, 0xb0c},
|
||||
{0xb0f, 0xb10},
|
||||
{0xb13, 0xb28},
|
||||
{0xb2a, 0xb30},
|
||||
{0xb32, 0xb33},
|
||||
{0xb35, 0xb39},
|
||||
{0xb3d, 0xb3d},
|
||||
{0xb5c, 0xb5d},
|
||||
{0xb5f, 0xb61},
|
||||
{0xb66, 0xb6f},
|
||||
{0xb71, 0xb77},
|
||||
{0xb83, 0xb83},
|
||||
{0xb85, 0xb8a},
|
||||
{0xb8e, 0xb90},
|
||||
{0xb92, 0xb95},
|
||||
{0xb99, 0xb9a},
|
||||
{0xb9c, 0xb9c},
|
||||
{0xb9e, 0xb9f},
|
||||
{0xba3, 0xba4},
|
||||
{0xba8, 0xbaa},
|
||||
{0xbae, 0xbb9},
|
||||
{0xbd0, 0xbd0},
|
||||
{0xbe6, 0xbf2},
|
||||
{0xc05, 0xc0c},
|
||||
{0xc0e, 0xc10},
|
||||
{0xc12, 0xc28},
|
||||
{0xc2a, 0xc39},
|
||||
{0xc3d, 0xc3d},
|
||||
{0xc58, 0xc5a},
|
||||
{0xc5d, 0xc5d},
|
||||
{0xc60, 0xc61},
|
||||
{0xc66, 0xc6f},
|
||||
{0xc78, 0xc7e},
|
||||
{0xc80, 0xc80},
|
||||
{0xc85, 0xc8c},
|
||||
{0xc8e, 0xc90},
|
||||
{0xc92, 0xca8},
|
||||
{0xcaa, 0xcb3},
|
||||
{0xcb5, 0xcb9},
|
||||
{0xcbd, 0xcbd},
|
||||
{0xcdd, 0xcde},
|
||||
{0xce0, 0xce1},
|
||||
{0xce6, 0xcef},
|
||||
{0xcf1, 0xcf2},
|
||||
{0xd04, 0xd0c},
|
||||
{0xd0e, 0xd10},
|
||||
{0xd12, 0xd3a},
|
||||
{0xd3d, 0xd3d},
|
||||
{0xd4e, 0xd4e},
|
||||
{0xd54, 0xd56},
|
||||
{0xd58, 0xd61},
|
||||
{0xd66, 0xd78},
|
||||
{0xd7a, 0xd7f},
|
||||
{0xd85, 0xd96},
|
||||
{0xd9a, 0xdb1},
|
||||
{0xdb3, 0xdbb},
|
||||
{0xdbd, 0xdbd},
|
||||
{0xdc0, 0xdc6},
|
||||
{0xde6, 0xdef},
|
||||
{0xe01, 0xe30},
|
||||
{0xe32, 0xe33},
|
||||
{0xe40, 0xe46},
|
||||
{0xe50, 0xe59},
|
||||
{0xe81, 0xe82},
|
||||
{0xe84, 0xe84},
|
||||
{0xe86, 0xe8a},
|
||||
{0xe8c, 0xea3},
|
||||
{0xea5, 0xea5},
|
||||
{0xea7, 0xeb0},
|
||||
{0xeb2, 0xeb3},
|
||||
{0xebd, 0xebd},
|
||||
{0xec0, 0xec4},
|
||||
{0xec6, 0xec6},
|
||||
{0xed0, 0xed9},
|
||||
{0xedc, 0xedf},
|
||||
{0xf00, 0xf00},
|
||||
{0xf20, 0xf33},
|
||||
{0xf40, 0xf47},
|
||||
{0xf49, 0xf6c},
|
||||
{0xf88, 0xf8c},
|
||||
{0x1000, 0x102a},
|
||||
{0x103f, 0x1049},
|
||||
{0x1050, 0x1055},
|
||||
{0x105a, 0x105d},
|
||||
{0x1061, 0x1061},
|
||||
{0x1065, 0x1066},
|
||||
{0x106e, 0x1070},
|
||||
{0x1075, 0x1081},
|
||||
{0x108e, 0x108e},
|
||||
{0x1090, 0x1099},
|
||||
{0x10a0, 0x10c5},
|
||||
{0x10c7, 0x10c7},
|
||||
{0x10cd, 0x10cd},
|
||||
{0x10d0, 0x10fa},
|
||||
{0x10fc, 0x1248},
|
||||
{0x124a, 0x124d},
|
||||
{0x1250, 0x1256},
|
||||
{0x1258, 0x1258},
|
||||
{0x125a, 0x125d},
|
||||
{0x1260, 0x1288},
|
||||
{0x128a, 0x128d},
|
||||
{0x1290, 0x12b0},
|
||||
{0x12b2, 0x12b5},
|
||||
{0x12b8, 0x12be},
|
||||
{0x12c0, 0x12c0},
|
||||
{0x12c2, 0x12c5},
|
||||
{0x12c8, 0x12d6},
|
||||
{0x12d8, 0x1310},
|
||||
{0x1312, 0x1315},
|
||||
{0x1318, 0x135a},
|
||||
{0x1369, 0x137c},
|
||||
{0x1380, 0x138f},
|
||||
{0x13a0, 0x13f5},
|
||||
{0x13f8, 0x13fd},
|
||||
{0x1401, 0x166c},
|
||||
{0x166f, 0x167f},
|
||||
{0x1681, 0x169a},
|
||||
{0x16a0, 0x16ea},
|
||||
{0x16ee, 0x16f8},
|
||||
{0x1700, 0x1711},
|
||||
{0x171f, 0x1731},
|
||||
{0x1740, 0x1751},
|
||||
{0x1760, 0x176c},
|
||||
{0x176e, 0x1770},
|
||||
{0x1780, 0x17b3},
|
||||
{0x17d7, 0x17d7},
|
||||
{0x17dc, 0x17dc},
|
||||
{0x17e0, 0x17e9},
|
||||
{0x17f0, 0x17f9},
|
||||
{0x1810, 0x1819},
|
||||
{0x1820, 0x1878},
|
||||
{0x1880, 0x1884},
|
||||
{0x1887, 0x18a8},
|
||||
{0x18aa, 0x18aa},
|
||||
{0x18b0, 0x18f5},
|
||||
{0x1900, 0x191e},
|
||||
{0x1946, 0x196d},
|
||||
{0x1970, 0x1974},
|
||||
{0x1980, 0x19ab},
|
||||
{0x19b0, 0x19c9},
|
||||
{0x19d0, 0x19da},
|
||||
{0x1a00, 0x1a16},
|
||||
{0x1a20, 0x1a54},
|
||||
{0x1a80, 0x1a89},
|
||||
{0x1a90, 0x1a99},
|
||||
{0x1aa7, 0x1aa7},
|
||||
{0x1b05, 0x1b33},
|
||||
{0x1b45, 0x1b4c},
|
||||
{0x1b50, 0x1b59},
|
||||
{0x1b83, 0x1ba0},
|
||||
{0x1bae, 0x1be5},
|
||||
{0x1c00, 0x1c23},
|
||||
{0x1c40, 0x1c49},
|
||||
{0x1c4d, 0x1c7d},
|
||||
{0x1c80, 0x1c8a},
|
||||
{0x1c90, 0x1cba},
|
||||
{0x1cbd, 0x1cbf},
|
||||
{0x1ce9, 0x1cec},
|
||||
{0x1cee, 0x1cf3},
|
||||
{0x1cf5, 0x1cf6},
|
||||
{0x1cfa, 0x1cfa},
|
||||
{0x1d00, 0x1dbf},
|
||||
{0x1e00, 0x1f15},
|
||||
{0x1f18, 0x1f1d},
|
||||
{0x1f20, 0x1f45},
|
||||
{0x1f48, 0x1f4d},
|
||||
{0x1f50, 0x1f57},
|
||||
{0x1f59, 0x1f59},
|
||||
{0x1f5b, 0x1f5b},
|
||||
{0x1f5d, 0x1f5d},
|
||||
{0x1f5f, 0x1f7d},
|
||||
{0x1f80, 0x1fb4},
|
||||
{0x1fb6, 0x1fbc},
|
||||
{0x1fbe, 0x1fbe},
|
||||
{0x1fc2, 0x1fc4},
|
||||
{0x1fc6, 0x1fcc},
|
||||
{0x1fd0, 0x1fd3},
|
||||
{0x1fd6, 0x1fdb},
|
||||
{0x1fe0, 0x1fec},
|
||||
{0x1ff2, 0x1ff4},
|
||||
{0x1ff6, 0x1ffc},
|
||||
{0x2070, 0x2071},
|
||||
{0x2074, 0x2079},
|
||||
{0x207f, 0x2089},
|
||||
{0x2090, 0x209c},
|
||||
{0x2102, 0x2102},
|
||||
{0x2107, 0x2107},
|
||||
{0x210a, 0x2113},
|
||||
{0x2115, 0x2115},
|
||||
{0x2119, 0x211d},
|
||||
{0x2124, 0x2124},
|
||||
{0x2126, 0x2126},
|
||||
{0x2128, 0x2128},
|
||||
{0x212a, 0x212d},
|
||||
{0x212f, 0x2139},
|
||||
{0x213c, 0x213f},
|
||||
{0x2145, 0x2149},
|
||||
{0x214e, 0x214e},
|
||||
{0x2150, 0x2189},
|
||||
{0x2460, 0x249b},
|
||||
{0x24ea, 0x24ff},
|
||||
{0x2776, 0x2793},
|
||||
{0x2c00, 0x2ce4},
|
||||
{0x2ceb, 0x2cee},
|
||||
{0x2cf2, 0x2cf3},
|
||||
{0x2cfd, 0x2cfd},
|
||||
{0x2d00, 0x2d25},
|
||||
{0x2d27, 0x2d27},
|
||||
{0x2d2d, 0x2d2d},
|
||||
{0x2d30, 0x2d67},
|
||||
{0x2d6f, 0x2d6f},
|
||||
{0x2d80, 0x2d96},
|
||||
{0x2da0, 0x2da6},
|
||||
{0x2da8, 0x2dae},
|
||||
{0x2db0, 0x2db6},
|
||||
{0x2db8, 0x2dbe},
|
||||
{0x2dc0, 0x2dc6},
|
||||
{0x2dc8, 0x2dce},
|
||||
{0x2dd0, 0x2dd6},
|
||||
{0x2dd8, 0x2dde},
|
||||
{0x2e2f, 0x2e2f},
|
||||
{0x3005, 0x3007},
|
||||
{0x3021, 0x3029},
|
||||
{0x3031, 0x3035},
|
||||
{0x3038, 0x303c},
|
||||
{0x3041, 0x3096},
|
||||
{0x309d, 0x309f},
|
||||
{0x30a1, 0x30fa},
|
||||
{0x30fc, 0x30ff},
|
||||
{0x3105, 0x312f},
|
||||
{0x3131, 0x318e},
|
||||
{0x3192, 0x3195},
|
||||
{0x31a0, 0x31bf},
|
||||
{0x31f0, 0x31ff},
|
||||
{0x3220, 0x3229},
|
||||
{0x3248, 0x324f},
|
||||
{0x3251, 0x325f},
|
||||
{0x3280, 0x3289},
|
||||
{0x32b1, 0x32bf},
|
||||
{0x3400, 0x4dbf},
|
||||
{0x4e00, 0xa48c},
|
||||
{0xa4d0, 0xa4fd},
|
||||
{0xa500, 0xa60c},
|
||||
{0xa610, 0xa62b},
|
||||
{0xa640, 0xa66e},
|
||||
{0xa67f, 0xa69d},
|
||||
{0xa6a0, 0xa6ef},
|
||||
{0xa717, 0xa71f},
|
||||
{0xa722, 0xa788},
|
||||
{0xa78b, 0xa7cd},
|
||||
{0xa7d0, 0xa7d1},
|
||||
{0xa7d3, 0xa7d3},
|
||||
{0xa7d5, 0xa7dc},
|
||||
{0xa7f2, 0xa801},
|
||||
{0xa803, 0xa805},
|
||||
{0xa807, 0xa80a},
|
||||
{0xa80c, 0xa822},
|
||||
{0xa830, 0xa835},
|
||||
{0xa840, 0xa873},
|
||||
{0xa882, 0xa8b3},
|
||||
{0xa8d0, 0xa8d9},
|
||||
{0xa8f2, 0xa8f7},
|
||||
{0xa8fb, 0xa8fb},
|
||||
{0xa8fd, 0xa8fe},
|
||||
{0xa900, 0xa925},
|
||||
{0xa930, 0xa946},
|
||||
{0xa960, 0xa97c},
|
||||
{0xa984, 0xa9b2},
|
||||
{0xa9cf, 0xa9d9},
|
||||
{0xa9e0, 0xa9e4},
|
||||
{0xa9e6, 0xa9fe},
|
||||
{0xaa00, 0xaa28},
|
||||
{0xaa40, 0xaa42},
|
||||
{0xaa44, 0xaa4b},
|
||||
{0xaa50, 0xaa59},
|
||||
{0xaa60, 0xaa76},
|
||||
{0xaa7a, 0xaa7a},
|
||||
{0xaa7e, 0xaaaf},
|
||||
{0xaab1, 0xaab1},
|
||||
{0xaab5, 0xaab6},
|
||||
{0xaab9, 0xaabd},
|
||||
{0xaac0, 0xaac0},
|
||||
{0xaac2, 0xaac2},
|
||||
{0xaadb, 0xaadd},
|
||||
{0xaae0, 0xaaea},
|
||||
{0xaaf2, 0xaaf4},
|
||||
{0xab01, 0xab06},
|
||||
{0xab09, 0xab0e},
|
||||
{0xab11, 0xab16},
|
||||
{0xab20, 0xab26},
|
||||
{0xab28, 0xab2e},
|
||||
{0xab30, 0xab5a},
|
||||
{0xab5c, 0xab69},
|
||||
{0xab70, 0xabe2},
|
||||
{0xabf0, 0xabf9},
|
||||
{0xac00, 0xd7a3},
|
||||
{0xd7b0, 0xd7c6},
|
||||
{0xd7cb, 0xd7fb},
|
||||
{0xf900, 0xfa6d},
|
||||
{0xfa70, 0xfad9},
|
||||
{0xfb00, 0xfb06},
|
||||
{0xfb13, 0xfb17},
|
||||
{0xfb1d, 0xfb1d},
|
||||
{0xfb1f, 0xfb28},
|
||||
{0xfb2a, 0xfb36},
|
||||
{0xfb38, 0xfb3c},
|
||||
{0xfb3e, 0xfb3e},
|
||||
{0xfb40, 0xfb41},
|
||||
{0xfb43, 0xfb44},
|
||||
{0xfb46, 0xfbb1},
|
||||
{0xfbd3, 0xfd3d},
|
||||
{0xfd50, 0xfd8f},
|
||||
{0xfd92, 0xfdc7},
|
||||
{0xfdf0, 0xfdfb},
|
||||
{0xfe70, 0xfe74},
|
||||
{0xfe76, 0xfefc},
|
||||
{0xff10, 0xff19},
|
||||
{0xff21, 0xff3a},
|
||||
{0xff41, 0xff5a},
|
||||
{0xff66, 0xffbe},
|
||||
{0xffc2, 0xffc7},
|
||||
{0xffca, 0xffcf},
|
||||
{0xffd2, 0xffd7},
|
||||
{0xffda, 0xffdc},
|
||||
{0x10000, 0x1000b},
|
||||
{0x1000d, 0x10026},
|
||||
{0x10028, 0x1003a},
|
||||
{0x1003c, 0x1003d},
|
||||
{0x1003f, 0x1004d},
|
||||
{0x10050, 0x1005d},
|
||||
{0x10080, 0x100fa},
|
||||
{0x10107, 0x10133},
|
||||
{0x10140, 0x10178},
|
||||
{0x1018a, 0x1018b},
|
||||
{0x10280, 0x1029c},
|
||||
{0x102a0, 0x102d0},
|
||||
{0x102e1, 0x102fb},
|
||||
{0x10300, 0x10323},
|
||||
{0x1032d, 0x1034a},
|
||||
{0x10350, 0x10375},
|
||||
{0x10380, 0x1039d},
|
||||
{0x103a0, 0x103c3},
|
||||
{0x103c8, 0x103cf},
|
||||
{0x103d1, 0x103d5},
|
||||
{0x10400, 0x1049d},
|
||||
{0x104a0, 0x104a9},
|
||||
{0x104b0, 0x104d3},
|
||||
{0x104d8, 0x104fb},
|
||||
{0x10500, 0x10527},
|
||||
{0x10530, 0x10563},
|
||||
{0x10570, 0x1057a},
|
||||
{0x1057c, 0x1058a},
|
||||
{0x1058c, 0x10592},
|
||||
{0x10594, 0x10595},
|
||||
{0x10597, 0x105a1},
|
||||
{0x105a3, 0x105b1},
|
||||
{0x105b3, 0x105b9},
|
||||
{0x105bb, 0x105bc},
|
||||
{0x105c0, 0x105f3},
|
||||
{0x10600, 0x10736},
|
||||
{0x10740, 0x10755},
|
||||
{0x10760, 0x10767},
|
||||
{0x10780, 0x10785},
|
||||
{0x10787, 0x107b0},
|
||||
{0x107b2, 0x107ba},
|
||||
{0x10800, 0x10805},
|
||||
{0x10808, 0x10808},
|
||||
{0x1080a, 0x10835},
|
||||
{0x10837, 0x10838},
|
||||
{0x1083c, 0x1083c},
|
||||
{0x1083f, 0x10855},
|
||||
{0x10858, 0x10876},
|
||||
{0x10879, 0x1089e},
|
||||
{0x108a7, 0x108af},
|
||||
{0x108e0, 0x108f2},
|
||||
{0x108f4, 0x108f5},
|
||||
{0x108fb, 0x1091b},
|
||||
{0x10920, 0x10939},
|
||||
{0x10980, 0x109b7},
|
||||
{0x109bc, 0x109cf},
|
||||
{0x109d2, 0x10a00},
|
||||
{0x10a10, 0x10a13},
|
||||
{0x10a15, 0x10a17},
|
||||
{0x10a19, 0x10a35},
|
||||
{0x10a40, 0x10a48},
|
||||
{0x10a60, 0x10a7e},
|
||||
{0x10a80, 0x10a9f},
|
||||
{0x10ac0, 0x10ac7},
|
||||
{0x10ac9, 0x10ae4},
|
||||
{0x10aeb, 0x10aef},
|
||||
{0x10b00, 0x10b35},
|
||||
{0x10b40, 0x10b55},
|
||||
{0x10b58, 0x10b72},
|
||||
{0x10b78, 0x10b91},
|
||||
{0x10ba9, 0x10baf},
|
||||
{0x10c00, 0x10c48},
|
||||
{0x10c80, 0x10cb2},
|
||||
{0x10cc0, 0x10cf2},
|
||||
{0x10cfa, 0x10d23},
|
||||
{0x10d30, 0x10d39},
|
||||
{0x10d40, 0x10d65},
|
||||
{0x10d6f, 0x10d85},
|
||||
{0x10e60, 0x10e7e},
|
||||
{0x10e80, 0x10ea9},
|
||||
{0x10eb0, 0x10eb1},
|
||||
{0x10ec2, 0x10ec4},
|
||||
{0x10f00, 0x10f27},
|
||||
{0x10f30, 0x10f45},
|
||||
{0x10f51, 0x10f54},
|
||||
{0x10f70, 0x10f81},
|
||||
{0x10fb0, 0x10fcb},
|
||||
{0x10fe0, 0x10ff6},
|
||||
{0x11003, 0x11037},
|
||||
{0x11052, 0x1106f},
|
||||
{0x11071, 0x11072},
|
||||
{0x11075, 0x11075},
|
||||
{0x11083, 0x110af},
|
||||
{0x110d0, 0x110e8},
|
||||
{0x110f0, 0x110f9},
|
||||
{0x11103, 0x11126},
|
||||
{0x11136, 0x1113f},
|
||||
{0x11144, 0x11144},
|
||||
{0x11147, 0x11147},
|
||||
{0x11150, 0x11172},
|
||||
{0x11176, 0x11176},
|
||||
{0x11183, 0x111b2},
|
||||
{0x111c1, 0x111c4},
|
||||
{0x111d0, 0x111da},
|
||||
{0x111dc, 0x111dc},
|
||||
{0x111e1, 0x111f4},
|
||||
{0x11200, 0x11211},
|
||||
{0x11213, 0x1122b},
|
||||
{0x1123f, 0x11240},
|
||||
{0x11280, 0x11286},
|
||||
{0x11288, 0x11288},
|
||||
{0x1128a, 0x1128d},
|
||||
{0x1128f, 0x1129d},
|
||||
{0x1129f, 0x112a8},
|
||||
{0x112b0, 0x112de},
|
||||
{0x112f0, 0x112f9},
|
||||
{0x11305, 0x1130c},
|
||||
{0x1130f, 0x11310},
|
||||
{0x11313, 0x11328},
|
||||
{0x1132a, 0x11330},
|
||||
{0x11332, 0x11333},
|
||||
{0x11335, 0x11339},
|
||||
{0x1133d, 0x1133d},
|
||||
{0x11350, 0x11350},
|
||||
{0x1135d, 0x11361},
|
||||
{0x11380, 0x11389},
|
||||
{0x1138b, 0x1138b},
|
||||
{0x1138e, 0x1138e},
|
||||
{0x11390, 0x113b5},
|
||||
{0x113b7, 0x113b7},
|
||||
{0x113d1, 0x113d1},
|
||||
{0x113d3, 0x113d3},
|
||||
{0x11400, 0x11434},
|
||||
{0x11447, 0x1144a},
|
||||
{0x11450, 0x11459},
|
||||
{0x1145f, 0x11461},
|
||||
{0x11480, 0x114af},
|
||||
{0x114c4, 0x114c5},
|
||||
{0x114c7, 0x114c7},
|
||||
{0x114d0, 0x114d9},
|
||||
{0x11580, 0x115ae},
|
||||
{0x115d8, 0x115db},
|
||||
{0x11600, 0x1162f},
|
||||
{0x11644, 0x11644},
|
||||
{0x11650, 0x11659},
|
||||
{0x11680, 0x116aa},
|
||||
{0x116b8, 0x116b8},
|
||||
{0x116c0, 0x116c9},
|
||||
{0x116d0, 0x116e3},
|
||||
{0x11700, 0x1171a},
|
||||
{0x11730, 0x1173b},
|
||||
{0x11740, 0x11746},
|
||||
{0x11800, 0x1182b},
|
||||
{0x118a0, 0x118f2},
|
||||
{0x118ff, 0x11906},
|
||||
{0x11909, 0x11909},
|
||||
{0x1190c, 0x11913},
|
||||
{0x11915, 0x11916},
|
||||
{0x11918, 0x1192f},
|
||||
{0x1193f, 0x1193f},
|
||||
{0x11941, 0x11941},
|
||||
{0x11950, 0x11959},
|
||||
{0x119a0, 0x119a7},
|
||||
{0x119aa, 0x119d0},
|
||||
{0x119e1, 0x119e1},
|
||||
{0x119e3, 0x119e3},
|
||||
{0x11a00, 0x11a00},
|
||||
{0x11a0b, 0x11a32},
|
||||
{0x11a3a, 0x11a3a},
|
||||
{0x11a50, 0x11a50},
|
||||
{0x11a5c, 0x11a89},
|
||||
{0x11a9d, 0x11a9d},
|
||||
{0x11ab0, 0x11af8},
|
||||
{0x11bc0, 0x11be0},
|
||||
{0x11bf0, 0x11bf9},
|
||||
{0x11c00, 0x11c08},
|
||||
{0x11c0a, 0x11c2e},
|
||||
{0x11c40, 0x11c40},
|
||||
{0x11c50, 0x11c6c},
|
||||
{0x11c72, 0x11c8f},
|
||||
{0x11d00, 0x11d06},
|
||||
{0x11d08, 0x11d09},
|
||||
{0x11d0b, 0x11d30},
|
||||
{0x11d46, 0x11d46},
|
||||
{0x11d50, 0x11d59},
|
||||
{0x11d60, 0x11d65},
|
||||
{0x11d67, 0x11d68},
|
||||
{0x11d6a, 0x11d89},
|
||||
{0x11d98, 0x11d98},
|
||||
{0x11da0, 0x11da9},
|
||||
{0x11ee0, 0x11ef2},
|
||||
{0x11f02, 0x11f02},
|
||||
{0x11f04, 0x11f10},
|
||||
{0x11f12, 0x11f33},
|
||||
{0x11f50, 0x11f59},
|
||||
{0x11fb0, 0x11fb0},
|
||||
{0x11fc0, 0x11fd4},
|
||||
{0x12000, 0x12399},
|
||||
{0x12400, 0x1246e},
|
||||
{0x12480, 0x12543},
|
||||
{0x12f90, 0x12ff0},
|
||||
{0x13000, 0x1342f},
|
||||
{0x13441, 0x13446},
|
||||
{0x13460, 0x143fa},
|
||||
{0x14400, 0x14646},
|
||||
{0x16100, 0x1611d},
|
||||
{0x16130, 0x16139},
|
||||
{0x16800, 0x16a38},
|
||||
{0x16a40, 0x16a5e},
|
||||
{0x16a60, 0x16a69},
|
||||
{0x16a70, 0x16abe},
|
||||
{0x16ac0, 0x16ac9},
|
||||
{0x16ad0, 0x16aed},
|
||||
{0x16b00, 0x16b2f},
|
||||
{0x16b40, 0x16b43},
|
||||
{0x16b50, 0x16b59},
|
||||
{0x16b5b, 0x16b61},
|
||||
{0x16b63, 0x16b77},
|
||||
{0x16b7d, 0x16b8f},
|
||||
{0x16d40, 0x16d6c},
|
||||
{0x16d70, 0x16d79},
|
||||
{0x16e40, 0x16e96},
|
||||
{0x16f00, 0x16f4a},
|
||||
{0x16f50, 0x16f50},
|
||||
{0x16f93, 0x16f9f},
|
||||
{0x16fe0, 0x16fe1},
|
||||
{0x16fe3, 0x16fe3},
|
||||
{0x17000, 0x187f7},
|
||||
{0x18800, 0x18cd5},
|
||||
{0x18cff, 0x18d08},
|
||||
{0x1aff0, 0x1aff3},
|
||||
{0x1aff5, 0x1affb},
|
||||
{0x1affd, 0x1affe},
|
||||
{0x1b000, 0x1b122},
|
||||
{0x1b132, 0x1b132},
|
||||
{0x1b150, 0x1b152},
|
||||
{0x1b155, 0x1b155},
|
||||
{0x1b164, 0x1b167},
|
||||
{0x1b170, 0x1b2fb},
|
||||
{0x1bc00, 0x1bc6a},
|
||||
{0x1bc70, 0x1bc7c},
|
||||
{0x1bc80, 0x1bc88},
|
||||
{0x1bc90, 0x1bc99},
|
||||
{0x1ccf0, 0x1ccf9},
|
||||
{0x1d2c0, 0x1d2d3},
|
||||
{0x1d2e0, 0x1d2f3},
|
||||
{0x1d360, 0x1d378},
|
||||
{0x1d400, 0x1d454},
|
||||
{0x1d456, 0x1d49c},
|
||||
{0x1d49e, 0x1d49f},
|
||||
{0x1d4a2, 0x1d4a2},
|
||||
{0x1d4a5, 0x1d4a6},
|
||||
{0x1d4a9, 0x1d4ac},
|
||||
{0x1d4ae, 0x1d4b9},
|
||||
{0x1d4bb, 0x1d4bb},
|
||||
{0x1d4bd, 0x1d4c3},
|
||||
{0x1d4c5, 0x1d505},
|
||||
{0x1d507, 0x1d50a},
|
||||
{0x1d50d, 0x1d514},
|
||||
{0x1d516, 0x1d51c},
|
||||
{0x1d51e, 0x1d539},
|
||||
{0x1d53b, 0x1d53e},
|
||||
{0x1d540, 0x1d544},
|
||||
{0x1d546, 0x1d546},
|
||||
{0x1d54a, 0x1d550},
|
||||
{0x1d552, 0x1d6a5},
|
||||
{0x1d6a8, 0x1d6c0},
|
||||
{0x1d6c2, 0x1d6da},
|
||||
{0x1d6dc, 0x1d6fa},
|
||||
{0x1d6fc, 0x1d714},
|
||||
{0x1d716, 0x1d734},
|
||||
{0x1d736, 0x1d74e},
|
||||
{0x1d750, 0x1d76e},
|
||||
{0x1d770, 0x1d788},
|
||||
{0x1d78a, 0x1d7a8},
|
||||
{0x1d7aa, 0x1d7c2},
|
||||
{0x1d7c4, 0x1d7cb},
|
||||
{0x1d7ce, 0x1d7ff},
|
||||
{0x1df00, 0x1df1e},
|
||||
{0x1df25, 0x1df2a},
|
||||
{0x1e030, 0x1e06d},
|
||||
{0x1e100, 0x1e12c},
|
||||
{0x1e137, 0x1e13d},
|
||||
{0x1e140, 0x1e149},
|
||||
{0x1e14e, 0x1e14e},
|
||||
{0x1e290, 0x1e2ad},
|
||||
{0x1e2c0, 0x1e2eb},
|
||||
{0x1e2f0, 0x1e2f9},
|
||||
{0x1e4d0, 0x1e4eb},
|
||||
{0x1e4f0, 0x1e4f9},
|
||||
{0x1e5d0, 0x1e5ed},
|
||||
{0x1e5f0, 0x1e5fa},
|
||||
{0x1e7e0, 0x1e7e6},
|
||||
{0x1e7e8, 0x1e7eb},
|
||||
{0x1e7ed, 0x1e7ee},
|
||||
{0x1e7f0, 0x1e7fe},
|
||||
{0x1e800, 0x1e8c4},
|
||||
{0x1e8c7, 0x1e8cf},
|
||||
{0x1e900, 0x1e943},
|
||||
{0x1e94b, 0x1e94b},
|
||||
{0x1e950, 0x1e959},
|
||||
{0x1ec71, 0x1ecab},
|
||||
{0x1ecad, 0x1ecaf},
|
||||
{0x1ecb1, 0x1ecb4},
|
||||
{0x1ed01, 0x1ed2d},
|
||||
{0x1ed2f, 0x1ed3d},
|
||||
{0x1ee00, 0x1ee03},
|
||||
{0x1ee05, 0x1ee1f},
|
||||
{0x1ee21, 0x1ee22},
|
||||
{0x1ee24, 0x1ee24},
|
||||
{0x1ee27, 0x1ee27},
|
||||
{0x1ee29, 0x1ee32},
|
||||
{0x1ee34, 0x1ee37},
|
||||
{0x1ee39, 0x1ee39},
|
||||
{0x1ee3b, 0x1ee3b},
|
||||
{0x1ee42, 0x1ee42},
|
||||
{0x1ee47, 0x1ee47},
|
||||
{0x1ee49, 0x1ee49},
|
||||
{0x1ee4b, 0x1ee4b},
|
||||
{0x1ee4d, 0x1ee4f},
|
||||
{0x1ee51, 0x1ee52},
|
||||
{0x1ee54, 0x1ee54},
|
||||
{0x1ee57, 0x1ee57},
|
||||
{0x1ee59, 0x1ee59},
|
||||
{0x1ee5b, 0x1ee5b},
|
||||
{0x1ee5d, 0x1ee5d},
|
||||
{0x1ee5f, 0x1ee5f},
|
||||
{0x1ee61, 0x1ee62},
|
||||
{0x1ee64, 0x1ee64},
|
||||
{0x1ee67, 0x1ee6a},
|
||||
{0x1ee6c, 0x1ee72},
|
||||
{0x1ee74, 0x1ee77},
|
||||
{0x1ee79, 0x1ee7c},
|
||||
{0x1ee7e, 0x1ee7e},
|
||||
{0x1ee80, 0x1ee89},
|
||||
{0x1ee8b, 0x1ee9b},
|
||||
{0x1eea1, 0x1eea3},
|
||||
{0x1eea5, 0x1eea9},
|
||||
{0x1eeab, 0x1eebb},
|
||||
{0x1f100, 0x1f10c},
|
||||
{0x1fbf0, 0x1fbf9},
|
||||
{0x20000, 0x2a6df},
|
||||
{0x2a700, 0x2b739},
|
||||
{0x2b740, 0x2b81d},
|
||||
{0x2b820, 0x2cea1},
|
||||
{0x2ceb0, 0x2ebe0},
|
||||
{0x2ebf0, 0x2ee5d},
|
||||
{0x2f800, 0x2fa1d},
|
||||
{0x30000, 0x3134a},
|
||||
{0x31350, 0x323af},
|
||||
}};
|
||||
|
||||
// re.IGNORECASE: code points outside ASCII that match an ASCII
|
||||
// letter of a pattern, sorted.
|
||||
constexpr std::array<Fold, 4> IgnoreCase = {{
|
||||
|
|
|
|||
|
|
@ -10,4 +10,16 @@ export import :Crypto;
|
|||
export import :Platform;
|
||||
export import :Process;
|
||||
export import :Core;
|
||||
export import :Schema;
|
||||
export import :Tools;
|
||||
export import :Plan;
|
||||
export import :Artifact;
|
||||
export import :Inventory;
|
||||
export import :Dispositions;
|
||||
export import :State;
|
||||
export import :Normalize;
|
||||
export import :PgWire;
|
||||
export import :Stream;
|
||||
export import :Prompt;
|
||||
export import :Config;
|
||||
export import :Driver;
|
||||
|
|
|
|||
Loading…
Reference in a new issue