feat(modes): the kernel modes - a patch series and a mailing-list reply
Beside the push gates, crafter-review runs the pre-send review of a kernel contributor's workbench: --mode series over a format-patch export with its cover letter, --mode reply over a mail draft. The inventory partitions a cover letter, a plain mail and the camera review's shape; ten kernel lenses join the table with their gates (structure, code, artifact truth) and the persona gate - one maintainer-persona instance per --reviewers entry, its whole pass gating its blocks and pruning the units that are not theirs. A series' code units are keyed on the code like the push gates'; every text unit on its text, the references it cites and the anchor when it is dated. The references an artifact cites are prefetched through utilities/lore.py into the gate-state, with a digest in the run layer, and a persona's calibration map is its lore listing. The kernel schema carries the persona vocabulary, the kernel note kinds, the predicted review and the not-mine list; the matrix the persona holes, the thin and the modelled units; gates clean without a persona pass is GATES CLEAN. A reply's gate-state sits beside the draft and is read once; a kernel mode needs --subsystem, defaults the effort to max and the trees to work/linux,work/milos-linux, and excuses no unit from the holes. The kernel lenses and the common text ship verbatim; the table lists them after the push gates' lenses and the config template holds them off, so a repository that never turns them on changes nothing. Prompts, keys and plans were checked byte for byte against the pipeline these modes replace, on its own artifacts: two replies and a series, every system prompt, user prompt, job and reuse key identical.
This commit is contained in:
parent
4fc15fe1c2
commit
339f4b9b40
46 changed files with 5732 additions and 362 deletions
|
|
@ -187,6 +187,14 @@ namespace Crafter::Review::Plan {
|
|||
throw Dyn::Error("KeyError", Dyn::Repr(tier));
|
||||
}
|
||||
|
||||
bool IsKernelMode(std::string_view mode) {
|
||||
return mode == "reply" || mode == "series";
|
||||
}
|
||||
|
||||
std::int32_t ReadsFor(std::string_view mode) {
|
||||
return mode == "reply" ? ReplyReads : Reads;
|
||||
}
|
||||
|
||||
bool IsLens(std::string_view file) {
|
||||
return std::ranges::any_of(Lenses, [&](const Lens& lens) { return lens.file == file; });
|
||||
}
|
||||
|
|
@ -198,14 +206,25 @@ namespace Crafter::Review::Plan {
|
|||
throw Dyn::Error("KeyError", Dyn::Repr(file));
|
||||
}
|
||||
|
||||
const std::vector<Gate>& Gates() {
|
||||
static const std::vector<Gate> gates = {
|
||||
const std::vector<Gate>& Gates(std::string_view mode) {
|
||||
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}},
|
||||
};
|
||||
return gates;
|
||||
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 (mode == "reply") return reply;
|
||||
if (mode == "series") return series;
|
||||
if (mode == "commits") return commits;
|
||||
throw Dyn::Error("KeyError", Dyn::Repr(mode));
|
||||
}
|
||||
|
||||
const Environment* FindEnvironment(std::string_view name) {
|
||||
|
|
@ -227,7 +246,10 @@ namespace Crafter::Review::Plan {
|
|||
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};
|
||||
// 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)));
|
||||
}
|
||||
|
||||
|
|
@ -235,6 +257,16 @@ namespace Crafter::Review::Plan {
|
|||
return Effort;
|
||||
}
|
||||
|
||||
std::string_view DefaultEffort(std::string_view mode) {
|
||||
return IsKernelMode(mode) ? 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);
|
||||
|
|
@ -265,11 +297,12 @@ namespace Crafter::Review::Plan {
|
|||
return path && IsDocPath(*path);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
|
@ -319,21 +352,27 @@ namespace Crafter::Review::Plan {
|
|||
return Sha256Hex(text);
|
||||
}
|
||||
|
||||
std::string LensName(std::string_view lensFile) {
|
||||
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> ModeLenses() {
|
||||
std::vector<std::string> ModeLenses(std::string_view mode) {
|
||||
std::vector<std::string> out;
|
||||
for (const Gate& gate : Gates()) {
|
||||
for (const Gate& gate : Gates(mode)) {
|
||||
for (const std::string_view lens : gate.lenses) out.emplace_back(lens);
|
||||
}
|
||||
if (IsKernelMode(mode)) 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 mode, 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()) {
|
||||
for (const Gate& gate : Gates(mode)) {
|
||||
Gate kept{gate.title, {}};
|
||||
for (const std::string_view lens : gate.lenses) {
|
||||
if (!only.empty() && !Contains(only, lens)) continue;
|
||||
|
|
@ -346,18 +385,30 @@ namespace Crafter::Review::Plan {
|
|||
}
|
||||
|
||||
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::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);
|
||||
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&>();
|
||||
if (UnitKind(unit) == "slot" && std::ranges::contains(lens.slots, std::string_view(id))) slots.push_back(id);
|
||||
// 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 = {
|
||||
plan.whole = stamp({
|
||||
{"label", std::format("{}:whole", name)},
|
||||
{"lens", name},
|
||||
{"lensFile", lensFile},
|
||||
|
|
@ -367,21 +418,23 @@ namespace Crafter::Review::Plan {
|
|||
{"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.
|
||||
if (!std::ranges::contains(lens.slots, std::string_view(id))) continue;
|
||||
// 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({
|
||||
plan.blocks.push_back(stamp({
|
||||
{"label", std::format("{}:slot:{}", name, id)},
|
||||
{"lens", name},
|
||||
{"lensFile", lensFile},
|
||||
|
|
@ -393,7 +446,19 @@ namespace Crafter::Review::Plan {
|
|||
{"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")) {
|
||||
|
|
@ -426,7 +491,7 @@ namespace Crafter::Review::Plan {
|
|||
}
|
||||
}
|
||||
const Budget budget = BudgetFor("block", kind, lensFile);
|
||||
plan.blocks.push_back({
|
||||
plan.blocks.push_back(stamp({
|
||||
{"label", std::format("{}:block:{}", name, id)},
|
||||
{"lens", name},
|
||||
{"lensFile", lensFile},
|
||||
|
|
@ -438,7 +503,7 @@ namespace Crafter::Review::Plan {
|
|||
{"model", ModelFor(lens.model)},
|
||||
{"maxTurns", budget.maxTurns},
|
||||
{"timeoutMin", budget.timeoutMin},
|
||||
});
|
||||
}));
|
||||
}
|
||||
return plan;
|
||||
}
|
||||
|
|
@ -450,7 +515,7 @@ namespace Crafter::Review::Plan {
|
|||
std::ranges::sort(deps);
|
||||
std::ranges::sort(inForceIds);
|
||||
const Json& model = Dyn::Get(job, "model");
|
||||
const std::vector<std::string> parts = {
|
||||
std::vector<std::string> parts = {
|
||||
std::string(lensFile),
|
||||
ident.empty() ? Dyn::At(job, "unit").get<std::string>() : std::string(ident),
|
||||
Sha(unitText),
|
||||
|
|
@ -461,6 +526,7 @@ namespace Crafter::Review::Plan {
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
@ -481,7 +547,7 @@ namespace Crafter::Review::Plan {
|
|||
return Dyn::Join(lines, "\n");
|
||||
}
|
||||
|
||||
PatchParts PartsOf(std::string_view patchText) {
|
||||
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) {
|
||||
|
|
@ -524,7 +590,7 @@ namespace Crafter::Review::Plan {
|
|||
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 (section.starts_with("diff --git ") && IsDocPath(path)) {
|
||||
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();) {
|
||||
|
|
@ -556,12 +622,13 @@ namespace Crafter::Review::Plan {
|
|||
return parts;
|
||||
}
|
||||
|
||||
CommitCode CommitCodeOf(const std::map<std::int64_t, std::string>& patches) {
|
||||
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);
|
||||
PatchParts parts = PartsOf(text, !kernel);
|
||||
cc.body[k] = parts.hasBody;
|
||||
cc.code[k] = Sha(parts.code);
|
||||
cc.docs[k] = std::move(parts.docs);
|
||||
|
|
@ -590,7 +657,10 @@ namespace Crafter::Review::Plan {
|
|||
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), "|")));
|
||||
if (rest == "absent") deps.push_back(std::format("body={}", cc.body.at(*k) ? "yes" : "no"));
|
||||
// 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('#');
|
||||
|
|
@ -703,6 +773,96 @@ namespace Crafter::Review::Plan {
|
|||
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");
|
||||
|
|
@ -750,8 +910,9 @@ namespace Crafter::Review::Plan {
|
|||
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 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);
|
||||
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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue