@webpieces/pr-gate 0.4.497 → 0.4.499

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 (53) hide show
  1. package/package.json +3 -3
  2. package/src/dashboard/dashboard.d.ts +15 -0
  3. package/src/dashboard/dashboard.js +25 -2
  4. package/src/dashboard/dashboard.js.map +1 -1
  5. package/src/scripts/commands/cleanup-command.d.ts +42 -3
  6. package/src/scripts/commands/cleanup-command.js +69 -11
  7. package/src/scripts/commands/cleanup-command.js.map +1 -1
  8. package/src/scripts/commands/finish-upsert-pr-command.d.ts +55 -5
  9. package/src/scripts/commands/finish-upsert-pr-command.js +162 -30
  10. package/src/scripts/commands/finish-upsert-pr-command.js.map +1 -1
  11. package/src/scripts/commands/land-pr-command.d.ts +17 -2
  12. package/src/scripts/commands/land-pr-command.js +32 -3
  13. package/src/scripts/commands/land-pr-command.js.map +1 -1
  14. package/src/scripts/commands/review-upsert-pr-command.d.ts +88 -0
  15. package/src/scripts/commands/review-upsert-pr-command.js +244 -0
  16. package/src/scripts/commands/review-upsert-pr-command.js.map +1 -0
  17. package/src/scripts/commands/start-upsert-pr-command.d.ts +10 -15
  18. package/src/scripts/commands/start-upsert-pr-command.js +41 -47
  19. package/src/scripts/commands/start-upsert-pr-command.js.map +1 -1
  20. package/src/scripts/commands/worktree-cleanup.d.ts +45 -0
  21. package/src/scripts/commands/worktree-cleanup.js +130 -0
  22. package/src/scripts/commands/worktree-cleanup.js.map +1 -0
  23. package/src/scripts/pr-gate-app.d.ts +9 -5
  24. package/src/scripts/pr-gate-app.js +12 -8
  25. package/src/scripts/pr-gate-app.js.map +1 -1
  26. package/src/scripts/workflow/checklist-scanner.d.ts +27 -11
  27. package/src/scripts/workflow/checklist-scanner.js +37 -15
  28. package/src/scripts/workflow/checklist-scanner.js.map +1 -1
  29. package/src/scripts/workflow/diff-basis.d.ts +53 -0
  30. package/src/scripts/workflow/diff-basis.js +113 -0
  31. package/src/scripts/workflow/diff-basis.js.map +1 -0
  32. package/src/scripts/workflow/diff-materializer.d.ts +85 -0
  33. package/src/scripts/workflow/diff-materializer.js +253 -0
  34. package/src/scripts/workflow/diff-materializer.js.map +1 -0
  35. package/src/scripts/workflow/git-findForkPoint.d.ts +1 -1
  36. package/src/scripts/workflow/git-findForkPoint.js +1 -1
  37. package/src/scripts/workflow/git-findForkPoint.js.map +1 -1
  38. package/src/scripts/workflow/pr-context-writer.d.ts +16 -10
  39. package/src/scripts/workflow/pr-context-writer.js +20 -14
  40. package/src/scripts/workflow/pr-context-writer.js.map +1 -1
  41. package/src/scripts/workflow/review-stage-receipt.d.ts +36 -0
  42. package/src/scripts/workflow/review-stage-receipt.js +82 -0
  43. package/src/scripts/workflow/review-stage-receipt.js.map +1 -0
  44. package/src/scripts/workflow/reviewer-briefing-builder.d.ts +36 -0
  45. package/src/scripts/workflow/reviewer-briefing-builder.js +104 -0
  46. package/src/scripts/workflow/reviewer-briefing-builder.js.map +1 -0
  47. package/src/scripts/{wp-checklist.js → wp-review-upsert-pr.js} +3 -3
  48. package/src/scripts/wp-review-upsert-pr.js.map +1 -0
  49. package/src/scripts/commands/checklist-command.d.ts +0 -27
  50. package/src/scripts/commands/checklist-command.js +0 -97
  51. package/src/scripts/commands/checklist-command.js.map +0 -1
  52. package/src/scripts/wp-checklist.js.map +0 -1
  53. /package/src/scripts/{wp-checklist.d.ts → wp-review-upsert-pr.d.ts} +0 -0
@@ -1,10 +1,11 @@
1
1
  import { ChecklistReviewContext, DiffScope, ReviewJsonService } from '@webpieces/rules-config';
2
+ import { DiffBasis } from './diff-basis';
2
3
  /**
3
4
  * Writes `pr-context.json` (the 3-point base/head sha + the FULL changed-file set) and returns the context
4
5
  * every printed review instruction inlines.
5
6
  *
6
- * Extracted so `wp-start-upsert-pr` and `wp-checklist` cannot diverge. It used to live only in wp-start,
7
- * which meant `wp-checklist` — the command wp-start tells the AI to run FIRST — silently rendered its
7
+ * Extracted so `wp-start-upsert-pr` and `wp-review-upsert-pr` cannot diverge. It used to live only in wp-start,
8
+ * which meant `wp-review-upsert-pr` — the command wp-start tells the AI to run FIRST — silently rendered its
8
9
  * reviewer blocks with NO `git diff` command and NO full-file-set path whenever wp-start had not run this
9
10
  * cycle. A reviewer handed filenames and no way to read the diff is exactly the failure this instruction
10
11
  * exists to prevent, and it failed silently, which is worse.
@@ -16,14 +17,19 @@ export declare class PrContextWriter {
16
17
  private readonly reviewJsonService;
17
18
  constructor(diffScope: DiffScope, reviewJsonService: ReviewJsonService);
18
19
  /**
19
- * Compute + persist the context against the fork point the CALLER already resolved, so the sha a
20
- * reviewer is told to diff against is byte-identical to the one the pattern matching used. Returns an
21
- * EMPTY context when `forkPoint` is '' (no fork point from main) — callers must say so out loud rather
22
- * than quietly omit the lines it would have filled; see ChecklistInstructionsService.diffLines.
20
+ * Compute + persist the context against the {@link DiffBasis} the CALLER already resolved, so the range
21
+ * a reviewer is told to diff is byte-identical to the one the pattern matching used. Returns an EMPTY
22
+ * context when the basis has no fork point — callers must say so out loud rather than quietly omit the
23
+ * lines it would have filled; see ChecklistInstructionsService.diffLines.
23
24
  *
24
- * Two things mirror ChecklistScanner deliberately: no head argument to `getChangedFiles` (so the
25
- * working tree and untracked files are included, not just commits), and `tsOnly:false` (the default
26
- * drops every *.sql / Dockerfile / .env* file a checklist most wants to key on).
25
+ * Taking a DiffBasis rather than a bare `forkPoint: string` is the fix, not a refactor: with a lone sha
26
+ * this method had no way to know whether the tree was dirty, so it recorded `head:'HEAD'` and every
27
+ * downstream reader assembled a commit-to-commit command that shows nothing when work is uncommitted.
28
+ *
29
+ * Two things mirror ChecklistScanner deliberately: no head argument to `getChangedFiles` (so the working
30
+ * tree and untracked files are included, not just commits), and `tsOnly:false` (the default drops every
31
+ * *.sql / Dockerfile / .env* file a checklist most wants to key on). `includeDeletions` is on for the
32
+ * same reason: a DELETED migration is exactly what a checklist wants to see.
27
33
  */
28
- ensure(repoRoot: string, featureName: string, forkPoint: string): ChecklistReviewContext;
34
+ ensure(repoRoot: string, featureName: string, basis: DiffBasis, diffDir?: string): ChecklistReviewContext;
29
35
  }
@@ -8,8 +8,8 @@ const inversify_1 = require("inversify");
8
8
  * Writes `pr-context.json` (the 3-point base/head sha + the FULL changed-file set) and returns the context
9
9
  * every printed review instruction inlines.
10
10
  *
11
- * Extracted so `wp-start-upsert-pr` and `wp-checklist` cannot diverge. It used to live only in wp-start,
12
- * which meant `wp-checklist` — the command wp-start tells the AI to run FIRST — silently rendered its
11
+ * Extracted so `wp-start-upsert-pr` and `wp-review-upsert-pr` cannot diverge. It used to live only in wp-start,
12
+ * which meant `wp-review-upsert-pr` — the command wp-start tells the AI to run FIRST — silently rendered its
13
13
  * reviewer blocks with NO `git diff` command and NO full-file-set path whenever wp-start had not run this
14
14
  * cycle. A reviewer handed filenames and no way to read the diff is exactly the failure this instruction
15
15
  * exists to prevent, and it failed silently, which is worse.
@@ -24,23 +24,29 @@ let PrContextWriter = class PrContextWriter {
24
24
  this.reviewJsonService = reviewJsonService;
25
25
  }
26
26
  /**
27
- * Compute + persist the context against the fork point the CALLER already resolved, so the sha a
28
- * reviewer is told to diff against is byte-identical to the one the pattern matching used. Returns an
29
- * EMPTY context when `forkPoint` is '' (no fork point from main) — callers must say so out loud rather
30
- * than quietly omit the lines it would have filled; see ChecklistInstructionsService.diffLines.
27
+ * Compute + persist the context against the {@link DiffBasis} the CALLER already resolved, so the range
28
+ * a reviewer is told to diff is byte-identical to the one the pattern matching used. Returns an EMPTY
29
+ * context when the basis has no fork point — callers must say so out loud rather than quietly omit the
30
+ * lines it would have filled; see ChecklistInstructionsService.diffLines.
31
31
  *
32
- * Two things mirror ChecklistScanner deliberately: no head argument to `getChangedFiles` (so the
33
- * working tree and untracked files are included, not just commits), and `tsOnly:false` (the default
34
- * drops every *.sql / Dockerfile / .env* file a checklist most wants to key on).
32
+ * Taking a DiffBasis rather than a bare `forkPoint: string` is the fix, not a refactor: with a lone sha
33
+ * this method had no way to know whether the tree was dirty, so it recorded `head:'HEAD'` and every
34
+ * downstream reader assembled a commit-to-commit command that shows nothing when work is uncommitted.
35
+ *
36
+ * Two things mirror ChecklistScanner deliberately: no head argument to `getChangedFiles` (so the working
37
+ * tree and untracked files are included, not just commits), and `tsOnly:false` (the default drops every
38
+ * *.sql / Dockerfile / .env* file a checklist most wants to key on). `includeDeletions` is on for the
39
+ * same reason: a DELETED migration is exactly what a checklist wants to see.
35
40
  */
36
- ensure(repoRoot, featureName, forkPoint) {
37
- if (forkPoint === '')
41
+ ensure(repoRoot, featureName, basis, diffDir = '') {
42
+ if (basis.unresolved)
38
43
  return new rules_config_1.ChecklistReviewContext();
39
44
  const opts = new rules_config_1.ChangedFilesOptions();
40
45
  opts.tsOnly = false;
41
- const changed = this.diffScope.getChangedFiles(repoRoot, forkPoint, undefined, opts);
42
- const p = this.reviewJsonService.writePrContext(repoRoot, featureName, new rules_config_1.PrContext(forkPoint, 'HEAD', changed));
43
- return new rules_config_1.ChecklistReviewContext(forkPoint, p);
46
+ opts.includeDeletions = true;
47
+ const changed = this.diffScope.getChangedFiles(repoRoot, basis.base, undefined, opts);
48
+ const p = this.reviewJsonService.writePrContext(repoRoot, featureName, new rules_config_1.PrContext(basis.base, basis.headSha, changed, basis.dirty, basis.dirtyFiles, basis.diffCommand, diffDir, new Date().toISOString()));
49
+ return new rules_config_1.ChecklistReviewContext(basis.base, p, basis.fileDiffCommand, diffDir, basis.dirty);
44
50
  }
45
51
  };
46
52
  exports.PrContextWriter = PrContextWriter;
@@ -1 +1 @@
1
- {"version":3,"file":"pr-context-writer.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/pr-context-writer.ts"],"names":[],"mappings":";;;;AAAA,0DAEiC;AACjC,yCAA2D;AAE3D;;;;;;;;;;;GAWG;AAEI,IAAM,eAAe,GAArB,MAAM,eAAe;IAEH;IACA;IAFrB,YACqB,SAAoB,EACpB,iBAAoC;QADpC,cAAS,GAAT,SAAS,CAAW;QACpB,sBAAiB,GAAjB,iBAAiB,CAAmB;IACtD,CAAC;IAEJ;;;;;;;;;OASG;IACH,MAAM,CAAC,QAAgB,EAAE,WAAmB,EAAE,SAAiB;QAC3D,IAAI,SAAS,KAAK,EAAE;YAAE,OAAO,IAAI,qCAAsB,EAAE,CAAC;QAC1D,MAAM,IAAI,GAAG,IAAI,kCAAmB,EAAE,CAAC;QACvC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;QACrF,MAAM,CAAC,GAAG,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAC3C,QAAQ,EAAE,WAAW,EAAE,IAAI,wBAAS,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,CACnE,CAAC;QACF,OAAO,IAAI,qCAAsB,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IACpD,CAAC;CACJ,CAAA;AA1BY,0CAAe;0BAAf,eAAe;IAD3B,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAGL,wBAAS;QACD,gCAAiB;GAHhD,eAAe,CA0B3B","sourcesContent":["import {\n ChangedFilesOptions, ChecklistReviewContext, DiffScope, PrContext, ReviewJsonService,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\n/**\n * Writes `pr-context.json` (the 3-point base/head sha + the FULL changed-file set) and returns the context\n * every printed review instruction inlines.\n *\n * Extracted so `wp-start-upsert-pr` and `wp-checklist` cannot diverge. It used to live only in wp-start,\n * which meant `wp-checklist` — the command wp-start tells the AI to run FIRST — silently rendered its\n * reviewer blocks with NO `git diff` command and NO full-file-set path whenever wp-start had not run this\n * cycle. A reviewer handed filenames and no way to read the diff is exactly the failure this instruction\n * exists to prevent, and it failed silently, which is worse.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class PrContextWriter {\n constructor(\n private readonly diffScope: DiffScope,\n private readonly reviewJsonService: ReviewJsonService,\n ) {}\n\n /**\n * Compute + persist the context against the fork point the CALLER already resolved, so the sha a\n * reviewer is told to diff against is byte-identical to the one the pattern matching used. Returns an\n * EMPTY context when `forkPoint` is '' (no fork point from main) — callers must say so out loud rather\n * than quietly omit the lines it would have filled; see ChecklistInstructionsService.diffLines.\n *\n * Two things mirror ChecklistScanner deliberately: no head argument to `getChangedFiles` (so the\n * working tree and untracked files are included, not just commits), and `tsOnly:false` (the default\n * drops every *.sql / Dockerfile / .env* file a checklist most wants to key on).\n */\n ensure(repoRoot: string, featureName: string, forkPoint: string): ChecklistReviewContext {\n if (forkPoint === '') return new ChecklistReviewContext();\n const opts = new ChangedFilesOptions();\n opts.tsOnly = false;\n const changed = this.diffScope.getChangedFiles(repoRoot, forkPoint, undefined, opts);\n const p = this.reviewJsonService.writePrContext(\n repoRoot, featureName, new PrContext(forkPoint, 'HEAD', changed),\n );\n return new ChecklistReviewContext(forkPoint, p);\n }\n}\n"]}
1
+ {"version":3,"file":"pr-context-writer.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/pr-context-writer.ts"],"names":[],"mappings":";;;;AAAA,0DAEiC;AACjC,yCAA2D;AAG3D;;;;;;;;;;;GAWG;AAEI,IAAM,eAAe,GAArB,MAAM,eAAe;IAEH;IACA;IAFrB,YACqB,SAAoB,EACpB,iBAAoC;QADpC,cAAS,GAAT,SAAS,CAAW;QACpB,sBAAiB,GAAjB,iBAAiB,CAAmB;IACtD,CAAC;IAEJ;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,QAAgB,EAAE,WAAmB,EAAE,KAAgB,EAAE,OAAO,GAAG,EAAE;QACxE,IAAI,KAAK,CAAC,UAAU;YAAE,OAAO,IAAI,qCAAsB,EAAE,CAAC;QAC1D,MAAM,IAAI,GAAG,IAAI,kCAAmB,EAAE,CAAC;QACvC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;QACtF,MAAM,CAAC,GAAG,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,QAAQ,EAAE,WAAW,EAAE,IAAI,wBAAS,CAChF,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,EAClC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,WAAW,EAAE,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CACtF,CAAC,CAAC;QACH,OAAO,IAAI,qCAAsB,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC,eAAe,EAAE,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAClG,CAAC;CACJ,CAAA;AAjCY,0CAAe;0BAAf,eAAe;IAD3B,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAGL,wBAAS;QACD,gCAAiB;GAHhD,eAAe,CAiC3B","sourcesContent":["import {\n ChangedFilesOptions, ChecklistReviewContext, DiffScope, PrContext, ReviewJsonService,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { DiffBasis } from './diff-basis';\n\n/**\n * Writes `pr-context.json` (the 3-point base/head sha + the FULL changed-file set) and returns the context\n * every printed review instruction inlines.\n *\n * Extracted so `wp-start-upsert-pr` and `wp-review-upsert-pr` cannot diverge. It used to live only in wp-start,\n * which meant `wp-review-upsert-pr` — the command wp-start tells the AI to run FIRST — silently rendered its\n * reviewer blocks with NO `git diff` command and NO full-file-set path whenever wp-start had not run this\n * cycle. A reviewer handed filenames and no way to read the diff is exactly the failure this instruction\n * exists to prevent, and it failed silently, which is worse.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class PrContextWriter {\n constructor(\n private readonly diffScope: DiffScope,\n private readonly reviewJsonService: ReviewJsonService,\n ) {}\n\n /**\n * Compute + persist the context against the {@link DiffBasis} the CALLER already resolved, so the range\n * a reviewer is told to diff is byte-identical to the one the pattern matching used. Returns an EMPTY\n * context when the basis has no fork point — callers must say so out loud rather than quietly omit the\n * lines it would have filled; see ChecklistInstructionsService.diffLines.\n *\n * Taking a DiffBasis rather than a bare `forkPoint: string` is the fix, not a refactor: with a lone sha\n * this method had no way to know whether the tree was dirty, so it recorded `head:'HEAD'` and every\n * downstream reader assembled a commit-to-commit command that shows nothing when work is uncommitted.\n *\n * Two things mirror ChecklistScanner deliberately: no head argument to `getChangedFiles` (so the working\n * tree and untracked files are included, not just commits), and `tsOnly:false` (the default drops every\n * *.sql / Dockerfile / .env* file a checklist most wants to key on). `includeDeletions` is on for the\n * same reason: a DELETED migration is exactly what a checklist wants to see.\n */\n ensure(repoRoot: string, featureName: string, basis: DiffBasis, diffDir = ''): ChecklistReviewContext {\n if (basis.unresolved) return new ChecklistReviewContext();\n const opts = new ChangedFilesOptions();\n opts.tsOnly = false;\n opts.includeDeletions = true;\n const changed = this.diffScope.getChangedFiles(repoRoot, basis.base, undefined, opts);\n const p = this.reviewJsonService.writePrContext(repoRoot, featureName, new PrContext(\n basis.base, basis.headSha, changed,\n basis.dirty, basis.dirtyFiles, basis.diffCommand, diffDir, new Date().toISOString(),\n ));\n return new ChecklistReviewContext(basis.base, p, basis.fileDiffCommand, diffDir, basis.dirty);\n }\n}\n"]}
@@ -0,0 +1,36 @@
1
+ import { ReviewJsonService } from '@webpieces/rules-config';
2
+ export declare const RECEIPT_FILE = "review-stage.json";
3
+ /**
4
+ * Proof that stage ② (`wp-review-upsert-pr`) actually ran, and on WHICH commit. Data-only (per CLAUDE.md).
5
+ */
6
+ export declare class ReviewStageReceipt {
7
+ headSha: string;
8
+ mergeValidated: boolean;
9
+ buildCommand: string;
10
+ buildPassedAt: string;
11
+ reviewersBriefed: string[];
12
+ constructor(headSha?: string, mergeValidated?: boolean, buildCommand?: string, buildPassedAt?: string, reviewersBriefed?: string[]);
13
+ }
14
+ /**
15
+ * Reads/writes the stage-② receipt.
16
+ *
17
+ * WHY a receipt rather than relying on the artifacts that already exist: `wp-finish-upsert-pr` already
18
+ * refuses when a reviewer has no verdict and when review.json is absent, so a repo WITH checklists is
19
+ * mostly interlocked already. A repo with NO checklists is not — review.json is the only thing standing
20
+ * between it and a PR, and the AI writes review.json itself. Nothing stopped it from writing that file and
21
+ * going straight to finish, skipping the merge validation and the build entirely.
22
+ *
23
+ * The receipt also pays for itself in the other direction: because it records the sha the build passed on,
24
+ * finish can SKIP its own build when HEAD has not moved, so adding a gate in the middle did not cost a
25
+ * second full `nx affected` run.
26
+ *
27
+ * `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.
28
+ */
29
+ export declare class ReviewStageReceiptService {
30
+ private readonly reviewJsonService;
31
+ constructor(reviewJsonService: ReviewJsonService);
32
+ receiptPath(repoRoot: string, featureName: string): string;
33
+ write(repoRoot: string, featureName: string, receipt: ReviewStageReceipt): string;
34
+ /** The receipt, or null when stage ② never ran (or left something unreadable behind). */
35
+ read(repoRoot: string, featureName: string): ReviewStageReceipt | null;
36
+ }
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ReviewStageReceiptService = exports.ReviewStageReceipt = exports.RECEIPT_FILE = 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
+ exports.RECEIPT_FILE = 'review-stage.json';
10
+ /**
11
+ * Proof that stage ② (`wp-review-upsert-pr`) actually ran, and on WHICH commit. Data-only (per CLAUDE.md).
12
+ */
13
+ class ReviewStageReceipt {
14
+ headSha; // the sha the merge was validated and the build was run against
15
+ mergeValidated; // a 3-point merge was finalized here, or there was none to finalize
16
+ buildCommand;
17
+ buildPassedAt; // ISO; '' when the gate was skipped (mode OFF / no command)
18
+ reviewersBriefed;
19
+ // eslint-disable-next-line @typescript-eslint/max-params
20
+ constructor(headSha = '', mergeValidated = false, buildCommand = '', buildPassedAt = '', reviewersBriefed = []) {
21
+ this.headSha = headSha;
22
+ this.mergeValidated = mergeValidated;
23
+ this.buildCommand = buildCommand;
24
+ this.buildPassedAt = buildPassedAt;
25
+ this.reviewersBriefed = reviewersBriefed;
26
+ }
27
+ }
28
+ exports.ReviewStageReceipt = ReviewStageReceipt;
29
+ /**
30
+ * Reads/writes the stage-② receipt.
31
+ *
32
+ * WHY a receipt rather than relying on the artifacts that already exist: `wp-finish-upsert-pr` already
33
+ * refuses when a reviewer has no verdict and when review.json is absent, so a repo WITH checklists is
34
+ * mostly interlocked already. A repo with NO checklists is not — review.json is the only thing standing
35
+ * between it and a PR, and the AI writes review.json itself. Nothing stopped it from writing that file and
36
+ * going straight to finish, skipping the merge validation and the build entirely.
37
+ *
38
+ * The receipt also pays for itself in the other direction: because it records the sha the build passed on,
39
+ * finish can SKIP its own build when HEAD has not moved, so adding a gate in the middle did not cost a
40
+ * second full `nx affected` run.
41
+ *
42
+ * `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.
43
+ */
44
+ let ReviewStageReceiptService = class ReviewStageReceiptService {
45
+ reviewJsonService;
46
+ constructor(reviewJsonService) {
47
+ this.reviewJsonService = reviewJsonService;
48
+ }
49
+ receiptPath(repoRoot, featureName) {
50
+ return path.join(this.reviewJsonService.prDirFor(repoRoot, featureName), exports.RECEIPT_FILE);
51
+ }
52
+ write(repoRoot, featureName, receipt) {
53
+ const p = this.receiptPath(repoRoot, featureName);
54
+ fs.mkdirSync(path.dirname(p), { recursive: true });
55
+ fs.writeFileSync(p, JSON.stringify(receipt, null, 2) + '\n');
56
+ return p;
57
+ }
58
+ /** The receipt, or null when stage ② never ran (or left something unreadable behind). */
59
+ read(repoRoot, featureName) {
60
+ const p = this.receiptPath(repoRoot, featureName);
61
+ if (!fs.existsSync(p))
62
+ return null;
63
+ // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unreadable receipt is treated as absent, which re-runs stage ②
64
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
65
+ try {
66
+ // webpieces-disable no-any-unknown -- opaque parsed JSON, narrowed field-by-field below
67
+ const raw = JSON.parse(fs.readFileSync(p, 'utf8'));
68
+ return new ReviewStageReceipt(typeof raw['headSha'] === 'string' ? raw['headSha'] : '', raw['mergeValidated'] === true, typeof raw['buildCommand'] === 'string' ? raw['buildCommand'] : '', typeof raw['buildPassedAt'] === 'string' ? raw['buildPassedAt'] : '', Array.isArray(raw['reviewersBriefed']) ? raw['reviewersBriefed'] : []);
69
+ }
70
+ catch (err) {
71
+ const error = (0, rules_config_1.toError)(err);
72
+ void error; // unreadable ⇒ treated as absent, which re-runs stage ② (the safe direction)
73
+ return null;
74
+ }
75
+ }
76
+ };
77
+ exports.ReviewStageReceiptService = ReviewStageReceiptService;
78
+ exports.ReviewStageReceiptService = ReviewStageReceiptService = tslib_1.__decorate([
79
+ (0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
80
+ tslib_1.__metadata("design:paramtypes", [rules_config_1.ReviewJsonService])
81
+ ], ReviewStageReceiptService);
82
+ //# sourceMappingURL=review-stage-receipt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-stage-receipt.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/review-stage-receipt.ts"],"names":[],"mappings":";;;;AAAA,+CAAyB;AACzB,mDAA6B;AAC7B,0DAAqE;AACrE,yCAA2D;AAE9C,QAAA,YAAY,GAAG,mBAAmB,CAAC;AAEhD;;GAEG;AACH,MAAa,kBAAkB;IAC3B,OAAO,CAAS,CAAU,gEAAgE;IAC1F,cAAc,CAAU,CAAE,oEAAoE;IAC9F,YAAY,CAAS;IACrB,aAAa,CAAS,CAAI,4DAA4D;IACtF,gBAAgB,CAAW;IAE3B,yDAAyD;IACzD,YAAY,OAAO,GAAG,EAAE,EAAE,cAAc,GAAG,KAAK,EAAE,YAAY,GAAG,EAAE,EAAE,aAAa,GAAG,EAAE,EAAE,mBAA6B,EAAE;QACpH,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACrC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;IAC7C,CAAC;CACJ;AAfD,gDAeC;AAED;;;;;;;;;;;;;;GAcG;AAEI,IAAM,yBAAyB,GAA/B,MAAM,yBAAyB;IACL;IAA7B,YAA6B,iBAAoC;QAApC,sBAAiB,GAAjB,iBAAiB,CAAmB;IAAG,CAAC;IAErE,WAAW,CAAC,QAAgB,EAAE,WAAmB;QAC7C,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,oBAAY,CAAC,CAAC;IAC3F,CAAC;IAED,KAAK,CAAC,QAAgB,EAAE,WAAmB,EAAE,OAA2B;QACpE,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAClD,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAC7D,OAAO,CAAC,CAAC;IACb,CAAC;IAED,yFAAyF;IACzF,IAAI,CAAC,QAAgB,EAAE,WAAmB;QACtC,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAClD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACnC,6HAA6H;QAC7H,8DAA8D;QAC9D,IAAI,CAAC;YACD,wFAAwF;YACxF,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAA4B,CAAC;YAC9E,OAAO,IAAI,kBAAkB,CACzB,OAAO,GAAG,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,GAAG,CAAC,SAAS,CAAY,CAAC,CAAC,CAAC,EAAE,EACpE,GAAG,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAC9B,OAAO,GAAG,CAAC,cAAc,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,GAAG,CAAC,cAAc,CAAY,CAAC,CAAC,CAAC,EAAE,EAC9E,OAAO,GAAG,CAAC,eAAe,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,GAAG,CAAC,eAAe,CAAY,CAAC,CAAC,CAAC,EAAE,EAChF,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,kBAAkB,CAAc,CAAC,CAAC,CAAC,EAAE,CACtF,CAAC;QACN,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,sBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,KAAK,KAAK,CAAC,CAAC,6EAA6E;YACzF,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;CACJ,CAAA;AApCY,8DAAyB;oCAAzB,yBAAyB;IADrC,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAEW,gCAAiB;GADxD,yBAAyB,CAoCrC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { ReviewJsonService, toError } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nexport const RECEIPT_FILE = 'review-stage.json';\n\n/**\n * Proof that stage ② (`wp-review-upsert-pr`) actually ran, and on WHICH commit. Data-only (per CLAUDE.md).\n */\nexport class ReviewStageReceipt {\n headSha: string; // the sha the merge was validated and the build was run against\n mergeValidated: boolean; // a 3-point merge was finalized here, or there was none to finalize\n buildCommand: string;\n buildPassedAt: string; // ISO; '' when the gate was skipped (mode OFF / no command)\n reviewersBriefed: string[];\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(headSha = '', mergeValidated = false, buildCommand = '', buildPassedAt = '', reviewersBriefed: string[] = []) {\n this.headSha = headSha;\n this.mergeValidated = mergeValidated;\n this.buildCommand = buildCommand;\n this.buildPassedAt = buildPassedAt;\n this.reviewersBriefed = reviewersBriefed;\n }\n}\n\n/**\n * Reads/writes the stage-② receipt.\n *\n * WHY a receipt rather than relying on the artifacts that already exist: `wp-finish-upsert-pr` already\n * refuses when a reviewer has no verdict and when review.json is absent, so a repo WITH checklists is\n * mostly interlocked already. A repo with NO checklists is not — review.json is the only thing standing\n * between it and a PR, and the AI writes review.json itself. Nothing stopped it from writing that file and\n * going straight to finish, skipping the merge validation and the build entirely.\n *\n * The receipt also pays for itself in the other direction: because it records the sha the build passed on,\n * finish can SKIP its own build when HEAD has not moved, so adding a gate in the middle did not cost a\n * second full `nx affected` run.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ReviewStageReceiptService {\n constructor(private readonly reviewJsonService: ReviewJsonService) {}\n\n receiptPath(repoRoot: string, featureName: string): string {\n return path.join(this.reviewJsonService.prDirFor(repoRoot, featureName), RECEIPT_FILE);\n }\n\n write(repoRoot: string, featureName: string, receipt: ReviewStageReceipt): string {\n const p = this.receiptPath(repoRoot, featureName);\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(p, JSON.stringify(receipt, null, 2) + '\\n');\n return p;\n }\n\n /** The receipt, or null when stage ② never ran (or left something unreadable behind). */\n read(repoRoot: string, featureName: string): ReviewStageReceipt | null {\n const p = this.receiptPath(repoRoot, featureName);\n if (!fs.existsSync(p)) return null;\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unreadable receipt is treated as absent, which re-runs stage ②\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- opaque parsed JSON, narrowed field-by-field below\n const raw = JSON.parse(fs.readFileSync(p, 'utf8')) as Record<string, unknown>;\n return new ReviewStageReceipt(\n typeof raw['headSha'] === 'string' ? (raw['headSha'] as string) : '',\n raw['mergeValidated'] === true,\n typeof raw['buildCommand'] === 'string' ? (raw['buildCommand'] as string) : '',\n typeof raw['buildPassedAt'] === 'string' ? (raw['buildPassedAt'] as string) : '',\n Array.isArray(raw['reviewersBriefed']) ? (raw['reviewersBriefed'] as string[]) : [],\n );\n } catch (err: unknown) {\n const error = toError(err);\n void error; // unreadable ⇒ treated as absent, which re-runs stage ② (the safe direction)\n return null;\n }\n }\n}\n"]}
@@ -0,0 +1,36 @@
1
+ import { PrGateConfig, ReviewerBriefing, ReviewJsonService } from '@webpieces/rules-config';
2
+ import { DiffManifest } from './diff-materializer';
3
+ import { ChecklistScan } from './checklist-scanner';
4
+ /**
5
+ * Turns a {@link ChecklistScan} plus the materialized {@link DiffManifest} into one {@link ReviewerBriefing}
6
+ * per reviewer — resolving every path to an absolute one, because a reviewer that has to resolve a path
7
+ * ends up searching, and searching is the cost this whole feature exists to remove.
8
+ *
9
+ * `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.
10
+ */
11
+ export declare class ReviewerBriefingBuilder {
12
+ private readonly reviewJsonService;
13
+ constructor(reviewJsonService: ReviewJsonService);
14
+ build(repoRoot: string, scan: ChecklistScan, manifest: DiffManifest, diffDir: string, config: PrGateConfig): ReviewerBriefing[];
15
+ private one;
16
+ /**
17
+ * Deduped absolute parent dirs of the matched files. Dirs rather than files because the point is to say
18
+ * "the neighbourhood is here" — a reviewer already has the file list; what it lacks is where to look for
19
+ * the code AROUND the change.
20
+ */
21
+ private sourceDirsFor;
22
+ /**
23
+ * The pre-resolved context list, from two config keys with deliberately different jobs:
24
+ *
25
+ * `reviewContextPackages` — installed packages, resolved to their real on-disk dir. This is what deletes
26
+ * the repeated `node_modules/@webpieces` greps: the tooling knows where a package resolved to, and the
27
+ * reviewer does not.
28
+ * `reviewContext` — the repo-specific escape hatch (`{label, path}`) for what tooling cannot possibly
29
+ * guess, e.g. "queue names live in terraform/services/".
30
+ *
31
+ * Neither ever drops an entry: an unresolvable package or a missing path is LISTED, with the reason. A
32
+ * silently shorter list is indistinguishable from a complete one.
33
+ */
34
+ private contextEntries;
35
+ private resolvePackage;
36
+ }
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ReviewerBriefingBuilder = 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
+ /**
10
+ * Turns a {@link ChecklistScan} plus the materialized {@link DiffManifest} into one {@link ReviewerBriefing}
11
+ * per reviewer — resolving every path to an absolute one, because a reviewer that has to resolve a path
12
+ * ends up searching, and searching is the cost this whole feature exists to remove.
13
+ *
14
+ * `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.
15
+ */
16
+ let ReviewerBriefingBuilder = class ReviewerBriefingBuilder {
17
+ reviewJsonService;
18
+ constructor(reviewJsonService) {
19
+ this.reviewJsonService = reviewJsonService;
20
+ }
21
+ build(repoRoot, scan, manifest, diffDir, config) {
22
+ // Resolved ONCE for the whole run, not per reviewer: `require.resolve` hits the filesystem, and the
23
+ // answer cannot differ between two reviewers in the same process.
24
+ const shared = this.contextEntries(repoRoot, config);
25
+ const diffByFile = new Map();
26
+ for (const e of manifest.entries)
27
+ diffByFile.set(e.file, path.resolve(repoRoot, e.diffFile));
28
+ return scan.applicable.map((req) => this.one(repoRoot, req, scan, diffByFile, diffDir, shared));
29
+ }
30
+ // eslint-disable-next-line @typescript-eslint/max-params
31
+ one(repoRoot, req, scan, diffByFile, diffDir, shared) {
32
+ const b = new rules_config_1.ReviewerBriefing(req.subagent, req.id, repoRoot);
33
+ b.docPath = req.doc.trim() === '' ? '' : path.resolve(repoRoot, req.doc);
34
+ b.diffDir = diffDir;
35
+ b.allDiffPath = diffDir === '' ? '' : path.join(diffDir, 'ALL.diff');
36
+ b.manifestPath = diffDir === '' ? '' : path.join(diffDir, 'manifest.json');
37
+ b.myFiles = req.matchedFiles.map((f) => new rules_config_1.BriefedFile(f, diffByFile.get(f) ?? ''));
38
+ b.matchedPatterns = req.matchedPatterns;
39
+ b.sourceDirs = this.sourceDirsFor(repoRoot, req.matchedFiles);
40
+ b.contextEntries = shared;
41
+ b.verdictPath = this.reviewJsonService.checklistResultPath(scan.reviewPath, req.id);
42
+ b.fileDiffCommand = scan.context.fileDiffCommand;
43
+ b.dirty = scan.basis.dirty;
44
+ return b;
45
+ }
46
+ /**
47
+ * Deduped absolute parent dirs of the matched files. Dirs rather than files because the point is to say
48
+ * "the neighbourhood is here" — a reviewer already has the file list; what it lacks is where to look for
49
+ * the code AROUND the change.
50
+ */
51
+ sourceDirsFor(repoRoot, files) {
52
+ const dirs = new Set();
53
+ for (const f of files)
54
+ dirs.add(path.resolve(repoRoot, path.dirname(f)));
55
+ return [...dirs].sort();
56
+ }
57
+ /**
58
+ * The pre-resolved context list, from two config keys with deliberately different jobs:
59
+ *
60
+ * `reviewContextPackages` — installed packages, resolved to their real on-disk dir. This is what deletes
61
+ * the repeated `node_modules/@webpieces` greps: the tooling knows where a package resolved to, and the
62
+ * reviewer does not.
63
+ * `reviewContext` — the repo-specific escape hatch (`{label, path}`) for what tooling cannot possibly
64
+ * guess, e.g. "queue names live in terraform/services/".
65
+ *
66
+ * Neither ever drops an entry: an unresolvable package or a missing path is LISTED, with the reason. A
67
+ * silently shorter list is indistinguishable from a complete one.
68
+ */
69
+ contextEntries(repoRoot, config) {
70
+ const entries = [];
71
+ for (const pkg of config.reviewContextPackages)
72
+ entries.push(this.resolvePackage(repoRoot, pkg));
73
+ for (const item of config.reviewContext) {
74
+ const abs = path.resolve(repoRoot, item.path);
75
+ entries.push(fs.existsSync(abs)
76
+ ? new rules_config_1.ContextEntry(item.label, abs)
77
+ : new rules_config_1.ContextEntry(item.label, '', `(configured as "${item.path}" but missing)`));
78
+ }
79
+ return entries;
80
+ }
81
+ resolvePackage(repoRoot, pkg) {
82
+ // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unresolvable package is REPORTED as such, never fatal
83
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
84
+ try {
85
+ const manifestPath = require.resolve(`${pkg}/package.json`, { paths: [repoRoot] });
86
+ const dir = path.dirname(manifestPath);
87
+ // webpieces-disable no-any-unknown -- opaque package.json, only `description` is read
88
+ const meta = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
89
+ const desc = typeof meta['description'] === 'string' ? meta['description'] : '';
90
+ return new rules_config_1.ContextEntry(desc === '' ? pkg : `${pkg} — ${desc}`, dir);
91
+ }
92
+ catch (err) {
93
+ const error = (0, rules_config_1.toError)(err);
94
+ void error; // an unresolvable package is REPORTED below, never fatal
95
+ return new rules_config_1.ContextEntry(pkg, '', '(not installed — configured in pr-gate.reviewContextPackages)');
96
+ }
97
+ }
98
+ };
99
+ exports.ReviewerBriefingBuilder = ReviewerBriefingBuilder;
100
+ exports.ReviewerBriefingBuilder = ReviewerBriefingBuilder = tslib_1.__decorate([
101
+ (0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
102
+ tslib_1.__metadata("design:paramtypes", [rules_config_1.ReviewJsonService])
103
+ ], ReviewerBriefingBuilder);
104
+ //# sourceMappingURL=reviewer-briefing-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reviewer-briefing-builder.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/reviewer-briefing-builder.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport {\n BriefedFile, ContextEntry, PrGateConfig, RequiredChecklist, ReviewerBriefing, ReviewJsonService, toError,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { DiffManifest } from './diff-materializer';\nimport { ChecklistScan } from './checklist-scanner';\n\n/**\n * Turns a {@link ChecklistScan} plus the materialized {@link DiffManifest} into one {@link ReviewerBriefing}\n * per reviewer — resolving every path to an absolute one, because a reviewer that has to resolve a path\n * ends up searching, and searching is the cost this whole feature exists to remove.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ReviewerBriefingBuilder {\n constructor(private readonly reviewJsonService: ReviewJsonService) {}\n\n build(repoRoot: string, scan: ChecklistScan, manifest: DiffManifest, diffDir: string, config: PrGateConfig): ReviewerBriefing[] {\n // Resolved ONCE for the whole run, not per reviewer: `require.resolve` hits the filesystem, and the\n // answer cannot differ between two reviewers in the same process.\n const shared = this.contextEntries(repoRoot, config);\n const diffByFile = new Map<string, string>();\n for (const e of manifest.entries) diffByFile.set(e.file, path.resolve(repoRoot, e.diffFile));\n return scan.applicable.map((req: RequiredChecklist): ReviewerBriefing =>\n this.one(repoRoot, req, scan, diffByFile, diffDir, shared));\n }\n\n // eslint-disable-next-line @typescript-eslint/max-params\n private one(\n repoRoot: string, req: RequiredChecklist, scan: ChecklistScan,\n diffByFile: Map<string, string>, diffDir: string, shared: ContextEntry[],\n ): ReviewerBriefing {\n const b = new ReviewerBriefing(req.subagent, req.id, repoRoot);\n b.docPath = req.doc.trim() === '' ? '' : path.resolve(repoRoot, req.doc);\n b.diffDir = diffDir;\n b.allDiffPath = diffDir === '' ? '' : path.join(diffDir, 'ALL.diff');\n b.manifestPath = diffDir === '' ? '' : path.join(diffDir, 'manifest.json');\n b.myFiles = req.matchedFiles.map((f: string): BriefedFile => new BriefedFile(f, diffByFile.get(f) ?? ''));\n b.matchedPatterns = req.matchedPatterns;\n b.sourceDirs = this.sourceDirsFor(repoRoot, req.matchedFiles);\n b.contextEntries = shared;\n b.verdictPath = this.reviewJsonService.checklistResultPath(scan.reviewPath, req.id);\n b.fileDiffCommand = scan.context.fileDiffCommand;\n b.dirty = scan.basis.dirty;\n return b;\n }\n\n /**\n * Deduped absolute parent dirs of the matched files. Dirs rather than files because the point is to say\n * \"the neighbourhood is here\" — a reviewer already has the file list; what it lacks is where to look for\n * the code AROUND the change.\n */\n private sourceDirsFor(repoRoot: string, files: readonly string[]): string[] {\n const dirs = new Set<string>();\n for (const f of files) dirs.add(path.resolve(repoRoot, path.dirname(f)));\n return [...dirs].sort();\n }\n\n /**\n * The pre-resolved context list, from two config keys with deliberately different jobs:\n *\n * `reviewContextPackages` — installed packages, resolved to their real on-disk dir. This is what deletes\n * the repeated `node_modules/@webpieces` greps: the tooling knows where a package resolved to, and the\n * reviewer does not.\n * `reviewContext` — the repo-specific escape hatch (`{label, path}`) for what tooling cannot possibly\n * guess, e.g. \"queue names live in terraform/services/\".\n *\n * Neither ever drops an entry: an unresolvable package or a missing path is LISTED, with the reason. A\n * silently shorter list is indistinguishable from a complete one.\n */\n private contextEntries(repoRoot: string, config: PrGateConfig): ContextEntry[] {\n const entries: ContextEntry[] = [];\n for (const pkg of config.reviewContextPackages) entries.push(this.resolvePackage(repoRoot, pkg));\n for (const item of config.reviewContext) {\n const abs = path.resolve(repoRoot, item.path);\n entries.push(fs.existsSync(abs)\n ? new ContextEntry(item.label, abs)\n : new ContextEntry(item.label, '', `(configured as \"${item.path}\" but missing)`));\n }\n return entries;\n }\n\n private resolvePackage(repoRoot: string, pkg: string): ContextEntry {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unresolvable package is REPORTED as such, never fatal\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const manifestPath = require.resolve(`${pkg}/package.json`, { paths: [repoRoot] });\n const dir = path.dirname(manifestPath);\n // webpieces-disable no-any-unknown -- opaque package.json, only `description` is read\n const meta = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as Record<string, unknown>;\n const desc = typeof meta['description'] === 'string' ? (meta['description'] as string) : '';\n return new ContextEntry(desc === '' ? pkg : `${pkg} — ${desc}`, dir);\n } catch (err: unknown) {\n const error = toError(err);\n void error; // an unresolvable package is REPORTED below, never fatal\n return new ContextEntry(pkg, '', '(not installed — configured in pr-gate.reviewContextPackages)');\n }\n }\n}\n"]}
@@ -10,7 +10,7 @@ const pr_gate_app_1 = require("./pr-gate-app");
10
10
  // autobind self-binds every @injectable(Singleton) tooling class (replaces the buildProviderModule registry scan)
11
11
  const container = new inversify_1.Container({ autobind: true });
12
12
  // Reject `--help`/bogus flags BEFORE the app touches git — an ignored flag must never start the flow.
13
- container.get(rules_config_1.CliArgs).assertNoArgs(new rules_config_1.CliUsage('wp-checklist', 'Print which reviewer subagents this diff owes, and exactly what to tell them.'));
14
- await container.get(pr_gate_app_1.PrGateApp).checklist();
13
+ container.get(rules_config_1.CliArgs).assertNoArgs(new rules_config_1.CliUsage('wp-review-upsert-pr', 'Validate the 3-point merge, build it, extract this branch\'s diff, and brief the reviewer subagents.'));
14
+ await container.get(pr_gate_app_1.PrGateApp).reviewUpsertPr();
15
15
  });
16
- //# sourceMappingURL=wp-checklist.js.map
16
+ //# sourceMappingURL=wp-review-upsert-pr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wp-review-upsert-pr.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/wp-review-upsert-pr.ts"],"names":[],"mappings":";;;AACA,4BAA0B;AAC1B,yCAAsC;AACtC,0DAAqE;AACrE,+CAA0C;AAE1C,mGAAmG;AACnG,IAAA,sBAAO,EAAC,KAAK,IAAmB,EAAE;IAC9B,kHAAkH;IAClH,MAAM,SAAS,GAAG,IAAI,qBAAS,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,sGAAsG;IACtG,SAAS,CAAC,GAAG,CAAC,sBAAO,CAAC,CAAC,YAAY,CAAC,IAAI,uBAAQ,CAC5C,qBAAqB,EACrB,sGAAsG,CACzG,CAAC,CAAC;IACH,MAAM,SAAS,CAAC,GAAG,CAAC,uBAAS,CAAC,CAAC,cAAc,EAAE,CAAC;AACpD,CAAC,CAAC,CAAC","sourcesContent":["#!/usr/bin/env node\nimport 'reflect-metadata';\nimport { Container } from 'inversify';\nimport { runMain, CliArgs, CliUsage } from '@webpieces/rules-config';\nimport { PrGateApp } from './pr-gate-app';\n\n// Composition root: build the container and resolve the app so inversify constructs the whole DAG.\nrunMain(async (): Promise<void> => {\n // autobind self-binds every @injectable(Singleton) tooling class (replaces the buildProviderModule registry scan)\n const container = new Container({ autobind: true });\n // Reject `--help`/bogus flags BEFORE the app touches git — an ignored flag must never start the flow.\n container.get(CliArgs).assertNoArgs(new CliUsage(\n 'wp-review-upsert-pr',\n 'Validate the 3-point merge, build it, extract this branch\\'s diff, and brief the reviewer subagents.',\n ));\n await container.get(PrGateApp).reviewUpsertPr();\n});\n"]}
@@ -1,27 +0,0 @@
1
- import { RepoRootFinder, ChecklistInstructionsService } from '@webpieces/rules-config';
2
- import { ChecklistNotice } from '../workflow/checklist-notice';
3
- import { ChecklistScanner } from '../workflow/checklist-scanner';
4
- /**
5
- * `wp-checklist` — ALWAYS SUCCEEDS, always just lists. It answers the one question the coding agent has at
6
- * review time: *which reviewer subagents do I owe on this branch, and what exactly do I tell them?*
7
- *
8
- * Safe to run at any time, including mid-work with a dirty tree — uncommitted and untracked files are part
9
- * of the diff it matches against (see {@link ChecklistScanner}). It exits 0 in every case: zero checklists
10
- * configured, zero matched, or several still outstanding. Reporting is not gating; `wp-finish-upsert-pr` is
11
- * the only command that refuses, and it re-uses this exact scan with `filterAlreadyReviewed: true`, so the
12
- * two can never disagree about what is owed.
13
- */
14
- export declare class ChecklistCommand {
15
- private readonly repoRootFinder;
16
- private readonly checklistScanner;
17
- private readonly instructions;
18
- private readonly checklistNotice;
19
- constructor(repoRootFinder: RepoRootFinder, checklistScanner: ChecklistScanner, instructions: ChecklistInstructionsService, checklistNotice: ChecklistNotice);
20
- run(): Promise<void>;
21
- private report;
22
- private formatWarnings;
23
- private header;
24
- private rosterLines;
25
- private why;
26
- private allDone;
27
- }
@@ -1,97 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ChecklistCommand = void 0;
4
- const tslib_1 = require("tslib");
5
- const rules_config_1 = require("@webpieces/rules-config");
6
- const inversify_1 = require("inversify");
7
- const checklist_notice_1 = require("../workflow/checklist-notice");
8
- const checklist_scanner_1 = require("../workflow/checklist-scanner");
9
- const SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
10
- /**
11
- * `wp-checklist` — ALWAYS SUCCEEDS, always just lists. It answers the one question the coding agent has at
12
- * review time: *which reviewer subagents do I owe on this branch, and what exactly do I tell them?*
13
- *
14
- * Safe to run at any time, including mid-work with a dirty tree — uncommitted and untracked files are part
15
- * of the diff it matches against (see {@link ChecklistScanner}). It exits 0 in every case: zero checklists
16
- * configured, zero matched, or several still outstanding. Reporting is not gating; `wp-finish-upsert-pr` is
17
- * the only command that refuses, and it re-uses this exact scan with `filterAlreadyReviewed: true`, so the
18
- * two can never disagree about what is owed.
19
- */
20
- let ChecklistCommand = class ChecklistCommand {
21
- repoRootFinder;
22
- checklistScanner;
23
- instructions;
24
- checklistNotice;
25
- constructor(repoRootFinder, checklistScanner, instructions, checklistNotice) {
26
- this.repoRootFinder = repoRootFinder;
27
- this.checklistScanner = checklistScanner;
28
- this.instructions = instructions;
29
- this.checklistNotice = checklistNotice;
30
- }
31
- run() {
32
- const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
33
- // Refresh the long-form doc so the block below can cite a file that is present and current.
34
- (0, rules_config_1.writeTemplate)(repoRoot, 'webpieces.review-checklists.md');
35
- process.stdout.write('\n' + SEP + '📋 Review checklists for this branch\n' + SEP + '\n');
36
- // filterAlreadyReviewed:false — LIST everything that applies, marking what is already done. Hiding the
37
- // done ones would make a second run look like it found fewer checklists than the first.
38
- const defined = (0, rules_config_1.loadAndValidate)(repoRoot).prGate.checklists;
39
- process.stdout.write(this.report(this.checklistScanner.scan(repoRoot, defined, new checklist_scanner_1.ChecklistScanOptions(false))));
40
- return Promise.resolve();
41
- }
42
- report(scan) {
43
- if (scan.applicable.length === 0) {
44
- // Zero is a SUPPORTED state, never a blocker (see ChecklistNotice).
45
- return this.checklistNotice.build(scan.defined.length, 'wp-finish-upsert-pr');
46
- }
47
- const reviewedIds = new Set(scan.reviewed.map((r) => r.id));
48
- const owed = scan.applicable.filter((r) => !reviewedIds.has(r.id));
49
- return (this.header(scan) +
50
- this.rosterLines(scan, owed) +
51
- this.formatWarnings(scan) +
52
- (owed.length === 0 ? this.allDone() : `\n${this.instructions.render(owed, scan.reviewPath, scan.context)}\n`));
53
- }
54
- // A verdict file that EXISTS but cannot be read as a verdict (almost always one still using the removed
55
- // `success` field) is called out here. Without it this command reports the checklist as simply owed, and
56
- // the AI re-runs a reviewer that already ran instead of correcting the file sitting right there.
57
- formatWarnings(scan) {
58
- if (scan.formatErrors.length === 0)
59
- return '';
60
- return '\n' + scan.formatErrors.map((e) => ` ⛔ ${e}\n`).join('');
61
- }
62
- // State the base out loud: a reader (and a reviewer) needs to know the diff was taken from the fork point
63
- // of main and that uncommitted work counted, because both change which checklists fired.
64
- header(scan) {
65
- const base = scan.forkPoint === '' ? 'no fork point resolved' : `fork point ${scan.forkPoint.slice(0, 8)}`;
66
- return `${scan.defined.length} checklist(s) configured, ${scan.applicable.length} apply to this branch\n` +
67
- `(${base}, including uncommitted + untracked work).\n\n`;
68
- }
69
- // REVIEW ONCE, PER SUBAGENT: a checklist with a passing verdict is reused, not re-run. 2 of 4 done means
70
- // the other 2 still need running, so both groups are shown and only the owed ones become instructions.
71
- rosterLines(scan, owed) {
72
- const lines = scan.reviewed.map((r) => ` ✓ ${r.subagent} — already reviewed on this branch (reusing its review-${r.id}.json)\n`);
73
- for (const r of owed)
74
- lines.push(` ▶ ${r.subagent} — ${this.why(r)}\n`);
75
- return lines.join('');
76
- }
77
- // Why this one is in scope. A patternless checklist is NOT "matched" — it always runs, over the whole diff.
78
- why(req) {
79
- if (req.matchedPatterns.length === 0)
80
- return 'ALWAYS RUNS (no patterns), whole diff in scope';
81
- const globs = req.matchedPatterns.map((p) => `"${p}"`).join(', ');
82
- return `${req.matchedFiles.length} file(s) matched ${globs}`;
83
- }
84
- allDone() {
85
- return '\n✅ Every checklist that applies is already reviewed — nothing to run.\n' +
86
- ' Write review.json and run: pnpm wp-finish-upsert-pr\n';
87
- }
88
- };
89
- exports.ChecklistCommand = ChecklistCommand;
90
- exports.ChecklistCommand = ChecklistCommand = tslib_1.__decorate([
91
- (0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
92
- tslib_1.__metadata("design:paramtypes", [rules_config_1.RepoRootFinder,
93
- checklist_scanner_1.ChecklistScanner,
94
- rules_config_1.ChecklistInstructionsService,
95
- checklist_notice_1.ChecklistNotice])
96
- ], ChecklistCommand);
97
- //# sourceMappingURL=checklist-command.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"checklist-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/checklist-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { loadAndValidate, writeTemplate, RepoRootFinder, ChecklistInstructionsService, RequiredChecklist } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { ChecklistNotice } from '../workflow/checklist-notice';\nimport { ChecklistScan, ChecklistScanOptions, ChecklistScanner } from '../workflow/checklist-scanner';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n/**\n * `wp-checklist` — ALWAYS SUCCEEDS, always just lists. It answers the one question the coding agent has at\n * review time: *which reviewer subagents do I owe on this branch, and what exactly do I tell them?*\n *\n * Safe to run at any time, including mid-work with a dirty tree — uncommitted and untracked files are part\n * of the diff it matches against (see {@link ChecklistScanner}). It exits 0 in every case: zero checklists\n * configured, zero matched, or several still outstanding. Reporting is not gating; `wp-finish-upsert-pr` is\n * the only command that refuses, and it re-uses this exact scan with `filterAlreadyReviewed: true`, so the\n * two can never disagree about what is owed.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ChecklistCommand {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly checklistScanner: ChecklistScanner,\n private readonly instructions: ChecklistInstructionsService,\n private readonly checklistNotice: ChecklistNotice,\n ) {}\n\n run(): Promise<void> {\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n // Refresh the long-form doc so the block below can cite a file that is present and current.\n writeTemplate(repoRoot, 'webpieces.review-checklists.md');\n process.stdout.write('\\n' + SEP + '📋 Review checklists for this branch\\n' + SEP + '\\n');\n // filterAlreadyReviewed:false — LIST everything that applies, marking what is already done. Hiding the\n // done ones would make a second run look like it found fewer checklists than the first.\n const defined = loadAndValidate(repoRoot).prGate.checklists;\n process.stdout.write(this.report(this.checklistScanner.scan(repoRoot, defined, new ChecklistScanOptions(false))));\n return Promise.resolve();\n }\n\n private report(scan: ChecklistScan): string {\n if (scan.applicable.length === 0) {\n // Zero is a SUPPORTED state, never a blocker (see ChecklistNotice).\n return this.checklistNotice.build(scan.defined.length, 'wp-finish-upsert-pr');\n }\n const reviewedIds = new Set(scan.reviewed.map((r: RequiredChecklist): string => r.id));\n const owed = scan.applicable.filter((r: RequiredChecklist): boolean => !reviewedIds.has(r.id));\n return (\n this.header(scan) +\n this.rosterLines(scan, owed) +\n this.formatWarnings(scan) +\n (owed.length === 0 ? this.allDone() : `\\n${this.instructions.render(owed, scan.reviewPath, scan.context)}\\n`)\n );\n }\n\n // A verdict file that EXISTS but cannot be read as a verdict (almost always one still using the removed\n // `success` field) is called out here. Without it this command reports the checklist as simply owed, and\n // the AI re-runs a reviewer that already ran instead of correcting the file sitting right there.\n private formatWarnings(scan: ChecklistScan): string {\n if (scan.formatErrors.length === 0) return '';\n return '\\n' + scan.formatErrors.map((e: string): string => ` ⛔ ${e}\\n`).join('');\n }\n\n // State the base out loud: a reader (and a reviewer) needs to know the diff was taken from the fork point\n // of main and that uncommitted work counted, because both change which checklists fired.\n private header(scan: ChecklistScan): string {\n const base = scan.forkPoint === '' ? 'no fork point resolved' : `fork point ${scan.forkPoint.slice(0, 8)}`;\n return `${scan.defined.length} checklist(s) configured, ${scan.applicable.length} apply to this branch\\n` +\n `(${base}, including uncommitted + untracked work).\\n\\n`;\n }\n\n // REVIEW ONCE, PER SUBAGENT: a checklist with a passing verdict is reused, not re-run. 2 of 4 done means\n // the other 2 still need running, so both groups are shown and only the owed ones become instructions.\n private rosterLines(scan: ChecklistScan, owed: readonly RequiredChecklist[]): string {\n const lines = scan.reviewed.map((r: RequiredChecklist): string =>\n ` ✓ ${r.subagent} — already reviewed on this branch (reusing its review-${r.id}.json)\\n`);\n for (const r of owed) lines.push(` ▶ ${r.subagent} — ${this.why(r)}\\n`);\n return lines.join('');\n }\n\n // Why this one is in scope. A patternless checklist is NOT \"matched\" — it always runs, over the whole diff.\n private why(req: RequiredChecklist): string {\n if (req.matchedPatterns.length === 0) return 'ALWAYS RUNS (no patterns), whole diff in scope';\n const globs = req.matchedPatterns.map((p: string): string => `\"${p}\"`).join(', ');\n return `${req.matchedFiles.length} file(s) matched ${globs}`;\n }\n\n private allDone(): string {\n return '\\n✅ Every checklist that applies is already reviewed — nothing to run.\\n' +\n ' Write review.json and run: pnpm wp-finish-upsert-pr\\n';\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"wp-checklist.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/wp-checklist.ts"],"names":[],"mappings":";;;AACA,4BAA0B;AAC1B,yCAAsC;AACtC,0DAAqE;AACrE,+CAA0C;AAE1C,mGAAmG;AACnG,IAAA,sBAAO,EAAC,KAAK,IAAmB,EAAE;IAC9B,kHAAkH;IAClH,MAAM,SAAS,GAAG,IAAI,qBAAS,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,sGAAsG;IACtG,SAAS,CAAC,GAAG,CAAC,sBAAO,CAAC,CAAC,YAAY,CAAC,IAAI,uBAAQ,CAAC,cAAc,EAAE,+EAA+E,CAAC,CAAC,CAAC;IACnJ,MAAM,SAAS,CAAC,GAAG,CAAC,uBAAS,CAAC,CAAC,SAAS,EAAE,CAAC;AAC/C,CAAC,CAAC,CAAC","sourcesContent":["#!/usr/bin/env node\nimport 'reflect-metadata';\nimport { Container } from 'inversify';\nimport { runMain, CliArgs, CliUsage } from '@webpieces/rules-config';\nimport { PrGateApp } from './pr-gate-app';\n\n// Composition root: build the container and resolve the app so inversify constructs the whole DAG.\nrunMain(async (): Promise<void> => {\n // autobind self-binds every @injectable(Singleton) tooling class (replaces the buildProviderModule registry scan)\n const container = new Container({ autobind: true });\n // Reject `--help`/bogus flags BEFORE the app touches git — an ignored flag must never start the flow.\n container.get(CliArgs).assertNoArgs(new CliUsage('wp-checklist', 'Print which reviewer subagents this diff owes, and exactly what to tell them.'));\n await container.get(PrGateApp).checklist();\n});\n"]}