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:
parent
61ee4602f4
commit
febbfc3fcc
19 changed files with 2564 additions and 646 deletions
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue