// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® module Crafter.Review:Prompt_impl; import :Prompt; import :Json; import :Text; import :Core; import :Plan; import :PgWire; import std; namespace Crafter::Review::Prompt { const std::string_view GenericVocab = "Coverage vocabulary: clear (you checked this unit by your charter and found nothing - the basis says what you checked it against: the tree ref, the command run, the steps taken in the application and the evidence file), " "defect (a finding with items:[id] exists), immaterial (a note kind=immaterial with items:[id] exists), n/a (the unit carries nothing your charter judges - the basis says why). " "For the functional lens \"clear\" means the behaviour of the unit was exercised in the running application and observed as the code intends, never read as plausible; a unit with no reachable behaviour is n/a with the reason, and a behaviour you could not reach for a reason that is not by design is an `unverified` note with n/a, never clear."; const std::string_view WholeFirstRepo = "SCOPE - WHOLE PASS, and it gates the block passes of this lens: judge the range as an APPROACH for THIS repository - is this the right solution at all (an existing mechanism in the repository or its platform, the right layer or component, the right commit shape, compatibility with what users already have installed)? " "Do not review individual hunks; per-block passes of this lens run only if you pass. Return `summary` (5-10 sentences: the approach as you judged it and the constraints the block passes must respect). Cover exactly the units listed below (the whole:* slots)."; const std::string_view WholeFunctional = "SCOPE - WHOLE PASS, and it gates the block passes of this lens: exercise the range as a USER of the running application named under APPLICATION UNDER TEST. From the code and the commit messages derive every behaviour the range adds, changes or removes and every flow it touches; " "reach each one in the browser, act, observe, and compare what you see with what the code says; then exercise one adjacent flow per shared component, store, RPC or function the range touches. A behaviour that does not do what the code intends, a flow the range breaks, or a promised behaviour the application does not deliver is a finding with the steps and the evidence. " "Do not judge individual hunks in isolation; the block passes of this lens do that and run only if you pass. Return `summary` (5-10 sentences the block passes rest on: how to reach each touched screen, the login used, what was exercised, what the block passes must not touch or change). Cover exactly the units listed below (the whole:* slots)."; const std::string_view FunctionalBlockHint = "This pass judges ONE hunk in the running application: find the behaviour this hunk changes (what a user does, what the application answers), reach it as a user - the whole pass of this lens says how - exercise it, including the error or empty path the hunk touches, and compare what you observe with what the code says it does. " "A hunk whose change has no reachable behaviour of its own (a type, a helper only other hunks make visible, a test, a build script) is n/a with the reason - never clear. " "The stack (the application, its database, the account) is yours alone while this pass runs, but it is not reset between passes: earlier passes may have left records in it and later ones will see yours - create only what you need, name it with this unit id, never delete or change what you did not create in this pass, and leave the account as you found it."; const std::string_view ShotRules = "Take a screenshot (browser_take_screenshot, WITHOUT a filename - the server stores it under this pass's output directory and returns the path; a filename you choose is written into the repository's working tree) of every state a finding or a clear verdict rests on; the returned path is your evidence. browser_snapshot before every action - element refs come from the snapshot and die on navigation. After two failed attempts at the same target, stop and record an `unverified` note saying what blocked you."; const std::string_view HintOutside = "Judge the in-repository users and callers of what this commit changes, outside its own diff - at the base (`git -C show :`) and at HEAD."; const std::string_view HintAbsent = "Judge what a reviewer of this repository expects in this commit and does not find: a commit body at all (a body-less commit has no pK:msg unit), a test, a version or changelog bump, a doc or packaging update, an error path, a migration for existing installs."; namespace { // An optional string as the prompt prints it: None when absent. std::string OrNone(const std::optional& value) { return value ? *value : "None"; } // A string, or Error (TypeError): only a string is concatenated. const std::string& Text(const Json& value) { if (!value.is_string()) throw Dyn::Error("TypeError", std::format("can only concatenate str (not \"{}\") to str", Dyn::TypeName(value))); return value.get_ref(); } std::string JoinStrings(const Json& list, std::string_view separator) { std::vector parts; for (const Json& item : list) parts.push_back(Text(item)); return Dyn::Join(parts, separator); } std::int64_t IntItem(const Json& range, std::size_t index) { const Json& value = range.at(index); if (value.is_boolean()) return value.get() ? 1 : 0; return value.get(); } } std::string SystemText() { return Dyn::Replace(Dyn::ReadText(LensDir() / "_common-repo.md"), "{{ARTIFACT_KIND}}", "commit range"); } std::string Charter(std::string_view lensFile) { return Dyn::ReadText(LensDir() / std::string(lensFile)); } std::string RunLayer(std::string_view artifactRel, std::string_view base, std::string_view date, std::span trees, std::string_view hash, std::span lines, const Json& units, std::span contextPaths, std::string_view repoName) { std::vector inputs; inputs.push_back(std::format("- The frozen artifact (review THIS, at hash {}): the *.patch files in {} - `git format-patch` of the commit range under review, inlined below in sorted order; nothing else in that dir is an input", hash, artifactRel)); inputs.push_back(std::format("- Repository under review ({}), checked out at the range HEAD (the last commit of the artifact): {} - file content, history and blame come from this path; spell it exactly like this in every git -C call{}", repoName, Dyn::Join(trees, ", "), !trees.empty() && trees.front() == "." ? " (`.` is the working directory)" : "")); inputs.push_back(base.empty() ? std::string("- Range base: the base-commit line of the last patch") : std::format("- Range base (the state before the first commit; `git -C show :`): {}", base)); if (!contextPaths.empty()) { inputs.push_back(std::format("- Material the artifact discusses (read-only evidence; do not spend turns locating it): {}", Dyn::Join(contextPaths, ", "))); } inputs.push_back("- No mailing list, no network beyond the application under test: what is not on disk or in the running application is unverifiable and becomes a note"); inputs.push_back(std::format("- Run date: {}", date)); std::vector artifact; artifact.reserve(lines.size()); for (std::size_t i = 0; i < lines.size(); ++i) artifact.push_back(std::format("{:04d}| {}", i + 1, lines[i])); std::vector inventory; for (const Json& unit : units) { const Json& range = Dyn::At(unit, "lines"); inventory.push_back(std::format("- {} [{}-{}] {}", Dyn::Str(Dyn::At(unit, "id")), Dyn::Str(range.at(0)), Dyn::Str(range.at(1)), Dyn::Str(Dyn::At(unit, "excerpt")))); } return std::format("INPUTS:\n{}\n\nARTIFACT (frozen, hash {}), inlined with line numbers:\n{}\n\nINVENTORY - {} units, a MECHANICAL partition of the artifact (every non-blank line belongs to exactly one unit; fixed slots with lines 0-0 stand for what is NOT in the text):\n{}", Dyn::Join(inputs, "\n"), hash, Dyn::Join(artifact, "\n"), units.size(), Dyn::Join(inventory, "\n")); } std::string AppBlock(const Json& functional, const std::optional& loginFixture, const std::optional& totpCommand, bool slots) { const Json& f = Dyn::Or(functional, Dyn::EmptyDict()); std::vector lines = {"APPLICATION UNDER TEST (this lens drives it with the Playwright MCP tools mcp__playwright__*):"}; if (slots) { lines.emplace_back("- The application: a stack of this pass's own (the application, its API, its database and a login), one of several the operator brought up side by side from the tree named under INPUTS at the range HEAD; STACK FOR THIS PASS, at the end of the prompt, names its URL, the other origins it answers on and its login (the driver verified each stack answers before the gate started)"); } else { lines.push_back(std::format("- The application: {} - served from the tree named under INPUTS at the range HEAD (the driver verified the tree is at that HEAD and that this URL answers before the gate started)", Dyn::Str(Dyn::Get(f, "appUrl")))); if (Dyn::Truthy(Dyn::Get(f, "apiUrls"))) lines.push_back(std::format("- Other local origins you may call (the API, mail): {}", JoinStrings(Dyn::At(f, "apiUrls"), ", "))); if (loginFixture && !loginFixture->empty()) { lines.push_back(std::format("- Login: the fixture {} (JSON: email, password, totpSecret - a throwaway test account on the local stack); read it with the Read tool, and for the one-time code run exactly `{}` (prints a 6-digit code and the seconds left in its window; regenerate when it rolled over)", *loginFixture, OrNone(totpCommand))); } else { lines.emplace_back("- Login: no fixture was found on disk; what the application shows without a session is what you can exercise, and a behaviour behind a login is an `unverified` note with n/a"); } } if (Dyn::Truthy(Dyn::Get(Dyn::Or(Dyn::Get(f, "login"), Dyn::EmptyDict()), "notes"))) { lines.push_back(std::format("- Login notes: {}", Text(Dyn::At(Dyn::At(f, "login"), "notes")))); } if (Dyn::Truthy(Dyn::Get(f, "notes"))) lines.push_back(std::format("- Notes from the repository owner: {}", Text(Dyn::At(f, "notes")))); lines.push_back(std::format("- Rules: navigate only to the origins named here; the browser and the stack (the application, its database, the account) are yours alone while this pass runs, but the stack is not reset between passes: earlier passes of this gate may have left records in it and later ones will see what you leave - create only what you need, name it with your unit id, never delete or change what you did not create in this pass, and leave the account as you found it (where the range makes you change its password, an MFA factor, an organisation setting or the subscription, restore it; where you cannot, say so in an `info` note). Never sign up a new account unless the range is about signup. {}", ShotRules)); return Dyn::Join(lines, "\n"); } std::string StackText(const Json& stack, const std::optional& totpCommand) { std::vector lines = { std::format("STACK FOR THIS PASS ({}, yours alone while this pass runs):", Dyn::Str(Dyn::At(stack, "id"))), std::format("- The application: {}", Dyn::Str(Dyn::At(stack, "appUrl"))), }; if (Dyn::Truthy(Dyn::Get(stack, "apiUrls"))) lines.push_back(std::format("- Other local origins you may call (the API, mail): {}", JoinStrings(Dyn::At(stack, "apiUrls"), ", "))); if (Dyn::Truthy(Dyn::Get(stack, "login"))) { lines.push_back(std::format("- Login: the fixture {} (JSON: email, password, totpSecret - a throwaway test account on this stack); read it with the Read tool, and for the one-time code run exactly `{}` (prints a 6-digit code and the seconds left in its window; regenerate when it rolled over)", Dyn::Str(Dyn::At(stack, "login")), OrNone(totpCommand))); } else { lines.emplace_back("- Login: this stack has no login fixture; what the application shows without a session is what you can exercise, and a behaviour behind a login is an `unverified` note with n/a"); } return Dyn::Join(lines, "\n"); } std::string DbBlock(const Json& supabase, std::string_view sqlCommand, const std::optional& dbUser, bool withApp, const std::optional& checked, std::optional bypass) { const Json& s = Dyn::Or(supabase, Dyn::EmptyDict()); std::string role; if (dbUser && !dbUser->empty()) { role = std::format("The connection is the role `{}`{}", *dbUser, !bypass ? "." : (*bypass ? ", which bypasses row-level security." : ", which row-level security applies to.")); } else { role = "The connection's role differs between the stacks: `select current_user` says which is yours, and its pg_roles row whether row-level security applies to it."; } std::vector lines = { std::format("DATABASE UNDER TEST (this lens runs SQL against it with the exact command `{}`):", sqlCommand), std::format("- The database: the Postgres of the local Supabase stack the operator brought up from the tree named under INPUTS - a throwaway stack, never a hosted project. Checked by the driver before this gate: {}. {}", checked && !checked->empty() ? *checked : std::string("nothing (a dry run)"), role), std::format("- Run SQL with exactly `{} <<'SQL'`, the statements on the lines after it, and a last line `SQL`. Every call is ONE transaction that the helper rolls back at its end: what a statement writes is seen by the later statements of the same call and by nothing else - not by the application, not by the other passes running at the same time, not by your next call. So a proof goes into one call: set up the state, act, read the outcome. BEGIN, COMMIT, ROLLBACK, END, ABORT, PREPARE TRANSACTION and COPY are refused (SAVEPOINT and ROLLBACK TO are fine); the first statement that fails stops the call.", sqlCommand), "- Acting as a caller of the API: `set local role authenticated;` then `select set_config('request.jwt.claims', '{\"sub\":\"\",\"role\":\"authenticated\"}', true);` makes row-level security, auth.uid() and auth.jwt() behave as for that signed-in user; `set local role anon` as for a visitor without a session; `set local role service_role` as the service key. Say in your evidence which role each query ran as.", std::format("- Limits: statement_timeout {} s and lock_timeout {} s per statement; at most {} rows of a result are printed. A lock you take (DDL, an UPDATE) holds until your call ends while the other passes and the application wait on it - keep such calls short.", PgWire::StatementTimeoutS, PgWire::LockTimeoutS, PgWire::MaxRows), "- Evidence: the SQL you ran and the lines the helper printed for it - quote both in the finding or the coverage basis. Output that starts `review-gates sql: NO DATABASE` means the database did not answer: what rests on it is unverified, never clear.", }; if (withApp) { lines.emplace_back("- Nothing you write here reaches the application: create what the browser must see through the application itself, and use this database to read back what the application persisted and to inspect state no screen shows."); } if (Dyn::Truthy(Dyn::Get(s, "notes"))) lines.push_back(std::format("- Notes from the repository owner: {}", Text(Dyn::At(s, "notes")))); return Dyn::Join(lines, "\n"); } std::string LensLayer(std::string_view lensFile, std::string_view charterText, std::string_view dispositionsBlock, std::string_view app, std::string_view wholeSummary, std::string_view db) { std::vector parts = {std::format("YOUR CHARTER (process/lenses/{}), verbatim:\n\n{}", lensFile, Dyn::Strip(charterText))}; if (!app.empty()) parts.emplace_back(app); if (!db.empty()) parts.emplace_back(db); parts.emplace_back(dispositionsBlock); if (!wholeSummary.empty()) { parts.push_back(std::format("The whole pass of this lens on this hash concluded (the constraints your block pass rests on):\n{}", Dyn::Slice(wholeSummary, 0, 2500))); } return Dyn::Join(parts, "\n\n"); } std::string Tail(const Json& job, const Json& units, std::span lines, std::string_view wholeSummary) { const std::string lensFile = Dyn::At(job, "lensFile").get(); const Json& kind = Dyn::At(job, "kind"); if (kind == "whole") { const std::string_view whole = Plan::LensOf(lensFile).whole; if (whole != "first-repo" && whole != "functional") throw Dyn::Error("KeyError", Dyn::ReprValue(whole.empty() ? Json() : Json(whole))); std::vector listed; const Json& expected = Dyn::At(job, "expectedIds"); for (const Json& unit : units) { if (Dyn::In(Dyn::At(unit, "id"), expected)) listed.push_back(std::format("- {} [0-0] {}", Dyn::Str(Dyn::At(unit, "id")), Dyn::Str(Dyn::At(unit, "excerpt")))); } return std::format("{}\n{}\nUnits to cover:\n{}", whole == "first-repo" ? WholeFirstRepo : WholeFunctional, GenericVocab, Dyn::Join(listed, "\n")); } const Json* found = nullptr; for (const Json& unit : units) { if (Dyn::Equal(Dyn::At(unit, "id"), Dyn::At(job, "unit"))) { found = &unit; break; } } if (!found) throw Dyn::Error("StopIteration", ""); const Json& u = *found; const std::string id = Dyn::Str(Dyn::At(u, "id")); if (kind == "slot") { const std::string description = Dyn::Replace(Text(Dyn::At(u, "excerpt")), "FIXED SLOT: ", ""); return std::format("SCOPE - SLOT PASS, unit \"{}\": this unit has no lines; it stands for a question about the whole artifact: {}. Answer it for your lens. Coverage: exactly this one unit.\n{}", id, description, GenericVocab); } const Json& range = Dyn::At(u, "lines"); const std::int64_t a = IntItem(range, 0); const std::int64_t b = IntItem(range, 1); std::string_view hint; if (id.ends_with(":outside")) { hint = HintOutside; } else if (id.ends_with(":absent")) { hint = HintAbsent; } else if (lensFile == Plan::FunctionalLens) { hint = FunctionalBlockHint; } const std::string text = Plan::UnitText(lines, u); const std::string scope = std::format("SCOPE - BLOCK PASS, unit \"{}\" [lines {}-{}]: judge ONLY this unit, with your full attention by your charter - a hunk against the standard - reading the rest of the artifact only as context. Do not re-litigate the approach as a whole{}. Coverage: exactly this one unit.", id, a, b, wholeSummary.empty() ? "" : " - the whole pass of this lens concluded as stated above"); return std::format("{}{}\n\nUNIT TEXT (verbatim, lines {}-{}):\n{}\n\n{}", scope, hint.empty() ? std::string() : std::format("\n{}", hint), a, b, text, GenericVocab); } std::string BudgetLine(const Json& job) { return std::format("\n\nTool-call budget for this pass: {} (every tool call is one request; the pipeline stops the process at that count and a stopped pass " "returns NOTHING - the unit is then unaddressed and the lens NOT RETURNED). Batch independent reads into one call and return the structured output with margin to spare.", Dyn::Str(Dyn::Get(job, "maxTurns"))); } std::string SystemPrompt(std::string_view systemText, std::string_view run, std::string_view lens) { return std::format("{}{}{}{}{}", systemText, SepRun, run, SepLens, lens); } std::string UserPrompt(std::string_view tail, const Json& job, std::string_view stack) { return std::format("{}{}{}\n\nReturn the structured output described in your instructions.\n", tail, stack.empty() ? std::string() : std::format("\n\n{}", stack), Dyn::Truthy(job) ? BudgetLine(job) : std::string()); } }