@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.
- package/package.json +3 -3
- package/src/dashboard/dashboard.d.ts +15 -0
- package/src/dashboard/dashboard.js +25 -2
- package/src/dashboard/dashboard.js.map +1 -1
- package/src/scripts/commands/cleanup-command.d.ts +42 -3
- package/src/scripts/commands/cleanup-command.js +69 -11
- package/src/scripts/commands/cleanup-command.js.map +1 -1
- package/src/scripts/commands/finish-upsert-pr-command.d.ts +55 -5
- package/src/scripts/commands/finish-upsert-pr-command.js +162 -30
- package/src/scripts/commands/finish-upsert-pr-command.js.map +1 -1
- package/src/scripts/commands/land-pr-command.d.ts +17 -2
- package/src/scripts/commands/land-pr-command.js +32 -3
- package/src/scripts/commands/land-pr-command.js.map +1 -1
- package/src/scripts/commands/review-upsert-pr-command.d.ts +88 -0
- package/src/scripts/commands/review-upsert-pr-command.js +244 -0
- package/src/scripts/commands/review-upsert-pr-command.js.map +1 -0
- package/src/scripts/commands/start-upsert-pr-command.d.ts +10 -15
- package/src/scripts/commands/start-upsert-pr-command.js +41 -47
- package/src/scripts/commands/start-upsert-pr-command.js.map +1 -1
- package/src/scripts/commands/worktree-cleanup.d.ts +45 -0
- package/src/scripts/commands/worktree-cleanup.js +130 -0
- package/src/scripts/commands/worktree-cleanup.js.map +1 -0
- package/src/scripts/pr-gate-app.d.ts +9 -5
- package/src/scripts/pr-gate-app.js +12 -8
- package/src/scripts/pr-gate-app.js.map +1 -1
- package/src/scripts/workflow/checklist-scanner.d.ts +27 -11
- package/src/scripts/workflow/checklist-scanner.js +37 -15
- package/src/scripts/workflow/checklist-scanner.js.map +1 -1
- package/src/scripts/workflow/diff-basis.d.ts +53 -0
- package/src/scripts/workflow/diff-basis.js +113 -0
- package/src/scripts/workflow/diff-basis.js.map +1 -0
- package/src/scripts/workflow/diff-materializer.d.ts +85 -0
- package/src/scripts/workflow/diff-materializer.js +253 -0
- package/src/scripts/workflow/diff-materializer.js.map +1 -0
- package/src/scripts/workflow/git-findForkPoint.d.ts +1 -1
- package/src/scripts/workflow/git-findForkPoint.js +1 -1
- package/src/scripts/workflow/git-findForkPoint.js.map +1 -1
- package/src/scripts/workflow/pr-context-writer.d.ts +16 -10
- package/src/scripts/workflow/pr-context-writer.js +20 -14
- package/src/scripts/workflow/pr-context-writer.js.map +1 -1
- package/src/scripts/workflow/review-stage-receipt.d.ts +36 -0
- package/src/scripts/workflow/review-stage-receipt.js +82 -0
- package/src/scripts/workflow/review-stage-receipt.js.map +1 -0
- package/src/scripts/workflow/reviewer-briefing-builder.d.ts +36 -0
- package/src/scripts/workflow/reviewer-briefing-builder.js +104 -0
- package/src/scripts/workflow/reviewer-briefing-builder.js.map +1 -0
- package/src/scripts/{wp-checklist.js → wp-review-upsert-pr.js} +3 -3
- package/src/scripts/wp-review-upsert-pr.js.map +1 -0
- package/src/scripts/commands/checklist-command.d.ts +0 -27
- package/src/scripts/commands/checklist-command.js +0 -97
- package/src/scripts/commands/checklist-command.js.map +0 -1
- package/src/scripts/wp-checklist.js.map +0 -1
- /package/src/scripts/{wp-checklist.d.ts → wp-review-upsert-pr.d.ts} +0 -0
|
@@ -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-
|
|
7
|
-
* which meant `wp-
|
|
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
|
|
20
|
-
* reviewer is told to diff
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
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,
|
|
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-
|
|
12
|
-
* which meant `wp-
|
|
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
|
|
28
|
-
* reviewer is told to diff
|
|
29
|
-
*
|
|
30
|
-
*
|
|
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
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
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,
|
|
37
|
-
if (
|
|
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
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
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;
|
|
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":"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","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-
|
|
14
|
-
await container.get(pr_gate_app_1.PrGateApp).
|
|
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-
|
|
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"]}
|
|
File without changes
|