@webpieces/pr-gate 0.4.564 → 0.4.566

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.
Files changed (57) hide show
  1. package/bin/wp-finish-push-dev.js +27 -0
  2. package/bin/wp-push-dev.js +27 -0
  3. package/package.json +5 -3
  4. package/src/dashboard/checklist-comment-row.d.ts +42 -0
  5. package/src/dashboard/checklist-comment-row.js +59 -0
  6. package/src/dashboard/checklist-comment-row.js.map +1 -0
  7. package/src/dashboard/dashboard.d.ts +19 -29
  8. package/src/dashboard/dashboard.js +57 -53
  9. package/src/dashboard/dashboard.js.map +1 -1
  10. package/src/scripts/commands/finish-push-dev-command.d.ts +28 -0
  11. package/src/scripts/commands/finish-push-dev-command.js +62 -0
  12. package/src/scripts/commands/finish-push-dev-command.js.map +1 -0
  13. package/src/scripts/commands/finish-upsert-pr-command.d.ts +9 -0
  14. package/src/scripts/commands/finish-upsert-pr-command.js +24 -4
  15. package/src/scripts/commands/finish-upsert-pr-command.js.map +1 -1
  16. package/src/scripts/commands/push-dev-command.d.ts +89 -0
  17. package/src/scripts/commands/push-dev-command.js +249 -0
  18. package/src/scripts/commands/push-dev-command.js.map +1 -0
  19. package/src/scripts/commands/review-upsert-pr-command.d.ts +12 -1
  20. package/src/scripts/commands/review-upsert-pr-command.js +20 -4
  21. package/src/scripts/commands/review-upsert-pr-command.js.map +1 -1
  22. package/src/scripts/pr-gate-app.d.ts +24 -3
  23. package/src/scripts/pr-gate-app.js +48 -4
  24. package/src/scripts/pr-gate-app.js.map +1 -1
  25. package/src/scripts/workflow/checklist-detector.js +1 -1
  26. package/src/scripts/workflow/checklist-detector.js.map +1 -1
  27. package/src/scripts/workflow/checklist-scanner.d.ts +21 -1
  28. package/src/scripts/workflow/checklist-scanner.js +31 -3
  29. package/src/scripts/workflow/checklist-scanner.js.map +1 -1
  30. package/src/scripts/workflow/dev-deploy-refs.d.ts +66 -0
  31. package/src/scripts/workflow/dev-deploy-refs.js +143 -0
  32. package/src/scripts/workflow/dev-deploy-refs.js.map +1 -0
  33. package/src/scripts/workflow/dev-resolve-runner.d.ts +72 -0
  34. package/src/scripts/workflow/dev-resolve-runner.js +180 -0
  35. package/src/scripts/workflow/dev-resolve-runner.js.map +1 -0
  36. package/src/scripts/workflow/git-exec.d.ts +20 -1
  37. package/src/scripts/workflow/git-exec.js +30 -3
  38. package/src/scripts/workflow/git-exec.js.map +1 -1
  39. package/src/scripts/workflow/push-dev-state.d.ts +52 -0
  40. package/src/scripts/workflow/push-dev-state.js +147 -0
  41. package/src/scripts/workflow/push-dev-state.js.map +1 -0
  42. package/src/scripts/workflow/review-report.d.ts +62 -1
  43. package/src/scripts/workflow/review-report.js +165 -34
  44. package/src/scripts/workflow/review-report.js.map +1 -1
  45. package/src/scripts/workflow/reviewer-briefing-builder.js +1 -0
  46. package/src/scripts/workflow/reviewer-briefing-builder.js.map +1 -1
  47. package/src/scripts/workflow/reviewer-verdict-gate.d.ts +10 -0
  48. package/src/scripts/workflow/reviewer-verdict-gate.js +17 -1
  49. package/src/scripts/workflow/reviewer-verdict-gate.js.map +1 -1
  50. package/src/scripts/wp-finish-push-dev.d.ts +2 -0
  51. package/src/scripts/wp-finish-push-dev.js +20 -0
  52. package/src/scripts/wp-finish-push-dev.js.map +1 -0
  53. package/src/scripts/wp-push-dev.d.ts +2 -0
  54. package/src/scripts/wp-push-dev.js +44 -0
  55. package/src/scripts/wp-push-dev.js.map +1 -0
  56. package/src/scripts/wp-review-upsert-pr.js +9 -2
  57. package/src/scripts/wp-review-upsert-pr.js.map +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push-dev-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/push-dev-command.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n CliExitError,\n DevDeployConfig,\n RepoRootFinder,\n WP_FINISH_PUSH_DEV,\n WP_PUSH_DEV,\n writeTemplate,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { GitExec } from '../workflow/git-exec';\nimport { DevCopy, DevDeployRefs } from '../workflow/dev-deploy-refs';\nimport { PushDevState, PushDevStateStore } from '../workflow/push-dev-state';\nimport { DevResolveRunner } from '../workflow/dev-resolve-runner';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n/**\n * Which of `wp-push-dev`'s modes was asked for. Data-only (per CLAUDE.md) with plain assigned fields\n * rather than a seven-argument constructor — the bin sets what argv carried and nothing else.\n */\nexport class PushDevOptions {\n /** Delete the remote copy — the rollback. */\n remove = false;\n /** List the live copies and stop. The one mode that needs no branch preconditions. */\n list = false;\n /** Discard a conflict resolution already published on the copy, and overwrite it. */\n force = false;\n /** Replay that resolution on top of the new local commits instead of discarding it. */\n rebaseResolution = false;\n /** Compose the other copies onto this one so the shared environment can build them together. */\n resolve = false;\n /** With `--resolve`: merge ONLY this developer's branch. Empty ⇒ every other copy. */\n resolveTarget = '';\n}\n\n/**\n * `wp-push-dev` — publish a DISPOSABLE copy of this branch so a shared dev environment can build it,\n * without landing anything on `main` and without opening a PR.\n *\n * WHY THIS COMMAND HAS TO EXIST AT ALL: `pr-creation-or-push-guard` blocks every `git push`, no\n * exceptions, by design. Its own fix-hint offers \"ask a human to run the push\" for out-of-band cases —\n * which is the right answer for a genuine exception and the wrong one for a routine daily action. Without\n * a gated command there is simply no legal way for an AI to get a branch onto a dev server, so the\n * feature was undoable rather than merely awkward.\n *\n * WHY THERE IS NO BUILD, deliberately unlike `wp-start-upsert-pr`: the shared environment's CI builds the\n * COMPOSED tree (main + every listed branch). A local build here proves the branch compiles against\n * `main` alone, which is not the question — it would be both slower and reassuring about the wrong thing.\n * The gate that matters runs on the composition, in CI, where it belongs.\n *\n * WHAT IT NEVER DOES: touch the feature branch. See DevDeployRefs for the invariant and why the copy\n * exists to hold conflict resolutions that must not reach the PR.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class PushDevCommand {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly gitExec: GitExec,\n private readonly refs: DevDeployRefs,\n private readonly store: PushDevStateStore,\n private readonly runner: DevResolveRunner,\n ) {}\n\n async run(opts: PushDevOptions): Promise<void> {\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n // Refresh the AI-facing workflow doc so it is present + current for any failure message to cite —\n // same reason every other wp-* command opens this way.\n writeTemplate(repoRoot, 'webpieces.git-workflow.md');\n const cfg = this.refs.config(repoRoot);\n\n // A resolve already in flight owns the checkout; starting a second one would strand the first.\n this.assertNoResolveInProgress(repoRoot);\n\n if (opts.list) {\n this.list(repoRoot, cfg);\n return Promise.resolve();\n }\n\n const branch = this.refs.currentBranch(repoRoot);\n this.refs.assertPushable(cfg, branch);\n const targetRef = cfg.copyRefFor(branch);\n\n if (opts.remove) {\n this.remove(repoRoot, cfg, branch, targetRef);\n return Promise.resolve();\n }\n // Every remaining mode commits or merges, so the tree must be the AI's committed work and nothing\n // else. The tooling never commits for you.\n this.gitExec.assertCleanTree(repoRoot);\n\n if (opts.resolve) {\n this.startResolve(repoRoot, cfg, branch, targetRef, opts.resolveTarget);\n return Promise.resolve();\n }\n this.publish(repoRoot, cfg, branch, targetRef, opts);\n return Promise.resolve();\n }\n\n /** `--list`: exactly the live copies, straight from the remote — never a cached local view. */\n private list(repoRoot: string, cfg: DevDeployConfig): void {\n const copies = this.refs.liveCopies(repoRoot, cfg);\n if (copies.length === 0) {\n process.stdout.write(\n `\\nNo dev copies published — nothing matches \\`${cfg.copyRefGlob()}\\` on origin.\\n`\n + `Publish this branch's with: ${WP_PUSH_DEV}\\n`);\n return;\n }\n let out = '\\n' + SEP + `📋 ${String(copies.length)} dev cop${copies.length === 1 ? 'y' : 'ies'} on origin\\n` + SEP + '\\n';\n for (const copy of copies) out += ` ${copy.sha.slice(0, 8)} ${copy.ref}\\n`;\n out += `\\nYour CI composes \\`${cfg.devBranch}\\` from origin/main plus every ref above, on every run.\\n`\n + `Remove yours with: ${WP_PUSH_DEV} --remove\\n`;\n process.stdout.write(out);\n }\n\n /** `--remove`: delete the remote copy. This IS the rollback — nothing else has to be undone. */\n private remove(repoRoot: string, cfg: DevDeployConfig, branch: string, targetRef: string): void {\n if (this.refs.remoteSha(repoRoot, targetRef) === '') {\n process.stdout.write(\n `\\nNothing to remove — \\`${targetRef}\\` is not published.\\n`\n + `List what IS published with: ${WP_PUSH_DEV} --list\\n`);\n return;\n }\n this.gitExec.runGitChecked(['-C', repoRoot, 'push', 'origin', '--delete', `refs/heads/${targetRef}`],\n `Failed to delete the dev copy ${targetRef}`);\n process.stdout.write(\n '\\n' + SEP + `🗑️ Removed ${targetRef}\\n` + SEP + '\\n'\n + `\\`${branch}\\` will drop out of \\`${cfg.devBranch}\\` on its next composition run. Your branch and any\\n`\n + 'PR on it are untouched.\\n');\n }\n\n /**\n * The ordinary path: publish HEAD as `<namespace>/<branch>`.\n *\n * THE CLOBBER GUARD is the whole of the interesting logic here. If the remote copy holds commits the\n * local branch does not, somebody (quite possibly this developer, days ago) resolved a real conflict\n * over there. A silent force-push destroys work that cost a human a genuine merge resolution, so the\n * default is refusal with two named ways forward — never a \"use --force\" one-liner, which is how a\n * warning becomes a reflex.\n */\n private publish(repoRoot: string, cfg: DevDeployConfig, branch: string, targetRef: string, opts: PushDevOptions): void {\n const remoteSha = this.refs.remoteSha(repoRoot, targetRef);\n if (remoteSha !== '') {\n this.refs.fetchCopies(repoRoot, [targetRef]);\n const ahead = this.refs.commitsOnlyOnRemote(repoRoot, targetRef);\n if (ahead > 0 && opts.rebaseResolution) {\n this.rebaseResolution(repoRoot, branch, targetRef);\n return;\n }\n if (ahead > 0 && !opts.force) this.refuseClobber(cfg, branch, targetRef, ahead);\n }\n this.gitExec.runGitChecked(\n ['-C', repoRoot, 'push', ...this.pushSafety(targetRef, remoteSha, opts.force),\n 'origin', `HEAD:refs/heads/${targetRef}`],\n `Failed to publish the dev copy ${targetRef}`);\n process.stdout.write(\n '\\n' + SEP + `✅ Published ${targetRef}\\n` + SEP + '\\n'\n + `\\`${branch}\\` is now in the pool your CI composes \\`${cfg.devBranch}\\` from — it will be picked up on\\n`\n + 'the next composition run. No PR was opened and nothing landed on main.\\n\\n'\n + ` ${WP_PUSH_DEV} --list ← what else is in the pool\\n`\n + ` ${WP_PUSH_DEV} --remove ← take this branch back out\\n`);\n }\n\n /**\n * `--rebase-resolution`: keep the published resolution, replay it on top of the new commits.\n *\n * Implemented as a resolve whose BASE is the local HEAD and whose queue is the copy itself, so a\n * replay that conflicts again lands in exactly the same halt/`wp-finish-push-dev` state machine as\n * any other conflict, rather than inventing a second way to be stuck.\n */\n private rebaseResolution(repoRoot: string, branch: string, targetRef: string): void {\n const state = new PushDevState(branch, this.runner.tmpBranchFor(branch), targetRef, [targetRef]);\n process.stdout.write(\n '\\n' + SEP + `♻️ Replaying the published resolution onto \\`${branch}\\`\\n` + SEP + '\\n'\n + `Merging \\`${targetRef}\\` (which holds the resolution) into your new commits on a throwaway branch.\\n`\n + `Your feature branch \\`${branch}\\` is not touched.\\n\\n`);\n this.runner.start(repoRoot, state, 'HEAD', [targetRef]);\n }\n\n /**\n * `--resolve`: compose the other copies onto this one, so what is published is what CI will build.\n *\n * With no argument the queue is EVERY other copy, in the same sorted order CI composes them — the\n * local resolution is then a rehearsal of the real composition, not a guess at it. With an argument\n * (the branch a CI conflict message names) it is just that one.\n */\n private startResolve(repoRoot: string, cfg: DevDeployConfig, branch: string, targetRef: string, only: string): void {\n if (this.refs.remoteSha(repoRoot, targetRef) === '') {\n throw new CliExitError(2,\n '\\n' + SEP + `⛔ \\`${targetRef}\\` is not published yet\\n` + SEP + '\\n'\n + 'There is nothing to compose against. Publish your copy first, then resolve:\\n'\n + ` ${WP_PUSH_DEV}\\n ${WP_PUSH_DEV} --resolve\\n`);\n }\n const queue = this.resolveQueue(repoRoot, cfg, targetRef, only);\n if (queue.length === 0) {\n process.stdout.write(\n `\\nNothing to compose — \\`${targetRef}\\` is the only copy in \\`${cfg.copyRefGlob()}\\`.\\n`\n + `Your CI will merge it onto origin/main by itself; there is no other branch to conflict with.\\n`);\n return;\n }\n const state = new PushDevState(branch, this.runner.tmpBranchFor(branch), targetRef, queue);\n process.stdout.write(this.runner.announce(cfg, state));\n this.runner.start(repoRoot, state, `refs/remotes/origin/${targetRef}`, [targetRef, ...queue]);\n }\n\n // The refs to merge: one named copy, or every copy that is not mine. A named branch is accepted with\n // or without the namespace prefix, because the CI message that names it may print either.\n private resolveQueue(repoRoot: string, cfg: DevDeployConfig, targetRef: string, only: string): string[] {\n const copies = this.refs.liveCopies(repoRoot, cfg);\n if (only === '') {\n return copies.filter((c: DevCopy): boolean => c.ref !== targetRef).map((c: DevCopy): string => c.ref);\n }\n const wanted = only.startsWith(`${cfg.branchNamespace}/`) ? only : cfg.copyRefFor(only);\n if (wanted === targetRef) {\n throw new CliExitError(2, `\\n⛔ \\`${only}\\` is your own copy — there is nothing to compose it with.\\n`);\n }\n if (!copies.some((c: DevCopy): boolean => c.ref === wanted)) {\n throw new CliExitError(2,\n '\\n' + SEP + `⛔ No published copy \\`${wanted}\\`\\n` + SEP + '\\n'\n + `Live copies (${WP_PUSH_DEV} --list):\\n`\n + copies.map((c: DevCopy): string => ` ${c.ref}`).join('\\n') + '\\n');\n }\n return [wanted];\n }\n\n /**\n * `--force-with-lease` pinned to the sha we just inspected, so the clobber guard's verdict cannot go\n * stale between the check and the push. Plain `--force` only when the caller explicitly asked to\n * discard, and no force at all for a brand-new copy.\n */\n private pushSafety(targetRef: string, remoteSha: string, force: boolean): string[] {\n if (remoteSha === '') return [];\n if (force) return ['--force'];\n return [`--force-with-lease=refs/heads/${targetRef}:${remoteSha}`];\n }\n\n private refuseClobber(cfg: DevDeployConfig, branch: string, targetRef: string, ahead: number): never {\n throw new CliExitError(2,\n '\\n' + SEP + `⛔ \\`${targetRef}\\` holds ${String(ahead)} commit(s) your branch does not\\n` + SEP + '\\n'\n + 'That is what a CONFLICT RESOLUTION looks like: somebody composed this copy against another\\n'\n + `developer's branch so \\`${cfg.devBranch}\\` could build, and that work exists ONLY on the copy —\\n`\n + 'by design, because it must never reach your feature branch or your PR.\\n\\n'\n + 'Publishing now would force-push over it and destroy it, so this command refuses. Pick one:\\n\\n'\n + ` ${WP_PUSH_DEV} --rebase-resolution\\n`\n + ' Replay that resolution on top of your new commits. Keeps the work; may ask you to\\n'\n + ' re-resolve the parts your new commits actually changed.\\n\\n'\n + ` ${WP_PUSH_DEV} --force\\n`\n + ' Discard it and publish your branch as-is. The composition breaks again until someone\\n'\n + ` re-resolves it with \\`${WP_PUSH_DEV} --resolve\\`.\\n\\n`\n + `See what is on the copy: git log --oneline ${branch}..refs/remotes/origin/${targetRef}\\n`);\n }\n\n // A half-finished resolve owns the checkout — it is parked on a throwaway branch, so \"the current\n // branch\" is not the feature branch this command would otherwise act on.\n private assertNoResolveInProgress(repoRoot: string): void {\n const state = this.store.read(repoRoot);\n if (state === null) return;\n throw new CliExitError(2,\n '\\n' + SEP + '⛔ A dev-deploy resolve is already in progress\\n' + SEP + '\\n'\n + `You are on the throwaway branch \\`${state.tmpBranch}\\`, mid-composition. Finish it or bail out:\\n`\n + ` ${WP_FINISH_PUSH_DEV} ← commit the resolution, resume the queue, publish\\n`\n + ` ${WP_FINISH_PUSH_DEV} --abort ← throw it away and go back to \\`${state.originalBranch}\\`\\n`);\n }\n}\n"]}
@@ -11,6 +11,17 @@ import { PrContextWriter } from '../workflow/pr-context-writer';
11
11
  import { ReviewerBriefingBuilder } from '../workflow/reviewer-briefing-builder';
12
12
  import { ReviewReport } from '../workflow/review-report';
13
13
  import { ReviewStageReceiptService } from '../workflow/review-stage-receipt';
14
+ /** The `--no-optional` decision, as data (per CLAUDE.md) rather than a bare boolean parameter. */
15
+ export declare class ReviewUpsertPrOptions {
16
+ /**
17
+ * true ⇒ the human ALREADY said to submit without the optional reviews, so stage ② must not print the
18
+ * block telling the AI to offer them. It changes nothing about what BLOCKS: required checklists are
19
+ * unaffected, and an optional checklist that already has a red verdict on this branch still refuses the
20
+ * PR at finish. This only suppresses the offer.
21
+ */
22
+ skipOptional: boolean;
23
+ constructor(skipOptional?: boolean);
24
+ }
14
25
  /**
15
26
  * `wp-review-upsert-pr` — STAGE ② of the three-stage PR flow, between `wp-start-upsert-pr` (sync from main)
16
27
  * and `wp-finish-upsert-pr` (post the PR).
@@ -51,7 +62,7 @@ export declare class ReviewUpsertPrCommand {
51
62
  private readonly receipts;
52
63
  private readonly reviewJsonService;
53
64
  constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, gitExec: GitExec, buildAffected: BuildAffected, buildArtifactGate: BuildArtifactGate, mergeState: MergeState, mergeEnd: MergeEnd, checklistScanner: ChecklistScanner, reviewReport: ReviewReport, materializer: DiffMaterializer, prContextWriter: PrContextWriter, briefingBuilder: ReviewerBriefingBuilder, reviewerInstructions: ReviewerInstructionsService, receipts: ReviewStageReceiptService, reviewJsonService: ReviewJsonService);
54
- run(): Promise<void>;
65
+ run(opts?: ReviewUpsertPrOptions): Promise<void>;
55
66
  /**
56
67
  * Validate + commit + finalize a conflict resolution, if one is in flight. MOVED here from
57
68
  * `wp-finish-upsert-pr` so that exactly one command owns merge finalization — two owners is the drift
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ReviewUpsertPrCommand = void 0;
3
+ exports.ReviewUpsertPrCommand = exports.ReviewUpsertPrOptions = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const fs = tslib_1.__importStar(require("fs"));
6
6
  const rules_config_1 = require("@webpieces/rules-config");
@@ -19,6 +19,20 @@ const reviewer_briefing_builder_1 = require("../workflow/reviewer-briefing-build
19
19
  const review_report_1 = require("../workflow/review-report");
20
20
  const review_stage_receipt_1 = require("../workflow/review-stage-receipt");
21
21
  const SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
22
+ /** The `--no-optional` decision, as data (per CLAUDE.md) rather than a bare boolean parameter. */
23
+ class ReviewUpsertPrOptions {
24
+ /**
25
+ * true ⇒ the human ALREADY said to submit without the optional reviews, so stage ② must not print the
26
+ * block telling the AI to offer them. It changes nothing about what BLOCKS: required checklists are
27
+ * unaffected, and an optional checklist that already has a red verdict on this branch still refuses the
28
+ * PR at finish. This only suppresses the offer.
29
+ */
30
+ skipOptional;
31
+ constructor(skipOptional = false) {
32
+ this.skipOptional = skipOptional;
33
+ }
34
+ }
35
+ exports.ReviewUpsertPrOptions = ReviewUpsertPrOptions;
22
36
  /**
23
37
  * `wp-review-upsert-pr` — STAGE ② of the three-stage PR flow, between `wp-start-upsert-pr` (sync from main)
24
38
  * and `wp-finish-upsert-pr` (post the PR).
@@ -78,7 +92,7 @@ let ReviewUpsertPrCommand = class ReviewUpsertPrCommand {
78
92
  this.receipts = receipts;
79
93
  this.reviewJsonService = reviewJsonService;
80
94
  }
81
- async run() {
95
+ async run(opts = new ReviewUpsertPrOptions()) {
82
96
  const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
83
97
  (0, rules_config_1.writeTemplate)(repoRoot, 'webpieces.git-workflow.md');
84
98
  (0, rules_config_1.writeTemplate)(repoRoot, 'webpieces.review-checklists.md');
@@ -90,7 +104,7 @@ let ReviewUpsertPrCommand = class ReviewUpsertPrCommand {
90
104
  const scan = this.checklistScanner.scan(repoRoot, config.checklists, new checklist_scanner_1.ChecklistScanOptions(false, '')); // '' — THIS command writes the context itself, after materializing
91
105
  const briefings = this.briefReviewers(repoRoot, featureName, scan, config);
92
106
  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)));
93
- this.report(repoRoot, featureName, scan, briefings);
107
+ this.report(repoRoot, featureName, scan, briefings, opts);
94
108
  }
95
109
  /**
96
110
  * Validate + commit + finalize a conflict resolution, if one is in flight. MOVED here from
@@ -162,7 +176,8 @@ let ReviewUpsertPrCommand = class ReviewUpsertPrCommand {
162
176
  * while the block right beneath it said to write review.json first. An agent that reads top to bottom
163
177
  * obeyed the first line and posted a PR with no review at all.
164
178
  */
165
- report(repoRoot, featureName, scan, briefings) {
179
+ // eslint-disable-next-line @typescript-eslint/max-params
180
+ report(repoRoot, featureName, scan, briefings, opts) {
166
181
  const input = new review_report_1.ReviewReportInput(repoRoot, featureName, scan.reviewPath);
167
182
  input.definedCount = scan.defined.length;
168
183
  input.applicableCount = scan.applicable.length;
@@ -170,6 +185,7 @@ let ReviewUpsertPrCommand = class ReviewUpsertPrCommand {
170
185
  input.formatErrors = scan.formatErrors.slice();
171
186
  input.briefings = briefings.slice();
172
187
  input.refused = this.refusals(scan);
188
+ input.skipOptional = opts.skipOptional;
173
189
  process.stdout.write(this.reviewReport.render(input));
174
190
  }
175
191
  /**
@@ -1 +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 {\n loadAndValidate, writeTemplate, PrGateConfig, RepoRootFinder, RequiredChecklist,\n ReviewerBriefing, ReviewerInstructionsService, ReviewJsonService,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BuildAffected, BuildGateOptions } from '../workflow/build-affected';\nimport { BuildArtifactGate } from '../workflow/build-artifact-gate';\nimport { ChecklistScan, ChecklistScanOptions, ChecklistScanner } from '../workflow/checklist-scanner';\nimport { 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 { PrContextWriter } from '../workflow/pr-context-writer';\nimport { ReviewerBriefingBuilder } from '../workflow/reviewer-briefing-builder';\nimport { RefusedReviewer, ReviewReport, ReviewReportInput } from '../workflow/review-report';\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 buildArtifactGate: BuildArtifactGate,\n private readonly mergeState: MergeState,\n private readonly mergeEnd: MergeEnd,\n private readonly checklistScanner: ChecklistScanner,\n private readonly reviewReport: ReviewReport,\n private readonly materializer: DiffMaterializer,\n private readonly prContextWriter: PrContextWriter,\n private readonly briefingBuilder: ReviewerBriefingBuilder,\n private readonly reviewerInstructions: ReviewerInstructionsService,\n private readonly receipts: ReviewStageReceiptService,\n // Injected only to RESOLVE + RENDER refusals (see refusals()). This stage never archives a verdict.\n private readonly reviewJsonService: ReviewJsonService,\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, '')); // '' — THIS command writes the context itself, after materializing\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',\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 // Repo-wide: the build must not have left anything uncommitted AND unstaged. Runs HERE, not in\n // finish, because this is the stage that ran buildCommand and is therefore holding the dirty\n // tree at the exact moment the question is answerable. Replaces the per-project\n // validate-di-graph-unchanged nx target — see BuildArtifactGate for why that one had to go.\n this.buildArtifactGate.assertBuildLeftNothingUncommitted(repoRoot);\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 // Write pr-context.json ONCE, here — after materializing, so `diffDir` is populated on the first\n // write. The scan deliberately did not write it (ChecklistScanOptions.contextStage === ''); it used\n // to, which meant this command wrote the same file twice, the first time with an empty diffDir.\n // `scan.changedFiles` is passed through so the context is not recomputed from a second git call.\n scan.context = this.prContextWriter.ensure(\n repoRoot, featureName, scan.basis, 'stage2-review', scan.changedFiles, 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 return briefings;\n }\n\n /**\n * What the AI must do next — rendered by `ReviewReport`, which owns the ordering.\n *\n * The rendering moved OUT of this command so the ordering could be asserted on as a string. It had a\n * real bug: the zero-checklist notice signed off with \"Carry on and run: pnpm wp-finish-upsert-pr\"\n * while the block right beneath it said to write review.json first. An agent that reads top to bottom\n * obeyed the first line and posted a PR with no review at all.\n */\n private report(repoRoot: string, featureName: string, scan: ChecklistScan, briefings: readonly ReviewerBriefing[]): void {\n const input = new ReviewReportInput(repoRoot, featureName, scan.reviewPath);\n input.definedCount = scan.defined.length;\n input.applicableCount = scan.applicable.length;\n input.reviewed = scan.reviewed.slice();\n input.formatErrors = scan.formatErrors.slice();\n input.briefings = briefings.slice();\n input.refused = this.refusals(scan);\n process.stdout.write(this.reviewReport.render(input));\n }\n\n /**\n * The applicable checklists that ALREADY ANSWERED and refused, each with its refusal rendered by the ONE\n * renderer `wp-finish-upsert-pr` also uses.\n *\n * Stage ② had the identical defect finish did: a refused checklist has no passing verdict, so it is not\n * in `scan.reviewed`, so it was printed as an ordinary owed reviewer and re-instructed word for word —\n * and an agent that obeys spawns it against unchanged code, gets the same refusal, and loops one stage\n * earlier than the loop that was reported.\n *\n * NO archive path is passed, deliberately, and this stage moves nothing. Retiring a verdict is finish's\n * act on the refusal it is actually enforcing; doing it here would delete the live verdict of a branch\n * that has not even been asked to finish yet, and `refusalError`'s un-archived wording — \"set an override\n * in review-<id>.json\" — is only correct while that file is still there, which here it is.\n */\n private refusals(scan: ChecklistScan): RefusedReviewer[] {\n const refused = this.reviewJsonService.refusedChecklists(scan.applicable, scan.results);\n return refused.map((req: RequiredChecklist): RefusedReviewer => new RefusedReviewer(\n req.id, this.reviewJsonService.refusalError(req, this.reviewJsonService.resolveVerdict(req, scan.results))));\n }\n}\n"]}
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 {\n loadAndValidate, writeTemplate, PrGateConfig, RepoRootFinder, RequiredChecklist,\n ReviewerBriefing, ReviewerInstructionsService, ReviewJsonService,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BuildAffected, BuildGateOptions } from '../workflow/build-affected';\nimport { BuildArtifactGate } from '../workflow/build-artifact-gate';\nimport { ChecklistScan, ChecklistScanOptions, ChecklistScanner } from '../workflow/checklist-scanner';\nimport { 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 { PrContextWriter } from '../workflow/pr-context-writer';\nimport { ReviewerBriefingBuilder } from '../workflow/reviewer-briefing-builder';\nimport { RefusedReviewer, ReviewReport, ReviewReportInput } from '../workflow/review-report';\nimport { ReviewStageReceipt, ReviewStageReceiptService } from '../workflow/review-stage-receipt';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n/** The `--no-optional` decision, as data (per CLAUDE.md) rather than a bare boolean parameter. */\nexport class ReviewUpsertPrOptions {\n /**\n * true ⇒ the human ALREADY said to submit without the optional reviews, so stage ② must not print the\n * block telling the AI to offer them. It changes nothing about what BLOCKS: required checklists are\n * unaffected, and an optional checklist that already has a red verdict on this branch still refuses the\n * PR at finish. This only suppresses the offer.\n */\n skipOptional: boolean;\n\n constructor(skipOptional = false) {\n this.skipOptional = skipOptional;\n }\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 buildArtifactGate: BuildArtifactGate,\n private readonly mergeState: MergeState,\n private readonly mergeEnd: MergeEnd,\n private readonly checklistScanner: ChecklistScanner,\n private readonly reviewReport: ReviewReport,\n private readonly materializer: DiffMaterializer,\n private readonly prContextWriter: PrContextWriter,\n private readonly briefingBuilder: ReviewerBriefingBuilder,\n private readonly reviewerInstructions: ReviewerInstructionsService,\n private readonly receipts: ReviewStageReceiptService,\n // Injected only to RESOLVE + RENDER refusals (see refusals()). This stage never archives a verdict.\n private readonly reviewJsonService: ReviewJsonService,\n ) {}\n\n async run(opts: ReviewUpsertPrOptions = new ReviewUpsertPrOptions()): 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, '')); // '' — THIS command writes the context itself, after materializing\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, opts);\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',\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 // Repo-wide: the build must not have left anything uncommitted AND unstaged. Runs HERE, not in\n // finish, because this is the stage that ran buildCommand and is therefore holding the dirty\n // tree at the exact moment the question is answerable. Replaces the per-project\n // validate-di-graph-unchanged nx target — see BuildArtifactGate for why that one had to go.\n this.buildArtifactGate.assertBuildLeftNothingUncommitted(repoRoot);\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 // Write pr-context.json ONCE, here — after materializing, so `diffDir` is populated on the first\n // write. The scan deliberately did not write it (ChecklistScanOptions.contextStage === ''); it used\n // to, which meant this command wrote the same file twice, the first time with an empty diffDir.\n // `scan.changedFiles` is passed through so the context is not recomputed from a second git call.\n scan.context = this.prContextWriter.ensure(\n repoRoot, featureName, scan.basis, 'stage2-review', scan.changedFiles, 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 return briefings;\n }\n\n /**\n * What the AI must do next — rendered by `ReviewReport`, which owns the ordering.\n *\n * The rendering moved OUT of this command so the ordering could be asserted on as a string. It had a\n * real bug: the zero-checklist notice signed off with \"Carry on and run: pnpm wp-finish-upsert-pr\"\n * while the block right beneath it said to write review.json first. An agent that reads top to bottom\n * obeyed the first line and posted a PR with no review at all.\n */\n // eslint-disable-next-line @typescript-eslint/max-params\n private report(\n repoRoot: string, featureName: string, scan: ChecklistScan, briefings: readonly ReviewerBriefing[],\n opts: ReviewUpsertPrOptions,\n ): void {\n const input = new ReviewReportInput(repoRoot, featureName, scan.reviewPath);\n input.definedCount = scan.defined.length;\n input.applicableCount = scan.applicable.length;\n input.reviewed = scan.reviewed.slice();\n input.formatErrors = scan.formatErrors.slice();\n input.briefings = briefings.slice();\n input.refused = this.refusals(scan);\n input.skipOptional = opts.skipOptional;\n process.stdout.write(this.reviewReport.render(input));\n }\n\n /**\n * The applicable checklists that ALREADY ANSWERED and refused, each with its refusal rendered by the ONE\n * renderer `wp-finish-upsert-pr` also uses.\n *\n * Stage ② had the identical defect finish did: a refused checklist has no passing verdict, so it is not\n * in `scan.reviewed`, so it was printed as an ordinary owed reviewer and re-instructed word for word —\n * and an agent that obeys spawns it against unchanged code, gets the same refusal, and loops one stage\n * earlier than the loop that was reported.\n *\n * NO archive path is passed, deliberately, and this stage moves nothing. Retiring a verdict is finish's\n * act on the refusal it is actually enforcing; doing it here would delete the live verdict of a branch\n * that has not even been asked to finish yet, and `refusalError`'s un-archived wording — \"set an override\n * in review-<id>.json\" — is only correct while that file is still there, which here it is.\n */\n private refusals(scan: ChecklistScan): RefusedReviewer[] {\n const refused = this.reviewJsonService.refusedChecklists(scan.applicable, scan.results);\n return refused.map((req: RequiredChecklist): RefusedReviewer => new RefusedReviewer(\n req.id, this.reviewJsonService.refusalError(req, this.reviewJsonService.resolveVerdict(req, scan.results))));\n }\n}\n"]}
@@ -5,8 +5,12 @@ 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 { ReviewUpsertPrCommand } from './commands/review-upsert-pr-command';
8
+ import { ReviewUpsertPrCommand, ReviewUpsertPrOptions } from './commands/review-upsert-pr-command';
9
9
  import { ReapWorktreeCommand } from './commands/reap-worktree-command';
10
+ import { PushDevCommand, PushDevOptions } from './commands/push-dev-command';
11
+ import { FinishPushDevCommand, FinishPushDevOptions } from './commands/finish-push-dev-command';
12
+ import { PushDevStateStore } from './workflow/push-dev-state';
13
+ import { RepoRootFinder } from '@webpieces/rules-config';
10
14
  /**
11
15
  * The pr-gate application root. `container.get(PrGateApp)` resolves the entire workflow DAG (the command
12
16
  * classes → the injected git/merge/dashboard services). `@DocumentDesign` marks it the
@@ -23,7 +27,24 @@ export declare class PrGateApp {
23
27
  private readonly checkPrCommand;
24
28
  private readonly reviewUpsertPrCommand;
25
29
  private readonly reapWorktreeCommand;
26
- constructor(startUpdateCommand: StartUpdateCommand, finishUpdateCommand: FinishUpdateCommand, startUpsertPrCommand: StartUpsertPrCommand, finishUpsertPrCommand: FinishUpsertPrCommand, cleanupCommand: CleanupCommand, landPrCommand: LandPrCommand, checkPrCommand: CheckPrCommand, reviewUpsertPrCommand: ReviewUpsertPrCommand, reapWorktreeCommand: ReapWorktreeCommand);
30
+ private readonly pushDevCommand;
31
+ private readonly finishPushDevCommand;
32
+ private readonly pushDevStateStore;
33
+ private readonly repoRootFinder;
34
+ constructor(startUpdateCommand: StartUpdateCommand, finishUpdateCommand: FinishUpdateCommand, startUpsertPrCommand: StartUpsertPrCommand, finishUpsertPrCommand: FinishUpsertPrCommand, cleanupCommand: CleanupCommand, landPrCommand: LandPrCommand, checkPrCommand: CheckPrCommand, reviewUpsertPrCommand: ReviewUpsertPrCommand, reapWorktreeCommand: ReapWorktreeCommand, pushDevCommand: PushDevCommand, finishPushDevCommand: FinishPushDevCommand, pushDevStateStore: PushDevStateStore, repoRootFinder: RepoRootFinder);
35
+ /**
36
+ * Refuse `command` while a `wp-push-dev --resolve` is half-finished.
37
+ *
38
+ * Enforced HERE, at the one place every bin funnels through, rather than in each command: a resolve
39
+ * parks the checkout on a throwaway branch, so every command below would act on a branch that is not
40
+ * the one it thinks it is. Putting the check in nine constructors is nine chances to forget it in the
41
+ * tenth. PushDevStateStore owns the blocked list AND renders the hint from it, so the two cannot drift.
42
+ */
43
+ private assertNoResolveInProgress;
44
+ /** `wp-push-dev`: publish a DISPOSABLE copy of this branch for the shared dev environment. No PR, no build. */
45
+ pushDev(opts?: PushDevOptions): Promise<void>;
46
+ /** `wp-finish-push-dev`: commit a resolved dev composition and publish it (conflict path only). */
47
+ finishPushDev(opts?: FinishPushDevOptions): Promise<void>;
27
48
  /**
28
49
  * INTERNAL (`wp-reap-worktree.js`, no `bin` entry): remove ONE named worktree and its branch.
29
50
  * `wp-land-pr` spawns it with cwd = the primary clone so the tree it just landed from can be
@@ -47,7 +68,7 @@ export declare class PrGateApp {
47
68
  * branch's diff, and brief the reviewer subagents. Unlike the report-only command it replaces, this CAN
48
69
  * fail — before any reviewer is spawned, so a broken branch costs no reviewer tokens.
49
70
  */
50
- reviewUpsertPr(): Promise<void>;
71
+ reviewUpsertPr(opts?: ReviewUpsertPrOptions): Promise<void>;
51
72
  /** `wp-check-pr`: READ-ONLY CI check — verify the PR body carries a valid HMAC gate token for its head sha. */
52
73
  checkPr(): Promise<void>;
53
74
  }
@@ -13,6 +13,10 @@ const land_pr_command_1 = require("./commands/land-pr-command");
13
13
  const check_pr_command_1 = require("./commands/check-pr-command");
14
14
  const review_upsert_pr_command_1 = require("./commands/review-upsert-pr-command");
15
15
  const reap_worktree_command_1 = require("./commands/reap-worktree-command");
16
+ const push_dev_command_1 = require("./commands/push-dev-command");
17
+ const finish_push_dev_command_1 = require("./commands/finish-push-dev-command");
18
+ const push_dev_state_1 = require("./workflow/push-dev-state");
19
+ const rules_config_2 = require("@webpieces/rules-config");
16
20
  /**
17
21
  * The pr-gate application root. `container.get(PrGateApp)` resolves the entire workflow DAG (the command
18
22
  * classes → the injected git/merge/dashboard services). `@DocumentDesign` marks it the
@@ -29,7 +33,11 @@ let PrGateApp = class PrGateApp {
29
33
  checkPrCommand;
30
34
  reviewUpsertPrCommand;
31
35
  reapWorktreeCommand;
32
- constructor(startUpdateCommand, finishUpdateCommand, startUpsertPrCommand, finishUpsertPrCommand, cleanupCommand, landPrCommand, checkPrCommand, reviewUpsertPrCommand, reapWorktreeCommand) {
36
+ pushDevCommand;
37
+ finishPushDevCommand;
38
+ pushDevStateStore;
39
+ repoRootFinder;
40
+ constructor(startUpdateCommand, finishUpdateCommand, startUpsertPrCommand, finishUpsertPrCommand, cleanupCommand, landPrCommand, checkPrCommand, reviewUpsertPrCommand, reapWorktreeCommand, pushDevCommand, finishPushDevCommand, pushDevStateStore, repoRootFinder) {
33
41
  this.startUpdateCommand = startUpdateCommand;
34
42
  this.finishUpdateCommand = finishUpdateCommand;
35
43
  this.startUpsertPrCommand = startUpsertPrCommand;
@@ -39,6 +47,31 @@ let PrGateApp = class PrGateApp {
39
47
  this.checkPrCommand = checkPrCommand;
40
48
  this.reviewUpsertPrCommand = reviewUpsertPrCommand;
41
49
  this.reapWorktreeCommand = reapWorktreeCommand;
50
+ this.pushDevCommand = pushDevCommand;
51
+ this.finishPushDevCommand = finishPushDevCommand;
52
+ this.pushDevStateStore = pushDevStateStore;
53
+ this.repoRootFinder = repoRootFinder;
54
+ }
55
+ /**
56
+ * Refuse `command` while a `wp-push-dev --resolve` is half-finished.
57
+ *
58
+ * Enforced HERE, at the one place every bin funnels through, rather than in each command: a resolve
59
+ * parks the checkout on a throwaway branch, so every command below would act on a branch that is not
60
+ * the one it thinks it is. Putting the check in nine constructors is nine chances to forget it in the
61
+ * tenth. PushDevStateStore owns the blocked list AND renders the hint from it, so the two cannot drift.
62
+ */
63
+ assertNoResolveInProgress(command) {
64
+ if (!this.pushDevStateStore.isBlockedDuringResolve(command))
65
+ return;
66
+ this.pushDevStateStore.assertIdle(this.repoRootFinder.resolveRepoRoot(process.cwd()), command);
67
+ }
68
+ /** `wp-push-dev`: publish a DISPOSABLE copy of this branch for the shared dev environment. No PR, no build. */
69
+ pushDev(opts = new push_dev_command_1.PushDevOptions()) {
70
+ return this.pushDevCommand.run(opts);
71
+ }
72
+ /** `wp-finish-push-dev`: commit a resolved dev composition and publish it (conflict path only). */
73
+ finishPushDev(opts = new finish_push_dev_command_1.FinishPushDevOptions()) {
74
+ return this.finishPushDevCommand.run(opts);
42
75
  }
43
76
  /**
44
77
  * INTERNAL (`wp-reap-worktree.js`, no `bin` entry): remove ONE named worktree and its branch.
@@ -50,26 +83,32 @@ let PrGateApp = class PrGateApp {
50
83
  }
51
84
  /** `wp-start-update`: 3-point squash-update from main (no PR). */
52
85
  startUpdate() {
86
+ this.assertNoResolveInProgress('wp-start-update');
53
87
  return this.startUpdateCommand.run();
54
88
  }
55
89
  /** `wp-finish-update`: validate + finalize a resolved 3-point merge (no PR). */
56
90
  finishUpdate() {
91
+ this.assertNoResolveInProgress('wp-finish-update');
57
92
  return this.finishUpdateCommand.run();
58
93
  }
59
94
  /** `wp-start-upsert-pr`: update from main (3-point merge), hand off review.json. No build gate, no push. */
60
95
  startUpsertPr() {
96
+ this.assertNoResolveInProgress('wp-start-upsert-pr');
61
97
  return this.startUpsertPrCommand.run();
62
98
  }
63
99
  /** `wp-finish-upsert-pr`: finalize merge, authoritative build gate, dashboard, create/update PR. */
64
100
  finishUpsertPr() {
101
+ this.assertNoResolveInProgress('wp-finish-upsert-pr');
65
102
  return this.finishUpsertPrCommand.run();
66
103
  }
67
104
  /** `wp-cleanup`: reap what is provably merged, and ASK about everything that merely looks dead. */
68
105
  cleanup() {
106
+ this.assertNoResolveInProgress('wp-cleanup');
69
107
  return this.cleanupCommand.run();
70
108
  }
71
109
  /** `wp-land-pr`: squash-merge this branch's PR into main with the compact commit body. */
72
110
  landPr() {
111
+ this.assertNoResolveInProgress('wp-land-pr');
73
112
  return this.landPrCommand.run();
74
113
  }
75
114
  /**
@@ -77,8 +116,9 @@ let PrGateApp = class PrGateApp {
77
116
  * branch's diff, and brief the reviewer subagents. Unlike the report-only command it replaces, this CAN
78
117
  * fail — before any reviewer is spawned, so a broken branch costs no reviewer tokens.
79
118
  */
80
- reviewUpsertPr() {
81
- return this.reviewUpsertPrCommand.run();
119
+ reviewUpsertPr(opts = new review_upsert_pr_command_1.ReviewUpsertPrOptions()) {
120
+ this.assertNoResolveInProgress('wp-review-upsert-pr');
121
+ return this.reviewUpsertPrCommand.run(opts);
82
122
  }
83
123
  /** `wp-check-pr`: READ-ONLY CI check — verify the PR body carries a valid HMAC gate token for its head sha. */
84
124
  checkPr() {
@@ -97,6 +137,10 @@ exports.PrGateApp = PrGateApp = tslib_1.__decorate([
97
137
  land_pr_command_1.LandPrCommand,
98
138
  check_pr_command_1.CheckPrCommand,
99
139
  review_upsert_pr_command_1.ReviewUpsertPrCommand,
100
- reap_worktree_command_1.ReapWorktreeCommand])
140
+ reap_worktree_command_1.ReapWorktreeCommand,
141
+ push_dev_command_1.PushDevCommand,
142
+ finish_push_dev_command_1.FinishPushDevCommand,
143
+ push_dev_state_1.PushDevStateStore,
144
+ rules_config_2.RepoRootFinder])
101
145
  ], PrGateApp);
102
146
  //# 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;AAC7D,kFAA4E;AAC5E,4EAAuE;AAEvE;;;;;GAKG;AAGI,IAAM,SAAS,GAAf,MAAM,SAAS;IAEG;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IATrB,YACqB,kBAAsC,EACtC,mBAAwC,EACxC,oBAA0C,EAC1C,qBAA4C,EAC5C,cAA8B,EAC9B,aAA4B,EAC5B,cAA8B,EAC9B,qBAA4C,EAC5C,mBAAwC;QARxC,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,0BAAqB,GAArB,qBAAqB,CAAuB;QAC5C,wBAAmB,GAAnB,mBAAmB,CAAqB;IAC1D,CAAC;IAEJ;;;;OAIG;IACH,YAAY,CAAC,IAAc;QACvB,OAAO,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9C,CAAC;IAED,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,4GAA4G;IAC5G,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,mGAAmG;IACnG,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;;;;OAIG;IACH,cAAc;QACV,OAAO,IAAI,CAAC,qBAAqB,CAAC,GAAG,EAAE,CAAC;IAC5C,CAAC;IAED,+GAA+G;IAC/G,OAAO;QACH,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,CAAC;IACrC,CAAC;CACJ,CAAA;AAjEY,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;QACP,gDAAqB;QACvB,2CAAmB;GAVpD,SAAS,CAiErB","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 { ReviewUpsertPrCommand } from './commands/review-upsert-pr-command';\nimport { ReapWorktreeCommand } from './commands/reap-worktree-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 reviewUpsertPrCommand: ReviewUpsertPrCommand,\n private readonly reapWorktreeCommand: ReapWorktreeCommand,\n ) {}\n\n /**\n * INTERNAL (`wp-reap-worktree.js`, no `bin` entry): remove ONE named worktree and its branch.\n * `wp-land-pr` spawns it with cwd = the primary clone so the tree it just landed from can be\n * reaped by a process that is not standing in it.\n */\n reapWorktree(args: string[]): Promise<void> {\n return this.reapWorktreeCommand.run(args);\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 (3-point merge), hand off review.json. No build gate, no push. */\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`: reap what is provably merged, and ASK about everything that merely looks dead. */\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 /**\n * `wp-review-upsert-pr`: STAGE ② — validate the 3-point merge, run the build gate, extract this\n * branch's diff, and brief the reviewer subagents. Unlike the report-only command it replaces, this CAN\n * fail — before any reviewer is spawned, so a broken branch costs no reviewer tokens.\n */\n reviewUpsertPr(): Promise<void> {\n return this.reviewUpsertPrCommand.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"]}
1
+ {"version":3,"file":"pr-gate-app.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/pr-gate-app.ts"],"names":[],"mappings":"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","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 { ReviewUpsertPrCommand, ReviewUpsertPrOptions } from './commands/review-upsert-pr-command';\nimport { ReapWorktreeCommand } from './commands/reap-worktree-command';\nimport { PushDevCommand, PushDevOptions } from './commands/push-dev-command';\nimport { FinishPushDevCommand, FinishPushDevOptions } from './commands/finish-push-dev-command';\nimport { PushDevStateStore } from './workflow/push-dev-state';\nimport { RepoRootFinder } from '@webpieces/rules-config';\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 reviewUpsertPrCommand: ReviewUpsertPrCommand,\n private readonly reapWorktreeCommand: ReapWorktreeCommand,\n private readonly pushDevCommand: PushDevCommand,\n private readonly finishPushDevCommand: FinishPushDevCommand,\n private readonly pushDevStateStore: PushDevStateStore,\n private readonly repoRootFinder: RepoRootFinder,\n ) {}\n\n /**\n * Refuse `command` while a `wp-push-dev --resolve` is half-finished.\n *\n * Enforced HERE, at the one place every bin funnels through, rather than in each command: a resolve\n * parks the checkout on a throwaway branch, so every command below would act on a branch that is not\n * the one it thinks it is. Putting the check in nine constructors is nine chances to forget it in the\n * tenth. PushDevStateStore owns the blocked list AND renders the hint from it, so the two cannot drift.\n */\n private assertNoResolveInProgress(command: string): void {\n if (!this.pushDevStateStore.isBlockedDuringResolve(command)) return;\n this.pushDevStateStore.assertIdle(this.repoRootFinder.resolveRepoRoot(process.cwd()), command);\n }\n\n /** `wp-push-dev`: publish a DISPOSABLE copy of this branch for the shared dev environment. No PR, no build. */\n pushDev(opts: PushDevOptions = new PushDevOptions()): Promise<void> {\n return this.pushDevCommand.run(opts);\n }\n\n /** `wp-finish-push-dev`: commit a resolved dev composition and publish it (conflict path only). */\n finishPushDev(opts: FinishPushDevOptions = new FinishPushDevOptions()): Promise<void> {\n return this.finishPushDevCommand.run(opts);\n }\n\n /**\n * INTERNAL (`wp-reap-worktree.js`, no `bin` entry): remove ONE named worktree and its branch.\n * `wp-land-pr` spawns it with cwd = the primary clone so the tree it just landed from can be\n * reaped by a process that is not standing in it.\n */\n reapWorktree(args: string[]): Promise<void> {\n return this.reapWorktreeCommand.run(args);\n }\n\n /** `wp-start-update`: 3-point squash-update from main (no PR). */\n startUpdate(): Promise<void> {\n this.assertNoResolveInProgress('wp-start-update');\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 this.assertNoResolveInProgress('wp-finish-update');\n return this.finishUpdateCommand.run();\n }\n\n /** `wp-start-upsert-pr`: update from main (3-point merge), hand off review.json. No build gate, no push. */\n startUpsertPr(): Promise<void> {\n this.assertNoResolveInProgress('wp-start-upsert-pr');\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 this.assertNoResolveInProgress('wp-finish-upsert-pr');\n return this.finishUpsertPrCommand.run();\n }\n\n /** `wp-cleanup`: reap what is provably merged, and ASK about everything that merely looks dead. */\n cleanup(): Promise<void> {\n this.assertNoResolveInProgress('wp-cleanup');\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 this.assertNoResolveInProgress('wp-land-pr');\n return this.landPrCommand.run();\n }\n\n /**\n * `wp-review-upsert-pr`: STAGE ② — validate the 3-point merge, run the build gate, extract this\n * branch's diff, and brief the reviewer subagents. Unlike the report-only command it replaces, this CAN\n * fail — before any reviewer is spawned, so a broken branch costs no reviewer tokens.\n */\n reviewUpsertPr(opts: ReviewUpsertPrOptions = new ReviewUpsertPrOptions()): Promise<void> {\n this.assertNoResolveInProgress('wp-review-upsert-pr');\n return this.reviewUpsertPrCommand.run(opts);\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"]}
@@ -105,7 +105,7 @@ let ChecklistDetector = class ChecklistDetector {
105
105
  // Flatten matched checklists into the RequiredChecklist shape that review.json enforcement + the hint
106
106
  // consume.
107
107
  toRequired(triggered) {
108
- return triggered.map((t) => new rules_config_1.RequiredChecklist(t.def.id, t.def.subagent, t.def.doc, t.matchedFiles, t.matchedPatterns));
108
+ return triggered.map((t) => new rules_config_1.RequiredChecklist(t.def.id, t.def.subagent, t.def.doc, t.matchedFiles, t.matchedPatterns, t.def.required));
109
109
  }
110
110
  };
111
111
  exports.ChecklistDetector = ChecklistDetector;
@@ -1 +1 @@
1
- {"version":3,"file":"checklist-detector.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/checklist-detector.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ChecklistDefinition, RequiredChecklist, DiffScope, ChangedFilesOptions, isPathExcluded,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\n// A checklist whose `patterns` matched the diff (so its reviewer subagent must run), plus the matched\n// files (for the dashboard + hint). Data-only (per CLAUDE.md, classes for data).\nexport class TriggeredChecklist {\n def: ChecklistDefinition;\n matchedFiles: string[];\n // Which of `def.patterns` actually hit a changed file. Carried so the printed instruction can say WHAT\n // matched, not just which files: a reviewer judges a `db/migrations/**` hit differently from a `**` one.\n matchedPatterns: string[];\n\n constructor(def: ChecklistDefinition, matchedFiles: string[], matchedPatterns: string[] = []) {\n this.def = def;\n this.matchedFiles = matchedFiles;\n this.matchedPatterns = matchedPatterns;\n }\n}\n\n/**\n * Every DEFINED checklist paired with what it matched, plus the two facts a reader needs to judge a skip:\n * how many files were even considered, and whether a diff was computable at all. Data-only (per CLAUDE.md).\n */\nexport class ChecklistRoster {\n entries: TriggeredChecklist[]; // one per defined checklist, in config order; matchedFiles may be []\n changedFileCount: number; // N — \"matched 0 of N changed files\" is only meaningful with N\n /**\n * false when no fork point resolved. Load-bearing: with no base the changed-file set is EMPTY, so\n * nothing matches — including patternless ALWAYS-RUNS checklists. Rendering that as \"all skipped ✅\"\n * would post a green all-clear to GitHub for a PR where no checklist was actually evaluated.\n */\n baseResolved: boolean;\n\n constructor(entries: TriggeredChecklist[], changedFileCount: number, baseResolved: boolean) {\n this.entries = entries;\n this.changedFileCount = changedFileCount;\n this.baseResolved = baseResolved;\n }\n}\n\n/**\n * Decides which company review checklists a branch matched, from what the diff CHANGED. Path matching\n * uses `isPathExcluded` — the SAME minimatch-based matcher rules-config already shares across\n * exclude-paths and the rule validators. A checklist with empty `patterns` matches every PR (always runs).\n *\n * `@injectable(bindingScopeValues.Singleton)` + injects {@link DiffScope} so it appears in the DI design\n * and reuses the ONE git-diff service instead of adding new git plumbing.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ChecklistDetector {\n constructor(private readonly diffScope: DiffScope) {}\n\n /**\n * Pure matching over EVERY defined checklist, matched or not — one entry per def, in config order, with\n * `matchedFiles: []` for the ones nothing hit. This is what lets the PR comment publish the full roster:\n * a skipped checklist is the normal, healthy outcome, and a comment that lists only the ones that fired\n * cannot distinguish \"evaluated and irrelevant\" from \"never configured\".\n *\n * A skipped entry has `matchedPatterns: []` — and so does a PATTERNLESS one. They mean opposite things,\n * so a renderer must decide \"always runs\" from `def.patterns.length === 0`, NEVER from\n * `matchedPatterns.length`, or every skipped checklist claims the whole diff was in its scope.\n */\n roster(defs: readonly ChecklistDefinition[], changedFiles: readonly string[]): TriggeredChecklist[] {\n return defs.map((def: ChecklistDefinition): TriggeredChecklist => {\n const matched = def.patterns.length === 0\n ? [...changedFiles]\n : changedFiles.filter((f: string): boolean => isPathExcluded(f, def.patterns));\n return new TriggeredChecklist(def, matched, this.firedPatterns(def, changedFiles));\n });\n }\n\n // Pure matching — unit-testable without git. Return every checklist whose patterns hit a changed file\n // (empty patterns ⇒ always matches). Defined as the roster minus the empty entries so the set that GATES\n // and the set the comment REPORTS can never be computed two different ways.\n detect(defs: readonly ChecklistDefinition[], changedFiles: readonly string[]): TriggeredChecklist[] {\n return this.roster(defs, changedFiles)\n .filter((t: TriggeredChecklist): boolean => t.matchedFiles.length > 0);\n }\n\n // Which of a checklist's globs hit at least one changed file. [] for a patternless checklist (it matches\n // every PR, and saying \"matched by nothing\" would read as a bug).\n private firedPatterns(def: ChecklistDefinition, changedFiles: readonly string[]): string[] {\n return def.patterns.filter((p: string): boolean => changedFiles.some((f: string): boolean => isPathExcluded(f, [p])));\n }\n\n // Resolve the diff base for the local branch, then detect. Returns [] when there is no base to diff.\n detectForRepo(repoRoot: string, defs: readonly ChecklistDefinition[]): TriggeredChecklist[] {\n if (defs.length === 0) return [];\n const range = this.diffScope.resolveBase(repoRoot);\n if (!range.base) return [];\n return this.detectForRange(repoRoot, defs, range.base, range.head);\n }\n\n // Same detection against an EXPLICIT (base, head) — used by CI, where the useful range is the PR's\n // merge base .. head. tsOnly=false is load-bearing: the default drops every *.sql / Dockerfile / .env*\n // / metadata file a checklist most wants to key on, silently producing \"no checklists matched\".\n detectForRange(repoRoot: string, defs: readonly ChecklistDefinition[], base: string, head?: string): TriggeredChecklist[] {\n if (defs.length === 0 || !base) return [];\n const opts = new ChangedFilesOptions();\n opts.tsOnly = false;\n const changedFiles = this.diffScope.getChangedFiles(repoRoot, base, head, opts);\n return this.detect(defs, changedFiles);\n }\n\n // Flatten matched checklists into the RequiredChecklist shape that review.json enforcement + the hint\n // consume.\n toRequired(triggered: readonly TriggeredChecklist[]): RequiredChecklist[] {\n return triggered.map((t: TriggeredChecklist): RequiredChecklist =>\n new RequiredChecklist(t.def.id, t.def.subagent, t.def.doc, t.matchedFiles, t.matchedPatterns));\n }\n}\n"]}
1
+ {"version":3,"file":"checklist-detector.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/checklist-detector.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ChecklistDefinition, RequiredChecklist, DiffScope, ChangedFilesOptions, isPathExcluded,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\n// A checklist whose `patterns` matched the diff (so its reviewer subagent must run), plus the matched\n// files (for the dashboard + hint). Data-only (per CLAUDE.md, classes for data).\nexport class TriggeredChecklist {\n def: ChecklistDefinition;\n matchedFiles: string[];\n // Which of `def.patterns` actually hit a changed file. Carried so the printed instruction can say WHAT\n // matched, not just which files: a reviewer judges a `db/migrations/**` hit differently from a `**` one.\n matchedPatterns: string[];\n\n constructor(def: ChecklistDefinition, matchedFiles: string[], matchedPatterns: string[] = []) {\n this.def = def;\n this.matchedFiles = matchedFiles;\n this.matchedPatterns = matchedPatterns;\n }\n}\n\n/**\n * Every DEFINED checklist paired with what it matched, plus the two facts a reader needs to judge a skip:\n * how many files were even considered, and whether a diff was computable at all. Data-only (per CLAUDE.md).\n */\nexport class ChecklistRoster {\n entries: TriggeredChecklist[]; // one per defined checklist, in config order; matchedFiles may be []\n changedFileCount: number; // N — \"matched 0 of N changed files\" is only meaningful with N\n /**\n * false when no fork point resolved. Load-bearing: with no base the changed-file set is EMPTY, so\n * nothing matches — including patternless ALWAYS-RUNS checklists. Rendering that as \"all skipped ✅\"\n * would post a green all-clear to GitHub for a PR where no checklist was actually evaluated.\n */\n baseResolved: boolean;\n\n constructor(entries: TriggeredChecklist[], changedFileCount: number, baseResolved: boolean) {\n this.entries = entries;\n this.changedFileCount = changedFileCount;\n this.baseResolved = baseResolved;\n }\n}\n\n/**\n * Decides which company review checklists a branch matched, from what the diff CHANGED. Path matching\n * uses `isPathExcluded` — the SAME minimatch-based matcher rules-config already shares across\n * exclude-paths and the rule validators. A checklist with empty `patterns` matches every PR (always runs).\n *\n * `@injectable(bindingScopeValues.Singleton)` + injects {@link DiffScope} so it appears in the DI design\n * and reuses the ONE git-diff service instead of adding new git plumbing.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ChecklistDetector {\n constructor(private readonly diffScope: DiffScope) {}\n\n /**\n * Pure matching over EVERY defined checklist, matched or not — one entry per def, in config order, with\n * `matchedFiles: []` for the ones nothing hit. This is what lets the PR comment publish the full roster:\n * a skipped checklist is the normal, healthy outcome, and a comment that lists only the ones that fired\n * cannot distinguish \"evaluated and irrelevant\" from \"never configured\".\n *\n * A skipped entry has `matchedPatterns: []` — and so does a PATTERNLESS one. They mean opposite things,\n * so a renderer must decide \"always runs\" from `def.patterns.length === 0`, NEVER from\n * `matchedPatterns.length`, or every skipped checklist claims the whole diff was in its scope.\n */\n roster(defs: readonly ChecklistDefinition[], changedFiles: readonly string[]): TriggeredChecklist[] {\n return defs.map((def: ChecklistDefinition): TriggeredChecklist => {\n const matched = def.patterns.length === 0\n ? [...changedFiles]\n : changedFiles.filter((f: string): boolean => isPathExcluded(f, def.patterns));\n return new TriggeredChecklist(def, matched, this.firedPatterns(def, changedFiles));\n });\n }\n\n // Pure matching — unit-testable without git. Return every checklist whose patterns hit a changed file\n // (empty patterns ⇒ always matches). Defined as the roster minus the empty entries so the set that GATES\n // and the set the comment REPORTS can never be computed two different ways.\n detect(defs: readonly ChecklistDefinition[], changedFiles: readonly string[]): TriggeredChecklist[] {\n return this.roster(defs, changedFiles)\n .filter((t: TriggeredChecklist): boolean => t.matchedFiles.length > 0);\n }\n\n // Which of a checklist's globs hit at least one changed file. [] for a patternless checklist (it matches\n // every PR, and saying \"matched by nothing\" would read as a bug).\n private firedPatterns(def: ChecklistDefinition, changedFiles: readonly string[]): string[] {\n return def.patterns.filter((p: string): boolean => changedFiles.some((f: string): boolean => isPathExcluded(f, [p])));\n }\n\n // Resolve the diff base for the local branch, then detect. Returns [] when there is no base to diff.\n detectForRepo(repoRoot: string, defs: readonly ChecklistDefinition[]): TriggeredChecklist[] {\n if (defs.length === 0) return [];\n const range = this.diffScope.resolveBase(repoRoot);\n if (!range.base) return [];\n return this.detectForRange(repoRoot, defs, range.base, range.head);\n }\n\n // Same detection against an EXPLICIT (base, head) — used by CI, where the useful range is the PR's\n // merge base .. head. tsOnly=false is load-bearing: the default drops every *.sql / Dockerfile / .env*\n // / metadata file a checklist most wants to key on, silently producing \"no checklists matched\".\n detectForRange(repoRoot: string, defs: readonly ChecklistDefinition[], base: string, head?: string): TriggeredChecklist[] {\n if (defs.length === 0 || !base) return [];\n const opts = new ChangedFilesOptions();\n opts.tsOnly = false;\n const changedFiles = this.diffScope.getChangedFiles(repoRoot, base, head, opts);\n return this.detect(defs, changedFiles);\n }\n\n // Flatten matched checklists into the RequiredChecklist shape that review.json enforcement + the hint\n // consume.\n toRequired(triggered: readonly TriggeredChecklist[]): RequiredChecklist[] {\n return triggered.map((t: TriggeredChecklist): RequiredChecklist =>\n new RequiredChecklist(\n t.def.id, t.def.subagent, t.def.doc, t.matchedFiles, t.matchedPatterns, t.def.required));\n }\n}\n"]}
@@ -62,7 +62,17 @@ export declare class ChecklistScan {
62
62
  * message, which is exactly the loop this field exists to break.
63
63
  */
64
64
  results: ChecklistResult[];
65
- constructor(defined: ChecklistDefinition[], applicable: RequiredChecklist[], reviewed: RequiredChecklist[], outstanding: RequiredChecklist[], context: ChecklistReviewContext, reviewPath: string, forkPoint: string, roster: ChecklistRoster, formatErrors: string[], basis?: DiffBasis, changedFiles?: string[], results?: ChecklistResult[]);
65
+ /**
66
+ * The OPTIONAL (`required: false`) applicable checklists carrying no verdict file — i.e. nobody ran them.
67
+ *
68
+ * Deliberately NOT in `outstanding` when filtering: that is the exemption that makes `required: false`
69
+ * mean something. Carried out as its own set rather than merely subtracted, because both readers need it
70
+ * BY NAME and neither can recover it from what is left: stage ② offers exactly these to the human, and
71
+ * the PR dashboard must publish them as "not run" rather than let a shorter roster imply everything
72
+ * passed. An optional checklist that ran and went RED is absent from here and stays in `outstanding`.
73
+ */
74
+ optionalNotRun: RequiredChecklist[];
75
+ constructor(defined: ChecklistDefinition[], applicable: RequiredChecklist[], reviewed: RequiredChecklist[], outstanding: RequiredChecklist[], context: ChecklistReviewContext, reviewPath: string, forkPoint: string, roster: ChecklistRoster, formatErrors: string[], basis?: DiffBasis, changedFiles?: string[], results?: ChecklistResult[], optionalNotRun?: RequiredChecklist[]);
66
76
  }
67
77
  /**
68
78
  * The ONE computation of which reviewer subagents a branch owes, shared by `wp-review-upsert-pr` (which lists) and
@@ -101,6 +111,16 @@ export declare class ChecklistScanner {
101
111
  * here leaves this a function of its inputs rather than of the filesystem.
102
112
  */
103
113
  scan(repoRoot: string, defined: ChecklistDefinition[], opts: ChecklistScanOptions): ChecklistScan;
114
+ /**
115
+ * What `wp-finish-upsert-pr` actually REFUSES on: everything still owing a verdict, minus the optional
116
+ * checklists nobody ran.
117
+ *
118
+ * The subtraction happens HERE — in the one place `outstanding` is computed — and not in the gate, so
119
+ * there is a single answer to "does this branch owe review?". When the gate did its own filtering, the
120
+ * command that lists and the command that blocks were two implementations of the same question, which is
121
+ * exactly the divergence this class's docstring exists to prevent.
122
+ */
123
+ private blocking;
104
124
  /**
105
125
  * Every file changed since the fork point, INCLUDING uncommitted and untracked ones. Two non-default
106
126
  * options, both load-bearing:
@@ -77,11 +77,21 @@ class ChecklistScan {
77
77
  * message, which is exactly the loop this field exists to break.
78
78
  */
79
79
  results;
80
+ /**
81
+ * The OPTIONAL (`required: false`) applicable checklists carrying no verdict file — i.e. nobody ran them.
82
+ *
83
+ * Deliberately NOT in `outstanding` when filtering: that is the exemption that makes `required: false`
84
+ * mean something. Carried out as its own set rather than merely subtracted, because both readers need it
85
+ * BY NAME and neither can recover it from what is left: stage ② offers exactly these to the human, and
86
+ * the PR dashboard must publish them as "not run" rather than let a shorter roster imply everything
87
+ * passed. An optional checklist that ran and went RED is absent from here and stays in `outstanding`.
88
+ */
89
+ optionalNotRun;
80
90
  // eslint-disable-next-line @typescript-eslint/max-params
81
91
  constructor(defined, applicable, reviewed, outstanding, context, reviewPath, forkPoint, roster, formatErrors, basis = new diff_basis_1.DiffBasis(), changedFiles = [],
82
92
  // Defaulted so a caller that only cares about the X/N/Z counts (and every existing test construction)
83
93
  // stays a one-liner; the scanner itself always passes the real set.
84
- results = []) {
94
+ results = [], optionalNotRun = []) {
85
95
  this.defined = defined;
86
96
  this.applicable = applicable;
87
97
  this.reviewed = reviewed;
@@ -94,6 +104,7 @@ class ChecklistScan {
94
104
  this.basis = basis;
95
105
  this.changedFiles = changedFiles;
96
106
  this.results = results;
107
+ this.optionalNotRun = optionalNotRun;
97
108
  }
98
109
  }
99
110
  exports.ChecklistScan = ChecklistScan;
@@ -155,10 +166,27 @@ let ChecklistScanner = class ChecklistScanner {
155
166
  const results = this.reviewJsonService.loadChecklistResults(reviewPath, applicable);
156
167
  const stillOwed = this.reviewJsonService.pendingChecklists(applicable, results);
157
168
  const owedIds = new Set(stillOwed.map((r) => r.id));
169
+ // NOT `!owedIds.has(...)`-with-the-optional-exemption-folded-in: an optional checklist nobody ran is
170
+ // neither reviewed nor blocking, and calling it "reviewed" would put a ✓ on the dashboard for a review
171
+ // that never happened.
158
172
  const reviewed = applicable.filter((r) => !owedIds.has(r.id));
159
- return new ChecklistScan(defined, applicable, reviewed, opts.filterAlreadyReviewed ? stillOwed : applicable, opts.contextStage === ''
173
+ const optionalNotRun = this.reviewJsonService.optionalWithoutVerdict(applicable, results);
174
+ return new ChecklistScan(defined, applicable, reviewed, opts.filterAlreadyReviewed ? this.blocking(stillOwed, optionalNotRun) : applicable, opts.contextStage === ''
160
175
  ? this.prContextWriter.contextFor(repoRoot, featureName, basis)
161
- : this.prContextWriter.ensure(repoRoot, featureName, basis, opts.contextStage, changedFiles), reviewPath, base, roster, this.reviewJsonService.checklistFormatErrors(applicable, results), basis, changedFiles, results);
176
+ : this.prContextWriter.ensure(repoRoot, featureName, basis, opts.contextStage, changedFiles), reviewPath, base, roster, this.reviewJsonService.checklistFormatErrors(applicable, results), basis, changedFiles, results, optionalNotRun);
177
+ }
178
+ /**
179
+ * What `wp-finish-upsert-pr` actually REFUSES on: everything still owing a verdict, minus the optional
180
+ * checklists nobody ran.
181
+ *
182
+ * The subtraction happens HERE — in the one place `outstanding` is computed — and not in the gate, so
183
+ * there is a single answer to "does this branch owe review?". When the gate did its own filtering, the
184
+ * command that lists and the command that blocks were two implementations of the same question, which is
185
+ * exactly the divergence this class's docstring exists to prevent.
186
+ */
187
+ blocking(stillOwed, optionalNotRun) {
188
+ const skipped = new Set(optionalNotRun.map((r) => r.id));
189
+ return stillOwed.filter((r) => !skipped.has(r.id));
162
190
  }
163
191
  /**
164
192
  * Every file changed since the fork point, INCLUDING uncommitted and untracked ones. Two non-default