@webpieces/pr-gate 0.4.469 → 0.4.471

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/pr-gate",
3
- "version": "0.4.469",
3
+ "version": "0.4.471",
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",
@@ -10,7 +10,8 @@
10
10
  "wp-start-update": "./src/scripts/wp-start-update.js",
11
11
  "wp-finish-update": "./src/scripts/wp-finish-update.js",
12
12
  "wp-cleanup": "./src/scripts/wp-cleanup.js",
13
- "wp-land-pr": "./src/scripts/wp-land-pr.js"
13
+ "wp-land-pr": "./src/scripts/wp-land-pr.js",
14
+ "wp-check-pr": "./src/scripts/wp-check-pr.js"
14
15
  },
15
16
  "files": [
16
17
  "src/**/*"
@@ -23,7 +24,7 @@
23
24
  "directory": "packages/tooling/pr-gate"
24
25
  },
25
26
  "dependencies": {
26
- "@webpieces/rules-config": "0.4.469",
27
+ "@webpieces/rules-config": "0.4.471",
27
28
  "@inversifyjs/binding-decorators": "1.1.5",
28
29
  "inversify": "7.10.4",
29
30
  "reflect-metadata": "0.2.2"
@@ -8,8 +8,9 @@ export declare class GateResult {
8
8
  export declare class ChecklistRow {
9
9
  title: string;
10
10
  severity: string;
11
- acknowledged: boolean;
12
- constructor(title: string, severity: string, acknowledged: boolean);
11
+ status: string;
12
+ detail: string;
13
+ constructor(title: string, severity: string, status: string, detail?: string);
13
14
  }
14
15
  export declare class DisableCounts {
15
16
  webpiecesCount: number;
@@ -36,6 +37,7 @@ export declare class Dashboard {
36
37
  renderDashboard(input: DashboardInput): string;
37
38
  renderCommitBody(input: DashboardInput, prUrl: string): string;
38
39
  private nonGreenFlags;
40
+ private checklistStatusText;
39
41
  private firstSentences;
40
42
  private globToRegex;
41
43
  private matchesAny;
@@ -15,19 +15,22 @@ class GateResult {
15
15
  }
16
16
  }
17
17
  exports.GateResult = GateResult;
18
- // One row for a consumer review checklist the branch triggered. `acknowledged` reflects the AI's
19
- // review.json ack (a BLOCK row is always acknowledged by the time it renders an unacknowledged BLOCK
20
- // throws before the dashboard is built; a WARN row may render un-acknowledged). Rendered into the PR
21
- // body so the acknowledgment reaches the server `.webpieces/` is gitignored, so the PR body is the
22
- // only artifact of the local flow that ever leaves the checkout.
18
+ // One row for a consumer review checklist the branch triggered. `status` is the resolved verdict (one of
19
+ // CK_PASS | CK_OVERRIDDEN | CK_FAIL | CK_MISSING | CK_ACKED); `detail` is the reviewer output / override
20
+ // justification. A BLOCK row is always PASS/OVERRIDDEN/ACKED by the time it renders a failed or missing
21
+ // BLOCK throws before the dashboard is built; a WARN row may render in any state. Rendered into the PR
22
+ // body so the verdict reaches the server — the PR body is the artifact of the local flow that leaves the
23
+ // checkout (alongside the HMAC gate token that proves the flow ran).
23
24
  class ChecklistRow {
24
25
  title;
25
26
  severity; // 'BLOCK' | 'WARN'
26
- acknowledged;
27
- constructor(title, severity, acknowledged) {
27
+ status; // CK_* verdict
28
+ detail; // reviewer output / override justification (surfaced for OVERRIDDEN + WARN-FAIL)
29
+ constructor(title, severity, status, detail = '') {
28
30
  this.title = title;
29
31
  this.severity = severity;
30
- this.acknowledged = acknowledged;
32
+ this.status = status;
33
+ this.detail = detail;
31
34
  }
32
35
  }
33
36
  exports.ChecklistRow = ChecklistRow;
@@ -179,12 +182,25 @@ let Dashboard = class Dashboard {
179
182
  flags.push(`ESLint Disables Added: 🟡 ${input.disables.eslintCount} line(s)`);
180
183
  // A triggered checklist is noteworthy in main's history — carry each into the commit body.
181
184
  for (const row of input.checklists) {
182
- const emoji = row.severity === 'BLOCK' ? '🔴' : '🟡';
183
- const ack = row.acknowledged ? 'acknowledged' : 'NOT acknowledged';
184
- flags.push(`Checklist — ${row.title}: ${emoji} ${row.severity} — ${ack}`);
185
+ flags.push(`Checklist ${row.title}: ${this.checklistStatusText(row)}`);
185
186
  }
186
187
  return flags;
187
188
  }
189
+ // Emoji + words for a checklist verdict, shared by the dashboard row and the commit-body flag.
190
+ checklistStatusText(row) {
191
+ const sev = row.severity;
192
+ if (row.status === rules_config_1.CK_OVERRIDDEN) {
193
+ const why = row.detail.trim() !== '' ? ` — override: ${row.detail.trim()}` : '';
194
+ return `🟡 ${sev} — OVERRIDDEN${why}`;
195
+ }
196
+ if (row.status === rules_config_1.CK_FAIL)
197
+ return `🔴 ${sev} — FAILED review`;
198
+ if (row.status === rules_config_1.CK_MISSING)
199
+ return `⚪ ${sev} — not reviewed`;
200
+ if (row.status === rules_config_1.CK_ACKED)
201
+ return `🟢 ${sev} — acknowledged`;
202
+ return `🟢 ${sev} — passed`; // CK_PASS
203
+ }
188
204
  // First `max` sentences of `text`. A sentence ends at `. ! ?` ONLY when followed by whitespace or
189
205
  // end-of-string, so interior dots in filenames/paths/versions (dependencies.json, runtime-graph.ts,
190
206
  // 0.4.447) do NOT split — and, unlike a greedy `[^.!?]+` regex, no text is ever dropped when such a
@@ -258,11 +274,9 @@ let Dashboard = class Dashboard {
258
274
  const emoji = result.warningColor === 'red' ? '🔴' : '🟡';
259
275
  return `**${result.name}:** ${emoji} Yes (${result.matchedFiles.length} file(s))`;
260
276
  }
261
- // A triggered consumer checklist: 🔴 for BLOCK (harder flag), 🟡 for WARN, plus the ack state.
277
+ // A triggered consumer checklist row: the resolved verdict (passed / overridden / failed / …).
262
278
  checklistLine(row) {
263
- const emoji = row.severity === 'BLOCK' ? '🔴' : '🟡';
264
- const ack = row.acknowledged ? 'acknowledged' : 'NOT acknowledged';
265
- return `**Checklist — ${row.title}:** ${emoji} ${row.severity} — ${ack}`;
279
+ return `**Checklist ${row.title}:** ${this.checklistStatusText(row)}`;
266
280
  }
267
281
  // 10-cell risk bar colored by band (🟩 ≤25, 🟨 ≤50, 🟧 ≤75, 🟥 >75), at least one filled cell.
268
282
  riskBar(score) {
@@ -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 { GateDefinition, WEBPIECES_DISABLE, RULE_NAMES, ReviewJson } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\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. `acknowledged` reflects the AI's\n// review.json ack (a BLOCK row is always acknowledged by the time it renders — an unacknowledged BLOCK\n// throws before the dashboard is built; a WARN row may render un-acknowledged). Rendered into the PR\n// body so the acknowledgment reaches the server — `.webpieces/` is gitignored, so the PR body is the\n// only artifact of the local flow that ever leaves the checkout.\nexport class ChecklistRow {\n title: string;\n severity: string; // 'BLOCK' | 'WARN'\n acknowledged: boolean;\n\n constructor(title: string, severity: string, acknowledged: boolean) {\n this.title = title;\n this.severity = severity;\n this.acknowledged = acknowledged;\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 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 const emoji = row.severity === 'BLOCK' ? '🔴' : '🟡';\n const ack = row.acknowledged ? 'acknowledged' : 'NOT acknowledged';\n flags.push(`Checklist — ${row.title}: ${emoji} ${row.severity} — ${ack}`);\n }\n return flags;\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: 🔴 for BLOCK (harder flag), 🟡 for WARN, plus the ack state.\n private checklistLine(row: ChecklistRow): string {\n const emoji = row.severity === 'BLOCK' ? '🔴' : '🟡';\n const ack = row.acknowledged ? 'acknowledged' : 'NOT acknowledged';\n return `**Checklist — ${row.title}:** ${emoji} ${row.severity} — ${ack}`;\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,\n CK_PASS, CK_OVERRIDDEN, CK_FAIL, CK_MISSING, CK_ACKED,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\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;\n severity: string; // 'BLOCK' | 'WARN'\n status: string; // CK_* verdict\n detail: string; // reviewer output / override justification (surfaced for OVERRIDDEN + WARN-FAIL)\n\n constructor(title: string, severity: string, status: string, detail = '') {\n this.title = title;\n this.severity = severity;\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 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 const sev = row.severity;\n if (row.status === CK_OVERRIDDEN) {\n const why = row.detail.trim() !== '' ? ` — override: ${row.detail.trim()}` : '';\n return `🟡 ${sev} — OVERRIDDEN${why}`;\n }\n if (row.status === CK_FAIL) return `🔴 ${sev} — FAILED review`;\n if (row.status === CK_MISSING) return `⚪ ${sev} — not reviewed`;\n if (row.status === CK_ACKED) return `🟢 ${sev} — acknowledged`;\n return `🟢 ${sev} — 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"]}
@@ -0,0 +1,28 @@
1
+ import { RepoRootFinder, GateTokenService } from '@webpieces/rules-config';
2
+ /**
3
+ * `wp-check-pr` — the SERVER-SIDE half of the gate, meant to run as a required CI check. It is READ-ONLY:
4
+ * it never touches git state, never pushes, never calls `gh pr create`. It recomputes
5
+ * `HMAC(prGate.gateSalt, PR_head_sha)` from the committed salt and verifies the PR body carries that
6
+ * token. Because `wp-finish-upsert-pr` refuses to mint the token unless the build gate + every BLOCK
7
+ * checklist passed, a valid token IS proof the gated flow ran and passed on this exact commit.
8
+ *
9
+ * This is what catches a PR opened OUTSIDE the gated flow — an unhooked teammate who `git push`ed and
10
+ * clicked "Create pull request" in the web UI carries no valid token for its head sha and fails here.
11
+ *
12
+ * A repo with no `gateSalt` configured has not opted in → this is a no-op success (exit 0), so it is safe
13
+ * to add the workflow before turning enforcement on.
14
+ *
15
+ * `@injectable(bindingScopeValues.Singleton)` so it is drawn in the DI design and resolved by PrGateApp.
16
+ */
17
+ export declare class CheckPrCommand {
18
+ private readonly repoRootFinder;
19
+ private readonly gateTokenService;
20
+ constructor(repoRootFinder: RepoRootFinder, gateTokenService: GateTokenService);
21
+ run(): Promise<void>;
22
+ private verifyWithRetry;
23
+ private delay;
24
+ private postStatus;
25
+ private failureMessage;
26
+ private resolvePr;
27
+ private prNumber;
28
+ }
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CheckPrCommand = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const child_process_1 = require("child_process");
6
+ const rules_config_1 = require("@webpieces/rules-config");
7
+ const inversify_1 = require("inversify");
8
+ // The head sha + body of the PR under check, resolved from `gh`. Data-only.
9
+ class PrUnderCheck {
10
+ number;
11
+ headSha;
12
+ body;
13
+ constructor(number, headSha, body) {
14
+ this.number = number;
15
+ this.headSha = headSha;
16
+ this.body = body;
17
+ }
18
+ }
19
+ /**
20
+ * `wp-check-pr` — the SERVER-SIDE half of the gate, meant to run as a required CI check. It is READ-ONLY:
21
+ * it never touches git state, never pushes, never calls `gh pr create`. It recomputes
22
+ * `HMAC(prGate.gateSalt, PR_head_sha)` from the committed salt and verifies the PR body carries that
23
+ * token. Because `wp-finish-upsert-pr` refuses to mint the token unless the build gate + every BLOCK
24
+ * checklist passed, a valid token IS proof the gated flow ran and passed on this exact commit.
25
+ *
26
+ * This is what catches a PR opened OUTSIDE the gated flow — an unhooked teammate who `git push`ed and
27
+ * clicked "Create pull request" in the web UI carries no valid token for its head sha and fails here.
28
+ *
29
+ * A repo with no `gateSalt` configured has not opted in → this is a no-op success (exit 0), so it is safe
30
+ * to add the workflow before turning enforcement on.
31
+ *
32
+ * `@injectable(bindingScopeValues.Singleton)` so it is drawn in the DI design and resolved by PrGateApp.
33
+ */
34
+ let CheckPrCommand = class CheckPrCommand {
35
+ repoRootFinder;
36
+ gateTokenService;
37
+ constructor(repoRootFinder, gateTokenService) {
38
+ this.repoRootFinder = repoRootFinder;
39
+ this.gateTokenService = gateTokenService;
40
+ }
41
+ async run() {
42
+ const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
43
+ const gateSalt = (0, rules_config_1.loadAndValidate)(repoRoot).prGate.gateSalt;
44
+ if (gateSalt.trim() === '') {
45
+ throw new rules_config_1.CliExitError(0, 'ℹ️ wp-check-pr: no prGate.gateSalt configured — server-side gate token enforcement is disabled. ' +
46
+ 'Add a committed "gateSalt" under the pr-gate section of webpieces.config.json to enable it.');
47
+ }
48
+ let pr = this.resolvePr();
49
+ if (pr.headSha === '') {
50
+ throw new rules_config_1.CliExitError(1, '❌ wp-check-pr: could not resolve the PR head sha via `gh`. Ensure the workflow runs on a pull_request ' +
51
+ 'event with `gh` authenticated (GH_TOKEN) and the PR number available (WP_PR_NUMBER or GITHUB_REF).');
52
+ }
53
+ pr = await this.verifyWithRetry(pr, gateSalt);
54
+ if (this.gateTokenService.verifyGateToken(pr.body, gateSalt, pr.headSha)) {
55
+ this.postStatus(pr.headSha, 'success', 'gated flow verified');
56
+ process.stdout.write(`✅ wp-check-pr: valid webpieces gate token for PR #${pr.number} @ ${pr.headSha.slice(0, 12)} — created through the gated flow.\n`);
57
+ return;
58
+ }
59
+ // An UNHOOKED push never reaches wp-finish-upsert-pr, so no status ever appears — mark it failed
60
+ // with an actionable status (attached to this sha) and fail the job so the PR shows red with a reason.
61
+ this.postStatus(pr.headSha, 'failure', 'Not created through the webpieces gated flow — run pnpm wp-start-upsert-pr');
62
+ throw new rules_config_1.CliExitError(1, this.failureMessage(pr));
63
+ }
64
+ // Re-read the PR ONCE after a short delay if the token looks stale. `wp-finish-upsert-pr` posts the
65
+ // authoritative commit status directly, but this workflow can be triggered by the `synchronize` push a
66
+ // beat BEFORE the body edit lands — so a first stale read is re-checked rather than red-flagging a
67
+ // correctly-gated PR on a timing coin-flip (see the gate-token-race bug). Returns the freshest PR.
68
+ async verifyWithRetry(pr, gateSalt) {
69
+ if (this.gateTokenService.verifyGateToken(pr.body, gateSalt, pr.headSha))
70
+ return pr;
71
+ process.stdout.write('… no valid token yet — waiting for the PR body edit to land, then re-checking once…\n');
72
+ await this.delay(15000);
73
+ const refreshed = this.resolvePr();
74
+ return refreshed.headSha !== '' ? refreshed : pr;
75
+ }
76
+ delay(ms) {
77
+ return new Promise((resolve) => {
78
+ setTimeout(resolve, ms);
79
+ });
80
+ }
81
+ // Post the webpieces/pr-gate commit status on `sha`. Best-effort: a missing statuses:write scope is a
82
+ // warning, not a hard failure (the job's own exit code still reflects pass/fail).
83
+ postStatus(sha, state, description) {
84
+ const res = (0, child_process_1.spawnSync)('gh', [
85
+ 'api', '--method', 'POST', `repos/{owner}/{repo}/statuses/${sha}`,
86
+ '-f', `state=${state}`,
87
+ '-f', 'context=webpieces/pr-gate',
88
+ '-f', `description=${description}`,
89
+ ], { encoding: 'utf8' });
90
+ if (res.status !== 0) {
91
+ process.stderr.write('⚠️ wp-check-pr could not post the webpieces/pr-gate commit status (needs statuses:write).\n');
92
+ }
93
+ }
94
+ // The actionable red-check message: this PR did not come through the gated flow (or hooks are missing).
95
+ failureMessage(pr) {
96
+ return (`❌ wp-check-pr: PR #${pr.number} (head ${pr.headSha.slice(0, 12)}) has no valid webpieces gate token.\n\n` +
97
+ `This PR was NOT created through the webpieces gated flow — or was pushed after finishing without re-running it.\n` +
98
+ `Every commit that lands here must go through it, so:\n\n` +
99
+ ` 1. Install the webpieces hooks if you don't have them.\n` +
100
+ ` 2. Recreate/update this PR by running: pnpm wp-start-upsert-pr → write review.json → pnpm wp-finish-upsert-pr\n\n` +
101
+ `That re-stamps the PR title, body, and the gate token for the current head commit, and this check goes green.`);
102
+ }
103
+ // Resolve the PR (number, head sha, body) from `gh`. Prefers an explicit WP_PR_NUMBER, then the
104
+ // pull_request number in GITHUB_REF (refs/pull/<n>/merge), then `gh`'s current-branch detection.
105
+ resolvePr() {
106
+ const num = this.prNumber();
107
+ const args = num !== ''
108
+ ? ['pr', 'view', num, '--json', 'number,headRefOid,body', '--jq', '"\\(.number)\\t\\(.headRefOid)\\t\\(.body)"']
109
+ : ['pr', 'view', '--json', 'number,headRefOid,body', '--jq', '"\\(.number)\\t\\(.headRefOid)\\t\\(.body)"'];
110
+ const result = (0, child_process_1.spawnSync)('gh', args, { encoding: 'utf8', maxBuffer: 1024 * 1024 * 16 });
111
+ if (result.status !== 0)
112
+ return new PrUnderCheck(num, '', '');
113
+ // jq joins body (which may contain tabs/newlines) last, so split on the FIRST two tabs only.
114
+ const out = (result.stdout ?? '').trim();
115
+ const firstTab = out.indexOf('\t');
116
+ const secondTab = firstTab >= 0 ? out.indexOf('\t', firstTab + 1) : -1;
117
+ if (firstTab < 0 || secondTab < 0)
118
+ return new PrUnderCheck(num, '', '');
119
+ const number = out.slice(0, firstTab);
120
+ const headSha = out.slice(firstTab + 1, secondTab);
121
+ const body = out.slice(secondTab + 1);
122
+ return new PrUnderCheck(number, headSha, body);
123
+ }
124
+ prNumber() {
125
+ const explicit = (process.env['WP_PR_NUMBER'] ?? '').trim();
126
+ if (explicit !== '')
127
+ return explicit;
128
+ // GitHub Actions pull_request: GITHUB_REF = refs/pull/<n>/merge
129
+ const ref = process.env['GITHUB_REF'] ?? '';
130
+ const m = /refs\/pull\/(\d+)\//.exec(ref);
131
+ return m ? m[1] : '';
132
+ }
133
+ };
134
+ exports.CheckPrCommand = CheckPrCommand;
135
+ exports.CheckPrCommand = CheckPrCommand = tslib_1.__decorate([
136
+ (0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
137
+ tslib_1.__metadata("design:paramtypes", [rules_config_1.RepoRootFinder,
138
+ rules_config_1.GateTokenService])
139
+ ], CheckPrCommand);
140
+ //# sourceMappingURL=check-pr-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-pr-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/check-pr-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'child_process';\nimport {\n loadAndValidate, RepoRootFinder, GateTokenService, CliExitError,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\n// The head sha + body of the PR under check, resolved from `gh`. Data-only.\nclass PrUnderCheck {\n number: string;\n headSha: string;\n body: string;\n\n constructor(number: string, headSha: string, body: string) {\n this.number = number;\n this.headSha = headSha;\n this.body = body;\n }\n}\n\n/**\n * `wp-check-pr` — the SERVER-SIDE half of the gate, meant to run as a required CI check. It is READ-ONLY:\n * it never touches git state, never pushes, never calls `gh pr create`. It recomputes\n * `HMAC(prGate.gateSalt, PR_head_sha)` from the committed salt and verifies the PR body carries that\n * token. Because `wp-finish-upsert-pr` refuses to mint the token unless the build gate + every BLOCK\n * checklist passed, a valid token IS proof the gated flow ran and passed on this exact commit.\n *\n * This is what catches a PR opened OUTSIDE the gated flow — an unhooked teammate who `git push`ed and\n * clicked \"Create pull request\" in the web UI carries no valid token for its head sha and fails here.\n *\n * A repo with no `gateSalt` configured has not opted in → this is a no-op success (exit 0), so it is safe\n * to add the workflow before turning enforcement on.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is drawn in the DI design and resolved by PrGateApp.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class CheckPrCommand {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly gateTokenService: GateTokenService,\n ) {}\n\n async run(): Promise<void> {\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n const gateSalt = loadAndValidate(repoRoot).prGate.gateSalt;\n if (gateSalt.trim() === '') {\n throw new CliExitError(0,\n 'ℹ️ wp-check-pr: no prGate.gateSalt configured — server-side gate token enforcement is disabled. ' +\n 'Add a committed \"gateSalt\" under the pr-gate section of webpieces.config.json to enable it.');\n }\n\n let pr = this.resolvePr();\n if (pr.headSha === '') {\n throw new CliExitError(1,\n '❌ wp-check-pr: could not resolve the PR head sha via `gh`. Ensure the workflow runs on a pull_request ' +\n 'event with `gh` authenticated (GH_TOKEN) and the PR number available (WP_PR_NUMBER or GITHUB_REF).');\n }\n\n pr = await this.verifyWithRetry(pr, gateSalt);\n if (this.gateTokenService.verifyGateToken(pr.body, gateSalt, pr.headSha)) {\n this.postStatus(pr.headSha, 'success', 'gated flow verified');\n process.stdout.write(`✅ wp-check-pr: valid webpieces gate token for PR #${pr.number} @ ${pr.headSha.slice(0, 12)} — created through the gated flow.\\n`);\n return;\n }\n\n // An UNHOOKED push never reaches wp-finish-upsert-pr, so no status ever appears — mark it failed\n // with an actionable status (attached to this sha) and fail the job so the PR shows red with a reason.\n this.postStatus(pr.headSha, 'failure', 'Not created through the webpieces gated flow — run pnpm wp-start-upsert-pr');\n throw new CliExitError(1, this.failureMessage(pr));\n }\n\n // Re-read the PR ONCE after a short delay if the token looks stale. `wp-finish-upsert-pr` posts the\n // authoritative commit status directly, but this workflow can be triggered by the `synchronize` push a\n // beat BEFORE the body edit lands — so a first stale read is re-checked rather than red-flagging a\n // correctly-gated PR on a timing coin-flip (see the gate-token-race bug). Returns the freshest PR.\n private async verifyWithRetry(pr: PrUnderCheck, gateSalt: string): Promise<PrUnderCheck> {\n if (this.gateTokenService.verifyGateToken(pr.body, gateSalt, pr.headSha)) return pr;\n process.stdout.write('… no valid token yet — waiting for the PR body edit to land, then re-checking once…\\n');\n await this.delay(15000);\n const refreshed = this.resolvePr();\n return refreshed.headSha !== '' ? refreshed : pr;\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve: () => void): void => {\n setTimeout(resolve, ms);\n });\n }\n\n // Post the webpieces/pr-gate commit status on `sha`. Best-effort: a missing statuses:write scope is a\n // warning, not a hard failure (the job's own exit code still reflects pass/fail).\n private postStatus(sha: string, state: string, description: string): void {\n const res = spawnSync('gh', [\n 'api', '--method', 'POST', `repos/{owner}/{repo}/statuses/${sha}`,\n '-f', `state=${state}`,\n '-f', 'context=webpieces/pr-gate',\n '-f', `description=${description}`,\n ], { encoding: 'utf8' });\n if (res.status !== 0) {\n process.stderr.write('⚠️ wp-check-pr could not post the webpieces/pr-gate commit status (needs statuses:write).\\n');\n }\n }\n\n // The actionable red-check message: this PR did not come through the gated flow (or hooks are missing).\n private failureMessage(pr: PrUnderCheck): string {\n return (\n `❌ wp-check-pr: PR #${pr.number} (head ${pr.headSha.slice(0, 12)}) has no valid webpieces gate token.\\n\\n` +\n `This PR was NOT created through the webpieces gated flow — or was pushed after finishing without re-running it.\\n` +\n `Every commit that lands here must go through it, so:\\n\\n` +\n ` 1. Install the webpieces hooks if you don't have them.\\n` +\n ` 2. Recreate/update this PR by running: pnpm wp-start-upsert-pr → write review.json → pnpm wp-finish-upsert-pr\\n\\n` +\n `That re-stamps the PR title, body, and the gate token for the current head commit, and this check goes green.`\n );\n }\n\n // Resolve the PR (number, head sha, body) from `gh`. Prefers an explicit WP_PR_NUMBER, then the\n // pull_request number in GITHUB_REF (refs/pull/<n>/merge), then `gh`'s current-branch detection.\n private resolvePr(): PrUnderCheck {\n const num = this.prNumber();\n const args = num !== ''\n ? ['pr', 'view', num, '--json', 'number,headRefOid,body', '--jq', '\"\\\\(.number)\\\\t\\\\(.headRefOid)\\\\t\\\\(.body)\"']\n : ['pr', 'view', '--json', 'number,headRefOid,body', '--jq', '\"\\\\(.number)\\\\t\\\\(.headRefOid)\\\\t\\\\(.body)\"'];\n const result = spawnSync('gh', args, { encoding: 'utf8', maxBuffer: 1024 * 1024 * 16 });\n if (result.status !== 0) return new PrUnderCheck(num, '', '');\n // jq joins body (which may contain tabs/newlines) last, so split on the FIRST two tabs only.\n const out = (result.stdout ?? '').trim();\n const firstTab = out.indexOf('\\t');\n const secondTab = firstTab >= 0 ? out.indexOf('\\t', firstTab + 1) : -1;\n if (firstTab < 0 || secondTab < 0) return new PrUnderCheck(num, '', '');\n const number = out.slice(0, firstTab);\n const headSha = out.slice(firstTab + 1, secondTab);\n const body = out.slice(secondTab + 1);\n return new PrUnderCheck(number, headSha, body);\n }\n\n private prNumber(): string {\n const explicit = (process.env['WP_PR_NUMBER'] ?? '').trim();\n if (explicit !== '') return explicit;\n // GitHub Actions pull_request: GITHUB_REF = refs/pull/<n>/merge\n const ref = process.env['GITHUB_REF'] ?? '';\n const m = /refs\\/pull\\/(\\d+)\\//.exec(ref);\n return m ? m[1] : '';\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { RepoRootFinder } from '@webpieces/rules-config';
1
+ import { RepoRootFinder, ReviewJsonService, GateTokenService, SubagentProvenanceService } from '@webpieces/rules-config';
2
2
  import { AiBranchName } from '../workflow/git-readAiBranchName';
3
3
  import { BranchNaming } from '../workflow/branch-naming';
4
4
  import { ChecklistDetector } from '../workflow/checklist-detector';
@@ -19,12 +19,18 @@ export declare class FinishUpsertPrCommand {
19
19
  private readonly prMerger;
20
20
  private readonly dashboard;
21
21
  private readonly checklistDetector;
22
- constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, branchNaming: BranchNaming, gitExec: GitExec, buildAffected: BuildAffected, mergeState: MergeState, mergeEnd: MergeEnd, prMerger: PrMerger, dashboard: Dashboard, checklistDetector: ChecklistDetector);
22
+ private readonly reviewJsonService;
23
+ private readonly gateTokenService;
24
+ private readonly provenance;
25
+ constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, branchNaming: BranchNaming, gitExec: GitExec, buildAffected: BuildAffected, mergeState: MergeState, mergeEnd: MergeEnd, prMerger: PrMerger, dashboard: Dashboard, checklistDetector: ChecklistDetector, reviewJsonService: ReviewJsonService, gateTokenService: GateTokenService, provenance: SubagentProvenanceService);
23
26
  run(): Promise<void>;
24
27
  private gitOut;
25
28
  private prTitleFrom;
26
29
  private computeDashboardInput;
27
30
  private checklistRows;
31
+ private gateTokenBody;
32
+ private postGateStatus;
33
+ private enforceProvenance;
28
34
  private upsertPr;
29
35
  private prRef;
30
36
  }
@@ -52,7 +52,10 @@ let FinishUpsertPrCommand = class FinishUpsertPrCommand {
52
52
  prMerger;
53
53
  dashboard;
54
54
  checklistDetector;
55
- constructor(repoRootFinder, aiBranchName, branchNaming, gitExec, buildAffected, mergeState, mergeEnd, prMerger, dashboard, checklistDetector) {
55
+ reviewJsonService;
56
+ gateTokenService;
57
+ provenance;
58
+ constructor(repoRootFinder, aiBranchName, branchNaming, gitExec, buildAffected, mergeState, mergeEnd, prMerger, dashboard, checklistDetector, reviewJsonService, gateTokenService, provenance) {
56
59
  this.repoRootFinder = repoRootFinder;
57
60
  this.aiBranchName = aiBranchName;
58
61
  this.branchNaming = branchNaming;
@@ -63,6 +66,9 @@ let FinishUpsertPrCommand = class FinishUpsertPrCommand {
63
66
  this.prMerger = prMerger;
64
67
  this.dashboard = dashboard;
65
68
  this.checklistDetector = checklistDetector;
69
+ this.reviewJsonService = reviewJsonService;
70
+ this.gateTokenService = gateTokenService;
71
+ this.provenance = provenance;
66
72
  }
67
73
  async run() {
68
74
  const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
@@ -80,7 +86,16 @@ let FinishUpsertPrCommand = class FinishUpsertPrCommand {
80
86
  // here — BEFORE any `gh pr create` — matching the guarantee buildCommand already provides.
81
87
  const checklists = (0, rules_config_1.loadAndValidate)(repoRoot).prGate.checklists;
82
88
  const required = this.checklistDetector.toRequired(this.checklistDetector.detectForRepo(repoRoot, checklists));
83
- const review = (0, rules_config_1.loadReviewJson)((0, rules_config_1.reviewJsonPath)(repoRoot, this.aiBranchName.getFeatureName()), required);
89
+ // review-<id>.json files persist locally between runs, so a re-run after a push re-validates the
90
+ // EXISTING verdicts against the (possibly changed) triggered set for free: an unchanged checklist
91
+ // needs no re-review, a newly-triggered one refuses until its file is written. That is the
92
+ // "full review only when the checklist surface changes" behavior — no special-casing here.
93
+ const review = this.reviewJsonService.loadReviewJson((0, rules_config_1.reviewJsonPath)(repoRoot, this.aiBranchName.getFeatureName()), required);
94
+ // 2c. For any BLOCK checklist that names a reviewer `subagent`, VERIFY (from the harness's own
95
+ // artifacts) that such a subagent actually ran on this branch — the coding agent may not
96
+ // self-certify. Absent CLAUDE_CODE_SESSION_ID this skips with a warning (CI / plain terminal).
97
+ const currentBranch = (0, child_process_1.execSync)('git branch --show-current', { encoding: 'utf8' }).trim();
98
+ this.enforceProvenance(required, currentBranch);
84
99
  // 2b. The build gate validates the WORKING TREE but we push HEAD — so they MUST be identical.
85
100
  this.gitExec.assertCleanTree(repoRoot);
86
101
  // 3. Authoritative build gate, then push, then post.
@@ -90,8 +105,15 @@ let FinishUpsertPrCommand = class FinishUpsertPrCommand {
90
105
  process.stdout.write('\n' + SEP + '📋 Dashboard + PR\n' + SEP + '\n');
91
106
  const title = this.prTitleFrom(review);
92
107
  const input = this.computeDashboardInput(repoRoot, true, review, title, required);
93
- const body = this.dashboard.renderDashboard(input);
108
+ // Append the hidden HMAC gate token bound to the pushed HEAD sha. A valid token in the PR body is
109
+ // proof this gated flow ran + passed on this exact commit — CI (`wp-check-pr`) recomputes it. We
110
+ // reach here only after the build gate + every BLOCK checklist passed, so minting is legitimate.
111
+ const gateSalt = (0, rules_config_1.loadAndValidate)(repoRoot).prGate.gateSalt;
112
+ const headSha = this.gitOut(['rev-parse', 'HEAD']);
113
+ const body = this.dashboard.renderDashboard(input) + this.gateTokenBody(gateSalt, headSha);
94
114
  const result = this.upsertPr(repoRoot, base, body, title, input);
115
+ // Race-free required check: post the commit status on the head sha AFTER the body edit (see method).
116
+ this.postGateStatus(headSha, gateSalt);
95
117
  const prNum = result.prNumber;
96
118
  process.stdout.write('\n' + SEP + '✅ PR finished — here is exactly what I did\n' + SEP + '\n' +
97
119
  ` 1. validated the build gate (authoritative)\n` +
@@ -125,14 +147,65 @@ let FinishUpsertPrCommand = class FinishUpsertPrCommand {
125
147
  const rows = this.checklistRows(required, review);
126
148
  return new dashboard_1.DashboardInput(title, gateResults, disables, buildPassed, forkPoint, featureHead, mainHead, review, rows);
127
149
  }
128
- // Pair each triggered checklist with the AI's acknowledgment from review.json for the dashboard.
129
- // (A BLOCK reaching this point is always acknowledged — loadReviewJson already threw otherwise.)
150
+ // Pair each triggered checklist with its resolved verdict for the dashboard. (A BLOCK reaching this
151
+ // point is always PASS/OVERRIDDEN/ACKED — loadReviewJson already threw on FAIL/MISSING.)
130
152
  checklistRows(required, review) {
131
153
  return required.map((req) => {
132
- const ack = review.checklists.find((a) => a.id === req.id);
133
- return new dashboard_1.ChecklistRow(req.title, req.severity, ack ? ack.acknowledged : false);
154
+ const verdict = this.reviewJsonService.resolveVerdict(req, review.checklists, review.results);
155
+ return new dashboard_1.ChecklistRow(req.title, req.severity, verdict.status, verdict.detail);
134
156
  });
135
157
  }
158
+ // Hidden HMAC gate-token marker (with a leading blank line) to append to the PR body, or '' when the
159
+ // repo sets no gateSalt (byte-identical body to before this feature). Bound to the pushed HEAD sha.
160
+ gateTokenBody(gateSalt, headSha) {
161
+ const marker = this.gateTokenService.gateTokenMarker(gateSalt, headSha);
162
+ return marker === '' ? '' : `\n\n${marker}\n`;
163
+ }
164
+ // Post `webpieces/pr-gate = success` as a commit status on the head sha. This is the authoritative,
165
+ // race-free required check: it is attached to the sha, so unlike the PR body it cannot be read before
166
+ // it exists. No-op when the repo sets no gateSalt. A failure to post (missing statuses:write) is only
167
+ // a warning — the CI wp-check-pr workflow still enforces the gate.
168
+ postGateStatus(headSha, gateSalt) {
169
+ if (gateSalt.trim() === '' || headSha === '')
170
+ return;
171
+ const res = (0, child_process_1.spawnSync)('gh', [
172
+ 'api', '--method', 'POST', `repos/{owner}/{repo}/statuses/${headSha}`,
173
+ '-f', 'state=success',
174
+ '-f', 'context=webpieces/pr-gate',
175
+ '-f', 'description=gated flow ran and passed',
176
+ ], { encoding: 'utf8' });
177
+ if (res.status !== 0) {
178
+ process.stderr.write('⚠️ Could not post the webpieces/pr-gate commit status (needs a token with statuses:write). ' +
179
+ 'The CI wp-check-pr workflow still enforces the gate.\n');
180
+ }
181
+ else {
182
+ process.stdout.write(` posted webpieces/pr-gate ✓ status on ${headSha.slice(0, 12)}\n`);
183
+ }
184
+ }
185
+ // Enforce every BLOCK checklist's `subagent:` provenance requirement. A verified run passes silently;
186
+ // a skipped check (no session id) prints a warning but passes; a missing reviewer subagent throws an
187
+ // InformAiError so the PR does not open until an independent reviewer of that type has run.
188
+ enforceProvenance(required, branch) {
189
+ const errors = [];
190
+ for (const req of required) {
191
+ if (req.severity !== 'BLOCK' || req.subagent.trim() === '')
192
+ continue;
193
+ // A FAIL/MISSING BLOCK already threw in loadReviewJson, so every BLOCK here PASSED review — now
194
+ // additionally require that the independent reviewer subagent actually ran.
195
+ const result = this.provenance.verify(req.subagent.trim(), branch);
196
+ if (result.status === rules_config_1.PROVENANCE_MISSING) {
197
+ errors.push(`Checklist "${req.id}" (${req.title}): ${result.detail}`);
198
+ }
199
+ else if (result.status === rules_config_1.PROVENANCE_SKIPPED) {
200
+ process.stderr.write(`⚠️ Checklist "${req.id}": ${result.detail}\n`);
201
+ }
202
+ }
203
+ if (errors.length > 0) {
204
+ throw new rules_config_1.InformAiError(`${errors.length} checklist(s) require an independent reviewer subagent that did not run — fix, then re-run pnpm wp-finish-upsert-pr:\n\n` +
205
+ errors.map((e) => ` • ${e}`).join('\n') +
206
+ `\n\nSpawn the named reviewer subagent to review the checklist on THIS branch, then re-run.`);
207
+ }
208
+ }
136
209
  // The PR, the remote branch, and the local branch all share the one stable feature name. Look up /
137
210
  // create / merge against `baseBranch` (baseBranchName tolerates a leftover `…wpN` mid-transition).
138
211
  upsertPr(repoRoot, baseBranch, body, title, input) {
@@ -196,6 +269,9 @@ exports.FinishUpsertPrCommand = FinishUpsertPrCommand = tslib_1.__decorate([
196
269
  merge_end_1.MergeEnd,
197
270
  pr_merger_1.PrMerger,
198
271
  dashboard_1.Dashboard,
199
- checklist_detector_1.ChecklistDetector])
272
+ checklist_detector_1.ChecklistDetector,
273
+ rules_config_1.ReviewJsonService,
274
+ rules_config_1.GateTokenService,
275
+ rules_config_1.SubagentProvenanceService])
200
276
  ], FinishUpsertPrCommand);
201
277
  //# sourceMappingURL=finish-upsert-pr-command.js.map
@@ -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, loadReviewJson, prDirFor, reviewJsonPath, ReviewJson, ChecklistAck, RequiredChecklist,\n writeTemplate, RepoRootFinder,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BranchNaming } from '../workflow/branch-naming';\nimport { ChecklistDetector } from '../workflow/checklist-detector';\nimport { GitExec } from '../workflow/git-exec';\nimport { BuildAffected, BuildGateOptions } from '../workflow/build-affected';\nimport { MergeState } from '../workflow/merge-state';\nimport { MergeEnd } from '../workflow/merge-end';\nimport { MergeContext } from '../workflow/merge-start';\nimport { PrMerger, MergeOutcome } from '../workflow/pr-merger';\nimport { Dashboard, DashboardInput, ChecklistRow } 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 ('' when it could not be resolved) plus what actually\n// happened to the merge, so the final summary reports the REAL result rather than assuming success.\nclass UpsertResult {\n prNumber: string;\n merge: MergeOutcome;\n\n constructor(prNumber: string, merge: MergeOutcome) {\n this.prNumber = prNumber;\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 dashboard: Dashboard,\n private readonly checklistDetector: ChecklistDetector,\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 const home = this.mergeState.mergeDirFor(repoRoot, this.aiBranchName.getFeatureName());\n\n // 1. Finish any in-progress conflict resolution: validate + commit + finalize the branch swap.\n const activeDir = this.mergeState.findActiveMergeRunDir(home);\n const marker = activeDir ? this.mergeState.readMergeMarker(activeDir) : null;\n if (activeDir && marker && !marker.validated) {\n await this.mergeEnd.mergeEnd(\n repoRoot, 'wp-finish-upsert-pr', activeDir,\n new MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber),\n marker.conflictedFiles,\n );\n }\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 const checklists = loadAndValidate(repoRoot).prGate.checklists;\n const required = this.checklistDetector.toRequired(this.checklistDetector.detectForRepo(repoRoot, checklists));\n const review = loadReviewJson(reviewJsonPath(repoRoot, this.aiBranchName.getFeatureName()), required);\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 push, then post.\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 this.gitExec.ensurePushed(base);\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 const body = this.dashboard.renderDashboard(input);\n const result = this.upsertPr(repoRoot, base, body, title, input);\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. force-pushed your work to origin/${base}\\n` +\n ` 3. ${prNum ? `updated/created PR #${prNum}` : 'created the PR'} titled: \"${title}\"\\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 }\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 triggered checklist with the AI's acknowledgment from review.json for the dashboard.\n // (A BLOCK reaching this point is always acknowledged — loadReviewJson already threw otherwise.)\n private checklistRows(required: readonly RequiredChecklist[], review: ReviewJson): ChecklistRow[] {\n return required.map((req: RequiredChecklist): ChecklistRow => {\n const ack = review.checklists.find((a: ChecklistAck): boolean => a.id === req.id);\n return new ChecklistRow(req.title, req.severity, ack ? ack.acknowledged : false);\n });\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 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 prNumber = spawnSync(\n 'gh', ['pr', 'list', '--head', baseBranch, '--json', 'number', '--jq', '.[0].number'],\n { encoding: 'utf8' },\n );\n const num = prNumber.status === 0 ? (prNumber.stdout ?? '').trim() : '';\n\n if (num === '') {\n process.stdout.write('Creating PR...\\n');\n const create = spawnSync('gh', ['pr', 'create', '--head', baseBranch, '--base', 'main', '--title', title, '--body-file', bodyFile], { stdio: 'inherit' });\n if (create.status !== 0) {\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 } else {\n process.stdout.write(`Updating PR #${num}...\\n`);\n const edit = spawnSync('gh', ['pr', 'edit', num, '--title', title, '--body-file', bodyFile], { stdio: 'inherit' });\n if (edit.status !== 0) {\n process.stderr.write(`⚠️ gh pr edit failed — PR #${num} still shows its OLD title/body. The new body is in:\\n ` + bodyFile + '\\n');\n }\n }\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, 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,\n writeTemplate, RepoRootFinder, ReviewJsonService,\n GateTokenService, SubagentProvenanceService, PROVENANCE_MISSING, PROVENANCE_SKIPPED,\n InformAiError,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BranchNaming } from '../workflow/branch-naming';\nimport { ChecklistDetector } from '../workflow/checklist-detector';\nimport { GitExec } from '../workflow/git-exec';\nimport { BuildAffected, BuildGateOptions } from '../workflow/build-affected';\nimport { MergeState } from '../workflow/merge-state';\nimport { MergeEnd } from '../workflow/merge-end';\nimport { MergeContext } from '../workflow/merge-start';\nimport { PrMerger, MergeOutcome } from '../workflow/pr-merger';\nimport { Dashboard, DashboardInput, ChecklistRow } 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 ('' when it could not be resolved) plus what actually\n// happened to the merge, so the final summary reports the REAL result rather than assuming success.\nclass UpsertResult {\n prNumber: string;\n merge: MergeOutcome;\n\n constructor(prNumber: string, merge: MergeOutcome) {\n this.prNumber = prNumber;\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 dashboard: Dashboard,\n private readonly checklistDetector: ChecklistDetector,\n private readonly reviewJsonService: ReviewJsonService,\n private readonly gateTokenService: GateTokenService,\n private readonly provenance: SubagentProvenanceService,\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 const home = this.mergeState.mergeDirFor(repoRoot, this.aiBranchName.getFeatureName());\n\n // 1. Finish any in-progress conflict resolution: validate + commit + finalize the branch swap.\n const activeDir = this.mergeState.findActiveMergeRunDir(home);\n const marker = activeDir ? this.mergeState.readMergeMarker(activeDir) : null;\n if (activeDir && marker && !marker.validated) {\n await this.mergeEnd.mergeEnd(\n repoRoot, 'wp-finish-upsert-pr', activeDir,\n new MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber),\n marker.conflictedFiles,\n );\n }\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 const checklists = loadAndValidate(repoRoot).prGate.checklists;\n const required = this.checklistDetector.toRequired(this.checklistDetector.detectForRepo(repoRoot, checklists));\n // review-<id>.json files persist locally between runs, so a re-run after a push re-validates the\n // EXISTING verdicts against the (possibly changed) triggered set for free: an unchanged checklist\n // needs no re-review, a newly-triggered one refuses until its file is written. That is the\n // \"full review only when the checklist surface changes\" behavior — no special-casing here.\n const review = this.reviewJsonService.loadReviewJson(reviewJsonPath(repoRoot, this.aiBranchName.getFeatureName()), 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 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 push, then post.\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 this.gitExec.ensurePushed(base);\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 pushed HEAD sha. A valid token in the PR body is\n // proof this gated flow ran + passed on this exact commit — CI (`wp-check-pr`) recomputes it. We\n // reach here only after the build gate + every BLOCK checklist passed, so minting is legitimate.\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 // Race-free required check: post the commit status on the head sha AFTER the body edit (see method).\n this.postGateStatus(headSha, gateSalt);\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. force-pushed your work to origin/${base}\\n` +\n ` 3. ${prNum ? `updated/created PR #${prNum}` : 'created the PR'} titled: \"${title}\"\\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 }\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 triggered checklist with its resolved verdict for the dashboard. (A BLOCK reaching this\n // point is always PASS/OVERRIDDEN/ACKED — 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.checklists, review.results);\n return new ChecklistRow(req.title, req.severity, 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 // Post `webpieces/pr-gate = success` as a commit status on the head sha. This is the authoritative,\n // race-free required check: it is attached to the sha, so unlike the PR body it cannot be read before\n // it exists. No-op when the repo sets no gateSalt. A failure to post (missing statuses:write) is only\n // a warning — the CI wp-check-pr workflow still enforces the gate.\n private postGateStatus(headSha: string, gateSalt: string): void {\n if (gateSalt.trim() === '' || headSha === '') return;\n const res = spawnSync('gh', [\n 'api', '--method', 'POST', `repos/{owner}/{repo}/statuses/${headSha}`,\n '-f', 'state=success',\n '-f', 'context=webpieces/pr-gate',\n '-f', 'description=gated flow ran and passed',\n ], { encoding: 'utf8' });\n if (res.status !== 0) {\n process.stderr.write(\n '⚠️ Could not post the webpieces/pr-gate commit status (needs a token with statuses:write). ' +\n 'The CI wp-check-pr workflow still enforces the gate.\\n',\n );\n } else {\n process.stdout.write(` posted webpieces/pr-gate ✓ status on ${headSha.slice(0, 12)}\\n`);\n }\n }\n\n // Enforce every BLOCK checklist's `subagent:` provenance requirement. A verified run passes silently;\n // a skipped check (no session id) prints a warning but passes; a missing reviewer subagent throws an\n // InformAiError so the PR does not open until an independent reviewer of that type has run.\n private enforceProvenance(required: readonly RequiredChecklist[], branch: string): void {\n const errors: string[] = [];\n for (const req of required) {\n if (req.severity !== 'BLOCK' || req.subagent.trim() === '') continue;\n // A FAIL/MISSING BLOCK already threw in loadReviewJson, so every BLOCK here PASSED review — now\n // additionally require that the independent reviewer subagent actually ran.\n const result = this.provenance.verify(req.subagent.trim(), branch);\n if (result.status === PROVENANCE_MISSING) {\n errors.push(`Checklist \"${req.id}\" (${req.title}): ${result.detail}`);\n } else if (result.status === PROVENANCE_SKIPPED) {\n process.stderr.write(`⚠️ Checklist \"${req.id}\": ${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 }\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 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 prNumber = spawnSync(\n 'gh', ['pr', 'list', '--head', baseBranch, '--json', 'number', '--jq', '.[0].number'],\n { encoding: 'utf8' },\n );\n const num = prNumber.status === 0 ? (prNumber.stdout ?? '').trim() : '';\n\n if (num === '') {\n process.stdout.write('Creating PR...\\n');\n const create = spawnSync('gh', ['pr', 'create', '--head', baseBranch, '--base', 'main', '--title', title, '--body-file', bodyFile], { stdio: 'inherit' });\n if (create.status !== 0) {\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 } else {\n process.stdout.write(`Updating PR #${num}...\\n`);\n const edit = spawnSync('gh', ['pr', 'edit', num, '--title', title, '--body-file', bodyFile], { stdio: 'inherit' });\n if (edit.status !== 0) {\n process.stderr.write(`⚠️ gh pr edit failed — PR #${num} still shows its OLD title/body. The new body is in:\\n ` + bodyFile + '\\n');\n }\n }\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, 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"]}
@@ -15,5 +15,6 @@ export declare class StartUpsertPrCommand {
15
15
  private readonly checklistDetector;
16
16
  constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, branchNaming: BranchNaming, buildAffected: BuildAffected, gitExec: GitExec, runUpdate: RunUpdate, checklistDetector: ChecklistDetector);
17
17
  run(): Promise<void>;
18
+ private scaffoldCiWorkflow;
18
19
  private updateBranchFromMain;
19
20
  }
@@ -36,6 +36,11 @@ let StartUpsertPrCommand = class StartUpsertPrCommand {
36
36
  const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
37
37
  // Refresh the AI-facing workflow doc so it's present + current for any failure message to cite.
38
38
  (0, rules_config_1.writeTemplate)(repoRoot, 'webpieces.git-workflow.md');
39
+ // When this repo has opted into server-side enforcement (a committed gateSalt), scaffold the CI
40
+ // workflow into the gitignored instruct-ai dir (never .github directly — that would dirty the tree
41
+ // before the clean-tree check) and tell the human to copy + require it. IfMissing so it is written
42
+ // once and never clobbers a customized copy.
43
+ this.scaffoldCiWorkflow(repoRoot);
39
44
  // Precondition: a fully-committed tree. This flow updates, pushes HEAD, and builds — the tooling
40
45
  // must not commit your work for you, and pushing HEAD while building the working tree would let
41
46
  // an uncommitted change build green yet push a stale commit. Fail early if dirty.
@@ -58,6 +63,17 @@ let StartUpsertPrCommand = class StartUpsertPrCommand {
58
63
  `Then run: pnpm wp-finish-upsert-pr\n` +
59
64
  `(It re-validates the build, renders the dashboard with your risk/violations, and creates/updates the PR.)\n\n`);
60
65
  }
66
+ // Scaffold the server-side CI check when (and only when) this repo set a gateSalt. Written to the
67
+ // gitignored instruct-ai dir so it never dirties the tree; the human copies it to .github/workflows
68
+ // and marks it required (webpieces can't set branch protection). No-op for repos with no gateSalt.
69
+ scaffoldCiWorkflow(repoRoot) {
70
+ if ((0, rules_config_1.loadAndValidate)(repoRoot).prGate.gateSalt.trim() === '')
71
+ return;
72
+ (0, rules_config_1.writeTemplateIfMissing)(repoRoot, 'webpieces-pr-gate.yml');
73
+ process.stdout.write(`\nℹ️ Server-side gate enforcement is ON (gateSalt set). If you have not already:\n` +
74
+ ` • copy .webpieces/instruct-ai/webpieces-pr-gate.yml → .github/workflows/ and commit it\n` +
75
+ ` • mark the "webpieces-pr-gate" check REQUIRED in branch protection (repo admin only)\n`);
76
+ }
61
77
  // Bring the branch up to date with main via the shared 3-point engine (in-process). On conflict the
62
78
  // merge process doc it writes names `wp-finish-upsert-pr` as the finish command.
63
79
  async updateBranchFromMain(repoRoot) {
@@ -1 +1 @@
1
- {"version":3,"file":"start-upsert-pr-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/start-upsert-pr-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from 'child_process';\nimport { loadAndValidate, reviewJsonPath, reviewJsonSchemaHint, writeTemplate, CliExitError, RepoRootFinder } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BranchNaming } from '../workflow/branch-naming';\nimport { BuildAffected, BuildGateOptions } from '../workflow/build-affected';\nimport { ChecklistDetector } from '../workflow/checklist-detector';\nimport { GitExec } from '../workflow/git-exec';\nimport { RunUpdate } from '../workflow/run-update';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// START of the AI-first PR flow: the deterministic setup — update from main, push, run the advisory\n// build gate — then hand the AI instructions to WRITE review.json and run `wp-finish-upsert-pr` (which\n// reads it and posts the PR). This command NEVER creates/updates a PR; all `gh` posting lives in finish.\n@injectable(bindingScopeValues.Singleton)\nexport class StartUpsertPrCommand {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly aiBranchName: AiBranchName,\n private readonly branchNaming: BranchNaming,\n private readonly buildAffected: BuildAffected,\n private readonly gitExec: GitExec,\n private readonly runUpdate: RunUpdate,\n private readonly checklistDetector: ChecklistDetector,\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\n // Precondition: a fully-committed tree. This flow updates, pushes HEAD, and builds — the tooling\n // must not commit your work for you, and pushing HEAD while building the working tree would let\n // an uncommitted change build green yet push a stale commit. Fail early if dirty.\n this.gitExec.assertCleanTree(repoRoot);\n\n await this.updateBranchFromMain(repoRoot);\n // Local branch, remote branch, and PR share the one stable feature name.\n this.gitExec.ensurePushed(this.branchNaming.baseBranchName(execSync('git branch --show-current', { encoding: 'utf8' }).trim()));\n\n // Advisory build gate — early feedback before the AI writes review.json. wp-finish-upsert-pr\n // runs the authoritative one. Both go through the same runBuildGate (only the framing differs).\n this.buildAffected.runBuildGate(repoRoot, new BuildGateOptions(\n '② Build gate (nx affected)', 'pnpm wp-start-upsert-pr', 'Build failed — fix it before reviewing.',\n ));\n\n // Hand the AI its next step: write review.json, then run finish (which posts the PR). Compute the\n // consumer checklists this diff triggered so the schema hint names the exact docs to read BEFORE\n // review.json is written (empty for repos with no checklists ⇒ the hint is unchanged).\n const checklists = loadAndValidate(repoRoot).prGate.checklists;\n const required = this.checklistDetector.toRequired(this.checklistDetector.detectForRepo(repoRoot, checklists));\n const reviewPath = reviewJsonPath(repoRoot, this.aiBranchName.getFeatureName());\n process.stdout.write('\\n' + SEP + '③ Review the PR, then finish\\n' + SEP + '\\n');\n process.stdout.write(\n `Branch is updated, pushed, and the build gate passed. Now review your own changes and\\n` +\n `${reviewJsonSchemaHint(reviewPath, required)}\\n\\n` +\n `Then run: pnpm wp-finish-upsert-pr\\n` +\n `(It re-validates the build, renders the dashboard with your risk/violations, and creates/updates the PR.)\\n\\n`,\n );\n }\n\n // Bring the branch up to date with main via the shared 3-point engine (in-process). On conflict the\n // merge process doc it writes names `wp-finish-upsert-pr` as the finish command.\n private async updateBranchFromMain(repoRoot: string): Promise<void> {\n process.stdout.write('\\n' + SEP + '① Updating branch from main\\n' + SEP + '\\n');\n const outcome = await this.runUpdate.runUpdateFromMain(repoRoot, 'wp-start-upsert-pr', 'wp-finish-upsert-pr');\n if (outcome === 'conflict' || outcome === 'unvalidatedResume') {\n throw new CliExitError(2,\n '\\n⏸️ Conflicts — resolve them, then run pnpm wp-finish-upsert-pr (it validates the merge AND finishes the PR).',\n );\n }\n }\n}\n"]}
1
+ {"version":3,"file":"start-upsert-pr-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/start-upsert-pr-command.ts"],"names":[],"mappings":";;;;AAAA,iDAAyC;AACzC,0DAAqK;AACrK,yCAA2D;AAC3D,2EAAgE;AAChE,6DAAyD;AACzD,+DAA6E;AAC7E,uEAAmE;AACnE,mDAA+C;AAC/C,uDAAmD;AAEnD,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAEvE,oGAAoG;AACpG,uGAAuG;AACvG,yGAAyG;AAElG,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IAER;IACA;IACA;IACA;IACA;IACA;IACA;IAPrB,YACqB,cAA8B,EAC9B,YAA0B,EAC1B,YAA0B,EAC1B,aAA4B,EAC5B,OAAgB,EAChB,SAAoB,EACpB,iBAAoC;QANpC,mBAAc,GAAd,cAAc,CAAgB;QAC9B,iBAAY,GAAZ,YAAY,CAAc;QAC1B,iBAAY,GAAZ,YAAY,CAAc;QAC1B,kBAAa,GAAb,aAAa,CAAe;QAC5B,YAAO,GAAP,OAAO,CAAS;QAChB,cAAS,GAAT,SAAS,CAAW;QACpB,sBAAiB,GAAjB,iBAAiB,CAAmB;IACtD,CAAC;IAEJ,KAAK,CAAC,GAAG;QACL,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACpE,gGAAgG;QAChG,IAAA,4BAAa,EAAC,QAAQ,EAAE,2BAA2B,CAAC,CAAC;QACrD,gGAAgG;QAChG,mGAAmG;QACnG,mGAAmG;QACnG,6CAA6C;QAC7C,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QAElC,iGAAiG;QACjG,gGAAgG;QAChG,kFAAkF;QAClF,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAEvC,MAAM,IAAI,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QAC1C,yEAAyE;QACzE,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,IAAA,wBAAQ,EAAC,2BAA2B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAEhI,6FAA6F;QAC7F,gGAAgG;QAChG,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,QAAQ,EAAE,IAAI,iCAAgB,CAC1D,4BAA4B,EAAE,yBAAyB,EAAE,yCAAyC,CACrG,CAAC,CAAC;QAEH,kGAAkG;QAClG,iGAAiG;QACjG,uFAAuF;QACvF,MAAM,UAAU,GAAG,IAAA,8BAAe,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC;QAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,IAAI,CAAC,iBAAiB,CAAC,aAAa,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC;QAC/G,MAAM,UAAU,GAAG,IAAA,6BAAc,EAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC,CAAC;QAChF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,gCAAgC,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;QACjF,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,yFAAyF;YACzF,GAAG,IAAA,mCAAoB,EAAC,UAAU,EAAE,QAAQ,CAAC,MAAM;YACnD,uCAAuC;YACvC,+GAA+G,CAClH,CAAC;IACN,CAAC;IAED,kGAAkG;IAClG,oGAAoG;IACpG,mGAAmG;IAC3F,kBAAkB,CAAC,QAAgB;QACvC,IAAI,IAAA,8BAAe,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO;QACpE,IAAA,qCAAsB,EAAC,QAAQ,EAAE,uBAAuB,CAAC,CAAC;QAC1D,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,qFAAqF;YACrF,gGAAgG;YAChG,2FAA2F,CAC9F,CAAC;IACN,CAAC;IAED,oGAAoG;IACpG,iFAAiF;IACzE,KAAK,CAAC,oBAAoB,CAAC,QAAgB;QAC/C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,+BAA+B,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;QAChF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,QAAQ,EAAE,oBAAoB,EAAE,qBAAqB,CAAC,CAAC;QAC9G,IAAI,OAAO,KAAK,UAAU,IAAI,OAAO,KAAK,mBAAmB,EAAE,CAAC;YAC5D,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,iHAAiH,CACpH,CAAC;QACN,CAAC;IACL,CAAC;CACJ,CAAA;AA3EY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAGA,6BAAc;QAChB,mCAAY;QACZ,4BAAY;QACX,8BAAa;QACnB,kBAAO;QACL,sBAAS;QACD,sCAAiB;GARhD,oBAAoB,CA2EhC","sourcesContent":["import { execSync } from 'child_process';\nimport { loadAndValidate, reviewJsonPath, reviewJsonSchemaHint, writeTemplate, writeTemplateIfMissing, CliExitError, RepoRootFinder } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BranchNaming } from '../workflow/branch-naming';\nimport { BuildAffected, BuildGateOptions } from '../workflow/build-affected';\nimport { ChecklistDetector } from '../workflow/checklist-detector';\nimport { GitExec } from '../workflow/git-exec';\nimport { RunUpdate } from '../workflow/run-update';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// START of the AI-first PR flow: the deterministic setup — update from main, push, run the advisory\n// build gate — then hand the AI instructions to WRITE review.json and run `wp-finish-upsert-pr` (which\n// reads it and posts the PR). This command NEVER creates/updates a PR; all `gh` posting lives in finish.\n@injectable(bindingScopeValues.Singleton)\nexport class StartUpsertPrCommand {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly aiBranchName: AiBranchName,\n private readonly branchNaming: BranchNaming,\n private readonly buildAffected: BuildAffected,\n private readonly gitExec: GitExec,\n private readonly runUpdate: RunUpdate,\n private readonly checklistDetector: ChecklistDetector,\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 // When this repo has opted into server-side enforcement (a committed gateSalt), scaffold the CI\n // workflow into the gitignored instruct-ai dir (never .github directly — that would dirty the tree\n // before the clean-tree check) and tell the human to copy + require it. IfMissing so it is written\n // once and never clobbers a customized copy.\n this.scaffoldCiWorkflow(repoRoot);\n\n // Precondition: a fully-committed tree. This flow updates, pushes HEAD, and builds — the tooling\n // must not commit your work for you, and pushing HEAD while building the working tree would let\n // an uncommitted change build green yet push a stale commit. Fail early if dirty.\n this.gitExec.assertCleanTree(repoRoot);\n\n await this.updateBranchFromMain(repoRoot);\n // Local branch, remote branch, and PR share the one stable feature name.\n this.gitExec.ensurePushed(this.branchNaming.baseBranchName(execSync('git branch --show-current', { encoding: 'utf8' }).trim()));\n\n // Advisory build gate — early feedback before the AI writes review.json. wp-finish-upsert-pr\n // runs the authoritative one. Both go through the same runBuildGate (only the framing differs).\n this.buildAffected.runBuildGate(repoRoot, new BuildGateOptions(\n '② Build gate (nx affected)', 'pnpm wp-start-upsert-pr', 'Build failed — fix it before reviewing.',\n ));\n\n // Hand the AI its next step: write review.json, then run finish (which posts the PR). Compute the\n // consumer checklists this diff triggered so the schema hint names the exact docs to read BEFORE\n // review.json is written (empty for repos with no checklists ⇒ the hint is unchanged).\n const checklists = loadAndValidate(repoRoot).prGate.checklists;\n const required = this.checklistDetector.toRequired(this.checklistDetector.detectForRepo(repoRoot, checklists));\n const reviewPath = reviewJsonPath(repoRoot, this.aiBranchName.getFeatureName());\n process.stdout.write('\\n' + SEP + '③ Review the PR, then finish\\n' + SEP + '\\n');\n process.stdout.write(\n `Branch is updated, pushed, and the build gate passed. Now review your own changes and\\n` +\n `${reviewJsonSchemaHint(reviewPath, required)}\\n\\n` +\n `Then run: pnpm wp-finish-upsert-pr\\n` +\n `(It re-validates the build, renders the dashboard with your risk/violations, and creates/updates the PR.)\\n\\n`,\n );\n }\n\n // Scaffold the server-side CI check when (and only when) this repo set a gateSalt. Written to the\n // gitignored instruct-ai dir so it never dirties the tree; the human copies it to .github/workflows\n // and marks it required (webpieces can't set branch protection). No-op for repos with no gateSalt.\n private scaffoldCiWorkflow(repoRoot: string): void {\n if (loadAndValidate(repoRoot).prGate.gateSalt.trim() === '') return;\n writeTemplateIfMissing(repoRoot, 'webpieces-pr-gate.yml');\n process.stdout.write(\n `\\nℹ️ Server-side gate enforcement is ON (gateSalt set). If you have not already:\\n` +\n ` • copy .webpieces/instruct-ai/webpieces-pr-gate.yml → .github/workflows/ and commit it\\n` +\n ` • mark the \"webpieces-pr-gate\" check REQUIRED in branch protection (repo admin only)\\n`,\n );\n }\n\n // Bring the branch up to date with main via the shared 3-point engine (in-process). On conflict the\n // merge process doc it writes names `wp-finish-upsert-pr` as the finish command.\n private async updateBranchFromMain(repoRoot: string): Promise<void> {\n process.stdout.write('\\n' + SEP + '① Updating branch from main\\n' + SEP + '\\n');\n const outcome = await this.runUpdate.runUpdateFromMain(repoRoot, 'wp-start-upsert-pr', 'wp-finish-upsert-pr');\n if (outcome === 'conflict' || outcome === 'unvalidatedResume') {\n throw new CliExitError(2,\n '\\n⏸️ Conflicts — resolve them, then run pnpm wp-finish-upsert-pr (it validates the merge AND finishes the PR).',\n );\n }\n }\n}\n"]}
@@ -4,6 +4,7 @@ import { StartUpsertPrCommand } from './commands/start-upsert-pr-command';
4
4
  import { FinishUpsertPrCommand } from './commands/finish-upsert-pr-command';
5
5
  import { CleanupCommand } from './commands/cleanup-command';
6
6
  import { LandPrCommand } from './commands/land-pr-command';
7
+ import { CheckPrCommand } from './commands/check-pr-command';
7
8
  /**
8
9
  * The pr-gate application root. `container.get(PrGateApp)` resolves the entire workflow DAG (the command
9
10
  * classes → the injected git/merge/dashboard services). `@DocumentDesign` marks it the
@@ -17,7 +18,8 @@ export declare class PrGateApp {
17
18
  private readonly finishUpsertPrCommand;
18
19
  private readonly cleanupCommand;
19
20
  private readonly landPrCommand;
20
- constructor(startUpdateCommand: StartUpdateCommand, finishUpdateCommand: FinishUpdateCommand, startUpsertPrCommand: StartUpsertPrCommand, finishUpsertPrCommand: FinishUpsertPrCommand, cleanupCommand: CleanupCommand, landPrCommand: LandPrCommand);
21
+ private readonly checkPrCommand;
22
+ constructor(startUpdateCommand: StartUpdateCommand, finishUpdateCommand: FinishUpdateCommand, startUpsertPrCommand: StartUpsertPrCommand, finishUpsertPrCommand: FinishUpsertPrCommand, cleanupCommand: CleanupCommand, landPrCommand: LandPrCommand, checkPrCommand: CheckPrCommand);
21
23
  /** `wp-start-update`: 3-point squash-update from main (no PR). */
22
24
  startUpdate(): Promise<void>;
23
25
  /** `wp-finish-update`: validate + finalize a resolved 3-point merge (no PR). */
@@ -30,4 +32,6 @@ export declare class PrGateApp {
30
32
  cleanup(): Promise<void>;
31
33
  /** `wp-land-pr`: squash-merge this branch's PR into main with the compact commit body. */
32
34
  landPr(): Promise<void>;
35
+ /** `wp-check-pr`: READ-ONLY CI check — verify the PR body carries a valid HMAC gate token for its head sha. */
36
+ checkPr(): Promise<void>;
33
37
  }
@@ -10,6 +10,7 @@ const start_upsert_pr_command_1 = require("./commands/start-upsert-pr-command");
10
10
  const finish_upsert_pr_command_1 = require("./commands/finish-upsert-pr-command");
11
11
  const cleanup_command_1 = require("./commands/cleanup-command");
12
12
  const land_pr_command_1 = require("./commands/land-pr-command");
13
+ const check_pr_command_1 = require("./commands/check-pr-command");
13
14
  /**
14
15
  * The pr-gate application root. `container.get(PrGateApp)` resolves the entire workflow DAG (the command
15
16
  * classes → the injected git/merge/dashboard services). `@DocumentDesign` marks it the
@@ -23,13 +24,15 @@ let PrGateApp = class PrGateApp {
23
24
  finishUpsertPrCommand;
24
25
  cleanupCommand;
25
26
  landPrCommand;
26
- constructor(startUpdateCommand, finishUpdateCommand, startUpsertPrCommand, finishUpsertPrCommand, cleanupCommand, landPrCommand) {
27
+ checkPrCommand;
28
+ constructor(startUpdateCommand, finishUpdateCommand, startUpsertPrCommand, finishUpsertPrCommand, cleanupCommand, landPrCommand, checkPrCommand) {
27
29
  this.startUpdateCommand = startUpdateCommand;
28
30
  this.finishUpdateCommand = finishUpdateCommand;
29
31
  this.startUpsertPrCommand = startUpsertPrCommand;
30
32
  this.finishUpsertPrCommand = finishUpsertPrCommand;
31
33
  this.cleanupCommand = cleanupCommand;
32
34
  this.landPrCommand = landPrCommand;
35
+ this.checkPrCommand = checkPrCommand;
33
36
  }
34
37
  /** `wp-start-update`: 3-point squash-update from main (no PR). */
35
38
  startUpdate() {
@@ -55,6 +58,10 @@ let PrGateApp = class PrGateApp {
55
58
  landPr() {
56
59
  return this.landPrCommand.run();
57
60
  }
61
+ /** `wp-check-pr`: READ-ONLY CI check — verify the PR body carries a valid HMAC gate token for its head sha. */
62
+ checkPr() {
63
+ return this.checkPrCommand.run();
64
+ }
58
65
  };
59
66
  exports.PrGateApp = PrGateApp;
60
67
  exports.PrGateApp = PrGateApp = tslib_1.__decorate([
@@ -65,6 +72,7 @@ exports.PrGateApp = PrGateApp = tslib_1.__decorate([
65
72
  start_upsert_pr_command_1.StartUpsertPrCommand,
66
73
  finish_upsert_pr_command_1.FinishUpsertPrCommand,
67
74
  cleanup_command_1.CleanupCommand,
68
- land_pr_command_1.LandPrCommand])
75
+ land_pr_command_1.LandPrCommand,
76
+ check_pr_command_1.CheckPrCommand])
69
77
  ], PrGateApp);
70
78
  //# sourceMappingURL=pr-gate-app.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pr-gate-app.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/pr-gate-app.ts"],"names":[],"mappings":";;;;AAAA,0DAAyD;AACzD,yCAA2D;AAC3D,0EAAqE;AACrE,4EAAuE;AACvE,gFAA0E;AAC1E,kFAA4E;AAC5E,gEAA4D;AAC5D,gEAA2D;AAE3D;;;;;GAKG;AAGI,IAAM,SAAS,GAAf,MAAM,SAAS;IAEG;IACA;IACA;IACA;IACA;IACA;IANrB,YACqB,kBAAsC,EACtC,mBAAwC,EACxC,oBAA0C,EAC1C,qBAA4C,EAC5C,cAA8B,EAC9B,aAA4B;QAL5B,uBAAkB,GAAlB,kBAAkB,CAAoB;QACtC,wBAAmB,GAAnB,mBAAmB,CAAqB;QACxC,yBAAoB,GAApB,oBAAoB,CAAsB;QAC1C,0BAAqB,GAArB,qBAAqB,CAAuB;QAC5C,mBAAc,GAAd,cAAc,CAAgB;QAC9B,kBAAa,GAAb,aAAa,CAAe;IAC9C,CAAC;IAEJ,kEAAkE;IAClE,WAAW;QACP,OAAO,IAAI,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC;IACzC,CAAC;IAED,gFAAgF;IAChF,YAAY;QACR,OAAO,IAAI,CAAC,mBAAmB,CAAC,GAAG,EAAE,CAAC;IAC1C,CAAC;IAED,+FAA+F;IAC/F,aAAa;QACT,OAAO,IAAI,CAAC,oBAAoB,CAAC,GAAG,EAAE,CAAC;IAC3C,CAAC;IAED,oGAAoG;IACpG,cAAc;QACV,OAAO,IAAI,CAAC,qBAAqB,CAAC,GAAG,EAAE,CAAC;IAC5C,CAAC;IAED,gGAAgG;IAChG,OAAO;QACH,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,CAAC;IACrC,CAAC;IAED,0FAA0F;IAC1F,MAAM;QACF,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC;IACpC,CAAC;CACJ,CAAA;AAvCY,8BAAS;oBAAT,SAAS;IAFrB,IAAA,6BAAc,GAAE;IAChB,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAGI,yCAAkB;QACjB,2CAAmB;QAClB,8CAAoB;QACnB,gDAAqB;QAC5B,gCAAc;QACf,+BAAa;GAPxC,SAAS,CAuCrB","sourcesContent":["import { DocumentDesign } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { StartUpdateCommand } from './commands/start-update-command';\nimport { FinishUpdateCommand } from './commands/finish-update-command';\nimport { StartUpsertPrCommand } from './commands/start-upsert-pr-command';\nimport { FinishUpsertPrCommand } from './commands/finish-upsert-pr-command';\nimport { CleanupCommand } from './commands/cleanup-command';\nimport { LandPrCommand } from './commands/land-pr-command';\n\n/**\n * The pr-gate application root. `container.get(PrGateApp)` resolves the entire workflow DAG (the command\n * classes → the injected git/merge/dashboard services). `@DocumentDesign` marks it the\n * top-of-DAG the DI-design analyzer roots on, so `role:app` pr-gate draws its design. Each `bin/*`\n * entry resolves THIS and calls the matching command method.\n */\n@DocumentDesign()\n@injectable(bindingScopeValues.Singleton)\nexport class PrGateApp {\n constructor(\n private readonly startUpdateCommand: StartUpdateCommand,\n private readonly finishUpdateCommand: FinishUpdateCommand,\n private readonly startUpsertPrCommand: StartUpsertPrCommand,\n private readonly finishUpsertPrCommand: FinishUpsertPrCommand,\n private readonly cleanupCommand: CleanupCommand,\n private readonly landPrCommand: LandPrCommand,\n ) {}\n\n /** `wp-start-update`: 3-point squash-update from main (no PR). */\n startUpdate(): Promise<void> {\n return this.startUpdateCommand.run();\n }\n\n /** `wp-finish-update`: validate + finalize a resolved 3-point merge (no PR). */\n finishUpdate(): Promise<void> {\n return this.finishUpdateCommand.run();\n }\n\n /** `wp-start-upsert-pr`: update from main, push, advisory build gate, hand off review.json. */\n startUpsertPr(): Promise<void> {\n return this.startUpsertPrCommand.run();\n }\n\n /** `wp-finish-upsert-pr`: finalize merge, authoritative build gate, dashboard, create/update PR. */\n finishUpsertPr(): Promise<void> {\n return this.finishUpsertPrCommand.run();\n }\n\n /** `wp-cleanup`: delete local branches whose PR is already merged (or that hold no commits). */\n cleanup(): Promise<void> {\n return this.cleanupCommand.run();\n }\n\n /** `wp-land-pr`: squash-merge this branch's PR into main with the compact commit body. */\n landPr(): Promise<void> {\n return this.landPrCommand.run();\n }\n}\n"]}
1
+ {"version":3,"file":"pr-gate-app.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/pr-gate-app.ts"],"names":[],"mappings":";;;;AAAA,0DAAyD;AACzD,yCAA2D;AAC3D,0EAAqE;AACrE,4EAAuE;AACvE,gFAA0E;AAC1E,kFAA4E;AAC5E,gEAA4D;AAC5D,gEAA2D;AAC3D,kEAA6D;AAE7D;;;;;GAKG;AAGI,IAAM,SAAS,GAAf,MAAM,SAAS;IAEG;IACA;IACA;IACA;IACA;IACA;IACA;IAPrB,YACqB,kBAAsC,EACtC,mBAAwC,EACxC,oBAA0C,EAC1C,qBAA4C,EAC5C,cAA8B,EAC9B,aAA4B,EAC5B,cAA8B;QAN9B,uBAAkB,GAAlB,kBAAkB,CAAoB;QACtC,wBAAmB,GAAnB,mBAAmB,CAAqB;QACxC,yBAAoB,GAApB,oBAAoB,CAAsB;QAC1C,0BAAqB,GAArB,qBAAqB,CAAuB;QAC5C,mBAAc,GAAd,cAAc,CAAgB;QAC9B,kBAAa,GAAb,aAAa,CAAe;QAC5B,mBAAc,GAAd,cAAc,CAAgB;IAChD,CAAC;IAEJ,kEAAkE;IAClE,WAAW;QACP,OAAO,IAAI,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC;IACzC,CAAC;IAED,gFAAgF;IAChF,YAAY;QACR,OAAO,IAAI,CAAC,mBAAmB,CAAC,GAAG,EAAE,CAAC;IAC1C,CAAC;IAED,+FAA+F;IAC/F,aAAa;QACT,OAAO,IAAI,CAAC,oBAAoB,CAAC,GAAG,EAAE,CAAC;IAC3C,CAAC;IAED,oGAAoG;IACpG,cAAc;QACV,OAAO,IAAI,CAAC,qBAAqB,CAAC,GAAG,EAAE,CAAC;IAC5C,CAAC;IAED,gGAAgG;IAChG,OAAO;QACH,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,CAAC;IACrC,CAAC;IAED,0FAA0F;IAC1F,MAAM;QACF,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC;IACpC,CAAC;IAED,+GAA+G;IAC/G,OAAO;QACH,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,CAAC;IACrC,CAAC;CACJ,CAAA;AA7CY,8BAAS;oBAAT,SAAS;IAFrB,IAAA,6BAAc,GAAE;IAChB,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAGI,yCAAkB;QACjB,2CAAmB;QAClB,8CAAoB;QACnB,gDAAqB;QAC5B,gCAAc;QACf,+BAAa;QACZ,iCAAc;GAR1C,SAAS,CA6CrB","sourcesContent":["import { DocumentDesign } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { StartUpdateCommand } from './commands/start-update-command';\nimport { FinishUpdateCommand } from './commands/finish-update-command';\nimport { StartUpsertPrCommand } from './commands/start-upsert-pr-command';\nimport { FinishUpsertPrCommand } from './commands/finish-upsert-pr-command';\nimport { CleanupCommand } from './commands/cleanup-command';\nimport { LandPrCommand } from './commands/land-pr-command';\nimport { CheckPrCommand } from './commands/check-pr-command';\n\n/**\n * The pr-gate application root. `container.get(PrGateApp)` resolves the entire workflow DAG (the command\n * classes → the injected git/merge/dashboard services). `@DocumentDesign` marks it the\n * top-of-DAG the DI-design analyzer roots on, so `role:app` pr-gate draws its design. Each `bin/*`\n * entry resolves THIS and calls the matching command method.\n */\n@DocumentDesign()\n@injectable(bindingScopeValues.Singleton)\nexport class PrGateApp {\n constructor(\n private readonly startUpdateCommand: StartUpdateCommand,\n private readonly finishUpdateCommand: FinishUpdateCommand,\n private readonly startUpsertPrCommand: StartUpsertPrCommand,\n private readonly finishUpsertPrCommand: FinishUpsertPrCommand,\n private readonly cleanupCommand: CleanupCommand,\n private readonly landPrCommand: LandPrCommand,\n private readonly checkPrCommand: CheckPrCommand,\n ) {}\n\n /** `wp-start-update`: 3-point squash-update from main (no PR). */\n startUpdate(): Promise<void> {\n return this.startUpdateCommand.run();\n }\n\n /** `wp-finish-update`: validate + finalize a resolved 3-point merge (no PR). */\n finishUpdate(): Promise<void> {\n return this.finishUpdateCommand.run();\n }\n\n /** `wp-start-upsert-pr`: update from main, push, advisory build gate, hand off review.json. */\n startUpsertPr(): Promise<void> {\n return this.startUpsertPrCommand.run();\n }\n\n /** `wp-finish-upsert-pr`: finalize merge, authoritative build gate, dashboard, create/update PR. */\n finishUpsertPr(): Promise<void> {\n return this.finishUpsertPrCommand.run();\n }\n\n /** `wp-cleanup`: delete local branches whose PR is already merged (or that hold no commits). */\n cleanup(): Promise<void> {\n return this.cleanupCommand.run();\n }\n\n /** `wp-land-pr`: squash-merge this branch's PR into main with the compact commit body. */\n landPr(): Promise<void> {\n return this.landPrCommand.run();\n }\n\n /** `wp-check-pr`: READ-ONLY CI check — verify the PR body carries a valid HMAC gate token for its head sha. */\n checkPr(): Promise<void> {\n return this.checkPrCommand.run();\n }\n}\n"]}
@@ -21,6 +21,7 @@ export declare class ChecklistDetector {
21
21
  detect(defs: readonly ChecklistDefinition[], changedFiles: readonly string[], addedLinesByFile: ReadonlyMap<string, string[]>): TriggeredChecklist[];
22
22
  private matchContent;
23
23
  detectForRepo(repoRoot: string, defs: readonly ChecklistDefinition[]): TriggeredChecklist[];
24
+ detectForRange(repoRoot: string, defs: readonly ChecklistDefinition[], base: string, head?: string): TriggeredChecklist[];
24
25
  private addedLines;
25
26
  toRequired(triggered: readonly TriggeredChecklist[]): RequiredChecklist[];
26
27
  }
@@ -79,18 +79,25 @@ let ChecklistDetector = class ChecklistDetector {
79
79
  if (defs.length === 0)
80
80
  return [];
81
81
  const range = this.diffScope.resolveBase(repoRoot);
82
- const base = range.base;
83
- if (!base)
82
+ if (!range.base)
83
+ return [];
84
+ return this.detectForRange(repoRoot, defs, range.base, range.head);
85
+ }
86
+ // Same detection against an EXPLICIT (base, head) — used by CI (`wp-check-pr`), where the useful
87
+ // range is the PR's merge base .. head rather than the local branch's inferred base. Keep tsOnly=false
88
+ // (see below) so a re-implementation in CI never silently drops the very files a checklist keys on.
89
+ detectForRange(repoRoot, defs, base, head) {
90
+ if (defs.length === 0 || !base)
84
91
  return [];
85
92
  // tsOnly:false is REQUIRED and load-bearing — the default (true) restricts to *.ts/*.tsx AND
86
93
  // drops test files, silently discarding every *.sql / *.gql / Dockerfile / .env* / metadata file
87
94
  // a checklist most wants to key on. The default would produce "no checklists triggered".
88
95
  const opts = new rules_config_1.ChangedFilesOptions();
89
96
  opts.tsOnly = false;
90
- const changedFiles = this.diffScope.getChangedFiles(repoRoot, base, range.head, opts);
97
+ const changedFiles = this.diffScope.getChangedFiles(repoRoot, base, head, opts);
91
98
  const addedLinesByFile = new Map();
92
99
  for (const file of changedFiles) {
93
- const diff = this.diffScope.getFileDiff(repoRoot, file, base, range.head);
100
+ const diff = this.diffScope.getFileDiff(repoRoot, file, base, head);
94
101
  addedLinesByFile.set(file, this.addedLines(diff));
95
102
  }
96
103
  return this.detect(defs, changedFiles, addedLinesByFile);
@@ -108,7 +115,7 @@ let ChecklistDetector = class ChecklistDetector {
108
115
  // Flatten triggered checklists into the RequiredChecklist shape that review.json validation + the
109
116
  // schema hint consume.
110
117
  toRequired(triggered) {
111
- return triggered.map((t) => new rules_config_1.RequiredChecklist(t.def.id, t.def.title, t.def.severity, t.def.docs, t.def.blockMessage, t.matchedFiles));
118
+ return triggered.map((t) => new rules_config_1.RequiredChecklist(t.def.id, t.def.title, t.def.severity, t.def.docs, t.def.blockMessage, t.matchedFiles, t.def.subagent));
112
119
  }
113
120
  };
114
121
  exports.ChecklistDetector = ChecklistDetector;
@@ -1 +1 @@
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 that the branch's diff actually triggered, plus WHY it triggered (the matched files and,\n// for content-keyed checklists, the specific added lines). Data-only (per CLAUDE.md, classes for data).\nexport class TriggeredChecklist {\n def: ChecklistDefinition;\n matchedFiles: string[];\n matchedContent: string[]; // the added diff lines that matched a contentPattern ([] for path-only)\n\n constructor(def: ChecklistDefinition, matchedFiles: string[], matchedContent: string[]) {\n this.def = def;\n this.matchedFiles = matchedFiles;\n this.matchedContent = matchedContent;\n }\n}\n\n/**\n * Decides which consumer review checklists a branch triggered, from what the diff CHANGED. Path\n * triggers use `isPathExcluded` — the SAME minimatch-based matcher rules-config already shares across\n * exclude-paths and the rule validators — so a checklist glob behaves identically to those. (The\n * Dashboard's own hand-rolled gate matcher is deliberately left untouched: changing gate semantics is a\n * separate behavior change per the backlog note, and gates are a different feature.)\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 // Pure matching — unit-testable without git. Given the changed files and the added (`+`) lines per\n // file, return every non-disabled checklist that fired.\n detect(\n defs: readonly ChecklistDefinition[],\n changedFiles: readonly string[],\n addedLinesByFile: ReadonlyMap<string, string[]>,\n ): TriggeredChecklist[] {\n const triggered: TriggeredChecklist[] = [];\n for (const def of defs) {\n if (def.disabled) continue;\n const candidates = def.patterns.length === 0\n ? [...changedFiles]\n : changedFiles.filter((f: string): boolean => isPathExcluded(f, def.patterns));\n if (candidates.length === 0) continue;\n\n if (def.contentPatterns.length === 0) {\n // Path-only trigger: any candidate file firing is enough.\n triggered.push(new TriggeredChecklist(def, candidates, []));\n continue;\n }\n const hit = this.matchContent(def, candidates, addedLinesByFile);\n if (hit.matchedFiles.length > 0) triggered.push(hit);\n }\n return triggered;\n }\n\n // Content trigger: keep only candidate files with an added line matching a contentPattern. Keying on\n // ADDED lines is why contentPatterns exists — path globs structurally cannot express \"a line adding\n // @Post( / @Cron( / CloudTasksClient\".\n private matchContent(\n def: ChecklistDefinition,\n candidates: readonly string[],\n addedLinesByFile: ReadonlyMap<string, string[]>,\n ): TriggeredChecklist {\n const regexes = def.contentPatterns.map((p: string): RegExp => new RegExp(p));\n const matchedFiles: string[] = [];\n const matchedContent: string[] = [];\n for (const file of candidates) {\n const lines = addedLinesByFile.get(file) ?? [];\n const hits = lines.filter((l: string): boolean => regexes.some((rx: RegExp): boolean => rx.test(l)));\n if (hits.length > 0) {\n matchedFiles.push(file);\n for (const h of hits) matchedContent.push(h);\n }\n }\n return new TriggeredChecklist(def, matchedFiles, matchedContent);\n }\n\n // Convenience for the pr-gate commands: resolve the diff base, gather changed files + added lines\n // from git (via the shared DiffScope), then detect. Returns [] when there is no base to diff against.\n detectForRepo(repoRoot: string, defs: readonly ChecklistDefinition[]): TriggeredChecklist[] {\n if (defs.length === 0) return [];\n const range = this.diffScope.resolveBase(repoRoot);\n const base = range.base;\n if (!base) return [];\n\n // tsOnly:false is REQUIRED and load-bearing — the default (true) restricts to *.ts/*.tsx AND\n // drops test files, silently discarding every *.sql / *.gql / Dockerfile / .env* / metadata file\n // a checklist most wants to key on. The default would produce \"no checklists triggered\".\n const opts = new ChangedFilesOptions();\n opts.tsOnly = false;\n const changedFiles = this.diffScope.getChangedFiles(repoRoot, base, range.head, opts);\n\n const addedLinesByFile = new Map<string, string[]>();\n for (const file of changedFiles) {\n const diff = this.diffScope.getFileDiff(repoRoot, file, base, range.head);\n addedLinesByFile.set(file, this.addedLines(diff));\n }\n return this.detect(defs, changedFiles, addedLinesByFile);\n }\n\n // The added-content lines of a single-file diff: `+` lines with the marker stripped, excluding the\n // `+++` file header (mirrors DiffScope.getChangedLineNumbers' own `+`/`+++` handling).\n private addedLines(diff: string): string[] {\n const out: string[] = [];\n for (const line of diff.split('\\n')) {\n if (line.startsWith('+') && !line.startsWith('+++')) out.push(line.slice(1));\n }\n return out;\n }\n\n // Flatten triggered checklists into the RequiredChecklist shape that review.json validation + the\n // schema hint consume.\n toRequired(triggered: readonly TriggeredChecklist[]): RequiredChecklist[] {\n return triggered.map((t: TriggeredChecklist): RequiredChecklist =>\n new RequiredChecklist(t.def.id, t.def.title, t.def.severity, t.def.docs, t.def.blockMessage, t.matchedFiles));\n }\n}\n"]}
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 that the branch's diff actually triggered, plus WHY it triggered (the matched files and,\n// for content-keyed checklists, the specific added lines). Data-only (per CLAUDE.md, classes for data).\nexport class TriggeredChecklist {\n def: ChecklistDefinition;\n matchedFiles: string[];\n matchedContent: string[]; // the added diff lines that matched a contentPattern ([] for path-only)\n\n constructor(def: ChecklistDefinition, matchedFiles: string[], matchedContent: string[]) {\n this.def = def;\n this.matchedFiles = matchedFiles;\n this.matchedContent = matchedContent;\n }\n}\n\n/**\n * Decides which consumer review checklists a branch triggered, from what the diff CHANGED. Path\n * triggers use `isPathExcluded` — the SAME minimatch-based matcher rules-config already shares across\n * exclude-paths and the rule validators — so a checklist glob behaves identically to those. (The\n * Dashboard's own hand-rolled gate matcher is deliberately left untouched: changing gate semantics is a\n * separate behavior change per the backlog note, and gates are a different feature.)\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 // Pure matching — unit-testable without git. Given the changed files and the added (`+`) lines per\n // file, return every non-disabled checklist that fired.\n detect(\n defs: readonly ChecklistDefinition[],\n changedFiles: readonly string[],\n addedLinesByFile: ReadonlyMap<string, string[]>,\n ): TriggeredChecklist[] {\n const triggered: TriggeredChecklist[] = [];\n for (const def of defs) {\n if (def.disabled) continue;\n const candidates = def.patterns.length === 0\n ? [...changedFiles]\n : changedFiles.filter((f: string): boolean => isPathExcluded(f, def.patterns));\n if (candidates.length === 0) continue;\n\n if (def.contentPatterns.length === 0) {\n // Path-only trigger: any candidate file firing is enough.\n triggered.push(new TriggeredChecklist(def, candidates, []));\n continue;\n }\n const hit = this.matchContent(def, candidates, addedLinesByFile);\n if (hit.matchedFiles.length > 0) triggered.push(hit);\n }\n return triggered;\n }\n\n // Content trigger: keep only candidate files with an added line matching a contentPattern. Keying on\n // ADDED lines is why contentPatterns exists — path globs structurally cannot express \"a line adding\n // @Post( / @Cron( / CloudTasksClient\".\n private matchContent(\n def: ChecklistDefinition,\n candidates: readonly string[],\n addedLinesByFile: ReadonlyMap<string, string[]>,\n ): TriggeredChecklist {\n const regexes = def.contentPatterns.map((p: string): RegExp => new RegExp(p));\n const matchedFiles: string[] = [];\n const matchedContent: string[] = [];\n for (const file of candidates) {\n const lines = addedLinesByFile.get(file) ?? [];\n const hits = lines.filter((l: string): boolean => regexes.some((rx: RegExp): boolean => rx.test(l)));\n if (hits.length > 0) {\n matchedFiles.push(file);\n for (const h of hits) matchedContent.push(h);\n }\n }\n return new TriggeredChecklist(def, matchedFiles, matchedContent);\n }\n\n // Convenience for the pr-gate commands: resolve the diff base, gather changed files + added lines\n // from git (via the shared DiffScope), then detect. Returns [] when there is no base to diff against.\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 (`wp-check-pr`), where the useful\n // range is the PR's merge base .. head rather than the local branch's inferred base. Keep tsOnly=false\n // (see below) so a re-implementation in CI never silently drops the very files a checklist keys on.\n detectForRange(repoRoot: string, defs: readonly ChecklistDefinition[], base: string, head?: string): TriggeredChecklist[] {\n if (defs.length === 0 || !base) return [];\n\n // tsOnly:false is REQUIRED and load-bearing — the default (true) restricts to *.ts/*.tsx AND\n // drops test files, silently discarding every *.sql / *.gql / Dockerfile / .env* / metadata file\n // a checklist most wants to key on. The default would produce \"no checklists triggered\".\n const opts = new ChangedFilesOptions();\n opts.tsOnly = false;\n const changedFiles = this.diffScope.getChangedFiles(repoRoot, base, head, opts);\n\n const addedLinesByFile = new Map<string, string[]>();\n for (const file of changedFiles) {\n const diff = this.diffScope.getFileDiff(repoRoot, file, base, head);\n addedLinesByFile.set(file, this.addedLines(diff));\n }\n return this.detect(defs, changedFiles, addedLinesByFile);\n }\n\n // The added-content lines of a single-file diff: `+` lines with the marker stripped, excluding the\n // `+++` file header (mirrors DiffScope.getChangedLineNumbers' own `+`/`+++` handling).\n private addedLines(diff: string): string[] {\n const out: string[] = [];\n for (const line of diff.split('\\n')) {\n if (line.startsWith('+') && !line.startsWith('+++')) out.push(line.slice(1));\n }\n return out;\n }\n\n // Flatten triggered checklists into the RequiredChecklist shape that review.json validation + the\n // schema hint consume.\n toRequired(triggered: readonly TriggeredChecklist[]): RequiredChecklist[] {\n return triggered.map((t: TriggeredChecklist): RequiredChecklist =>\n new RequiredChecklist(t.def.id, t.def.title, t.def.severity, t.def.docs, t.def.blockMessage, t.matchedFiles, t.def.subagent));\n }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import 'reflect-metadata';
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ require("reflect-metadata");
5
+ const inversify_1 = require("inversify");
6
+ const rules_config_1 = require("@webpieces/rules-config");
7
+ const pr_gate_app_1 = require("./pr-gate-app");
8
+ // Composition root for the SERVER-SIDE gate check. Read-only: verifies the PR body carries a valid HMAC
9
+ // gate token for its head sha. Intended as a required CI status check (see the scaffolded workflow).
10
+ (0, rules_config_1.runMain)(async () => {
11
+ // autobind self-binds every @injectable(Singleton) tooling class (replaces the buildProviderModule registry scan)
12
+ const container = new inversify_1.Container({ autobind: true });
13
+ container.get(rules_config_1.CliArgs).assertNoArgs(new rules_config_1.CliUsage('wp-check-pr', 'CI check: verify this PR was created through the webpieces gated flow (valid gate token).'));
14
+ await container.get(pr_gate_app_1.PrGateApp).checkPr();
15
+ });
16
+ //# sourceMappingURL=wp-check-pr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wp-check-pr.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/wp-check-pr.ts"],"names":[],"mappings":";;;AACA,4BAA0B;AAC1B,yCAAsC;AACtC,0DAAqE;AACrE,+CAA0C;AAE1C,wGAAwG;AACxG,qGAAqG;AACrG,IAAA,sBAAO,EAAC,KAAK,IAAmB,EAAE;IAC9B,kHAAkH;IAClH,MAAM,SAAS,GAAG,IAAI,qBAAS,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,SAAS,CAAC,GAAG,CAAC,sBAAO,CAAC,CAAC,YAAY,CAAC,IAAI,uBAAQ,CAAC,aAAa,EAAE,2FAA2F,CAAC,CAAC,CAAC;IAC9J,MAAM,SAAS,CAAC,GAAG,CAAC,uBAAS,CAAC,CAAC,OAAO,EAAE,CAAC;AAC7C,CAAC,CAAC,CAAC","sourcesContent":["#!/usr/bin/env node\nimport 'reflect-metadata';\nimport { Container } from 'inversify';\nimport { runMain, CliArgs, CliUsage } from '@webpieces/rules-config';\nimport { PrGateApp } from './pr-gate-app';\n\n// Composition root for the SERVER-SIDE gate check. Read-only: verifies the PR body carries a valid HMAC\n// gate token for its head sha. Intended as a required CI status check (see the scaffolded workflow).\nrunMain(async (): Promise<void> => {\n // autobind self-binds every @injectable(Singleton) tooling class (replaces the buildProviderModule registry scan)\n const container = new Container({ autobind: true });\n container.get(CliArgs).assertNoArgs(new CliUsage('wp-check-pr', 'CI check: verify this PR was created through the webpieces gated flow (valid gate token).'));\n await container.get(PrGateApp).checkPr();\n});\n"]}