@webpieces/rules-config 0.4.779 → 0.4.781
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 +1 -1
- package/src/checklist-config.d.ts +4 -3
- package/src/checklist-config.js +5 -4
- package/src/checklist-config.js.map +1 -1
- package/src/checklist-validator.d.ts +2 -1
- package/src/checklist-validator.js +10 -4
- package/src/checklist-validator.js.map +1 -1
- package/src/pr-gate-config.d.ts +5 -5
- package/src/pr-gate-config.js +13 -8
- package/src/pr-gate-config.js.map +1 -1
- package/src/pr-gate-section-validators.d.ts +8 -4
- package/src/pr-gate-section-validators.js +41 -21
- package/src/pr-gate-section-validators.js.map +1 -1
- package/src/retired-config-keys.js +3 -3
- package/src/retired-config-keys.js.map +1 -1
- package/src/reviewer-instructions.d.ts +1 -1
- package/src/reviewer-instructions.js +2 -2
- package/src/reviewer-instructions.js.map +1 -1
- package/src/subagent-provenance.d.ts +1 -1
- package/src/subagent-provenance.js +1 -1
- package/src/subagent-provenance.js.map +1 -1
- package/src/validate-config.js +0 -1
- package/src/validate-config.js.map +1 -1
- package/templates/webpieces.review-checklists.md +13 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/rules-config",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.781",
|
|
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",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/** The {@link ReviewerAgentPolicy.maxAgents} value meaning "`reviewerAgents` is not configured". */
|
|
2
2
|
export declare const REVIEWER_AGENTS_ONE_PER_CHECKLIST = 0;
|
|
3
|
-
/** The generic reviewer agent webpieces ships, and the
|
|
3
|
+
/** The generic reviewer agent webpieces ships, and the reviewer every repo gets unless it sets `overrideReviewerAgent`. */
|
|
4
4
|
export declare const DEFAULT_REVIEWER_AGENT_NAME = "webpieces-reviewer";
|
|
5
5
|
/**
|
|
6
6
|
* WHICH reviewer agent the gate briefs, and HOW MANY of them one round may use — straight from
|
|
7
|
-
* `commands.pr-gate.reviewerAgentName` / `commands.pr-gate.reviewerAgents`. Data-only.
|
|
7
|
+
* `commands.pr-gate.overrideReviewerAgent` + `reviewerAgentName` / `commands.pr-gate.reviewerAgents`. Data-only.
|
|
8
8
|
*
|
|
9
9
|
* One instance per loaded config, shared by every {@link ChecklistDefinition} and copied onto every matched
|
|
10
10
|
* checklist, because the answer is repo-wide: a checklist no longer names an agent type of its own. It used
|
|
@@ -16,7 +16,8 @@ export declare class ReviewerAgentPolicy {
|
|
|
16
16
|
/**
|
|
17
17
|
* The `subagent_type` every reviewer briefing names, e.g. `webpieces-reviewer` — the generic,
|
|
18
18
|
* checklist-agnostic agent webpieces ships (`.claude/agents/webpieces-reviewer.md`, written by
|
|
19
|
-
* `wp-install-ai-hooks` / `wp-upgrade-shim`). A repo may
|
|
19
|
+
* `wp-install-ai-hooks` / `wp-upgrade-shim`). A repo may use its own agent instead by setting
|
|
20
|
+
* `"overrideReviewerAgent": true` and `"reviewerAgentName"`.
|
|
20
21
|
*/
|
|
21
22
|
agentName: string;
|
|
22
23
|
/**
|
package/src/checklist-config.js
CHANGED
|
@@ -8,11 +8,11 @@ const tslib_1 = require("tslib");
|
|
|
8
8
|
const path = tslib_1.__importStar(require("path"));
|
|
9
9
|
/** The {@link ReviewerAgentPolicy.maxAgents} value meaning "`reviewerAgents` is not configured". */
|
|
10
10
|
exports.REVIEWER_AGENTS_ONE_PER_CHECKLIST = 0;
|
|
11
|
-
/** The generic reviewer agent webpieces ships, and the
|
|
11
|
+
/** The generic reviewer agent webpieces ships, and the reviewer every repo gets unless it sets `overrideReviewerAgent`. */
|
|
12
12
|
exports.DEFAULT_REVIEWER_AGENT_NAME = 'webpieces-reviewer';
|
|
13
13
|
/**
|
|
14
14
|
* WHICH reviewer agent the gate briefs, and HOW MANY of them one round may use — straight from
|
|
15
|
-
* `commands.pr-gate.reviewerAgentName` / `commands.pr-gate.reviewerAgents`. Data-only.
|
|
15
|
+
* `commands.pr-gate.overrideReviewerAgent` + `reviewerAgentName` / `commands.pr-gate.reviewerAgents`. Data-only.
|
|
16
16
|
*
|
|
17
17
|
* One instance per loaded config, shared by every {@link ChecklistDefinition} and copied onto every matched
|
|
18
18
|
* checklist, because the answer is repo-wide: a checklist no longer names an agent type of its own. It used
|
|
@@ -24,7 +24,8 @@ class ReviewerAgentPolicy {
|
|
|
24
24
|
/**
|
|
25
25
|
* The `subagent_type` every reviewer briefing names, e.g. `webpieces-reviewer` — the generic,
|
|
26
26
|
* checklist-agnostic agent webpieces ships (`.claude/agents/webpieces-reviewer.md`, written by
|
|
27
|
-
* `wp-install-ai-hooks` / `wp-upgrade-shim`). A repo may
|
|
27
|
+
* `wp-install-ai-hooks` / `wp-upgrade-shim`). A repo may use its own agent instead by setting
|
|
28
|
+
* `"overrideReviewerAgent": true` and `"reviewerAgentName"`.
|
|
28
29
|
*/
|
|
29
30
|
agentName;
|
|
30
31
|
/**
|
|
@@ -47,7 +48,7 @@ exports.ReviewerAgentPolicy = ReviewerAgentPolicy;
|
|
|
47
48
|
// A company review checklist: a diff-triggered extension point that lets a CONSUMER inject its own
|
|
48
49
|
// PR-time review process into the webpieces gated flow WITHOUT forking the tooling. Each checklist has an
|
|
49
50
|
// `id` and the doc its reviewer reads; when the diff matches the checklist's `patterns`,
|
|
50
|
-
// wp-review-upsert-pr tells the AI to spawn the repo's reviewer agent (`
|
|
51
|
+
// wp-review-upsert-pr tells the AI to spawn the repo's reviewer agent (`webpieces-reviewer`, or an override)
|
|
51
52
|
// over it, and wp-finish-upsert-pr refuses to open the PR until a well-formed, passing review-<id>.json
|
|
52
53
|
// exists AND a reviewer subagent is proven (from the harness's own artifacts) to have actually run.
|
|
53
54
|
//
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checklist-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/checklist-config.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from 'path';\n\n/** The {@link ReviewerAgentPolicy.maxAgents} value meaning \"`reviewerAgents` is not configured\". */\nexport const REVIEWER_AGENTS_ONE_PER_CHECKLIST = 0;\n\n/** The generic reviewer agent webpieces ships, and the value the config error tells a repo to add. */\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.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 point it at its own agent instead.\n */\n agentName: string;\n /**\n * The most reviewer subagents one stage-② round may use, or {@link REVIEWER_AGENTS_ONE_PER_CHECKLIST}\n * when `reviewerAgents` is not configured — which keeps the original contract: one separate subagent per\n * checklist. When set, 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 maxAgents: number;\n\n constructor(agentName: string, maxAgents: number) {\n this.agentName = agentName;\n this.maxAgents = maxAgents;\n }\n\n /** true when `reviewerAgents` is configured, i.e. one subagent may cover several checklists. */\n grouped(): boolean {\n return this.maxAgents !== REVIEWER_AGENTS_ONE_PER_CHECKLIST;\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 (`commands.pr-gate.reviewerAgentName`)\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":"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","sourcesContent":["import * as path from 'path';\n\n/** The {@link ReviewerAgentPolicy.maxAgents} value meaning \"`reviewerAgents` is not configured\". */\nexport const REVIEWER_AGENTS_ONE_PER_CHECKLIST = 0;\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, or {@link REVIEWER_AGENTS_ONE_PER_CHECKLIST}\n * when `reviewerAgents` is not configured — which keeps the original contract: one separate subagent per\n * checklist. When set, 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 maxAgents: number;\n\n constructor(agentName: string, maxAgents: number) {\n this.agentName = agentName;\n this.maxAgents = maxAgents;\n }\n\n /** true when `reviewerAgents` is configured, i.e. one subagent may cover several checklists. */\n grouped(): boolean {\n return this.maxAgents !== REVIEWER_AGENTS_ONE_PER_CHECKLIST;\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"]}
|
|
@@ -17,7 +17,8 @@ export declare class ChecklistValidator {
|
|
|
17
17
|
*/
|
|
18
18
|
validate(repoRoot: string | undefined, defs: readonly ChecklistDefinition[]): string[];
|
|
19
19
|
/**
|
|
20
|
-
* The reviewer agent file
|
|
20
|
+
* The reviewer agent file — `webpieces-reviewer`, or `commands.pr-gate.reviewerAgentName` under
|
|
21
|
+
* `"overrideReviewerAgent": true` — checked ONCE for the repo.
|
|
21
22
|
*
|
|
22
23
|
* Only enforced when this repo HAS a `.claude/agents` dir — a non-Claude-Code consumer that drives the
|
|
23
24
|
* gate some other way must not be broken by a check for a directory it never has. Without it a typo
|
|
@@ -48,7 +48,8 @@ class ChecklistValidator {
|
|
|
48
48
|
return errors;
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
|
-
* The reviewer agent file
|
|
51
|
+
* The reviewer agent file — `webpieces-reviewer`, or `commands.pr-gate.reviewerAgentName` under
|
|
52
|
+
* `"overrideReviewerAgent": true` — checked ONCE for the repo.
|
|
52
53
|
*
|
|
53
54
|
* Only enforced when this repo HAS a `.claude/agents` dir — a non-Claude-Code consumer that drives the
|
|
54
55
|
* gate some other way must not be broken by a check for a directory it never has. Without it a typo
|
|
@@ -61,11 +62,16 @@ class ChecklistValidator {
|
|
|
61
62
|
return [];
|
|
62
63
|
if (fs.existsSync(path.join(agentsDir, `${reviewer.agentName}.md`)))
|
|
63
64
|
return [];
|
|
64
|
-
const
|
|
65
|
+
const isDefault = reviewer.agentName === checklist_config_1.DEFAULT_REVIEWER_AGENT_NAME;
|
|
66
|
+
const cure = isDefault
|
|
65
67
|
? `Run \`${constants_1.UPGRADE_SHIM_COMMAND}\` — it writes that webpieces-owned file.`
|
|
66
|
-
: 'Create that agent file, or set "reviewerAgentName" to an agent that exists
|
|
68
|
+
: 'Create that agent file, or set "reviewerAgentName" to an agent that exists, or delete both ' +
|
|
69
|
+
'"overrideReviewerAgent" and "reviewerAgentName" to use the webpieces reviewer.';
|
|
70
|
+
const source = isDefault
|
|
71
|
+
? 'the webpieces reviewer agent'
|
|
72
|
+
: 'commands.pr-gate.reviewerAgentName';
|
|
67
73
|
return [
|
|
68
|
-
`[pr-gate]
|
|
74
|
+
`[pr-gate] ${source} "${reviewer.agentName}" names no reviewer — ` +
|
|
69
75
|
`${AGENTS_DIR}/${reviewer.agentName}.md does not exist, so nothing can spawn it and wp-finish-upsert-pr ` +
|
|
70
76
|
`would block forever on verdicts no reviewer can write. ${cure}`,
|
|
71
77
|
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checklist-validator.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/checklist-validator.ts"],"names":[],"mappings":";;;;AAAA,+CAAyB;AACzB,mDAA6B;AAC7B,yDAA2G;AAC3G,2CAAmD;AAEnD,2FAA2F;AAC3F,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAElD,0GAA0G;AAC1G,yEAAyE;AACzE,MAAM,MAAM,GAAG,6CAA6C,CAAC;AAE7D,kGAAkG;AAClG,sEAAsE;AACtE,MAAM,YAAY,GAAG,8BAA8B,CAAC;AAEpD;;;;;;;;;GASG;AACH,MAAa,kBAAkB;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,QAA4B,EAAE,IAAoC;QACvE,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,IAAI,CAAC,OAAO,CAAC,CAAC,GAAwB,EAAE,CAAS,EAAQ,EAAE;YACvD,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;YAC9C,MAAM,KAAK,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC;YACjE,IAAI,GAAG,CAAC,GAAG,KAAK,EAAE,EAAE,CAAC;gBACjB,MAAM,CAAC,IAAI,CACP,aAAa,MAAM,IAAI,KAAK,iEAAiE;oBAC7F,yCAAyC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,sBAAsB;oBACtG,gDAAgD,CAAC,CAAC;YAC1D,CAAC;iBAAM,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBAChF,MAAM,CAAC,IAAI,CAAC,aAAa,MAAM,IAAI,KAAK,SAAS,GAAG,CAAC,GAAG,6CAA6C,CAAC,CAAC;YAC3G,CAAC;QACL,CAAC,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAClB,CAAC;IAED
|
|
1
|
+
{"version":3,"file":"checklist-validator.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/checklist-validator.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { ChecklistDefinition, DEFAULT_REVIEWER_AGENT_NAME, ReviewerAgentPolicy } from './checklist-config';\nimport { UPGRADE_SHIM_COMMAND } from './constants';\n\n// Where a reviewer subagent's definition must live for Claude Code to be able to spawn it.\nconst AGENTS_DIR = path.join('.claude', 'agents');\n\n// Every checklist error names this, so a reader always knows which file and key to open. There is exactly\n// ONE place checklists can be configured, so there is exactly one label.\nconst SOURCE = 'pr-gate.checklists in webpieces.config.json';\n\n// A checklist id becomes a FILE NAME (review-<id>.json, <id>.instructions.md), so it is held to a\n// file-name-safe alphabet rather than trusted to not contain a slash.\nconst CHECKLIST_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;\n\n/**\n * Validates the review checklists declared in `pr-gate.checklists`. The array in webpieces.config.json is\n * the ONLY accepted shape: there is deliberately no fallback, no second location, and no back-compat path\n * for the `{ doc }` + `<!-- webpieces:checklists -->` HTML-comment manifest this replaced. A consumer on the\n * old shape gets a hard config error naming the exact edit — which an AI applies in one pass — and that is\n * strictly better than carrying two code paths forever so that nobody has to read an error message.\n *\n * Deliberately NOT `@injectable`: config validation runs from module-level functions in validate-config.ts,\n * before any container exists, so this is constructed directly.\n */\nexport class ChecklistValidator {\n /**\n * Human-readable errors for the configured checklists, or [] when they are valid. Never throws.\n * `defs` are already narrowed by buildPrGateConfig; this checks ids plus what only the filesystem can\n * answer (each doc exists — skipped when `repoRoot` is undefined, i.e. a structure-only validation).\n */\n validate(repoRoot: string | undefined, defs: readonly ChecklistDefinition[]): string[] {\n const errors: string[] = [];\n const seen = new Set<string>();\n defs.forEach((def: ChecklistDefinition, i: number): void => {\n errors.push(...this.validateId(def, i, seen));\n const label = def.id !== '' ? `\"${def.id}\"` : `checklists[${i}]`;\n if (def.doc === '') {\n errors.push(\n `[pr-gate] ${SOURCE} ${label}.doc is required — the REPO-relative guidance doc the reviewer ` +\n `reviews against (e.g. \".claude/review/${def.id !== '' ? def.id : 'my-checklist'}.md\"). The reviewer ` +\n `agent is generic, so the doc IS the checklist.`);\n } else if (repoRoot !== undefined && !fs.existsSync(path.join(repoRoot, def.doc))) {\n errors.push(`[pr-gate] ${SOURCE} ${label}.doc \"${def.doc}\" does not exist (paths are REPO-relative).`);\n }\n });\n return errors;\n }\n\n /**\n * The reviewer agent file — `webpieces-reviewer`, or `commands.pr-gate.reviewerAgentName` under\n * `\"overrideReviewerAgent\": true` — checked ONCE for the repo.\n *\n * Only enforced when this repo HAS a `.claude/agents` dir — a non-Claude-Code consumer that drives the\n * gate some other way must not be broken by a check for a directory it never has. Without it a typo\n * validates clean, gets printed to the coding agent as \"spawn this\", and the path of least resistance\n * becomes writing the verdicts itself — the self-certification the gate exists to prevent.\n */\n validateReviewerAgent(repoRoot: string, reviewer: ReviewerAgentPolicy): string[] {\n const agentsDir = path.join(repoRoot, AGENTS_DIR);\n if (reviewer.agentName === '' || !fs.existsSync(agentsDir)) return [];\n if (fs.existsSync(path.join(agentsDir, `${reviewer.agentName}.md`))) return [];\n const isDefault = reviewer.agentName === DEFAULT_REVIEWER_AGENT_NAME;\n const cure = isDefault\n ? `Run \\`${UPGRADE_SHIM_COMMAND}\\` — it writes that webpieces-owned file.`\n : 'Create that agent file, or set \"reviewerAgentName\" to an agent that exists, or delete both ' +\n '\"overrideReviewerAgent\" and \"reviewerAgentName\" to use the webpieces reviewer.';\n const source = isDefault\n ? 'the webpieces reviewer agent'\n : 'commands.pr-gate.reviewerAgentName';\n return [\n `[pr-gate] ${source} \"${reviewer.agentName}\" names no reviewer — ` +\n `${AGENTS_DIR}/${reviewer.agentName}.md does not exist, so nothing can spawn it and wp-finish-upsert-pr ` +\n `would block forever on verdicts no reviewer can write. ${cure}`,\n ];\n }\n\n // `id` keys every per-checklist file and the dashboard row, so it must be present, unique and file-safe.\n private validateId(def: ChecklistDefinition, i: number, seen: Set<string>): string[] {\n if (def.id === '') {\n return [`[pr-gate] ${SOURCE} checklists[${i}].id must be a non-empty string — the checklist's name; it keys review-<id>.json.`];\n }\n if (!CHECKLIST_ID.test(def.id)) {\n return [`[pr-gate] ${SOURCE} checklists[${i}].id \"${def.id}\" must use only letters, digits, \".\", \"_\" and \"-\" (it becomes a file name).`];\n }\n if (seen.has(def.id)) {\n return [`[pr-gate] ${SOURCE} duplicate id \"${def.id}\" — every checklist needs its own id, because each writes its own review-<id>.json.`];\n }\n seen.add(def.id);\n return [];\n }\n}\n"]}
|
package/src/pr-gate-config.d.ts
CHANGED
|
@@ -102,11 +102,11 @@ export declare class PrGateConfig {
|
|
|
102
102
|
mergeMode: string;
|
|
103
103
|
checklists: ChecklistDefinition[];
|
|
104
104
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
105
|
+
* The ONE agent type every checklist is reviewed by — `webpieces-reviewer` unless
|
|
106
|
+
* `overrideReviewerAgent` is true, in which case `reviewerAgentName` — and `reviewerAgents`, the most such
|
|
107
|
+
* subagents a round may use. The same instance is bound into every {@link checklists} entry, so a caller
|
|
108
|
+
* holding only a checklist still knows what to spawn. Field-with-default because the constructor is at
|
|
109
|
+
* max-params; the default is the webpieces reviewer, exactly what a config that says nothing gets.
|
|
110
110
|
*/
|
|
111
111
|
reviewer: ReviewerAgentPolicy;
|
|
112
112
|
checklistComments: boolean;
|
package/src/pr-gate-config.js
CHANGED
|
@@ -167,13 +167,13 @@ class PrGateConfig {
|
|
|
167
167
|
// form is a hard config error; see validateChecklistsSection.
|
|
168
168
|
checklists;
|
|
169
169
|
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
170
|
+
* The ONE agent type every checklist is reviewed by — `webpieces-reviewer` unless
|
|
171
|
+
* `overrideReviewerAgent` is true, in which case `reviewerAgentName` — and `reviewerAgents`, the most such
|
|
172
|
+
* subagents a round may use. The same instance is bound into every {@link checklists} entry, so a caller
|
|
173
|
+
* holding only a checklist still knows what to spawn. Field-with-default because the constructor is at
|
|
174
|
+
* max-params; the default is the webpieces reviewer, exactly what a config that says nothing gets.
|
|
175
175
|
*/
|
|
176
|
-
reviewer = new checklist_config_1.ReviewerAgentPolicy(
|
|
176
|
+
reviewer = new checklist_config_1.ReviewerAgentPolicy(checklist_config_1.DEFAULT_REVIEWER_AGENT_NAME, checklist_config_1.REVIEWER_AGENTS_ONE_PER_CHECKLIST);
|
|
177
177
|
// Whether wp-finish-upsert-pr publishes each reviewer's full `output` as ONE combined PR comment
|
|
178
178
|
// (idempotently updated on every push). Defaults to true. Set false to keep the PR body-only.
|
|
179
179
|
checklistComments;
|
|
@@ -282,8 +282,13 @@ function buildPrGateConfig(section) {
|
|
|
282
282
|
const mergeMode = raw.mergeMode ?? defaults.mergeMode;
|
|
283
283
|
// Optional extension point — omitted ⇒ [] ⇒ no checklists computed anywhere downstream. A non-array here
|
|
284
284
|
// cannot reach us: validateChecklistsSection has already failed the load.
|
|
285
|
-
//
|
|
286
|
-
|
|
285
|
+
// The webpieces reviewer unless `overrideReviewerAgent` is true, in which case `reviewerAgentName` names the
|
|
286
|
+
// agent (validatePrGateSection rejects a name without the override, and the override without a name).
|
|
287
|
+
// Optional positive integer cap; absent ⇒ one subagent per checklist.
|
|
288
|
+
const agentName = raw.overrideReviewerAgent === true
|
|
289
|
+
? (raw.reviewerAgentName ?? '').trim()
|
|
290
|
+
: checklist_config_1.DEFAULT_REVIEWER_AGENT_NAME;
|
|
291
|
+
const reviewer = new checklist_config_1.ReviewerAgentPolicy(agentName, typeof raw.reviewerAgents === 'number' ? raw.reviewerAgents : checklist_config_1.REVIEWER_AGENTS_ONE_PER_CHECKLIST);
|
|
287
292
|
const checklists = Array.isArray(raw.checklists)
|
|
288
293
|
? raw.checklists.map((item) => (0, checklist_config_1.toChecklist)(item, reviewer))
|
|
289
294
|
: defaults.checklists;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pr-gate-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/pr-gate-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { BRANCH_RETENTION_ARCHIVE_TAG, BRANCH_RETENTIONS } from './branch-archiver';\nimport {\n ChecklistDefinition, RawChecklistItem, REVIEWER_AGENTS_ONE_PER_CHECKLIST, ReviewerAgentPolicy, toChecklist,\n} from './checklist-config';\n\n// PrGateConfig is the \"special section\" for the pr-gate dashboard. It does NOT live in the\n// validated `rules` map (the FieldDef schema can't express nested object arrays), but as a\n// top-level `pr-gate` key in webpieces.config.json. It is built and validated by\n// loadAndValidate (load-config.ts); this module holds only the data classes + defaults + toGate.\n\n/**\n * The build the pr-gate runs when a repo configures no `commands.pr-gate.buildCommand` — and the ONE\n * definition of it, because two things now need it: the gate that RUNS it (pr-gate's BuildAffected)\n * and the guard that REFUSES a whole-monorepo build and prints it instead (ai-hook-rules'\n * whole-repo-build-guard). A second copy in either package is a message that can teach a command the\n * gate does not run.\n *\n * `--base=$(git merge-base origin/main HEAD)` — the FORK POINT, not `origin/main`: basing on\n * origin/main marks projects touched by other people's already-merged PRs as affected. The `$(…)`\n * resolves because the gate spawns with `shell: true`; the guard resolves it itself before printing,\n * so the AI is never handed a template it might paste somewhere without a shell.\n */\nexport const DEFAULT_BUILD_COMMAND = 'pnpm nx affected --target=ci --base=$(git merge-base origin/main HEAD)';\n\nexport class GateDefinition {\n name: string;\n patterns: string[];\n // The warning color shown on the dashboard WHEN this gate's patterns match a changed file. Green\n // is implicit (shown when nothing matched), so it is never configured. warningColor is purely\n // visual — even 'red' never fails/blocks the PR (only the build gate can). 'yellow' = caution,\n // 'red' = louder \"look here\" flag (e.g. DB schema / migration changes). REQUIRED on every gate.\n warningColor: string; // 'yellow' | 'red'\n // Example/inactive gate: parsed and kept in the file (JSON has no comments) but skipped at\n // compute/render time. Other projects flip this to false and tune patterns/warningColor.\n disabled: boolean;\n\n constructor(name: string, patterns: string[], warningColor: string, disabled = false) {\n this.name = name;\n this.patterns = patterns;\n this.warningColor = warningColor;\n this.disabled = disabled;\n }\n}\n\n// How wp-finish-upsert-pr should try to LAND the PR once it has posted it. GitHub's auto-merge queue\n// is a REPO-level setting (`allow_auto_merge`) that many orgs turn OFF as a policy control, and it\n// CANNOT be forced from the client: `gh pr merge --auto` calls the enablePullRequestAutoMerge GraphQL\n// mutation, which hard-errors with \"Auto merge is not allowed for this repository\" when the repo says\n// no. So the only lever a config knob has is WHICH PATHS WE ATTEMPT — never whether the queue exists.\nexport const MERGE_MODE_AUTO = 'AUTO';\nexport const MERGE_MODE_NONE = 'NONE';\nexport const MERGE_MODES = [MERGE_MODE_AUTO, MERGE_MODE_NONE];\n\n/**\n * `pr-gate.landPr` — what happens to the LOCAL branch once its PR has landed.\n *\n * A rich object rather than a bare string (the `commands.pr-gate` precedent) so the next knob about\n * landing has a home, and so the rationale can sit beside the setting as a `*Why` sibling: JSON has no\n * comments, and `<key>Why` is how this repo documents non-obvious config.\n */\n/**\n * One `pr-gate.reviewContext` entry: a human label and a repo-relative path a reviewer is handed instead of\n * having to find it. Data-only (per CLAUDE.md).\n */\nexport class ReviewContextEntry {\n label: string;\n path: string; // repo-relative in config; resolved to absolute when written into an instructions file\n\n constructor(label: string, entryPath: string) {\n this.label = label;\n this.path = entryPath;\n }\n}\n\nexport class LandPrConfig {\n // One of BRANCH_RETENTIONS. Defaults to 'archive-tag' — see BranchArchiver for why a tag beats\n // keeping the branch, storing a patch, or trusting the reflog.\n branchRetention: string;\n\n constructor(branchRetention: string = BRANCH_RETENTION_ARCHIVE_TAG) {\n this.branchRetention = branchRetention;\n }\n}\n\n// webpieces-disable no-function-outside-class -- module-level config default, matches defaultGates/defaultPrGateConfig in this file\nexport function defaultLandPrConfig(): LandPrConfig {\n return new LandPrConfig(BRANCH_RETENTION_ARCHIVE_TAG);\n}\n\n// The two literal ref names the dev-deploy flow derives everything else from. Defaults, not policy —\n// `pr-gate.devDeploy` overrides both, because a consumer whose shared environment is called `staging`\n// must not be forced onto webpieces' vocabulary.\nexport const DEFAULT_DEV_BRANCH_NAMESPACE = 'dev-include';\nexport const DEFAULT_DEV_BRANCH = 'dev';\n\n/**\n * `pr-gate.devDeploy` — where `wp-push-dev` publishes the throwaway copy of a feature branch, and which\n * ref is the shared dev branch itself.\n *\n * WHY A NAMESPACE AT ALL, i.e. why the copy is not just the feature branch: the feature branch is the PR\n * head, and landing that PR ships whatever is on it. The moment a conflict between two devs has to be\n * resolved SOMEWHERE for the shared environment to build, that resolution needs a home that is not the PR\n * branch — otherwise \"test it in dev\" silently ships another dev's unreviewed work to production. The\n * `<branchNamespace>/<feature>` copy is that home, and it is disposable by construction.\n *\n * `devBranch` is REFUSED as a source branch (you never push the composed branch back into itself); it is\n * written by the consumer's CI only, which recomputes it from `origin/main` on every run.\n */\nexport class DevDeployConfig {\n // Literal prefix on the feature branch name: `dev-include/dean/ONE-2275`. Git refs allow slashes, and\n // nobody ever types this — the command derives it.\n branchNamespace: string;\n // The composed, CI-owned branch that actually deploys. Never a source, never pushed to by this flow.\n devBranch: string;\n\n constructor(branchNamespace: string = DEFAULT_DEV_BRANCH_NAMESPACE, devBranch: string = DEFAULT_DEV_BRANCH) {\n this.branchNamespace = branchNamespace;\n this.devBranch = devBranch;\n }\n\n /** `<branchNamespace>/<branch>` — the remote ref holding the disposable copy of `branch`. */\n copyRefFor(branch: string): string {\n return `${this.branchNamespace}/${branch}`;\n }\n\n /** The `git ls-remote --heads origin <pattern>` pattern matching every live copy. */\n copyRefGlob(): string {\n return `${this.branchNamespace}/*`;\n }\n}\n\n// webpieces-disable no-function-outside-class -- module-level config default, matches defaultGates/defaultPrGateConfig in this file\nexport function defaultDevDeployConfig(): DevDeployConfig {\n return new DevDeployConfig(DEFAULT_DEV_BRANCH_NAMESPACE, DEFAULT_DEV_BRANCH);\n}\n\nexport class PrGateConfig {\n mode: string;\n /**\n * The nx-affected build gate command, shared by the two commands that can run it.\n *\n * It is NOT finish-only any more, and the change is the point. It used to run once, in\n * wp-finish-upsert-pr, which meant reviewers were spawned against a branch nobody had built — they could\n * spend a full review on code that does not compile, or on an unresolved 3-point merge.\n *\n * Now `wp-review-upsert-pr` finalizes the merge and runs this gate BEFORE briefing any reviewer, and\n * records the passing HEAD sha in the stage receipt. `wp-finish-upsert-pr` re-runs it only when HEAD has\n * moved since — so the flow gained a gate where it mattered without paying for two full builds.\n *\n * Empty string => BuildAffected falls back to DEFAULT_BUILD_COMMAND.\n */\n buildCommand: string;\n gates: GateDefinition[];\n /**\n * REQUIRED — every repo must state its policy; there is deliberately no default, because the two\n * answers are a real policy decision and guessing it either merges when a team did not want that,\n * or silently stops landing PRs on a team that relied on it.\n *\n * AUTO — wp-finish-upsert-pr LANDS the PR: squash-merge it right away when it is mergeable, else\n * enable GitHub auto-merge so it lands when the checks pass. Both carry an explicit --subject /\n * --body-file, which is the ONLY way main's history gets the PR title plus the compact\n * risk/flags body — no repo setting can produce that. Requires allow_auto_merge on the repo.\n * NONE — wp-finish-upsert-pr only opens/updates the PR and stops; a human merges. NOTE the cost:\n * a UI merge cannot use the compact body, so main's commit falls back to the repo's\n * squash_merge_commit_title/message settings. Set squash_merge_commit_title=PR_TITLE there, or\n * commits land as the internal \"Squash merge of <branch>\" subject.\n */\n mergeMode: string;\n // This repo's review checklists, straight from the `pr-gate.checklists` ARRAY in webpieces.config.json —\n // the ONLY accepted shape (`patterns` is a path-glob dispatch table and `id` the checklist's name, so both\n // are config). Every def carries {@link reviewer}. [] = no checklists. The removed `{ doc }` manifest\n // form is a hard config error; see validateChecklistsSection.\n checklists: ChecklistDefinition[];\n /**\n * `reviewerAgentName` + `reviewerAgents`: the ONE agent type every checklist is reviewed by, and the most\n * such subagents a round may use. The same instance is bound into every {@link checklists} entry, so a\n * caller holding only a checklist still knows what to spawn. Field-with-default because the constructor\n * is at max-params; the empty name only ever survives on the no-config / mode-OFF paths, where no\n * reviewer is briefed (validatePrGateSection requires the key whenever the gate is active).\n */\n reviewer: ReviewerAgentPolicy = new ReviewerAgentPolicy('', REVIEWER_AGENTS_ONE_PER_CHECKLIST);\n // Whether wp-finish-upsert-pr publishes each reviewer's full `output` as ONE combined PR comment\n // (idempotently updated on every push). Defaults to true. Set false to keep the PR body-only.\n checklistComments: boolean;\n /**\n * Shared secret used to mint the server-verifiable gate token. `wp-finish-upsert-pr` writes\n * `HMAC(gateSalt, HEAD_sha)` as a hidden marker into the PR body (and REFUSES to mint it unless\n * every BLOCK checklist passed), so a valid token IS proof the local gate ran and passed. A CI\n * check (`wp-check-pr` + the scaffolded workflow) recomputes it from the PR head sha and this salt.\n *\n * Optional, defaults to '' — empty means \"no token minted, no CI enforcement\" (byte-identical to\n * before this field existed). This is COMMITTED, obscurity-grade: it stops unhooked teammates who\n * push + open a PR in the web UI, but is readable in-repo and therefore forgeable by a determined\n * reader. It is deliberately NOT cryptographically sound; nothing local can stop a filesystem-reading\n * agent. See RESPONSE-pr-gate-ci-enforcement / the design memo for the full tradeoff.\n */\n gateSalt: string;\n /**\n * What `wp-land-pr` (and `wp-cleanup`, which reaps the same branches) does with the LOCAL branch\n * once its PR is in main. Field-with-default rather than another positional constructor param —\n * this constructor is already at the max-params limit, and every existing `new PrGateConfig(...)`\n * call site correctly wants the default.\n */\n landPr: LandPrConfig = defaultLandPrConfig();\n /**\n * Globs whose diffs are NOT extracted into `.webpieces/pr-review/<feature>/diff/` — regenerated noise a\n * reviewer should not spend context reading (lockfiles, generated graphs). Default [].\n *\n * They are still MATCHED against checklists and still listed in the manifest, with a stub naming the\n * command that gets the real diff. Removing them from the changed-file set would read as \"this file did\n * not change\", which is a different and false claim.\n *\n * Fields-with-defaults rather than constructor params: that constructor is already at max-params, and\n * every existing `new PrGateConfig(...)` correctly wants the defaults.\n */\n reviewDiffExclude: string[] = [];\n /**\n * Repo-specific places a reviewer would otherwise hunt for, as `{label, path}` — e.g.\n * `{\"label\":\"Cloud Tasks queue names\",\"path\":\"terraform/services/\"}`. Resolved to absolute paths in each\n * generated instructions file; a configured-but-missing path is printed as missing, never dropped.\n */\n reviewContext: ReviewContextEntry[] = [];\n /**\n * Installed packages to resolve and hand reviewers by absolute directory. This is the knob aimed at a\n * measured failure: one reviewer burned three separate greps into `node_modules/@webpieces` looking for\n * a scanner the tooling could have pointed at directly.\n */\n reviewContextPackages: string[] = [];\n /**\n * Promote \"this reviewer wrote a verdict without ever opening the diff\" from a warning to a refusal.\n * Default false, and it should stay false until a repo has watched the warning for a while: the signal\n * is derived from undocumented Claude Code transcript internals, so a format change would otherwise\n * wedge every PR in the repo with no self-service way out.\n */\n requireDiffEvidence = false;\n /**\n * Where `wp-push-dev` publishes the disposable copy. Omitted ⇒ `dev-include` / `dev`, which is what\n * makes the whole flow work with NO config edit at all.\n */\n devDeploy: DevDeployConfig = defaultDevDeployConfig();\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(mode: string, buildCommand: string, gates: GateDefinition[], mergeMode: string, checklists: ChecklistDefinition[] = [], gateSalt = '', checklistComments = true) {\n this.mode = mode;\n this.buildCommand = buildCommand;\n this.gates = gates;\n this.mergeMode = mergeMode;\n this.checklists = checklists;\n this.gateSalt = gateSalt;\n this.checklistComments = checklistComments;\n }\n}\n\n// Default infra gates — path-pattern based, tuned for this monorepo. Clients override the\n// whole list via the `pr-gate.gates` array in webpieces.config.json.\nexport function defaultGates(): GateDefinition[] {\n return [\n new GateDefinition('API Changed', ['libraries/apis/**', '**/*Api.ts'], 'yellow'),\n new GateDefinition('Config Files Changed', ['**/package.json', '**/tsconfig*.json', 'nx.json', '**/*.config.*'], 'yellow'),\n new GateDefinition('Dependency Graph Changed', ['architecture/dependencies.json'], 'yellow'),\n new GateDefinition('Claude / Rules Changed', ['**/CLAUDE.md', '**/claude.*.md', '.claude/**', 'webpieces.config.json'], 'yellow'),\n ];\n}\n\nexport function defaultPrGateConfig(): PrGateConfig {\n // No default checklists — the extension point is opt-in; the default monorepo ships none.\n return new PrGateConfig('ON', '', defaultGates(), MERGE_MODE_AUTO, []);\n}\n\ninterface RawGate {\n name?: string;\n patterns?: string[];\n warningColor?: string;\n disabled?: boolean;\n}\n\ninterface RawPrGateSection {\n mode?: string;\n buildCommand?: string;\n gates?: RawGate[];\n mergeMode?: string;\n // An ARRAY, always. validateChecklistsSection rejects every other shape (including the removed { doc }).\n checklists?: RawChecklistItem[];\n reviewerAgentName?: string;\n reviewerAgents?: number;\n gateSalt?: string;\n checklistComments?: boolean;\n landPr?: RawLandPr;\n reviewDiffExclude?: string[];\n reviewContext?: RawReviewContext[];\n reviewContextPackages?: string[];\n requireDiffEvidence?: boolean;\n devDeploy?: RawDevDeploy;\n}\n\ninterface RawDevDeploy {\n branchNamespace?: string;\n devBranch?: string;\n}\n\ninterface RawLandPr {\n branchRetention?: string;\n}\n\ninterface RawReviewContext {\n label?: string;\n path?: string;\n}\n\nfunction toGate(raw: RawGate): GateDefinition {\n return new GateDefinition(raw.name ?? '', raw.patterns ?? [], raw.warningColor ?? 'yellow', raw.disabled ?? false);\n}\n\n/**\n * Build a PrGateConfig from the already-parsed top-level `pr-gate` section, falling back to defaults\n * for any field the consumer omits. Pure transform — the file read + structural validation happen in\n * loadAndValidate (load-config.ts) so every consumer goes through one validated path. Pass undefined\n * (no `pr-gate` key / no config file) to get full defaults.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed here\nexport function buildPrGateConfig(section: unknown): PrGateConfig {\n const defaults = defaultPrGateConfig();\n if (section === undefined || section === null || typeof section !== 'object') return defaults;\n\n const raw = section as RawPrGateSection;\n const mode = raw.mode ?? defaults.mode;\n const buildCommand = raw.buildCommand ?? defaults.buildCommand;\n const gates = raw.gates !== undefined ? raw.gates.map(toGate) : defaults.gates;\n // REQUIRED — validatePrGateSection rejects an omitted/unknown value, so this fallback only ever\n // applies to the no-config-file path that defaultPrGateConfig() serves.\n const mergeMode = raw.mergeMode ?? defaults.mergeMode;\n // Optional extension point — omitted ⇒ [] ⇒ no checklists computed anywhere downstream. A non-array here\n // cannot reach us: validateChecklistsSection has already failed the load.\n // Required (validatePrGateSection) / optional positive integer; absent cap ⇒ one subagent per checklist.\n const reviewer = new ReviewerAgentPolicy(\n (raw.reviewerAgentName ?? '').trim(),\n typeof raw.reviewerAgents === 'number' ? raw.reviewerAgents : REVIEWER_AGENTS_ONE_PER_CHECKLIST);\n const checklists = Array.isArray(raw.checklists)\n ? raw.checklists.map((item: RawChecklistItem): ChecklistDefinition => toChecklist(item, reviewer))\n : defaults.checklists;\n // Optional — omitted ⇒ '' ⇒ no gate token minted and CI enforcement is a no-op (back-compat).\n const gateSalt = raw.gateSalt ?? defaults.gateSalt;\n // Optional — omitted ⇒ true ⇒ reviewer output published as a PR comment.\n const checklistComments = raw.checklistComments ?? defaults.checklistComments;\n const built = new PrGateConfig(mode, buildCommand, gates, mergeMode, checklists, gateSalt, checklistComments);\n built.landPr = buildLandPrConfig(raw.landPr);\n built.reviewer = reviewer;\n // Review-context knobs. All optional and all defaulted, so a config that omits every one of them (which\n // is every consumer's config today) behaves exactly as it did before they existed.\n built.reviewDiffExclude = Array.isArray(raw.reviewDiffExclude) ? raw.reviewDiffExclude : defaults.reviewDiffExclude;\n built.reviewContextPackages = Array.isArray(raw.reviewContextPackages) ? raw.reviewContextPackages : defaults.reviewContextPackages;\n built.reviewContext = Array.isArray(raw.reviewContext)\n ? raw.reviewContext.map((e: RawReviewContext): ReviewContextEntry => new ReviewContextEntry(e.label ?? '', e.path ?? ''))\n : defaults.reviewContext;\n built.requireDiffEvidence = raw.requireDiffEvidence ?? defaults.requireDiffEvidence;\n built.devDeploy = buildDevDeployConfig(raw.devDeploy);\n return built;\n}\n\n/**\n * Build the `pr-gate.devDeploy` block. Omitted (the state of every consumer config today) ⇒ the\n * `dev-include` / `dev` defaults. An invalid value cannot reach here — validateDevDeploySection has\n * already failed the load.\n */\n// webpieces-disable no-function-outside-class -- module-level config transform, matches buildPrGateConfig above\nexport function buildDevDeployConfig(raw: RawDevDeploy | undefined): DevDeployConfig {\n const defaults = defaultDevDeployConfig();\n if (raw === undefined || raw === null || typeof raw !== 'object') return defaults;\n const namespace = typeof raw.branchNamespace === 'string' && raw.branchNamespace.trim() !== ''\n ? raw.branchNamespace.trim() : defaults.branchNamespace;\n const devBranch = typeof raw.devBranch === 'string' && raw.devBranch.trim() !== ''\n ? raw.devBranch.trim() : defaults.devBranch;\n return new DevDeployConfig(namespace, devBranch);\n}\n\n/**\n * Build the `pr-gate.landPr` block. Omitted (the current state of every consumer's config) ⇒ the\n * 'archive-tag' default, which is what makes this feature work with NO config edit at all. An invalid\n * value cannot reach here — validatePrGateSection has already failed the load.\n */\n// webpieces-disable no-function-outside-class -- module-level config transform, matches buildPrGateConfig above\nexport function buildLandPrConfig(raw: RawLandPr | undefined): LandPrConfig {\n const defaults = defaultLandPrConfig();\n if (raw === undefined || raw === null || typeof raw !== 'object') return defaults;\n const retention = raw.branchRetention;\n if (typeof retention !== 'string' || !BRANCH_RETENTIONS.includes(retention)) return defaults;\n return new LandPrConfig(retention);\n}\n\n"]}
|
|
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_ONE_PER_CHECKLIST, ReviewerAgentPolicy, toChecklist,\n} from './checklist-config';\n\n// PrGateConfig is the \"special section\" for the pr-gate dashboard. It does NOT live in the\n// validated `rules` map (the FieldDef schema can't express nested object arrays), but as a\n// top-level `pr-gate` key in webpieces.config.json. It is built and validated by\n// loadAndValidate (load-config.ts); this module holds only the data classes + defaults + toGate.\n\n/**\n * The build the pr-gate runs when a repo configures no `commands.pr-gate.buildCommand` — and the ONE\n * definition of it, because two things now need it: the gate that RUNS it (pr-gate's BuildAffected)\n * and the guard that REFUSES a whole-monorepo build and prints it instead (ai-hook-rules'\n * whole-repo-build-guard). A second copy in either package is a message that can teach a command the\n * gate does not run.\n *\n * `--base=$(git merge-base origin/main HEAD)` — the FORK POINT, not `origin/main`: basing on\n * origin/main marks projects touched by other people's already-merged PRs as affected. The `$(…)`\n * resolves because the gate spawns with `shell: true`; the guard resolves it itself before printing,\n * so the AI is never handed a template it might paste somewhere without a shell.\n */\nexport const DEFAULT_BUILD_COMMAND = 'pnpm nx affected --target=ci --base=$(git merge-base origin/main HEAD)';\n\nexport class GateDefinition {\n name: string;\n patterns: string[];\n // The warning color shown on the dashboard WHEN this gate's patterns match a changed file. Green\n // is implicit (shown when nothing matched), so it is never configured. warningColor is purely\n // visual — even 'red' never fails/blocks the PR (only the build gate can). 'yellow' = caution,\n // 'red' = louder \"look here\" flag (e.g. DB schema / migration changes). REQUIRED on every gate.\n warningColor: string; // 'yellow' | 'red'\n // Example/inactive gate: parsed and kept in the file (JSON has no comments) but skipped at\n // compute/render time. Other projects flip this to false and tune patterns/warningColor.\n disabled: boolean;\n\n constructor(name: string, patterns: string[], warningColor: string, disabled = false) {\n this.name = name;\n this.patterns = patterns;\n this.warningColor = warningColor;\n this.disabled = disabled;\n }\n}\n\n// How wp-finish-upsert-pr should try to LAND the PR once it has posted it. GitHub's auto-merge queue\n// is a REPO-level setting (`allow_auto_merge`) that many orgs turn OFF as a policy control, and it\n// CANNOT be forced from the client: `gh pr merge --auto` calls the enablePullRequestAutoMerge GraphQL\n// mutation, which hard-errors with \"Auto merge is not allowed for this repository\" when the repo says\n// no. So the only lever a config knob has is WHICH PATHS WE ATTEMPT — never whether the queue exists.\nexport const MERGE_MODE_AUTO = 'AUTO';\nexport const MERGE_MODE_NONE = 'NONE';\nexport const MERGE_MODES = [MERGE_MODE_AUTO, MERGE_MODE_NONE];\n\n/**\n * `pr-gate.landPr` — what happens to the LOCAL branch once its PR has landed.\n *\n * A rich object rather than a bare string (the `commands.pr-gate` precedent) so the next knob about\n * landing has a home, and so the rationale can sit beside the setting as a `*Why` sibling: JSON has no\n * comments, and `<key>Why` is how this repo documents non-obvious config.\n */\n/**\n * One `pr-gate.reviewContext` entry: a human label and a repo-relative path a reviewer is handed instead of\n * having to find it. Data-only (per CLAUDE.md).\n */\nexport class ReviewContextEntry {\n label: string;\n path: string; // repo-relative in config; resolved to absolute when written into an instructions file\n\n constructor(label: string, entryPath: string) {\n this.label = label;\n this.path = entryPath;\n }\n}\n\nexport class LandPrConfig {\n // One of BRANCH_RETENTIONS. Defaults to 'archive-tag' — see BranchArchiver for why a tag beats\n // keeping the branch, storing a patch, or trusting the reflog.\n branchRetention: string;\n\n constructor(branchRetention: string = BRANCH_RETENTION_ARCHIVE_TAG) {\n this.branchRetention = branchRetention;\n }\n}\n\n// webpieces-disable no-function-outside-class -- module-level config default, matches defaultGates/defaultPrGateConfig in this file\nexport function defaultLandPrConfig(): LandPrConfig {\n return new LandPrConfig(BRANCH_RETENTION_ARCHIVE_TAG);\n}\n\n// The two literal ref names the dev-deploy flow derives everything else from. Defaults, not policy —\n// `pr-gate.devDeploy` overrides both, because a consumer whose shared environment is called `staging`\n// must not be forced onto webpieces' vocabulary.\nexport const DEFAULT_DEV_BRANCH_NAMESPACE = 'dev-include';\nexport const DEFAULT_DEV_BRANCH = 'dev';\n\n/**\n * `pr-gate.devDeploy` — where `wp-push-dev` publishes the throwaway copy of a feature branch, and which\n * ref is the shared dev branch itself.\n *\n * WHY A NAMESPACE AT ALL, i.e. why the copy is not just the feature branch: the feature branch is the PR\n * head, and landing that PR ships whatever is on it. The moment a conflict between two devs has to be\n * resolved SOMEWHERE for the shared environment to build, that resolution needs a home that is not the PR\n * branch — otherwise \"test it in dev\" silently ships another dev's unreviewed work to production. The\n * `<branchNamespace>/<feature>` copy is that home, and it is disposable by construction.\n *\n * `devBranch` is REFUSED as a source branch (you never push the composed branch back into itself); it is\n * written by the consumer's CI only, which recomputes it from `origin/main` on every run.\n */\nexport class DevDeployConfig {\n // Literal prefix on the feature branch name: `dev-include/dean/ONE-2275`. Git refs allow slashes, and\n // nobody ever types this — the command derives it.\n branchNamespace: string;\n // The composed, CI-owned branch that actually deploys. Never a source, never pushed to by this flow.\n devBranch: string;\n\n constructor(branchNamespace: string = DEFAULT_DEV_BRANCH_NAMESPACE, devBranch: string = DEFAULT_DEV_BRANCH) {\n this.branchNamespace = branchNamespace;\n this.devBranch = devBranch;\n }\n\n /** `<branchNamespace>/<branch>` — the remote ref holding the disposable copy of `branch`. */\n copyRefFor(branch: string): string {\n return `${this.branchNamespace}/${branch}`;\n }\n\n /** The `git ls-remote --heads origin <pattern>` pattern matching every live copy. */\n copyRefGlob(): string {\n return `${this.branchNamespace}/*`;\n }\n}\n\n// webpieces-disable no-function-outside-class -- module-level config default, matches defaultGates/defaultPrGateConfig in this file\nexport function defaultDevDeployConfig(): DevDeployConfig {\n return new DevDeployConfig(DEFAULT_DEV_BRANCH_NAMESPACE, DEFAULT_DEV_BRANCH);\n}\n\nexport class PrGateConfig {\n mode: string;\n /**\n * The nx-affected build gate command, shared by the two commands that can run it.\n *\n * It is NOT finish-only any more, and the change is the point. It used to run once, in\n * wp-finish-upsert-pr, which meant reviewers were spawned against a branch nobody had built — they could\n * spend a full review on code that does not compile, or on an unresolved 3-point merge.\n *\n * Now `wp-review-upsert-pr` finalizes the merge and runs this gate BEFORE briefing any reviewer, and\n * records the passing HEAD sha in the stage receipt. `wp-finish-upsert-pr` re-runs it only when HEAD has\n * moved since — so the flow gained a gate where it mattered without paying for two full builds.\n *\n * Empty string => BuildAffected falls back to DEFAULT_BUILD_COMMAND.\n */\n buildCommand: string;\n gates: GateDefinition[];\n /**\n * REQUIRED — every repo must state its policy; there is deliberately no default, because the two\n * answers are a real policy decision and guessing it either merges when a team did not want that,\n * or silently stops landing PRs on a team that relied on it.\n *\n * AUTO — wp-finish-upsert-pr LANDS the PR: squash-merge it right away when it is mergeable, else\n * enable GitHub auto-merge so it lands when the checks pass. Both carry an explicit --subject /\n * --body-file, which is the ONLY way main's history gets the PR title plus the compact\n * risk/flags body — no repo setting can produce that. Requires allow_auto_merge on the repo.\n * NONE — wp-finish-upsert-pr only opens/updates the PR and stops; a human merges. NOTE the cost:\n * a UI merge cannot use the compact body, so main's commit falls back to the repo's\n * squash_merge_commit_title/message settings. Set squash_merge_commit_title=PR_TITLE there, or\n * commits land as the internal \"Squash merge of <branch>\" subject.\n */\n mergeMode: string;\n // This repo's review checklists, straight from the `pr-gate.checklists` ARRAY in webpieces.config.json —\n // the ONLY accepted shape (`patterns` is a path-glob dispatch table and `id` the checklist's name, so both\n // are config). Every def carries {@link reviewer}. [] = no checklists. The removed `{ doc }` manifest\n // form is a hard config error; see validateChecklistsSection.\n checklists: ChecklistDefinition[];\n /**\n * 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 default is the webpieces reviewer, exactly what a config that says nothing gets.\n */\n reviewer: ReviewerAgentPolicy = new ReviewerAgentPolicy(DEFAULT_REVIEWER_AGENT_NAME, REVIEWER_AGENTS_ONE_PER_CHECKLIST);\n // Whether wp-finish-upsert-pr publishes each reviewer's full `output` as ONE combined PR comment\n // (idempotently updated on every push). Defaults to true. Set false to keep the PR body-only.\n checklistComments: boolean;\n /**\n * Shared secret used to mint the server-verifiable gate token. `wp-finish-upsert-pr` writes\n * `HMAC(gateSalt, HEAD_sha)` as a hidden marker into the PR body (and REFUSES to mint it unless\n * every BLOCK checklist passed), so a valid token IS proof the local gate ran and passed. A CI\n * check (`wp-check-pr` + the scaffolded workflow) recomputes it from the PR head sha and this salt.\n *\n * Optional, defaults to '' — empty means \"no token minted, no CI enforcement\" (byte-identical to\n * before this field existed). This is COMMITTED, obscurity-grade: it stops unhooked teammates who\n * push + open a PR in the web UI, but is readable in-repo and therefore forgeable by a determined\n * reader. It is deliberately NOT cryptographically sound; nothing local can stop a filesystem-reading\n * agent. See RESPONSE-pr-gate-ci-enforcement / the design memo for the full tradeoff.\n */\n gateSalt: string;\n /**\n * What `wp-land-pr` (and `wp-cleanup`, which reaps the same branches) does with the LOCAL branch\n * once its PR is in main. Field-with-default rather than another positional constructor param —\n * this constructor is already at the max-params limit, and every existing `new PrGateConfig(...)`\n * call site correctly wants the default.\n */\n landPr: LandPrConfig = defaultLandPrConfig();\n /**\n * Globs whose diffs are NOT extracted into `.webpieces/pr-review/<feature>/diff/` — regenerated noise a\n * reviewer should not spend context reading (lockfiles, generated graphs). Default [].\n *\n * They are still MATCHED against checklists and still listed in the manifest, with a stub naming the\n * command that gets the real diff. Removing them from the changed-file set would read as \"this file did\n * not change\", which is a different and false claim.\n *\n * Fields-with-defaults rather than constructor params: that constructor is already at max-params, and\n * every existing `new PrGateConfig(...)` correctly wants the defaults.\n */\n reviewDiffExclude: string[] = [];\n /**\n * Repo-specific places a reviewer would otherwise hunt for, as `{label, path}` — e.g.\n * `{\"label\":\"Cloud Tasks queue names\",\"path\":\"terraform/services/\"}`. Resolved to absolute paths in each\n * generated instructions file; a configured-but-missing path is printed as missing, never dropped.\n */\n reviewContext: ReviewContextEntry[] = [];\n /**\n * Installed packages to resolve and hand reviewers by absolute directory. This is the knob aimed at a\n * measured failure: one reviewer burned three separate greps into `node_modules/@webpieces` looking for\n * a scanner the tooling could have pointed at directly.\n */\n reviewContextPackages: string[] = [];\n /**\n * Promote \"this reviewer wrote a verdict without ever opening the diff\" from a warning to a refusal.\n * Default false, and it should stay false until a repo has watched the warning for a while: the signal\n * is derived from undocumented Claude Code transcript internals, so a format change would otherwise\n * wedge every PR in the repo with no self-service way out.\n */\n requireDiffEvidence = false;\n /**\n * Where `wp-push-dev` publishes the disposable copy. Omitted ⇒ `dev-include` / `dev`, which is what\n * makes the whole flow work with NO config edit at all.\n */\n devDeploy: DevDeployConfig = defaultDevDeployConfig();\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(mode: string, buildCommand: string, gates: GateDefinition[], mergeMode: string, checklists: ChecklistDefinition[] = [], gateSalt = '', checklistComments = true) {\n this.mode = mode;\n this.buildCommand = buildCommand;\n this.gates = gates;\n this.mergeMode = mergeMode;\n this.checklists = checklists;\n this.gateSalt = gateSalt;\n this.checklistComments = checklistComments;\n }\n}\n\n// Default infra gates — path-pattern based, tuned for this monorepo. Clients override the\n// whole list via the `pr-gate.gates` array in webpieces.config.json.\nexport function defaultGates(): GateDefinition[] {\n return [\n new GateDefinition('API Changed', ['libraries/apis/**', '**/*Api.ts'], 'yellow'),\n new GateDefinition('Config Files Changed', ['**/package.json', '**/tsconfig*.json', 'nx.json', '**/*.config.*'], 'yellow'),\n new GateDefinition('Dependency Graph Changed', ['architecture/dependencies.json'], 'yellow'),\n new GateDefinition('Claude / Rules Changed', ['**/CLAUDE.md', '**/claude.*.md', '.claude/**', 'webpieces.config.json'], 'yellow'),\n ];\n}\n\nexport function defaultPrGateConfig(): PrGateConfig {\n // No default checklists — the extension point is opt-in; the default monorepo ships none.\n return new PrGateConfig('ON', '', defaultGates(), MERGE_MODE_AUTO, []);\n}\n\ninterface RawGate {\n name?: string;\n patterns?: string[];\n warningColor?: string;\n disabled?: boolean;\n}\n\ninterface RawPrGateSection {\n mode?: string;\n buildCommand?: string;\n gates?: RawGate[];\n mergeMode?: string;\n // An ARRAY, always. validateChecklistsSection rejects every other shape (including the removed { doc }).\n checklists?: RawChecklistItem[];\n 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 const reviewer = new ReviewerAgentPolicy(\n agentName,\n typeof raw.reviewerAgents === 'number' ? raw.reviewerAgents : REVIEWER_AGENTS_ONE_PER_CHECKLIST);\n const checklists = Array.isArray(raw.checklists)\n ? raw.checklists.map((item: RawChecklistItem): ChecklistDefinition => toChecklist(item, reviewer))\n : defaults.checklists;\n // Optional — omitted ⇒ '' ⇒ no gate token minted and CI enforcement is a no-op (back-compat).\n const gateSalt = raw.gateSalt ?? defaults.gateSalt;\n // Optional — omitted ⇒ true ⇒ reviewer output published as a PR comment.\n const checklistComments = raw.checklistComments ?? defaults.checklistComments;\n const built = new PrGateConfig(mode, buildCommand, gates, mergeMode, checklists, gateSalt, checklistComments);\n built.landPr = buildLandPrConfig(raw.landPr);\n built.reviewer = reviewer;\n // Review-context knobs. All optional and all defaulted, so a config that omits every one of them (which\n // is every consumer's config today) behaves exactly as it did before they existed.\n built.reviewDiffExclude = Array.isArray(raw.reviewDiffExclude) ? raw.reviewDiffExclude : defaults.reviewDiffExclude;\n built.reviewContextPackages = Array.isArray(raw.reviewContextPackages) ? raw.reviewContextPackages : defaults.reviewContextPackages;\n built.reviewContext = Array.isArray(raw.reviewContext)\n ? raw.reviewContext.map((e: RawReviewContext): ReviewContextEntry => new ReviewContextEntry(e.label ?? '', e.path ?? ''))\n : defaults.reviewContext;\n built.requireDiffEvidence = raw.requireDiffEvidence ?? defaults.requireDiffEvidence;\n built.devDeploy = buildDevDeployConfig(raw.devDeploy);\n return built;\n}\n\n/**\n * Build the `pr-gate.devDeploy` block. Omitted (the state of every consumer config today) ⇒ the\n * `dev-include` / `dev` defaults. An invalid value cannot reach here — validateDevDeploySection has\n * already failed the load.\n */\n// webpieces-disable no-function-outside-class -- module-level config transform, matches buildPrGateConfig above\nexport function buildDevDeployConfig(raw: RawDevDeploy | undefined): DevDeployConfig {\n const defaults = defaultDevDeployConfig();\n if (raw === undefined || raw === null || typeof raw !== 'object') return defaults;\n const namespace = typeof raw.branchNamespace === 'string' && raw.branchNamespace.trim() !== ''\n ? raw.branchNamespace.trim() : defaults.branchNamespace;\n const devBranch = typeof raw.devBranch === 'string' && raw.devBranch.trim() !== ''\n ? raw.devBranch.trim() : defaults.devBranch;\n return new DevDeployConfig(namespace, devBranch);\n}\n\n/**\n * Build the `pr-gate.landPr` block. Omitted (the current state of every consumer's config) ⇒ the\n * 'archive-tag' default, which is what makes this feature work with NO config edit at all. An invalid\n * value cannot reach here — validatePrGateSection has already failed the load.\n */\n// webpieces-disable no-function-outside-class -- module-level config transform, matches buildPrGateConfig above\nexport function buildLandPrConfig(raw: RawLandPr | undefined): LandPrConfig {\n const defaults = defaultLandPrConfig();\n if (raw === undefined || raw === null || typeof raw !== 'object') return defaults;\n const retention = raw.branchRetention;\n if (typeof retention !== 'string' || !BRANCH_RETENTIONS.includes(retention)) return defaults;\n return new LandPrConfig(retention);\n}\n\n"]}
|
|
@@ -25,13 +25,17 @@ export declare const CHECKLIST_ENTRY_LABEL = "[pr-gate.checklists]";
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function validateChecklistsSection(value: unknown, repoRoot?: string): string[];
|
|
27
27
|
export declare const REVIEWER_AGENT_NAME_KEY = "reviewerAgentName";
|
|
28
|
+
export declare const OVERRIDE_REVIEWER_AGENT_KEY = "overrideReviewerAgent";
|
|
28
29
|
export declare const REVIEWER_AGENTS_KEY = "reviewerAgents";
|
|
29
30
|
/**
|
|
30
|
-
* `
|
|
31
|
+
* `overrideReviewerAgent` (optional boolean, absent == false), `reviewerAgentName` (only with the override)
|
|
32
|
+
* and `reviewerAgents` (optional positive integer).
|
|
31
33
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
34
|
+
* The reviewer agent defaults to {@link DEFAULT_REVIEWER_AGENT_NAME} — the generic reviewer webpieces ships
|
|
35
|
+
* and manages — so a new repo writes NOTHING here (issue #947). Replacing it changes how every PR is
|
|
36
|
+
* reviewed, so it must be said out loud with `"overrideReviewerAgent": true` (greppable) AND a
|
|
37
|
+
* `reviewerAgentName`. A name WITHOUT the override is rejected rather than silently ignored: a key that looks
|
|
38
|
+
* meaningful but controls nothing is a second spelling of a decision (.claude/rules/no-backwards-compat.md).
|
|
35
39
|
*
|
|
36
40
|
* `reviewerAgents` absent is the documented "one subagent per checklist" contract — the count every repo had
|
|
37
41
|
* before the key existed, so it widens nothing; present, it CAPS the subagents one round may use.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.REVIEWER_AGENTS_KEY = exports.REVIEWER_AGENT_NAME_KEY = exports.CHECKLIST_ENTRY_LABEL = void 0;
|
|
3
|
+
exports.REVIEWER_AGENTS_KEY = exports.OVERRIDE_REVIEWER_AGENT_KEY = exports.REVIEWER_AGENT_NAME_KEY = exports.CHECKLIST_ENTRY_LABEL = void 0;
|
|
4
4
|
exports.validateNoGateSaltRationale = validateNoGateSaltRationale;
|
|
5
5
|
exports.validateChecklistsSection = validateChecklistsSection;
|
|
6
6
|
exports.validateReviewerAgentKeys = validateReviewerAgentKeys;
|
|
@@ -41,18 +41,18 @@ function validateNoGateSaltRationale(s) {
|
|
|
41
41
|
];
|
|
42
42
|
}
|
|
43
43
|
const CHECKLIST_EXAMPLE = ('Example:\n' +
|
|
44
|
-
' "reviewerAgentName": "webpieces-reviewer",\n' +
|
|
45
44
|
' "checklists": [\n' +
|
|
46
45
|
' { "id": "db-migrations",\n' +
|
|
47
46
|
' "doc": ".claude/review/db-migrations.md",\n' +
|
|
48
47
|
' "patterns": ["**/migrations/**", "**/*.sql"],\n' +
|
|
49
48
|
' "required": true }\n' +
|
|
50
49
|
' ]\n' +
|
|
51
|
-
' "id" names the checklist and keys its review-<id>.json. Every checklist is reviewed by the
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
'
|
|
55
|
-
' DO run it, a red verdict
|
|
50
|
+
' "id" names the checklist and keys its review-<id>.json. Every checklist is reviewed by the\n' +
|
|
51
|
+
' webpieces-reviewer agent (or your own: "overrideReviewerAgent": true + "reviewerAgentName"), against\n' +
|
|
52
|
+
' its own "doc" (REQUIRED, REPO-relative). Omit "patterns" (or use []) to run on every PR.\n' +
|
|
53
|
+
' "required" is MANDATORY on every entry: true blocks the PR until the reviewer passes; false makes\n' +
|
|
54
|
+
' it an OPTIONAL review the human is offered and may decline (but if they DO run it, a red verdict\n' +
|
|
55
|
+
' still blocks).');
|
|
56
56
|
// The label every RETIRED checklist-entry key is filed under in RETIRED_CONFIG_KEYS.
|
|
57
57
|
exports.CHECKLIST_ENTRY_LABEL = '[pr-gate.checklists]';
|
|
58
58
|
/**
|
|
@@ -150,7 +150,7 @@ function checklistEntryErrors(e, i) {
|
|
|
150
150
|
}
|
|
151
151
|
// Structurally check each entry, then hand the narrowed defs to ChecklistValidator for the id rules and the
|
|
152
152
|
// checks only the filesystem can answer (the guidance doc exists). The reviewer agent itself is repo-wide
|
|
153
|
-
// and is validated once, beside `reviewerAgentName`, by validateReviewerAgentKeys.
|
|
153
|
+
// and is validated once, beside `overrideReviewerAgent` / `reviewerAgentName`, by validateReviewerAgentKeys.
|
|
154
154
|
// webpieces-disable no-any-unknown -- opaque consumer JSON entries, narrowed per-field below
|
|
155
155
|
// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file
|
|
156
156
|
function validateChecklistArray(value, repoRoot) {
|
|
@@ -179,38 +179,58 @@ function validateChecklistArray(value, repoRoot) {
|
|
|
179
179
|
return [...errors, ...new checklist_validator_1.ChecklistValidator().validate(repoRoot, defs)];
|
|
180
180
|
}
|
|
181
181
|
exports.REVIEWER_AGENT_NAME_KEY = 'reviewerAgentName';
|
|
182
|
+
exports.OVERRIDE_REVIEWER_AGENT_KEY = 'overrideReviewerAgent';
|
|
182
183
|
exports.REVIEWER_AGENTS_KEY = 'reviewerAgents';
|
|
183
184
|
/**
|
|
184
|
-
* `
|
|
185
|
+
* `overrideReviewerAgent` (optional boolean, absent == false), `reviewerAgentName` (only with the override)
|
|
186
|
+
* and `reviewerAgents` (optional positive integer).
|
|
185
187
|
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
188
|
+
* The reviewer agent defaults to {@link DEFAULT_REVIEWER_AGENT_NAME} — the generic reviewer webpieces ships
|
|
189
|
+
* and manages — so a new repo writes NOTHING here (issue #947). Replacing it changes how every PR is
|
|
190
|
+
* reviewed, so it must be said out loud with `"overrideReviewerAgent": true` (greppable) AND a
|
|
191
|
+
* `reviewerAgentName`. A name WITHOUT the override is rejected rather than silently ignored: a key that looks
|
|
192
|
+
* meaningful but controls nothing is a second spelling of a decision (.claude/rules/no-backwards-compat.md).
|
|
189
193
|
*
|
|
190
194
|
* `reviewerAgents` absent is the documented "one subagent per checklist" contract — the count every repo had
|
|
191
195
|
* before the key existed, so it widens nothing; present, it CAPS the subagents one round may use.
|
|
192
196
|
*/
|
|
193
|
-
// webpieces-disable no-any-unknown -- the already-narrowed opaque pr-gate section;
|
|
197
|
+
// webpieces-disable no-any-unknown -- the already-narrowed opaque pr-gate section; three keys are read
|
|
194
198
|
// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file
|
|
195
199
|
function validateReviewerAgentKeys(s, repoRoot) {
|
|
196
200
|
const errors = [];
|
|
201
|
+
const override = s[exports.OVERRIDE_REVIEWER_AGENT_KEY];
|
|
202
|
+
const hasName = exports.REVIEWER_AGENT_NAME_KEY in s;
|
|
197
203
|
const name = s[exports.REVIEWER_AGENT_NAME_KEY];
|
|
198
204
|
const named = typeof name === 'string' && name.trim() !== '';
|
|
199
|
-
if (
|
|
200
|
-
errors.push(`[pr-gate]
|
|
201
|
-
`
|
|
202
|
-
`
|
|
203
|
-
` ${checklist_config_1.DEFAULT_REVIEWER_AGENT_NAME} is the generic reviewer webpieces ships as .claude/agents/${checklist_config_1.DEFAULT_REVIEWER_AGENT_NAME}.md ` +
|
|
204
|
-
`(\`${constants_1.UPGRADE_SHIM_COMMAND}\` writes it). Point the key at your own agent to use that one instead.`);
|
|
205
|
+
if (exports.OVERRIDE_REVIEWER_AGENT_KEY in s && typeof override !== 'boolean') {
|
|
206
|
+
errors.push(`[pr-gate] "${exports.OVERRIDE_REVIEWER_AGENT_KEY}" = ${JSON.stringify(override)} is not valid — it must be true or ` +
|
|
207
|
+
`false. Delete the key to use the webpieces reviewer (${checklist_config_1.DEFAULT_REVIEWER_AGENT_NAME}); set it to true, together ` +
|
|
208
|
+
`with "${exports.REVIEWER_AGENT_NAME_KEY}", to use an agent of your own.`);
|
|
205
209
|
}
|
|
210
|
+
else if (override === true && !named) {
|
|
211
|
+
errors.push(`[pr-gate] "${exports.OVERRIDE_REVIEWER_AGENT_KEY}": true needs "${exports.REVIEWER_AGENT_NAME_KEY}" — add the agent type every ` +
|
|
212
|
+
`reviewer subagent is spawned as (its .claude/agents/<name>.md must exist) to commands.pr-gate in ` +
|
|
213
|
+
`webpieces.config.json:\n` +
|
|
214
|
+
` "${exports.REVIEWER_AGENT_NAME_KEY}": "my-reviewer",\n` +
|
|
215
|
+
` Or delete "${exports.OVERRIDE_REVIEWER_AGENT_KEY}" to use the webpieces reviewer (${checklist_config_1.DEFAULT_REVIEWER_AGENT_NAME}).`);
|
|
216
|
+
}
|
|
217
|
+
else if (override !== true && hasName) {
|
|
218
|
+
errors.push(`[pr-gate] "${exports.REVIEWER_AGENT_NAME_KEY}" is set but "${exports.OVERRIDE_REVIEWER_AGENT_KEY}" is not true, so it would be ` +
|
|
219
|
+
`ignored. The reviewer agent defaults to ${checklist_config_1.DEFAULT_REVIEWER_AGENT_NAME} (.claude/agents/${checklist_config_1.DEFAULT_REVIEWER_AGENT_NAME}.md, ` +
|
|
220
|
+
`written by \`${constants_1.UPGRADE_SHIM_COMMAND}\`). Either:\n` +
|
|
221
|
+
` • remove "${exports.REVIEWER_AGENT_NAME_KEY}" from commands.pr-gate in webpieces.config.json to use that default, or\n` +
|
|
222
|
+
` • set "${exports.OVERRIDE_REVIEWER_AGENT_KEY}": true beside it to review with your own agent.`);
|
|
223
|
+
}
|
|
224
|
+
const agentKeysValid = errors.length === 0;
|
|
206
225
|
const max = s[exports.REVIEWER_AGENTS_KEY];
|
|
207
226
|
if (exports.REVIEWER_AGENTS_KEY in s && !(typeof max === 'number' && Number.isInteger(max) && max >= 1)) {
|
|
208
227
|
errors.push(`[pr-gate] "${exports.REVIEWER_AGENTS_KEY}" = ${JSON.stringify(max)} is not valid — it must be a positive integer: the MOST ` +
|
|
209
228
|
`reviewer subagents one review round may use, with the checklists grouped across them (1 = a single ` +
|
|
210
229
|
`subagent reviews every checklist). Delete the key to keep one subagent per checklist.`);
|
|
211
230
|
}
|
|
212
|
-
if (repoRoot !== undefined &&
|
|
213
|
-
|
|
231
|
+
if (repoRoot !== undefined && agentKeysValid) {
|
|
232
|
+
const agentName = override === true ? name.trim() : checklist_config_1.DEFAULT_REVIEWER_AGENT_NAME;
|
|
233
|
+
errors.push(...new checklist_validator_1.ChecklistValidator().validateReviewerAgent(repoRoot, new checklist_config_1.ReviewerAgentPolicy(agentName, checklist_config_1.REVIEWER_AGENTS_ONE_PER_CHECKLIST)));
|
|
214
234
|
}
|
|
215
235
|
return errors;
|
|
216
236
|
}
|