diff --git a/.forgejo/workflows/ci.yaml b/.forgejo/workflows/ci.yaml index 02ecae3..3665414 100644 --- a/.forgejo/workflows/ci.yaml +++ b/.forgejo/workflows/ci.yaml @@ -148,7 +148,7 @@ jobs: git config user.email ci@catcrafts.net printf ".review-gates/push/\n.review-gates/trees/\n" > .gitignore mkdir .review-gates - printf "{\"lenses\": {\"repo-better-solution\": {\"run\": true}, \"repo-bug-prover\": {\"run\": true}, \"repo-security\": {\"run\": true}, \"repo-facts\": {\"run\": true}}}\n" > .review-gates/config.json + printf "{\"modes\": {\"commits\": {\"lenses\": [\"repo-better-solution\", \"repo-bug-prover\", \"repo-security\", \"repo-facts\"]}}}\n" > .review-gates/config.json git add -A git commit -q -m base base=$(git rev-parse HEAD) diff --git a/AGENTS.md b/AGENTS.md index 2cdc686..9ec7166 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,7 @@ This file is for changing crafter-review itself. `README.md` says what it is and - Processes run on the seats of the consumer's `.env` (`:SeatPool`). A lens's processes get the environment the config names: `none`; `browser`, a Playwright MCP server per pass (`:Mcp`, `:Pool`); `supabase`, the local stack's database through the `sql` helper (`:PgWire`); or `browser+supabase`. - A browser pass holds a stack of its own (`Pool::Stacks`), and a pass launches only when it fits in free memory at its environment's measured cost (`Pool::Admission`, `:Memory`). - Drivers on one machine share one board, `~/.cache/review-gates/seat-board.json` (`SeatPool::Board`). `State::RunLock` allows one full run per hash. -- The kernel modes (2026-10-31): `--mode series` and `--mode reply` run the pre-send pipeline of a kernel contributor's workbench with the same machinery. Their own pieces: the inventory of a cover letter, a mail draft and the camera review's shape (`:Inventory` PartitionReply, PartitionMail), the ten kernel lenses with their gates (`Plan::Lenses` rows with `kernel`, `Plan::Gates(mode)`), text units keyed on what they cite and the anchor (`Plan::TextDeps`) and a series' code units on the code like the push gates, the persona gate - one `maintainer-persona` instance per `--reviewers` entry, its whole pass pruning the not-mine units - with the persona vocabulary in `:Schema`, `:Normalize` and the matrix, the references prefetched through `utilities/lore.py` and the persona corpora (`:Refs`), the kernel texts in `:Prompt`, the `GATES CLEAN` verdict, `personaPass` in unit-state, and the subsystem's journal in the run layer and the dispositions block. Their prompts, keys and plans were checked byte for byte against the pipeline they replace, on its own artifacts. +- The kernel modes (2026-10-31): `--mode series` and `--mode reply` run the pre-send pipeline of a kernel contributor's workbench with the same machinery. Their own pieces: the inventory of a cover letter, a mail draft and the camera review's shape (`:Inventory` PartitionReply, PartitionMail), the ten kernel lenses with their gates (`Plan::Lenses` rows with `kernel`, `Plan::Gates(kind)`), text units keyed on what they cite and the anchor (`Plan::TextDeps`) and a series' code units on the code like the push gates, the persona gate - one `maintainer-persona` instance per `--reviewers` entry, its whole pass pruning the not-mine units - with the persona vocabulary in `:Schema`, `:Normalize` and the matrix, the references prefetched through `utilities/lore.py` and the persona corpora (`:Refs`), the kernel texts in `:Prompt`, the `GATES CLEAN` verdict, `personaPass` in unit-state, and the subsystem's journal in the run layer and the dispositions block. Their prompts, keys and plans were checked byte for byte against the pipeline they replace, on its own artifacts. ## The rule it lives by @@ -100,7 +100,7 @@ Conventions: - **The dump's `driver` field and the first log line** read `crafter-review (prompt v1)`. Nothing reads them. - **`freeze.json` records `gitVersion`.** When the hashes differ, `freeze --verify`'s MISMATCH names a git version change as the likely cause (Freeze). - **A repository's own lens sections** (`lenses..section` in the config, `Config::LensSectionsOf`, `SectionProblems`): `Prompt::Charter` appends the file the config names for the lens to the shipped charter after one blank line, so `charterSha` and every reuse key cover it; the path is relative and inside the repository (the check `slots.dir` shares: an absolute path or a `..` part is a config problem, and `LensSectionsOf` resolves no other), a file missing or one the run could not use (`Prompt::ReadSection`, the reader `Charter` uses too: a dangling link, no regular file, unreadable, not UTF-8) is a config problem (the run refuses), a name that is no lens is Merge's unknown key, and the run logs which charters got one. Nothing lists a directory (2026-11-01, Jorijn: a path per lens, where a listed directory had to tolerate or refuse every foreign entry) (Config, CommitsMode, KernelModes). -- **A reply's gate-state is beside the draft**, a directory artifact's inside it; the run lock, the dumps and the resume file are named by the mode (`reply--run-N.json`). A kernel mode requires `--subsystem`; commits refuses `--reviewers`; `--reads` is 1 for a reply. The kernel modes check no tree (the lenses read several), excuse no unit from the holes, and their `lensesOff` and `environments` in the dump list the mode's lenses alone (KernelModes). +- **A reply's gate-state is beside the draft**, a directory artifact's inside it; the run lock and the dumps are named by the kind (`reply--run-N.json`), the resume file by the hash alone (`resume-.json`); a mode of another name shares them all, and the dump's `mode` says which ran. A kernel mode requires `--subsystem`; commits refuses `--reviewers`; `--reads` is 1 for a reply. The kernel modes check no tree (the lenses read several), excuse no unit from the holes, and their `lensesOff` and `environments` in the dump list the kind's lenses alone (`Plan::KindLenses`; `lensesOff` those of them the mode does not list) (KernelModes). - **The repository under review is the freeze record's**, not the working directory's: `Run::reviewed` is named in the prompt and keys the calibration, and `--trees` defaults to the record's worktree, else its repository when that is not the working directory, else `.` (`Freeze::ReviewedBy`, `TreeOf`). The working directory stays `Run::repo`: the config, `.env`, the slots, the lens processes' cwd and every logged path. That is how a workbench reviews other repositories with `freeze --repo` (Freeze, Runs). - **`config` reports an absolute `functional.mcp.command` that is no program here**, such as `/usr/bin/npx` inside the Flatpak, whose `/usr` is the Sdk's. `config` then exits 1, and a run stops at preflight (Config). - **JSON edge cases.** @@ -138,7 +138,7 @@ Conventions: - In the Flatpak, a pass's `SHELL` is `/bin/bash` (Platform). - **Commands beyond the gates**: `init`, `doc`, `browser install`, `stop`, and `--version`, which prints the home and the charters' sha256s (Commands). - **Order, not value:** `estimate.cacheWrite`'s last digit depends on the order the passes ran, because it is a float sum. -- **Every lens is off until the consumer's config turns it on** (2026-10-31, Jorijn: "all lenses off by default and opt in only"). `Config::Defaults()` gives every row `run: false`; a config that turns on none - no `lenses` key included - has the one problem that names the lenses, so `config`, `freeze` and `run` all say so, the template ships every lens off, and `init` tells the consumer to turn lenses on (Config, Commands, ReviewFixes, the Goldens' config cases). A consumer config without a `lenses` key needs one. +- **A mode is an artifact kind plus the lenses it runs and its effort** (2026-11-01: `modes.` in the config, `Config::ModeOf`, `ModeNames`, `LensesOff(config, mode)`). The three kinds (`Plan::Kinds`: reply, series, commits) are modes of their own name; a mode of another name states its `kind`. The kind decides the partition and hash, the common text and run layer, the schemas, the disposition and reuse-key rules and what the CLI requires (`Run::Arguments::kind`, `Plan::IsKernelKind`); the mode decides which of the kind's lenses run (`Plan::KindLenses`, in the kind's gate order, `Plan::Gates`) and the effort (`modes..effort`, else the kind's default). Every lens is off until a mode lists it (2026-10-31, Jorijn: "all lenses off by default and opt in only"): `Config::Defaults()` runs no lens, while the template lists every lens of each kind at the kind's effort, to trim (Jorijn, 2026-11-01, on the populated template as the example: "looks good"; `init` says to trim); `lenses..run` is retired with a warning that says so; a lens of another kind, a name that is no lens, a lens listed twice and an unknown effort are config problems; `--mode` names any configured mode, the dump records `mode` and `kind`, and a reuse key carries no mode name, so a clean read bought in one mode counts in another of the same kind. The run lock, the run numbers and the dump names are the kind's (`--run-N.json`): one full run per hash across the modes of a kind, their rounds counting on together; the dump's `mode` says which ran (Config, CommitsMode, KernelModes). - **The commit-truth lens** (`repo-facts.md`, 2026-10-31): Gate 4, after the proofs, so the messages and documents are read once the code they describe has settled. Its block kinds are text, doc and slot: the message units keyed on their text, the base and the code up to their commit (`MessageDeps`); a documentation unit on its file, its normalised text, the base and the code of the whole range (`DocKeyParts`); a diffstat on its text alone; `whole:order` and `whole:absent` in slot passes of its own (a lens with no whole pass asks the slots named in its row; never stored). A documentation file's diff header and a part the commit writes nothing in are mechanical n/a. Budgets 60/40 for a text or doc block, 50/35 for a slot; one read. The owner's gate is 5. The other lenses' prompts, keys and plans are untouched (CommitsMode, CodeKeys, Runs, the Goldens' series and gates). **Known quirks, kept:** diff --git a/README.md b/README.md index 6ce260b..14b4717 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ crafter-review config # edit .review-gates/config.json until it prints no ``` `init` does four things: -- it writes `.review-gates/config.json` from the template when there is none - every lens off: turn on the ones this repository runs; +- it writes `.review-gates/config.json` from the template when there is none - the three artifact kinds as modes with every lens of their kind: trim each to what this repository runs; - it adds `.review-gates/push/` and `.review-gates/trees/` to `.gitignore`; - it writes the `/prepush` skill to `.claude/skills/prepush/SKILL.md`, which a later `init` refreshes (the line after its frontmatter marks it as init's); - it prints the next steps. @@ -52,7 +52,8 @@ It writes nothing in a subdirectory, in your home directory (`--force` when that `.review-gates/config.json` is committed. It says what the pipeline must know about the repository: - who may author its commits, and which branch a range is measured against; -- which lenses run (every lens is off until `lenses..run` is true) and the environment each gets (`none`, `browser`, `supabase` or `browser+supabase`); +- its modes (`modes.`): each an artifact kind (`reply`, `series`, `commits`; a kind is a mode of its own name), the lenses it runs (every lens is off until a mode lists it; they run in their kind's gate order - the table above for commits, the kernel modes' gates below for a series or a reply) and the effort it runs at; `run --mode` names one; +- the environment each lens gets (`none`, `browser`, `supabase` or `browser+supabase`); - each lens's own section (`lenses..section`), a file in the repository appended to that lens's shipped charter, for the calibration entries and conventions that are this repository's rather than the lens's (the reuse keys carry the combined text's hash); a lesson general to the lens goes into the shipped charter; - how its application is started and reached: one stack, or several side by side (`slots`); - where its local database answers; @@ -66,7 +67,7 @@ It writes nothing in a subdirectory, in your home directory (`--force` when that 2. `git submodule deinit -f review-gates && git rm review-gates` 3. `crafter-review init`. If `.claude/skills/prepush` is the old link into `review-gates/`, it removes the link, never writing through it, and makes a real directory with the skill. -`config.json` keeps working unchanged. So do the reuse keys: a range reviewed before the move is reused, not bought again. +An existing `config.json` needs one change since 2026-11-01: `lenses..run` is retired, so list the lenses in `modes.commits.lenses` (`config` names what is missing, and `freeze`, `run` and `slots` refuse until it is there). The reuse keys keep working: a range reviewed before the move is reused, not bought again. ## Running a review @@ -91,14 +92,14 @@ A directory can review repositories other than itself: a workbench whose config, ### The kernel modes: a patch series, a mailing-list reply -Beside the push gates, crafter-review runs the pre-send review a kernel contributor's workbench keeps: `--mode series` over a `git format-patch` export with its cover letter, and `--mode reply` over a mail draft (a reply's gate-state sits beside the draft). Both take `--subsystem `, whose journal - `journal//ledger.md`, `captures/`, `captures/threads/` - is the lenses' evidence, and the trees `--trees` names (default `work/linux,work/milos-linux`; history only in the full clone). lore.kernel.org is reached through `python3 utilities/lore.py` in the working directory, which also prefetches every reference the artifact cites into `gate-state/refs/` before the gates run. The series gates are structure (better-solution), code (code-quality, minimalism, bug-prover) and artifact truth (facts, consistency, process-docs, etiquette, conciseness); a reply runs artifact truth alone. `--reviewers "A B,C D"` adds the persona gate after them: one instance of the maintainer-persona lens per reviewer, calibrated on their own lore mail, its whole pass gating its blocks and pruning the units that are not theirs (`--persona-blocks all` keeps them). Gates clean without a persona pass is GATES CLEAN, not SEND-READY. These lenses are off until the config turns them on, like every other; the effort defaults to max, and a reply's units are read once. +Beside the push gates, crafter-review runs the pre-send review a kernel contributor's workbench keeps: `--mode series` over a `git format-patch` export with its cover letter, and `--mode reply` over a mail draft (a reply's gate-state sits beside the draft). Both take `--subsystem `, whose journal - `journal//ledger.md`, `captures/`, `captures/threads/` - is the lenses' evidence, and the trees `--trees` names (default `work/linux,work/milos-linux`; history only in the full clone). lore.kernel.org is reached through `python3 utilities/lore.py` in the working directory, which also prefetches every reference the artifact cites into `gate-state/refs/` before the gates run. The series gates are structure (better-solution), code (code-quality, minimalism, bug-prover) and artifact truth (facts, consistency, process-docs, etiquette, conciseness); a reply runs an artifact truth of its own alone (facts, etiquette, consistency, conciseness). `--reviewers "A B,C D"` adds the persona gate after them: one instance of the maintainer-persona lens per reviewer, calibrated on their own lore mail, its whole pass gating its blocks and pruning the units that are not theirs (`--persona-blocks all` keeps them). Gates clean without a persona pass is GATES CLEAN, not SEND-READY. These lenses are off until the config turns them on, like every other; the effort defaults to max, and a reply's units are read once. To stop a run, run `crafter-review stop `, or `crafter-review stop --hash ` for every live run of a hash. Its running passes are killed, no later gate starts and the dump is written. A run takes its passes with it however it ends, SIGKILL included: each pass runs under a supervisor that kills its whole tree, the MCP server and Chromium included. `flatpak kill net.catcrafts.CrafterReview` is the hard stop. | Command | What it does | |---|---| | `freeze` | Gate 0: the checks, the export, the hash, the run line; `--verify` at push time | -| `run` | the gates of the mode (`--mode commits`, `series` or `reply`): the lenses, the dump; `--dry-run` launches nothing | +| `run` | the gates of the mode (`--mode`: `commits`, `series`, `reply` or one of the repository's own, as the config defines it): the lenses, the dump; `--dry-run` launches nothing | | `slots` | how many stacks fit in free memory, and the commands for the missing ones | | `config` | the merged config and its problems | | `seats` | every seat probed: both windows, their resets, the drivers running | diff --git a/implementations/Crafter.Review-Config.cpp b/implementations/Crafter.Review-Config.cpp index 8d86a65..f2b539b 100644 --- a/implementations/Crafter.Review-Config.cpp +++ b/implementations/Crafter.Review-Config.cpp @@ -81,13 +81,25 @@ namespace Crafter::Review::Config { return value.is_number_float() ? value.get() > 0.0 : (value.is_number_unsigned() ? value.get() > 0 : value.get() > 0); } + // A mode's defaults: its kind (null for a name that is no kind - the + // config says which), no lens, the kind's effort. + Json ModeDefaults(std::string_view kind) { + return {{"kind", kind.empty() ? Json() : Json(std::string(kind))}, {"lenses", Json::array()}, {"effort", nullptr}}; + } + std::vector Merge(Json& destination, const Json& source, const std::string& path) { std::vector problems; for (auto it = source.begin(); it != source.end(); ++it) { const std::string& key = it.key(); if (!Dyn::Has(destination, key)) { - problems.push_back(std::format("unknown key {}{}", path, key)); - continue; + // A mode of the repository's own name: its defaults, then + // the config's values over them. + if (path == "modes.") { + destination[key] = ModeDefaults(""); + } else { + problems.push_back(std::format("unknown key {}{}", path, key)); + continue; + } } Json& target = destination[key]; if (target.is_object() && it.value().is_object()) { @@ -120,12 +132,17 @@ namespace Crafter::Review::Config { } Json Defaults() { + // Each lens's environment, and its section: a file of the repository's + // own appended to its shipped charter (null: none). Which lenses run is + // the modes'. Json lenses = Json::object(); - // Every lens is off until the repository's config turns it on - // (2026-10-31, Jorijn: "all lenses off by default and opt in only"); - // its section is a file of the repository's own appended to its - // shipped charter (null: none). - for (const Plan::Lens& lens : Plan::Lenses) lenses[Plan::LensName(lens.file)] = {{"run", false}, {"environment", lens.env}, {"section", nullptr}}; + for (const Plan::Lens& lens : Plan::Lenses) lenses[Plan::LensName(lens.file)] = {{"environment", lens.env}, {"section", nullptr}}; + // Every lens is off until a mode lists it (2026-10-31, Jorijn: "all + // lenses off by default and opt in only"): the three artifact kinds + // are modes of their own name with no lens; a mode of any name is a + // kind, the lenses it runs and the effort it runs at (2026-11-01). + Json modes = Json::object(); + for (const std::string_view kind : Plan::Kinds) modes[std::string(kind)] = ModeDefaults(kind); return { // The integration branch a range is measured against (e.g. // origin/dev): merge-base HEAD . @@ -142,14 +159,16 @@ namespace Crafter::Review::Config { {"aiTrailers", "forbid"}, // An unsigned commit fails the freeze (else a warning). {"requireSigned", false}, + // The modes: {: {kind, lenses, effort}}. + {"modes", modes}, // Tracked paths the local stack rewrites in place while the lenses // run. {"allowDirty", Json::array()}, // The commands Gate 0 runs by hand, in order (documentation for the // skill; the driver runs none of them). {"gate0", Json::array()}, - // Per lens: run (off until true: every lens is opt-in) and - // environment (Plan::Environments). + // Per lens: its environment (Plan::Environments); which lenses + // run is the modes'. {"lenses", lenses}, // The database under test, for every lens whose environment // reaches it. The URL reaches a db pass's broker, never a prompt, @@ -198,6 +217,17 @@ namespace Crafter::Review::Config { data[std::string(retired.section)].erase(std::string(retired.key)); } } + // lenses..run is retired (2026-11-01): a mode lists what runs. + if (const Json& given = Dyn::Get(data, "lenses"); given.is_object()) { + std::vector retiredRuns; + for (auto it = given.begin(); it != given.end(); ++it) { + if (it.value().is_object() && Dyn::Has(it.value(), "run")) retiredRuns.push_back(it.key()); + } + for (const std::string& name : retiredRuns) { + warnings.push_back(std::format("lenses.{}.run is no longer read - remove it from {}: a mode lists the lenses it runs (modes..lenses)", name, p.filename().string())); + data["lenses"][name].erase("run"); + } + } loaded.problems = Merge(config, data, ""); } else if (!path.empty()) { loaded.problems.push_back(std::format("{}: no such file", p.string())); @@ -243,31 +273,84 @@ namespace Crafter::Review::Config { const Json environments = EnvironmentTable(); Json lenses = Dyn::At(config, "lenses"); if (!lenses.is_object()) { - out.push_back(std::format("lenses must be an object: {{: {{run: bool, environment: {}}}}}", EnvironmentNames("|"))); + out.push_back(std::format("lenses must be an object: {{: {{environment: {}}}}}", EnvironmentNames("|"))); lenses = Json::object(); } for (auto it = lenses.begin(); it != lenses.end(); ++it) { const Json& entry = it.value(); if (!entry.is_object()) { - out.push_back(std::format("lenses.{} must be an object {{run: bool, environment: {}}}", it.key(), EnvironmentNames("|"))); + out.push_back(std::format("lenses.{} must be an object {{environment: {}}}", it.key(), EnvironmentNames("|"))); continue; } - if (!Dyn::Get(entry, "run").is_boolean()) out.push_back(std::format("lenses.{}.run must be true or false", it.key())); if (!Dyn::In(Dyn::Get(entry, "environment"), environments)) out.push_back(std::format("lenses.{}.environment must be one of {}", it.key(), EnvironmentNames(", "))); const Json& section = Dyn::Get(entry, "section"); if (!section.is_null() && !InsideRepository(section)) out.push_back(std::format("lenses.{}.section must be null or a file inside the repository (a relative path)", it.key())); } - auto runs = [](const Json& entry) { return entry.is_object() && Dyn::Get(entry, "run") == true; }; - // Every lens is off until the repository turns it on: a config that - // turns on none has nothing to run. - if (std::ranges::none_of(Plan::Lenses, [&](const Plan::Lens& lens) { return runs(Dyn::Get(lenses, Plan::LensName(lens.file))); })) { - std::vector names; - for (const Plan::Lens& lens : Plan::Lenses) names.push_back(Plan::LensName(lens.file)); - out.push_back(std::format("lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository ({})", Dyn::Join(names, ", "))); + // The modes (2026-11-01): each an artifact kind and the lenses it + // runs, at an effort of its own when set. Every lens is off until a + // mode lists it. + auto joined = [](std::span items, std::string_view separator) { + std::vector strings(items.begin(), items.end()); + return Dyn::Join(strings, separator); + }; + std::set listed; + const Json& modes = Dyn::At(config, "modes"); + if (!modes.is_object()) { + out.push_back(std::format("modes must be an object: {{: {{kind: {}, lenses: [, ...], effort: {}|null}}}}", joined(Plan::Kinds, "|"), joined(Plan::Efforts, "|"))); + } else { + for (auto it = modes.begin(); it != modes.end(); ++it) { + const std::string& name = it.key(); + const Json& mode = it.value(); + if (!mode.is_object()) { + out.push_back(std::format("modes.{} must be an object {{kind, lenses, effort}}", name)); + continue; + } + const Json& kindValue = Dyn::Get(mode, "kind"); + std::string kind; + if (std::ranges::contains(Plan::Kinds, std::string_view(name))) { + if (!kindValue.is_null() && kindValue != name) out.push_back(std::format("modes.{}.kind must be {} (the mode of a kind's name is that kind) or null", name, name)); + kind = name; + } else if (kindValue.is_string() && std::ranges::contains(Plan::Kinds, std::string_view(kindValue.get_ref()))) { + kind = kindValue.get(); + } else { + out.push_back(std::format("modes.{}.kind must name the artifact kind the mode reviews ({})", name, joined(Plan::Kinds, ", "))); + } + const Json& lensList = Dyn::Get(mode, "lenses"); + if (!AllStrings(lensList)) { + out.push_back(std::format("modes.{}.lenses must be a list of lens names", name)); + } else if (!kind.empty()) { + const std::vector ofKind = Plan::KindLenses(kind); + std::vector ofKindNames; + for (const std::string& lf : ofKind) ofKindNames.push_back(Plan::LensName(lf)); + std::set seen; + for (const Json& item : lensList) { + const std::string lf = std::format("{}.md", item.get()); + if (!std::ranges::contains(ofKind, lf)) { + out.push_back(std::format("modes.{}: {} is no lens of a {} ({})", name, Dyn::Str(item), kind, Dyn::Join(ofKindNames, ", "))); + } else if (!seen.insert(lf).second) { + out.push_back(std::format("modes.{}: {} is listed twice", name, Dyn::Str(item))); + } else { + listed.insert(lf); + } + } + } + const Json& effort = Dyn::Get(mode, "effort"); + if (!effort.is_null() && !(effort.is_string() && std::ranges::contains(Plan::Efforts, std::string_view(effort.get_ref())))) { + out.push_back(std::format("modes.{}.effort must be one of {} or null (the kind's default: {} for a reply or a series, {} for a commit range)", name, joined(Plan::Efforts, ", "), + Plan::KernelEffort, Plan::Effort)); + } + } + if (listed.empty()) { + std::vector names; + for (const Plan::Lens& lens : Plan::Lenses) names.push_back(Plan::LensName(lens.file)); + out.push_back(std::format("modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses ({})", Dyn::Join(names, ", "))); + } } + auto runs = [&](std::string_view lensFile) { return listed.contains(std::string(lensFile)); }; for (const Plan::Lens& lens : Plan::Lenses) { + if (!lens.empirical || !runs(lens.file)) continue; const Json& entry = Dyn::Get(lenses, Plan::LensName(lens.file)); - if (!lens.empirical || !runs(entry)) continue; + if (!entry.is_object()) continue; const Json& name = Dyn::Get(entry, "environment"); if (!Dyn::In(name, environments)) continue; if (!Plan::FindEnvironment(name.get_ref())->app) { @@ -275,7 +358,7 @@ namespace Crafter::Review::Config { for (const Plan::Environment& environment : Plan::Environments) { if (environment.app) withApp.emplace_back(environment.name); } - out.push_back(std::format("lenses.{}: an empirical lens needs an environment with an application under test ({}) - or turn it off", Plan::LensName(lens.file), + out.push_back(std::format("lenses.{}: an empirical lens needs an environment with an application under test ({}) - or no mode lists it", Plan::LensName(lens.file), Dyn::Join(withApp, ", "))); } } @@ -296,8 +379,10 @@ namespace Crafter::Review::Config { return out; } // The application's URL matters only to a lens that drives it. - const bool drivesApp = std::ranges::any_of(lenses, [&](const Json& entry) { - if (!runs(entry)) return false; + const bool drivesApp = std::ranges::any_of(Plan::Lenses, [&](const Plan::Lens& lens) { + if (!runs(lens.file)) return false; + const Json& entry = Dyn::Get(lenses, Plan::LensName(lens.file)); + if (!entry.is_object()) return false; const Json& name = Dyn::Get(entry, "environment"); if (name.is_array() || name.is_object()) throw Dyn::Error("TypeError", std::format("unhashable type: '{}'", Dyn::TypeName(name))); const Plan::Environment* environment = name.is_string() ? Plan::FindEnvironment(name.get_ref()) : nullptr; @@ -410,10 +495,58 @@ namespace Crafter::Review::Config { return out; } + std::optional ModeOf(const Json& config, std::string_view name) { + const Json& modes = Dyn::Get(config, "modes"); + if (!modes.is_object() || !Dyn::Has(modes, name)) return std::nullopt; + const Json& mode = Dyn::At(modes, name); + if (!mode.is_object()) return std::nullopt; + Mode out{std::string(name), {}, {}, std::nullopt}; + const Json& kind = Dyn::Get(mode, "kind"); + if (std::ranges::contains(Plan::Kinds, name)) { + out.kind = std::string(name); + } else if (kind.is_string() && std::ranges::contains(Plan::Kinds, std::string_view(kind.get_ref()))) { + out.kind = kind.get(); + } else { + return std::nullopt; + } + const Json& list = Dyn::Get(mode, "lenses"); + if (!AllStrings(list)) return std::nullopt; + std::set wanted; + for (const Json& item : list) wanted.insert(std::format("{}.md", item.get())); + for (const std::string& lf : Plan::KindLenses(out.kind)) { + if (wanted.contains(lf)) out.lensFiles.push_back(lf); + } + const Json& effort = Dyn::Get(mode, "effort"); + if (effort.is_string() && !effort.get_ref().empty()) out.effort = effort.get(); + return out; + } + + std::vector ModeNames(const Json& config) { + std::vector out; + const Json& modes = Dyn::Get(config, "modes"); + if (!modes.is_object()) return out; + for (auto it = modes.begin(); it != modes.end(); ++it) out.push_back(it.key()); + return out; + } + std::vector LensesOff(const Json& config) { + std::set listed; + for (const std::string& name : ModeNames(config)) { + if (const std::optional mode = ModeOf(config, name)) listed.insert(mode->lensFiles.begin(), mode->lensFiles.end()); + } std::vector out; for (const Plan::Lens& lens : Plan::Lenses) { - if (Dyn::Get(LensEntry(config, lens.file), "run") == false) out.emplace_back(lens.file); + if (!listed.contains(std::string(lens.file))) out.emplace_back(lens.file); + } + return out; + } + + std::vector LensesOff(const Json& config, std::string_view modeName) { + std::vector out; + const std::optional mode = ModeOf(config, modeName); + if (!mode) return out; + for (const std::string& lf : Plan::KindLenses(mode->kind)) { + if (!std::ranges::contains(mode->lensFiles, lf)) out.push_back(lf); } return out; } @@ -500,6 +633,14 @@ namespace Crafter::Review::Config { example["authors"] = {"you@example.com"}; example["aiTrailers"] = "forbid"; example["gate0"] = {"make check"}; + // The three kinds as modes, each with every lens of its kind at the + // kind's effort: trim each to what the repository runs. + for (const std::string_view kind : Plan::Kinds) { + Json names = Json::array(); + for (const std::string& lf : Plan::KindLenses(kind)) names.push_back(Plan::LensName(lf)); + example["modes"][std::string(kind)]["lenses"] = names; + example["modes"][std::string(kind)]["effort"] = std::string(Plan::DefaultEffort(kind)); + } Json& functional = example["functional"]; functional["appUrl"] = "http://localhost:8080"; functional["start"] = "make run"; diff --git a/implementations/Crafter.Review-Driver.cpp b/implementations/Crafter.Review-Driver.cpp index fe88ef4..52451bb 100644 --- a/implementations/Crafter.Review-Driver.cpp +++ b/implementations/Crafter.Review-Driver.cpp @@ -267,12 +267,11 @@ inventory unit, against a frozen commit range.)"; {"--verify", false, "", "push-time check: HEAD, a clean tree and a fresh export still match freeze.json"}, }}; - constexpr std::array Modes = {"reply", "series", "commits"}; constexpr std::array Harnesses = {"claude", "opencode"}; constexpr std::array PersonaBlocks = {"pruned", "all"}; const std::vector& RunOptions() { - static const std::string effort = std::format("reasoning effort of every lens process (default {})", Plan::Effort); + static const std::string effort = std::format("reasoning effort of every lens process (default: the mode's, else {} for a reply or a series and {} for a commit range)", Plan::KernelEffort, Plan::Effort); static const std::string reads = std::format("clean reads a code unit of the static code lenses needs before its verdict is reused, one per round (default {})", Plan::Reads); static const std::string models = [] { @@ -282,7 +281,7 @@ inventory unit, against a frozen commit range.)"; }(); static const std::string variant = std::format("opencode harness: the model's reasoning variant (default {})", OpenCode::DefaultVariant); static const std::vector specs = { - {"--mode", true, "MODE", "", Kind::String, Modes, false, true}, + {"--mode", true, "MODE", "a mode of the config (modes.: its artifact kind, the lenses it runs, its effort; default commits)"}, {"--subsystem", true, "SUBSYSTEM", "journal subsystem: required for reply and series; optional in commits mode (names the ledger and captures the lenses may read)"}, {"--artifact", true, "ARTIFACT", "the frozen artifact: a dir of *.patch (.review-gates/push//, a series), or the reply draft file"}, {"--hash", true, "HASH", ""}, @@ -336,6 +335,12 @@ inventory unit, against a frozen commit range.)"; Run::Arguments a; a.config = configPath; a.mode = parsed.Value("mode").value_or("commits"); + const std::optional mode = Config::ModeOf(*config, a.mode); + if (!mode) { + return Die(std::format("--mode {}: no such mode in the config (modes: {}) - a mode is an artifact kind and the lenses it runs, modes. in .review-gates/config.json", a.mode, + Dyn::Join(Config::ModeNames(*config), ", "))); + } + a.kind = mode->kind; a.subsystem = parsed.Value("subsystem"); a.artifact = parsed.Value("artifact").value_or(""); if (a.artifact.empty()) return UsageError(UsageLine(Prog, specs), Prog, "the following arguments are required: --artifact"); @@ -369,8 +374,8 @@ inventory unit, against a frozen commit range.)"; if (*a.concurrency < 1) return UsageError(UsageLine(Prog, specs), Prog, "argument --concurrency: a number from 1 up (leave it out for no cap)"); } a.effort = parsed.Value("effort").value_or(""); - if (a.effort.empty()) a.effort = std::string(Plan::DefaultEffort(a.mode)); - if (a.mode == "commits") { + if (a.effort.empty()) a.effort = mode->effort.value_or(std::string(Plan::DefaultEffort(a.kind))); + if (a.kind == "commits") { if (!a.reviewers.empty()) return Die("commits mode has no persona gate (the owner is the last gate): drop --reviewers"); } else { if (!a.subsystem || a.subsystem->empty()) return Die(std::format("{} mode: --subsystem is required", a.mode)); @@ -391,10 +396,10 @@ inventory unit, against a frozen commit range.)"; a.variant = parsed.Value("variant"); if (!a.variant || a.variant->empty()) a.variant = a.harness == "opencode" ? std::optional(std::string(OpenCode::DefaultVariant)) : std::nullopt; a.opencodeBin = parsed.Value("opencode-bin"); - a.reads = Plan::ReadsFor(a.mode); + a.reads = Plan::ReadsFor(a.kind); if (const std::optional reads = parsed.Value("reads")) { const std::int64_t n = *Dyn::ParseInt(*reads); - if (n < 1 || (a.mode == "reply" && n != 1)) return Die("--reads: a number from 1 up; a reply has no code units and is read once"); + if (n < 1 || (a.kind == "reply" && n != 1)) return Die("--reads: a number from 1 up; a reply has no code units and is read once"); a.reads = n; } for (const std::string& part : Dyn::Split(a.models, ",")) { @@ -857,7 +862,7 @@ inventory unit, against a frozen commit range.)"; } else { fs::create_directories(config.parent_path()); std::ofstream(config, std::ios::binary) << Platform::ReadFile(Home() / "process" / "templates" / "config.json").value_or("{}\n"); - std::println(" wrote .review-gates/config.json (the template: every lens off - turn on the ones this repository runs, set base, then what their environments need)"); + std::println(" wrote .review-gates/config.json (the template: the three kinds as modes with every lens of their kind - trim each to what this repository runs, set base, then what the lenses' environments need)"); } // The artifacts and the worktrees stay out of git. const fs::path ignore = cwd / ".gitignore"; @@ -902,7 +907,10 @@ inventory unit, against a frozen commit range.)"; if (!std::ranges::contains(off, it.key()) && environment && !environment->mcp.empty()) browser = true; } std::println("\nnext:\n {} config - until it reports no problem", Invocation()); - if (off.size() == Plan::Lenses.size()) std::println(" turn lenses on in .review-gates/config.json (lenses..run = true) - every lens is off until the repository says which run"); + // Only a config that runs no lens has a lens step left: the + // template init wrote says to trim itself, on its own line, and + // a kept config that lists lenses is the repository's own. + if (off.size() == Plan::Lenses.size()) std::println(" list the lenses each mode runs in .review-gates/config.json (modes..lenses) - every lens is off until a mode lists it"); if (browser) std::println(" {} browser install - a lens drives the application in Chromium", Invocation()); return 0; } diff --git a/implementations/Crafter.Review-Plan.cpp b/implementations/Crafter.Review-Plan.cpp index 21fd5d0..1364380 100644 --- a/implementations/Crafter.Review-Plan.cpp +++ b/implementations/Crafter.Review-Plan.cpp @@ -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& Gates(std::string_view mode) { + const std::vector& Gates(std::string_view kind) { static const std::vector 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 ModeLenses(std::string_view mode) { + std::vector KindLenses(std::string_view kind) { std::vector 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 GatesFor(std::string_view mode, std::span only, std::span without, std::span 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(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; diff --git a/implementations/Crafter.Review-Run.cpp b/implementations/Crafter.Review-Run.cpp index 4f73367..cf6117d 100644 --- a/implementations/Crafter.Review-Run.cpp +++ b/implementations/Crafter.Review-Run.cpp @@ -173,6 +173,7 @@ namespace Crafter::Review::Run { return { {"config", opt(a.config)}, {"mode", a.mode}, + {"kind", a.kind}, {"subsystem", opt(a.subsystem)}, {"artifact", a.artifact}, {"hash", opt(a.hash)}, @@ -331,12 +332,12 @@ namespace Crafter::Review::Run { a = std::move(given); cfg = std::move(config); // The lens files the config turns off, and the environment of each. - off = Config::LensesOff(cfg); + off = Config::LensesOff(cfg, a.mode); environments = Config::Environments(cfg); } bool Run::Kernel() const { - return Plan::IsKernelMode(a.mode); + return Plan::IsKernelKind(a.kind); } Run::~Run() { @@ -380,10 +381,10 @@ namespace Crafter::Review::Run { // ---- Gate 0 ------------------------------------------------------------------------- void Run::Gate0() { if (!fs::exists(a.artifact)) Die(std::format("artifact {} does not exist", a.artifact)); - hash = Artifact::ArtifactHash(a.mode, a.artifact); + hash = Artifact::ArtifactHash(a.kind, a.artifact); if (a.hash && !a.hash->empty() && *a.hash != hash) Die(std::format("--hash {} does not match the artifact ({})", Dyn::Slice(*a.hash, 0, 12), hash.substr(0, 12))); h8 = hash.substr(0, 8); - if (a.mode == "commits") { + if (a.kind == "commits") { // The lenses read the live checkout named first in --trees: it // must be the range HEAD the artifact was cut from, and clean - // anything else is evidence the review never saw (re-checked @@ -398,11 +399,11 @@ namespace Crafter::Review::Run { } // A reply's gate-state sits beside the draft; a directory artifact's // inside it. - gateDir = Dyn::JoinPath(a.mode == "reply" ? Dyn::DirName(a.artifact) : a.artifact, "gate-state"); + gateDir = Dyn::JoinPath(a.kind == "reply" ? Dyn::DirName(a.artifact) : a.artifact, "gate-state"); fs::create_directories(gateDir); Inventory::Built built; try { - built = Inventory::Build(a.mode, a.artifact); + built = Inventory::Build(a.kind, a.artifact); } catch (const Inventory::InvariantError& e) { Die(e.what()); } catch (const Dyn::Error& e) { @@ -430,19 +431,23 @@ namespace Crafter::Review::Run { // same, clobber the resume file and buy every pass twice. The lock is // the kernel's: a killed driver releases it, no lens child inherits it. // A test or dry run takes none but numbers itself past a live holder. + // The lock, the run numbers and the dump names are the kind's: the + // gate-state is shared by every mode of a kind, so one full run per + // hash holds across them and their rounds count on together; the dump + // says which mode ran (2026-11-01, the first self-review). Json holder; if (!a.dryRun && !testMode) { try { - runLock = std::make_unique(gateDir, a.mode, h8); + runLock = std::make_unique(gateDir, a.kind, h8); } catch (const State::RunLocked& e) { const std::string how = Platform::InSandbox() ? std::format("wait for it or stop it (`{} stop `)", Invocation()) : std::string("wait for it or kill it"); Die(std::format("a full run on this hash is already live in {}: {} - {}; a dead holder releases the lock", Dyn::RelPath(gateDir.string(), repo.string()), e.what(), how)); } } else { - const std::optional found = State::LockHolder(gateDir, a.mode, h8); + const std::optional found = State::LockHolder(gateDir, a.kind, h8); if (found) holder = *found; } - std::int64_t runN = State::NextRunNumber(gateDir, a.mode, h8); + std::int64_t runN = State::NextRunNumber(gateDir, a.kind, h8); if (Dyn::Truthy(holder) && Dyn::Truthy(Dyn::Get(holder, "run_n"))) runN = std::max(runN, Int(holder.at("run_n")) + 1); const fs::path scratchRoot = ScratchRoot(a.scratch); // A run killed before its first checkpoint left no dump but did leave @@ -460,7 +465,7 @@ namespace Crafter::Review::Run { // run that bought nothing (no outputs in its dir) would otherwise have // its number taken again and its dump written over. auto dumped = [&](std::int64_t n) { - return fs::exists(gateDir / std::format("{}-{}-run-{}.json", a.mode, h8, n)) || fs::exists(gateDir / std::format("{}-{}-run-{}-test.json", a.mode, h8, n)); + return fs::exists(gateDir / std::format("{}-{}-run-{}.json", a.kind, h8, n)) || fs::exists(gateDir / std::format("{}-{}-run-{}-test.json", a.kind, h8, n)); }; while (!a.dryRun) { const fs::path candidate = scratchRoot / std::format("{}-run-{}", h8, runN); @@ -488,7 +493,7 @@ namespace Crafter::Review::Run { const std::lock_guard guard(shared); stopFile = scratch / "stop"; } - dumpPath = a.dryRun ? scratch / "dry-run.json" : gateDir / std::format("{}-{}-run-{}{}.json", a.mode, h8, runN, testMode ? "-test" : ""); + dumpPath = a.dryRun ? scratch / "dry-run.json" : gateDir / std::format("{}-{}-run-{}{}.json", a.kind, h8, runN, testMode ? "-test" : ""); if (runLock) { runLock->WriteInfo({{"pid", Platform::PublishedPid()}, {"since", Platform::LocalTime("%Y-%m-%dT%H:%M:%S")}, @@ -497,18 +502,18 @@ namespace Crafter::Review::Run { {"run_n", runN}}); } logFile = scratch / "log.txt"; - log(std::format("crafter-review {} prompt v{}: {} {} @ {}", Version(), PromptVersion, a.mode, Dyn::RelPath(a.artifact, repo.string()), hash)); + log(std::format("crafter-review {} prompt v{}: {}{} {} @ {}", Version(), PromptVersion, a.mode, a.mode == a.kind ? std::string() : std::format(" (a {})", a.kind), Dyn::RelPath(a.artifact, repo.string()), hash)); std::error_code canonical; - if (a.mode == "commits" && fs::weakly_canonical(reviewed, canonical) != fs::weakly_canonical(repo, canonical)) log(std::format("repository under review: {} (read at {})", reviewed.string(), trees.front())); + if (a.kind == "commits" && fs::weakly_canonical(reviewed, canonical) != fs::weakly_canonical(repo, canonical)) log(std::format("repository under review: {} (read at {})", reviewed.string(), trees.front())); log(std::format("config: {}", Dyn::Truthy(Dyn::Get(cfg, "_path")) ? Dyn::Str(cfg.at("_path")) : std::string("defaults (no .review-gates/config.json)"))); for (const Json& w : Dyn::Or(Dyn::Get(cfg, "_warnings"), Dyn::EmptyList())) log(std::format("config: warning: {}", Dyn::Str(w))); // The mode's lenses: the others are another mode's and say nothing // about this run. - const std::vector modeLenses = Plan::ModeLenses(a.mode); + const std::vector modeLenses = Plan::KindLenses(a.kind); std::vector lensParts; for (const std::string& lf : modeLenses) { const std::string name = Plan::LensName(lf); - lensParts.push_back(std::ranges::contains(off, lf) ? std::format("{} OFF (lenses.{}.run = false)", name, name) : std::format("{} ({})", name, Dyn::Str(Dyn::At(environments, lf)))); + lensParts.push_back(std::ranges::contains(off, lf) ? std::format("{} OFF (not in modes.{}.lenses)", name, a.mode) : std::format("{} ({})", name, Dyn::Str(Dyn::At(environments, lf)))); } log(std::format("lenses: {}", Dyn::Join(lensParts, ", "))); std::vector running; @@ -534,7 +539,7 @@ namespace Crafter::Review::Run { if (!unknown.empty()) Die(std::format("--units names ids not in the inventory: {}", Dyn::Join(unknown, ", "))); } dispEntries = Dispositions::Load(a.dispositions.value_or("")); - dispRows = Dispositions::Classify(dispEntries, hash, a.artifact, a.mode); + dispRows = Dispositions::Classify(dispEntries, hash, a.artifact, a.kind); inForce = Dispositions::InForce(dispEntries, dispRows); std::vector rowParts; for (const Json& r : dispRows) rowParts.push_back(std::format("{}:{}", Dyn::Str(r.at("id")), Dyn::Str(r.at("status")))); @@ -543,7 +548,7 @@ namespace Crafter::Review::Run { // The references the artifact cites, prefetched into the // gate-state (a dry run reads an existing index and fetches // nothing), and each reviewer's calibration map. - const std::string text = a.mode == "reply" ? Dyn::ReadText(fs::path(a.artifact)) : Dyn::Join(lines, "\n"); + const std::string text = a.kind == "reply" ? Dyn::ReadText(fs::path(a.artifact)) : Dyn::Join(lines, "\n"); refUrls = Refs::ParseRefs(text); const fs::path refsDir = gateDir / "refs"; const fs::path indexPath = gateDir / std::format("refs-{}.json", h8); @@ -633,10 +638,10 @@ namespace Crafter::Review::Run { charters[lf] = Prompt::Charter(lf); charterSha[lf] = Plan::Sha(charters[lf]); } - systemText = Prompt::SystemText(a.mode); + systemText = Prompt::SystemText(a.kind); systemPaths.clear(); systemByLens.clear(); - runLayer = Kernel() ? Prompt::RunLayerKernel(a.mode, Dyn::RelPath(a.artifact, repo.string()), a.subsystem.value_or(""), a.base, reviewers, a.date, trees, hash, lines, units, refDigest, contextPaths) + runLayer = Kernel() ? Prompt::RunLayerKernel(a.kind, Dyn::RelPath(a.artifact, repo.string()), a.subsystem.value_or(""), a.base, reviewers, a.date, trees, hash, lines, units, refDigest, contextPaths) : Prompt::RunLayer(Dyn::RelPath(a.artifact, repo.string()), a.base, a.date, trees, hash, lines, units, contextPaths, Basename(reviewed), a.subsystem.value_or("")); // The application under test: the login fixture, the TOTP helper. const Json& fcfg = cfg.at("functional"); @@ -644,7 +649,7 @@ namespace Crafter::Review::Run { stacks = LoadStacks(); const Json first = *stacks->First(); appBlock = Prompt::AppBlock(fcfg, loginFixture, Pool::TotpCommand(loginFixture), slotMode); - schemas = {{"block", Schema::SchemaJson("block", a.mode)}, {"whole", Schema::SchemaJson("whole", a.mode)}}; + schemas = {{"block", Schema::SchemaJson("block", a.kind)}, {"whole", Schema::SchemaJson("whole", a.kind)}}; // Memory: every pass's tree measured, its peak recorded per repository // and environment; the budget per gate. calibration = std::make_unique(scratchRoot / "memory.json", reviewed, DefaultCosts()); @@ -659,11 +664,12 @@ namespace Crafter::Review::Run { }); } // The database URLs stay here: a db pass gets the broker's address and - // its stack's token. + // its stack's token. Only a lens this mode runs counts: `off` is the + // mode's own kind's, as every reader of it reads it. bool anyDb = false; for (auto it = environments.begin(); it != environments.end(); ++it) { const Plan::Environment* e = Plan::FindEnvironment(Dyn::Str(it.value())); - anyDb = anyDb || (e && e->db && !std::ranges::contains(off, it.key())); + anyDb = anyDb || (e && e->db && std::ranges::contains(modeLenses, it.key()) && !std::ranges::contains(off, it.key())); } if (!a.dryRun && anyDb) { broker = std::make_unique(); @@ -1183,8 +1189,8 @@ namespace Crafter::Review::Run { // ---- reuse -------------------------------------------------------------------------- const Plan::CommitCode& Run::RangeCode() { if (!commitCode) { - commitCode = Plan::CommitCodeOf(Artifact::PatchTexts(a.artifact), a.mode == "series"); - if (a.mode == "series" && !a.base.empty()) { + commitCode = Plan::CommitCodeOf(Artifact::PatchTexts(a.artifact), a.kind == "series"); + if (a.kind == "series" && !a.base.empty()) { // A kernel export carries a base-commit line only when it was // cut with --base, and then in the cover letter: the // submission base the run was given is part of every code @@ -1200,7 +1206,7 @@ namespace Crafter::Review::Run { // a part, re-normalised against today's dispositions, with its summary // for the block passes. nullopt: the whole pass runs. const std::string lensFile = Dyn::Str(job.at("lensFile")); - if (a.mode != "commits") { + if (a.kind != "commits") { // Only the push gates reuse a whole pass; a kernel whole pass // runs on every hash. job["reuseKey"] = nullptr; @@ -1268,7 +1274,7 @@ namespace Crafter::Review::Run { // rule, 2026-10-06.6); every text unit on its text and what it // cites: the [n] URLs, the anchor when it is dated, the R-units // it names. - if (a.mode == "series" && Dyn::Get(job, "unitKind") == "code") return Plan::CodeReuseKey(job, lensFile, text, RangeCode(), ids, charterSha.at(lensFile), PromptVersion, env); + if (a.kind == "series" && Dyn::Get(job, "unitKind") == "code") return Plan::CodeReuseKey(job, lensFile, text, RangeCode(), ids, charterSha.at(lensFile), PromptVersion, env); return Plan::ReuseKey(job, lensFile, text, Plan::TextDeps(text, units, lines, refUrls, anchorText), ids, charterSha.at(lensFile), PromptVersion); } return Plan::BlockReuseKey(job, lensFile, text, RangeCode(), ids, charterSha.at(lensFile), PromptVersion, env); @@ -1428,12 +1434,12 @@ namespace Crafter::Review::Run { const std::lock_guard guard(shared); if (sched) sched->Abort(reason); }; - const std::string now = Artifact::ArtifactHash(a.mode, a.artifact); + const std::string now = Artifact::ArtifactHash(a.kind, a.artifact); if (now != hash) { abortPool(std::format("artifact changed during the run ({} != {})", now.substr(0, 8), h8)); return {{{"label", job.at("label")}, {"status", "error"}, {"reason", "artifact changed during the run - aborted"}}, nullptr}; } - if (const std::optional drift = a.mode == "commits" ? treeDrift() : std::nullopt) { + if (const std::optional drift = a.kind == "commits" ? treeDrift() : std::nullopt) { abortPool(std::format("evidence drift during the run ({})", *drift)); return {{{"label", job.at("label")}, {"status", "error"}, {"reason", std::format("{} - aborted", *drift)}}, nullptr}; } @@ -1849,11 +1855,11 @@ namespace Crafter::Review::Run { } reviewers = CommaList(a.reviewers); if (!reviewers.empty() && std::ranges::contains(off, std::string(Plan::PersonaLens))) { - Die(std::format("--reviewers names {} but the config turns off the persona lens (lenses.{}.run = false): turn it on, or drop --reviewers", Dyn::Join(reviewers, ", "), + Die(std::format("--reviewers names {} but modes.{}.lenses does not list the persona lens ({}): list it, or drop --reviewers", Dyn::Join(reviewers, ", "), a.mode, Plan::LensName(Plan::PersonaLens))); } Gate0(); - const std::vector modeLenses = Plan::ModeLenses(a.mode); + const std::vector modeLenses = Plan::KindLenses(a.kind); std::vector only; if (a.only && !a.only->empty()) { only = CommaList(*a.only); @@ -1872,7 +1878,7 @@ namespace Crafter::Review::Run { if (std::ranges::contains(off, lf)) isOff.push_back(lf); } if (!isOff.empty()) { - Die(std::format("--only names {}, which the config turns off (lenses.{}.run = false): the config is the record - edit it for a test run", Dyn::Join(isOff, ", "), Plan::LensName(isOff.front()))); + Die(std::format("--only names {}, which modes.{}.lenses does not list: the config is the record - edit it for a test run", Dyn::Join(isOff, ", "), a.mode)); } } std::vector without; @@ -1890,7 +1896,7 @@ namespace Crafter::Review::Run { if (!foreign.empty()) Die(std::format("--without names lens file(s) the {} mode never runs: {}", a.mode, Dyn::Join(foreign, ", "))); log(std::format("without: {} - left out of this run by owner ruling (recorded in the dump's args); the run stays a full run", Dyn::Join(without, ", "))); } - const std::vector gates = Plan::GatesFor(a.mode, only, without, off); + const std::vector gates = Plan::GatesFor(a.kind, only, without, off); if (gates.empty()) { std::vector why; std::vector offHere; @@ -1898,7 +1904,7 @@ namespace Crafter::Review::Run { if (std::ranges::contains(modeLenses, lf)) offHere.push_back(lf); } if (offHere.size() == modeLenses.size()) { - why.emplace_back("the config turns on no lens (every lens is off until lenses..run = true turns it on)"); + why.emplace_back(std::format("modes.{}.lenses lists no lens of this kind (every lens is off until a mode lists it)", a.mode)); } else if (!offHere.empty()) { std::vector names; for (const std::string& lf : offHere) names.push_back(Plan::LensName(lf)); @@ -2008,7 +2014,7 @@ namespace Crafter::Review::Run { owedReads += o.at("reads").get(); } overall = std::format("CONFIRMING - NOT SEND-READY (gates clean; {} code unit(s) owe {} clean read(s) to reach {}: run the same line again)", owedUnits, owedReads, x); - } else if (personaRan || a.mode == "commits") { + } else if (personaRan || a.kind == "commits") { // commits: no persona gate - the gates clean = push-ready (the // owner is the last gate). overall = "SEND-READY"; @@ -2020,7 +2026,7 @@ namespace Crafter::Review::Run { std::int32_t Run::Finish(std::string overall, const Json& stoppedAt, Json incomplete) { if (broker) broker->Close(); - if (Artifact::ArtifactHash(a.mode, a.artifact) != hash) { + if (Artifact::ArtifactHash(a.kind, a.artifact) != hash) { overall = "INCOMPLETE - NOT SEND-READY (artifact changed during the run)"; incomplete.push_back({{"gate", "end"}, {"reason", "artifact hash changed during the run"}}); } @@ -2121,7 +2127,7 @@ namespace Crafter::Review::Run { const Json& db = Dyn::Get(s, "dbUrl"); stackList.push_back({{"id", s.at("id")}, {"appUrl", s.at("appUrl")}, {"dbUrl", Dyn::Truthy(db) ? Json(PgWire::Redact(Dyn::Str(db))) : Json()}, {"login", Dyn::Get(s, "login")}}); } - const std::vector modeLenses = Plan::ModeLenses(a.mode); + const std::vector modeLenses = Plan::KindLenses(a.kind); Json lensesOff = Json::array(); for (const std::string& lf : off) { if (std::ranges::contains(modeLenses, lf)) lensesOff.push_back(Plan::LensName(lf)); @@ -2144,6 +2150,7 @@ namespace Crafter::Review::Run { Json dump = { {"driver", DriverLine()}, {"mode", a.mode}, + {"kind", a.kind}, {"artifact", Dyn::RelPath(a.artifact, repo.string())}, {"hash", hash}, {"date", a.date}, @@ -2388,7 +2395,7 @@ namespace Crafter::Review::Run { // second store of the same run, or the relaunch of an unfinished run, // adds nothing. The persona pass is recorded once the persona gate // passed. - const Json read = a.mode == "reply" ? Json() : Json({{"hash", h8}, {"round", round}, {"run", Basename(dumpPath)}}); + const Json read = a.kind == "reply" ? Json() : Json({{"hash", h8}, {"round", round}, {"run", Basename(dumpPath)}}); const Json persona = personaRan ? Json({{"hash", hash}, {"reviewers", Array(reviewers)}, {"date", a.date}}) : Json(); const std::vector drops(dirty.begin(), dirty.end()); Json merged = State::SaveState(statePath, sets, drops, read, persona); diff --git a/implementations/Crafter.Review-State.cpp b/implementations/Crafter.Review-State.cpp index defb1e7..a2fff19 100644 --- a/implementations/Crafter.Review-State.cpp +++ b/implementations/Crafter.Review-State.cpp @@ -250,8 +250,8 @@ namespace Crafter::Review::State { } const Json& coverage = Dyn::Or(Dyn::Get(dump, "coverage"), Dyn::EmptyDict()); if (Dyn::Truthy(coverage)) { - const Json& mode = Dyn::Get(dump, "mode"); - if (mode == "reply" || mode == "series") { + const Json kind = Dyn::Or(Dyn::Get(dump, "kind"), Dyn::Get(dump, "mode")); + if (kind == "reply" || kind == "series") { lines.push_back(std::format("matrix: {} units x {} lenses; holes {}, persona holes {}, thin {}, modelled {}, units with a defect verdict {}", Dyn::Str(Dyn::At(coverage, "units")), Len(Dyn::At(coverage, "lenses")), Len(Dyn::At(coverage, "holes")), Len(Dyn::Or(Dyn::Get(coverage, "personaHoles"), Dyn::EmptyList())), Len(Dyn::Or(Dyn::Get(coverage, "thin"), Dyn::EmptyList())), Len(Dyn::Or(Dyn::Get(coverage, "modelled"), Dyn::EmptyList())), Len(Dyn::At(coverage, "defects")))); diff --git a/interfaces/Crafter.Review-Config.cppm b/interfaces/Crafter.Review-Config.cppm index baa87f8..d51bbe2 100644 --- a/interfaces/Crafter.Review-Config.cppm +++ b/interfaces/Crafter.Review-Config.cppm @@ -40,8 +40,7 @@ export namespace Crafter::Review::Config { // repository's git identity. std::vector Authors(const Json& config, const std::filesystem::path& repo); std::string GitUserEmail(const std::filesystem::path& repo); - // The lens files the config turns off (lenses..run = false), in gate - // order. + // The lens files no mode lists (what runs nowhere: the slots and init). std::vector LensesOff(const Json& config); // {lens file: environment} for every lens of the table, as the config // holds it (validated elsewhere). @@ -66,6 +65,23 @@ export namespace Crafter::Review::Config { // What is wrong with those files: one missing, or one // Prompt::ReadSection cannot use. std::vector SectionProblems(const Json& config, const std::filesystem::path& repo); + // A mode as the config defines it (modes., 2026-11-01): the + // artifact kind it reviews, the lens files it runs in the table's order, + // and the effort it runs at when set. nullopt: no such mode, or one + // whose kind or lens list cannot be read; lenient on the rest Validate + // refuses (a lens of another kind dropped, an unknown effort kept), + // which no command reaches: each stops on the config's problems first. + struct Mode { + std::string name; + std::string kind; + std::vector lensFiles; + std::optional effort; + }; + std::optional ModeOf(const Json& config, std::string_view name); + // The configured mode names, in the config's order. + std::vector ModeNames(const Json& config); + // The lens files of the mode's kind that the mode does not list. + std::vector LensesOff(const Json& config, std::string_view mode); // The example a consumer starts from (process/templates/config.json is // this, rendered). Json Example(); diff --git a/interfaces/Crafter.Review-Plan.cppm b/interfaces/Crafter.Review-Plan.cppm index e1b2f9b..a9a7a98 100644 --- a/interfaces/Crafter.Review-Plan.cppm +++ b/interfaces/Crafter.Review-Plan.cppm @@ -11,11 +11,11 @@ import :Json; // builds, both kept as JSON with their keys in a fixed order: the dump, // progress.json and the prompts are made from them. export namespace Crafter::Review::Plan { - // The three modes: a mailing-list reply, a kernel patch series (the - // kernel modes, with the persona gate), and a commit range about to be - // pushed. - constexpr std::array Modes = {"reply", "series", "commits"}; - bool IsKernelMode(std::string_view mode); + // The artifact kinds. A mode (the config's modes.) is a kind plus + // the lenses it runs and the effort it runs at; a kind is a mode of its + // own name. + constexpr std::array Kinds = {"reply", "series", "commits"}; + bool IsKernelKind(std::string_view kind); constexpr std::string_view FunctionalLens = "repo-functional.md"; // The persona lens of the kernel modes: one instance per reviewer // (--reviewers), its whole pass gating its blocks, in the gate after @@ -29,6 +29,8 @@ export namespace Crafter::Review::Plan { // stay at max; the push gates run at xhigh (2026-10-02). constexpr std::string_view Effort = "xhigh"; constexpr std::string_view KernelEffort = "max"; + // The efforts a mode may run at (modes..effort). + constexpr std::array Efforts = {"low", "medium", "high", "xhigh", "max"}; // Clean reads a code unit of a lens marked `reads` needs before its // verdict is reused, one per round (--reads). 2026-10-06: a static lens // is not deterministic — a second and third read of unchanged code still @@ -36,7 +38,7 @@ export namespace Crafter::Review::Plan { // read once. constexpr std::int32_t Reads = 3; constexpr std::int32_t ReplyReads = 1; - std::int32_t ReadsFor(std::string_view mode); + std::int32_t ReadsFor(std::string_view kind); // The key's last slot held the run's effort from 2026-09-22 to // 2026-10-02; it is frozen at the value every stored verdict was built // with, so a clean verdict bought at one effort is reused at any other @@ -148,7 +150,7 @@ export namespace Crafter::Review::Plan { // (code-quality, minimalism, bug-prover), Gate 3 (facts, consistency, // process-docs, etiquette, conciseness); the persona gate follows them // when reviewers are given. commits: the push gates. - const std::vector& Gates(std::string_view mode = "commits"); + const std::vector& Gates(std::string_view kind = "commits"); // What a lens's processes get beyond the read-only tools, by the // consumer's lenses..environment: mcp, the server rendered into @@ -200,7 +202,7 @@ export namespace Crafter::Review::Plan { Budget BudgetFor(std::string_view kind, std::string_view unitKind, std::string_view lensFile); std::string_view DefaultEffort(); - std::string_view DefaultEffort(std::string_view mode); + std::string_view DefaultEffort(std::string_view kind); // A text unit that is a bare section header of the camera review // (`camss-milos`, `ov13b10-of`, `milos-dts-camss`, `across the three // series`): nothing to judge, a mechanical n/a for every text lens. @@ -232,13 +234,13 @@ export namespace Crafter::Review::Plan { // instance. std::string LensName(std::string_view lensFile, std::string_view persona = {}); // Every lens of the gates, and the persona lens in the kernel modes. - std::vector ModeLenses(std::string_view mode = "commits"); + std::vector KindLenses(std::string_view kind = "commits"); // The gates with their lens files. off: the lenses the config turns off; // only keeps (test runs), without drops (an owner ruling); an empty span // filters nothing, and a gate left empty disappears. std::vector GatesFor(std::span only = {}, std::span without = {}, std::span off = {}); - std::vector GatesFor(std::string_view mode, std::span only, std::span without, + std::vector GatesFor(std::string_view kind, std::span only, std::span without, std::span off); // The whole job (null when the lens has none) and the block jobs. diff --git a/interfaces/Crafter.Review-Run.cppm b/interfaces/Crafter.Review-Run.cppm index 65e301c..aff4ebf 100644 --- a/interfaces/Crafter.Review-Run.cppm +++ b/interfaces/Crafter.Review-Run.cppm @@ -38,7 +38,11 @@ export namespace Crafter::Review::Run { // this order. struct Arguments { std::optional config; + // The mode's name (the config's modes.) and its artifact + // kind (reply, series, commits): the kind decides the partition, the + // prompts, the schemas and the rules; the mode the lenses and effort. std::string mode = "commits"; + std::string kind = "commits"; // The journal subsystem: required in the kernel modes (its ledger, // captures and threads are the lenses' evidence); optional in // commits mode. diff --git a/interfaces/Crafter.Review-State.cppm b/interfaces/Crafter.Review-State.cppm index 8594b59..4b457da 100644 --- a/interfaces/Crafter.Review-State.cppm +++ b/interfaces/Crafter.Review-State.cppm @@ -43,7 +43,7 @@ export namespace Crafter::Review::State { Json ReadLockInfo(const std::filesystem::path& path); std::string Describe(const Json& info); - // An exclusive, non-blocking flock on gate-state/.run--.lock, + // An exclusive, non-blocking flock on gate-state/.run--.lock, // kept for the life of the process: the kernel releases it when the // holder dies, and no child inherits it. The file is never unlinked: an // unlink and re-create would hand two drivers one hash. diff --git a/process/prepush.md b/process/prepush.md index 2e53abc..a9aaa11 100644 --- a/process/prepush.md +++ b/process/prepush.md @@ -139,15 +139,22 @@ rebased, never a merge of the integration branch), `authors` (the emails the freeze accepts; null = `git config user.email`), `aiTrailers` (`forbid` or `allow`), `requireSigned`, `allowDirty` (tracked files the local stack rewrites in place while the lenses run), `gate0` (the -commands Gate 0 runs by hand, for the skill), `lenses` (per lens, by its -name: `run` — every lens is off until it is `true` here (opt-in only, -2026-10-31); a lens the config does not turn on is out of every run of this -repository: -its gate disappears, the units only it reads are listed as not reviewed, -never as holes, and the run stays a full run; the ruling of record for a -repository with nothing to exercise, where `--without` is a per-run ruling -that leaves a hole — and `environment`, what its processes get beyond the -read-only tools: `none`; `browser` = a Playwright MCP server per pass, +commands Gate 0 runs by hand, for the skill), `modes` (`modes.`: the +kind of artifact the mode reviews — `reply`, `series` or `commits`, the +three shipped partitions, a kind being a mode of its own name — the +`lenses` it runs, in its kind's gate order (the table below for +`commits`, the README's kernel modes for a `series` or a `reply`), and +its `effort`, else the kind's default; every lens is off until a mode +lists it (opt-in only, +2026-10-31; `lenses..run` is retired, 2026-11-01); a lens no mode +lists is out of every run of this repository: its passes leave its gate, +which disappears when none of its lenses runs; in a commit range the units +only it reads are listed as not reviewed, never as holes, and the run stays +a full run (the ruling of record for a repository with nothing to +exercise, where `--without` is a per-run ruling that leaves a hole), while +the `reply` and `series` kinds excuse no unit), +`lenses` (per lens, by its name: `environment`, what its processes get +beyond the read-only tools: `none`; `browser` = a Playwright MCP server per pass, the browser tools and `curl`, the APPLICATION UNDER TEST block, the probe before its gate, a stack of its own per pass and the fail-closed server check; `supabase` = the database under test: the `sql` helper as its @@ -256,7 +263,9 @@ therefore the hash: re-freeze with `--replace` into the same dir | 4 Commit truth | `repo-facts` (message blocks, documentation blocks and the `whole:order` / `whole:absent` slots, static) | every claim in every commit message, and in the documentation the range adds or changes, proven or gone | | 5 | the owner | approves this exact hash, then the push happens | -Every lens runs on Opus 5.5 at effort `xhigh` (`--effort` overrides). +Every lens runs on Opus 5.5 at the mode's effort (`modes..effort` in +the config, else `xhigh` for a commit range); `--effort` overrides it for one +run. There is no width of the machine by default (2026-10-31, Jorijn: "there is no need to do a global cap anymore, the cap should be 128 per available account"): every OAuth account takes at most 128 passes at once, every @@ -321,7 +330,9 @@ diffstats — and the units of documentation files (`*.md`), each heading-delimited part of a hunk on its own, and it asks `whole:order` and `whole:absent` in slot passes of its own; a documentation file's diff header and a part the commit writes nothing in are a mechanical `n/a`. A lens the -config leaves off (every lens is off until `lenses..run = true`) reads +run's mode does not list (in `modes..lenses` of the mode `--mode` +names: every lens is off until it does, and another mode's list turns +nothing on in this run) reads nothing: the units only it would have read (its whole slot — `whole:behaviour` for the functional lens; the prose for the commit-truth lens) are listed under `coverage.notReviewed`, never as holes, and the @@ -630,3 +641,16 @@ repository. turns on no lens is a problem `config` names and `freeze` and `run` stop on; `init` says to turn lenses on; the owner's gate is 5. The commit-truth lens's firing proof in this pipeline is owed (the owner's go). +- 2026-11-01 — **a mode is an artifact kind plus the lenses it runs and + its effort** (Jorijn: "instead of defining lenses in the config you + define the mode and each mode enables the lenses"; "make effort + configurable per mode"): `modes.` in the config, the three kinds as + modes of their own name, `lenses..run` retired with a warning, + `--mode` naming any configured mode. `Config::Defaults()` runs no lens; + the template lists every lens of each kind at the kind's effort, to trim + (Jorijn, 2026-11-01, on the populated template as the example: "looks + good"; `init` says to trim). The first self-review - this + pipeline on its own range - found the retirement not carried to the CI + smoke config, this file, the shipped skill and the manifest template: + every reader of a setting must agree (2026-10-12), and the run lock still + keyed on the mode's name where the gate-state is the kind's. diff --git a/process/skill/prepush/SKILL.md b/process/skill/prepush/SKILL.md index d7f2d61..dd4a925 100644 --- a/process/skill/prepush/SKILL.md +++ b/process/skill/prepush/SKILL.md @@ -14,8 +14,8 @@ of running it: (`git log --oneline @{u}..HEAD`, or `$(git merge-base HEAD )..HEAD` for a branch that tracks nothing yet), the slug (the branch or release name), and the config: `flatpak run net.catcrafts.CrafterReview config` - must print no problem (every lens is off until `lenses..run = true` - turns it on: turn on the ones this repository runs). + must print no problem (every lens is off until a mode lists it: + `modes..lenses` names what this repository runs, `effort` how hard). 2. **Gate 0**: run the config's `gate0` commands on that HEAD and keep their output. Then freeze: diff --git a/process/templates/config.json b/process/templates/config.json index 2410bfd..b260e61 100644 --- a/process/templates/config.json +++ b/process/templates/config.json @@ -6,83 +6,108 @@ ], "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [ + "facts", + "etiquette", + "consistency", + "conciseness", + "maintainer-persona" + ], + "effort": "max" + }, + "series": { + "kind": "series", + "lenses": [ + "better-solution", + "code-quality", + "minimalism", + "bug-prover", + "facts", + "consistency", + "process-docs", + "etiquette", + "conciseness", + "maintainer-persona" + ], + "effort": "max" + }, + "commits": { + "kind": "commits", + "lenses": [ + "repo-better-solution", + "repo-functional", + "repo-bug-prover", + "repo-security", + "repo-facts" + ], + "effort": "xhigh" + } + }, "allowDirty": [], "gate0": [ "make check" ], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } diff --git a/process/templates/manifest.md b/process/templates/manifest.md index 18889ef..955de35 100644 --- a/process/templates/manifest.md +++ b/process/templates/manifest.md @@ -18,8 +18,8 @@ run line: `run-line.txt` (built from freeze.json) INCOMPLETE runs (a lens did not return) are rows marked INCOMPLETE, never a pass; test runs with `--only` / `--units` are never rows. A round without the functional lens says so in its row: `--without repo-functional.md` (a per-run -ruling) or `lenses.repo-functional.run = false` in the config (the -repository's rule). +ruling) or a mode's list without it, `modes..lenses`, in the config +(the repository's rule). ## Push-time checks (banked from `send-time-check` and `unverified` notes) diff --git a/tests/Commands/main.cpp b/tests/Commands/main.cpp index 73be4e6..989fe3f 100644 --- a/tests/Commands/main.cpp +++ b/tests/Commands/main.cpp @@ -87,12 +87,23 @@ echo '{"type":"result","subtype":"success","is_error":false,"result":"ok","total const Process::Captured first = Self({"init"}, r); const std::string skill = Read(r / ".claude" / "skills" / "prepush" / "SKILL.md"); const std::size_t frontmatter = shipped.find("\n---\n", 3) + 5; - Check("init: the config from the template (every lens off), the artifact and worktree dirs ignored, the skill with init's marker after its frontmatter, the next steps (turn lenses on; no browser line while none drives the application)", + Check("init: the config from the template (the three kinds as modes), the artifact and worktree dirs ignored, the skill with init's marker after its frontmatter, the next steps (trim each mode's lenses; the browser line, since the template lists the functional lens)", first.returnCode == 0 && Read(r / ".review-gates" / "config.json") == Read(checkout / "process" / "templates" / "config.json") && Read(r / ".gitignore") == ".review-gates/push/\n.review-gates/trees/\n" && skill.starts_with(shipped.substr(0, frontmatter)) && skill.substr(frontmatter).starts_with(marker) && skill.ends_with(shipped.substr(frontmatter)) && first.out.contains(" config - until it reports no problem") - && first.out.contains("turn lenses on in .review-gates/config.json") && !first.out.contains(" browser install - "), + && first.out.contains("trim each to what this repository runs") && first.out.contains(" browser install - "), first.out + first.err); + // A kept config has no trim step (that line is the template's own, + // printed as init writes it); one that lists no lens is told to list + // them. + Write(r / ".review-gates" / "config.json", R"({"modes": {"commits": {"lenses": ["repo-facts"]}}})"); + const Process::Captured listed = Self({"init"}, r); + Write(r / ".review-gates" / "config.json", R"({"base": "origin/dev"})"); + const Process::Captured unlisted = Self({"init"}, r); + Check("init on a kept config: one whose modes list lenses gets no lens line; one that lists none is told to list them", + listed.returnCode == 0 && listed.out.contains("kept .review-gates/config.json") && !listed.out.contains("trim ") && !listed.out.contains("list the lenses") + && unlisted.returnCode == 0 && unlisted.out.contains("list the lenses each mode runs in .review-gates/config.json (modes..lenses) - every lens is off until a mode lists it"), + listed.out + unlisted.out); Write(r / ".review-gates" / "config.json", R"({"base": "origin/dev"})"); Write(r / ".gitignore", "node_modules/\n/.review-gates/push\n"); Write(r / ".claude" / "skills" / "prepush" / "SKILL.md", std::format("---\nname: prepush\n---\n{} 0.0.1: old -->\nold text\n", marker)); diff --git a/tests/CommitsMode/main.cpp b/tests/CommitsMode/main.cpp index ae0e07d..3bf5848 100644 --- a/tests/CommitsMode/main.cpp +++ b/tests/CommitsMode/main.cpp @@ -122,7 +122,7 @@ int main() { } using GateList = std::vector>>; Check("mode_lenses = the five; --only keeps, --without drops, an emptied gate disappears; Gate 3 with one of its lenses dropped keeps the other", - Set(Plan::ModeLenses()) == lensSet && Gates(Plan::GatesFor(onlyFn)) == GateList{{"Gate 2 — Functional", {Fn}}} + Set(Plan::KindLenses()) == lensSet && Gates(Plan::GatesFor(onlyFn)) == GateList{{"Gate 2 — Functional", {Fn}}} && Titles(Plan::GatesFor({}, onlyFn)) == std::vector{"Gate 1 — Approach", "Gate 3 — Proof", "Gate 4 — Commit truth"} && Plan::GatesFor(onlyFn, onlyFn).empty() && Gates(Plan::GatesFor({}, security))[2] == std::pair>{"Gate 3 — Proof", {"repo-bug-prover.md"}} && Gates(Plan::GatesFor(security)) == GateList{{"Gate 3 — Proof", {"repo-security.md"}}}); @@ -135,7 +135,7 @@ int main() { && Gates(Plan::GatesFor(onlyFacts)) == GateList{{"Gate 4 — Commit truth", {Facts}}} && Titles(Plan::GatesFor({}, {}, onlyFacts)).size() == 3); const std::vector allLenses(lensSet.begin(), lensSet.end()); const std::set textAndDoc = {"p1:subject", "p1:diffstat", "p2:subject", "p2:msg", "p2:diffstat", "p2:README.md#hdr", "p2:README.md#1"}; - const std::set nrAll = Set(Plan::NotReviewed(units, lines, Plan::ModeLenses())); + const std::set nrAll = Set(Plan::NotReviewed(units, lines, Plan::KindLenses())); std::vector withoutFacts; for (const Plan::Lens& lens : Plan::Lenses) { if (lens.kernel) continue; diff --git a/tests/Config/main.cpp b/tests/Config/main.cpp index 6f21ab7..853d815 100644 --- a/tests/Config/main.cpp +++ b/tests/Config/main.cpp @@ -42,21 +42,21 @@ int main() { fs::create_directories(td); auto load = [&] { return Config::Load(td); }; Config::Loaded loaded = load(); - auto noLensOn = [](const std::vector& problems) { return problems.size() == 1 && problems[0].starts_with("lenses: no lens is on"); }; + auto noLensOn = [](const std::vector& problems) { return problems.size() == 1 && problems[0].starts_with("modes: no mode lists a lens"); }; Check("no file: the defaults, _path None, and the one problem of a repository that turns no lens on (2026-10-31)", noLensOn(loaded.problems) && loaded.config.at("aiTrailers") == "forbid" && loaded.config.at("functional").at("appUrl") == "http://localhost:8080" && loaded.config.at("_path").is_null(), Show(loaded.problems)); fs::create_directories(td / ".review-gates"); const fs::path p = td / ".review-gates" / "config.json"; auto put = [&](const Json& value) { std::ofstream(p, std::ios::binary) << DumpSpaced(value); }; - // A config under test turns the bug-prover on unless it says which lenses - // run itself: every lens is off by default, and a config with none on is - // a problem of its own (checked below). + // A config under test lists the bug-prover in the commits mode unless it + // defines modes itself: every lens is off until a mode lists it, and a + // config with none listed is a problem of its own (checked below). auto bad = [&](Json value) { - if (!value.contains("lenses")) value["lenses"] = {{"repo-bug-prover", {{"run", true}}}}; + if (!value.contains("modes")) value["modes"] = {{"commits", {{"lenses", {"repo-bug-prover"}}}}}; put(value); return load().problems; }; - put({{"base", "origin/dev"}, {"aiTrailers", "allow"}, {"allowDirty", {"supabase/config.toml"}}, {"lenses", {{"repo-bug-prover", {{"run", true}}}}}, + put({{"base", "origin/dev"}, {"aiTrailers", "allow"}, {"allowDirty", {"supabase/config.toml"}}, {"modes", {{"commits", {{"lenses", {"repo-bug-prover"}}}}}}, {"functional", {{"exclude", {"**/*.test.ts"}}, {"readyPath", "/health"}, {"extraTools", {"Bash(redis-cli:*)", "mcp__x__y"}}}}}); loaded = load(); Check("the file merges over the defaults, nested too", loaded.problems.empty() && loaded.config.at("base") == "origin/dev" && loaded.config.at("aiTrailers") == "allow" @@ -65,7 +65,7 @@ int main() { Check("rangeFrom 'base' without a base branch is a problem; 'sideways' is one too", Any(bad({{"rangeFrom", "base"}}), "needs a base") && Any(bad({{"rangeFrom", "sideways"}, {"base", "origin/dev"}}), "rangeFrom must be")); std::vector problems = bad({{"bogus", 1}, {"functional", {{"nope", 2}, {"appUrl", "ftp://x"}}}, {"aiTrailers", "maybe"}, {"authors", {"nomail"}}, - {"lenses", {{"repo-functional", {{"run", true}}}}}}); + {"modes", {{"commits", {{"lenses", {"repo-functional"}}}}}}}); Check("unknown keys and bad values are each a problem", problems.size() == 5 && Any(problems, "bogus") && Any(problems, "functional.nope") && Any(problems, "appUrl") && Any(problems, "aiTrailers") && Any(problems, "authors"), Show(problems)); problems = bad({{"functional", {{"extraTools", {"rm -rf /"}}, {"mcp", {{"args", "x"}}}}}}); @@ -126,15 +126,14 @@ int main() { Check("--config to a missing file is a problem", Any(Config::Load(td, td / "none.json").problems, "no such file")); put(Json::object()); loaded = load(); - bool noneRun = true; - for (const Json& entry : loaded.config.at("lenses")) noneRun = noneRun && entry.at("run") == false; std::vector everyLens; for (const Plan::Lens& lens : Plan::Lenses) everyLens.emplace_back(lens.file); bool defaultsMatch = true; std::set names; for (const Plan::Lens& lens : Plan::Lenses) { names.insert(Plan::LensName(lens.file)); - defaultsMatch = defaultsMatch && Config::Defaults().at("lenses").at(Plan::LensName(lens.file)).at("environment") == lens.env; + const Json entry = Config::Defaults().at("lenses").at(Plan::LensName(lens.file)); + defaultsMatch = defaultsMatch && entry.at("environment") == lens.env && !entry.contains("run"); } const Json defaults = Config::Defaults(); std::set defaultNames; @@ -144,31 +143,59 @@ int main() { for (const std::string_view lf : {"better-solution.md", "code-quality.md", "minimalism.md", "bug-prover.md", "facts.md", "consistency.md", "process-docs.md", "etiquette.md", "conciseness.md", "maintainer-persona.md"}) { environments[std::string(lf)] = "none"; } - Check("lenses: every lens is off by default (2026-10-31, opt-in only) with plan.LENSES' env - none/browser/none/none/none - and a config that turns none on has the one problem that says so; the table names every lens of plan.LENSES", - noLensOn(loaded.problems) && noneRun && Config::LensesOff(loaded.config) == everyLens && Dyn::Equal(Config::Environments(loaded.config), environments) && defaultNames == names && defaultsMatch, + Check("modes: the defaults are the three kinds as modes of their own name with no lens and no effort of their own; every lens is off until a mode lists it (2026-10-31, opt-in only) and the one problem names every lens; lenses hold the environment alone - none/browser/none/none/none", + noLensOn(loaded.problems) && loaded.problems[0].contains("repo-facts") && loaded.problems[0].contains("maintainer-persona") && Config::LensesOff(loaded.config) == everyLens + && Dyn::Equal(Config::Environments(loaded.config), environments) && defaultNames == names && defaultsMatch && defaults.at("modes").size() == 3 + && Dyn::Equal(defaults.at("modes").at("reply"), {{"kind", "reply"}, {"lenses", Json::array()}, {"effort", nullptr}}) + && Config::ModeNames(loaded.config) == std::vector{"reply", "series", "commits"} && Config::ModeOf(loaded.config, "commits")->lensFiles.empty() + && Config::ModeOf(loaded.config, "commits")->kind == "commits" && !Config::ModeOf(loaded.config, "nope"), Show(loaded.problems)); - put({{"lenses", {{"repo-functional", {{"run", false}}}, {"repo-bug-prover", {{"run", true}, {"environment", "browser"}}}}}}); + put({{"modes", {{"commits", {{"lenses", {"repo-bug-prover"}}}}}}, {"lenses", {{"repo-bug-prover", {{"environment", "browser"}}}}}}); loaded = load(); std::vector allButProver; for (const Plan::Lens& lens : Plan::Lenses) { if (lens.file != "repo-bug-prover.md") allButProver.emplace_back(lens.file); } - Check("lenses: run=true merges and every lens not turned on is listed off in gate order; a static lens may take the browser", loaded.problems.empty() - && Config::LensesOff(loaded.config) == allButProver && Config::Environments(loaded.config).at("repo-bug-prover.md") == "browser" - && loaded.config.at("lenses").at("repo-functional").at("environment") == "browser", Show(loaded.problems)); - problems = bad({{"lenses", {{"nope", {{"run", true}}}, {"repo-better-solution", {{"run", "yes"}, {"environment", "http"}}}, {"repo-bug-prover", {{"run", true}}}}}}); - Check("lenses: an unknown lens is an unknown key; run must be a bool; environment must be one of none, browser, supabase, browser+supabase", problems.size() == 3 - && Any(problems, "lenses.nope") && Any(problems, "run must be") && Any(problems, "one of none, browser, supabase, browser+supabase"), Show(problems)); - problems = bad({{"lenses", {{"repo-functional", {{"run", true}, {"environment", "none"}}}}}}); - Check("the functional lens running with no application under test is a problem; off with none is fine; an unknown environment is reported once", + Check("modes: a kind's mode lists its lenses; the lenses no mode lists, and the kind's lenses the mode leaves out, come in table order; a static lens may take the browser", loaded.problems.empty() + && Config::LensesOff(loaded.config) == allButProver + && Config::LensesOff(loaded.config, "commits") == std::vector{"repo-better-solution.md", "repo-functional.md", "repo-security.md", "repo-facts.md"} + && Config::ModeOf(loaded.config, "commits")->lensFiles == std::vector{"repo-bug-prover.md"} && !Config::ModeOf(loaded.config, "commits")->effort + && Config::Environments(loaded.config).at("repo-bug-prover.md") == "browser" && loaded.config.at("lenses").at("repo-functional").at("environment") == "browser", Show(loaded.problems)); + problems = bad({{"modes", {{"quick", {{"lenses", {"repo-bug-prover"}}}}}}}); + Check("modes: a mode of the repository's own name needs its kind (its lenses cannot be checked without one, so nothing is listed)", problems.size() == 2 + && Any(problems, "modes.quick.kind must name the artifact kind") && Any(problems, "no mode lists a lens"), Show(problems)); + put({{"modes", {{"quick", {{"kind", "commits"}, {"lenses", {"repo-facts", "repo-bug-prover"}}, {"effort", "high"}}}}}}); + loaded = load(); + const std::optional quick = Config::ModeOf(loaded.config, "quick"); + Check("modes: a named mode is a kind, the lenses it lists in the table's order and its effort; the kinds' own modes stay beside it with no lens", loaded.problems.empty() && quick + && quick->kind == "commits" && quick->lensFiles == std::vector{"repo-bug-prover.md", "repo-facts.md"} && quick->effort == "high" + && Config::ModeNames(loaded.config) == std::vector{"reply", "series", "commits", "quick"} + && Config::LensesOff(loaded.config, "quick") == std::vector{"repo-better-solution.md", "repo-functional.md", "repo-security.md"} + && Config::ModeOf(loaded.config, "commits")->lensFiles.empty() && Config::LensesOff(loaded.config, "nope").empty(), Show(loaded.problems)); + problems = bad({{"modes", {{"commits", {{"kind", "series"}, {"lenses", {"repo-bug-prover", "facts", "nope", "repo-bug-prover"}}, {"effort", "ultra"}}}}}}); + Check("modes: a kind's own mode is that kind; a lens of another kind, a name that is no lens and a lens listed twice are each a problem; the effort is one of the five", problems.size() == 5 + && Any(problems, "modes.commits.kind must be commits") && Any(problems, "facts is no lens of a commits") && Any(problems, "nope is no lens of a commits") + && Any(problems, "repo-bug-prover is listed twice") && Any(problems, "modes.commits.effort must be one of low, medium, high, xhigh, max"), Show(problems)); + Check("modes: not an object, a mode that is no object, lenses that are no list", Any(bad({{"modes", 5}}), "modes must be an object") + && Any(bad({{"modes", {{"commits", 5}}}}), "modes.commits must be an object") && Any(bad({{"modes", {{"commits", {{"lenses", "repo-bug-prover"}}}}}}), "modes.commits.lenses must be a list of lens names")); + put({{"lenses", {{"repo-bug-prover", {{"run", true}}}}}}); + loaded = load(); + Check("lenses..run is retired: a warning that says a mode lists what runs, and nothing runs until one does", loaded.config.at("_warnings").size() == 1 + && Dyn::Str(loaded.config.at("_warnings").at(0)).contains("lenses.repo-bug-prover.run is no longer read") && noLensOn(loaded.problems), Show(loaded.problems)); + problems = bad({{"lenses", {{"nope", {{"environment", "none"}}}, {"repo-better-solution", {{"environment", "http"}}}}}}); + Check("lenses: an unknown lens is an unknown key; environment must be one of none, browser, supabase, browser+supabase", problems.size() == 2 && Any(problems, "lenses.nope") + && Any(problems, "one of none, browser, supabase, browser+supabase"), Show(problems)); + problems = bad({{"modes", {{"commits", {{"lenses", {"repo-functional"}}}}}}, {"lenses", {{"repo-functional", {{"environment", "none"}}}}}}); + Check("the functional lens listed by a mode with no application under test is a problem; unlisted with none is fine; an unknown environment is reported once", problems.size() == 1 && problems[0].contains("empirical") - && bad({{"lenses", {{"repo-functional", {{"run", false}, {"environment", "none"}}}, {"repo-bug-prover", {{"run", true}}}}}}).empty() - && bad({{"lenses", {{"repo-functional", {{"run", true}, {"environment", "http"}}}}}}).size() == 1, Show(problems)); - problems = bad({{"lenses", {{"repo-functional", {{"run", true}, {"environment", "supabase"}}}}}}); + && bad({{"modes", {{"commits", {{"lenses", {"repo-bug-prover"}}}}}}, {"lenses", {{"repo-functional", {{"environment", "none"}}}}}}).empty() + && bad({{"modes", {{"commits", {{"lenses", {"repo-functional"}}}}}}, {"lenses", {{"repo-functional", {{"environment", "http"}}}}}}).size() == 1, Show(problems)); + problems = bad({{"modes", {{"commits", {{"lenses", {"repo-functional"}}}}}}, {"lenses", {{"repo-functional", {{"environment", "supabase"}}}}}}); Check("the database alone is no application under test for the functional lens; browser+supabase is; a static lens may take either, or the database alone", problems.size() == 1 && problems[0].contains("empirical") && problems[0].contains("browser, browser+supabase") - && bad({{"lenses", {{"repo-functional", {{"run", true}, {"environment", "browser+supabase"}}}, {"repo-bug-prover", {{"run", true}, {"environment", "supabase"}}}, - {"repo-better-solution", {{"run", true}, {"environment", "browser+supabase"}}}}}}).empty(), Show(problems)); + && bad({{"modes", {{"commits", {{"lenses", {"repo-functional", "repo-bug-prover", "repo-better-solution"}}}}}}, + {"lenses", {{"repo-functional", {{"environment", "browser+supabase"}}}, {"repo-bug-prover", {{"environment", "supabase"}}}, {"repo-better-solution", {{"environment", "browser+supabase"}}}}}}).empty(), + Show(problems)); loaded = load(); Check("supabase: the defaults are the local stack's Postgres (54322), no start/stop, no notes", Dyn::Equal(loaded.config.at("supabase"), {{"dbUrl", "postgresql://postgres:postgres@127.0.0.1:54322/postgres"}, {"start", nullptr}, {"stop", nullptr}, {"notes", ""}})); @@ -179,11 +206,11 @@ int main() { Any(bad({{"supabase", {{"dbUrl", "http://127.0.0.1:54321"}}}}), "postgres://") && bad({{"supabase", {{"dbUrl", "postgres://u:p@localhost:6543/x"}}}}).empty() && bad({{"supabase", {{"dbUrl", "postgresql://u@[::1]:5432/x"}}}}).empty() && Any(bad({{"supabase", {{"start", {"bash", "x"}}}}}), "supabase.start") && Any(bad({{"supabase", {{"nope", 1}}}}), "supabase.nope")); - Check("functional.appUrl is checked only while a lens that runs drives the application (plan.ENVIRONMENTS item 4)", - Any(bad({{"functional", {{"appUrl", "ftp://x"}}}, {"lenses", {{"repo-functional", {{"run", true}}}}}}), "appUrl") + Check("functional.appUrl is checked only while a listed lens drives the application (plan.ENVIRONMENTS item 4)", + Any(bad({{"functional", {{"appUrl", "ftp://x"}}}, {"modes", {{"commits", {{"lenses", {"repo-functional"}}}}}}}), "appUrl") && !Any(bad({{"functional", {{"appUrl", "ftp://x"}}}}), "appUrl") - && bad({{"functional", {{"appUrl", "ftp://x"}}}, {"lenses", {{"repo-functional", {{"run", false}}}, {"repo-bug-prover", {{"run", true}, {"environment", "supabase"}}}}}}).empty() - && Any(bad({{"functional", {{"appUrl", "ftp://x"}}}, {"lenses", {{"repo-functional", {{"run", false}}}, {"repo-bug-prover", {{"run", true}, {"environment", "browser+supabase"}}}}}}), "appUrl")); + && bad({{"functional", {{"appUrl", "ftp://x"}}}, {"modes", {{"commits", {{"lenses", {"repo-bug-prover"}}}}}}, {"lenses", {{"repo-bug-prover", {{"environment", "supabase"}}}}}}).empty() + && Any(bad({{"functional", {{"appUrl", "ftp://x"}}}, {"modes", {{"commits", {{"lenses", {"repo-bug-prover"}}}}}}, {"lenses", {{"repo-bug-prover", {{"environment", "browser+supabase"}}}}}}), "appUrl")); problems = bad({{"functional", {{"extraTools", {"Bash(psql:*)", "Bash(psql -h 127.0.0.1:*)", "Bash(psqlx:*)", "Bash(npm run e2e:*)", "mcp__x__y"}}}}}); Check("functional.extraTools: an entry pool.DENY overrides is a problem (psql commits to the shared database; npm is a package manager); a mere prefix twin is not", problems.size() == 1 && problems[0].contains("Bash(psql:*)") && problems[0].contains("Bash(psql -h 127.0.0.1:*)") && problems[0].contains("Bash(npm run e2e:*)") @@ -193,17 +220,19 @@ int main() { const Json rc = Config::Redacted(withPassword); Check("redacted: the dump's copy of the config masks the database password and drops _path", rc.at("supabase").at("dbUrl") == "postgresql://postgres:***@127.0.0.1:54322/postgres" && !DumpSpaced(rc).contains("hunter2") && !rc.contains("_path")); - problems = bad({{"lenses", {{"repo-functional", false}, {"repo-bug-prover", {{"run", true}}}}}}); + problems = bad({{"lenses", {{"repo-functional", false}}}}); Check("a bare false under lenses is a shape problem, never an exception; lenses_off and environments fall back to the defaults (off, browser)", problems.size() == 1 && problems[0].contains("must be an object") && Config::LensesOff(load().config) == allButProver && Config::Environments(load().config).at(fn) == "browser", Show(problems)); - Json allOff = Json::object(); - for (auto it = defaults.at("lenses").begin(); it != defaults.at("lenses").end(); ++it) allOff[it.key()] = {{"run", false}}; - problems = bad({{"lenses", allOff}}); - Check("every lens off is the problem that names them", problems.size() == 1 && problems[0].contains("no lens is on") && problems[0].contains("repo-facts"), Show(problems)); + Json noLens = Json::object(); + for (const std::string_view kind : Plan::Kinds) noLens[std::string(kind)] = {{"lenses", Json::array()}}; + problems = bad({{"modes", noLens}}); + Check("no mode listing a lens is the problem that names them", problems.size() == 1 && problems[0].contains("no mode lists a lens") && problems[0].contains("repo-facts"), Show(problems)); const Json ex = Config::Example(); - Check("the example is what process/templates/config.json holds, every lens off, and its only problem says to turn one on", noLensOn(Config::Validate(ex)) + Check("the example is what process/templates/config.json holds: the three kinds as modes with every lens of their kind at the kind's effort, no problem", Config::Validate(ex).empty() && Dyn::Equal(Dyn::LoadJsonFile(fs::current_path() / "process" / "templates" / "config.json"), ex) - && std::ranges::all_of(ex.at("lenses"), [](const Json& entry) { return entry.at("run") == false; }), Show(Config::Validate(ex))); + && ex.at("modes").at("commits").at("lenses").size() == 5 && ex.at("modes").at("series").at("lenses").size() == 10 && ex.at("modes").at("reply").at("lenses").size() == 5 + && ex.at("modes").at("commits").at("effort") == "xhigh" && ex.at("modes").at("series").at("effort") == "max" && ex.at("modes").at("reply").at("effort") == "max" + && std::ranges::none_of(ex.at("lenses"), [](const Json& entry) { return entry.contains("run"); }), Show(Config::Validate(ex))); const fs::path repo = td / "r"; fs::create_directories(repo); Process::Run({"git", "-C", repo.string(), "init", "-q"}); diff --git a/tests/Goldens/derive.py b/tests/Goldens/derive.py index f7e7c50..b8e8f79 100644 --- a/tests/Goldens/derive.py +++ b/tests/Goldens/derive.py @@ -27,6 +27,8 @@ TABLE = [('repo-better-solution.md', 'none'), ('repo-functional.md', 'browser'), ('process-docs.md', 'none'), ('etiquette.md', 'none'), ('conciseness.md', 'none'), ('maintainer-persona.md', 'none')] # The push gates' lenses: the plans and estimates of the series goldens are theirs. REPO_TABLE = TABLE[:5] +# The lenses of each artifact kind (Plan::KindLenses): a mode lists lenses of its kind. +KIND_LENSES = {'commits': [f for f, _ in REPO_TABLE], 'series': [f for f, _ in TABLE[5:]], 'reply': ['facts.md', 'etiquette.md', 'consistency.md', 'conciseness.md', 'maintainer-persona.md']} GATES = [('Gate 1 — Approach', ['repo-better-solution.md']), ('Gate 2 — Functional', ['repo-functional.md']), ('Gate 3 — Proof', ['repo-bug-prover.md', 'repo-security.md']), ('Gate 4 — Commit truth', ['repo-facts.md'])] name_of = lambda f: f.replace('.md', '') @@ -69,7 +71,14 @@ for case in c['cases']: case['config'] = json.loads(spelled(json.dumps(shown), repo)) case['path'] = None if path is None else spelled(path, repo) lenses = shown.get('lenses') - case['lensesOff'] = [f for f, _ in TABLE if entry_of(lenses, name_of(f)).get('run') is False] + # The lenses no mode lists (Config::LensesOff with no mode): a mode's list counts for the lenses of its kind. + listed = set() + for mname, mode in (shown.get('modes') or {}).items(): + if not isinstance(mode, dict): continue + kind = mname if mname in KIND_LENSES else mode.get('kind') + if kind in KIND_LENSES and isinstance(mode.get('lenses'), list): + listed |= {n + '.md' for n in mode['lenses'] if isinstance(n, str) and n + '.md' in KIND_LENSES[kind]} + case['lensesOff'] = [f for f, _ in TABLE if f not in listed] case['environments'] = {f: entry_of(lenses, name_of(f)).get('environment', 'none') for f, _ in TABLE} if old_problems != case['problems']: changed_problems[case['name']] = (old_problems, case['problems']) missing = scratch / 'missing'; missing.mkdir() diff --git a/tests/Goldens/fixture/pipeline.json b/tests/Goldens/fixture/pipeline.json index 8bfe5e7..ef5ebc6 100644 --- a/tests/Goldens/fixture/pipeline.json +++ b/tests/Goldens/fixture/pipeline.json @@ -24853,7 +24853,7 @@ "name": "none", "body": null, "problems": [ - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" ], "config": { "base": null, @@ -24861,81 +24861,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -25014,7 +25016,7 @@ "name": "empty", "body": "{}", "problems": [ - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" ], "config": { "base": null, @@ -25022,81 +25024,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -25175,7 +25179,7 @@ "name": "merge", "body": "{\"base\": \"origin/dev\", \"aiTrailers\": \"allow\", \"allowDirty\": [\"supabase/config.toml\"], \"functional\": {\"exclude\": [\"**/*.test.ts\"], \"readyPath\": \"/health\", \"extraTools\": [\"Bash(redis-cli:*)\", \"mcp__x__y\"]}}", "problems": [ - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" ], "config": { "base": "origin/dev", @@ -25183,83 +25187,85 @@ "authors": null, "aiTrailers": "allow", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [ "supabase/config.toml" ], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -25347,7 +25353,7 @@ "unknown key functional.nope", "aiTrailers must be 'forbid' or 'allow'", "authors must be null or a list of email addresses", - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" ], "config": { "base": null, @@ -25357,81 +25363,83 @@ ], "aiTrailers": "maybe", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -25510,7 +25518,7 @@ "name": "tools", "body": "{\"functional\": {\"extraTools\": [\"rm -rf /\"], \"mcp\": {\"args\": \"x\"}}}", "problems": [ - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)", + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)", "functional.extraTools entries must be Bash(:*) or mcp__[__]", "functional.mcp.args must be null or a list of strings" ], @@ -25520,81 +25528,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -25683,81 +25693,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -25844,81 +25856,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -25997,7 +26011,7 @@ "name": "retired", "body": "{\"functional\": {\"concurrency\": 4, \"appUrl\": \"http://x\"}}", "problems": [ - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" ], "config": { "base": null, @@ -26005,81 +26019,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -26168,81 +26184,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -26322,9 +26340,8 @@ "body": "{\"lenses\": {\"nope\": {\"run\": true}, \"repo-better-solution\": {\"run\": \"yes\", \"environment\": \"http\"}}}", "problems": [ "unknown key lenses.nope", - "lenses.repo-better-solution.run must be true or false", "lenses.repo-better-solution.environment must be one of none, browser, supabase, browser+supabase", - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" ], "config": { "base": null, @@ -26332,81 +26349,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": "yes", "environment": "http", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -26444,9 +26463,13 @@ } }, "path": "/.review-gates/config.json", - "warnings": [], + "warnings": [ + "lenses.nope.run is no longer read - remove it from config.json: a mode lists the lenses it runs (modes..lenses)", + "lenses.repo-better-solution.run is no longer read - remove it from config.json: a mode lists the lenses it runs (modes..lenses)" + ], "hasPath": true, "lensesOff": [ + "repo-better-solution.md", "repo-functional.md", "repo-bug-prover.md", "repo-security.md", @@ -26484,7 +26507,7 @@ "name": "functionalNone", "body": "{\"lenses\": {\"repo-functional\": {\"environment\": \"none\"}}}", "problems": [ - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" ], "config": { "base": null, @@ -26492,81 +26515,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "none", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -26645,7 +26670,7 @@ "name": "functionalDb", "body": "{\"lenses\": {\"repo-functional\": {\"environment\": \"supabase\"}}}", "problems": [ - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" ], "config": { "base": null, @@ -26653,81 +26678,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "supabase", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -26806,7 +26833,7 @@ "name": "allOff", "body": "{\"lenses\": {\"repo-better-solution\": {\"run\": false}, \"repo-functional\": {\"run\": false}, \"repo-bug-prover\": {\"run\": false}, \"repo-security\": {\"run\": false}}}", "problems": [ - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" ], "config": { "base": null, @@ -26814,81 +26841,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -26926,7 +26955,12 @@ } }, "path": "/.review-gates/config.json", - "warnings": [], + "warnings": [ + "lenses.repo-better-solution.run is no longer read - remove it from config.json: a mode lists the lenses it runs (modes..lenses)", + "lenses.repo-functional.run is no longer read - remove it from config.json: a mode lists the lenses it runs (modes..lenses)", + "lenses.repo-bug-prover.run is no longer read - remove it from config.json: a mode lists the lenses it runs (modes..lenses)", + "lenses.repo-security.run is no longer read - remove it from config.json: a mode lists the lenses it runs (modes..lenses)" + ], "hasPath": true, "lensesOff": [ "repo-better-solution.md", @@ -26967,8 +27001,8 @@ "name": "bareFalse", "body": "{\"lenses\": {\"repo-functional\": false}}", "problems": [ - "lenses.repo-functional must be an object {run: bool, environment: none|browser|supabase|browser+supabase}", - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" + "lenses.repo-functional must be an object {environment: none|browser|supabase|browser+supabase}", + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" ], "config": { "base": null, @@ -26976,77 +27010,80 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": false, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -27125,7 +27162,7 @@ "name": "hosted", "body": "{\"supabase\": {\"dbUrl\": \"postgresql://postgres:secret@db.abcdefgh.supabase.co:5432/postgres\"}}", "problems": [ - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)", + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)", "supabase.dbUrl: host db.abcdefgh.supabase.co is not this machine (127.0.0.1, localhost, ::1): the database under test is a local stack, never a hosted one" ], "config": { @@ -27134,81 +27171,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -27288,7 +27327,7 @@ "body": "{\"supabase\": {\"dbUrl\": \"http://127.0.0.1:54321\", \"start\": [\"bash\", \"x\"], \"notes\": 5, \"nope\": 1}}", "problems": [ "unknown key supabase.nope", - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)", + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)", "supabase.dbUrl: not a postgres:// or postgresql:// URL", "supabase.start must be null or a command line", "supabase.notes must be text" @@ -27299,81 +27338,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -27453,91 +27494,93 @@ }, { "name": "appUrlOff", - "body": "{\"functional\": {\"appUrl\": \"ftp://x\"}, \"lenses\": {\"repo-functional\": {\"run\": false}, \"repo-bug-prover\": {\"environment\": \"supabase\"}}}", - "problems": [ - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" - ], + "body": "{\"functional\": {\"appUrl\": \"ftp://x\"}, \"modes\": {\"commits\": {\"lenses\": [\"repo-bug-prover\"]}}, \"lenses\": {\"repo-bug-prover\": {\"environment\": \"supabase\"}}}", + "problems": [], "config": { "base": null, "rangeFrom": "upstream", "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [ + "repo-bug-prover" + ], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "supabase", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -27580,7 +27623,6 @@ "lensesOff": [ "repo-better-solution.md", "repo-functional.md", - "repo-bug-prover.md", "repo-security.md", "repo-facts.md", "better-solution.md", @@ -27616,7 +27658,7 @@ "name": "deny", "body": "{\"functional\": {\"extraTools\": [\"Bash(psql:*)\", \"Bash(psql -h 127.0.0.1:*)\", \"Bash(psqlx:*)\", \"Bash(npm run e2e:*)\", \"mcp__x__y\"]}}", "problems": [ - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)", + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)", "functional.extraTools: Bash(psql:*), Bash(psql -h 127.0.0.1:*), Bash(npm run e2e:*) - denied to every pass (pool.DENY wins over an allowlist); a lens reaches the database through the supabase environment, every call rolled back" ], "config": { @@ -27625,81 +27667,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -27784,7 +27828,7 @@ "name": "slots", "body": "{\"slots\": {\"dir\": \"../up\", \"start\": \"go\", \"stop\": \"stop {n}\", \"memMb\": true, \"startMb\": -1}}", "problems": [ - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)", + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)", "slots.dir must be null or a directory inside the repository (a relative path)", "slots.start must be null or a command line with {n}, the slot number", "slots.memMb must be the measured memory of one slot in MB (a number > 0): ` slots` sizes the slots by it", @@ -27796,81 +27840,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -27949,7 +27995,7 @@ "name": "slotsOk", "body": "{\"slots\": {\"dir\": \".local-stack/slots\", \"start\": \"up {n}\", \"stop\": null, \"memMb\": 1500, \"startMb\": 2000.5}}", "problems": [ - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" ], "config": { "base": null, @@ -27957,81 +28003,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -28110,7 +28158,7 @@ "name": "slotsDir", "body": "{\"slots\": {\"dir\": \" \", \"memMb\": 0}}", "problems": [ - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)", + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)", "slots.dir must be null or a directory inside the repository (a relative path)", "slots.memMb must be the measured memory of one slot in MB (a number > 0): ` slots` sizes the slots by it" ], @@ -28120,81 +28168,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -28273,7 +28323,7 @@ "name": "login", "body": "{\"functional\": {\"login\": {\"fixtures\": \"x\"}}}", "problems": [ - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)", + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)", "functional.login must be {fixtures: [paths], notes: text}" ], "config": { @@ -28282,81 +28332,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -28436,7 +28488,7 @@ "body": "{\"authors\": \"me@x\"}", "problems": [ "authors must be null or a list of email addresses", - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" ], "config": { "base": null, @@ -28444,81 +28496,83 @@ "authors": "me@x", "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -28599,7 +28653,7 @@ "problems": [ "allowDirty must be a list of strings", "gate0 must be a list of strings", - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" ], "config": { "base": null, @@ -28607,83 +28661,85 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [ 1 ], "gate0": "make", "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -28763,7 +28819,7 @@ "body": "{\"rangeFrom\": \"base\"}", "problems": [ "rangeFrom 'base' needs a base branch", - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" ], "config": { "base": null, @@ -28771,81 +28827,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -28925,7 +28983,7 @@ "body": "{\"functional\": {\"login\": {\"fixtures\": [\"a.json\"], \"notes\": \"n\", \"extra\": 1}, \"mcp\": {\"command\": null, \"args\": [\"--x\"]}}}", "problems": [ "unknown key functional.login.extra", - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" ], "config": { "base": null, @@ -28933,81 +28991,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -29090,7 +29150,7 @@ "name": "utf8", "body": "{\"base\": \"é
\"}", "problems": [ - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" ], "config": { "base": "é
", @@ -29098,81 +29158,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -29259,81 +29321,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -29420,81 +29484,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -29581,81 +29647,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -29729,11 +29797,183 @@ "conciseness.md": "none", "maintainer-persona.md": "none" } + }, + { + "name": "modes", + "body": "{\"modes\": {\"quick\": {\"kind\": \"commits\", \"lenses\": [\"repo-bug-prover\"], \"effort\": \"high\"}, \"series\": {\"lenses\": [\"facts\", \"repo-facts\"]}, \"reply\": {\"kind\": \"commits\"}}}", + "problems": [ + "modes.reply.kind must be reply (the mode of a kind's name is that kind) or null", + "modes.series: repo-facts is no lens of a series (better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" + ], + "warnings": [], + "config": { + "base": null, + "rangeFrom": "upstream", + "authors": null, + "aiTrailers": "forbid", + "requireSigned": false, + "modes": { + "reply": { + "kind": "commits", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [ + "facts", + "repo-facts" + ], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + }, + "quick": { + "kind": "commits", + "lenses": [ + "repo-bug-prover" + ], + "effort": "high" + } + }, + "allowDirty": [], + "gate0": [], + "lenses": { + "repo-better-solution": { + "environment": "none", + "section": null + }, + "repo-functional": { + "environment": "browser", + "section": null + }, + "repo-bug-prover": { + "environment": "none", + "section": null + }, + "repo-security": { + "environment": "none", + "section": null + }, + "repo-facts": { + "environment": "none", + "section": null + }, + "better-solution": { + "environment": "none", + "section": null + }, + "code-quality": { + "environment": "none", + "section": null + }, + "minimalism": { + "environment": "none", + "section": null + }, + "bug-prover": { + "environment": "none", + "section": null + }, + "facts": { + "environment": "none", + "section": null + }, + "consistency": { + "environment": "none", + "section": null + }, + "process-docs": { + "environment": "none", + "section": null + }, + "etiquette": { + "environment": "none", + "section": null + }, + "conciseness": { + "environment": "none", + "section": null + }, + "maintainer-persona": { + "environment": "none", + "section": null + } + }, + "supabase": { + "dbUrl": "postgresql://postgres:***@127.0.0.1:54322/postgres", + "start": null, + "stop": null, + "notes": "" + }, + "slots": { + "dir": null, + "start": null, + "stop": null, + "memMb": null, + "startMb": null + }, + "functional": { + "appUrl": "http://localhost:8080", + "readyPath": "/", + "apiUrls": [], + "start": null, + "stop": null, + "login": { + "fixtures": [], + "notes": "" + }, + "exclude": [], + "extraTools": [], + "mcp": { + "command": "npx", + "args": null + }, + "notes": "" + } + }, + "path": "/.review-gates/config.json", + "lensesOff": [ + "repo-better-solution.md", + "repo-functional.md", + "repo-security.md", + "repo-facts.md", + "better-solution.md", + "code-quality.md", + "minimalism.md", + "bug-prover.md", + "consistency.md", + "process-docs.md", + "etiquette.md", + "conciseness.md", + "maintainer-persona.md" + ], + "environments": { + "repo-better-solution.md": "none", + "repo-functional.md": "browser", + "repo-bug-prover.md": "none", + "repo-security.md": "none", + "repo-facts.md": "none", + "better-solution.md": "none", + "code-quality.md": "none", + "minimalism.md": "none", + "bug-prover.md": "none", + "facts.md": "none", + "consistency.md": "none", + "process-docs.md": "none", + "etiquette.md": "none", + "conciseness.md": "none", + "maintainer-persona.md": "none" + }, + "hasPath": true } ], "missing": [ "/none.json: no such file", - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" + "modes: no mode lists a lens - every lens is off until a mode lists it in modes..lenses (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" ], "defaults": { "base": null, @@ -29741,81 +29981,83 @@ "authors": null, "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [], + "effort": null + }, + "series": { + "kind": "series", + "lenses": [], + "effort": null + }, + "commits": { + "kind": "commits", + "lenses": [], + "effort": null + } + }, "allowDirty": [], "gate0": [], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -29860,83 +30102,108 @@ ], "aiTrailers": "forbid", "requireSigned": false, + "modes": { + "reply": { + "kind": "reply", + "lenses": [ + "facts", + "etiquette", + "consistency", + "conciseness", + "maintainer-persona" + ], + "effort": "max" + }, + "series": { + "kind": "series", + "lenses": [ + "better-solution", + "code-quality", + "minimalism", + "bug-prover", + "facts", + "consistency", + "process-docs", + "etiquette", + "conciseness", + "maintainer-persona" + ], + "effort": "max" + }, + "commits": { + "kind": "commits", + "lenses": [ + "repo-better-solution", + "repo-functional", + "repo-bug-prover", + "repo-security", + "repo-facts" + ], + "effort": "xhigh" + } + }, "allowDirty": [], "gate0": [ "make check" ], "lenses": { "repo-better-solution": { - "run": false, "environment": "none", "section": null }, "repo-functional": { - "run": false, "environment": "browser", "section": null }, "repo-bug-prover": { - "run": false, "environment": "none", "section": null }, "repo-security": { - "run": false, "environment": "none", "section": null }, "repo-facts": { - "run": false, "environment": "none", "section": null }, "better-solution": { - "run": false, "environment": "none", "section": null }, "code-quality": { - "run": false, "environment": "none", "section": null }, "minimalism": { - "run": false, "environment": "none", "section": null }, "bug-prover": { - "run": false, "environment": "none", "section": null }, "facts": { - "run": false, "environment": "none", "section": null }, "consistency": { - "run": false, "environment": "none", "section": null }, "process-docs": { - "run": false, "environment": "none", "section": null }, "etiquette": { - "run": false, "environment": "none", "section": null }, "conciseness": { - "run": false, "environment": "none", "section": null }, "maintainer-persona": { - "run": false, "environment": "none", "section": null } @@ -29981,9 +30248,7 @@ "notes": "" } }, - "exampleProblems": [ - "lenses: no lens is on - every lens is off until lenses..run = true turns it on for this repository (repo-better-solution, repo-functional, repo-bug-prover, repo-security, repo-facts, better-solution, code-quality, minimalism, bug-prover, facts, consistency, process-docs, etiquette, conciseness, maintainer-persona)" - ], + "exampleProblems": [], "denied": [ [ "Bash(psql:*)", diff --git a/tests/KernelModes/main.cpp b/tests/KernelModes/main.cpp index cc123be..cdd9190 100644 --- a/tests/KernelModes/main.cpp +++ b/tests/KernelModes/main.cpp @@ -200,8 +200,8 @@ echo "{\"type\":\"result\",\"subtype\":\"success\",\"is_error\":false,\"result\" && Plan::GatesFor("reply", std::vector{"facts.md"}, std::vector{"facts.md"}, {}).empty() && Plan::GatesFor("commits", std::vector{"facts.md"}, {}, {}).empty() && Plan::GatesFor().size() == Plan::Gates("commits").size()); Check("mode lenses: the kernel modes have the persona lens, commits does not; effort max for the kernel modes, xhigh for commits; reads 1 for a reply, 3 otherwise", - std::ranges::contains(Plan::ModeLenses("series"), std::string(Plan::PersonaLens)) && std::ranges::contains(Plan::ModeLenses("reply"), std::string(Plan::PersonaLens)) - && !std::ranges::contains(Plan::ModeLenses("commits"), std::string(Plan::PersonaLens)) && Plan::DefaultEffort("reply") == "max" && Plan::DefaultEffort("series") == "max" + std::ranges::contains(Plan::KindLenses("series"), std::string(Plan::PersonaLens)) && std::ranges::contains(Plan::KindLenses("reply"), std::string(Plan::PersonaLens)) + && !std::ranges::contains(Plan::KindLenses("commits"), std::string(Plan::PersonaLens)) && Plan::DefaultEffort("reply") == "max" && Plan::DefaultEffort("series") == "max" && Plan::DefaultEffort("commits") == "xhigh" && Plan::DefaultEffort() == "xhigh" && Plan::ReadsFor("reply") == 1 && Plan::ReadsFor("series") == 3 && Plan::ReadsFor("commits") == 3); auto budget = [](std::string_view kind, std::string_view unitKind, std::string_view lens) { const Plan::Budget b = Plan::BudgetFor(kind, unitKind, lens); @@ -595,13 +595,18 @@ echo "{\"type\":\"result\",\"subtype\":\"success\",\"is_error\":false,\"result\" { const fs::path bench = base / "bench"; fs::create_directories(bench / ".review-gates"); - Json lenses = Json::object(); + // The bench's modes: a series runs every kernel lens, a reply its + // kind's five. + Json modes = {{"series", {{"lenses", Json::array()}}}, {"reply", {{"lenses", Json::array()}}}}; const std::array on = {"facts.md", "etiquette.md", "consistency.md", "conciseness.md", "better-solution.md", "code-quality.md", "minimalism.md", "bug-prover.md", "process-docs.md", Plan::PersonaLens}; - for (const std::string_view lf : on) lenses[Plan::LensName(lf)] = {{"run", true}}; + for (const std::string_view lf : on) modes["series"]["lenses"].push_back(Plan::LensName(lf)); + const std::array replyOn = {"facts.md", "etiquette.md", "consistency.md", "conciseness.md", Plan::PersonaLens}; + for (const std::string_view lf : replyOn) modes["reply"]["lenses"].push_back(Plan::LensName(lf)); + // A mode of the bench's own name: a reply with the facts lens alone, at effort high. + modes["quick"] = {{"kind", "reply"}, {"lenses", Json::array({"facts"})}, {"effort", "high"}}; // The bench's own section for the facts lens (lenses.facts.section). Write(bench / "process" / "lens-sections" / "facts.md", "## The bench's own section\n\n- 2026-10-31: a section of the bench repository, appended to the facts charter.\n"); - lenses["facts"]["section"] = "process/lens-sections/facts.md"; - Write(bench / ".review-gates" / "config.json", DumpFile(Json({{"lenses", lenses}}))); + Write(bench / ".review-gates" / "config.json", DumpFile(Json({{"modes", modes}, {"lenses", {{"facts", {{"section", "process/lens-sections/facts.md"}}}}}}))); const fs::path fakeDir = base / "fake"; const fs::path fake = fakeDir / "claude"; Write(fake, FakeClaude); @@ -697,6 +702,25 @@ echo "{\"type\":\"result\",\"subtype\":\"success\",\"is_error\":false,\"result\" && !Dyn::Has(state.at("entries").begin().value(), "reads"), std::format("exit {} / {}; {} / {}; {}{}", plain.returnCode, withPersona.returnCode, Dyn::StrOr(Dyn::Get(plainDump, "overall"), "?"), Dyn::StrOr(Dyn::Get(personaDump, "overall"), "?"), Dyn::Slice(withPersona.out, -800), withPersona.err)); + // A mode of the repository's own name (2026-11-01): `quick`, a + // reply with the facts lens alone at effort high. The run takes + // its kind, lenses and effort from the config and the dump says + // mode and kind; the run lock, the run number and the dump name + // are the kind's, so this is the artifact's third reply dump. An + // unknown mode is refused with the configured names. + const Process::Captured quick = run({"--mode", "quick", "--subsystem", "x", "--artifact", "journal/x/draft.eml", "--no-reuse", "--no-salvage"}); + Json quickDump; + try { + quickDump = Dyn::LoadJsonFile(bench / "journal" / "x" / "gate-state" / std::format("reply-{}-run-3.json", replyHash.substr(0, 8))); + } catch (const std::exception&) { + } + const Process::Captured nope = run({"--mode", "nope", "--subsystem", "x", "--artifact", "journal/x/draft.eml", "--dry-run"}); + const Json quickLenses = quickDump.is_object() ? quickDump.at("coverage").at("lenses") : Json::array(); + Check("a named mode: its kind, lenses and effort from the config, mode and kind in the dump, the dump numbered and named by the kind; an unknown mode is refused naming the configured ones", + quick.returnCode == 2 && quickDump.is_object() && quickDump.at("mode") == "quick" && quickDump.at("kind") == "reply" && quickDump.at("args").at("effort") == "high" + && quickLenses == Json({"facts"}) && !quickDump.at("lensesOff").empty() && quick.out.contains("quick (a reply)") && nope.returnCode == 3 + && nope.err.contains("--mode nope: no such mode in the config (modes: ") && nope.err.contains("quick"), + std::format("exit {} / {}; {}{}", quick.returnCode, nope.returnCode, Dyn::Slice(quick.out, -600), nope.err)); } fs::remove_all(base); return Failures ? 1 : 0; diff --git a/tests/ReviewFixes/main.cpp b/tests/ReviewFixes/main.cpp index bb1c845..6d44789 100644 --- a/tests/ReviewFixes/main.cpp +++ b/tests/ReviewFixes/main.cpp @@ -366,8 +366,8 @@ namespace { fs::create_directories(td / ".review-gates"); fs::copy_file(fs::current_path() / "process" / "templates" / "config.json", td / ".review-gates" / "config.json"); const Config::Loaded loaded = Config::Load(td); - Check("template: process/templates/config.json, copied as the README says, loads with no warning and no problem but the one that says to turn a lens on - unknown keys included (finding 2; every lens off since 2026-10-31)", - loaded.problems.size() == 1 && loaded.problems[0].starts_with("lenses: no lens is on") && loaded.config.at("_warnings").empty(), Show(loaded.problems)); + Check("template: process/templates/config.json, copied as the README says, loads with no warning and no problem - unknown keys included (finding 2; the three kinds as modes with every lens of their kind, 2026-11-01)", + loaded.problems.empty() && loaded.config.at("_warnings").empty(), Show(loaded.problems)); } fs::remove_all(base); return Failures ? 1 : 0; diff --git a/tests/Runs/main.cpp b/tests/Runs/main.cpp index 2f0a7d1..e04cb2e 100644 --- a/tests/Runs/main.cpp +++ b/tests/Runs/main.cpp @@ -205,7 +205,7 @@ echo "{\"type\":\"result\",\"subtype\":\"success\",\"is_error\":false,\"result\" Git(repo, {"config", "user.email", "me@example.com"}); Git(repo, {"config", "user.name", "Me"}); Write(repo / ".gitignore", ".review-gates/push/\n.review-gates/trees/\n"); - Write(repo / ".review-gates" / "config.json", R"({"lenses": {"repo-better-solution": {"run": true}, "repo-bug-prover": {"run": true}, "repo-security": {"run": true}}})"); + Write(repo / ".review-gates" / "config.json", R"({"modes": {"commits": {"lenses": ["repo-better-solution", "repo-bug-prover", "repo-security"]}}})"); Write(repo / "README.md", "a greeter\n"); Git(repo, {"add", "-A"}); Git(repo, {"commit", "-q", "-m", "base"}); @@ -429,7 +429,7 @@ echo "{\"type\":\"result\",\"subtype\":\"success\",\"is_error\":false,\"result\" // too) - push-ready, with the code units, the per-commit slots and // the functional lens's slot listed as not reviewed. { - Write(base / "facts.json", R"({"lenses": {"repo-facts": {"run": true}}})"); + Write(base / "facts.json", R"({"modes": {"commits": {"lenses": ["repo-facts"]}}})"); const std::int64_t before = calls(); std::vector truthArgs = runArgs({"--no-reuse", "--no-salvage"}); truthArgs.insert(truthArgs.begin() + 1, {"--config", (base / "facts.json").string()}); @@ -457,7 +457,7 @@ echo "{\"type\":\"result\",\"subtype\":\"success\",\"is_error\":false,\"result\" { const fs::path bench = base / "bench"; fs::create_directories(bench / ".review-gates"); - Write(bench / ".review-gates" / "config.json", R"({"lenses": {"repo-bug-prover": {"run": true}}})"); + Write(bench / ".review-gates" / "config.json", R"({"modes": {"commits": {"lenses": ["repo-bug-prover"]}}})"); const fs::path out = bench / "journal" / "push" / "y"; const Process::Captured benchFreeze = Process::Run({Platform::SelfExe().string(), "freeze", "--repo", repo.string(), "--out", out.string(), "--worktree", "--base", baseSha}, bench, std::chrono::seconds(60), {}, Env); diff --git a/tests/StacksMemory/main.cpp b/tests/StacksMemory/main.cpp index 629308b..1e07197 100644 --- a/tests/StacksMemory/main.cpp +++ b/tests/StacksMemory/main.cpp @@ -53,14 +53,14 @@ int main() { }; // Every lens is off by default: a config under test turns the // bug-prover on, or the one problem it would have is that none runs. - auto sl = [](const Json& kw) { return Json {{"slots", kw}, {"lenses", {{"repo-bug-prover", {{"run", true}}}}}}; }; + auto sl = [](const Json& kw) { return Json {{"slots", kw}, {"modes", {{"commits", {{"lenses", {"repo-bug-prover"}}}}}}}; }; Check("config: slots default to none; a dir inside the repository, start/stop with {n}, and the measured memMb with it; anything else is a problem", Dyn::Equal(Config::Defaults().at("slots"), {{"dir", nullptr}, {"start", nullptr}, {"stop", nullptr}, {"memMb", nullptr}, {"startMb", nullptr}}) && AnyContains(bad(sl({{"dir", "s"}, {"memMb", 1}, {"startMb", 0}})), "slots.startMb") && bad(sl({{"dir", ".local-stack/slots"}, {"start", "bash s.sh full --slot {n}"}, {"stop", "bash s.sh stop --slot {n}"}, {"memMb", 1500}})).empty() && AnyContains(bad(sl({{"dir", "/abs"}, {"memMb", 1}})), "inside the repository") && AnyContains(bad(sl({{"dir", "s"}, {"start", "bash s.sh"}, {"memMb", 1}})), "{n}") && AnyContains(bad(sl({{"dir", "s"}})), "slots.memMb") && AnyContains(bad(sl({{"dir", "s"}, {"memMb", true}})), "slots.memMb") && bad(sl({{"nope", 1}})).size() == 1); - Write(p, R"({"functional": {"concurrency": 4}, "lenses": {"repo-bug-prover": {"run": true}}})"); + Write(p, R"({"functional": {"concurrency": 4}, "modes": {"commits": {"lenses": ["repo-bug-prover"]}}})"); const Config::Loaded cr = Config::Load(td); const Config::Loaded cr2 = Config::Load("/", p); const Json& warnings = cr.config.at("_warnings"); @@ -343,7 +343,7 @@ int main() { Write(td / ".local-stack" / "slots" / "1" / "slot.json", R"({"appUrl": "http://localhost:20180", "apiUrls": [], "dbUrl": null, "login": null})"); Json cfg = Config::Load(td).config; cfg["slots"] = {{"dir", ".local-stack/slots"}, {"start", "up --slot {n}"}, {"stop", nullptr}, {"memMb", 1500}, {"startMb", 6000}}; - cfg["lenses"]["repo-functional"]["run"] = true; + cfg["modes"]["commits"]["lenses"].push_back("repo-functional"); cfg["lenses"]["repo-functional"]["environment"] = "browser+supabase"; std::vector out; const Run::Machine machine = {.available = [] { return std::optional(20'000); }, .reserve = [] { return std::int64_t {4000}; }};