@webpieces/pr-gate 0.4.486 → 0.4.488
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 +2 -2
- package/src/scripts/commands/checklist-command.d.ts +14 -22
- package/src/scripts/commands/checklist-command.js +47 -54
- package/src/scripts/commands/checklist-command.js.map +1 -1
- package/src/scripts/commands/finish-upsert-pr-command.d.ts +7 -5
- package/src/scripts/commands/finish-upsert-pr-command.js +26 -29
- package/src/scripts/commands/finish-upsert-pr-command.js.map +1 -1
- package/src/scripts/commands/start-upsert-pr-command.d.ts +6 -5
- package/src/scripts/commands/start-upsert-pr-command.js +10 -22
- package/src/scripts/commands/start-upsert-pr-command.js.map +1 -1
- package/src/scripts/workflow/checklist-scanner.d.ts +72 -0
- package/src/scripts/workflow/checklist-scanner.js +125 -0
- package/src/scripts/workflow/checklist-scanner.js.map +1 -0
- package/src/scripts/workflow/git-findForkPoint.d.ts +15 -0
- package/src/scripts/workflow/git-findForkPoint.js +27 -3
- package/src/scripts/workflow/git-findForkPoint.js.map +1 -1
- package/src/scripts/workflow/pr-context-writer.d.ts +29 -0
- package/src/scripts/workflow/pr-context-writer.js +52 -0
- package/src/scripts/workflow/pr-context-writer.js.map +1 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { RepoRootFinder
|
|
1
|
+
import { RepoRootFinder } from '@webpieces/rules-config';
|
|
2
2
|
import { AiBranchName } from '../workflow/git-readAiBranchName';
|
|
3
3
|
import { BranchNaming } from '../workflow/branch-naming';
|
|
4
4
|
import { GitExec } from '../workflow/git-exec';
|
|
5
|
+
import { ForkPoint } from '../workflow/git-findForkPoint';
|
|
6
|
+
import { PrContextWriter } from '../workflow/pr-context-writer';
|
|
5
7
|
import { RunUpdate } from '../workflow/run-update';
|
|
6
8
|
export declare class StartUpsertPrCommand {
|
|
7
9
|
private readonly repoRootFinder;
|
|
@@ -9,9 +11,9 @@ export declare class StartUpsertPrCommand {
|
|
|
9
11
|
private readonly branchNaming;
|
|
10
12
|
private readonly gitExec;
|
|
11
13
|
private readonly runUpdate;
|
|
12
|
-
private readonly
|
|
13
|
-
private readonly
|
|
14
|
-
constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, branchNaming: BranchNaming, gitExec: GitExec, runUpdate: RunUpdate,
|
|
14
|
+
private readonly forkPoint;
|
|
15
|
+
private readonly prContextWriter;
|
|
16
|
+
constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, branchNaming: BranchNaming, gitExec: GitExec, runUpdate: RunUpdate, forkPoint: ForkPoint, prContextWriter: PrContextWriter);
|
|
15
17
|
run(): Promise<void>;
|
|
16
18
|
/**
|
|
17
19
|
* Hand the AI its next step: run `wp-checklist`, write review.json, then run finish (which posts the PR).
|
|
@@ -29,7 +31,6 @@ export declare class StartUpsertPrCommand {
|
|
|
29
31
|
* forever at a repo that will never have one is noise, and silence reads as "a checklist passed".
|
|
30
32
|
*/
|
|
31
33
|
private checklistPointer;
|
|
32
|
-
private writePrContext;
|
|
33
34
|
private contextLines;
|
|
34
35
|
private scaffoldCiWorkflow;
|
|
35
36
|
private updateBranchFromMain;
|
|
@@ -7,6 +7,8 @@ const inversify_1 = require("inversify");
|
|
|
7
7
|
const git_readAiBranchName_1 = require("../workflow/git-readAiBranchName");
|
|
8
8
|
const branch_naming_1 = require("../workflow/branch-naming");
|
|
9
9
|
const git_exec_1 = require("../workflow/git-exec");
|
|
10
|
+
const git_findForkPoint_1 = require("../workflow/git-findForkPoint");
|
|
11
|
+
const pr_context_writer_1 = require("../workflow/pr-context-writer");
|
|
10
12
|
const run_update_1 = require("../workflow/run-update");
|
|
11
13
|
const SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
|
|
12
14
|
// START of the AI-first PR flow: the deterministic setup — update from main — then hand the AI
|
|
@@ -21,16 +23,16 @@ let StartUpsertPrCommand = class StartUpsertPrCommand {
|
|
|
21
23
|
branchNaming;
|
|
22
24
|
gitExec;
|
|
23
25
|
runUpdate;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
constructor(repoRootFinder, aiBranchName, branchNaming, gitExec, runUpdate,
|
|
26
|
+
forkPoint;
|
|
27
|
+
prContextWriter;
|
|
28
|
+
constructor(repoRootFinder, aiBranchName, branchNaming, gitExec, runUpdate, forkPoint, prContextWriter) {
|
|
27
29
|
this.repoRootFinder = repoRootFinder;
|
|
28
30
|
this.aiBranchName = aiBranchName;
|
|
29
31
|
this.branchNaming = branchNaming;
|
|
30
32
|
this.gitExec = gitExec;
|
|
31
33
|
this.runUpdate = runUpdate;
|
|
32
|
-
this.
|
|
33
|
-
this.
|
|
34
|
+
this.forkPoint = forkPoint;
|
|
35
|
+
this.prContextWriter = prContextWriter;
|
|
34
36
|
}
|
|
35
37
|
async run() {
|
|
36
38
|
const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
|
|
@@ -68,7 +70,7 @@ let StartUpsertPrCommand = class StartUpsertPrCommand {
|
|
|
68
70
|
(0, rules_config_1.writeTemplate)(repoRoot, 'webpieces.review-checklists.md');
|
|
69
71
|
// Persist the PR diff context (base sha + the full changed-file set) — wp-checklist and every reviewer
|
|
70
72
|
// subagent read it, so it must exist before either runs.
|
|
71
|
-
const context = this.
|
|
73
|
+
const context = this.prContextWriter.ensure(repoRoot, this.aiBranchName.getFeatureName(), this.forkPoint.resolveForkPoint(repoRoot));
|
|
72
74
|
process.stdout.write('\n' + SEP + '② Review the PR, then finish\n' + SEP + '\n');
|
|
73
75
|
process.stdout.write(`Branch is updated (nothing pushed yet — finish does the one push, behind the build gate).\n` +
|
|
74
76
|
`${this.contextLines(context)}\n` +
|
|
@@ -92,20 +94,6 @@ let StartUpsertPrCommand = class StartUpsertPrCommand {
|
|
|
92
94
|
' subagents you must spawn, plus the exact review-<id>.json each must write. Do that BEFORE\n' +
|
|
93
95
|
' finishing — wp-finish-upsert-pr refuses to open the PR until every one of them has run.\n');
|
|
94
96
|
}
|
|
95
|
-
// Write pr-context.json (base/head sha + the full changed-file set, tsOnly:false) so a reviewer
|
|
96
|
-
// subagent knows the exact base the gate uses and can `git diff <base> HEAD -- <file>` for content.
|
|
97
|
-
// Returns what every printed instruction needs to inline; an empty context when no base resolves.
|
|
98
|
-
writePrContext(repoRoot) {
|
|
99
|
-
const range = this.diffScope.resolveBase(repoRoot);
|
|
100
|
-
if (!range.base)
|
|
101
|
-
return new rules_config_1.ChecklistReviewContext();
|
|
102
|
-
const opts = new rules_config_1.ChangedFilesOptions();
|
|
103
|
-
opts.tsOnly = false;
|
|
104
|
-
const changed = this.diffScope.getChangedFiles(repoRoot, range.base, range.head, opts);
|
|
105
|
-
const head = range.head && range.head.trim() !== '' ? range.head : 'HEAD';
|
|
106
|
-
const p = this.reviewJsonService.writePrContext(repoRoot, this.aiBranchName.getFeatureName(), new rules_config_1.PrContext(range.base, head, changed));
|
|
107
|
-
return new rules_config_1.ChecklistReviewContext(range.base, p);
|
|
108
|
-
}
|
|
109
97
|
// The diff facts, stated where the instruction to use them is — not in a separate block above it.
|
|
110
98
|
contextLines(context) {
|
|
111
99
|
if (context.baseSha.trim() === '')
|
|
@@ -143,7 +131,7 @@ exports.StartUpsertPrCommand = StartUpsertPrCommand = tslib_1.__decorate([
|
|
|
143
131
|
branch_naming_1.BranchNaming,
|
|
144
132
|
git_exec_1.GitExec,
|
|
145
133
|
run_update_1.RunUpdate,
|
|
146
|
-
|
|
147
|
-
|
|
134
|
+
git_findForkPoint_1.ForkPoint,
|
|
135
|
+
pr_context_writer_1.PrContextWriter])
|
|
148
136
|
], StartUpsertPrCommand);
|
|
149
137
|
//# sourceMappingURL=start-upsert-pr-command.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start-upsert-pr-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/start-upsert-pr-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from 'child_process';\nimport { loadAndValidate, reviewJsonPath, reviewJsonSchemaHint, writeTemplate, writeTemplateIfMissing, CliExitError, RepoRootFinder, ReviewJsonService, DiffScope, ChangedFilesOptions, PrContext, ChecklistReviewContext } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BranchNaming } from '../workflow/branch-naming';\nimport { GitExec } from '../workflow/git-exec';\nimport { RunUpdate } from '../workflow/run-update';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// START of the AI-first PR flow: the deterministic setup — update from main — then hand the AI\n// instructions to run `wp-checklist`, WRITE review.json, and run `wp-finish-upsert-pr` (which reads it\n// and posts the PR). It runs NO build gate: start's job is the update / 3-point merge, and the SINGLE\n// build gate is authoritative and lives in wp-finish-upsert-pr (so `pr-gate.buildCommand` is a\n// finish-only knob). This command NEVER creates/updates a PR and NEVER pushes: all `gh` posting and the\n// ONE push live in finish, behind review.json + the checklists + the authoritative build gate.\n@injectable(bindingScopeValues.Singleton)\nexport class StartUpsertPrCommand {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly aiBranchName: AiBranchName,\n private readonly branchNaming: BranchNaming,\n private readonly gitExec: GitExec,\n private readonly runUpdate: RunUpdate,\n private readonly reviewJsonService: ReviewJsonService,\n private readonly diffScope: DiffScope,\n ) {}\n\n async run(): Promise<void> {\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n // Refresh the AI-facing workflow doc so it's present + current for any failure message to cite.\n writeTemplate(repoRoot, 'webpieces.git-workflow.md');\n // When this repo has opted into server-side enforcement (a committed gateSalt), scaffold the CI\n // workflow into the gitignored instruct-ai dir (never .github directly — that would dirty the tree\n // before the clean-tree check) and tell the human to copy + require it. IfMissing so it is written\n // once and never clobbers a customized copy.\n this.scaffoldCiWorkflow(repoRoot);\n\n // Precondition: a fully-committed tree. This flow squash-updates the branch and builds it — the\n // tooling must not commit your work for you, and building a dirty working tree would let an\n // uncommitted change build green over a different commit than the one that ships. Fail if dirty.\n this.gitExec.assertCleanTree(repoRoot);\n\n // Nothing here pushes. This command reviews; wp-finish-upsert-pr pushes ONCE, after review.json,\n // every BLOCK checklist, and the authoritative build gate — so no unreviewed commit reaches the\n // remote, and there is no early `synchronize` firing against a PR body with a stale gate token.\n await this.updateBranchFromMain(repoRoot);\n\n // No build gate here — start only updates from main / drives the 3-point merge. The one build\n // gate is authoritative and runs in wp-finish-upsert-pr, after review.json + every checklist.\n this.handOffToReview(repoRoot);\n }\n\n /**\n * Hand the AI its next step: run `wp-checklist`, write review.json, then run finish (which posts the PR).\n *\n * This USED to compute the matched checklists and print every reviewer's full instructions inline — twice,\n * in two blocks that could drift. That detail now lives in ONE command, `wp-checklist`, which wp-finish\n * validates against; here we only point at it. Keeping this section short is the point: it is the block\n * the AI must actually act on, and burying the three steps in forty lines of reviewer detail is how a\n * step gets skipped.\n */\n private handOffToReview(repoRoot: string): void {\n const reviewPath = reviewJsonPath(repoRoot, this.aiBranchName.getFeatureName());\n // Persist the review-format + process instructions where any failure message can cite them.\n writeTemplate(repoRoot, 'webpieces.review-checklists.md');\n // Persist the PR diff context (base sha + the full changed-file set) — wp-checklist and every reviewer\n // subagent read it, so it must exist before either runs.\n const context = this.writePrContext(repoRoot);\n process.stdout.write('\\n' + SEP + '② Review the PR, then finish\\n' + SEP + '\\n');\n process.stdout.write(\n `Branch is updated (nothing pushed yet — finish does the one push, behind the build gate).\\n` +\n `${this.contextLines(context)}\\n` +\n `${this.checklistPointer(repoRoot)}\\n` +\n `Then review your own changes and\\n` +\n `${reviewJsonSchemaHint(reviewPath)}\\n\\n` +\n `Finally run: pnpm wp-finish-upsert-pr\\n` +\n `(It re-validates the build, re-checks every checklist, renders the dashboard, and creates/updates the PR.)\\n\\n`,\n );\n }\n\n /**\n * Step 1 of the review: find out what review this diff owes. A repo with checklists gets pointed at\n * `wp-checklist`; a repo with none is told plainly that it has none, because printing \"run wp-checklist\"\n * forever at a repo that will never have one is noise, and silence reads as \"a checklist passed\".\n */\n private checklistPointer(repoRoot: string): string {\n if (loadAndValidate(repoRoot).prGate.checklists.length === 0) {\n return '📋 Review checklists: NONE CONFIGURED for this repo — nothing to run, and that is fine.\\n';\n }\n return (\n '📋 FIRST run: pnpm wp-checklist\\n' +\n ' It validates this repo\\'s checklist patterns against your diff and prints WHICH reviewer\\n' +\n ' subagents you must spawn, plus the exact review-<id>.json each must write. Do that BEFORE\\n' +\n ' finishing — wp-finish-upsert-pr refuses to open the PR until every one of them has run.\\n'\n );\n }\n\n // Write pr-context.json (base/head sha + the full changed-file set, tsOnly:false) so a reviewer\n // subagent knows the exact base the gate uses and can `git diff <base> HEAD -- <file>` for content.\n // Returns what every printed instruction needs to inline; an empty context when no base resolves.\n private writePrContext(repoRoot: string): ChecklistReviewContext {\n const range = this.diffScope.resolveBase(repoRoot);\n if (!range.base) return new ChecklistReviewContext();\n const opts = new ChangedFilesOptions();\n opts.tsOnly = false;\n const changed = this.diffScope.getChangedFiles(repoRoot, range.base, range.head, opts);\n const head = range.head && range.head.trim() !== '' ? range.head : 'HEAD';\n const p = this.reviewJsonService.writePrContext(\n repoRoot, this.aiBranchName.getFeatureName(), new PrContext(range.base, head, changed),\n );\n return new ChecklistReviewContext(range.base, p);\n }\n\n // The diff facts, stated where the instruction to use them is — not in a separate block above it.\n private contextLines(context: ChecklistReviewContext): string {\n if (context.baseSha.trim() === '') return '';\n return (\n `Your diff: git diff ${context.baseSha} HEAD\\n` +\n `Full changed-file set + base/head sha: ${context.prContextPath}\\n`\n );\n }\n\n // Scaffold the server-side CI check when (and only when) this repo set a gateSalt. Written to the\n // gitignored instruct-ai dir so it never dirties the tree; the human copies it to .github/workflows\n // and marks it required (webpieces can't set branch protection). No-op for repos with no gateSalt.\n private scaffoldCiWorkflow(repoRoot: string): void {\n if (loadAndValidate(repoRoot).prGate.gateSalt.trim() === '') return;\n writeTemplateIfMissing(repoRoot, 'webpieces-pr-gate.yml');\n process.stdout.write(\n `\\nℹ️ Server-side gate enforcement is ON (gateSalt set). If you have not already:\\n` +\n ` • copy .webpieces/instruct-ai/webpieces-pr-gate.yml → .github/workflows/ and commit it\\n` +\n ` • mark the \"webpieces-pr-gate\" check REQUIRED in branch protection (repo admin only)\\n`,\n );\n }\n\n // Bring the branch up to date with main via the shared 3-point engine (in-process). On conflict the\n // merge process doc it writes names `wp-finish-upsert-pr` as the finish command.\n private async updateBranchFromMain(repoRoot: string): Promise<void> {\n process.stdout.write('\\n' + SEP + '① Updating branch from main\\n' + SEP + '\\n');\n // pushRemote=false — finish owns the single push (see MergeEndOptions).\n const outcome = await this.runUpdate.runUpdateFromMain(repoRoot, 'wp-start-upsert-pr', 'wp-finish-upsert-pr', false);\n if (outcome === 'conflict' || outcome === 'unvalidatedResume') {\n throw new CliExitError(2,\n '\\n⏸️ Conflicts — resolve them, then run pnpm wp-finish-upsert-pr (it validates the merge AND finishes the PR).',\n );\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"start-upsert-pr-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/start-upsert-pr-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from 'child_process';\nimport { loadAndValidate, reviewJsonPath, reviewJsonSchemaHint, writeTemplate, writeTemplateIfMissing, CliExitError, RepoRootFinder, ChecklistReviewContext } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BranchNaming } from '../workflow/branch-naming';\nimport { GitExec } from '../workflow/git-exec';\nimport { ForkPoint } from '../workflow/git-findForkPoint';\nimport { PrContextWriter } from '../workflow/pr-context-writer';\nimport { RunUpdate } from '../workflow/run-update';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// START of the AI-first PR flow: the deterministic setup — update from main — then hand the AI\n// instructions to run `wp-checklist`, WRITE review.json, and run `wp-finish-upsert-pr` (which reads it\n// and posts the PR). It runs NO build gate: start's job is the update / 3-point merge, and the SINGLE\n// build gate is authoritative and lives in wp-finish-upsert-pr (so `pr-gate.buildCommand` is a\n// finish-only knob). This command NEVER creates/updates a PR and NEVER pushes: all `gh` posting and the\n// ONE push live in finish, behind review.json + the checklists + the authoritative build gate.\n@injectable(bindingScopeValues.Singleton)\nexport class StartUpsertPrCommand {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly aiBranchName: AiBranchName,\n private readonly branchNaming: BranchNaming,\n private readonly gitExec: GitExec,\n private readonly runUpdate: RunUpdate,\n private readonly forkPoint: ForkPoint,\n private readonly prContextWriter: PrContextWriter,\n ) {}\n\n async run(): Promise<void> {\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n // Refresh the AI-facing workflow doc so it's present + current for any failure message to cite.\n writeTemplate(repoRoot, 'webpieces.git-workflow.md');\n // When this repo has opted into server-side enforcement (a committed gateSalt), scaffold the CI\n // workflow into the gitignored instruct-ai dir (never .github directly — that would dirty the tree\n // before the clean-tree check) and tell the human to copy + require it. IfMissing so it is written\n // once and never clobbers a customized copy.\n this.scaffoldCiWorkflow(repoRoot);\n\n // Precondition: a fully-committed tree. This flow squash-updates the branch and builds it — the\n // tooling must not commit your work for you, and building a dirty working tree would let an\n // uncommitted change build green over a different commit than the one that ships. Fail if dirty.\n this.gitExec.assertCleanTree(repoRoot);\n\n // Nothing here pushes. This command reviews; wp-finish-upsert-pr pushes ONCE, after review.json,\n // every BLOCK checklist, and the authoritative build gate — so no unreviewed commit reaches the\n // remote, and there is no early `synchronize` firing against a PR body with a stale gate token.\n await this.updateBranchFromMain(repoRoot);\n\n // No build gate here — start only updates from main / drives the 3-point merge. The one build\n // gate is authoritative and runs in wp-finish-upsert-pr, after review.json + every checklist.\n this.handOffToReview(repoRoot);\n }\n\n /**\n * Hand the AI its next step: run `wp-checklist`, write review.json, then run finish (which posts the PR).\n *\n * This USED to compute the matched checklists and print every reviewer's full instructions inline — twice,\n * in two blocks that could drift. That detail now lives in ONE command, `wp-checklist`, which wp-finish\n * validates against; here we only point at it. Keeping this section short is the point: it is the block\n * the AI must actually act on, and burying the three steps in forty lines of reviewer detail is how a\n * step gets skipped.\n */\n private handOffToReview(repoRoot: string): void {\n const reviewPath = reviewJsonPath(repoRoot, this.aiBranchName.getFeatureName());\n // Persist the review-format + process instructions where any failure message can cite them.\n writeTemplate(repoRoot, 'webpieces.review-checklists.md');\n // Persist the PR diff context (base sha + the full changed-file set) — wp-checklist and every reviewer\n // subagent read it, so it must exist before either runs.\n const context = this.prContextWriter.ensure(repoRoot, this.aiBranchName.getFeatureName(), this.forkPoint.resolveForkPoint(repoRoot));\n process.stdout.write('\\n' + SEP + '② Review the PR, then finish\\n' + SEP + '\\n');\n process.stdout.write(\n `Branch is updated (nothing pushed yet — finish does the one push, behind the build gate).\\n` +\n `${this.contextLines(context)}\\n` +\n `${this.checklistPointer(repoRoot)}\\n` +\n `Then review your own changes and\\n` +\n `${reviewJsonSchemaHint(reviewPath)}\\n\\n` +\n `Finally run: pnpm wp-finish-upsert-pr\\n` +\n `(It re-validates the build, re-checks every checklist, renders the dashboard, and creates/updates the PR.)\\n\\n`,\n );\n }\n\n /**\n * Step 1 of the review: find out what review this diff owes. A repo with checklists gets pointed at\n * `wp-checklist`; a repo with none is told plainly that it has none, because printing \"run wp-checklist\"\n * forever at a repo that will never have one is noise, and silence reads as \"a checklist passed\".\n */\n private checklistPointer(repoRoot: string): string {\n if (loadAndValidate(repoRoot).prGate.checklists.length === 0) {\n return '📋 Review checklists: NONE CONFIGURED for this repo — nothing to run, and that is fine.\\n';\n }\n return (\n '📋 FIRST run: pnpm wp-checklist\\n' +\n ' It validates this repo\\'s checklist patterns against your diff and prints WHICH reviewer\\n' +\n ' subagents you must spawn, plus the exact review-<id>.json each must write. Do that BEFORE\\n' +\n ' finishing — wp-finish-upsert-pr refuses to open the PR until every one of them has run.\\n'\n );\n }\n\n // The diff facts, stated where the instruction to use them is — not in a separate block above it.\n private contextLines(context: ChecklistReviewContext): string {\n if (context.baseSha.trim() === '') return '';\n return (\n `Your diff: git diff ${context.baseSha} HEAD\\n` +\n `Full changed-file set + base/head sha: ${context.prContextPath}\\n`\n );\n }\n\n // Scaffold the server-side CI check when (and only when) this repo set a gateSalt. Written to the\n // gitignored instruct-ai dir so it never dirties the tree; the human copies it to .github/workflows\n // and marks it required (webpieces can't set branch protection). No-op for repos with no gateSalt.\n private scaffoldCiWorkflow(repoRoot: string): void {\n if (loadAndValidate(repoRoot).prGate.gateSalt.trim() === '') return;\n writeTemplateIfMissing(repoRoot, 'webpieces-pr-gate.yml');\n process.stdout.write(\n `\\nℹ️ Server-side gate enforcement is ON (gateSalt set). If you have not already:\\n` +\n ` • copy .webpieces/instruct-ai/webpieces-pr-gate.yml → .github/workflows/ and commit it\\n` +\n ` • mark the \"webpieces-pr-gate\" check REQUIRED in branch protection (repo admin only)\\n`,\n );\n }\n\n // Bring the branch up to date with main via the shared 3-point engine (in-process). On conflict the\n // merge process doc it writes names `wp-finish-upsert-pr` as the finish command.\n private async updateBranchFromMain(repoRoot: string): Promise<void> {\n process.stdout.write('\\n' + SEP + '① Updating branch from main\\n' + SEP + '\\n');\n // pushRemote=false — finish owns the single push (see MergeEndOptions).\n const outcome = await this.runUpdate.runUpdateFromMain(repoRoot, 'wp-start-upsert-pr', 'wp-finish-upsert-pr', false);\n if (outcome === 'conflict' || outcome === 'unvalidatedResume') {\n throw new CliExitError(2,\n '\\n⏸️ Conflicts — resolve them, then run pnpm wp-finish-upsert-pr (it validates the merge AND finishes the PR).',\n );\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ChecklistDefinition, ChecklistReviewContext, DiffScope, RequiredChecklist, ReviewJsonService } from '@webpieces/rules-config';
|
|
2
|
+
import { AiBranchName } from './git-readAiBranchName';
|
|
3
|
+
import { ChecklistDetector } from './checklist-detector';
|
|
4
|
+
import { ForkPoint } from './git-findForkPoint';
|
|
5
|
+
import { PrContextWriter } from './pr-context-writer';
|
|
6
|
+
/** How a caller wants the scan filtered. Data-only (per CLAUDE.md). */
|
|
7
|
+
export declare class ChecklistScanOptions {
|
|
8
|
+
/**
|
|
9
|
+
* false — `outstanding` is every applicable checklist (what `wp-checklist` LISTS).
|
|
10
|
+
* true — `outstanding` drops the ones already carrying a passing/overridden verdict, leaving only what
|
|
11
|
+
* still owes review (what `wp-finish-upsert-pr` BLOCKS on).
|
|
12
|
+
*/
|
|
13
|
+
filterAlreadyReviewed: boolean;
|
|
14
|
+
constructor(filterAlreadyReviewed?: boolean);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The answer to "what review does this branch owe?", in the X → N → Z terms the commands report:
|
|
18
|
+
* X = `defined` — every checklist in pr-gate.checklists
|
|
19
|
+
* N = `applicable` — those whose patterns matched (or that have no patterns, so always run)
|
|
20
|
+
* Z = `outstanding` — of N, those still owing a verdict (only when filterAlreadyReviewed)
|
|
21
|
+
* Data-only.
|
|
22
|
+
*/
|
|
23
|
+
export declare class ChecklistScan {
|
|
24
|
+
defined: ChecklistDefinition[];
|
|
25
|
+
applicable: RequiredChecklist[];
|
|
26
|
+
reviewed: RequiredChecklist[];
|
|
27
|
+
outstanding: RequiredChecklist[];
|
|
28
|
+
context: ChecklistReviewContext;
|
|
29
|
+
reviewPath: string;
|
|
30
|
+
forkPoint: string;
|
|
31
|
+
constructor(defined: ChecklistDefinition[], applicable: RequiredChecklist[], reviewed: RequiredChecklist[], outstanding: RequiredChecklist[], context: ChecklistReviewContext, reviewPath: string, forkPoint: string);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The ONE computation of which reviewer subagents a branch owes, shared by `wp-checklist` (which lists) and
|
|
35
|
+
* `wp-finish-upsert-pr` (which blocks). They previously each assembled this from the same parts in slightly
|
|
36
|
+
* different ways, and any divergence means the command that reports and the command that gates disagree.
|
|
37
|
+
*
|
|
38
|
+
* Two properties are deliberate and load-bearing:
|
|
39
|
+
*
|
|
40
|
+
* 1. **The base is the FORK POINT of main, computed directly** — never `DiffScope.resolveBase`, which
|
|
41
|
+
* overlays `NX_BASE`/`NX_HEAD` from the environment. That made review coverage depend on an env var.
|
|
42
|
+
* 2. **UNCOMMITTED work counts.** `getChangedFiles` is called with NO head, which is the branch of it that
|
|
43
|
+
* diffs base → WORKING TREE and unions in untracked files. Passing a head would diff commit-to-commit and
|
|
44
|
+
* silently miss staged, unstaged and untracked changes — so a checklist matching only uncommitted files
|
|
45
|
+
* would never fire and its reviewer would never be listed. `wp-checklist` is explicitly callable
|
|
46
|
+
* mid-work, which is exactly when that matters.
|
|
47
|
+
*
|
|
48
|
+
* `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.
|
|
49
|
+
*/
|
|
50
|
+
export declare class ChecklistScanner {
|
|
51
|
+
private readonly aiBranchName;
|
|
52
|
+
private readonly checklistDetector;
|
|
53
|
+
private readonly diffScope;
|
|
54
|
+
private readonly forkPoint;
|
|
55
|
+
private readonly prContextWriter;
|
|
56
|
+
private readonly reviewJsonService;
|
|
57
|
+
constructor(aiBranchName: AiBranchName, checklistDetector: ChecklistDetector, diffScope: DiffScope, forkPoint: ForkPoint, prContextWriter: PrContextWriter, reviewJsonService: ReviewJsonService);
|
|
58
|
+
/**
|
|
59
|
+
* `defined` is the caller's ALREADY-VALIDATED `prGate.checklists`. The scanner deliberately does not load
|
|
60
|
+
* config itself: `loadAndValidate` is the one gate on the checklist set (it rejects a non-array
|
|
61
|
+
* `checklists`, including the removed `{ doc }` manifest shape, and verifies every guidance doc and
|
|
62
|
+
* reviewer-agent file exists), both callers already run it for other fields, and keeping the read out of
|
|
63
|
+
* here leaves this a function of its inputs rather than of the filesystem.
|
|
64
|
+
*/
|
|
65
|
+
scan(repoRoot: string, defined: ChecklistDefinition[], opts: ChecklistScanOptions): ChecklistScan;
|
|
66
|
+
/**
|
|
67
|
+
* Every file changed since the fork point, INCLUDING uncommitted and untracked ones. `tsOnly:false` is
|
|
68
|
+
* load-bearing too: the default drops every *.sql / Dockerfile / .env* file a checklist most wants to
|
|
69
|
+
* key on, which would silently shrink the set a reviewer is pointed at.
|
|
70
|
+
*/
|
|
71
|
+
private changedFiles;
|
|
72
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ChecklistScanner = exports.ChecklistScan = exports.ChecklistScanOptions = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const rules_config_1 = require("@webpieces/rules-config");
|
|
6
|
+
const inversify_1 = require("inversify");
|
|
7
|
+
const git_readAiBranchName_1 = require("./git-readAiBranchName");
|
|
8
|
+
const checklist_detector_1 = require("./checklist-detector");
|
|
9
|
+
const git_findForkPoint_1 = require("./git-findForkPoint");
|
|
10
|
+
const pr_context_writer_1 = require("./pr-context-writer");
|
|
11
|
+
/** How a caller wants the scan filtered. Data-only (per CLAUDE.md). */
|
|
12
|
+
class ChecklistScanOptions {
|
|
13
|
+
/**
|
|
14
|
+
* false — `outstanding` is every applicable checklist (what `wp-checklist` LISTS).
|
|
15
|
+
* true — `outstanding` drops the ones already carrying a passing/overridden verdict, leaving only what
|
|
16
|
+
* still owes review (what `wp-finish-upsert-pr` BLOCKS on).
|
|
17
|
+
*/
|
|
18
|
+
filterAlreadyReviewed;
|
|
19
|
+
constructor(filterAlreadyReviewed = false) {
|
|
20
|
+
this.filterAlreadyReviewed = filterAlreadyReviewed;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.ChecklistScanOptions = ChecklistScanOptions;
|
|
24
|
+
/**
|
|
25
|
+
* The answer to "what review does this branch owe?", in the X → N → Z terms the commands report:
|
|
26
|
+
* X = `defined` — every checklist in pr-gate.checklists
|
|
27
|
+
* N = `applicable` — those whose patterns matched (or that have no patterns, so always run)
|
|
28
|
+
* Z = `outstanding` — of N, those still owing a verdict (only when filterAlreadyReviewed)
|
|
29
|
+
* Data-only.
|
|
30
|
+
*/
|
|
31
|
+
class ChecklistScan {
|
|
32
|
+
defined; // X
|
|
33
|
+
applicable; // N
|
|
34
|
+
reviewed; // N − Z: already have a passing/overridden review-<id>.json
|
|
35
|
+
outstanding; // Z (== applicable when not filtering)
|
|
36
|
+
context; // fork-point sha + pr-context.json path
|
|
37
|
+
reviewPath; // the branch's review.json; verdict files sit beside it
|
|
38
|
+
forkPoint; // '' when no fork point resolved
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/max-params
|
|
40
|
+
constructor(defined, applicable, reviewed, outstanding, context, reviewPath, forkPoint) {
|
|
41
|
+
this.defined = defined;
|
|
42
|
+
this.applicable = applicable;
|
|
43
|
+
this.reviewed = reviewed;
|
|
44
|
+
this.outstanding = outstanding;
|
|
45
|
+
this.context = context;
|
|
46
|
+
this.reviewPath = reviewPath;
|
|
47
|
+
this.forkPoint = forkPoint;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.ChecklistScan = ChecklistScan;
|
|
51
|
+
/**
|
|
52
|
+
* The ONE computation of which reviewer subagents a branch owes, shared by `wp-checklist` (which lists) and
|
|
53
|
+
* `wp-finish-upsert-pr` (which blocks). They previously each assembled this from the same parts in slightly
|
|
54
|
+
* different ways, and any divergence means the command that reports and the command that gates disagree.
|
|
55
|
+
*
|
|
56
|
+
* Two properties are deliberate and load-bearing:
|
|
57
|
+
*
|
|
58
|
+
* 1. **The base is the FORK POINT of main, computed directly** — never `DiffScope.resolveBase`, which
|
|
59
|
+
* overlays `NX_BASE`/`NX_HEAD` from the environment. That made review coverage depend on an env var.
|
|
60
|
+
* 2. **UNCOMMITTED work counts.** `getChangedFiles` is called with NO head, which is the branch of it that
|
|
61
|
+
* diffs base → WORKING TREE and unions in untracked files. Passing a head would diff commit-to-commit and
|
|
62
|
+
* silently miss staged, unstaged and untracked changes — so a checklist matching only uncommitted files
|
|
63
|
+
* would never fire and its reviewer would never be listed. `wp-checklist` is explicitly callable
|
|
64
|
+
* mid-work, which is exactly when that matters.
|
|
65
|
+
*
|
|
66
|
+
* `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.
|
|
67
|
+
*/
|
|
68
|
+
let ChecklistScanner = class ChecklistScanner {
|
|
69
|
+
aiBranchName;
|
|
70
|
+
checklistDetector;
|
|
71
|
+
diffScope;
|
|
72
|
+
forkPoint;
|
|
73
|
+
prContextWriter;
|
|
74
|
+
reviewJsonService;
|
|
75
|
+
constructor(aiBranchName, checklistDetector, diffScope, forkPoint, prContextWriter, reviewJsonService) {
|
|
76
|
+
this.aiBranchName = aiBranchName;
|
|
77
|
+
this.checklistDetector = checklistDetector;
|
|
78
|
+
this.diffScope = diffScope;
|
|
79
|
+
this.forkPoint = forkPoint;
|
|
80
|
+
this.prContextWriter = prContextWriter;
|
|
81
|
+
this.reviewJsonService = reviewJsonService;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* `defined` is the caller's ALREADY-VALIDATED `prGate.checklists`. The scanner deliberately does not load
|
|
85
|
+
* config itself: `loadAndValidate` is the one gate on the checklist set (it rejects a non-array
|
|
86
|
+
* `checklists`, including the removed `{ doc }` manifest shape, and verifies every guidance doc and
|
|
87
|
+
* reviewer-agent file exists), both callers already run it for other fields, and keeping the read out of
|
|
88
|
+
* here leaves this a function of its inputs rather than of the filesystem.
|
|
89
|
+
*/
|
|
90
|
+
scan(repoRoot, defined, opts) {
|
|
91
|
+
const featureName = this.aiBranchName.getFeatureName();
|
|
92
|
+
const reviewPath = (0, rules_config_1.reviewJsonPath)(repoRoot, featureName);
|
|
93
|
+
const base = this.forkPoint.resolveForkPoint(repoRoot);
|
|
94
|
+
const applicable = this.checklistDetector.toRequired(this.checklistDetector.detect(defined, this.changedFiles(repoRoot, base)));
|
|
95
|
+
const results = this.reviewJsonService.loadChecklistResults(reviewPath, applicable);
|
|
96
|
+
const stillOwed = this.reviewJsonService.pendingChecklists(applicable, results);
|
|
97
|
+
const owedIds = new Set(stillOwed.map((r) => r.id));
|
|
98
|
+
const reviewed = applicable.filter((r) => !owedIds.has(r.id));
|
|
99
|
+
return new ChecklistScan(defined, applicable, reviewed, opts.filterAlreadyReviewed ? stillOwed : applicable, this.prContextWriter.ensure(repoRoot, featureName, base), reviewPath, base);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Every file changed since the fork point, INCLUDING uncommitted and untracked ones. `tsOnly:false` is
|
|
103
|
+
* load-bearing too: the default drops every *.sql / Dockerfile / .env* file a checklist most wants to
|
|
104
|
+
* key on, which would silently shrink the set a reviewer is pointed at.
|
|
105
|
+
*/
|
|
106
|
+
changedFiles(repoRoot, base) {
|
|
107
|
+
if (base === '')
|
|
108
|
+
return [];
|
|
109
|
+
const opts = new rules_config_1.ChangedFilesOptions();
|
|
110
|
+
opts.tsOnly = false;
|
|
111
|
+
// No head argument — see the class comment. This is what includes the working tree.
|
|
112
|
+
return this.diffScope.getChangedFiles(repoRoot, base, undefined, opts);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
exports.ChecklistScanner = ChecklistScanner;
|
|
116
|
+
exports.ChecklistScanner = ChecklistScanner = tslib_1.__decorate([
|
|
117
|
+
(0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
|
|
118
|
+
tslib_1.__metadata("design:paramtypes", [git_readAiBranchName_1.AiBranchName,
|
|
119
|
+
checklist_detector_1.ChecklistDetector,
|
|
120
|
+
rules_config_1.DiffScope,
|
|
121
|
+
git_findForkPoint_1.ForkPoint,
|
|
122
|
+
pr_context_writer_1.PrContextWriter,
|
|
123
|
+
rules_config_1.ReviewJsonService])
|
|
124
|
+
], ChecklistScanner);
|
|
125
|
+
//# sourceMappingURL=checklist-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checklist-scanner.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/checklist-scanner.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangedFilesOptions, ChecklistDefinition, ChecklistReviewContext, DiffScope, RequiredChecklist,\n ReviewJsonService, reviewJsonPath,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from './git-readAiBranchName';\nimport { ChecklistDetector } from './checklist-detector';\nimport { ForkPoint } from './git-findForkPoint';\nimport { PrContextWriter } from './pr-context-writer';\n\n/** How a caller wants the scan filtered. Data-only (per CLAUDE.md). */\nexport class ChecklistScanOptions {\n /**\n * false — `outstanding` is every applicable checklist (what `wp-checklist` LISTS).\n * true — `outstanding` drops the ones already carrying a passing/overridden verdict, leaving only what\n * still owes review (what `wp-finish-upsert-pr` BLOCKS on).\n */\n filterAlreadyReviewed: boolean;\n\n constructor(filterAlreadyReviewed = false) {\n this.filterAlreadyReviewed = filterAlreadyReviewed;\n }\n}\n\n/**\n * The answer to \"what review does this branch owe?\", in the X → N → Z terms the commands report:\n * X = `defined` — every checklist in pr-gate.checklists\n * N = `applicable` — those whose patterns matched (or that have no patterns, so always run)\n * Z = `outstanding` — of N, those still owing a verdict (only when filterAlreadyReviewed)\n * Data-only.\n */\nexport class ChecklistScan {\n defined: ChecklistDefinition[]; // X\n applicable: RequiredChecklist[]; // N\n reviewed: RequiredChecklist[]; // N − Z: already have a passing/overridden review-<id>.json\n outstanding: RequiredChecklist[]; // Z (== applicable when not filtering)\n context: ChecklistReviewContext; // fork-point sha + pr-context.json path\n reviewPath: string; // the branch's review.json; verdict files sit beside it\n forkPoint: string; // '' when no fork point resolved\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n defined: ChecklistDefinition[],\n applicable: RequiredChecklist[],\n reviewed: RequiredChecklist[],\n outstanding: RequiredChecklist[],\n context: ChecklistReviewContext,\n reviewPath: string,\n forkPoint: string,\n ) {\n this.defined = defined;\n this.applicable = applicable;\n this.reviewed = reviewed;\n this.outstanding = outstanding;\n this.context = context;\n this.reviewPath = reviewPath;\n this.forkPoint = forkPoint;\n }\n}\n\n/**\n * The ONE computation of which reviewer subagents a branch owes, shared by `wp-checklist` (which lists) and\n * `wp-finish-upsert-pr` (which blocks). They previously each assembled this from the same parts in slightly\n * different ways, and any divergence means the command that reports and the command that gates disagree.\n *\n * Two properties are deliberate and load-bearing:\n *\n * 1. **The base is the FORK POINT of main, computed directly** — never `DiffScope.resolveBase`, which\n * overlays `NX_BASE`/`NX_HEAD` from the environment. That made review coverage depend on an env var.\n * 2. **UNCOMMITTED work counts.** `getChangedFiles` is called with NO head, which is the branch of it that\n * diffs base → WORKING TREE and unions in untracked files. Passing a head would diff commit-to-commit and\n * silently miss staged, unstaged and untracked changes — so a checklist matching only uncommitted files\n * would never fire and its reviewer would never be listed. `wp-checklist` is explicitly callable\n * mid-work, which is exactly when that matters.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ChecklistScanner {\n constructor(\n private readonly aiBranchName: AiBranchName,\n private readonly checklistDetector: ChecklistDetector,\n private readonly diffScope: DiffScope,\n private readonly forkPoint: ForkPoint,\n private readonly prContextWriter: PrContextWriter,\n private readonly reviewJsonService: ReviewJsonService,\n ) {}\n\n /**\n * `defined` is the caller's ALREADY-VALIDATED `prGate.checklists`. The scanner deliberately does not load\n * config itself: `loadAndValidate` is the one gate on the checklist set (it rejects a non-array\n * `checklists`, including the removed `{ doc }` manifest shape, and verifies every guidance doc and\n * reviewer-agent file exists), both callers already run it for other fields, and keeping the read out of\n * here leaves this a function of its inputs rather than of the filesystem.\n */\n scan(repoRoot: string, defined: ChecklistDefinition[], opts: ChecklistScanOptions): ChecklistScan {\n const featureName = this.aiBranchName.getFeatureName();\n const reviewPath = reviewJsonPath(repoRoot, featureName);\n const base = this.forkPoint.resolveForkPoint(repoRoot);\n const applicable = this.checklistDetector.toRequired(\n this.checklistDetector.detect(defined, this.changedFiles(repoRoot, base)),\n );\n const results = this.reviewJsonService.loadChecklistResults(reviewPath, applicable);\n const stillOwed = this.reviewJsonService.pendingChecklists(applicable, results);\n const owedIds = new Set(stillOwed.map((r: RequiredChecklist): string => r.id));\n const reviewed = applicable.filter((r: RequiredChecklist): boolean => !owedIds.has(r.id));\n return new ChecklistScan(\n defined,\n applicable,\n reviewed,\n opts.filterAlreadyReviewed ? stillOwed : applicable,\n this.prContextWriter.ensure(repoRoot, featureName, base),\n reviewPath,\n base,\n );\n }\n\n /**\n * Every file changed since the fork point, INCLUDING uncommitted and untracked ones. `tsOnly:false` is\n * load-bearing too: the default drops every *.sql / Dockerfile / .env* file a checklist most wants to\n * key on, which would silently shrink the set a reviewer is pointed at.\n */\n private changedFiles(repoRoot: string, base: string): string[] {\n if (base === '') return [];\n const opts = new ChangedFilesOptions();\n opts.tsOnly = false;\n // No head argument — see the class comment. This is what includes the working tree.\n return this.diffScope.getChangedFiles(repoRoot, base, undefined, opts);\n }\n}\n"]}
|
|
@@ -8,6 +8,21 @@ export declare class ForkPoint {
|
|
|
8
8
|
private readonly mergeState;
|
|
9
9
|
constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, mergeState: MergeState);
|
|
10
10
|
forkPointOutputDir(repoRoot: string, featureName: string, workflow: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* The 3-point FORK POINT: where this branch diverged from main. `git merge-base origin/main HEAD`,
|
|
13
|
+
* falling back to a local `main`, and '' when neither ref resolves.
|
|
14
|
+
*
|
|
15
|
+
* Deliberately does NOT fetch. The fork point is ABSOLUTE — advancing main cannot move it, because
|
|
16
|
+
* merge-base is the most recent common ancestor and main's new commits are not on this branch. (Verified
|
|
17
|
+
* on this repo: main went f415456 → ba8f674 → 61432d6 while merge-base with one branch stayed f415456
|
|
18
|
+
* throughout.) So a caller that needs only the fork point — see ChecklistScanner — pays no network cost
|
|
19
|
+
* and works offline. `findForkPoint` below still fetches, because the MERGE flow additionally needs
|
|
20
|
+
* main's CURRENT head as hash point C, which genuinely does require a fetch.
|
|
21
|
+
*
|
|
22
|
+
* Also writes no files and runs no merge-commit scan, so it cannot throw: `wp-checklist` must always
|
|
23
|
+
* succeed, and a raw merge-from-main is the merge flow's problem to report, not the checklist's.
|
|
24
|
+
*/
|
|
25
|
+
resolveForkPoint(repoRoot: string): string;
|
|
11
26
|
findForkPoint(workflow: string): Promise<void>;
|
|
12
27
|
private checkMergeCommits;
|
|
13
28
|
private printMergeFromMainError;
|
|
@@ -27,6 +27,29 @@ let ForkPoint = class ForkPoint {
|
|
|
27
27
|
forkPointOutputDir(repoRoot, featureName, workflow) {
|
|
28
28
|
return workflow === 'review' ? (0, rules_config_1.prDirFor)(repoRoot, featureName) : this.mergeState.mergeDirFor(repoRoot, featureName);
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* The 3-point FORK POINT: where this branch diverged from main. `git merge-base origin/main HEAD`,
|
|
32
|
+
* falling back to a local `main`, and '' when neither ref resolves.
|
|
33
|
+
*
|
|
34
|
+
* Deliberately does NOT fetch. The fork point is ABSOLUTE — advancing main cannot move it, because
|
|
35
|
+
* merge-base is the most recent common ancestor and main's new commits are not on this branch. (Verified
|
|
36
|
+
* on this repo: main went f415456 → ba8f674 → 61432d6 while merge-base with one branch stayed f415456
|
|
37
|
+
* throughout.) So a caller that needs only the fork point — see ChecklistScanner — pays no network cost
|
|
38
|
+
* and works offline. `findForkPoint` below still fetches, because the MERGE flow additionally needs
|
|
39
|
+
* main's CURRENT head as hash point C, which genuinely does require a fetch.
|
|
40
|
+
*
|
|
41
|
+
* Also writes no files and runs no merge-commit scan, so it cannot throw: `wp-checklist` must always
|
|
42
|
+
* succeed, and a raw merge-from-main is the merge flow's problem to report, not the checklist's.
|
|
43
|
+
*/
|
|
44
|
+
resolveForkPoint(repoRoot) {
|
|
45
|
+
for (const ref of ['origin/main', 'main']) {
|
|
46
|
+
const result = (0, child_process_1.spawnSync)('git', ['merge-base', ref, 'HEAD'], { cwd: repoRoot, encoding: 'utf8' });
|
|
47
|
+
const sha = (result.stdout ?? '').trim();
|
|
48
|
+
if (result.status === 0 && sha !== '')
|
|
49
|
+
return sha;
|
|
50
|
+
}
|
|
51
|
+
return '';
|
|
52
|
+
}
|
|
30
53
|
async findForkPoint(workflow) {
|
|
31
54
|
if (workflow !== 'review' && workflow !== 'merge') {
|
|
32
55
|
throw new rules_config_1.CliExitError(1, 'ERROR: Workflow argument required\n' +
|
|
@@ -43,9 +66,10 @@ let ForkPoint = class ForkPoint {
|
|
|
43
66
|
const featureHead = (0, child_process_1.execSync)('git rev-parse HEAD', { encoding: 'utf8' }).trim();
|
|
44
67
|
const originMain = (0, child_process_1.execSync)('git rev-parse origin/main', { encoding: 'utf8' }).trim();
|
|
45
68
|
process.stderr.write('Finding fork point using git merge-base...\n');
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
69
|
+
// ONE implementation of the fork-point computation, shared with ChecklistScanner. The fetch above is
|
|
70
|
+
// what this flow needs beyond it (hash point C = main's current head), not the fork point itself.
|
|
71
|
+
const forkPoint = this.resolveForkPoint(repoRoot);
|
|
72
|
+
if (!forkPoint) {
|
|
49
73
|
throw new rules_config_1.CliExitError(1, 'ERROR: Could not find common ancestor with origin/main');
|
|
50
74
|
}
|
|
51
75
|
process.stderr.write(`✅ Fork point found: ${forkPoint.slice(0, 7)}\n`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git-findForkPoint.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/git-findForkPoint.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync, spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { prDirFor, CliExitError, RepoRootFinder } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from './git-readAiBranchName';\nimport { MergeState } from './merge-state';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n/** Computes the 3-point fork point and guards against improper merges-from-main. */\n@injectable(bindingScopeValues.Singleton)\nexport class ForkPoint {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly aiBranchName: AiBranchName,\n private readonly mergeState: MergeState,\n ) {}\n\n // Single source of truth for the per-feature dir fork-point output is written to — the SAME nested\n // home the readers use (git-gatherInfo / merge-start read updatemain-hashes.json from mergeDirFor;\n // the review flow reads from prDirFor). Routing the writer through these same helpers is what keeps\n // them from diverging again — guarded by git-findForkPoint.spec.ts.\n forkPointOutputDir(repoRoot: string, featureName: string, workflow: string): string {\n return workflow === 'review' ? prDirFor(repoRoot, featureName) : this.mergeState.mergeDirFor(repoRoot, featureName);\n }\n\n async findForkPoint(workflow: string): Promise<void> {\n if (workflow !== 'review' && workflow !== 'merge') {\n throw new CliExitError(1,\n 'ERROR: Workflow argument required\\n' +\n 'Usage: git-findForkPoint <workflow>\\n' +\n \" workflow: 'review' or 'merge'\",\n );\n }\n\n const featureName = this.aiBranchName.getFeatureName();\n const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n\n const outputDir = this.forkPointOutputDir(repoRoot, featureName, workflow);\n const prefix = workflow === 'review' ? 'review-' : 'updatemain-';\n fs.mkdirSync(outputDir, { recursive: true });\n\n spawnSync('git', ['fetch', 'origin', 'main'], { stdio: 'ignore' });\n\n const featureHead = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim();\n const originMain = execSync('git rev-parse origin/main', { encoding: 'utf8' }).trim();\n\n process.stderr.write('Finding fork point using git merge-base...\\n');\n\n const forkPointResult = spawnSync('git', ['merge-base', 'origin/main', 'HEAD'], { encoding: 'utf8' });\n const forkPoint = (forkPointResult.stdout ?? '').trim();\n\n if (!forkPoint || forkPointResult.status !== 0) {\n throw new CliExitError(1, 'ERROR: Could not find common ancestor with origin/main');\n }\n\n process.stderr.write(`✅ Fork point found: ${forkPoint.slice(0, 7)}\\n`);\n process.stderr.write('Checking for improper merges from main...\\n');\n\n const mergeCommitsResult = spawnSync('git', ['log', `${forkPoint}..HEAD`, '--merges', '--format=%H'], { encoding: 'utf8' });\n const mergeCommitsRaw = (mergeCommitsResult.stdout ?? '').trim();\n const mergeCommits = mergeCommitsRaw ? mergeCommitsRaw.split('\\n') : [];\n\n if (mergeCommits.length > 0) {\n this.checkMergeCommits(mergeCommits, outputDir, prefix, featureName, currentBranch);\n } else {\n process.stderr.write('✅ No merge commits found (clean history)\\n');\n }\n\n const hashesJson = JSON.stringify({\n hashForkPoint: forkPoint,\n hashFeatureHead: featureHead,\n hashMainHead: originMain,\n timestamp: new Date().toISOString(),\n }, null, 2);\n\n fs.writeFileSync(path.join(outputDir, `${prefix}hashes.json`), hashesJson + '\\n');\n process.stderr.write(`✅ Hash points written to: ${outputDir}/${prefix}hashes.json\\n`);\n }\n\n private checkMergeCommits(mergeCommits: string[], outputDir: string, prefix: string, featureName: string, currentBranch: string): void {\n process.stderr.write(`Found ${mergeCommits.length} merge commit(s) to check...\\n`);\n\n for (const commit of mergeCommits) {\n const parentsResult = spawnSync('git', ['rev-list', '--parents', '-n', '1', commit], { encoding: 'utf8' });\n const parents = (parentsResult.stdout ?? '').trim().split(' ').slice(1);\n\n for (const parent of parents) {\n const ancestorCheck = spawnSync('git', ['merge-base', '--is-ancestor', parent, 'origin/main']);\n const reverseCheck = spawnSync('git', ['merge-base', '--is-ancestor', 'origin/main', parent]);\n\n if (ancestorCheck.status === 0 && reverseCheck.status === 0) {\n const errorJson = JSON.stringify({\n error: 'Merge from main detected',\n mergeCommit: commit,\n parentFromMain: parent,\n timestamp: new Date().toISOString(),\n }, null, 2);\n\n fs.writeFileSync(path.join(outputDir, `${prefix}forkpoint-error.json`), errorJson + '\\n');\n this.printMergeFromMainError(commit, parent, featureName, currentBranch);\n throw new CliExitError(1, '');\n }\n }\n }\n process.stderr.write('✅ No improper merges from main detected\\n');\n }\n\n private printMergeFromMainError(commit: string, parent: string, featureName: string, currentBranch: string): void {\n process.stderr.write('\\n');\n process.stderr.write(SEP);\n process.stderr.write('❌ This branch merged main without the gated update (pnpm wp-start-update, or\\n');\n process.stderr.write(' pnpm wp-start-upsert-pr when a PR is open)\\n');\n process.stderr.write(SEP);\n process.stderr.write('\\n');\n process.stderr.write(`Merge commit detected: ${commit}\\n`);\n process.stderr.write(`Parent from main: ${parent}\\n`);\n process.stderr.write('\\n');\n process.stderr.write('This prevents clean squash-merge. To recover, follow these steps:\\n');\n process.stderr.write('\\n');\n process.stderr.write('1. Fetch the latest main (works on the primary repo AND in a worktree):\\n');\n process.stderr.write(' git fetch origin main\\n');\n process.stderr.write('\\n');\n process.stderr.write('2. Create a new branch OFF origin/main — do NOT `git checkout main`\\n');\n process.stderr.write(' (it fatals inside a worktree: \"main is already checked out at <primary>\"):\\n');\n process.stderr.write(` git checkout -b ${featureName}-v2 origin/main\\n`);\n process.stderr.write('\\n');\n process.stderr.write('3. Squash merge your old branch — THIS STEP IS FOR THE HUMAN TO RUN:\\n');\n process.stderr.write(` git merge --squash ${currentBranch}\\n`);\n process.stderr.write(' AI: `git merge` is blocked for you (redirect-how-to-merge-main) and you must NOT\\n');\n process.stderr.write(' work around it. Ask the human to run that one command, and warn them:\\n');\n process.stderr.write(' \"This is a raw git merge. It is only correct here because the branch is already\\n');\n process.stderr.write(' broken and is being rebuilt onto origin/main. For a NORMAL update from main,\\n');\n process.stderr.write(' push back and tell me to use the gated 3-point merge instead: `pnpm wp-start-update`\\n');\n process.stderr.write(' (paired with `pnpm wp-finish-update`) when no PR is open, or `pnpm wp-start-upsert-pr`\\n');\n process.stderr.write(' (paired with `pnpm wp-finish-upsert-pr`) when one is.\"\\n');\n process.stderr.write('\\n');\n process.stderr.write('4. Commit the squashed changes:\\n');\n process.stderr.write(` git add -A && git commit -m \"Squashed from ${currentBranch}\"\\n`);\n process.stderr.write('\\n');\n process.stderr.write('5. If you have an existing PR:\\n');\n process.stderr.write(` - Create a NEW PR for ${featureName}-v2\\n`);\n process.stderr.write(` - Close the old PR for ${currentBranch}\\n`);\n process.stderr.write('\\n');\n process.stderr.write(SEP);\n process.stderr.write('\\n');\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"git-findForkPoint.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/git-findForkPoint.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync, spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { prDirFor, CliExitError, RepoRootFinder } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from './git-readAiBranchName';\nimport { MergeState } from './merge-state';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n/** Computes the 3-point fork point and guards against improper merges-from-main. */\n@injectable(bindingScopeValues.Singleton)\nexport class ForkPoint {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly aiBranchName: AiBranchName,\n private readonly mergeState: MergeState,\n ) {}\n\n // Single source of truth for the per-feature dir fork-point output is written to — the SAME nested\n // home the readers use (git-gatherInfo / merge-start read updatemain-hashes.json from mergeDirFor;\n // the review flow reads from prDirFor). Routing the writer through these same helpers is what keeps\n // them from diverging again — guarded by git-findForkPoint.spec.ts.\n forkPointOutputDir(repoRoot: string, featureName: string, workflow: string): string {\n return workflow === 'review' ? prDirFor(repoRoot, featureName) : this.mergeState.mergeDirFor(repoRoot, featureName);\n }\n\n /**\n * The 3-point FORK POINT: where this branch diverged from main. `git merge-base origin/main HEAD`,\n * falling back to a local `main`, and '' when neither ref resolves.\n *\n * Deliberately does NOT fetch. The fork point is ABSOLUTE — advancing main cannot move it, because\n * merge-base is the most recent common ancestor and main's new commits are not on this branch. (Verified\n * on this repo: main went f415456 → ba8f674 → 61432d6 while merge-base with one branch stayed f415456\n * throughout.) So a caller that needs only the fork point — see ChecklistScanner — pays no network cost\n * and works offline. `findForkPoint` below still fetches, because the MERGE flow additionally needs\n * main's CURRENT head as hash point C, which genuinely does require a fetch.\n *\n * Also writes no files and runs no merge-commit scan, so it cannot throw: `wp-checklist` must always\n * succeed, and a raw merge-from-main is the merge flow's problem to report, not the checklist's.\n */\n resolveForkPoint(repoRoot: string): string {\n for (const ref of ['origin/main', 'main']) {\n const result = spawnSync('git', ['merge-base', ref, 'HEAD'], { cwd: repoRoot, encoding: 'utf8' });\n const sha = (result.stdout ?? '').trim();\n if (result.status === 0 && sha !== '') return sha;\n }\n return '';\n }\n\n async findForkPoint(workflow: string): Promise<void> {\n if (workflow !== 'review' && workflow !== 'merge') {\n throw new CliExitError(1,\n 'ERROR: Workflow argument required\\n' +\n 'Usage: git-findForkPoint <workflow>\\n' +\n \" workflow: 'review' or 'merge'\",\n );\n }\n\n const featureName = this.aiBranchName.getFeatureName();\n const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n\n const outputDir = this.forkPointOutputDir(repoRoot, featureName, workflow);\n const prefix = workflow === 'review' ? 'review-' : 'updatemain-';\n fs.mkdirSync(outputDir, { recursive: true });\n\n spawnSync('git', ['fetch', 'origin', 'main'], { stdio: 'ignore' });\n\n const featureHead = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim();\n const originMain = execSync('git rev-parse origin/main', { encoding: 'utf8' }).trim();\n\n process.stderr.write('Finding fork point using git merge-base...\\n');\n\n // ONE implementation of the fork-point computation, shared with ChecklistScanner. The fetch above is\n // what this flow needs beyond it (hash point C = main's current head), not the fork point itself.\n const forkPoint = this.resolveForkPoint(repoRoot);\n\n if (!forkPoint) {\n throw new CliExitError(1, 'ERROR: Could not find common ancestor with origin/main');\n }\n\n process.stderr.write(`✅ Fork point found: ${forkPoint.slice(0, 7)}\\n`);\n process.stderr.write('Checking for improper merges from main...\\n');\n\n const mergeCommitsResult = spawnSync('git', ['log', `${forkPoint}..HEAD`, '--merges', '--format=%H'], { encoding: 'utf8' });\n const mergeCommitsRaw = (mergeCommitsResult.stdout ?? '').trim();\n const mergeCommits = mergeCommitsRaw ? mergeCommitsRaw.split('\\n') : [];\n\n if (mergeCommits.length > 0) {\n this.checkMergeCommits(mergeCommits, outputDir, prefix, featureName, currentBranch);\n } else {\n process.stderr.write('✅ No merge commits found (clean history)\\n');\n }\n\n const hashesJson = JSON.stringify({\n hashForkPoint: forkPoint,\n hashFeatureHead: featureHead,\n hashMainHead: originMain,\n timestamp: new Date().toISOString(),\n }, null, 2);\n\n fs.writeFileSync(path.join(outputDir, `${prefix}hashes.json`), hashesJson + '\\n');\n process.stderr.write(`✅ Hash points written to: ${outputDir}/${prefix}hashes.json\\n`);\n }\n\n private checkMergeCommits(mergeCommits: string[], outputDir: string, prefix: string, featureName: string, currentBranch: string): void {\n process.stderr.write(`Found ${mergeCommits.length} merge commit(s) to check...\\n`);\n\n for (const commit of mergeCommits) {\n const parentsResult = spawnSync('git', ['rev-list', '--parents', '-n', '1', commit], { encoding: 'utf8' });\n const parents = (parentsResult.stdout ?? '').trim().split(' ').slice(1);\n\n for (const parent of parents) {\n const ancestorCheck = spawnSync('git', ['merge-base', '--is-ancestor', parent, 'origin/main']);\n const reverseCheck = spawnSync('git', ['merge-base', '--is-ancestor', 'origin/main', parent]);\n\n if (ancestorCheck.status === 0 && reverseCheck.status === 0) {\n const errorJson = JSON.stringify({\n error: 'Merge from main detected',\n mergeCommit: commit,\n parentFromMain: parent,\n timestamp: new Date().toISOString(),\n }, null, 2);\n\n fs.writeFileSync(path.join(outputDir, `${prefix}forkpoint-error.json`), errorJson + '\\n');\n this.printMergeFromMainError(commit, parent, featureName, currentBranch);\n throw new CliExitError(1, '');\n }\n }\n }\n process.stderr.write('✅ No improper merges from main detected\\n');\n }\n\n private printMergeFromMainError(commit: string, parent: string, featureName: string, currentBranch: string): void {\n process.stderr.write('\\n');\n process.stderr.write(SEP);\n process.stderr.write('❌ This branch merged main without the gated update (pnpm wp-start-update, or\\n');\n process.stderr.write(' pnpm wp-start-upsert-pr when a PR is open)\\n');\n process.stderr.write(SEP);\n process.stderr.write('\\n');\n process.stderr.write(`Merge commit detected: ${commit}\\n`);\n process.stderr.write(`Parent from main: ${parent}\\n`);\n process.stderr.write('\\n');\n process.stderr.write('This prevents clean squash-merge. To recover, follow these steps:\\n');\n process.stderr.write('\\n');\n process.stderr.write('1. Fetch the latest main (works on the primary repo AND in a worktree):\\n');\n process.stderr.write(' git fetch origin main\\n');\n process.stderr.write('\\n');\n process.stderr.write('2. Create a new branch OFF origin/main — do NOT `git checkout main`\\n');\n process.stderr.write(' (it fatals inside a worktree: \"main is already checked out at <primary>\"):\\n');\n process.stderr.write(` git checkout -b ${featureName}-v2 origin/main\\n`);\n process.stderr.write('\\n');\n process.stderr.write('3. Squash merge your old branch — THIS STEP IS FOR THE HUMAN TO RUN:\\n');\n process.stderr.write(` git merge --squash ${currentBranch}\\n`);\n process.stderr.write(' AI: `git merge` is blocked for you (redirect-how-to-merge-main) and you must NOT\\n');\n process.stderr.write(' work around it. Ask the human to run that one command, and warn them:\\n');\n process.stderr.write(' \"This is a raw git merge. It is only correct here because the branch is already\\n');\n process.stderr.write(' broken and is being rebuilt onto origin/main. For a NORMAL update from main,\\n');\n process.stderr.write(' push back and tell me to use the gated 3-point merge instead: `pnpm wp-start-update`\\n');\n process.stderr.write(' (paired with `pnpm wp-finish-update`) when no PR is open, or `pnpm wp-start-upsert-pr`\\n');\n process.stderr.write(' (paired with `pnpm wp-finish-upsert-pr`) when one is.\"\\n');\n process.stderr.write('\\n');\n process.stderr.write('4. Commit the squashed changes:\\n');\n process.stderr.write(` git add -A && git commit -m \"Squashed from ${currentBranch}\"\\n`);\n process.stderr.write('\\n');\n process.stderr.write('5. If you have an existing PR:\\n');\n process.stderr.write(` - Create a NEW PR for ${featureName}-v2\\n`);\n process.stderr.write(` - Close the old PR for ${currentBranch}\\n`);\n process.stderr.write('\\n');\n process.stderr.write(SEP);\n process.stderr.write('\\n');\n }\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ChecklistReviewContext, DiffScope, ReviewJsonService } from '@webpieces/rules-config';
|
|
2
|
+
/**
|
|
3
|
+
* Writes `pr-context.json` (the 3-point base/head sha + the FULL changed-file set) and returns the context
|
|
4
|
+
* every printed review instruction inlines.
|
|
5
|
+
*
|
|
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
|
|
8
|
+
* reviewer blocks with NO `git diff` command and NO full-file-set path whenever wp-start had not run this
|
|
9
|
+
* cycle. A reviewer handed filenames and no way to read the diff is exactly the failure this instruction
|
|
10
|
+
* exists to prevent, and it failed silently, which is worse.
|
|
11
|
+
*
|
|
12
|
+
* `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.
|
|
13
|
+
*/
|
|
14
|
+
export declare class PrContextWriter {
|
|
15
|
+
private readonly diffScope;
|
|
16
|
+
private readonly reviewJsonService;
|
|
17
|
+
constructor(diffScope: DiffScope, reviewJsonService: ReviewJsonService);
|
|
18
|
+
/**
|
|
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.
|
|
23
|
+
*
|
|
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).
|
|
27
|
+
*/
|
|
28
|
+
ensure(repoRoot: string, featureName: string, forkPoint: string): ChecklistReviewContext;
|
|
29
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PrContextWriter = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const rules_config_1 = require("@webpieces/rules-config");
|
|
6
|
+
const inversify_1 = require("inversify");
|
|
7
|
+
/**
|
|
8
|
+
* Writes `pr-context.json` (the 3-point base/head sha + the FULL changed-file set) and returns the context
|
|
9
|
+
* every printed review instruction inlines.
|
|
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
|
|
13
|
+
* reviewer blocks with NO `git diff` command and NO full-file-set path whenever wp-start had not run this
|
|
14
|
+
* cycle. A reviewer handed filenames and no way to read the diff is exactly the failure this instruction
|
|
15
|
+
* exists to prevent, and it failed silently, which is worse.
|
|
16
|
+
*
|
|
17
|
+
* `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.
|
|
18
|
+
*/
|
|
19
|
+
let PrContextWriter = class PrContextWriter {
|
|
20
|
+
diffScope;
|
|
21
|
+
reviewJsonService;
|
|
22
|
+
constructor(diffScope, reviewJsonService) {
|
|
23
|
+
this.diffScope = diffScope;
|
|
24
|
+
this.reviewJsonService = reviewJsonService;
|
|
25
|
+
}
|
|
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.
|
|
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).
|
|
35
|
+
*/
|
|
36
|
+
ensure(repoRoot, featureName, forkPoint) {
|
|
37
|
+
if (forkPoint === '')
|
|
38
|
+
return new rules_config_1.ChecklistReviewContext();
|
|
39
|
+
const opts = new rules_config_1.ChangedFilesOptions();
|
|
40
|
+
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);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
exports.PrContextWriter = PrContextWriter;
|
|
47
|
+
exports.PrContextWriter = PrContextWriter = tslib_1.__decorate([
|
|
48
|
+
(0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
|
|
49
|
+
tslib_1.__metadata("design:paramtypes", [rules_config_1.DiffScope,
|
|
50
|
+
rules_config_1.ReviewJsonService])
|
|
51
|
+
], PrContextWriter);
|
|
52
|
+
//# sourceMappingURL=pr-context-writer.js.map
|