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).
This commit is contained in:
Jorijn van der Graaf 2026-10-31 20:08:26 +01:00
commit febbfc3fcc
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
19 changed files with 2564 additions and 646 deletions

View file

@ -163,6 +163,16 @@ namespace Crafter::Review::Plan {
bool Contains(std::span<const std::string> list, std::string_view item) {
return std::ranges::contains(list, item);
}
// any(l[:1] in ('+', '-') and l[1:].strip() for l in code_norm(text).split('\n')):
// does the commit add or remove a line with something on it in this
// part of a documentation hunk?
bool WritesIn(std::string_view text) {
for (const std::string& line : Dyn::Split(CodeNorm(text), "\n")) {
if ((line.starts_with('+') || line.starts_with('-')) && !Dyn::Strip(line.substr(1)).empty()) return true;
}
return false;
}
}
std::vector<ModelTier>& Models() {
@ -193,6 +203,7 @@ namespace Crafter::Review::Plan {
{"Gate 1 — Approach", {"repo-better-solution.md"}},
{"Gate 2 — Functional", {FunctionalLens}},
{"Gate 3 — Proof", {"repo-bug-prover.md", "repo-security.md"}},
{"Gate 4 — Commit truth", {FactsLens}},
};
return gates;
}
@ -213,8 +224,11 @@ namespace Crafter::Review::Plan {
Budget BudgetFor(std::string_view kind, std::string_view unitKind, std::string_view lensFile) {
if (kind == "whole") return lensFile == FunctionalLens ? Budget{220, 120} : Budget{180, 105};
if (unitKind != "code") throw Dyn::Error("KeyError", Dyn::Repr(std::format("block:{}", unitKind)));
return lensFile == FunctionalLens ? Budget{90, 60} : Budget{60, 40};
if (kind == "slot") return {50, 35};
if (unitKind == "code") return lensFile == FunctionalLens ? Budget{90, 60} : Budget{60, 40};
if (unitKind == "doc") return {60, 40};
if (unitKind == "text") return lensFile == FactsLens ? Budget{60, 40} : Budget{50, 30};
throw Dyn::Error("KeyError", Dyn::Repr(std::format("block:{}", unitKind)));
}
std::string_view DefaultEffort() {
@ -251,8 +265,9 @@ namespace Crafter::Review::Plan {
return path && IsDocPath(*path);
}
// The lens is not consulted (yet): no lens of this pipeline reads a
// documentation file since the facts lens was removed.
// Every lens of this pipeline is a repo lens: the units of a documentation
// file are "doc" for each of them - the commit-truth lens reads them, the
// code lenses do not.
std::string_view KindFor(std::string_view, const Json& unit) {
const std::string_view kind = UnitKind(unit);
return kind == "code" && IsDocUnit(Dyn::At(unit, "id").get_ref<const std::string&>()) ? "doc" : kind;
@ -361,6 +376,38 @@ namespace Crafter::Review::Plan {
if (!unitsFilter.empty() && !Contains(unitsFilter, id)) continue;
const std::string text = UnitText(lines, unit);
const std::optional<std::string> path = UnitPath(id);
if (kind == "slot") {
// A lens with no whole pass asks each of its own slots in a
// pass of its own; another lens's whole slot is not its.
if (!std::ranges::contains(lens.slots, std::string_view(id))) continue;
const Budget budget = BudgetFor("slot", "slot", lensFile);
plan.blocks.push_back({
{"label", std::format("{}:slot:{}", name, id)},
{"lens", name},
{"lensFile", lensFile},
{"kind", "slot"},
{"unit", id},
{"unitKind", "slot"},
{"expectedIds", {id}},
{"unitSha", Sha(text)},
{"model", ModelFor(lens.model)},
{"maxTurns", budget.maxTurns},
{"timeoutMin", budget.timeoutMin},
});
continue;
}
if (kind == "doc" && id.ends_with("#hdr")) {
// The diff header of a documentation file names the file, its
// blobs and its mode: no sentence, no claim.
plan.blocks.push_back(Mechanical(name, lensFile, unit, text, "mechanical: diff header of a documentation file"));
continue;
}
if (kind == "doc" && !WritesIn(text)) {
// A part of a hunk that holds only context (the lines above a
// section the commit adds): nothing was written here.
plan.blocks.push_back(Mechanical(name, lensFile, unit, text, "mechanical: context only - the commit adds and removes no line in this part"));
continue;
}
if (!lens.perCommitSlots && (id.ends_with(":outside") || id.ends_with(":absent"))) {
// The functional lens judges adjacent flows and what is missing
// in its whole pass, not per commit.
@ -570,9 +617,54 @@ namespace Crafter::Review::Plan {
std::format("functional:{}", ident));
}
std::pair<std::string, std::vector<std::string>> DocKeyParts(std::string_view unitId, const CommitCode& cc) {
const std::size_t colon = unitId.find(':');
if (colon == std::string_view::npos) throw Dyn::Error("ValueError", "not enough values to unpack (expected 2, got 1)");
const std::string_view rest = unitId.substr(colon + 1);
const std::size_t hash = rest.rfind('#');
return {std::format("doc:{}", hash == std::string_view::npos ? rest : rest.substr(0, hash)), {std::format("base={}", cc.base), std::format("range={}", cc.range)}};
}
std::vector<std::string> MessageDeps(std::string_view unitId, const CommitCode& cc) {
// ^p(\d+):(subject|msg)$; `$` also matches before a final newline.
const std::optional<std::size_t> prefix = PkPrefix(unitId);
if (!prefix) return {};
std::string_view name = unitId.substr(*prefix);
if (name.ends_with('\n')) name.remove_suffix(1);
if (name != "subject" && name != "msg") return {};
const std::string_view number = unitId.substr(1, *prefix - 2);
const std::optional<std::int64_t> k = Dyn::ParseInt(number);
if (!k) throw Dyn::Error("ValueError", Dyn::IntError(number));
const auto upto = cc.upto.find(*k);
if (upto == cc.upto.end()) throw Dyn::Error("KeyError", std::to_string(*k));
return {std::format("base={}", cc.base), std::format("upto={}", upto->second)};
}
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) {
auto [ident, deps] = DocKeyParts(Dyn::At(job, "unit").get_ref<const std::string&>(), cc);
std::ranges::move(EnvDeps(lensFile, environment), std::back_inserter(deps));
return ReuseKey(job, lensFile, CodeNorm(unitText), deps, {inForceIds.begin(), inForceIds.end()}, charterSha, promptVersion, ident);
}
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) {
std::vector<std::string> deps = MessageDeps(Dyn::At(job, "unit").get_ref<const std::string&>(), cc);
std::ranges::move(EnvDeps(lensFile, environment), std::back_inserter(deps));
return ReuseKey(job, lensFile, unitText, deps, {inForceIds.begin(), inForceIds.end()}, charterSha, promptVersion);
}
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 slot pass and a whole pass have no block key (ReuseKey's own
// rule, before any id is parsed: a `whole:*` id has no commit number).
if (Dyn::At(job, "kind") != "block") return std::nullopt;
const Json& unitKind = Dyn::Get(job, "unitKind");
if (unitKind == "doc") return DocReuseKey(job, lensFile, unitText, cc, inForceIds, charterSha, promptVersion, environment);
if (unitKind == "text") return TextReuseKey(job, lensFile, unitText, cc, inForceIds, charterSha, promptVersion, environment);
if (LensOf(lensFile).key == "functional") {
return FunctionalReuseKey(job, lensFile, unitText, cc, inForceIds, charterSha, promptVersion, environment);
}
@ -657,7 +749,9 @@ namespace Crafter::Review::Plan {
const auto layer = lensLayerBytes.find(lens);
const double lensTokens = static_cast<double>(layer == lensLayerBytes.end() ? 3000 : layer->second) / tpb;
const double tail = static_cast<double>(tailBytes) / tpb;
const std::int64_t turns = kind == "whole" ? 40 : (Dyn::At(job, "lensFile") == FunctionalLens ? 25 : 10);
const Json& unitKind = Dyn::Get(job, "unitKind");
const std::int64_t turns = kind == "whole" ? 40 : kind == "slot" ? 10 : Dyn::At(job, "lensFile") == FunctionalLens ? 25
: unitKind == "code" ? 10 : (Dyn::At(job, "lensFile") == FactsLens ? 12 : 8);
if (seen.insert({lens, kind}).second) cacheWrite += prefix + lensTokens;
cacheWrite += tail + static_cast<double>(turns * 3000);
std::vector<double> reads;