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:
Jorijn van der Graaf 2026-10-31 22:56:12 +01:00
commit 339f4b9b40
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
46 changed files with 5732 additions and 362 deletions

View file

@ -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;