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
|
|
@ -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) {
|
||||
if (mode == "reply" || mode == "series") return Dyn::Replace(Dyn::ReadText(LensDir() / "_common.md"), "{{ARTIFACT_KIND}}", mode == "reply" ? "mailing-list reply" : "patch series");
|
||||
return Dyn::Replace(Dyn::ReadText(LensDir() / "_common-repo.md"), "{{ARTIFACT_KIND}}", "commit range");
|
||||
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(LensFile("_common-repo.md")), "{{ARTIFACT_KIND}}", "commit range");
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
|
@ -166,8 +181,43 @@ namespace Crafter::Review::Prompt {
|
|||
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) {
|
||||
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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue