feat(config): lens sections - a repository's own text appended to a shipped charter
A lens has one charter, the one this pipeline ships; what a repository knows that is not general to the lens - its platform and layers, the record it keeps its decisions in, what its users already have, the calibration entries that are its own - goes in a file the config names for the lens (lenses.<name>.section), and Charter appends it after one blank line. The charter sha in every reuse key covers the combined text. The config refuses a path outside the repository - absolute, or climbing out with `..` - by the check slots.dir had, now one both settings share; a file missing or one the run could not use (a dangling link, unreadable, not UTF-8) is a problem too, checked through the reader Charter uses (Prompt::ReadSection), so a section is never dropped in silence, and a name that is no lens is the unknown key it always was. Nothing lists a directory, so what lies beside the files is no concern of the config's: a directory listed would have to tolerate or refuse every foreign entry. The run logs which charters got one. The better-solution charter gains a consumer's general entry (2026-10-31: a mechanism reworked a second time in one submission is the signal to derive the fix at every layer the data crosses). The procedure names the section wherever it names the charter: among the config's keys, in the lens inputs, in the reuse rule (an edit to either re-runs the lens) and in the feedback loop, which routes a repository's own lesson to its section and a general one to the shipped charter.
This commit is contained in:
parent
339f4b9b40
commit
6cf15919db
16 changed files with 1235 additions and 524 deletions
|
|
@ -51,7 +51,7 @@ Every seam a test needs is a member that starts as the real one: `Run` has `appR
|
||||||
|
|
||||||
- **`crafter-build --local test` passes** before and after; add a check for what you changed. It needs no network and no seat. Run `test` before `lint`: the four AST rules of Crafter.LintRules read the PCMs a test build leaves. `format` and `lint` are clean.
|
- **`crafter-build --local test` passes** before and after; add a check for what you changed. It needs no network and no seat. Run `test` before `lint`: the four AST rules of Crafter.LintRules read the PCMs a test build leaves. `format` and `lint` are clean.
|
||||||
- **A prompt change is proven with a `--dry-run`** on a frozen range before and after (`tests/Goldens/fixture/commits`, or a scratch repository frozen with `freeze`). The prompts under `<scratch>/<hash8>-run-<n>-dry/prompts/` and the system files must differ exactly where intended, and the goldens change with them. A text change a lens reads bumps `PromptVersion` (`:Core`) only when earlier clean results must NOT carry over (2026-09-27: a layout change that left the text a lens reads the same did not).
|
- **A prompt change is proven with a `--dry-run`** on a frozen range before and after (`tests/Goldens/fixture/commits`, or a scratch repository frozen with `freeze`). The prompts under `<scratch>/<hash8>-run-<n>-dry/prompts/` and the system files must differ exactly where intended, and the goldens change with them. A text change a lens reads bumps `PromptVersion` (`:Core`) only when earlier clean results must NOT carry over (2026-09-27: a layout change that left the text a lens reads the same did not).
|
||||||
- **The lens texts and `_common-repo.md` carry no header.** A charter's sha256 is part of every reuse key and both texts are part of every prompt; `--version` prints the sha256s. Editing one is a prompt change.
|
- **The lens texts and `_common-repo.md` carry no header.** A charter's sha256 - with the repository's own section appended, when `lenses.<name>.section` gives the lens one - is part of every reuse key, and the charter and its mode's common text (`_common-repo.md` for a commit range, `_common.md` in the kernel modes) are part of every prompt; `--version` prints the shipped texts' sha256s. Editing one is a prompt change.
|
||||||
- **A reuse-key change ships with an attack on its STORE side**: "two units, one key, different verdicts" and "a FIX FIRST round, then a text-only re-freeze" (2026-10-06; the StoreRules and CodeKeys tests).
|
- **A reuse-key change ships with an attack on its STORE side**: "two units, one key, different verdicts" and "a FIX FIRST round, then a text-only re-freeze" (2026-10-06; the StoreRules and CodeKeys tests).
|
||||||
- **A gate change ships with a firing proof**: a scratch repository with one planted defect per gate, each lens run with `--only <lens> --units <unit>` (PARTIAL by design) on the consumer's `.env` seats. It must give FIX FIRST on the planted range and SEND-READY on the corrected one. Record it in the lens's calibration section with the date, the units, the turns and the cost. It costs money: only with the owner's go.
|
- **A gate change ships with a firing proof**: a scratch repository with one planted defect per gate, each lens run with `--only <lens> --units <unit>` (PARTIAL by design) on the consumer's `.env` seats. It must give FIX FIRST on the planted range and SEND-READY on the corrected one. Record it in the lens's calibration section with the date, the units, the turns and the cost. It costs money: only with the owner's go.
|
||||||
- **The driver never runs a consumer's build, tests, stack or package manager**; the skill does that, by hand, from the config's commands. The functional lens observes a running application it did not start.
|
- **The driver never runs a consumer's build, tests, stack or package manager**; the skill does that, by hand, from the config's commands. The functional lens observes a running application it did not start.
|
||||||
|
|
@ -99,6 +99,7 @@ Conventions:
|
||||||
- **Messages that point at a pid.** In the sandbox, the run-lock message says "wait for it or stop it (`<invocation> stop <its scratch dir>`)", and `seats` adds a line naming `stop`; natively it says "wait for it or kill it". The pid inside a sandbox ignores SIGTERM.
|
- **Messages that point at a pid.** In the sandbox, the run-lock message says "wait for it or stop it (`<invocation> stop <its scratch dir>`)", and `seats` adds a line naming `stop`; natively it says "wait for it or kill it". The pid inside a sandbox ignores SIGTERM.
|
||||||
- **The dump's `driver` field and the first log line** read `crafter-review <version> (prompt v1)`. Nothing reads them.
|
- **The dump's `driver` field and the first log line** read `crafter-review <version> (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).
|
- **`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.<name>.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-<h8>-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, the dumps and the resume file are named by the mode (`reply-<h8>-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).
|
||||||
- **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).
|
- **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).
|
- **`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).
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ 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:
|
`.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;
|
- who may author its commits, and which branch a range is measured against;
|
||||||
- which lenses run (every lens is off until `lenses.<name>.run` is true) and the environment each gets (`none`, `browser`, `supabase` or `browser+supabase`);
|
- which lenses run (every lens is off until `lenses.<name>.run` is true) and the environment each gets (`none`, `browser`, `supabase` or `browser+supabase`);
|
||||||
|
- each lens's own section (`lenses.<name>.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`);
|
- how its application is started and reached: one stack, or several side by side (`slots`);
|
||||||
- where its local database answers;
|
- where its local database answers;
|
||||||
- which paths carry no runtime behaviour.
|
- which paths carry no runtime behaviour.
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import :Tools;
|
||||||
import :PgWire;
|
import :PgWire;
|
||||||
import :Process;
|
import :Process;
|
||||||
import :Platform;
|
import :Platform;
|
||||||
|
import :Prompt;
|
||||||
import std;
|
import std;
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
@ -121,8 +122,10 @@ namespace Crafter::Review::Config {
|
||||||
Json Defaults() {
|
Json Defaults() {
|
||||||
Json lenses = Json::object();
|
Json lenses = Json::object();
|
||||||
// Every lens is off until the repository's config turns it on
|
// 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").
|
// (2026-10-31, Jorijn: "all lenses off by default and opt in only");
|
||||||
for (const Plan::Lens& lens : Plan::Lenses) lenses[Plan::LensName(lens.file)] = {{"run", false}, {"environment", lens.env}};
|
// 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}};
|
||||||
return {
|
return {
|
||||||
// The integration branch a range is measured against (e.g.
|
// The integration branch a range is measured against (e.g.
|
||||||
// origin/dev): merge-base HEAD <base>.
|
// origin/dev): merge-base HEAD <base>.
|
||||||
|
|
@ -204,9 +207,23 @@ namespace Crafter::Review::Config {
|
||||||
for (const std::string& warning : warnings) warningList.push_back(warning);
|
for (const std::string& warning : warnings) warningList.push_back(warning);
|
||||||
config["_warnings"] = std::move(warningList);
|
config["_warnings"] = std::move(warningList);
|
||||||
std::ranges::move(Validate(config), std::back_inserter(loaded.problems));
|
std::ranges::move(Validate(config), std::back_inserter(loaded.problems));
|
||||||
|
std::ranges::move(SectionProblems(config, repo), std::back_inserter(loaded.problems));
|
||||||
return loaded;
|
return loaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// A path setting resolved against the repository root (slots.dir,
|
||||||
|
// lenses.<name>.section): a relative path that stays inside it - not
|
||||||
|
// blank, no leading `/`, no `..` part (2026-11-01: one check, so the
|
||||||
|
// settings cannot drift apart).
|
||||||
|
bool InsideRepository(const Json& value) {
|
||||||
|
if (!value.is_string()) return false;
|
||||||
|
const std::string& dir = value.get_ref<const std::string&>();
|
||||||
|
const std::vector<std::string> parts = Dyn::Split(Dyn::Replace(dir, "\\", "/"), "/");
|
||||||
|
return !Dyn::Strip(dir).empty() && !dir.starts_with('/') && !std::ranges::contains(parts, std::string(".."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::string> Validate(const Json& config) {
|
std::vector<std::string> Validate(const Json& config) {
|
||||||
std::vector<std::string> out;
|
std::vector<std::string> out;
|
||||||
const Json& trailers = Dyn::At(config, "aiTrailers");
|
const Json& trailers = Dyn::At(config, "aiTrailers");
|
||||||
|
|
@ -237,6 +254,8 @@ namespace Crafter::Review::Config {
|
||||||
}
|
}
|
||||||
if (!Dyn::Get(entry, "run").is_boolean()) out.push_back(std::format("lenses.{}.run must be true or false", it.key()));
|
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(", ")));
|
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; };
|
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
|
// Every lens is off until the repository turns it on: a config that
|
||||||
|
|
@ -305,14 +324,7 @@ namespace Crafter::Review::Config {
|
||||||
if (!slots.is_object()) {
|
if (!slots.is_object()) {
|
||||||
out.emplace_back("slots must be an object {dir, start, stop, memMb, startMb}");
|
out.emplace_back("slots must be an object {dir, start, stop, memMb, startMb}");
|
||||||
} else if (!Dyn::Get(slots, "dir").is_null()) {
|
} else if (!Dyn::Get(slots, "dir").is_null()) {
|
||||||
const Json& dir = Dyn::At(slots, "dir");
|
if (!InsideRepository(Dyn::At(slots, "dir"))) out.emplace_back("slots.dir must be null or a directory inside the repository (a relative path)");
|
||||||
bool bad = !dir.is_string();
|
|
||||||
if (!bad) {
|
|
||||||
const std::string& d = dir.get_ref<const std::string&>();
|
|
||||||
const std::vector<std::string> parts = Dyn::Split(Dyn::Replace(d, "\\", "/"), "/");
|
|
||||||
bad = Dyn::Strip(d).empty() || d.starts_with('/') || std::ranges::contains(parts, std::string(".."));
|
|
||||||
}
|
|
||||||
if (bad) out.emplace_back("slots.dir must be null or a directory inside the repository (a relative path)");
|
|
||||||
for (const std::string_view key : {"start", "stop"}) {
|
for (const std::string_view key : {"start", "stop"}) {
|
||||||
const Json& value = Dyn::Get(slots, key);
|
const Json& value = Dyn::Get(slots, key);
|
||||||
if (!value.is_null() && (!value.is_string() || !value.get_ref<const std::string&>().contains("{n}"))) {
|
if (!value.is_null() && (!value.is_string() || !value.get_ref<const std::string&>().contains("{n}"))) {
|
||||||
|
|
@ -373,6 +385,31 @@ namespace Crafter::Review::Config {
|
||||||
return {email};
|
return {email};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::map<std::string, fs::path> LensSectionsOf(const Json& config, const fs::path& repo) {
|
||||||
|
// Only a file inside the repository, by the rule Validate refuses the
|
||||||
|
// rest by: fs::path's `/` would let an absolute one replace the root.
|
||||||
|
std::map<std::string, fs::path> out;
|
||||||
|
for (const Plan::Lens& lens : Plan::Lenses) {
|
||||||
|
const Json& section = Dyn::Get(LensEntry(config, lens.file), "section");
|
||||||
|
if (InsideRepository(section)) out.emplace(std::string(lens.file), repo / section.get<std::string>());
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> SectionProblems(const Json& config, const fs::path& repo) {
|
||||||
|
// Through the reader the run itself uses (Prompt::ReadSection): a
|
||||||
|
// section the run could not use is a problem here, never a section
|
||||||
|
// left out in silence or an error in the run's Gate 0.
|
||||||
|
std::vector<std::string> out;
|
||||||
|
for (const auto& [lensFile, file] : LensSectionsOf(config, repo)) {
|
||||||
|
const std::string where = std::format("lenses.{}.section: {}", Plan::LensName(lensFile), Dyn::Str(Dyn::Get(LensEntry(config, lensFile), "section")));
|
||||||
|
const Prompt::Section section = Prompt::ReadSection(file);
|
||||||
|
if (!section.problem.empty()) out.push_back(std::format("{} {}", where, section.problem));
|
||||||
|
else if (!section.present) out.push_back(std::format("{} does not exist", where));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::string> LensesOff(const Json& config) {
|
std::vector<std::string> LensesOff(const Json& config) {
|
||||||
std::vector<std::string> out;
|
std::vector<std::string> out;
|
||||||
for (const Plan::Lens& lens : Plan::Lenses) {
|
for (const Plan::Lens& lens : Plan::Lenses) {
|
||||||
|
|
|
||||||
|
|
@ -122,9 +122,24 @@ namespace Crafter::Review::Prompt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::map<std::string, std::filesystem::path>& Sections() {
|
||||||
|
static std::map<std::string, std::filesystem::path> files;
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetLensSections(std::map<std::string, std::filesystem::path> files) {
|
||||||
|
Sections() = std::move(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path LensFile(std::string_view name) {
|
||||||
|
return LensDir() / std::string(name);
|
||||||
|
}
|
||||||
|
|
||||||
std::string SystemText(std::string_view mode) {
|
std::string SystemText(std::string_view mode) {
|
||||||
if (mode == "reply" || mode == "series") return Dyn::Replace(Dyn::ReadText(LensDir() / "_common.md"), "{{ARTIFACT_KIND}}", mode == "reply" ? "mailing-list reply" : "patch series");
|
if (mode == "reply" || mode == "series") return Dyn::Replace(Dyn::ReadText(LensFile("_common.md")), "{{ARTIFACT_KIND}}", mode == "reply" ? "mailing-list reply" : "patch series");
|
||||||
return Dyn::Replace(Dyn::ReadText(LensDir() / "_common-repo.md"), "{{ARTIFACT_KIND}}", "commit range");
|
return Dyn::Replace(Dyn::ReadText(LensFile("_common-repo.md")), "{{ARTIFACT_KIND}}", "commit range");
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
@ -166,8 +181,43 @@ namespace Crafter::Review::Prompt {
|
||||||
Dyn::Join(inputs, "\n"), hash, ArtifactLines(lines), units.size(), InventoryLines(units), refDigest);
|
Dyn::Join(inputs, "\n"), hash, ArtifactLines(lines), units.size(), InventoryLines(units), refDigest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Section ReadSection(const std::filesystem::path& file) {
|
||||||
|
Section out;
|
||||||
|
std::error_code error;
|
||||||
|
const std::filesystem::file_status entry = std::filesystem::symlink_status(file, error);
|
||||||
|
if (error && error != std::errc::no_such_file_or_directory && error != std::errc::not_a_directory) {
|
||||||
|
out.present = true;
|
||||||
|
out.problem = std::format("cannot be read ({})", error.message());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
if (!std::filesystem::exists(entry)) return out;
|
||||||
|
out.present = true;
|
||||||
|
if (!std::filesystem::is_regular_file(std::filesystem::status(file, error))) {
|
||||||
|
out.problem = std::format("cannot be read ({})", error ? error.message() : std::string("not a regular file"));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
out.text = Dyn::ReadText(file);
|
||||||
|
} catch (const Dyn::Error& failure) {
|
||||||
|
out.problem = failure.Kind() == "UnicodeDecodeError" ? std::string("is not UTF-8") : std::string("cannot be read");
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
std::string Charter(std::string_view lensFile) {
|
std::string Charter(std::string_view lensFile) {
|
||||||
return Dyn::ReadText(LensDir() / std::string(lensFile));
|
std::string text = Dyn::ReadText(LensFile(lensFile));
|
||||||
|
if (const auto own = Sections().find(std::string(lensFile)); own != Sections().end()) {
|
||||||
|
// Through the reader the config checks it with: a section the
|
||||||
|
// config reported never reaches a run, and none is dropped here.
|
||||||
|
const Section section = ReadSection(own->second);
|
||||||
|
if (!section.problem.empty() || !section.present) {
|
||||||
|
throw Dyn::Error("ValueError", std::format("lenses.{}.section: {} {}", Plan::LensName(lensFile), own->second.string(),
|
||||||
|
section.present ? section.problem : std::string("does not exist")));
|
||||||
|
}
|
||||||
|
while (text.ends_with('\n')) text.pop_back();
|
||||||
|
text += std::format("\n\n{}", section.text);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string RunLayer(std::string_view artifactRel, std::string_view base, std::string_view date, std::span<const std::string> trees, std::string_view hash,
|
std::string RunLayer(std::string_view artifactRel, std::string_view base, std::string_view date, std::span<const std::string> trees, std::string_view hash,
|
||||||
|
|
|
||||||
|
|
@ -615,6 +615,19 @@ namespace Crafter::Review::Run {
|
||||||
log(std::format("reads: every code unit of the {}code lenses needs {} clean reads, one per round; this is round {} on this hash{}", Kernel() ? "" : "static ", x, round,
|
log(std::format("reads: every code unit of the {}code lenses needs {} clean reads, one per round; this is round {} on this hash{}", Kernel() ? "" : "static ", x, round,
|
||||||
newRound || !sameHash ? std::string() : std::string(" (continuing an unfinished run)")));
|
newRound || !sameHash ? std::string() : std::string(" (continuing an unfinished run)")));
|
||||||
}
|
}
|
||||||
|
// The repository's own lens sections (lenses.<name>.section): each
|
||||||
|
// file is appended to its lens's shipped charter (Prompt::Charter), so
|
||||||
|
// the charter sha in every reuse key covers it. The config refused a
|
||||||
|
// path outside the repository and a file the run could not read.
|
||||||
|
const std::map<std::string, fs::path> sections = Config::LensSectionsOf(cfg, repo);
|
||||||
|
Prompt::SetLensSections(sections);
|
||||||
|
if (!sections.empty()) {
|
||||||
|
std::vector<std::string> own;
|
||||||
|
for (const std::string& lf : modeLenses) {
|
||||||
|
if (sections.contains(lf)) own.push_back(Plan::LensName(lf));
|
||||||
|
}
|
||||||
|
log(std::format("lenses: the repository's sections appended to {}", own.empty() ? std::string("no charter of this mode") : Dyn::Join(own, ", ")));
|
||||||
|
}
|
||||||
for (const Plan::Lens& lens : Plan::Lenses) {
|
for (const Plan::Lens& lens : Plan::Lenses) {
|
||||||
const std::string lf(lens.file);
|
const std::string lf(lens.file);
|
||||||
charters[lf] = Prompt::Charter(lf);
|
charters[lf] = Prompt::Charter(lf);
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,13 @@ export namespace Crafter::Review::Config {
|
||||||
std::vector<std::string> SlotCommands(const Json& config, std::string_view which, std::span<const std::int64_t> slots);
|
std::vector<std::string> SlotCommands(const Json& config, std::string_view which, std::span<const std::int64_t> slots);
|
||||||
// The first login fixture that exists, relative to the repository root.
|
// The first login fixture that exists, relative to the repository root.
|
||||||
std::optional<std::string> LoginFixture(const Json& config, const std::filesystem::path& repo);
|
std::optional<std::string> LoginFixture(const Json& config, const std::filesystem::path& repo);
|
||||||
|
// The repository's own lens sections (`lenses.<name>.section`, resolved
|
||||||
|
// against the repository root), keyed by lens file: each a file the
|
||||||
|
// config names inside the repository (a relative path, no `..` part).
|
||||||
|
std::map<std::string, std::filesystem::path> LensSectionsOf(const Json& config, const std::filesystem::path& repo);
|
||||||
|
// What is wrong with those files: one missing, or one
|
||||||
|
// Prompt::ReadSection cannot use.
|
||||||
|
std::vector<std::string> SectionProblems(const Json& config, const std::filesystem::path& repo);
|
||||||
// The example a consumer starts from (process/templates/config.json is
|
// The example a consumer starts from (process/templates/config.json is
|
||||||
// this, rendered).
|
// this, rendered).
|
||||||
Json Example();
|
Json Example();
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,10 @@ export namespace Crafter::Review {
|
||||||
void SetHome(std::filesystem::path home);
|
void SetHome(std::filesystem::path home);
|
||||||
// Home()/process/lenses.
|
// Home()/process/lenses.
|
||||||
std::filesystem::path LensDir();
|
std::filesystem::path LensDir();
|
||||||
// The sha256 of each lens file — the text read with universal newlines,
|
// The sha256 of each shipped lens file — the text read with universal
|
||||||
// as UTF-8 — keyed by file name. Every charter's is part of its lens's
|
// newlines, as UTF-8 — keyed by file name. A lens's reuse keys carry its
|
||||||
// reuse keys, so --version and the run log print them: a stale home shows
|
// charter's (the combined text's when lenses.<name>.section gives the
|
||||||
// up there before it shows up as nothing being reused.
|
// lens a section of the repository's own), so --version prints these: a
|
||||||
|
// stale home shows up there before it shows up as nothing being reused.
|
||||||
std::vector<std::pair<std::string, std::string>> CharterHashes();
|
std::vector<std::pair<std::string, std::string>> CharterHashes();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,10 +48,29 @@ export namespace Crafter::Review::Prompt {
|
||||||
// question; empty for every other lens and slot.
|
// question; empty for every other lens and slot.
|
||||||
std::string_view SlotDuty(std::string_view lensFile, std::string_view unitId);
|
std::string_view SlotDuty(std::string_view lensFile, std::string_view unitId);
|
||||||
|
|
||||||
|
// The repository's own section file per lens file (the config's
|
||||||
|
// lenses.<name>.section), appended to that charter by Charter. Empty:
|
||||||
|
// the shipped texts alone. Set by the run.
|
||||||
|
void SetLensSections(std::map<std::string, std::filesystem::path> files);
|
||||||
|
// A shipped lens text's path.
|
||||||
|
std::filesystem::path LensFile(std::string_view name);
|
||||||
// _common-repo.md with the artifact kind filled in; the kernel modes
|
// _common-repo.md with the artifact kind filled in; the kernel modes
|
||||||
// _common.md (a mailing-list reply, a patch series).
|
// _common.md (a mailing-list reply, a patch series).
|
||||||
std::string SystemText(std::string_view mode = "commits");
|
std::string SystemText(std::string_view mode = "commits");
|
||||||
// The lens's charter, read with universal newlines.
|
// A repository's section file as every reader takes it (2026-11-01, the
|
||||||
|
// self-review's round 16): absent (no entry at all), its text, or present
|
||||||
|
// but unusable - a dangling or looping link, no regular file, unreadable,
|
||||||
|
// not UTF-8 - with the problem said. The config reports a named file that
|
||||||
|
// is absent or unusable (Config::SectionProblems) and Charter refuses
|
||||||
|
// both, so a section is never dropped in silence.
|
||||||
|
struct Section {
|
||||||
|
bool present = false;
|
||||||
|
std::string text;
|
||||||
|
std::string problem;
|
||||||
|
};
|
||||||
|
Section ReadSection(const std::filesystem::path& file);
|
||||||
|
// The lens's charter, read with universal newlines: the shipped text,
|
||||||
|
// then one blank line and the repository's section when it has one.
|
||||||
std::string Charter(std::string_view lensFile);
|
std::string Charter(std::string_view lensFile);
|
||||||
|
|
||||||
// INPUTS, the artifact with line numbers, the inventory. trees: as they
|
// INPUTS, the artifact with line numbers, the inventory. trees: as they
|
||||||
|
|
|
||||||
|
|
@ -98,3 +98,10 @@ record is a `reserve-answer` or `info` note. Never grade a finding.
|
||||||
polished over six rounds and removed by the owner in one line ("they can
|
polished over six rounds and removed by the owner in one line ("they can
|
||||||
be viewed in the releases tab"). Ask first whether the thing needs to exist
|
be viewed in the releases tab"). Ask first whether the thing needs to exist
|
||||||
in the tree at all.
|
in the tree at all.
|
||||||
|
- 2026-10-31 (round 9 of a consumer's first push): rounds 6–8 reworked an
|
||||||
|
app's mapping of a daemon's message ids three times before round 9 asked
|
||||||
|
whether the ids should be unique at the layer that mints them — the
|
||||||
|
daemon's, a repository of the same owner, where the fix was one change. A
|
||||||
|
mechanism reworked a second time in one submission is the signal: at the
|
||||||
|
second rework, derive the fix at every layer the data crosses before
|
||||||
|
another variant on the consuming side.
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,8 @@ hash at Gate 5.
|
||||||
— content, history and blame; the state before the range via
|
— content, history and blame; the state before the range via
|
||||||
`git show <base>:<path>`.
|
`git show <base>:<path>`.
|
||||||
- `--context-paths` for the evidence dirs the commit messages' claims rest on.
|
- `--context-paths` for the evidence dirs the commit messages' claims rest on.
|
||||||
- The lens charter (`process/lenses/repo-*.md`, system text
|
- The lens charter (`process/lenses/repo-*.md`, with the repository's own
|
||||||
|
section, `lenses.<name>.section`, appended; system text
|
||||||
`_common-repo.md`) and the dispositions in force.
|
`_common-repo.md`) and the dispositions in force.
|
||||||
- A lens with the `browser` or `browser+supabase` environment (the
|
- A lens with the `browser` or `browser+supabase` environment (the
|
||||||
functional lens by default; `lenses.<name>.environment` in the config):
|
functional lens by default; `lenses.<name>.environment` in the config):
|
||||||
|
|
@ -159,7 +160,14 @@ is empirical and needs one with the browser while it runs; the static
|
||||||
lenses default to `none` and may take any. A verdict bought in one
|
lenses default to `none` and may take any. A verdict bought in one
|
||||||
environment is never reused, resumed or salvaged in another, and a defect
|
environment is never reused, resumed or salvaged in another, and a defect
|
||||||
found in one drops the clean verdicts the lens holds for the same code in
|
found in one drops the clean verdicts the lens holds for the same code in
|
||||||
the others), `supabase`, the database under test for every lens with
|
the others; `section`, a file of the repository's own — a relative path
|
||||||
|
inside it — appended to that lens's shipped charter after one blank line:
|
||||||
|
the calibration entries and conventions that are this repository's rather
|
||||||
|
than the lens's, which has the shipped charter for what is general to it;
|
||||||
|
the reuse keys carry the combined text's hash; a path outside the
|
||||||
|
repository and a file missing, unreadable or not UTF-8 (a dangling link
|
||||||
|
included) are config problems, a name that is no lens an unknown key),
|
||||||
|
`supabase`, the database under test for every lens with
|
||||||
`supabase` or `browser+supabase`: `dbUrl` (the local stack's Postgres —
|
`supabase` or `browser+supabase`: `dbUrl` (the local stack's Postgres —
|
||||||
this machine only; masked in the dump and the log), `start` / `stop` (how
|
this machine only; masked in the dump and the log), `start` / `stop` (how
|
||||||
the operator brings the stack up when no lens drives the application;
|
the operator brings the stack up when no lens drives the application;
|
||||||
|
|
@ -360,9 +368,10 @@ the functional lens):
|
||||||
never stored, so it runs on the next hash whatever changed;
|
never stored, so it runs on the next hash whatever changed;
|
||||||
- every code unit and both whole passes: the range base (a rebase re-runs
|
- every code unit and both whole passes: the range base (a rebase re-runs
|
||||||
all of them);
|
all of them);
|
||||||
- as always: the lens charter, the prompt version, the model, the
|
- as always: the lens charter (the shipped text with the repository's
|
||||||
dispositions in force. An edit to a `repo-*.md` charter re-runs that lens
|
section), the prompt version, the model, the dispositions in force. An
|
||||||
on the next hash; a re-run on the SAME hash resumes and salvages by hash and
|
edit to a `repo-*.md` charter, or to the repository's section of it,
|
||||||
|
re-runs that lens on the next hash; a re-run on the SAME hash resumes and salvages by hash and
|
||||||
model alone (use `--no-reuse --no-salvage` there).
|
model alone (use `--no-reuse --no-salvage` there).
|
||||||
|
|
||||||
What is stored as reusable: a pass that itself returned no undischarged
|
What is stored as reusable: a pass that itself returned no undischarged
|
||||||
|
|
@ -470,8 +479,10 @@ can still reject (a fixture repository with one planted defect per gate;
|
||||||
|
|
||||||
A defect that reaches a pushed release, or a review comment on the
|
A defect that reaches a pushed release, or a review comment on the
|
||||||
repository nobody's lens predicted, is a process bug: post-mortem it and
|
repository nobody's lens predicted, is a process bug: post-mortem it and
|
||||||
append a dated calibration entry to the `repo-*.md` lens that should have
|
append a dated calibration entry where the lesson belongs: a lesson of
|
||||||
caught it, or here. The entries dated before 2026-10-15 were carried from the
|
this repository's in its own section of the lens that should have caught
|
||||||
|
it (`lenses.<name>.section`), one general to the lens in the shipped `repo-*.md`
|
||||||
|
charter, one of the mechanics here. The entries dated before 2026-10-15 were carried from the
|
||||||
pipeline's earlier home because they are rules of the mechanics, not of one
|
pipeline's earlier home because they are rules of the mechanics, not of one
|
||||||
repository.
|
repository.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,63 +13,78 @@
|
||||||
"lenses": {
|
"lenses": {
|
||||||
"repo-better-solution": {
|
"repo-better-solution": {
|
||||||
"run": false,
|
"run": false,
|
||||||
"environment": "none"
|
"environment": "none",
|
||||||
|
"section": null
|
||||||
},
|
},
|
||||||
"repo-functional": {
|
"repo-functional": {
|
||||||
"run": false,
|
"run": false,
|
||||||
"environment": "browser"
|
"environment": "browser",
|
||||||
|
"section": null
|
||||||
},
|
},
|
||||||
"repo-bug-prover": {
|
"repo-bug-prover": {
|
||||||
"run": false,
|
"run": false,
|
||||||
"environment": "none"
|
"environment": "none",
|
||||||
|
"section": null
|
||||||
},
|
},
|
||||||
"repo-security": {
|
"repo-security": {
|
||||||
"run": false,
|
"run": false,
|
||||||
"environment": "none"
|
"environment": "none",
|
||||||
|
"section": null
|
||||||
},
|
},
|
||||||
"repo-facts": {
|
"repo-facts": {
|
||||||
"run": false,
|
"run": false,
|
||||||
"environment": "none"
|
"environment": "none",
|
||||||
|
"section": null
|
||||||
},
|
},
|
||||||
"better-solution": {
|
"better-solution": {
|
||||||
"run": false,
|
"run": false,
|
||||||
"environment": "none"
|
"environment": "none",
|
||||||
|
"section": null
|
||||||
},
|
},
|
||||||
"code-quality": {
|
"code-quality": {
|
||||||
"run": false,
|
"run": false,
|
||||||
"environment": "none"
|
"environment": "none",
|
||||||
|
"section": null
|
||||||
},
|
},
|
||||||
"minimalism": {
|
"minimalism": {
|
||||||
"run": false,
|
"run": false,
|
||||||
"environment": "none"
|
"environment": "none",
|
||||||
|
"section": null
|
||||||
},
|
},
|
||||||
"bug-prover": {
|
"bug-prover": {
|
||||||
"run": false,
|
"run": false,
|
||||||
"environment": "none"
|
"environment": "none",
|
||||||
|
"section": null
|
||||||
},
|
},
|
||||||
"facts": {
|
"facts": {
|
||||||
"run": false,
|
"run": false,
|
||||||
"environment": "none"
|
"environment": "none",
|
||||||
|
"section": null
|
||||||
},
|
},
|
||||||
"consistency": {
|
"consistency": {
|
||||||
"run": false,
|
"run": false,
|
||||||
"environment": "none"
|
"environment": "none",
|
||||||
|
"section": null
|
||||||
},
|
},
|
||||||
"process-docs": {
|
"process-docs": {
|
||||||
"run": false,
|
"run": false,
|
||||||
"environment": "none"
|
"environment": "none",
|
||||||
|
"section": null
|
||||||
},
|
},
|
||||||
"etiquette": {
|
"etiquette": {
|
||||||
"run": false,
|
"run": false,
|
||||||
"environment": "none"
|
"environment": "none",
|
||||||
|
"section": null
|
||||||
},
|
},
|
||||||
"conciseness": {
|
"conciseness": {
|
||||||
"run": false,
|
"run": false,
|
||||||
"environment": "none"
|
"environment": "none",
|
||||||
|
"section": null
|
||||||
},
|
},
|
||||||
"maintainer-persona": {
|
"maintainer-persona": {
|
||||||
"run": false,
|
"run": false,
|
||||||
"environment": "none"
|
"environment": "none",
|
||||||
|
"section": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"supabase": {
|
"supabase": {
|
||||||
|
|
|
||||||
|
|
@ -323,5 +323,22 @@ int main() {
|
||||||
&& charter.contains("Calibration (append dated entries at the bottom)"), Dyn::Join(banned, ", "));
|
&& charter.contains("Calibration (append dated entries at the bottom)"), Dyn::Join(banned, ", "));
|
||||||
Check("the security charter names what the other lenses own and tells its blocks to route it as a note, never to suppress it", charter.contains("`repo-better-solution`")
|
Check("the security charter names what the other lenses own and tells its blocks to route it as a note, never to suppress it", charter.contains("`repo-better-solution`")
|
||||||
&& charter.contains("`repo-bug-prover`") && charter.contains("`repo-functional`") && charter.contains("instead of suppressing it"));
|
&& charter.contains("`repo-bug-prover`") && charter.contains("`repo-functional`") && charter.contains("instead of suppressing it"));
|
||||||
|
// A repository's own section (2026-10-31): its file for a lens is
|
||||||
|
// appended to the shipped charter after one blank line - the shipped
|
||||||
|
// text first and unchanged - so the charter's hash, in every reuse key,
|
||||||
|
// covers it; another lens's charter stays as shipped.
|
||||||
|
const fs::path sectionsDir = fs::temp_directory_path() / std::format("crafter-review-sections-{}", Platform::PublishedPid());
|
||||||
|
fs::remove_all(sectionsDir);
|
||||||
|
fs::create_directories(sectionsDir);
|
||||||
|
const std::string section = "## This repository's section\n\n- 2026-10-31: SECTION MARKER.\n";
|
||||||
|
std::ofstream(sectionsDir / "repo-security.md", std::ios::binary) << section;
|
||||||
|
Prompt::SetLensSections({{"repo-security.md", sectionsDir / "repo-security.md"}});
|
||||||
|
const std::string withSection = Prompt::Charter("repo-security.md");
|
||||||
|
const std::string untouched = Prompt::Charter("repo-facts.md");
|
||||||
|
Prompt::SetLensSections({});
|
||||||
|
Check("a repository's section is appended to the shipped charter after one blank line, the shipped text first and unchanged; a lens without a file stays as shipped; the hash differs",
|
||||||
|
withSection == std::format("{}\n\n{}", charter.substr(0, charter.find_last_not_of('\n') + 1), section) && untouched == Prompt::Charter("repo-facts.md") && Plan::Sha(withSection) != Plan::Sha(charter)
|
||||||
|
&& Prompt::Charter("repo-security.md") == charter);
|
||||||
|
fs::remove_all(sectionsDir);
|
||||||
return Failures ? 1 : 0;
|
return Failures ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,51 @@ int main() {
|
||||||
missingCommand.size() == 1 && missingCommand[0].starts_with("functional.mcp.command '/nonexistent/bin/npx' is no program here")
|
missingCommand.size() == 1 && missingCommand[0].starts_with("functional.mcp.command '/nonexistent/bin/npx' is no program here")
|
||||||
&& bad({{"functional", {{"mcp", {{"command", "/bin/sh"}}}}}}).empty() && bad({{"functional", {{"mcp", {{"command", "npx"}}}}}}).empty(),
|
&& bad({{"functional", {{"mcp", {{"command", "/bin/sh"}}}}}}).empty() && bad({{"functional", {{"mcp", {{"command", "npx"}}}}}}).empty(),
|
||||||
Show(missingCommand));
|
Show(missingCommand));
|
||||||
|
// A lens's own section (lenses.<name>.section, 2026-11-01, Jorijn: a path
|
||||||
|
// per lens rather than a directory listed): a file of the repository's,
|
||||||
|
// appended to the shipped charter. Only the section problems and the
|
||||||
|
// unknown keys count here: a config under test may turn no lens on.
|
||||||
|
auto sectionProblems = [&](const Json& lenses) {
|
||||||
|
std::vector<std::string> out;
|
||||||
|
for (const std::string& problem : bad({{"lenses", lenses}})) {
|
||||||
|
if (problem.contains("section") || problem.contains("unknown key lenses.")) out.push_back(problem);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
const std::string outside = "lenses.repo-facts.section must be null or a file inside the repository (a relative path)";
|
||||||
|
Check("lenses.<name>.section: not a string, a blank one, an absolute path and one climbing out with .. are problems (slots.dir's rule) and resolve to nothing",
|
||||||
|
Any(sectionProblems({{"repo-facts", {{"section", 5}}}}), outside) && Any(sectionProblems({{"repo-facts", {{"section", " "}}}}), outside)
|
||||||
|
&& Any(sectionProblems({{"repo-facts", {{"section", "/elsewhere/facts.md"}}}}), outside) && Any(sectionProblems({{"repo-facts", {{"section", "../facts.md"}}}}), outside)
|
||||||
|
&& Config::LensSectionsOf({{"lenses", {{"repo-facts", {{"section", "/elsewhere/facts.md"}}}}}}, td).empty()
|
||||||
|
&& Config::LensSectionsOf({{"lenses", {{"repo-facts", {{"section", "a/../../facts.md"}}}}}}, td).empty());
|
||||||
|
fs::create_directories(td / "sections" / "adir");
|
||||||
|
std::ofstream(td / "sections" / "facts.md", std::ios::binary) << "## Ours\n";
|
||||||
|
std::ofstream(td / "sections" / "README.md", std::ios::binary) << "beside the sections, no concern of the config's\n";
|
||||||
|
std::ofstream(td / "sections" / "bad.md", std::ios::binary) << std::string("\xff\n", 2);
|
||||||
|
fs::create_symlink("missing-target.md", td / "sections" / "dangling.md");
|
||||||
|
const std::vector<std::string> named = sectionProblems({{"repo-facts", {{"section", "sections/facts.md"}}}, {"repo-bug-prover", {{"section", "sections/bad.md"}}},
|
||||||
|
{"repo-security", {{"section", "sections/dangling.md"}}}, {"repo-functional", {{"section", "sections/adir"}}},
|
||||||
|
{"facts", {{"section", "sections/nope.md"}}}});
|
||||||
|
Check("lenses.<name>.section: a file that is there is fine, whatever lies beside it; one not UTF-8, a dangling link, a directory or a path to nothing is a problem naming the lens and the path",
|
||||||
|
named.size() == 4 && Any(named, "lenses.repo-bug-prover.section: sections/bad.md is not UTF-8") && Any(named, "lenses.repo-security.section: sections/dangling.md cannot be read")
|
||||||
|
&& Any(named, "lenses.repo-functional.section: sections/adir cannot be read") && Any(named, "lenses.facts.section: sections/nope.md does not exist"),
|
||||||
|
Show(named));
|
||||||
|
const std::vector<std::string> unknown = sectionProblems({{"repo-fact", {{"section", "sections/facts.md"}}}, {"_common", {{"section", "sections/facts.md"}}},
|
||||||
|
{"repo-facts", {{"sectoin", "sections/facts.md"}}}});
|
||||||
|
Check("lenses.<name>.section: a name that is no lens - the common text's too - and a misspelt key are unknown keys",
|
||||||
|
Any(unknown, "unknown key lenses.repo-fact") && Any(unknown, "unknown key lenses._common") && Any(unknown, "unknown key lenses.repo-facts.sectoin"), Show(unknown));
|
||||||
|
// A file that cannot be read - where permissions bind, as they do not for
|
||||||
|
// root.
|
||||||
|
std::ofstream(td / "sections" / "locked.md", std::ios::binary) << "## Ours\n";
|
||||||
|
fs::permissions(td / "sections" / "locked.md", fs::perms::none);
|
||||||
|
const bool unreadable = !Platform::ReadFile(td / "sections" / "locked.md");
|
||||||
|
const std::vector<std::string> locked = sectionProblems({{"repo-facts", {{"section", "sections/locked.md"}}}});
|
||||||
|
fs::permissions(td / "sections" / "locked.md", fs::perms::owner_all);
|
||||||
|
Check("lenses.<name>.section: a file that cannot be read is a problem naming it", !unreadable || Any(locked, "lenses.repo-facts.section: sections/locked.md cannot be read"), Show(locked));
|
||||||
|
const std::map<std::string, fs::path> resolved = Config::LensSectionsOf({{"lenses", {{"repo-facts", {{"section", "sections/facts.md"}}}}}}, td);
|
||||||
|
Check("lenses.<name>.section: a file that is there has no problem and resolves against the repository, keyed by the lens's file; null is none",
|
||||||
|
sectionProblems({{"repo-facts", {{"section", "sections/facts.md"}}}}).empty() && resolved.size() == 1 && resolved.at("repo-facts.md") == td / "sections" / "facts.md"
|
||||||
|
&& Config::LensSectionsOf({{"lenses", {{"repo-facts", {{"section", nullptr}}}}}}, td).empty());
|
||||||
std::ofstream(p, std::ios::binary) << "{not json";
|
std::ofstream(p, std::ios::binary) << "{not json";
|
||||||
Check("not JSON -> one problem", load().problems.size() == 1);
|
Check("not JSON -> one problem", load().problems.size() == 1);
|
||||||
Check("--config to a missing file is a problem", Any(Config::Load(td, td / "none.json").problems, "no such file"));
|
Check("--config to a missing file is a problem", Any(Config::Load(td, td / "none.json").problems, "no such file"));
|
||||||
|
|
|
||||||
|
|
@ -166,5 +166,6 @@ g['gates'] = new_gates
|
||||||
|
|
||||||
# ---- the charter ----------------------------------------------------------------------------
|
# ---- the charter ----------------------------------------------------------------------------
|
||||||
g['prompts']['charterSha']['repo-facts.md'] = sha((W / 'process/lenses/repo-facts.md').read_text())
|
g['prompts']['charterSha']['repo-facts.md'] = sha((W / 'process/lenses/repo-facts.md').read_text())
|
||||||
|
g['prompts']['charterSha']['repo-better-solution.md'] = sha((W / 'process/lenses/repo-better-solution.md').read_text())
|
||||||
G.write_text(json.dumps(g, indent=1, ensure_ascii=False) + '\n', encoding='utf-8')
|
G.write_text(json.dumps(g, indent=1, ensure_ascii=False) + '\n', encoding='utf-8')
|
||||||
print('written', G)
|
print('written', G)
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -598,6 +598,9 @@ echo "{\"type\":\"result\",\"subtype\":\"success\",\"is_error\":false,\"result\"
|
||||||
Json lenses = Json::object();
|
Json lenses = Json::object();
|
||||||
const std::array<std::string_view, 10> 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};
|
const std::array<std::string_view, 10> 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) lenses[Plan::LensName(lf)] = {{"run", true}};
|
||||||
|
// 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({{"lenses", lenses}})));
|
||||||
const fs::path fakeDir = base / "fake";
|
const fs::path fakeDir = base / "fake";
|
||||||
const fs::path fake = fakeDir / "claude";
|
const fs::path fake = fakeDir / "claude";
|
||||||
|
|
@ -641,6 +644,10 @@ echo "{\"type\":\"result\",\"subtype\":\"success\",\"is_error\":false,\"result\"
|
||||||
replyLog.contains(": reply journal/x/draft.eml @ ") && replyLog.contains("refs: 1 entries loaded from the existing index (dry run, no fetch)") && replyLog.contains("persona corpus: Some Maintainer -> NO RESULTS")
|
replyLog.contains(": reply journal/x/draft.eml @ ") && replyLog.contains("refs: 1 entries loaded from the existing index (dry run, no fetch)") && replyLog.contains("persona corpus: Some Maintainer -> NO RESULTS")
|
||||||
&& replyLog.contains("Gate 3 — Artifact truth: facts, etiquette, consistency, conciseness") && replyLog.contains("Gate 4 — Personas: persona:Some Maintainer"),
|
&& replyLog.contains("Gate 3 — Artifact truth: facts, etiquette, consistency, conciseness") && replyLog.contains("Gate 4 — Personas: persona:Some Maintainer"),
|
||||||
replyLog);
|
replyLog);
|
||||||
|
Check("...the bench's section: appended to the facts charter in the system files, after the shipped text, and the log names the lens",
|
||||||
|
replySystems.contains("- 2026-10-31: a section of the bench repository, appended to the facts charter.")
|
||||||
|
&& replySystems.find("# Lens: facts") < replySystems.find("- 2026-10-31: a section of the bench repository")
|
||||||
|
&& replyLog.contains("lenses: the repository's sections appended to facts"), replyLog);
|
||||||
// The series: the fixture under the journal, its cover's
|
// The series: the fixture under the journal, its cover's
|
||||||
// reference indexed.
|
// reference indexed.
|
||||||
const fs::path seriesDir = bench / "journal" / "x" / "patch" / "widget" / "v1";
|
const fs::path seriesDir = bench / "journal" / "x" / "patch" / "widget" / "v1";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue