@webpieces/pr-gate 0.4.488 → 0.4.490
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 +2 -2
- package/src/dashboard/dashboard.d.ts +50 -2
- package/src/dashboard/dashboard.js +183 -25
- package/src/dashboard/dashboard.js.map +1 -1
- package/src/scripts/commands/checklist-command.d.ts +1 -0
- package/src/scripts/commands/checklist-command.js +9 -0
- package/src/scripts/commands/checklist-command.js.map +1 -1
- package/src/scripts/commands/finish-upsert-pr-command.d.ts +17 -0
- package/src/scripts/commands/finish-upsert-pr-command.js +41 -7
- package/src/scripts/commands/finish-upsert-pr-command.js.map +1 -1
- package/src/scripts/workflow/checklist-detector.d.ts +26 -0
- package/src/scripts/workflow/checklist-detector.js +42 -10
- package/src/scripts/workflow/checklist-detector.js.map +1 -1
- package/src/scripts/workflow/checklist-scanner.d.ts +4 -2
- package/src/scripts/workflow/checklist-scanner.js +18 -4
- package/src/scripts/workflow/checklist-scanner.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/pr-gate",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.490",
|
|
4
4
|
"description": "Gated PR system: 3-point squash-merge, merge validation gate, and red/yellow/green PR dashboard. Standalone scripts, no Nx dependency required.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"directory": "packages/tooling/pr-gate"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@webpieces/rules-config": "0.4.
|
|
28
|
+
"@webpieces/rules-config": "0.4.490",
|
|
29
29
|
"@inversifyjs/binding-decorators": "1.1.5",
|
|
30
30
|
"inversify": "7.10.4",
|
|
31
31
|
"reflect-metadata": "0.2.2"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GateDefinition, ReviewJson } from '@webpieces/rules-config';
|
|
2
|
-
export declare const CHECKLIST_COMMENT_MARKER = "<!-- webpieces-checklists
|
|
2
|
+
export declare const CHECKLIST_COMMENT_MARKER = "<!-- webpieces-checklists v2 -->";
|
|
3
3
|
export declare class GateResult {
|
|
4
4
|
name: string;
|
|
5
5
|
warningColor: string;
|
|
@@ -12,6 +12,26 @@ export declare class ChecklistRow {
|
|
|
12
12
|
detail: string;
|
|
13
13
|
constructor(title: string, status: string, detail?: string);
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* One roster line of the checklist COMMENT: a defined checklist, whether its reviewer ran, and the evidence
|
|
17
|
+
* for WHY. Deliberately separate from {@link ChecklistRow} rather than five more fields on it — ChecklistRow
|
|
18
|
+
* also feeds the PR body and the squash-commit body, and roster evidence has no business travelling into
|
|
19
|
+
* main's git history or through every DashboardInput construction site.
|
|
20
|
+
*
|
|
21
|
+
* Kept in pr-gate, not rules-config: the shape of a GitHub comment is this package's concern, and
|
|
22
|
+
* rules-config is the dependency, not the dependent.
|
|
23
|
+
*/
|
|
24
|
+
export declare class ChecklistCommentRow {
|
|
25
|
+
subagent: string;
|
|
26
|
+
status: string;
|
|
27
|
+
detail: string;
|
|
28
|
+
ran: boolean;
|
|
29
|
+
configuredPatterns: string[];
|
|
30
|
+
firedPatterns: string[];
|
|
31
|
+
matchedFiles: string[];
|
|
32
|
+
changedFileCount: number;
|
|
33
|
+
constructor(subagent: string, status: string, detail: string, ran: boolean, configuredPatterns: string[], firedPatterns: string[], matchedFiles: string[], changedFileCount: number);
|
|
34
|
+
}
|
|
15
35
|
export declare class DisableCounts {
|
|
16
36
|
webpiecesCount: number;
|
|
17
37
|
eslintCount: number;
|
|
@@ -35,7 +55,35 @@ export declare class Dashboard {
|
|
|
35
55
|
computeGateResults(gates: GateDefinition[], changedFiles: string[]): GateResult[];
|
|
36
56
|
countAddedDisables(patch: string): DisableCounts;
|
|
37
57
|
renderDashboard(input: DashboardInput): string;
|
|
38
|
-
|
|
58
|
+
/**
|
|
59
|
+
* The ONE combined PR comment. Two halves, in this order:
|
|
60
|
+
*
|
|
61
|
+
* 1. A roll-up plus the FULL ROSTER — every DEFINED checklist as a checkbox, each with a sub-bullet
|
|
62
|
+
* stating exactly which globs fired against which files, or which did not and out of how many.
|
|
63
|
+
* Skipped checklists are listed on purpose: skipping is the normal, healthy outcome, and a comment
|
|
64
|
+
* that names only the reviewers that fired cannot distinguish "evaluated and irrelevant" from
|
|
65
|
+
* "never wired up" — nor answer "why did the DB reviewer run on my frontend PR?".
|
|
66
|
+
* 2. One section per reviewer that RAN, carrying its full `output` — the depth a verdict line throws
|
|
67
|
+
* away. Overridden first, then warned, then passed: a reader should meet the exceptions first.
|
|
68
|
+
*
|
|
69
|
+
* Idempotent: keyed by the hidden marker so wp-finish PATCHes this same comment on every push.
|
|
70
|
+
*/
|
|
71
|
+
renderChecklistComment(rows: readonly ChecklistCommentRow[], provenanceVerified: boolean, baseResolved: boolean): string;
|
|
72
|
+
private rollupHeader;
|
|
73
|
+
private rollupCounts;
|
|
74
|
+
private rosterBullet;
|
|
75
|
+
private verdictEmoji;
|
|
76
|
+
private verdictWords;
|
|
77
|
+
/**
|
|
78
|
+
* WHY this checklist ran or did not — the line that answers "why was this reviewer involved?". Branches
|
|
79
|
+
* on `configuredPatterns`, NEVER on `firedPatterns.length`: a patternless checklist and a skipped one
|
|
80
|
+
* both fired zero globs and they mean opposite things, so keying off the fired list would tell every
|
|
81
|
+
* skipped checklist's reader that the whole diff had been in its scope.
|
|
82
|
+
*/
|
|
83
|
+
private whyLine;
|
|
84
|
+
private asCode;
|
|
85
|
+
private ranOrdered;
|
|
86
|
+
private rankOf;
|
|
39
87
|
private commentSection;
|
|
40
88
|
private fitComment;
|
|
41
89
|
private longestBodyIndex;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Dashboard = exports.DashboardInput = exports.DisableCounts = exports.ChecklistRow = exports.GateResult = exports.CHECKLIST_COMMENT_MARKER = void 0;
|
|
3
|
+
exports.Dashboard = exports.DashboardInput = exports.DisableCounts = exports.ChecklistCommentRow = exports.ChecklistRow = exports.GateResult = exports.CHECKLIST_COMMENT_MARKER = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
6
6
|
const inversify_1 = require("inversify");
|
|
7
7
|
// Hidden marker on the checklist review COMMENT, so wp-finish can find + PATCH its own comment on every
|
|
8
8
|
// push instead of appending a new one. Versioned so the format can evolve without matching an old shape.
|
|
9
|
-
|
|
9
|
+
// v2 = the full roster (every DEFINED checklist, matched or not) + tri-state verdicts.
|
|
10
|
+
exports.CHECKLIST_COMMENT_MARKER = '<!-- webpieces-checklists v2 -->';
|
|
10
11
|
const COMMENT_LIMIT = 65000; // under GitHub's 65536-char cap, with headroom for the marker + roll-up.
|
|
11
12
|
// One checklist section for the combined comment (heading + verbatim reviewer output), so oversize
|
|
12
13
|
// truncation can shrink the longest BODY without ever dropping a verdict heading.
|
|
@@ -30,11 +31,11 @@ class GateResult {
|
|
|
30
31
|
}
|
|
31
32
|
exports.GateResult = GateResult;
|
|
32
33
|
// One row for a consumer review checklist the branch triggered. `status` is the resolved verdict (one of
|
|
33
|
-
// CK_PASS | CK_OVERRIDDEN | CK_FAIL | CK_MISSING |
|
|
34
|
-
// justification. A
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
34
|
+
// CK_PASS | CK_WARN | CK_OVERRIDDEN | CK_FAIL | CK_MISSING | CK_BAD_FORMAT); `detail` is the reviewer
|
|
35
|
+
// output / override justification. A row is always PASS/WARN/OVERRIDDEN by the time it renders — a failed,
|
|
36
|
+
// missing or unreadable verdict throws before the dashboard is built. Rendered into the PR body so the
|
|
37
|
+
// verdict reaches the server — the PR body is the artifact of the local flow that leaves the checkout
|
|
38
|
+
// (alongside the HMAC gate token that proves the flow ran).
|
|
38
39
|
class ChecklistRow {
|
|
39
40
|
title; // the checklist id (= reviewer subagent name)
|
|
40
41
|
status; // CK_* verdict
|
|
@@ -46,6 +47,39 @@ class ChecklistRow {
|
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
exports.ChecklistRow = ChecklistRow;
|
|
50
|
+
/**
|
|
51
|
+
* One roster line of the checklist COMMENT: a defined checklist, whether its reviewer ran, and the evidence
|
|
52
|
+
* for WHY. Deliberately separate from {@link ChecklistRow} rather than five more fields on it — ChecklistRow
|
|
53
|
+
* also feeds the PR body and the squash-commit body, and roster evidence has no business travelling into
|
|
54
|
+
* main's git history or through every DashboardInput construction site.
|
|
55
|
+
*
|
|
56
|
+
* Kept in pr-gate, not rules-config: the shape of a GitHub comment is this package's concern, and
|
|
57
|
+
* rules-config is the dependency, not the dependent.
|
|
58
|
+
*/
|
|
59
|
+
class ChecklistCommentRow {
|
|
60
|
+
subagent; // the reviewer / checklist id
|
|
61
|
+
status; // CK_* verdict; '' when it did not run
|
|
62
|
+
detail; // verbatim reviewer output
|
|
63
|
+
ran; // false = skipped, which is a NORMAL, healthy outcome
|
|
64
|
+
// The checklist's CONFIGURED globs. The only safe signal for "always runs": a patternless checklist and
|
|
65
|
+
// a skipped one both have an empty `firedPatterns`, and they mean opposite things.
|
|
66
|
+
configuredPatterns;
|
|
67
|
+
firedPatterns; // which configured globs actually hit a changed file
|
|
68
|
+
matchedFiles;
|
|
69
|
+
changedFileCount; // how many files were considered at all — "0 of N" needs the N
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/max-params
|
|
71
|
+
constructor(subagent, status, detail, ran, configuredPatterns, firedPatterns, matchedFiles, changedFileCount) {
|
|
72
|
+
this.subagent = subagent;
|
|
73
|
+
this.status = status;
|
|
74
|
+
this.detail = detail;
|
|
75
|
+
this.ran = ran;
|
|
76
|
+
this.configuredPatterns = configuredPatterns;
|
|
77
|
+
this.firedPatterns = firedPatterns;
|
|
78
|
+
this.matchedFiles = matchedFiles;
|
|
79
|
+
this.changedFileCount = changedFileCount;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.ChecklistCommentRow = ChecklistCommentRow;
|
|
49
83
|
class DisableCounts {
|
|
50
84
|
webpiecesCount;
|
|
51
85
|
eslintCount;
|
|
@@ -144,27 +178,144 @@ let Dashboard = class Dashboard {
|
|
|
144
178
|
lines.push('<sub>🤖 Generated by `pnpm wp-finish-upsert-pr` (build ran via nx affected — not self-attested).</sub>');
|
|
145
179
|
return lines.join('\n');
|
|
146
180
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
181
|
+
/**
|
|
182
|
+
* The ONE combined PR comment. Two halves, in this order:
|
|
183
|
+
*
|
|
184
|
+
* 1. A roll-up plus the FULL ROSTER — every DEFINED checklist as a checkbox, each with a sub-bullet
|
|
185
|
+
* stating exactly which globs fired against which files, or which did not and out of how many.
|
|
186
|
+
* Skipped checklists are listed on purpose: skipping is the normal, healthy outcome, and a comment
|
|
187
|
+
* that names only the reviewers that fired cannot distinguish "evaluated and irrelevant" from
|
|
188
|
+
* "never wired up" — nor answer "why did the DB reviewer run on my frontend PR?".
|
|
189
|
+
* 2. One section per reviewer that RAN, carrying its full `output` — the depth a verdict line throws
|
|
190
|
+
* away. Overridden first, then warned, then passed: a reader should meet the exceptions first.
|
|
191
|
+
*
|
|
192
|
+
* Idempotent: keyed by the hidden marker so wp-finish PATCHes this same comment on every push.
|
|
193
|
+
*/
|
|
194
|
+
renderChecklistComment(rows, provenanceVerified, baseResolved) {
|
|
195
|
+
const ran = this.ranOrdered(rows);
|
|
157
196
|
const prov = provenanceVerified
|
|
158
197
|
? '_Each reviewer ran as its own independent subagent, verified from the Claude Code harness._'
|
|
159
198
|
: '_⚠️ Reviewer provenance was NOT verified (no Claude Code session) — treat these as unverified._';
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
199
|
+
// The roster lives in the HEADER, never in a section: fitComment only ever shrinks section bodies,
|
|
200
|
+
// so a roster line can never be the thing an oversize comment silently drops.
|
|
201
|
+
const lines = [exports.CHECKLIST_COMMENT_MARKER, this.rollupHeader(rows, baseResolved)];
|
|
202
|
+
// No reviewer ran ⇒ no provenance claim to make. Printing one either way would attest to nothing.
|
|
203
|
+
if (ran.length > 0)
|
|
204
|
+
lines.push(prov);
|
|
205
|
+
lines.push('', `### Checklists (all ${rows.length})`);
|
|
206
|
+
for (const row of rows)
|
|
207
|
+
lines.push(this.rosterBullet(row));
|
|
208
|
+
const header = lines.join('\n');
|
|
209
|
+
if (ran.length === 0) {
|
|
210
|
+
return `${header}\n\n_No reviewer had to run on this diff — every configured checklist was ` +
|
|
211
|
+
`evaluated and none of them applied._`;
|
|
212
|
+
}
|
|
213
|
+
return this.fitComment(`${header}\n\n### Reviews that ran`, ran.map((r) => this.commentSection(r)));
|
|
214
|
+
}
|
|
215
|
+
// The roll-up line. `baseResolved:false` replaces it entirely: with no fork point the changed-file set is
|
|
216
|
+
// EMPTY, so nothing matched — including patternless ALWAYS-RUNS checklists — and reporting that as
|
|
217
|
+
// "all skipped ✅" would post a green all-clear for a PR where nothing was actually evaluated.
|
|
218
|
+
rollupHeader(rows, baseResolved) {
|
|
219
|
+
if (!baseResolved) {
|
|
220
|
+
return `## 🔍 Company review checklists — ⚠️ NOT EVALUATED (${rows.length} defined)\n` +
|
|
221
|
+
`_No diff base (fork point of main) could be resolved, so no checklist was matched against ` +
|
|
222
|
+
`anything. This is **not** an all-clear._`;
|
|
223
|
+
}
|
|
224
|
+
const ran = rows.filter((r) => r.ran);
|
|
225
|
+
const skipped = rows.length - ran.length;
|
|
226
|
+
const parts = [];
|
|
227
|
+
for (const pair of this.rollupCounts(ran))
|
|
228
|
+
parts.push(pair);
|
|
229
|
+
const breakdown = parts.length > 0 ? ` (${parts.join(' · ')})` : '';
|
|
230
|
+
const skip = skipped > 0 ? ` · ${skipped} skipped ✅` : '';
|
|
231
|
+
return `## 🔍 Company review checklists — ${rows.length} defined · ${ran.length} ran${breakdown}${skip}`;
|
|
232
|
+
}
|
|
233
|
+
// `🟢 2 · 🟡 1` — only the non-zero buckets, so a clean run reads as one number rather than four.
|
|
234
|
+
rollupCounts(ran) {
|
|
235
|
+
const counts = [];
|
|
236
|
+
const emojiFor = ['🟢', '🟡', '🟠'];
|
|
237
|
+
const statusFor = [rules_config_1.CK_PASS, rules_config_1.CK_WARN, rules_config_1.CK_OVERRIDDEN];
|
|
238
|
+
statusFor.forEach((status, i) => {
|
|
239
|
+
const n = ran.filter((r) => r.status === status).length;
|
|
240
|
+
if (n > 0)
|
|
241
|
+
counts.push(`${emojiFor[i]} ${n}`);
|
|
242
|
+
});
|
|
243
|
+
return counts;
|
|
244
|
+
}
|
|
245
|
+
// One roster line + its why sub-bullet. A checked box means a reviewer ran; an unchecked one means the
|
|
246
|
+
// checklist was evaluated and did not apply, which the words state as the good news it is.
|
|
247
|
+
rosterBullet(row) {
|
|
248
|
+
const box = row.ran ? '- [x]' : '- [ ]';
|
|
249
|
+
return `${box} ${this.verdictEmoji(row)} **${row.subagent}** — ${this.verdictWords(row)}\n` +
|
|
250
|
+
` - ${this.whyLine(row)}`;
|
|
251
|
+
}
|
|
252
|
+
verdictEmoji(row) {
|
|
253
|
+
if (!row.ran)
|
|
254
|
+
return '⚪';
|
|
255
|
+
if (row.status === rules_config_1.CK_PASS)
|
|
256
|
+
return '🟢';
|
|
257
|
+
if (row.status === rules_config_1.CK_WARN)
|
|
258
|
+
return '🟡';
|
|
259
|
+
if (row.status === rules_config_1.CK_OVERRIDDEN)
|
|
260
|
+
return '🟠';
|
|
261
|
+
if (row.status === rules_config_1.CK_FAIL)
|
|
262
|
+
return '🔴';
|
|
263
|
+
return '⚪';
|
|
264
|
+
}
|
|
265
|
+
// SHORT words for a roster line / section heading. Short on purpose: the reviewer's own output and any
|
|
266
|
+
// override justification get their own section below, and a roster exists to be scanned.
|
|
267
|
+
verdictWords(row) {
|
|
268
|
+
if (!row.ran)
|
|
269
|
+
return 'skipped, not applicable to this diff (expected ✅)';
|
|
270
|
+
if (row.status === rules_config_1.CK_PASS)
|
|
271
|
+
return 'passed';
|
|
272
|
+
if (row.status === rules_config_1.CK_WARN)
|
|
273
|
+
return 'passed with concerns';
|
|
274
|
+
if (row.status === rules_config_1.CK_OVERRIDDEN)
|
|
275
|
+
return 'OVERRIDDEN — shipped with a stated justification';
|
|
276
|
+
if (row.status === rules_config_1.CK_FAIL)
|
|
277
|
+
return 'FAILED review';
|
|
278
|
+
if (row.status === rules_config_1.CK_MISSING)
|
|
279
|
+
return 'no verdict written';
|
|
280
|
+
return `unknown verdict (${row.status})`;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* WHY this checklist ran or did not — the line that answers "why was this reviewer involved?". Branches
|
|
284
|
+
* on `configuredPatterns`, NEVER on `firedPatterns.length`: a patternless checklist and a skipped one
|
|
285
|
+
* both fired zero globs and they mean opposite things, so keying off the fired list would tell every
|
|
286
|
+
* skipped checklist's reader that the whole diff had been in its scope.
|
|
287
|
+
*/
|
|
288
|
+
whyLine(row) {
|
|
289
|
+
const total = row.changedFileCount;
|
|
290
|
+
if (row.configuredPatterns.length === 0) {
|
|
291
|
+
return `ALWAYS RUNS (no patterns) — whole diff in scope, ${total} changed file(s): ` +
|
|
292
|
+
`${(0, rules_config_1.formatFileList)(row.matchedFiles)}`;
|
|
293
|
+
}
|
|
294
|
+
const configured = this.asCode(row.configuredPatterns);
|
|
295
|
+
if (row.firedPatterns.length === 0) {
|
|
296
|
+
return `${configured} matched 0 of ${total} changed file(s)`;
|
|
297
|
+
}
|
|
298
|
+
return `matched ${this.asCode(row.firedPatterns)} → ${row.matchedFiles.length} of ${total} ` +
|
|
299
|
+
`changed file(s): ${(0, rules_config_1.formatFileList)(row.matchedFiles)}`;
|
|
300
|
+
}
|
|
301
|
+
asCode(patterns) {
|
|
302
|
+
return patterns.map((p) => `\`${p}\``).join(', ');
|
|
303
|
+
}
|
|
304
|
+
// Reviewers that ran, exceptions first (overridden → warned → passed) so a reader meets what needs
|
|
305
|
+
// attention before a wall of green.
|
|
306
|
+
ranOrdered(rows) {
|
|
307
|
+
const rank = [rules_config_1.CK_OVERRIDDEN, rules_config_1.CK_WARN, rules_config_1.CK_PASS];
|
|
308
|
+
return rows
|
|
309
|
+
.filter((r) => r.ran)
|
|
310
|
+
.slice()
|
|
311
|
+
.sort((a, b) => this.rankOf(rank, a.status) - this.rankOf(rank, b.status));
|
|
312
|
+
}
|
|
313
|
+
rankOf(rank, status) {
|
|
314
|
+
const idx = rank.indexOf(status);
|
|
315
|
+
return idx < 0 ? rank.length : idx;
|
|
163
316
|
}
|
|
164
317
|
commentSection(row) {
|
|
165
|
-
const heading = row.
|
|
166
|
-
? `### 🟡 ${row.title} — OVERRIDDEN`
|
|
167
|
-
: `### 🟢 ${row.title} — passed`;
|
|
318
|
+
const heading = `#### ${this.verdictEmoji(row)} ${row.subagent} — ${this.verdictWords(row)}`;
|
|
168
319
|
const body = row.detail.trim() !== '' ? row.detail.trim() : '_(reviewer recorded no output)_';
|
|
169
320
|
return new CommentSection(heading, body);
|
|
170
321
|
}
|
|
@@ -251,16 +402,23 @@ let Dashboard = class Dashboard {
|
|
|
251
402
|
return flags;
|
|
252
403
|
}
|
|
253
404
|
// Emoji + words for a checklist verdict, shared by the dashboard row and the commit-body flag.
|
|
405
|
+
// Every state is matched EXPLICITLY and the fallthrough names the status it did not recognize. The
|
|
406
|
+
// previous `return '🟢 passed'` fallthrough meant any newly-added verdict rendered as a clean pass on
|
|
407
|
+
// both the PR body and main's commit history until someone noticed — exactly the wrong default.
|
|
254
408
|
checklistStatusText(row) {
|
|
255
409
|
if (row.status === rules_config_1.CK_OVERRIDDEN) {
|
|
256
410
|
const why = row.detail.trim() !== '' ? ` — override: ${row.detail.trim()}` : '';
|
|
257
|
-
return
|
|
411
|
+
return `🟠 OVERRIDDEN${why}`;
|
|
258
412
|
}
|
|
413
|
+
if (row.status === rules_config_1.CK_WARN)
|
|
414
|
+
return '🟡 passed with concerns';
|
|
259
415
|
if (row.status === rules_config_1.CK_FAIL)
|
|
260
416
|
return '🔴 FAILED review';
|
|
261
417
|
if (row.status === rules_config_1.CK_MISSING)
|
|
262
418
|
return '⚪ not reviewed';
|
|
263
|
-
|
|
419
|
+
if (row.status === rules_config_1.CK_PASS)
|
|
420
|
+
return '🟢 passed';
|
|
421
|
+
return `⚪ unknown verdict (${row.status})`;
|
|
264
422
|
}
|
|
265
423
|
// First `max` sentences of `text`. A sentence ends at `. ! ?` ONLY when followed by whitespace or
|
|
266
424
|
// end-of-string, so interior dots in filenames/paths/versions (dependencies.json, runtime-graph.ts,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/dashboard/dashboard.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n GateDefinition, WEBPIECES_DISABLE, RULE_NAMES, ReviewJson,\n CK_OVERRIDDEN, CK_FAIL, CK_MISSING,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\n// Hidden marker on the checklist review COMMENT, so wp-finish can find + PATCH its own comment on every\n// push instead of appending a new one. Versioned so the format can evolve without matching an old shape.\nexport const CHECKLIST_COMMENT_MARKER = '<!-- webpieces-checklists v1 -->';\nconst COMMENT_LIMIT = 65000; // under GitHub's 65536-char cap, with headroom for the marker + roll-up.\n\n// One checklist section for the combined comment (heading + verbatim reviewer output), so oversize\n// truncation can shrink the longest BODY without ever dropping a verdict heading.\nclass CommentSection {\n heading: string;\n body: string;\n\n constructor(heading: string, body: string) {\n this.heading = heading;\n this.body = body;\n }\n}\n\nexport class GateResult {\n name: string;\n warningColor: string; // 'yellow' | 'red' — the color shown WHEN files matched (green is implicit)\n matchedFiles: string[];\n\n constructor(name: string, warningColor: string, matchedFiles: string[]) {\n this.name = name;\n this.warningColor = warningColor;\n this.matchedFiles = matchedFiles;\n }\n}\n\n// One row for a consumer review checklist the branch triggered. `status` is the resolved verdict (one of\n// CK_PASS | CK_OVERRIDDEN | CK_FAIL | CK_MISSING | CK_ACKED); `detail` is the reviewer output / override\n// justification. A BLOCK row is always PASS/OVERRIDDEN/ACKED by the time it renders — a failed or missing\n// BLOCK throws before the dashboard is built; a WARN row may render in any state. Rendered into the PR\n// body so the verdict reaches the server — the PR body is the artifact of the local flow that leaves the\n// checkout (alongside the HMAC gate token that proves the flow ran).\nexport class ChecklistRow {\n title: string; // the checklist id (= reviewer subagent name)\n status: string; // CK_* verdict\n detail: string; // reviewer output / override justification (surfaced for OVERRIDDEN)\n\n constructor(title: string, status: string, detail = '') {\n this.title = title;\n this.status = status;\n this.detail = detail;\n }\n}\n\nexport class DisableCounts {\n webpiecesCount: number;\n eslintCount: number;\n webpiecesRules: string[];\n\n constructor(webpiecesCount: number, eslintCount: number, webpiecesRules: string[]) {\n this.webpiecesCount = webpiecesCount;\n this.eslintCount = eslintCount;\n this.webpiecesRules = webpiecesRules;\n }\n}\n\nexport class DashboardInput {\n title: string;\n gateResults: GateResult[];\n disables: DisableCounts;\n buildPassed: boolean;\n forkPoint: string;\n featureHead: string;\n mainHead: string;\n review: ReviewJson; // AI-authored risk/violations/summary (from review.json)\n checklists: ChecklistRow[]; // consumer checklists this branch triggered; [] for non-adopting repos\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n title: string, gateResults: GateResult[], disables: DisableCounts,\n buildPassed: boolean, forkPoint: string, featureHead: string, mainHead: string, review: ReviewJson,\n checklists: ChecklistRow[] = [],\n ) {\n this.title = title;\n this.gateResults = gateResults;\n this.disables = disables;\n this.buildPassed = buildPassed;\n this.forkPoint = forkPoint;\n this.featureHead = featureHead;\n this.mainHead = mainHead;\n this.review = review;\n this.checklists = checklists;\n }\n}\n\n/** Renders the PR-gate dashboard markdown (gates × changed files, disables, risk, 3-point hashes). */\n@injectable(bindingScopeValues.Singleton)\nexport class Dashboard {\n // Disabled gates are in-file examples (JSON has no comments) — skip them entirely.\n computeGateResults(gates: GateDefinition[], changedFiles: string[]): GateResult[] {\n return gates\n .filter((gate: GateDefinition): boolean => !gate.disabled)\n .map((gate: GateDefinition): GateResult => {\n const matched = changedFiles.filter((file: string): boolean => this.matchesAny(gate.patterns, file));\n return new GateResult(gate.name, gate.warningColor, matched);\n });\n }\n\n // Count disables ADDED in this PR by scanning added (`+`) lines of the diff patch. Rule-aware:\n // reports which webpieces rules were disabled, using the canonical RULE_NAMES vocabulary.\n countAddedDisables(patch: string): DisableCounts {\n let webpiecesCount = 0;\n let eslintCount = 0;\n const rules = new Set<string>();\n const allRuleTokens = Object.keys(RULE_NAMES).map((key: string): string => (RULE_NAMES as Record<string, string>)[key]);\n\n for (const line of patch.split('\\n')) {\n if (!line.startsWith('+') || line.startsWith('+++')) continue;\n if (line.includes(WEBPIECES_DISABLE)) {\n webpiecesCount += 1;\n for (const token of allRuleTokens) {\n if (line.includes(token)) rules.add(token);\n }\n }\n if (line.includes('eslint-disable')) eslintCount += 1;\n }\n return new DisableCounts(webpiecesCount, eslintCount, Array.from(rules).sort());\n }\n\n renderDashboard(input: DashboardInput): string {\n const lines: string[] = [];\n lines.push('## 🚦 PR Gate Dashboard');\n lines.push('');\n for (const line of this.riskLines(input.review)) lines.push(line);\n lines.push(`**Build (nx affected):** ${input.buildPassed ? '🟢 Passed' : '🔴 Failed'}`);\n for (const result of input.gateResults) lines.push(this.gateLine(result));\n lines.push(this.disableLine(input.disables));\n const eslintEmoji = input.disables.eslintCount === 0 ? '🟢 No' : `🟡 ${input.disables.eslintCount} line(s)`;\n lines.push(`**ESLint Disables Added:** ${eslintEmoji}`);\n // One row per triggered consumer checklist — only when some fired, so non-adopting repos see no\n // change to the dashboard at all.\n for (const row of input.checklists) lines.push(this.checklistLine(row));\n lines.push('');\n if (input.review.summary.trim() !== '') {\n lines.push('### Summary');\n lines.push(input.review.summary.trim());\n lines.push('');\n }\n lines.push('### 🔍 3-Point Hash Points');\n lines.push(`- Fork point (A): \\`${input.forkPoint.slice(0, 12)}\\``);\n lines.push(`- Feature HEAD (B): \\`${input.featureHead.slice(0, 12)}\\``);\n lines.push(`- Main HEAD (C): \\`${input.mainHead.slice(0, 12)}\\``);\n lines.push('');\n lines.push('<sub>🤖 Generated by `pnpm wp-finish-upsert-pr` (build ran via nx affected — not self-attested).</sub>');\n return lines.join('\\n');\n }\n\n // The ONE combined PR comment carrying every reviewer's full `output` — the depth the body-line\n // verdict throws away. Overridden sections first (the only non-green verdicts reaching a PR — a\n // hard FAIL refuses the PR before this runs), then passing reviews. Idempotent: keyed by the hidden\n // marker so wp-finish PATCHes this same comment on every push.\n renderChecklistComment(rows: readonly ChecklistRow[], provenanceVerified: boolean): string {\n const overridden = rows.filter((r: ChecklistRow): boolean => r.status === CK_OVERRIDDEN);\n const passed = rows.filter((r: ChecklistRow): boolean => r.status !== CK_OVERRIDDEN);\n const roll = overridden.length > 0\n ? `## 🔍 Company review checklists — ${rows.length} reviewed, 🟡 ${overridden.length} overridden`\n : `## 🔍 Company review checklists — 🟢 ${rows.length} reviewed, all passed`;\n const prov = provenanceVerified\n ? '_Each reviewer ran as its own independent subagent, verified from the Claude Code harness._'\n : '_⚠️ Reviewer provenance was NOT verified (no Claude Code session) — treat these as unverified._';\n const header = `${CHECKLIST_COMMENT_MARKER}\\n${roll}\\n${prov}`;\n const sections = [...overridden, ...passed].map((r: ChecklistRow): CommentSection => this.commentSection(r));\n return this.fitComment(header, sections);\n }\n\n private commentSection(row: ChecklistRow): CommentSection {\n const heading = row.status === CK_OVERRIDDEN\n ? `### 🟡 ${row.title} — OVERRIDDEN`\n : `### 🟢 ${row.title} — passed`;\n const body = row.detail.trim() !== '' ? row.detail.trim() : '_(reviewer recorded no output)_';\n return new CommentSection(heading, body);\n }\n\n // Keep the comment under GitHub's size cap by shrinking the LONGEST section body first (so a short\n // overridden note is never cut to make room for a long passing one), never dropping a verdict heading.\n private fitComment(header: string, sections: CommentSection[]): string {\n const assemble = (): string => `${header}\\n\\n${sections.map((s: CommentSection): string => `${s.heading}\\n\\n${s.body}`).join('\\n\\n')}`;\n const trunc = '\\n\\n…_[truncated to fit the GitHub comment size limit]_';\n let out = assemble();\n while (out.length > COMMENT_LIMIT) {\n const idx = this.longestBodyIndex(sections);\n if (idx < 0 || sections[idx].body.length <= trunc.length + 1) break;\n const over = out.length - COMMENT_LIMIT;\n const keep = Math.max(0, sections[idx].body.length - over - trunc.length - 8);\n sections[idx].body = sections[idx].body.slice(0, keep).trimEnd() + trunc;\n out = assemble();\n }\n return out;\n }\n\n private longestBodyIndex(sections: readonly CommentSection[]): number {\n let idx = -1;\n let max = -1;\n sections.forEach((s: CommentSection, i: number): void => {\n if (s.body.length > max) { max = s.body.length; idx = i; }\n });\n return idx;\n }\n\n // The squash-merge COMMIT body that lands in main's history (subject is the PR title, passed to\n // `gh pr merge --subject`). Deliberately compact — unlike the full PR-body dashboard: the risk score\n // (always), every NON-green flag (green rows omitted — a commit log should surface only what stands\n // out), the summary capped at 4 sentences, and a quick link back to the PR for the full dashboard.\n renderCommitBody(input: DashboardInput, prUrl: string): string {\n const lines: string[] = [];\n lines.push(`Risk: ${this.riskBar(input.review.riskScore)} ${input.review.riskScore}/100 ${input.review.riskEmoji} (${input.review.riskLevel})`);\n lines.push('');\n const flags = this.nonGreenFlags(input);\n if (flags.length === 0) {\n lines.push('Flags: 🟢 all green');\n } else {\n lines.push('Flags (non-green):');\n for (const flag of flags) lines.push(`- ${flag}`);\n }\n const summary = this.firstSentences(input.review.summary.trim(), 4);\n if (summary !== '') {\n lines.push('');\n lines.push(summary);\n }\n if (prUrl !== '') {\n lines.push('');\n lines.push(`PR: ${prUrl}`);\n }\n return lines.join('\\n');\n }\n\n // Every dashboard row that is NOT green, as a flat bullet list for the commit body. Green rows\n // (build passed, gate did not match, zero disables/violations) are intentionally omitted.\n private nonGreenFlags(input: DashboardInput): string[] {\n const flags: string[] = [];\n if (!input.buildPassed) flags.push('Build (nx affected): 🔴 Failed');\n if (input.review.violations.length > 0) flags.push(`Pattern Violations: 🟡 ${input.review.violations.length} violation(s)`);\n for (const result of input.gateResults) {\n if (result.matchedFiles.length === 0) continue;\n const emoji = result.warningColor === 'red' ? '🔴' : '🟡';\n flags.push(`${result.name}: ${emoji} ${result.matchedFiles.length} file(s)`);\n }\n if (input.disables.webpiecesCount > 0) {\n const which = input.disables.webpiecesRules.length > 0 ? ` — ${input.disables.webpiecesRules.join(', ')}` : '';\n flags.push(`Webpieces Disables Added: 🟡 ${input.disables.webpiecesCount} line(s)${which}`);\n }\n if (input.disables.eslintCount > 0) flags.push(`ESLint Disables Added: 🟡 ${input.disables.eslintCount} line(s)`);\n // A triggered checklist is noteworthy in main's history — carry each into the commit body.\n for (const row of input.checklists) {\n flags.push(`Checklist — ${row.title}: ${this.checklistStatusText(row)}`);\n }\n return flags;\n }\n\n // Emoji + words for a checklist verdict, shared by the dashboard row and the commit-body flag.\n private checklistStatusText(row: ChecklistRow): string {\n if (row.status === CK_OVERRIDDEN) {\n const why = row.detail.trim() !== '' ? ` — override: ${row.detail.trim()}` : '';\n return `🟡 OVERRIDDEN${why}`;\n }\n if (row.status === CK_FAIL) return '🔴 FAILED review';\n if (row.status === CK_MISSING) return '⚪ not reviewed';\n return '🟢 passed'; // CK_PASS\n }\n\n // First `max` sentences of `text`. A sentence ends at `. ! ?` ONLY when followed by whitespace or\n // end-of-string, so interior dots in filenames/paths/versions (dependencies.json, runtime-graph.ts,\n // 0.4.447) do NOT split — and, unlike a greedy `[^.!?]+` regex, no text is ever dropped when such a\n // dot appears (that footgun silently deleted the run of prose up to the next real boundary). Keeps\n // the commit body scannable; the full summary still lives in the PR-body dashboard.\n private firstSentences(text: string, max: number): string {\n if (text === '') return '';\n const sentences: string[] = [];\n let start = 0;\n for (let i = 0; i < text.length && sentences.length < max; i++) {\n const ch = text[i];\n const isTerminator = ch === '.' || ch === '!' || ch === '?';\n const next = text[i + 1];\n if (isTerminator && (next === undefined || /\\s/.test(next))) {\n sentences.push(text.slice(start, i + 1).trim());\n start = i + 1;\n }\n }\n // Trailing text with no terminator still counts as a sentence (up to the cap).\n if (sentences.length < max && start < text.length) {\n const tail = text.slice(start).trim();\n if (tail !== '') sentences.push(tail);\n }\n return sentences.join(' ').trim();\n }\n\n // Self-contained glob matcher (** , * , ?) so pr-gate needs no extra runtime dependency.\n private globToRegex(pattern: string): RegExp {\n let re = '';\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === '*' && pattern[i + 1] === '*') {\n re += '.*';\n i += 2;\n if (pattern[i] === '/') i += 1;\n continue;\n }\n if (ch === '*') { re += '[^/]*'; i += 1; continue; }\n if (ch === '?') { re += '[^/]'; i += 1; continue; }\n if ('.+^$(){}|[]\\\\'.includes(ch)) { re += '\\\\' + ch; i += 1; continue; }\n re += ch;\n i += 1;\n }\n return new RegExp('^' + re + '$');\n }\n\n private matchesAny(patterns: string[], file: string): boolean {\n for (const pattern of patterns) {\n if (this.globToRegex(pattern).test(file)) return true;\n }\n return false;\n }\n\n private gateLine(result: GateResult): string {\n if (result.matchedFiles.length === 0) return `**${result.name}:** 🟢 No`;\n const emoji = result.warningColor === 'red' ? '🔴' : '🟡';\n return `**${result.name}:** ${emoji} Yes (${result.matchedFiles.length} file(s))`;\n }\n\n // A triggered consumer checklist row: the resolved verdict (passed / overridden / failed / …).\n private checklistLine(row: ChecklistRow): string {\n return `**Checklist — ${row.title}:** ${this.checklistStatusText(row)}`;\n }\n\n // 10-cell risk bar colored by band (🟩 ≤25, 🟨 ≤50, 🟧 ≤75, 🟥 >75), at least one filled cell.\n private riskBar(score: number): string {\n const clamped = Math.max(0, Math.min(100, score));\n const cell = clamped <= 25 ? '🟩' : clamped <= 50 ? '🟨' : clamped <= 75 ? '🟧' : '🟥';\n const filled = Math.max(1, Math.min(10, Math.round(clamped / 10)));\n return cell.repeat(filled) + '⬜'.repeat(10 - filled);\n }\n\n // RISK section (the AI half): Risk Score bar, Risk Level, Pattern Violations.\n private riskLines(review: ReviewJson): string[] {\n const violations = review.violations.length;\n const violationLine = violations === 0 ? '🟢 No' : `🟡 Yes (${violations} violation(s))`;\n return [\n `**Risk Score:** ${this.riskBar(review.riskScore)} **${review.riskScore}/100** ${review.riskEmoji}`,\n `**Risk Level:** ${review.riskEmoji} **${review.riskLevel}**`,\n `**Pattern Violations:** ${violationLine}`,\n ];\n }\n\n private disableLine(disables: DisableCounts): string {\n if (disables.webpiecesCount === 0) return '**Webpieces Disables Added:** 🟢 No';\n const which = disables.webpiecesRules.length > 0 ? ` — ${disables.webpiecesRules.join(', ')}` : '';\n return `**Webpieces Disables Added:** 🟡 ${disables.webpiecesCount} line(s)${which}`;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/dashboard/dashboard.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n GateDefinition, WEBPIECES_DISABLE, RULE_NAMES, ReviewJson, formatFileList,\n CK_PASS, CK_WARN, CK_OVERRIDDEN, CK_FAIL, CK_MISSING,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\n// Hidden marker on the checklist review COMMENT, so wp-finish can find + PATCH its own comment on every\n// push instead of appending a new one. Versioned so the format can evolve without matching an old shape.\n// v2 = the full roster (every DEFINED checklist, matched or not) + tri-state verdicts.\nexport const CHECKLIST_COMMENT_MARKER = '<!-- webpieces-checklists v2 -->';\nconst COMMENT_LIMIT = 65000; // under GitHub's 65536-char cap, with headroom for the marker + roll-up.\n\n// One checklist section for the combined comment (heading + verbatim reviewer output), so oversize\n// truncation can shrink the longest BODY without ever dropping a verdict heading.\nclass CommentSection {\n heading: string;\n body: string;\n\n constructor(heading: string, body: string) {\n this.heading = heading;\n this.body = body;\n }\n}\n\nexport class GateResult {\n name: string;\n warningColor: string; // 'yellow' | 'red' — the color shown WHEN files matched (green is implicit)\n matchedFiles: string[];\n\n constructor(name: string, warningColor: string, matchedFiles: string[]) {\n this.name = name;\n this.warningColor = warningColor;\n this.matchedFiles = matchedFiles;\n }\n}\n\n// One row for a consumer review checklist the branch triggered. `status` is the resolved verdict (one of\n// CK_PASS | CK_WARN | CK_OVERRIDDEN | CK_FAIL | CK_MISSING | CK_BAD_FORMAT); `detail` is the reviewer\n// output / override justification. A row is always PASS/WARN/OVERRIDDEN by the time it renders — a failed,\n// missing or unreadable verdict throws before the dashboard is built. Rendered into the PR body so the\n// verdict reaches the server — the PR body is the artifact of the local flow that leaves the checkout\n// (alongside the HMAC gate token that proves the flow ran).\nexport class ChecklistRow {\n title: string; // the checklist id (= reviewer subagent name)\n status: string; // CK_* verdict\n detail: string; // reviewer output / override justification (surfaced for OVERRIDDEN)\n\n constructor(title: string, status: string, detail = '') {\n this.title = title;\n this.status = status;\n this.detail = detail;\n }\n}\n\n/**\n * One roster line of the checklist COMMENT: a defined checklist, whether its reviewer ran, and the evidence\n * for WHY. Deliberately separate from {@link ChecklistRow} rather than five more fields on it — ChecklistRow\n * also feeds the PR body and the squash-commit body, and roster evidence has no business travelling into\n * main's git history or through every DashboardInput construction site.\n *\n * Kept in pr-gate, not rules-config: the shape of a GitHub comment is this package's concern, and\n * rules-config is the dependency, not the dependent.\n */\nexport class ChecklistCommentRow {\n subagent: string; // the reviewer / checklist id\n status: string; // CK_* verdict; '' when it did not run\n detail: string; // verbatim reviewer output\n ran: boolean; // false = skipped, which is a NORMAL, healthy outcome\n // The checklist's CONFIGURED globs. The only safe signal for \"always runs\": a patternless checklist and\n // a skipped one both have an empty `firedPatterns`, and they mean opposite things.\n configuredPatterns: string[];\n firedPatterns: string[]; // which configured globs actually hit a changed file\n matchedFiles: string[];\n changedFileCount: number; // how many files were considered at all — \"0 of N\" needs the N\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n subagent: string, status: string, detail: string, ran: boolean,\n configuredPatterns: string[], firedPatterns: string[], matchedFiles: string[], changedFileCount: number,\n ) {\n this.subagent = subagent;\n this.status = status;\n this.detail = detail;\n this.ran = ran;\n this.configuredPatterns = configuredPatterns;\n this.firedPatterns = firedPatterns;\n this.matchedFiles = matchedFiles;\n this.changedFileCount = changedFileCount;\n }\n}\n\nexport class DisableCounts {\n webpiecesCount: number;\n eslintCount: number;\n webpiecesRules: string[];\n\n constructor(webpiecesCount: number, eslintCount: number, webpiecesRules: string[]) {\n this.webpiecesCount = webpiecesCount;\n this.eslintCount = eslintCount;\n this.webpiecesRules = webpiecesRules;\n }\n}\n\nexport class DashboardInput {\n title: string;\n gateResults: GateResult[];\n disables: DisableCounts;\n buildPassed: boolean;\n forkPoint: string;\n featureHead: string;\n mainHead: string;\n review: ReviewJson; // AI-authored risk/violations/summary (from review.json)\n checklists: ChecklistRow[]; // consumer checklists this branch triggered; [] for non-adopting repos\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n title: string, gateResults: GateResult[], disables: DisableCounts,\n buildPassed: boolean, forkPoint: string, featureHead: string, mainHead: string, review: ReviewJson,\n checklists: ChecklistRow[] = [],\n ) {\n this.title = title;\n this.gateResults = gateResults;\n this.disables = disables;\n this.buildPassed = buildPassed;\n this.forkPoint = forkPoint;\n this.featureHead = featureHead;\n this.mainHead = mainHead;\n this.review = review;\n this.checklists = checklists;\n }\n}\n\n/** Renders the PR-gate dashboard markdown (gates × changed files, disables, risk, 3-point hashes). */\n@injectable(bindingScopeValues.Singleton)\nexport class Dashboard {\n // Disabled gates are in-file examples (JSON has no comments) — skip them entirely.\n computeGateResults(gates: GateDefinition[], changedFiles: string[]): GateResult[] {\n return gates\n .filter((gate: GateDefinition): boolean => !gate.disabled)\n .map((gate: GateDefinition): GateResult => {\n const matched = changedFiles.filter((file: string): boolean => this.matchesAny(gate.patterns, file));\n return new GateResult(gate.name, gate.warningColor, matched);\n });\n }\n\n // Count disables ADDED in this PR by scanning added (`+`) lines of the diff patch. Rule-aware:\n // reports which webpieces rules were disabled, using the canonical RULE_NAMES vocabulary.\n countAddedDisables(patch: string): DisableCounts {\n let webpiecesCount = 0;\n let eslintCount = 0;\n const rules = new Set<string>();\n const allRuleTokens = Object.keys(RULE_NAMES).map((key: string): string => (RULE_NAMES as Record<string, string>)[key]);\n\n for (const line of patch.split('\\n')) {\n if (!line.startsWith('+') || line.startsWith('+++')) continue;\n if (line.includes(WEBPIECES_DISABLE)) {\n webpiecesCount += 1;\n for (const token of allRuleTokens) {\n if (line.includes(token)) rules.add(token);\n }\n }\n if (line.includes('eslint-disable')) eslintCount += 1;\n }\n return new DisableCounts(webpiecesCount, eslintCount, Array.from(rules).sort());\n }\n\n renderDashboard(input: DashboardInput): string {\n const lines: string[] = [];\n lines.push('## 🚦 PR Gate Dashboard');\n lines.push('');\n for (const line of this.riskLines(input.review)) lines.push(line);\n lines.push(`**Build (nx affected):** ${input.buildPassed ? '🟢 Passed' : '🔴 Failed'}`);\n for (const result of input.gateResults) lines.push(this.gateLine(result));\n lines.push(this.disableLine(input.disables));\n const eslintEmoji = input.disables.eslintCount === 0 ? '🟢 No' : `🟡 ${input.disables.eslintCount} line(s)`;\n lines.push(`**ESLint Disables Added:** ${eslintEmoji}`);\n // One row per triggered consumer checklist — only when some fired, so non-adopting repos see no\n // change to the dashboard at all.\n for (const row of input.checklists) lines.push(this.checklistLine(row));\n lines.push('');\n if (input.review.summary.trim() !== '') {\n lines.push('### Summary');\n lines.push(input.review.summary.trim());\n lines.push('');\n }\n lines.push('### 🔍 3-Point Hash Points');\n lines.push(`- Fork point (A): \\`${input.forkPoint.slice(0, 12)}\\``);\n lines.push(`- Feature HEAD (B): \\`${input.featureHead.slice(0, 12)}\\``);\n lines.push(`- Main HEAD (C): \\`${input.mainHead.slice(0, 12)}\\``);\n lines.push('');\n lines.push('<sub>🤖 Generated by `pnpm wp-finish-upsert-pr` (build ran via nx affected — not self-attested).</sub>');\n return lines.join('\\n');\n }\n\n /**\n * The ONE combined PR comment. Two halves, in this order:\n *\n * 1. A roll-up plus the FULL ROSTER — every DEFINED checklist as a checkbox, each with a sub-bullet\n * stating exactly which globs fired against which files, or which did not and out of how many.\n * Skipped checklists are listed on purpose: skipping is the normal, healthy outcome, and a comment\n * that names only the reviewers that fired cannot distinguish \"evaluated and irrelevant\" from\n * \"never wired up\" — nor answer \"why did the DB reviewer run on my frontend PR?\".\n * 2. One section per reviewer that RAN, carrying its full `output` — the depth a verdict line throws\n * away. Overridden first, then warned, then passed: a reader should meet the exceptions first.\n *\n * Idempotent: keyed by the hidden marker so wp-finish PATCHes this same comment on every push.\n */\n renderChecklistComment(rows: readonly ChecklistCommentRow[], provenanceVerified: boolean, baseResolved: boolean): string {\n const ran = this.ranOrdered(rows);\n const prov = provenanceVerified\n ? '_Each reviewer ran as its own independent subagent, verified from the Claude Code harness._'\n : '_⚠️ Reviewer provenance was NOT verified (no Claude Code session) — treat these as unverified._';\n // The roster lives in the HEADER, never in a section: fitComment only ever shrinks section bodies,\n // so a roster line can never be the thing an oversize comment silently drops.\n const lines: string[] = [CHECKLIST_COMMENT_MARKER, this.rollupHeader(rows, baseResolved)];\n // No reviewer ran ⇒ no provenance claim to make. Printing one either way would attest to nothing.\n if (ran.length > 0) lines.push(prov);\n lines.push('', `### Checklists (all ${rows.length})`);\n for (const row of rows) lines.push(this.rosterBullet(row));\n const header = lines.join('\\n');\n if (ran.length === 0) {\n return `${header}\\n\\n_No reviewer had to run on this diff — every configured checklist was ` +\n `evaluated and none of them applied._`;\n }\n return this.fitComment(`${header}\\n\\n### Reviews that ran`, ran.map((r: ChecklistCommentRow): CommentSection => this.commentSection(r)));\n }\n\n // The roll-up line. `baseResolved:false` replaces it entirely: with no fork point the changed-file set is\n // EMPTY, so nothing matched — including patternless ALWAYS-RUNS checklists — and reporting that as\n // \"all skipped ✅\" would post a green all-clear for a PR where nothing was actually evaluated.\n private rollupHeader(rows: readonly ChecklistCommentRow[], baseResolved: boolean): string {\n if (!baseResolved) {\n return `## 🔍 Company review checklists — ⚠️ NOT EVALUATED (${rows.length} defined)\\n` +\n `_No diff base (fork point of main) could be resolved, so no checklist was matched against ` +\n `anything. This is **not** an all-clear._`;\n }\n const ran = rows.filter((r: ChecklistCommentRow): boolean => r.ran);\n const skipped = rows.length - ran.length;\n const parts: string[] = [];\n for (const pair of this.rollupCounts(ran)) parts.push(pair);\n const breakdown = parts.length > 0 ? ` (${parts.join(' · ')})` : '';\n const skip = skipped > 0 ? ` · ${skipped} skipped ✅` : '';\n return `## 🔍 Company review checklists — ${rows.length} defined · ${ran.length} ran${breakdown}${skip}`;\n }\n\n // `🟢 2 · 🟡 1` — only the non-zero buckets, so a clean run reads as one number rather than four.\n private rollupCounts(ran: readonly ChecklistCommentRow[]): string[] {\n const counts: string[] = [];\n const emojiFor: string[] = ['🟢', '🟡', '🟠'];\n const statusFor: string[] = [CK_PASS, CK_WARN, CK_OVERRIDDEN];\n statusFor.forEach((status: string, i: number): void => {\n const n = ran.filter((r: ChecklistCommentRow): boolean => r.status === status).length;\n if (n > 0) counts.push(`${emojiFor[i]} ${n}`);\n });\n return counts;\n }\n\n // One roster line + its why sub-bullet. A checked box means a reviewer ran; an unchecked one means the\n // checklist was evaluated and did not apply, which the words state as the good news it is.\n private rosterBullet(row: ChecklistCommentRow): string {\n const box = row.ran ? '- [x]' : '- [ ]';\n return `${box} ${this.verdictEmoji(row)} **${row.subagent}** — ${this.verdictWords(row)}\\n` +\n ` - ${this.whyLine(row)}`;\n }\n\n private verdictEmoji(row: ChecklistCommentRow): string {\n if (!row.ran) return '⚪';\n if (row.status === CK_PASS) return '🟢';\n if (row.status === CK_WARN) return '🟡';\n if (row.status === CK_OVERRIDDEN) return '🟠';\n if (row.status === CK_FAIL) return '🔴';\n return '⚪';\n }\n\n // SHORT words for a roster line / section heading. Short on purpose: the reviewer's own output and any\n // override justification get their own section below, and a roster exists to be scanned.\n private verdictWords(row: ChecklistCommentRow): string {\n if (!row.ran) return 'skipped, not applicable to this diff (expected ✅)';\n if (row.status === CK_PASS) return 'passed';\n if (row.status === CK_WARN) return 'passed with concerns';\n if (row.status === CK_OVERRIDDEN) return 'OVERRIDDEN — shipped with a stated justification';\n if (row.status === CK_FAIL) return 'FAILED review';\n if (row.status === CK_MISSING) return 'no verdict written';\n return `unknown verdict (${row.status})`;\n }\n\n /**\n * WHY this checklist ran or did not — the line that answers \"why was this reviewer involved?\". Branches\n * on `configuredPatterns`, NEVER on `firedPatterns.length`: a patternless checklist and a skipped one\n * both fired zero globs and they mean opposite things, so keying off the fired list would tell every\n * skipped checklist's reader that the whole diff had been in its scope.\n */\n private whyLine(row: ChecklistCommentRow): string {\n const total = row.changedFileCount;\n if (row.configuredPatterns.length === 0) {\n return `ALWAYS RUNS (no patterns) — whole diff in scope, ${total} changed file(s): ` +\n `${formatFileList(row.matchedFiles)}`;\n }\n const configured = this.asCode(row.configuredPatterns);\n if (row.firedPatterns.length === 0) {\n return `${configured} matched 0 of ${total} changed file(s)`;\n }\n return `matched ${this.asCode(row.firedPatterns)} → ${row.matchedFiles.length} of ${total} ` +\n `changed file(s): ${formatFileList(row.matchedFiles)}`;\n }\n\n private asCode(patterns: readonly string[]): string {\n return patterns.map((p: string): string => `\\`${p}\\``).join(', ');\n }\n\n // Reviewers that ran, exceptions first (overridden → warned → passed) so a reader meets what needs\n // attention before a wall of green.\n private ranOrdered(rows: readonly ChecklistCommentRow[]): ChecklistCommentRow[] {\n const rank: string[] = [CK_OVERRIDDEN, CK_WARN, CK_PASS];\n return rows\n .filter((r: ChecklistCommentRow): boolean => r.ran)\n .slice()\n .sort((a: ChecklistCommentRow, b: ChecklistCommentRow): number =>\n this.rankOf(rank, a.status) - this.rankOf(rank, b.status));\n }\n\n private rankOf(rank: readonly string[], status: string): number {\n const idx = rank.indexOf(status);\n return idx < 0 ? rank.length : idx;\n }\n\n private commentSection(row: ChecklistCommentRow): CommentSection {\n const heading = `#### ${this.verdictEmoji(row)} ${row.subagent} — ${this.verdictWords(row)}`;\n const body = row.detail.trim() !== '' ? row.detail.trim() : '_(reviewer recorded no output)_';\n return new CommentSection(heading, body);\n }\n\n // Keep the comment under GitHub's size cap by shrinking the LONGEST section body first (so a short\n // overridden note is never cut to make room for a long passing one), never dropping a verdict heading.\n private fitComment(header: string, sections: CommentSection[]): string {\n const assemble = (): string => `${header}\\n\\n${sections.map((s: CommentSection): string => `${s.heading}\\n\\n${s.body}`).join('\\n\\n')}`;\n const trunc = '\\n\\n…_[truncated to fit the GitHub comment size limit]_';\n let out = assemble();\n while (out.length > COMMENT_LIMIT) {\n const idx = this.longestBodyIndex(sections);\n if (idx < 0 || sections[idx].body.length <= trunc.length + 1) break;\n const over = out.length - COMMENT_LIMIT;\n const keep = Math.max(0, sections[idx].body.length - over - trunc.length - 8);\n sections[idx].body = sections[idx].body.slice(0, keep).trimEnd() + trunc;\n out = assemble();\n }\n return out;\n }\n\n private longestBodyIndex(sections: readonly CommentSection[]): number {\n let idx = -1;\n let max = -1;\n sections.forEach((s: CommentSection, i: number): void => {\n if (s.body.length > max) { max = s.body.length; idx = i; }\n });\n return idx;\n }\n\n // The squash-merge COMMIT body that lands in main's history (subject is the PR title, passed to\n // `gh pr merge --subject`). Deliberately compact — unlike the full PR-body dashboard: the risk score\n // (always), every NON-green flag (green rows omitted — a commit log should surface only what stands\n // out), the summary capped at 4 sentences, and a quick link back to the PR for the full dashboard.\n renderCommitBody(input: DashboardInput, prUrl: string): string {\n const lines: string[] = [];\n lines.push(`Risk: ${this.riskBar(input.review.riskScore)} ${input.review.riskScore}/100 ${input.review.riskEmoji} (${input.review.riskLevel})`);\n lines.push('');\n const flags = this.nonGreenFlags(input);\n if (flags.length === 0) {\n lines.push('Flags: 🟢 all green');\n } else {\n lines.push('Flags (non-green):');\n for (const flag of flags) lines.push(`- ${flag}`);\n }\n const summary = this.firstSentences(input.review.summary.trim(), 4);\n if (summary !== '') {\n lines.push('');\n lines.push(summary);\n }\n if (prUrl !== '') {\n lines.push('');\n lines.push(`PR: ${prUrl}`);\n }\n return lines.join('\\n');\n }\n\n // Every dashboard row that is NOT green, as a flat bullet list for the commit body. Green rows\n // (build passed, gate did not match, zero disables/violations) are intentionally omitted.\n private nonGreenFlags(input: DashboardInput): string[] {\n const flags: string[] = [];\n if (!input.buildPassed) flags.push('Build (nx affected): 🔴 Failed');\n if (input.review.violations.length > 0) flags.push(`Pattern Violations: 🟡 ${input.review.violations.length} violation(s)`);\n for (const result of input.gateResults) {\n if (result.matchedFiles.length === 0) continue;\n const emoji = result.warningColor === 'red' ? '🔴' : '🟡';\n flags.push(`${result.name}: ${emoji} ${result.matchedFiles.length} file(s)`);\n }\n if (input.disables.webpiecesCount > 0) {\n const which = input.disables.webpiecesRules.length > 0 ? ` — ${input.disables.webpiecesRules.join(', ')}` : '';\n flags.push(`Webpieces Disables Added: 🟡 ${input.disables.webpiecesCount} line(s)${which}`);\n }\n if (input.disables.eslintCount > 0) flags.push(`ESLint Disables Added: 🟡 ${input.disables.eslintCount} line(s)`);\n // A triggered checklist is noteworthy in main's history — carry each into the commit body.\n for (const row of input.checklists) {\n flags.push(`Checklist — ${row.title}: ${this.checklistStatusText(row)}`);\n }\n return flags;\n }\n\n // Emoji + words for a checklist verdict, shared by the dashboard row and the commit-body flag.\n // Every state is matched EXPLICITLY and the fallthrough names the status it did not recognize. The\n // previous `return '🟢 passed'` fallthrough meant any newly-added verdict rendered as a clean pass on\n // both the PR body and main's commit history until someone noticed — exactly the wrong default.\n private checklistStatusText(row: ChecklistRow): string {\n if (row.status === CK_OVERRIDDEN) {\n const why = row.detail.trim() !== '' ? ` — override: ${row.detail.trim()}` : '';\n return `🟠 OVERRIDDEN${why}`;\n }\n if (row.status === CK_WARN) return '🟡 passed with concerns';\n if (row.status === CK_FAIL) return '🔴 FAILED review';\n if (row.status === CK_MISSING) return '⚪ not reviewed';\n if (row.status === CK_PASS) return '🟢 passed';\n return `⚪ unknown verdict (${row.status})`;\n }\n\n // First `max` sentences of `text`. A sentence ends at `. ! ?` ONLY when followed by whitespace or\n // end-of-string, so interior dots in filenames/paths/versions (dependencies.json, runtime-graph.ts,\n // 0.4.447) do NOT split — and, unlike a greedy `[^.!?]+` regex, no text is ever dropped when such a\n // dot appears (that footgun silently deleted the run of prose up to the next real boundary). Keeps\n // the commit body scannable; the full summary still lives in the PR-body dashboard.\n private firstSentences(text: string, max: number): string {\n if (text === '') return '';\n const sentences: string[] = [];\n let start = 0;\n for (let i = 0; i < text.length && sentences.length < max; i++) {\n const ch = text[i];\n const isTerminator = ch === '.' || ch === '!' || ch === '?';\n const next = text[i + 1];\n if (isTerminator && (next === undefined || /\\s/.test(next))) {\n sentences.push(text.slice(start, i + 1).trim());\n start = i + 1;\n }\n }\n // Trailing text with no terminator still counts as a sentence (up to the cap).\n if (sentences.length < max && start < text.length) {\n const tail = text.slice(start).trim();\n if (tail !== '') sentences.push(tail);\n }\n return sentences.join(' ').trim();\n }\n\n // Self-contained glob matcher (** , * , ?) so pr-gate needs no extra runtime dependency.\n private globToRegex(pattern: string): RegExp {\n let re = '';\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === '*' && pattern[i + 1] === '*') {\n re += '.*';\n i += 2;\n if (pattern[i] === '/') i += 1;\n continue;\n }\n if (ch === '*') { re += '[^/]*'; i += 1; continue; }\n if (ch === '?') { re += '[^/]'; i += 1; continue; }\n if ('.+^$(){}|[]\\\\'.includes(ch)) { re += '\\\\' + ch; i += 1; continue; }\n re += ch;\n i += 1;\n }\n return new RegExp('^' + re + '$');\n }\n\n private matchesAny(patterns: string[], file: string): boolean {\n for (const pattern of patterns) {\n if (this.globToRegex(pattern).test(file)) return true;\n }\n return false;\n }\n\n private gateLine(result: GateResult): string {\n if (result.matchedFiles.length === 0) return `**${result.name}:** 🟢 No`;\n const emoji = result.warningColor === 'red' ? '🔴' : '🟡';\n return `**${result.name}:** ${emoji} Yes (${result.matchedFiles.length} file(s))`;\n }\n\n // A triggered consumer checklist row: the resolved verdict (passed / overridden / failed / …).\n private checklistLine(row: ChecklistRow): string {\n return `**Checklist — ${row.title}:** ${this.checklistStatusText(row)}`;\n }\n\n // 10-cell risk bar colored by band (🟩 ≤25, 🟨 ≤50, 🟧 ≤75, 🟥 >75), at least one filled cell.\n private riskBar(score: number): string {\n const clamped = Math.max(0, Math.min(100, score));\n const cell = clamped <= 25 ? '🟩' : clamped <= 50 ? '🟨' : clamped <= 75 ? '🟧' : '🟥';\n const filled = Math.max(1, Math.min(10, Math.round(clamped / 10)));\n return cell.repeat(filled) + '⬜'.repeat(10 - filled);\n }\n\n // RISK section (the AI half): Risk Score bar, Risk Level, Pattern Violations.\n private riskLines(review: ReviewJson): string[] {\n const violations = review.violations.length;\n const violationLine = violations === 0 ? '🟢 No' : `🟡 Yes (${violations} violation(s))`;\n return [\n `**Risk Score:** ${this.riskBar(review.riskScore)} **${review.riskScore}/100** ${review.riskEmoji}`,\n `**Risk Level:** ${review.riskEmoji} **${review.riskLevel}**`,\n `**Pattern Violations:** ${violationLine}`,\n ];\n }\n\n private disableLine(disables: DisableCounts): string {\n if (disables.webpiecesCount === 0) return '**Webpieces Disables Added:** 🟢 No';\n const which = disables.webpiecesRules.length > 0 ? ` — ${disables.webpiecesRules.join(', ')}` : '';\n return `**Webpieces Disables Added:** 🟡 ${disables.webpiecesCount} line(s)${which}`;\n }\n}\n"]}
|
|
@@ -19,6 +19,7 @@ export declare class ChecklistCommand {
|
|
|
19
19
|
constructor(repoRootFinder: RepoRootFinder, checklistScanner: ChecklistScanner, instructions: ChecklistInstructionsService, checklistNotice: ChecklistNotice);
|
|
20
20
|
run(): Promise<void>;
|
|
21
21
|
private report;
|
|
22
|
+
private formatWarnings;
|
|
22
23
|
private header;
|
|
23
24
|
private rosterLines;
|
|
24
25
|
private why;
|
|
@@ -48,8 +48,17 @@ let ChecklistCommand = class ChecklistCommand {
|
|
|
48
48
|
const owed = scan.applicable.filter((r) => !reviewedIds.has(r.id));
|
|
49
49
|
return (this.header(scan) +
|
|
50
50
|
this.rosterLines(scan, owed) +
|
|
51
|
+
this.formatWarnings(scan) +
|
|
51
52
|
(owed.length === 0 ? this.allDone() : `\n${this.instructions.render(owed, scan.reviewPath, scan.context)}\n`));
|
|
52
53
|
}
|
|
54
|
+
// A verdict file that EXISTS but cannot be read as a verdict (almost always one still using the removed
|
|
55
|
+
// `success` field) is called out here. Without it this command reports the checklist as simply owed, and
|
|
56
|
+
// the AI re-runs a reviewer that already ran instead of correcting the file sitting right there.
|
|
57
|
+
formatWarnings(scan) {
|
|
58
|
+
if (scan.formatErrors.length === 0)
|
|
59
|
+
return '';
|
|
60
|
+
return '\n' + scan.formatErrors.map((e) => ` ⛔ ${e}\n`).join('');
|
|
61
|
+
}
|
|
53
62
|
// State the base out loud: a reader (and a reviewer) needs to know the diff was taken from the fork point
|
|
54
63
|
// of main and that uncommitted work counted, because both change which checklists fired.
|
|
55
64
|
header(scan) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checklist-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/checklist-command.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"checklist-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/checklist-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { loadAndValidate, writeTemplate, RepoRootFinder, ChecklistInstructionsService, RequiredChecklist } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { ChecklistNotice } from '../workflow/checklist-notice';\nimport { ChecklistScan, ChecklistScanOptions, ChecklistScanner } from '../workflow/checklist-scanner';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n/**\n * `wp-checklist` — ALWAYS SUCCEEDS, always just lists. It answers the one question the coding agent has at\n * review time: *which reviewer subagents do I owe on this branch, and what exactly do I tell them?*\n *\n * Safe to run at any time, including mid-work with a dirty tree — uncommitted and untracked files are part\n * of the diff it matches against (see {@link ChecklistScanner}). It exits 0 in every case: zero checklists\n * configured, zero matched, or several still outstanding. Reporting is not gating; `wp-finish-upsert-pr` is\n * the only command that refuses, and it re-uses this exact scan with `filterAlreadyReviewed: true`, so the\n * two can never disagree about what is owed.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ChecklistCommand {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly checklistScanner: ChecklistScanner,\n private readonly instructions: ChecklistInstructionsService,\n private readonly checklistNotice: ChecklistNotice,\n ) {}\n\n run(): Promise<void> {\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n // Refresh the long-form doc so the block below can cite a file that is present and current.\n writeTemplate(repoRoot, 'webpieces.review-checklists.md');\n process.stdout.write('\\n' + SEP + '📋 Review checklists for this branch\\n' + SEP + '\\n');\n // filterAlreadyReviewed:false — LIST everything that applies, marking what is already done. Hiding the\n // done ones would make a second run look like it found fewer checklists than the first.\n const defined = loadAndValidate(repoRoot).prGate.checklists;\n process.stdout.write(this.report(this.checklistScanner.scan(repoRoot, defined, new ChecklistScanOptions(false))));\n return Promise.resolve();\n }\n\n private report(scan: ChecklistScan): string {\n if (scan.applicable.length === 0) {\n // Zero is a SUPPORTED state, never a blocker (see ChecklistNotice).\n return this.checklistNotice.build(scan.defined.length, 'wp-finish-upsert-pr');\n }\n const reviewedIds = new Set(scan.reviewed.map((r: RequiredChecklist): string => r.id));\n const owed = scan.applicable.filter((r: RequiredChecklist): boolean => !reviewedIds.has(r.id));\n return (\n this.header(scan) +\n this.rosterLines(scan, owed) +\n this.formatWarnings(scan) +\n (owed.length === 0 ? this.allDone() : `\\n${this.instructions.render(owed, scan.reviewPath, scan.context)}\\n`)\n );\n }\n\n // A verdict file that EXISTS but cannot be read as a verdict (almost always one still using the removed\n // `success` field) is called out here. Without it this command reports the checklist as simply owed, and\n // the AI re-runs a reviewer that already ran instead of correcting the file sitting right there.\n private formatWarnings(scan: ChecklistScan): string {\n if (scan.formatErrors.length === 0) return '';\n return '\\n' + scan.formatErrors.map((e: string): string => ` ⛔ ${e}\\n`).join('');\n }\n\n // State the base out loud: a reader (and a reviewer) needs to know the diff was taken from the fork point\n // of main and that uncommitted work counted, because both change which checklists fired.\n private header(scan: ChecklistScan): string {\n const base = scan.forkPoint === '' ? 'no fork point resolved' : `fork point ${scan.forkPoint.slice(0, 8)}`;\n return `${scan.defined.length} checklist(s) configured, ${scan.applicable.length} apply to this branch\\n` +\n `(${base}, including uncommitted + untracked work).\\n\\n`;\n }\n\n // REVIEW ONCE, PER SUBAGENT: a checklist with a passing verdict is reused, not re-run. 2 of 4 done means\n // the other 2 still need running, so both groups are shown and only the owed ones become instructions.\n private rosterLines(scan: ChecklistScan, owed: readonly RequiredChecklist[]): string {\n const lines = scan.reviewed.map((r: RequiredChecklist): string =>\n ` ✓ ${r.subagent} — already reviewed on this branch (reusing its review-${r.id}.json)\\n`);\n for (const r of owed) lines.push(` ▶ ${r.subagent} — ${this.why(r)}\\n`);\n return lines.join('');\n }\n\n // Why this one is in scope. A patternless checklist is NOT \"matched\" — it always runs, over the whole diff.\n private why(req: RequiredChecklist): string {\n if (req.matchedPatterns.length === 0) return 'ALWAYS RUNS (no patterns), whole diff in scope';\n const globs = req.matchedPatterns.map((p: string): string => `\"${p}\"`).join(', ');\n return `${req.matchedFiles.length} file(s) matched ${globs}`;\n }\n\n private allDone(): string {\n return '\\n✅ Every checklist that applies is already reviewed — nothing to run.\\n' +\n ' Write review.json and run: pnpm wp-finish-upsert-pr\\n';\n }\n}\n"]}
|
|
@@ -41,8 +41,25 @@ export declare class FinishUpsertPrCommand {
|
|
|
41
41
|
private prTitleFrom;
|
|
42
42
|
private computeDashboardInput;
|
|
43
43
|
private checklistRows;
|
|
44
|
+
/**
|
|
45
|
+
* One comment row per DEFINED checklist — matched or not — pairing the roster's match evidence with the
|
|
46
|
+
* resolved verdict. Built from `scan.roster` rather than from the applicable set: the skipped ones are
|
|
47
|
+
* absent from `applicable` by construction, and their "why not" evidence (the configured globs and the
|
|
48
|
+
* changed-file total) exists nowhere else without recomputing the diff a second way.
|
|
49
|
+
*/
|
|
50
|
+
private commentRows;
|
|
44
51
|
private gateTokenBody;
|
|
45
52
|
private enforceProvenance;
|
|
53
|
+
/**
|
|
54
|
+
* Publish the roster + every reviewer's full `output` as ONE combined PR comment, idempotently (find the
|
|
55
|
+
* marker comment → PATCH it, else POST). Never fatal: by here the PR is already created/updated, so a
|
|
56
|
+
* `gh` failure only warns.
|
|
57
|
+
*
|
|
58
|
+
* Posted on EVERY run of a repo that defines checklists — including one where nothing matched, because
|
|
59
|
+
* "all five were evaluated and none applied" is the good news the old comment could not deliver. The
|
|
60
|
+
* guard is `scan.defined.length`, deliberately NOT the number of rows that ran: a repo with no
|
|
61
|
+
* checklists configured must still see no comment at all (see ChecklistNotice / renderDashboard).
|
|
62
|
+
*/
|
|
46
63
|
private postChecklistComment;
|
|
47
64
|
private findChecklistCommentId;
|
|
48
65
|
private upsertPr;
|
|
@@ -125,7 +125,7 @@ let FinishUpsertPrCommand = class FinishUpsertPrCommand {
|
|
|
125
125
|
const result = this.upsertPr(repoRoot, base, body, title, input);
|
|
126
126
|
// Publish each reviewer's full output as ONE combined PR comment (idempotent, opt-out-aware). Never
|
|
127
127
|
// fatal — the PR is already up by now, so a comment failure only warns.
|
|
128
|
-
this.postChecklistComment(repoRoot, result.prNumber,
|
|
128
|
+
this.postChecklistComment(repoRoot, result.prNumber, scan, review, provenanceVerified);
|
|
129
129
|
const prNum = result.prNumber;
|
|
130
130
|
process.stdout.write('\n' + SEP + '✅ PR finished — here is exactly what I did\n' + SEP + '\n' +
|
|
131
131
|
` 1. validated the build gate (authoritative)\n` +
|
|
@@ -168,8 +168,16 @@ let FinishUpsertPrCommand = class FinishUpsertPrCommand {
|
|
|
168
168
|
assertEveryReviewerRan(scan) {
|
|
169
169
|
if (scan.outstanding.length === 0)
|
|
170
170
|
return;
|
|
171
|
+
// Unreadable verdict files come FIRST. A reviewer that wrote its verdict in the removed `success`
|
|
172
|
+
// format is otherwise indistinguishable from one that never ran, and the AI would go re-run a
|
|
173
|
+
// subagent instead of correcting four characters of JSON.
|
|
174
|
+
const format = scan.formatErrors.length === 0
|
|
175
|
+
? ''
|
|
176
|
+
: `${scan.formatErrors.length} verdict file(s) are in an UNREADABLE format:\n\n` +
|
|
177
|
+
scan.formatErrors.map((e) => ` • ${e}`).join('\n') + '\n\n';
|
|
171
178
|
throw new rules_config_1.InformAiError(`⛔ NO PR — ${scan.outstanding.length} of ${scan.applicable.length} review checklist(s) that apply to this ` +
|
|
172
179
|
`branch have no passing verdict yet: ${this.instructions.names(scan.outstanding)}\n\n` +
|
|
180
|
+
format +
|
|
173
181
|
`${this.instructions.render(scan.outstanding, scan.reviewPath, scan.context)}\n\n` +
|
|
174
182
|
`Then re-run: pnpm wp-finish-upsert-pr (or check anytime with: pnpm wp-checklist)`);
|
|
175
183
|
}
|
|
@@ -206,6 +214,24 @@ let FinishUpsertPrCommand = class FinishUpsertPrCommand {
|
|
|
206
214
|
return new dashboard_1.ChecklistRow(req.id, verdict.status, verdict.detail);
|
|
207
215
|
});
|
|
208
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* One comment row per DEFINED checklist — matched or not — pairing the roster's match evidence with the
|
|
219
|
+
* resolved verdict. Built from `scan.roster` rather than from the applicable set: the skipped ones are
|
|
220
|
+
* absent from `applicable` by construction, and their "why not" evidence (the configured globs and the
|
|
221
|
+
* changed-file total) exists nowhere else without recomputing the diff a second way.
|
|
222
|
+
*/
|
|
223
|
+
commentRows(scan, review) {
|
|
224
|
+
return scan.roster.entries.map((entry) => {
|
|
225
|
+
const ran = entry.matchedFiles.length > 0;
|
|
226
|
+
const req = new rules_config_1.RequiredChecklist(entry.def.id, entry.def.subagent, entry.def.doc, entry.matchedFiles, entry.matchedPatterns);
|
|
227
|
+
// A skipped checklist has no verdict to resolve — asking for one would report it as MISSING,
|
|
228
|
+
// i.e. as an unreviewed obligation, when in fact it never had one.
|
|
229
|
+
const verdict = ran
|
|
230
|
+
? this.reviewJsonService.resolveVerdict(req, review.results)
|
|
231
|
+
: new rules_config_1.ChecklistVerdict(entry.def.id, '', '');
|
|
232
|
+
return new dashboard_1.ChecklistCommentRow(entry.def.subagent, verdict.status, verdict.detail, ran, entry.def.patterns, entry.matchedPatterns, entry.matchedFiles, scan.roster.changedFileCount);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
209
235
|
// Hidden HMAC gate-token marker (with a leading blank line) to append to the PR body, or '' when the
|
|
210
236
|
// repo sets no gateSalt (byte-identical body to before this feature). Bound to the pushed HEAD sha.
|
|
211
237
|
gateTokenBody(gateSalt, headSha) {
|
|
@@ -236,15 +262,23 @@ let FinishUpsertPrCommand = class FinishUpsertPrCommand {
|
|
|
236
262
|
}
|
|
237
263
|
return verified;
|
|
238
264
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
265
|
+
/**
|
|
266
|
+
* Publish the roster + every reviewer's full `output` as ONE combined PR comment, idempotently (find the
|
|
267
|
+
* marker comment → PATCH it, else POST). Never fatal: by here the PR is already created/updated, so a
|
|
268
|
+
* `gh` failure only warns.
|
|
269
|
+
*
|
|
270
|
+
* Posted on EVERY run of a repo that defines checklists — including one where nothing matched, because
|
|
271
|
+
* "all five were evaluated and none applied" is the good news the old comment could not deliver. The
|
|
272
|
+
* guard is `scan.defined.length`, deliberately NOT the number of rows that ran: a repo with no
|
|
273
|
+
* checklists configured must still see no comment at all (see ChecklistNotice / renderDashboard).
|
|
274
|
+
*/
|
|
275
|
+
// eslint-disable-next-line @typescript-eslint/max-params
|
|
276
|
+
postChecklistComment(repoRoot, prNumber, scan, review, provenanceVerified) {
|
|
277
|
+
if (prNumber === '' || scan.defined.length === 0)
|
|
244
278
|
return;
|
|
245
279
|
if (!(0, rules_config_1.loadAndValidate)(repoRoot).prGate.checklistComments)
|
|
246
280
|
return;
|
|
247
|
-
const body = this.dashboard.renderChecklistComment(
|
|
281
|
+
const body = this.dashboard.renderChecklistComment(this.commentRows(scan, review), provenanceVerified, scan.roster.baseResolved);
|
|
248
282
|
const prDir = (0, rules_config_1.prDirFor)(repoRoot, this.aiBranchName.getFeatureName());
|
|
249
283
|
fs.mkdirSync(prDir, { recursive: true });
|
|
250
284
|
const payload = path.join(prDir, 'checklist-comment.json');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"finish-upsert-pr-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/finish-upsert-pr-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync, spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n loadAndValidate, prDirFor, reviewJsonPath, ReviewJson, RequiredChecklist,\n writeTemplate, RepoRootFinder, ReviewJsonService,\n GateTokenService, SubagentProvenanceService, PROVENANCE_OK, PROVENANCE_MISSING, PROVENANCE_SKIPPED,\n ChecklistInstructionsService, InformAiError,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BranchNaming } from '../workflow/branch-naming';\nimport { ChecklistScan, ChecklistScanOptions, ChecklistScanner } from '../workflow/checklist-scanner';\nimport { GitExec } from '../workflow/git-exec';\nimport { BuildAffected, BuildGateOptions } from '../workflow/build-affected';\nimport { MergeState } from '../workflow/merge-state';\nimport { MergeEnd, MergeEndOptions } from '../workflow/merge-end';\nimport { MergeContext } from '../workflow/merge-start';\nimport { PrMerger, MergeOutcome } from '../workflow/pr-merger';\nimport { GatedPrPublisher } from '../workflow/gated-pr-publisher';\nimport { Dashboard, DashboardInput, ChecklistRow, CHECKLIST_COMMENT_MARKER } from '../../dashboard/dashboard';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// A resolved PR's number + web URL. Both '' when the PR can't be resolved (e.g. create failed).\nclass PrRef {\n number: string;\n url: string;\n\n constructor(number: string, url: string) {\n this.number = number;\n this.url = url;\n }\n}\n\n// The outcome of the whole upsert: the PR number + web URL ('' each when unresolved) plus what actually\n// happened to the merge, so the final summary reports the REAL result rather than assuming success. The\n// URL is carried so the closing block can hand the AI a clickable link to relay to the user verbatim.\nclass UpsertResult {\n prNumber: string;\n prUrl: string;\n merge: MergeOutcome;\n\n constructor(prNumber: string, prUrl: string, merge: MergeOutcome) {\n this.prNumber = prNumber;\n this.prUrl = prUrl;\n this.merge = merge;\n }\n}\n\n// FINISH of the AI-first PR flow. Runs after the AI wrote review.json. In order: (1) if a 3-point merge\n// was in progress, validate + commit + FINALIZE via merge-END; (2) REQUIRE review.json; (3) run the\n// authoritative build gate; (4) render the dashboard; (5) create/update the PR via `gh`. The ONLY\n// command that posts PRs.\n@injectable(bindingScopeValues.Singleton)\nexport class FinishUpsertPrCommand {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly aiBranchName: AiBranchName,\n private readonly branchNaming: BranchNaming,\n private readonly gitExec: GitExec,\n private readonly buildAffected: BuildAffected,\n private readonly mergeState: MergeState,\n private readonly mergeEnd: MergeEnd,\n private readonly prMerger: PrMerger,\n private readonly publisher: GatedPrPublisher,\n private readonly dashboard: Dashboard,\n private readonly checklistScanner: ChecklistScanner,\n private readonly reviewJsonService: ReviewJsonService,\n private readonly gateTokenService: GateTokenService,\n private readonly provenance: SubagentProvenanceService,\n private readonly instructions: ChecklistInstructionsService,\n ) {}\n\n async run(): Promise<void> {\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n // Refresh the AI-facing workflow doc so it's present + current for any failure message to cite.\n writeTemplate(repoRoot, 'webpieces.git-workflow.md');\n // 1. Finish any in-progress conflict resolution: validate + commit + finalize the branch swap.\n await this.finalizeAnyInProgressMerge(repoRoot);\n\n // 2. REQUIRE the AI-authored review.json (throws InformAiError with the schema if missing/invalid).\n // Compute the consumer checklists this diff triggered FIRST so an unacknowledged BLOCK throws\n // here — BEFORE any `gh pr create` — matching the guarantee buildCommand already provides.\n // The SAME scan wp-checklist runs, with filterAlreadyReviewed:true so `outstanding` is exactly what\n // still owes a verdict (Z of N of X). Sharing the computation is the point: the command that REPORTS\n // and the command that GATES must not be able to disagree about what is owed. review-<id>.json files\n // persist locally, so a re-run re-validates the EXISTING verdicts against the (possibly changed)\n // applicable set for free — an unchanged checklist needs no re-review, a newly-applicable one refuses\n // until its file is written.\n const featureName = this.aiBranchName.getFeatureName();\n const scan = this.checklistScanner.scan(repoRoot, loadAndValidate(repoRoot).prGate.checklists, new ChecklistScanOptions(true));\n const required = scan.applicable;\n // FAIL FAST on missing reviewers, BEFORE review.json is parsed and before the build gate runs. A\n // missing reviewer is not a review.json defect (folding it in made the AI fix the wrong thing), and\n // nobody should wait on a build to be told a reviewer never ran.\n this.assertEveryReviewerRan(scan);\n const review = this.reviewJsonService.loadReviewJson(reviewJsonPath(repoRoot, featureName), required);\n\n // 2c. For any BLOCK checklist that names a reviewer `subagent`, VERIFY (from the harness's own\n // artifacts) that such a subagent actually ran on this branch — the coding agent may not\n // self-certify. Absent CLAUDE_CODE_SESSION_ID this skips with a warning (CI / plain terminal).\n const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();\n const provenanceVerified = this.enforceProvenance(required, currentBranch);\n\n // 2b. The build gate validates the WORKING TREE but we push HEAD — so they MUST be identical.\n this.gitExec.assertCleanTree(repoRoot);\n\n // 3. Authoritative build gate, then post the gated body, then push (that ORDER — see GatedPrPublisher).\n this.buildAffected.runBuildGate(repoRoot, new BuildGateOptions(\n '🛠️ Build gate (authoritative)', 'pnpm wp-finish-upsert-pr', 'Build failed — no PR created/updated.',\n ));\n const base = this.branchNaming.baseBranchName(execSync('git branch --show-current', { encoding: 'utf8' }).trim());\n\n process.stdout.write('\\n' + SEP + '📋 Dashboard + PR\\n' + SEP + '\\n');\n const title = this.prTitleFrom(review);\n const input = this.computeDashboardInput(repoRoot, true, review, title, required);\n // Append the hidden HMAC gate token bound to the LOCAL HEAD sha — computed BEFORE the push, because\n // GatedPrPublisher writes the body first so CI's `synchronize` read can never see a stale token. A\n // valid token in the PR body is proof this gated flow ran + passed on this exact commit, which CI\n // (`wp-check-pr`) recomputes. We reach here only after the build gate + every BLOCK checklist\n // passed, so minting is legitimate. Nothing about HMAC(salt, HEAD) needs the remote to have it.\n const gateSalt = loadAndValidate(repoRoot).prGate.gateSalt;\n const headSha = this.gitOut(['rev-parse', 'HEAD']);\n const body = this.dashboard.renderDashboard(input) + this.gateTokenBody(gateSalt, headSha);\n const result = this.upsertPr(repoRoot, base, body, title, input);\n // Publish each reviewer's full output as ONE combined PR comment (idempotent, opt-out-aware). Never\n // fatal — the PR is already up by now, so a comment failure only warns.\n this.postChecklistComment(repoRoot, result.prNumber, input.checklists, provenanceVerified);\n const prNum = result.prNumber;\n\n process.stdout.write(\n '\\n' + SEP + '✅ PR finished — here is exactly what I did\\n' + SEP + '\\n' +\n ` 1. validated the build gate (authoritative)\\n` +\n ` 2. ${prNum ? `wrote the gated body to PR #${prNum}` : 'composed the gated PR body'} titled: \"${title}\"\\n` +\n ` 3. force-pushed your work to origin/${base} (after the body, so CI reads the right token)\\n` +\n ` 4. ${result.merge.message}\\n` +\n ` You are on ${base} — same name as the remote branch and the PR head.\\n\\n`,\n );\n // Close by handing the AI a clickable link + directive to end its summary with it (see helper).\n this.emitClickableLinkDirective(prNum, result.prUrl, title);\n }\n\n // Print the closing AI directive: the resolved PR's number/title/URL and an instruction to end the\n // user-facing summary with EXACTLY `[#N title](url)` as the final line, nothing after it. No-op when\n // the PR could not be resolved — better to say nothing than to point the user at a link that isn't real.\n private emitClickableLinkDirective(prNum: string, prUrl: string, title: string): void {\n if (prNum === '' || prUrl === '') return;\n process.stdout.write(\n SEP + `🔗 PR #${prNum}: ${title}\\n ${prUrl}\\n\\n` + SEP +\n `AI — the LAST line of your summary to the user MUST be EXACTLY this clickable link, with\\n` +\n `NOTHING printed after it (no sign-off, no next steps, no blank commentary):\\n\\n` +\n ` [#${prNum} ${title}](${prUrl})\\n\\n`,\n );\n }\n\n // Validate + commit + finalize a 3-point merge the AI resolved, if one is in progress. Finalizing here\n // does NOT push (pushRemote=false): this command pushes exactly ONCE, from GatedPrPublisher, and only\n // after review.json + every BLOCK checklist + the build gate pass and the gated PR body is written.\n private async finalizeAnyInProgressMerge(repoRoot: string): Promise<void> {\n const home = this.mergeState.mergeDirFor(repoRoot, this.aiBranchName.getFeatureName());\n const activeDir = this.mergeState.findActiveMergeRunDir(home);\n const marker = activeDir ? this.mergeState.readMergeMarker(activeDir) : null;\n if (!activeDir || !marker || marker.validated) return;\n await this.mergeEnd.mergeEnd(\n repoRoot, 'wp-finish-upsert-pr', activeDir,\n new MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber),\n new MergeEndOptions(marker.conflictedFiles, false),\n );\n }\n\n /**\n * Refuse the PR while ANY applicable checklist still owes a verdict, naming exactly those reviewers and\n * exactly what to tell them — via the same renderer `wp-checklist` uses, so the AI sees the identical\n * block it would have seen there. Lists ONLY the outstanding ones: re-instructing an already-reviewed\n * checklist invites a redundant second run and reads as though the earlier verdict did not count.\n * No-op for a repo with no applicable checklists.\n */\n private assertEveryReviewerRan(scan: ChecklistScan): void {\n if (scan.outstanding.length === 0) return;\n throw new InformAiError(\n `⛔ NO PR — ${scan.outstanding.length} of ${scan.applicable.length} review checklist(s) that apply to this ` +\n `branch have no passing verdict yet: ${this.instructions.names(scan.outstanding)}\\n\\n` +\n `${this.instructions.render(scan.outstanding, scan.reviewPath, scan.context)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr (or check anytime with: pnpm wp-checklist)`,\n );\n }\n\n private gitOut(args: string[]): string {\n const result = spawnSync('git', args, { encoding: 'utf8' });\n return result.status === 0 ? (result.stdout ?? '').trim() : '';\n }\n\n // The user-facing PR title: the AI-authored review.title, or — if omitted — a readable fallback\n // derived from the stable feature name (NEVER the internal `Squash merge of <branch>` commit subject).\n private prTitleFrom(review: ReviewJson): string {\n if (review.title !== '') return review.title;\n return this.aiBranchName.getFeatureName().replace(/[-/]+/g, ' ').trim();\n }\n\n // eslint-disable-next-line @typescript-eslint/max-params\n private computeDashboardInput(repoRoot: string, buildPassed: boolean, review: ReviewJson, title: string, required: readonly RequiredChecklist[]): DashboardInput {\n const config = loadAndValidate(repoRoot).prGate;\n const forkPoint = this.gitOut(['merge-base', 'origin/main', 'HEAD']);\n const featureHead = this.gitOut(['rev-parse', 'HEAD']);\n const mainHead = this.gitOut(['rev-parse', 'origin/main']);\n const range = `${forkPoint}..${featureHead}`;\n const changedFiles = this.gitOut(['diff', range, '--name-only']).split('\\n').filter((f: string): boolean => f.trim() !== '');\n const patch = this.gitOut(['diff', range]);\n\n const gateResults = this.dashboard.computeGateResults(config.gates, changedFiles);\n const disables = this.dashboard.countAddedDisables(patch);\n const rows = this.checklistRows(required, review);\n return new DashboardInput(title, gateResults, disables, buildPassed, forkPoint, featureHead, mainHead, review, rows);\n }\n\n // Pair each matched checklist with its resolved verdict for the dashboard. (A checklist reaching this\n // point is always PASS/OVERRIDDEN — loadReviewJson already threw on FAIL/MISSING.)\n private checklistRows(required: readonly RequiredChecklist[], review: ReviewJson): ChecklistRow[] {\n return required.map((req: RequiredChecklist): ChecklistRow => {\n const verdict = this.reviewJsonService.resolveVerdict(req, review.results);\n return new ChecklistRow(req.id, verdict.status, verdict.detail);\n });\n }\n\n // Hidden HMAC gate-token marker (with a leading blank line) to append to the PR body, or '' when the\n // repo sets no gateSalt (byte-identical body to before this feature). Bound to the pushed HEAD sha.\n private gateTokenBody(gateSalt: string, headSha: string): string {\n const marker = this.gateTokenService.gateTokenMarker(gateSalt, headSha);\n return marker === '' ? '' : `\\n\\n${marker}\\n`;\n }\n\n // Enforce that EACH matched checklist was reviewed by its OWN named subagent, as a DISTINCT run —\n // the coding agent may not self-certify, and one reviewer may not stand in for several. A verified set\n // passes silently; no session id warns but passes; any missing reviewer throws so the PR does not open.\n private enforceProvenance(required: readonly RequiredChecklist[], branch: string): boolean {\n const errors: string[] = [];\n let verified = true; // no reviewers to verify ⇒ vacuously true\n const subagents = required.map((r: RequiredChecklist): string => r.subagent.trim()).filter((s: string): boolean => s !== '');\n if (subagents.length > 0) {\n const result = this.provenance.verifyDistinct(subagents, branch);\n verified = result.status === PROVENANCE_OK;\n if (result.status === PROVENANCE_MISSING) {\n errors.push(result.detail);\n } else if (result.status === PROVENANCE_SKIPPED) {\n process.stderr.write(`⚠️ ${result.detail}\\n`);\n }\n }\n if (errors.length > 0) {\n throw new InformAiError(\n `${errors.length} checklist(s) require an independent reviewer subagent that did not run — fix, then re-run pnpm wp-finish-upsert-pr:\\n\\n` +\n errors.map((e: string): string => ` • ${e}`).join('\\n') +\n `\\n\\nSpawn the named reviewer subagent to review the checklist on THIS branch, then re-run.`,\n );\n }\n return verified;\n }\n\n // Publish every reviewer's full `output` as ONE combined PR comment, idempotently (find the marker\n // comment → PATCH it, else POST). No-op when there is no PR number or no matched checklists. Never\n // fatal: by here the PR is already created/updated, so a `gh` failure only warns.\n private postChecklistComment(repoRoot: string, prNumber: string, rows: readonly ChecklistRow[], provenanceVerified: boolean): void {\n if (prNumber === '' || rows.length === 0) return;\n if (!loadAndValidate(repoRoot).prGate.checklistComments) return;\n const body = this.dashboard.renderChecklistComment(rows, provenanceVerified);\n const prDir = prDirFor(repoRoot, this.aiBranchName.getFeatureName());\n fs.mkdirSync(prDir, { recursive: true });\n const payload = path.join(prDir, 'checklist-comment.json');\n fs.writeFileSync(payload, JSON.stringify({ body }));\n const commentId = this.findChecklistCommentId(prNumber);\n const args = commentId !== ''\n ? ['api', '--method', 'PATCH', `repos/{owner}/{repo}/issues/comments/${commentId}`, '--input', payload]\n : ['api', '--method', 'POST', `repos/{owner}/{repo}/issues/${prNumber}/comments`, '--input', payload];\n const res = spawnSync('gh', args, { encoding: 'utf8' });\n if (res.status !== 0) {\n process.stderr.write('⚠️ Could not post the checklist review comment (non-fatal — the PR is already up).\\n');\n } else {\n process.stdout.write(` ${commentId !== '' ? 'updated' : 'posted'} the checklist review comment ✓\\n`);\n }\n }\n\n // The id of THIS tool's existing checklist comment on the PR (by the hidden marker), or '' if none.\n private findChecklistCommentId(prNumber: string): string {\n const res = spawnSync('gh', [\n 'api', '--paginate', `repos/{owner}/{repo}/issues/${prNumber}/comments`,\n '--jq', `.[] | select(.body | contains(\"${CHECKLIST_COMMENT_MARKER}\")) | .id`,\n ], { encoding: 'utf8' });\n if (res.status !== 0) return '';\n return (res.stdout ?? '').trim().split('\\n')[0] ?? '';\n }\n\n // The PR, the remote branch, and the local branch all share the one stable feature name. Look up /\n // create / merge against `baseBranch` (baseBranchName tolerates a leftover `…wpN` mid-transition).\n // GatedPrPublisher owns the edit/push/create half and its ORDERING — the gated body goes up before\n // the push, so CI's `synchronize` read cannot see the previous run's token.\n private upsertPr(repoRoot: string, baseBranch: string, body: string, title: string, input: DashboardInput): UpsertResult {\n const prDir = prDirFor(repoRoot, this.aiBranchName.getFeatureName());\n fs.mkdirSync(prDir, { recursive: true });\n const bodyFile = path.join(prDir, 'pr-body.md');\n fs.writeFileSync(bodyFile, body + '\\n');\n\n const published = this.publisher.publish(baseBranch, title, bodyFile);\n if (published.createFailed) {\n process.stderr.write('⚠️ gh pr create failed — create the PR manually with the body in:\\n ' + bodyFile + '\\n');\n return new UpsertResult('', '', new MergeOutcome(false, false,\n '⚠️ did NOT merge — there is no PR to merge (gh pr create failed above)'));\n }\n const num = published.number;\n\n // Set the squash-merge SUBJECT to the PR title (+ the `(#N)` GitHub normally appends, which an\n // explicit --subject would otherwise drop) and the BODY to the compact commit summary, so main's\n // history carries the PR title + risk/flags/link — NOT the internal `Squash merge of <branch>`\n // subject GitHub would inherit from the single squash commit on the branch.\n const ref = this.prRef(baseBranch);\n const subject = ref.number !== '' ? `${title} (#${ref.number})` : title;\n const mergeBodyFile = path.join(prDir, 'merge-commit-body.md');\n fs.writeFileSync(mergeBodyFile, this.dashboard.renderCommitBody(input, ref.url) + '\\n');\n // PrMerger owns the direct-merge / auto-merge-fallback decision AND checks every gh status, so a\n // merge that did not happen is reported as such instead of being swallowed (see pr-merger.ts).\n // REQUIRED config — no default here on purpose. A missing value (an older published\n // rules-config that has no such field) reaches PrMerger as '' and is treated as \"do not merge\".\n const mergeMode = loadAndValidate(repoRoot).prGate.mergeMode ?? '';\n const outcome = this.prMerger.merge(baseBranch, subject, mergeBodyFile, mergeMode);\n return new UpsertResult(ref.number !== '' ? ref.number : num, ref.url, outcome);\n }\n\n // The PR's number + web URL (for the merge subject `(#N)` and the commit-body back-link). Both ''\n // if it can't be resolved. Rendered via jq into one tab-separated line so no JSON parsing is needed.\n private prRef(baseBranch: string): PrRef {\n const result = spawnSync(\n 'gh', ['pr', 'view', baseBranch, '--json', 'number,url', '--jq', '\"\\\\(.number)\\\\t\\\\(.url)\"'],\n { encoding: 'utf8' },\n );\n if (result.status !== 0) {\n return new PrRef('', '');\n }\n const parts = (result.stdout ?? '').trim().split('\\t');\n return new PrRef(parts[0] ?? '', parts[1] ?? '');\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"finish-upsert-pr-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/finish-upsert-pr-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync, spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n loadAndValidate, prDirFor, reviewJsonPath, ReviewJson, RequiredChecklist, ChecklistVerdict,\n writeTemplate, RepoRootFinder, ReviewJsonService,\n GateTokenService, SubagentProvenanceService, PROVENANCE_OK, PROVENANCE_MISSING, PROVENANCE_SKIPPED,\n ChecklistInstructionsService, InformAiError,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BranchNaming } from '../workflow/branch-naming';\nimport { ChecklistScan, ChecklistScanOptions, ChecklistScanner } from '../workflow/checklist-scanner';\nimport { GitExec } from '../workflow/git-exec';\nimport { BuildAffected, BuildGateOptions } from '../workflow/build-affected';\nimport { MergeState } from '../workflow/merge-state';\nimport { MergeEnd, MergeEndOptions } from '../workflow/merge-end';\nimport { MergeContext } from '../workflow/merge-start';\nimport { PrMerger, MergeOutcome } from '../workflow/pr-merger';\nimport { GatedPrPublisher } from '../workflow/gated-pr-publisher';\nimport { TriggeredChecklist } from '../workflow/checklist-detector';\nimport {\n Dashboard, DashboardInput, ChecklistRow, ChecklistCommentRow, CHECKLIST_COMMENT_MARKER,\n} from '../../dashboard/dashboard';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// A resolved PR's number + web URL. Both '' when the PR can't be resolved (e.g. create failed).\nclass PrRef {\n number: string;\n url: string;\n\n constructor(number: string, url: string) {\n this.number = number;\n this.url = url;\n }\n}\n\n// The outcome of the whole upsert: the PR number + web URL ('' each when unresolved) plus what actually\n// happened to the merge, so the final summary reports the REAL result rather than assuming success. The\n// URL is carried so the closing block can hand the AI a clickable link to relay to the user verbatim.\nclass UpsertResult {\n prNumber: string;\n prUrl: string;\n merge: MergeOutcome;\n\n constructor(prNumber: string, prUrl: string, merge: MergeOutcome) {\n this.prNumber = prNumber;\n this.prUrl = prUrl;\n this.merge = merge;\n }\n}\n\n// FINISH of the AI-first PR flow. Runs after the AI wrote review.json. In order: (1) if a 3-point merge\n// was in progress, validate + commit + FINALIZE via merge-END; (2) REQUIRE review.json; (3) run the\n// authoritative build gate; (4) render the dashboard; (5) create/update the PR via `gh`. The ONLY\n// command that posts PRs.\n@injectable(bindingScopeValues.Singleton)\nexport class FinishUpsertPrCommand {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly aiBranchName: AiBranchName,\n private readonly branchNaming: BranchNaming,\n private readonly gitExec: GitExec,\n private readonly buildAffected: BuildAffected,\n private readonly mergeState: MergeState,\n private readonly mergeEnd: MergeEnd,\n private readonly prMerger: PrMerger,\n private readonly publisher: GatedPrPublisher,\n private readonly dashboard: Dashboard,\n private readonly checklistScanner: ChecklistScanner,\n private readonly reviewJsonService: ReviewJsonService,\n private readonly gateTokenService: GateTokenService,\n private readonly provenance: SubagentProvenanceService,\n private readonly instructions: ChecklistInstructionsService,\n ) {}\n\n async run(): Promise<void> {\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n // Refresh the AI-facing workflow doc so it's present + current for any failure message to cite.\n writeTemplate(repoRoot, 'webpieces.git-workflow.md');\n // 1. Finish any in-progress conflict resolution: validate + commit + finalize the branch swap.\n await this.finalizeAnyInProgressMerge(repoRoot);\n\n // 2. REQUIRE the AI-authored review.json (throws InformAiError with the schema if missing/invalid).\n // Compute the consumer checklists this diff triggered FIRST so an unacknowledged BLOCK throws\n // here — BEFORE any `gh pr create` — matching the guarantee buildCommand already provides.\n // The SAME scan wp-checklist runs, with filterAlreadyReviewed:true so `outstanding` is exactly what\n // still owes a verdict (Z of N of X). Sharing the computation is the point: the command that REPORTS\n // and the command that GATES must not be able to disagree about what is owed. review-<id>.json files\n // persist locally, so a re-run re-validates the EXISTING verdicts against the (possibly changed)\n // applicable set for free — an unchanged checklist needs no re-review, a newly-applicable one refuses\n // until its file is written.\n const featureName = this.aiBranchName.getFeatureName();\n const scan = this.checklistScanner.scan(repoRoot, loadAndValidate(repoRoot).prGate.checklists, new ChecklistScanOptions(true));\n const required = scan.applicable;\n // FAIL FAST on missing reviewers, BEFORE review.json is parsed and before the build gate runs. A\n // missing reviewer is not a review.json defect (folding it in made the AI fix the wrong thing), and\n // nobody should wait on a build to be told a reviewer never ran.\n this.assertEveryReviewerRan(scan);\n const review = this.reviewJsonService.loadReviewJson(reviewJsonPath(repoRoot, featureName), required);\n\n // 2c. For any BLOCK checklist that names a reviewer `subagent`, VERIFY (from the harness's own\n // artifacts) that such a subagent actually ran on this branch — the coding agent may not\n // self-certify. Absent CLAUDE_CODE_SESSION_ID this skips with a warning (CI / plain terminal).\n const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();\n const provenanceVerified = this.enforceProvenance(required, currentBranch);\n\n // 2b. The build gate validates the WORKING TREE but we push HEAD — so they MUST be identical.\n this.gitExec.assertCleanTree(repoRoot);\n\n // 3. Authoritative build gate, then post the gated body, then push (that ORDER — see GatedPrPublisher).\n this.buildAffected.runBuildGate(repoRoot, new BuildGateOptions(\n '🛠️ Build gate (authoritative)', 'pnpm wp-finish-upsert-pr', 'Build failed — no PR created/updated.',\n ));\n const base = this.branchNaming.baseBranchName(execSync('git branch --show-current', { encoding: 'utf8' }).trim());\n\n process.stdout.write('\\n' + SEP + '📋 Dashboard + PR\\n' + SEP + '\\n');\n const title = this.prTitleFrom(review);\n const input = this.computeDashboardInput(repoRoot, true, review, title, required);\n // Append the hidden HMAC gate token bound to the LOCAL HEAD sha — computed BEFORE the push, because\n // GatedPrPublisher writes the body first so CI's `synchronize` read can never see a stale token. A\n // valid token in the PR body is proof this gated flow ran + passed on this exact commit, which CI\n // (`wp-check-pr`) recomputes. We reach here only after the build gate + every BLOCK checklist\n // passed, so minting is legitimate. Nothing about HMAC(salt, HEAD) needs the remote to have it.\n const gateSalt = loadAndValidate(repoRoot).prGate.gateSalt;\n const headSha = this.gitOut(['rev-parse', 'HEAD']);\n const body = this.dashboard.renderDashboard(input) + this.gateTokenBody(gateSalt, headSha);\n const result = this.upsertPr(repoRoot, base, body, title, input);\n // Publish each reviewer's full output as ONE combined PR comment (idempotent, opt-out-aware). Never\n // fatal — the PR is already up by now, so a comment failure only warns.\n this.postChecklistComment(repoRoot, result.prNumber, scan, review, provenanceVerified);\n const prNum = result.prNumber;\n\n process.stdout.write(\n '\\n' + SEP + '✅ PR finished — here is exactly what I did\\n' + SEP + '\\n' +\n ` 1. validated the build gate (authoritative)\\n` +\n ` 2. ${prNum ? `wrote the gated body to PR #${prNum}` : 'composed the gated PR body'} titled: \"${title}\"\\n` +\n ` 3. force-pushed your work to origin/${base} (after the body, so CI reads the right token)\\n` +\n ` 4. ${result.merge.message}\\n` +\n ` You are on ${base} — same name as the remote branch and the PR head.\\n\\n`,\n );\n // Close by handing the AI a clickable link + directive to end its summary with it (see helper).\n this.emitClickableLinkDirective(prNum, result.prUrl, title);\n }\n\n // Print the closing AI directive: the resolved PR's number/title/URL and an instruction to end the\n // user-facing summary with EXACTLY `[#N title](url)` as the final line, nothing after it. No-op when\n // the PR could not be resolved — better to say nothing than to point the user at a link that isn't real.\n private emitClickableLinkDirective(prNum: string, prUrl: string, title: string): void {\n if (prNum === '' || prUrl === '') return;\n process.stdout.write(\n SEP + `🔗 PR #${prNum}: ${title}\\n ${prUrl}\\n\\n` + SEP +\n `AI — the LAST line of your summary to the user MUST be EXACTLY this clickable link, with\\n` +\n `NOTHING printed after it (no sign-off, no next steps, no blank commentary):\\n\\n` +\n ` [#${prNum} ${title}](${prUrl})\\n\\n`,\n );\n }\n\n // Validate + commit + finalize a 3-point merge the AI resolved, if one is in progress. Finalizing here\n // does NOT push (pushRemote=false): this command pushes exactly ONCE, from GatedPrPublisher, and only\n // after review.json + every BLOCK checklist + the build gate pass and the gated PR body is written.\n private async finalizeAnyInProgressMerge(repoRoot: string): Promise<void> {\n const home = this.mergeState.mergeDirFor(repoRoot, this.aiBranchName.getFeatureName());\n const activeDir = this.mergeState.findActiveMergeRunDir(home);\n const marker = activeDir ? this.mergeState.readMergeMarker(activeDir) : null;\n if (!activeDir || !marker || marker.validated) return;\n await this.mergeEnd.mergeEnd(\n repoRoot, 'wp-finish-upsert-pr', activeDir,\n new MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber),\n new MergeEndOptions(marker.conflictedFiles, false),\n );\n }\n\n /**\n * Refuse the PR while ANY applicable checklist still owes a verdict, naming exactly those reviewers and\n * exactly what to tell them — via the same renderer `wp-checklist` uses, so the AI sees the identical\n * block it would have seen there. Lists ONLY the outstanding ones: re-instructing an already-reviewed\n * checklist invites a redundant second run and reads as though the earlier verdict did not count.\n * No-op for a repo with no applicable checklists.\n */\n private assertEveryReviewerRan(scan: ChecklistScan): void {\n if (scan.outstanding.length === 0) return;\n // Unreadable verdict files come FIRST. A reviewer that wrote its verdict in the removed `success`\n // format is otherwise indistinguishable from one that never ran, and the AI would go re-run a\n // subagent instead of correcting four characters of JSON.\n const format = scan.formatErrors.length === 0\n ? ''\n : `${scan.formatErrors.length} verdict file(s) are in an UNREADABLE format:\\n\\n` +\n scan.formatErrors.map((e: string): string => ` • ${e}`).join('\\n') + '\\n\\n';\n throw new InformAiError(\n `⛔ NO PR — ${scan.outstanding.length} of ${scan.applicable.length} review checklist(s) that apply to this ` +\n `branch have no passing verdict yet: ${this.instructions.names(scan.outstanding)}\\n\\n` +\n format +\n `${this.instructions.render(scan.outstanding, scan.reviewPath, scan.context)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr (or check anytime with: pnpm wp-checklist)`,\n );\n }\n\n private gitOut(args: string[]): string {\n const result = spawnSync('git', args, { encoding: 'utf8' });\n return result.status === 0 ? (result.stdout ?? '').trim() : '';\n }\n\n // The user-facing PR title: the AI-authored review.title, or — if omitted — a readable fallback\n // derived from the stable feature name (NEVER the internal `Squash merge of <branch>` commit subject).\n private prTitleFrom(review: ReviewJson): string {\n if (review.title !== '') return review.title;\n return this.aiBranchName.getFeatureName().replace(/[-/]+/g, ' ').trim();\n }\n\n // eslint-disable-next-line @typescript-eslint/max-params\n private computeDashboardInput(repoRoot: string, buildPassed: boolean, review: ReviewJson, title: string, required: readonly RequiredChecklist[]): DashboardInput {\n const config = loadAndValidate(repoRoot).prGate;\n const forkPoint = this.gitOut(['merge-base', 'origin/main', 'HEAD']);\n const featureHead = this.gitOut(['rev-parse', 'HEAD']);\n const mainHead = this.gitOut(['rev-parse', 'origin/main']);\n const range = `${forkPoint}..${featureHead}`;\n const changedFiles = this.gitOut(['diff', range, '--name-only']).split('\\n').filter((f: string): boolean => f.trim() !== '');\n const patch = this.gitOut(['diff', range]);\n\n const gateResults = this.dashboard.computeGateResults(config.gates, changedFiles);\n const disables = this.dashboard.countAddedDisables(patch);\n const rows = this.checklistRows(required, review);\n return new DashboardInput(title, gateResults, disables, buildPassed, forkPoint, featureHead, mainHead, review, rows);\n }\n\n // Pair each matched checklist with its resolved verdict for the dashboard. (A checklist reaching this\n // point is always PASS/OVERRIDDEN — loadReviewJson already threw on FAIL/MISSING.)\n private checklistRows(required: readonly RequiredChecklist[], review: ReviewJson): ChecklistRow[] {\n return required.map((req: RequiredChecklist): ChecklistRow => {\n const verdict = this.reviewJsonService.resolveVerdict(req, review.results);\n return new ChecklistRow(req.id, verdict.status, verdict.detail);\n });\n }\n\n /**\n * One comment row per DEFINED checklist — matched or not — pairing the roster's match evidence with the\n * resolved verdict. Built from `scan.roster` rather than from the applicable set: the skipped ones are\n * absent from `applicable` by construction, and their \"why not\" evidence (the configured globs and the\n * changed-file total) exists nowhere else without recomputing the diff a second way.\n */\n private commentRows(scan: ChecklistScan, review: ReviewJson): ChecklistCommentRow[] {\n return scan.roster.entries.map((entry: TriggeredChecklist): ChecklistCommentRow => {\n const ran = entry.matchedFiles.length > 0;\n const req = new RequiredChecklist(\n entry.def.id, entry.def.subagent, entry.def.doc, entry.matchedFiles, entry.matchedPatterns);\n // A skipped checklist has no verdict to resolve — asking for one would report it as MISSING,\n // i.e. as an unreviewed obligation, when in fact it never had one.\n const verdict = ran\n ? this.reviewJsonService.resolveVerdict(req, review.results)\n : new ChecklistVerdict(entry.def.id, '', '');\n return new ChecklistCommentRow(\n entry.def.subagent, verdict.status, verdict.detail, ran,\n entry.def.patterns, entry.matchedPatterns, entry.matchedFiles, scan.roster.changedFileCount);\n });\n }\n\n // Hidden HMAC gate-token marker (with a leading blank line) to append to the PR body, or '' when the\n // repo sets no gateSalt (byte-identical body to before this feature). Bound to the pushed HEAD sha.\n private gateTokenBody(gateSalt: string, headSha: string): string {\n const marker = this.gateTokenService.gateTokenMarker(gateSalt, headSha);\n return marker === '' ? '' : `\\n\\n${marker}\\n`;\n }\n\n // Enforce that EACH matched checklist was reviewed by its OWN named subagent, as a DISTINCT run —\n // the coding agent may not self-certify, and one reviewer may not stand in for several. A verified set\n // passes silently; no session id warns but passes; any missing reviewer throws so the PR does not open.\n private enforceProvenance(required: readonly RequiredChecklist[], branch: string): boolean {\n const errors: string[] = [];\n let verified = true; // no reviewers to verify ⇒ vacuously true\n const subagents = required.map((r: RequiredChecklist): string => r.subagent.trim()).filter((s: string): boolean => s !== '');\n if (subagents.length > 0) {\n const result = this.provenance.verifyDistinct(subagents, branch);\n verified = result.status === PROVENANCE_OK;\n if (result.status === PROVENANCE_MISSING) {\n errors.push(result.detail);\n } else if (result.status === PROVENANCE_SKIPPED) {\n process.stderr.write(`⚠️ ${result.detail}\\n`);\n }\n }\n if (errors.length > 0) {\n throw new InformAiError(\n `${errors.length} checklist(s) require an independent reviewer subagent that did not run — fix, then re-run pnpm wp-finish-upsert-pr:\\n\\n` +\n errors.map((e: string): string => ` • ${e}`).join('\\n') +\n `\\n\\nSpawn the named reviewer subagent to review the checklist on THIS branch, then re-run.`,\n );\n }\n return verified;\n }\n\n /**\n * Publish the roster + every reviewer's full `output` as ONE combined PR comment, idempotently (find the\n * marker comment → PATCH it, else POST). Never fatal: by here the PR is already created/updated, so a\n * `gh` failure only warns.\n *\n * Posted on EVERY run of a repo that defines checklists — including one where nothing matched, because\n * \"all five were evaluated and none applied\" is the good news the old comment could not deliver. The\n * guard is `scan.defined.length`, deliberately NOT the number of rows that ran: a repo with no\n * checklists configured must still see no comment at all (see ChecklistNotice / renderDashboard).\n */\n // eslint-disable-next-line @typescript-eslint/max-params\n private postChecklistComment(repoRoot: string, prNumber: string, scan: ChecklistScan, review: ReviewJson, provenanceVerified: boolean): void {\n if (prNumber === '' || scan.defined.length === 0) return;\n if (!loadAndValidate(repoRoot).prGate.checklistComments) return;\n const body = this.dashboard.renderChecklistComment(\n this.commentRows(scan, review), provenanceVerified, scan.roster.baseResolved);\n const prDir = prDirFor(repoRoot, this.aiBranchName.getFeatureName());\n fs.mkdirSync(prDir, { recursive: true });\n const payload = path.join(prDir, 'checklist-comment.json');\n fs.writeFileSync(payload, JSON.stringify({ body }));\n const commentId = this.findChecklistCommentId(prNumber);\n const args = commentId !== ''\n ? ['api', '--method', 'PATCH', `repos/{owner}/{repo}/issues/comments/${commentId}`, '--input', payload]\n : ['api', '--method', 'POST', `repos/{owner}/{repo}/issues/${prNumber}/comments`, '--input', payload];\n const res = spawnSync('gh', args, { encoding: 'utf8' });\n if (res.status !== 0) {\n process.stderr.write('⚠️ Could not post the checklist review comment (non-fatal — the PR is already up).\\n');\n } else {\n process.stdout.write(` ${commentId !== '' ? 'updated' : 'posted'} the checklist review comment ✓\\n`);\n }\n }\n\n // The id of THIS tool's existing checklist comment on the PR (by the hidden marker), or '' if none.\n private findChecklistCommentId(prNumber: string): string {\n const res = spawnSync('gh', [\n 'api', '--paginate', `repos/{owner}/{repo}/issues/${prNumber}/comments`,\n '--jq', `.[] | select(.body | contains(\"${CHECKLIST_COMMENT_MARKER}\")) | .id`,\n ], { encoding: 'utf8' });\n if (res.status !== 0) return '';\n return (res.stdout ?? '').trim().split('\\n')[0] ?? '';\n }\n\n // The PR, the remote branch, and the local branch all share the one stable feature name. Look up /\n // create / merge against `baseBranch` (baseBranchName tolerates a leftover `…wpN` mid-transition).\n // GatedPrPublisher owns the edit/push/create half and its ORDERING — the gated body goes up before\n // the push, so CI's `synchronize` read cannot see the previous run's token.\n private upsertPr(repoRoot: string, baseBranch: string, body: string, title: string, input: DashboardInput): UpsertResult {\n const prDir = prDirFor(repoRoot, this.aiBranchName.getFeatureName());\n fs.mkdirSync(prDir, { recursive: true });\n const bodyFile = path.join(prDir, 'pr-body.md');\n fs.writeFileSync(bodyFile, body + '\\n');\n\n const published = this.publisher.publish(baseBranch, title, bodyFile);\n if (published.createFailed) {\n process.stderr.write('⚠️ gh pr create failed — create the PR manually with the body in:\\n ' + bodyFile + '\\n');\n return new UpsertResult('', '', new MergeOutcome(false, false,\n '⚠️ did NOT merge — there is no PR to merge (gh pr create failed above)'));\n }\n const num = published.number;\n\n // Set the squash-merge SUBJECT to the PR title (+ the `(#N)` GitHub normally appends, which an\n // explicit --subject would otherwise drop) and the BODY to the compact commit summary, so main's\n // history carries the PR title + risk/flags/link — NOT the internal `Squash merge of <branch>`\n // subject GitHub would inherit from the single squash commit on the branch.\n const ref = this.prRef(baseBranch);\n const subject = ref.number !== '' ? `${title} (#${ref.number})` : title;\n const mergeBodyFile = path.join(prDir, 'merge-commit-body.md');\n fs.writeFileSync(mergeBodyFile, this.dashboard.renderCommitBody(input, ref.url) + '\\n');\n // PrMerger owns the direct-merge / auto-merge-fallback decision AND checks every gh status, so a\n // merge that did not happen is reported as such instead of being swallowed (see pr-merger.ts).\n // REQUIRED config — no default here on purpose. A missing value (an older published\n // rules-config that has no such field) reaches PrMerger as '' and is treated as \"do not merge\".\n const mergeMode = loadAndValidate(repoRoot).prGate.mergeMode ?? '';\n const outcome = this.prMerger.merge(baseBranch, subject, mergeBodyFile, mergeMode);\n return new UpsertResult(ref.number !== '' ? ref.number : num, ref.url, outcome);\n }\n\n // The PR's number + web URL (for the merge subject `(#N)` and the commit-body back-link). Both ''\n // if it can't be resolved. Rendered via jq into one tab-separated line so no JSON parsing is needed.\n private prRef(baseBranch: string): PrRef {\n const result = spawnSync(\n 'gh', ['pr', 'view', baseBranch, '--json', 'number,url', '--jq', '\"\\\\(.number)\\\\t\\\\(.url)\"'],\n { encoding: 'utf8' },\n );\n if (result.status !== 0) {\n return new PrRef('', '');\n }\n const parts = (result.stdout ?? '').trim().split('\\t');\n return new PrRef(parts[0] ?? '', parts[1] ?? '');\n }\n}\n"]}
|
|
@@ -5,6 +5,21 @@ export declare class TriggeredChecklist {
|
|
|
5
5
|
matchedPatterns: string[];
|
|
6
6
|
constructor(def: ChecklistDefinition, matchedFiles: string[], matchedPatterns?: string[]);
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Every DEFINED checklist paired with what it matched, plus the two facts a reader needs to judge a skip:
|
|
10
|
+
* how many files were even considered, and whether a diff was computable at all. Data-only (per CLAUDE.md).
|
|
11
|
+
*/
|
|
12
|
+
export declare class ChecklistRoster {
|
|
13
|
+
entries: TriggeredChecklist[];
|
|
14
|
+
changedFileCount: number;
|
|
15
|
+
/**
|
|
16
|
+
* false when no fork point resolved. Load-bearing: with no base the changed-file set is EMPTY, so
|
|
17
|
+
* nothing matches — including patternless ALWAYS-RUNS checklists. Rendering that as "all skipped ✅"
|
|
18
|
+
* would post a green all-clear to GitHub for a PR where no checklist was actually evaluated.
|
|
19
|
+
*/
|
|
20
|
+
baseResolved: boolean;
|
|
21
|
+
constructor(entries: TriggeredChecklist[], changedFileCount: number, baseResolved: boolean);
|
|
22
|
+
}
|
|
8
23
|
/**
|
|
9
24
|
* Decides which company review checklists a branch matched, from what the diff CHANGED. Path matching
|
|
10
25
|
* uses `isPathExcluded` — the SAME minimatch-based matcher rules-config already shares across
|
|
@@ -16,6 +31,17 @@ export declare class TriggeredChecklist {
|
|
|
16
31
|
export declare class ChecklistDetector {
|
|
17
32
|
private readonly diffScope;
|
|
18
33
|
constructor(diffScope: DiffScope);
|
|
34
|
+
/**
|
|
35
|
+
* Pure matching over EVERY defined checklist, matched or not — one entry per def, in config order, with
|
|
36
|
+
* `matchedFiles: []` for the ones nothing hit. This is what lets the PR comment publish the full roster:
|
|
37
|
+
* a skipped checklist is the normal, healthy outcome, and a comment that lists only the ones that fired
|
|
38
|
+
* cannot distinguish "evaluated and irrelevant" from "never configured".
|
|
39
|
+
*
|
|
40
|
+
* A skipped entry has `matchedPatterns: []` — and so does a PATTERNLESS one. They mean opposite things,
|
|
41
|
+
* so a renderer must decide "always runs" from `def.patterns.length === 0`, NEVER from
|
|
42
|
+
* `matchedPatterns.length`, or every skipped checklist claims the whole diff was in its scope.
|
|
43
|
+
*/
|
|
44
|
+
roster(defs: readonly ChecklistDefinition[], changedFiles: readonly string[]): TriggeredChecklist[];
|
|
19
45
|
detect(defs: readonly ChecklistDefinition[], changedFiles: readonly string[]): TriggeredChecklist[];
|
|
20
46
|
private firedPatterns;
|
|
21
47
|
detectForRepo(repoRoot: string, defs: readonly ChecklistDefinition[]): TriggeredChecklist[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ChecklistDetector = exports.TriggeredChecklist = void 0;
|
|
3
|
+
exports.ChecklistDetector = exports.ChecklistRoster = exports.TriggeredChecklist = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
6
6
|
const inversify_1 = require("inversify");
|
|
@@ -19,6 +19,26 @@ class TriggeredChecklist {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
exports.TriggeredChecklist = TriggeredChecklist;
|
|
22
|
+
/**
|
|
23
|
+
* Every DEFINED checklist paired with what it matched, plus the two facts a reader needs to judge a skip:
|
|
24
|
+
* how many files were even considered, and whether a diff was computable at all. Data-only (per CLAUDE.md).
|
|
25
|
+
*/
|
|
26
|
+
class ChecklistRoster {
|
|
27
|
+
entries; // one per defined checklist, in config order; matchedFiles may be []
|
|
28
|
+
changedFileCount; // N — "matched 0 of N changed files" is only meaningful with N
|
|
29
|
+
/**
|
|
30
|
+
* false when no fork point resolved. Load-bearing: with no base the changed-file set is EMPTY, so
|
|
31
|
+
* nothing matches — including patternless ALWAYS-RUNS checklists. Rendering that as "all skipped ✅"
|
|
32
|
+
* would post a green all-clear to GitHub for a PR where no checklist was actually evaluated.
|
|
33
|
+
*/
|
|
34
|
+
baseResolved;
|
|
35
|
+
constructor(entries, changedFileCount, baseResolved) {
|
|
36
|
+
this.entries = entries;
|
|
37
|
+
this.changedFileCount = changedFileCount;
|
|
38
|
+
this.baseResolved = baseResolved;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.ChecklistRoster = ChecklistRoster;
|
|
22
42
|
/**
|
|
23
43
|
* Decides which company review checklists a branch matched, from what the diff CHANGED. Path matching
|
|
24
44
|
* uses `isPathExcluded` — the SAME minimatch-based matcher rules-config already shares across
|
|
@@ -32,18 +52,30 @@ let ChecklistDetector = class ChecklistDetector {
|
|
|
32
52
|
constructor(diffScope) {
|
|
33
53
|
this.diffScope = diffScope;
|
|
34
54
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Pure matching over EVERY defined checklist, matched or not — one entry per def, in config order, with
|
|
57
|
+
* `matchedFiles: []` for the ones nothing hit. This is what lets the PR comment publish the full roster:
|
|
58
|
+
* a skipped checklist is the normal, healthy outcome, and a comment that lists only the ones that fired
|
|
59
|
+
* cannot distinguish "evaluated and irrelevant" from "never configured".
|
|
60
|
+
*
|
|
61
|
+
* A skipped entry has `matchedPatterns: []` — and so does a PATTERNLESS one. They mean opposite things,
|
|
62
|
+
* so a renderer must decide "always runs" from `def.patterns.length === 0`, NEVER from
|
|
63
|
+
* `matchedPatterns.length`, or every skipped checklist claims the whole diff was in its scope.
|
|
64
|
+
*/
|
|
65
|
+
roster(defs, changedFiles) {
|
|
66
|
+
return defs.map((def) => {
|
|
40
67
|
const matched = def.patterns.length === 0
|
|
41
68
|
? [...changedFiles]
|
|
42
69
|
: changedFiles.filter((f) => (0, rules_config_1.isPathExcluded)(f, def.patterns));
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
70
|
+
return new TriggeredChecklist(def, matched, this.firedPatterns(def, changedFiles));
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// Pure matching — unit-testable without git. Return every checklist whose patterns hit a changed file
|
|
74
|
+
// (empty patterns ⇒ always matches). Defined as the roster minus the empty entries so the set that GATES
|
|
75
|
+
// and the set the comment REPORTS can never be computed two different ways.
|
|
76
|
+
detect(defs, changedFiles) {
|
|
77
|
+
return this.roster(defs, changedFiles)
|
|
78
|
+
.filter((t) => t.matchedFiles.length > 0);
|
|
47
79
|
}
|
|
48
80
|
// Which of a checklist's globs hit at least one changed file. [] for a patternless checklist (it matches
|
|
49
81
|
// every PR, and saying "matched by nothing" would read as a bug).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checklist-detector.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/checklist-detector.ts"],"names":[],"mappings":";;;;AAAA,0DAEiC;AACjC,yCAA2D;AAE3D,sGAAsG;AACtG,iFAAiF;AACjF,MAAa,kBAAkB;IAC3B,GAAG,CAAsB;IACzB,YAAY,CAAW;IACvB,uGAAuG;IACvG,yGAAyG;IACzG,eAAe,CAAW;IAE1B,YAAY,GAAwB,EAAE,YAAsB,EAAE,kBAA4B,EAAE;QACxF,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;IAC3C,CAAC;CACJ;AAZD,gDAYC;AAED;;;;;;;GAOG;AAEI,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IACG;IAA7B,YAA6B,SAAoB;QAApB,cAAS,GAAT,SAAS,CAAW;IAAG,CAAC;IAErD
|
|
1
|
+
{"version":3,"file":"checklist-detector.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/checklist-detector.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ChecklistDefinition, RequiredChecklist, DiffScope, ChangedFilesOptions, isPathExcluded,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\n// A checklist whose `patterns` matched the diff (so its reviewer subagent must run), plus the matched\n// files (for the dashboard + hint). Data-only (per CLAUDE.md, classes for data).\nexport class TriggeredChecklist {\n def: ChecklistDefinition;\n matchedFiles: string[];\n // Which of `def.patterns` actually hit a changed file. Carried so the printed instruction can say WHAT\n // matched, not just which files: a reviewer judges a `db/migrations/**` hit differently from a `**` one.\n matchedPatterns: string[];\n\n constructor(def: ChecklistDefinition, matchedFiles: string[], matchedPatterns: string[] = []) {\n this.def = def;\n this.matchedFiles = matchedFiles;\n this.matchedPatterns = matchedPatterns;\n }\n}\n\n/**\n * Every DEFINED checklist paired with what it matched, plus the two facts a reader needs to judge a skip:\n * how many files were even considered, and whether a diff was computable at all. Data-only (per CLAUDE.md).\n */\nexport class ChecklistRoster {\n entries: TriggeredChecklist[]; // one per defined checklist, in config order; matchedFiles may be []\n changedFileCount: number; // N — \"matched 0 of N changed files\" is only meaningful with N\n /**\n * false when no fork point resolved. Load-bearing: with no base the changed-file set is EMPTY, so\n * nothing matches — including patternless ALWAYS-RUNS checklists. Rendering that as \"all skipped ✅\"\n * would post a green all-clear to GitHub for a PR where no checklist was actually evaluated.\n */\n baseResolved: boolean;\n\n constructor(entries: TriggeredChecklist[], changedFileCount: number, baseResolved: boolean) {\n this.entries = entries;\n this.changedFileCount = changedFileCount;\n this.baseResolved = baseResolved;\n }\n}\n\n/**\n * Decides which company review checklists a branch matched, from what the diff CHANGED. Path matching\n * uses `isPathExcluded` — the SAME minimatch-based matcher rules-config already shares across\n * exclude-paths and the rule validators. A checklist with empty `patterns` matches every PR (always runs).\n *\n * `@injectable(bindingScopeValues.Singleton)` + injects {@link DiffScope} so it appears in the DI design\n * and reuses the ONE git-diff service instead of adding new git plumbing.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ChecklistDetector {\n constructor(private readonly diffScope: DiffScope) {}\n\n /**\n * Pure matching over EVERY defined checklist, matched or not — one entry per def, in config order, with\n * `matchedFiles: []` for the ones nothing hit. This is what lets the PR comment publish the full roster:\n * a skipped checklist is the normal, healthy outcome, and a comment that lists only the ones that fired\n * cannot distinguish \"evaluated and irrelevant\" from \"never configured\".\n *\n * A skipped entry has `matchedPatterns: []` — and so does a PATTERNLESS one. They mean opposite things,\n * so a renderer must decide \"always runs\" from `def.patterns.length === 0`, NEVER from\n * `matchedPatterns.length`, or every skipped checklist claims the whole diff was in its scope.\n */\n roster(defs: readonly ChecklistDefinition[], changedFiles: readonly string[]): TriggeredChecklist[] {\n return defs.map((def: ChecklistDefinition): TriggeredChecklist => {\n const matched = def.patterns.length === 0\n ? [...changedFiles]\n : changedFiles.filter((f: string): boolean => isPathExcluded(f, def.patterns));\n return new TriggeredChecklist(def, matched, this.firedPatterns(def, changedFiles));\n });\n }\n\n // Pure matching — unit-testable without git. Return every checklist whose patterns hit a changed file\n // (empty patterns ⇒ always matches). Defined as the roster minus the empty entries so the set that GATES\n // and the set the comment REPORTS can never be computed two different ways.\n detect(defs: readonly ChecklistDefinition[], changedFiles: readonly string[]): TriggeredChecklist[] {\n return this.roster(defs, changedFiles)\n .filter((t: TriggeredChecklist): boolean => t.matchedFiles.length > 0);\n }\n\n // Which of a checklist's globs hit at least one changed file. [] for a patternless checklist (it matches\n // every PR, and saying \"matched by nothing\" would read as a bug).\n private firedPatterns(def: ChecklistDefinition, changedFiles: readonly string[]): string[] {\n return def.patterns.filter((p: string): boolean => changedFiles.some((f: string): boolean => isPathExcluded(f, [p])));\n }\n\n // Resolve the diff base for the local branch, then detect. Returns [] when there is no base to diff.\n detectForRepo(repoRoot: string, defs: readonly ChecklistDefinition[]): TriggeredChecklist[] {\n if (defs.length === 0) return [];\n const range = this.diffScope.resolveBase(repoRoot);\n if (!range.base) return [];\n return this.detectForRange(repoRoot, defs, range.base, range.head);\n }\n\n // Same detection against an EXPLICIT (base, head) — used by CI, where the useful range is the PR's\n // merge base .. head. tsOnly=false is load-bearing: the default drops every *.sql / Dockerfile / .env*\n // / metadata file a checklist most wants to key on, silently producing \"no checklists matched\".\n detectForRange(repoRoot: string, defs: readonly ChecklistDefinition[], base: string, head?: string): TriggeredChecklist[] {\n if (defs.length === 0 || !base) return [];\n const opts = new ChangedFilesOptions();\n opts.tsOnly = false;\n const changedFiles = this.diffScope.getChangedFiles(repoRoot, base, head, opts);\n return this.detect(defs, changedFiles);\n }\n\n // Flatten matched checklists into the RequiredChecklist shape that review.json enforcement + the hint\n // consume.\n toRequired(triggered: readonly TriggeredChecklist[]): RequiredChecklist[] {\n return triggered.map((t: TriggeredChecklist): RequiredChecklist =>\n new RequiredChecklist(t.def.id, t.def.subagent, t.def.doc, t.matchedFiles, t.matchedPatterns));\n }\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ChecklistDefinition, ChecklistReviewContext, DiffScope, RequiredChecklist, ReviewJsonService } from '@webpieces/rules-config';
|
|
2
2
|
import { AiBranchName } from './git-readAiBranchName';
|
|
3
|
-
import { ChecklistDetector } from './checklist-detector';
|
|
3
|
+
import { ChecklistDetector, ChecklistRoster } from './checklist-detector';
|
|
4
4
|
import { ForkPoint } from './git-findForkPoint';
|
|
5
5
|
import { PrContextWriter } from './pr-context-writer';
|
|
6
6
|
/** How a caller wants the scan filtered. Data-only (per CLAUDE.md). */
|
|
@@ -28,7 +28,9 @@ export declare class ChecklistScan {
|
|
|
28
28
|
context: ChecklistReviewContext;
|
|
29
29
|
reviewPath: string;
|
|
30
30
|
forkPoint: string;
|
|
31
|
-
|
|
31
|
+
roster: ChecklistRoster;
|
|
32
|
+
formatErrors: string[];
|
|
33
|
+
constructor(defined: ChecklistDefinition[], applicable: RequiredChecklist[], reviewed: RequiredChecklist[], outstanding: RequiredChecklist[], context: ChecklistReviewContext, reviewPath: string, forkPoint: string, roster: ChecklistRoster, formatErrors: string[]);
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
36
|
* The ONE computation of which reviewer subagents a branch owes, shared by `wp-checklist` (which lists) and
|
|
@@ -31,13 +31,21 @@ exports.ChecklistScanOptions = ChecklistScanOptions;
|
|
|
31
31
|
class ChecklistScan {
|
|
32
32
|
defined; // X
|
|
33
33
|
applicable; // N
|
|
34
|
-
reviewed; // N − Z: already have a passing/overridden review-<id>.json
|
|
34
|
+
reviewed; // N − Z: already have a passing/warned/overridden review-<id>.json
|
|
35
35
|
outstanding; // Z (== applicable when not filtering)
|
|
36
36
|
context; // fork-point sha + pr-context.json path
|
|
37
37
|
reviewPath; // the branch's review.json; verdict files sit beside it
|
|
38
38
|
forkPoint; // '' when no fork point resolved
|
|
39
|
+
// ALL X, matched or not, with why — what the PR comment publishes as its roster. Skipped checklists are
|
|
40
|
+
// absent from `applicable` by construction, and recovering them downstream would mean a second
|
|
41
|
+
// changed-file computation with different semantics (see the class comment).
|
|
42
|
+
roster;
|
|
43
|
+
// Verdict files that exist but cannot be read as a verdict (e.g. still using the removed `success`).
|
|
44
|
+
// Carried on the SCAN because wp-finish-upsert-pr refuses on missing reviewers before it ever parses
|
|
45
|
+
// review.json — a complaint raised only in there would never reach the AI.
|
|
46
|
+
formatErrors;
|
|
39
47
|
// eslint-disable-next-line @typescript-eslint/max-params
|
|
40
|
-
constructor(defined, applicable, reviewed, outstanding, context, reviewPath, forkPoint) {
|
|
48
|
+
constructor(defined, applicable, reviewed, outstanding, context, reviewPath, forkPoint, roster, formatErrors) {
|
|
41
49
|
this.defined = defined;
|
|
42
50
|
this.applicable = applicable;
|
|
43
51
|
this.reviewed = reviewed;
|
|
@@ -45,6 +53,8 @@ class ChecklistScan {
|
|
|
45
53
|
this.context = context;
|
|
46
54
|
this.reviewPath = reviewPath;
|
|
47
55
|
this.forkPoint = forkPoint;
|
|
56
|
+
this.roster = roster;
|
|
57
|
+
this.formatErrors = formatErrors;
|
|
48
58
|
}
|
|
49
59
|
}
|
|
50
60
|
exports.ChecklistScan = ChecklistScan;
|
|
@@ -91,12 +101,16 @@ let ChecklistScanner = class ChecklistScanner {
|
|
|
91
101
|
const featureName = this.aiBranchName.getFeatureName();
|
|
92
102
|
const reviewPath = (0, rules_config_1.reviewJsonPath)(repoRoot, featureName);
|
|
93
103
|
const base = this.forkPoint.resolveForkPoint(repoRoot);
|
|
94
|
-
|
|
104
|
+
// ONE changed-file computation feeds both the roster (all X) and the applicable set (N). `detect` is
|
|
105
|
+
// pure and defined as the roster minus its empty entries, so the two cannot disagree about a match.
|
|
106
|
+
const changedFiles = this.changedFiles(repoRoot, base);
|
|
107
|
+
const roster = new checklist_detector_1.ChecklistRoster(this.checklistDetector.roster(defined, changedFiles), changedFiles.length, base !== '');
|
|
108
|
+
const applicable = this.checklistDetector.toRequired(this.checklistDetector.detect(defined, changedFiles));
|
|
95
109
|
const results = this.reviewJsonService.loadChecklistResults(reviewPath, applicable);
|
|
96
110
|
const stillOwed = this.reviewJsonService.pendingChecklists(applicable, results);
|
|
97
111
|
const owedIds = new Set(stillOwed.map((r) => r.id));
|
|
98
112
|
const reviewed = applicable.filter((r) => !owedIds.has(r.id));
|
|
99
|
-
return new ChecklistScan(defined, applicable, reviewed, opts.filterAlreadyReviewed ? stillOwed : applicable, this.prContextWriter.ensure(repoRoot, featureName, base), reviewPath, base);
|
|
113
|
+
return new ChecklistScan(defined, applicable, reviewed, opts.filterAlreadyReviewed ? stillOwed : applicable, this.prContextWriter.ensure(repoRoot, featureName, base), reviewPath, base, roster, this.reviewJsonService.checklistFormatErrors(applicable, results));
|
|
100
114
|
}
|
|
101
115
|
/**
|
|
102
116
|
* Every file changed since the fork point, INCLUDING uncommitted and untracked ones. `tsOnly:false` is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checklist-scanner.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/checklist-scanner.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangedFilesOptions, ChecklistDefinition, ChecklistReviewContext, DiffScope, RequiredChecklist,\n ReviewJsonService, reviewJsonPath,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from './git-readAiBranchName';\nimport { ChecklistDetector } from './checklist-detector';\nimport { ForkPoint } from './git-findForkPoint';\nimport { PrContextWriter } from './pr-context-writer';\n\n/** How a caller wants the scan filtered. Data-only (per CLAUDE.md). */\nexport class ChecklistScanOptions {\n /**\n * false — `outstanding` is every applicable checklist (what `wp-checklist` LISTS).\n * true — `outstanding` drops the ones already carrying a passing/overridden verdict, leaving only what\n * still owes review (what `wp-finish-upsert-pr` BLOCKS on).\n */\n filterAlreadyReviewed: boolean;\n\n constructor(filterAlreadyReviewed = false) {\n this.filterAlreadyReviewed = filterAlreadyReviewed;\n }\n}\n\n/**\n * The answer to \"what review does this branch owe?\", in the X → N → Z terms the commands report:\n * X = `defined` — every checklist in pr-gate.checklists\n * N = `applicable` — those whose patterns matched (or that have no patterns, so always run)\n * Z = `outstanding` — of N, those still owing a verdict (only when filterAlreadyReviewed)\n * Data-only.\n */\nexport class ChecklistScan {\n defined: ChecklistDefinition[]; // X\n applicable: RequiredChecklist[]; // N\n reviewed: RequiredChecklist[]; // N − Z: already have a passing/overridden review-<id>.json\n outstanding: RequiredChecklist[]; // Z (== applicable when not filtering)\n context: ChecklistReviewContext; // fork-point sha + pr-context.json path\n reviewPath: string; // the branch's review.json; verdict files sit beside it\n forkPoint: string; // '' when no fork point resolved\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n defined: ChecklistDefinition[],\n applicable: RequiredChecklist[],\n reviewed: RequiredChecklist[],\n outstanding: RequiredChecklist[],\n context: ChecklistReviewContext,\n reviewPath: string,\n forkPoint: string,\n ) {\n this.defined = defined;\n this.applicable = applicable;\n this.reviewed = reviewed;\n this.outstanding = outstanding;\n this.context = context;\n this.reviewPath = reviewPath;\n this.forkPoint = forkPoint;\n }\n}\n\n/**\n * The ONE computation of which reviewer subagents a branch owes, shared by `wp-checklist` (which lists) and\n * `wp-finish-upsert-pr` (which blocks). They previously each assembled this from the same parts in slightly\n * different ways, and any divergence means the command that reports and the command that gates disagree.\n *\n * Two properties are deliberate and load-bearing:\n *\n * 1. **The base is the FORK POINT of main, computed directly** — never `DiffScope.resolveBase`, which\n * overlays `NX_BASE`/`NX_HEAD` from the environment. That made review coverage depend on an env var.\n * 2. **UNCOMMITTED work counts.** `getChangedFiles` is called with NO head, which is the branch of it that\n * diffs base → WORKING TREE and unions in untracked files. Passing a head would diff commit-to-commit and\n * silently miss staged, unstaged and untracked changes — so a checklist matching only uncommitted files\n * would never fire and its reviewer would never be listed. `wp-checklist` is explicitly callable\n * mid-work, which is exactly when that matters.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ChecklistScanner {\n constructor(\n private readonly aiBranchName: AiBranchName,\n private readonly checklistDetector: ChecklistDetector,\n private readonly diffScope: DiffScope,\n private readonly forkPoint: ForkPoint,\n private readonly prContextWriter: PrContextWriter,\n private readonly reviewJsonService: ReviewJsonService,\n ) {}\n\n /**\n * `defined` is the caller's ALREADY-VALIDATED `prGate.checklists`. The scanner deliberately does not load\n * config itself: `loadAndValidate` is the one gate on the checklist set (it rejects a non-array\n * `checklists`, including the removed `{ doc }` manifest shape, and verifies every guidance doc and\n * reviewer-agent file exists), both callers already run it for other fields, and keeping the read out of\n * here leaves this a function of its inputs rather than of the filesystem.\n */\n scan(repoRoot: string, defined: ChecklistDefinition[], opts: ChecklistScanOptions): ChecklistScan {\n const featureName = this.aiBranchName.getFeatureName();\n const reviewPath = reviewJsonPath(repoRoot, featureName);\n const base = this.forkPoint.resolveForkPoint(repoRoot);\n const applicable = this.checklistDetector.toRequired(\n this.checklistDetector.detect(defined, this.changedFiles(repoRoot, base)),\n );\n const results = this.reviewJsonService.loadChecklistResults(reviewPath, applicable);\n const stillOwed = this.reviewJsonService.pendingChecklists(applicable, results);\n const owedIds = new Set(stillOwed.map((r: RequiredChecklist): string => r.id));\n const reviewed = applicable.filter((r: RequiredChecklist): boolean => !owedIds.has(r.id));\n return new ChecklistScan(\n defined,\n applicable,\n reviewed,\n opts.filterAlreadyReviewed ? stillOwed : applicable,\n this.prContextWriter.ensure(repoRoot, featureName, base),\n reviewPath,\n base,\n );\n }\n\n /**\n * Every file changed since the fork point, INCLUDING uncommitted and untracked ones. `tsOnly:false` is\n * load-bearing too: the default drops every *.sql / Dockerfile / .env* file a checklist most wants to\n * key on, which would silently shrink the set a reviewer is pointed at.\n */\n private changedFiles(repoRoot: string, base: string): string[] {\n if (base === '') return [];\n const opts = new ChangedFilesOptions();\n opts.tsOnly = false;\n // No head argument — see the class comment. This is what includes the working tree.\n return this.diffScope.getChangedFiles(repoRoot, base, undefined, opts);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"checklist-scanner.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/checklist-scanner.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangedFilesOptions, ChecklistDefinition, ChecklistReviewContext, DiffScope, RequiredChecklist,\n ReviewJsonService, reviewJsonPath,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from './git-readAiBranchName';\nimport { ChecklistDetector, ChecklistRoster } from './checklist-detector';\nimport { ForkPoint } from './git-findForkPoint';\nimport { PrContextWriter } from './pr-context-writer';\n\n/** How a caller wants the scan filtered. Data-only (per CLAUDE.md). */\nexport class ChecklistScanOptions {\n /**\n * false — `outstanding` is every applicable checklist (what `wp-checklist` LISTS).\n * true — `outstanding` drops the ones already carrying a passing/overridden verdict, leaving only what\n * still owes review (what `wp-finish-upsert-pr` BLOCKS on).\n */\n filterAlreadyReviewed: boolean;\n\n constructor(filterAlreadyReviewed = false) {\n this.filterAlreadyReviewed = filterAlreadyReviewed;\n }\n}\n\n/**\n * The answer to \"what review does this branch owe?\", in the X → N → Z terms the commands report:\n * X = `defined` — every checklist in pr-gate.checklists\n * N = `applicable` — those whose patterns matched (or that have no patterns, so always run)\n * Z = `outstanding` — of N, those still owing a verdict (only when filterAlreadyReviewed)\n * Data-only.\n */\nexport class ChecklistScan {\n defined: ChecklistDefinition[]; // X\n applicable: RequiredChecklist[]; // N\n reviewed: RequiredChecklist[]; // N − Z: already have a passing/warned/overridden review-<id>.json\n outstanding: RequiredChecklist[]; // Z (== applicable when not filtering)\n context: ChecklistReviewContext; // fork-point sha + pr-context.json path\n reviewPath: string; // the branch's review.json; verdict files sit beside it\n forkPoint: string; // '' when no fork point resolved\n // ALL X, matched or not, with why — what the PR comment publishes as its roster. Skipped checklists are\n // absent from `applicable` by construction, and recovering them downstream would mean a second\n // changed-file computation with different semantics (see the class comment).\n roster: ChecklistRoster;\n // Verdict files that exist but cannot be read as a verdict (e.g. still using the removed `success`).\n // Carried on the SCAN because wp-finish-upsert-pr refuses on missing reviewers before it ever parses\n // review.json — a complaint raised only in there would never reach the AI.\n formatErrors: string[];\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n defined: ChecklistDefinition[],\n applicable: RequiredChecklist[],\n reviewed: RequiredChecklist[],\n outstanding: RequiredChecklist[],\n context: ChecklistReviewContext,\n reviewPath: string,\n forkPoint: string,\n roster: ChecklistRoster,\n formatErrors: string[],\n ) {\n this.defined = defined;\n this.applicable = applicable;\n this.reviewed = reviewed;\n this.outstanding = outstanding;\n this.context = context;\n this.reviewPath = reviewPath;\n this.forkPoint = forkPoint;\n this.roster = roster;\n this.formatErrors = formatErrors;\n }\n}\n\n/**\n * The ONE computation of which reviewer subagents a branch owes, shared by `wp-checklist` (which lists) and\n * `wp-finish-upsert-pr` (which blocks). They previously each assembled this from the same parts in slightly\n * different ways, and any divergence means the command that reports and the command that gates disagree.\n *\n * Two properties are deliberate and load-bearing:\n *\n * 1. **The base is the FORK POINT of main, computed directly** — never `DiffScope.resolveBase`, which\n * overlays `NX_BASE`/`NX_HEAD` from the environment. That made review coverage depend on an env var.\n * 2. **UNCOMMITTED work counts.** `getChangedFiles` is called with NO head, which is the branch of it that\n * diffs base → WORKING TREE and unions in untracked files. Passing a head would diff commit-to-commit and\n * silently miss staged, unstaged and untracked changes — so a checklist matching only uncommitted files\n * would never fire and its reviewer would never be listed. `wp-checklist` is explicitly callable\n * mid-work, which is exactly when that matters.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ChecklistScanner {\n constructor(\n private readonly aiBranchName: AiBranchName,\n private readonly checklistDetector: ChecklistDetector,\n private readonly diffScope: DiffScope,\n private readonly forkPoint: ForkPoint,\n private readonly prContextWriter: PrContextWriter,\n private readonly reviewJsonService: ReviewJsonService,\n ) {}\n\n /**\n * `defined` is the caller's ALREADY-VALIDATED `prGate.checklists`. The scanner deliberately does not load\n * config itself: `loadAndValidate` is the one gate on the checklist set (it rejects a non-array\n * `checklists`, including the removed `{ doc }` manifest shape, and verifies every guidance doc and\n * reviewer-agent file exists), both callers already run it for other fields, and keeping the read out of\n * here leaves this a function of its inputs rather than of the filesystem.\n */\n scan(repoRoot: string, defined: ChecklistDefinition[], opts: ChecklistScanOptions): ChecklistScan {\n const featureName = this.aiBranchName.getFeatureName();\n const reviewPath = reviewJsonPath(repoRoot, featureName);\n const base = this.forkPoint.resolveForkPoint(repoRoot);\n // ONE changed-file computation feeds both the roster (all X) and the applicable set (N). `detect` is\n // pure and defined as the roster minus its empty entries, so the two cannot disagree about a match.\n const changedFiles = this.changedFiles(repoRoot, base);\n const roster = new ChecklistRoster(\n this.checklistDetector.roster(defined, changedFiles), changedFiles.length, base !== '');\n const applicable = this.checklistDetector.toRequired(this.checklistDetector.detect(defined, changedFiles));\n const results = this.reviewJsonService.loadChecklistResults(reviewPath, applicable);\n const stillOwed = this.reviewJsonService.pendingChecklists(applicable, results);\n const owedIds = new Set(stillOwed.map((r: RequiredChecklist): string => r.id));\n const reviewed = applicable.filter((r: RequiredChecklist): boolean => !owedIds.has(r.id));\n return new ChecklistScan(\n defined,\n applicable,\n reviewed,\n opts.filterAlreadyReviewed ? stillOwed : applicable,\n this.prContextWriter.ensure(repoRoot, featureName, base),\n reviewPath,\n base,\n roster,\n this.reviewJsonService.checklistFormatErrors(applicable, results),\n );\n }\n\n /**\n * Every file changed since the fork point, INCLUDING uncommitted and untracked ones. `tsOnly:false` is\n * load-bearing too: the default drops every *.sql / Dockerfile / .env* file a checklist most wants to\n * key on, which would silently shrink the set a reviewer is pointed at.\n */\n private changedFiles(repoRoot: string, base: string): string[] {\n if (base === '') return [];\n const opts = new ChangedFilesOptions();\n opts.tsOnly = false;\n // No head argument — see the class comment. This is what includes the working tree.\n return this.diffScope.getChangedFiles(repoRoot, base, undefined, opts);\n }\n}\n"]}
|