@webpieces/rules-config 0.4.774 → 0.4.776

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/package.json +1 -1
  2. package/src/checklist-config.d.ts +37 -6
  3. package/src/checklist-config.js +55 -19
  4. package/src/checklist-config.js.map +1 -1
  5. package/src/checklist-docs-validator.d.ts +2 -2
  6. package/src/checklist-docs-validator.js +2 -2
  7. package/src/checklist-docs-validator.js.map +1 -1
  8. package/src/checklist-instructions.d.ts +11 -0
  9. package/src/checklist-instructions.js +43 -11
  10. package/src/checklist-instructions.js.map +1 -1
  11. package/src/checklist-validator.d.ts +13 -12
  12. package/src/checklist-validator.js +46 -34
  13. package/src/checklist-validator.js.map +1 -1
  14. package/src/constants.d.ts +6 -0
  15. package/src/constants.js +7 -1
  16. package/src/constants.js.map +1 -1
  17. package/src/index.d.ts +3 -3
  18. package/src/index.js +11 -6
  19. package/src/index.js.map +1 -1
  20. package/src/pr-gate-config.d.ts +9 -1
  21. package/src/pr-gate-config.js +15 -4
  22. package/src/pr-gate-config.js.map +1 -1
  23. package/src/pr-gate-section-validators.d.ts +17 -3
  24. package/src/pr-gate-section-validators.js +91 -30
  25. package/src/pr-gate-section-validators.js.map +1 -1
  26. package/src/retired-config-keys.js +8 -0
  27. package/src/retired-config-keys.js.map +1 -1
  28. package/src/review-json-data.d.ts +3 -2
  29. package/src/review-json-data.js +4 -4
  30. package/src/review-json-data.js.map +1 -1
  31. package/src/review-json.js +3 -3
  32. package/src/review-json.js.map +1 -1
  33. package/src/review-provenance.js +2 -2
  34. package/src/review-provenance.js.map +1 -1
  35. package/src/reviewer-instructions.d.ts +13 -9
  36. package/src/reviewer-instructions.js +27 -16
  37. package/src/reviewer-instructions.js.map +1 -1
  38. package/src/subagent-provenance.d.ts +30 -15
  39. package/src/subagent-provenance.js +100 -50
  40. package/src/subagent-provenance.js.map +1 -1
  41. package/src/validate-config.js +5 -2
  42. package/src/validate-config.js.map +1 -1
  43. package/templates/webpieces.review-checklists.md +50 -27
@@ -1 +1 @@
1
- {"version":3,"file":"pr-gate-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/pr-gate-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { BRANCH_RETENTION_ARCHIVE_TAG, BRANCH_RETENTIONS } from './branch-archiver';\nimport { ChecklistDefinition, RawChecklistItem, toChecklist } from './checklist-config';\n\n// PrGateConfig is the \"special section\" for the pr-gate dashboard. It does NOT live in the\n// validated `rules` map (the FieldDef schema can't express nested object arrays), but as a\n// top-level `pr-gate` key in webpieces.config.json. It is built and validated by\n// loadAndValidate (load-config.ts); this module holds only the data classes + defaults + toGate.\n\n/**\n * The build the pr-gate runs when a repo configures no `commands.pr-gate.buildCommand` — and the ONE\n * definition of it, because two things now need it: the gate that RUNS it (pr-gate's BuildAffected)\n * and the guard that REFUSES a whole-monorepo build and prints it instead (ai-hook-rules'\n * whole-repo-build-guard). A second copy in either package is a message that can teach a command the\n * gate does not run.\n *\n * `--base=$(git merge-base origin/main HEAD)` — the FORK POINT, not `origin/main`: basing on\n * origin/main marks projects touched by other people's already-merged PRs as affected. The `$(…)`\n * resolves because the gate spawns with `shell: true`; the guard resolves it itself before printing,\n * so the AI is never handed a template it might paste somewhere without a shell.\n */\nexport const DEFAULT_BUILD_COMMAND = 'pnpm nx affected --target=ci --base=$(git merge-base origin/main HEAD)';\n\nexport class GateDefinition {\n name: string;\n patterns: string[];\n // The warning color shown on the dashboard WHEN this gate's patterns match a changed file. Green\n // is implicit (shown when nothing matched), so it is never configured. warningColor is purely\n // visual — even 'red' never fails/blocks the PR (only the build gate can). 'yellow' = caution,\n // 'red' = louder \"look here\" flag (e.g. DB schema / migration changes). REQUIRED on every gate.\n warningColor: string; // 'yellow' | 'red'\n // Example/inactive gate: parsed and kept in the file (JSON has no comments) but skipped at\n // compute/render time. Other projects flip this to false and tune patterns/warningColor.\n disabled: boolean;\n\n constructor(name: string, patterns: string[], warningColor: string, disabled = false) {\n this.name = name;\n this.patterns = patterns;\n this.warningColor = warningColor;\n this.disabled = disabled;\n }\n}\n\n// How wp-finish-upsert-pr should try to LAND the PR once it has posted it. GitHub's auto-merge queue\n// is a REPO-level setting (`allow_auto_merge`) that many orgs turn OFF as a policy control, and it\n// CANNOT be forced from the client: `gh pr merge --auto` calls the enablePullRequestAutoMerge GraphQL\n// mutation, which hard-errors with \"Auto merge is not allowed for this repository\" when the repo says\n// no. So the only lever a config knob has is WHICH PATHS WE ATTEMPT — never whether the queue exists.\nexport const MERGE_MODE_AUTO = 'AUTO';\nexport const MERGE_MODE_NONE = 'NONE';\nexport const MERGE_MODES = [MERGE_MODE_AUTO, MERGE_MODE_NONE];\n\n/**\n * `pr-gate.landPr` — what happens to the LOCAL branch once its PR has landed.\n *\n * A rich object rather than a bare string (the `commands.pr-gate` precedent) so the next knob about\n * landing has a home, and so the rationale can sit beside the setting as a `*Why` sibling: JSON has no\n * comments, and `<key>Why` is how this repo documents non-obvious config.\n */\n/**\n * One `pr-gate.reviewContext` entry: a human label and a repo-relative path a reviewer is handed instead of\n * having to find it. Data-only (per CLAUDE.md).\n */\nexport class ReviewContextEntry {\n label: string;\n path: string; // repo-relative in config; resolved to absolute when written into an instructions file\n\n constructor(label: string, entryPath: string) {\n this.label = label;\n this.path = entryPath;\n }\n}\n\nexport class LandPrConfig {\n // One of BRANCH_RETENTIONS. Defaults to 'archive-tag' — see BranchArchiver for why a tag beats\n // keeping the branch, storing a patch, or trusting the reflog.\n branchRetention: string;\n\n constructor(branchRetention: string = BRANCH_RETENTION_ARCHIVE_TAG) {\n this.branchRetention = branchRetention;\n }\n}\n\n// webpieces-disable no-function-outside-class -- module-level config default, matches defaultGates/defaultPrGateConfig in this file\nexport function defaultLandPrConfig(): LandPrConfig {\n return new LandPrConfig(BRANCH_RETENTION_ARCHIVE_TAG);\n}\n\n// The two literal ref names the dev-deploy flow derives everything else from. Defaults, not policy —\n// `pr-gate.devDeploy` overrides both, because a consumer whose shared environment is called `staging`\n// must not be forced onto webpieces' vocabulary.\nexport const DEFAULT_DEV_BRANCH_NAMESPACE = 'dev-include';\nexport const DEFAULT_DEV_BRANCH = 'dev';\n\n/**\n * `pr-gate.devDeploy` — where `wp-push-dev` publishes the throwaway copy of a feature branch, and which\n * ref is the shared dev branch itself.\n *\n * WHY A NAMESPACE AT ALL, i.e. why the copy is not just the feature branch: the feature branch is the PR\n * head, and landing that PR ships whatever is on it. The moment a conflict between two devs has to be\n * resolved SOMEWHERE for the shared environment to build, that resolution needs a home that is not the PR\n * branch — otherwise \"test it in dev\" silently ships another dev's unreviewed work to production. The\n * `<branchNamespace>/<feature>` copy is that home, and it is disposable by construction.\n *\n * `devBranch` is REFUSED as a source branch (you never push the composed branch back into itself); it is\n * written by the consumer's CI only, which recomputes it from `origin/main` on every run.\n */\nexport class DevDeployConfig {\n // Literal prefix on the feature branch name: `dev-include/dean/ONE-2275`. Git refs allow slashes, and\n // nobody ever types this — the command derives it.\n branchNamespace: string;\n // The composed, CI-owned branch that actually deploys. Never a source, never pushed to by this flow.\n devBranch: string;\n\n constructor(branchNamespace: string = DEFAULT_DEV_BRANCH_NAMESPACE, devBranch: string = DEFAULT_DEV_BRANCH) {\n this.branchNamespace = branchNamespace;\n this.devBranch = devBranch;\n }\n\n /** `<branchNamespace>/<branch>` — the remote ref holding the disposable copy of `branch`. */\n copyRefFor(branch: string): string {\n return `${this.branchNamespace}/${branch}`;\n }\n\n /** The `git ls-remote --heads origin <pattern>` pattern matching every live copy. */\n copyRefGlob(): string {\n return `${this.branchNamespace}/*`;\n }\n}\n\n// webpieces-disable no-function-outside-class -- module-level config default, matches defaultGates/defaultPrGateConfig in this file\nexport function defaultDevDeployConfig(): DevDeployConfig {\n return new DevDeployConfig(DEFAULT_DEV_BRANCH_NAMESPACE, DEFAULT_DEV_BRANCH);\n}\n\nexport class PrGateConfig {\n mode: string;\n /**\n * The nx-affected build gate command, shared by the two commands that can run it.\n *\n * It is NOT finish-only any more, and the change is the point. It used to run once, in\n * wp-finish-upsert-pr, which meant reviewers were spawned against a branch nobody had built — they could\n * spend a full review on code that does not compile, or on an unresolved 3-point merge.\n *\n * Now `wp-review-upsert-pr` finalizes the merge and runs this gate BEFORE briefing any reviewer, and\n * records the passing HEAD sha in the stage receipt. `wp-finish-upsert-pr` re-runs it only when HEAD has\n * moved since — so the flow gained a gate where it mattered without paying for two full builds.\n *\n * Empty string => BuildAffected falls back to DEFAULT_BUILD_COMMAND.\n */\n buildCommand: string;\n gates: GateDefinition[];\n /**\n * REQUIRED — every repo must state its policy; there is deliberately no default, because the two\n * answers are a real policy decision and guessing it either merges when a team did not want that,\n * or silently stops landing PRs on a team that relied on it.\n *\n * AUTO — wp-finish-upsert-pr LANDS the PR: squash-merge it right away when it is mergeable, else\n * enable GitHub auto-merge so it lands when the checks pass. Both carry an explicit --subject /\n * --body-file, which is the ONLY way main's history gets the PR title plus the compact\n * risk/flags body — no repo setting can produce that. Requires allow_auto_merge on the repo.\n * NONE — wp-finish-upsert-pr only opens/updates the PR and stops; a human merges. NOTE the cost:\n * a UI merge cannot use the compact body, so main's commit falls back to the repo's\n * squash_merge_commit_title/message settings. Set squash_merge_commit_title=PR_TITLE there, or\n * commits land as the internal \"Squash merge of <branch>\" subject.\n */\n mergeMode: string;\n // This repo's review checklists, straight from the `pr-gate.checklists` ARRAY in webpieces.config.json —\n // the ONLY accepted shape (`patterns` is a path-glob dispatch table and `subagent` a name binding, so both\n // are config). [] = no checklists. The removed `{ doc }` manifest form is a hard config error; see\n // validateChecklistsSection.\n checklists: ChecklistDefinition[];\n // Whether wp-finish-upsert-pr publishes each reviewer's full `output` as ONE combined PR comment\n // (idempotently updated on every push). Defaults to true. Set false to keep the PR body-only.\n checklistComments: boolean;\n /**\n * Shared secret used to mint the server-verifiable gate token. `wp-finish-upsert-pr` writes\n * `HMAC(gateSalt, HEAD_sha)` as a hidden marker into the PR body (and REFUSES to mint it unless\n * every BLOCK checklist passed), so a valid token IS proof the local gate ran and passed. A CI\n * check (`wp-check-pr` + the scaffolded workflow) recomputes it from the PR head sha and this salt.\n *\n * Optional, defaults to '' — empty means \"no token minted, no CI enforcement\" (byte-identical to\n * before this field existed). This is COMMITTED, obscurity-grade: it stops unhooked teammates who\n * push + open a PR in the web UI, but is readable in-repo and therefore forgeable by a determined\n * reader. It is deliberately NOT cryptographically sound; nothing local can stop a filesystem-reading\n * agent. See RESPONSE-pr-gate-ci-enforcement / the design memo for the full tradeoff.\n */\n gateSalt: string;\n /**\n * What `wp-land-pr` (and `wp-cleanup`, which reaps the same branches) does with the LOCAL branch\n * once its PR is in main. Field-with-default rather than another positional constructor param —\n * this constructor is already at the max-params limit, and every existing `new PrGateConfig(...)`\n * call site correctly wants the default.\n */\n landPr: LandPrConfig = defaultLandPrConfig();\n /**\n * Globs whose diffs are NOT extracted into `.webpieces/pr-review/<feature>/diff/` — regenerated noise a\n * reviewer should not spend context reading (lockfiles, generated graphs). Default [].\n *\n * They are still MATCHED against checklists and still listed in the manifest, with a stub naming the\n * command that gets the real diff. Removing them from the changed-file set would read as \"this file did\n * not change\", which is a different and false claim.\n *\n * Fields-with-defaults rather than constructor params: that constructor is already at max-params, and\n * every existing `new PrGateConfig(...)` correctly wants the defaults.\n */\n reviewDiffExclude: string[] = [];\n /**\n * Repo-specific places a reviewer would otherwise hunt for, as `{label, path}` — e.g.\n * `{\"label\":\"Cloud Tasks queue names\",\"path\":\"terraform/services/\"}`. Resolved to absolute paths in each\n * generated instructions file; a configured-but-missing path is printed as missing, never dropped.\n */\n reviewContext: ReviewContextEntry[] = [];\n /**\n * Installed packages to resolve and hand reviewers by absolute directory. This is the knob aimed at a\n * measured failure: one reviewer burned three separate greps into `node_modules/@webpieces` looking for\n * a scanner the tooling could have pointed at directly.\n */\n reviewContextPackages: string[] = [];\n /**\n * Promote \"this reviewer wrote a verdict without ever opening the diff\" from a warning to a refusal.\n * Default false, and it should stay false until a repo has watched the warning for a while: the signal\n * is derived from undocumented Claude Code transcript internals, so a format change would otherwise\n * wedge every PR in the repo with no self-service way out.\n */\n requireDiffEvidence = false;\n /**\n * Where `wp-push-dev` publishes the disposable copy. Omitted ⇒ `dev-include` / `dev`, which is what\n * makes the whole flow work with NO config edit at all.\n */\n devDeploy: DevDeployConfig = defaultDevDeployConfig();\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(mode: string, buildCommand: string, gates: GateDefinition[], mergeMode: string, checklists: ChecklistDefinition[] = [], gateSalt = '', checklistComments = true) {\n this.mode = mode;\n this.buildCommand = buildCommand;\n this.gates = gates;\n this.mergeMode = mergeMode;\n this.checklists = checklists;\n this.gateSalt = gateSalt;\n this.checklistComments = checklistComments;\n }\n}\n\n// Default infra gates — path-pattern based, tuned for this monorepo. Clients override the\n// whole list via the `pr-gate.gates` array in webpieces.config.json.\nexport function defaultGates(): GateDefinition[] {\n return [\n new GateDefinition('API Changed', ['libraries/apis/**', '**/*Api.ts'], 'yellow'),\n new GateDefinition('Config Files Changed', ['**/package.json', '**/tsconfig*.json', 'nx.json', '**/*.config.*'], 'yellow'),\n new GateDefinition('Dependency Graph Changed', ['architecture/dependencies.json'], 'yellow'),\n new GateDefinition('Claude / Rules Changed', ['**/CLAUDE.md', '**/claude.*.md', '.claude/**', 'webpieces.config.json'], 'yellow'),\n ];\n}\n\nexport function defaultPrGateConfig(): PrGateConfig {\n // No default checklists — the extension point is opt-in; the default monorepo ships none.\n return new PrGateConfig('ON', '', defaultGates(), MERGE_MODE_AUTO, []);\n}\n\ninterface RawGate {\n name?: string;\n patterns?: string[];\n warningColor?: string;\n disabled?: boolean;\n}\n\ninterface RawPrGateSection {\n mode?: string;\n buildCommand?: string;\n gates?: RawGate[];\n mergeMode?: string;\n // An ARRAY, always. validateChecklistsSection rejects every other shape (including the removed { doc }).\n checklists?: RawChecklistItem[];\n gateSalt?: string;\n checklistComments?: boolean;\n landPr?: RawLandPr;\n reviewDiffExclude?: string[];\n reviewContext?: RawReviewContext[];\n reviewContextPackages?: string[];\n requireDiffEvidence?: boolean;\n devDeploy?: RawDevDeploy;\n}\n\ninterface RawDevDeploy {\n branchNamespace?: string;\n devBranch?: string;\n}\n\ninterface RawLandPr {\n branchRetention?: string;\n}\n\ninterface RawReviewContext {\n label?: string;\n path?: string;\n}\n\nfunction toGate(raw: RawGate): GateDefinition {\n return new GateDefinition(raw.name ?? '', raw.patterns ?? [], raw.warningColor ?? 'yellow', raw.disabled ?? false);\n}\n\n/**\n * Build a PrGateConfig from the already-parsed top-level `pr-gate` section, falling back to defaults\n * for any field the consumer omits. Pure transform — the file read + structural validation happen in\n * loadAndValidate (load-config.ts) so every consumer goes through one validated path. Pass undefined\n * (no `pr-gate` key / no config file) to get full defaults.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed here\nexport function buildPrGateConfig(section: unknown): PrGateConfig {\n const defaults = defaultPrGateConfig();\n if (section === undefined || section === null || typeof section !== 'object') return defaults;\n\n const raw = section as RawPrGateSection;\n const mode = raw.mode ?? defaults.mode;\n const buildCommand = raw.buildCommand ?? defaults.buildCommand;\n const gates = raw.gates !== undefined ? raw.gates.map(toGate) : defaults.gates;\n // REQUIRED — validatePrGateSection rejects an omitted/unknown value, so this fallback only ever\n // applies to the no-config-file path that defaultPrGateConfig() serves.\n const mergeMode = raw.mergeMode ?? defaults.mergeMode;\n // Optional extension point — omitted ⇒ [] ⇒ no checklists computed anywhere downstream. A non-array here\n // cannot reach us: validateChecklistsSection has already failed the load.\n const checklists = Array.isArray(raw.checklists)\n ? raw.checklists.map((item: RawChecklistItem): ChecklistDefinition => toChecklist(item))\n : defaults.checklists;\n // Optional — omitted ⇒ '' ⇒ no gate token minted and CI enforcement is a no-op (back-compat).\n const gateSalt = raw.gateSalt ?? defaults.gateSalt;\n // Optional — omitted ⇒ true ⇒ reviewer output published as a PR comment.\n const checklistComments = raw.checklistComments ?? defaults.checklistComments;\n const built = new PrGateConfig(mode, buildCommand, gates, mergeMode, checklists, gateSalt, checklistComments);\n built.landPr = buildLandPrConfig(raw.landPr);\n // Review-context knobs. All optional and all defaulted, so a config that omits every one of them (which\n // is every consumer's config today) behaves exactly as it did before they existed.\n built.reviewDiffExclude = Array.isArray(raw.reviewDiffExclude) ? raw.reviewDiffExclude : defaults.reviewDiffExclude;\n built.reviewContextPackages = Array.isArray(raw.reviewContextPackages) ? raw.reviewContextPackages : defaults.reviewContextPackages;\n built.reviewContext = Array.isArray(raw.reviewContext)\n ? raw.reviewContext.map((e: RawReviewContext): ReviewContextEntry => new ReviewContextEntry(e.label ?? '', e.path ?? ''))\n : defaults.reviewContext;\n built.requireDiffEvidence = raw.requireDiffEvidence ?? defaults.requireDiffEvidence;\n built.devDeploy = buildDevDeployConfig(raw.devDeploy);\n return built;\n}\n\n/**\n * Build the `pr-gate.devDeploy` block. Omitted (the state of every consumer config today) ⇒ the\n * `dev-include` / `dev` defaults. An invalid value cannot reach here — validateDevDeploySection has\n * already failed the load.\n */\n// webpieces-disable no-function-outside-class -- module-level config transform, matches buildPrGateConfig above\nexport function buildDevDeployConfig(raw: RawDevDeploy | undefined): DevDeployConfig {\n const defaults = defaultDevDeployConfig();\n if (raw === undefined || raw === null || typeof raw !== 'object') return defaults;\n const namespace = typeof raw.branchNamespace === 'string' && raw.branchNamespace.trim() !== ''\n ? raw.branchNamespace.trim() : defaults.branchNamespace;\n const devBranch = typeof raw.devBranch === 'string' && raw.devBranch.trim() !== ''\n ? raw.devBranch.trim() : defaults.devBranch;\n return new DevDeployConfig(namespace, devBranch);\n}\n\n/**\n * Build the `pr-gate.landPr` block. Omitted (the current state of every consumer's config) ⇒ the\n * 'archive-tag' default, which is what makes this feature work with NO config edit at all. An invalid\n * value cannot reach here — validatePrGateSection has already failed the load.\n */\n// webpieces-disable no-function-outside-class -- module-level config transform, matches buildPrGateConfig above\nexport function buildLandPrConfig(raw: RawLandPr | undefined): LandPrConfig {\n const defaults = defaultLandPrConfig();\n if (raw === undefined || raw === null || typeof raw !== 'object') return defaults;\n const retention = raw.branchRetention;\n if (typeof retention !== 'string' || !BRANCH_RETENTIONS.includes(retention)) return defaults;\n return new LandPrConfig(retention);\n}\n\n"]}
1
+ {"version":3,"file":"pr-gate-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/pr-gate-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { BRANCH_RETENTION_ARCHIVE_TAG, BRANCH_RETENTIONS } from './branch-archiver';\nimport {\n ChecklistDefinition, RawChecklistItem, REVIEWER_AGENTS_ONE_PER_CHECKLIST, ReviewerAgentPolicy, toChecklist,\n} from './checklist-config';\n\n// PrGateConfig is the \"special section\" for the pr-gate dashboard. It does NOT live in the\n// validated `rules` map (the FieldDef schema can't express nested object arrays), but as a\n// top-level `pr-gate` key in webpieces.config.json. It is built and validated by\n// loadAndValidate (load-config.ts); this module holds only the data classes + defaults + toGate.\n\n/**\n * The build the pr-gate runs when a repo configures no `commands.pr-gate.buildCommand` — and the ONE\n * definition of it, because two things now need it: the gate that RUNS it (pr-gate's BuildAffected)\n * and the guard that REFUSES a whole-monorepo build and prints it instead (ai-hook-rules'\n * whole-repo-build-guard). A second copy in either package is a message that can teach a command the\n * gate does not run.\n *\n * `--base=$(git merge-base origin/main HEAD)` — the FORK POINT, not `origin/main`: basing on\n * origin/main marks projects touched by other people's already-merged PRs as affected. The `$(…)`\n * resolves because the gate spawns with `shell: true`; the guard resolves it itself before printing,\n * so the AI is never handed a template it might paste somewhere without a shell.\n */\nexport const DEFAULT_BUILD_COMMAND = 'pnpm nx affected --target=ci --base=$(git merge-base origin/main HEAD)';\n\nexport class GateDefinition {\n name: string;\n patterns: string[];\n // The warning color shown on the dashboard WHEN this gate's patterns match a changed file. Green\n // is implicit (shown when nothing matched), so it is never configured. warningColor is purely\n // visual — even 'red' never fails/blocks the PR (only the build gate can). 'yellow' = caution,\n // 'red' = louder \"look here\" flag (e.g. DB schema / migration changes). REQUIRED on every gate.\n warningColor: string; // 'yellow' | 'red'\n // Example/inactive gate: parsed and kept in the file (JSON has no comments) but skipped at\n // compute/render time. Other projects flip this to false and tune patterns/warningColor.\n disabled: boolean;\n\n constructor(name: string, patterns: string[], warningColor: string, disabled = false) {\n this.name = name;\n this.patterns = patterns;\n this.warningColor = warningColor;\n this.disabled = disabled;\n }\n}\n\n// How wp-finish-upsert-pr should try to LAND the PR once it has posted it. GitHub's auto-merge queue\n// is a REPO-level setting (`allow_auto_merge`) that many orgs turn OFF as a policy control, and it\n// CANNOT be forced from the client: `gh pr merge --auto` calls the enablePullRequestAutoMerge GraphQL\n// mutation, which hard-errors with \"Auto merge is not allowed for this repository\" when the repo says\n// no. So the only lever a config knob has is WHICH PATHS WE ATTEMPT — never whether the queue exists.\nexport const MERGE_MODE_AUTO = 'AUTO';\nexport const MERGE_MODE_NONE = 'NONE';\nexport const MERGE_MODES = [MERGE_MODE_AUTO, MERGE_MODE_NONE];\n\n/**\n * `pr-gate.landPr` — what happens to the LOCAL branch once its PR has landed.\n *\n * A rich object rather than a bare string (the `commands.pr-gate` precedent) so the next knob about\n * landing has a home, and so the rationale can sit beside the setting as a `*Why` sibling: JSON has no\n * comments, and `<key>Why` is how this repo documents non-obvious config.\n */\n/**\n * One `pr-gate.reviewContext` entry: a human label and a repo-relative path a reviewer is handed instead of\n * having to find it. Data-only (per CLAUDE.md).\n */\nexport class ReviewContextEntry {\n label: string;\n path: string; // repo-relative in config; resolved to absolute when written into an instructions file\n\n constructor(label: string, entryPath: string) {\n this.label = label;\n this.path = entryPath;\n }\n}\n\nexport class LandPrConfig {\n // One of BRANCH_RETENTIONS. Defaults to 'archive-tag' — see BranchArchiver for why a tag beats\n // keeping the branch, storing a patch, or trusting the reflog.\n branchRetention: string;\n\n constructor(branchRetention: string = BRANCH_RETENTION_ARCHIVE_TAG) {\n this.branchRetention = branchRetention;\n }\n}\n\n// webpieces-disable no-function-outside-class -- module-level config default, matches defaultGates/defaultPrGateConfig in this file\nexport function defaultLandPrConfig(): LandPrConfig {\n return new LandPrConfig(BRANCH_RETENTION_ARCHIVE_TAG);\n}\n\n// The two literal ref names the dev-deploy flow derives everything else from. Defaults, not policy —\n// `pr-gate.devDeploy` overrides both, because a consumer whose shared environment is called `staging`\n// must not be forced onto webpieces' vocabulary.\nexport const DEFAULT_DEV_BRANCH_NAMESPACE = 'dev-include';\nexport const DEFAULT_DEV_BRANCH = 'dev';\n\n/**\n * `pr-gate.devDeploy` — where `wp-push-dev` publishes the throwaway copy of a feature branch, and which\n * ref is the shared dev branch itself.\n *\n * WHY A NAMESPACE AT ALL, i.e. why the copy is not just the feature branch: the feature branch is the PR\n * head, and landing that PR ships whatever is on it. The moment a conflict between two devs has to be\n * resolved SOMEWHERE for the shared environment to build, that resolution needs a home that is not the PR\n * branch — otherwise \"test it in dev\" silently ships another dev's unreviewed work to production. The\n * `<branchNamespace>/<feature>` copy is that home, and it is disposable by construction.\n *\n * `devBranch` is REFUSED as a source branch (you never push the composed branch back into itself); it is\n * written by the consumer's CI only, which recomputes it from `origin/main` on every run.\n */\nexport class DevDeployConfig {\n // Literal prefix on the feature branch name: `dev-include/dean/ONE-2275`. Git refs allow slashes, and\n // nobody ever types this — the command derives it.\n branchNamespace: string;\n // The composed, CI-owned branch that actually deploys. Never a source, never pushed to by this flow.\n devBranch: string;\n\n constructor(branchNamespace: string = DEFAULT_DEV_BRANCH_NAMESPACE, devBranch: string = DEFAULT_DEV_BRANCH) {\n this.branchNamespace = branchNamespace;\n this.devBranch = devBranch;\n }\n\n /** `<branchNamespace>/<branch>` — the remote ref holding the disposable copy of `branch`. */\n copyRefFor(branch: string): string {\n return `${this.branchNamespace}/${branch}`;\n }\n\n /** The `git ls-remote --heads origin <pattern>` pattern matching every live copy. */\n copyRefGlob(): string {\n return `${this.branchNamespace}/*`;\n }\n}\n\n// webpieces-disable no-function-outside-class -- module-level config default, matches defaultGates/defaultPrGateConfig in this file\nexport function defaultDevDeployConfig(): DevDeployConfig {\n return new DevDeployConfig(DEFAULT_DEV_BRANCH_NAMESPACE, DEFAULT_DEV_BRANCH);\n}\n\nexport class PrGateConfig {\n mode: string;\n /**\n * The nx-affected build gate command, shared by the two commands that can run it.\n *\n * It is NOT finish-only any more, and the change is the point. It used to run once, in\n * wp-finish-upsert-pr, which meant reviewers were spawned against a branch nobody had built — they could\n * spend a full review on code that does not compile, or on an unresolved 3-point merge.\n *\n * Now `wp-review-upsert-pr` finalizes the merge and runs this gate BEFORE briefing any reviewer, and\n * records the passing HEAD sha in the stage receipt. `wp-finish-upsert-pr` re-runs it only when HEAD has\n * moved since — so the flow gained a gate where it mattered without paying for two full builds.\n *\n * Empty string => BuildAffected falls back to DEFAULT_BUILD_COMMAND.\n */\n buildCommand: string;\n gates: GateDefinition[];\n /**\n * REQUIRED — every repo must state its policy; there is deliberately no default, because the two\n * answers are a real policy decision and guessing it either merges when a team did not want that,\n * or silently stops landing PRs on a team that relied on it.\n *\n * AUTO — wp-finish-upsert-pr LANDS the PR: squash-merge it right away when it is mergeable, else\n * enable GitHub auto-merge so it lands when the checks pass. Both carry an explicit --subject /\n * --body-file, which is the ONLY way main's history gets the PR title plus the compact\n * risk/flags body — no repo setting can produce that. Requires allow_auto_merge on the repo.\n * NONE — wp-finish-upsert-pr only opens/updates the PR and stops; a human merges. NOTE the cost:\n * a UI merge cannot use the compact body, so main's commit falls back to the repo's\n * squash_merge_commit_title/message settings. Set squash_merge_commit_title=PR_TITLE there, or\n * commits land as the internal \"Squash merge of <branch>\" subject.\n */\n mergeMode: string;\n // This repo's review checklists, straight from the `pr-gate.checklists` ARRAY in webpieces.config.json —\n // the ONLY accepted shape (`patterns` is a path-glob dispatch table and `id` the checklist's name, so both\n // are config). Every def carries {@link reviewer}. [] = no checklists. The removed `{ doc }` manifest\n // form is a hard config error; see validateChecklistsSection.\n checklists: ChecklistDefinition[];\n /**\n * `reviewerAgentName` + `reviewerAgents`: the ONE agent type every checklist is reviewed by, and the most\n * such subagents a round may use. The same instance is bound into every {@link checklists} entry, so a\n * caller holding only a checklist still knows what to spawn. Field-with-default because the constructor\n * is at max-params; the empty name only ever survives on the no-config / mode-OFF paths, where no\n * reviewer is briefed (validatePrGateSection requires the key whenever the gate is active).\n */\n reviewer: ReviewerAgentPolicy = new ReviewerAgentPolicy('', REVIEWER_AGENTS_ONE_PER_CHECKLIST);\n // Whether wp-finish-upsert-pr publishes each reviewer's full `output` as ONE combined PR comment\n // (idempotently updated on every push). Defaults to true. Set false to keep the PR body-only.\n checklistComments: boolean;\n /**\n * Shared secret used to mint the server-verifiable gate token. `wp-finish-upsert-pr` writes\n * `HMAC(gateSalt, HEAD_sha)` as a hidden marker into the PR body (and REFUSES to mint it unless\n * every BLOCK checklist passed), so a valid token IS proof the local gate ran and passed. A CI\n * check (`wp-check-pr` + the scaffolded workflow) recomputes it from the PR head sha and this salt.\n *\n * Optional, defaults to '' — empty means \"no token minted, no CI enforcement\" (byte-identical to\n * before this field existed). This is COMMITTED, obscurity-grade: it stops unhooked teammates who\n * push + open a PR in the web UI, but is readable in-repo and therefore forgeable by a determined\n * reader. It is deliberately NOT cryptographically sound; nothing local can stop a filesystem-reading\n * agent. See RESPONSE-pr-gate-ci-enforcement / the design memo for the full tradeoff.\n */\n gateSalt: string;\n /**\n * What `wp-land-pr` (and `wp-cleanup`, which reaps the same branches) does with the LOCAL branch\n * once its PR is in main. Field-with-default rather than another positional constructor param —\n * this constructor is already at the max-params limit, and every existing `new PrGateConfig(...)`\n * call site correctly wants the default.\n */\n landPr: LandPrConfig = defaultLandPrConfig();\n /**\n * Globs whose diffs are NOT extracted into `.webpieces/pr-review/<feature>/diff/` — regenerated noise a\n * reviewer should not spend context reading (lockfiles, generated graphs). Default [].\n *\n * They are still MATCHED against checklists and still listed in the manifest, with a stub naming the\n * command that gets the real diff. Removing them from the changed-file set would read as \"this file did\n * not change\", which is a different and false claim.\n *\n * Fields-with-defaults rather than constructor params: that constructor is already at max-params, and\n * every existing `new PrGateConfig(...)` correctly wants the defaults.\n */\n reviewDiffExclude: string[] = [];\n /**\n * Repo-specific places a reviewer would otherwise hunt for, as `{label, path}` — e.g.\n * `{\"label\":\"Cloud Tasks queue names\",\"path\":\"terraform/services/\"}`. Resolved to absolute paths in each\n * generated instructions file; a configured-but-missing path is printed as missing, never dropped.\n */\n reviewContext: ReviewContextEntry[] = [];\n /**\n * Installed packages to resolve and hand reviewers by absolute directory. This is the knob aimed at a\n * measured failure: one reviewer burned three separate greps into `node_modules/@webpieces` looking for\n * a scanner the tooling could have pointed at directly.\n */\n reviewContextPackages: string[] = [];\n /**\n * Promote \"this reviewer wrote a verdict without ever opening the diff\" from a warning to a refusal.\n * Default false, and it should stay false until a repo has watched the warning for a while: the signal\n * is derived from undocumented Claude Code transcript internals, so a format change would otherwise\n * wedge every PR in the repo with no self-service way out.\n */\n requireDiffEvidence = false;\n /**\n * Where `wp-push-dev` publishes the disposable copy. Omitted ⇒ `dev-include` / `dev`, which is what\n * makes the whole flow work with NO config edit at all.\n */\n devDeploy: DevDeployConfig = defaultDevDeployConfig();\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(mode: string, buildCommand: string, gates: GateDefinition[], mergeMode: string, checklists: ChecklistDefinition[] = [], gateSalt = '', checklistComments = true) {\n this.mode = mode;\n this.buildCommand = buildCommand;\n this.gates = gates;\n this.mergeMode = mergeMode;\n this.checklists = checklists;\n this.gateSalt = gateSalt;\n this.checklistComments = checklistComments;\n }\n}\n\n// Default infra gates — path-pattern based, tuned for this monorepo. Clients override the\n// whole list via the `pr-gate.gates` array in webpieces.config.json.\nexport function defaultGates(): GateDefinition[] {\n return [\n new GateDefinition('API Changed', ['libraries/apis/**', '**/*Api.ts'], 'yellow'),\n new GateDefinition('Config Files Changed', ['**/package.json', '**/tsconfig*.json', 'nx.json', '**/*.config.*'], 'yellow'),\n new GateDefinition('Dependency Graph Changed', ['architecture/dependencies.json'], 'yellow'),\n new GateDefinition('Claude / Rules Changed', ['**/CLAUDE.md', '**/claude.*.md', '.claude/**', 'webpieces.config.json'], 'yellow'),\n ];\n}\n\nexport function defaultPrGateConfig(): PrGateConfig {\n // No default checklists — the extension point is opt-in; the default monorepo ships none.\n return new PrGateConfig('ON', '', defaultGates(), MERGE_MODE_AUTO, []);\n}\n\ninterface RawGate {\n name?: string;\n patterns?: string[];\n warningColor?: string;\n disabled?: boolean;\n}\n\ninterface RawPrGateSection {\n mode?: string;\n buildCommand?: string;\n gates?: RawGate[];\n mergeMode?: string;\n // An ARRAY, always. validateChecklistsSection rejects every other shape (including the removed { doc }).\n checklists?: RawChecklistItem[];\n reviewerAgentName?: string;\n reviewerAgents?: number;\n gateSalt?: string;\n checklistComments?: boolean;\n landPr?: RawLandPr;\n reviewDiffExclude?: string[];\n reviewContext?: RawReviewContext[];\n reviewContextPackages?: string[];\n requireDiffEvidence?: boolean;\n devDeploy?: RawDevDeploy;\n}\n\ninterface RawDevDeploy {\n branchNamespace?: string;\n devBranch?: string;\n}\n\ninterface RawLandPr {\n branchRetention?: string;\n}\n\ninterface RawReviewContext {\n label?: string;\n path?: string;\n}\n\nfunction toGate(raw: RawGate): GateDefinition {\n return new GateDefinition(raw.name ?? '', raw.patterns ?? [], raw.warningColor ?? 'yellow', raw.disabled ?? false);\n}\n\n/**\n * Build a PrGateConfig from the already-parsed top-level `pr-gate` section, falling back to defaults\n * for any field the consumer omits. Pure transform — the file read + structural validation happen in\n * loadAndValidate (load-config.ts) so every consumer goes through one validated path. Pass undefined\n * (no `pr-gate` key / no config file) to get full defaults.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed here\nexport function buildPrGateConfig(section: unknown): PrGateConfig {\n const defaults = defaultPrGateConfig();\n if (section === undefined || section === null || typeof section !== 'object') return defaults;\n\n const raw = section as RawPrGateSection;\n const mode = raw.mode ?? defaults.mode;\n const buildCommand = raw.buildCommand ?? defaults.buildCommand;\n const gates = raw.gates !== undefined ? raw.gates.map(toGate) : defaults.gates;\n // REQUIRED — validatePrGateSection rejects an omitted/unknown value, so this fallback only ever\n // applies to the no-config-file path that defaultPrGateConfig() serves.\n const mergeMode = raw.mergeMode ?? defaults.mergeMode;\n // Optional extension point — omitted ⇒ [] ⇒ no checklists computed anywhere downstream. A non-array here\n // cannot reach us: validateChecklistsSection has already failed the load.\n // Required (validatePrGateSection) / optional positive integer; absent cap ⇒ one subagent per checklist.\n const reviewer = new ReviewerAgentPolicy(\n (raw.reviewerAgentName ?? '').trim(),\n typeof raw.reviewerAgents === 'number' ? raw.reviewerAgents : REVIEWER_AGENTS_ONE_PER_CHECKLIST);\n const checklists = Array.isArray(raw.checklists)\n ? raw.checklists.map((item: RawChecklistItem): ChecklistDefinition => toChecklist(item, reviewer))\n : defaults.checklists;\n // Optional — omitted ⇒ '' ⇒ no gate token minted and CI enforcement is a no-op (back-compat).\n const gateSalt = raw.gateSalt ?? defaults.gateSalt;\n // Optional — omitted ⇒ true ⇒ reviewer output published as a PR comment.\n const checklistComments = raw.checklistComments ?? defaults.checklistComments;\n const built = new PrGateConfig(mode, buildCommand, gates, mergeMode, checklists, gateSalt, checklistComments);\n built.landPr = buildLandPrConfig(raw.landPr);\n built.reviewer = reviewer;\n // Review-context knobs. All optional and all defaulted, so a config that omits every one of them (which\n // is every consumer's config today) behaves exactly as it did before they existed.\n built.reviewDiffExclude = Array.isArray(raw.reviewDiffExclude) ? raw.reviewDiffExclude : defaults.reviewDiffExclude;\n built.reviewContextPackages = Array.isArray(raw.reviewContextPackages) ? raw.reviewContextPackages : defaults.reviewContextPackages;\n built.reviewContext = Array.isArray(raw.reviewContext)\n ? raw.reviewContext.map((e: RawReviewContext): ReviewContextEntry => new ReviewContextEntry(e.label ?? '', e.path ?? ''))\n : defaults.reviewContext;\n built.requireDiffEvidence = raw.requireDiffEvidence ?? defaults.requireDiffEvidence;\n built.devDeploy = buildDevDeployConfig(raw.devDeploy);\n return built;\n}\n\n/**\n * Build the `pr-gate.devDeploy` block. Omitted (the state of every consumer config today) ⇒ the\n * `dev-include` / `dev` defaults. An invalid value cannot reach here — validateDevDeploySection has\n * already failed the load.\n */\n// webpieces-disable no-function-outside-class -- module-level config transform, matches buildPrGateConfig above\nexport function buildDevDeployConfig(raw: RawDevDeploy | undefined): DevDeployConfig {\n const defaults = defaultDevDeployConfig();\n if (raw === undefined || raw === null || typeof raw !== 'object') return defaults;\n const namespace = typeof raw.branchNamespace === 'string' && raw.branchNamespace.trim() !== ''\n ? raw.branchNamespace.trim() : defaults.branchNamespace;\n const devBranch = typeof raw.devBranch === 'string' && raw.devBranch.trim() !== ''\n ? raw.devBranch.trim() : defaults.devBranch;\n return new DevDeployConfig(namespace, devBranch);\n}\n\n/**\n * Build the `pr-gate.landPr` block. Omitted (the current state of every consumer's config) ⇒ the\n * 'archive-tag' default, which is what makes this feature work with NO config edit at all. An invalid\n * value cannot reach here — validatePrGateSection has already failed the load.\n */\n// webpieces-disable no-function-outside-class -- module-level config transform, matches buildPrGateConfig above\nexport function buildLandPrConfig(raw: RawLandPr | undefined): LandPrConfig {\n const defaults = defaultLandPrConfig();\n if (raw === undefined || raw === null || typeof raw !== 'object') return defaults;\n const retention = raw.branchRetention;\n if (typeof retention !== 'string' || !BRANCH_RETENTIONS.includes(retention)) return defaults;\n return new LandPrConfig(retention);\n}\n\n"]}
@@ -9,8 +9,9 @@
9
9
  * stays allowed; this is not a general ban on documenting your config.
10
10
  */
11
11
  export declare function validateNoGateSaltRationale(s: Record<string, unknown>): string[];
12
+ export declare const CHECKLIST_ENTRY_LABEL = "[pr-gate.checklists]";
12
13
  /**
13
- * The `checklists` section of a pr-gate config: an ARRAY of { subagent, doc?, patterns? }, and nothing else.
14
+ * The `checklists` section of a pr-gate config: an ARRAY of { id, doc, patterns?, required }, and nothing else.
14
15
  *
15
16
  * The previous `{ "doc": "..." }` shape — which hid the same array in a `<!-- webpieces:checklists -->` HTML
16
17
  * comment inside a markdown doc — is REMOVED, not deprecated. It is rejected with the exact edit to make.
@@ -19,10 +20,23 @@ export declare function validateNoGateSaltRationale(s: Record<string, unknown>):
19
20
  * config edit that the coding agent reading this error applies in one pass. A hard failure naming the fix is
20
21
  * cheaper than permanent duality.
21
22
  *
22
- * Exported so the isolated validate-checklist-docs target reuses it. `repoRoot` (when known) lets the doc +
23
- * reviewer-agent existence checks run.
23
+ * Exported so the isolated validate-checklist-docs target reuses it. `repoRoot` (when known) lets the doc
24
+ * existence checks run.
24
25
  */
25
26
  export declare function validateChecklistsSection(value: unknown, repoRoot?: string): string[];
27
+ export declare const REVIEWER_AGENT_NAME_KEY = "reviewerAgentName";
28
+ export declare const REVIEWER_AGENTS_KEY = "reviewerAgents";
29
+ /**
30
+ * `reviewerAgentName` (REQUIRED while the gate is active) and `reviewerAgents` (optional positive integer).
31
+ *
32
+ * `reviewerAgentName` has no default for the same reason `mergeMode` has none: it names a file the repo
33
+ * commits, and a silent default would brief every reviewer with an agent type the repo may not have. The
34
+ * error hands the reader the exact line, which an agent applies in one pass.
35
+ *
36
+ * `reviewerAgents` absent is the documented "one subagent per checklist" contract — the count every repo had
37
+ * before the key existed, so it widens nothing; present, it CAPS the subagents one round may use.
38
+ */
39
+ export declare function validateReviewerAgentKeys(s: Record<string, unknown>, repoRoot?: string): string[];
26
40
  export declare function validateLandPrSection(value: unknown): string[];
27
41
  /**
28
42
  * Validate the optional `pr-gate.devDeploy` block — where `wp-push-dev` publishes the disposable copy of
@@ -1,12 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.REVIEWER_AGENTS_KEY = exports.REVIEWER_AGENT_NAME_KEY = exports.CHECKLIST_ENTRY_LABEL = void 0;
3
4
  exports.validateNoGateSaltRationale = validateNoGateSaltRationale;
4
5
  exports.validateChecklistsSection = validateChecklistsSection;
6
+ exports.validateReviewerAgentKeys = validateReviewerAgentKeys;
5
7
  exports.validateLandPrSection = validateLandPrSection;
6
8
  exports.validateDevDeploySection = validateDevDeploySection;
7
9
  const branch_archiver_1 = require("./branch-archiver");
8
10
  const checklist_validator_1 = require("./checklist-validator");
9
11
  const checklist_config_1 = require("./checklist-config");
12
+ const constants_1 = require("./constants");
13
+ const retired_config_keys_1 = require("./retired-config-keys");
10
14
  // The two `pr-gate` sub-sections whose validation is bulky enough to own a file: the review `checklists`
11
15
  // and the one rationale key that is rejected outright. Split out of validate-config.ts only for size;
12
16
  // loadAndValidate still reaches both through validatePrGateSection.
@@ -37,19 +41,22 @@ function validateNoGateSaltRationale(s) {
37
41
  ];
38
42
  }
39
43
  const CHECKLIST_EXAMPLE = ('Example:\n' +
44
+ ' "reviewerAgentName": "webpieces-reviewer",\n' +
40
45
  ' "checklists": [\n' +
41
- ' { "subagent": "db-migration-reviewer",\n' +
46
+ ' { "id": "db-migrations",\n' +
42
47
  ' "doc": ".claude/review/db-migrations.md",\n' +
43
48
  ' "patterns": ["**/migrations/**", "**/*.sql"],\n' +
44
49
  ' "required": true }\n' +
45
50
  ' ]\n' +
46
- ' Each entry needs its OWN reviewer subagent (a .claude/agents/<subagent>.md) that is how independent\n' +
47
- ' review is enforced. "doc" is REPO-relative. Omit "patterns" (or use []) to run on every PR.\n' +
48
- ' "required" is MANDATORY on every entry: true blocks the PR until the reviewer passes; false makes it\n' +
49
- ' an OPTIONAL review the human is offered and may decline (but if they DO run it, a red verdict still\n' +
50
- ' blocks).');
51
+ ' "id" names the checklist and keys its review-<id>.json. Every checklist is reviewed by the agent\n' +
52
+ ' "reviewerAgentName" names, against its own "doc" (REQUIRED, REPO-relative). Omit "patterns" (or use\n' +
53
+ ' []) to run on every PR. "required" is MANDATORY on every entry: true blocks the PR until the\n' +
54
+ ' reviewer passes; false makes it an OPTIONAL review the human is offered and may decline (but if they\n' +
55
+ ' DO run it, a red verdict still blocks).');
56
+ // The label every RETIRED checklist-entry key is filed under in RETIRED_CONFIG_KEYS.
57
+ exports.CHECKLIST_ENTRY_LABEL = '[pr-gate.checklists]';
51
58
  /**
52
- * The `checklists` section of a pr-gate config: an ARRAY of { subagent, doc?, patterns? }, and nothing else.
59
+ * The `checklists` section of a pr-gate config: an ARRAY of { id, doc, patterns?, required }, and nothing else.
53
60
  *
54
61
  * The previous `{ "doc": "..." }` shape — which hid the same array in a `<!-- webpieces:checklists -->` HTML
55
62
  * comment inside a markdown doc — is REMOVED, not deprecated. It is rejected with the exact edit to make.
@@ -58,8 +65,8 @@ const CHECKLIST_EXAMPLE = ('Example:\n' +
58
65
  * config edit that the coding agent reading this error applies in one pass. A hard failure naming the fix is
59
66
  * cheaper than permanent duality.
60
67
  *
61
- * Exported so the isolated validate-checklist-docs target reuses it. `repoRoot` (when known) lets the doc +
62
- * reviewer-agent existence checks run.
68
+ * Exported so the isolated validate-checklist-docs target reuses it. `repoRoot` (when known) lets the doc
69
+ * existence checks run.
63
70
  */
64
71
  // webpieces-disable no-any-unknown -- `value` is opaque consumer JSON until narrowed below
65
72
  // webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file
@@ -68,7 +75,7 @@ function validateChecklistsSection(value, repoRoot) {
68
75
  return validateChecklistArray(value, repoRoot);
69
76
  if (typeof value === 'object' && value !== null && 'doc' in value)
70
77
  return [legacyManifestError(value)];
71
- return [`[pr-gate] "checklists" must be an ARRAY of { "subagent", "doc"?, "patterns"? }. ${CHECKLIST_EXAMPLE}`];
78
+ return [`[pr-gate] "checklists" must be an ARRAY of { "id", "doc", "patterns"?, "required" }. ${CHECKLIST_EXAMPLE}`];
72
79
  }
73
80
  /**
74
81
  * The migration message for the removed `{ doc }` manifest shape. It names the doc the consumer pointed at,
@@ -92,9 +99,9 @@ function legacyManifestError(value) {
92
99
  /**
93
100
  * `required` is MANDATORY on every checklist entry — omitting it is an error, never a default.
94
101
  *
95
- * The error names the entry's own subagent, because that is what the consumer recognizes in a
96
- * twelve-entry array; `checklists[7]` alone means counting braces. It also states BOTH edits, because the
97
- * whole point of the key is that the answer differs per checklist and only the consumer knows which.
102
+ * The error names the entry's own id, because that is what the consumer recognizes in a twelve-entry
103
+ * array; `checklists[7]` alone means counting braces. It also states BOTH edits, because the whole point of
104
+ * the key is that the answer differs per checklist and only the consumer knows which.
98
105
  *
99
106
  * Why a hard rejection instead of `?? true`: an accepted shape is never migrated. Defaulting to true
100
107
  * silently keeps the all-blocking behavior this key exists to relieve, and every consumer that would have
@@ -105,7 +112,7 @@ function legacyManifestError(value) {
105
112
  // webpieces-disable no-any-unknown -- one opaque checklist entry, narrowed by the typeof guards here
106
113
  // webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file
107
114
  function requiredKeyErrors(e, i) {
108
- const name = typeof e['subagent'] === 'string' && e['subagent'].trim() !== '' ? ` ("${e['subagent']}")` : '';
115
+ const name = typeof e['id'] === 'string' && e['id'].trim() !== '' ? ` ("${e['id']}")` : '';
109
116
  if (e['required'] === undefined) {
110
117
  return [
111
118
  `[pr-gate] checklists[${i}]${name} is missing "required". Every checklist must state one — there is\n` +
@@ -122,9 +129,28 @@ function requiredKeyErrors(e, i) {
122
129
  }
123
130
  return [];
124
131
  }
125
- // Structurally check each entry HERE — a bad `patterns` or a non-object entry is a config-file typo and
126
- // deserves a `checklists[i]` message then hand the narrowed defs to ChecklistValidator for the checks only
127
- // the filesystem can answer (the guidance doc exists, the reviewer agent exists).
132
+ // Structurally check ONE entry — a bad `patterns` or a non-string `doc` is a config-file typo and deserves a
133
+ // `checklists[i]` message. A RETIRED key (the per-entry `subagent`) is reported with its migration edit.
134
+ // webpieces-disable no-any-unknown -- one opaque checklist entry, narrowed per-field below
135
+ // webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file
136
+ function checklistEntryErrors(e, i) {
137
+ const errors = (0, retired_config_keys_1.retiredKeyErrorsIn)(e, exports.CHECKLIST_ENTRY_LABEL).map((m) => `checklists[${i}] ${m}`);
138
+ if (e['id'] !== undefined && typeof e['id'] !== 'string') {
139
+ errors.push(`[pr-gate] checklists[${i}].id must be a string — the checklist's name; it keys review-<id>.json.`);
140
+ }
141
+ if (e['doc'] !== undefined && typeof e['doc'] !== 'string') {
142
+ errors.push(`[pr-gate] checklists[${i}].doc must be a string — the REPO-relative path to the checklist's guidance doc.`);
143
+ }
144
+ // webpieces-disable no-any-unknown -- opaque array element, narrowed by the typeof guard
145
+ if (e['patterns'] !== undefined && !(Array.isArray(e['patterns']) && e['patterns'].every((p) => typeof p === 'string'))) {
146
+ errors.push(`[pr-gate] checklists[${i}].patterns must be a string[] of path globs (omit or [] to run on every PR).`);
147
+ }
148
+ errors.push(...requiredKeyErrors(e, i));
149
+ return errors;
150
+ }
151
+ // Structurally check each entry, then hand the narrowed defs to ChecklistValidator for the id rules and the
152
+ // checks only the filesystem can answer (the guidance doc exists). The reviewer agent itself is repo-wide
153
+ // and is validated once, beside `reviewerAgentName`, by validateReviewerAgentKeys.
128
154
  // webpieces-disable no-any-unknown -- opaque consumer JSON entries, narrowed per-field below
129
155
  // webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file
130
156
  function validateChecklistArray(value, repoRoot) {
@@ -133,26 +159,61 @@ function validateChecklistArray(value, repoRoot) {
133
159
  // webpieces-disable no-any-unknown -- each array entry is opaque consumer JSON, narrowed field-by-field below
134
160
  value.forEach((entry, i) => {
135
161
  if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
136
- errors.push(`[pr-gate] checklists[${i}] must be an object { "subagent", "doc"?, "patterns"? }.`);
162
+ errors.push(`[pr-gate] checklists[${i}] must be an object { "id", "doc", "patterns"?, "required" }.`);
137
163
  return;
138
164
  }
139
165
  // webpieces-disable no-any-unknown -- narrowing one opaque checklist entry
140
166
  const e = entry;
141
- if (e['doc'] !== undefined && typeof e['doc'] !== 'string') {
142
- errors.push(`[pr-gate] checklists[${i}].doc must be a string a REPO-relative path to the reviewer's guidance doc (omit it and the reviewer just reads the diff).`);
143
- }
144
- // webpieces-disable no-any-unknown -- opaque array element, narrowed by the typeof guard
145
- if (e['patterns'] !== undefined && !(Array.isArray(e['patterns']) && e['patterns'].every((p) => typeof p === 'string'))) {
146
- errors.push(`[pr-gate] checklists[${i}].patterns must be a string[] of path globs (omit or [] to run on every PR).`);
147
- }
148
- errors.push(...requiredKeyErrors(e, i));
149
- items.push(e);
167
+ errors.push(...checklistEntryErrors(e, i));
168
+ // Only the fields that passed their type check reach toChecklist the wrong-typed ones already
169
+ // have their own error above, and must not crash the id/doc checks below.
170
+ items.push({
171
+ id: typeof e['id'] === 'string' ? e['id'] : undefined,
172
+ doc: typeof e['doc'] === 'string' ? e['doc'] : undefined,
173
+ required: e['required'] === true,
174
+ });
150
175
  });
151
- if (repoRoot === undefined)
152
- return errors;
153
- const defs = items.map((item) => (0, checklist_config_1.toChecklist)(item));
176
+ // The reviewer policy plays no part in these checks (ids and docs), so a placeholder is bound here.
177
+ const placeholder = new checklist_config_1.ReviewerAgentPolicy('', checklist_config_1.REVIEWER_AGENTS_ONE_PER_CHECKLIST);
178
+ const defs = items.map((item) => (0, checklist_config_1.toChecklist)(item, placeholder));
154
179
  return [...errors, ...new checklist_validator_1.ChecklistValidator().validate(repoRoot, defs)];
155
180
  }
181
+ exports.REVIEWER_AGENT_NAME_KEY = 'reviewerAgentName';
182
+ exports.REVIEWER_AGENTS_KEY = 'reviewerAgents';
183
+ /**
184
+ * `reviewerAgentName` (REQUIRED while the gate is active) and `reviewerAgents` (optional positive integer).
185
+ *
186
+ * `reviewerAgentName` has no default for the same reason `mergeMode` has none: it names a file the repo
187
+ * commits, and a silent default would brief every reviewer with an agent type the repo may not have. The
188
+ * error hands the reader the exact line, which an agent applies in one pass.
189
+ *
190
+ * `reviewerAgents` absent is the documented "one subagent per checklist" contract — the count every repo had
191
+ * before the key existed, so it widens nothing; present, it CAPS the subagents one round may use.
192
+ */
193
+ // webpieces-disable no-any-unknown -- the already-narrowed opaque pr-gate section; two keys are read
194
+ // webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file
195
+ function validateReviewerAgentKeys(s, repoRoot) {
196
+ const errors = [];
197
+ const name = s[exports.REVIEWER_AGENT_NAME_KEY];
198
+ const named = typeof name === 'string' && name.trim() !== '';
199
+ if (!named) {
200
+ errors.push(`[pr-gate] Missing required field "${exports.REVIEWER_AGENT_NAME_KEY}" — the agent type every reviewer subagent is ` +
201
+ `spawned as. Add this line to commands.pr-gate in webpieces.config.json:\n` +
202
+ ` "${exports.REVIEWER_AGENT_NAME_KEY}": "${checklist_config_1.DEFAULT_REVIEWER_AGENT_NAME}",\n` +
203
+ ` ${checklist_config_1.DEFAULT_REVIEWER_AGENT_NAME} is the generic reviewer webpieces ships as .claude/agents/${checklist_config_1.DEFAULT_REVIEWER_AGENT_NAME}.md ` +
204
+ `(\`${constants_1.UPGRADE_SHIM_COMMAND}\` writes it). Point the key at your own agent to use that one instead.`);
205
+ }
206
+ const max = s[exports.REVIEWER_AGENTS_KEY];
207
+ if (exports.REVIEWER_AGENTS_KEY in s && !(typeof max === 'number' && Number.isInteger(max) && max >= 1)) {
208
+ errors.push(`[pr-gate] "${exports.REVIEWER_AGENTS_KEY}" = ${JSON.stringify(max)} is not valid — it must be a positive integer: the MOST ` +
209
+ `reviewer subagents one review round may use, with the checklists grouped across them (1 = a single ` +
210
+ `subagent reviews every checklist). Delete the key to keep one subagent per checklist.`);
211
+ }
212
+ if (repoRoot !== undefined && named) {
213
+ errors.push(...new checklist_validator_1.ChecklistValidator().validateReviewerAgent(repoRoot, new checklist_config_1.ReviewerAgentPolicy(name.trim(), checklist_config_1.REVIEWER_AGENTS_ONE_PER_CHECKLIST)));
214
+ }
215
+ return errors;
216
+ }
156
217
  // The `landPr` block: what happens to the LOCAL branch once its PR is in main. Optional — omitted
157
218
  // means "archive-tag", which is deliberately the DEFAULT so a consumer gets the branch-accumulation
158
219
  // fix without editing config at all. `branchRetentionWhy` (and any other `*Why` sibling) is free-form
@@ -1 +1 @@
1
- {"version":3,"file":"pr-gate-section-validators.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/pr-gate-section-validators.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BRANCH_RETENTIONS,\n BRANCH_RETENTION_ARCHIVE_TAG,\n BRANCH_RETENTION_DELETE,\n BRANCH_RETENTION_KEEP,\n} from './branch-archiver';\nimport { ChecklistValidator } from './checklist-validator';\nimport { ChecklistDefinition, RawChecklistItem, toChecklist } from './checklist-config';\n\n// The two `pr-gate` sub-sections whose validation is bulky enough to own a file: the review `checklists`\n// and the one rationale key that is rejected outright. Split out of validate-config.ts only for size;\n// loadAndValidate still reaches both through validatePrGateSection.\n\n// The `*Why` convention (buildCommandWhy, mergeModeWhy, gatesWhy…) is free-form rationale a consumer keeps\n// beside a field, and pr-gate tolerates any of them — EXCEPT this one. See validateNoGateSaltRationale.\nconst GATE_SALT_WHY = 'gateSaltWhy';\n\n/**\n * Reject `gateSaltWhy` outright, and say why, so the next validate on upgrade FORCES its removal.\n *\n * webpieces.config.json is one of the first files a coding agent reads. A rationale note next to `gateSalt`\n * necessarily explains what the token protects, that the salt is committed, and therefore how to forge it —\n * i.e. it is a bypass how-to, sitting in the most-read file in the repo, defeating the only thing an\n * obscurity-grade mechanism has going for it. The rationale belongs in the webpieces source (pr-gate-config.ts\n * documents it in full for humans reading the tooling), never in consumer config. Every other `*Why` key\n * stays allowed; this is not a general ban on documenting your config.\n */\n// webpieces-disable no-any-unknown -- the already-narrowed opaque pr-gate section; only key PRESENCE is read\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateNoGateSaltRationale(s: Record<string, unknown>): string[] {\n if (!(GATE_SALT_WHY in s)) return [];\n return [\n `[pr-gate] DELETE the \"${GATE_SALT_WHY}\" key from webpieces.config.json. It is a rationale note next to ` +\n `\"gateSalt\", which means it spells out what the gate token protects and that the salt is committed — a ` +\n `bypass how-to in the file a coding agent reads first. The mechanism is obscurity-grade; documenting it ` +\n `here removes the obscurity. Nothing else needs changing: the reasoning is already documented in the ` +\n `webpieces source (PrGateConfig.gateSalt) for humans reading the tooling.`,\n ];\n}\n\nconst CHECKLIST_EXAMPLE = (\n 'Example:\\n' +\n ' \"checklists\": [\\n' +\n ' { \"subagent\": \"db-migration-reviewer\",\\n' +\n ' \"doc\": \".claude/review/db-migrations.md\",\\n' +\n ' \"patterns\": [\"**/migrations/**\", \"**/*.sql\"],\\n' +\n ' \"required\": true }\\n' +\n ' ]\\n' +\n ' Each entry needs its OWN reviewer subagent (a .claude/agents/<subagent>.md) — that is how independent\\n' +\n ' review is enforced. \"doc\" is REPO-relative. Omit \"patterns\" (or use []) to run on every PR.\\n' +\n ' \"required\" is MANDATORY on every entry: true blocks the PR until the reviewer passes; false makes it\\n' +\n ' an OPTIONAL review the human is offered and may decline (but if they DO run it, a red verdict still\\n' +\n ' blocks).'\n);\n\n/**\n * The `checklists` section of a pr-gate config: an ARRAY of { subagent, doc?, patterns? }, and nothing else.\n *\n * The previous `{ \"doc\": \"...\" }` shape — which hid the same array in a `<!-- webpieces:checklists -->` HTML\n * comment inside a markdown doc — is REMOVED, not deprecated. It is rejected with the exact edit to make.\n * There is deliberately no back-compat branch: two accepted shapes means two code paths, two doc-resolution\n * rules and two sets of error messages to keep honest forever, while the migration itself is a mechanical\n * config edit that the coding agent reading this error applies in one pass. A hard failure naming the fix is\n * cheaper than permanent duality.\n *\n * Exported so the isolated validate-checklist-docs target reuses it. `repoRoot` (when known) lets the doc +\n * reviewer-agent existence checks run.\n */\n// webpieces-disable no-any-unknown -- `value` is opaque consumer JSON until narrowed below\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateChecklistsSection(value: unknown, repoRoot?: string): string[] {\n if (Array.isArray(value)) return validateChecklistArray(value, repoRoot);\n if (typeof value === 'object' && value !== null && 'doc' in value) return [legacyManifestError(value)];\n return [`[pr-gate] \"checklists\" must be an ARRAY of { \"subagent\", \"doc\"?, \"patterns\"? }. ${CHECKLIST_EXAMPLE}`];\n}\n\n/**\n * The migration message for the removed `{ doc }` manifest shape. It names the doc the consumer pointed at,\n * because that is the file holding the array they must move, and spells out the one non-obvious part of the\n * move: entry `doc` paths used to resolve relative to that manifest doc and are now REPO-relative.\n */\n// webpieces-disable no-any-unknown -- narrowing the opaque checklists section to read the old `doc` key\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction legacyManifestError(value: object): string {\n const doc = (value as Record<string, unknown>)['doc'];\n const docRel = typeof doc === 'string' && doc.trim() !== '' ? doc : '<your review index doc>';\n return (\n `[pr-gate] \"checklists\" is the REMOVED { \"doc\": \"${docRel}\" } shape. The checklist array no longer lives in\\n` +\n ` an HTML comment inside a markdown doc — put it directly in webpieces.config.json:\\n` +\n ` 1. Open \"${docRel}\" and copy the JSON array out of its <!-- webpieces:checklists [...] --> comment.\\n` +\n ` 2. Replace \"checklists\": { \"doc\": \"${docRel}\" } with \"checklists\": <that array>.\\n` +\n ` 3. Rewrite each entry's \"doc\" to be REPO-relative — they used to resolve relative to\\n` +\n ` \"${docRel}\", so a bare \"db-migrations.md\" becomes e.g. \".claude/review/db-migrations.md\".\\n` +\n ` 4. Delete the <!-- webpieces:checklists ... --> comment from \"${docRel}\"; keep the prose.\\n` +\n ` ${CHECKLIST_EXAMPLE}`\n );\n}\n\n/**\n * `required` is MANDATORY on every checklist entry — omitting it is an error, never a default.\n *\n * The error names the entry's own subagent, because that is what the consumer recognizes in a\n * twelve-entry array; `checklists[7]` alone means counting braces. It also states BOTH edits, because the\n * whole point of the key is that the answer differs per checklist and only the consumer knows which.\n *\n * Why a hard rejection instead of `?? true`: an accepted shape is never migrated. Defaulting to true\n * silently keeps the all-blocking behavior this key exists to relieve, and every consumer that would have\n * benefited stays on the old behavior forever without ever being told the dial exists. Defaulting to false\n * is worse — it would silently DOWNGRADE a live review gate on upgrade. Per CLAUDE.md the reader of this\n * message is a coding agent, so the migration is one mechanical pass.\n */\n// webpieces-disable no-any-unknown -- one opaque checklist entry, narrowed by the typeof guards here\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction requiredKeyErrors(e: Record<string, unknown>, i: number): string[] {\n const name = typeof e['subagent'] === 'string' && e['subagent'].trim() !== '' ? ` (\"${e['subagent']}\")` : '';\n if (e['required'] === undefined) {\n return [\n `[pr-gate] checklists[${i}]${name} is missing \"required\". Every checklist must state one — there is\\n` +\n ` no default, in either direction.\\n` +\n ` \"required\": true → BLOCKING. wp-finish-upsert-pr refuses the PR until this reviewer passes.\\n` +\n ` This is what every checklist did before this key existed.\\n` +\n ` \"required\": false → OPTIONAL. When it matches the diff, wp-review-upsert-pr offers it and the\\n` +\n ` human may decline it. If they DO run it, a red verdict still blocks.\\n` +\n ` ${CHECKLIST_EXAMPLE}`,\n ];\n }\n if (typeof e['required'] !== 'boolean') {\n return [`[pr-gate] checklists[${i}]${name}.required must be a boolean (true = blocking, false = optional) — not a string or number.`];\n }\n return [];\n}\n\n// Structurally check each entry HERE — a bad `patterns` or a non-object entry is a config-file typo and\n// deserves a `checklists[i]` message — then hand the narrowed defs to ChecklistValidator for the checks only\n// the filesystem can answer (the guidance doc exists, the reviewer agent exists).\n// webpieces-disable no-any-unknown -- opaque consumer JSON entries, narrowed per-field below\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction validateChecklistArray(value: readonly unknown[], repoRoot?: string): string[] {\n const errors: string[] = [];\n const items: RawChecklistItem[] = [];\n // webpieces-disable no-any-unknown -- each array entry is opaque consumer JSON, narrowed field-by-field below\n value.forEach((entry: unknown, i: number): void => {\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n errors.push(`[pr-gate] checklists[${i}] must be an object { \"subagent\", \"doc\"?, \"patterns\"? }.`);\n return;\n }\n // webpieces-disable no-any-unknown -- narrowing one opaque checklist entry\n const e = entry as Record<string, unknown>;\n if (e['doc'] !== undefined && typeof e['doc'] !== 'string') {\n errors.push(`[pr-gate] checklists[${i}].doc must be a string — a REPO-relative path to the reviewer's guidance doc (omit it and the reviewer just reads the diff).`);\n }\n // webpieces-disable no-any-unknown -- opaque array element, narrowed by the typeof guard\n if (e['patterns'] !== undefined && !(Array.isArray(e['patterns']) && e['patterns'].every((p: unknown): boolean => typeof p === 'string'))) {\n errors.push(`[pr-gate] checklists[${i}].patterns must be a string[] of path globs (omit or [] to run on every PR).`);\n }\n errors.push(...requiredKeyErrors(e, i));\n items.push(e as RawChecklistItem);\n });\n if (repoRoot === undefined) return errors;\n const defs = items.map((item: RawChecklistItem): ChecklistDefinition => toChecklist(item));\n return [...errors, ...new ChecklistValidator().validate(repoRoot, defs)];\n}\n\n// The `landPr` block: what happens to the LOCAL branch once its PR is in main. Optional — omitted\n// means \"archive-tag\", which is deliberately the DEFAULT so a consumer gets the branch-accumulation\n// fix without editing config at all. `branchRetentionWhy` (and any other `*Why` sibling) is free-form\n// rationale prose and is tolerated, per the repo's convention for documenting comment-less JSON.\n// webpieces-disable no-any-unknown -- `value` is the opaque consumer `landPr` value until narrowed here\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateLandPrSection(value: unknown): string[] {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return [`[pr-gate] \"landPr\" must be an object, e.g. { \"branchRetention\": \"${BRANCH_RETENTION_ARCHIVE_TAG}\" }.`];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque landPr object from consumer JSON\n const s = value as Record<string, unknown>;\n if (!('branchRetention' in s)) return [];\n const retention = s['branchRetention'];\n if (typeof retention === 'string' && BRANCH_RETENTIONS.includes(retention)) return [];\n return [\n `[pr-gate] \"landPr.branchRetention\" = \"${String(retention)}\" is not valid. ` +\n `Must be one of: ${BRANCH_RETENTIONS.join(', ')}.\\n` +\n ` \"${BRANCH_RETENTION_ARCHIVE_TAG}\" — (default) tag the branch tip as archive/<date>/<branch>, THEN delete it. The\\n` +\n ` history stays byte-identical and restorable, but the branch stops counting\\n` +\n ` toward the branch cap and cannot be committed onto by accident.\\n` +\n ` \"${BRANCH_RETENTION_DELETE}\" — delete outright; recoverable only from the reflog, which expires.\\n` +\n ` \"${BRANCH_RETENTION_KEEP}\" — do not delete. Branches then accumulate until branch-creation-guard trips.`,\n ];\n}\n\n// A git ref COMPONENT this flow is willing to build a ref name out of. Deliberately much narrower than\n// git's own check-ref-format: these two values are concatenated into a ref that a command then\n// force-pushes, so anything that could be read as a flag, a path escape, or a glob is rejected outright\n// rather than trusted to `git push` argument order.\nconst REF_COMPONENT = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;\n\n/**\n * Validate the optional `pr-gate.devDeploy` block — where `wp-push-dev` publishes the disposable copy of\n * a feature branch, and which ref is the shared dev branch itself. Absent ⇒ `dev-include` / `dev`.\n *\n * `devBranch` must NOT contain a slash and must not sit inside `branchNamespace`: the whole point of the\n * namespace is that the composed dev branch is written by CI and the copies are written by developers, so\n * a config where one contains the other makes `--list` enumerate the deploy branch and makes the\n * \"refused as a source branch\" check ambiguous.\n */\n// webpieces-disable no-any-unknown -- `value` is the opaque consumer devDeploy value until narrowed here\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateDevDeploySection(value: unknown): string[] {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return ['[pr-gate] \"devDeploy\" must be an object, e.g. { \"branchNamespace\": \"dev-include\", \"devBranch\": \"dev\" }.'];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque devDeploy object from consumer JSON\n const s = value as Record<string, unknown>;\n const errors: string[] = [];\n errors.push(...validateRefComponent(s, 'branchNamespace', 'dev-include'));\n errors.push(...validateRefComponent(s, 'devBranch', 'dev'));\n if (errors.length > 0) return errors;\n\n const namespace = typeof s['branchNamespace'] === 'string' ? s['branchNamespace'].trim() : 'dev-include';\n const devBranch = typeof s['devBranch'] === 'string' ? s['devBranch'].trim() : 'dev';\n if (devBranch.includes('/')) {\n errors.push(\n `[pr-gate] \"devDeploy.devBranch\" = \"${devBranch}\" must be a single ref name with no \"/\" — it is the ` +\n `branch your CI composes and deploys, not a namespace.`);\n }\n if (devBranch === namespace || devBranch.startsWith(`${namespace}/`) || namespace.startsWith(`${devBranch}/`)) {\n errors.push(\n `[pr-gate] \"devDeploy.devBranch\" (\"${devBranch}\") and \"devDeploy.branchNamespace\" (\"${namespace}\") must not ` +\n `contain one another. The namespace holds the DISPOSABLE per-developer copies (written by wp-push-dev); ` +\n `devBranch is the COMPOSED branch your CI rebuilds from origin/main. Overlapping them makes wp-push-dev ` +\n `--list enumerate the deploy branch and makes \"refused as a source branch\" ambiguous.`);\n }\n return errors;\n}\n\n// One `devDeploy` string field: present ⇒ must be a non-empty, ref-safe string. Absent ⇒ the default.\n// webpieces-disable no-any-unknown -- the already-narrowed opaque devDeploy object\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction validateRefComponent(s: Record<string, unknown>, key: string, example: string): string[] {\n if (!(key in s)) return [];\n const raw = s[key];\n if (typeof raw !== 'string' || raw.trim() === '') {\n return [`[pr-gate] \"devDeploy.${key}\" must be a non-empty string, e.g. \"${example}\". Omit the key for the \"${example}\" default.`];\n }\n if (!REF_COMPONENT.test(raw.trim())) {\n return [\n `[pr-gate] \"devDeploy.${key}\" = \"${raw}\" is not a usable git ref name. Use letters, digits, \".\", \"_\", ` +\n `\"-\" and \"/\" only, starting with a letter or digit (e.g. \"${example}\").`,\n ];\n }\n return [];\n}\n"]}
1
+ {"version":3,"file":"pr-gate-section-validators.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/pr-gate-section-validators.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BRANCH_RETENTIONS,\n BRANCH_RETENTION_ARCHIVE_TAG,\n BRANCH_RETENTION_DELETE,\n BRANCH_RETENTION_KEEP,\n} from './branch-archiver';\nimport { ChecklistValidator } from './checklist-validator';\nimport {\n ChecklistDefinition, DEFAULT_REVIEWER_AGENT_NAME, RawChecklistItem, REVIEWER_AGENTS_ONE_PER_CHECKLIST,\n ReviewerAgentPolicy, toChecklist,\n} from './checklist-config';\nimport { UPGRADE_SHIM_COMMAND } from './constants';\nimport { retiredKeyErrorsIn } from './retired-config-keys';\n\n// The two `pr-gate` sub-sections whose validation is bulky enough to own a file: the review `checklists`\n// and the one rationale key that is rejected outright. Split out of validate-config.ts only for size;\n// loadAndValidate still reaches both through validatePrGateSection.\n\n// The `*Why` convention (buildCommandWhy, mergeModeWhy, gatesWhy…) is free-form rationale a consumer keeps\n// beside a field, and pr-gate tolerates any of them — EXCEPT this one. See validateNoGateSaltRationale.\nconst GATE_SALT_WHY = 'gateSaltWhy';\n\n/**\n * Reject `gateSaltWhy` outright, and say why, so the next validate on upgrade FORCES its removal.\n *\n * webpieces.config.json is one of the first files a coding agent reads. A rationale note next to `gateSalt`\n * necessarily explains what the token protects, that the salt is committed, and therefore how to forge it —\n * i.e. it is a bypass how-to, sitting in the most-read file in the repo, defeating the only thing an\n * obscurity-grade mechanism has going for it. The rationale belongs in the webpieces source (pr-gate-config.ts\n * documents it in full for humans reading the tooling), never in consumer config. Every other `*Why` key\n * stays allowed; this is not a general ban on documenting your config.\n */\n// webpieces-disable no-any-unknown -- the already-narrowed opaque pr-gate section; only key PRESENCE is read\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateNoGateSaltRationale(s: Record<string, unknown>): string[] {\n if (!(GATE_SALT_WHY in s)) return [];\n return [\n `[pr-gate] DELETE the \"${GATE_SALT_WHY}\" key from webpieces.config.json. It is a rationale note next to ` +\n `\"gateSalt\", which means it spells out what the gate token protects and that the salt is committed — a ` +\n `bypass how-to in the file a coding agent reads first. The mechanism is obscurity-grade; documenting it ` +\n `here removes the obscurity. Nothing else needs changing: the reasoning is already documented in the ` +\n `webpieces source (PrGateConfig.gateSalt) for humans reading the tooling.`,\n ];\n}\n\nconst CHECKLIST_EXAMPLE = (\n 'Example:\\n' +\n ' \"reviewerAgentName\": \"webpieces-reviewer\",\\n' +\n ' \"checklists\": [\\n' +\n ' { \"id\": \"db-migrations\",\\n' +\n ' \"doc\": \".claude/review/db-migrations.md\",\\n' +\n ' \"patterns\": [\"**/migrations/**\", \"**/*.sql\"],\\n' +\n ' \"required\": true }\\n' +\n ' ]\\n' +\n ' \"id\" names the checklist and keys its review-<id>.json. Every checklist is reviewed by the agent\\n' +\n ' \"reviewerAgentName\" names, against its own \"doc\" (REQUIRED, REPO-relative). Omit \"patterns\" (or use\\n' +\n ' []) to run on every PR. \"required\" is MANDATORY on every entry: true blocks the PR until the\\n' +\n ' reviewer passes; false makes it an OPTIONAL review the human is offered and may decline (but if they\\n' +\n ' DO run it, a red verdict still blocks).'\n);\n\n// The label every RETIRED checklist-entry key is filed under in RETIRED_CONFIG_KEYS.\nexport const CHECKLIST_ENTRY_LABEL = '[pr-gate.checklists]';\n\n/**\n * The `checklists` section of a pr-gate config: an ARRAY of { id, doc, patterns?, required }, and nothing else.\n *\n * The previous `{ \"doc\": \"...\" }` shape — which hid the same array in a `<!-- webpieces:checklists -->` HTML\n * comment inside a markdown doc — is REMOVED, not deprecated. It is rejected with the exact edit to make.\n * There is deliberately no back-compat branch: two accepted shapes means two code paths, two doc-resolution\n * rules and two sets of error messages to keep honest forever, while the migration itself is a mechanical\n * config edit that the coding agent reading this error applies in one pass. A hard failure naming the fix is\n * cheaper than permanent duality.\n *\n * Exported so the isolated validate-checklist-docs target reuses it. `repoRoot` (when known) lets the doc\n * existence checks run.\n */\n// webpieces-disable no-any-unknown -- `value` is opaque consumer JSON until narrowed below\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateChecklistsSection(value: unknown, repoRoot?: string): string[] {\n if (Array.isArray(value)) return validateChecklistArray(value, repoRoot);\n if (typeof value === 'object' && value !== null && 'doc' in value) return [legacyManifestError(value)];\n return [`[pr-gate] \"checklists\" must be an ARRAY of { \"id\", \"doc\", \"patterns\"?, \"required\" }. ${CHECKLIST_EXAMPLE}`];\n}\n\n/**\n * The migration message for the removed `{ doc }` manifest shape. It names the doc the consumer pointed at,\n * because that is the file holding the array they must move, and spells out the one non-obvious part of the\n * move: entry `doc` paths used to resolve relative to that manifest doc and are now REPO-relative.\n */\n// webpieces-disable no-any-unknown -- narrowing the opaque checklists section to read the old `doc` key\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction legacyManifestError(value: object): string {\n const doc = (value as Record<string, unknown>)['doc'];\n const docRel = typeof doc === 'string' && doc.trim() !== '' ? doc : '<your review index doc>';\n return (\n `[pr-gate] \"checklists\" is the REMOVED { \"doc\": \"${docRel}\" } shape. The checklist array no longer lives in\\n` +\n ` an HTML comment inside a markdown doc — put it directly in webpieces.config.json:\\n` +\n ` 1. Open \"${docRel}\" and copy the JSON array out of its <!-- webpieces:checklists [...] --> comment.\\n` +\n ` 2. Replace \"checklists\": { \"doc\": \"${docRel}\" } with \"checklists\": <that array>.\\n` +\n ` 3. Rewrite each entry's \"doc\" to be REPO-relative — they used to resolve relative to\\n` +\n ` \"${docRel}\", so a bare \"db-migrations.md\" becomes e.g. \".claude/review/db-migrations.md\".\\n` +\n ` 4. Delete the <!-- webpieces:checklists ... --> comment from \"${docRel}\"; keep the prose.\\n` +\n ` ${CHECKLIST_EXAMPLE}`\n );\n}\n\n/**\n * `required` is MANDATORY on every checklist entry — omitting it is an error, never a default.\n *\n * The error names the entry's own id, because that is what the consumer recognizes in a twelve-entry\n * array; `checklists[7]` alone means counting braces. It also states BOTH edits, because the whole point of\n * the key is that the answer differs per checklist and only the consumer knows which.\n *\n * Why a hard rejection instead of `?? true`: an accepted shape is never migrated. Defaulting to true\n * silently keeps the all-blocking behavior this key exists to relieve, and every consumer that would have\n * benefited stays on the old behavior forever without ever being told the dial exists. Defaulting to false\n * is worse — it would silently DOWNGRADE a live review gate on upgrade. Per CLAUDE.md the reader of this\n * message is a coding agent, so the migration is one mechanical pass.\n */\n// webpieces-disable no-any-unknown -- one opaque checklist entry, narrowed by the typeof guards here\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction requiredKeyErrors(e: Record<string, unknown>, i: number): string[] {\n const name = typeof e['id'] === 'string' && e['id'].trim() !== '' ? ` (\"${e['id']}\")` : '';\n if (e['required'] === undefined) {\n return [\n `[pr-gate] checklists[${i}]${name} is missing \"required\". Every checklist must state one — there is\\n` +\n ` no default, in either direction.\\n` +\n ` \"required\": true → BLOCKING. wp-finish-upsert-pr refuses the PR until this reviewer passes.\\n` +\n ` This is what every checklist did before this key existed.\\n` +\n ` \"required\": false → OPTIONAL. When it matches the diff, wp-review-upsert-pr offers it and the\\n` +\n ` human may decline it. If they DO run it, a red verdict still blocks.\\n` +\n ` ${CHECKLIST_EXAMPLE}`,\n ];\n }\n if (typeof e['required'] !== 'boolean') {\n return [`[pr-gate] checklists[${i}]${name}.required must be a boolean (true = blocking, false = optional) — not a string or number.`];\n }\n return [];\n}\n\n// Structurally check ONE entry — a bad `patterns` or a non-string `doc` is a config-file typo and deserves a\n// `checklists[i]` message. A RETIRED key (the per-entry `subagent`) is reported with its migration edit.\n// webpieces-disable no-any-unknown -- one opaque checklist entry, narrowed per-field below\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction checklistEntryErrors(e: Record<string, unknown>, i: number): string[] {\n const errors = retiredKeyErrorsIn(e, CHECKLIST_ENTRY_LABEL).map((m: string): string => `checklists[${i}] ${m}`);\n if (e['id'] !== undefined && typeof e['id'] !== 'string') {\n errors.push(`[pr-gate] checklists[${i}].id must be a string — the checklist's name; it keys review-<id>.json.`);\n }\n if (e['doc'] !== undefined && typeof e['doc'] !== 'string') {\n errors.push(`[pr-gate] checklists[${i}].doc must be a string — the REPO-relative path to the checklist's guidance doc.`);\n }\n // webpieces-disable no-any-unknown -- opaque array element, narrowed by the typeof guard\n if (e['patterns'] !== undefined && !(Array.isArray(e['patterns']) && e['patterns'].every((p: unknown): boolean => typeof p === 'string'))) {\n errors.push(`[pr-gate] checklists[${i}].patterns must be a string[] of path globs (omit or [] to run on every PR).`);\n }\n errors.push(...requiredKeyErrors(e, i));\n return errors;\n}\n\n// Structurally check each entry, then hand the narrowed defs to ChecklistValidator for the id rules and the\n// checks only the filesystem can answer (the guidance doc exists). The reviewer agent itself is repo-wide\n// and is validated once, beside `reviewerAgentName`, by validateReviewerAgentKeys.\n// webpieces-disable no-any-unknown -- opaque consumer JSON entries, narrowed per-field below\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction validateChecklistArray(value: readonly unknown[], repoRoot?: string): string[] {\n const errors: string[] = [];\n const items: RawChecklistItem[] = [];\n // webpieces-disable no-any-unknown -- each array entry is opaque consumer JSON, narrowed field-by-field below\n value.forEach((entry: unknown, i: number): void => {\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n errors.push(`[pr-gate] checklists[${i}] must be an object { \"id\", \"doc\", \"patterns\"?, \"required\" }.`);\n return;\n }\n // webpieces-disable no-any-unknown -- narrowing one opaque checklist entry\n const e = entry as Record<string, unknown>;\n errors.push(...checklistEntryErrors(e, i));\n // Only the fields that passed their type check reach toChecklist — the wrong-typed ones already\n // have their own error above, and must not crash the id/doc checks below.\n items.push({\n id: typeof e['id'] === 'string' ? e['id'] : undefined,\n doc: typeof e['doc'] === 'string' ? e['doc'] : undefined,\n required: e['required'] === true,\n });\n });\n // The reviewer policy plays no part in these checks (ids and docs), so a placeholder is bound here.\n const placeholder = new ReviewerAgentPolicy('', REVIEWER_AGENTS_ONE_PER_CHECKLIST);\n const defs = items.map((item: RawChecklistItem): ChecklistDefinition => toChecklist(item, placeholder));\n return [...errors, ...new ChecklistValidator().validate(repoRoot, defs)];\n}\n\nexport const REVIEWER_AGENT_NAME_KEY = 'reviewerAgentName';\nexport const REVIEWER_AGENTS_KEY = 'reviewerAgents';\n\n/**\n * `reviewerAgentName` (REQUIRED while the gate is active) and `reviewerAgents` (optional positive integer).\n *\n * `reviewerAgentName` has no default for the same reason `mergeMode` has none: it names a file the repo\n * commits, and a silent default would brief every reviewer with an agent type the repo may not have. The\n * error hands the reader the exact line, which an agent applies in one pass.\n *\n * `reviewerAgents` absent is the documented \"one subagent per checklist\" contract — the count every repo had\n * before the key existed, so it widens nothing; present, it CAPS the subagents one round may use.\n */\n// webpieces-disable no-any-unknown -- the already-narrowed opaque pr-gate section; two keys are read\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateReviewerAgentKeys(s: Record<string, unknown>, repoRoot?: string): string[] {\n const errors: string[] = [];\n const name = s[REVIEWER_AGENT_NAME_KEY];\n const named = typeof name === 'string' && name.trim() !== '';\n if (!named) {\n errors.push(\n `[pr-gate] Missing required field \"${REVIEWER_AGENT_NAME_KEY}\" — the agent type every reviewer subagent is ` +\n `spawned as. Add this line to commands.pr-gate in webpieces.config.json:\\n` +\n ` \"${REVIEWER_AGENT_NAME_KEY}\": \"${DEFAULT_REVIEWER_AGENT_NAME}\",\\n` +\n ` ${DEFAULT_REVIEWER_AGENT_NAME} is the generic reviewer webpieces ships as .claude/agents/${DEFAULT_REVIEWER_AGENT_NAME}.md ` +\n `(\\`${UPGRADE_SHIM_COMMAND}\\` writes it). Point the key at your own agent to use that one instead.`);\n }\n const max = s[REVIEWER_AGENTS_KEY];\n if (REVIEWER_AGENTS_KEY in s && !(typeof max === 'number' && Number.isInteger(max) && max >= 1)) {\n errors.push(\n `[pr-gate] \"${REVIEWER_AGENTS_KEY}\" = ${JSON.stringify(max)} is not valid — it must be a positive integer: the MOST ` +\n `reviewer subagents one review round may use, with the checklists grouped across them (1 = a single ` +\n `subagent reviews every checklist). Delete the key to keep one subagent per checklist.`);\n }\n if (repoRoot !== undefined && named) {\n errors.push(...new ChecklistValidator().validateReviewerAgent(\n repoRoot, new ReviewerAgentPolicy((name as string).trim(), REVIEWER_AGENTS_ONE_PER_CHECKLIST)));\n }\n return errors;\n}\n\n// The `landPr` block: what happens to the LOCAL branch once its PR is in main. Optional — omitted\n// means \"archive-tag\", which is deliberately the DEFAULT so a consumer gets the branch-accumulation\n// fix without editing config at all. `branchRetentionWhy` (and any other `*Why` sibling) is free-form\n// rationale prose and is tolerated, per the repo's convention for documenting comment-less JSON.\n// webpieces-disable no-any-unknown -- `value` is the opaque consumer `landPr` value until narrowed here\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateLandPrSection(value: unknown): string[] {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return [`[pr-gate] \"landPr\" must be an object, e.g. { \"branchRetention\": \"${BRANCH_RETENTION_ARCHIVE_TAG}\" }.`];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque landPr object from consumer JSON\n const s = value as Record<string, unknown>;\n if (!('branchRetention' in s)) return [];\n const retention = s['branchRetention'];\n if (typeof retention === 'string' && BRANCH_RETENTIONS.includes(retention)) return [];\n return [\n `[pr-gate] \"landPr.branchRetention\" = \"${String(retention)}\" is not valid. ` +\n `Must be one of: ${BRANCH_RETENTIONS.join(', ')}.\\n` +\n ` \"${BRANCH_RETENTION_ARCHIVE_TAG}\" — (default) tag the branch tip as archive/<date>/<branch>, THEN delete it. The\\n` +\n ` history stays byte-identical and restorable, but the branch stops counting\\n` +\n ` toward the branch cap and cannot be committed onto by accident.\\n` +\n ` \"${BRANCH_RETENTION_DELETE}\" — delete outright; recoverable only from the reflog, which expires.\\n` +\n ` \"${BRANCH_RETENTION_KEEP}\" — do not delete. Branches then accumulate until branch-creation-guard trips.`,\n ];\n}\n\n// A git ref COMPONENT this flow is willing to build a ref name out of. Deliberately much narrower than\n// git's own check-ref-format: these two values are concatenated into a ref that a command then\n// force-pushes, so anything that could be read as a flag, a path escape, or a glob is rejected outright\n// rather than trusted to `git push` argument order.\nconst REF_COMPONENT = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;\n\n/**\n * Validate the optional `pr-gate.devDeploy` block — where `wp-push-dev` publishes the disposable copy of\n * a feature branch, and which ref is the shared dev branch itself. Absent ⇒ `dev-include` / `dev`.\n *\n * `devBranch` must NOT contain a slash and must not sit inside `branchNamespace`: the whole point of the\n * namespace is that the composed dev branch is written by CI and the copies are written by developers, so\n * a config where one contains the other makes `--list` enumerate the deploy branch and makes the\n * \"refused as a source branch\" check ambiguous.\n */\n// webpieces-disable no-any-unknown -- `value` is the opaque consumer devDeploy value until narrowed here\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateDevDeploySection(value: unknown): string[] {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return ['[pr-gate] \"devDeploy\" must be an object, e.g. { \"branchNamespace\": \"dev-include\", \"devBranch\": \"dev\" }.'];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque devDeploy object from consumer JSON\n const s = value as Record<string, unknown>;\n const errors: string[] = [];\n errors.push(...validateRefComponent(s, 'branchNamespace', 'dev-include'));\n errors.push(...validateRefComponent(s, 'devBranch', 'dev'));\n if (errors.length > 0) return errors;\n\n const namespace = typeof s['branchNamespace'] === 'string' ? s['branchNamespace'].trim() : 'dev-include';\n const devBranch = typeof s['devBranch'] === 'string' ? s['devBranch'].trim() : 'dev';\n if (devBranch.includes('/')) {\n errors.push(\n `[pr-gate] \"devDeploy.devBranch\" = \"${devBranch}\" must be a single ref name with no \"/\" — it is the ` +\n `branch your CI composes and deploys, not a namespace.`);\n }\n if (devBranch === namespace || devBranch.startsWith(`${namespace}/`) || namespace.startsWith(`${devBranch}/`)) {\n errors.push(\n `[pr-gate] \"devDeploy.devBranch\" (\"${devBranch}\") and \"devDeploy.branchNamespace\" (\"${namespace}\") must not ` +\n `contain one another. The namespace holds the DISPOSABLE per-developer copies (written by wp-push-dev); ` +\n `devBranch is the COMPOSED branch your CI rebuilds from origin/main. Overlapping them makes wp-push-dev ` +\n `--list enumerate the deploy branch and makes \"refused as a source branch\" ambiguous.`);\n }\n return errors;\n}\n\n// One `devDeploy` string field: present ⇒ must be a non-empty, ref-safe string. Absent ⇒ the default.\n// webpieces-disable no-any-unknown -- the already-narrowed opaque devDeploy object\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction validateRefComponent(s: Record<string, unknown>, key: string, example: string): string[] {\n if (!(key in s)) return [];\n const raw = s[key];\n if (typeof raw !== 'string' || raw.trim() === '') {\n return [`[pr-gate] \"devDeploy.${key}\" must be a non-empty string, e.g. \"${example}\". Omit the key for the \"${example}\" default.`];\n }\n if (!REF_COMPONENT.test(raw.trim())) {\n return [\n `[pr-gate] \"devDeploy.${key}\" = \"${raw}\" is not a usable git ref name. Use letters, digits, \".\", \"_\", ` +\n `\"-\" and \"/\" only, starting with a letter or digit (e.g. \"${example}\").`,\n ];\n }\n return [];\n}\n"]}
@@ -157,6 +157,14 @@ exports.RETIRED_CONFIG_KEYS = [
157
157
  // renaming the first and deleting the rest.
158
158
  ...branchStateRetirements(),
159
159
  ...prLifecycleRetirements(),
160
+ // --- The per-checklist reviewer agent (issue #938). A checklist used to name its own agent type in
161
+ // `subagent`, which also served as its id; every checklist is now reviewed by the ONE agent type that
162
+ // `commands.pr-gate.reviewerAgentName` names, so the key that is left is only the checklist's NAME.
163
+ // A rename, so `prunable: false` — deleting it would discard the id every verdict file is keyed by.
164
+ new RetiredConfigKey(exports.RETIRED_SCOPE_KEY, 'subagent', 'id', 'Rename "subagent" to "id" in EVERY commands.pr-gate.checklists entry, keeping its value (it still ' +
165
+ 'names the checklist and keys review-<id>.json). Checklists no longer choose an agent type: every one ' +
166
+ 'is reviewed by the agent commands.pr-gate.reviewerAgentName names — add ' +
167
+ '"reviewerAgentName": "webpieces-reviewer" there if it is missing.', '[pr-gate.checklists]', false),
160
168
  ];
161
169
  // The four branch-state classes. Split into a helper purely to keep the table above readable; the
162
170
  // instruction is per-key because the fields that carry over differ (only feature-branch-guard had