// 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& 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"}; // 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 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; }; // In the order the dump and the prompts list them. 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"}, }}; 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; }; const std::vector& Gates(); // 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(); // "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', '') std::string LensName(std::string_view lensFile); // Every lens of the gates. std::vector 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 GatesFor(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. LensPlan PlanLens(std::string_view lensFile, const Json& units, std::span lines, std::span unitsFilter = {}, std::span 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 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; }; PatchParts PartsOf(std::string_view patchText); // 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; }; CommitCode CommitCodeOf(const std::map& 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> 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 = {}); // 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 = {}); // 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); }