@webpieces/rules-config 0.4.793 → 0.4.795

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/rules-config",
3
- "version": "0.4.793",
3
+ "version": "0.4.795",
4
4
  "description": "Shared webpieces.config.json loader. Single source of truth for validation rule configuration consumed by @webpieces/ai-hook-rules, @webpieces/code-rules, and @webpieces/nx-webpieces-rules.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -2,7 +2,7 @@
2
2
  * The {@link ReviewerAgentPolicy.maxAgents} stand-in for the code paths that build a policy where the CAP
3
3
  * plays no part — the structure-only checklist validation, and the agent-file existence check. It is NOT a
4
4
  * default and never reaches a loaded config: `reviewerAgents` is required, so a real config's cap is always
5
- * the number the consumer wrote. 1 rather than 0 so the value is a legal cap even if one ever leaked.
5
+ * the number the consumer wrote. 1 keeps structure-only paths in the enabled-review shape.
6
6
  */
7
7
  export declare const REVIEWER_AGENTS_PLACEHOLDER = 1;
8
8
  /** The generic reviewer agent webpieces ships, and the reviewer every repo gets unless it sets `overrideReviewerAgent`. */
@@ -26,14 +26,10 @@ export declare class ReviewerAgentPolicy {
26
26
  */
27
27
  agentName: string;
28
28
  /**
29
- * The most reviewer subagents one stage-② round may use — always >= 1, because `reviewerAgents` is a
30
- * REQUIRED config field. The main AI groups the owed checklists across at most this many subagents, and
31
- * each subagent still writes one verdict file per checklist it covers.
32
- *
33
- * There is no "unset" value and no second mode. This used to carry a 0 sentinel meaning "not configured",
34
- * which selected a separate one-subagent-per-checklist code path; making the key required deleted that
35
- * branch rather than leaving it reachable, because a consumer who sets the cap to their checklist count
36
- * gets the identical behaviour from the one path that is left.
29
+ * The most reviewer subagents one stage-② round may use. `0` explicitly disables reviewer-agent
30
+ * reviews for the project while leaving the build and PR lifecycle active. Positive values cap the
31
+ * round; the main AI groups owed checklists across at most that many subagents, and each subagent still
32
+ * writes one verdict file per checklist it covers.
37
33
  */
38
34
  maxAgents: number;
39
35
  constructor(agentName: string, maxAgents: number);
@@ -10,7 +10,7 @@ const path = tslib_1.__importStar(require("path"));
10
10
  * The {@link ReviewerAgentPolicy.maxAgents} stand-in for the code paths that build a policy where the CAP
11
11
  * plays no part — the structure-only checklist validation, and the agent-file existence check. It is NOT a
12
12
  * default and never reaches a loaded config: `reviewerAgents` is required, so a real config's cap is always
13
- * the number the consumer wrote. 1 rather than 0 so the value is a legal cap even if one ever leaked.
13
+ * the number the consumer wrote. 1 keeps structure-only paths in the enabled-review shape.
14
14
  */
15
15
  exports.REVIEWER_AGENTS_PLACEHOLDER = 1;
16
16
  /** The generic reviewer agent webpieces ships, and the reviewer every repo gets unless it sets `overrideReviewerAgent`. */
@@ -34,14 +34,10 @@ class ReviewerAgentPolicy {
34
34
  */
35
35
  agentName;
36
36
  /**
37
- * The most reviewer subagents one stage-② round may use — always >= 1, because `reviewerAgents` is a
38
- * REQUIRED config field. The main AI groups the owed checklists across at most this many subagents, and
39
- * each subagent still writes one verdict file per checklist it covers.
40
- *
41
- * There is no "unset" value and no second mode. This used to carry a 0 sentinel meaning "not configured",
42
- * which selected a separate one-subagent-per-checklist code path; making the key required deleted that
43
- * branch rather than leaving it reachable, because a consumer who sets the cap to their checklist count
44
- * gets the identical behaviour from the one path that is left.
37
+ * The most reviewer subagents one stage-② round may use. `0` explicitly disables reviewer-agent
38
+ * reviews for the project while leaving the build and PR lifecycle active. Positive values cap the
39
+ * round; the main AI groups owed checklists across at most that many subagents, and each subagent still
40
+ * writes one verdict file per checklist it covers.
45
41
  */
46
42
  maxAgents;
47
43
  constructor(agentName, maxAgents) {
@@ -1 +1 @@
1
- {"version":3,"file":"checklist-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/checklist-config.ts"],"names":[],"mappings":";;;AAiHA,kCAGC;AAID,sDAIC;AAYD,wCAIC;;AA5ID,mDAA6B;AAE7B;;;;;GAKG;AACU,QAAA,2BAA2B,GAAG,CAAC,CAAC;AAE7C,2HAA2H;AAC9G,QAAA,2BAA2B,GAAG,oBAAoB,CAAC;AAEhE;;;;;;;;;GASG;AACH,MAAa,mBAAmB;IAC5B;;;;;OAKG;IACH,SAAS,CAAS;IAClB;;;;;;;;;OASG;IACH,SAAS,CAAS;IAElB,YAAY,SAAiB,EAAE,SAAiB;QAC5C,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC/B,CAAC;CACJ;AAxBD,kDAwBC;AAED,mGAAmG;AACnG,0GAA0G;AAC1G,yFAAyF;AACzF,6GAA6G;AAC7G,wGAAwG;AACxG,oGAAoG;AACpG,EAAE;AACF,oGAAoG;AACpG,sGAAsG;AACtG,wGAAwG;AACxG,aAAa;AACb,MAAa,mBAAmB;IAC5B,EAAE,CAAS,CAAoB,qEAAqE;IACpG,QAAQ,CAAsB,CAAC,gEAAgE;IAC/F,sGAAsG;IACtG,sGAAsG;IACtG,iGAAiG;IACjG,GAAG,CAAS;IACZ,QAAQ,CAAW,CAAC,qFAAqF;IACzG;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAU;IAElB,yDAAyD;IACzD,YAAY,EAAU,EAAE,QAA6B,EAAE,GAAW,EAAE,QAAkB,EAAE,QAAiB;QACrG,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC7B,CAAC;CACJ;AAlCD,kDAkCC;AAUD;;;;;;;GAOG;AACH,6FAA6F;AAC7F,SAAgB,WAAW,CAAC,GAAqB,EAAE,QAA6B;IAC5E,OAAO,IAAI,mBAAmB,CAC1B,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,qBAAqB,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC;AAC1H,CAAC;AAED,wGAAwG;AACxG,2FAA2F;AAC3F,SAAgB,qBAAqB,CAAC,GAAW;IAC7C,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,EAAE,CAAC;IAC9B,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,0GAA0G;AAC1G,+FAA+F;AAClF,QAAA,iBAAiB,GAAG,CAAC,CAAC;AAEnC;;;;GAIG;AACH,mGAAmG;AACnG,SAAgB,cAAc,CAAC,KAAwB,EAAE,MAAc,yBAAiB;IACpF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IACxC,IAAI,KAAK,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,MAAM,GAAG,GAAG,UAAU,KAAK,CAAC,MAAM,SAAS,CAAC;AACpG,CAAC","sourcesContent":["import * as path from 'path';\n\n/**\n * The {@link ReviewerAgentPolicy.maxAgents} stand-in for the code paths that build a policy where the CAP\n * plays no part — the structure-only checklist validation, and the agent-file existence check. It is NOT a\n * default and never reaches a loaded config: `reviewerAgents` is required, so a real config's cap is always\n * the number the consumer wrote. 1 rather than 0 so the value is a legal cap even if one ever leaked.\n */\nexport const REVIEWER_AGENTS_PLACEHOLDER = 1;\n\n/** The generic reviewer agent webpieces ships, and the reviewer every repo gets unless it sets `overrideReviewerAgent`. */\nexport const DEFAULT_REVIEWER_AGENT_NAME = 'webpieces-reviewer';\n\n/**\n * WHICH reviewer agent the gate briefs, and HOW MANY of them one round may use — straight from\n * `commands.pr-gate.overrideReviewerAgent` + `reviewerAgentName` / `commands.pr-gate.reviewerAgents`. Data-only.\n *\n * One instance per loaded config, shared by every {@link ChecklistDefinition} and copied onto every matched\n * checklist, because the answer is repo-wide: a checklist no longer names an agent type of its own. It used\n * to — a per-entry key pointing at its own `.claude/agents/<name>.md` — and those files added almost nothing\n * over the checklist `doc` while forcing one subagent per checklist, so a PR matching four checklists paid\n * for four agents re-reading the same diff (issue #938).\n */\nexport class ReviewerAgentPolicy {\n /**\n * The `subagent_type` every reviewer briefing names, e.g. `webpieces-reviewer` — the generic,\n * checklist-agnostic agent webpieces ships (`.claude/agents/webpieces-reviewer.md`, written by\n * `wp-install-ai-hooks` / `wp-upgrade-shim`). A repo may use its own agent instead by setting\n * `\"overrideReviewerAgent\": true` and `\"reviewerAgentName\"`.\n */\n agentName: string;\n /**\n * The most reviewer subagents one stage-② round may use — always >= 1, because `reviewerAgents` is a\n * REQUIRED config field. The main AI groups the owed checklists across at most this many subagents, and\n * each subagent still writes one verdict file per checklist it covers.\n *\n * There is no \"unset\" value and no second mode. This used to carry a 0 sentinel meaning \"not configured\",\n * which selected a separate one-subagent-per-checklist code path; making the key required deleted that\n * branch rather than leaving it reachable, because a consumer who sets the cap to their checklist count\n * gets the identical behaviour from the one path that is left.\n */\n maxAgents: number;\n\n constructor(agentName: string, maxAgents: number) {\n this.agentName = agentName;\n this.maxAgents = maxAgents;\n }\n}\n\n// A company review checklist: a diff-triggered extension point that lets a CONSUMER inject its own\n// PR-time review process into the webpieces gated flow WITHOUT forking the tooling. Each checklist has an\n// `id` and the doc its reviewer reads; when the diff matches the checklist's `patterns`,\n// wp-review-upsert-pr tells the AI to spawn the repo's reviewer agent (`webpieces-reviewer`, or an override)\n// over it, and wp-finish-upsert-pr refuses to open the PR until a well-formed, passing review-<id>.json\n// exists AND a reviewer subagent is proven (from the harness's own artifacts) to have actually run.\n//\n// Checklists are configured as an ARRAY in `pr-gate.checklists` in webpieces.config.json — the ONLY\n// accepted shape. `patterns` is a path-glob dispatch table and `id` is the checklist's name: both are\n// config, so they live where every tool that reads webpieces.config.json can see, grep and schema them.\n// Data-only.\nexport class ChecklistDefinition {\n id: string; // keys review-<id>.json, its instructions file and its dashboard row\n reviewer: ReviewerAgentPolicy; // repo-wide: which agent type reviews it, and the per-round cap\n // REPO-RELATIVE guidance doc the reviewer reads. REQUIRED: with one generic reviewer agent the doc is\n // the whole checklist. Repo-relative because this value is printed verbatim to a reviewer subagent as\n // \"the file to open\", and a path relative to anything else is unresolvable from where it stands.\n doc: string;\n patterns: string[]; // path globs (isPathExcluded semantics); [] = matches any changed file (always runs)\n /**\n * true — BLOCKING. `wp-finish-upsert-pr` refuses the PR until this checklist has a passing verdict.\n * false — OPTIONAL. When it matches, stage ② OFFERS it: the AI asks the human which optional reviewers\n * to run, and the human may decline every one of them. A declined optional checklist never\n * blocks.\n *\n * This governs whether the reviewer must RUN — NOT whether its verdict counts. An optional reviewer\n * that is actually spawned and comes back red blocks finish exactly like a required one; otherwise\n * running it would be theater.\n *\n * There is deliberately NO default (see the validator): a review process is the last thing that should\n * acquire a scope silently, in either direction. Defaulting to true would leave the all-blocking status\n * quo in place for anyone who did not read the release note; defaulting to false would quietly\n * DOWNGRADE every existing consumer's gate on upgrade. Both are worse than one mechanical config edit\n * that the coding agent reading the error applies in a single pass.\n */\n required: boolean;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(id: string, reviewer: ReviewerAgentPolicy, doc: string, patterns: string[], required: boolean) {\n this.id = id;\n this.reviewer = reviewer;\n this.doc = doc;\n this.patterns = patterns;\n this.required = required;\n }\n}\n\n// One config entry straight from JSON, before it is validated + narrowed into a class.\nexport interface RawChecklistItem {\n id?: string;\n doc?: string;\n patterns?: string[];\n required?: boolean;\n}\n\n/**\n * Build a ChecklistDefinition from a raw entry, bound to the repo's reviewer policy.\n *\n * `required` is coerced with `=== true` rather than defaulted, and that is not a default in disguise:\n * `validateChecklistArray` has already REJECTED any entry that omitted it or gave a non-boolean, so the\n * only values that reach here are real booleans. The coercion exists so a validator that runs without a\n * repoRoot (structure-only) still produces a well-typed def instead of `undefined` leaking through.\n */\n// webpieces-disable no-function-outside-class -- pure config transform beside its data class\nexport function toChecklist(raw: RawChecklistItem, reviewer: ReviewerAgentPolicy): ChecklistDefinition {\n return new ChecklistDefinition(\n (raw.id ?? '').trim(), reviewer, normalizeChecklistDoc(raw.doc ?? ''), raw.patterns ?? [], raw.required === true);\n}\n\n/** Normalize a checklist entry's repo-relative `doc` to a POSIX path, so every printed path matches. */\n// webpieces-disable no-function-outside-class -- pure path transform beside its data class\nexport function normalizeChecklistDoc(doc: string): string {\n const trimmed = doc.trim();\n if (trimmed === '') return '';\n return path.posix.normalize(trimmed.split(path.sep).join('/'));\n}\n\n// The ONE cap for every printed matched-file list. Two print sites once used to slice to 4 and to 5 — two\n// different caps for the same list, neither chosen deliberately, and both without an ellipsis.\nexport const MATCHED_FILES_CAP = 6;\n\n/**\n * Render a file list for a message, NEVER silently. A truncated list that looks complete is how a reviewer\n * gets pointed at 4 of 40 changed files and reports success having reviewed a tenth of the diff, so the\n * dropped count is always stated and the caller is expected to name the file holding the full set.\n */\n// webpieces-disable no-function-outside-class -- pure display formatter beside the data it formats\nexport function formatFileList(files: readonly string[], cap: number = MATCHED_FILES_CAP): string {\n if (files.length === 0) return '(none)';\n if (files.length <= cap) return files.join(', ');\n return `${files.slice(0, cap).join(', ')}, +${files.length - cap} more (${files.length} total)`;\n}\n"]}
1
+ {"version":3,"file":"checklist-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/checklist-config.ts"],"names":[],"mappings":";;;AA6GA,kCAGC;AAID,sDAIC;AAYD,wCAIC;;AAxID,mDAA6B;AAE7B;;;;;GAKG;AACU,QAAA,2BAA2B,GAAG,CAAC,CAAC;AAE7C,2HAA2H;AAC9G,QAAA,2BAA2B,GAAG,oBAAoB,CAAC;AAEhE;;;;;;;;;GASG;AACH,MAAa,mBAAmB;IAC5B;;;;;OAKG;IACH,SAAS,CAAS;IAClB;;;;;OAKG;IACH,SAAS,CAAS;IAElB,YAAY,SAAiB,EAAE,SAAiB;QAC5C,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC/B,CAAC;CACJ;AApBD,kDAoBC;AAED,mGAAmG;AACnG,0GAA0G;AAC1G,yFAAyF;AACzF,6GAA6G;AAC7G,wGAAwG;AACxG,oGAAoG;AACpG,EAAE;AACF,oGAAoG;AACpG,sGAAsG;AACtG,wGAAwG;AACxG,aAAa;AACb,MAAa,mBAAmB;IAC5B,EAAE,CAAS,CAAoB,qEAAqE;IACpG,QAAQ,CAAsB,CAAC,gEAAgE;IAC/F,sGAAsG;IACtG,sGAAsG;IACtG,iGAAiG;IACjG,GAAG,CAAS;IACZ,QAAQ,CAAW,CAAC,qFAAqF;IACzG;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAU;IAElB,yDAAyD;IACzD,YAAY,EAAU,EAAE,QAA6B,EAAE,GAAW,EAAE,QAAkB,EAAE,QAAiB;QACrG,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC7B,CAAC;CACJ;AAlCD,kDAkCC;AAUD;;;;;;;GAOG;AACH,6FAA6F;AAC7F,SAAgB,WAAW,CAAC,GAAqB,EAAE,QAA6B;IAC5E,OAAO,IAAI,mBAAmB,CAC1B,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,qBAAqB,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC;AAC1H,CAAC;AAED,wGAAwG;AACxG,2FAA2F;AAC3F,SAAgB,qBAAqB,CAAC,GAAW;IAC7C,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,EAAE,CAAC;IAC9B,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,0GAA0G;AAC1G,+FAA+F;AAClF,QAAA,iBAAiB,GAAG,CAAC,CAAC;AAEnC;;;;GAIG;AACH,mGAAmG;AACnG,SAAgB,cAAc,CAAC,KAAwB,EAAE,MAAc,yBAAiB;IACpF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IACxC,IAAI,KAAK,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,MAAM,GAAG,GAAG,UAAU,KAAK,CAAC,MAAM,SAAS,CAAC;AACpG,CAAC","sourcesContent":["import * as path from 'path';\n\n/**\n * The {@link ReviewerAgentPolicy.maxAgents} stand-in for the code paths that build a policy where the CAP\n * plays no part — the structure-only checklist validation, and the agent-file existence check. It is NOT a\n * default and never reaches a loaded config: `reviewerAgents` is required, so a real config's cap is always\n * the number the consumer wrote. 1 keeps structure-only paths in the enabled-review shape.\n */\nexport const REVIEWER_AGENTS_PLACEHOLDER = 1;\n\n/** The generic reviewer agent webpieces ships, and the reviewer every repo gets unless it sets `overrideReviewerAgent`. */\nexport const DEFAULT_REVIEWER_AGENT_NAME = 'webpieces-reviewer';\n\n/**\n * WHICH reviewer agent the gate briefs, and HOW MANY of them one round may use — straight from\n * `commands.pr-gate.overrideReviewerAgent` + `reviewerAgentName` / `commands.pr-gate.reviewerAgents`. Data-only.\n *\n * One instance per loaded config, shared by every {@link ChecklistDefinition} and copied onto every matched\n * checklist, because the answer is repo-wide: a checklist no longer names an agent type of its own. It used\n * to — a per-entry key pointing at its own `.claude/agents/<name>.md` — and those files added almost nothing\n * over the checklist `doc` while forcing one subagent per checklist, so a PR matching four checklists paid\n * for four agents re-reading the same diff (issue #938).\n */\nexport class ReviewerAgentPolicy {\n /**\n * The `subagent_type` every reviewer briefing names, e.g. `webpieces-reviewer` — the generic,\n * checklist-agnostic agent webpieces ships (`.claude/agents/webpieces-reviewer.md`, written by\n * `wp-install-ai-hooks` / `wp-upgrade-shim`). A repo may use its own agent instead by setting\n * `\"overrideReviewerAgent\": true` and `\"reviewerAgentName\"`.\n */\n agentName: string;\n /**\n * The most reviewer subagents one stage-② round may use. `0` explicitly disables reviewer-agent\n * reviews for the project while leaving the build and PR lifecycle active. Positive values cap the\n * round; the main AI groups owed checklists across at most that many subagents, and each subagent still\n * writes one verdict file per checklist it covers.\n */\n maxAgents: number;\n\n constructor(agentName: string, maxAgents: number) {\n this.agentName = agentName;\n this.maxAgents = maxAgents;\n }\n}\n\n// A company review checklist: a diff-triggered extension point that lets a CONSUMER inject its own\n// PR-time review process into the webpieces gated flow WITHOUT forking the tooling. Each checklist has an\n// `id` and the doc its reviewer reads; when the diff matches the checklist's `patterns`,\n// wp-review-upsert-pr tells the AI to spawn the repo's reviewer agent (`webpieces-reviewer`, or an override)\n// over it, and wp-finish-upsert-pr refuses to open the PR until a well-formed, passing review-<id>.json\n// exists AND a reviewer subagent is proven (from the harness's own artifacts) to have actually run.\n//\n// Checklists are configured as an ARRAY in `pr-gate.checklists` in webpieces.config.json — the ONLY\n// accepted shape. `patterns` is a path-glob dispatch table and `id` is the checklist's name: both are\n// config, so they live where every tool that reads webpieces.config.json can see, grep and schema them.\n// Data-only.\nexport class ChecklistDefinition {\n id: string; // keys review-<id>.json, its instructions file and its dashboard row\n reviewer: ReviewerAgentPolicy; // repo-wide: which agent type reviews it, and the per-round cap\n // REPO-RELATIVE guidance doc the reviewer reads. REQUIRED: with one generic reviewer agent the doc is\n // the whole checklist. Repo-relative because this value is printed verbatim to a reviewer subagent as\n // \"the file to open\", and a path relative to anything else is unresolvable from where it stands.\n doc: string;\n patterns: string[]; // path globs (isPathExcluded semantics); [] = matches any changed file (always runs)\n /**\n * true — BLOCKING. `wp-finish-upsert-pr` refuses the PR until this checklist has a passing verdict.\n * false — OPTIONAL. When it matches, stage ② OFFERS it: the AI asks the human which optional reviewers\n * to run, and the human may decline every one of them. A declined optional checklist never\n * blocks.\n *\n * This governs whether the reviewer must RUN — NOT whether its verdict counts. An optional reviewer\n * that is actually spawned and comes back red blocks finish exactly like a required one; otherwise\n * running it would be theater.\n *\n * There is deliberately NO default (see the validator): a review process is the last thing that should\n * acquire a scope silently, in either direction. Defaulting to true would leave the all-blocking status\n * quo in place for anyone who did not read the release note; defaulting to false would quietly\n * DOWNGRADE every existing consumer's gate on upgrade. Both are worse than one mechanical config edit\n * that the coding agent reading the error applies in a single pass.\n */\n required: boolean;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(id: string, reviewer: ReviewerAgentPolicy, doc: string, patterns: string[], required: boolean) {\n this.id = id;\n this.reviewer = reviewer;\n this.doc = doc;\n this.patterns = patterns;\n this.required = required;\n }\n}\n\n// One config entry straight from JSON, before it is validated + narrowed into a class.\nexport interface RawChecklistItem {\n id?: string;\n doc?: string;\n patterns?: string[];\n required?: boolean;\n}\n\n/**\n * Build a ChecklistDefinition from a raw entry, bound to the repo's reviewer policy.\n *\n * `required` is coerced with `=== true` rather than defaulted, and that is not a default in disguise:\n * `validateChecklistArray` has already REJECTED any entry that omitted it or gave a non-boolean, so the\n * only values that reach here are real booleans. The coercion exists so a validator that runs without a\n * repoRoot (structure-only) still produces a well-typed def instead of `undefined` leaking through.\n */\n// webpieces-disable no-function-outside-class -- pure config transform beside its data class\nexport function toChecklist(raw: RawChecklistItem, reviewer: ReviewerAgentPolicy): ChecklistDefinition {\n return new ChecklistDefinition(\n (raw.id ?? '').trim(), reviewer, normalizeChecklistDoc(raw.doc ?? ''), raw.patterns ?? [], raw.required === true);\n}\n\n/** Normalize a checklist entry's repo-relative `doc` to a POSIX path, so every printed path matches. */\n// webpieces-disable no-function-outside-class -- pure path transform beside its data class\nexport function normalizeChecklistDoc(doc: string): string {\n const trimmed = doc.trim();\n if (trimmed === '') return '';\n return path.posix.normalize(trimmed.split(path.sep).join('/'));\n}\n\n// The ONE cap for every printed matched-file list. Two print sites once used to slice to 4 and to 5 — two\n// different caps for the same list, neither chosen deliberately, and both without an ellipsis.\nexport const MATCHED_FILES_CAP = 6;\n\n/**\n * Render a file list for a message, NEVER silently. A truncated list that looks complete is how a reviewer\n * gets pointed at 4 of 40 changed files and reports success having reviewed a tenth of the diff, so the\n * dropped count is always stated and the caller is expected to name the file holding the full set.\n */\n// webpieces-disable no-function-outside-class -- pure display formatter beside the data it formats\nexport function formatFileList(files: readonly string[], cap: number = MATCHED_FILES_CAP): string {\n if (files.length === 0) return '(none)';\n if (files.length <= cap) return files.join(', ');\n return `${files.slice(0, cap).join(', ')}, +${files.length - cap} more (${files.length} total)`;\n}\n"]}
@@ -36,6 +36,8 @@ let ChecklistInstructionsService = class ChecklistInstructionsService {
36
36
  render(pending, reviewPath, context) {
37
37
  if (pending.length === 0)
38
38
  return '';
39
+ if (pending[0].reviewer.maxAgents === 0)
40
+ return this.spawnRule(pending).join('\n');
39
41
  const lines = [...this.spawnRule(pending), ''];
40
42
  for (const req of pending)
41
43
  lines.push(...this.oneReviewer(req, reviewPath));
@@ -50,6 +52,12 @@ let ChecklistInstructionsService = class ChecklistInstructionsService {
50
52
  */
51
53
  spawnRule(pending) {
52
54
  const reviewer = pending[0].reviewer;
55
+ if (reviewer.maxAgents === 0) {
56
+ return [
57
+ 'Reviewer-agent reviews are disabled for this project (commands.pr-gate.reviewerAgents = 0).',
58
+ 'Do not spawn reviewer subagents and do not write checklist verdict files.',
59
+ ];
60
+ }
53
61
  const own = [
54
62
  'You may NOT review your own work, and you may NOT write a reviewer\'s verdict file on its behalf.',
55
63
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"checklist-instructions.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/checklist-instructions.ts"],"names":[],"mappings":"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","sourcesContent":["import { injectable, bindingScopeValues } from 'inversify';\nimport { formatFileList } from './checklist-config';\nimport {\n ChecklistReviewContext, RequiredChecklist, ReviewJsonService,\n} from './review-json';\n\n/**\n * Renders the ONE block that tells the coding agent which reviewer subagents it must run and exactly what\n * to tell them. There is a single renderer because three callers need the identical text and any drift\n * between them is a correctness bug, not a cosmetic one:\n *\n * - `wp-review-upsert-pr` — the AI asks \"what review do I owe on this diff?\"\n * - `wp-finish-upsert-pr` — fails fast, listing ONLY the reviewers that still have not run\n * - `ReviewJsonService` — the same list appended to a review.json validation failure\n *\n * Callers pass ONLY the checklists still needing a verdict. A checklist already reviewed on this branch is\n * never re-listed — re-instructing it invites a redundant second run, and (worse) reads as though the\n * earlier verdict did not count.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ChecklistInstructionsService {\n // Injected so the verdict path comes from the ONE place that knows it. This class used to hand-roll\n // dirname with a regex and a hardcoded '/', a second implementation of ReviewJsonService's\n // checklistResultPath that agreed on POSIX and disagreed on Windows — the same duplicate-logic trap as\n // the two different matched-file truncation caps.\n constructor(private readonly reviewJsonService: ReviewJsonService) {}\n\n /**\n * The full instruction block, or '' when nothing is pending (so a caller can concatenate it blindly).\n * `reviewPath` is the branch's review.json — each verdict file sits beside it as review-<id>.json.\n */\n render(pending: readonly RequiredChecklist[], reviewPath: string, context: ChecklistReviewContext): string {\n if (pending.length === 0) return '';\n const lines: string[] = [...this.spawnRule(pending), ''];\n for (const req of pending) lines.push(...this.oneReviewer(req, reviewPath));\n lines.push('', ...this.verdictFormat());\n lines.push('', ...this.diffLines(context));\n return lines.join('\\n');\n }\n\n /**\n * HOW MANY subagents, of WHICH type: at most `reviewerAgents` of them, with the owed checklists grouped\n * across them as the AI judges best. Every subagent is the repo's ONE reviewer agent type, and every\n * checklist still gets its own verdict file no matter how they were grouped.\n */\n spawnRule(pending: readonly RequiredChecklist[]): string[] {\n const reviewer = pending[0].reviewer;\n const own = [\n 'You may NOT review your own work, and you may NOT write a reviewer\\'s verdict file on its behalf.',\n ];\n const cap = Math.min(reviewer.maxAgents, pending.length);\n return [\n `You MUST run these ${pending.length} checklist review(s) using AT MOST ${cap} \\`${reviewer.agentName}\\` subagent(s)`,\n `(commands.pr-gate.reviewerAgents = ${reviewer.maxAgents}). ${this.groupingHint(pending.length, cap)}`,\n 'Give each subagent every checklist it covers (doc, in-scope files, verdict path); it writes ONE verdict',\n 'file per checklist it covers, and never one for a checklist it was not given.',\n ...own,\n ];\n }\n\n /**\n * The grouping sentence, with an example that fits THIS round's numbers — a cap of 1 has no choice to\n * make, and saying \"group them as you judge best\" there would invite a second subagent.\n */\n groupingHint(count: number, cap: number): string {\n if (cap <= 1) return 'Use ONE subagent for all of them.';\n const per = Math.ceil(count / cap);\n return `Group the checklists across them as you judge best — e.g. one subagent for all of them, or ${cap} with about ${per} each.`;\n }\n\n // Just the checklist IDS, for a caller that wants a one-line summary rather than the whole block.\n names(pending: readonly RequiredChecklist[]): string {\n return pending.map((r: RequiredChecklist): string => r.id).join(', ');\n }\n\n // What the subagent reviewing ONE checklist must be given: its doc, why it is running + over what, and\n // the file it must write.\n private oneReviewer(req: RequiredChecklist, reviewPath: string): string[] {\n const lines = [` • checklist ${req.id}`];\n // The doc is REPO-relative by the time it reaches here (see ChecklistDefinition.doc), so a subagent\n // handed this string can actually open it. Printing the raw config value would not resolve.\n if (req.doc.trim() !== '') lines.push(` doc to read: ${req.doc}`);\n for (const scopeLine of this.scope(req)) lines.push(` ${scopeLine}`);\n lines.push(` must write: ${this.reviewJsonService.checklistResultPath(reviewPath, req.id)}`);\n return lines;\n }\n\n /**\n * WHY this reviewer is running, and over what. NOT every checklist is pattern-matched: one with no\n * `patterns` runs on EVERY PR, and calling its file list \"matched\" implies the list is a narrow,\n * pre-filtered slice of the diff when it is in fact the whole thing. When patterns DID fire, they are\n * named — the reviewer cannot otherwise tell a precise migrations-only glob from a blanket match-all one.\n */\n private scope(req: RequiredChecklist): string[] {\n if (req.matchedPatterns.length === 0) {\n return [\n `in scope: ALWAYS RUNS — this checklist has no patterns, so the WHOLE diff is in scope`,\n ` all ${req.matchedFiles.length} changed file(s): ${formatFileList(req.matchedFiles)}`,\n ];\n }\n const globs = req.matchedPatterns.map((p: string): string => `\"${p}\"`).join(', ');\n return [\n `in scope: ${req.matchedFiles.length} file(s) matched ${globs}`,\n ` ${formatFileList(req.matchedFiles)}`,\n ];\n }\n\n // ONE shared format block for every reviewer, rather than repeating the schema under each name — and\n // the schema itself comes from ReviewJsonService, the single renderer, so a change to the verdict shape\n // cannot leave a stale copy in print. (It did once: `success` outlived its own removal in every\n // hand-written agent .md.)\n private verdictFormat(): string[] {\n return [\n 'TELL EACH subagent to write each of its verdict files with EXACTLY this format (there is NO \"success\" field —',\n 'it was removed; \"status\" is a tri-state so a reviewer can pass a change AND still raise a concern):',\n ...this.reviewJsonService.verdictSchemaFor('<the checklist id>', '', ' ').split('\\n'),\n ];\n }\n\n // The diff every reviewer judges. Stated once, here, because path matching is deliberately coarse and a\n // reviewer that only sees filenames cannot make the content-level call the checklist is asking for.\n private diffLines(context: ChecklistReviewContext): string[] {\n // NEVER omit these lines quietly. A reviewer given filenames but no way to read the change cannot make\n // the content-level judgment the checklist asks for, and a silently shorter instruction block is\n // indistinguishable from a complete one — so an unresolvable base is stated as the problem it is.\n if (context.baseSha.trim() === '') {\n return [\n '⚠️ No diff base resolved for this branch, so the exact `git diff` command could not be given.',\n ' Tell each reviewer to diff against main itself (`git diff $(git merge-base origin/main HEAD) HEAD -- <file>`)',\n ' and note that path matching is COARSE — judge the real change, not the path.',\n ];\n }\n const lines = ['Also give EACH one the real diff — path matching is COARSE, so judge the change, not the path:'];\n // The MATERIALIZED diff when it exists: it cannot go empty the way a hand-assembled range can. The\n // MANIFEST leads, and ALL.diff follows it, for the same reason the per-reviewer instructions were\n // reordered: whichever one is printed first and framed as \"everything\" is the only one that gets\n // opened — measured 4/4 — and ALL.diff is the one that can be silently incomplete.\n if (context.diffDir.trim() !== '') {\n lines.push(` path map, AUTHORITATIVE: ${context.diffDir}/manifest.json`);\n lines.push(' per file: status, bytes, absolute source + diff paths,');\n lines.push(' plus what was truncated or excluded');\n lines.push(` per-file diffs: ${context.diffDir}/files/`);\n lines.push(` combined view (may be capped): ${context.diffDir}/ALL.diff`);\n }\n // NEVER hand-assemble `<baseSha> HEAD` here. On a dirty tree the changed-file set is base→working\n // tree, so that range is empty and the reviewer is handed a command that shows it nothing — the\n // failure this whole class of field exists to prevent. The command is derived from the same basis\n // the file set was; when it is absent (an older pr-gate wrote the context), say so instead.\n if (context.fileDiffCommand.trim() !== '') {\n lines.push(` reproduce one file: ${context.fileDiffCommand}`);\n } else {\n lines.push(` ⚠️ no reproduce command recorded — derive it yourself from base ${context.baseSha},`);\n lines.push(' and check `git status` first: uncommitted work needs `git diff <base>` with NO head.');\n }\n if (context.dirty) {\n lines.push(' ⚠️ this diff INCLUDES uncommitted + untracked work, so it is not what a commit-to-commit');\n lines.push(' range would show. Judge it anyway — it is what the checklist matched against.');\n }\n if (context.prContextPath.trim() !== '') {\n lines.push(` full changed-file set + base/head sha: ${context.prContextPath}`);\n }\n return lines;\n }\n\n}\n"]}
1
+ {"version":3,"file":"checklist-instructions.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/checklist-instructions.ts"],"names":[],"mappings":"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","sourcesContent":["import { injectable, bindingScopeValues } from 'inversify';\nimport { formatFileList } from './checklist-config';\nimport {\n ChecklistReviewContext, RequiredChecklist, ReviewJsonService,\n} from './review-json';\n\n/**\n * Renders the ONE block that tells the coding agent which reviewer subagents it must run and exactly what\n * to tell them. There is a single renderer because three callers need the identical text and any drift\n * between them is a correctness bug, not a cosmetic one:\n *\n * - `wp-review-upsert-pr` — the AI asks \"what review do I owe on this diff?\"\n * - `wp-finish-upsert-pr` — fails fast, listing ONLY the reviewers that still have not run\n * - `ReviewJsonService` — the same list appended to a review.json validation failure\n *\n * Callers pass ONLY the checklists still needing a verdict. A checklist already reviewed on this branch is\n * never re-listed — re-instructing it invites a redundant second run, and (worse) reads as though the\n * earlier verdict did not count.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ChecklistInstructionsService {\n // Injected so the verdict path comes from the ONE place that knows it. This class used to hand-roll\n // dirname with a regex and a hardcoded '/', a second implementation of ReviewJsonService's\n // checklistResultPath that agreed on POSIX and disagreed on Windows — the same duplicate-logic trap as\n // the two different matched-file truncation caps.\n constructor(private readonly reviewJsonService: ReviewJsonService) {}\n\n /**\n * The full instruction block, or '' when nothing is pending (so a caller can concatenate it blindly).\n * `reviewPath` is the branch's review.json — each verdict file sits beside it as review-<id>.json.\n */\n render(pending: readonly RequiredChecklist[], reviewPath: string, context: ChecklistReviewContext): string {\n if (pending.length === 0) return '';\n if (pending[0].reviewer.maxAgents === 0) return this.spawnRule(pending).join('\\n');\n const lines: string[] = [...this.spawnRule(pending), ''];\n for (const req of pending) lines.push(...this.oneReviewer(req, reviewPath));\n lines.push('', ...this.verdictFormat());\n lines.push('', ...this.diffLines(context));\n return lines.join('\\n');\n }\n\n /**\n * HOW MANY subagents, of WHICH type: at most `reviewerAgents` of them, with the owed checklists grouped\n * across them as the AI judges best. Every subagent is the repo's ONE reviewer agent type, and every\n * checklist still gets its own verdict file no matter how they were grouped.\n */\n spawnRule(pending: readonly RequiredChecklist[]): string[] {\n const reviewer = pending[0].reviewer;\n if (reviewer.maxAgents === 0) {\n return [\n 'Reviewer-agent reviews are disabled for this project (commands.pr-gate.reviewerAgents = 0).',\n 'Do not spawn reviewer subagents and do not write checklist verdict files.',\n ];\n }\n const own = [\n 'You may NOT review your own work, and you may NOT write a reviewer\\'s verdict file on its behalf.',\n ];\n const cap = Math.min(reviewer.maxAgents, pending.length);\n return [\n `You MUST run these ${pending.length} checklist review(s) using AT MOST ${cap} \\`${reviewer.agentName}\\` subagent(s)`,\n `(commands.pr-gate.reviewerAgents = ${reviewer.maxAgents}). ${this.groupingHint(pending.length, cap)}`,\n 'Give each subagent every checklist it covers (doc, in-scope files, verdict path); it writes ONE verdict',\n 'file per checklist it covers, and never one for a checklist it was not given.',\n ...own,\n ];\n }\n\n /**\n * The grouping sentence, with an example that fits THIS round's numbers — a cap of 1 has no choice to\n * make, and saying \"group them as you judge best\" there would invite a second subagent.\n */\n groupingHint(count: number, cap: number): string {\n if (cap <= 1) return 'Use ONE subagent for all of them.';\n const per = Math.ceil(count / cap);\n return `Group the checklists across them as you judge best — e.g. one subagent for all of them, or ${cap} with about ${per} each.`;\n }\n\n // Just the checklist IDS, for a caller that wants a one-line summary rather than the whole block.\n names(pending: readonly RequiredChecklist[]): string {\n return pending.map((r: RequiredChecklist): string => r.id).join(', ');\n }\n\n // What the subagent reviewing ONE checklist must be given: its doc, why it is running + over what, and\n // the file it must write.\n private oneReviewer(req: RequiredChecklist, reviewPath: string): string[] {\n const lines = [` • checklist ${req.id}`];\n // The doc is REPO-relative by the time it reaches here (see ChecklistDefinition.doc), so a subagent\n // handed this string can actually open it. Printing the raw config value would not resolve.\n if (req.doc.trim() !== '') lines.push(` doc to read: ${req.doc}`);\n for (const scopeLine of this.scope(req)) lines.push(` ${scopeLine}`);\n lines.push(` must write: ${this.reviewJsonService.checklistResultPath(reviewPath, req.id)}`);\n return lines;\n }\n\n /**\n * WHY this reviewer is running, and over what. NOT every checklist is pattern-matched: one with no\n * `patterns` runs on EVERY PR, and calling its file list \"matched\" implies the list is a narrow,\n * pre-filtered slice of the diff when it is in fact the whole thing. When patterns DID fire, they are\n * named — the reviewer cannot otherwise tell a precise migrations-only glob from a blanket match-all one.\n */\n private scope(req: RequiredChecklist): string[] {\n if (req.matchedPatterns.length === 0) {\n return [\n `in scope: ALWAYS RUNS — this checklist has no patterns, so the WHOLE diff is in scope`,\n ` all ${req.matchedFiles.length} changed file(s): ${formatFileList(req.matchedFiles)}`,\n ];\n }\n const globs = req.matchedPatterns.map((p: string): string => `\"${p}\"`).join(', ');\n return [\n `in scope: ${req.matchedFiles.length} file(s) matched ${globs}`,\n ` ${formatFileList(req.matchedFiles)}`,\n ];\n }\n\n // ONE shared format block for every reviewer, rather than repeating the schema under each name — and\n // the schema itself comes from ReviewJsonService, the single renderer, so a change to the verdict shape\n // cannot leave a stale copy in print. (It did once: `success` outlived its own removal in every\n // hand-written agent .md.)\n private verdictFormat(): string[] {\n return [\n 'TELL EACH subagent to write each of its verdict files with EXACTLY this format (there is NO \"success\" field —',\n 'it was removed; \"status\" is a tri-state so a reviewer can pass a change AND still raise a concern):',\n ...this.reviewJsonService.verdictSchemaFor('<the checklist id>', '', ' ').split('\\n'),\n ];\n }\n\n // The diff every reviewer judges. Stated once, here, because path matching is deliberately coarse and a\n // reviewer that only sees filenames cannot make the content-level call the checklist is asking for.\n private diffLines(context: ChecklistReviewContext): string[] {\n // NEVER omit these lines quietly. A reviewer given filenames but no way to read the change cannot make\n // the content-level judgment the checklist asks for, and a silently shorter instruction block is\n // indistinguishable from a complete one — so an unresolvable base is stated as the problem it is.\n if (context.baseSha.trim() === '') {\n return [\n '⚠️ No diff base resolved for this branch, so the exact `git diff` command could not be given.',\n ' Tell each reviewer to diff against main itself (`git diff $(git merge-base origin/main HEAD) HEAD -- <file>`)',\n ' and note that path matching is COARSE — judge the real change, not the path.',\n ];\n }\n const lines = ['Also give EACH one the real diff — path matching is COARSE, so judge the change, not the path:'];\n // The MATERIALIZED diff when it exists: it cannot go empty the way a hand-assembled range can. The\n // MANIFEST leads, and ALL.diff follows it, for the same reason the per-reviewer instructions were\n // reordered: whichever one is printed first and framed as \"everything\" is the only one that gets\n // opened — measured 4/4 — and ALL.diff is the one that can be silently incomplete.\n if (context.diffDir.trim() !== '') {\n lines.push(` path map, AUTHORITATIVE: ${context.diffDir}/manifest.json`);\n lines.push(' per file: status, bytes, absolute source + diff paths,');\n lines.push(' plus what was truncated or excluded');\n lines.push(` per-file diffs: ${context.diffDir}/files/`);\n lines.push(` combined view (may be capped): ${context.diffDir}/ALL.diff`);\n }\n // NEVER hand-assemble `<baseSha> HEAD` here. On a dirty tree the changed-file set is base→working\n // tree, so that range is empty and the reviewer is handed a command that shows it nothing — the\n // failure this whole class of field exists to prevent. The command is derived from the same basis\n // the file set was; when it is absent (an older pr-gate wrote the context), say so instead.\n if (context.fileDiffCommand.trim() !== '') {\n lines.push(` reproduce one file: ${context.fileDiffCommand}`);\n } else {\n lines.push(` ⚠️ no reproduce command recorded — derive it yourself from base ${context.baseSha},`);\n lines.push(' and check `git status` first: uncommitted work needs `git diff <base>` with NO head.');\n }\n if (context.dirty) {\n lines.push(' ⚠️ this diff INCLUDES uncommitted + untracked work, so it is not what a commit-to-commit');\n lines.push(' range would show. Judge it anyway — it is what the checklist matched against.');\n }\n if (context.prContextPath.trim() !== '') {\n lines.push(` full changed-file set + base/head sha: ${context.prContextPath}`);\n }\n return lines;\n }\n\n}\n"]}
@@ -286,11 +286,11 @@ function buildPrGateConfig(section) {
286
286
  // cannot reach us: validateChecklistsSection has already failed the load.
287
287
  // The webpieces reviewer unless `overrideReviewerAgent` is true, in which case `reviewerAgentName` names the
288
288
  // agent (validatePrGateSection rejects a name without the override, and the override without a name).
289
- // Optional positive integer cap; absent one subagent per checklist.
289
+ // Required nonnegative integer: 0 disables reviewer-agent reviews; positive values cap the round.
290
290
  const agentName = raw.overrideReviewerAgent === true
291
291
  ? (raw.reviewerAgentName ?? '').trim()
292
292
  : checklist_config_1.DEFAULT_REVIEWER_AGENT_NAME;
293
- // `reviewerAgents` is REQUIRED and validated as a positive integer before this runs, so the fallback is
293
+ // `reviewerAgents` is REQUIRED and validated as a nonnegative integer before this runs, so the fallback is
294
294
  // unreachable in a loaded config and exists only to keep this total for a structure-only caller.
295
295
  const reviewer = new checklist_config_1.ReviewerAgentPolicy(agentName, typeof raw.reviewerAgents === 'number' ? raw.reviewerAgents : checklist_config_1.REVIEWER_AGENTS_PLACEHOLDER);
296
296
  const checklists = Array.isArray(raw.checklists)
@@ -1 +1 @@
1
- {"version":3,"file":"pr-gate-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/pr-gate-config.ts"],"names":[],"mappings":";;;AAqFA,kDAEC;AA6CD,wDAEC;AA2HD,oCAOC;AAED,kDAGC;AAsDD,8CA4CC;AAQD,oDAQC;AAQD,8CAMC;AA7YD,uDAAoF;AACpF,yDAE4B;AAE5B,2FAA2F;AAC3F,2FAA2F;AAC3F,iFAAiF;AACjF,iGAAiG;AAEjG;;;;;;;;;;;GAWG;AACU,QAAA,qBAAqB,GAAG,wEAAwE,CAAC;AAE9G,MAAa,cAAc;IACvB,IAAI,CAAS;IACb,QAAQ,CAAW;IACnB,iGAAiG;IACjG,8FAA8F;IAC9F,+FAA+F;IAC/F,gGAAgG;IAChG,YAAY,CAAS,CAAC,mBAAmB;IACzC,2FAA2F;IAC3F,yFAAyF;IACzF,QAAQ,CAAU;IAElB,YAAY,IAAY,EAAE,QAAkB,EAAE,YAAoB,EAAE,QAAQ,GAAG,KAAK;QAChF,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC7B,CAAC;CACJ;AAlBD,wCAkBC;AAED,qGAAqG;AACrG,mGAAmG;AACnG,sGAAsG;AACtG,sGAAsG;AACtG,sGAAsG;AACzF,QAAA,eAAe,GAAG,MAAM,CAAC;AACzB,QAAA,eAAe,GAAG,MAAM,CAAC;AACzB,QAAA,WAAW,GAAG,CAAC,uBAAe,EAAE,uBAAe,CAAC,CAAC;AAE9D;;;;;;GAMG;AACH;;;GAGG;AACH,MAAa,kBAAkB;IAC3B,KAAK,CAAS;IACd,IAAI,CAAS,CAAC,uFAAuF;IAErG,YAAY,KAAa,EAAE,SAAiB;QACxC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;IAC1B,CAAC;CACJ;AARD,gDAQC;AAED,MAAa,YAAY;IACrB,+FAA+F;IAC/F,+DAA+D;IAC/D,eAAe,CAAS;IAExB,YAAY,kBAA0B,8CAA4B;QAC9D,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;IAC3C,CAAC;CACJ;AARD,oCAQC;AAED,oIAAoI;AACpI,SAAgB,mBAAmB;IAC/B,OAAO,IAAI,YAAY,CAAC,8CAA4B,CAAC,CAAC;AAC1D,CAAC;AAED,qGAAqG;AACrG,sGAAsG;AACtG,iDAAiD;AACpC,QAAA,4BAA4B,GAAG,aAAa,CAAC;AAC7C,QAAA,kBAAkB,GAAG,KAAK,CAAC;AAExC;;;;;;;;;;;;GAYG;AACH,MAAa,eAAe;IACxB,sGAAsG;IACtG,mDAAmD;IACnD,eAAe,CAAS;IACxB,qGAAqG;IACrG,SAAS,CAAS;IAElB,YAAY,kBAA0B,oCAA4B,EAAE,YAAoB,0BAAkB;QACtG,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QACvC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC/B,CAAC;IAED,6FAA6F;IAC7F,UAAU,CAAC,MAAc;QACrB,OAAO,GAAG,IAAI,CAAC,eAAe,IAAI,MAAM,EAAE,CAAC;IAC/C,CAAC;IAED,qFAAqF;IACrF,WAAW;QACP,OAAO,GAAG,IAAI,CAAC,eAAe,IAAI,CAAC;IACvC,CAAC;CACJ;AArBD,0CAqBC;AAED,oIAAoI;AACpI,SAAgB,sBAAsB;IAClC,OAAO,IAAI,eAAe,CAAC,oCAA4B,EAAE,0BAAkB,CAAC,CAAC;AACjF,CAAC;AAED,MAAa,YAAY;IACrB,IAAI,CAAS;IACb;;;;;;;;;;;;OAYG;IACH,YAAY,CAAS;IACrB,KAAK,CAAmB;IACxB;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAS;IAClB,yGAAyG;IACzG,2GAA2G;IAC3G,sGAAsG;IACtG,8DAA8D;IAC9D,UAAU,CAAwB;IAClC;;;;;;;;OAQG;IACH,QAAQ,GAAwB,IAAI,sCAAmB,CAAC,8CAA2B,EAAE,8CAA2B,CAAC,CAAC;IAClH,iGAAiG;IACjG,8FAA8F;IAC9F,iBAAiB,CAAU;IAC3B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAS;IACjB;;;;;OAKG;IACH,MAAM,GAAiB,mBAAmB,EAAE,CAAC;IAC7C;;;;;;;;;;OAUG;IACH,iBAAiB,GAAa,EAAE,CAAC;IACjC;;;;OAIG;IACH,aAAa,GAAyB,EAAE,CAAC;IACzC;;;;OAIG;IACH,qBAAqB,GAAa,EAAE,CAAC;IACrC;;;;;OAKG;IACH,mBAAmB,GAAG,KAAK,CAAC;IAC5B;;;OAGG;IACH,SAAS,GAAoB,sBAAsB,EAAE,CAAC;IAEtD,yDAAyD;IACzD,YAAY,IAAY,EAAE,YAAoB,EAAE,KAAuB,EAAE,SAAiB,EAAE,aAAoC,EAAE,EAAE,QAAQ,GAAG,EAAE,EAAE,iBAAiB,GAAG,IAAI;QACvK,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,iBAAiB,GAAG,iBAAiB,CAAC;IAC/C,CAAC;CACJ;AArHD,oCAqHC;AAED,0FAA0F;AAC1F,qEAAqE;AACrE,SAAgB,YAAY;IACxB,OAAO;QACH,IAAI,cAAc,CAAC,aAAa,EAAE,CAAC,mBAAmB,EAAE,YAAY,CAAC,EAAE,QAAQ,CAAC;QAChF,IAAI,cAAc,CAAC,sBAAsB,EAAE,CAAC,iBAAiB,EAAE,mBAAmB,EAAE,SAAS,EAAE,eAAe,CAAC,EAAE,QAAQ,CAAC;QAC1H,IAAI,cAAc,CAAC,0BAA0B,EAAE,CAAC,gCAAgC,CAAC,EAAE,QAAQ,CAAC;QAC5F,IAAI,cAAc,CAAC,wBAAwB,EAAE,CAAC,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,uBAAuB,CAAC,EAAE,QAAQ,CAAC;KACpI,CAAC;AACN,CAAC;AAED,SAAgB,mBAAmB;IAC/B,0FAA0F;IAC1F,OAAO,IAAI,YAAY,CAAC,IAAI,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,uBAAe,EAAE,EAAE,CAAC,CAAC;AAC3E,CAAC;AA2CD,SAAS,MAAM,CAAC,GAAY;IACxB,OAAO,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,GAAG,CAAC,YAAY,IAAI,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC;AACvH,CAAC;AAED;;;;;GAKG;AACH,4FAA4F;AAC5F,SAAgB,iBAAiB,CAAC,OAAgB;IAC9C,MAAM,QAAQ,GAAG,mBAAmB,EAAE,CAAC;IACvC,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAE9F,MAAM,GAAG,GAAG,OAA2B,CAAC;IACxC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC;IACvC,MAAM,YAAY,GAAG,GAAG,CAAC,YAAY,IAAI,QAAQ,CAAC,YAAY,CAAC;IAC/D,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;IAC/E,gGAAgG;IAChG,wEAAwE;IACxE,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,IAAI,QAAQ,CAAC,SAAS,CAAC;IACtD,yGAAyG;IACzG,0EAA0E;IAC1E,6GAA6G;IAC7G,sGAAsG;IACtG,sEAAsE;IACtE,MAAM,SAAS,GAAG,GAAG,CAAC,qBAAqB,KAAK,IAAI;QAChD,CAAC,CAAC,CAAC,GAAG,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;QACtC,CAAC,CAAC,8CAA2B,CAAC;IAClC,wGAAwG;IACxG,iGAAiG;IACjG,MAAM,QAAQ,GAAG,IAAI,sCAAmB,CACpC,SAAS,EACT,OAAO,GAAG,CAAC,cAAc,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,8CAA2B,CAAC,CAAC;IAC/F,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;QAC5C,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,IAAsB,EAAuB,EAAE,CAAC,IAAA,8BAAW,EAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAClG,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC;IAC1B,8FAA8F;IAC9F,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC;IACnD,yEAAyE;IACzE,MAAM,iBAAiB,GAAG,GAAG,CAAC,iBAAiB,IAAI,QAAQ,CAAC,iBAAiB,CAAC;IAC9E,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IAC9G,KAAK,CAAC,MAAM,GAAG,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7C,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC1B,wGAAwG;IACxG,mFAAmF;IACnF,KAAK,CAAC,iBAAiB,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,iBAAiB,CAAC;IACpH,KAAK,CAAC,qBAAqB,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,CAAC,QAAQ,CAAC,qBAAqB,CAAC;IACpI,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;QAClD,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAmB,EAAsB,EAAE,CAAC,IAAI,kBAAkB,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QACzH,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC;IAC7B,KAAK,CAAC,mBAAmB,GAAG,GAAG,CAAC,mBAAmB,IAAI,QAAQ,CAAC,mBAAmB,CAAC;IACpF,KAAK,CAAC,SAAS,GAAG,oBAAoB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACtD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,gHAAgH;AAChH,SAAgB,oBAAoB,CAAC,GAA6B;IAC9D,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;IAC1C,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAClF,MAAM,SAAS,GAAG,OAAO,GAAG,CAAC,eAAe,KAAK,QAAQ,IAAI,GAAG,CAAC,eAAe,CAAC,IAAI,EAAE,KAAK,EAAE;QAC1F,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC;IAC5D,MAAM,SAAS,GAAG,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ,IAAI,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE;QAC9E,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC;IAChD,OAAO,IAAI,eAAe,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;AACrD,CAAC;AAED;;;;GAIG;AACH,gHAAgH;AAChH,SAAgB,iBAAiB,CAAC,GAA0B;IACxD,MAAM,QAAQ,GAAG,mBAAmB,EAAE,CAAC;IACvC,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAClF,MAAM,SAAS,GAAG,GAAG,CAAC,eAAe,CAAC;IACtC,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,mCAAiB,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC7F,OAAO,IAAI,YAAY,CAAC,SAAS,CAAC,CAAC;AACvC,CAAC","sourcesContent":["import { BRANCH_RETENTION_ARCHIVE_TAG, BRANCH_RETENTIONS } from './branch-archiver';\nimport {\n ChecklistDefinition, DEFAULT_REVIEWER_AGENT_NAME, RawChecklistItem, REVIEWER_AGENTS_PLACEHOLDER, 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 * The ONE agent type every checklist is reviewed by — `webpieces-reviewer` unless\n * `overrideReviewerAgent` is true, in which case `reviewerAgentName` — and `reviewerAgents`, the most such\n * subagents a round may use. The same instance is bound into every {@link checklists} entry, so a caller\n * holding only a checklist still knows what to spawn. Field-with-default because the constructor is at\n * max-params; the agent name defaults to the webpieces reviewer, exactly what a config that says nothing\n * gets, while the CAP is a required field, so the placeholder here is only ever overwritten by the real\n * number buildPrGateConfig reads.\n */\n reviewer: ReviewerAgentPolicy = new ReviewerAgentPolicy(DEFAULT_REVIEWER_AGENT_NAME, REVIEWER_AGENTS_PLACEHOLDER);\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 overrideReviewerAgent?: boolean;\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 // The webpieces reviewer unless `overrideReviewerAgent` is true, in which case `reviewerAgentName` names the\n // agent (validatePrGateSection rejects a name without the override, and the override without a name).\n // Optional positive integer cap; absent ⇒ one subagent per checklist.\n const agentName = raw.overrideReviewerAgent === true\n ? (raw.reviewerAgentName ?? '').trim()\n : DEFAULT_REVIEWER_AGENT_NAME;\n // `reviewerAgents` is REQUIRED and validated as a positive integer before this runs, so the fallback is\n // unreachable in a loaded config and exists only to keep this total for a structure-only caller.\n const reviewer = new ReviewerAgentPolicy(\n agentName,\n typeof raw.reviewerAgents === 'number' ? raw.reviewerAgents : REVIEWER_AGENTS_PLACEHOLDER);\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"]}
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, DEFAULT_REVIEWER_AGENT_NAME, RawChecklistItem, REVIEWER_AGENTS_PLACEHOLDER, 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 * The ONE agent type every checklist is reviewed by — `webpieces-reviewer` unless\n * `overrideReviewerAgent` is true, in which case `reviewerAgentName` — and `reviewerAgents`, the most such\n * subagents a round may use. The same instance is bound into every {@link checklists} entry, so a caller\n * holding only a checklist still knows what to spawn. Field-with-default because the constructor is at\n * max-params; the agent name defaults to the webpieces reviewer, exactly what a config that says nothing\n * gets, while the CAP is a required field, so the placeholder here is only ever overwritten by the real\n * number buildPrGateConfig reads.\n */\n reviewer: ReviewerAgentPolicy = new ReviewerAgentPolicy(DEFAULT_REVIEWER_AGENT_NAME, REVIEWER_AGENTS_PLACEHOLDER);\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 overrideReviewerAgent?: boolean;\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 // The webpieces reviewer unless `overrideReviewerAgent` is true, in which case `reviewerAgentName` names the\n // agent (validatePrGateSection rejects a name without the override, and the override without a name).\n // Required nonnegative integer: 0 disables reviewer-agent reviews; positive values cap the round.\n const agentName = raw.overrideReviewerAgent === true\n ? (raw.reviewerAgentName ?? '').trim()\n : DEFAULT_REVIEWER_AGENT_NAME;\n // `reviewerAgents` is REQUIRED and validated as a nonnegative integer before this runs, so the fallback is\n // unreachable in a loaded config and exists only to keep this total for a structure-only caller.\n const reviewer = new ReviewerAgentPolicy(\n agentName,\n typeof raw.reviewerAgents === 'number' ? raw.reviewerAgents : REVIEWER_AGENTS_PLACEHOLDER);\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"]}
@@ -29,7 +29,7 @@ export declare const OVERRIDE_REVIEWER_AGENT_KEY = "overrideReviewerAgent";
29
29
  export declare const REVIEWER_AGENTS_KEY = "reviewerAgents";
30
30
  /**
31
31
  * `overrideReviewerAgent` (optional boolean, absent == false), `reviewerAgentName` (only with the override)
32
- * and `reviewerAgents` (optional positive integer).
32
+ * and `reviewerAgents` (required nonnegative integer; zero disables reviewer-agent reviews).
33
33
  *
34
34
  * The reviewer agent defaults to {@link DEFAULT_REVIEWER_AGENT_NAME} — the generic reviewer webpieces ships
35
35
  * and manages — so a new repo writes NOTHING here (issue #947). Replacing it changes how every PR is
@@ -183,7 +183,7 @@ exports.OVERRIDE_REVIEWER_AGENT_KEY = 'overrideReviewerAgent';
183
183
  exports.REVIEWER_AGENTS_KEY = 'reviewerAgents';
184
184
  /**
185
185
  * `overrideReviewerAgent` (optional boolean, absent == false), `reviewerAgentName` (only with the override)
186
- * and `reviewerAgents` (optional positive integer).
186
+ * and `reviewerAgents` (required nonnegative integer; zero disables reviewer-agent reviews).
187
187
  *
188
188
  * The reviewer agent defaults to {@link DEFAULT_REVIEWER_AGENT_NAME} — the generic reviewer webpieces ships
189
189
  * and manages — so a new repo writes NOTHING here (issue #947). Replacing it changes how every PR is
@@ -227,14 +227,15 @@ function validateReviewerAgentKeys(s, repoRoot) {
227
227
  }
228
228
  const agentKeysValid = errors.length === 0;
229
229
  errors.push(...reviewerAgentsErrors(s));
230
- if (repoRoot !== undefined && agentKeysValid) {
230
+ // A disabled project never spawns this agent, so requiring its file would make `0` an incomplete opt-out.
231
+ if (repoRoot !== undefined && agentKeysValid && s[exports.REVIEWER_AGENTS_KEY] !== 0) {
231
232
  const agentName = override === true ? name.trim() : checklist_config_1.DEFAULT_REVIEWER_AGENT_NAME;
232
233
  errors.push(...new checklist_validator_1.ChecklistValidator().validateReviewerAgent(repoRoot, new checklist_config_1.ReviewerAgentPolicy(agentName, checklist_config_1.REVIEWER_AGENTS_PLACEHOLDER)));
233
234
  }
234
235
  return errors;
235
236
  }
236
237
  /**
237
- * `reviewerAgents`: present, and a positive integer. Missing and wrong-typed get DIFFERENT messages — the
238
+ * `reviewerAgents`: present, and a nonnegative integer. Missing and wrong-typed get DIFFERENT messages — the
238
239
  * missing one has to teach a reader what the number means and what to weigh when picking it, because they
239
240
  * have never seen the key; the wrong-typed one only has to name the constraint they just broke.
240
241
  */
@@ -247,18 +248,19 @@ function reviewerAgentsErrors(s) {
247
248
  `[pr-gate] Missing required field "${exports.REVIEWER_AGENTS_KEY}" — the MOST reviewer subagents one review ` +
248
249
  `round may use. Add this line to commands.pr-gate in webpieces.config.json:\n` +
249
250
  ` "${exports.REVIEWER_AGENTS_KEY}": 1,\n` +
250
- ` 1 = ONE subagent reviews every owed checklist (cheapest; it is handed each checklist's instructions ` +
251
- `file and still writes one verdict file per checklist). A HIGHER number buys independent readers ` +
251
+ ` 0 = reviewer-agent reviews are disabled for this project while the build and PR gate stay active. ` +
252
+ `1 = ONE subagent reviews every owed checklist (cheapest enabled setting; it is handed each checklist's ` +
253
+ `instructions file and still writes one verdict file per checklist). A HIGHER number buys independent readers — ` +
252
254
  `set it to your checklist count for one subagent each — at that many times the tokens per PR. There is ` +
253
255
  `deliberately no default: this sets the price of every review round, and an unchosen price is what ` +
254
256
  `makes a gate expensive enough that people stop running it.`,
255
257
  ];
256
258
  }
257
- if (!(typeof max === 'number' && Number.isInteger(max) && max >= 1)) {
259
+ if (!(typeof max === 'number' && Number.isInteger(max) && max >= 0)) {
258
260
  return [
259
- `[pr-gate] "${exports.REVIEWER_AGENTS_KEY}" = ${JSON.stringify(max)} is not valid — it must be a positive integer: ` +
260
- `the MOST reviewer subagents one review round may use, with the owed checklists grouped across them ` +
261
- `(1 = a single subagent reviews every checklist).`,
261
+ `[pr-gate] "${exports.REVIEWER_AGENTS_KEY}" = ${JSON.stringify(max)} is not valid — it must be a nonnegative integer: ` +
262
+ `0 disables reviewer-agent reviews for this project while leaving the build and PR gate active; a positive ` +
263
+ `value is the MOST reviewer subagents one review round may use, with owed checklists grouped across them.`,
262
264
  ];
263
265
  }
264
266
  return [];
@@ -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 {\n ChecklistDefinition, DEFAULT_REVIEWER_AGENT_NAME, RawChecklistItem, REVIEWER_AGENTS_PLACEHOLDER,\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 ' \"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\\n' +\n ' webpieces-reviewer agent (or your own: \"overrideReviewerAgent\": true + \"reviewerAgentName\"), against\\n' +\n ' its own \"doc\" (REQUIRED, 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\\n' +\n ' it an OPTIONAL review the human is offered and may decline (but if they DO run it, a red verdict\\n' +\n ' 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 `overrideReviewerAgent` / `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_PLACEHOLDER);\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 OVERRIDE_REVIEWER_AGENT_KEY = 'overrideReviewerAgent';\nexport const REVIEWER_AGENTS_KEY = 'reviewerAgents';\n\n/**\n * `overrideReviewerAgent` (optional boolean, absent == false), `reviewerAgentName` (only with the override)\n * and `reviewerAgents` (optional positive integer).\n *\n * The reviewer agent defaults to {@link DEFAULT_REVIEWER_AGENT_NAME} — the generic reviewer webpieces ships\n * and manages — so a new repo writes NOTHING here (issue #947). Replacing it changes how every PR is\n * reviewed, so it must be said out loud with `\"overrideReviewerAgent\": true` (greppable) AND a\n * `reviewerAgentName`. A name WITHOUT the override is rejected rather than silently ignored: a key that looks\n * meaningful but controls nothing is a second spelling of a decision (.claude/rules/no-backwards-compat.md).\n *\n * `reviewerAgents` is REQUIRED while the gate is active, for the same reason `required` and `mergeMode` have\n * no default: it sets the PRICE of every review round, and a price nobody chose is the one that stops the\n * gate being run. It used to be optional, and absent meant one SEPARATE subagent per checklist — so a repo\n * with four required checklists charged a one-line typo fix four full reviewer spawns re-reading the same\n * diff, silently, having never been asked. The absent branch is GONE rather than kept as a fallback: an\n * accepted shape is never migrated, and the number is the whole decision the key exists to record.\n */\n// webpieces-disable no-any-unknown -- the already-narrowed opaque pr-gate section; three 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 override = s[OVERRIDE_REVIEWER_AGENT_KEY];\n const hasName = REVIEWER_AGENT_NAME_KEY in s;\n const name = s[REVIEWER_AGENT_NAME_KEY];\n const named = typeof name === 'string' && name.trim() !== '';\n if (OVERRIDE_REVIEWER_AGENT_KEY in s && typeof override !== 'boolean') {\n errors.push(\n `[pr-gate] \"${OVERRIDE_REVIEWER_AGENT_KEY}\" = ${JSON.stringify(override)} is not valid — it must be true or ` +\n `false. Delete the key to use the webpieces reviewer (${DEFAULT_REVIEWER_AGENT_NAME}); set it to true, together ` +\n `with \"${REVIEWER_AGENT_NAME_KEY}\", to use an agent of your own.`);\n } else if (override === true && !named) {\n errors.push(\n `[pr-gate] \"${OVERRIDE_REVIEWER_AGENT_KEY}\": true needs \"${REVIEWER_AGENT_NAME_KEY}\" — add the agent type every ` +\n `reviewer subagent is spawned as (its .claude/agents/<name>.md must exist) to commands.pr-gate in ` +\n `webpieces.config.json:\\n` +\n ` \"${REVIEWER_AGENT_NAME_KEY}\": \"my-reviewer\",\\n` +\n ` Or delete \"${OVERRIDE_REVIEWER_AGENT_KEY}\" to use the webpieces reviewer (${DEFAULT_REVIEWER_AGENT_NAME}).`);\n } else if (override !== true && hasName) {\n errors.push(\n `[pr-gate] \"${REVIEWER_AGENT_NAME_KEY}\" is set but \"${OVERRIDE_REVIEWER_AGENT_KEY}\" is not true, so it would be ` +\n `ignored. The reviewer agent defaults to ${DEFAULT_REVIEWER_AGENT_NAME} (.claude/agents/${DEFAULT_REVIEWER_AGENT_NAME}.md, ` +\n `written by \\`${UPGRADE_SHIM_COMMAND}\\`). Either:\\n` +\n ` • remove \"${REVIEWER_AGENT_NAME_KEY}\" from commands.pr-gate in webpieces.config.json to use that default, or\\n` +\n ` • set \"${OVERRIDE_REVIEWER_AGENT_KEY}\": true beside it to review with your own agent.`);\n }\n const agentKeysValid = errors.length === 0;\n errors.push(...reviewerAgentsErrors(s));\n if (repoRoot !== undefined && agentKeysValid) {\n const agentName = override === true ? (name as string).trim() : DEFAULT_REVIEWER_AGENT_NAME;\n errors.push(...new ChecklistValidator().validateReviewerAgent(\n repoRoot, new ReviewerAgentPolicy(agentName, REVIEWER_AGENTS_PLACEHOLDER)));\n }\n return errors;\n}\n\n/**\n * `reviewerAgents`: present, and a positive integer. Missing and wrong-typed get DIFFERENT messages — the\n * missing one has to teach a reader what the number means and what to weigh when picking it, because they\n * have never seen the key; the wrong-typed one only has to name the constraint they just broke.\n */\n// webpieces-disable no-any-unknown -- the already-narrowed opaque pr-gate section; one key is read\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction reviewerAgentsErrors(s: Record<string, unknown>): string[] {\n const max = s[REVIEWER_AGENTS_KEY];\n if (!(REVIEWER_AGENTS_KEY in s)) {\n return [\n `[pr-gate] Missing required field \"${REVIEWER_AGENTS_KEY}\" — the MOST reviewer subagents one review ` +\n `round may use. Add this line to commands.pr-gate in webpieces.config.json:\\n` +\n ` \"${REVIEWER_AGENTS_KEY}\": 1,\\n` +\n ` 1 = ONE subagent reviews every owed checklist (cheapest; it is handed each checklist's instructions ` +\n `file and still writes one verdict file per checklist). A HIGHER number buys independent readers — ` +\n `set it to your checklist count for one subagent each — at that many times the tokens per PR. There is ` +\n `deliberately no default: this sets the price of every review round, and an unchosen price is what ` +\n `makes a gate expensive enough that people stop running it.`,\n ];\n }\n if (!(typeof max === 'number' && Number.isInteger(max) && max >= 1)) {\n return [\n `[pr-gate] \"${REVIEWER_AGENTS_KEY}\" = ${JSON.stringify(max)} is not valid — it must be a positive integer: ` +\n `the MOST reviewer subagents one review round may use, with the owed checklists grouped across them ` +\n `(1 = a single subagent reviews every checklist).`,\n ];\n }\n return [];\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":";;;AAkCA,kEASC;AAoCD,8DAIC;AAoID,8DAmCC;AAwCD,sDAkBC;AAmBD,4DA0BC;AAjWD,uDAK2B;AAC3B,+DAA2D;AAC3D,yDAG4B;AAC5B,2CAAmD;AACnD,+DAA2D;AAE3D,yGAAyG;AACzG,sGAAsG;AACtG,oEAAoE;AAEpE,2GAA2G;AAC3G,wGAAwG;AACxG,MAAM,aAAa,GAAG,aAAa,CAAC;AAEpC;;;;;;;;;GASG;AACH,6GAA6G;AAC7G,8GAA8G;AAC9G,SAAgB,2BAA2B,CAAC,CAA0B;IAClE,IAAI,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IACrC,OAAO;QACH,yBAAyB,aAAa,mEAAmE;YACzG,wGAAwG;YACxG,yGAAyG;YACzG,sGAAsG;YACtG,0EAA0E;KAC7E,CAAC;AACN,CAAC;AAED,MAAM,iBAAiB,GAAG,CACtB,YAAY;IACZ,uBAAuB;IACvB,kCAAkC;IAClC,qDAAqD;IACrD,yDAAyD;IACzD,8BAA8B;IAC9B,SAAS;IACT,gGAAgG;IAChG,0GAA0G;IAC1G,8FAA8F;IAC9F,uGAAuG;IACvG,sGAAsG;IACtG,kBAAkB,CACrB,CAAC;AAEF,qFAAqF;AACxE,QAAA,qBAAqB,GAAG,sBAAsB,CAAC;AAE5D;;;;;;;;;;;;GAYG;AACH,2FAA2F;AAC3F,8GAA8G;AAC9G,SAAgB,yBAAyB,CAAC,KAAc,EAAE,QAAiB;IACvE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,sBAAsB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACzE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,IAAI,KAAK;QAAE,OAAO,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC;IACvG,OAAO,CAAC,wFAAwF,iBAAiB,EAAE,CAAC,CAAC;AACzH,CAAC;AAED;;;;GAIG;AACH,wGAAwG;AACxG,8GAA8G;AAC9G,SAAS,mBAAmB,CAAC,KAAa;IACtC,MAAM,GAAG,GAAI,KAAiC,CAAC,KAAK,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,yBAAyB,CAAC;IAC9F,OAAO,CACH,mDAAmD,MAAM,qDAAqD;QAC9G,uFAAuF;QACvF,gBAAgB,MAAM,qFAAqF;QAC3G,2CAA2C,MAAM,0CAA0C;QAC3F,4FAA4F;QAC5F,WAAW,MAAM,mFAAmF;QACpG,qEAAqE,MAAM,sBAAsB;QACjG,KAAK,iBAAiB,EAAE,CAC3B,CAAC;AACN,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,qGAAqG;AACrG,8GAA8G;AAC9G,SAAS,iBAAiB,CAAC,CAA0B,EAAE,CAAS;IAC5D,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3F,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO;YACH,wBAAwB,CAAC,IAAI,IAAI,qEAAqE;gBACtG,sCAAsC;gBACtC,qGAAqG;gBACrG,sFAAsF;gBACtF,sGAAsG;gBACtG,iGAAiG;gBACjG,KAAK,iBAAiB,EAAE;SAC3B,CAAC;IACN,CAAC;IACD,IAAI,OAAO,CAAC,CAAC,UAAU,CAAC,KAAK,SAAS,EAAE,CAAC;QACrC,OAAO,CAAC,wBAAwB,CAAC,IAAI,IAAI,2FAA2F,CAAC,CAAC;IAC1I,CAAC;IACD,OAAO,EAAE,CAAC;AACd,CAAC;AAED,6GAA6G;AAC7G,yGAAyG;AACzG,2FAA2F;AAC3F,8GAA8G;AAC9G,SAAS,oBAAoB,CAAC,CAA0B,EAAE,CAAS;IAC/D,MAAM,MAAM,GAAG,IAAA,wCAAkB,EAAC,CAAC,EAAE,6BAAqB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAS,EAAU,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAChH,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;QACvD,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,yEAAyE,CAAC,CAAC;IACpH,CAAC;IACD,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,KAAK,CAAC,KAAK,QAAQ,EAAE,CAAC;QACzD,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,kFAAkF,CAAC,CAAC;IAC7H,CAAC;IACD,yFAAyF;IACzF,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,SAAS,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,CAAU,EAAW,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,EAAE,CAAC;QACxI,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,8EAA8E,CAAC,CAAC;IACzH,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACxC,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,4GAA4G;AAC5G,0GAA0G;AAC1G,6GAA6G;AAC7G,6FAA6F;AAC7F,8GAA8G;AAC9G,SAAS,sBAAsB,CAAC,KAAyB,EAAE,QAAiB;IACxE,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,KAAK,GAAuB,EAAE,CAAC;IACrC,8GAA8G;IAC9G,KAAK,CAAC,OAAO,CAAC,CAAC,KAAc,EAAE,CAAS,EAAQ,EAAE;QAC9C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACtE,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,+DAA+D,CAAC,CAAC;YACtG,OAAO;QACX,CAAC;QACD,2EAA2E;QAC3E,MAAM,CAAC,GAAG,KAAgC,CAAC;QAC3C,MAAM,CAAC,IAAI,CAAC,GAAG,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3C,gGAAgG;QAChG,0EAA0E;QAC1E,KAAK,CAAC,IAAI,CAAC;YACP,EAAE,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;YACrD,GAAG,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS;YACxD,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,IAAI;SACnC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IACH,oGAAoG;IACpG,MAAM,WAAW,GAAG,IAAI,sCAAmB,CAAC,EAAE,EAAE,8CAA2B,CAAC,CAAC;IAC7E,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAsB,EAAuB,EAAE,CAAC,IAAA,8BAAW,EAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC;IACxG,OAAO,CAAC,GAAG,MAAM,EAAE,GAAG,IAAI,wCAAkB,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;AAC7E,CAAC;AAEY,QAAA,uBAAuB,GAAG,mBAAmB,CAAC;AAC9C,QAAA,2BAA2B,GAAG,uBAAuB,CAAC;AACtD,QAAA,mBAAmB,GAAG,gBAAgB,CAAC;AAEpD;;;;;;;;;;;;;;;;GAgBG;AACH,uGAAuG;AACvG,8GAA8G;AAC9G,SAAgB,yBAAyB,CAAC,CAA0B,EAAE,QAAiB;IACnF,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,QAAQ,GAAG,CAAC,CAAC,mCAA2B,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,+BAAuB,IAAI,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,CAAC,CAAC,+BAAuB,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;IAC7D,IAAI,mCAA2B,IAAI,CAAC,IAAI,OAAO,QAAQ,KAAK,SAAS,EAAE,CAAC;QACpE,MAAM,CAAC,IAAI,CACP,cAAc,mCAA2B,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,qCAAqC;YAC7G,wDAAwD,8CAA2B,8BAA8B;YACjH,SAAS,+BAAuB,iCAAiC,CAAC,CAAC;IAC3E,CAAC;SAAM,IAAI,QAAQ,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACrC,MAAM,CAAC,IAAI,CACP,cAAc,mCAA2B,kBAAkB,+BAAuB,+BAA+B;YACjH,mGAAmG;YACnG,0BAA0B;YAC1B,QAAQ,+BAAuB,qBAAqB;YACpD,gBAAgB,mCAA2B,oCAAoC,8CAA2B,IAAI,CAAC,CAAC;IACxH,CAAC;SAAM,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAO,EAAE,CAAC;QACtC,MAAM,CAAC,IAAI,CACP,cAAc,+BAAuB,iBAAiB,mCAA2B,gCAAgC;YACjH,2CAA2C,8CAA2B,oBAAoB,8CAA2B,OAAO;YAC5H,gBAAgB,gCAAoB,gBAAgB;YACpD,eAAe,+BAAuB,4EAA4E;YAClH,YAAY,mCAA2B,kDAAkD,CAAC,CAAC;IACnG,CAAC;IACD,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC;IAC3C,MAAM,CAAC,IAAI,CAAC,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,0GAA0G;IAC1G,IAAI,QAAQ,KAAK,SAAS,IAAI,cAAc,IAAI,CAAC,CAAC,2BAAmB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3E,MAAM,SAAS,GAAG,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAE,IAAe,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,8CAA2B,CAAC;QAC5F,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,wCAAkB,EAAE,CAAC,qBAAqB,CACzD,QAAQ,EAAE,IAAI,sCAAmB,CAAC,SAAS,EAAE,8CAA2B,CAAC,CAAC,CAAC,CAAC;IACpF,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED;;;;GAIG;AACH,mGAAmG;AACnG,8GAA8G;AAC9G,SAAS,oBAAoB,CAAC,CAA0B;IACpD,MAAM,GAAG,GAAG,CAAC,CAAC,2BAAmB,CAAC,CAAC;IACnC,IAAI,CAAC,CAAC,2BAAmB,IAAI,CAAC,CAAC,EAAE,CAAC;QAC9B,OAAO;YACH,qCAAqC,2BAAmB,6CAA6C;gBACrG,8EAA8E;gBAC9E,QAAQ,2BAAmB,SAAS;gBACpC,sGAAsG;gBACtG,yGAAyG;gBACzG,iHAAiH;gBACjH,wGAAwG;gBACxG,oGAAoG;gBACpG,4DAA4D;SAC/D,CAAC;IACN,CAAC;IACD,IAAI,CAAC,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC;QAClE,OAAO;YACH,cAAc,2BAAmB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,oDAAoD;gBAC/G,4GAA4G;gBAC5G,0GAA0G;SAC7G,CAAC;IACN,CAAC;IACD,OAAO,EAAE,CAAC;AACd,CAAC;AAED,kGAAkG;AAClG,oGAAoG;AACpG,sGAAsG;AACtG,iGAAiG;AACjG,wGAAwG;AACxG,8GAA8G;AAC9G,SAAgB,qBAAqB,CAAC,KAAc;IAChD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACtE,OAAO,CAAC,oEAAoE,8CAA4B,MAAM,CAAC,CAAC;IACpH,CAAC;IACD,4FAA4F;IAC5F,MAAM,CAAC,GAAG,KAAgC,CAAC;IAC3C,IAAI,CAAC,CAAC,iBAAiB,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IACzC,MAAM,SAAS,GAAG,CAAC,CAAC,iBAAiB,CAAC,CAAC;IACvC,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,mCAAiB,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,CAAC;IACtF,OAAO;QACH,yCAAyC,MAAM,CAAC,SAAS,CAAC,kBAAkB;YAC5E,mBAAmB,mCAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;YACpD,MAAM,8CAA4B,oFAAoF;YACtH,+FAA+F;YAC/F,oFAAoF;YACpF,MAAM,yCAAuB,8EAA8E;YAC3G,MAAM,uCAAqB,uFAAuF;KACrH,CAAC;AACN,CAAC;AAED,uGAAuG;AACvG,+FAA+F;AAC/F,wGAAwG;AACxG,oDAAoD;AACpD,MAAM,aAAa,GAAG,+BAA+B,CAAC;AAEtD;;;;;;;;GAQG;AACH,yGAAyG;AACzG,8GAA8G;AAC9G,SAAgB,wBAAwB,CAAC,KAAc;IACnD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACtE,OAAO,CAAC,yGAAyG,CAAC,CAAC;IACvH,CAAC;IACD,+FAA+F;IAC/F,MAAM,CAAC,GAAG,KAAgC,CAAC;IAC3C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,CAAC,IAAI,CAAC,GAAG,oBAAoB,CAAC,CAAC,EAAE,iBAAiB,EAAE,aAAa,CAAC,CAAC,CAAC;IAC1E,MAAM,CAAC,IAAI,CAAC,GAAG,oBAAoB,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC;IAC5D,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC;IAErC,MAAM,SAAS,GAAG,OAAO,CAAC,CAAC,iBAAiB,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;IACzG,MAAM,SAAS,GAAG,OAAO,CAAC,CAAC,WAAW,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IACrF,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,IAAI,CACP,sCAAsC,SAAS,sDAAsD;YACrG,uDAAuD,CAAC,CAAC;IACjE,CAAC;IACD,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,SAAS,GAAG,CAAC,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,SAAS,GAAG,CAAC,EAAE,CAAC;QAC5G,MAAM,CAAC,IAAI,CACP,qCAAqC,SAAS,wCAAwC,SAAS,cAAc;YAC7G,yGAAyG;YACzG,yGAAyG;YACzG,sFAAsF,CAAC,CAAC;IAChG,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,sGAAsG;AACtG,mFAAmF;AACnF,8GAA8G;AAC9G,SAAS,oBAAoB,CAAC,CAA0B,EAAE,GAAW,EAAE,OAAe;IAClF,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IACnB,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC/C,OAAO,CAAC,wBAAwB,GAAG,uCAAuC,OAAO,4BAA4B,OAAO,YAAY,CAAC,CAAC;IACtI,CAAC;IACD,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;QAClC,OAAO;YACH,wBAAwB,GAAG,QAAQ,GAAG,iEAAiE;gBACvG,4DAA4D,OAAO,KAAK;SAC3E,CAAC;IACN,CAAC;IACD,OAAO,EAAE,CAAC;AACd,CAAC","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_PLACEHOLDER,\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 ' \"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\\n' +\n ' webpieces-reviewer agent (or your own: \"overrideReviewerAgent\": true + \"reviewerAgentName\"), against\\n' +\n ' its own \"doc\" (REQUIRED, 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\\n' +\n ' it an OPTIONAL review the human is offered and may decline (but if they DO run it, a red verdict\\n' +\n ' 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 `overrideReviewerAgent` / `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_PLACEHOLDER);\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 OVERRIDE_REVIEWER_AGENT_KEY = 'overrideReviewerAgent';\nexport const REVIEWER_AGENTS_KEY = 'reviewerAgents';\n\n/**\n * `overrideReviewerAgent` (optional boolean, absent == false), `reviewerAgentName` (only with the override)\n * and `reviewerAgents` (required nonnegative integer; zero disables reviewer-agent reviews).\n *\n * The reviewer agent defaults to {@link DEFAULT_REVIEWER_AGENT_NAME} — the generic reviewer webpieces ships\n * and manages — so a new repo writes NOTHING here (issue #947). Replacing it changes how every PR is\n * reviewed, so it must be said out loud with `\"overrideReviewerAgent\": true` (greppable) AND a\n * `reviewerAgentName`. A name WITHOUT the override is rejected rather than silently ignored: a key that looks\n * meaningful but controls nothing is a second spelling of a decision (.claude/rules/no-backwards-compat.md).\n *\n * `reviewerAgents` is REQUIRED while the gate is active, for the same reason `required` and `mergeMode` have\n * no default: it sets the PRICE of every review round, and a price nobody chose is the one that stops the\n * gate being run. It used to be optional, and absent meant one SEPARATE subagent per checklist — so a repo\n * with four required checklists charged a one-line typo fix four full reviewer spawns re-reading the same\n * diff, silently, having never been asked. The absent branch is GONE rather than kept as a fallback: an\n * accepted shape is never migrated, and the number is the whole decision the key exists to record.\n */\n// webpieces-disable no-any-unknown -- the already-narrowed opaque pr-gate section; three 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 override = s[OVERRIDE_REVIEWER_AGENT_KEY];\n const hasName = REVIEWER_AGENT_NAME_KEY in s;\n const name = s[REVIEWER_AGENT_NAME_KEY];\n const named = typeof name === 'string' && name.trim() !== '';\n if (OVERRIDE_REVIEWER_AGENT_KEY in s && typeof override !== 'boolean') {\n errors.push(\n `[pr-gate] \"${OVERRIDE_REVIEWER_AGENT_KEY}\" = ${JSON.stringify(override)} is not valid — it must be true or ` +\n `false. Delete the key to use the webpieces reviewer (${DEFAULT_REVIEWER_AGENT_NAME}); set it to true, together ` +\n `with \"${REVIEWER_AGENT_NAME_KEY}\", to use an agent of your own.`);\n } else if (override === true && !named) {\n errors.push(\n `[pr-gate] \"${OVERRIDE_REVIEWER_AGENT_KEY}\": true needs \"${REVIEWER_AGENT_NAME_KEY}\" — add the agent type every ` +\n `reviewer subagent is spawned as (its .claude/agents/<name>.md must exist) to commands.pr-gate in ` +\n `webpieces.config.json:\\n` +\n ` \"${REVIEWER_AGENT_NAME_KEY}\": \"my-reviewer\",\\n` +\n ` Or delete \"${OVERRIDE_REVIEWER_AGENT_KEY}\" to use the webpieces reviewer (${DEFAULT_REVIEWER_AGENT_NAME}).`);\n } else if (override !== true && hasName) {\n errors.push(\n `[pr-gate] \"${REVIEWER_AGENT_NAME_KEY}\" is set but \"${OVERRIDE_REVIEWER_AGENT_KEY}\" is not true, so it would be ` +\n `ignored. The reviewer agent defaults to ${DEFAULT_REVIEWER_AGENT_NAME} (.claude/agents/${DEFAULT_REVIEWER_AGENT_NAME}.md, ` +\n `written by \\`${UPGRADE_SHIM_COMMAND}\\`). Either:\\n` +\n ` • remove \"${REVIEWER_AGENT_NAME_KEY}\" from commands.pr-gate in webpieces.config.json to use that default, or\\n` +\n ` • set \"${OVERRIDE_REVIEWER_AGENT_KEY}\": true beside it to review with your own agent.`);\n }\n const agentKeysValid = errors.length === 0;\n errors.push(...reviewerAgentsErrors(s));\n // A disabled project never spawns this agent, so requiring its file would make `0` an incomplete opt-out.\n if (repoRoot !== undefined && agentKeysValid && s[REVIEWER_AGENTS_KEY] !== 0) {\n const agentName = override === true ? (name as string).trim() : DEFAULT_REVIEWER_AGENT_NAME;\n errors.push(...new ChecklistValidator().validateReviewerAgent(\n repoRoot, new ReviewerAgentPolicy(agentName, REVIEWER_AGENTS_PLACEHOLDER)));\n }\n return errors;\n}\n\n/**\n * `reviewerAgents`: present, and a nonnegative integer. Missing and wrong-typed get DIFFERENT messages — the\n * missing one has to teach a reader what the number means and what to weigh when picking it, because they\n * have never seen the key; the wrong-typed one only has to name the constraint they just broke.\n */\n// webpieces-disable no-any-unknown -- the already-narrowed opaque pr-gate section; one key is read\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction reviewerAgentsErrors(s: Record<string, unknown>): string[] {\n const max = s[REVIEWER_AGENTS_KEY];\n if (!(REVIEWER_AGENTS_KEY in s)) {\n return [\n `[pr-gate] Missing required field \"${REVIEWER_AGENTS_KEY}\" — the MOST reviewer subagents one review ` +\n `round may use. Add this line to commands.pr-gate in webpieces.config.json:\\n` +\n ` \"${REVIEWER_AGENTS_KEY}\": 1,\\n` +\n ` 0 = reviewer-agent reviews are disabled for this project while the build and PR gate stay active. ` +\n `1 = ONE subagent reviews every owed checklist (cheapest enabled setting; it is handed each checklist's ` +\n `instructions file and still writes one verdict file per checklist). A HIGHER number buys independent readers — ` +\n `set it to your checklist count for one subagent each — at that many times the tokens per PR. There is ` +\n `deliberately no default: this sets the price of every review round, and an unchosen price is what ` +\n `makes a gate expensive enough that people stop running it.`,\n ];\n }\n if (!(typeof max === 'number' && Number.isInteger(max) && max >= 0)) {\n return [\n `[pr-gate] \"${REVIEWER_AGENTS_KEY}\" = ${JSON.stringify(max)} is not valid — it must be a nonnegative integer: ` +\n `0 disables reviewer-agent reviews for this project while leaving the build and PR gate active; a positive ` +\n `value is the MOST reviewer subagents one review round may use, with owed checklists grouped across them.`,\n ];\n }\n return [];\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"]}
@@ -295,7 +295,7 @@ function prGateExample() {
295
295
  ` "gates": [\n` +
296
296
  ` { "name": "API Changed", "patterns": ["libraries/apis/**", "**/*Api.ts"], "warningColor": "yellow" }\n` +
297
297
  ` ],\n` +
298
- ` "reviewerAgents": 1, // OPTIONALthe most reviewer subagents per round; omit for one per checklist\n` +
298
+ ` "reviewerAgents": 1, // REQUIRED0 disables reviews; positive values cap subagents per round\n` +
299
299
  ` "checklists": [ // OPTIONAL — per-area review, each against its own doc\n` +
300
300
  ` { "id": "db-migrations", "doc": ".claude/review/db-migrations.md", "patterns": ["**/*.sql"], "required": true }\n` +
301
301
  ` ]\n` +
@@ -1 +1 @@
1
- {"version":3,"file":"validate-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/validate-config.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { FieldDef } from './field-def';\n// Imported, never re-typed: the banner offers the bulk migrator only for errors it actually covers, and\n// this marker is how it recognizes a placement error. See config-error-banner.ts.\nimport { SECTION_PLACEMENT_MARKER } from './config-error-banner';\nimport { sectionForRule, isHookGuard } from './sections';\nimport { RULE_SCHEMAS, allRuleNames } from './rule-schemas';\nimport { recommendedSeedModeFor, isGradualMode } from './seed-entry';\nimport { MODIFIED_CODE_MODES } from './rule-configs';\nimport {\n validateChecklistsSection, validateDevDeploySection, validateLandPrSection, validateNoGateSaltRationale,\n validateReviewerAgentKeys,\n} from './pr-gate-section-validators';\nimport { retiredEntry, retiredKeyError, retiredRuleFor } from './retired-config-keys';\nimport { PRUNE_UNKNOWN_COMMAND } from './constants';\n\n// Re-exported so the isolated validate-checklist-docs target keeps importing it from here.\nexport { validateChecklistsSection };\n// Re-exported from their new homes: this module was the historical entry point for both.\nexport { allRuleNames } from './rule-schemas';\nexport { recommendedSeedMode, recommendedSeedModeFor, seedEntryForRule } from './seed-entry';\nimport { DEFAULT_MATCH_RULES } from './match-rules-config';\nimport { toError } from './to-error';\n\n/**\n * The longest a `turnOffRuleUntilEpoch` may reach into the future: ONE WEEK. Poke it again next week to\n * extend — that weekly re-set IS the intended workflow, not a workaround: it is what makes a rule that is\n * off on purpose stay a decision somebody keeps making, instead of one nobody remembers.\n *\n * DELIBERATELY ASYMMETRIC with turnOffRuleWhileOnBranch, which has NO cap and must not get one. The epoch\n * hatch is REPO-WIDE while it lasts, so a long window shelters every unrelated change that lands inside it\n * (a fleet repo was found with max-file-lines/max-method-lines switched off until 2026-10-01 — 43 days\n * out, six times this cap). The branch hatch fires only on ONE exact branch name — a big refactor\n * legitimately runs 35+ days, and capping that would only interrupt the one branch that opted in.\n */\nexport const MAX_TURN_OFF_EPOCH_DAYS = 7;\n\nconst SECONDS_PER_DAY = 24 * 60 * 60;\n\n/**\n * Reject a `turnOffRuleUntilEpoch` more than MAX_TURN_OFF_EPOCH_DAYS into the future.\n *\n * A PAST epoch is always valid: it is inert (it skips nothing), and every existing config is full of them.\n * The BOUNDARY is inclusive — exactly MAX_TURN_OFF_EPOCH_DAYS out is accepted, only strictly beyond it is\n * refused — so a config written as \"now + 7 days\" is never rejected by the second it took to save.\n */\n// webpieces-disable no-function-outside-class -- module-scope validator helper, matching every other check in this file\nfunction epochCapError(label: string, value: number): string | null {\n const maxEpoch = Math.floor(Date.now() / 1000) + MAX_TURN_OFF_EPOCH_DAYS * SECONDS_PER_DAY;\n if (value <= maxEpoch) return null;\n const daysOut = Math.round((value - Date.now() / 1000) / SECONDS_PER_DAY);\n const maxDate = new Date(maxEpoch * 1000).toISOString().split('T')[0];\n return (\n `${label} \"turnOffRuleUntilEpoch\": ${value} is ${daysOut} days in the future — the maximum is ` +\n `${MAX_TURN_OFF_EPOCH_DAYS} days. A rule switched off for longer than that is off for work nobody ` +\n `had in mind when it was written, because this hatch is REPO-WIDE while it lasts.\\n` +\n ` CURE: set it to at most ${maxEpoch} (${maxDate}), then re-set it WEEKLY for as long as you ` +\n `still need it — that renewal is the intended workflow, not a workaround. If the rule is off for ` +\n `ONE branch's work, use \"turnOffRuleWhileOnBranch\": \"<exact branch name>\" instead; that one has ` +\n `no cap.`\n );\n}\n\nfunction valueHint(def: FieldDef, key?: string): string {\n // The two universal escape hatches, REQUIRED on every rule so they're always visible. Spell out the\n // \"off\" value so a fresh config seeds them in the active/no-op state (0 / null), not a placeholder.\n if (key === 'turnOffRuleUntilEpoch') return '0 (0 = active; future unix-epoch seconds = temporarily off)';\n if (key === 'turnOffRuleWhileOnBranch') return 'null (null = always on; an EXACT branch name — no globs — disables the rule while that branch is checked out)';\n return def.enumValues\n ? `\"${def.enumValues.join(' | ')}\"`\n : def.type === 'string[]' ? '[\"<string>\", ...]'\n : def.type === 'number' ? '<number>'\n : def.type === 'boolean' ? '<boolean>'\n : '\"<string>\"';\n}\n\n/** A rollout hint for the copy-paste snippet: recommend the narrowest gradual mode the rule supports. */\nfunction rolloutTip(schema: Record<string, FieldDef>): string {\n const modes = schema['mode']?.enumValues ?? [];\n // Same source of truth as the seeder; only a GRADUAL recommendation gets the rollout prose, so a\n // rule whose recommendation falls through to ON/RUN_EVERY_TIME/OFF prints no tip (as before).\n const recommended = recommendedSeedModeFor(modes);\n if (!isGradualMode(recommended)) return '';\n const optOut = modes.includes('OFF') ? ' Set \"mode\": \"OFF\" to opt out entirely.' : '';\n return (\n `\\n\\n💡 Recommended: start with \"mode\": \"${recommended}\" — it enforces only on what you ` +\n `actually change, so the rule rolls out gradually (existing code stays grandfathered until ` +\n `you next touch that project/file/method).${optOut}`\n );\n}\n\nfunction missingRuleSnippet(ruleName: string, schema: Record<string, FieldDef>): string {\n // Required fields go in the copy-paste entry. The two universal escape hatches\n // (turnOffRuleUntilEpoch / turnOffRuleWhileOnBranch) are now REQUIRED, so they land in that block —\n // which is the whole point: every seeded rule shows both hatches. Optional fields are listed separately.\n const fields = Object.keys(schema);\n const required = fields.filter(f => !schema[f].optional);\n const optional = fields.filter(f => schema[f].optional);\n\n const requiredLines = required.map(f => ` \"${f}\": ${valueHint(schema[f], f)}`);\n const section = sectionForRule(ruleName);\n let out =\n `[${ruleName}] Not configured in webpieces.config.json. Add this entry to the \"${section}\" section\\n` +\n `(choose values appropriate for your project):\\n\\n` +\n ` \"${ruleName}\": {\\n${requiredLines.join(',\\n')}\\n }`;\n\n if (optional.length > 0) {\n const optionalLines = optional.map(f => ` \"${f}\": ${valueHint(schema[f], f)}`);\n out +=\n `\\n\\nOptional fields you may add to this rule (omit if not needed):\\n` +\n `${optionalLines.join(',\\n')}`;\n }\n out += rolloutTip(schema);\n return out;\n}\n\n/**\n * A config key under rules/hookGuards that the RUNNING validator has no schema for (and no rulesDir is set\n * to supply custom rules). THE FALLBACK: it fires for any name the table in retired-config-keys.ts does not\n * know, which very much includes RETIRED names on a tree whose validator predates the retirement — the\n * ordinary linked-worktree layout, where the worktree is on one release and the parent checkout that\n * supplies the hook's resolution is on an older one. So this text has to be useful with NO table entry.\n *\n * DELETION LEADS, and it is not a hedge. A key the running validator has no schema for controls nothing:\n * every code path that would read it is keyed off the schema. Leaving it is dead config that reads as live\n * config, and for a retired key deleting it is the entire fix.\n *\n * This message used to lead with `pnpm install` instead, on the theory that the key might be valid and the\n * validator merely stale. Two things are wrong with that. First, it CONTRADICTED the banner this error is\n * printed inside, which states outright that `pnpm install` cannot help — one output, two opposite orders.\n * Second, the premise is already handled upstream: the shim's version-drift guard compares the pin against\n * the installed version and denies every tool call BEFORE exec'ing this validator, with its own message and\n * its own cure. If this text is on screen, that guard found no drift. The pin therefore appears here only as\n * a secondary note, so a valid-but-newer key is never dropped without the reader being told the case exists.\n */\nfunction unknownRuleError(ruleName: string): string {\n return (\n `[${ruleName}] Unknown rule — the running @webpieces validator has no schema for it, and no ` +\n `\"rulesDir\" is configured to supply custom rules. A key no validator knows controls NOTHING, so ` +\n `DELETE the \"${ruleName}\" key from webpieces.config.json — run \\`${PRUNE_UNKNOWN_COMMAND}\\` to ` +\n `strip it (and every other unknown key) mechanically. It may be RETIRED: a newer release can move ` +\n `a setting out of this file entirely, in which case deleting the key here is the WHOLE fix. ` +\n `MACHINE-LOCAL settings in particular now live in ~/.webpieces/config.json under \"experimental\" — ` +\n `an optional file tracked by no repo, whose absence is the default behaviour — so if ` +\n `\"${ruleName}\" is one of those, delete it here and set it there. Secondary, and rare: a key is ` +\n `valid-but-unlearned when package.json pins an @webpieces OLDER than this config was written for, ` +\n `and the version-drift guard reports THAT separately with its own cure (bump the pin) before this ` +\n `validator ever runs — so it is not what you are looking at.`\n );\n}\n\n// Fields we DELETED from a rule's schema, keyed by \"<rule>.<field>\". The generic unknown-field error\n// (\"Unknown field ... Valid fields: [...]\") is correct but doesn't say WHY the field vanished, so an AI\n// might re-add it. These hints explain the removal so the only action left is to delete the key.\nconst RETIRED_FIELD_HINTS: Record<string, string> = {\n 'runtime-architecture.servicePaths':\n 'This field was removed — it was never read. The runtime graph is derived automatically from ' +\n 'architecture/dependencies.json (apiRelations + project roles). Delete it.',\n 'runtime-architecture.apiProjectPaths':\n 'This field was removed — it was never read. The runtime graph is derived automatically from ' +\n 'architecture/dependencies.json, so there is NO list of api libs to maintain. Delete it (do not ' +\n 'enumerate api libs and do not replace it with a glob).',\n 'runtime-architecture.allowedCycles':\n 'This field was removed — a runtime cycle is not allowable at all any more. Levelling now FAILS ' +\n 'on any cycle, because CD deploys services in dependency order and a cycle has no such order. ' +\n 'Delete the key. If a cycle genuinely cannot be broken yet, declare it PER EDGE with a ' +\n '`cutLegacyCycle:<targetService>` nx tag on the CALLING project, which admits the debt where ' +\n '`grep -rn cutLegacyCycle` can enumerate it — there is no config key for it.',\n // The two per-guard command strings, deleted as SECOND SPELLINGS of commands.guardHints. They were\n // read at the point of use, so they BEAT the commands section — which made guardHintsWhy's promise\n // (\"rename a gated command here and every guard message follows\") false for any repo that set them.\n // Keyed on the new policy key, since that is the entry a consumer's field now sits inside.\n 'pr-lifecycle-guard.upsertPrCommand':\n 'This field was removed. The command the push/PR-creation block prints lives in ONE place: ' +\n '\"commands\": { \"guardHints\": { \"prCreationOrPush\": <value> } }. Move your value there and delete ' +\n 'this key.',\n 'pr-lifecycle-guard.mergeCompleteCommand':\n 'This field was removed. The command the unfinished-merge block prints lives in ONE place: ' +\n '\"commands\": { \"guardHints\": { \"mergeInProgress\": <value> } }. Move your value there and delete ' +\n 'this key.',\n};\n\n// Universal field renames (apply to EVERY rule/guard AND every match-rule, unlike the per-rule\n// RETIRED_FIELD_HINTS). When an entry still uses the old escape-hatch name, the generic unknown-field\n// error is replaced with a precise \"renamed to X — rename it\" instruction so the fix is mechanical.\nconst RENAMED_FIELD_ALIASES: Record<string, string> = {\n ignoreModifiedUntilEpoch: 'turnOffRuleUntilEpoch',\n ignoreRuleWhileOnBranch: 'turnOffRuleWhileOnBranch',\n};\n\n\n// The renamed-field message shared by keyed rules and match-rules.\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction renamedFieldError(scope: string, oldKey: string, newKey: string): string {\n return `${scope} Unknown field \"${oldKey}\" — it was renamed to \"${newKey}\". Rename it (the value carries over unchanged; for the branch hatch, use null when there is no branch).`;\n}\n\n/**\n * Field-level errors for ONE configured rule entry: unknown/renamed/retired keys, type and enum\n * mismatches, and the two-week cap on turnOffRuleUntilEpoch.\n *\n * Extracted out of validateWebpiecesConfig purely so that function stays inside max-method-lines; it is\n * the same checks in the same order.\n */\n// webpieces-disable no-any-unknown -- one config entry as opaque JSON; every field is typed-checked below\n// webpieces-disable no-function-outside-class -- module-scope validator helper, matching every other check in this file\nfunction fieldErrors(ruleName: string, entry: Record<string, unknown>, schema: Record<string, FieldDef>): string[] {\n const errors: string[] = [];\n for (const [key, value] of Object.entries(entry)) {\n const fieldDef = schema[key];\n if (!fieldDef) {\n const renamedTo = RENAMED_FIELD_ALIASES[key];\n if (renamedTo) {\n errors.push(renamedFieldError(`[${ruleName}]`, key, renamedTo));\n continue;\n }\n const retiredHint = RETIRED_FIELD_HINTS[`${ruleName}.${key}`];\n const suffix = retiredHint ? ` ${retiredHint}` : '';\n errors.push(`[${ruleName}] Unknown field \"${key}\". Valid fields: [${Object.keys(schema).join(', ')}].${suffix}`);\n continue;\n }\n // A nullable field (e.g. turnOffRuleWhileOnBranch) accepts JSON null in addition to its type.\n if (value === null && fieldDef.nullable) continue;\n if (fieldDef.type === 'string[]') {\n if (!Array.isArray(value) || !value.every(v => typeof v === 'string'))\n errors.push(`[${ruleName}] \"${key}\" must be string[], got ${typeof value}.`);\n } else if (typeof value !== fieldDef.type) {\n errors.push(`[${ruleName}] \"${key}\" must be ${fieldDef.type}, got ${typeof value}.`);\n } else if (fieldDef.enumValues && !fieldDef.enumValues.includes(value as string)) {\n errors.push(`[${ruleName}] \"${key}\" = \"${value}\" is not valid. Must be one of: ${fieldDef.enumValues.join(', ')}.`);\n } else if (key === 'turnOffRuleUntilEpoch') {\n const capError = epochCapError(`[${ruleName}]`, value as number);\n if (capError) errors.push(capError);\n } else if (ruleName === 'branch-state-guard' && key === 'maxCommitsBehind'\n && (!Number.isInteger(value) || (value as number) < 0)) {\n errors.push('[branch-state-guard] \"maxCommitsBehind\" must be a non-negative integer.');\n }\n }\n return errors;\n}\n\n// webpieces-disable no-any-unknown -- rawRules values are opaque JSON; each field is validated individually\nexport function validateWebpiecesConfig(\n rawRules: Record<string, Record<string, unknown>>,\n hasCustomRulesDir: boolean = false,\n): string[] {\n const errors: string[] = [];\n\n // Check field-level correctness for rules that are present\n for (const [ruleName, entry] of Object.entries(rawRules)) {\n const schema = RULE_SCHEMAS[ruleName];\n if (!schema) {\n // A name we KNOW is retired beats the generic unknown-rule message, because only the table\n // knows WHERE the setting went — a rename carries its value over, and a bare \"delete it\"\n // would throw that value away. It fires even when a rulesDir is set, because a custom rule\n // must not reuse a retired name.\n const retired = retiredRuleFor(ruleName);\n if (retired) {\n errors.push(retiredKeyError(retired));\n continue;\n }\n // No built-in schema. With no rulesDir there are no custom rules, so this key is a\n // dead/typo'd entry — tell the AI to remove it (a removed rule like no-shell-substitution\n // lingers here otherwise). With a rulesDir it may be a legitimate custom rule → skip.\n if (!hasCustomRulesDir) errors.push(unknownRuleError(ruleName));\n continue;\n }\n errors.push(...fieldErrors(ruleName, entry, schema));\n // Required fields must actually be present. Until now the loop above only checked\n // fields that WERE present, so an entry like `{}` (or one missing `mode` /\n // `turnOffRuleUntilEpoch`) slipped through. Every non-optional schema field is mandatory.\n for (const [key, fieldDef] of Object.entries(schema)) {\n if (!fieldDef.optional && !(key in entry)) {\n errors.push(`[${ruleName}] Missing required field \"${key}\". Add ${key}: ${valueHint(fieldDef, key)}.`);\n }\n }\n }\n\n // Every built-in rule must be explicitly configured — no silent defaults.\n // When a new rule is added to the framework, this check surfaces it immediately\n // with a ready-to-copy snippet so AI can configure it in one pass.\n for (const [ruleName, schema] of Object.entries(RULE_SCHEMAS)) {\n if (!(ruleName in rawRules)) {\n errors.push(missingRuleSnippet(ruleName, schema));\n }\n }\n\n return errors;\n}\n\nconst PR_GATE_MODES = ['ON', 'OFF'] as const;\n// Optional — omitted means DETECT. Kept inline (like prGateExample) to avoid a load-config ↔\n// pr-gate-config import cycle; the canonical list + semantics live in pr-gate-config.ts.\nconst PR_GATE_MERGE_MODES = ['AUTO', 'NONE'] as const;\n\n// Spelled out because the choice is a POLICY decision with a consequence the chooser cannot see:\n// only the AUTO path can put the compact risk/flags body in main's history, because a UI merge is\n// composed from the repo's squash_merge_commit_title/message settings, which the tooling pins.\nconst MERGE_MODE_HELP = (\n `Must be one of: ${PR_GATE_MERGE_MODES.join(', ')}.\\n` +\n ` \"AUTO\" — wp-finish-upsert-pr lands the PR: it squash-merges when mergeable, else enables\\n` +\n ` GitHub auto-merge, both with an explicit --subject/--body-file so main's history\\n` +\n ` gets the PR title + the compact risk/flags body. Needs allow_auto_merge on the repo\\n` +\n ` (gh api repos/{owner}/{repo} --jq .allow_auto_merge).\\n` +\n ` \"NONE\" — wp-finish-upsert-pr only opens/updates the PR; a human merges it, and that still\\n` +\n ` lands the compact body: the PR DESCRIPTION *is* that body, and stage \\u2462 keeps the repo's\\n` +\n ` squash_merge_commit_title/message pinned to PR_TITLE/PR_BODY so a UI merge copies it\\n` +\n ` verbatim (SquashSettingsEnforcer). Nothing to set by hand.`\n);\n\n// Copy-paste example for the top-level `pr-gate` block (sibling of `rules`). Kept inline rather\n// than imported from pr-gate-config.ts to avoid a load-config ↔ pr-gate-config import cycle.\nfunction prGateExample(): string {\n return (\n ` \"pr-gate\": {\\n` +\n ` \"mode\": \"ON\",\\n` +\n ` \"buildCommand\": \"<command CI runs to validate a PR, e.g. pnpm nx affected --target=ci --base=$(git merge-base origin/main HEAD)>\",\\n` +\n ` \"gates\": [\\n` +\n ` { \"name\": \"API Changed\", \"patterns\": [\"libraries/apis/**\", \"**/*Api.ts\"], \"warningColor\": \"yellow\" }\\n` +\n ` ],\\n` +\n ` \"reviewerAgents\": 1, // OPTIONAL — the most reviewer subagents per round; omit for one per checklist\\n` +\n ` \"checklists\": [ // OPTIONAL — per-area review, each against its own doc\\n` +\n ` { \"id\": \"db-migrations\", \"doc\": \".claude/review/db-migrations.md\", \"patterns\": [\"**/*.sql\"], \"required\": true }\\n` +\n ` ]\\n` +\n ` }`\n );\n}\n\n// The `gates` array: dashboard-only warning flags. Extracted from validatePrGateSection to keep that\n// method inside the length limit.\n// webpieces-disable no-any-unknown -- `value` is the opaque consumer `gates` value until narrowed here\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction validateGatesSection(value: unknown): string[] {\n if (!Array.isArray(value)) {\n return [`[pr-gate] \"gates\" must be an array of { name, patterns, warningColor, disabled? }.`];\n }\n const errors: string[] = [];\n for (let i = 0; i < value.length; i += 1) {\n errors.push(...validateGate(value[i], i));\n }\n return errors;\n}\n\n// webpieces-disable no-any-unknown -- one gate entry from opaque consumer JSON, validated field-by-field\nfunction validateGate(gate: unknown, index: number): string[] {\n if (typeof gate !== 'object' || gate === null) {\n return [`[pr-gate] gates[${index}] must be an object { name, patterns, warningColor, disabled? }.`];\n }\n // webpieces-disable no-any-unknown -- narrowing one opaque gate object from consumer JSON\n const g = gate as Record<string, unknown>;\n const errors: string[] = [];\n if (typeof g['name'] !== 'string') errors.push(`[pr-gate] gates[${index}].name must be a string.`);\n if (!Array.isArray(g['patterns']) || !g['patterns'].every(p => typeof p === 'string'))\n errors.push(`[pr-gate] gates[${index}].patterns must be string[].`);\n if (g['warningColor'] === undefined)\n errors.push(`[pr-gate] gates[${index}].warningColor is required — set it to \"yellow\" or \"red\" (green is implicit when nothing matches).`);\n else if (g['warningColor'] !== 'yellow' && g['warningColor'] !== 'red')\n errors.push(`[pr-gate] gates[${index}].warningColor must be \"yellow\" or \"red\" (green is implicit when nothing matches).`);\n if (g['disabled'] !== undefined && typeof g['disabled'] !== 'boolean')\n errors.push(`[pr-gate] gates[${index}].disabled must be a boolean (example/inactive gate kept in the file).`);\n return errors;\n}\n\n/**\n * Validate the top-level `pr-gate` section. It is REQUIRED (a client that opts out sets mode \"OFF\").\n * `buildCommand` is required unless mode is \"OFF\". Returns human-readable, copy-paste-friendly errors\n * — never throws. The pr-gate block lives outside the FieldDef-driven `rules` schema because its\n * nested `gates`/`checklists` arrays can't be expressed there, so they get structural validation here.\n * `repoRoot` (when known) lets the `checklists[].docs` existence check run.\n */\n// webpieces-disable no-any-unknown -- `section` 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 validatePrGateSection(section: unknown, repoRoot?: string): string[] {\n if (section === undefined || section === null) {\n return [\n `[pr-gate] Not configured in webpieces.config.json. Add this block under the \"commands\" ` +\n `section (set \"mode\": \"OFF\" to opt out):\\n\\n${prGateExample()}`,\n ];\n }\n if (typeof section !== 'object' || Array.isArray(section)) {\n return [`[pr-gate] Must be an object. Example:\\n\\n${prGateExample()}`];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque pr-gate section from consumer JSON\n const s = section as Record<string, unknown>;\n const errors: string[] = [];\n\n if (!('mode' in s)) {\n errors.push(`[pr-gate] Missing required field \"mode\". Must be one of: ${PR_GATE_MODES.join(', ')}.`);\n } else if (typeof s['mode'] !== 'string' || !PR_GATE_MODES.includes(s['mode'] as typeof PR_GATE_MODES[number])) {\n errors.push(`[pr-gate] \"mode\" = \"${String(s['mode'])}\" is not valid. Must be one of: ${PR_GATE_MODES.join(', ')}.`);\n }\n\n // buildCommand and mergeMode are both required whenever the gate is active (mode !== OFF). There\n // is deliberately NO default for mergeMode: whether the tooling may land your PRs is a policy\n // decision, and either guess is wrong for somebody.\n if (s['mode'] !== 'OFF') {\n const cmd = s['buildCommand'];\n if (typeof cmd !== 'string' || cmd.trim() === '') {\n errors.push(\n `[pr-gate] Missing required field \"buildCommand\" — the command CI runs to validate a PR. ` +\n `Add e.g. \"buildCommand\": \"pnpm nx affected --target=ci --base=$(git merge-base origin/main HEAD)\".`,\n );\n }\n const mm = s['mergeMode'];\n if (!('mergeMode' in s)) {\n errors.push(`[pr-gate] Missing required field \"mergeMode\". ${MERGE_MODE_HELP}`);\n } else if (typeof mm !== 'string' || !PR_GATE_MERGE_MODES.includes(mm as typeof PR_GATE_MERGE_MODES[number])) {\n errors.push(`[pr-gate] \"mergeMode\" = \"${String(mm)}\" is not valid. ${MERGE_MODE_HELP}`);\n }\n errors.push(...validateReviewerAgentKeys(s, repoRoot));\n }\n\n if ('gates' in s) errors.push(...validateGatesSection(s['gates']));\n\n // Optional extension point: company review checklists, as an ARRAY right here in the config. Absent ⇒\n // none. The removed { doc } manifest shape is rejected with the exact migration edit.\n if ('checklists' in s) errors.push(...validateChecklistsSection(s['checklists'], repoRoot));\n\n // Optional server-token salt. Absent ⇒ no token minted, CI enforcement is a no-op. Present ⇒ must be\n // a non-empty string (an empty salt would mint a token anyone can forge from a known-empty secret).\n if ('gateSalt' in s) {\n const salt = s['gateSalt'];\n if (typeof salt !== 'string' || salt.trim() === '') {\n errors.push(`[pr-gate] \"gateSalt\" must be a non-empty string — it is the shared secret the gate token is HMAC'd with. Omit the key entirely to disable server-side token enforcement.`);\n }\n }\n\n // Optional: what happens to the LOCAL branch once its PR lands. Absent ⇒ \"archive-tag\".\n if ('landPr' in s) errors.push(...validateLandPrSection(s['landPr']));\n\n // Optional: where wp-push-dev publishes the disposable copy. Absent ⇒ \"dev-include\" / \"dev\".\n if ('devDeploy' in s) errors.push(...validateDevDeploySection(s['devDeploy']));\n\n errors.push(...validateNoGateSaltRationale(s));\n\n // Optional: publish reviewer output as a PR comment (defaults true). Must be a boolean when present.\n if ('checklistComments' in s && typeof s['checklistComments'] !== 'boolean') {\n errors.push(`[pr-gate] \"checklistComments\" must be a boolean (defaults to true; set false to keep the PR body-only).`);\n }\n\n return errors;\n}\n\nfunction excludePathsExample(): string {\n return '\"excludePaths\": [\"repositories/**\"]';\n}\n\n// A glob list: must be a string[] (may be empty). `key` names it for the error.\n// webpieces-disable no-any-unknown -- `value` is opaque consumer JSON until narrowed here\nfunction validateExcludeList(value: unknown, key: string): string[] {\n if (!(Array.isArray(value) && value.every(p => typeof p === 'string'))) {\n return [`[excludePaths] \"${key}\" must be a string[] of glob paths (use [] for none).`];\n }\n return [];\n}\n\n/**\n * Validate the REQUIRED top-level `excludePaths` block: ONE glob list suppressing hook enforcement per\n * file path, for code-style rules and file-scoped guards alike. Required so every client upgrading is\n * forced to declare it (as [] to keep today's behavior, or with real paths). Returns copy-paste\n * friendly errors and never throws — same contract as validatePrGateSection.\n *\n * The two-list object form `{ \"rules\": [...], \"guards\": [...] }` is RETIRED, not tolerated. It used to be\n * accepted and silently unioned, which is why this repo's own config sat on the dead shape for releases: an\n * accepted shape is never migrated. Rejecting it cannot wedge a consumer — editing webpieces.config.json is\n * always permitted, even while it is invalid. See retired-config-keys.ts.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed below\nexport function validateExcludePaths(section: unknown): string[] {\n if (section === undefined || section === null) {\n return [\n `[excludePaths] Not configured in webpieces.config.json. Add this REQUIRED block ` +\n `(use an empty array to keep enforcing everywhere):\\n\\n ${excludePathsExample()}`,\n ];\n }\n if (Array.isArray(section)) return validateExcludeList(section, 'excludePaths');\n if (typeof section !== 'object') {\n return [`[excludePaths] Must be a string[] of glob paths. Example:\\n\\n ${excludePathsExample()}`];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque excludePaths section from consumer JSON\n const s = section as Record<string, unknown>;\n // The retired two-list object. One table row owns the message for either key present.\n const retired = retiredEntry('rules', '[excludePaths]');\n if (retired && (s['rules'] !== undefined || s['guards'] !== undefined)) {\n return [`${retiredKeyError(retired)}\\n\\n ${excludePathsExample()}`];\n }\n return [`[excludePaths] Must be a string[] of glob paths. Example:\\n\\n ${excludePathsExample()}`];\n}\n\n// ---------------------------------------------------------------------------\n// match-rules — a new top-level ARRAY section (parallel to pr-gate/excludePaths). Each entry is a\n// client-authored content guard (raw-regex patterns + message + scoping). Validated structurally here\n// the same way pr-gate's `gates` are, because an array of objects can't be expressed in FieldDef schema.\n// ---------------------------------------------------------------------------\n\nfunction matchRulesExample(): string {\n return `\"match-rules\": ${JSON.stringify(DEFAULT_MATCH_RULES, null, 4)}`;\n}\n\n// webpieces-disable no-any-unknown -- generic type guard over an opaque JSON value\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every(v => typeof v === 'string');\n}\n\n// Compile a pattern to validate it; returns the error message, or undefined when it compiles.\nfunction regexError(pattern: string): string | undefined {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Constructed only to validate the syntax; the object is intentionally discarded.\n void new RegExp(pattern);\n return undefined;\n } catch (err: unknown) {\n const error = toError(err);\n return error.message;\n }\n}\n\n// One entry of the match-rules array, validated field-by-field (see validateGate for the pattern).\n// webpieces-disable no-any-unknown -- one match-rule entry from opaque consumer JSON, validated field-by-field\nfunction validateMatchRule(entry: unknown, index: number): string[] {\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n return [`[match-rules] entry[${index}] must be an object { name, patterns, mainMessage, mode, turnOffRuleUntilEpoch, turnOffRuleWhileOnBranch, ... }.`];\n }\n // webpieces-disable no-any-unknown -- narrowing one opaque match-rule entry from consumer JSON\n const e = entry as Record<string, unknown>;\n const label = typeof e['name'] === 'string' ? `\"${e['name']}\"` : `entry[${index}]`;\n const errors: string[] = [];\n\n if (typeof e['name'] !== 'string' || e['name'].trim() === '')\n errors.push(`[match-rules] entry[${index}].name must be a non-empty string (it is the disable token and report label).`);\n\n if (!isStringArray(e['patterns']) || e['patterns'].length === 0) {\n errors.push(`[match-rules] ${label}.patterns must be a non-empty string[] of regexes.`);\n } else {\n e['patterns'].forEach((p: string, pi: number) => {\n const rxErr = regexError(p);\n if (rxErr) errors.push(`[match-rules] ${label}.patterns[${pi}] is not a valid regex: ${rxErr}`);\n });\n }\n\n if (typeof e['mainMessage'] !== 'string' || e['mainMessage'].trim() === '')\n errors.push(`[match-rules] ${label}.mainMessage must be a non-empty string.`);\n\n if (typeof e['mode'] !== 'string' || !MODIFIED_CODE_MODES.includes(e['mode'] as typeof MODIFIED_CODE_MODES[number]))\n errors.push(`[match-rules] ${label}.mode must be one of: ${MODIFIED_CODE_MODES.join(', ')}.`);\n\n // Both escape hatches are REQUIRED on every match-rule too (same as keyed rules), so they are always\n // visible. turnOffRuleUntilEpoch: number (0 = active; a future unix-epoch in seconds = temporarily\n // off). turnOffRuleWhileOnBranch: string | null (null = always on; an EXACT branch name — matched\n // with ===, never as a glob — disables the rule while that branch is checked out).\n if (typeof e['turnOffRuleUntilEpoch'] !== 'number') {\n errors.push(`[match-rules] ${label}.turnOffRuleUntilEpoch must be a number (0 = active; future unix-epoch seconds = temporarily off).`);\n } else {\n const capError = epochCapError(`[match-rules] ${label}`, e['turnOffRuleUntilEpoch']);\n if (capError) errors.push(capError);\n }\n if (!(e['turnOffRuleWhileOnBranch'] === null || typeof e['turnOffRuleWhileOnBranch'] === 'string'))\n errors.push(`[match-rules] ${label}.turnOffRuleWhileOnBranch must be a string or null (null = no branch / always on; a string is an EXACT branch name, globs are not supported).`);\n\n // The old escape-hatch names were renamed — flag them precisely.\n for (const oldKey of Object.keys(RENAMED_FIELD_ALIASES)) {\n if (oldKey in e) errors.push(renamedFieldError(`[match-rules] ${label}`, oldKey, RENAMED_FIELD_ALIASES[oldKey]));\n }\n\n if (e['options'] !== undefined && !isStringArray(e['options']))\n errors.push(`[match-rules] ${label}.options must be a string[] (omit if not needed).`);\n if (e['allowedPaths'] !== undefined && !isStringArray(e['allowedPaths']))\n errors.push(`[match-rules] ${label}.allowedPaths must be a string[] of globs (omit if not needed).`);\n if (e['disableAllowed'] !== undefined && typeof e['disableAllowed'] !== 'boolean')\n errors.push(`[match-rules] ${label}.disableAllowed must be a boolean.`);\n\n return errors;\n}\n\n/**\n * Validate the REQUIRED top-level `match-rules` array (client-authored content guards). MISSING →\n * one error printing the ready-to-paste `no-fetch` example (add at least this; more can follow).\n * Present-but-`[]` → allowed (a conscious opt-out, matching pr-gate mode:OFF / excludePaths []).\n * Otherwise every entry is validated field-by-field (each regex compile-checked) plus name\n * uniqueness. Copy-paste-friendly errors; never throws — same contract as validatePrGateSection.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed below\nexport function validateMatchRulesSection(section: unknown): string[] {\n if (section === undefined || section === null) {\n return [\n `[match-rules] Not configured in webpieces.config.json. Add this REQUIRED top-level array — ` +\n `seed it with the no-fetch guard below (you can add more entries: no-moment, no-lodash-chain, …):\\n\\n${matchRulesExample()}`,\n ];\n }\n if (!Array.isArray(section)) {\n return [`[match-rules] Must be an array of content-guard objects. Example:\\n\\n${matchRulesExample()}`];\n }\n\n const errors: string[] = [];\n const seen = new Set<string>();\n for (let i = 0; i < section.length; i += 1) {\n errors.push(...validateMatchRule(section[i], i));\n // webpieces-disable no-any-unknown -- reading the name off an opaque entry only to dedupe\n const name = (section[i] as Record<string, unknown> | null)?.['name'];\n if (typeof name === 'string') {\n if (seen.has(name)) errors.push(`[match-rules] duplicate entry name \"${name}\" — each match-rule name must be unique.`);\n seen.add(name);\n }\n }\n return errors;\n}\n\n/**\n * Enforce that each built-in lives in its correct section: code rules under `rules`, bash guards\n * under `hookGuards`. A guard left in `rules` (or a rule placed in `hookGuards`) is reported with a\n * \"move it\" message so the split stays clean. Unknown/custom names are ignored (they may be custom\n * rules from rulesDir). Presence (\"every built-in must be configured\") is checked separately by\n * validateWebpiecesConfig against the merged map.\n */\n// webpieces-disable no-any-unknown -- section maps are opaque consumer JSON\nexport function validateSectionPlacement(\n rulesSection: Record<string, Record<string, unknown>>,\n hookGuardsSection: Record<string, Record<string, unknown>>,\n): string[] {\n const errors: string[] = [];\n for (const name of Object.keys(rulesSection)) {\n if (isHookGuard(name)) {\n errors.push(\n `[${name}] is a hook guard and ${SECTION_PLACEMENT_MARKER} \"hookGuards\" section, not \"rules\". ` +\n `Move it.`,\n );\n }\n }\n for (const name of Object.keys(hookGuardsSection)) {\n // Only flag KNOWN code rules misplaced into hookGuards; unknown names may be custom rules.\n if (!isHookGuard(name) && RULE_SCHEMAS[name]) {\n errors.push(\n `[${name}] is a code rule and ${SECTION_PLACEMENT_MARKER} \"rules\" section, not \"hookGuards\". ` +\n `Move it.`,\n );\n }\n }\n return errors;\n}\n"]}
1
+ {"version":3,"file":"validate-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/validate-config.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { FieldDef } from './field-def';\n// Imported, never re-typed: the banner offers the bulk migrator only for errors it actually covers, and\n// this marker is how it recognizes a placement error. See config-error-banner.ts.\nimport { SECTION_PLACEMENT_MARKER } from './config-error-banner';\nimport { sectionForRule, isHookGuard } from './sections';\nimport { RULE_SCHEMAS, allRuleNames } from './rule-schemas';\nimport { recommendedSeedModeFor, isGradualMode } from './seed-entry';\nimport { MODIFIED_CODE_MODES } from './rule-configs';\nimport {\n validateChecklistsSection, validateDevDeploySection, validateLandPrSection, validateNoGateSaltRationale,\n validateReviewerAgentKeys,\n} from './pr-gate-section-validators';\nimport { retiredEntry, retiredKeyError, retiredRuleFor } from './retired-config-keys';\nimport { PRUNE_UNKNOWN_COMMAND } from './constants';\n\n// Re-exported so the isolated validate-checklist-docs target keeps importing it from here.\nexport { validateChecklistsSection };\n// Re-exported from their new homes: this module was the historical entry point for both.\nexport { allRuleNames } from './rule-schemas';\nexport { recommendedSeedMode, recommendedSeedModeFor, seedEntryForRule } from './seed-entry';\nimport { DEFAULT_MATCH_RULES } from './match-rules-config';\nimport { toError } from './to-error';\n\n/**\n * The longest a `turnOffRuleUntilEpoch` may reach into the future: ONE WEEK. Poke it again next week to\n * extend — that weekly re-set IS the intended workflow, not a workaround: it is what makes a rule that is\n * off on purpose stay a decision somebody keeps making, instead of one nobody remembers.\n *\n * DELIBERATELY ASYMMETRIC with turnOffRuleWhileOnBranch, which has NO cap and must not get one. The epoch\n * hatch is REPO-WIDE while it lasts, so a long window shelters every unrelated change that lands inside it\n * (a fleet repo was found with max-file-lines/max-method-lines switched off until 2026-10-01 — 43 days\n * out, six times this cap). The branch hatch fires only on ONE exact branch name — a big refactor\n * legitimately runs 35+ days, and capping that would only interrupt the one branch that opted in.\n */\nexport const MAX_TURN_OFF_EPOCH_DAYS = 7;\n\nconst SECONDS_PER_DAY = 24 * 60 * 60;\n\n/**\n * Reject a `turnOffRuleUntilEpoch` more than MAX_TURN_OFF_EPOCH_DAYS into the future.\n *\n * A PAST epoch is always valid: it is inert (it skips nothing), and every existing config is full of them.\n * The BOUNDARY is inclusive — exactly MAX_TURN_OFF_EPOCH_DAYS out is accepted, only strictly beyond it is\n * refused — so a config written as \"now + 7 days\" is never rejected by the second it took to save.\n */\n// webpieces-disable no-function-outside-class -- module-scope validator helper, matching every other check in this file\nfunction epochCapError(label: string, value: number): string | null {\n const maxEpoch = Math.floor(Date.now() / 1000) + MAX_TURN_OFF_EPOCH_DAYS * SECONDS_PER_DAY;\n if (value <= maxEpoch) return null;\n const daysOut = Math.round((value - Date.now() / 1000) / SECONDS_PER_DAY);\n const maxDate = new Date(maxEpoch * 1000).toISOString().split('T')[0];\n return (\n `${label} \"turnOffRuleUntilEpoch\": ${value} is ${daysOut} days in the future — the maximum is ` +\n `${MAX_TURN_OFF_EPOCH_DAYS} days. A rule switched off for longer than that is off for work nobody ` +\n `had in mind when it was written, because this hatch is REPO-WIDE while it lasts.\\n` +\n ` CURE: set it to at most ${maxEpoch} (${maxDate}), then re-set it WEEKLY for as long as you ` +\n `still need it — that renewal is the intended workflow, not a workaround. If the rule is off for ` +\n `ONE branch's work, use \"turnOffRuleWhileOnBranch\": \"<exact branch name>\" instead; that one has ` +\n `no cap.`\n );\n}\n\nfunction valueHint(def: FieldDef, key?: string): string {\n // The two universal escape hatches, REQUIRED on every rule so they're always visible. Spell out the\n // \"off\" value so a fresh config seeds them in the active/no-op state (0 / null), not a placeholder.\n if (key === 'turnOffRuleUntilEpoch') return '0 (0 = active; future unix-epoch seconds = temporarily off)';\n if (key === 'turnOffRuleWhileOnBranch') return 'null (null = always on; an EXACT branch name — no globs — disables the rule while that branch is checked out)';\n return def.enumValues\n ? `\"${def.enumValues.join(' | ')}\"`\n : def.type === 'string[]' ? '[\"<string>\", ...]'\n : def.type === 'number' ? '<number>'\n : def.type === 'boolean' ? '<boolean>'\n : '\"<string>\"';\n}\n\n/** A rollout hint for the copy-paste snippet: recommend the narrowest gradual mode the rule supports. */\nfunction rolloutTip(schema: Record<string, FieldDef>): string {\n const modes = schema['mode']?.enumValues ?? [];\n // Same source of truth as the seeder; only a GRADUAL recommendation gets the rollout prose, so a\n // rule whose recommendation falls through to ON/RUN_EVERY_TIME/OFF prints no tip (as before).\n const recommended = recommendedSeedModeFor(modes);\n if (!isGradualMode(recommended)) return '';\n const optOut = modes.includes('OFF') ? ' Set \"mode\": \"OFF\" to opt out entirely.' : '';\n return (\n `\\n\\n💡 Recommended: start with \"mode\": \"${recommended}\" — it enforces only on what you ` +\n `actually change, so the rule rolls out gradually (existing code stays grandfathered until ` +\n `you next touch that project/file/method).${optOut}`\n );\n}\n\nfunction missingRuleSnippet(ruleName: string, schema: Record<string, FieldDef>): string {\n // Required fields go in the copy-paste entry. The two universal escape hatches\n // (turnOffRuleUntilEpoch / turnOffRuleWhileOnBranch) are now REQUIRED, so they land in that block —\n // which is the whole point: every seeded rule shows both hatches. Optional fields are listed separately.\n const fields = Object.keys(schema);\n const required = fields.filter(f => !schema[f].optional);\n const optional = fields.filter(f => schema[f].optional);\n\n const requiredLines = required.map(f => ` \"${f}\": ${valueHint(schema[f], f)}`);\n const section = sectionForRule(ruleName);\n let out =\n `[${ruleName}] Not configured in webpieces.config.json. Add this entry to the \"${section}\" section\\n` +\n `(choose values appropriate for your project):\\n\\n` +\n ` \"${ruleName}\": {\\n${requiredLines.join(',\\n')}\\n }`;\n\n if (optional.length > 0) {\n const optionalLines = optional.map(f => ` \"${f}\": ${valueHint(schema[f], f)}`);\n out +=\n `\\n\\nOptional fields you may add to this rule (omit if not needed):\\n` +\n `${optionalLines.join(',\\n')}`;\n }\n out += rolloutTip(schema);\n return out;\n}\n\n/**\n * A config key under rules/hookGuards that the RUNNING validator has no schema for (and no rulesDir is set\n * to supply custom rules). THE FALLBACK: it fires for any name the table in retired-config-keys.ts does not\n * know, which very much includes RETIRED names on a tree whose validator predates the retirement — the\n * ordinary linked-worktree layout, where the worktree is on one release and the parent checkout that\n * supplies the hook's resolution is on an older one. So this text has to be useful with NO table entry.\n *\n * DELETION LEADS, and it is not a hedge. A key the running validator has no schema for controls nothing:\n * every code path that would read it is keyed off the schema. Leaving it is dead config that reads as live\n * config, and for a retired key deleting it is the entire fix.\n *\n * This message used to lead with `pnpm install` instead, on the theory that the key might be valid and the\n * validator merely stale. Two things are wrong with that. First, it CONTRADICTED the banner this error is\n * printed inside, which states outright that `pnpm install` cannot help — one output, two opposite orders.\n * Second, the premise is already handled upstream: the shim's version-drift guard compares the pin against\n * the installed version and denies every tool call BEFORE exec'ing this validator, with its own message and\n * its own cure. If this text is on screen, that guard found no drift. The pin therefore appears here only as\n * a secondary note, so a valid-but-newer key is never dropped without the reader being told the case exists.\n */\nfunction unknownRuleError(ruleName: string): string {\n return (\n `[${ruleName}] Unknown rule — the running @webpieces validator has no schema for it, and no ` +\n `\"rulesDir\" is configured to supply custom rules. A key no validator knows controls NOTHING, so ` +\n `DELETE the \"${ruleName}\" key from webpieces.config.json — run \\`${PRUNE_UNKNOWN_COMMAND}\\` to ` +\n `strip it (and every other unknown key) mechanically. It may be RETIRED: a newer release can move ` +\n `a setting out of this file entirely, in which case deleting the key here is the WHOLE fix. ` +\n `MACHINE-LOCAL settings in particular now live in ~/.webpieces/config.json under \"experimental\" — ` +\n `an optional file tracked by no repo, whose absence is the default behaviour — so if ` +\n `\"${ruleName}\" is one of those, delete it here and set it there. Secondary, and rare: a key is ` +\n `valid-but-unlearned when package.json pins an @webpieces OLDER than this config was written for, ` +\n `and the version-drift guard reports THAT separately with its own cure (bump the pin) before this ` +\n `validator ever runs — so it is not what you are looking at.`\n );\n}\n\n// Fields we DELETED from a rule's schema, keyed by \"<rule>.<field>\". The generic unknown-field error\n// (\"Unknown field ... Valid fields: [...]\") is correct but doesn't say WHY the field vanished, so an AI\n// might re-add it. These hints explain the removal so the only action left is to delete the key.\nconst RETIRED_FIELD_HINTS: Record<string, string> = {\n 'runtime-architecture.servicePaths':\n 'This field was removed — it was never read. The runtime graph is derived automatically from ' +\n 'architecture/dependencies.json (apiRelations + project roles). Delete it.',\n 'runtime-architecture.apiProjectPaths':\n 'This field was removed — it was never read. The runtime graph is derived automatically from ' +\n 'architecture/dependencies.json, so there is NO list of api libs to maintain. Delete it (do not ' +\n 'enumerate api libs and do not replace it with a glob).',\n 'runtime-architecture.allowedCycles':\n 'This field was removed — a runtime cycle is not allowable at all any more. Levelling now FAILS ' +\n 'on any cycle, because CD deploys services in dependency order and a cycle has no such order. ' +\n 'Delete the key. If a cycle genuinely cannot be broken yet, declare it PER EDGE with a ' +\n '`cutLegacyCycle:<targetService>` nx tag on the CALLING project, which admits the debt where ' +\n '`grep -rn cutLegacyCycle` can enumerate it — there is no config key for it.',\n // The two per-guard command strings, deleted as SECOND SPELLINGS of commands.guardHints. They were\n // read at the point of use, so they BEAT the commands section — which made guardHintsWhy's promise\n // (\"rename a gated command here and every guard message follows\") false for any repo that set them.\n // Keyed on the new policy key, since that is the entry a consumer's field now sits inside.\n 'pr-lifecycle-guard.upsertPrCommand':\n 'This field was removed. The command the push/PR-creation block prints lives in ONE place: ' +\n '\"commands\": { \"guardHints\": { \"prCreationOrPush\": <value> } }. Move your value there and delete ' +\n 'this key.',\n 'pr-lifecycle-guard.mergeCompleteCommand':\n 'This field was removed. The command the unfinished-merge block prints lives in ONE place: ' +\n '\"commands\": { \"guardHints\": { \"mergeInProgress\": <value> } }. Move your value there and delete ' +\n 'this key.',\n};\n\n// Universal field renames (apply to EVERY rule/guard AND every match-rule, unlike the per-rule\n// RETIRED_FIELD_HINTS). When an entry still uses the old escape-hatch name, the generic unknown-field\n// error is replaced with a precise \"renamed to X — rename it\" instruction so the fix is mechanical.\nconst RENAMED_FIELD_ALIASES: Record<string, string> = {\n ignoreModifiedUntilEpoch: 'turnOffRuleUntilEpoch',\n ignoreRuleWhileOnBranch: 'turnOffRuleWhileOnBranch',\n};\n\n\n// The renamed-field message shared by keyed rules and match-rules.\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction renamedFieldError(scope: string, oldKey: string, newKey: string): string {\n return `${scope} Unknown field \"${oldKey}\" — it was renamed to \"${newKey}\". Rename it (the value carries over unchanged; for the branch hatch, use null when there is no branch).`;\n}\n\n/**\n * Field-level errors for ONE configured rule entry: unknown/renamed/retired keys, type and enum\n * mismatches, and the two-week cap on turnOffRuleUntilEpoch.\n *\n * Extracted out of validateWebpiecesConfig purely so that function stays inside max-method-lines; it is\n * the same checks in the same order.\n */\n// webpieces-disable no-any-unknown -- one config entry as opaque JSON; every field is typed-checked below\n// webpieces-disable no-function-outside-class -- module-scope validator helper, matching every other check in this file\nfunction fieldErrors(ruleName: string, entry: Record<string, unknown>, schema: Record<string, FieldDef>): string[] {\n const errors: string[] = [];\n for (const [key, value] of Object.entries(entry)) {\n const fieldDef = schema[key];\n if (!fieldDef) {\n const renamedTo = RENAMED_FIELD_ALIASES[key];\n if (renamedTo) {\n errors.push(renamedFieldError(`[${ruleName}]`, key, renamedTo));\n continue;\n }\n const retiredHint = RETIRED_FIELD_HINTS[`${ruleName}.${key}`];\n const suffix = retiredHint ? ` ${retiredHint}` : '';\n errors.push(`[${ruleName}] Unknown field \"${key}\". Valid fields: [${Object.keys(schema).join(', ')}].${suffix}`);\n continue;\n }\n // A nullable field (e.g. turnOffRuleWhileOnBranch) accepts JSON null in addition to its type.\n if (value === null && fieldDef.nullable) continue;\n if (fieldDef.type === 'string[]') {\n if (!Array.isArray(value) || !value.every(v => typeof v === 'string'))\n errors.push(`[${ruleName}] \"${key}\" must be string[], got ${typeof value}.`);\n } else if (typeof value !== fieldDef.type) {\n errors.push(`[${ruleName}] \"${key}\" must be ${fieldDef.type}, got ${typeof value}.`);\n } else if (fieldDef.enumValues && !fieldDef.enumValues.includes(value as string)) {\n errors.push(`[${ruleName}] \"${key}\" = \"${value}\" is not valid. Must be one of: ${fieldDef.enumValues.join(', ')}.`);\n } else if (key === 'turnOffRuleUntilEpoch') {\n const capError = epochCapError(`[${ruleName}]`, value as number);\n if (capError) errors.push(capError);\n } else if (ruleName === 'branch-state-guard' && key === 'maxCommitsBehind'\n && (!Number.isInteger(value) || (value as number) < 0)) {\n errors.push('[branch-state-guard] \"maxCommitsBehind\" must be a non-negative integer.');\n }\n }\n return errors;\n}\n\n// webpieces-disable no-any-unknown -- rawRules values are opaque JSON; each field is validated individually\nexport function validateWebpiecesConfig(\n rawRules: Record<string, Record<string, unknown>>,\n hasCustomRulesDir: boolean = false,\n): string[] {\n const errors: string[] = [];\n\n // Check field-level correctness for rules that are present\n for (const [ruleName, entry] of Object.entries(rawRules)) {\n const schema = RULE_SCHEMAS[ruleName];\n if (!schema) {\n // A name we KNOW is retired beats the generic unknown-rule message, because only the table\n // knows WHERE the setting went — a rename carries its value over, and a bare \"delete it\"\n // would throw that value away. It fires even when a rulesDir is set, because a custom rule\n // must not reuse a retired name.\n const retired = retiredRuleFor(ruleName);\n if (retired) {\n errors.push(retiredKeyError(retired));\n continue;\n }\n // No built-in schema. With no rulesDir there are no custom rules, so this key is a\n // dead/typo'd entry — tell the AI to remove it (a removed rule like no-shell-substitution\n // lingers here otherwise). With a rulesDir it may be a legitimate custom rule → skip.\n if (!hasCustomRulesDir) errors.push(unknownRuleError(ruleName));\n continue;\n }\n errors.push(...fieldErrors(ruleName, entry, schema));\n // Required fields must actually be present. Until now the loop above only checked\n // fields that WERE present, so an entry like `{}` (or one missing `mode` /\n // `turnOffRuleUntilEpoch`) slipped through. Every non-optional schema field is mandatory.\n for (const [key, fieldDef] of Object.entries(schema)) {\n if (!fieldDef.optional && !(key in entry)) {\n errors.push(`[${ruleName}] Missing required field \"${key}\". Add ${key}: ${valueHint(fieldDef, key)}.`);\n }\n }\n }\n\n // Every built-in rule must be explicitly configured — no silent defaults.\n // When a new rule is added to the framework, this check surfaces it immediately\n // with a ready-to-copy snippet so AI can configure it in one pass.\n for (const [ruleName, schema] of Object.entries(RULE_SCHEMAS)) {\n if (!(ruleName in rawRules)) {\n errors.push(missingRuleSnippet(ruleName, schema));\n }\n }\n\n return errors;\n}\n\nconst PR_GATE_MODES = ['ON', 'OFF'] as const;\n// Optional — omitted means DETECT. Kept inline (like prGateExample) to avoid a load-config ↔\n// pr-gate-config import cycle; the canonical list + semantics live in pr-gate-config.ts.\nconst PR_GATE_MERGE_MODES = ['AUTO', 'NONE'] as const;\n\n// Spelled out because the choice is a POLICY decision with a consequence the chooser cannot see:\n// only the AUTO path can put the compact risk/flags body in main's history, because a UI merge is\n// composed from the repo's squash_merge_commit_title/message settings, which the tooling pins.\nconst MERGE_MODE_HELP = (\n `Must be one of: ${PR_GATE_MERGE_MODES.join(', ')}.\\n` +\n ` \"AUTO\" — wp-finish-upsert-pr lands the PR: it squash-merges when mergeable, else enables\\n` +\n ` GitHub auto-merge, both with an explicit --subject/--body-file so main's history\\n` +\n ` gets the PR title + the compact risk/flags body. Needs allow_auto_merge on the repo\\n` +\n ` (gh api repos/{owner}/{repo} --jq .allow_auto_merge).\\n` +\n ` \"NONE\" — wp-finish-upsert-pr only opens/updates the PR; a human merges it, and that still\\n` +\n ` lands the compact body: the PR DESCRIPTION *is* that body, and stage \\u2462 keeps the repo's\\n` +\n ` squash_merge_commit_title/message pinned to PR_TITLE/PR_BODY so a UI merge copies it\\n` +\n ` verbatim (SquashSettingsEnforcer). Nothing to set by hand.`\n);\n\n// Copy-paste example for the top-level `pr-gate` block (sibling of `rules`). Kept inline rather\n// than imported from pr-gate-config.ts to avoid a load-config ↔ pr-gate-config import cycle.\nfunction prGateExample(): string {\n return (\n ` \"pr-gate\": {\\n` +\n ` \"mode\": \"ON\",\\n` +\n ` \"buildCommand\": \"<command CI runs to validate a PR, e.g. pnpm nx affected --target=ci --base=$(git merge-base origin/main HEAD)>\",\\n` +\n ` \"gates\": [\\n` +\n ` { \"name\": \"API Changed\", \"patterns\": [\"libraries/apis/**\", \"**/*Api.ts\"], \"warningColor\": \"yellow\" }\\n` +\n ` ],\\n` +\n ` \"reviewerAgents\": 1, // REQUIRED — 0 disables reviews; positive values cap subagents per round\\n` +\n ` \"checklists\": [ // OPTIONAL — per-area review, each against its own doc\\n` +\n ` { \"id\": \"db-migrations\", \"doc\": \".claude/review/db-migrations.md\", \"patterns\": [\"**/*.sql\"], \"required\": true }\\n` +\n ` ]\\n` +\n ` }`\n );\n}\n\n// The `gates` array: dashboard-only warning flags. Extracted from validatePrGateSection to keep that\n// method inside the length limit.\n// webpieces-disable no-any-unknown -- `value` is the opaque consumer `gates` value until narrowed here\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction validateGatesSection(value: unknown): string[] {\n if (!Array.isArray(value)) {\n return [`[pr-gate] \"gates\" must be an array of { name, patterns, warningColor, disabled? }.`];\n }\n const errors: string[] = [];\n for (let i = 0; i < value.length; i += 1) {\n errors.push(...validateGate(value[i], i));\n }\n return errors;\n}\n\n// webpieces-disable no-any-unknown -- one gate entry from opaque consumer JSON, validated field-by-field\nfunction validateGate(gate: unknown, index: number): string[] {\n if (typeof gate !== 'object' || gate === null) {\n return [`[pr-gate] gates[${index}] must be an object { name, patterns, warningColor, disabled? }.`];\n }\n // webpieces-disable no-any-unknown -- narrowing one opaque gate object from consumer JSON\n const g = gate as Record<string, unknown>;\n const errors: string[] = [];\n if (typeof g['name'] !== 'string') errors.push(`[pr-gate] gates[${index}].name must be a string.`);\n if (!Array.isArray(g['patterns']) || !g['patterns'].every(p => typeof p === 'string'))\n errors.push(`[pr-gate] gates[${index}].patterns must be string[].`);\n if (g['warningColor'] === undefined)\n errors.push(`[pr-gate] gates[${index}].warningColor is required — set it to \"yellow\" or \"red\" (green is implicit when nothing matches).`);\n else if (g['warningColor'] !== 'yellow' && g['warningColor'] !== 'red')\n errors.push(`[pr-gate] gates[${index}].warningColor must be \"yellow\" or \"red\" (green is implicit when nothing matches).`);\n if (g['disabled'] !== undefined && typeof g['disabled'] !== 'boolean')\n errors.push(`[pr-gate] gates[${index}].disabled must be a boolean (example/inactive gate kept in the file).`);\n return errors;\n}\n\n/**\n * Validate the top-level `pr-gate` section. It is REQUIRED (a client that opts out sets mode \"OFF\").\n * `buildCommand` is required unless mode is \"OFF\". Returns human-readable, copy-paste-friendly errors\n * — never throws. The pr-gate block lives outside the FieldDef-driven `rules` schema because its\n * nested `gates`/`checklists` arrays can't be expressed there, so they get structural validation here.\n * `repoRoot` (when known) lets the `checklists[].docs` existence check run.\n */\n// webpieces-disable no-any-unknown -- `section` 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 validatePrGateSection(section: unknown, repoRoot?: string): string[] {\n if (section === undefined || section === null) {\n return [\n `[pr-gate] Not configured in webpieces.config.json. Add this block under the \"commands\" ` +\n `section (set \"mode\": \"OFF\" to opt out):\\n\\n${prGateExample()}`,\n ];\n }\n if (typeof section !== 'object' || Array.isArray(section)) {\n return [`[pr-gate] Must be an object. Example:\\n\\n${prGateExample()}`];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque pr-gate section from consumer JSON\n const s = section as Record<string, unknown>;\n const errors: string[] = [];\n\n if (!('mode' in s)) {\n errors.push(`[pr-gate] Missing required field \"mode\". Must be one of: ${PR_GATE_MODES.join(', ')}.`);\n } else if (typeof s['mode'] !== 'string' || !PR_GATE_MODES.includes(s['mode'] as typeof PR_GATE_MODES[number])) {\n errors.push(`[pr-gate] \"mode\" = \"${String(s['mode'])}\" is not valid. Must be one of: ${PR_GATE_MODES.join(', ')}.`);\n }\n\n // buildCommand and mergeMode are both required whenever the gate is active (mode !== OFF). There\n // is deliberately NO default for mergeMode: whether the tooling may land your PRs is a policy\n // decision, and either guess is wrong for somebody.\n if (s['mode'] !== 'OFF') {\n const cmd = s['buildCommand'];\n if (typeof cmd !== 'string' || cmd.trim() === '') {\n errors.push(\n `[pr-gate] Missing required field \"buildCommand\" — the command CI runs to validate a PR. ` +\n `Add e.g. \"buildCommand\": \"pnpm nx affected --target=ci --base=$(git merge-base origin/main HEAD)\".`,\n );\n }\n const mm = s['mergeMode'];\n if (!('mergeMode' in s)) {\n errors.push(`[pr-gate] Missing required field \"mergeMode\". ${MERGE_MODE_HELP}`);\n } else if (typeof mm !== 'string' || !PR_GATE_MERGE_MODES.includes(mm as typeof PR_GATE_MERGE_MODES[number])) {\n errors.push(`[pr-gate] \"mergeMode\" = \"${String(mm)}\" is not valid. ${MERGE_MODE_HELP}`);\n }\n errors.push(...validateReviewerAgentKeys(s, repoRoot));\n }\n\n if ('gates' in s) errors.push(...validateGatesSection(s['gates']));\n\n // Optional extension point: company review checklists, as an ARRAY right here in the config. Absent ⇒\n // none. The removed { doc } manifest shape is rejected with the exact migration edit.\n if ('checklists' in s) errors.push(...validateChecklistsSection(s['checklists'], repoRoot));\n\n // Optional server-token salt. Absent ⇒ no token minted, CI enforcement is a no-op. Present ⇒ must be\n // a non-empty string (an empty salt would mint a token anyone can forge from a known-empty secret).\n if ('gateSalt' in s) {\n const salt = s['gateSalt'];\n if (typeof salt !== 'string' || salt.trim() === '') {\n errors.push(`[pr-gate] \"gateSalt\" must be a non-empty string — it is the shared secret the gate token is HMAC'd with. Omit the key entirely to disable server-side token enforcement.`);\n }\n }\n\n // Optional: what happens to the LOCAL branch once its PR lands. Absent ⇒ \"archive-tag\".\n if ('landPr' in s) errors.push(...validateLandPrSection(s['landPr']));\n\n // Optional: where wp-push-dev publishes the disposable copy. Absent ⇒ \"dev-include\" / \"dev\".\n if ('devDeploy' in s) errors.push(...validateDevDeploySection(s['devDeploy']));\n\n errors.push(...validateNoGateSaltRationale(s));\n\n // Optional: publish reviewer output as a PR comment (defaults true). Must be a boolean when present.\n if ('checklistComments' in s && typeof s['checklistComments'] !== 'boolean') {\n errors.push(`[pr-gate] \"checklistComments\" must be a boolean (defaults to true; set false to keep the PR body-only).`);\n }\n\n return errors;\n}\n\nfunction excludePathsExample(): string {\n return '\"excludePaths\": [\"repositories/**\"]';\n}\n\n// A glob list: must be a string[] (may be empty). `key` names it for the error.\n// webpieces-disable no-any-unknown -- `value` is opaque consumer JSON until narrowed here\nfunction validateExcludeList(value: unknown, key: string): string[] {\n if (!(Array.isArray(value) && value.every(p => typeof p === 'string'))) {\n return [`[excludePaths] \"${key}\" must be a string[] of glob paths (use [] for none).`];\n }\n return [];\n}\n\n/**\n * Validate the REQUIRED top-level `excludePaths` block: ONE glob list suppressing hook enforcement per\n * file path, for code-style rules and file-scoped guards alike. Required so every client upgrading is\n * forced to declare it (as [] to keep today's behavior, or with real paths). Returns copy-paste\n * friendly errors and never throws — same contract as validatePrGateSection.\n *\n * The two-list object form `{ \"rules\": [...], \"guards\": [...] }` is RETIRED, not tolerated. It used to be\n * accepted and silently unioned, which is why this repo's own config sat on the dead shape for releases: an\n * accepted shape is never migrated. Rejecting it cannot wedge a consumer — editing webpieces.config.json is\n * always permitted, even while it is invalid. See retired-config-keys.ts.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed below\nexport function validateExcludePaths(section: unknown): string[] {\n if (section === undefined || section === null) {\n return [\n `[excludePaths] Not configured in webpieces.config.json. Add this REQUIRED block ` +\n `(use an empty array to keep enforcing everywhere):\\n\\n ${excludePathsExample()}`,\n ];\n }\n if (Array.isArray(section)) return validateExcludeList(section, 'excludePaths');\n if (typeof section !== 'object') {\n return [`[excludePaths] Must be a string[] of glob paths. Example:\\n\\n ${excludePathsExample()}`];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque excludePaths section from consumer JSON\n const s = section as Record<string, unknown>;\n // The retired two-list object. One table row owns the message for either key present.\n const retired = retiredEntry('rules', '[excludePaths]');\n if (retired && (s['rules'] !== undefined || s['guards'] !== undefined)) {\n return [`${retiredKeyError(retired)}\\n\\n ${excludePathsExample()}`];\n }\n return [`[excludePaths] Must be a string[] of glob paths. Example:\\n\\n ${excludePathsExample()}`];\n}\n\n// ---------------------------------------------------------------------------\n// match-rules — a new top-level ARRAY section (parallel to pr-gate/excludePaths). Each entry is a\n// client-authored content guard (raw-regex patterns + message + scoping). Validated structurally here\n// the same way pr-gate's `gates` are, because an array of objects can't be expressed in FieldDef schema.\n// ---------------------------------------------------------------------------\n\nfunction matchRulesExample(): string {\n return `\"match-rules\": ${JSON.stringify(DEFAULT_MATCH_RULES, null, 4)}`;\n}\n\n// webpieces-disable no-any-unknown -- generic type guard over an opaque JSON value\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every(v => typeof v === 'string');\n}\n\n// Compile a pattern to validate it; returns the error message, or undefined when it compiles.\nfunction regexError(pattern: string): string | undefined {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Constructed only to validate the syntax; the object is intentionally discarded.\n void new RegExp(pattern);\n return undefined;\n } catch (err: unknown) {\n const error = toError(err);\n return error.message;\n }\n}\n\n// One entry of the match-rules array, validated field-by-field (see validateGate for the pattern).\n// webpieces-disable no-any-unknown -- one match-rule entry from opaque consumer JSON, validated field-by-field\nfunction validateMatchRule(entry: unknown, index: number): string[] {\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n return [`[match-rules] entry[${index}] must be an object { name, patterns, mainMessage, mode, turnOffRuleUntilEpoch, turnOffRuleWhileOnBranch, ... }.`];\n }\n // webpieces-disable no-any-unknown -- narrowing one opaque match-rule entry from consumer JSON\n const e = entry as Record<string, unknown>;\n const label = typeof e['name'] === 'string' ? `\"${e['name']}\"` : `entry[${index}]`;\n const errors: string[] = [];\n\n if (typeof e['name'] !== 'string' || e['name'].trim() === '')\n errors.push(`[match-rules] entry[${index}].name must be a non-empty string (it is the disable token and report label).`);\n\n if (!isStringArray(e['patterns']) || e['patterns'].length === 0) {\n errors.push(`[match-rules] ${label}.patterns must be a non-empty string[] of regexes.`);\n } else {\n e['patterns'].forEach((p: string, pi: number) => {\n const rxErr = regexError(p);\n if (rxErr) errors.push(`[match-rules] ${label}.patterns[${pi}] is not a valid regex: ${rxErr}`);\n });\n }\n\n if (typeof e['mainMessage'] !== 'string' || e['mainMessage'].trim() === '')\n errors.push(`[match-rules] ${label}.mainMessage must be a non-empty string.`);\n\n if (typeof e['mode'] !== 'string' || !MODIFIED_CODE_MODES.includes(e['mode'] as typeof MODIFIED_CODE_MODES[number]))\n errors.push(`[match-rules] ${label}.mode must be one of: ${MODIFIED_CODE_MODES.join(', ')}.`);\n\n // Both escape hatches are REQUIRED on every match-rule too (same as keyed rules), so they are always\n // visible. turnOffRuleUntilEpoch: number (0 = active; a future unix-epoch in seconds = temporarily\n // off). turnOffRuleWhileOnBranch: string | null (null = always on; an EXACT branch name — matched\n // with ===, never as a glob — disables the rule while that branch is checked out).\n if (typeof e['turnOffRuleUntilEpoch'] !== 'number') {\n errors.push(`[match-rules] ${label}.turnOffRuleUntilEpoch must be a number (0 = active; future unix-epoch seconds = temporarily off).`);\n } else {\n const capError = epochCapError(`[match-rules] ${label}`, e['turnOffRuleUntilEpoch']);\n if (capError) errors.push(capError);\n }\n if (!(e['turnOffRuleWhileOnBranch'] === null || typeof e['turnOffRuleWhileOnBranch'] === 'string'))\n errors.push(`[match-rules] ${label}.turnOffRuleWhileOnBranch must be a string or null (null = no branch / always on; a string is an EXACT branch name, globs are not supported).`);\n\n // The old escape-hatch names were renamed — flag them precisely.\n for (const oldKey of Object.keys(RENAMED_FIELD_ALIASES)) {\n if (oldKey in e) errors.push(renamedFieldError(`[match-rules] ${label}`, oldKey, RENAMED_FIELD_ALIASES[oldKey]));\n }\n\n if (e['options'] !== undefined && !isStringArray(e['options']))\n errors.push(`[match-rules] ${label}.options must be a string[] (omit if not needed).`);\n if (e['allowedPaths'] !== undefined && !isStringArray(e['allowedPaths']))\n errors.push(`[match-rules] ${label}.allowedPaths must be a string[] of globs (omit if not needed).`);\n if (e['disableAllowed'] !== undefined && typeof e['disableAllowed'] !== 'boolean')\n errors.push(`[match-rules] ${label}.disableAllowed must be a boolean.`);\n\n return errors;\n}\n\n/**\n * Validate the REQUIRED top-level `match-rules` array (client-authored content guards). MISSING →\n * one error printing the ready-to-paste `no-fetch` example (add at least this; more can follow).\n * Present-but-`[]` → allowed (a conscious opt-out, matching pr-gate mode:OFF / excludePaths []).\n * Otherwise every entry is validated field-by-field (each regex compile-checked) plus name\n * uniqueness. Copy-paste-friendly errors; never throws — same contract as validatePrGateSection.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed below\nexport function validateMatchRulesSection(section: unknown): string[] {\n if (section === undefined || section === null) {\n return [\n `[match-rules] Not configured in webpieces.config.json. Add this REQUIRED top-level array — ` +\n `seed it with the no-fetch guard below (you can add more entries: no-moment, no-lodash-chain, …):\\n\\n${matchRulesExample()}`,\n ];\n }\n if (!Array.isArray(section)) {\n return [`[match-rules] Must be an array of content-guard objects. Example:\\n\\n${matchRulesExample()}`];\n }\n\n const errors: string[] = [];\n const seen = new Set<string>();\n for (let i = 0; i < section.length; i += 1) {\n errors.push(...validateMatchRule(section[i], i));\n // webpieces-disable no-any-unknown -- reading the name off an opaque entry only to dedupe\n const name = (section[i] as Record<string, unknown> | null)?.['name'];\n if (typeof name === 'string') {\n if (seen.has(name)) errors.push(`[match-rules] duplicate entry name \"${name}\" — each match-rule name must be unique.`);\n seen.add(name);\n }\n }\n return errors;\n}\n\n/**\n * Enforce that each built-in lives in its correct section: code rules under `rules`, bash guards\n * under `hookGuards`. A guard left in `rules` (or a rule placed in `hookGuards`) is reported with a\n * \"move it\" message so the split stays clean. Unknown/custom names are ignored (they may be custom\n * rules from rulesDir). Presence (\"every built-in must be configured\") is checked separately by\n * validateWebpiecesConfig against the merged map.\n */\n// webpieces-disable no-any-unknown -- section maps are opaque consumer JSON\nexport function validateSectionPlacement(\n rulesSection: Record<string, Record<string, unknown>>,\n hookGuardsSection: Record<string, Record<string, unknown>>,\n): string[] {\n const errors: string[] = [];\n for (const name of Object.keys(rulesSection)) {\n if (isHookGuard(name)) {\n errors.push(\n `[${name}] is a hook guard and ${SECTION_PLACEMENT_MARKER} \"hookGuards\" section, not \"rules\". ` +\n `Move it.`,\n );\n }\n }\n for (const name of Object.keys(hookGuardsSection)) {\n // Only flag KNOWN code rules misplaced into hookGuards; unknown names may be custom rules.\n if (!isHookGuard(name) && RULE_SCHEMAS[name]) {\n errors.push(\n `[${name}] is a code rule and ${SECTION_PLACEMENT_MARKER} \"rules\" section, not \"hookGuards\". ` +\n `Move it.`,\n );\n }\n }\n return errors;\n}\n"]}
@@ -9,7 +9,7 @@ reviewed by the ONE reviewer agent — `webpieces-reviewer` unless you override
9
9
 
10
10
  ```jsonc
11
11
  "commands": { "pr-gate": {
12
- "reviewerAgents": 1, // OPTIONAL — see below
12
+ "reviewerAgents": 1, // REQUIRED0 disables reviews; see below
13
13
  "checklists": [
14
14
  { "id": "db-migrations",
15
15
  "doc": ".claude/review/db-migrations.md",
@@ -35,14 +35,17 @@ reviewed by the ONE reviewer agent — `webpieces-reviewer` unless you override
35
35
  your own instead: set `"overrideReviewerAgent": true` and `"reviewerAgentName": "my-reviewer"`. The two go
36
36
  together — a `reviewerAgentName` without the override, or the override without a name, fails config
37
37
  validation naming the edit.
38
- - **`reviewerAgents`** (**REQUIRED** positive integer) caps how many reviewer subagents one round may use.
39
- `wp-review-upsert-pr` tells you to use AT MOST N subagents for the owed checklists and to group them as
38
+ - **`reviewerAgents`** (**REQUIRED** nonnegative integer) controls reviewer-agent execution. `0` disables
39
+ reviewer subagents, checklist verdict requirements and reviewer provenance for this project while keeping
40
+ the build, PR lifecycle, merge policy, gate token and other gates active. A positive value caps how many
41
+ reviewer subagents one round may use. `wp-review-upsert-pr` tells you to use AT MOST N subagents and group them as
40
42
  you judge best (one for all of them, or e.g. two with four each). Each subagent is handed the instructions
41
43
  file of every checklist it covers and still writes ONE verdict file per checklist, so the verdict gate and
42
44
  the PR dashboard behave identically however you grouped them. A re-run after a red verdict re-reviews only
43
45
  the checklists still owed, under the same cap.
44
46
 
45
- **Picking the number.** `1` is the cheapest: one reviewer reads the diff once holding every owed
47
+ **Picking the number.** `0` is the explicit project-level opt-out. `1` is the cheapest enabled setting:
48
+ one reviewer reads the diff once holding every owed
46
49
  checklist. Setting it to your checklist count buys independent readers — no cross-checklist contamination,
47
50
  deeper per-checklist attention — at that many times the tokens per PR. Note it is a MAXIMUM, not a quota:
48
51
  at N the AI may still group everything into one subagent if that is the sensible read.
@@ -54,7 +57,8 @@ reviewed by the ONE reviewer agent — `webpieces-reviewer` unless you override
54
57
  > absent branch is DELETED rather than kept as a fallback, per the no-backwards-compat policy: the number
55
58
  > IS the decision this key exists to record, so there is nothing sensible to default it to.
56
59
  >
57
- > **To keep the old behaviour exactly, set it to your checklist count.** To make reviews cheap, set `1`.
60
+ > **To keep the old behaviour exactly, set it to your checklist count.** To make reviews cheap, set `1`;
61
+ > to turn reviewer-agent reviews off for this project, set `0`.
58
62
 
59
63
  ## `required` — which reviews block, and which are offered
60
64
 
@@ -139,12 +143,12 @@ The changed files + the exact base sha the gate uses are in
139
143
  { "base": "<merge-base sha>", "head": "<HEAD sha>", "changedFiles": ["path/a.ts", "db/003.sql", ...] }
140
144
  ```
141
145
 
142
- For each matched **required** checklist — and each **optional** one the human picked — you must:
146
+ When `reviewerAgents` is positive, for each matched **required** checklist — and each **optional** one the human picked — you must:
143
147
 
144
- 1. **Have a reviewer-agent subagent review it** a SEPARATE one per checklist, unless
145
- `reviewerAgents` lets one subagent cover several. The coding agent may **not** review its own work.
148
+ 1. **Have a reviewer-agent subagent review it**, grouping checklists under the configured positive cap.
149
+ The coding agent may **not** review its own work.
146
150
  `wp-finish` verifies from the harness's own records that a reviewer subagent actually ran on this branch
147
- for each checklist (a distinct run per checklist when `reviewerAgents` is absent).
151
+ for each checklist.
148
152
  2. Have that subagent **read the checklist's doc, then inspect the real diff** of the files in its scope —
149
153
  `git diff <base> HEAD -- <file>` (base is in `pr-context.json`) — and decide whether the change
150
154
  satisfies the checklist. (A path-coarse checklist like "new API/queues" simply reports