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.
This commit is contained in:
Jorijn van der Graaf 2026-11-01 01:31:40 +01:00
commit d5e54ffeb3
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
25 changed files with 1277 additions and 711 deletions

View file

@ -187,12 +187,12 @@ namespace Crafter::Review::Plan {
throw Dyn::Error("KeyError", Dyn::Repr(tier));
}
bool IsKernelMode(std::string_view mode) {
return mode == "reply" || mode == "series";
bool IsKernelKind(std::string_view kind) {
return kind == "reply" || kind == "series";
}
std::int32_t ReadsFor(std::string_view mode) {
return mode == "reply" ? ReplyReads : Reads;
std::int32_t ReadsFor(std::string_view kind) {
return kind == "reply" ? ReplyReads : Reads;
}
bool IsLens(std::string_view file) {
@ -206,7 +206,7 @@ namespace Crafter::Review::Plan {
throw Dyn::Error("KeyError", Dyn::Repr(file));
}
const std::vector<Gate>& Gates(std::string_view mode) {
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}},
@ -221,10 +221,10 @@ namespace Crafter::Review::Plan {
{"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));
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) {
@ -257,8 +257,8 @@ namespace Crafter::Review::Plan {
return Effort;
}
std::string_view DefaultEffort(std::string_view mode) {
return IsKernelMode(mode) ? KernelEffort : Effort;
std::string_view DefaultEffort(std::string_view kind) {
return IsKernelKind(kind) ? KernelEffort : Effort;
}
bool IsBareHeader(const Json& unit, std::string_view text) {
@ -357,12 +357,12 @@ namespace Crafter::Review::Plan {
return Dyn::Replace(lensFile, ".md", "");
}
std::vector<std::string> ModeLenses(std::string_view mode) {
std::vector<std::string> KindLenses(std::string_view kind) {
std::vector<std::string> out;
for (const Gate& gate : Gates(mode)) {
for (const Gate& gate : Gates(kind)) {
for (const std::string_view lens : gate.lenses) out.emplace_back(lens);
}
if (IsKernelMode(mode)) out.emplace_back(PersonaLens);
if (IsKernelKind(kind)) out.emplace_back(PersonaLens);
return out;
}
@ -370,9 +370,9 @@ namespace Crafter::Review::Plan {
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> 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(mode)) {
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;