@webpieces/rules-config 0.4.498 → 0.4.499
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/branch-mutation-log.d.ts +1 -1
- package/src/branch-mutation-log.js.map +1 -1
- package/src/checklist-config.js +1 -1
- package/src/checklist-config.js.map +1 -1
- package/src/checklist-instructions.d.ts +1 -1
- package/src/checklist-instructions.js +28 -13
- package/src/checklist-instructions.js.map +1 -1
- package/src/diff-scope.d.ts +10 -0
- package/src/diff-scope.js +14 -1
- package/src/diff-scope.js.map +1 -1
- package/src/index.d.ts +3 -2
- package/src/index.js +11 -3
- package/src/index.js.map +1 -1
- package/src/pr-gate-config.d.ts +53 -0
- package/src/pr-gate-config.js +66 -4
- package/src/pr-gate-config.js.map +1 -1
- package/src/review-json.d.ts +35 -2
- package/src/review-json.js +73 -14
- package/src/review-json.js.map +1 -1
- package/src/reviewer-instructions.d.ts +86 -0
- package/src/reviewer-instructions.js +236 -0
- package/src/reviewer-instructions.js.map +1 -0
- package/src/subagent-provenance.d.ts +55 -1
- package/src/subagent-provenance.js +143 -5
- package/src/subagent-provenance.js.map +1 -1
- package/templates/webpieces.git-workflow.md +13 -7
- package/templates/webpieces.review-checklists.md +19 -8
package/src/pr-gate-config.d.ts
CHANGED
|
@@ -16,6 +16,15 @@ export declare const MERGE_MODES: string[];
|
|
|
16
16
|
* landing has a home, and so the rationale can sit beside the setting as a `*Why` sibling: JSON has no
|
|
17
17
|
* comments, and `<key>Why` is how this repo documents non-obvious config.
|
|
18
18
|
*/
|
|
19
|
+
/**
|
|
20
|
+
* One `pr-gate.reviewContext` entry: a human label and a repo-relative path a reviewer is handed instead of
|
|
21
|
+
* having to find it. Data-only (per CLAUDE.md).
|
|
22
|
+
*/
|
|
23
|
+
export declare class ReviewContextEntry {
|
|
24
|
+
label: string;
|
|
25
|
+
path: string;
|
|
26
|
+
constructor(label: string, entryPath: string);
|
|
27
|
+
}
|
|
19
28
|
export declare class LandPrConfig {
|
|
20
29
|
branchRetention: string;
|
|
21
30
|
constructor(branchRetention?: string);
|
|
@@ -23,6 +32,19 @@ export declare class LandPrConfig {
|
|
|
23
32
|
export declare function defaultLandPrConfig(): LandPrConfig;
|
|
24
33
|
export declare class PrGateConfig {
|
|
25
34
|
mode: string;
|
|
35
|
+
/**
|
|
36
|
+
* The nx-affected build gate command, shared by the two commands that can run it.
|
|
37
|
+
*
|
|
38
|
+
* It is NOT finish-only any more, and the change is the point. It used to run once, in
|
|
39
|
+
* wp-finish-upsert-pr, which meant reviewers were spawned against a branch nobody had built — they could
|
|
40
|
+
* spend a full review on code that does not compile, or on an unresolved 3-point merge.
|
|
41
|
+
*
|
|
42
|
+
* Now `wp-review-upsert-pr` finalizes the merge and runs this gate BEFORE briefing any reviewer, and
|
|
43
|
+
* records the passing HEAD sha in the stage receipt. `wp-finish-upsert-pr` re-runs it only when HEAD has
|
|
44
|
+
* moved since — so the flow gained a gate where it mattered without paying for two full builds.
|
|
45
|
+
*
|
|
46
|
+
* Empty string => BuildAffected falls back to DEFAULT_BUILD_COMMAND.
|
|
47
|
+
*/
|
|
26
48
|
buildCommand: string;
|
|
27
49
|
gates: GateDefinition[];
|
|
28
50
|
/**
|
|
@@ -62,6 +84,37 @@ export declare class PrGateConfig {
|
|
|
62
84
|
* call site correctly wants the default.
|
|
63
85
|
*/
|
|
64
86
|
landPr: LandPrConfig;
|
|
87
|
+
/**
|
|
88
|
+
* Globs whose diffs are NOT extracted into `.webpieces/pr-review/<feature>/diff/` — regenerated noise a
|
|
89
|
+
* reviewer should not spend context reading (lockfiles, generated graphs). Default [].
|
|
90
|
+
*
|
|
91
|
+
* They are still MATCHED against checklists and still listed in the manifest, with a stub naming the
|
|
92
|
+
* command that gets the real diff. Removing them from the changed-file set would read as "this file did
|
|
93
|
+
* not change", which is a different and false claim.
|
|
94
|
+
*
|
|
95
|
+
* Fields-with-defaults rather than constructor params: that constructor is already at max-params, and
|
|
96
|
+
* every existing `new PrGateConfig(...)` correctly wants the defaults.
|
|
97
|
+
*/
|
|
98
|
+
reviewDiffExclude: string[];
|
|
99
|
+
/**
|
|
100
|
+
* Repo-specific places a reviewer would otherwise hunt for, as `{label, path}` — e.g.
|
|
101
|
+
* `{"label":"Cloud Tasks queue names","path":"terraform/services/"}`. Resolved to absolute paths in each
|
|
102
|
+
* generated instructions file; a configured-but-missing path is printed as missing, never dropped.
|
|
103
|
+
*/
|
|
104
|
+
reviewContext: ReviewContextEntry[];
|
|
105
|
+
/**
|
|
106
|
+
* Installed packages to resolve and hand reviewers by absolute directory. This is the knob aimed at a
|
|
107
|
+
* measured failure: one reviewer burned three separate greps into `node_modules/@webpieces` looking for
|
|
108
|
+
* a scanner the tooling could have pointed at directly.
|
|
109
|
+
*/
|
|
110
|
+
reviewContextPackages: string[];
|
|
111
|
+
/**
|
|
112
|
+
* Promote "this reviewer wrote a verdict without ever opening the diff" from a warning to a refusal.
|
|
113
|
+
* Default false, and it should stay false until a repo has watched the warning for a while: the signal
|
|
114
|
+
* is derived from undocumented Claude Code transcript internals, so a format change would otherwise
|
|
115
|
+
* wedge every PR in the repo with no self-service way out.
|
|
116
|
+
*/
|
|
117
|
+
requireDiffEvidence: boolean;
|
|
65
118
|
constructor(mode: string, buildCommand: string, gates: GateDefinition[], mergeMode: string, checklists?: ChecklistDefinition[], gateSalt?: string, checklistComments?: boolean);
|
|
66
119
|
}
|
|
67
120
|
export declare function defaultGates(): GateDefinition[];
|
package/src/pr-gate-config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PrGateConfig = exports.LandPrConfig = exports.MERGE_MODES = exports.MERGE_MODE_NONE = exports.MERGE_MODE_AUTO = exports.GateDefinition = void 0;
|
|
3
|
+
exports.PrGateConfig = exports.LandPrConfig = exports.ReviewContextEntry = exports.MERGE_MODES = exports.MERGE_MODE_NONE = exports.MERGE_MODE_AUTO = exports.GateDefinition = void 0;
|
|
4
4
|
exports.defaultLandPrConfig = defaultLandPrConfig;
|
|
5
5
|
exports.defaultGates = defaultGates;
|
|
6
6
|
exports.defaultPrGateConfig = defaultPrGateConfig;
|
|
@@ -46,6 +46,19 @@ exports.MERGE_MODES = [exports.MERGE_MODE_AUTO, exports.MERGE_MODE_NONE];
|
|
|
46
46
|
* landing has a home, and so the rationale can sit beside the setting as a `*Why` sibling: JSON has no
|
|
47
47
|
* comments, and `<key>Why` is how this repo documents non-obvious config.
|
|
48
48
|
*/
|
|
49
|
+
/**
|
|
50
|
+
* One `pr-gate.reviewContext` entry: a human label and a repo-relative path a reviewer is handed instead of
|
|
51
|
+
* having to find it. Data-only (per CLAUDE.md).
|
|
52
|
+
*/
|
|
53
|
+
class ReviewContextEntry {
|
|
54
|
+
label;
|
|
55
|
+
path; // repo-relative in config; resolved to absolute when written into an instructions file
|
|
56
|
+
constructor(label, entryPath) {
|
|
57
|
+
this.label = label;
|
|
58
|
+
this.path = entryPath;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.ReviewContextEntry = ReviewContextEntry;
|
|
49
62
|
class LandPrConfig {
|
|
50
63
|
// One of BRANCH_RETENTIONS. Defaults to 'archive-tag' — see BranchArchiver for why a tag beats
|
|
51
64
|
// keeping the branch, storing a patch, or trusting the reflog.
|
|
@@ -61,9 +74,19 @@ function defaultLandPrConfig() {
|
|
|
61
74
|
}
|
|
62
75
|
class PrGateConfig {
|
|
63
76
|
mode;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
77
|
+
/**
|
|
78
|
+
* The nx-affected build gate command, shared by the two commands that can run it.
|
|
79
|
+
*
|
|
80
|
+
* It is NOT finish-only any more, and the change is the point. It used to run once, in
|
|
81
|
+
* wp-finish-upsert-pr, which meant reviewers were spawned against a branch nobody had built — they could
|
|
82
|
+
* spend a full review on code that does not compile, or on an unresolved 3-point merge.
|
|
83
|
+
*
|
|
84
|
+
* Now `wp-review-upsert-pr` finalizes the merge and runs this gate BEFORE briefing any reviewer, and
|
|
85
|
+
* records the passing HEAD sha in the stage receipt. `wp-finish-upsert-pr` re-runs it only when HEAD has
|
|
86
|
+
* moved since — so the flow gained a gate where it mattered without paying for two full builds.
|
|
87
|
+
*
|
|
88
|
+
* Empty string => BuildAffected falls back to DEFAULT_BUILD_COMMAND.
|
|
89
|
+
*/
|
|
67
90
|
buildCommand;
|
|
68
91
|
gates;
|
|
69
92
|
/**
|
|
@@ -109,6 +132,37 @@ class PrGateConfig {
|
|
|
109
132
|
* call site correctly wants the default.
|
|
110
133
|
*/
|
|
111
134
|
landPr = defaultLandPrConfig();
|
|
135
|
+
/**
|
|
136
|
+
* Globs whose diffs are NOT extracted into `.webpieces/pr-review/<feature>/diff/` — regenerated noise a
|
|
137
|
+
* reviewer should not spend context reading (lockfiles, generated graphs). Default [].
|
|
138
|
+
*
|
|
139
|
+
* They are still MATCHED against checklists and still listed in the manifest, with a stub naming the
|
|
140
|
+
* command that gets the real diff. Removing them from the changed-file set would read as "this file did
|
|
141
|
+
* not change", which is a different and false claim.
|
|
142
|
+
*
|
|
143
|
+
* Fields-with-defaults rather than constructor params: that constructor is already at max-params, and
|
|
144
|
+
* every existing `new PrGateConfig(...)` correctly wants the defaults.
|
|
145
|
+
*/
|
|
146
|
+
reviewDiffExclude = [];
|
|
147
|
+
/**
|
|
148
|
+
* Repo-specific places a reviewer would otherwise hunt for, as `{label, path}` — e.g.
|
|
149
|
+
* `{"label":"Cloud Tasks queue names","path":"terraform/services/"}`. Resolved to absolute paths in each
|
|
150
|
+
* generated instructions file; a configured-but-missing path is printed as missing, never dropped.
|
|
151
|
+
*/
|
|
152
|
+
reviewContext = [];
|
|
153
|
+
/**
|
|
154
|
+
* Installed packages to resolve and hand reviewers by absolute directory. This is the knob aimed at a
|
|
155
|
+
* measured failure: one reviewer burned three separate greps into `node_modules/@webpieces` looking for
|
|
156
|
+
* a scanner the tooling could have pointed at directly.
|
|
157
|
+
*/
|
|
158
|
+
reviewContextPackages = [];
|
|
159
|
+
/**
|
|
160
|
+
* Promote "this reviewer wrote a verdict without ever opening the diff" from a warning to a refusal.
|
|
161
|
+
* Default false, and it should stay false until a repo has watched the warning for a while: the signal
|
|
162
|
+
* is derived from undocumented Claude Code transcript internals, so a format change would otherwise
|
|
163
|
+
* wedge every PR in the repo with no self-service way out.
|
|
164
|
+
*/
|
|
165
|
+
requireDiffEvidence = false;
|
|
112
166
|
// eslint-disable-next-line @typescript-eslint/max-params
|
|
113
167
|
constructor(mode, buildCommand, gates, mergeMode, checklists = [], gateSalt = '', checklistComments = true) {
|
|
114
168
|
this.mode = mode;
|
|
@@ -167,6 +221,14 @@ function buildPrGateConfig(section) {
|
|
|
167
221
|
const checklistComments = raw.checklistComments ?? defaults.checklistComments;
|
|
168
222
|
const built = new PrGateConfig(mode, buildCommand, gates, mergeMode, checklists, gateSalt, checklistComments);
|
|
169
223
|
built.landPr = buildLandPrConfig(raw.landPr);
|
|
224
|
+
// Review-context knobs. All optional and all defaulted, so a config that omits every one of them (which
|
|
225
|
+
// is every consumer's config today) behaves exactly as it did before they existed.
|
|
226
|
+
built.reviewDiffExclude = Array.isArray(raw.reviewDiffExclude) ? raw.reviewDiffExclude : defaults.reviewDiffExclude;
|
|
227
|
+
built.reviewContextPackages = Array.isArray(raw.reviewContextPackages) ? raw.reviewContextPackages : defaults.reviewContextPackages;
|
|
228
|
+
built.reviewContext = Array.isArray(raw.reviewContext)
|
|
229
|
+
? raw.reviewContext.map((e) => new ReviewContextEntry(e.label ?? '', e.path ?? ''))
|
|
230
|
+
: defaults.reviewContext;
|
|
231
|
+
built.requireDiffEvidence = raw.requireDiffEvidence ?? defaults.requireDiffEvidence;
|
|
170
232
|
return built;
|
|
171
233
|
}
|
|
172
234
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pr-gate-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/pr-gate-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { BRANCH_RETENTION_ARCHIVE_TAG, BRANCH_RETENTIONS } from './branch-archiver';\nimport { ChecklistDefinition, RawChecklistItem, toChecklist } from './checklist-config';\n\n// PrGateConfig is the \"special section\" for the pr-gate dashboard. It does NOT live in the\n// validated `rules` map (the FieldDef schema can't express nested object arrays), but as a\n// top-level `pr-gate` key in webpieces.config.json. It is built and validated by\n// loadAndValidate (load-config.ts); this module holds only the data classes + defaults + toGate.\n\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 */\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\nexport class PrGateConfig {\n mode: string;\n // The nx-affected build gate command. FINISH-ONLY: only wp-finish-upsert-pr runs it (authoritatively,\n // before the one push). wp-start-upsert-pr runs no build gate — it only syncs the branch from main.\n // Empty string => BuildAffected falls back to DEFAULT_BUILD_COMMAND.\n buildCommand: string;\n gates: GateDefinition[];\n /**\n * REQUIRED — every repo must state its policy; there is deliberately no default, because the two\n * answers are a real policy decision and guessing it either merges when a team did not want that,\n * or silently stops landing PRs on a team that relied on it.\n *\n * AUTO — wp-finish-upsert-pr LANDS the PR: squash-merge it right away when it is mergeable, else\n * enable GitHub auto-merge so it lands when the checks pass. Both carry an explicit --subject /\n * --body-file, which is the ONLY way main's history gets the PR title plus the compact\n * risk/flags body — no repo setting can produce that. Requires allow_auto_merge on the repo.\n * NONE — wp-finish-upsert-pr only opens/updates the PR and stops; a human merges. NOTE the cost:\n * a UI merge cannot use the compact body, so main's commit falls back to the repo's\n * squash_merge_commit_title/message settings. Set squash_merge_commit_title=PR_TITLE there, or\n * commits land as the internal \"Squash merge of <branch>\" subject.\n */\n mergeMode: string;\n // This repo's review checklists, straight from the `pr-gate.checklists` ARRAY in webpieces.config.json —\n // the ONLY accepted shape (`patterns` is a path-glob dispatch table and `subagent` a name binding, so both\n // are config). [] = no checklists. The removed `{ doc }` manifest form is a hard config error; see\n // validateChecklistsSection.\n checklists: ChecklistDefinition[];\n // Whether wp-finish-upsert-pr publishes each reviewer's full `output` as ONE combined PR comment\n // (idempotently updated on every push). Defaults to true. Set false to keep the PR body-only.\n checklistComments: boolean;\n /**\n * Shared secret used to mint the server-verifiable gate token. `wp-finish-upsert-pr` writes\n * `HMAC(gateSalt, HEAD_sha)` as a hidden marker into the PR body (and REFUSES to mint it unless\n * every BLOCK checklist passed), so a valid token IS proof the local gate ran and passed. A CI\n * check (`wp-check-pr` + the scaffolded workflow) recomputes it from the PR head sha and this salt.\n *\n * Optional, defaults to '' — empty means \"no token minted, no CI enforcement\" (byte-identical to\n * before this field existed). This is COMMITTED, obscurity-grade: it stops unhooked teammates who\n * push + open a PR in the web UI, but is readable in-repo and therefore forgeable by a determined\n * reader. It is deliberately NOT cryptographically sound; nothing local can stop a filesystem-reading\n * agent. See RESPONSE-pr-gate-ci-enforcement / the design memo for the full tradeoff.\n */\n gateSalt: string;\n /**\n * What `wp-land-pr` (and `wp-cleanup`, which reaps the same branches) does with the LOCAL branch\n * once its PR is in main. Field-with-default rather than another positional constructor param —\n * this constructor is already at the max-params limit, and every existing `new PrGateConfig(...)`\n * call site correctly wants the default.\n */\n landPr: LandPrConfig = defaultLandPrConfig();\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(mode: string, buildCommand: string, gates: GateDefinition[], mergeMode: string, checklists: ChecklistDefinition[] = [], gateSalt = '', checklistComments = true) {\n this.mode = mode;\n this.buildCommand = buildCommand;\n this.gates = gates;\n this.mergeMode = mergeMode;\n this.checklists = checklists;\n this.gateSalt = gateSalt;\n this.checklistComments = checklistComments;\n }\n}\n\n// Default infra gates — path-pattern based, tuned for this monorepo. Clients override the\n// whole list via the `pr-gate.gates` array in webpieces.config.json.\nexport function defaultGates(): GateDefinition[] {\n return [\n new GateDefinition('API Changed', ['libraries/apis/**', '**/*Api.ts'], 'yellow'),\n new GateDefinition('Config Files Changed', ['**/package.json', '**/tsconfig*.json', 'nx.json', '**/*.config.*'], 'yellow'),\n new GateDefinition('Dependency Graph Changed', ['architecture/dependencies.json'], 'yellow'),\n new GateDefinition('Claude / Rules Changed', ['**/CLAUDE.md', '**/claude.*.md', '.claude/**', 'webpieces.config.json'], 'yellow'),\n ];\n}\n\nexport function defaultPrGateConfig(): PrGateConfig {\n // No default checklists — the extension point is opt-in; the default monorepo ships none.\n return new PrGateConfig('ON', '', defaultGates(), MERGE_MODE_AUTO, []);\n}\n\ninterface RawGate {\n name?: string;\n patterns?: string[];\n warningColor?: string;\n disabled?: boolean;\n}\n\ninterface RawPrGateSection {\n mode?: string;\n buildCommand?: string;\n gates?: RawGate[];\n mergeMode?: string;\n // An ARRAY, always. validateChecklistsSection rejects every other shape (including the removed { doc }).\n checklists?: RawChecklistItem[];\n gateSalt?: string;\n checklistComments?: boolean;\n landPr?: RawLandPr;\n}\n\ninterface RawLandPr {\n branchRetention?: string;\n}\n\nfunction toGate(raw: RawGate): GateDefinition {\n return new GateDefinition(raw.name ?? '', raw.patterns ?? [], raw.warningColor ?? 'yellow', raw.disabled ?? false);\n}\n\n/**\n * Build a PrGateConfig from the already-parsed top-level `pr-gate` section, falling back to defaults\n * for any field the consumer omits. Pure transform — the file read + structural validation happen in\n * loadAndValidate (load-config.ts) so every consumer goes through one validated path. Pass undefined\n * (no `pr-gate` key / no config file) to get full defaults.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed here\nexport function buildPrGateConfig(section: unknown): PrGateConfig {\n const defaults = defaultPrGateConfig();\n if (section === undefined || section === null || typeof section !== 'object') return defaults;\n\n const raw = section as RawPrGateSection;\n const mode = raw.mode ?? defaults.mode;\n const buildCommand = raw.buildCommand ?? defaults.buildCommand;\n const gates = raw.gates !== undefined ? raw.gates.map(toGate) : defaults.gates;\n // REQUIRED — validatePrGateSection rejects an omitted/unknown value, so this fallback only ever\n // applies to the no-config-file path that defaultPrGateConfig() serves.\n const mergeMode = raw.mergeMode ?? defaults.mergeMode;\n // Optional extension point — omitted ⇒ [] ⇒ no checklists computed anywhere downstream. A non-array here\n // cannot reach us: validateChecklistsSection has already failed the load.\n const checklists = Array.isArray(raw.checklists)\n ? raw.checklists.map((item: RawChecklistItem): ChecklistDefinition => toChecklist(item))\n : defaults.checklists;\n // Optional — omitted ⇒ '' ⇒ no gate token minted and CI enforcement is a no-op (back-compat).\n const gateSalt = raw.gateSalt ?? defaults.gateSalt;\n // Optional — omitted ⇒ true ⇒ reviewer output published as a PR comment.\n const checklistComments = raw.checklistComments ?? defaults.checklistComments;\n const built = new PrGateConfig(mode, buildCommand, gates, mergeMode, checklists, gateSalt, checklistComments);\n built.landPr = buildLandPrConfig(raw.landPr);\n return built;\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 { ChecklistDefinition, RawChecklistItem, toChecklist } from './checklist-config';\n\n// PrGateConfig is the \"special section\" for the pr-gate dashboard. It does NOT live in the\n// validated `rules` map (the FieldDef schema can't express nested object arrays), but as a\n// top-level `pr-gate` key in webpieces.config.json. It is built and validated by\n// loadAndValidate (load-config.ts); this module holds only the data classes + defaults + toGate.\n\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\nexport class PrGateConfig {\n mode: string;\n /**\n * The nx-affected build gate command, shared by the two commands that can run it.\n *\n * It is NOT finish-only any more, and the change is the point. It used to run once, in\n * wp-finish-upsert-pr, which meant reviewers were spawned against a branch nobody had built — they could\n * spend a full review on code that does not compile, or on an unresolved 3-point merge.\n *\n * Now `wp-review-upsert-pr` finalizes the merge and runs this gate BEFORE briefing any reviewer, and\n * records the passing HEAD sha in the stage receipt. `wp-finish-upsert-pr` re-runs it only when HEAD has\n * moved since — so the flow gained a gate where it mattered without paying for two full builds.\n *\n * Empty string => BuildAffected falls back to DEFAULT_BUILD_COMMAND.\n */\n buildCommand: string;\n gates: GateDefinition[];\n /**\n * REQUIRED — every repo must state its policy; there is deliberately no default, because the two\n * answers are a real policy decision and guessing it either merges when a team did not want that,\n * or silently stops landing PRs on a team that relied on it.\n *\n * AUTO — wp-finish-upsert-pr LANDS the PR: squash-merge it right away when it is mergeable, else\n * enable GitHub auto-merge so it lands when the checks pass. Both carry an explicit --subject /\n * --body-file, which is the ONLY way main's history gets the PR title plus the compact\n * risk/flags body — no repo setting can produce that. Requires allow_auto_merge on the repo.\n * NONE — wp-finish-upsert-pr only opens/updates the PR and stops; a human merges. NOTE the cost:\n * a UI merge cannot use the compact body, so main's commit falls back to the repo's\n * squash_merge_commit_title/message settings. Set squash_merge_commit_title=PR_TITLE there, or\n * commits land as the internal \"Squash merge of <branch>\" subject.\n */\n mergeMode: string;\n // This repo's review checklists, straight from the `pr-gate.checklists` ARRAY in webpieces.config.json —\n // the ONLY accepted shape (`patterns` is a path-glob dispatch table and `subagent` a name binding, so both\n // are config). [] = no checklists. The removed `{ doc }` manifest form is a hard config error; see\n // validateChecklistsSection.\n checklists: ChecklistDefinition[];\n // Whether wp-finish-upsert-pr publishes each reviewer's full `output` as ONE combined PR comment\n // (idempotently updated on every push). Defaults to true. Set false to keep the PR body-only.\n checklistComments: boolean;\n /**\n * Shared secret used to mint the server-verifiable gate token. `wp-finish-upsert-pr` writes\n * `HMAC(gateSalt, HEAD_sha)` as a hidden marker into the PR body (and REFUSES to mint it unless\n * every BLOCK checklist passed), so a valid token IS proof the local gate ran and passed. A CI\n * check (`wp-check-pr` + the scaffolded workflow) recomputes it from the PR head sha and this salt.\n *\n * Optional, defaults to '' — empty means \"no token minted, no CI enforcement\" (byte-identical to\n * before this field existed). This is COMMITTED, obscurity-grade: it stops unhooked teammates who\n * push + open a PR in the web UI, but is readable in-repo and therefore forgeable by a determined\n * reader. It is deliberately NOT cryptographically sound; nothing local can stop a filesystem-reading\n * agent. See RESPONSE-pr-gate-ci-enforcement / the design memo for the full tradeoff.\n */\n gateSalt: string;\n /**\n * What `wp-land-pr` (and `wp-cleanup`, which reaps the same branches) does with the LOCAL branch\n * once its PR is in main. Field-with-default rather than another positional constructor param —\n * this constructor is already at the max-params limit, and every existing `new PrGateConfig(...)`\n * call site correctly wants the default.\n */\n landPr: LandPrConfig = defaultLandPrConfig();\n /**\n * Globs whose diffs are NOT extracted into `.webpieces/pr-review/<feature>/diff/` — regenerated noise a\n * reviewer should not spend context reading (lockfiles, generated graphs). Default [].\n *\n * They are still MATCHED against checklists and still listed in the manifest, with a stub naming the\n * command that gets the real diff. Removing them from the changed-file set would read as \"this file did\n * not change\", which is a different and false claim.\n *\n * Fields-with-defaults rather than constructor params: that constructor is already at max-params, and\n * every existing `new PrGateConfig(...)` correctly wants the defaults.\n */\n reviewDiffExclude: string[] = [];\n /**\n * Repo-specific places a reviewer would otherwise hunt for, as `{label, path}` — e.g.\n * `{\"label\":\"Cloud Tasks queue names\",\"path\":\"terraform/services/\"}`. Resolved to absolute paths in each\n * generated instructions file; a configured-but-missing path is printed as missing, never dropped.\n */\n reviewContext: ReviewContextEntry[] = [];\n /**\n * Installed packages to resolve and hand reviewers by absolute directory. This is the knob aimed at a\n * measured failure: one reviewer burned three separate greps into `node_modules/@webpieces` looking for\n * a scanner the tooling could have pointed at directly.\n */\n reviewContextPackages: string[] = [];\n /**\n * Promote \"this reviewer wrote a verdict without ever opening the diff\" from a warning to a refusal.\n * Default false, and it should stay false until a repo has watched the warning for a while: the signal\n * is derived from undocumented Claude Code transcript internals, so a format change would otherwise\n * wedge every PR in the repo with no self-service way out.\n */\n requireDiffEvidence = false;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(mode: string, buildCommand: string, gates: GateDefinition[], mergeMode: string, checklists: ChecklistDefinition[] = [], gateSalt = '', checklistComments = true) {\n this.mode = mode;\n this.buildCommand = buildCommand;\n this.gates = gates;\n this.mergeMode = mergeMode;\n this.checklists = checklists;\n this.gateSalt = gateSalt;\n this.checklistComments = checklistComments;\n }\n}\n\n// Default infra gates — path-pattern based, tuned for this monorepo. Clients override the\n// whole list via the `pr-gate.gates` array in webpieces.config.json.\nexport function defaultGates(): GateDefinition[] {\n return [\n new GateDefinition('API Changed', ['libraries/apis/**', '**/*Api.ts'], 'yellow'),\n new GateDefinition('Config Files Changed', ['**/package.json', '**/tsconfig*.json', 'nx.json', '**/*.config.*'], 'yellow'),\n new GateDefinition('Dependency Graph Changed', ['architecture/dependencies.json'], 'yellow'),\n new GateDefinition('Claude / Rules Changed', ['**/CLAUDE.md', '**/claude.*.md', '.claude/**', 'webpieces.config.json'], 'yellow'),\n ];\n}\n\nexport function defaultPrGateConfig(): PrGateConfig {\n // No default checklists — the extension point is opt-in; the default monorepo ships none.\n return new PrGateConfig('ON', '', defaultGates(), MERGE_MODE_AUTO, []);\n}\n\ninterface RawGate {\n name?: string;\n patterns?: string[];\n warningColor?: string;\n disabled?: boolean;\n}\n\ninterface RawPrGateSection {\n mode?: string;\n buildCommand?: string;\n gates?: RawGate[];\n mergeMode?: string;\n // An ARRAY, always. validateChecklistsSection rejects every other shape (including the removed { doc }).\n checklists?: RawChecklistItem[];\n gateSalt?: string;\n checklistComments?: boolean;\n landPr?: RawLandPr;\n reviewDiffExclude?: string[];\n reviewContext?: RawReviewContext[];\n reviewContextPackages?: string[];\n requireDiffEvidence?: boolean;\n}\n\ninterface RawLandPr {\n branchRetention?: string;\n}\n\ninterface RawReviewContext {\n label?: string;\n path?: string;\n}\n\nfunction toGate(raw: RawGate): GateDefinition {\n return new GateDefinition(raw.name ?? '', raw.patterns ?? [], raw.warningColor ?? 'yellow', raw.disabled ?? false);\n}\n\n/**\n * Build a PrGateConfig from the already-parsed top-level `pr-gate` section, falling back to defaults\n * for any field the consumer omits. Pure transform — the file read + structural validation happen in\n * loadAndValidate (load-config.ts) so every consumer goes through one validated path. Pass undefined\n * (no `pr-gate` key / no config file) to get full defaults.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed here\nexport function buildPrGateConfig(section: unknown): PrGateConfig {\n const defaults = defaultPrGateConfig();\n if (section === undefined || section === null || typeof section !== 'object') return defaults;\n\n const raw = section as RawPrGateSection;\n const mode = raw.mode ?? defaults.mode;\n const buildCommand = raw.buildCommand ?? defaults.buildCommand;\n const gates = raw.gates !== undefined ? raw.gates.map(toGate) : defaults.gates;\n // REQUIRED — validatePrGateSection rejects an omitted/unknown value, so this fallback only ever\n // applies to the no-config-file path that defaultPrGateConfig() serves.\n const mergeMode = raw.mergeMode ?? defaults.mergeMode;\n // Optional extension point — omitted ⇒ [] ⇒ no checklists computed anywhere downstream. A non-array here\n // cannot reach us: validateChecklistsSection has already failed the load.\n const checklists = Array.isArray(raw.checklists)\n ? raw.checklists.map((item: RawChecklistItem): ChecklistDefinition => toChecklist(item))\n : defaults.checklists;\n // Optional — omitted ⇒ '' ⇒ no gate token minted and CI enforcement is a no-op (back-compat).\n const gateSalt = raw.gateSalt ?? defaults.gateSalt;\n // Optional — omitted ⇒ true ⇒ reviewer output published as a PR comment.\n const checklistComments = raw.checklistComments ?? defaults.checklistComments;\n const built = new PrGateConfig(mode, buildCommand, gates, mergeMode, checklists, gateSalt, checklistComments);\n built.landPr = buildLandPrConfig(raw.landPr);\n // Review-context knobs. All optional and all defaulted, so a config that omits every one of them (which\n // is every consumer's config today) behaves exactly as it did before they existed.\n built.reviewDiffExclude = Array.isArray(raw.reviewDiffExclude) ? raw.reviewDiffExclude : defaults.reviewDiffExclude;\n built.reviewContextPackages = Array.isArray(raw.reviewContextPackages) ? raw.reviewContextPackages : defaults.reviewContextPackages;\n built.reviewContext = Array.isArray(raw.reviewContext)\n ? raw.reviewContext.map((e: RawReviewContext): ReviewContextEntry => new ReviewContextEntry(e.label ?? '', e.path ?? ''))\n : defaults.reviewContext;\n built.requireDiffEvidence = raw.requireDiffEvidence ?? defaults.requireDiffEvidence;\n return built;\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"]}
|
package/src/review-json.d.ts
CHANGED
|
@@ -27,7 +27,16 @@ export declare class RequiredChecklist {
|
|
|
27
27
|
export declare class ChecklistReviewContext {
|
|
28
28
|
baseSha: string;
|
|
29
29
|
prContextPath: string;
|
|
30
|
-
|
|
30
|
+
/**
|
|
31
|
+
* The exact command that reproduces ONE file's diff, with a `-- <file>` tail — NOT assembled by the
|
|
32
|
+
* caller. This used to be hardcoded as `git diff <baseSha> HEAD -- <file>`, which returns NOTHING on a
|
|
33
|
+
* dirty tree because the changed-file set is computed base→working-tree. See DiffBasis, which derives
|
|
34
|
+
* this string from the same range the file set came from.
|
|
35
|
+
*/
|
|
36
|
+
fileDiffCommand: string;
|
|
37
|
+
diffDir: string;
|
|
38
|
+
dirty: boolean;
|
|
39
|
+
constructor(baseSha?: string, prContextPath?: string, fileDiffCommand?: string, diffDir?: string, dirty?: boolean);
|
|
31
40
|
}
|
|
32
41
|
export declare class ReviewJson {
|
|
33
42
|
title: string;
|
|
@@ -55,9 +64,19 @@ export declare class ChecklistVerdict {
|
|
|
55
64
|
}
|
|
56
65
|
export declare class PrContext {
|
|
57
66
|
base: string;
|
|
67
|
+
/**
|
|
68
|
+
* The real HEAD sha. This was once the literal string 'HEAD', which is not a fact — it cannot be
|
|
69
|
+
* compared later to detect that the tree moved under a review, and it reads as a range that was never
|
|
70
|
+
* actually diffed. Its only reader (reviewContextFor) takes `base`, so recording the sha is free.
|
|
71
|
+
*/
|
|
58
72
|
head: string;
|
|
59
73
|
changedFiles: string[];
|
|
60
|
-
|
|
74
|
+
dirty: boolean;
|
|
75
|
+
dirtyFiles: string[];
|
|
76
|
+
diffCommand: string;
|
|
77
|
+
diffDir: string;
|
|
78
|
+
generatedAt: string;
|
|
79
|
+
constructor(base: string, head: string, changedFiles: string[], dirty?: boolean, dirtyFiles?: string[], diffCommand?: string, diffDir?: string, generatedAt?: string);
|
|
61
80
|
}
|
|
62
81
|
/** Locates + loads/validates the AI-authored review.json. `@injectable(bindingScopeValues.Singleton)` so it's drawn in the design. */
|
|
63
82
|
export declare class ReviewJsonService {
|
|
@@ -100,6 +119,20 @@ export declare class ReviewJsonService {
|
|
|
100
119
|
checklistFormatErrors(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): string[];
|
|
101
120
|
private requiredChecklistErrors;
|
|
102
121
|
private checklistFileName;
|
|
122
|
+
/**
|
|
123
|
+
* THE renderer for a reviewer's verdict schema — with the reviewer's own `id` already filled in and,
|
|
124
|
+
* when known, the exact file it must write.
|
|
125
|
+
*
|
|
126
|
+
* There is one because a verdict schema that lives anywhere a human maintains it goes stale, and a
|
|
127
|
+
* reviewer follows the stale copy. That is not a hypothetical: when `success` was replaced by the
|
|
128
|
+
* tri-state `status`, hand-written `.claude/agents/*.md` files kept documenting `success`, and a real
|
|
129
|
+
* PR had to carry "the verdict format in your own agent .md file is OUT OF DATE" in the spawn prompt to
|
|
130
|
+
* work around it. Every printed copy — the stage-② roster, the generated per-reviewer instructions
|
|
131
|
+
* file, and the complaint raised against a malformed verdict — now comes from here.
|
|
132
|
+
*
|
|
133
|
+
* `verdictPath` may be '' when the caller is describing the shape rather than a specific file.
|
|
134
|
+
*/
|
|
135
|
+
verdictSchemaFor(id: string, verdictPath?: string, indent?: string): string;
|
|
103
136
|
/**
|
|
104
137
|
* Parse one review-<id>.json into a ChecklistResult. `null` ONLY when the bytes do not parse as a JSON
|
|
105
138
|
* object at all — that tolerance is why a half-written file never wedges a branch, and it degrades to
|
package/src/review-json.js
CHANGED
|
@@ -36,7 +36,7 @@ class ChecklistResult {
|
|
|
36
36
|
override; // '' = no override; non-empty = ship-anyway justification (renders 🟠 overridden)
|
|
37
37
|
// '' = a well-formed verdict. Non-empty = the file exists and parses but its verdict cannot be READ
|
|
38
38
|
// (most often: it still uses the removed `success` field). Carried as data rather than thrown so the
|
|
39
|
-
// complaint can be reported by BOTH wp-
|
|
39
|
+
// complaint can be reported by BOTH wp-review-upsert-pr and wp-finish-upsert-pr in identical words, and so a
|
|
40
40
|
// legacy file is never silently mistaken for a missing one.
|
|
41
41
|
problem;
|
|
42
42
|
// eslint-disable-next-line @typescript-eslint/max-params
|
|
@@ -78,11 +78,24 @@ exports.RequiredChecklist = RequiredChecklist;
|
|
|
78
78
|
* them over — so the printed block could not stand on its own. Data-only; empty = omit those lines.
|
|
79
79
|
*/
|
|
80
80
|
class ChecklistReviewContext {
|
|
81
|
-
baseSha; // the 3-point merge-base sha
|
|
81
|
+
baseSha; // the 3-point merge-base sha
|
|
82
82
|
prContextPath; // path of pr-context.json — the AUTHORITATIVE full changed-file set
|
|
83
|
-
|
|
83
|
+
/**
|
|
84
|
+
* The exact command that reproduces ONE file's diff, with a `-- <file>` tail — NOT assembled by the
|
|
85
|
+
* caller. This used to be hardcoded as `git diff <baseSha> HEAD -- <file>`, which returns NOTHING on a
|
|
86
|
+
* dirty tree because the changed-file set is computed base→working-tree. See DiffBasis, which derives
|
|
87
|
+
* this string from the same range the file set came from.
|
|
88
|
+
*/
|
|
89
|
+
fileDiffCommand;
|
|
90
|
+
diffDir; // dir of the MATERIALIZED diff (diff/ALL.diff + diff/files/…); '' when not written
|
|
91
|
+
dirty; // true ⇒ the range includes uncommitted + untracked work, and must be said out loud
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/max-params
|
|
93
|
+
constructor(baseSha = '', prContextPath = '', fileDiffCommand = '', diffDir = '', dirty = false) {
|
|
84
94
|
this.baseSha = baseSha;
|
|
85
95
|
this.prContextPath = prContextPath;
|
|
96
|
+
this.fileDiffCommand = fileDiffCommand;
|
|
97
|
+
this.diffDir = diffDir;
|
|
98
|
+
this.dirty = dirty;
|
|
86
99
|
}
|
|
87
100
|
}
|
|
88
101
|
exports.ChecklistReviewContext = ChecklistReviewContext;
|
|
@@ -138,12 +151,28 @@ exports.ChecklistVerdict = ChecklistVerdict;
|
|
|
138
151
|
// coarsely by path (in the config) while the subagent makes the fine, content-level judgment. Data-only.
|
|
139
152
|
class PrContext {
|
|
140
153
|
base; // the 3-point merge-base sha the gate diffs against
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
154
|
+
/**
|
|
155
|
+
* The real HEAD sha. This was once the literal string 'HEAD', which is not a fact — it cannot be
|
|
156
|
+
* compared later to detect that the tree moved under a review, and it reads as a range that was never
|
|
157
|
+
* actually diffed. Its only reader (reviewContextFor) takes `base`, so recording the sha is free.
|
|
158
|
+
*/
|
|
159
|
+
head;
|
|
160
|
+
changedFiles; // every file changed in the range (NOT tsOnly — includes .sql/.gql/Dockerfile/…)
|
|
161
|
+
dirty; // true ⇒ changedFiles includes uncommitted + untracked work
|
|
162
|
+
dirtyFiles; // exactly which paths are uncommitted/untracked — why `dirty` is true
|
|
163
|
+
diffCommand; // the command that reproduces the WHOLE diff (see DiffBasis; correct when dirty)
|
|
164
|
+
diffDir; // dir holding the materialized per-file diffs + ALL.diff; '' when not materialized
|
|
165
|
+
generatedAt; // ISO timestamp, so a stale context is detectable rather than silently trusted
|
|
166
|
+
// eslint-disable-next-line @typescript-eslint/max-params
|
|
167
|
+
constructor(base, head, changedFiles, dirty = false, dirtyFiles = [], diffCommand = '', diffDir = '', generatedAt = '') {
|
|
144
168
|
this.base = base;
|
|
145
169
|
this.head = head;
|
|
146
170
|
this.changedFiles = changedFiles;
|
|
171
|
+
this.dirty = dirty;
|
|
172
|
+
this.dirtyFiles = dirtyFiles;
|
|
173
|
+
this.diffCommand = diffCommand;
|
|
174
|
+
this.diffDir = diffDir;
|
|
175
|
+
this.generatedAt = generatedAt;
|
|
147
176
|
}
|
|
148
177
|
}
|
|
149
178
|
exports.PrContext = PrContext;
|
|
@@ -188,7 +217,13 @@ let ReviewJsonService = class ReviewJsonService {
|
|
|
188
217
|
// webpieces-disable no-any-unknown -- parsed JSON is opaque until narrowed on the next line
|
|
189
218
|
const raw = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
190
219
|
const base = typeof raw['base'] === 'string' ? raw['base'] : '';
|
|
191
|
-
|
|
220
|
+
// Recover the REPRODUCE command rather than re-deriving it: a context written by an older
|
|
221
|
+
// pr-gate has no diffCommand, and guessing `<base> HEAD` there would resurrect the exact
|
|
222
|
+
// empty-on-a-dirty-tree bug this field exists to kill. Absent ⇒ omit the line entirely.
|
|
223
|
+
const cmd = typeof raw['diffCommand'] === 'string' ? raw['diffCommand'] : '';
|
|
224
|
+
const diffDir = typeof raw['diffDir'] === 'string' ? raw['diffDir'] : '';
|
|
225
|
+
const dirty = raw['dirty'] === true;
|
|
226
|
+
return new ChecklistReviewContext(base, p, cmd === '' ? '' : `${cmd} -- <file>`, diffDir, dirty);
|
|
192
227
|
}
|
|
193
228
|
catch (err) {
|
|
194
229
|
const error = (0, to_error_1.toError)(err);
|
|
@@ -326,7 +361,7 @@ let ReviewJsonService = class ReviewJsonService {
|
|
|
326
361
|
// Every matched checklist whose verdict is FAIL (reviewed, found a problem, no override) or MISSING (no
|
|
327
362
|
// review-<id>.json written) → one error each, printing the reviewer's `output` verbatim.
|
|
328
363
|
requiredChecklistErrors(required, results) {
|
|
329
|
-
// Format complaints come from the ONE renderer, so wp-
|
|
364
|
+
// Format complaints come from the ONE renderer, so wp-review-upsert-pr and wp-finish word them identically.
|
|
330
365
|
const errors = this.checklistFormatErrors(required, results);
|
|
331
366
|
for (const req of required) {
|
|
332
367
|
const verdict = this.resolveVerdict(req, results);
|
|
@@ -349,6 +384,33 @@ let ReviewJsonService = class ReviewJsonService {
|
|
|
349
384
|
checklistFileName(checklistId) {
|
|
350
385
|
return `review-${checklistId}.json`;
|
|
351
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* THE renderer for a reviewer's verdict schema — with the reviewer's own `id` already filled in and,
|
|
389
|
+
* when known, the exact file it must write.
|
|
390
|
+
*
|
|
391
|
+
* There is one because a verdict schema that lives anywhere a human maintains it goes stale, and a
|
|
392
|
+
* reviewer follows the stale copy. That is not a hypothetical: when `success` was replaced by the
|
|
393
|
+
* tri-state `status`, hand-written `.claude/agents/*.md` files kept documenting `success`, and a real
|
|
394
|
+
* PR had to carry "the verdict format in your own agent .md file is OUT OF DATE" in the spawn prompt to
|
|
395
|
+
* work around it. Every printed copy — the stage-② roster, the generated per-reviewer instructions
|
|
396
|
+
* file, and the complaint raised against a malformed verdict — now comes from here.
|
|
397
|
+
*
|
|
398
|
+
* `verdictPath` may be '' when the caller is describing the shape rather than a specific file.
|
|
399
|
+
*/
|
|
400
|
+
verdictSchemaFor(id, verdictPath = '', indent = ' ') {
|
|
401
|
+
const lines = [
|
|
402
|
+
`${indent}{ "id": "${id}", "status": "${exports.VERDICT_GREEN} | ${exports.VERDICT_YELLOW} | ${exports.VERDICT_RED}", ` +
|
|
403
|
+
`"output": "what you checked / found", "override": "" }`,
|
|
404
|
+
`${indent} ${exports.VERDICT_GREEN} → passes, nothing to flag`,
|
|
405
|
+
`${indent} ${exports.VERDICT_YELLOW} → passes WITH CONCERNS; nothing is blocked and the concern is published on the PR`,
|
|
406
|
+
`${indent} ${exports.VERDICT_RED} → REFUSES the PR (set a non-empty "override" to ship anyway with a stated justification)`,
|
|
407
|
+
`${indent}Prefer "${exports.VERDICT_YELLOW}" over red-plus-override when the change is acceptable but worth a human's`,
|
|
408
|
+
`${indent}attention — an override reads as a deliberately-accepted defect, a yellow reads as a note.`,
|
|
409
|
+
];
|
|
410
|
+
if (verdictPath !== '')
|
|
411
|
+
lines.push(`${indent}File: ${verdictPath}`);
|
|
412
|
+
return lines.join('\n');
|
|
413
|
+
}
|
|
352
414
|
/**
|
|
353
415
|
* Parse one review-<id>.json into a ChecklistResult. `null` ONLY when the bytes do not parse as a JSON
|
|
354
416
|
* object at all — that tolerance is why a half-written file never wedges a branch, and it degrades to
|
|
@@ -389,12 +451,9 @@ let ReviewJsonService = class ReviewJsonService {
|
|
|
389
451
|
// webpieces-disable no-any-unknown -- comparing against the readonly literal tuple of valid colors
|
|
390
452
|
if (exports.VERDICT_STATUSES.includes(status))
|
|
391
453
|
return '';
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
` ${exports.VERDICT_YELLOW} → passes WITH CONCERNS; nothing is blocked and the concern is published on the PR\n` +
|
|
396
|
-
` ${exports.VERDICT_RED} → REFUSES the PR (set a non-empty "override" to ship anyway with a stated justification)\n` +
|
|
397
|
-
` File: ${filePath}`;
|
|
454
|
+
// The ONE renderer — see verdictSchemaFor. A second copy here is what let the old `success` shape
|
|
455
|
+
// survive in print after it was removed from the parser.
|
|
456
|
+
const shape = this.verdictSchemaFor(id, filePath);
|
|
398
457
|
if ('success' in raw) {
|
|
399
458
|
return `Checklist "${id}" wrote its verdict with the REMOVED "success" field. It is now a tri-state ` +
|
|
400
459
|
`"status" — there is no compatibility mode. Rewrite the file as:\n${shape}`;
|