Crafter.Review/implementations/Crafter.Review-Plan.cpp
Jorijn van der Graaf d5e54ffeb3
feat(config): modes - a mode is an artifact kind, the lenses it runs and its effort
The config defines the modes (modes.<name>): the kind of artifact the mode
reviews - reply, series or commits, the three shipped partitions; a kind
is a mode of its own name - the lenses it runs, in its kind's gate order,
and the effort it runs at, else the kind's default (max for a reply or a
series, xhigh for a commit range). `run --mode` names any of them, the
dump records mode and kind, and everything that decided by the mode's
name - the partition and hash, the prompts, the schemas, the disposition
and reuse-key rules, what the CLI requires - decides by the kind.

Every lens stays off until a mode lists it; lenses.<name>.run is retired
with a warning that says so, and a lens of another kind, a name that is
no lens, a lens listed twice and an unknown effort are config problems.
The defaults run no lens; the template ships the three kinds as modes
with every lens of their kind at the kind's effort, to trim (the owner's
ruling of 2026-11-01), and init says so as it writes it - a kept config
is the repository's own, and only one that lists no lens gets a step.

The readers of the retired key follow: the CI smoke config, the shipped
skill, the procedure (its description of the config as well as its
mechanics), the manifest template, the run's messages and the comments
that named the three modes. The run lock, the run numbers and the dump
names are the kind's, so one full run per hash holds across the modes
of a kind and their rounds count on together; the dump says which mode
ran. The database broker starts for a lens the mode runs, as every other
reader of the lenses off reads them. A named mode runs through the CLI
in the kernel-modes test.
2026-11-02 00:29:00 +01:00

935 lines
46 KiB
C++

// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
module Crafter.Review:Plan_impl;
import :Plan;
import :Json;
import :Text;
import :Crypto;
import std;
namespace Crafter::Review::Plan {
namespace {
// The bytes of `p\d+:` at the start of a unit id, where \d is any
// Unicode decimal digit, as re.match reads it.
std::optional<std::size_t> PkPrefix(std::string_view id) {
if (!id.starts_with('p')) return std::nullopt;
const std::size_t digits = Dyn::DecimalRun(id, 1);
if (digits == 0 || 1 + digits >= id.size() || id[1 + digits] != ':') return std::nullopt;
return 2 + digits;
}
// group(1) of ^p\d+:(.+)#[^#]+$: the path between the prefix and the
// last '#', which must have something after it. `.` stops at a
// newline; [^#] does not.
std::optional<std::string_view> FileOfUnit(std::string_view id) {
const std::optional<std::size_t> prefix = PkPrefix(id);
if (!prefix) return std::nullopt;
const std::string_view rest = id.substr(*prefix);
const std::size_t hash = rest.rfind('#');
if (hash == std::string_view::npos || hash == 0 || hash + 1 >= rest.size()) return std::nullopt;
const std::string_view path = rest.substr(0, hash);
if (path.contains('\n')) return std::nullopt;
return path;
}
bool IsHex(std::string_view text) {
return !text.empty() && std::ranges::all_of(text, [](char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); });
}
// ^\d+\.\d+
bool StartsWithVersion(std::string_view line) {
const std::size_t major = Dyn::DecimalRun(line, 0);
if (major == 0 || major >= line.size() || line[major] != '.') return false;
return Dyn::DecimalRun(line, major + 1) > 0;
}
// ^(base-commit|prerequisite-patch-id): [0-9a-f]+$
bool IsTailLine(std::string_view line) {
for (const std::string_view head : {std::string_view("base-commit: "), std::string_view("prerequisite-patch-id: ")}) {
if (line.starts_with(head) && IsHex(line.substr(head.size()))) return true;
}
return false;
}
// ^index [0-9a-f]+\.\.[0-9a-f]+( \d+)?$ -> group(1) ("" when absent)
std::optional<std::string_view> IndexMode(std::string_view line) {
if (!line.starts_with("index ")) return std::nullopt;
const std::string_view rest = line.substr(6);
const std::size_t dots = rest.find("..");
if (dots == std::string_view::npos || !IsHex(rest.substr(0, dots))) return std::nullopt;
const std::string_view after = rest.substr(dots + 2);
const std::size_t space = after.find(' ');
if (space == std::string_view::npos) {
if (!IsHex(after)) return std::nullopt;
return std::string_view();
}
if (!IsHex(after.substr(0, space))) return std::nullopt;
const std::string_view mode = after.substr(space);
if (mode.size() < 2 || Dyn::DecimalRun(mode, 1) != mode.size() - 1) return std::nullopt;
return mode;
}
// \d+(?:,\d+)? at `at`: the bytes it takes, 0 for no match.
std::size_t LineRange(std::string_view text, std::size_t at) {
const std::size_t start = Dyn::DecimalRun(text, at);
if (start == 0) return 0;
std::size_t end = at + start;
if (end < text.size() && text[end] == ',') {
const std::size_t count = Dyn::DecimalRun(text, end + 1);
if (count > 0) end += 1 + count;
}
return end - at;
}
// ^@@ -\d+(?:,\d+)? \+\d+(?:,\d+)? @@(.*)$ -> group(1)
std::optional<std::string_view> HunkTail(std::string_view line) {
if (!line.starts_with("@@ -")) return std::nullopt;
std::size_t at = 4;
const std::size_t old = LineRange(line, at);
if (old == 0) return std::nullopt;
at += old;
if (!line.substr(at).starts_with(" +")) return std::nullopt;
at += 2;
const std::size_t now = LineRange(line, at);
if (now == 0) return std::nullopt;
at += now;
if (!line.substr(at).starts_with(" @@")) return std::nullopt;
return line.substr(at + 3);
}
// Every line start that `head` follows: where the text is cut.
// Position 0 is one when the text starts with it, which makes the
// first piece empty.
std::vector<std::string> SplitBeforeLines(std::string_view text, std::string_view head) {
std::vector<std::size_t> cuts;
for (std::size_t at = 0; at <= text.size();) {
if (text.substr(at).starts_with(head)) cuts.push_back(at);
const std::size_t newline = text.find('\n', at);
if (newline == std::string_view::npos) break;
at = newline + 1;
}
std::vector<std::string> pieces;
std::size_t from = 0;
for (const std::size_t cut : cuts) {
pieces.emplace_back(text.substr(from, cut - from));
from = cut;
}
pieces.emplace_back(text.substr(from));
return pieces;
}
// re.sub(r'^Subject: (\[[^\]]*\]\s*)*', 'Subject: ', line): the
// `[PATCH n/m]` tags after `Subject: ` dropped, and the space after
// each.
std::string StripSubjectTags(std::string_view line) {
if (!line.starts_with("Subject: ")) return std::string(line);
std::size_t at = 9;
while (at < line.size() && line[at] == '[') {
const std::size_t close = line.find(']', at + 1);
if (close == std::string_view::npos) break;
at = close + 1;
at += Dyn::SpaceRun(line, at);
}
return std::format("Subject: {}", line.substr(at));
}
// ^base-commit: [0-9a-f]{40}$
bool IsBaseCommit(std::string_view line) {
constexpr std::string_view Head = "base-commit: ";
return line.size() == Head.size() + 40 && line.starts_with(Head) && IsHex(line.substr(Head.size()));
}
std::int64_t IntItem(const Json& range, std::size_t index) {
const Json& value = range.at(index);
if (value.is_boolean()) return value.get<bool>() ? 1 : 0;
return value.get<std::int64_t>();
}
Json Mechanical(std::string_view name, std::string_view lensFile, const Json& unit, std::string_view text, std::string_view basis) {
const std::string id = Dyn::At(unit, "id").get<std::string>();
return {
{"label", std::format("{}:na:{}", name, id)},
{"lens", name},
{"lensFile", lensFile},
{"kind", "auto-na"},
{"unit", id},
{"expectedIds", {id}},
{"unitSha", Sha(text)},
{"basis", basis},
};
}
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() {
static std::vector<ModelTier> models = {{"opus", "claude-opus-5-5"}, {"fable", "claude-opus-5-5"}};
return models;
}
const std::string& ModelFor(std::string_view tier) {
for (const ModelTier& model : Models()) {
if (model.tier == tier) return model.id;
}
throw Dyn::Error("KeyError", Dyn::Repr(tier));
}
bool IsKernelKind(std::string_view kind) {
return kind == "reply" || kind == "series";
}
std::int32_t ReadsFor(std::string_view kind) {
return kind == "reply" ? ReplyReads : Reads;
}
bool IsLens(std::string_view file) {
return std::ranges::any_of(Lenses, [&](const Lens& lens) { return lens.file == file; });
}
const Lens& LensOf(std::string_view file) {
for (const Lens& lens : Lenses) {
if (lens.file == file) return lens;
}
throw Dyn::Error("KeyError", Dyn::Repr(file));
}
const std::vector<Gate>& Gates(std::string_view kind) {
static const std::vector<Gate> commits = {
{"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}},
};
static const std::vector<Gate> reply = {
{"Gate 3 — Artifact truth", {"facts.md", "etiquette.md", "consistency.md", "conciseness.md"}},
};
static const std::vector<Gate> series = {
{"Gate 1 — Structure", {"better-solution.md"}},
{"Gate 2 — Code", {"code-quality.md", "minimalism.md", "bug-prover.md"}},
{"Gate 3 — Artifact truth", {"facts.md", "consistency.md", "process-docs.md", "etiquette.md", "conciseness.md"}},
};
if (kind == "reply") return reply;
if (kind == "series") return series;
if (kind == "commits") return commits;
throw Dyn::Error("KeyError", Dyn::Repr(kind));
}
const Environment* FindEnvironment(std::string_view name) {
for (const Environment& environment : Environments) {
if (environment.name == name) return &environment;
}
return nullptr;
}
std::vector<std::string> EnvDeps(std::string_view lensFile, std::string_view environment) {
const std::string_view fallback = LensOf(lensFile).env;
const std::string_view used = environment.empty() ? fallback : environment;
if (used == fallback) return {};
return {std::format("env={}", used)};
}
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 (kind == "slot") return {50, 35};
if (unitKind == "code") return lensFile == FunctionalLens ? Budget{90, 60} : Budget{60, 40};
if (unitKind == "doc") return {60, 40};
// The facts lens of the kernel modes gets the most (2026-09-28: a
// facts pass on the densest unit used 58 of 60 calls and left one
// claim unverified).
if (unitKind == "text") return lensFile == "facts.md" ? Budget{80, 50} : lensFile == FactsLens ? Budget{60, 40} : Budget{50, 30};
throw Dyn::Error("KeyError", Dyn::Repr(std::format("block:{}", unitKind)));
}
std::string_view DefaultEffort() {
return Effort;
}
std::string_view DefaultEffort(std::string_view kind) {
return IsKernelKind(kind) ? KernelEffort : Effort;
}
bool IsBareHeader(const Json& unit, std::string_view text) {
constexpr std::array<std::string_view, 4> Bare = {"camss-milos", "ov13b10-of", "milos-dts-camss", "across the three series"};
const std::string& id = Dyn::At(unit, "id").get_ref<const std::string&>();
return id.ends_with(":header") && std::ranges::contains(Bare, std::string_view(Dyn::Lower(Dyn::Strip(text))));
}
std::string_view UnitKindOf(std::string_view unitId) {
if (unitId.starts_with("whole:")) return "slot";
const std::optional<std::size_t> prefix = PkPrefix(unitId);
if (!prefix) return "text";
// `$` also matches before a final newline.
std::string_view name = unitId.substr(*prefix);
if (name.ends_with('\n')) name.remove_suffix(1);
if (name == "subject" || name == "msg" || name == "trailers" || name == "diffstat") return "text";
return "code";
}
std::string_view UnitKind(const Json& unit) {
return UnitKindOf(Dyn::At(unit, "id").get_ref<const std::string&>());
}
bool IsDocPath(std::string_view path) {
auto endsWithMd = [](std::string_view text) {
const std::u32string decoded = Dyn::Decode(text);
if (decoded.size() < 3) return false;
const std::size_t n = decoded.size();
return decoded[n - 3] == '.' && Dyn::IgnoreCaseLetter(decoded[n - 2]) == 'm' && Dyn::IgnoreCaseLetter(decoded[n - 1]) == 'd';
};
return endsWithMd(path) || (path.ends_with('\n') && endsWithMd(path.substr(0, path.size() - 1)));
}
bool IsDocUnit(std::string_view unitId) {
const std::optional<std::string_view> path = FileOfUnit(unitId);
return path && IsDocPath(*path);
}
// For the repo lenses the units of a documentation file are "doc" - the
// commit-truth lens reads them, the code lenses do not; for the kernel
// lenses every unit of a diff stays code.
std::string_view KindFor(std::string_view lensFile, const Json& unit) {
const std::string_view kind = UnitKind(unit);
if (IsLens(lensFile) && LensOf(lensFile).kernel) return kind;
return kind == "code" && IsDocUnit(Dyn::At(unit, "id").get_ref<const std::string&>()) ? "doc" : kind;
}
std::optional<std::string> UnitPath(std::string_view unitId) {
const std::optional<std::string_view> path = FileOfUnit(unitId);
if (!path) return std::nullopt;
return std::string(*path);
}
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) {
std::set<std::string> covered;
for (const std::string& lensFile : lensFiles) {
const LensPlan plan = PlanLens(lensFile, units, lines, {}, exclude);
if (Dyn::Truthy(plan.whole)) {
for (const Json& id : Dyn::At(plan.whole, "expectedIds")) covered.insert(id.get<std::string>());
}
for (const Json& block : plan.blocks) {
if (Dyn::At(block, "kind") != "auto-na") covered.insert(Dyn::At(block, "unit").get<std::string>());
}
}
std::vector<std::string> out;
for (const Json& unit : units) {
const std::string& id = Dyn::At(unit, "id").get_ref<const std::string&>();
if (!covered.contains(id)) out.push_back(id);
}
return out;
}
std::optional<std::string> ExcludedBy(std::string_view path, std::span<const std::string> globs) {
for (const std::string& glob : globs) {
if (Dyn::FnMatch(path, glob) || (glob.starts_with("**/") && Dyn::FnMatch(path, std::string_view(glob).substr(3)))) return glob;
}
return std::nullopt;
}
std::string UnitText(std::span<const std::string> lines, const Json& unit) {
const Json& range = Dyn::At(unit, "lines");
const std::int64_t a = IntItem(range, 0);
const std::int64_t b = IntItem(range, 1);
if (a == 0) return "";
const auto [from, to] = Dyn::SliceBounds(lines.size(), a - 1, b);
return Dyn::Join(lines.subspan(from, to - from), "\n");
}
std::string Sha(std::string_view text) {
return Sha256Hex(text);
}
std::string LensName(std::string_view lensFile, std::string_view persona) {
if (!persona.empty()) return std::format("persona:{}", persona);
return Dyn::Replace(lensFile, ".md", "");
}
std::vector<std::string> KindLenses(std::string_view kind) {
std::vector<std::string> out;
for (const Gate& gate : Gates(kind)) {
for (const std::string_view lens : gate.lenses) out.emplace_back(lens);
}
if (IsKernelKind(kind)) out.emplace_back(PersonaLens);
return out;
}
std::vector<Gate> GatesFor(std::span<const std::string> only, std::span<const std::string> without, std::span<const std::string> off) {
return GatesFor("commits", only, without, off);
}
std::vector<Gate> GatesFor(std::string_view kind, std::span<const std::string> only, std::span<const std::string> without, std::span<const std::string> off) {
std::vector<Gate> out;
for (const Gate& gate : Gates(kind)) {
Gate kept{gate.title, {}};
for (const std::string_view lens : gate.lenses) {
if (!only.empty() && !Contains(only, lens)) continue;
if (Contains(without, lens) || Contains(off, lens)) continue;
kept.lenses.push_back(lens);
}
if (!kept.lenses.empty()) out.push_back(std::move(kept));
}
return out;
}
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, std::string_view persona, std::span<const std::string> excludeUnits) {
const Lens& lens = LensOf(lensFile);
const std::string name = LensName(lensFile, persona);
// A persona's jobs carry its name, after the lens file.
auto stamp = [&](Json job) {
if (persona.empty()) return job;
Json out = Json::object();
for (auto it = job.begin(); it != job.end(); ++it) {
out[it.key()] = it.value();
if (it.key() == "lensFile") out["persona"] = persona;
}
return out;
};
LensPlan plan;
if (!lens.whole.empty()) {
const Budget budget = BudgetFor("whole", "", lensFile);
Json slots = Json::array();
for (const Json& unit : units) {
const std::string& id = Dyn::At(unit, "id").get_ref<const std::string&>();
// A kernel lens's whole pass covers every whole:* slot of the
// artifact; a repo lens's the slots the table names.
if (UnitKind(unit) == "slot" && (lens.kernel || std::ranges::contains(lens.slots, std::string_view(id)))) slots.push_back(id);
}
plan.whole = stamp({
{"label", std::format("{}:whole", name)},
{"lens", name},
{"lensFile", lensFile},
{"kind", "whole"},
{"unit", nullptr},
{"expectedIds", slots},
{"model", ModelFor(lens.model)},
{"maxTurns", budget.maxTurns},
{"timeoutMin", budget.timeoutMin},
});
}
for (const Json& unit : units) {
const std::string_view kind = KindFor(lensFile, unit);
if (!std::ranges::contains(lens.block, kind)) continue;
const std::string& id = Dyn::At(unit, "id").get_ref<const std::string&>();
if (Contains(excludeUnits, id)) continue;
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. A
// kernel lens asks every whole:* slot.
if (!lens.kernel && !std::ranges::contains(lens.slots, std::string_view(id))) continue;
const Budget budget = BudgetFor("slot", "slot", lensFile);
plan.blocks.push_back(stamp({
{"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 == "text" && IsBareHeader(unit, text)) {
plan.blocks.push_back(stamp({
{"label", std::format("{}:na:{}", name, id)},
{"lens", name},
{"lensFile", lensFile},
{"kind", "auto-na"},
{"unit", id},
{"expectedIds", {id}},
{"unitSha", Sha(text)},
}));
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.
continue;
}
if (lensFile == FunctionalLens && path && id.ends_with("#hdr")) {
plan.blocks.push_back(Mechanical(name, lensFile, unit, text,
"mechanical: a diff header (mode, rename, new or deleted file) carries no behaviour of its own; the hunks of the file do"));
continue;
}
if (lensFile == FunctionalLens && path) {
if (const std::optional<std::string> glob = ExcludedBy(*path, exclude)) {
plan.blocks.push_back(Mechanical(name, lensFile, unit, text,
std::format("mechanical: no runtime behaviour - the file matches functional.exclude ({})", *glob)));
continue;
}
}
const Budget budget = BudgetFor("block", kind, lensFile);
plan.blocks.push_back(stamp({
{"label", std::format("{}:block:{}", name, id)},
{"lens", name},
{"lensFile", lensFile},
{"kind", "block"},
{"unit", id},
{"unitKind", kind},
{"expectedIds", {id}},
{"unitSha", Sha(text)},
{"model", ModelFor(lens.model)},
{"maxTurns", budget.maxTurns},
{"timeoutMin", budget.timeoutMin},
}));
}
return plan;
}
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) {
if (!LensOf(lensFile).reuse || Dyn::At(job, "kind") != "block") return std::nullopt;
std::ranges::sort(deps);
std::ranges::sort(inForceIds);
const Json& model = Dyn::Get(job, "model");
std::vector<std::string> parts = {
std::string(lensFile),
ident.empty() ? Dyn::At(job, "unit").get<std::string>() : std::string(ident),
Sha(unitText),
Sha(Dyn::Join(deps, "\n")),
Dyn::Join(inForceIds, ","),
std::string(charterSha),
std::to_string(promptVersion),
Dyn::Has(job, "model") ? model.get<std::string>() : std::string(),
std::string(ReuseKeyEffortSlot),
};
if (Dyn::Truthy(Dyn::Get(job, "persona"))) parts.push_back(std::format("persona={}", Dyn::Str(job.at("persona"))));
return Sha(Dyn::Join(parts, "|")).substr(0, 16);
}
std::string CodeNorm(std::string_view text) {
std::vector<std::string> lines = Dyn::Split(text, "\n");
while (!lines.empty() && lines.back().empty()) lines.pop_back();
if (lines.size() >= 2 && lines[lines.size() - 2] == "-- " && StartsWithVersion(lines.back())) {
lines.resize(lines.size() - 2);
while (!lines.empty() && (lines.back().empty() || IsTailLine(lines.back()))) lines.pop_back();
}
for (std::string& line : lines) {
if (const std::optional<std::string_view> mode = IndexMode(line)) {
line = std::format("index{}", *mode);
} else if (const std::optional<std::string_view> tail = HunkTail(line)) {
line = std::format("@@{}", *tail);
}
}
return Dyn::Join(lines, "\n");
}
PatchParts PartsOf(std::string_view patchText, bool docFiles) {
const std::vector<std::string> lines = Dyn::Split(patchText, "\n");
std::optional<std::size_t> subject;
for (std::size_t i = 0; i < lines.size(); ++i) {
if (lines[i].starts_with("Subject:")) {
subject = i;
break;
}
}
std::size_t body0 = 1;
if (subject) {
for (std::size_t i = *subject + 1; i < lines.size(); ++i) {
if (Dyn::Strip(lines[i]).empty()) {
body0 = i + 1;
break;
}
}
}
std::size_t diff0 = lines.size();
for (std::size_t i = body0; i < lines.size(); ++i) {
if (lines[i].starts_with("---")) {
diff0 = i;
break;
}
}
std::size_t first = lines.size();
for (std::size_t i = diff0 + 1; i < lines.size(); ++i) {
if (lines[i].starts_with("diff --git")) {
first = i;
break;
}
}
PatchParts parts;
for (const std::string& line : lines) {
if (IsBaseCommit(line)) {
parts.base = Dyn::Split(line)[1];
break;
}
}
const std::span<const std::string> code(lines.begin() + static_cast<std::ptrdiff_t>(std::min(first, lines.size())), lines.end());
for (const std::string& section : SplitBeforeLines(CodeNorm(Dyn::Join(code, "\n")), "diff --git ")) {
const std::string firstLine = Dyn::Split(section, "\n", 1)[0];
const std::string path = Dyn::Strip(Dyn::Split(firstLine, " b/").back());
if (docFiles && section.starts_with("diff --git ") && IsDocPath(path)) {
// re.split(r'(?m)^@@', section, maxsplit=1)[0].rstrip('\n')
std::size_t header = section.size();
for (std::size_t at = 0; at < section.size();) {
if (std::string_view(section).substr(at).starts_with("@@")) {
header = at;
break;
}
const std::size_t newline = section.find('\n', at);
if (newline == std::string::npos) break;
at = newline + 1;
}
parts.docs.push_back(Dyn::RStrip(std::string_view(section).substr(0, header), "\n"));
} else {
parts.code += section;
}
}
std::ranges::sort(parts.docs);
const auto [messageFrom, messageTo] = Dyn::SliceBounds(lines.size(), static_cast<std::int64_t>(subject ? *subject : body0), static_cast<std::int64_t>(diff0));
std::vector<std::string> message;
for (std::size_t i = messageFrom; i < messageTo; ++i) message.push_back(StripSubjectTags(lines[i]));
parts.message = Dyn::Join(message, "\n");
const auto [bodyFrom, bodyTo] = Dyn::SliceBounds(lines.size(), static_cast<std::int64_t>(body0), static_cast<std::int64_t>(diff0));
for (std::size_t i = bodyFrom; i < bodyTo; ++i) {
if (!Dyn::Strip(lines[i]).empty()) {
parts.hasBody = true;
break;
}
}
return parts;
}
CommitCode CommitCodeOf(const std::map<std::int64_t, std::string>& patches, bool kernel) {
CommitCode cc;
cc.kernel = kernel;
std::set<std::string> bases;
std::vector<std::string> codes;
for (const auto& [k, text] : patches) {
PatchParts parts = PartsOf(text, !kernel);
cc.body[k] = parts.hasBody;
cc.code[k] = Sha(parts.code);
cc.docs[k] = std::move(parts.docs);
cc.message[k] = Sha(parts.message);
if (!parts.base.empty()) bases.insert(parts.base);
codes.push_back(cc.code[k]);
cc.upto[k] = Sha(Dyn::Join(codes, "\n"));
}
cc.range = Sha(Dyn::Join(codes, "\n"));
cc.base = Dyn::Join(std::vector<std::string>(bases.begin(), bases.end()), ",");
return cc;
}
std::pair<std::string, std::vector<std::string>> CodeKeyParts(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 pk = unitId.substr(0, colon);
const std::string_view rest = unitId.substr(colon + 1);
const std::string_view number = pk.empty() ? pk : pk.substr(1);
const std::optional<std::int64_t> k = Dyn::ParseInt(number);
if (!k) throw Dyn::Error("ValueError", Dyn::IntError(number));
std::vector<std::string> deps = {std::format("base={}", cc.base)};
if (rest == "outside" || rest == "absent") {
const auto code = cc.code.find(*k);
if (code == cc.code.end()) throw Dyn::Error("KeyError", std::to_string(*k));
deps.push_back(std::format("commit={}:{}", *k, code->second));
deps.push_back(std::format("range={}", cc.range));
deps.push_back(std::format("docs={}", Dyn::Join(cc.docs.at(*k), "|")));
// A kernel reviewer looks for a trailer, a Cc, a test statement:
// the patch's message is what `absent` depends on there; in an
// own repository it looks for a body at all.
if (rest == "absent") deps.push_back(cc.kernel ? std::format("msg={}", cc.message.at(*k)) : std::format("body={}", cc.body.at(*k) ? "yes" : "no"));
return {std::format("code:{}", rest), deps};
}
const std::size_t hash = rest.rfind('#');
if (hash == std::string_view::npos) throw Dyn::Error("ValueError", "not enough values to unpack (expected 2, got 1)");
const std::string_view tag = rest.substr(hash + 1);
return {std::format("code:{}#{}", rest.substr(0, hash), tag == "hdr" ? "hdr" : "hunk"), deps};
}
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) {
auto [ident, deps] = CodeKeyParts(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> 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) {
auto [ident, deps] = CodeKeyParts(Dyn::At(job, "unit").get_ref<const std::string&>(), cc);
deps.push_back(std::format("range={}", cc.range));
std::ranges::move(EnvDeps(lensFile, environment), std::back_inserter(deps));
return ReuseKey(job, lensFile, CodeNorm(unitText), deps, {inForceIds.begin(), inForceIds.end()}, charterSha, promptVersion,
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);
}
return CodeReuseKey(job, lensFile, unitText, cc, inForceIds, charterSha, promptVersion, environment);
}
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) {
if (!LensOf(lensFile).wholeReuse || Dyn::At(job, "kind") != "whole") return std::nullopt;
std::string body;
for (const auto& [k, has] : cc.body) {
if (!body.empty()) body.push_back(',');
body.push_back(has ? 'y' : 'n');
}
std::vector<std::string> docs;
for (const auto& [k, files] : cc.docs) docs.push_back(Dyn::Join(files, "|"));
std::vector<std::string> deps = {
std::format("base={}", cc.base),
std::format("range={}", cc.range),
std::format("body={}", body),
std::format("docs={}", Dyn::Join(docs, ";")),
};
std::ranges::move(EnvDeps(lensFile, environment), std::back_inserter(deps));
std::vector<std::string> ids(inForceIds.begin(), inForceIds.end());
std::ranges::sort(ids);
const std::vector<std::string> parts = {
std::string(lensFile),
"code:whole",
Sha(Dyn::Join(deps, "\n")),
Dyn::Join(ids, ","),
std::string(charterSha),
std::to_string(promptVersion),
Dyn::Has(job, "model") ? Dyn::Get(job, "model").get<std::string>() : std::string(),
std::string(ReuseKeyEffortSlot),
};
return Sha(Dyn::Join(parts, "|")).substr(0, 16);
}
namespace {
// \b before `at` and after `end` in the decoded text.
bool Bounded(const std::u32string& text, std::size_t at, std::size_t end) {
const bool before = at == 0 || !Dyn::IsWord(text[at - 1]);
const bool after = end >= text.size() || !Dyn::IsWord(text[end]);
return before && after;
}
// A phrase at `at`, letters compared without case.
bool PhraseAt(const std::u32string& text, std::size_t at, std::string_view phrase) {
if (at + phrase.size() > text.size()) return false;
for (std::size_t i = 0; i < phrase.size(); ++i) {
const char32_t c = text[at + i];
const char p = phrase[i];
if (p >= 'a' && p <= 'z') {
if (Dyn::IgnoreCaseLetter(c) != p) return false;
} else if (c != static_cast<char32_t>(p)) {
return false;
}
}
return true;
}
// re.search(r'\d{4}-\d{2}-\d{2}|\bv\d+\b|\bas of\b|...', text, re.I)
bool Dated(std::string_view unitText) {
constexpr std::array<std::string_view, 9> Words = {"as of", "still", "unmerged", "in review", "no reply", "latest", "merged", "posted", "applied"};
const std::u32string text = Dyn::Decode(unitText);
auto digits = [&](std::size_t at, std::size_t n) {
for (std::size_t i = 0; i < n; ++i) {
if (at + i >= text.size() || !Dyn::IsDecimal(text[at + i])) return false;
}
return true;
};
for (std::size_t at = 0; at < text.size(); ++at) {
if (digits(at, 4) && at + 4 < text.size() && text[at + 4] == '-' && digits(at + 5, 2) && at + 7 < text.size() && text[at + 7] == '-' && digits(at + 8, 2)) return true;
if (Dyn::IgnoreCaseLetter(text[at]) == 'v' && at + 1 < text.size() && Dyn::IsDecimal(text[at + 1])) {
std::size_t end = at + 1;
while (end < text.size() && Dyn::IsDecimal(text[end])) ++end;
if (Bounded(text, at, end)) return true;
}
for (const std::string_view word : Words) {
if (PhraseAt(text, at, word) && Bounded(text, at, at + word.size())) return true;
}
}
return false;
}
// re.fullmatch(r'R\d+', id)
bool IsReversalId(std::string_view id) {
return id.size() > 1 && id[0] == 'R' && Dyn::DecimalRun(id, 1) == id.size() - 1;
}
// re.search(r'\b' + re.escape(id) + r'\b', text)
bool NamesUnit(std::string_view unitText, std::string_view id) {
const std::u32string text = Dyn::Decode(unitText);
const std::u32string needle = Dyn::Decode(id);
for (std::size_t at = 0; at + needle.size() <= text.size(); ++at) {
if (std::u32string_view(text).substr(at, needle.size()) == needle && Bounded(text, at, at + needle.size())) return true;
}
return false;
}
}
std::vector<std::string> TextDeps(std::string_view unitText, const Json& units, std::span<const std::string> lines, std::span<const std::pair<std::string, std::string>> refs,
std::string_view anchorText) {
std::vector<std::string> deps;
// sorted(set(int(x) for x in re.findall(r'\[(\d+)\]', text)))
std::set<std::int64_t> cited;
for (std::size_t at = 0; at < unitText.size(); ++at) {
if (unitText[at] != '[') continue;
const std::size_t digits = Dyn::DecimalRun(unitText, at + 1);
if (digits == 0 || at + 1 + digits >= unitText.size() || unitText[at + 1 + digits] != ']') continue;
if (const std::optional<std::int64_t> n = Dyn::ParseInt(unitText.substr(at + 1, digits))) cited.insert(*n);
at += digits + 1;
}
for (const std::int64_t n : cited) {
std::string url = "?";
for (const auto& [key, value] : refs) {
if (key == std::to_string(n)) url = value;
}
deps.push_back(std::format("ref[{}]={}", n, url));
}
if (Dated(unitText)) deps.push_back(std::format("anchor={}", Sha(anchorText)));
for (const Json& unit : units) {
const std::string& id = Dyn::At(unit, "id").get_ref<const std::string&>();
if (IsReversalId(id) && NamesUnit(unitText, id)) deps.push_back(std::format("unit[{}]={}", id, Sha(UnitText(lines, unit))));
}
return deps;
}
std::int64_t ReadsOf(const Json& entry) {
if (!Dyn::Truthy(entry)) return 0;
const Json& reads = Dyn::Get(entry, "reads");
if (!Dyn::Truthy(reads)) return 1;
if (reads.is_string()) return static_cast<std::int64_t>(Dyn::Length(reads.get_ref<const std::string&>()));
return static_cast<std::int64_t>(reads.size());
}
bool ReadIn(const Json& entry, std::string_view hash8, std::int64_t round) {
const Json& reads = Dyn::Or(Dyn::Get(entry, "reads"), Dyn::EmptyList());
return std::ranges::any_of(reads, [&](const Json& read) {
return Dyn::Equal(Dyn::Get(read, "hash"), Json(hash8)) && Dyn::Equal(Dyn::Get(read, "round"), Json(round));
});
}
Json Rebind(const Json& entry, std::string_view unitId) {
Json out = entry;
out["unit"] = unitId;
Json coverage = Dyn::At(out, "coverage");
if (!coverage.is_object()) throw Dyn::Error("TypeError", std::format("'{}' object is not iterable", Dyn::TypeName(coverage)));
coverage["item"] = unitId;
out["coverage"] = std::move(coverage);
for (const std::string_view field : {std::string_view("findings"), std::string_view("notes")}) {
if (!Dyn::Has(out, field)) continue;
for (Json& item : out[std::string(field)]) {
if (Dyn::Get(item, "items").is_array()) item["items"] = Json::array({unitId});
}
}
return out;
}
Json Estimate(std::span<const Json> jobs, std::int64_t prefixBytes, const std::map<std::string, std::int64_t>& lensLayerBytes,
std::int64_t tailBytes, double tpb, std::int64_t sysTokens, std::int64_t outPerTurn) {
const double prefix = static_cast<double>(prefixBytes) / tpb + static_cast<double>(sysTokens);
double cacheRead = 0.0;
double cacheWrite = 0.0;
double output = 0.0;
std::int64_t processes = 0;
std::set<std::pair<std::string, std::string>> seen;
for (const Json& job : jobs) {
const std::string kind = Dyn::At(job, "kind").get<std::string>();
if (kind == "auto-na") continue;
const std::string lens = Dyn::At(job, "lens").get<std::string>();
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 Json& unitKind = Dyn::Get(job, "unitKind");
const Json& lensFile = Dyn::At(job, "lensFile");
const std::int64_t turns = kind == "whole" ? 40 : kind == "slot" ? 10 : lensFile == FunctionalLens ? 25
: unitKind == "code" ? 10 : (lensFile == FactsLens || lensFile == "facts.md" ? 12 : 8);
if (seen.insert({lens, kind}).second) cacheWrite += prefix + lensTokens;
cacheWrite += tail + static_cast<double>(turns * 3000);
std::vector<double> reads;
for (std::int64_t i = 0; i < turns; ++i) reads.push_back(prefix + lensTokens + tail + static_cast<double>(3000 * i));
cacheRead += Dyn::Sum(reads);
output += static_cast<double>(turns * outPerTurn);
++processes;
}
const double costUnits = 1.25 * cacheWrite + 0.1 * cacheRead + 5.0 * output;
const double listUsd = Dyn::Round((6.25 * cacheWrite + 0.5 * cacheRead + 25.0 * output) / 1e6, 2);
return {
{"cacheRead", cacheRead},
{"cacheWrite", cacheWrite},
{"output", output},
{"processes", processes},
{"costUnits", costUnits},
{"listUsd", listUsd},
};
}
}