Crafter.Review/interfaces/Crafter.Review-Plan.cppm
Jorijn van der Graaf febbfc3fcc
feat(lenses): the commit-truth lens, repo-facts, as Gate 4
Every claim in every commit message, and in the documentation the range
adds or changes, proven or gone. The lens's block kinds are text, doc and
slot: a message unit is keyed on its text, the base and the code up to its
commit (MessageDeps); a documentation unit on its file, its normalised
text, the base and the code of the whole range (DocKeyParts); a diffstat on
its text alone; whole:order and whole:absent are asked in slot passes of
its own, never stored. A documentation file's diff header and a part the
commit writes nothing in are a mechanical n/a. Budgets 60/40 for a text or
doc block, 50/35 for a slot; one read. Gate 4, after the proofs, so the
messages and documents are read once the code they describe has settled;
the owner's gate is 5.

The goldens that follow from the lens table are derived by
tests/Goldens/derive.py, which asserts that the recorded table still
reproduces the recorded entries before it writes.

Proven, with the opt-in commit after this one: the suite (26 passed); a dry
run of a scratch range (2 commits, a README with sections) by the previous
binary and this one with the three static lenses on and the rest off, 25
prompts and 4 system files byte for byte; with repo-facts on as well,
those files unchanged and 10 passes and 1 system file added.
The lens's firing proof in this pipeline is owed (the owner's go).
2026-10-31 20:08:26 +01:00

294 lines
17 KiB
C++

// 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 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 (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"};
constexpr std::array<std::string_view, 3> TruthBlocks = {"text", "doc", "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.
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, 5> 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"},
}};
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 = {});
// (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<std::string, std::vector<std::string>> 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<std::string> MessageDeps(std::string_view unitId, const CommitCode& cc);
std::optional<std::string> DocReuseKey(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> TextReuseKey(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 job's unit kind (doc, text), else 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);
}