// 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 { // The artifact kinds. A mode (the config's modes.) is a kind plus // the lenses it runs and the effort it runs at; a kind is a mode of its // own name. constexpr std::array Kinds = {"reply", "series", "commits"}; bool IsKernelKind(std::string_view kind); constexpr std::string_view FunctionalLens = "repo-functional.md"; // The persona lens of the kernel modes: one instance per reviewer // (--reviewers), its whole pass gating its blocks, in the gate after // every other. constexpr std::string_view PersonaLens = "maintainer-persona.md"; constexpr std::string_view PersonaGate = "Gate 4 — Personas"; // The commit-truth lens (2026-10-31, the fp6 pipeline's repo-facts): the // messages and the documentation of the range. constexpr std::string_view FactsLens = "repo-facts.md"; // The reasoning effort when --effort is not given: the kernel modes // stay at max; the push gates run at xhigh (2026-10-02). constexpr std::string_view Effort = "xhigh"; constexpr std::string_view KernelEffort = "max"; // The efforts a mode may run at (modes..effort). constexpr std::array Efforts = {"low", "medium", "high", "xhigh", "max"}; // 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". A reply has no code: its paragraphs are // read once. constexpr std::int32_t Reads = 3; constexpr std::int32_t ReplyReads = 1; std::int32_t ReadsFor(std::string_view kind); // 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& Models(); // MODELS[tier]; Dyn::Error (KeyError) for an unknown tier. const std::string& ModelFor(std::string_view tier); constexpr std::array ApproachSlots = {"whole:order", "whole:absent"}; constexpr std::array BehaviourSlots = {"whole:behaviour"}; constexpr std::array CodeBlocks = {"code"}; constexpr std::array TruthBlocks = {"text", "doc", "slot"}; constexpr std::array TextSlotBlocks = {"text", "slot"}; constexpr std::array TextCodeBlocks = {"text", "code"}; constexpr std::array CodeSlotBlocks = {"code", "slot"}; // 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`. A lens with no whole pass and "slot" among its block kinds // asks each slot named in `slots` in a slot pass of its own (the // commit-truth lens: whole:order and whole:absent). 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 code 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); a text // or doc block is keyed by its kind whatever the lens's key (TextReuseKey, // DocReuseKey). 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. // kernel: a lens of the kernel modes - its whole pass and its slot // passes cover every whole:* slot of the artifact, a hunk of a // documentation file is code to it, and its keys and prompts are the // kernel pipeline's. struct Lens { std::string_view file; std::int32_t gate; std::string_view whole; std::span slots; std::span block; std::string_view model; bool reuse; bool wholeReuse; bool reads; std::string_view key; bool empirical; bool perCommitSlots; std::string_view env; bool kernel = false; }; // In the order the dump and the prompts list them: the push gates' // lenses, then the kernel modes'. constexpr std::array 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"}, // The commit-truth lens (2026-10-31): every claim in every commit // message, and in the documentation the range adds or changes, proven // or gone. The text and doc units and the two whole slots, in a gate // of its own after the proofs, so the messages and documents are read // once the code they describe has settled. One read: its units are // prose, keyed on their text and the code they describe. {FactsLens, 4, "", ApproachSlots, TruthBlocks, "opus", true, false, false, "text", false, true, "none"}, // The kernel modes (reply, series). whole: "first" judges the series // as an approach, "all" the consistency of the whole, "persona" reads // it as one reviewer. better-solution's code blocks are reused and // need the clean reads like the other code lenses (2026-10-06.7); its // whole pass has no key: it runs on every hash against what is on // the list today. A persona's block passes are reusable, keyed with // the persona's name (2026-10-06.6); its whole pass runs every round. {"better-solution.md", 1, "first", {}, CodeBlocks, "fable", true, false, true, "code", false, true, "none", true}, {"code-quality.md", 2, "", {}, CodeBlocks, "opus", true, false, true, "code", false, true, "none", true}, {"minimalism.md", 2, "", {}, CodeSlotBlocks, "opus", true, false, true, "code", false, true, "none", true}, {"bug-prover.md", 2, "", {}, CodeBlocks, "opus", true, false, true, "code", false, true, "none", true}, {"facts.md", 3, "", {}, TextSlotBlocks, "opus", true, false, false, "code", false, true, "none", true}, {"consistency.md", 3, "all", {}, TextCodeBlocks, "opus", false, false, false, "code", false, true, "none", true}, {"process-docs.md", 3, "", {}, TextSlotBlocks, "opus", true, false, false, "code", false, true, "none", true}, {"etiquette.md", 3, "", {}, TextSlotBlocks, "opus", true, false, false, "code", false, true, "none", true}, {"conciseness.md", 3, "", {}, TextSlotBlocks, "fable", true, false, false, "code", false, true, "none", true}, {PersonaLens, 4, "persona", {}, TextCodeBlocks, "fable", true, false, false, "code", false, true, "none", true}, }}; 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 lenses; }; // The gates of a mode: reply runs Gate 3 alone (facts, etiquette, // consistency, conciseness); series Gate 1 (better-solution), Gate 2 // (code-quality, minimalism, bug-prover), Gate 3 (facts, consistency, // process-docs, etiquette, conciseness); the persona gate follows them // when reviewers are given. commits: the push gates. const std::vector& Gates(std::string_view kind = "commits"); // What a lens's processes get beyond the read-only tools, by the // consumer's lenses..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 BrowserTools = {"Bash(curl:*)"}; struct Environment { std::string_view name; std::string_view mcp; std::span tools; bool app; bool db; std::int32_t memMb; }; constexpr std::array 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 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(); std::string_view DefaultEffort(std::string_view kind); // A text unit that is a bare section header of the camera review // (`camss-milos`, `ov13b10-of`, `milos-dts-camss`, `across the three // series`): nothing to judge, a mechanical n/a for every text lens. bool IsBareHeader(const Json& unit, std::string_view text); // "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 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 NotReviewed(const Json& units, std::span lines, std::span lensFiles, std::span exclude = {}); // The first glob of functional.exclude the path matches (`**/` prefixes // match at the root too). std::optional ExcludedBy(std::string_view path, std::span globs); std::string UnitText(std::span lines, const Json& unit); // sha256 of the text's UTF-8, in hex. std::string Sha(std::string_view text); // lens_file.replace('.md', ''); `persona:` for a persona's // instance. std::string LensName(std::string_view lensFile, std::string_view persona = {}); // Every lens of the gates, and the persona lens in the kernel modes. std::vector KindLenses(std::string_view kind = "commits"); // 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 GatesFor(std::span only = {}, std::span without = {}, std::span off = {}); std::vector GatesFor(std::string_view kind, std::span only, std::span without, std::span off); // The whole job (null when the lens has none) and the block jobs. struct LensPlan { Json whole; std::vector blocks; }; // unitsFilter: --units (empty: every unit); exclude: the functional // lens's path globs; persona: the reviewer a persona instance reads as; // excludeUnits: the unit ids a persona's whole pass marked not-mine. LensPlan PlanLens(std::string_view lensFile, const Json& units, std::span lines, std::span unitsFilter = {}, std::span exclude = {}, std::string_view persona = {}, std::span excludeUnits = {}); // 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; a persona's block carries the // persona's name. nullopt: the job is not reusable. std::optional ReuseKey(const Json& job, std::string_view lensFile, std::string_view unitText, std::vector deps, std::vector 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 docs; std::string message; }; // docFiles false (a kernel series): nothing is documentation - a .md or // .rst hunk is part of the code of its patch, as the kernel lenses read // it. PatchParts PartsOf(std::string_view patchText, bool docFiles = true); // What the code keys of a range are built from, by commit number K. struct CommitCode { std::map body; std::map code; std::map> docs; std::map message; std::string range; std::map upto; std::string base; bool kernel = false; }; // kernel: the series mode's code - nothing is documentation, and the // message of each patch is kept for its `absent` slot (a kernel reviewer // looks there for a trailer or a test statement). CommitCode CommitCodeOf(const std::map& patches, bool kernel = false); // (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 (a kernel // series: its message). Every code unit depends on the base. std::pair> CodeKeyParts(std::string_view unitId, const CommitCode& cc); std::optional CodeReuseKey(const Json& job, std::string_view lensFile, std::string_view unitText, const CommitCode& cc, std::span inForceIds, std::string_view charterSha, std::int32_t promptVersion, std::string_view environment = {}); std::optional FunctionalReuseKey(const Json& job, std::string_view lensFile, std::string_view unitText, const CommitCode& cc, std::span inForceIds, std::string_view charterSha, std::int32_t promptVersion, std::string_view environment = {}); // (ident, deps) of a doc unit - a hunk of a documentation file, or one // heading-delimited part of it: keyed on its file and its own normalised // text, like a code hunk, and on the base and the code of the whole // range - a sentence must hold for the code as its commit leaves it AND // as the range leaves it, so any code change in the range asks it again // (2026-10-06). std::pair> DocKeyParts(std::string_view unitId, const CommitCode& cc); // What a message unit (pK:subject, pK:msg) depends on beside its own text: // the base and the code of the range up to and including its commit - a // message describes its commit's change and the state it changes, so a // fix to that code asks the message again. Empty for every other unit (a // diffstat states only its own numbers). std::vector MessageDeps(std::string_view unitId, const CommitCode& cc); std::optional DocReuseKey(const Json& job, std::string_view lensFile, std::string_view unitText, const CommitCode& cc, std::span inForceIds, std::string_view charterSha, std::int32_t promptVersion, std::string_view environment = {}); std::optional TextReuseKey(const Json& job, std::string_view lensFile, std::string_view unitText, const CommitCode& cc, std::span inForceIds, std::string_view charterSha, std::int32_t promptVersion, std::string_view environment = {}); // By the job's unit kind (doc, text), else by the lens's `key` kind. std::optional BlockReuseKey(const Json& job, std::string_view lensFile, std::string_view unitText, const CommitCode& cc, std::span 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 WholeCodeKey(const Json& job, std::string_view lensFile, const CommitCode& cc, std::span inForceIds, std::string_view charterSha, std::int32_t promptVersion, std::string_view environment = {}); // The dependencies of a text unit's verdict in the kernel modes: the // URLs of every [n] it cites, the anchor text when it carries a dated or // versioned statement (otherwise every anchor-date bump would invalidate // every unit), and every R-unit it names verbatim. refs: the parsed // reference table ({"n": url} rows). std::vector TextDeps(std::string_view unitText, const Json& units, std::span lines, std::span> refs, std::string_view anchorText); // 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 jobs, std::int64_t prefixBytes, const std::map& lensLayerBytes, std::int64_t tailBytes = 1800, double tpb = 3.7, std::int64_t sysTokens = 32000, std::int64_t outPerTurn = 1200); }