@webpieces/pr-gate 0.4.742 → 0.4.744
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 +3 -3
- package/src/dashboard/checklist-comment-renderer.d.ts +25 -1
- package/src/dashboard/checklist-comment-renderer.js +53 -1
- package/src/dashboard/checklist-comment-renderer.js.map +1 -1
- package/src/dashboard/dashboard.d.ts +32 -1
- package/src/dashboard/dashboard.js +57 -3
- package/src/dashboard/dashboard.js.map +1 -1
- package/src/scripts/commands/finish-upsert-pr-command.js +7 -4
- package/src/scripts/commands/finish-upsert-pr-command.js.map +1 -1
- package/src/scripts/commands/review-upsert-pr-command.js +6 -0
- package/src/scripts/commands/review-upsert-pr-command.js.map +1 -1
- package/src/scripts/workflow/checklist-scanner.d.ts +31 -3
- package/src/scripts/workflow/checklist-scanner.js +59 -7
- package/src/scripts/workflow/checklist-scanner.js.map +1 -1
- package/src/scripts/workflow/review-report.d.ts +30 -0
- package/src/scripts/workflow/review-report.js +55 -0
- package/src/scripts/workflow/review-report.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.744",
|
|
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",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@inversifyjs/binding-decorators": "1.1.5",
|
|
19
|
-
"@webpieces/ai-hook-rules": "0.4.
|
|
20
|
-
"@webpieces/rules-config": "0.4.
|
|
19
|
+
"@webpieces/ai-hook-rules": "0.4.744",
|
|
20
|
+
"@webpieces/rules-config": "0.4.744",
|
|
21
21
|
"inversify": "7.10.4",
|
|
22
22
|
"reflect-metadata": "0.2.2"
|
|
23
23
|
},
|
|
@@ -32,7 +32,31 @@ export declare class ChecklistCommentRenderer {
|
|
|
32
32
|
*
|
|
33
33
|
* Idempotent: keyed by the hidden marker so wp-finish PATCHes this same comment on every push.
|
|
34
34
|
*/
|
|
35
|
-
render(rows: readonly ChecklistCommentRow[], provenanceVerified: boolean, baseResolved: boolean): string;
|
|
35
|
+
render(rows: readonly ChecklistCommentRow[], provenanceVerified: boolean, baseResolved: boolean, suppressedCount: number): string;
|
|
36
|
+
/**
|
|
37
|
+
* The whole comment when `experimental.turnOffAllReviewers` killed every reviewer.
|
|
38
|
+
*
|
|
39
|
+
* It REPLACES the ordinary body rather than adding a line to it, because three things in there would
|
|
40
|
+
* each be false here and none of them is fixable in place: the roll-up would count matched checklists
|
|
41
|
+
* as "skipped ✅", the provenance line would attest that each reviewer ran as its own verified
|
|
42
|
+
* subagent, and the closing note would say every checklist was evaluated and none applied. Under
|
|
43
|
+
* suppression they DID apply, nobody looked, and this comment is one of only three places that says so.
|
|
44
|
+
*
|
|
45
|
+
* It keeps the full roster — every checklist unchecked, with which ones were REQUIRED — because the
|
|
46
|
+
* count alone cannot answer "which required reviewer did this PR skip?".
|
|
47
|
+
*/
|
|
48
|
+
private suppressedComment;
|
|
49
|
+
/**
|
|
50
|
+
* One roster line under suppression. ALWAYS an unchecked box — never `rosterBullet`, whose box is
|
|
51
|
+
* `reviewerRan(row)`, which is true for any REQUIRED checklist that applied. Under suppression that
|
|
52
|
+
* would tick every required reviewer's box on a PR none of them looked at, which is the precise
|
|
53
|
+
* inversion this whole feature has to avoid.
|
|
54
|
+
*
|
|
55
|
+
* `row.ran` here means "this checklist APPLIED", so it is what separates a SUPPRESSED entry from one
|
|
56
|
+
* that genuinely did not match. Both stay listed: the second is the good news the roster exists to
|
|
57
|
+
* deliver, and dropping either would make the reader recompute the diff to tell them apart.
|
|
58
|
+
*/
|
|
59
|
+
private suppressedBullet;
|
|
36
60
|
/**
|
|
37
61
|
* The closing note when no reviewer produced a verdict. The original wording — "every configured
|
|
38
62
|
* checklist was evaluated and none of them applied" — is an all-clear, and it becomes FALSE the moment a
|
|
@@ -48,7 +48,15 @@ let ChecklistCommentRenderer = class ChecklistCommentRenderer {
|
|
|
48
48
|
*
|
|
49
49
|
* Idempotent: keyed by the hidden marker so wp-finish PATCHes this same comment on every push.
|
|
50
50
|
*/
|
|
51
|
-
render(rows, provenanceVerified, baseResolved
|
|
51
|
+
render(rows, provenanceVerified, baseResolved,
|
|
52
|
+
// How many checklists `experimental.turnOffAllReviewers` killed. REQUIRED, no default: this comment
|
|
53
|
+
// renders a roster of checkboxes and a provenance ATTESTATION, and under suppression both are
|
|
54
|
+
// false-by-omission — the boxes are unchecked because nothing ran, and "each reviewer ran as its own
|
|
55
|
+
// independent subagent, verified" would be an outright untrue sentence under a PR nobody reviewed.
|
|
56
|
+
// A defaulted 0 would let every existing caller keep printing it. See HOME_KEY_TURN_OFF_ALL_REVIEWERS.
|
|
57
|
+
suppressedCount) {
|
|
58
|
+
if (suppressedCount > 0)
|
|
59
|
+
return this.suppressedComment(rows, suppressedCount);
|
|
52
60
|
const ran = this.ranOrdered(rows);
|
|
53
61
|
const prov = provenanceVerified
|
|
54
62
|
? '_Each reviewer ran as its own independent subagent, verified from the Claude Code harness._'
|
|
@@ -68,6 +76,50 @@ let ChecklistCommentRenderer = class ChecklistCommentRenderer {
|
|
|
68
76
|
}
|
|
69
77
|
return this.fitComment(`${header}\n\n### Reviews that ran`, ran.map((r) => this.commentSection(r)));
|
|
70
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* The whole comment when `experimental.turnOffAllReviewers` killed every reviewer.
|
|
81
|
+
*
|
|
82
|
+
* It REPLACES the ordinary body rather than adding a line to it, because three things in there would
|
|
83
|
+
* each be false here and none of them is fixable in place: the roll-up would count matched checklists
|
|
84
|
+
* as "skipped ✅", the provenance line would attest that each reviewer ran as its own verified
|
|
85
|
+
* subagent, and the closing note would say every checklist was evaluated and none applied. Under
|
|
86
|
+
* suppression they DID apply, nobody looked, and this comment is one of only three places that says so.
|
|
87
|
+
*
|
|
88
|
+
* It keeps the full roster — every checklist unchecked, with which ones were REQUIRED — because the
|
|
89
|
+
* count alone cannot answer "which required reviewer did this PR skip?".
|
|
90
|
+
*/
|
|
91
|
+
suppressedComment(rows, suppressedCount) {
|
|
92
|
+
const required = rows.filter((r) => r.required && r.ran);
|
|
93
|
+
const lines = [
|
|
94
|
+
exports.CHECKLIST_COMMENT_MARKER,
|
|
95
|
+
`## 🔍 Company review checklists — ⚫ ALL SUPPRESSED (${suppressedCount} of ${rows.length} would have run)`,
|
|
96
|
+
`_No reviewer subagent ran on this PR. Switched off by \`experimental.${rules_config_1.HOME_KEY_TURN_OFF_ALL_REVIEWERS}: true\` `
|
|
97
|
+
+ `in the machine-local \`~/${rules_config_1.HOME_CONFIG_DIR}/${rules_config_1.HOME_CONFIG_FILE}\` — a MACHINE setting, not a repo one, so `
|
|
98
|
+
+ `nothing in this branch could have caused it. **This is not an all-clear**, and `
|
|
99
|
+
+ `${required.length} of the suppressed checklist(s) were REQUIRED._`,
|
|
100
|
+
'',
|
|
101
|
+
`### Checklists (all ${rows.length})`,
|
|
102
|
+
];
|
|
103
|
+
for (const row of rows)
|
|
104
|
+
lines.push(this.suppressedBullet(row));
|
|
105
|
+
return lines.join('\n');
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* One roster line under suppression. ALWAYS an unchecked box — never `rosterBullet`, whose box is
|
|
109
|
+
* `reviewerRan(row)`, which is true for any REQUIRED checklist that applied. Under suppression that
|
|
110
|
+
* would tick every required reviewer's box on a PR none of them looked at, which is the precise
|
|
111
|
+
* inversion this whole feature has to avoid.
|
|
112
|
+
*
|
|
113
|
+
* `row.ran` here means "this checklist APPLIED", so it is what separates a SUPPRESSED entry from one
|
|
114
|
+
* that genuinely did not match. Both stay listed: the second is the good news the roster exists to
|
|
115
|
+
* deliver, and dropping either would make the reader recompute the diff to tell them apart.
|
|
116
|
+
*/
|
|
117
|
+
suppressedBullet(row) {
|
|
118
|
+
const state = row.ran
|
|
119
|
+
? `⚫ SUPPRESSED${row.required ? ' (REQUIRED)' : ' (optional)'} — no reviewer ran`
|
|
120
|
+
: '⚪ did not apply to this diff';
|
|
121
|
+
return `- [ ] **${row.subagent}** — ${state}\n - ${this.whyLine(row)}`;
|
|
122
|
+
}
|
|
71
123
|
/**
|
|
72
124
|
* The closing note when no reviewer produced a verdict. The original wording — "every configured
|
|
73
125
|
* checklist was evaluated and none of them applied" — is an all-clear, and it becomes FALSE the moment a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checklist-comment-renderer.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/dashboard/checklist-comment-renderer.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n formatFileList, CK_PASS, CK_WARN, CK_OVERRIDDEN, CK_FAIL, CK_MISSING,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { ChecklistCommentRow } from './checklist-comment-row';\n\n/**\n * Hidden marker on the checklist review COMMENT — the \"2nd comment\" the PR description points at — so\n * wp-finish can find and PATCH its own comment on every push instead of appending a new one. Versioned so\n * the format can evolve without matching an old shape. v2 = the full roster (every DEFINED checklist,\n * matched or not) + tri-state verdicts.\n */\nexport const CHECKLIST_COMMENT_MARKER = '<!-- webpieces-checklists v2 -->';\n\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\n/**\n * Renders the 2nd PR comment: the reviewer checklist — full roster plus each reviewer's verbatim output.\n *\n * Split out of `Dashboard` when the PR-description/commit-body swap pushed that file over the 700-line\n * cap, and the seam was already there to be found: this is ONE of the three surfaces the gated flow\n * writes, it shares no rendering helper with the other two (it speaks `ChecklistCommentRow`, where the\n * dashboard speaks `ChecklistRow`), and it owns the only size-fitting logic in the package. One class per\n * surface is the shape the rest of this change assumes — see `Dashboard.renderPrBody` and\n * `Dashboard.renderDetailComment`.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is drawn in the DI design and injected by type.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ChecklistCommentRenderer {\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 render(\n rows: readonly ChecklistCommentRow[],\n provenanceVerified: boolean,\n baseResolved: boolean,\n ): 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${this.nothingRanNote(rows)}`;\n }\n return this.fitComment(\n `${header}\\n\\n### Reviews that ran`,\n ran.map((r: ChecklistCommentRow): CommentSection => this.commentSection(r)),\n );\n }\n\n /**\n * The closing note when no reviewer produced a verdict. The original wording — \"every configured\n * checklist was evaluated and none of them applied\" — is an all-clear, and it becomes FALSE the moment a\n * checklist did apply and was declined. That sentence under a PR nobody reviewed is precisely the\n * misreport this feature could otherwise introduce, so the declined case gets its own words.\n */\n private nothingRanNote(rows: readonly ChecklistCommentRow[]): string {\n const declined = rows.filter((r: ChecklistCommentRow): boolean => this.declined(r));\n if (declined.length === 0) {\n return '_No reviewer had to run on this diff — every configured checklist was evaluated and none of them applied._';\n }\n return (\n `_No reviewer ran. ${declined.length} OPTIONAL checklist(s) DID apply to this diff and were not ` +\n `run; the rest were evaluated and did not apply._`\n );\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 (\n `## 🔍 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 }\n const ran = rows.filter((r: ChecklistCommentRow): boolean => this.reviewerRan(r));\n const declined = rows.filter((r: ChecklistCommentRow): boolean => this.declined(r));\n const skipped = rows.length - ran.length - declined.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 // Counted SEPARATELY from \"skipped\", and without a ✅. A declined optional review is a legitimate\n // outcome, but it is not the same good news as a checklist that had nothing to look at — folding the\n // two together would let a PR that declined every optional review read as fully covered.\n const notRun = declined.length > 0 ? ` · ${declined.length} optional not run` : '';\n return `## 🔍 Company review checklists — ${rows.length} defined · ${ran.length} ran${breakdown}${skip}${notRun}`;\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 = this.reviewerRan(row) ? '- [x]' : '- [ ]';\n return (\n `${box} ${this.verdictEmoji(row)} **${row.subagent}**${this.optionalTag(row)} — ` +\n `${this.verdictWords(row)}${this.evidenceSuffix(row)}\\n` +\n ` - ${this.whyLine(row)}`\n );\n }\n\n /**\n * Did a reviewer actually produce a verdict for this row?\n *\n * `row.ran` is really \"this checklist APPLIED to the diff\" — for a required checklist the two are the\n * same thing, because the PR cannot open otherwise, and the field was named before optional checklists\n * existed. For a DECLINED optional one they diverge, and using `ran` alone would put a checked box and a\n * reviewer section on a review that nobody performed.\n */\n private reviewerRan(row: ChecklistCommentRow): boolean {\n return row.ran && !this.declined(row);\n }\n\n // Applied, optional, and carrying no verdict — i.e. the human was offered this review and said no (or\n // `--no-optional` skipped the offer). Never true of a required checklist: one of those with no verdict\n // does not reach a PR at all.\n private declined(row: ChecklistCommentRow): boolean {\n return row.ran && !row.required && (row.status === CK_MISSING || row.status === '');\n }\n\n // Marks which rows the human could have declined. Without it a reader cannot tell a review that was\n // skippable from one that simply passed, and so cannot judge how much this PR was actually reviewed.\n private optionalTag(row: ChecklistCommentRow): string {\n return row.required ? '' : ' _(optional)_';\n }\n\n /**\n * Whether the reviewer demonstrably opened the diff, read from its own transcript. A QUALITY signal and\n * never a blocker (see SubagentProvenanceService.evidenceFor) — but published, because \"wrote a verdict\n * without reading the change\" is exactly what a reader of this comment would want to weigh.\n */\n private evidenceSuffix(row: ChecklistCommentRow): string {\n if (!this.reviewerRan(row) || row.diffRead === '') return '';\n return row.diffRead === 'yes' ? ' _(diff read ✓)_' : ' _(⚠️ no diff read recorded)_';\n }\n\n private verdictEmoji(row: ChecklistCommentRow): string {\n if (!this.reviewerRan(row)) 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 // Two different unchecked boxes, two different sentences. \"Not applicable\" is the diff's doing;\n // \"not run\" is a person's, and reporting the second as the first would quietly credit a review that\n // a human deliberately declined.\n if (this.declined(row)) return 'OPTIONAL — applied to this diff but was NOT run (not selected)';\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 // Names the FILE the authorization lives in, so a reader of the PR can go and check the provenance\n // rather than take \"someone approved this\" on trust. The reason, who authorized it and when are\n // rendered verbatim in this row's section below.\n if (row.status === CK_OVERRIDDEN) {\n return `OVERRIDDEN — a human authorized shipping it (recorded in override-${row.subagent}.json)`;\n }\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 // State the fact, not a suspicion. Patternless is a deliberate configuration — an always-runs\n // gate (every PR names a ticket, every PR has an owner) is exactly what it is FOR — so telling\n // every such row to \"add `patterns` if that is not intended\" nags the repos that meant it, on\n // every PR, forever. A reader who wants to know whether it was intended can read the config.\n return (\n `ALWAYS RUNS (no patterns) — whole diff in scope, ${total} changed file(s): ` +\n `${formatFileList(row.matchedFiles)}`\n );\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 (\n `matched ${this.asCode(row.firedPatterns)} → ${row.matchedFiles.length} of ${total} ` +\n `changed file(s): ${formatFileList(row.matchedFiles)}`\n );\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 => this.reviewerRan(r))\n .slice()\n .sort(\n (a: ChecklistCommentRow, b: ChecklistCommentRow): number =>\n this.rankOf(rank, a.status) - this.rankOf(rank, b.status),\n );\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 =\n 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 =>\n `${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) {\n max = s.body.length;\n idx = i;\n }\n });\n return idx;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"checklist-comment-renderer.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/dashboard/checklist-comment-renderer.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n formatFileList, CK_PASS, CK_WARN, CK_OVERRIDDEN, CK_FAIL, CK_MISSING,\n HOME_CONFIG_DIR, HOME_CONFIG_FILE, HOME_KEY_TURN_OFF_ALL_REVIEWERS,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { ChecklistCommentRow } from './checklist-comment-row';\n\n/**\n * Hidden marker on the checklist review COMMENT — the \"2nd comment\" the PR description points at — so\n * wp-finish can find and PATCH its own comment on every push instead of appending a new one. Versioned so\n * the format can evolve without matching an old shape. v2 = the full roster (every DEFINED checklist,\n * matched or not) + tri-state verdicts.\n */\nexport const CHECKLIST_COMMENT_MARKER = '<!-- webpieces-checklists v2 -->';\n\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\n/**\n * Renders the 2nd PR comment: the reviewer checklist — full roster plus each reviewer's verbatim output.\n *\n * Split out of `Dashboard` when the PR-description/commit-body swap pushed that file over the 700-line\n * cap, and the seam was already there to be found: this is ONE of the three surfaces the gated flow\n * writes, it shares no rendering helper with the other two (it speaks `ChecklistCommentRow`, where the\n * dashboard speaks `ChecklistRow`), and it owns the only size-fitting logic in the package. One class per\n * surface is the shape the rest of this change assumes — see `Dashboard.renderPrBody` and\n * `Dashboard.renderDetailComment`.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is drawn in the DI design and injected by type.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ChecklistCommentRenderer {\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 render(\n rows: readonly ChecklistCommentRow[],\n provenanceVerified: boolean,\n baseResolved: boolean,\n // How many checklists `experimental.turnOffAllReviewers` killed. REQUIRED, no default: this comment\n // renders a roster of checkboxes and a provenance ATTESTATION, and under suppression both are\n // false-by-omission — the boxes are unchecked because nothing ran, and \"each reviewer ran as its own\n // independent subagent, verified\" would be an outright untrue sentence under a PR nobody reviewed.\n // A defaulted 0 would let every existing caller keep printing it. See HOME_KEY_TURN_OFF_ALL_REVIEWERS.\n suppressedCount: number,\n ): string {\n if (suppressedCount > 0) return this.suppressedComment(rows, suppressedCount);\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${this.nothingRanNote(rows)}`;\n }\n return this.fitComment(\n `${header}\\n\\n### Reviews that ran`,\n ran.map((r: ChecklistCommentRow): CommentSection => this.commentSection(r)),\n );\n }\n\n /**\n * The whole comment when `experimental.turnOffAllReviewers` killed every reviewer.\n *\n * It REPLACES the ordinary body rather than adding a line to it, because three things in there would\n * each be false here and none of them is fixable in place: the roll-up would count matched checklists\n * as \"skipped ✅\", the provenance line would attest that each reviewer ran as its own verified\n * subagent, and the closing note would say every checklist was evaluated and none applied. Under\n * suppression they DID apply, nobody looked, and this comment is one of only three places that says so.\n *\n * It keeps the full roster — every checklist unchecked, with which ones were REQUIRED — because the\n * count alone cannot answer \"which required reviewer did this PR skip?\".\n */\n private suppressedComment(rows: readonly ChecklistCommentRow[], suppressedCount: number): string {\n const required = rows.filter((r: ChecklistCommentRow): boolean => r.required && r.ran);\n const lines: string[] = [\n CHECKLIST_COMMENT_MARKER,\n `## 🔍 Company review checklists — ⚫ ALL SUPPRESSED (${suppressedCount} of ${rows.length} would have run)`,\n `_No reviewer subagent ran on this PR. Switched off by \\`experimental.${HOME_KEY_TURN_OFF_ALL_REVIEWERS}: true\\` `\n + `in the machine-local \\`~/${HOME_CONFIG_DIR}/${HOME_CONFIG_FILE}\\` — a MACHINE setting, not a repo one, so `\n + `nothing in this branch could have caused it. **This is not an all-clear**, and `\n + `${required.length} of the suppressed checklist(s) were REQUIRED._`,\n '',\n `### Checklists (all ${rows.length})`,\n ];\n for (const row of rows) lines.push(this.suppressedBullet(row));\n return lines.join('\\n');\n }\n\n /**\n * One roster line under suppression. ALWAYS an unchecked box — never `rosterBullet`, whose box is\n * `reviewerRan(row)`, which is true for any REQUIRED checklist that applied. Under suppression that\n * would tick every required reviewer's box on a PR none of them looked at, which is the precise\n * inversion this whole feature has to avoid.\n *\n * `row.ran` here means \"this checklist APPLIED\", so it is what separates a SUPPRESSED entry from one\n * that genuinely did not match. Both stay listed: the second is the good news the roster exists to\n * deliver, and dropping either would make the reader recompute the diff to tell them apart.\n */\n private suppressedBullet(row: ChecklistCommentRow): string {\n const state = row.ran\n ? `⚫ SUPPRESSED${row.required ? ' (REQUIRED)' : ' (optional)'} — no reviewer ran`\n : '⚪ did not apply to this diff';\n return `- [ ] **${row.subagent}** — ${state}\\n - ${this.whyLine(row)}`;\n }\n\n /**\n * The closing note when no reviewer produced a verdict. The original wording — \"every configured\n * checklist was evaluated and none of them applied\" — is an all-clear, and it becomes FALSE the moment a\n * checklist did apply and was declined. That sentence under a PR nobody reviewed is precisely the\n * misreport this feature could otherwise introduce, so the declined case gets its own words.\n */\n private nothingRanNote(rows: readonly ChecklistCommentRow[]): string {\n const declined = rows.filter((r: ChecklistCommentRow): boolean => this.declined(r));\n if (declined.length === 0) {\n return '_No reviewer had to run on this diff — every configured checklist was evaluated and none of them applied._';\n }\n return (\n `_No reviewer ran. ${declined.length} OPTIONAL checklist(s) DID apply to this diff and were not ` +\n `run; the rest were evaluated and did not apply._`\n );\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 (\n `## 🔍 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 }\n const ran = rows.filter((r: ChecklistCommentRow): boolean => this.reviewerRan(r));\n const declined = rows.filter((r: ChecklistCommentRow): boolean => this.declined(r));\n const skipped = rows.length - ran.length - declined.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 // Counted SEPARATELY from \"skipped\", and without a ✅. A declined optional review is a legitimate\n // outcome, but it is not the same good news as a checklist that had nothing to look at — folding the\n // two together would let a PR that declined every optional review read as fully covered.\n const notRun = declined.length > 0 ? ` · ${declined.length} optional not run` : '';\n return `## 🔍 Company review checklists — ${rows.length} defined · ${ran.length} ran${breakdown}${skip}${notRun}`;\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 = this.reviewerRan(row) ? '- [x]' : '- [ ]';\n return (\n `${box} ${this.verdictEmoji(row)} **${row.subagent}**${this.optionalTag(row)} — ` +\n `${this.verdictWords(row)}${this.evidenceSuffix(row)}\\n` +\n ` - ${this.whyLine(row)}`\n );\n }\n\n /**\n * Did a reviewer actually produce a verdict for this row?\n *\n * `row.ran` is really \"this checklist APPLIED to the diff\" — for a required checklist the two are the\n * same thing, because the PR cannot open otherwise, and the field was named before optional checklists\n * existed. For a DECLINED optional one they diverge, and using `ran` alone would put a checked box and a\n * reviewer section on a review that nobody performed.\n */\n private reviewerRan(row: ChecklistCommentRow): boolean {\n return row.ran && !this.declined(row);\n }\n\n // Applied, optional, and carrying no verdict — i.e. the human was offered this review and said no (or\n // `--no-optional` skipped the offer). Never true of a required checklist: one of those with no verdict\n // does not reach a PR at all.\n private declined(row: ChecklistCommentRow): boolean {\n return row.ran && !row.required && (row.status === CK_MISSING || row.status === '');\n }\n\n // Marks which rows the human could have declined. Without it a reader cannot tell a review that was\n // skippable from one that simply passed, and so cannot judge how much this PR was actually reviewed.\n private optionalTag(row: ChecklistCommentRow): string {\n return row.required ? '' : ' _(optional)_';\n }\n\n /**\n * Whether the reviewer demonstrably opened the diff, read from its own transcript. A QUALITY signal and\n * never a blocker (see SubagentProvenanceService.evidenceFor) — but published, because \"wrote a verdict\n * without reading the change\" is exactly what a reader of this comment would want to weigh.\n */\n private evidenceSuffix(row: ChecklistCommentRow): string {\n if (!this.reviewerRan(row) || row.diffRead === '') return '';\n return row.diffRead === 'yes' ? ' _(diff read ✓)_' : ' _(⚠️ no diff read recorded)_';\n }\n\n private verdictEmoji(row: ChecklistCommentRow): string {\n if (!this.reviewerRan(row)) 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 // Two different unchecked boxes, two different sentences. \"Not applicable\" is the diff's doing;\n // \"not run\" is a person's, and reporting the second as the first would quietly credit a review that\n // a human deliberately declined.\n if (this.declined(row)) return 'OPTIONAL — applied to this diff but was NOT run (not selected)';\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 // Names the FILE the authorization lives in, so a reader of the PR can go and check the provenance\n // rather than take \"someone approved this\" on trust. The reason, who authorized it and when are\n // rendered verbatim in this row's section below.\n if (row.status === CK_OVERRIDDEN) {\n return `OVERRIDDEN — a human authorized shipping it (recorded in override-${row.subagent}.json)`;\n }\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 // State the fact, not a suspicion. Patternless is a deliberate configuration — an always-runs\n // gate (every PR names a ticket, every PR has an owner) is exactly what it is FOR — so telling\n // every such row to \"add `patterns` if that is not intended\" nags the repos that meant it, on\n // every PR, forever. A reader who wants to know whether it was intended can read the config.\n return (\n `ALWAYS RUNS (no patterns) — whole diff in scope, ${total} changed file(s): ` +\n `${formatFileList(row.matchedFiles)}`\n );\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 (\n `matched ${this.asCode(row.firedPatterns)} → ${row.matchedFiles.length} of ${total} ` +\n `changed file(s): ${formatFileList(row.matchedFiles)}`\n );\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 => this.reviewerRan(r))\n .slice()\n .sort(\n (a: ChecklistCommentRow, b: ChecklistCommentRow): number =>\n this.rankOf(rank, a.status) - this.rankOf(rank, b.status),\n );\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 =\n 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 =>\n `${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) {\n max = s.body.length;\n idx = i;\n }\n });\n return idx;\n }\n}\n"]}
|
|
@@ -49,7 +49,20 @@ export declare class DashboardInput {
|
|
|
49
49
|
* repo that genuinely has no build command, but only when someone writes it down.
|
|
50
50
|
*/
|
|
51
51
|
buildCommand: string;
|
|
52
|
-
|
|
52
|
+
/**
|
|
53
|
+
* How many checklists WOULD have run but were killed by `experimental.turnOffAllReviewers` in
|
|
54
|
+
* `~/.webpieces/config.json` — required ones included. `0` means the flag is off and every reviewer
|
|
55
|
+
* ran normally; it is NOT a way of saying "no checklist matched", which `checklists.length === 0`
|
|
56
|
+
* already says.
|
|
57
|
+
*
|
|
58
|
+
* The two states are byte-identical in `checklists` (both empty) and that is precisely the defect this
|
|
59
|
+
* field closes: without it, a PR whose four REQUIRED reviewers were switched off is indistinguishable
|
|
60
|
+
* in `main`'s history from a docs-only typo fix that legitimately matched nothing. REQUIRED with no
|
|
61
|
+
* default, for the reason `buildCommand` is: a defaulted `0` would let every existing construction
|
|
62
|
+
* keep compiling while asserting, silently, that nothing was suppressed.
|
|
63
|
+
*/
|
|
64
|
+
suppressedChecklistCount: number;
|
|
65
|
+
constructor(title: string, gateResults: GateResult[], disables: DisableCounts, buildPassed: boolean, forkPoint: string, featureHead: string, mainHead: string, review: ReviewJson, checklists: ChecklistRow[], buildCommand: string, suppressedChecklistCount: number);
|
|
53
66
|
}
|
|
54
67
|
/** Renders the PR-gate dashboard markdown (gates × changed files, disables, risk, 3-point hashes). */
|
|
55
68
|
export declare class Dashboard {
|
|
@@ -158,6 +171,24 @@ export declare class Dashboard {
|
|
|
158
171
|
* added later all land there, matching review.json's own "not pass|warn|overridden ⇒ refuse" rule
|
|
159
172
|
* rather than a second list that could silently drift green.
|
|
160
173
|
*/
|
|
174
|
+
/**
|
|
175
|
+
* The full-dashboard row for a suppressed PR. Verbose is FINE here — this lands in the PR's 1st
|
|
176
|
+
* comment, not in `git log`.
|
|
177
|
+
*/
|
|
178
|
+
private suppressedChecklistLine;
|
|
179
|
+
/**
|
|
180
|
+
* The ONE sentence both the dashboard row and the commit-body bullet are built from, so the two can
|
|
181
|
+
* never disagree about what was switched off or by what.
|
|
182
|
+
*
|
|
183
|
+
* Every part of it earns its place in `main`'s permanent history:
|
|
184
|
+
* • the COUNT, because "suppressed 4" and "0 applicable" are different facts and both must be
|
|
185
|
+
* tellable apart forever — an absent bullet says the second while meaning the first;
|
|
186
|
+
* • "required included", because otherwise it reads as an optional-only skip, which it is not;
|
|
187
|
+
* • the FLAG and the FILE, because they are the only actionable thing a reader of `git log` has.
|
|
188
|
+
* ⚫ is deliberately outside the verdict palette (🟢🟡🔴🟠⚪ are all taken by checklistStatusText), so
|
|
189
|
+
* nothing here can be mistaken for a verdict a reviewer actually returned.
|
|
190
|
+
*/
|
|
191
|
+
private suppressedTail;
|
|
161
192
|
private rollupBuckets;
|
|
162
193
|
private bucketPhrase;
|
|
163
194
|
private compactNames;
|
|
@@ -112,8 +112,21 @@ class DashboardInput {
|
|
|
112
112
|
* repo that genuinely has no build command, but only when someone writes it down.
|
|
113
113
|
*/
|
|
114
114
|
buildCommand;
|
|
115
|
+
/**
|
|
116
|
+
* How many checklists WOULD have run but were killed by `experimental.turnOffAllReviewers` in
|
|
117
|
+
* `~/.webpieces/config.json` — required ones included. `0` means the flag is off and every reviewer
|
|
118
|
+
* ran normally; it is NOT a way of saying "no checklist matched", which `checklists.length === 0`
|
|
119
|
+
* already says.
|
|
120
|
+
*
|
|
121
|
+
* The two states are byte-identical in `checklists` (both empty) and that is precisely the defect this
|
|
122
|
+
* field closes: without it, a PR whose four REQUIRED reviewers were switched off is indistinguishable
|
|
123
|
+
* in `main`'s history from a docs-only typo fix that legitimately matched nothing. REQUIRED with no
|
|
124
|
+
* default, for the reason `buildCommand` is: a defaulted `0` would let every existing construction
|
|
125
|
+
* keep compiling while asserting, silently, that nothing was suppressed.
|
|
126
|
+
*/
|
|
127
|
+
suppressedChecklistCount;
|
|
115
128
|
// eslint-disable-next-line @typescript-eslint/max-params
|
|
116
|
-
constructor(title, gateResults, disables, buildPassed, forkPoint, featureHead, mainHead, review, checklists, buildCommand) {
|
|
129
|
+
constructor(title, gateResults, disables, buildPassed, forkPoint, featureHead, mainHead, review, checklists, buildCommand, suppressedChecklistCount) {
|
|
117
130
|
this.title = title;
|
|
118
131
|
this.gateResults = gateResults;
|
|
119
132
|
this.disables = disables;
|
|
@@ -124,6 +137,7 @@ class DashboardInput {
|
|
|
124
137
|
this.review = review;
|
|
125
138
|
this.checklists = checklists;
|
|
126
139
|
this.buildCommand = buildCommand;
|
|
140
|
+
this.suppressedChecklistCount = suppressedChecklistCount;
|
|
127
141
|
}
|
|
128
142
|
}
|
|
129
143
|
exports.DashboardInput = DashboardInput;
|
|
@@ -192,7 +206,7 @@ let Dashboard = class Dashboard {
|
|
|
192
206
|
// checklist COMMENT, per reviewer, in full. ALWAYS emitted, including the zero case: "no reviewer
|
|
193
207
|
// looked at this PR" is a fact a reader must be told, and an absent row silently reads as a green
|
|
194
208
|
// all-clear (see checklistRollupLine).
|
|
195
|
-
lines.push(this.checklistRollupLine(input.checklists));
|
|
209
|
+
lines.push(this.checklistRollupLine(input.checklists, input.suppressedChecklistCount));
|
|
196
210
|
lines.push('');
|
|
197
211
|
if (input.review.summary.trim() !== '') {
|
|
198
212
|
lines.push('### Summary');
|
|
@@ -329,6 +343,17 @@ let Dashboard = class Dashboard {
|
|
|
329
343
|
if (input.disables.eslintCount > 0)
|
|
330
344
|
flags.push(`ESLint Disables Added: 🟡 ${input.disables.eslintCount} line(s)`);
|
|
331
345
|
// A triggered checklist is noteworthy in main's history — carry each into the commit body.
|
|
346
|
+
//
|
|
347
|
+
// SUPPRESSED replaces those N bullets with exactly ONE, whatever N is. Not because the per-reviewer
|
|
348
|
+
// bullets would lie — `checklistStatusText` was already hardened against a false "passed" — but
|
|
349
|
+
// because with the kill switch on there are NO rows at all, and a body with no `Checklist — `
|
|
350
|
+
// bullets is byte-identical to a PR that legitimately matched zero checklists (a docs-only typo
|
|
351
|
+
// fix). Without this line, "four REQUIRED reviewers were killed by a flag" and "nothing applied
|
|
352
|
+
// here" are indistinguishable in main's history forever. One compact bullet, in the existing
|
|
353
|
+
// style — the loud version belongs in stage ②'s output and the 1st comment, not in `git log`.
|
|
354
|
+
if (input.suppressedChecklistCount > 0) {
|
|
355
|
+
flags.push(`Checklists — ${this.suppressedTail(input.suppressedChecklistCount)}`);
|
|
356
|
+
}
|
|
332
357
|
for (const row of input.checklists) {
|
|
333
358
|
flags.push(`Checklist — ${row.title}: ${this.checklistStatusText(row)}`);
|
|
334
359
|
}
|
|
@@ -431,7 +456,12 @@ let Dashboard = class Dashboard {
|
|
|
431
456
|
* shape as its neighbours (`**Build (nx affected):** 🟢 Passed`). Counts and names only — never a line
|
|
432
457
|
* of reviewer output or override justification, which is the whole reason this row exists.
|
|
433
458
|
*/
|
|
434
|
-
checklistRollupLine(rows) {
|
|
459
|
+
checklistRollupLine(rows, suppressedCount) {
|
|
460
|
+
// FIRST, and ahead of the zero-ran case below, which would say "no review checklist matched this
|
|
461
|
+
// PR" — false here, and the one sentence that would turn a kill switch into an all-clear. ⚫ is a
|
|
462
|
+
// colour no verdict uses, so it cannot be mistaken for a pass, a warning or a refusal.
|
|
463
|
+
if (suppressedCount > 0)
|
|
464
|
+
return this.suppressedChecklistLine(suppressedCount);
|
|
435
465
|
const buckets = this.rollupBuckets(rows);
|
|
436
466
|
const filled = buckets.filter((b) => b.titles.length > 0);
|
|
437
467
|
// NO reviewer ran ⇒ the SKIPPED icon, never green. Green claims something was checked and came back
|
|
@@ -455,6 +485,30 @@ let Dashboard = class Dashboard {
|
|
|
455
485
|
* added later all land there, matching review.json's own "not pass|warn|overridden ⇒ refuse" rule
|
|
456
486
|
* rather than a second list that could silently drift green.
|
|
457
487
|
*/
|
|
488
|
+
/**
|
|
489
|
+
* The full-dashboard row for a suppressed PR. Verbose is FINE here — this lands in the PR's 1st
|
|
490
|
+
* comment, not in `git log`.
|
|
491
|
+
*/
|
|
492
|
+
suppressedChecklistLine(suppressedCount) {
|
|
493
|
+
return `**Checklists:** ${this.suppressedTail(suppressedCount)}`
|
|
494
|
+
+ ' · NO reviewer subagent ran on this PR — set that key to false (or delete it) to get them back';
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* The ONE sentence both the dashboard row and the commit-body bullet are built from, so the two can
|
|
498
|
+
* never disagree about what was switched off or by what.
|
|
499
|
+
*
|
|
500
|
+
* Every part of it earns its place in `main`'s permanent history:
|
|
501
|
+
* • the COUNT, because "suppressed 4" and "0 applicable" are different facts and both must be
|
|
502
|
+
* tellable apart forever — an absent bullet says the second while meaning the first;
|
|
503
|
+
* • "required included", because otherwise it reads as an optional-only skip, which it is not;
|
|
504
|
+
* • the FLAG and the FILE, because they are the only actionable thing a reader of `git log` has.
|
|
505
|
+
* ⚫ is deliberately outside the verdict palette (🟢🟡🔴🟠⚪ are all taken by checklistStatusText), so
|
|
506
|
+
* nothing here can be mistaken for a verdict a reviewer actually returned.
|
|
507
|
+
*/
|
|
508
|
+
suppressedTail(suppressedCount) {
|
|
509
|
+
return `⚫ ALL ${suppressedCount} SUPPRESSED (required included) by `
|
|
510
|
+
+ `${rules_config_1.HOME_KEY_TURN_OFF_ALL_REVIEWERS} in ~/${rules_config_1.HOME_CONFIG_DIR}/${rules_config_1.HOME_CONFIG_FILE}`;
|
|
511
|
+
}
|
|
458
512
|
rollupBuckets(rows) {
|
|
459
513
|
const blocking = new RollupBucket('blocking', '🔴', true);
|
|
460
514
|
const overridden = new RollupBucket('overridden', '🟠', true);
|