@webpieces/pr-gate 0.4.481 → 0.4.482
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 +4 -3
- package/src/scripts/commands/checklist-command.d.ts +35 -0
- package/src/scripts/commands/checklist-command.js +97 -0
- package/src/scripts/commands/checklist-command.js.map +1 -0
- package/src/scripts/commands/finish-upsert-pr-command.d.ts +9 -2
- package/src/scripts/commands/finish-upsert-pr-command.js +32 -4
- package/src/scripts/commands/finish-upsert-pr-command.js.map +1 -1
- package/src/scripts/commands/start-upsert-pr-command.d.ts +18 -10
- package/src/scripts/commands/start-upsert-pr-command.js +43 -76
- package/src/scripts/commands/start-upsert-pr-command.js.map +1 -1
- package/src/scripts/pr-gate-app.d.ts +5 -1
- package/src/scripts/pr-gate-app.js +10 -2
- package/src/scripts/pr-gate-app.js.map +1 -1
- package/src/scripts/workflow/checklist-detector.d.ts +3 -1
- package/src/scripts/workflow/checklist-detector.js +12 -3
- package/src/scripts/workflow/checklist-detector.js.map +1 -1
- package/src/scripts/workflow/checklist-notice.d.ts +7 -6
- package/src/scripts/workflow/checklist-notice.js +26 -26
- package/src/scripts/workflow/checklist-notice.js.map +1 -1
- package/src/scripts/wp-checklist.d.ts +2 -0
- package/src/scripts/wp-checklist.js +16 -0
- package/src/scripts/wp-checklist.js.map +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/pr-gate",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.482",
|
|
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",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"wp-finish-update": "./src/scripts/wp-finish-update.js",
|
|
12
12
|
"wp-cleanup": "./src/scripts/wp-cleanup.js",
|
|
13
13
|
"wp-land-pr": "./src/scripts/wp-land-pr.js",
|
|
14
|
-
"wp-check-pr": "./src/scripts/wp-check-pr.js"
|
|
14
|
+
"wp-check-pr": "./src/scripts/wp-check-pr.js",
|
|
15
|
+
"wp-checklist": "./src/scripts/wp-checklist.js"
|
|
15
16
|
},
|
|
16
17
|
"files": [
|
|
17
18
|
"src/**/*"
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
"directory": "packages/tooling/pr-gate"
|
|
25
26
|
},
|
|
26
27
|
"dependencies": {
|
|
27
|
-
"@webpieces/rules-config": "0.4.
|
|
28
|
+
"@webpieces/rules-config": "0.4.482",
|
|
28
29
|
"@inversifyjs/binding-decorators": "1.1.5",
|
|
29
30
|
"inversify": "7.10.4",
|
|
30
31
|
"reflect-metadata": "0.2.2"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { RepoRootFinder, ChecklistManifestService, ChecklistInstructionsService, ReviewJsonService } from '@webpieces/rules-config';
|
|
2
|
+
import { AiBranchName } from '../workflow/git-readAiBranchName';
|
|
3
|
+
import { ChecklistDetector } from '../workflow/checklist-detector';
|
|
4
|
+
import { ChecklistNotice } from '../workflow/checklist-notice';
|
|
5
|
+
/**
|
|
6
|
+
* `wp-checklist` — READ-ONLY. Answers the one question the coding agent has at review time: *which reviewer
|
|
7
|
+
* subagents do I owe on THIS diff, and what exactly do I tell them?*
|
|
8
|
+
*
|
|
9
|
+
* It exists because that answer used to be smeared across `wp-start-upsert-pr`'s output in two partly
|
|
10
|
+
* duplicated blocks, which made the instruction long, drift-prone, and easy to skim past. Now `wp-start`
|
|
11
|
+
* points here, `wp-finish` fails fast against the same computation, and this command is the single place
|
|
12
|
+
* that renders it — via {@link ChecklistInstructionsService}, shared with both.
|
|
13
|
+
*
|
|
14
|
+
* It NEVER mutates the repo beyond refreshing the AI-facing template, and never blocks: a diff owing no
|
|
15
|
+
* review says so and exits 0. Safe to run any number of times.
|
|
16
|
+
*/
|
|
17
|
+
export declare class ChecklistCommand {
|
|
18
|
+
private readonly repoRootFinder;
|
|
19
|
+
private readonly aiBranchName;
|
|
20
|
+
private readonly manifestService;
|
|
21
|
+
private readonly checklistDetector;
|
|
22
|
+
private readonly reviewJsonService;
|
|
23
|
+
private readonly instructions;
|
|
24
|
+
private readonly checklistNotice;
|
|
25
|
+
constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, manifestService: ChecklistManifestService, checklistDetector: ChecklistDetector, reviewJsonService: ReviewJsonService, instructions: ChecklistInstructionsService, checklistNotice: ChecklistNotice);
|
|
26
|
+
run(): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* The whole report. Computed in the same order wp-finish-upsert-pr computes it, from the same services,
|
|
29
|
+
* so "wp-checklist said I was done" and "wp-finish let me through" can never disagree.
|
|
30
|
+
*/
|
|
31
|
+
private report;
|
|
32
|
+
private plan;
|
|
33
|
+
private doneLines;
|
|
34
|
+
private allDone;
|
|
35
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ChecklistCommand = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const rules_config_1 = require("@webpieces/rules-config");
|
|
6
|
+
const inversify_1 = require("inversify");
|
|
7
|
+
const git_readAiBranchName_1 = require("../workflow/git-readAiBranchName");
|
|
8
|
+
const checklist_detector_1 = require("../workflow/checklist-detector");
|
|
9
|
+
const checklist_notice_1 = require("../workflow/checklist-notice");
|
|
10
|
+
const SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
|
|
11
|
+
/**
|
|
12
|
+
* `wp-checklist` — READ-ONLY. Answers the one question the coding agent has at review time: *which reviewer
|
|
13
|
+
* subagents do I owe on THIS diff, and what exactly do I tell them?*
|
|
14
|
+
*
|
|
15
|
+
* It exists because that answer used to be smeared across `wp-start-upsert-pr`'s output in two partly
|
|
16
|
+
* duplicated blocks, which made the instruction long, drift-prone, and easy to skim past. Now `wp-start`
|
|
17
|
+
* points here, `wp-finish` fails fast against the same computation, and this command is the single place
|
|
18
|
+
* that renders it — via {@link ChecklistInstructionsService}, shared with both.
|
|
19
|
+
*
|
|
20
|
+
* It NEVER mutates the repo beyond refreshing the AI-facing template, and never blocks: a diff owing no
|
|
21
|
+
* review says so and exits 0. Safe to run any number of times.
|
|
22
|
+
*/
|
|
23
|
+
let ChecklistCommand = class ChecklistCommand {
|
|
24
|
+
repoRootFinder;
|
|
25
|
+
aiBranchName;
|
|
26
|
+
manifestService;
|
|
27
|
+
checklistDetector;
|
|
28
|
+
reviewJsonService;
|
|
29
|
+
instructions;
|
|
30
|
+
checklistNotice;
|
|
31
|
+
constructor(repoRootFinder, aiBranchName, manifestService, checklistDetector, reviewJsonService, instructions, checklistNotice) {
|
|
32
|
+
this.repoRootFinder = repoRootFinder;
|
|
33
|
+
this.aiBranchName = aiBranchName;
|
|
34
|
+
this.manifestService = manifestService;
|
|
35
|
+
this.checklistDetector = checklistDetector;
|
|
36
|
+
this.reviewJsonService = reviewJsonService;
|
|
37
|
+
this.instructions = instructions;
|
|
38
|
+
this.checklistNotice = checklistNotice;
|
|
39
|
+
}
|
|
40
|
+
run() {
|
|
41
|
+
const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
|
|
42
|
+
// Refresh the long-form doc so the block below can cite a file that is present and current.
|
|
43
|
+
(0, rules_config_1.writeTemplate)(repoRoot, 'webpieces.review-checklists.md');
|
|
44
|
+
process.stdout.write('\n' + SEP + '📋 Review checklists for this diff\n' + SEP + '\n');
|
|
45
|
+
process.stdout.write(this.report(repoRoot));
|
|
46
|
+
return Promise.resolve();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The whole report. Computed in the same order wp-finish-upsert-pr computes it, from the same services,
|
|
50
|
+
* so "wp-checklist said I was done" and "wp-finish let me through" can never disagree.
|
|
51
|
+
*/
|
|
52
|
+
report(repoRoot) {
|
|
53
|
+
const source = (0, rules_config_1.loadAndValidate)(repoRoot).prGate.checklists;
|
|
54
|
+
const defs = this.manifestService.load(repoRoot, source);
|
|
55
|
+
const triggered = this.checklistDetector.detectForRepo(repoRoot, defs);
|
|
56
|
+
if (triggered.length === 0) {
|
|
57
|
+
// Zero is a SUPPORTED state, never a blocker — and a configured-but-broken source is reported
|
|
58
|
+
// loudly here rather than silently enforcing nothing (see ChecklistNotice).
|
|
59
|
+
return this.checklistNotice.build(source, this.manifestService.validate(repoRoot, source), defs.length, 'wp-finish-upsert-pr');
|
|
60
|
+
}
|
|
61
|
+
return this.plan(repoRoot, defs, triggered);
|
|
62
|
+
}
|
|
63
|
+
// The triggered set split into "already reviewed on this branch" (reused, not re-run) and "still owed".
|
|
64
|
+
plan(repoRoot, defs, triggered) {
|
|
65
|
+
const featureName = this.aiBranchName.getFeatureName();
|
|
66
|
+
const reviewPath = (0, rules_config_1.reviewJsonPath)(repoRoot, featureName);
|
|
67
|
+
const required = this.checklistDetector.toRequired(triggered);
|
|
68
|
+
const results = this.reviewJsonService.loadChecklistResults(reviewPath, required);
|
|
69
|
+
const pending = this.reviewJsonService.pendingChecklists(required, results);
|
|
70
|
+
const done = required.filter((r) => !pending.includes(r));
|
|
71
|
+
const context = this.reviewJsonService.reviewContextFor(repoRoot, featureName);
|
|
72
|
+
return (`${defs.length} checklist(s) configured, ${required.length} apply to this diff.\n` +
|
|
73
|
+
this.doneLines(done) +
|
|
74
|
+
(pending.length === 0 ? this.allDone() : `\n${this.instructions.render(pending, reviewPath, context)}\n`));
|
|
75
|
+
}
|
|
76
|
+
// REVIEW ONCE PER BRANCH: verdict files persist in .webpieces, so a second cycle re-instructs nothing.
|
|
77
|
+
doneLines(done) {
|
|
78
|
+
if (done.length === 0)
|
|
79
|
+
return '';
|
|
80
|
+
return done.map((r) => ` ✓ ${r.subagent} — already reviewed on this branch (reusing its review-${r.id}.json)\n`).join('');
|
|
81
|
+
}
|
|
82
|
+
allDone() {
|
|
83
|
+
return '\n✅ Every checklist that applies is already reviewed — nothing to run.\n Write review.json and run: pnpm wp-finish-upsert-pr\n';
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
exports.ChecklistCommand = ChecklistCommand;
|
|
87
|
+
exports.ChecklistCommand = ChecklistCommand = tslib_1.__decorate([
|
|
88
|
+
(0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
|
|
89
|
+
tslib_1.__metadata("design:paramtypes", [rules_config_1.RepoRootFinder,
|
|
90
|
+
git_readAiBranchName_1.AiBranchName,
|
|
91
|
+
rules_config_1.ChecklistManifestService,
|
|
92
|
+
checklist_detector_1.ChecklistDetector,
|
|
93
|
+
rules_config_1.ReviewJsonService,
|
|
94
|
+
rules_config_1.ChecklistInstructionsService,
|
|
95
|
+
checklist_notice_1.ChecklistNotice])
|
|
96
|
+
], ChecklistCommand);
|
|
97
|
+
//# sourceMappingURL=checklist-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checklist-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/checklist-command.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n loadAndValidate, reviewJsonPath, writeTemplate, RepoRootFinder, ChecklistManifestService,\n ChecklistDefinition, ChecklistInstructionsService, ReviewJsonService, RequiredChecklist,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { ChecklistDetector, TriggeredChecklist } from '../workflow/checklist-detector';\nimport { ChecklistNotice } from '../workflow/checklist-notice';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n/**\n * `wp-checklist` — READ-ONLY. Answers the one question the coding agent has at review time: *which reviewer\n * subagents do I owe on THIS diff, and what exactly do I tell them?*\n *\n * It exists because that answer used to be smeared across `wp-start-upsert-pr`'s output in two partly\n * duplicated blocks, which made the instruction long, drift-prone, and easy to skim past. Now `wp-start`\n * points here, `wp-finish` fails fast against the same computation, and this command is the single place\n * that renders it — via {@link ChecklistInstructionsService}, shared with both.\n *\n * It NEVER mutates the repo beyond refreshing the AI-facing template, and never blocks: a diff owing no\n * review says so and exits 0. Safe to run any number of times.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ChecklistCommand {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly aiBranchName: AiBranchName,\n private readonly manifestService: ChecklistManifestService,\n private readonly checklistDetector: ChecklistDetector,\n private readonly reviewJsonService: ReviewJsonService,\n private readonly instructions: ChecklistInstructionsService,\n private readonly checklistNotice: ChecklistNotice,\n ) {}\n\n run(): Promise<void> {\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n // Refresh the long-form doc so the block below can cite a file that is present and current.\n writeTemplate(repoRoot, 'webpieces.review-checklists.md');\n process.stdout.write('\\n' + SEP + '📋 Review checklists for this diff\\n' + SEP + '\\n');\n process.stdout.write(this.report(repoRoot));\n return Promise.resolve();\n }\n\n /**\n * The whole report. Computed in the same order wp-finish-upsert-pr computes it, from the same services,\n * so \"wp-checklist said I was done\" and \"wp-finish let me through\" can never disagree.\n */\n private report(repoRoot: string): string {\n const source = loadAndValidate(repoRoot).prGate.checklists;\n const defs = this.manifestService.load(repoRoot, source);\n const triggered = this.checklistDetector.detectForRepo(repoRoot, defs);\n if (triggered.length === 0) {\n // Zero is a SUPPORTED state, never a blocker — and a configured-but-broken source is reported\n // loudly here rather than silently enforcing nothing (see ChecklistNotice).\n return this.checklistNotice.build(source, this.manifestService.validate(repoRoot, source), defs.length, 'wp-finish-upsert-pr');\n }\n return this.plan(repoRoot, defs, triggered);\n }\n\n // The triggered set split into \"already reviewed on this branch\" (reused, not re-run) and \"still owed\".\n private plan(repoRoot: string, defs: readonly ChecklistDefinition[], triggered: readonly TriggeredChecklist[]): string {\n const featureName = this.aiBranchName.getFeatureName();\n const reviewPath = reviewJsonPath(repoRoot, featureName);\n const required = this.checklistDetector.toRequired(triggered);\n const results = this.reviewJsonService.loadChecklistResults(reviewPath, required);\n const pending = this.reviewJsonService.pendingChecklists(required, results);\n const done = required.filter((r: RequiredChecklist): boolean => !pending.includes(r));\n const context = this.reviewJsonService.reviewContextFor(repoRoot, featureName);\n return (\n `${defs.length} checklist(s) configured, ${required.length} apply to this diff.\\n` +\n this.doneLines(done) +\n (pending.length === 0 ? this.allDone() : `\\n${this.instructions.render(pending, reviewPath, context)}\\n`)\n );\n }\n\n // REVIEW ONCE PER BRANCH: verdict files persist in .webpieces, so a second cycle re-instructs nothing.\n private doneLines(done: readonly RequiredChecklist[]): string {\n if (done.length === 0) return '';\n return done.map((r: RequiredChecklist): string =>\n ` ✓ ${r.subagent} — already reviewed on this branch (reusing its review-${r.id}.json)\\n`).join('');\n }\n\n private allDone(): string {\n return '\\n✅ Every checklist that applies is already reviewed — nothing to run.\\n Write review.json and run: pnpm wp-finish-upsert-pr\\n';\n }\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RepoRootFinder, ReviewJsonService, ChecklistManifestService, GateTokenService, SubagentProvenanceService } from '@webpieces/rules-config';
|
|
1
|
+
import { RepoRootFinder, ReviewJsonService, ChecklistManifestService, GateTokenService, SubagentProvenanceService, ChecklistInstructionsService } 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';
|
|
@@ -25,9 +25,16 @@ export declare class FinishUpsertPrCommand {
|
|
|
25
25
|
private readonly reviewJsonService;
|
|
26
26
|
private readonly gateTokenService;
|
|
27
27
|
private readonly provenance;
|
|
28
|
-
|
|
28
|
+
private readonly instructions;
|
|
29
|
+
constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, branchNaming: BranchNaming, gitExec: GitExec, buildAffected: BuildAffected, mergeState: MergeState, mergeEnd: MergeEnd, prMerger: PrMerger, publisher: GatedPrPublisher, dashboard: Dashboard, checklistDetector: ChecklistDetector, manifestService: ChecklistManifestService, reviewJsonService: ReviewJsonService, gateTokenService: GateTokenService, provenance: SubagentProvenanceService, instructions: ChecklistInstructionsService);
|
|
29
30
|
run(): Promise<void>;
|
|
30
31
|
private finalizeAnyInProgressMerge;
|
|
32
|
+
/**
|
|
33
|
+
* Refuse the PR while ANY applicable checklist still owes a verdict, naming exactly those reviewers and
|
|
34
|
+
* exactly what to tell them. Throws InformAiError so the AI gets one actionable message; passes silently
|
|
35
|
+
* when nothing is pending, and is a no-op for a repo with no checklists.
|
|
36
|
+
*/
|
|
37
|
+
private assertEveryReviewerRan;
|
|
31
38
|
private gitOut;
|
|
32
39
|
private prTitleFrom;
|
|
33
40
|
private computeDashboardInput;
|
|
@@ -58,7 +58,8 @@ let FinishUpsertPrCommand = class FinishUpsertPrCommand {
|
|
|
58
58
|
reviewJsonService;
|
|
59
59
|
gateTokenService;
|
|
60
60
|
provenance;
|
|
61
|
-
|
|
61
|
+
instructions;
|
|
62
|
+
constructor(repoRootFinder, aiBranchName, branchNaming, gitExec, buildAffected, mergeState, mergeEnd, prMerger, publisher, dashboard, checklistDetector, manifestService, reviewJsonService, gateTokenService, provenance, instructions) {
|
|
62
63
|
this.repoRootFinder = repoRootFinder;
|
|
63
64
|
this.aiBranchName = aiBranchName;
|
|
64
65
|
this.branchNaming = branchNaming;
|
|
@@ -74,6 +75,7 @@ let FinishUpsertPrCommand = class FinishUpsertPrCommand {
|
|
|
74
75
|
this.reviewJsonService = reviewJsonService;
|
|
75
76
|
this.gateTokenService = gateTokenService;
|
|
76
77
|
this.provenance = provenance;
|
|
78
|
+
this.instructions = instructions;
|
|
77
79
|
}
|
|
78
80
|
async run() {
|
|
79
81
|
const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
|
|
@@ -84,13 +86,19 @@ let FinishUpsertPrCommand = class FinishUpsertPrCommand {
|
|
|
84
86
|
// 2. REQUIRE the AI-authored review.json (throws InformAiError with the schema if missing/invalid).
|
|
85
87
|
// Compute the consumer checklists this diff triggered FIRST so an unacknowledged BLOCK throws
|
|
86
88
|
// here — BEFORE any `gh pr create` — matching the guarantee buildCommand already provides.
|
|
87
|
-
const defs = this.manifestService.load(repoRoot, (0, rules_config_1.loadAndValidate)(repoRoot).prGate.
|
|
89
|
+
const defs = this.manifestService.load(repoRoot, (0, rules_config_1.loadAndValidate)(repoRoot).prGate.checklists);
|
|
88
90
|
const required = this.checklistDetector.toRequired(this.checklistDetector.detectForRepo(repoRoot, defs));
|
|
89
91
|
// review-<id>.json files persist locally between runs, so a re-run after a push re-validates the
|
|
90
92
|
// EXISTING verdicts against the (possibly changed) triggered set for free: an unchanged checklist
|
|
91
93
|
// needs no re-review, a newly-triggered one refuses until its file is written. That is the
|
|
92
94
|
// "full review only when the checklist surface changes" behavior — no special-casing here.
|
|
93
|
-
|
|
95
|
+
// FAIL FAST on missing reviewers, BEFORE review.json is even parsed. A missing reviewer is not a
|
|
96
|
+
// review.json defect and folding it into that error made the AI fix the wrong thing; and the message
|
|
97
|
+
// lists ONLY the subagents that still owe a verdict, so an already-reviewed checklist is never
|
|
98
|
+
// re-instructed. Same computation + same renderer as `wp-checklist`, so the two cannot disagree.
|
|
99
|
+
const featureName = this.aiBranchName.getFeatureName();
|
|
100
|
+
this.assertEveryReviewerRan(repoRoot, featureName, required);
|
|
101
|
+
const review = this.reviewJsonService.loadReviewJson((0, rules_config_1.reviewJsonPath)(repoRoot, featureName), required);
|
|
94
102
|
// 2c. For any BLOCK checklist that names a reviewer `subagent`, VERIFY (from the harness's own
|
|
95
103
|
// artifacts) that such a subagent actually ran on this branch — the coding agent may not
|
|
96
104
|
// self-certify. Absent CLAUDE_CODE_SESSION_ID this skips with a warning (CI / plain terminal).
|
|
@@ -135,6 +143,25 @@ let FinishUpsertPrCommand = class FinishUpsertPrCommand {
|
|
|
135
143
|
return;
|
|
136
144
|
await this.mergeEnd.mergeEnd(repoRoot, 'wp-finish-upsert-pr', activeDir, new merge_start_1.MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber), new merge_end_1.MergeEndOptions(marker.conflictedFiles, false));
|
|
137
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Refuse the PR while ANY applicable checklist still owes a verdict, naming exactly those reviewers and
|
|
148
|
+
* exactly what to tell them. Throws InformAiError so the AI gets one actionable message; passes silently
|
|
149
|
+
* when nothing is pending, and is a no-op for a repo with no checklists.
|
|
150
|
+
*/
|
|
151
|
+
assertEveryReviewerRan(repoRoot, featureName, required) {
|
|
152
|
+
if (required.length === 0)
|
|
153
|
+
return;
|
|
154
|
+
const reviewPath = (0, rules_config_1.reviewJsonPath)(repoRoot, featureName);
|
|
155
|
+
const results = this.reviewJsonService.loadChecklistResults(reviewPath, required);
|
|
156
|
+
const pending = this.reviewJsonService.pendingChecklists(required, results);
|
|
157
|
+
if (pending.length === 0)
|
|
158
|
+
return;
|
|
159
|
+
const context = this.reviewJsonService.reviewContextFor(repoRoot, featureName);
|
|
160
|
+
throw new rules_config_1.InformAiError(`⛔ NO PR — ${pending.length} of ${required.length} review checklist(s) that apply to this diff have ` +
|
|
161
|
+
`no passing verdict yet: ${this.instructions.names(pending)}\n\n` +
|
|
162
|
+
`${this.instructions.render(pending, reviewPath, context)}\n\n` +
|
|
163
|
+
`Then re-run: pnpm wp-finish-upsert-pr (or check anytime with: pnpm wp-checklist)`);
|
|
164
|
+
}
|
|
138
165
|
gitOut(args) {
|
|
139
166
|
const result = (0, child_process_1.spawnSync)('git', args, { encoding: 'utf8' });
|
|
140
167
|
return result.status === 0 ? (result.stdout ?? '').trim() : '';
|
|
@@ -292,6 +319,7 @@ exports.FinishUpsertPrCommand = FinishUpsertPrCommand = tslib_1.__decorate([
|
|
|
292
319
|
rules_config_1.ChecklistManifestService,
|
|
293
320
|
rules_config_1.ReviewJsonService,
|
|
294
321
|
rules_config_1.GateTokenService,
|
|
295
|
-
rules_config_1.SubagentProvenanceService
|
|
322
|
+
rules_config_1.SubagentProvenanceService,
|
|
323
|
+
rules_config_1.ChecklistInstructionsService])
|
|
296
324
|
], FinishUpsertPrCommand);
|
|
297
325
|
//# 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, prDirFor, reviewJsonPath, ReviewJson, RequiredChecklist,\n writeTemplate, RepoRootFinder, ReviewJsonService, ChecklistManifestService,\n GateTokenService, SubagentProvenanceService, PROVENANCE_OK, 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, MergeEndOptions } from '../workflow/merge-end';\nimport { MergeContext } from '../workflow/merge-start';\nimport { PrMerger, MergeOutcome } from '../workflow/pr-merger';\nimport { GatedPrPublisher } from '../workflow/gated-pr-publisher';\nimport { Dashboard, DashboardInput, ChecklistRow, CHECKLIST_COMMENT_MARKER } from '../../dashboard/dashboard';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// A resolved PR's number + web URL. Both '' when the PR can't be resolved (e.g. create failed).\nclass PrRef {\n number: string;\n url: string;\n\n constructor(number: string, url: string) {\n this.number = number;\n this.url = url;\n }\n}\n\n// The outcome of the whole upsert: the PR number ('' 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 publisher: GatedPrPublisher,\n private readonly dashboard: Dashboard,\n private readonly checklistDetector: ChecklistDetector,\n private readonly manifestService: ChecklistManifestService,\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 // 1. Finish any in-progress conflict resolution: validate + commit + finalize the branch swap.\n await this.finalizeAnyInProgressMerge(repoRoot);\n\n // 2. REQUIRE the AI-authored review.json (throws InformAiError with the schema if missing/invalid).\n // Compute the consumer checklists this diff triggered FIRST so an unacknowledged BLOCK throws\n // here — BEFORE any `gh pr create` — matching the guarantee buildCommand already provides.\n const defs = this.manifestService.load(repoRoot, loadAndValidate(repoRoot).prGate.checklistDoc);\n const required = this.checklistDetector.toRequired(this.checklistDetector.detectForRepo(repoRoot, defs));\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 const provenanceVerified = this.enforceProvenance(required, currentBranch);\n\n // 2b. The build gate validates the WORKING TREE but we push HEAD — so they MUST be identical.\n this.gitExec.assertCleanTree(repoRoot);\n\n // 3. Authoritative build gate, then post the gated body, then push (that ORDER — see GatedPrPublisher).\n this.buildAffected.runBuildGate(repoRoot, new BuildGateOptions(\n '🛠️ Build gate (authoritative)', 'pnpm wp-finish-upsert-pr', 'Build failed — no PR created/updated.',\n ));\n const base = this.branchNaming.baseBranchName(execSync('git branch --show-current', { encoding: 'utf8' }).trim());\n\n process.stdout.write('\\n' + SEP + '📋 Dashboard + PR\\n' + SEP + '\\n');\n const title = this.prTitleFrom(review);\n const input = this.computeDashboardInput(repoRoot, true, review, title, required);\n // Append the hidden HMAC gate token bound to the LOCAL HEAD sha — computed BEFORE the push, because\n // GatedPrPublisher writes the body first so CI's `synchronize` read can never see a stale token. A\n // valid token in the PR body is proof this gated flow ran + passed on this exact commit, which CI\n // (`wp-check-pr`) recomputes. We reach here only after the build gate + every BLOCK checklist\n // passed, so minting is legitimate. Nothing about HMAC(salt, HEAD) needs the remote to have it.\n const gateSalt = loadAndValidate(repoRoot).prGate.gateSalt;\n const headSha = this.gitOut(['rev-parse', 'HEAD']);\n const body = this.dashboard.renderDashboard(input) + this.gateTokenBody(gateSalt, headSha);\n const result = this.upsertPr(repoRoot, base, body, title, input);\n // Publish each reviewer's full output as ONE combined PR comment (idempotent, opt-out-aware). Never\n // fatal — the PR is already up by now, so a comment failure only warns.\n this.postChecklistComment(repoRoot, result.prNumber, input.checklists, provenanceVerified);\n const prNum = result.prNumber;\n\n process.stdout.write(\n '\\n' + SEP + '✅ PR finished — here is exactly what I did\\n' + SEP + '\\n' +\n ` 1. validated the build gate (authoritative)\\n` +\n ` 2. ${prNum ? `wrote the gated body to PR #${prNum}` : 'composed the gated PR body'} titled: \"${title}\"\\n` +\n ` 3. force-pushed your work to origin/${base} (after the body, so CI reads the right token)\\n` +\n ` 4. ${result.merge.message}\\n` +\n ` You are on ${base} — same name as the remote branch and the PR head.\\n\\n`,\n );\n }\n\n // Validate + commit + finalize a 3-point merge the AI resolved, if one is in progress. Finalizing here\n // does NOT push (pushRemote=false): this command pushes exactly ONCE, from GatedPrPublisher, and only\n // after review.json + every BLOCK checklist + the build gate pass and the gated PR body is written.\n private async finalizeAnyInProgressMerge(repoRoot: string): Promise<void> {\n const home = this.mergeState.mergeDirFor(repoRoot, this.aiBranchName.getFeatureName());\n const activeDir = this.mergeState.findActiveMergeRunDir(home);\n const marker = activeDir ? this.mergeState.readMergeMarker(activeDir) : null;\n if (!activeDir || !marker || marker.validated) return;\n await this.mergeEnd.mergeEnd(\n repoRoot, 'wp-finish-upsert-pr', activeDir,\n new MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber),\n new MergeEndOptions(marker.conflictedFiles, false),\n );\n }\n\n private gitOut(args: string[]): string {\n const result = spawnSync('git', args, { encoding: 'utf8' });\n return result.status === 0 ? (result.stdout ?? '').trim() : '';\n }\n\n // The user-facing PR title: the AI-authored review.title, or — if omitted — a readable fallback\n // derived from the stable feature name (NEVER the internal `Squash merge of <branch>` commit subject).\n private prTitleFrom(review: ReviewJson): string {\n if (review.title !== '') return review.title;\n return this.aiBranchName.getFeatureName().replace(/[-/]+/g, ' ').trim();\n }\n\n // eslint-disable-next-line @typescript-eslint/max-params\n private computeDashboardInput(repoRoot: string, buildPassed: boolean, review: ReviewJson, title: string, required: readonly RequiredChecklist[]): DashboardInput {\n const config = loadAndValidate(repoRoot).prGate;\n const forkPoint = this.gitOut(['merge-base', 'origin/main', 'HEAD']);\n const featureHead = this.gitOut(['rev-parse', 'HEAD']);\n const mainHead = this.gitOut(['rev-parse', 'origin/main']);\n const range = `${forkPoint}..${featureHead}`;\n const changedFiles = this.gitOut(['diff', range, '--name-only']).split('\\n').filter((f: string): boolean => f.trim() !== '');\n const patch = this.gitOut(['diff', range]);\n\n const gateResults = this.dashboard.computeGateResults(config.gates, changedFiles);\n const disables = this.dashboard.countAddedDisables(patch);\n const rows = this.checklistRows(required, review);\n return new DashboardInput(title, gateResults, disables, buildPassed, forkPoint, featureHead, mainHead, review, rows);\n }\n\n // Pair each matched checklist with its resolved verdict for the dashboard. (A checklist reaching this\n // point is always PASS/OVERRIDDEN — loadReviewJson already threw on FAIL/MISSING.)\n private checklistRows(required: readonly RequiredChecklist[], review: ReviewJson): ChecklistRow[] {\n return required.map((req: RequiredChecklist): ChecklistRow => {\n const verdict = this.reviewJsonService.resolveVerdict(req, review.results);\n return new ChecklistRow(req.id, verdict.status, verdict.detail);\n });\n }\n\n // Hidden HMAC gate-token marker (with a leading blank line) to append to the PR body, or '' when the\n // repo sets no gateSalt (byte-identical body to before this feature). Bound to the pushed HEAD sha.\n private gateTokenBody(gateSalt: string, headSha: string): string {\n const marker = this.gateTokenService.gateTokenMarker(gateSalt, headSha);\n return marker === '' ? '' : `\\n\\n${marker}\\n`;\n }\n\n // Enforce that EACH matched checklist was reviewed by its OWN named subagent, as a DISTINCT run —\n // the coding agent may not self-certify, and one reviewer may not stand in for several. A verified set\n // passes silently; no session id warns but passes; any missing reviewer throws so the PR does not open.\n private enforceProvenance(required: readonly RequiredChecklist[], branch: string): boolean {\n const errors: string[] = [];\n let verified = true; // no reviewers to verify ⇒ vacuously true\n const subagents = required.map((r: RequiredChecklist): string => r.subagent.trim()).filter((s: string): boolean => s !== '');\n if (subagents.length > 0) {\n const result = this.provenance.verifyDistinct(subagents, branch);\n verified = result.status === PROVENANCE_OK;\n if (result.status === PROVENANCE_MISSING) {\n errors.push(result.detail);\n } else if (result.status === PROVENANCE_SKIPPED) {\n process.stderr.write(`⚠️ ${result.detail}\\n`);\n }\n }\n if (errors.length > 0) {\n throw new InformAiError(\n `${errors.length} checklist(s) require an independent reviewer subagent that did not run — fix, then re-run pnpm wp-finish-upsert-pr:\\n\\n` +\n errors.map((e: string): string => ` • ${e}`).join('\\n') +\n `\\n\\nSpawn the named reviewer subagent to review the checklist on THIS branch, then re-run.`,\n );\n }\n return verified;\n }\n\n // Publish every reviewer's full `output` as ONE combined PR comment, idempotently (find the marker\n // comment → PATCH it, else POST). No-op when there is no PR number or no matched checklists. Never\n // fatal: by here the PR is already created/updated, so a `gh` failure only warns.\n private postChecklistComment(repoRoot: string, prNumber: string, rows: readonly ChecklistRow[], provenanceVerified: boolean): void {\n if (prNumber === '' || rows.length === 0) return;\n if (!loadAndValidate(repoRoot).prGate.checklistComments) return;\n const body = this.dashboard.renderChecklistComment(rows, provenanceVerified);\n const prDir = prDirFor(repoRoot, this.aiBranchName.getFeatureName());\n fs.mkdirSync(prDir, { recursive: true });\n const payload = path.join(prDir, 'checklist-comment.json');\n fs.writeFileSync(payload, JSON.stringify({ body }));\n const commentId = this.findChecklistCommentId(prNumber);\n const args = commentId !== ''\n ? ['api', '--method', 'PATCH', `repos/{owner}/{repo}/issues/comments/${commentId}`, '--input', payload]\n : ['api', '--method', 'POST', `repos/{owner}/{repo}/issues/${prNumber}/comments`, '--input', payload];\n const res = spawnSync('gh', args, { encoding: 'utf8' });\n if (res.status !== 0) {\n process.stderr.write('⚠️ Could not post the checklist review comment (non-fatal — the PR is already up).\\n');\n } else {\n process.stdout.write(` ${commentId !== '' ? 'updated' : 'posted'} the checklist review comment ✓\\n`);\n }\n }\n\n // The id of THIS tool's existing checklist comment on the PR (by the hidden marker), or '' if none.\n private findChecklistCommentId(prNumber: string): string {\n const res = spawnSync('gh', [\n 'api', '--paginate', `repos/{owner}/{repo}/issues/${prNumber}/comments`,\n '--jq', `.[] | select(.body | contains(\"${CHECKLIST_COMMENT_MARKER}\")) | .id`,\n ], { encoding: 'utf8' });\n if (res.status !== 0) return '';\n return (res.stdout ?? '').trim().split('\\n')[0] ?? '';\n }\n\n // The PR, the remote branch, and the local branch all share the one stable feature name. Look up /\n // create / merge against `baseBranch` (baseBranchName tolerates a leftover `…wpN` mid-transition).\n // GatedPrPublisher owns the edit/push/create half and its ORDERING — the gated body goes up before\n // the push, so CI's `synchronize` read cannot see the previous run's token.\n private upsertPr(repoRoot: string, baseBranch: string, body: string, title: string, input: DashboardInput): UpsertResult {\n const prDir = prDirFor(repoRoot, this.aiBranchName.getFeatureName());\n fs.mkdirSync(prDir, { recursive: true });\n const bodyFile = path.join(prDir, 'pr-body.md');\n fs.writeFileSync(bodyFile, body + '\\n');\n\n const published = this.publisher.publish(baseBranch, title, bodyFile);\n if (published.createFailed) {\n process.stderr.write('⚠️ gh pr create failed — create the PR manually with the body in:\\n ' + bodyFile + '\\n');\n return new UpsertResult('', new MergeOutcome(false, false,\n '⚠️ did NOT merge — there is no PR to merge (gh pr create failed above)'));\n }\n const num = published.number;\n\n // Set the squash-merge SUBJECT to the PR title (+ the `(#N)` GitHub normally appends, which an\n // explicit --subject would otherwise drop) and the BODY to the compact commit summary, so main's\n // history carries the PR title + risk/flags/link — NOT the internal `Squash merge of <branch>`\n // subject GitHub would inherit from the single squash commit on the branch.\n const ref = this.prRef(baseBranch);\n const subject = ref.number !== '' ? `${title} (#${ref.number})` : title;\n const mergeBodyFile = path.join(prDir, 'merge-commit-body.md');\n fs.writeFileSync(mergeBodyFile, this.dashboard.renderCommitBody(input, ref.url) + '\\n');\n // PrMerger owns the direct-merge / auto-merge-fallback decision AND checks every gh status, so a\n // merge that did not happen is reported as such instead of being swallowed (see pr-merger.ts).\n // REQUIRED config — no default here on purpose. A missing value (an older published\n // rules-config that has no such field) reaches PrMerger as '' and is treated as \"do not merge\".\n const mergeMode = loadAndValidate(repoRoot).prGate.mergeMode ?? '';\n const outcome = this.prMerger.merge(baseBranch, subject, mergeBodyFile, mergeMode);\n return new UpsertResult(ref.number !== '' ? ref.number : num, 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":";;;;AAAA,iDAAoD;AACpD,+CAAyB;AACzB,mDAA6B;AAC7B,0DAKiC;AACjC,yCAA2D;AAC3D,2EAAgE;AAChE,6DAAyD;AACzD,uEAAmE;AACnE,mDAA+C;AAC/C,+DAA6E;AAC7E,yDAAqD;AACrD,qDAAkE;AAClE,yDAAuD;AACvD,qDAA+D;AAC/D,uEAAkE;AAClE,yDAA8G;AAE9G,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAEvE,gGAAgG;AAChG,MAAM,KAAK;IACP,MAAM,CAAS;IACf,GAAG,CAAS;IAEZ,YAAY,MAAc,EAAE,GAAW;QACnC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACnB,CAAC;CACJ;AAED,uGAAuG;AACvG,oGAAoG;AACpG,MAAM,YAAY;IACd,QAAQ,CAAS;IACjB,KAAK,CAAe;IAEpB,YAAY,QAAgB,EAAE,KAAmB;QAC7C,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;CACJ;AAED,wGAAwG;AACxG,oGAAoG;AACpG,kGAAkG;AAClG,0BAA0B;AAEnB,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAET;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IAhBrB,YACqB,cAA8B,EAC9B,YAA0B,EAC1B,YAA0B,EAC1B,OAAgB,EAChB,aAA4B,EAC5B,UAAsB,EACtB,QAAkB,EAClB,QAAkB,EAClB,SAA2B,EAC3B,SAAoB,EACpB,iBAAoC,EACpC,eAAyC,EACzC,iBAAoC,EACpC,gBAAkC,EAClC,UAAqC,EACrC,YAA0C;QAf1C,mBAAc,GAAd,cAAc,CAAgB;QAC9B,iBAAY,GAAZ,YAAY,CAAc;QAC1B,iBAAY,GAAZ,YAAY,CAAc;QAC1B,YAAO,GAAP,OAAO,CAAS;QAChB,kBAAa,GAAb,aAAa,CAAe;QAC5B,eAAU,GAAV,UAAU,CAAY;QACtB,aAAQ,GAAR,QAAQ,CAAU;QAClB,aAAQ,GAAR,QAAQ,CAAU;QAClB,cAAS,GAAT,SAAS,CAAkB;QAC3B,cAAS,GAAT,SAAS,CAAW;QACpB,sBAAiB,GAAjB,iBAAiB,CAAmB;QACpC,oBAAe,GAAf,eAAe,CAA0B;QACzC,sBAAiB,GAAjB,iBAAiB,CAAmB;QACpC,qBAAgB,GAAhB,gBAAgB,CAAkB;QAClC,eAAU,GAAV,UAAU,CAA2B;QACrC,iBAAY,GAAZ,YAAY,CAA8B;IAC5D,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,+FAA+F;QAC/F,MAAM,IAAI,CAAC,0BAA0B,CAAC,QAAQ,CAAC,CAAC;QAEhD,oGAAoG;QACpG,iGAAiG;QACjG,8FAA8F;QAC9F,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAA,8BAAe,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC9F,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,IAAI,CAAC,iBAAiB,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;QACzG,iGAAiG;QACjG,kGAAkG;QAClG,2FAA2F;QAC3F,2FAA2F;QAC3F,iGAAiG;QACjG,qGAAqG;QACrG,+FAA+F;QAC/F,iGAAiG;QACjG,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC;QACvD,IAAI,CAAC,sBAAsB,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,IAAA,6BAAc,EAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,QAAQ,CAAC,CAAC;QAEtG,+FAA+F;QAC/F,6FAA6F;QAC7F,mGAAmG;QACnG,MAAM,aAAa,GAAG,IAAA,wBAAQ,EAAC,2BAA2B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACzF,MAAM,kBAAkB,GAAG,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;QAE3E,8FAA8F;QAC9F,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAEvC,wGAAwG;QACxG,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,QAAQ,EAAE,IAAI,iCAAgB,CAC1D,iCAAiC,EAAE,0BAA0B,EAAE,uCAAuC,CACzG,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,IAAA,wBAAQ,EAAC,2BAA2B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAElH,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,qBAAqB,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;QACtE,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACvC,MAAM,KAAK,GAAG,IAAI,CAAC,qBAAqB,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;QAClF,oGAAoG;QACpG,mGAAmG;QACnG,kGAAkG;QAClG,8FAA8F;QAC9F,gGAAgG;QAChG,MAAM,QAAQ,GAAG,IAAA,8BAAe,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;QAC3D,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;QACnD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC3F,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QACjE,oGAAoG;QACpG,wEAAwE;QACxE,IAAI,CAAC,oBAAoB,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC;QAC3F,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC;QAE9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,IAAI,GAAG,GAAG,GAAG,8CAA8C,GAAG,GAAG,GAAG,IAAI;YACxE,kDAAkD;YAClD,SAAS,KAAK,CAAC,CAAC,CAAC,+BAA+B,KAAK,EAAE,CAAC,CAAC,CAAC,4BAA4B,aAAa,KAAK,KAAK;YAC7G,0CAA0C,IAAI,kDAAkD;YAChG,SAAS,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI;YACjC,kBAAkB,IAAI,yDAAyD,CAClF,CAAC;IACN,CAAC;IAED,uGAAuG;IACvG,sGAAsG;IACtG,oGAAoG;IAC5F,KAAK,CAAC,0BAA0B,CAAC,QAAgB;QACrD,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC,CAAC;QACvF,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAC9D,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC7E,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,SAAS;YAAE,OAAO;QACtD,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CACxB,QAAQ,EAAE,qBAAqB,EAAE,SAAS,EAC1C,IAAI,0BAAY,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,EACjG,IAAI,2BAAe,CAAC,MAAM,CAAC,eAAe,EAAE,KAAK,CAAC,CACrD,CAAC;IACN,CAAC;IAED;;;;OAIG;IACK,sBAAsB,CAAC,QAAgB,EAAE,WAAmB,EAAE,QAAsC;QACxG,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAClC,MAAM,UAAU,GAAG,IAAA,6BAAc,EAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACzD,MAAM,OAAO,GAAG,IAAI,CAAC,iBAAiB,CAAC,oBAAoB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAClF,MAAM,OAAO,GAAG,IAAI,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC5E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACjC,MAAM,OAAO,GAAG,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC/E,MAAM,IAAI,4BAAa,CACnB,aAAa,OAAO,CAAC,MAAM,OAAO,QAAQ,CAAC,MAAM,oDAAoD;YACrG,2BAA2B,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM;YACjE,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,MAAM;YAC/D,oFAAoF,CACvF,CAAC;IACN,CAAC;IAEO,MAAM,CAAC,IAAc;QACzB,MAAM,MAAM,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAC5D,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACnE,CAAC;IAED,gGAAgG;IAChG,uGAAuG;IAC/F,WAAW,CAAC,MAAkB;QAClC,IAAI,MAAM,CAAC,KAAK,KAAK,EAAE;YAAE,OAAO,MAAM,CAAC,KAAK,CAAC;QAC7C,OAAO,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5E,CAAC;IAED,yDAAyD;IACjD,qBAAqB,CAAC,QAAgB,EAAE,WAAoB,EAAE,MAAkB,EAAE,KAAa,EAAE,QAAsC;QAC3I,MAAM,MAAM,GAAG,IAAA,8BAAe,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC;QAChD,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,YAAY,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;QACrE,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;QACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,GAAG,SAAS,KAAK,WAAW,EAAE,CAAC;QAC7C,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAS,EAAW,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC7H,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;QAE3C,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC,MAAM,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;QAClF,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAClD,OAAO,IAAI,0BAAc,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IACzH,CAAC;IAED,sGAAsG;IACtG,mFAAmF;IAC3E,aAAa,CAAC,QAAsC,EAAE,MAAkB;QAC5E,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAsB,EAAgB,EAAE;YACzD,MAAM,OAAO,GAAG,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;YAC3E,OAAO,IAAI,wBAAY,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QACpE,CAAC,CAAC,CAAC;IACP,CAAC;IAED,qGAAqG;IACrG,oGAAoG;IAC5F,aAAa,CAAC,QAAgB,EAAE,OAAe;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC,eAAe,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACxE,OAAO,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,MAAM,IAAI,CAAC;IAClD,CAAC;IAED,kGAAkG;IAClG,uGAAuG;IACvG,wGAAwG;IAChG,iBAAiB,CAAC,QAAsC,EAAE,MAAc;QAC5E,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,QAAQ,GAAG,IAAI,CAAC,CAAC,0CAA0C;QAC/D,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAoB,EAAU,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAS,EAAW,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAC7H,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;YACjE,QAAQ,GAAG,MAAM,CAAC,MAAM,KAAK,4BAAa,CAAC;YAC3C,IAAI,MAAM,CAAC,MAAM,KAAK,iCAAkB,EAAE,CAAC;gBACvC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC/B,CAAC;iBAAM,IAAI,MAAM,CAAC,MAAM,KAAK,iCAAkB,EAAE,CAAC;gBAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;YACnD,CAAC;QACL,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,MAAM,IAAI,4BAAa,CACnB,GAAG,MAAM,CAAC,MAAM,0HAA0H;gBAC1I,MAAM,CAAC,GAAG,CAAC,CAAC,CAAS,EAAU,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;gBACxD,4FAA4F,CAC/F,CAAC;QACN,CAAC;QACD,OAAO,QAAQ,CAAC;IACpB,CAAC;IAED,mGAAmG;IACnG,mGAAmG;IACnG,kFAAkF;IAC1E,oBAAoB,CAAC,QAAgB,EAAE,QAAgB,EAAE,IAA6B,EAAE,kBAA2B;QACvH,IAAI,QAAQ,KAAK,EAAE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACjD,IAAI,CAAC,IAAA,8BAAe,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,iBAAiB;YAAE,OAAO;QAChE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,sBAAsB,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;QAC7E,MAAM,KAAK,GAAG,IAAA,uBAAQ,EAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC,CAAC;QACrE,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,wBAAwB,CAAC,CAAC;QAC3D,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACpD,MAAM,SAAS,GAAG,IAAI,CAAC,sBAAsB,CAAC,QAAQ,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,SAAS,KAAK,EAAE;YACzB,CAAC,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,wCAAwC,SAAS,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC;YACvG,CAAC,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,+BAA+B,QAAQ,WAAW,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QAC1G,MAAM,GAAG,GAAG,IAAA,yBAAS,EAAC,IAAI,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QACxD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,uFAAuF,CAAC,CAAC;QAClH,CAAC;aAAM,CAAC;YACJ,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,SAAS,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,mCAAmC,CAAC,CAAC;QAC3G,CAAC;IACL,CAAC;IAED,oGAAoG;IAC5F,sBAAsB,CAAC,QAAgB;QAC3C,MAAM,GAAG,GAAG,IAAA,yBAAS,EAAC,IAAI,EAAE;YACxB,KAAK,EAAE,YAAY,EAAE,+BAA+B,QAAQ,WAAW;YACvE,MAAM,EAAE,kCAAkC,oCAAwB,WAAW;SAChF,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QACzB,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1D,CAAC;IAED,mGAAmG;IACnG,mGAAmG;IACnG,mGAAmG;IACnG,4EAA4E;IACpE,QAAQ,CAAC,QAAgB,EAAE,UAAkB,EAAE,IAAY,EAAE,KAAa,EAAE,KAAqB;QACrG,MAAM,KAAK,GAAG,IAAA,uBAAQ,EAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC,CAAC;QACrE,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;QAChD,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC,CAAC;QAExC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;QACtE,IAAI,SAAS,CAAC,YAAY,EAAE,CAAC;YACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wEAAwE,GAAG,QAAQ,GAAG,IAAI,CAAC,CAAC;YACjH,OAAO,IAAI,YAAY,CAAC,EAAE,EAAE,IAAI,wBAAY,CAAC,KAAK,EAAE,KAAK,EACrD,yEAAyE,CAAC,CAAC,CAAC;QACpF,CAAC;QACD,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC;QAE7B,+FAA+F;QAC/F,iGAAiG;QACjG,+FAA+F;QAC/F,4EAA4E;QAC5E,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACnC,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;QACxE,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,sBAAsB,CAAC,CAAC;QAC/D,EAAE,CAAC,aAAa,CAAC,aAAa,EAAE,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;QACxF,iGAAiG;QACjG,+FAA+F;QAC/F,oFAAoF;QACpF,gGAAgG;QAChG,MAAM,SAAS,GAAG,IAAA,8BAAe,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC;QACnE,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;QACnF,OAAO,IAAI,YAAY,CAAC,GAAG,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC3E,CAAC;IAED,kGAAkG;IAClG,qGAAqG;IAC7F,KAAK,CAAC,UAAkB;QAC5B,MAAM,MAAM,GAAG,IAAA,yBAAS,EACpB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,0BAA0B,CAAC,EAC5F,EAAE,QAAQ,EAAE,MAAM,EAAE,CACvB,CAAC;QACF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,IAAI,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAC7B,CAAC;QACD,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACvD,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACrD,CAAC;CACJ,CAAA;AAhRY,sDAAqB;gCAArB,qBAAqB;IADjC,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAGA,6BAAc;QAChB,mCAAY;QACZ,4BAAY;QACjB,kBAAO;QACD,8BAAa;QAChB,wBAAU;QACZ,oBAAQ;QACR,oBAAQ;QACP,qCAAgB;QAChB,qBAAS;QACD,sCAAiB;QACnB,uCAAwB;QACtB,gCAAiB;QAClB,+BAAgB;QACtB,wCAAyB;QACvB,2CAA4B;GAjBtD,qBAAqB,CAgRjC","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, ChecklistManifestService,\n GateTokenService, SubagentProvenanceService, PROVENANCE_OK, PROVENANCE_MISSING, PROVENANCE_SKIPPED,\n ChecklistInstructionsService, InformAiError,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BranchNaming } from '../workflow/branch-naming';\nimport { 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, MergeEndOptions } from '../workflow/merge-end';\nimport { MergeContext } from '../workflow/merge-start';\nimport { PrMerger, MergeOutcome } from '../workflow/pr-merger';\nimport { GatedPrPublisher } from '../workflow/gated-pr-publisher';\nimport { Dashboard, DashboardInput, ChecklistRow, CHECKLIST_COMMENT_MARKER } from '../../dashboard/dashboard';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// A resolved PR's number + web URL. Both '' when the PR can't be resolved (e.g. create failed).\nclass PrRef {\n number: string;\n url: string;\n\n constructor(number: string, url: string) {\n this.number = number;\n this.url = url;\n }\n}\n\n// The outcome of the whole upsert: the PR number ('' 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 publisher: GatedPrPublisher,\n private readonly dashboard: Dashboard,\n private readonly checklistDetector: ChecklistDetector,\n private readonly manifestService: ChecklistManifestService,\n private readonly reviewJsonService: ReviewJsonService,\n private readonly gateTokenService: GateTokenService,\n private readonly provenance: SubagentProvenanceService,\n private readonly instructions: ChecklistInstructionsService,\n ) {}\n\n async run(): Promise<void> {\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n // Refresh the AI-facing workflow doc so it's present + current for any failure message to cite.\n writeTemplate(repoRoot, 'webpieces.git-workflow.md');\n // 1. Finish any in-progress conflict resolution: validate + commit + finalize the branch swap.\n await this.finalizeAnyInProgressMerge(repoRoot);\n\n // 2. REQUIRE the AI-authored review.json (throws InformAiError with the schema if missing/invalid).\n // Compute the consumer checklists this diff triggered FIRST so an unacknowledged BLOCK throws\n // here — BEFORE any `gh pr create` — matching the guarantee buildCommand already provides.\n const defs = this.manifestService.load(repoRoot, loadAndValidate(repoRoot).prGate.checklists);\n const required = this.checklistDetector.toRequired(this.checklistDetector.detectForRepo(repoRoot, defs));\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 // FAIL FAST on missing reviewers, BEFORE review.json is even parsed. A missing reviewer is not a\n // review.json defect and folding it into that error made the AI fix the wrong thing; and the message\n // lists ONLY the subagents that still owe a verdict, so an already-reviewed checklist is never\n // re-instructed. Same computation + same renderer as `wp-checklist`, so the two cannot disagree.\n const featureName = this.aiBranchName.getFeatureName();\n this.assertEveryReviewerRan(repoRoot, featureName, required);\n const review = this.reviewJsonService.loadReviewJson(reviewJsonPath(repoRoot, featureName), required);\n\n // 2c. For any BLOCK checklist that names a reviewer `subagent`, VERIFY (from the harness's own\n // artifacts) that such a subagent actually ran on this branch — the coding agent may not\n // self-certify. Absent CLAUDE_CODE_SESSION_ID this skips with a warning (CI / plain terminal).\n const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();\n const provenanceVerified = this.enforceProvenance(required, currentBranch);\n\n // 2b. The build gate validates the WORKING TREE but we push HEAD — so they MUST be identical.\n this.gitExec.assertCleanTree(repoRoot);\n\n // 3. Authoritative build gate, then post the gated body, then push (that ORDER — see GatedPrPublisher).\n this.buildAffected.runBuildGate(repoRoot, new BuildGateOptions(\n '🛠️ Build gate (authoritative)', 'pnpm wp-finish-upsert-pr', 'Build failed — no PR created/updated.',\n ));\n const base = this.branchNaming.baseBranchName(execSync('git branch --show-current', { encoding: 'utf8' }).trim());\n\n process.stdout.write('\\n' + SEP + '📋 Dashboard + PR\\n' + SEP + '\\n');\n const title = this.prTitleFrom(review);\n const input = this.computeDashboardInput(repoRoot, true, review, title, required);\n // Append the hidden HMAC gate token bound to the LOCAL HEAD sha — computed BEFORE the push, because\n // GatedPrPublisher writes the body first so CI's `synchronize` read can never see a stale token. A\n // valid token in the PR body is proof this gated flow ran + passed on this exact commit, which CI\n // (`wp-check-pr`) recomputes. We reach here only after the build gate + every BLOCK checklist\n // passed, so minting is legitimate. Nothing about HMAC(salt, HEAD) needs the remote to have it.\n const gateSalt = loadAndValidate(repoRoot).prGate.gateSalt;\n const headSha = this.gitOut(['rev-parse', 'HEAD']);\n const body = this.dashboard.renderDashboard(input) + this.gateTokenBody(gateSalt, headSha);\n const result = this.upsertPr(repoRoot, base, body, title, input);\n // Publish each reviewer's full output as ONE combined PR comment (idempotent, opt-out-aware). Never\n // fatal — the PR is already up by now, so a comment failure only warns.\n this.postChecklistComment(repoRoot, result.prNumber, input.checklists, provenanceVerified);\n const prNum = result.prNumber;\n\n process.stdout.write(\n '\\n' + SEP + '✅ PR finished — here is exactly what I did\\n' + SEP + '\\n' +\n ` 1. validated the build gate (authoritative)\\n` +\n ` 2. ${prNum ? `wrote the gated body to PR #${prNum}` : 'composed the gated PR body'} titled: \"${title}\"\\n` +\n ` 3. force-pushed your work to origin/${base} (after the body, so CI reads the right token)\\n` +\n ` 4. ${result.merge.message}\\n` +\n ` You are on ${base} — same name as the remote branch and the PR head.\\n\\n`,\n );\n }\n\n // Validate + commit + finalize a 3-point merge the AI resolved, if one is in progress. Finalizing here\n // does NOT push (pushRemote=false): this command pushes exactly ONCE, from GatedPrPublisher, and only\n // after review.json + every BLOCK checklist + the build gate pass and the gated PR body is written.\n private async finalizeAnyInProgressMerge(repoRoot: string): Promise<void> {\n const home = this.mergeState.mergeDirFor(repoRoot, this.aiBranchName.getFeatureName());\n const activeDir = this.mergeState.findActiveMergeRunDir(home);\n const marker = activeDir ? this.mergeState.readMergeMarker(activeDir) : null;\n if (!activeDir || !marker || marker.validated) return;\n await this.mergeEnd.mergeEnd(\n repoRoot, 'wp-finish-upsert-pr', activeDir,\n new MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber),\n new MergeEndOptions(marker.conflictedFiles, false),\n );\n }\n\n /**\n * Refuse the PR while ANY applicable checklist still owes a verdict, naming exactly those reviewers and\n * exactly what to tell them. Throws InformAiError so the AI gets one actionable message; passes silently\n * when nothing is pending, and is a no-op for a repo with no checklists.\n */\n private assertEveryReviewerRan(repoRoot: string, featureName: string, required: readonly RequiredChecklist[]): void {\n if (required.length === 0) return;\n const reviewPath = reviewJsonPath(repoRoot, featureName);\n const results = this.reviewJsonService.loadChecklistResults(reviewPath, required);\n const pending = this.reviewJsonService.pendingChecklists(required, results);\n if (pending.length === 0) return;\n const context = this.reviewJsonService.reviewContextFor(repoRoot, featureName);\n throw new InformAiError(\n `⛔ NO PR — ${pending.length} of ${required.length} review checklist(s) that apply to this diff have ` +\n `no passing verdict yet: ${this.instructions.names(pending)}\\n\\n` +\n `${this.instructions.render(pending, reviewPath, context)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr (or check anytime with: pnpm wp-checklist)`,\n );\n }\n\n private gitOut(args: string[]): string {\n const result = spawnSync('git', args, { encoding: 'utf8' });\n return result.status === 0 ? (result.stdout ?? '').trim() : '';\n }\n\n // The user-facing PR title: the AI-authored review.title, or — if omitted — a readable fallback\n // derived from the stable feature name (NEVER the internal `Squash merge of <branch>` commit subject).\n private prTitleFrom(review: ReviewJson): string {\n if (review.title !== '') return review.title;\n return this.aiBranchName.getFeatureName().replace(/[-/]+/g, ' ').trim();\n }\n\n // eslint-disable-next-line @typescript-eslint/max-params\n private computeDashboardInput(repoRoot: string, buildPassed: boolean, review: ReviewJson, title: string, required: readonly RequiredChecklist[]): DashboardInput {\n const config = loadAndValidate(repoRoot).prGate;\n const forkPoint = this.gitOut(['merge-base', 'origin/main', 'HEAD']);\n const featureHead = this.gitOut(['rev-parse', 'HEAD']);\n const mainHead = this.gitOut(['rev-parse', 'origin/main']);\n const range = `${forkPoint}..${featureHead}`;\n const changedFiles = this.gitOut(['diff', range, '--name-only']).split('\\n').filter((f: string): boolean => f.trim() !== '');\n const patch = this.gitOut(['diff', range]);\n\n const gateResults = this.dashboard.computeGateResults(config.gates, changedFiles);\n const disables = this.dashboard.countAddedDisables(patch);\n const rows = this.checklistRows(required, review);\n return new DashboardInput(title, gateResults, disables, buildPassed, forkPoint, featureHead, mainHead, review, rows);\n }\n\n // Pair each matched checklist with its resolved verdict for the dashboard. (A checklist reaching this\n // point is always PASS/OVERRIDDEN — loadReviewJson already threw on FAIL/MISSING.)\n private checklistRows(required: readonly RequiredChecklist[], review: ReviewJson): ChecklistRow[] {\n return required.map((req: RequiredChecklist): ChecklistRow => {\n const verdict = this.reviewJsonService.resolveVerdict(req, review.results);\n return new ChecklistRow(req.id, verdict.status, verdict.detail);\n });\n }\n\n // Hidden HMAC gate-token marker (with a leading blank line) to append to the PR body, or '' when the\n // repo sets no gateSalt (byte-identical body to before this feature). Bound to the pushed HEAD sha.\n private gateTokenBody(gateSalt: string, headSha: string): string {\n const marker = this.gateTokenService.gateTokenMarker(gateSalt, headSha);\n return marker === '' ? '' : `\\n\\n${marker}\\n`;\n }\n\n // Enforce that EACH matched checklist was reviewed by its OWN named subagent, as a DISTINCT run —\n // the coding agent may not self-certify, and one reviewer may not stand in for several. A verified set\n // passes silently; no session id warns but passes; any missing reviewer throws so the PR does not open.\n private enforceProvenance(required: readonly RequiredChecklist[], branch: string): boolean {\n const errors: string[] = [];\n let verified = true; // no reviewers to verify ⇒ vacuously true\n const subagents = required.map((r: RequiredChecklist): string => r.subagent.trim()).filter((s: string): boolean => s !== '');\n if (subagents.length > 0) {\n const result = this.provenance.verifyDistinct(subagents, branch);\n verified = result.status === PROVENANCE_OK;\n if (result.status === PROVENANCE_MISSING) {\n errors.push(result.detail);\n } else if (result.status === PROVENANCE_SKIPPED) {\n process.stderr.write(`⚠️ ${result.detail}\\n`);\n }\n }\n if (errors.length > 0) {\n throw new InformAiError(\n `${errors.length} checklist(s) require an independent reviewer subagent that did not run — fix, then re-run pnpm wp-finish-upsert-pr:\\n\\n` +\n errors.map((e: string): string => ` • ${e}`).join('\\n') +\n `\\n\\nSpawn the named reviewer subagent to review the checklist on THIS branch, then re-run.`,\n );\n }\n return verified;\n }\n\n // Publish every reviewer's full `output` as ONE combined PR comment, idempotently (find the marker\n // comment → PATCH it, else POST). No-op when there is no PR number or no matched checklists. Never\n // fatal: by here the PR is already created/updated, so a `gh` failure only warns.\n private postChecklistComment(repoRoot: string, prNumber: string, rows: readonly ChecklistRow[], provenanceVerified: boolean): void {\n if (prNumber === '' || rows.length === 0) return;\n if (!loadAndValidate(repoRoot).prGate.checklistComments) return;\n const body = this.dashboard.renderChecklistComment(rows, provenanceVerified);\n const prDir = prDirFor(repoRoot, this.aiBranchName.getFeatureName());\n fs.mkdirSync(prDir, { recursive: true });\n const payload = path.join(prDir, 'checklist-comment.json');\n fs.writeFileSync(payload, JSON.stringify({ body }));\n const commentId = this.findChecklistCommentId(prNumber);\n const args = commentId !== ''\n ? ['api', '--method', 'PATCH', `repos/{owner}/{repo}/issues/comments/${commentId}`, '--input', payload]\n : ['api', '--method', 'POST', `repos/{owner}/{repo}/issues/${prNumber}/comments`, '--input', payload];\n const res = spawnSync('gh', args, { encoding: 'utf8' });\n if (res.status !== 0) {\n process.stderr.write('⚠️ Could not post the checklist review comment (non-fatal — the PR is already up).\\n');\n } else {\n process.stdout.write(` ${commentId !== '' ? 'updated' : 'posted'} the checklist review comment ✓\\n`);\n }\n }\n\n // The id of THIS tool's existing checklist comment on the PR (by the hidden marker), or '' if none.\n private findChecklistCommentId(prNumber: string): string {\n const res = spawnSync('gh', [\n 'api', '--paginate', `repos/{owner}/{repo}/issues/${prNumber}/comments`,\n '--jq', `.[] | select(.body | contains(\"${CHECKLIST_COMMENT_MARKER}\")) | .id`,\n ], { encoding: 'utf8' });\n if (res.status !== 0) return '';\n return (res.stdout ?? '').trim().split('\\n')[0] ?? '';\n }\n\n // The PR, the remote branch, and the local branch all share the one stable feature name. Look up /\n // create / merge against `baseBranch` (baseBranchName tolerates a leftover `…wpN` mid-transition).\n // GatedPrPublisher owns the edit/push/create half and its ORDERING — the gated body goes up before\n // the push, so CI's `synchronize` read cannot see the previous run's token.\n private upsertPr(repoRoot: string, baseBranch: string, body: string, title: string, input: DashboardInput): UpsertResult {\n const prDir = prDirFor(repoRoot, this.aiBranchName.getFeatureName());\n fs.mkdirSync(prDir, { recursive: true });\n const bodyFile = path.join(prDir, 'pr-body.md');\n fs.writeFileSync(bodyFile, body + '\\n');\n\n const published = this.publisher.publish(baseBranch, title, bodyFile);\n if (published.createFailed) {\n process.stderr.write('⚠️ gh pr create failed — create the PR manually with the body in:\\n ' + bodyFile + '\\n');\n return new UpsertResult('', new MergeOutcome(false, false,\n '⚠️ did NOT merge — there is no PR to merge (gh pr create failed above)'));\n }\n const num = published.number;\n\n // Set the squash-merge SUBJECT to the PR title (+ the `(#N)` GitHub normally appends, which an\n // explicit --subject would otherwise drop) and the BODY to the compact commit summary, so main's\n // history carries the PR title + risk/flags/link — NOT the internal `Squash merge of <branch>`\n // subject GitHub would inherit from the single squash commit on the branch.\n const ref = this.prRef(baseBranch);\n const subject = ref.number !== '' ? `${title} (#${ref.number})` : title;\n const mergeBodyFile = path.join(prDir, 'merge-commit-body.md');\n fs.writeFileSync(mergeBodyFile, this.dashboard.renderCommitBody(input, ref.url) + '\\n');\n // PrMerger owns the direct-merge / auto-merge-fallback decision AND checks every gh status, so a\n // merge that did not happen is reported as such instead of being swallowed (see pr-merger.ts).\n // REQUIRED config — no default here on purpose. A missing value (an older published\n // rules-config that has no such field) reaches PrMerger as '' and is treated as \"do not merge\".\n const mergeMode = loadAndValidate(repoRoot).prGate.mergeMode ?? '';\n const outcome = this.prMerger.merge(baseBranch, subject, mergeBodyFile, mergeMode);\n return new UpsertResult(ref.number !== '' ? ref.number : num, 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,9 +1,7 @@
|
|
|
1
|
-
import { RepoRootFinder,
|
|
2
|
-
import { ChecklistNotice } from '../workflow/checklist-notice';
|
|
1
|
+
import { RepoRootFinder, ReviewJsonService, DiffScope } from '@webpieces/rules-config';
|
|
3
2
|
import { AiBranchName } from '../workflow/git-readAiBranchName';
|
|
4
3
|
import { BranchNaming } from '../workflow/branch-naming';
|
|
5
4
|
import { BuildAffected } from '../workflow/build-affected';
|
|
6
|
-
import { ChecklistDetector } from '../workflow/checklist-detector';
|
|
7
5
|
import { GitExec } from '../workflow/git-exec';
|
|
8
6
|
import { RunUpdate } from '../workflow/run-update';
|
|
9
7
|
export declare class StartUpsertPrCommand {
|
|
@@ -13,18 +11,28 @@ export declare class StartUpsertPrCommand {
|
|
|
13
11
|
private readonly buildAffected;
|
|
14
12
|
private readonly gitExec;
|
|
15
13
|
private readonly runUpdate;
|
|
16
|
-
private readonly checklistDetector;
|
|
17
|
-
private readonly manifestService;
|
|
18
14
|
private readonly reviewJsonService;
|
|
19
15
|
private readonly diffScope;
|
|
20
|
-
|
|
21
|
-
constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, branchNaming: BranchNaming, buildAffected: BuildAffected, gitExec: GitExec, runUpdate: RunUpdate, checklistDetector: ChecklistDetector, manifestService: ChecklistManifestService, reviewJsonService: ReviewJsonService, diffScope: DiffScope, checklistNotice: ChecklistNotice);
|
|
16
|
+
constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, branchNaming: BranchNaming, buildAffected: BuildAffected, gitExec: GitExec, runUpdate: RunUpdate, reviewJsonService: ReviewJsonService, diffScope: DiffScope);
|
|
22
17
|
run(): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Hand the AI its next step: run `wp-checklist`, write review.json, then run finish (which posts the PR).
|
|
20
|
+
*
|
|
21
|
+
* This USED to compute the matched checklists and print every reviewer's full instructions inline — twice,
|
|
22
|
+
* in two blocks that could drift. That detail now lives in ONE command, `wp-checklist`, which wp-finish
|
|
23
|
+
* validates against; here we only point at it. Keeping this section short is the point: it is the block
|
|
24
|
+
* the AI must actually act on, and burying the three steps in forty lines of reviewer detail is how a
|
|
25
|
+
* step gets skipped.
|
|
26
|
+
*/
|
|
23
27
|
private handOffToReview;
|
|
28
|
+
/**
|
|
29
|
+
* Step 1 of the review: find out what review this diff owes. A repo with checklists gets pointed at
|
|
30
|
+
* `wp-checklist`; a repo with none is told plainly that it has none, because printing "run wp-checklist"
|
|
31
|
+
* forever at a repo that will never have one is noise, and silence reads as "a checklist passed".
|
|
32
|
+
*/
|
|
33
|
+
private checklistPointer;
|
|
24
34
|
private writePrContext;
|
|
25
|
-
private
|
|
26
|
-
private printChecklistPlan;
|
|
27
|
-
private printEmptyChecklistNotice;
|
|
35
|
+
private contextLines;
|
|
28
36
|
private scaffoldCiWorkflow;
|
|
29
37
|
private updateBranchFromMain;
|
|
30
38
|
}
|
|
@@ -3,12 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.StartUpsertPrCommand = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
6
|
-
const checklist_notice_1 = require("../workflow/checklist-notice");
|
|
7
6
|
const inversify_1 = require("inversify");
|
|
8
7
|
const git_readAiBranchName_1 = require("../workflow/git-readAiBranchName");
|
|
9
8
|
const branch_naming_1 = require("../workflow/branch-naming");
|
|
10
9
|
const build_affected_1 = require("../workflow/build-affected");
|
|
11
|
-
const checklist_detector_1 = require("../workflow/checklist-detector");
|
|
12
10
|
const git_exec_1 = require("../workflow/git-exec");
|
|
13
11
|
const run_update_1 = require("../workflow/run-update");
|
|
14
12
|
const SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
|
|
@@ -23,23 +21,17 @@ let StartUpsertPrCommand = class StartUpsertPrCommand {
|
|
|
23
21
|
buildAffected;
|
|
24
22
|
gitExec;
|
|
25
23
|
runUpdate;
|
|
26
|
-
checklistDetector;
|
|
27
|
-
manifestService;
|
|
28
24
|
reviewJsonService;
|
|
29
25
|
diffScope;
|
|
30
|
-
|
|
31
|
-
constructor(repoRootFinder, aiBranchName, branchNaming, buildAffected, gitExec, runUpdate, checklistDetector, manifestService, reviewJsonService, diffScope, checklistNotice) {
|
|
26
|
+
constructor(repoRootFinder, aiBranchName, branchNaming, buildAffected, gitExec, runUpdate, reviewJsonService, diffScope) {
|
|
32
27
|
this.repoRootFinder = repoRootFinder;
|
|
33
28
|
this.aiBranchName = aiBranchName;
|
|
34
29
|
this.branchNaming = branchNaming;
|
|
35
30
|
this.buildAffected = buildAffected;
|
|
36
31
|
this.gitExec = gitExec;
|
|
37
32
|
this.runUpdate = runUpdate;
|
|
38
|
-
this.checklistDetector = checklistDetector;
|
|
39
|
-
this.manifestService = manifestService;
|
|
40
33
|
this.reviewJsonService = reviewJsonService;
|
|
41
34
|
this.diffScope = diffScope;
|
|
42
|
-
this.checklistNotice = checklistNotice;
|
|
43
35
|
}
|
|
44
36
|
async run() {
|
|
45
37
|
const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
|
|
@@ -63,87 +55,65 @@ let StartUpsertPrCommand = class StartUpsertPrCommand {
|
|
|
63
55
|
this.buildAffected.runBuildGate(repoRoot, new build_affected_1.BuildGateOptions('② Build gate (nx affected)', 'pnpm wp-start-upsert-pr', 'Build failed — fix it before reviewing.'));
|
|
64
56
|
this.handOffToReview(repoRoot);
|
|
65
57
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Hand the AI its next step: run `wp-checklist`, write review.json, then run finish (which posts the PR).
|
|
60
|
+
*
|
|
61
|
+
* This USED to compute the matched checklists and print every reviewer's full instructions inline — twice,
|
|
62
|
+
* in two blocks that could drift. That detail now lives in ONE command, `wp-checklist`, which wp-finish
|
|
63
|
+
* validates against; here we only point at it. Keeping this section short is the point: it is the block
|
|
64
|
+
* the AI must actually act on, and burying the three steps in forty lines of reviewer detail is how a
|
|
65
|
+
* step gets skipped.
|
|
66
|
+
*/
|
|
70
67
|
handOffToReview(repoRoot) {
|
|
71
|
-
const defs = this.manifestService.load(repoRoot, (0, rules_config_1.loadAndValidate)(repoRoot).prGate.checklistDoc);
|
|
72
|
-
const triggered = this.checklistDetector.detectForRepo(repoRoot, defs);
|
|
73
|
-
const required = this.checklistDetector.toRequired(triggered);
|
|
74
68
|
const reviewPath = (0, rules_config_1.reviewJsonPath)(repoRoot, this.aiBranchName.getFeatureName());
|
|
75
|
-
// REVIEW ONCE PER BRANCH: a checklist that already has a passing/overridden review-<id>.json from an
|
|
76
|
-
// earlier cycle is NOT re-reviewed — only the ones still needing a verdict are handed to the AI. The
|
|
77
|
-
// review files persist in .webpieces, so a second wp-start/wp-finish cycle re-instructs nothing.
|
|
78
|
-
const results = this.reviewJsonService.loadChecklistResults(reviewPath, required);
|
|
79
|
-
const toReview = required.filter((req) => !this.alreadyReviewed(req, results));
|
|
80
69
|
// Persist the review-format + process instructions where any failure message can cite them.
|
|
81
70
|
(0, rules_config_1.writeTemplate)(repoRoot, 'webpieces.review-checklists.md');
|
|
82
|
-
// Persist the PR diff context (base sha + changed
|
|
83
|
-
//
|
|
84
|
-
this.writePrContext(repoRoot);
|
|
85
|
-
this.printChecklistPlan(repoRoot, defs, triggered, toReview);
|
|
71
|
+
// Persist the PR diff context (base sha + the full changed-file set) — wp-checklist and every reviewer
|
|
72
|
+
// subagent read it, so it must exist before either runs.
|
|
73
|
+
const context = this.writePrContext(repoRoot);
|
|
86
74
|
process.stdout.write('\n' + SEP + '③ Review the PR, then finish\n' + SEP + '\n');
|
|
87
75
|
process.stdout.write(`Branch is updated and the build gate passed (nothing pushed yet — finish does the one push).\n` +
|
|
88
|
-
|
|
89
|
-
`${
|
|
90
|
-
`Then
|
|
91
|
-
|
|
76
|
+
`${this.contextLines(context)}\n` +
|
|
77
|
+
`${this.checklistPointer(repoRoot)}\n` +
|
|
78
|
+
`Then review your own changes and\n` +
|
|
79
|
+
`${(0, rules_config_1.reviewJsonSchemaHint)(reviewPath)}\n\n` +
|
|
80
|
+
`Finally run: pnpm wp-finish-upsert-pr\n` +
|
|
81
|
+
`(It re-validates the build, re-checks every checklist, renders the dashboard, and creates/updates the PR.)\n\n`);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Step 1 of the review: find out what review this diff owes. A repo with checklists gets pointed at
|
|
85
|
+
* `wp-checklist`; a repo with none is told plainly that it has none, because printing "run wp-checklist"
|
|
86
|
+
* forever at a repo that will never have one is noise, and silence reads as "a checklist passed".
|
|
87
|
+
*/
|
|
88
|
+
checklistPointer(repoRoot) {
|
|
89
|
+
if ((0, rules_config_1.loadAndValidate)(repoRoot).prGate.checklists.isEmpty()) {
|
|
90
|
+
return '📋 Review checklists: NONE CONFIGURED for this repo — nothing to run, and that is fine.\n';
|
|
91
|
+
}
|
|
92
|
+
return ('📋 FIRST run: pnpm wp-checklist\n' +
|
|
93
|
+
' It validates this repo\'s checklist patterns against your diff and prints WHICH reviewer\n' +
|
|
94
|
+
' subagents you must spawn, plus the exact review-<id>.json each must write. Do that BEFORE\n' +
|
|
95
|
+
' finishing — wp-finish-upsert-pr refuses to open the PR until every one of them has run.\n');
|
|
92
96
|
}
|
|
93
97
|
// Write pr-context.json (base/head sha + the full changed-file set, tsOnly:false) so a reviewer
|
|
94
98
|
// subagent knows the exact base the gate uses and can `git diff <base> HEAD -- <file>` for content.
|
|
99
|
+
// Returns what every printed instruction needs to inline; an empty context when no base resolves.
|
|
95
100
|
writePrContext(repoRoot) {
|
|
96
101
|
const range = this.diffScope.resolveBase(repoRoot);
|
|
97
102
|
if (!range.base)
|
|
98
|
-
return;
|
|
103
|
+
return new rules_config_1.ChecklistReviewContext();
|
|
99
104
|
const opts = new rules_config_1.ChangedFilesOptions();
|
|
100
105
|
opts.tsOnly = false;
|
|
101
106
|
const changed = this.diffScope.getChangedFiles(repoRoot, range.base, range.head, opts);
|
|
102
107
|
const head = range.head && range.head.trim() !== '' ? range.head : 'HEAD';
|
|
103
108
|
const p = this.reviewJsonService.writePrContext(repoRoot, this.aiBranchName.getFeatureName(), new rules_config_1.PrContext(range.base, head, changed));
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
// A checklist already reviewed on this branch — its review-<id>.json resolves to PASS or OVERRIDDEN —
|
|
107
|
-
// so it is NOT handed back to the AI to re-review (review once per branch).
|
|
108
|
-
alreadyReviewed(req, results) {
|
|
109
|
-
const status = this.reviewJsonService.resolveVerdict(req, results).status;
|
|
110
|
-
return status === rules_config_1.CK_PASS || status === rules_config_1.CK_OVERRIDDEN;
|
|
111
|
-
}
|
|
112
|
-
// Show which matched checklists still need a reviewer subagent (spawn each as a distinct one) and which
|
|
113
|
-
// are already reviewed (reused, not re-run) — so a second cycle re-instructs nothing.
|
|
114
|
-
printChecklistPlan(repoRoot, defs, triggered, toReview) {
|
|
115
|
-
if (triggered.length === 0) {
|
|
116
|
-
this.printEmptyChecklistNotice(repoRoot, defs);
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
process.stdout.write('\n' + SEP + '📋 Review checklists\n' + SEP + '\n');
|
|
120
|
-
const toReviewIds = new Set(toReview.map((r) => r.id));
|
|
121
|
-
const reused = triggered.filter((t) => !toReviewIds.has(t.def.id));
|
|
122
|
-
for (const t of reused) {
|
|
123
|
-
process.stdout.write(` ✓ ${t.def.subagent} — already reviewed on this branch (reusing its review-${t.def.id}.json)\n`);
|
|
124
|
-
}
|
|
125
|
-
if (toReview.length === 0) {
|
|
126
|
-
process.stdout.write('All matched checklists are already reviewed — nothing to re-run. Just write review.json and finish.\n');
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
process.stdout.write('Spawn EACH of these as a SEPARATE subagent (a different one per checklist — do not self-certify):\n');
|
|
130
|
-
for (const t of triggered.filter((x) => toReviewIds.has(x.def.id))) {
|
|
131
|
-
process.stdout.write(` • subagent "${t.def.subagent}"`);
|
|
132
|
-
if (t.def.doc.trim() !== '')
|
|
133
|
-
process.stdout.write(` — reads ${t.def.doc}`);
|
|
134
|
-
process.stdout.write(` (matched: ${t.matchedFiles.slice(0, 4).join(', ')})\n`);
|
|
135
|
-
}
|
|
136
|
-
process.stdout.write('See .webpieces/instruct-ai/webpieces.review-checklists.md for the review-<id>.json format each must write.\n');
|
|
109
|
+
return new rules_config_1.ChecklistReviewContext(range.base, p);
|
|
137
110
|
}
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const errors = this.manifestService.validate(repoRoot, docRel);
|
|
145
|
-
process.stdout.write('\n' + SEP + '📋 Review checklists\n' + SEP + '\n');
|
|
146
|
-
process.stdout.write(this.checklistNotice.build(docRel, errors, defs.length, 'wp-finish-upsert-pr'));
|
|
111
|
+
// The diff facts, stated where the instruction to use them is — not in a separate block above it.
|
|
112
|
+
contextLines(context) {
|
|
113
|
+
if (context.baseSha.trim() === '')
|
|
114
|
+
return '';
|
|
115
|
+
return (`Your diff: git diff ${context.baseSha} HEAD\n` +
|
|
116
|
+
`Full changed-file set + base/head sha: ${context.prContextPath}\n`);
|
|
147
117
|
}
|
|
148
118
|
// Scaffold the server-side CI check when (and only when) this repo set a gateSalt. Written to the
|
|
149
119
|
// gitignored instruct-ai dir so it never dirties the tree; the human copies it to .github/workflows
|
|
@@ -176,10 +146,7 @@ exports.StartUpsertPrCommand = StartUpsertPrCommand = tslib_1.__decorate([
|
|
|
176
146
|
build_affected_1.BuildAffected,
|
|
177
147
|
git_exec_1.GitExec,
|
|
178
148
|
run_update_1.RunUpdate,
|
|
179
|
-
checklist_detector_1.ChecklistDetector,
|
|
180
|
-
rules_config_1.ChecklistManifestService,
|
|
181
149
|
rules_config_1.ReviewJsonService,
|
|
182
|
-
rules_config_1.DiffScope
|
|
183
|
-
checklist_notice_1.ChecklistNotice])
|
|
150
|
+
rules_config_1.DiffScope])
|
|
184
151
|
], StartUpsertPrCommand);
|
|
185
152
|
//# sourceMappingURL=start-upsert-pr-command.js.map
|
|
@@ -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, writeTemplateIfMissing, CliExitError, RepoRootFinder, ChecklistManifestService, ChecklistDefinition, ReviewJsonService, DiffScope, ChangedFilesOptions, PrContext, RequiredChecklist, ChecklistResult, CK_PASS, CK_OVERRIDDEN } from '@webpieces/rules-config';\nimport { ChecklistNotice } from '../workflow/checklist-notice';\nimport { TriggeredChecklist } from '../workflow/checklist-detector';\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, run the advisory build gate\n// — then hand the AI instructions to WRITE review.json and run `wp-finish-upsert-pr` (which reads it and\n// posts the PR). This command NEVER creates/updates a PR and NEVER pushes: all `gh` posting and the ONE\n// push live in finish, behind review.json + the checklists + the authoritative build gate.\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 private readonly manifestService: ChecklistManifestService,\n private readonly reviewJsonService: ReviewJsonService,\n private readonly diffScope: DiffScope,\n private readonly checklistNotice: ChecklistNotice,\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 squash-updates the branch and builds it — the\n // tooling must not commit your work for you, and building a dirty working tree would let an\n // uncommitted change build green over a different commit than the one that ships. Fail if dirty.\n this.gitExec.assertCleanTree(repoRoot);\n\n // Nothing here pushes. This command reviews; wp-finish-upsert-pr pushes ONCE, after review.json,\n // every BLOCK checklist, and the authoritative build gate — so no unreviewed commit reaches the\n // remote, and there is no early `synchronize` firing against a PR body with a stale gate token.\n await this.updateBranchFromMain(repoRoot);\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 this.handOffToReview(repoRoot);\n }\n\n // Hand the AI its next step: write review.json, then run finish (which posts the PR). Computes the\n // checklists this diff MATCHED (from the manifest doc) so the hint names each reviewer subagent to\n // spawn BEFORE review.json is written — and, when NOTHING matched, says so instead of staying silent\n // (zero checklists is a supported state, never a blocker — see ChecklistNotice).\n private handOffToReview(repoRoot: string): void {\n const defs = this.manifestService.load(repoRoot, loadAndValidate(repoRoot).prGate.checklistDoc);\n const triggered = this.checklistDetector.detectForRepo(repoRoot, defs);\n const required = this.checklistDetector.toRequired(triggered);\n const reviewPath = reviewJsonPath(repoRoot, this.aiBranchName.getFeatureName());\n // REVIEW ONCE PER BRANCH: a checklist that already has a passing/overridden review-<id>.json from an\n // earlier cycle is NOT re-reviewed — only the ones still needing a verdict are handed to the AI. The\n // review files persist in .webpieces, so a second wp-start/wp-finish cycle re-instructs nothing.\n const results = this.reviewJsonService.loadChecklistResults(reviewPath, required);\n const toReview = required.filter((req: RequiredChecklist): boolean => !this.alreadyReviewed(req, results));\n // Persist the review-format + process instructions where any failure message can cite them.\n writeTemplate(repoRoot, 'webpieces.review-checklists.md');\n // Persist the PR diff context (base sha + changed files) so reviewer subagents can `git diff` for\n // content instead of the tooling matching on regexes. Written whenever a base resolves.\n this.writePrContext(repoRoot);\n this.printChecklistPlan(repoRoot, defs, triggered, toReview);\n process.stdout.write('\\n' + SEP + '③ Review the PR, then finish\\n' + SEP + '\\n');\n process.stdout.write(\n `Branch is updated and the build gate passed (nothing pushed yet — finish does the one push).\\n` +\n `Now review your own changes and\\n` +\n `${reviewJsonSchemaHint(reviewPath, toReview)}\\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 // Write pr-context.json (base/head sha + the full changed-file set, tsOnly:false) so a reviewer\n // subagent knows the exact base the gate uses and can `git diff <base> HEAD -- <file>` for content.\n private writePrContext(repoRoot: string): void {\n const range = this.diffScope.resolveBase(repoRoot);\n if (!range.base) return;\n const opts = new ChangedFilesOptions();\n opts.tsOnly = false;\n const changed = this.diffScope.getChangedFiles(repoRoot, range.base, range.head, opts);\n const head = range.head && range.head.trim() !== '' ? range.head : 'HEAD';\n const p = this.reviewJsonService.writePrContext(\n repoRoot, this.aiBranchName.getFeatureName(), new PrContext(range.base, head, changed),\n );\n process.stdout.write(`\\n📂 Wrote PR diff context (${changed.length} changed file(s)) → ${p}\\n`);\n }\n\n // A checklist already reviewed on this branch — its review-<id>.json resolves to PASS or OVERRIDDEN —\n // so it is NOT handed back to the AI to re-review (review once per branch).\n private alreadyReviewed(req: RequiredChecklist, results: readonly ChecklistResult[]): boolean {\n const status = this.reviewJsonService.resolveVerdict(req, results).status;\n return status === CK_PASS || status === CK_OVERRIDDEN;\n }\n\n // Show which matched checklists still need a reviewer subagent (spawn each as a distinct one) and which\n // are already reviewed (reused, not re-run) — so a second cycle re-instructs nothing.\n private printChecklistPlan(repoRoot: string, defs: readonly ChecklistDefinition[], triggered: readonly TriggeredChecklist[], toReview: readonly RequiredChecklist[]): void {\n if (triggered.length === 0) {\n this.printEmptyChecklistNotice(repoRoot, defs);\n return;\n }\n process.stdout.write('\\n' + SEP + '📋 Review checklists\\n' + SEP + '\\n');\n const toReviewIds = new Set(toReview.map((r: RequiredChecklist): string => r.id));\n const reused = triggered.filter((t: TriggeredChecklist): boolean => !toReviewIds.has(t.def.id));\n for (const t of reused) {\n process.stdout.write(` ✓ ${t.def.subagent} — already reviewed on this branch (reusing its review-${t.def.id}.json)\\n`);\n }\n if (toReview.length === 0) {\n process.stdout.write('All matched checklists are already reviewed — nothing to re-run. Just write review.json and finish.\\n');\n return;\n }\n process.stdout.write('Spawn EACH of these as a SEPARATE subagent (a different one per checklist — do not self-certify):\\n');\n for (const t of triggered.filter((x: TriggeredChecklist): boolean => toReviewIds.has(x.def.id))) {\n process.stdout.write(` • subagent \"${t.def.subagent}\"`);\n if (t.def.doc.trim() !== '') process.stdout.write(` — reads ${t.def.doc}`);\n process.stdout.write(` (matched: ${t.matchedFiles.slice(0, 4).join(', ')})\\n`);\n }\n process.stdout.write('See .webpieces/instruct-ai/webpieces.review-checklists.md for the review-<id>.json format each must write.\\n');\n }\n\n // ZERO checklists matched. Say so — this used to print nothing at all, which reads exactly like \"the\n // checklist ran and passed\". Purely informational: 0 is a supported state and never blocks finishing.\n // validate() is called ONLY here (the runtime load() is deliberately tolerant), so a missing/malformed\n // checklist doc stops being a silent no-op and gets reported.\n private printEmptyChecklistNotice(repoRoot: string, defs: readonly ChecklistDefinition[]): void {\n const docRel = loadAndValidate(repoRoot).prGate.checklistDoc;\n const errors = this.manifestService.validate(repoRoot, docRel);\n process.stdout.write('\\n' + SEP + '📋 Review checklists\\n' + SEP + '\\n');\n process.stdout.write(this.checklistNotice.build(docRel, errors, defs.length, 'wp-finish-upsert-pr'));\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 // pushRemote=false — finish owns the single push (see MergeEndOptions).\n const outcome = await this.runUpdate.runUpdateFromMain(repoRoot, 'wp-start-upsert-pr', 'wp-finish-upsert-pr', false);\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":";;;;AACA,0DAA2P;AAC3P,yCAA2D;AAC3D,2EAAgE;AAChE,6DAAyD;AACzD,+DAA6E;AAC7E,mDAA+C;AAC/C,uDAAmD;AAEnD,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAEvE,yGAAyG;AACzG,yGAAyG;AACzG,wGAAwG;AACxG,2FAA2F;AAEpF,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IAER;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IARrB,YACqB,cAA8B,EAC9B,YAA0B,EAC1B,YAA0B,EAC1B,aAA4B,EAC5B,OAAgB,EAChB,SAAoB,EACpB,iBAAoC,EACpC,SAAoB;QAPpB,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;QACpC,cAAS,GAAT,SAAS,CAAW;IACtC,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,gGAAgG;QAChG,4FAA4F;QAC5F,iGAAiG;QACjG,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAEvC,iGAAiG;QACjG,gGAAgG;QAChG,gGAAgG;QAChG,MAAM,IAAI,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QAE1C,6FAA6F;QAC7F,gGAAgG;QAChG,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,QAAQ,EAAE,IAAI,iCAAgB,CAC1D,4BAA4B,EAAE,yBAAyB,EAAE,yCAAyC,CACrG,CAAC,CAAC;QAEH,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;IAED;;;;;;;;OAQG;IACK,eAAe,CAAC,QAAgB;QACpC,MAAM,UAAU,GAAG,IAAA,6BAAc,EAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC,CAAC;QAChF,4FAA4F;QAC5F,IAAA,4BAAa,EAAC,QAAQ,EAAE,gCAAgC,CAAC,CAAC;QAC1D,uGAAuG;QACvG,yDAAyD;QACzD,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,gCAAgC,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;QACjF,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,gGAAgG;YAChG,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI;YACjC,GAAG,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI;YACtC,oCAAoC;YACpC,GAAG,IAAA,mCAAoB,EAAC,UAAU,CAAC,MAAM;YACzC,0CAA0C;YAC1C,gHAAgH,CACnH,CAAC;IACN,CAAC;IAED;;;;OAIG;IACK,gBAAgB,CAAC,QAAgB;QACrC,IAAI,IAAA,8BAAe,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC;YACxD,OAAO,2FAA2F,CAAC;QACvG,CAAC;QACD,OAAO,CACH,oCAAoC;YACpC,+FAA+F;YAC/F,gGAAgG;YAChG,8FAA8F,CACjG,CAAC;IACN,CAAC;IAED,gGAAgG;IAChG,oGAAoG;IACpG,kGAAkG;IAC1F,cAAc,CAAC,QAAgB;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QACnD,IAAI,CAAC,KAAK,CAAC,IAAI;YAAE,OAAO,IAAI,qCAAsB,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,IAAI,kCAAmB,EAAE,CAAC;QACvC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACvF,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;QAC1E,MAAM,CAAC,GAAG,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAC3C,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,EAAE,IAAI,wBAAS,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CACzF,CAAC;QACF,OAAO,IAAI,qCAAsB,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,kGAAkG;IAC1F,YAAY,CAAC,OAA+B;QAChD,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,EAAE,CAAC;QAC7C,OAAO,CACH,wBAAwB,OAAO,CAAC,OAAO,SAAS;YAChD,2CAA2C,OAAO,CAAC,aAAa,IAAI,CACvE,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,wEAAwE;QACxE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,QAAQ,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,KAAK,CAAC,CAAC;QACrH,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;AAxIY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAGA,6BAAc;QAChB,mCAAY;QACZ,4BAAY;QACX,8BAAa;QACnB,kBAAO;QACL,sBAAS;QACD,gCAAiB;QACzB,wBAAS;GAThC,oBAAoB,CAwIhC","sourcesContent":["import { execSync } from 'child_process';\nimport { loadAndValidate, reviewJsonPath, reviewJsonSchemaHint, writeTemplate, writeTemplateIfMissing, CliExitError, RepoRootFinder, ReviewJsonService, DiffScope, ChangedFilesOptions, PrContext, ChecklistReviewContext } 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 { 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, run the advisory build gate\n// — then hand the AI instructions to WRITE review.json and run `wp-finish-upsert-pr` (which reads it and\n// posts the PR). This command NEVER creates/updates a PR and NEVER pushes: all `gh` posting and the ONE\n// push live in finish, behind review.json + the checklists + the authoritative build gate.\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 reviewJsonService: ReviewJsonService,\n private readonly diffScope: DiffScope,\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 squash-updates the branch and builds it — the\n // tooling must not commit your work for you, and building a dirty working tree would let an\n // uncommitted change build green over a different commit than the one that ships. Fail if dirty.\n this.gitExec.assertCleanTree(repoRoot);\n\n // Nothing here pushes. This command reviews; wp-finish-upsert-pr pushes ONCE, after review.json,\n // every BLOCK checklist, and the authoritative build gate — so no unreviewed commit reaches the\n // remote, and there is no early `synchronize` firing against a PR body with a stale gate token.\n await this.updateBranchFromMain(repoRoot);\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 this.handOffToReview(repoRoot);\n }\n\n /**\n * Hand the AI its next step: run `wp-checklist`, write review.json, then run finish (which posts the PR).\n *\n * This USED to compute the matched checklists and print every reviewer's full instructions inline — twice,\n * in two blocks that could drift. That detail now lives in ONE command, `wp-checklist`, which wp-finish\n * validates against; here we only point at it. Keeping this section short is the point: it is the block\n * the AI must actually act on, and burying the three steps in forty lines of reviewer detail is how a\n * step gets skipped.\n */\n private handOffToReview(repoRoot: string): void {\n const reviewPath = reviewJsonPath(repoRoot, this.aiBranchName.getFeatureName());\n // Persist the review-format + process instructions where any failure message can cite them.\n writeTemplate(repoRoot, 'webpieces.review-checklists.md');\n // Persist the PR diff context (base sha + the full changed-file set) — wp-checklist and every reviewer\n // subagent read it, so it must exist before either runs.\n const context = this.writePrContext(repoRoot);\n process.stdout.write('\\n' + SEP + '③ Review the PR, then finish\\n' + SEP + '\\n');\n process.stdout.write(\n `Branch is updated and the build gate passed (nothing pushed yet — finish does the one push).\\n` +\n `${this.contextLines(context)}\\n` +\n `${this.checklistPointer(repoRoot)}\\n` +\n `Then review your own changes and\\n` +\n `${reviewJsonSchemaHint(reviewPath)}\\n\\n` +\n `Finally run: pnpm wp-finish-upsert-pr\\n` +\n `(It re-validates the build, re-checks every checklist, renders the dashboard, and creates/updates the PR.)\\n\\n`,\n );\n }\n\n /**\n * Step 1 of the review: find out what review this diff owes. A repo with checklists gets pointed at\n * `wp-checklist`; a repo with none is told plainly that it has none, because printing \"run wp-checklist\"\n * forever at a repo that will never have one is noise, and silence reads as \"a checklist passed\".\n */\n private checklistPointer(repoRoot: string): string {\n if (loadAndValidate(repoRoot).prGate.checklists.isEmpty()) {\n return '📋 Review checklists: NONE CONFIGURED for this repo — nothing to run, and that is fine.\\n';\n }\n return (\n '📋 FIRST run: pnpm wp-checklist\\n' +\n ' It validates this repo\\'s checklist patterns against your diff and prints WHICH reviewer\\n' +\n ' subagents you must spawn, plus the exact review-<id>.json each must write. Do that BEFORE\\n' +\n ' finishing — wp-finish-upsert-pr refuses to open the PR until every one of them has run.\\n'\n );\n }\n\n // Write pr-context.json (base/head sha + the full changed-file set, tsOnly:false) so a reviewer\n // subagent knows the exact base the gate uses and can `git diff <base> HEAD -- <file>` for content.\n // Returns what every printed instruction needs to inline; an empty context when no base resolves.\n private writePrContext(repoRoot: string): ChecklistReviewContext {\n const range = this.diffScope.resolveBase(repoRoot);\n if (!range.base) return new ChecklistReviewContext();\n const opts = new ChangedFilesOptions();\n opts.tsOnly = false;\n const changed = this.diffScope.getChangedFiles(repoRoot, range.base, range.head, opts);\n const head = range.head && range.head.trim() !== '' ? range.head : 'HEAD';\n const p = this.reviewJsonService.writePrContext(\n repoRoot, this.aiBranchName.getFeatureName(), new PrContext(range.base, head, changed),\n );\n return new ChecklistReviewContext(range.base, p);\n }\n\n // The diff facts, stated where the instruction to use them is — not in a separate block above it.\n private contextLines(context: ChecklistReviewContext): string {\n if (context.baseSha.trim() === '') return '';\n return (\n `Your diff: git diff ${context.baseSha} HEAD\\n` +\n `Full changed-file set + base/head sha: ${context.prContextPath}\\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 // pushRemote=false — finish owns the single push (see MergeEndOptions).\n const outcome = await this.runUpdate.runUpdateFromMain(repoRoot, 'wp-start-upsert-pr', 'wp-finish-upsert-pr', false);\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"]}
|
|
@@ -5,6 +5,7 @@ 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
7
|
import { CheckPrCommand } from './commands/check-pr-command';
|
|
8
|
+
import { ChecklistCommand } from './commands/checklist-command';
|
|
8
9
|
/**
|
|
9
10
|
* The pr-gate application root. `container.get(PrGateApp)` resolves the entire workflow DAG (the command
|
|
10
11
|
* classes → the injected git/merge/dashboard services). `@DocumentDesign` marks it the
|
|
@@ -19,7 +20,8 @@ export declare class PrGateApp {
|
|
|
19
20
|
private readonly cleanupCommand;
|
|
20
21
|
private readonly landPrCommand;
|
|
21
22
|
private readonly checkPrCommand;
|
|
22
|
-
|
|
23
|
+
private readonly checklistCommand;
|
|
24
|
+
constructor(startUpdateCommand: StartUpdateCommand, finishUpdateCommand: FinishUpdateCommand, startUpsertPrCommand: StartUpsertPrCommand, finishUpsertPrCommand: FinishUpsertPrCommand, cleanupCommand: CleanupCommand, landPrCommand: LandPrCommand, checkPrCommand: CheckPrCommand, checklistCommand: ChecklistCommand);
|
|
23
25
|
/** `wp-start-update`: 3-point squash-update from main (no PR). */
|
|
24
26
|
startUpdate(): Promise<void>;
|
|
25
27
|
/** `wp-finish-update`: validate + finalize a resolved 3-point merge (no PR). */
|
|
@@ -32,6 +34,8 @@ export declare class PrGateApp {
|
|
|
32
34
|
cleanup(): Promise<void>;
|
|
33
35
|
/** `wp-land-pr`: squash-merge this branch's PR into main with the compact commit body. */
|
|
34
36
|
landPr(): Promise<void>;
|
|
37
|
+
/** `wp-checklist`: READ-ONLY — which reviewer subagents this diff still owes, and what to tell them. */
|
|
38
|
+
checklist(): Promise<void>;
|
|
35
39
|
/** `wp-check-pr`: READ-ONLY CI check — verify the PR body carries a valid HMAC gate token for its head sha. */
|
|
36
40
|
checkPr(): Promise<void>;
|
|
37
41
|
}
|
|
@@ -11,6 +11,7 @@ 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
13
|
const check_pr_command_1 = require("./commands/check-pr-command");
|
|
14
|
+
const checklist_command_1 = require("./commands/checklist-command");
|
|
14
15
|
/**
|
|
15
16
|
* The pr-gate application root. `container.get(PrGateApp)` resolves the entire workflow DAG (the command
|
|
16
17
|
* classes → the injected git/merge/dashboard services). `@DocumentDesign` marks it the
|
|
@@ -25,7 +26,8 @@ let PrGateApp = class PrGateApp {
|
|
|
25
26
|
cleanupCommand;
|
|
26
27
|
landPrCommand;
|
|
27
28
|
checkPrCommand;
|
|
28
|
-
|
|
29
|
+
checklistCommand;
|
|
30
|
+
constructor(startUpdateCommand, finishUpdateCommand, startUpsertPrCommand, finishUpsertPrCommand, cleanupCommand, landPrCommand, checkPrCommand, checklistCommand) {
|
|
29
31
|
this.startUpdateCommand = startUpdateCommand;
|
|
30
32
|
this.finishUpdateCommand = finishUpdateCommand;
|
|
31
33
|
this.startUpsertPrCommand = startUpsertPrCommand;
|
|
@@ -33,6 +35,7 @@ let PrGateApp = class PrGateApp {
|
|
|
33
35
|
this.cleanupCommand = cleanupCommand;
|
|
34
36
|
this.landPrCommand = landPrCommand;
|
|
35
37
|
this.checkPrCommand = checkPrCommand;
|
|
38
|
+
this.checklistCommand = checklistCommand;
|
|
36
39
|
}
|
|
37
40
|
/** `wp-start-update`: 3-point squash-update from main (no PR). */
|
|
38
41
|
startUpdate() {
|
|
@@ -58,6 +61,10 @@ let PrGateApp = class PrGateApp {
|
|
|
58
61
|
landPr() {
|
|
59
62
|
return this.landPrCommand.run();
|
|
60
63
|
}
|
|
64
|
+
/** `wp-checklist`: READ-ONLY — which reviewer subagents this diff still owes, and what to tell them. */
|
|
65
|
+
checklist() {
|
|
66
|
+
return this.checklistCommand.run();
|
|
67
|
+
}
|
|
61
68
|
/** `wp-check-pr`: READ-ONLY CI check — verify the PR body carries a valid HMAC gate token for its head sha. */
|
|
62
69
|
checkPr() {
|
|
63
70
|
return this.checkPrCommand.run();
|
|
@@ -73,6 +80,7 @@ exports.PrGateApp = PrGateApp = tslib_1.__decorate([
|
|
|
73
80
|
finish_upsert_pr_command_1.FinishUpsertPrCommand,
|
|
74
81
|
cleanup_command_1.CleanupCommand,
|
|
75
82
|
land_pr_command_1.LandPrCommand,
|
|
76
|
-
check_pr_command_1.CheckPrCommand
|
|
83
|
+
check_pr_command_1.CheckPrCommand,
|
|
84
|
+
checklist_command_1.ChecklistCommand])
|
|
77
85
|
], PrGateApp);
|
|
78
86
|
//# 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;AAC3D,kEAA6D;
|
|
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;AAC7D,oEAAgE;AAEhE;;;;;GAKG;AAGI,IAAM,SAAS,GAAf,MAAM,SAAS;IAEG;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IARrB,YACqB,kBAAsC,EACtC,mBAAwC,EACxC,oBAA0C,EAC1C,qBAA4C,EAC5C,cAA8B,EAC9B,aAA4B,EAC5B,cAA8B,EAC9B,gBAAkC;QAPlC,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;QAC9B,qBAAgB,GAAhB,gBAAgB,CAAkB;IACpD,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,wGAAwG;IACxG,SAAS;QACL,OAAO,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,CAAC;IACvC,CAAC;IAED,+GAA+G;IAC/G,OAAO;QACH,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,CAAC;IACrC,CAAC;CACJ,CAAA;AAnDY,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;QACZ,oCAAgB;GAT9C,SAAS,CAmDrB","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';\nimport { ChecklistCommand } from './commands/checklist-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 private readonly checklistCommand: ChecklistCommand,\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-checklist`: READ-ONLY — which reviewer subagents this diff still owes, and what to tell them. */\n checklist(): Promise<void> {\n return this.checklistCommand.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"]}
|
|
@@ -2,7 +2,8 @@ import { ChecklistDefinition, RequiredChecklist, DiffScope } from '@webpieces/ru
|
|
|
2
2
|
export declare class TriggeredChecklist {
|
|
3
3
|
def: ChecklistDefinition;
|
|
4
4
|
matchedFiles: string[];
|
|
5
|
-
|
|
5
|
+
matchedPatterns: string[];
|
|
6
|
+
constructor(def: ChecklistDefinition, matchedFiles: string[], matchedPatterns?: string[]);
|
|
6
7
|
}
|
|
7
8
|
/**
|
|
8
9
|
* Decides which company review checklists a branch matched, from what the diff CHANGED. Path matching
|
|
@@ -16,6 +17,7 @@ export declare class ChecklistDetector {
|
|
|
16
17
|
private readonly diffScope;
|
|
17
18
|
constructor(diffScope: DiffScope);
|
|
18
19
|
detect(defs: readonly ChecklistDefinition[], changedFiles: readonly string[]): TriggeredChecklist[];
|
|
20
|
+
private firedPatterns;
|
|
19
21
|
detectForRepo(repoRoot: string, defs: readonly ChecklistDefinition[]): TriggeredChecklist[];
|
|
20
22
|
detectForRange(repoRoot: string, defs: readonly ChecklistDefinition[], base: string, head?: string): TriggeredChecklist[];
|
|
21
23
|
toRequired(triggered: readonly TriggeredChecklist[]): RequiredChecklist[];
|
|
@@ -9,9 +9,13 @@ const inversify_1 = require("inversify");
|
|
|
9
9
|
class TriggeredChecklist {
|
|
10
10
|
def;
|
|
11
11
|
matchedFiles;
|
|
12
|
-
|
|
12
|
+
// Which of `def.patterns` actually hit a changed file. Carried so the printed instruction can say WHAT
|
|
13
|
+
// matched, not just which files: a reviewer judges a `db/migrations/**` hit differently from a `**` one.
|
|
14
|
+
matchedPatterns;
|
|
15
|
+
constructor(def, matchedFiles, matchedPatterns = []) {
|
|
13
16
|
this.def = def;
|
|
14
17
|
this.matchedFiles = matchedFiles;
|
|
18
|
+
this.matchedPatterns = matchedPatterns;
|
|
15
19
|
}
|
|
16
20
|
}
|
|
17
21
|
exports.TriggeredChecklist = TriggeredChecklist;
|
|
@@ -37,10 +41,15 @@ let ChecklistDetector = class ChecklistDetector {
|
|
|
37
41
|
? [...changedFiles]
|
|
38
42
|
: changedFiles.filter((f) => (0, rules_config_1.isPathExcluded)(f, def.patterns));
|
|
39
43
|
if (matched.length > 0)
|
|
40
|
-
triggered.push(new TriggeredChecklist(def, matched));
|
|
44
|
+
triggered.push(new TriggeredChecklist(def, matched, this.firedPatterns(def, changedFiles)));
|
|
41
45
|
}
|
|
42
46
|
return triggered;
|
|
43
47
|
}
|
|
48
|
+
// Which of a checklist's globs hit at least one changed file. [] for a patternless checklist (it matches
|
|
49
|
+
// every PR, and saying "matched by nothing" would read as a bug).
|
|
50
|
+
firedPatterns(def, changedFiles) {
|
|
51
|
+
return def.patterns.filter((p) => changedFiles.some((f) => (0, rules_config_1.isPathExcluded)(f, [p])));
|
|
52
|
+
}
|
|
44
53
|
// Resolve the diff base for the local branch, then detect. Returns [] when there is no base to diff.
|
|
45
54
|
detectForRepo(repoRoot, defs) {
|
|
46
55
|
if (defs.length === 0)
|
|
@@ -64,7 +73,7 @@ let ChecklistDetector = class ChecklistDetector {
|
|
|
64
73
|
// Flatten matched checklists into the RequiredChecklist shape that review.json enforcement + the hint
|
|
65
74
|
// consume.
|
|
66
75
|
toRequired(triggered) {
|
|
67
|
-
return triggered.map((t) => new rules_config_1.RequiredChecklist(t.def.id, t.def.subagent, t.def.doc, t.matchedFiles));
|
|
76
|
+
return triggered.map((t) => new rules_config_1.RequiredChecklist(t.def.id, t.def.subagent, t.def.doc, t.matchedFiles, t.matchedPatterns));
|
|
68
77
|
}
|
|
69
78
|
};
|
|
70
79
|
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":";;;;AAAA,0DAEiC;AACjC,yCAA2D;AAE3D,sGAAsG;AACtG,iFAAiF;AACjF,MAAa,kBAAkB;IAC3B,GAAG,CAAsB;IACzB,YAAY,CAAW;
|
|
1
|
+
{"version":3,"file":"checklist-detector.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/checklist-detector.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ChecklistDefinition, RequiredChecklist, DiffScope, ChangedFilesOptions, isPathExcluded,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\n// A checklist whose `patterns` matched the diff (so its reviewer subagent must run), plus the matched\n// files (for the dashboard + hint). Data-only (per CLAUDE.md, classes for data).\nexport class TriggeredChecklist {\n def: ChecklistDefinition;\n matchedFiles: string[];\n // Which of `def.patterns` actually hit a changed file. Carried so the printed instruction can say WHAT\n // matched, not just which files: a reviewer judges a `db/migrations/**` hit differently from a `**` one.\n matchedPatterns: string[];\n\n constructor(def: ChecklistDefinition, matchedFiles: string[], matchedPatterns: string[] = []) {\n this.def = def;\n this.matchedFiles = matchedFiles;\n this.matchedPatterns = matchedPatterns;\n }\n}\n\n/**\n * Decides which company review checklists a branch matched, from what the diff CHANGED. Path matching\n * uses `isPathExcluded` — the SAME minimatch-based matcher rules-config already shares across\n * exclude-paths and the rule validators. A checklist with empty `patterns` matches every PR (always runs).\n *\n * `@injectable(bindingScopeValues.Singleton)` + injects {@link DiffScope} so it appears in the DI design\n * and reuses the ONE git-diff service instead of adding new git plumbing.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ChecklistDetector {\n constructor(private readonly diffScope: DiffScope) {}\n\n // Pure matching — unit-testable without git. Return every checklist whose patterns hit a changed file\n // (empty patterns ⇒ always matches).\n detect(defs: readonly ChecklistDefinition[], changedFiles: readonly string[]): TriggeredChecklist[] {\n const triggered: TriggeredChecklist[] = [];\n for (const def of defs) {\n const matched = def.patterns.length === 0\n ? [...changedFiles]\n : changedFiles.filter((f: string): boolean => isPathExcluded(f, def.patterns));\n if (matched.length > 0) triggered.push(new TriggeredChecklist(def, matched, this.firedPatterns(def, changedFiles)));\n }\n return triggered;\n }\n\n // Which of a checklist's globs hit at least one changed file. [] for a patternless checklist (it matches\n // every PR, and saying \"matched by nothing\" would read as a bug).\n private firedPatterns(def: ChecklistDefinition, changedFiles: readonly string[]): string[] {\n return def.patterns.filter((p: string): boolean => changedFiles.some((f: string): boolean => isPathExcluded(f, [p])));\n }\n\n // Resolve the diff base for the local branch, then detect. Returns [] when there is no base to diff.\n detectForRepo(repoRoot: string, defs: readonly ChecklistDefinition[]): TriggeredChecklist[] {\n if (defs.length === 0) return [];\n const range = this.diffScope.resolveBase(repoRoot);\n if (!range.base) return [];\n return this.detectForRange(repoRoot, defs, range.base, range.head);\n }\n\n // Same detection against an EXPLICIT (base, head) — used by CI, where the useful range is the PR's\n // merge base .. head. tsOnly=false is load-bearing: the default drops every *.sql / Dockerfile / .env*\n // / metadata file a checklist most wants to key on, silently producing \"no checklists matched\".\n detectForRange(repoRoot: string, defs: readonly ChecklistDefinition[], base: string, head?: string): TriggeredChecklist[] {\n if (defs.length === 0 || !base) return [];\n const opts = new ChangedFilesOptions();\n opts.tsOnly = false;\n const changedFiles = this.diffScope.getChangedFiles(repoRoot, base, head, opts);\n return this.detect(defs, changedFiles);\n }\n\n // Flatten matched checklists into the RequiredChecklist shape that review.json enforcement + the hint\n // consume.\n toRequired(triggered: readonly TriggeredChecklist[]): RequiredChecklist[] {\n return triggered.map((t: TriggeredChecklist): RequiredChecklist =>\n new RequiredChecklist(t.def.id, t.def.subagent, t.def.doc, t.matchedFiles, t.matchedPatterns));\n }\n}\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ChecklistSource } from '@webpieces/rules-config';
|
|
1
2
|
/**
|
|
2
3
|
* Builds the message `wp-start-upsert-pr` prints when a diff matched ZERO review checklists.
|
|
3
4
|
*
|
|
@@ -6,9 +7,9 @@
|
|
|
6
7
|
* checklist ran and passed". Silence is the bug; refusing would be a worse one.
|
|
7
8
|
*
|
|
8
9
|
* The three empty cases need different fixes, so they get different text:
|
|
9
|
-
* - NONE CONFIGURED — the repo
|
|
10
|
-
* checklist *.md docs if they want reviews, and move on.
|
|
11
|
-
* - MISCONFIGURED — checklists
|
|
10
|
+
* - NONE CONFIGURED — the repo configured no checklists at all. Perfectly fine; mention that a human
|
|
11
|
+
* MAY add checklist *.md docs if they want reviews, and move on.
|
|
12
|
+
* - MISCONFIGURED — checklists IS configured but broken. This one is worth shouting about:
|
|
12
13
|
* the tolerant loader returns [] for it, so a broken doc silently enforces
|
|
13
14
|
* NOTHING while looking configured.
|
|
14
15
|
* - NONE MATCHED — checklists exist and are valid, but none of their patterns hit this diff.
|
|
@@ -19,12 +20,12 @@
|
|
|
19
20
|
*/
|
|
20
21
|
export declare class ChecklistNotice {
|
|
21
22
|
/**
|
|
22
|
-
* @param
|
|
23
|
+
* @param source `prGate.checklists` — where this repo's checklists come from (empty = none)
|
|
23
24
|
* @param manifestErrors `ChecklistManifestService.validate()` output ([] when valid or unconfigured)
|
|
24
|
-
* @param definedCount how many checklists
|
|
25
|
+
* @param definedCount how many checklists that source defines
|
|
25
26
|
* @param finishCommand the command to continue with, named in every branch
|
|
26
27
|
*/
|
|
27
|
-
build(
|
|
28
|
+
build(source: ChecklistSource, manifestErrors: readonly string[], definedCount: number, finishCommand: string): string;
|
|
28
29
|
private reason;
|
|
29
30
|
private continueLine;
|
|
30
31
|
private noneConfigured;
|
|
@@ -11,9 +11,9 @@ const inversify_1 = require("inversify");
|
|
|
11
11
|
* checklist ran and passed". Silence is the bug; refusing would be a worse one.
|
|
12
12
|
*
|
|
13
13
|
* The three empty cases need different fixes, so they get different text:
|
|
14
|
-
* - NONE CONFIGURED — the repo
|
|
15
|
-
* checklist *.md docs if they want reviews, and move on.
|
|
16
|
-
* - MISCONFIGURED — checklists
|
|
14
|
+
* - NONE CONFIGURED — the repo configured no checklists at all. Perfectly fine; mention that a human
|
|
15
|
+
* MAY add checklist *.md docs if they want reviews, and move on.
|
|
16
|
+
* - MISCONFIGURED — checklists IS configured but broken. This one is worth shouting about:
|
|
17
17
|
* the tolerant loader returns [] for it, so a broken doc silently enforces
|
|
18
18
|
* NOTHING while looking configured.
|
|
19
19
|
* - NONE MATCHED — checklists exist and are valid, but none of their patterns hit this diff.
|
|
@@ -24,23 +24,23 @@ const inversify_1 = require("inversify");
|
|
|
24
24
|
*/
|
|
25
25
|
let ChecklistNotice = class ChecklistNotice {
|
|
26
26
|
/**
|
|
27
|
-
* @param
|
|
27
|
+
* @param source `prGate.checklists` — where this repo's checklists come from (empty = none)
|
|
28
28
|
* @param manifestErrors `ChecklistManifestService.validate()` output ([] when valid or unconfigured)
|
|
29
|
-
* @param definedCount how many checklists
|
|
29
|
+
* @param definedCount how many checklists that source defines
|
|
30
30
|
* @param finishCommand the command to continue with, named in every branch
|
|
31
31
|
*/
|
|
32
32
|
// eslint-disable-next-line @typescript-eslint/max-params
|
|
33
|
-
build(
|
|
34
|
-
return `${this.reason(
|
|
33
|
+
build(source, manifestErrors, definedCount, finishCommand) {
|
|
34
|
+
return `${this.reason(source, manifestErrors, definedCount)}\n${this.continueLine(finishCommand)}`;
|
|
35
35
|
}
|
|
36
|
-
reason(
|
|
36
|
+
reason(source, manifestErrors, definedCount) {
|
|
37
37
|
if (manifestErrors.length > 0)
|
|
38
|
-
return this.misconfigured(
|
|
39
|
-
if (
|
|
38
|
+
return this.misconfigured(source.describe(), manifestErrors);
|
|
39
|
+
if (source.isEmpty())
|
|
40
40
|
return this.noneConfigured();
|
|
41
41
|
if (definedCount === 0)
|
|
42
|
-
return this.emptyManifest(
|
|
43
|
-
return this.noneMatched(
|
|
42
|
+
return this.emptyManifest(source.describe());
|
|
43
|
+
return this.noneMatched(source.describe(), definedCount);
|
|
44
44
|
}
|
|
45
45
|
// Every branch ends here: 0 checklists is OK, keep going. Stated plainly so neither the AI nor the
|
|
46
46
|
// human reads the notice as a gate that needs satisfying before finishing.
|
|
@@ -52,29 +52,29 @@ let ChecklistNotice = class ChecklistNotice {
|
|
|
52
52
|
return ('📋 Review checklists: NONE CONFIGURED (0 ran) — that is fine, this repo simply has none.\n' +
|
|
53
53
|
'\n' +
|
|
54
54
|
' FYI for the human: if you ever want per-area reviews enforced on PRs that touch certain\n' +
|
|
55
|
-
' paths, add checklist *.md docs and
|
|
56
|
-
' "commands": { "pr-gate": { "checklists":
|
|
57
|
-
'
|
|
58
|
-
'
|
|
59
|
-
'
|
|
60
|
-
'
|
|
61
|
-
'
|
|
62
|
-
'
|
|
55
|
+
' paths, add checklist *.md docs and list them in webpieces.config.json:\n' +
|
|
56
|
+
' "commands": { "pr-gate": { "checklists": [\n' +
|
|
57
|
+
' { "subagent": "db-migration-reviewer",\n' +
|
|
58
|
+
' "doc": ".claude/review/db-migrations.md",\n' +
|
|
59
|
+
' "patterns": ["**/migrations/**", "**/*.sql"] }\n' +
|
|
60
|
+
' ] } }\n' +
|
|
61
|
+
' `doc` is REPO-relative, `patterns` are path globs, and each entry needs its OWN reviewer\n' +
|
|
62
|
+
' subagent (a .claude/agents/<subagent>.md) — that is how independent review is enforced.');
|
|
63
63
|
}
|
|
64
|
-
emptyManifest(
|
|
65
|
-
return (`📋 Review checklists: 0 defined —
|
|
64
|
+
emptyManifest(sourceLabel) {
|
|
65
|
+
return (`📋 Review checklists: 0 defined — ${sourceLabel} is readable but lists no usable\n` +
|
|
66
66
|
' checklists (every entry needs a non-empty "subagent"). Fine to proceed; worth a look if\n' +
|
|
67
67
|
' you expected some to run.');
|
|
68
68
|
}
|
|
69
69
|
// The one case that deserves volume: it LOOKS configured but enforces nothing.
|
|
70
|
-
misconfigured(
|
|
71
|
-
return (`⚠️ Review checklists: 0 ran because
|
|
70
|
+
misconfigured(sourceLabel, manifestErrors) {
|
|
71
|
+
return (`⚠️ Review checklists: 0 ran because ${sourceLabel} is MISCONFIGURED — so this PR is getting NO\n` +
|
|
72
72
|
' checklist review even though this repo asked for one. Not fatal, but almost certainly not\n' +
|
|
73
73
|
' what you want:\n\n' +
|
|
74
74
|
manifestErrors.map((e) => ` • ${e}`).join('\n'));
|
|
75
75
|
}
|
|
76
|
-
noneMatched(
|
|
77
|
-
return (`📋 Review checklists: ${definedCount} defined in
|
|
76
|
+
noneMatched(sourceLabel, definedCount) {
|
|
77
|
+
return (`📋 Review checklists: ${definedCount} defined in ${sourceLabel}, 0 matched this diff — none of their\n` +
|
|
78
78
|
' path patterns hit a changed file. Expected for changes outside those areas; if you thought\n' +
|
|
79
79
|
' one should have run, check its "patterns" against the changed-file list above.');
|
|
80
80
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checklist-notice.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/checklist-notice.ts"],"names":[],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"checklist-notice.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/checklist-notice.ts"],"names":[],"mappings":";;;;AACA,yCAA2D;AAE3D;;;;;;;;;;;;;;;;;;GAkBG;AAEI,IAAM,eAAe,GAArB,MAAM,eAAe;IACxB;;;;;OAKG;IACH,yDAAyD;IACzD,KAAK,CAAC,MAAuB,EAAE,cAAiC,EAAE,YAAoB,EAAE,aAAqB;QACzG,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,YAAY,CAAC,KAAK,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,EAAE,CAAC;IACvG,CAAC;IAEO,MAAM,CAAC,MAAuB,EAAE,cAAiC,EAAE,YAAoB;QAC3F,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,cAAc,CAAC,CAAC;QAC5F,IAAI,MAAM,CAAC,OAAO,EAAE;YAAE,OAAO,IAAI,CAAC,cAAc,EAAE,CAAC;QACnD,IAAI,YAAY,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;QACrE,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,YAAY,CAAC,CAAC;IAC7D,CAAC;IAED,mGAAmG;IACnG,2EAA2E;IACnE,YAAY,CAAC,aAAqB;QACtC,OAAO,CACH,4FAA4F;YAC5F,6EAA6E,aAAa,IAAI,CACjG,CAAC;IACN,CAAC;IAEO,cAAc;QAClB,OAAO,CACH,4FAA4F;YAC5F,IAAI;YACJ,8FAA8F;YAC9F,6EAA6E;YAC7E,mDAAmD;YACnD,iDAAiD;YACjD,sDAAsD;YACtD,2DAA2D;YAC3D,cAAc;YACd,+FAA+F;YAC/F,4FAA4F,CAC/F,CAAC;IACN,CAAC;IAEO,aAAa,CAAC,WAAmB;QACrC,OAAO,CACH,qCAAqC,WAAW,oCAAoC;YACpF,8FAA8F;YAC9F,8BAA8B,CACjC,CAAC;IACN,CAAC;IAED,+EAA+E;IACvE,aAAa,CAAC,WAAmB,EAAE,cAAiC;QACxE,OAAO,CACH,wCAAwC,WAAW,gDAAgD;YACnG,gGAAgG;YAChG,uBAAuB;YACvB,cAAc,CAAC,GAAG,CAAC,CAAC,CAAS,EAAU,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CACtE,CAAC;IACN,CAAC;IAEO,WAAW,CAAC,WAAmB,EAAE,YAAoB;QACzD,OAAO,CACH,yBAAyB,YAAY,eAAe,WAAW,yCAAyC;YACxG,iGAAiG;YACjG,mFAAmF,CACtF,CAAC;IACN,CAAC;CACJ,CAAA;AArEY,0CAAe;0BAAf,eAAe;IAD3B,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;GAC5B,eAAe,CAqE3B","sourcesContent":["import { ChecklistSource } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\n/**\n * Builds the message `wp-start-upsert-pr` prints when a diff matched ZERO review checklists.\n *\n * ZERO IS A VALID, SUPPORTED STATE — it is NOT an error and NEVER blocks the flow. This exists only\n * because the previous behavior was to print nothing at all, which is indistinguishable from \"the\n * checklist ran and passed\". Silence is the bug; refusing would be a worse one.\n *\n * The three empty cases need different fixes, so they get different text:\n * - NONE CONFIGURED — the repo configured no checklists at all. Perfectly fine; mention that a human\n * MAY add checklist *.md docs if they want reviews, and move on.\n * - MISCONFIGURED — checklists IS configured but broken. This one is worth shouting about:\n * the tolerant loader returns [] for it, so a broken doc silently enforces\n * NOTHING while looking configured.\n * - NONE MATCHED — checklists exist and are valid, but none of their patterns hit this diff.\n * Also fine; report the count so a human can judge whether that is expected.\n *\n * Pure string building, no I/O, so it is unit-testable without git or a repo.\n * `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ChecklistNotice {\n /**\n * @param source `prGate.checklists` — where this repo's checklists come from (empty = none)\n * @param manifestErrors `ChecklistManifestService.validate()` output ([] when valid or unconfigured)\n * @param definedCount how many checklists that source defines\n * @param finishCommand the command to continue with, named in every branch\n */\n // eslint-disable-next-line @typescript-eslint/max-params\n build(source: ChecklistSource, manifestErrors: readonly string[], definedCount: number, finishCommand: string): string {\n return `${this.reason(source, manifestErrors, definedCount)}\\n${this.continueLine(finishCommand)}`;\n }\n\n private reason(source: ChecklistSource, manifestErrors: readonly string[], definedCount: number): string {\n if (manifestErrors.length > 0) return this.misconfigured(source.describe(), manifestErrors);\n if (source.isEmpty()) return this.noneConfigured();\n if (definedCount === 0) return this.emptyManifest(source.describe());\n return this.noneMatched(source.describe(), definedCount);\n }\n\n // Every branch ends here: 0 checklists is OK, keep going. Stated plainly so neither the AI nor the\n // human reads the notice as a gate that needs satisfying before finishing.\n private continueLine(finishCommand: string): string {\n return (\n `\\n✅ Zero checklists is a perfectly valid state — this is INFORMATION, not a blocker, and\\n` +\n ` nothing here needs fixing before you continue. Carry on and run: pnpm ${finishCommand}\\n`\n );\n }\n\n private noneConfigured(): string {\n return (\n '📋 Review checklists: NONE CONFIGURED (0 ran) — that is fine, this repo simply has none.\\n' +\n '\\n' +\n ' FYI for the human: if you ever want per-area reviews enforced on PRs that touch certain\\n' +\n ' paths, add checklist *.md docs and list them in webpieces.config.json:\\n' +\n ' \"commands\": { \"pr-gate\": { \"checklists\": [\\n' +\n ' { \"subagent\": \"db-migration-reviewer\",\\n' +\n ' \"doc\": \".claude/review/db-migrations.md\",\\n' +\n ' \"patterns\": [\"**/migrations/**\", \"**/*.sql\"] }\\n' +\n ' ] } }\\n' +\n ' `doc` is REPO-relative, `patterns` are path globs, and each entry needs its OWN reviewer\\n' +\n ' subagent (a .claude/agents/<subagent>.md) — that is how independent review is enforced.'\n );\n }\n\n private emptyManifest(sourceLabel: string): string {\n return (\n `📋 Review checklists: 0 defined — ${sourceLabel} is readable but lists no usable\\n` +\n ' checklists (every entry needs a non-empty \"subagent\"). Fine to proceed; worth a look if\\n' +\n ' you expected some to run.'\n );\n }\n\n // The one case that deserves volume: it LOOKS configured but enforces nothing.\n private misconfigured(sourceLabel: string, manifestErrors: readonly string[]): string {\n return (\n `⚠️ Review checklists: 0 ran because ${sourceLabel} is MISCONFIGURED — so this PR is getting NO\\n` +\n ' checklist review even though this repo asked for one. Not fatal, but almost certainly not\\n' +\n ' what you want:\\n\\n' +\n manifestErrors.map((e: string): string => ` • ${e}`).join('\\n')\n );\n }\n\n private noneMatched(sourceLabel: string, definedCount: number): string {\n return (\n `📋 Review checklists: ${definedCount} defined in ${sourceLabel}, 0 matched this diff — none of their\\n` +\n ' path patterns hit a changed file. Expected for changes outside those areas; if you thought\\n' +\n ' one should have run, check its \"patterns\" against the changed-file list above.'\n );\n }\n}\n"]}
|
|
@@ -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: build the container and resolve the app so inversify constructs the whole DAG.
|
|
9
|
+
(0, rules_config_1.runMain)(async () => {
|
|
10
|
+
// autobind self-binds every @injectable(Singleton) tooling class (replaces the buildProviderModule registry scan)
|
|
11
|
+
const container = new inversify_1.Container({ autobind: true });
|
|
12
|
+
// Reject `--help`/bogus flags BEFORE the app touches git — an ignored flag must never start the flow.
|
|
13
|
+
container.get(rules_config_1.CliArgs).assertNoArgs(new rules_config_1.CliUsage('wp-checklist', 'Print which reviewer subagents this diff owes, and exactly what to tell them.'));
|
|
14
|
+
await container.get(pr_gate_app_1.PrGateApp).checklist();
|
|
15
|
+
});
|
|
16
|
+
//# sourceMappingURL=wp-checklist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wp-checklist.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/wp-checklist.ts"],"names":[],"mappings":";;;AACA,4BAA0B;AAC1B,yCAAsC;AACtC,0DAAqE;AACrE,+CAA0C;AAE1C,mGAAmG;AACnG,IAAA,sBAAO,EAAC,KAAK,IAAmB,EAAE;IAC9B,kHAAkH;IAClH,MAAM,SAAS,GAAG,IAAI,qBAAS,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,sGAAsG;IACtG,SAAS,CAAC,GAAG,CAAC,sBAAO,CAAC,CAAC,YAAY,CAAC,IAAI,uBAAQ,CAAC,cAAc,EAAE,+EAA+E,CAAC,CAAC,CAAC;IACnJ,MAAM,SAAS,CAAC,GAAG,CAAC,uBAAS,CAAC,CAAC,SAAS,EAAE,CAAC;AAC/C,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: build the container and resolve the app so inversify constructs the whole DAG.\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 // Reject `--help`/bogus flags BEFORE the app touches git — an ignored flag must never start the flow.\n container.get(CliArgs).assertNoArgs(new CliUsage('wp-checklist', 'Print which reviewer subagents this diff owes, and exactly what to tell them.'));\n await container.get(PrGateApp).checklist();\n});\n"]}
|