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

@ -13,15 +13,21 @@ namespace fs = std::filesystem;
namespace Crafter::Review::Inventory {
namespace {
constexpr std::string_view Usage = R"(Partition a frozen commit range (a `git format-patch` export) into review units for the coverage contract.
constexpr std::string_view Usage = R"(Partition a frozen artifact into review units for the coverage contract.
The inventory is MECHANICAL: no agent decides what the units are. Every non-blank line of the artifact belongs to
exactly one unit, and the tool fails if that invariant does not hold. Fixed slots are added so that what is NOT in
the text still has a row in the coverage matrix.
crafter-review inventory commits <dir-with-*.patch> > gate-state/inventory-<hash8>.json
crafter-review inventory reply <draft.txt> > gate-state/inventory-<hash8>.json
crafter-review inventory series <dir-with-*.patch> > gate-state/inventory-<hash8>.json
crafter-review inventory commits <dir-with-*.patch> > gate-state/inventory-<hash8>.json (own-repo push: git format-patch of a range)
Units (ids):
Reply mode units: nl:intro, nl:<file>, anchor, RN, <sec>:N, <sec>:Na, x:N, refs, whole:* - or, for a plain mail
without the dated anchor: hdr, quote:N, para:N, trailers, whole:*. Series mode: cover:header, cover:N and the
units below (every hunk whole), whole:* from the series slots.
Commits mode units (ids):
pK:subject commit K's subject
pK:msg commit K's message body (before the trailers) - only when non-empty
pK:trailers commit K's trailer block - only when non-empty
@ -79,6 +85,74 @@ Units (ids):
return {{"id", std::move(id)}, {"lines", {first, last}}, {"excerpt", std::move(excerpt)}};
}
// ^On .+ wrote:$ on a stripped line: an attribution.
bool IsAttribution(std::string_view stripped) {
return stripped.starts_with("On ") && stripped.ends_with(" wrote:") && stripped.size() > 10 && !stripped.contains('\n');
}
// ^[A-Z][A-Za-z-]+: \S : a trailer line of a mail (Tested-by: ...).
bool IsMailTrailer(std::string_view line) {
if (line.empty() || line[0] < 'A' || line[0] > 'Z') return false;
std::size_t at = 1;
while (at < line.size() && ((line[at] >= 'A' && line[at] <= 'Z') || (line[at] >= 'a' && line[at] <= 'z') || line[at] == '-')) ++at;
if (at < 2 || !line.substr(at).starts_with(": ")) return false;
return at + 2 < line.size() && !Dyn::IsSpace(Dyn::CodePointAt(line, at + 2).first);
}
// ^[\w.-]+/\d{4}-[^\s]+\.patch$ : a bare patch path, the header of a
// per-file group of the camera review.
bool IsPatchPath(std::string_view text) {
std::size_t at = 0;
std::size_t head = 0;
while (at < text.size()) {
const auto [c, used] = Dyn::CodePointAt(text, at);
if (!(Dyn::IsWord(c) || c == '.' || c == '-')) break;
at += used;
++head;
}
if (head == 0 || at >= text.size() || text[at] != '/') return false;
++at;
if (Dyn::DecimalRun(text, at) != 4) return false;
at += 4;
if (at >= text.size() || text[at] != '-') return false;
++at;
const std::size_t rest = Dyn::NonSpaceRun(text, at);
if (rest <= 6 || at + rest != text.size()) return false;
return text.ends_with(".patch");
}
// ^R\d+\. and ^\d+\. : a reversal or a numbered item; the id is the
// text before the dot.
std::optional<std::string> NumberedHead(std::string_view line, bool reversal) {
std::size_t at = 0;
if (reversal) {
if (!line.starts_with('R')) return std::nullopt;
at = 1;
}
const std::size_t digits = Dyn::DecimalRun(line, at);
if (digits == 0 || !line.substr(at + digits).starts_with(". ")) return std::nullopt;
return std::string(line.substr(0, at + digits));
}
// ^\s+[a-z]\. \S : a lettered sub-item; the letter.
std::optional<char> LetteredHead(std::string_view line) {
const std::size_t spaces = Dyn::SpaceRun(line, 0);
if (spaces == 0 || spaces + 3 >= line.size()) return std::nullopt;
const char letter = line[spaces];
if (letter < 'a' || letter > 'z' || !line.substr(spaces + 1).starts_with(". ")) return std::nullopt;
if (Dyn::IsSpace(Dyn::CodePointAt(line, spaces + 3).first)) return std::nullopt;
return letter;
}
// ^\[1\] https?://
bool IsFirstReference(std::string_view line) {
return line.starts_with("[1] http://") || line.starts_with("[1] https://");
}
std::span<const std::string> Range(const std::vector<std::string>& lines, std::size_t from, std::size_t to) {
return std::span<const std::string>(lines).subspan(from, to - from);
}
std::string ReprInts(std::span<const std::int64_t> values) {
std::string out = "[";
for (std::size_t i = 0; i < values.size(); ++i) out += std::format("{}{}", i ? ", " : "", values[i]);
@ -119,7 +193,7 @@ Units (ids):
return "";
}
Series PartitionSeries(const fs::path& dir, bool splitDocs) {
Series PartitionSeries(const fs::path& dir, bool splitDocs, bool kernel) {
Series series;
series.units = Json::array();
Json& units = series.units;
@ -129,7 +203,8 @@ Units (ids):
const std::vector<std::string> lines = Dyn::Split(text, "\n");
const auto count = static_cast<std::int64_t>(lines.size());
const std::optional<std::int64_t> number = Artifact::PatchNumber(name);
const std::string pid = std::format("p{}", number ? *number : -1);
const std::int64_t k = number ? *number : -1;
const std::string pid = k == 0 ? std::string("cover") : std::format("p{}", k);
const std::int64_t base = offset;
std::optional<std::int64_t> subject;
for (std::int64_t i = 0; i < count; ++i) {
@ -155,6 +230,26 @@ Units (ids):
break;
}
}
if (pid == "cover") {
// The header as one unit, the paragraphs of the body.
units.push_back(Unit("cover:header", base + 1, base + body0, subject ? Dyn::Slice(Dyn::Strip(lines[static_cast<std::size_t>(*subject)]), 0, 90) : std::string()));
std::int64_t i = body0;
std::int64_t pn = 0;
while (i < count) {
if (Dyn::Strip(lines[static_cast<std::size_t>(i)]).empty()) {
++i;
continue;
}
std::int64_t j = i;
while (j < count && !Dyn::Strip(lines[static_cast<std::size_t>(j)]).empty()) ++j;
++pn;
units.push_back(Unit(std::format("cover:{}", pn), base + i + 1, base + j, Excerpt(Lines(lines, i, j))));
i = j;
}
series.lines.insert(series.lines.end(), lines.begin(), lines.end());
offset += count;
continue;
}
units.push_back(Unit(std::format("{}:subject", pid), base + 1, base + body0,
subject ? Dyn::Slice(Dyn::Strip(lines[static_cast<std::size_t>(*subject)]), 0, 90) : std::string()));
std::int64_t t = diff0;
@ -216,19 +311,189 @@ Units (ids):
++i;
}
closeHunk(count);
units.push_back(Unit(std::format("{}:outside", pid), 0, 0, "FIXED SLOT: in-repository users/callers of what this commit changes, outside its diff"));
units.push_back(Unit(std::format("{}:absent", pid), 0, 0, "FIXED SLOT: what a reviewer expects in this commit and does not find"));
units.push_back(Unit(std::format("{}:outside", pid), 0, 0,
kernel ? "FIXED SLOT: in-tree users/callers of what this patch changes, outside its diff" : "FIXED SLOT: in-repository users/callers of what this commit changes, outside its diff"));
units.push_back(Unit(std::format("{}:absent", pid), 0, 0,
kernel ? "FIXED SLOT: what a reviewer expects in this patch and does not find" : "FIXED SLOT: what a reviewer expects in this commit and does not find"));
series.lines.insert(series.lines.end(), lines.begin(), lines.end());
offset += count;
}
return series;
}
Series PartitionMail(std::string_view text) {
Series out;
out.lines = Dyn::Split(text, "\n");
out.units = Json::array();
const std::vector<std::string>& lines = out.lines;
const std::size_t n = lines.size();
std::size_t hdrEnd = n;
for (std::size_t i = 0; i < n; ++i) {
if (Dyn::Strip(lines[i]).empty()) {
hdrEnd = i;
break;
}
}
if (hdrEnd > 0) out.units.push_back(Unit("hdr", 1, static_cast<std::int64_t>(hdrEnd), Excerpt(Range(lines, 0, hdrEnd))));
std::size_t i = hdrEnd;
std::int64_t q = 0;
std::int64_t pn = 0;
std::int64_t tr = 0;
while (i < n) {
if (Dyn::Strip(lines[i]).empty()) {
++i;
continue;
}
std::size_t j = i;
std::string uid;
if (lines[i].starts_with('>') || IsAttribution(Dyn::Strip(lines[i]))) {
// A quoted block: the attribution line (if any) and every
// following quoted line; a blank line inside the quote is
// part of it only when a quoted line follows.
while (j < n && (lines[j].starts_with('>') || IsAttribution(Dyn::Strip(lines[j])) || (Dyn::Strip(lines[j]).empty() && j + 1 < n && lines[j + 1].starts_with('>')))) ++j;
++q;
uid = std::format("quote:{}", q);
} else {
while (j < n && !Dyn::Strip(lines[j]).empty() && !lines[j].starts_with('>')) ++j;
if (std::all_of(lines.begin() + static_cast<std::ptrdiff_t>(i), lines.begin() + static_cast<std::ptrdiff_t>(j), [](const std::string& l) { return IsMailTrailer(l); })) {
++tr;
uid = tr == 1 ? std::string("trailers") : std::format("trailers:{}", tr);
} else {
++pn;
uid = std::format("para:{}", pn);
}
}
out.units.push_back(Unit(uid, static_cast<std::int64_t>(i) + 1, static_cast<std::int64_t>(j), Excerpt(Range(lines, i, j))));
i = j;
}
return out;
}
Series PartitionReply(std::string_view text) {
Series out;
out.lines = Dyn::Split(text, "\n");
out.units = Json::array();
const std::vector<std::string>& lines = out.lines;
const std::size_t n = lines.size();
// The block starts at the dated anchor line; a mail without one is a
// plain reply.
std::optional<std::size_t> anchor;
for (std::size_t i = 0; i < n; ++i) {
if (lines[i].starts_with("Dates: every")) {
anchor = i;
break;
}
}
if (!anchor) return PartitionMail(text);
const std::size_t blk = *anchor;
// The author's part: paragraphs grouped by the patch file they
// discuss - a group starts at a paragraph that is a bare path ending
// in .patch; everything before the first such header is nl:intro.
struct Group {
std::string id;
std::size_t start;
std::size_t end;
};
std::vector<Group> groups;
std::size_t i = 0;
while (i < blk) {
if (Dyn::Strip(lines[i]).empty()) {
++i;
continue;
}
std::size_t j = i;
while (j < blk && !Dyn::Strip(lines[j]).empty()) ++j;
const std::string first = Dyn::Strip(lines[i]);
if (IsPatchPath(first) && j - i == 1) {
groups.push_back({std::format("nl:{}", first), i, j});
} else if (!groups.empty()) {
groups.back().end = j;
} else {
groups.push_back({"nl:intro", i, j});
}
i = j;
}
for (const Group& g : groups) out.units.push_back(Unit(g.id, static_cast<std::int64_t>(g.start) + 1, static_cast<std::int64_t>(g.end), Excerpt(Range(lines, g.start, g.end))));
// The anchor paragraph.
std::size_t j = blk;
while (j < n && !Dyn::Strip(lines[j]).empty()) ++j;
out.units.push_back(Unit("anchor", static_cast<std::int64_t>(blk) + 1, static_cast<std::int64_t>(j), Excerpt(Range(lines, blk, j))));
// The rest of the block: reversals, sections, items, sub-items,
// bullets, refs.
std::optional<std::size_t> refsStart;
for (std::size_t r = 0; r < n; ++r) {
if (IsFirstReference(lines[r])) {
refsStart = r;
break;
}
}
if (!refsStart) throw InvariantError("StopIteration");
static const std::map<std::string, std::string> Sections = {{"camss-milos", "camss"}, {"ov13b10-of", "ov13b10"}, {"milos-dts-camss", "dts"}, {"across the three series", "x"}};
std::optional<std::string> sec;
std::optional<std::string> item;
std::int64_t bullet = 0;
std::vector<std::pair<std::size_t, std::string>> starts;
i = j;
while (i < *refsStart) {
const std::string& l = lines[i];
const std::string s = Dyn::Strip(l);
if (s.empty()) {
++i;
continue;
}
const std::string low = Dyn::Lower(s);
const bool indented = l.starts_with(' ');
if (Sections.contains(low) && !indented) {
sec = Sections.at(low);
item.reset();
bullet = 0;
starts.emplace_back(i, std::format("{}:header", *sec));
} else if (const std::optional<std::string> r = NumberedHead(l, true)) {
starts.emplace_back(i, *r);
} else if (l.starts_with("Reversals of the")) {
starts.emplace_back(i, "reversals:header");
} else if (const std::optional<std::string> num = NumberedHead(l, false); num && sec) {
item = *num;
starts.emplace_back(i, std::format("{}:{}", *sec, *item));
} else if (const std::optional<char> letter = LetteredHead(l); letter && sec && item) {
starts.emplace_back(i, std::format("{}:{}{}", *sec, *item, *letter));
} else if (l.starts_with("- ") && sec == "x") {
++bullet;
starts.emplace_back(i, std::format("x:{}", bullet));
} else if (!indented && starts.empty()) {
starts.emplace_back(i, std::format("para:{}", i + 1));
} else if (!indented && !NumberedHead(l, false) && !NumberedHead(l, true) && Dyn::Strip(lines[i - 1]).empty() && !sec) {
starts.emplace_back(i, std::format("para:{}", i + 1));
}
++i;
}
for (std::size_t k = 0; k < starts.size(); ++k) {
const auto& [a, uid] = starts[k];
std::size_t b = k + 1 < starts.size() ? starts[k + 1].first : *refsStart;
while (b > a && Dyn::Strip(lines[b - 1]).empty()) --b;
out.units.push_back(Unit(uid, static_cast<std::int64_t>(a) + 1, static_cast<std::int64_t>(b), Excerpt(Range(lines, a, b))));
}
std::size_t b = n;
while (b > *refsStart && Dyn::Strip(lines[b - 1]).empty()) --b;
out.units.push_back(Unit("refs", static_cast<std::int64_t>(*refsStart) + 1, static_cast<std::int64_t>(b), Excerpt(Range(lines, *refsStart, b))));
return out;
}
Built Build(std::string_view mode, std::string_view artifact) {
if (mode != "commits") throw InvariantError(std::format("inventory: mode {} is not supported here (commits only)", Dyn::Repr(mode)));
Series series = PartitionSeries(fs::path(artifact), true);
if (mode != "reply" && mode != "series" && mode != "commits") throw InvariantError(std::format("inventory: mode {} is not reply, series or commits", Dyn::Repr(mode)));
Series series;
std::string h8;
std::span<const WholeSlot> whole;
if (mode == "reply") {
series = PartitionReply(Dyn::ReadText(fs::path(artifact)));
h8 = Artifact::Sha256File(fs::path(artifact)).substr(0, 8);
whole = WholeReply;
} else {
series = PartitionSeries(fs::path(artifact), mode == "commits", mode == "series");
h8 = Artifact::ArtifactHash("commits", fs::path(artifact)).substr(0, 8);
whole = mode == "commits" ? std::span<const WholeSlot>(WholeCommits) : std::span<const WholeSlot>(WholeSeries);
}
Json& units = series.units;
const std::string h8 = Artifact::ArtifactHash("commits", fs::path(artifact)).substr(0, 8);
std::map<std::int64_t, std::string> owner;
for (const Json& unit : units) {
const std::int64_t a = Dyn::At(unit, "lines")[0].get<std::int64_t>();
@ -251,7 +516,7 @@ Units (ids):
const std::span<const std::int64_t> first(uncovered.data(), std::min<std::size_t>(uncovered.size(), 10));
throw InvariantError(std::format("INVARIANT: {} non-blank lines in no unit, first: {}", uncovered.size(), ReprInts(first)));
}
for (const WholeSlot& slot : WholeCommits) units.push_back(Unit(std::string(slot.id), 0, 0, std::format("FIXED SLOT: {}", slot.description)));
for (const WholeSlot& slot : whole) units.push_back(Unit(std::string(slot.id), 0, 0, std::format("FIXED SLOT: {}", slot.description)));
std::map<std::string, std::int32_t> seen;
for (const Json& unit : units) ++seen[Dyn::At(unit, "id").get<std::string>()];
Json duplicates = Json::array();
@ -274,7 +539,7 @@ Units (ids):
}
std::int32_t Main(std::span<const std::string_view> args) {
if (args.size() != 2 || args[0] != "commits") {
if (args.size() != 2 || (args[0] != "reply" && args[0] != "series" && args[0] != "commits")) {
std::print(std::cerr, "{}", Usage);
return 1;
}