// 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 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 FileOfUnit(std::string_view id) { const std::optional 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 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 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 SplitBeforeLines(std::string_view text, std::string_view head) { std::vector 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 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() ? 1 : 0; return value.get(); } 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(); 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 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& Models() { static std::vector 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& Gates(std::string_view kind) { static const std::vector 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 reply = { {"Gate 3 — Artifact truth", {"facts.md", "etiquette.md", "consistency.md", "conciseness.md"}}, }; static const std::vector 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 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 Bare = {"camss-milos", "ov13b10-of", "milos-dts-camss", "across the three series"}; const std::string& id = Dyn::At(unit, "id").get_ref(); 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 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()); } 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 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()) ? "doc" : kind; } std::optional UnitPath(std::string_view unitId) { const std::optional path = FileOfUnit(unitId); if (!path) return std::nullopt; return std::string(*path); } std::vector NotReviewed(const Json& units, std::span lines, std::span lensFiles, std::span exclude) { std::set 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()); } for (const Json& block : plan.blocks) { if (Dyn::At(block, "kind") != "auto-na") covered.insert(Dyn::At(block, "unit").get()); } } std::vector out; for (const Json& unit : units) { const std::string& id = Dyn::At(unit, "id").get_ref(); if (!covered.contains(id)) out.push_back(id); } return out; } std::optional ExcludedBy(std::string_view path, std::span 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 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 KindLenses(std::string_view kind) { std::vector 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 GatesFor(std::span only, std::span without, std::span off) { return GatesFor("commits", only, without, off); } std::vector GatesFor(std::string_view kind, std::span only, std::span without, std::span off) { std::vector 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 lines, std::span unitsFilter, std::span exclude, std::string_view persona, std::span 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(); // 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(); if (Contains(excludeUnits, id)) continue; if (!unitsFilter.empty() && !Contains(unitsFilter, id)) continue; const std::string text = UnitText(lines, unit); const std::optional 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 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 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) { 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 parts = { std::string(lensFile), ident.empty() ? Dyn::At(job, "unit").get() : 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(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 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 mode = IndexMode(line)) { line = std::format("index{}", *mode); } else if (const std::optional tail = HunkTail(line)) { line = std::format("@@{}", *tail); } } return Dyn::Join(lines, "\n"); } PatchParts PartsOf(std::string_view patchText, bool docFiles) { const std::vector lines = Dyn::Split(patchText, "\n"); std::optional 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 code(lines.begin() + static_cast(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(subject ? *subject : body0), static_cast(diff0)); std::vector 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(body0), static_cast(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& patches, bool kernel) { CommitCode cc; cc.kernel = kernel; std::set bases; std::vector 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(bases.begin(), bases.end()), ","); return cc; } std::pair> 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 k = Dyn::ParseInt(number); if (!k) throw Dyn::Error("ValueError", Dyn::IntError(number)); std::vector 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 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) { auto [ident, deps] = CodeKeyParts(Dyn::At(job, "unit").get_ref(), 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 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) { auto [ident, deps] = CodeKeyParts(Dyn::At(job, "unit").get_ref(), 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> 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 MessageDeps(std::string_view unitId, const CommitCode& cc) { // ^p(\d+):(subject|msg)$; `$` also matches before a final newline. const std::optional 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 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 DocReuseKey(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) { auto [ident, deps] = DocKeyParts(Dyn::At(job, "unit").get_ref(), 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 TextReuseKey(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::vector deps = MessageDeps(Dyn::At(job, "unit").get_ref(), 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 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 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 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) { 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 docs; for (const auto& [k, files] : cc.docs) docs.push_back(Dyn::Join(files, "|")); std::vector 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 ids(inForceIds.begin(), inForceIds.end()); std::ranges::sort(ids); const std::vector 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(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(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 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 TextDeps(std::string_view unitText, const Json& units, std::span lines, std::span> refs, std::string_view anchorText) { std::vector deps; // sorted(set(int(x) for x in re.findall(r'\[(\d+)\]', text))) std::set 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 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(); 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(Dyn::Length(reads.get_ref())); return static_cast(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 jobs, std::int64_t prefixBytes, const std::map& lensLayerBytes, std::int64_t tailBytes, double tpb, std::int64_t sysTokens, std::int64_t outPerTurn) { const double prefix = static_cast(prefixBytes) / tpb + static_cast(sysTokens); double cacheRead = 0.0; double cacheWrite = 0.0; double output = 0.0; std::int64_t processes = 0; std::set> seen; for (const Json& job : jobs) { const std::string kind = Dyn::At(job, "kind").get(); if (kind == "auto-na") continue; const std::string lens = Dyn::At(job, "lens").get(); const auto layer = lensLayerBytes.find(lens); const double lensTokens = static_cast(layer == lensLayerBytes.end() ? 3000 : layer->second) / tpb; const double tail = static_cast(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(turns * 3000); std::vector reads; for (std::int64_t i = 0; i < turns; ++i) reads.push_back(prefix + lensTokens + tail + static_cast(3000 * i)); cacheRead += Dyn::Sum(reads); output += static_cast(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}, }; } }