@webpieces/pr-gate 0.4.498 → 0.4.500
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/dashboard/dashboard.d.ts +15 -0
- package/src/dashboard/dashboard.js +25 -2
- package/src/dashboard/dashboard.js.map +1 -1
- package/src/scripts/commands/finish-upsert-pr-command.d.ts +55 -5
- package/src/scripts/commands/finish-upsert-pr-command.js +162 -30
- package/src/scripts/commands/finish-upsert-pr-command.js.map +1 -1
- package/src/scripts/commands/review-upsert-pr-command.d.ts +88 -0
- package/src/scripts/commands/review-upsert-pr-command.js +244 -0
- package/src/scripts/commands/review-upsert-pr-command.js.map +1 -0
- package/src/scripts/commands/start-upsert-pr-command.d.ts +10 -15
- package/src/scripts/commands/start-upsert-pr-command.js +41 -47
- package/src/scripts/commands/start-upsert-pr-command.js.map +1 -1
- package/src/scripts/pr-gate-app.d.ts +9 -5
- package/src/scripts/pr-gate-app.js +12 -8
- package/src/scripts/pr-gate-app.js.map +1 -1
- package/src/scripts/workflow/checklist-scanner.d.ts +27 -11
- package/src/scripts/workflow/checklist-scanner.js +37 -15
- package/src/scripts/workflow/checklist-scanner.js.map +1 -1
- package/src/scripts/workflow/diff-basis.d.ts +53 -0
- package/src/scripts/workflow/diff-basis.js +113 -0
- package/src/scripts/workflow/diff-basis.js.map +1 -0
- package/src/scripts/workflow/diff-materializer.d.ts +85 -0
- package/src/scripts/workflow/diff-materializer.js +253 -0
- package/src/scripts/workflow/diff-materializer.js.map +1 -0
- package/src/scripts/workflow/git-findForkPoint.d.ts +1 -1
- package/src/scripts/workflow/git-findForkPoint.js +1 -1
- package/src/scripts/workflow/git-findForkPoint.js.map +1 -1
- package/src/scripts/workflow/pr-context-writer.d.ts +16 -10
- package/src/scripts/workflow/pr-context-writer.js +20 -14
- package/src/scripts/workflow/pr-context-writer.js.map +1 -1
- package/src/scripts/workflow/review-stage-receipt.d.ts +36 -0
- package/src/scripts/workflow/review-stage-receipt.js +82 -0
- package/src/scripts/workflow/review-stage-receipt.js.map +1 -0
- package/src/scripts/workflow/reviewer-briefing-builder.d.ts +36 -0
- package/src/scripts/workflow/reviewer-briefing-builder.js +104 -0
- package/src/scripts/workflow/reviewer-briefing-builder.js.map +1 -0
- package/src/scripts/{wp-checklist.js → wp-review-upsert-pr.js} +3 -3
- package/src/scripts/wp-review-upsert-pr.js.map +1 -0
- package/src/scripts/commands/checklist-command.d.ts +0 -27
- package/src/scripts/commands/checklist-command.js +0 -97
- package/src/scripts/commands/checklist-command.js.map +0 -1
- package/src/scripts/wp-checklist.js.map +0 -1
- /package/src/scripts/{wp-checklist.d.ts → wp-review-upsert-pr.d.ts} +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { RepoRootFinder, ChecklistInstructionsService, ReviewerInstructionsService } from '@webpieces/rules-config';
|
|
2
|
+
import { AiBranchName } from '../workflow/git-readAiBranchName';
|
|
3
|
+
import { BuildAffected } from '../workflow/build-affected';
|
|
4
|
+
import { ChecklistNotice } from '../workflow/checklist-notice';
|
|
5
|
+
import { ChecklistScanner } from '../workflow/checklist-scanner';
|
|
6
|
+
import { DiffMaterializer } from '../workflow/diff-materializer';
|
|
7
|
+
import { GitExec } from '../workflow/git-exec';
|
|
8
|
+
import { MergeEnd } from '../workflow/merge-end';
|
|
9
|
+
import { MergeState } from '../workflow/merge-state';
|
|
10
|
+
import { ReviewerBriefingBuilder } from '../workflow/reviewer-briefing-builder';
|
|
11
|
+
import { ReviewStageReceiptService } from '../workflow/review-stage-receipt';
|
|
12
|
+
/**
|
|
13
|
+
* `wp-review-upsert-pr` — STAGE ② of the three-stage PR flow, between `wp-start-upsert-pr` (sync from main)
|
|
14
|
+
* and `wp-finish-upsert-pr` (post the PR).
|
|
15
|
+
*
|
|
16
|
+
* It exists because reviewing came before verifying. Under the old two-command flow the 3-point merge was
|
|
17
|
+
* finalized and the build gate ran inside `wp-finish-upsert-pr` — i.e. AFTER the reviewers had already run.
|
|
18
|
+
* A reviewer could therefore spend a full review on an unresolved merge, or on code that does not compile,
|
|
19
|
+
* and only afterwards would the branch be checked. This command puts the verification first:
|
|
20
|
+
*
|
|
21
|
+
* 1. finalize + VALIDATE any in-progress 3-point merge (moved here from finish)
|
|
22
|
+
* 2. assert a clean tree (meaningful now — step 1 committed the resolution)
|
|
23
|
+
* 3. run the build gate (on the merged, committed tree)
|
|
24
|
+
* 4. scan the checklists, EXTRACT the diff, BRIEF the reviewers
|
|
25
|
+
* 5. write the stage receipt, then print what to spawn + the review.json schema
|
|
26
|
+
*
|
|
27
|
+
* Unlike the report-only command it replaces — which always exited 0, because reporting is not gating — this
|
|
28
|
+
* command CAN FAIL, at steps 1, 2 and 3. That is the point: it fails BEFORE any reviewer is spawned, so a
|
|
29
|
+
* branch with an unresolved merge or a broken build costs zero reviewer tokens.
|
|
30
|
+
*
|
|
31
|
+
* The ordering of 1 → 2 → 3 is load-bearing. MergeEnd stages and commits the conflict resolution, so the
|
|
32
|
+
* tree is clean by the time the build runs; building first would either build a dirty tree (proving nothing
|
|
33
|
+
* about what ships) or refuse a tree that is legitimately mid-resolution.
|
|
34
|
+
*/
|
|
35
|
+
export declare class ReviewUpsertPrCommand {
|
|
36
|
+
private readonly repoRootFinder;
|
|
37
|
+
private readonly aiBranchName;
|
|
38
|
+
private readonly gitExec;
|
|
39
|
+
private readonly buildAffected;
|
|
40
|
+
private readonly mergeState;
|
|
41
|
+
private readonly mergeEnd;
|
|
42
|
+
private readonly checklistScanner;
|
|
43
|
+
private readonly checklistNotice;
|
|
44
|
+
private readonly instructions;
|
|
45
|
+
private readonly materializer;
|
|
46
|
+
private readonly briefingBuilder;
|
|
47
|
+
private readonly reviewerInstructions;
|
|
48
|
+
private readonly receipts;
|
|
49
|
+
constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, gitExec: GitExec, buildAffected: BuildAffected, mergeState: MergeState, mergeEnd: MergeEnd, checklistScanner: ChecklistScanner, checklistNotice: ChecklistNotice, instructions: ChecklistInstructionsService, materializer: DiffMaterializer, briefingBuilder: ReviewerBriefingBuilder, reviewerInstructions: ReviewerInstructionsService, receipts: ReviewStageReceiptService);
|
|
50
|
+
run(): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Validate + commit + finalize a conflict resolution, if one is in flight. MOVED here from
|
|
53
|
+
* `wp-finish-upsert-pr` so that exactly one command owns merge finalization — two owners is the drift
|
|
54
|
+
* failure `PrContextWriter`'s own docstring records having already happened once in this codebase.
|
|
55
|
+
*
|
|
56
|
+
* MergeEnd's three checks are what "did the AI do the 3-point merge right?" means concretely: no
|
|
57
|
+
* conflict markers left in the scoped files, `git diff --diff-filter=U` empty, and a non-empty
|
|
58
|
+
* merge-explanation.md beside every conflicted file.
|
|
59
|
+
*
|
|
60
|
+
* Returns true when a merge was finalized OR there was none to finalize; it throws rather than returning
|
|
61
|
+
* false when validation fails, so the receipt can never record an unvalidated merge as validated.
|
|
62
|
+
*/
|
|
63
|
+
private finalizeAnyInProgressMerge;
|
|
64
|
+
/**
|
|
65
|
+
* The build gate, run BEFORE any reviewer is briefed. Returns the ISO time it passed, for the receipt —
|
|
66
|
+
* which is what lets `wp-finish-upsert-pr` skip its own gate when HEAD has not moved, so moving the gate
|
|
67
|
+
* earlier did not turn one build into two.
|
|
68
|
+
*/
|
|
69
|
+
private runBuildGate;
|
|
70
|
+
/**
|
|
71
|
+
* Extract the diff once, then write one instructions file per reviewer.
|
|
72
|
+
*
|
|
73
|
+
* The changed-file set and the basis both come off the SCAN rather than being recomputed, so the diff a
|
|
74
|
+
* reviewer reads covers exactly the files its checklist matched against.
|
|
75
|
+
*/
|
|
76
|
+
private briefReviewers;
|
|
77
|
+
private rewritePrContext;
|
|
78
|
+
private printDiffSummary;
|
|
79
|
+
/** What the AI must do next: spawn each reviewer, then write review.json. */
|
|
80
|
+
private report;
|
|
81
|
+
/**
|
|
82
|
+
* One copy-paste block per owed reviewer. The prompt is deliberately a POINTER and nothing else: the
|
|
83
|
+
* generated instructions file is the contract, so anything restated here is a second copy that can go
|
|
84
|
+
* stale — which is exactly how a removed `success` field outlived its own removal in print.
|
|
85
|
+
*/
|
|
86
|
+
private spawnBlocks;
|
|
87
|
+
private why;
|
|
88
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ReviewUpsertPrCommand = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
6
|
+
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
const rules_config_1 = require("@webpieces/rules-config");
|
|
8
|
+
const inversify_1 = require("inversify");
|
|
9
|
+
const git_readAiBranchName_1 = require("../workflow/git-readAiBranchName");
|
|
10
|
+
const build_affected_1 = require("../workflow/build-affected");
|
|
11
|
+
const checklist_notice_1 = require("../workflow/checklist-notice");
|
|
12
|
+
const checklist_scanner_1 = require("../workflow/checklist-scanner");
|
|
13
|
+
const diff_materializer_1 = require("../workflow/diff-materializer");
|
|
14
|
+
const git_exec_1 = require("../workflow/git-exec");
|
|
15
|
+
const merge_start_1 = require("../workflow/merge-start");
|
|
16
|
+
const merge_end_1 = require("../workflow/merge-end");
|
|
17
|
+
const merge_state_1 = require("../workflow/merge-state");
|
|
18
|
+
const reviewer_briefing_builder_1 = require("../workflow/reviewer-briefing-builder");
|
|
19
|
+
const review_stage_receipt_1 = require("../workflow/review-stage-receipt");
|
|
20
|
+
const SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
|
|
21
|
+
/**
|
|
22
|
+
* `wp-review-upsert-pr` — STAGE ② of the three-stage PR flow, between `wp-start-upsert-pr` (sync from main)
|
|
23
|
+
* and `wp-finish-upsert-pr` (post the PR).
|
|
24
|
+
*
|
|
25
|
+
* It exists because reviewing came before verifying. Under the old two-command flow the 3-point merge was
|
|
26
|
+
* finalized and the build gate ran inside `wp-finish-upsert-pr` — i.e. AFTER the reviewers had already run.
|
|
27
|
+
* A reviewer could therefore spend a full review on an unresolved merge, or on code that does not compile,
|
|
28
|
+
* and only afterwards would the branch be checked. This command puts the verification first:
|
|
29
|
+
*
|
|
30
|
+
* 1. finalize + VALIDATE any in-progress 3-point merge (moved here from finish)
|
|
31
|
+
* 2. assert a clean tree (meaningful now — step 1 committed the resolution)
|
|
32
|
+
* 3. run the build gate (on the merged, committed tree)
|
|
33
|
+
* 4. scan the checklists, EXTRACT the diff, BRIEF the reviewers
|
|
34
|
+
* 5. write the stage receipt, then print what to spawn + the review.json schema
|
|
35
|
+
*
|
|
36
|
+
* Unlike the report-only command it replaces — which always exited 0, because reporting is not gating — this
|
|
37
|
+
* command CAN FAIL, at steps 1, 2 and 3. That is the point: it fails BEFORE any reviewer is spawned, so a
|
|
38
|
+
* branch with an unresolved merge or a broken build costs zero reviewer tokens.
|
|
39
|
+
*
|
|
40
|
+
* The ordering of 1 → 2 → 3 is load-bearing. MergeEnd stages and commits the conflict resolution, so the
|
|
41
|
+
* tree is clean by the time the build runs; building first would either build a dirty tree (proving nothing
|
|
42
|
+
* about what ships) or refuse a tree that is legitimately mid-resolution.
|
|
43
|
+
*/
|
|
44
|
+
let ReviewUpsertPrCommand = class ReviewUpsertPrCommand {
|
|
45
|
+
repoRootFinder;
|
|
46
|
+
aiBranchName;
|
|
47
|
+
gitExec;
|
|
48
|
+
buildAffected;
|
|
49
|
+
mergeState;
|
|
50
|
+
mergeEnd;
|
|
51
|
+
checklistScanner;
|
|
52
|
+
checklistNotice;
|
|
53
|
+
instructions;
|
|
54
|
+
materializer;
|
|
55
|
+
briefingBuilder;
|
|
56
|
+
reviewerInstructions;
|
|
57
|
+
receipts;
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/max-params
|
|
59
|
+
constructor(repoRootFinder, aiBranchName, gitExec, buildAffected, mergeState, mergeEnd, checklistScanner, checklistNotice, instructions, materializer, briefingBuilder, reviewerInstructions, receipts) {
|
|
60
|
+
this.repoRootFinder = repoRootFinder;
|
|
61
|
+
this.aiBranchName = aiBranchName;
|
|
62
|
+
this.gitExec = gitExec;
|
|
63
|
+
this.buildAffected = buildAffected;
|
|
64
|
+
this.mergeState = mergeState;
|
|
65
|
+
this.mergeEnd = mergeEnd;
|
|
66
|
+
this.checklistScanner = checklistScanner;
|
|
67
|
+
this.checklistNotice = checklistNotice;
|
|
68
|
+
this.instructions = instructions;
|
|
69
|
+
this.materializer = materializer;
|
|
70
|
+
this.briefingBuilder = briefingBuilder;
|
|
71
|
+
this.reviewerInstructions = reviewerInstructions;
|
|
72
|
+
this.receipts = receipts;
|
|
73
|
+
}
|
|
74
|
+
async run() {
|
|
75
|
+
const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
|
|
76
|
+
(0, rules_config_1.writeTemplate)(repoRoot, 'webpieces.git-workflow.md');
|
|
77
|
+
(0, rules_config_1.writeTemplate)(repoRoot, 'webpieces.review-checklists.md');
|
|
78
|
+
const featureName = this.aiBranchName.getFeatureName();
|
|
79
|
+
const config = (0, rules_config_1.loadAndValidate)(repoRoot).prGate;
|
|
80
|
+
const mergeValidated = await this.finalizeAnyInProgressMerge(repoRoot);
|
|
81
|
+
this.gitExec.assertCleanTree(repoRoot);
|
|
82
|
+
const buildPassedAt = this.runBuildGate(repoRoot);
|
|
83
|
+
const scan = this.checklistScanner.scan(repoRoot, config.checklists, new checklist_scanner_1.ChecklistScanOptions(false));
|
|
84
|
+
const briefings = this.briefReviewers(repoRoot, featureName, scan, config);
|
|
85
|
+
this.receipts.write(repoRoot, featureName, new review_stage_receipt_1.ReviewStageReceipt(scan.basis.headSha, mergeValidated, this.buildAffected.resolveBuildCommand(repoRoot), buildPassedAt, briefings.map((b) => b.subagent)));
|
|
86
|
+
this.report(repoRoot, featureName, scan, briefings);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Validate + commit + finalize a conflict resolution, if one is in flight. MOVED here from
|
|
90
|
+
* `wp-finish-upsert-pr` so that exactly one command owns merge finalization — two owners is the drift
|
|
91
|
+
* failure `PrContextWriter`'s own docstring records having already happened once in this codebase.
|
|
92
|
+
*
|
|
93
|
+
* MergeEnd's three checks are what "did the AI do the 3-point merge right?" means concretely: no
|
|
94
|
+
* conflict markers left in the scoped files, `git diff --diff-filter=U` empty, and a non-empty
|
|
95
|
+
* merge-explanation.md beside every conflicted file.
|
|
96
|
+
*
|
|
97
|
+
* Returns true when a merge was finalized OR there was none to finalize; it throws rather than returning
|
|
98
|
+
* false when validation fails, so the receipt can never record an unvalidated merge as validated.
|
|
99
|
+
*/
|
|
100
|
+
async finalizeAnyInProgressMerge(repoRoot) {
|
|
101
|
+
const home = this.mergeState.mergeDirFor(repoRoot, this.aiBranchName.getFeatureName());
|
|
102
|
+
const activeDir = this.mergeState.findActiveMergeRunDir(home);
|
|
103
|
+
const marker = activeDir ? this.mergeState.readMergeMarker(activeDir) : null;
|
|
104
|
+
if (!activeDir || !marker || marker.validated)
|
|
105
|
+
return true;
|
|
106
|
+
process.stdout.write('\n' + SEP + '① Validating your 3-point merge\n' + SEP + '\n');
|
|
107
|
+
// pushRemote:false — finish still owns the single push.
|
|
108
|
+
await this.mergeEnd.mergeEnd(repoRoot, 'wp-review-upsert-pr', activeDir, new merge_start_1.MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber), new merge_end_1.MergeEndOptions(marker.conflictedFiles, false));
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The build gate, run BEFORE any reviewer is briefed. Returns the ISO time it passed, for the receipt —
|
|
113
|
+
* which is what lets `wp-finish-upsert-pr` skip its own gate when HEAD has not moved, so moving the gate
|
|
114
|
+
* earlier did not turn one build into two.
|
|
115
|
+
*/
|
|
116
|
+
runBuildGate(repoRoot) {
|
|
117
|
+
this.buildAffected.runBuildGate(repoRoot, new build_affected_1.BuildGateOptions('🛠️ Build gate (pre-review)', 'pnpm wp-review-upsert-pr', 'Build failed — NO reviewer was briefed and no diff was extracted. Fix it, then re-run.'));
|
|
118
|
+
return new Date().toISOString();
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Extract the diff once, then write one instructions file per reviewer.
|
|
122
|
+
*
|
|
123
|
+
* The changed-file set and the basis both come off the SCAN rather than being recomputed, so the diff a
|
|
124
|
+
* reviewer reads covers exactly the files its checklist matched against.
|
|
125
|
+
*/
|
|
126
|
+
briefReviewers(repoRoot, featureName, scan, config) {
|
|
127
|
+
if (scan.basis.unresolved)
|
|
128
|
+
return [];
|
|
129
|
+
const manifest = this.materializer.materialize(repoRoot, featureName, scan.basis, scan.changedFiles, config.reviewDiffExclude);
|
|
130
|
+
const diffDir = this.materializer.diffDirFor(repoRoot, featureName);
|
|
131
|
+
// Re-point pr-context.json at the extracted diff now that there is one — the scan wrote it before
|
|
132
|
+
// materialization, so without this the reviewers' own context file would not mention the diff dir.
|
|
133
|
+
this.rewritePrContext(repoRoot, featureName, diffDir);
|
|
134
|
+
const briefings = this.briefingBuilder.build(repoRoot, scan, manifest, diffDir, config);
|
|
135
|
+
const dir = this.reviewerInstructions.instructionsDirFor(repoRoot, featureName);
|
|
136
|
+
fs.rmSync(dir, { recursive: true, force: true }); // stale instructions read as current are worse than none
|
|
137
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
138
|
+
for (const b of briefings) {
|
|
139
|
+
fs.writeFileSync(this.reviewerInstructions.pathFor(repoRoot, featureName, b.subagent), this.reviewerInstructions.render(b));
|
|
140
|
+
}
|
|
141
|
+
this.printDiffSummary(manifest, diffDir);
|
|
142
|
+
return briefings;
|
|
143
|
+
}
|
|
144
|
+
// pr-context.json is written by the scan (before the diff exists). Patch in the dir rather than
|
|
145
|
+
// re-deriving the whole context: re-deriving means a second basis resolution, which is the drift.
|
|
146
|
+
rewritePrContext(repoRoot, featureName, diffDir) {
|
|
147
|
+
const p = path.join(repoRoot, '.webpieces', 'pr-review', featureName, 'pr-context.json');
|
|
148
|
+
if (!fs.existsSync(p))
|
|
149
|
+
return;
|
|
150
|
+
// webpieces-disable no-unmanaged-exceptions -- chokepoint: an unreadable context is left as-is, never fatal
|
|
151
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
152
|
+
try {
|
|
153
|
+
// webpieces-disable no-any-unknown -- opaque parsed JSON, one key added
|
|
154
|
+
const raw = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
155
|
+
raw['diffDir'] = diffDir;
|
|
156
|
+
fs.writeFileSync(p, JSON.stringify(raw, null, 2) + '\n');
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
const error = (0, rules_config_1.toError)(err);
|
|
160
|
+
void error; // leave the file as-is — the instructions files carry the same paths directly
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
printDiffSummary(manifest, diffDir) {
|
|
164
|
+
process.stdout.write('\n' + SEP + '② Diff extracted for the reviewers\n' + SEP + '\n');
|
|
165
|
+
process.stdout.write(` ${manifest.entries.length} file(s) → ${diffDir}\n`);
|
|
166
|
+
if (manifest.excluded.length > 0) {
|
|
167
|
+
process.stdout.write(` ${manifest.excluded.length} excluded by pr-gate.reviewDiffExclude (stubbed, still matched)\n`);
|
|
168
|
+
}
|
|
169
|
+
const truncated = manifest.entries.filter((e) => e.truncated).length;
|
|
170
|
+
if (truncated > 0)
|
|
171
|
+
process.stdout.write(` ${truncated} truncated at the per-file cap (each says so in its footer)\n`);
|
|
172
|
+
}
|
|
173
|
+
/** What the AI must do next: spawn each reviewer, then write review.json. */
|
|
174
|
+
report(repoRoot, featureName, scan, briefings) {
|
|
175
|
+
process.stdout.write('\n' + SEP + '③ Review, then finish\n' + SEP + '\n');
|
|
176
|
+
if (scan.applicable.length === 0) {
|
|
177
|
+
process.stdout.write(this.checklistNotice.build(scan.defined.length, 'wp-finish-upsert-pr'));
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
process.stdout.write(this.spawnBlocks(repoRoot, featureName, scan, briefings));
|
|
181
|
+
}
|
|
182
|
+
process.stdout.write(`\nThen review your own changes and\n${(0, rules_config_1.reviewJsonSchemaHint)(scan.reviewPath)}\n\n` +
|
|
183
|
+
`Finally run: pnpm wp-finish-upsert-pr\n` +
|
|
184
|
+
`(The build gate is already green for this commit — finish reuses it unless HEAD moves.)\n\n`);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* One copy-paste block per owed reviewer. The prompt is deliberately a POINTER and nothing else: the
|
|
188
|
+
* generated instructions file is the contract, so anything restated here is a second copy that can go
|
|
189
|
+
* stale — which is exactly how a removed `success` field outlived its own removal in print.
|
|
190
|
+
*/
|
|
191
|
+
// eslint-disable-next-line @typescript-eslint/max-params
|
|
192
|
+
spawnBlocks(repoRoot, featureName, scan, briefings) {
|
|
193
|
+
const reviewedIds = new Set(scan.reviewed.map((r) => r.id));
|
|
194
|
+
const owed = briefings.filter((b) => !reviewedIds.has(b.checklistId));
|
|
195
|
+
const lines = [];
|
|
196
|
+
for (const r of scan.reviewed) {
|
|
197
|
+
lines.push(` ✓ ${r.subagent} — already reviewed on this branch (reusing its review-${r.id}.json)`);
|
|
198
|
+
}
|
|
199
|
+
// A verdict file that EXISTS but is unreadable as a verdict is called out here. Without it this
|
|
200
|
+
// reports the checklist as simply owed, and the AI re-runs a reviewer that already ran instead of
|
|
201
|
+
// correcting the file sitting right there.
|
|
202
|
+
for (const e of scan.formatErrors)
|
|
203
|
+
lines.push(` ⛔ ${e}`);
|
|
204
|
+
if (owed.length === 0) {
|
|
205
|
+
lines.push('', '✅ Every checklist that applies is already reviewed — nothing to spawn.');
|
|
206
|
+
return lines.join('\n') + '\n';
|
|
207
|
+
}
|
|
208
|
+
lines.push('', `Spawn these ${owed.length} reviewer subagent(s) — a SEPARATE one each. You may NOT review your own`, 'work, and you may NOT write a reviewer\'s verdict file on its behalf.', '');
|
|
209
|
+
for (const b of owed) {
|
|
210
|
+
lines.push(` ▶ ${b.subagent} — ${this.why(b)}`);
|
|
211
|
+
lines.push(` subagent_type: ${b.subagent}`);
|
|
212
|
+
lines.push(' prompt: Read your instructions file FIRST and follow it exactly:');
|
|
213
|
+
lines.push(` ${this.reviewerInstructions.pathFor(repoRoot, featureName, b.subagent)}`);
|
|
214
|
+
lines.push('');
|
|
215
|
+
}
|
|
216
|
+
return lines.join('\n');
|
|
217
|
+
}
|
|
218
|
+
// Why this one is in scope. A patternless checklist is NOT "matched" — it always runs, over the whole
|
|
219
|
+
// diff, and saying so is what tells a repo its checklist is firing on docs-only PRs by design.
|
|
220
|
+
why(b) {
|
|
221
|
+
if (b.matchedPatterns.length === 0) {
|
|
222
|
+
return `ALWAYS RUNS (no "patterns" configured), whole diff in scope — ${b.myFiles.length} file(s)`;
|
|
223
|
+
}
|
|
224
|
+
return `${b.myFiles.length} file(s) matched ${b.matchedPatterns.map((p) => `"${p}"`).join(', ')}`;
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
exports.ReviewUpsertPrCommand = ReviewUpsertPrCommand;
|
|
228
|
+
exports.ReviewUpsertPrCommand = ReviewUpsertPrCommand = tslib_1.__decorate([
|
|
229
|
+
(0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
|
|
230
|
+
tslib_1.__metadata("design:paramtypes", [rules_config_1.RepoRootFinder,
|
|
231
|
+
git_readAiBranchName_1.AiBranchName,
|
|
232
|
+
git_exec_1.GitExec,
|
|
233
|
+
build_affected_1.BuildAffected,
|
|
234
|
+
merge_state_1.MergeState,
|
|
235
|
+
merge_end_1.MergeEnd,
|
|
236
|
+
checklist_scanner_1.ChecklistScanner,
|
|
237
|
+
checklist_notice_1.ChecklistNotice,
|
|
238
|
+
rules_config_1.ChecklistInstructionsService,
|
|
239
|
+
diff_materializer_1.DiffMaterializer,
|
|
240
|
+
reviewer_briefing_builder_1.ReviewerBriefingBuilder,
|
|
241
|
+
rules_config_1.ReviewerInstructionsService,
|
|
242
|
+
review_stage_receipt_1.ReviewStageReceiptService])
|
|
243
|
+
], ReviewUpsertPrCommand);
|
|
244
|
+
//# sourceMappingURL=review-upsert-pr-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-upsert-pr-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/review-upsert-pr-command.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport {\n loadAndValidate, reviewJsonSchemaHint, writeTemplate, PrGateConfig, RepoRootFinder,\n ChecklistInstructionsService, RequiredChecklist, ReviewerBriefing, ReviewerInstructionsService, toError,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BuildAffected, BuildGateOptions } from '../workflow/build-affected';\nimport { ChecklistNotice } from '../workflow/checklist-notice';\nimport { ChecklistScan, ChecklistScanOptions, ChecklistScanner } from '../workflow/checklist-scanner';\nimport { DiffManifest, DiffManifestEntry, DiffMaterializer } from '../workflow/diff-materializer';\nimport { GitExec } from '../workflow/git-exec';\nimport { MergeContext } from '../workflow/merge-start';\nimport { MergeEnd, MergeEndOptions } from '../workflow/merge-end';\nimport { MergeState } from '../workflow/merge-state';\nimport { ReviewerBriefingBuilder } from '../workflow/reviewer-briefing-builder';\nimport { ReviewStageReceipt, ReviewStageReceiptService } from '../workflow/review-stage-receipt';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n/**\n * `wp-review-upsert-pr` — STAGE ② of the three-stage PR flow, between `wp-start-upsert-pr` (sync from main)\n * and `wp-finish-upsert-pr` (post the PR).\n *\n * It exists because reviewing came before verifying. Under the old two-command flow the 3-point merge was\n * finalized and the build gate ran inside `wp-finish-upsert-pr` — i.e. AFTER the reviewers had already run.\n * A reviewer could therefore spend a full review on an unresolved merge, or on code that does not compile,\n * and only afterwards would the branch be checked. This command puts the verification first:\n *\n * 1. finalize + VALIDATE any in-progress 3-point merge (moved here from finish)\n * 2. assert a clean tree (meaningful now — step 1 committed the resolution)\n * 3. run the build gate (on the merged, committed tree)\n * 4. scan the checklists, EXTRACT the diff, BRIEF the reviewers\n * 5. write the stage receipt, then print what to spawn + the review.json schema\n *\n * Unlike the report-only command it replaces — which always exited 0, because reporting is not gating — this\n * command CAN FAIL, at steps 1, 2 and 3. That is the point: it fails BEFORE any reviewer is spawned, so a\n * branch with an unresolved merge or a broken build costs zero reviewer tokens.\n *\n * The ordering of 1 → 2 → 3 is load-bearing. MergeEnd stages and commits the conflict resolution, so the\n * tree is clean by the time the build runs; building first would either build a dirty tree (proving nothing\n * about what ships) or refuse a tree that is legitimately mid-resolution.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ReviewUpsertPrCommand {\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly aiBranchName: AiBranchName,\n private readonly gitExec: GitExec,\n private readonly buildAffected: BuildAffected,\n private readonly mergeState: MergeState,\n private readonly mergeEnd: MergeEnd,\n private readonly checklistScanner: ChecklistScanner,\n private readonly checklistNotice: ChecklistNotice,\n private readonly instructions: ChecklistInstructionsService,\n private readonly materializer: DiffMaterializer,\n private readonly briefingBuilder: ReviewerBriefingBuilder,\n private readonly reviewerInstructions: ReviewerInstructionsService,\n private readonly receipts: ReviewStageReceiptService,\n ) {}\n\n async run(): Promise<void> {\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n writeTemplate(repoRoot, 'webpieces.git-workflow.md');\n writeTemplate(repoRoot, 'webpieces.review-checklists.md');\n const featureName = this.aiBranchName.getFeatureName();\n const config = loadAndValidate(repoRoot).prGate;\n\n const mergeValidated = await this.finalizeAnyInProgressMerge(repoRoot);\n this.gitExec.assertCleanTree(repoRoot);\n const buildPassedAt = this.runBuildGate(repoRoot);\n\n const scan = this.checklistScanner.scan(repoRoot, config.checklists, new ChecklistScanOptions(false));\n const briefings = this.briefReviewers(repoRoot, featureName, scan, config);\n this.receipts.write(repoRoot, featureName, new ReviewStageReceipt(\n scan.basis.headSha, mergeValidated, this.buildAffected.resolveBuildCommand(repoRoot), buildPassedAt,\n briefings.map((b: ReviewerBriefing): string => b.subagent),\n ));\n this.report(repoRoot, featureName, scan, briefings);\n }\n\n /**\n * Validate + commit + finalize a conflict resolution, if one is in flight. MOVED here from\n * `wp-finish-upsert-pr` so that exactly one command owns merge finalization — two owners is the drift\n * failure `PrContextWriter`'s own docstring records having already happened once in this codebase.\n *\n * MergeEnd's three checks are what \"did the AI do the 3-point merge right?\" means concretely: no\n * conflict markers left in the scoped files, `git diff --diff-filter=U` empty, and a non-empty\n * merge-explanation.md beside every conflicted file.\n *\n * Returns true when a merge was finalized OR there was none to finalize; it throws rather than returning\n * false when validation fails, so the receipt can never record an unvalidated merge as validated.\n */\n private async finalizeAnyInProgressMerge(repoRoot: string): Promise<boolean> {\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 true;\n process.stdout.write('\\n' + SEP + '① Validating your 3-point merge\\n' + SEP + '\\n');\n // pushRemote:false — finish still owns the single push.\n await this.mergeEnd.mergeEnd(\n repoRoot, 'wp-review-upsert-pr', activeDir,\n new MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber),\n new MergeEndOptions(marker.conflictedFiles, false),\n );\n return true;\n }\n\n /**\n * The build gate, run BEFORE any reviewer is briefed. Returns the ISO time it passed, for the receipt —\n * which is what lets `wp-finish-upsert-pr` skip its own gate when HEAD has not moved, so moving the gate\n * earlier did not turn one build into two.\n */\n private runBuildGate(repoRoot: string): string {\n this.buildAffected.runBuildGate(repoRoot, new BuildGateOptions(\n '🛠️ Build gate (pre-review)',\n 'pnpm wp-review-upsert-pr',\n 'Build failed — NO reviewer was briefed and no diff was extracted. Fix it, then re-run.',\n ));\n return new Date().toISOString();\n }\n\n /**\n * Extract the diff once, then write one instructions file per reviewer.\n *\n * The changed-file set and the basis both come off the SCAN rather than being recomputed, so the diff a\n * reviewer reads covers exactly the files its checklist matched against.\n */\n private briefReviewers(repoRoot: string, featureName: string, scan: ChecklistScan, config: PrGateConfig): ReviewerBriefing[] {\n if (scan.basis.unresolved) return [];\n const manifest = this.materializer.materialize(\n repoRoot, featureName, scan.basis, scan.changedFiles, config.reviewDiffExclude);\n const diffDir = this.materializer.diffDirFor(repoRoot, featureName);\n // Re-point pr-context.json at the extracted diff now that there is one — the scan wrote it before\n // materialization, so without this the reviewers' own context file would not mention the diff dir.\n this.rewritePrContext(repoRoot, featureName, diffDir);\n const briefings = this.briefingBuilder.build(repoRoot, scan, manifest, diffDir, config);\n const dir = this.reviewerInstructions.instructionsDirFor(repoRoot, featureName);\n fs.rmSync(dir, { recursive: true, force: true }); // stale instructions read as current are worse than none\n fs.mkdirSync(dir, { recursive: true });\n for (const b of briefings) {\n fs.writeFileSync(this.reviewerInstructions.pathFor(repoRoot, featureName, b.subagent),\n this.reviewerInstructions.render(b));\n }\n this.printDiffSummary(manifest, diffDir);\n return briefings;\n }\n\n // pr-context.json is written by the scan (before the diff exists). Patch in the dir rather than\n // re-deriving the whole context: re-deriving means a second basis resolution, which is the drift.\n private rewritePrContext(repoRoot: string, featureName: string, diffDir: string): void {\n const p = path.join(repoRoot, '.webpieces', 'pr-review', featureName, 'pr-context.json');\n if (!fs.existsSync(p)) return;\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unreadable context is left as-is, never fatal\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- opaque parsed JSON, one key added\n const raw = JSON.parse(fs.readFileSync(p, 'utf8')) as Record<string, unknown>;\n raw['diffDir'] = diffDir;\n fs.writeFileSync(p, JSON.stringify(raw, null, 2) + '\\n');\n } catch (err: unknown) {\n const error = toError(err);\n void error; // leave the file as-is — the instructions files carry the same paths directly\n }\n }\n\n private printDiffSummary(manifest: DiffManifest, diffDir: string): void {\n process.stdout.write('\\n' + SEP + '② Diff extracted for the reviewers\\n' + SEP + '\\n');\n process.stdout.write(` ${manifest.entries.length} file(s) → ${diffDir}\\n`);\n if (manifest.excluded.length > 0) {\n process.stdout.write(` ${manifest.excluded.length} excluded by pr-gate.reviewDiffExclude (stubbed, still matched)\\n`);\n }\n const truncated = manifest.entries.filter((e: DiffManifestEntry): boolean => e.truncated).length;\n if (truncated > 0) process.stdout.write(` ${truncated} truncated at the per-file cap (each says so in its footer)\\n`);\n }\n\n /** What the AI must do next: spawn each reviewer, then write review.json. */\n private report(repoRoot: string, featureName: string, scan: ChecklistScan, briefings: readonly ReviewerBriefing[]): void {\n process.stdout.write('\\n' + SEP + '③ Review, then finish\\n' + SEP + '\\n');\n if (scan.applicable.length === 0) {\n process.stdout.write(this.checklistNotice.build(scan.defined.length, 'wp-finish-upsert-pr'));\n } else {\n process.stdout.write(this.spawnBlocks(repoRoot, featureName, scan, briefings));\n }\n process.stdout.write(\n `\\nThen review your own changes and\\n${reviewJsonSchemaHint(scan.reviewPath)}\\n\\n` +\n `Finally run: pnpm wp-finish-upsert-pr\\n` +\n `(The build gate is already green for this commit — finish reuses it unless HEAD moves.)\\n\\n`);\n }\n\n /**\n * One copy-paste block per owed reviewer. The prompt is deliberately a POINTER and nothing else: the\n * generated instructions file is the contract, so anything restated here is a second copy that can go\n * stale — which is exactly how a removed `success` field outlived its own removal in print.\n */\n // eslint-disable-next-line @typescript-eslint/max-params\n private spawnBlocks(repoRoot: string, featureName: string, scan: ChecklistScan, briefings: readonly ReviewerBriefing[]): string {\n const reviewedIds = new Set(scan.reviewed.map((r: RequiredChecklist): string => r.id));\n const owed = briefings.filter((b: ReviewerBriefing): boolean => !reviewedIds.has(b.checklistId));\n const lines: string[] = [];\n for (const r of scan.reviewed) {\n lines.push(` ✓ ${r.subagent} — already reviewed on this branch (reusing its review-${r.id}.json)`);\n }\n // A verdict file that EXISTS but is unreadable as a verdict is called out here. Without it this\n // reports the checklist as simply owed, and the AI re-runs a reviewer that already ran instead of\n // correcting the file sitting right there.\n for (const e of scan.formatErrors) lines.push(` ⛔ ${e}`);\n if (owed.length === 0) {\n lines.push('', '✅ Every checklist that applies is already reviewed — nothing to spawn.');\n return lines.join('\\n') + '\\n';\n }\n lines.push('',\n `Spawn these ${owed.length} reviewer subagent(s) — a SEPARATE one each. You may NOT review your own`,\n 'work, and you may NOT write a reviewer\\'s verdict file on its behalf.',\n '');\n for (const b of owed) {\n lines.push(` ▶ ${b.subagent} — ${this.why(b)}`);\n lines.push(` subagent_type: ${b.subagent}`);\n lines.push(' prompt: Read your instructions file FIRST and follow it exactly:');\n lines.push(` ${this.reviewerInstructions.pathFor(repoRoot, featureName, b.subagent)}`);\n lines.push('');\n }\n return lines.join('\\n');\n }\n\n // Why this one is in scope. A patternless checklist is NOT \"matched\" — it always runs, over the whole\n // diff, and saying so is what tells a repo its checklist is firing on docs-only PRs by design.\n private why(b: ReviewerBriefing): string {\n if (b.matchedPatterns.length === 0) {\n return `ALWAYS RUNS (no \"patterns\" configured), whole diff in scope — ${b.myFiles.length} file(s)`;\n }\n return `${b.myFiles.length} file(s) matched ${b.matchedPatterns.map((p: string): string => `\"${p}\"`).join(', ')}`;\n }\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { RepoRootFinder } from '@webpieces/rules-config';
|
|
2
2
|
import { AiBranchName } from '../workflow/git-readAiBranchName';
|
|
3
3
|
import { BranchNaming } from '../workflow/branch-naming';
|
|
4
|
+
import { DiffBasisResolver } from '../workflow/diff-basis';
|
|
4
5
|
import { GitExec } from '../workflow/git-exec';
|
|
5
|
-
import { ForkPoint } from '../workflow/git-findForkPoint';
|
|
6
6
|
import { PrContextWriter } from '../workflow/pr-context-writer';
|
|
7
7
|
import { RunUpdate } from '../workflow/run-update';
|
|
8
8
|
export declare class StartUpsertPrCommand {
|
|
@@ -11,26 +11,21 @@ export declare class StartUpsertPrCommand {
|
|
|
11
11
|
private readonly branchNaming;
|
|
12
12
|
private readonly gitExec;
|
|
13
13
|
private readonly runUpdate;
|
|
14
|
-
private readonly
|
|
14
|
+
private readonly diffBasisResolver;
|
|
15
15
|
private readonly prContextWriter;
|
|
16
|
-
constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, branchNaming: BranchNaming, gitExec: GitExec, runUpdate: RunUpdate,
|
|
16
|
+
constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, branchNaming: BranchNaming, gitExec: GitExec, runUpdate: RunUpdate, diffBasisResolver: DiffBasisResolver, prContextWriter: PrContextWriter);
|
|
17
17
|
run(): Promise<void>;
|
|
18
18
|
/**
|
|
19
|
-
* Hand the AI its next step:
|
|
19
|
+
* Hand the AI its ONE next step: `wp-review-upsert-pr`.
|
|
20
20
|
*
|
|
21
|
-
* This
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* the
|
|
25
|
-
*
|
|
21
|
+
* This prints exactly one command on purpose. It used to print three (run the checklist, write
|
|
22
|
+
* review.json, then finish), and a three-item list is a list with a step to skip. Stage ② now owns all
|
|
23
|
+
* of it — it validates the 3-point merge, builds, materializes the diff, briefs the reviewers, AND
|
|
24
|
+
* prints the review.json schema — so there is nothing here to enumerate. Notably review.json is NOT
|
|
25
|
+
* requested here any more: asking for a written review before the branch is known to compile invites
|
|
26
|
+
* one that describes code that does not build.
|
|
26
27
|
*/
|
|
27
28
|
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;
|
|
34
29
|
private contextLines;
|
|
35
30
|
private scaffoldCiWorkflow;
|
|
36
31
|
private updateBranchFromMain;
|
|
@@ -6,32 +6,37 @@ const rules_config_1 = require("@webpieces/rules-config");
|
|
|
6
6
|
const inversify_1 = require("inversify");
|
|
7
7
|
const git_readAiBranchName_1 = require("../workflow/git-readAiBranchName");
|
|
8
8
|
const branch_naming_1 = require("../workflow/branch-naming");
|
|
9
|
+
const diff_basis_1 = require("../workflow/diff-basis");
|
|
9
10
|
const git_exec_1 = require("../workflow/git-exec");
|
|
10
|
-
const git_findForkPoint_1 = require("../workflow/git-findForkPoint");
|
|
11
11
|
const pr_context_writer_1 = require("../workflow/pr-context-writer");
|
|
12
12
|
const run_update_1 = require("../workflow/run-update");
|
|
13
13
|
const SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// build gate is
|
|
18
|
-
//
|
|
19
|
-
//
|
|
14
|
+
// STAGE ① of the AI-first PR flow: the deterministic setup — update from main / drive the 3-point merge —
|
|
15
|
+
// then hand the AI ONE next command, `wp-review-upsert-pr`.
|
|
16
|
+
//
|
|
17
|
+
// It runs NO build gate, and that is still deliberate, but the reason has changed. It is not that the build
|
|
18
|
+
// belongs only at the end: it is that at THIS point a conflicted 3-point merge may still be unresolved, so
|
|
19
|
+
// there is nothing worth building yet. The gate moved FORWARD, not away — stage ② finalizes the merge and
|
|
20
|
+
// then builds, so reviewers never judge a branch that does not compile, and wp-finish-upsert-pr skips the
|
|
21
|
+
// rebuild when HEAD has not moved since. `pr-gate.buildCommand` is therefore no longer a finish-only knob.
|
|
22
|
+
//
|
|
23
|
+
// This command NEVER creates/updates a PR and NEVER pushes: all `gh` posting and the ONE push live in
|
|
24
|
+
// finish, behind review.json + the checklists + the build gate.
|
|
20
25
|
let StartUpsertPrCommand = class StartUpsertPrCommand {
|
|
21
26
|
repoRootFinder;
|
|
22
27
|
aiBranchName;
|
|
23
28
|
branchNaming;
|
|
24
29
|
gitExec;
|
|
25
30
|
runUpdate;
|
|
26
|
-
|
|
31
|
+
diffBasisResolver;
|
|
27
32
|
prContextWriter;
|
|
28
|
-
constructor(repoRootFinder, aiBranchName, branchNaming, gitExec, runUpdate,
|
|
33
|
+
constructor(repoRootFinder, aiBranchName, branchNaming, gitExec, runUpdate, diffBasisResolver, prContextWriter) {
|
|
29
34
|
this.repoRootFinder = repoRootFinder;
|
|
30
35
|
this.aiBranchName = aiBranchName;
|
|
31
36
|
this.branchNaming = branchNaming;
|
|
32
37
|
this.gitExec = gitExec;
|
|
33
38
|
this.runUpdate = runUpdate;
|
|
34
|
-
this.
|
|
39
|
+
this.diffBasisResolver = diffBasisResolver;
|
|
35
40
|
this.prContextWriter = prContextWriter;
|
|
36
41
|
}
|
|
37
42
|
async run() {
|
|
@@ -51,54 +56,41 @@ let StartUpsertPrCommand = class StartUpsertPrCommand {
|
|
|
51
56
|
// every BLOCK checklist, and the authoritative build gate — so no unreviewed commit reaches the
|
|
52
57
|
// remote, and there is no early `synchronize` firing against a PR body with a stale gate token.
|
|
53
58
|
await this.updateBranchFromMain(repoRoot);
|
|
54
|
-
// No build gate here —
|
|
55
|
-
//
|
|
59
|
+
// No build gate here — a conflicted merge may still be unresolved, so there is nothing worth
|
|
60
|
+
// building. Stage ② finalizes the merge FIRST, then builds; see the class comment.
|
|
56
61
|
this.handOffToReview(repoRoot);
|
|
57
62
|
}
|
|
58
63
|
/**
|
|
59
|
-
* Hand the AI its next step:
|
|
64
|
+
* Hand the AI its ONE next step: `wp-review-upsert-pr`.
|
|
60
65
|
*
|
|
61
|
-
* This
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* the
|
|
65
|
-
*
|
|
66
|
+
* This prints exactly one command on purpose. It used to print three (run the checklist, write
|
|
67
|
+
* review.json, then finish), and a three-item list is a list with a step to skip. Stage ② now owns all
|
|
68
|
+
* of it — it validates the 3-point merge, builds, materializes the diff, briefs the reviewers, AND
|
|
69
|
+
* prints the review.json schema — so there is nothing here to enumerate. Notably review.json is NOT
|
|
70
|
+
* requested here any more: asking for a written review before the branch is known to compile invites
|
|
71
|
+
* one that describes code that does not build.
|
|
66
72
|
*/
|
|
67
73
|
handOffToReview(repoRoot) {
|
|
68
|
-
const reviewPath = (0, rules_config_1.reviewJsonPath)(repoRoot, this.aiBranchName.getFeatureName());
|
|
69
74
|
// Persist the review-format + process instructions where any failure message can cite them.
|
|
70
75
|
(0, rules_config_1.writeTemplate)(repoRoot, 'webpieces.review-checklists.md');
|
|
71
|
-
// Persist the PR diff context (base sha + the full changed-file set)
|
|
72
|
-
//
|
|
73
|
-
const context = this.prContextWriter.ensure(repoRoot, this.aiBranchName.getFeatureName(), this.
|
|
76
|
+
// Persist the PR diff context (base sha + the full changed-file set) so it exists even if the AI
|
|
77
|
+
// stops here. Stage ② rewrites it with the materialized-diff dir once it has one.
|
|
78
|
+
const context = this.prContextWriter.ensure(repoRoot, this.aiBranchName.getFeatureName(), this.diffBasisResolver.resolve(repoRoot));
|
|
74
79
|
process.stdout.write('\n' + SEP + '② Review the PR, then finish\n' + SEP + '\n');
|
|
75
80
|
process.stdout.write(`Branch is updated (nothing pushed yet — finish does the one push, behind the build gate).\n` +
|
|
76
81
|
`${this.contextLines(context)}\n` +
|
|
77
|
-
|
|
78
|
-
`
|
|
79
|
-
|
|
80
|
-
`
|
|
81
|
-
`(It re-validates the build, re-checks every checklist, renders the dashboard, and creates/updates the PR.)\n\n`);
|
|
82
|
+
`▶ NEXT run: pnpm wp-review-upsert-pr\n` +
|
|
83
|
+
` It validates the 3-point merge, runs the build gate, extracts this branch's diff for the\n` +
|
|
84
|
+
` reviewers, and prints what to spawn plus the review.json schema. Everything else waits on it —\n` +
|
|
85
|
+
` wp-finish-upsert-pr refuses to open a PR until it has run.\n\n`);
|
|
82
86
|
}
|
|
83
|
-
|
|
84
|
-
|
|
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.length === 0) {
|
|
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');
|
|
96
|
-
}
|
|
97
|
-
// The diff facts, stated where the instruction to use them is — not in a separate block above it.
|
|
87
|
+
// The diff facts, stated where the instruction to use them is — not in a separate block above it. The
|
|
88
|
+
// command comes from the resolved basis; hand-writing `<base> HEAD` here is empty on a dirty tree.
|
|
98
89
|
contextLines(context) {
|
|
99
90
|
if (context.baseSha.trim() === '')
|
|
100
91
|
return '';
|
|
101
|
-
|
|
92
|
+
const cmd = context.fileDiffCommand.replace(' -- <file>', '');
|
|
93
|
+
return (`${cmd === '' ? '' : `Your diff: ${cmd}\n`}` +
|
|
102
94
|
`Full changed-file set + base/head sha: ${context.prContextPath}\n`);
|
|
103
95
|
}
|
|
104
96
|
// Scaffold the server-side CI check when (and only when) this repo set a gateSalt. Written to the
|
|
@@ -113,13 +105,15 @@ let StartUpsertPrCommand = class StartUpsertPrCommand {
|
|
|
113
105
|
` • mark the "webpieces-pr-gate" check REQUIRED in branch protection (repo admin only)\n`);
|
|
114
106
|
}
|
|
115
107
|
// Bring the branch up to date with main via the shared 3-point engine (in-process). On conflict the
|
|
116
|
-
// merge process doc it writes names `wp-
|
|
108
|
+
// merge process doc it writes names `wp-review-upsert-pr` as the finish command — that is the command
|
|
109
|
+
// that now validates and commits a conflict resolution, so it is what the doc must send the AI to.
|
|
117
110
|
async updateBranchFromMain(repoRoot) {
|
|
118
111
|
process.stdout.write('\n' + SEP + '① Updating branch from main\n' + SEP + '\n');
|
|
119
112
|
// pushRemote=false — finish owns the single push (see MergeEndOptions).
|
|
120
|
-
const outcome = await this.runUpdate.runUpdateFromMain(repoRoot, 'wp-start-upsert-pr', 'wp-
|
|
113
|
+
const outcome = await this.runUpdate.runUpdateFromMain(repoRoot, 'wp-start-upsert-pr', 'wp-review-upsert-pr', false);
|
|
121
114
|
if (outcome === 'conflict' || outcome === 'unvalidatedResume') {
|
|
122
|
-
throw new rules_config_1.CliExitError(2, '\n⏸️ Conflicts — resolve them, then run pnpm wp-
|
|
115
|
+
throw new rules_config_1.CliExitError(2, '\n⏸️ Conflicts — resolve them, then run pnpm wp-review-upsert-pr (it validates the merge, builds it,\n' +
|
|
116
|
+
' and only then briefs the reviewers).');
|
|
123
117
|
}
|
|
124
118
|
}
|
|
125
119
|
};
|
|
@@ -131,7 +125,7 @@ exports.StartUpsertPrCommand = StartUpsertPrCommand = tslib_1.__decorate([
|
|
|
131
125
|
branch_naming_1.BranchNaming,
|
|
132
126
|
git_exec_1.GitExec,
|
|
133
127
|
run_update_1.RunUpdate,
|
|
134
|
-
|
|
128
|
+
diff_basis_1.DiffBasisResolver,
|
|
135
129
|
pr_context_writer_1.PrContextWriter])
|
|
136
130
|
], StartUpsertPrCommand);
|
|
137
131
|
//# sourceMappingURL=start-upsert-pr-command.js.map
|