@webpieces/pr-gate 0.4.479 → 0.4.480
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/finish-update-command.js +2 -1
- package/src/scripts/commands/finish-update-command.js.map +1 -1
- package/src/scripts/commands/finish-upsert-pr-command.d.ts +1 -0
- package/src/scripts/commands/finish-upsert-pr-command.js +12 -6
- package/src/scripts/commands/finish-upsert-pr-command.js.map +1 -1
- package/src/scripts/commands/start-update-command.js +3 -1
- package/src/scripts/commands/start-update-command.js.map +1 -1
- package/src/scripts/commands/start-upsert-pr-command.d.ts +5 -1
- package/src/scripts/commands/start-upsert-pr-command.js +42 -19
- package/src/scripts/commands/start-upsert-pr-command.js.map +1 -1
- package/src/scripts/workflow/checklist-notice.d.ts +34 -0
- package/src/scripts/workflow/checklist-notice.js +86 -0
- package/src/scripts/workflow/checklist-notice.js.map +1 -0
- package/src/scripts/workflow/gated-pr-publisher.d.ts +6 -6
- package/src/scripts/workflow/gated-pr-publisher.js +6 -6
- package/src/scripts/workflow/gated-pr-publisher.js.map +1 -1
- package/src/scripts/workflow/merge-end.d.ts +24 -5
- package/src/scripts/workflow/merge-end.js +95 -25
- package/src/scripts/workflow/merge-end.js.map +1 -1
- package/src/scripts/workflow/run-update.d.ts +1 -1
- package/src/scripts/workflow/run-update.js +9 -6
- package/src/scripts/workflow/run-update.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/pr-gate",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.480",
|
|
4
4
|
"description": "Gated PR system: 3-point squash-merge, merge validation gate, and red/yellow/green PR dashboard. Standalone scripts, no Nx dependency required.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"directory": "packages/tooling/pr-gate"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@webpieces/rules-config": "0.4.
|
|
27
|
+
"@webpieces/rules-config": "0.4.480",
|
|
28
28
|
"@inversifyjs/binding-decorators": "1.1.5",
|
|
29
29
|
"inversify": "7.10.4",
|
|
30
30
|
"reflect-metadata": "0.2.2"
|
|
@@ -36,7 +36,8 @@ let FinishUpdateCommand = class FinishUpdateCommand {
|
|
|
36
36
|
// Not yet validated => a conflict resolution the AI owns, so validate + commit it first;
|
|
37
37
|
// already validated => clean merge (or previously validated) => finalize only.
|
|
38
38
|
const conflictedFiles = marker.validated ? null : marker.conflictedFiles;
|
|
39
|
-
|
|
39
|
+
// pushRemote=true: terminal command of the update-only flow — nothing pushes after this.
|
|
40
|
+
await this.mergeEnd.mergeEnd(repoRoot, 'wp-finish-update', activeDir, new merge_start_1.MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber), new merge_end_1.MergeEndOptions(conflictedFiles, true));
|
|
40
41
|
process.stdout.write('\n✅ Merge finalized on ' + marker.currentBranch + '.\n');
|
|
41
42
|
}
|
|
42
43
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"finish-update-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/finish-update-command.ts"],"names":[],"mappings":";;;;AAAA,0DAAuE;AACvE,yCAA2D;AAC3D,2EAAgE;AAChE,yDAAqD;AACrD,
|
|
1
|
+
{"version":3,"file":"finish-update-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/finish-update-command.ts"],"names":[],"mappings":";;;;AAAA,0DAAuE;AACvE,yCAA2D;AAC3D,2EAAgE;AAChE,yDAAqD;AACrD,qDAAkE;AAClE,yDAAuD;AAEvD,wGAAwG;AACxG,sGAAsG;AACtG,qGAAqG;AACrG,mGAAmG;AAE5F,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAEP;IACA;IACA;IACA;IAJrB,YACqB,cAA8B,EAC9B,YAA0B,EAC1B,UAAsB,EACtB,QAAkB;QAHlB,mBAAc,GAAd,cAAc,CAAgB;QAC9B,iBAAY,GAAZ,YAAY,CAAc;QAC1B,eAAU,GAAV,UAAU,CAAY;QACtB,aAAQ,GAAR,QAAQ,CAAU;IACpC,CAAC;IAEJ,KAAK,CAAC,GAAG;QACL,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACpE,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC,CAAC;QAEvF,sEAAsE;QACtE,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAC9D,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC7E,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,EAAE,CAAC;YACxB,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,6DAA6D;gBAC7D,2EAA2E,CAC9E,CAAC;QACN,CAAC;QAED,yFAAyF;QACzF,+EAA+E;QAC/E,MAAM,eAAe,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC;QACzE,yFAAyF;QACzF,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CACxB,QAAQ,EAAE,kBAAkB,EAAE,SAAS,EACvC,IAAI,0BAAY,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,EACjG,IAAI,2BAAe,CAAC,eAAe,EAAE,IAAI,CAAC,CAC7C,CAAC;QACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,GAAG,MAAM,CAAC,aAAa,GAAG,KAAK,CAAC,CAAC;IACnF,CAAC;CACJ,CAAA;AAjCY,kDAAmB;8BAAnB,mBAAmB;IAD/B,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAGA,6BAAc;QAChB,mCAAY;QACd,wBAAU;QACZ,oBAAQ;GAL9B,mBAAmB,CAiC/B","sourcesContent":["import { CliExitError, RepoRootFinder } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { MergeState } from '../workflow/merge-state';\nimport { MergeEnd, MergeEndOptions } from '../workflow/merge-end';\nimport { MergeContext } from '../workflow/merge-start';\n\n// wp-finish-update — the finalize half of the 3-point squash-merge lifecycle. Run AFTER wp-start-update\n// handed back conflicts and you resolved them. Given an in-progress marker it validates + commits the\n// AI's resolution (when not yet validated) and ALWAYS finalizes the branch swap. It does NOT run the\n// build gate / dashboard / PR — that is wp-finish-upsert-pr. Refuses when no merge is in progress.\n@injectable(bindingScopeValues.Singleton)\nexport class FinishUpdateCommand {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly aiBranchName: AiBranchName,\n private readonly mergeState: MergeState,\n private readonly mergeEnd: MergeEnd,\n ) {}\n\n async run(): Promise<void> {\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n const home = this.mergeState.mergeDirFor(repoRoot, this.aiBranchName.getFeatureName());\n\n // The in-progress merge is the `merge-<n>/` run dir holding a marker.\n const activeDir = this.mergeState.findActiveMergeRunDir(home);\n const marker = activeDir ? this.mergeState.readMergeMarker(activeDir) : null;\n if (!activeDir || !marker) {\n throw new CliExitError(1,\n '❌ No merge in progress (no marker) — nothing to finalize.\\n' +\n 'Start one with: pnpm wp-start-update (a clean update finalizes itself).',\n );\n }\n\n // Not yet validated => a conflict resolution the AI owns, so validate + commit it first;\n // already validated => clean merge (or previously validated) => finalize only.\n const conflictedFiles = marker.validated ? null : marker.conflictedFiles;\n // pushRemote=true: terminal command of the update-only flow — nothing pushes after this.\n await this.mergeEnd.mergeEnd(\n repoRoot, 'wp-finish-update', activeDir,\n new MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber),\n new MergeEndOptions(conflictedFiles, true),\n );\n process.stdout.write('\\n✅ Merge finalized on ' + marker.currentBranch + '.\\n');\n }\n}\n"]}
|
|
@@ -27,6 +27,7 @@ export declare class FinishUpsertPrCommand {
|
|
|
27
27
|
private readonly provenance;
|
|
28
28
|
constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, branchNaming: BranchNaming, gitExec: GitExec, buildAffected: BuildAffected, mergeState: MergeState, mergeEnd: MergeEnd, prMerger: PrMerger, publisher: GatedPrPublisher, dashboard: Dashboard, checklistDetector: ChecklistDetector, manifestService: ChecklistManifestService, reviewJsonService: ReviewJsonService, gateTokenService: GateTokenService, provenance: SubagentProvenanceService);
|
|
29
29
|
run(): Promise<void>;
|
|
30
|
+
private finalizeAnyInProgressMerge;
|
|
30
31
|
private gitOut;
|
|
31
32
|
private prTitleFrom;
|
|
32
33
|
private computeDashboardInput;
|
|
@@ -79,13 +79,8 @@ let FinishUpsertPrCommand = class FinishUpsertPrCommand {
|
|
|
79
79
|
const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
|
|
80
80
|
// Refresh the AI-facing workflow doc so it's present + current for any failure message to cite.
|
|
81
81
|
(0, rules_config_1.writeTemplate)(repoRoot, 'webpieces.git-workflow.md');
|
|
82
|
-
const home = this.mergeState.mergeDirFor(repoRoot, this.aiBranchName.getFeatureName());
|
|
83
82
|
// 1. Finish any in-progress conflict resolution: validate + commit + finalize the branch swap.
|
|
84
|
-
|
|
85
|
-
const marker = activeDir ? this.mergeState.readMergeMarker(activeDir) : null;
|
|
86
|
-
if (activeDir && marker && !marker.validated) {
|
|
87
|
-
await this.mergeEnd.mergeEnd(repoRoot, 'wp-finish-upsert-pr', activeDir, new merge_start_1.MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber), marker.conflictedFiles);
|
|
88
|
-
}
|
|
83
|
+
await this.finalizeAnyInProgressMerge(repoRoot);
|
|
89
84
|
// 2. REQUIRE the AI-authored review.json (throws InformAiError with the schema if missing/invalid).
|
|
90
85
|
// Compute the consumer checklists this diff triggered FIRST so an unacknowledged BLOCK throws
|
|
91
86
|
// here — BEFORE any `gh pr create` — matching the guarantee buildCommand already provides.
|
|
@@ -129,6 +124,17 @@ let FinishUpsertPrCommand = class FinishUpsertPrCommand {
|
|
|
129
124
|
` 4. ${result.merge.message}\n` +
|
|
130
125
|
` You are on ${base} — same name as the remote branch and the PR head.\n\n`);
|
|
131
126
|
}
|
|
127
|
+
// Validate + commit + finalize a 3-point merge the AI resolved, if one is in progress. Finalizing here
|
|
128
|
+
// does NOT push (pushRemote=false): this command pushes exactly ONCE, from GatedPrPublisher, and only
|
|
129
|
+
// after review.json + every BLOCK checklist + the build gate pass and the gated PR body is written.
|
|
130
|
+
async finalizeAnyInProgressMerge(repoRoot) {
|
|
131
|
+
const home = this.mergeState.mergeDirFor(repoRoot, this.aiBranchName.getFeatureName());
|
|
132
|
+
const activeDir = this.mergeState.findActiveMergeRunDir(home);
|
|
133
|
+
const marker = activeDir ? this.mergeState.readMergeMarker(activeDir) : null;
|
|
134
|
+
if (!activeDir || !marker || marker.validated)
|
|
135
|
+
return;
|
|
136
|
+
await this.mergeEnd.mergeEnd(repoRoot, 'wp-finish-upsert-pr', activeDir, new merge_start_1.MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber), new merge_end_1.MergeEndOptions(marker.conflictedFiles, false));
|
|
137
|
+
}
|
|
132
138
|
gitOut(args) {
|
|
133
139
|
const result = (0, child_process_1.spawnSync)('git', args, { encoding: 'utf8' });
|
|
134
140
|
return result.status === 0 ? (result.stdout ?? '').trim() : '';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"finish-upsert-pr-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/finish-upsert-pr-command.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 {\n loadAndValidate, prDirFor, reviewJsonPath, ReviewJson, RequiredChecklist,\n writeTemplate, RepoRootFinder, ReviewJsonService, ChecklistManifestService,\n GateTokenService, SubagentProvenanceService, PROVENANCE_OK, PROVENANCE_MISSING, PROVENANCE_SKIPPED,\n InformAiError,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BranchNaming } from '../workflow/branch-naming';\nimport { ChecklistDetector } from '../workflow/checklist-detector';\nimport { GitExec } from '../workflow/git-exec';\nimport { BuildAffected, BuildGateOptions } from '../workflow/build-affected';\nimport { MergeState } from '../workflow/merge-state';\nimport { MergeEnd } from '../workflow/merge-end';\nimport { MergeContext } from '../workflow/merge-start';\nimport { PrMerger, MergeOutcome } from '../workflow/pr-merger';\nimport { GatedPrPublisher } from '../workflow/gated-pr-publisher';\nimport { Dashboard, DashboardInput, ChecklistRow, CHECKLIST_COMMENT_MARKER } from '../../dashboard/dashboard';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// A resolved PR's number + web URL. Both '' when the PR can't be resolved (e.g. create failed).\nclass PrRef {\n number: string;\n url: string;\n\n constructor(number: string, url: string) {\n this.number = number;\n this.url = url;\n }\n}\n\n// The outcome of the whole upsert: the PR number ('' when it could not be resolved) plus what actually\n// happened to the merge, so the final summary reports the REAL result rather than assuming success.\nclass UpsertResult {\n prNumber: string;\n merge: MergeOutcome;\n\n constructor(prNumber: string, merge: MergeOutcome) {\n this.prNumber = prNumber;\n this.merge = merge;\n }\n}\n\n// FINISH of the AI-first PR flow. Runs after the AI wrote review.json. In order: (1) if a 3-point merge\n// was in progress, validate + commit + FINALIZE via merge-END; (2) REQUIRE review.json; (3) run the\n// authoritative build gate; (4) render the dashboard; (5) create/update the PR via `gh`. The ONLY\n// command that posts PRs.\n@injectable(bindingScopeValues.Singleton)\nexport class FinishUpsertPrCommand {\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 buildAffected: BuildAffected,\n private readonly mergeState: MergeState,\n private readonly mergeEnd: MergeEnd,\n private readonly prMerger: PrMerger,\n private readonly publisher: GatedPrPublisher,\n private readonly dashboard: Dashboard,\n private readonly checklistDetector: ChecklistDetector,\n private readonly manifestService: ChecklistManifestService,\n private readonly reviewJsonService: ReviewJsonService,\n private readonly gateTokenService: GateTokenService,\n private readonly provenance: SubagentProvenanceService,\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 const home = this.mergeState.mergeDirFor(repoRoot, this.aiBranchName.getFeatureName());\n\n // 1. Finish any in-progress conflict resolution: validate + commit + finalize the branch swap.\n const activeDir = this.mergeState.findActiveMergeRunDir(home);\n const marker = activeDir ? this.mergeState.readMergeMarker(activeDir) : null;\n if (activeDir && marker && !marker.validated) {\n await this.mergeEnd.mergeEnd(\n repoRoot, 'wp-finish-upsert-pr', activeDir,\n new MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber),\n marker.conflictedFiles,\n );\n }\n\n // 2. REQUIRE the AI-authored review.json (throws InformAiError with the schema if missing/invalid).\n // Compute the consumer checklists this diff triggered FIRST so an unacknowledged BLOCK throws\n // here — BEFORE any `gh pr create` — matching the guarantee buildCommand already provides.\n const defs = this.manifestService.load(repoRoot, loadAndValidate(repoRoot).prGate.checklistDoc);\n const required = this.checklistDetector.toRequired(this.checklistDetector.detectForRepo(repoRoot, defs));\n // review-<id>.json files persist locally between runs, so a re-run after a push re-validates the\n // EXISTING verdicts against the (possibly changed) triggered set for free: an unchanged checklist\n // needs no re-review, a newly-triggered one refuses until its file is written. That is the\n // \"full review only when the checklist surface changes\" behavior — no special-casing here.\n const review = this.reviewJsonService.loadReviewJson(reviewJsonPath(repoRoot, this.aiBranchName.getFeatureName()), required);\n\n // 2c. For any BLOCK checklist that names a reviewer `subagent`, VERIFY (from the harness's own\n // artifacts) that such a subagent actually ran on this branch — the coding agent may not\n // self-certify. Absent CLAUDE_CODE_SESSION_ID this skips with a warning (CI / plain terminal).\n const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();\n const provenanceVerified = this.enforceProvenance(required, currentBranch);\n\n // 2b. The build gate validates the WORKING TREE but we push HEAD — so they MUST be identical.\n this.gitExec.assertCleanTree(repoRoot);\n\n // 3. Authoritative build gate, then post the gated body, then push (that ORDER — see GatedPrPublisher).\n this.buildAffected.runBuildGate(repoRoot, new BuildGateOptions(\n '🛠️ Build gate (authoritative)', 'pnpm wp-finish-upsert-pr', 'Build failed — no PR created/updated.',\n ));\n const base = this.branchNaming.baseBranchName(execSync('git branch --show-current', { encoding: 'utf8' }).trim());\n\n process.stdout.write('\\n' + SEP + '📋 Dashboard + PR\\n' + SEP + '\\n');\n const title = this.prTitleFrom(review);\n const input = this.computeDashboardInput(repoRoot, true, review, title, required);\n // Append the hidden HMAC gate token bound to the LOCAL HEAD sha — computed BEFORE the push, because\n // GatedPrPublisher writes the body first so CI's `synchronize` read can never see a stale token. A\n // valid token in the PR body is proof this gated flow ran + passed on this exact commit, which CI\n // (`wp-check-pr`) recomputes. We reach here only after the build gate + every BLOCK checklist\n // passed, so minting is legitimate. Nothing about HMAC(salt, HEAD) needs the remote to have it.\n const gateSalt = loadAndValidate(repoRoot).prGate.gateSalt;\n const headSha = this.gitOut(['rev-parse', 'HEAD']);\n const body = this.dashboard.renderDashboard(input) + this.gateTokenBody(gateSalt, headSha);\n const result = this.upsertPr(repoRoot, base, body, title, input);\n // Publish each reviewer's full output as ONE combined PR comment (idempotent, opt-out-aware). Never\n // fatal — the PR is already up by now, so a comment failure only warns.\n this.postChecklistComment(repoRoot, result.prNumber, input.checklists, provenanceVerified);\n const prNum = result.prNumber;\n\n process.stdout.write(\n '\\n' + SEP + '✅ PR finished — here is exactly what I did\\n' + SEP + '\\n' +\n ` 1. validated the build gate (authoritative)\\n` +\n ` 2. ${prNum ? `wrote the gated body to PR #${prNum}` : 'composed the gated PR body'} titled: \"${title}\"\\n` +\n ` 3. force-pushed your work to origin/${base} (after the body, so CI reads the right token)\\n` +\n ` 4. ${result.merge.message}\\n` +\n ` You are on ${base} — same name as the remote branch and the PR head.\\n\\n`,\n );\n }\n\n private gitOut(args: string[]): string {\n const result = spawnSync('git', args, { encoding: 'utf8' });\n return result.status === 0 ? (result.stdout ?? '').trim() : '';\n }\n\n // The user-facing PR title: the AI-authored review.title, or — if omitted — a readable fallback\n // derived from the stable feature name (NEVER the internal `Squash merge of <branch>` commit subject).\n private prTitleFrom(review: ReviewJson): string {\n if (review.title !== '') return review.title;\n return this.aiBranchName.getFeatureName().replace(/[-/]+/g, ' ').trim();\n }\n\n // eslint-disable-next-line @typescript-eslint/max-params\n private computeDashboardInput(repoRoot: string, buildPassed: boolean, review: ReviewJson, title: string, required: readonly RequiredChecklist[]): DashboardInput {\n const config = loadAndValidate(repoRoot).prGate;\n const forkPoint = this.gitOut(['merge-base', 'origin/main', 'HEAD']);\n const featureHead = this.gitOut(['rev-parse', 'HEAD']);\n const mainHead = this.gitOut(['rev-parse', 'origin/main']);\n const range = `${forkPoint}..${featureHead}`;\n const changedFiles = this.gitOut(['diff', range, '--name-only']).split('\\n').filter((f: string): boolean => f.trim() !== '');\n const patch = this.gitOut(['diff', range]);\n\n const gateResults = this.dashboard.computeGateResults(config.gates, changedFiles);\n const disables = this.dashboard.countAddedDisables(patch);\n const rows = this.checklistRows(required, review);\n return new DashboardInput(title, gateResults, disables, buildPassed, forkPoint, featureHead, mainHead, review, rows);\n }\n\n // Pair each matched checklist with its resolved verdict for the dashboard. (A checklist reaching this\n // point is always PASS/OVERRIDDEN — loadReviewJson already threw on FAIL/MISSING.)\n private checklistRows(required: readonly RequiredChecklist[], review: ReviewJson): ChecklistRow[] {\n return required.map((req: RequiredChecklist): ChecklistRow => {\n const verdict = this.reviewJsonService.resolveVerdict(req, review.results);\n return new ChecklistRow(req.id, verdict.status, verdict.detail);\n });\n }\n\n // Hidden HMAC gate-token marker (with a leading blank line) to append to the PR body, or '' when the\n // repo sets no gateSalt (byte-identical body to before this feature). Bound to the pushed HEAD sha.\n private gateTokenBody(gateSalt: string, headSha: string): string {\n const marker = this.gateTokenService.gateTokenMarker(gateSalt, headSha);\n return marker === '' ? '' : `\\n\\n${marker}\\n`;\n }\n\n // Enforce that EACH matched checklist was reviewed by its OWN named subagent, as a DISTINCT run —\n // the coding agent may not self-certify, and one reviewer may not stand in for several. A verified set\n // passes silently; no session id warns but passes; any missing reviewer throws so the PR does not open.\n private enforceProvenance(required: readonly RequiredChecklist[], branch: string): boolean {\n const errors: string[] = [];\n let verified = true; // no reviewers to verify ⇒ vacuously true\n const subagents = required.map((r: RequiredChecklist): string => r.subagent.trim()).filter((s: string): boolean => s !== '');\n if (subagents.length > 0) {\n const result = this.provenance.verifyDistinct(subagents, branch);\n verified = result.status === PROVENANCE_OK;\n if (result.status === PROVENANCE_MISSING) {\n errors.push(result.detail);\n } else if (result.status === PROVENANCE_SKIPPED) {\n process.stderr.write(`⚠️ ${result.detail}\\n`);\n }\n }\n if (errors.length > 0) {\n throw new InformAiError(\n `${errors.length} checklist(s) require an independent reviewer subagent that did not run — fix, then re-run pnpm wp-finish-upsert-pr:\\n\\n` +\n errors.map((e: string): string => ` • ${e}`).join('\\n') +\n `\\n\\nSpawn the named reviewer subagent to review the checklist on THIS branch, then re-run.`,\n );\n }\n return verified;\n }\n\n // Publish every reviewer's full `output` as ONE combined PR comment, idempotently (find the marker\n // comment → PATCH it, else POST). No-op when there is no PR number or no matched checklists. Never\n // fatal: by here the PR is already created/updated, so a `gh` failure only warns.\n private postChecklistComment(repoRoot: string, prNumber: string, rows: readonly ChecklistRow[], provenanceVerified: boolean): void {\n if (prNumber === '' || rows.length === 0) return;\n if (!loadAndValidate(repoRoot).prGate.checklistComments) return;\n const body = this.dashboard.renderChecklistComment(rows, provenanceVerified);\n const prDir = prDirFor(repoRoot, this.aiBranchName.getFeatureName());\n fs.mkdirSync(prDir, { recursive: true });\n const payload = path.join(prDir, 'checklist-comment.json');\n fs.writeFileSync(payload, JSON.stringify({ body }));\n const commentId = this.findChecklistCommentId(prNumber);\n const args = commentId !== ''\n ? ['api', '--method', 'PATCH', `repos/{owner}/{repo}/issues/comments/${commentId}`, '--input', payload]\n : ['api', '--method', 'POST', `repos/{owner}/{repo}/issues/${prNumber}/comments`, '--input', payload];\n const res = spawnSync('gh', args, { encoding: 'utf8' });\n if (res.status !== 0) {\n process.stderr.write('⚠️ Could not post the checklist review comment (non-fatal — the PR is already up).\\n');\n } else {\n process.stdout.write(` ${commentId !== '' ? 'updated' : 'posted'} the checklist review comment ✓\\n`);\n }\n }\n\n // The id of THIS tool's existing checklist comment on the PR (by the hidden marker), or '' if none.\n private findChecklistCommentId(prNumber: string): string {\n const res = spawnSync('gh', [\n 'api', '--paginate', `repos/{owner}/{repo}/issues/${prNumber}/comments`,\n '--jq', `.[] | select(.body | contains(\"${CHECKLIST_COMMENT_MARKER}\")) | .id`,\n ], { encoding: 'utf8' });\n if (res.status !== 0) return '';\n return (res.stdout ?? '').trim().split('\\n')[0] ?? '';\n }\n\n // The PR, the remote branch, and the local branch all share the one stable feature name. Look up /\n // create / merge against `baseBranch` (baseBranchName tolerates a leftover `…wpN` mid-transition).\n // GatedPrPublisher owns the edit/push/create half and its ORDERING — the gated body goes up before\n // the push, so CI's `synchronize` read cannot see the previous run's token.\n private upsertPr(repoRoot: string, baseBranch: string, body: string, title: string, input: DashboardInput): UpsertResult {\n const prDir = prDirFor(repoRoot, this.aiBranchName.getFeatureName());\n fs.mkdirSync(prDir, { recursive: true });\n const bodyFile = path.join(prDir, 'pr-body.md');\n fs.writeFileSync(bodyFile, body + '\\n');\n\n const published = this.publisher.publish(baseBranch, title, bodyFile);\n if (published.createFailed) {\n process.stderr.write('⚠️ gh pr create failed — create the PR manually with the body in:\\n ' + bodyFile + '\\n');\n return new UpsertResult('', new MergeOutcome(false, false,\n '⚠️ did NOT merge — there is no PR to merge (gh pr create failed above)'));\n }\n const num = published.number;\n\n // Set the squash-merge SUBJECT to the PR title (+ the `(#N)` GitHub normally appends, which an\n // explicit --subject would otherwise drop) and the BODY to the compact commit summary, so main's\n // history carries the PR title + risk/flags/link — NOT the internal `Squash merge of <branch>`\n // subject GitHub would inherit from the single squash commit on the branch.\n const ref = this.prRef(baseBranch);\n const subject = ref.number !== '' ? `${title} (#${ref.number})` : title;\n const mergeBodyFile = path.join(prDir, 'merge-commit-body.md');\n fs.writeFileSync(mergeBodyFile, this.dashboard.renderCommitBody(input, ref.url) + '\\n');\n // PrMerger owns the direct-merge / auto-merge-fallback decision AND checks every gh status, so a\n // merge that did not happen is reported as such instead of being swallowed (see pr-merger.ts).\n // REQUIRED config — no default here on purpose. A missing value (an older published\n // rules-config that has no such field) reaches PrMerger as '' and is treated as \"do not merge\".\n const mergeMode = loadAndValidate(repoRoot).prGate.mergeMode ?? '';\n const outcome = this.prMerger.merge(baseBranch, subject, mergeBodyFile, mergeMode);\n return new UpsertResult(ref.number !== '' ? ref.number : num, outcome);\n }\n\n // The PR's number + web URL (for the merge subject `(#N)` and the commit-body back-link). Both ''\n // if it can't be resolved. Rendered via jq into one tab-separated line so no JSON parsing is needed.\n private prRef(baseBranch: string): PrRef {\n const result = spawnSync(\n 'gh', ['pr', 'view', baseBranch, '--json', 'number,url', '--jq', '\"\\\\(.number)\\\\t\\\\(.url)\"'],\n { encoding: 'utf8' },\n );\n if (result.status !== 0) {\n return new PrRef('', '');\n }\n const parts = (result.stdout ?? '').trim().split('\\t');\n return new PrRef(parts[0] ?? '', parts[1] ?? '');\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"finish-upsert-pr-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/finish-upsert-pr-command.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 {\n loadAndValidate, prDirFor, reviewJsonPath, ReviewJson, RequiredChecklist,\n writeTemplate, RepoRootFinder, ReviewJsonService, ChecklistManifestService,\n GateTokenService, SubagentProvenanceService, PROVENANCE_OK, PROVENANCE_MISSING, PROVENANCE_SKIPPED,\n InformAiError,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BranchNaming } from '../workflow/branch-naming';\nimport { ChecklistDetector } from '../workflow/checklist-detector';\nimport { GitExec } from '../workflow/git-exec';\nimport { BuildAffected, BuildGateOptions } from '../workflow/build-affected';\nimport { MergeState } from '../workflow/merge-state';\nimport { MergeEnd, MergeEndOptions } from '../workflow/merge-end';\nimport { MergeContext } from '../workflow/merge-start';\nimport { PrMerger, MergeOutcome } from '../workflow/pr-merger';\nimport { GatedPrPublisher } from '../workflow/gated-pr-publisher';\nimport { Dashboard, DashboardInput, ChecklistRow, CHECKLIST_COMMENT_MARKER } from '../../dashboard/dashboard';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// A resolved PR's number + web URL. Both '' when the PR can't be resolved (e.g. create failed).\nclass PrRef {\n number: string;\n url: string;\n\n constructor(number: string, url: string) {\n this.number = number;\n this.url = url;\n }\n}\n\n// The outcome of the whole upsert: the PR number ('' when it could not be resolved) plus what actually\n// happened to the merge, so the final summary reports the REAL result rather than assuming success.\nclass UpsertResult {\n prNumber: string;\n merge: MergeOutcome;\n\n constructor(prNumber: string, merge: MergeOutcome) {\n this.prNumber = prNumber;\n this.merge = merge;\n }\n}\n\n// FINISH of the AI-first PR flow. Runs after the AI wrote review.json. In order: (1) if a 3-point merge\n// was in progress, validate + commit + FINALIZE via merge-END; (2) REQUIRE review.json; (3) run the\n// authoritative build gate; (4) render the dashboard; (5) create/update the PR via `gh`. The ONLY\n// command that posts PRs.\n@injectable(bindingScopeValues.Singleton)\nexport class FinishUpsertPrCommand {\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 buildAffected: BuildAffected,\n private readonly mergeState: MergeState,\n private readonly mergeEnd: MergeEnd,\n private readonly prMerger: PrMerger,\n private readonly publisher: GatedPrPublisher,\n private readonly dashboard: Dashboard,\n private readonly checklistDetector: ChecklistDetector,\n private readonly manifestService: ChecklistManifestService,\n private readonly reviewJsonService: ReviewJsonService,\n private readonly gateTokenService: GateTokenService,\n private readonly provenance: SubagentProvenanceService,\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 // 1. Finish any in-progress conflict resolution: validate + commit + finalize the branch swap.\n await this.finalizeAnyInProgressMerge(repoRoot);\n\n // 2. REQUIRE the AI-authored review.json (throws InformAiError with the schema if missing/invalid).\n // Compute the consumer checklists this diff triggered FIRST so an unacknowledged BLOCK throws\n // here — BEFORE any `gh pr create` — matching the guarantee buildCommand already provides.\n const defs = this.manifestService.load(repoRoot, loadAndValidate(repoRoot).prGate.checklistDoc);\n const required = this.checklistDetector.toRequired(this.checklistDetector.detectForRepo(repoRoot, defs));\n // review-<id>.json files persist locally between runs, so a re-run after a push re-validates the\n // EXISTING verdicts against the (possibly changed) triggered set for free: an unchanged checklist\n // needs no re-review, a newly-triggered one refuses until its file is written. That is the\n // \"full review only when the checklist surface changes\" behavior — no special-casing here.\n const review = this.reviewJsonService.loadReviewJson(reviewJsonPath(repoRoot, this.aiBranchName.getFeatureName()), required);\n\n // 2c. For any BLOCK checklist that names a reviewer `subagent`, VERIFY (from the harness's own\n // artifacts) that such a subagent actually ran on this branch — the coding agent may not\n // self-certify. Absent CLAUDE_CODE_SESSION_ID this skips with a warning (CI / plain terminal).\n const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();\n const provenanceVerified = this.enforceProvenance(required, currentBranch);\n\n // 2b. The build gate validates the WORKING TREE but we push HEAD — so they MUST be identical.\n this.gitExec.assertCleanTree(repoRoot);\n\n // 3. Authoritative build gate, then post the gated body, then push (that ORDER — see GatedPrPublisher).\n this.buildAffected.runBuildGate(repoRoot, new BuildGateOptions(\n '🛠️ Build gate (authoritative)', 'pnpm wp-finish-upsert-pr', 'Build failed — no PR created/updated.',\n ));\n const base = this.branchNaming.baseBranchName(execSync('git branch --show-current', { encoding: 'utf8' }).trim());\n\n process.stdout.write('\\n' + SEP + '📋 Dashboard + PR\\n' + SEP + '\\n');\n const title = this.prTitleFrom(review);\n const input = this.computeDashboardInput(repoRoot, true, review, title, required);\n // Append the hidden HMAC gate token bound to the LOCAL HEAD sha — computed BEFORE the push, because\n // GatedPrPublisher writes the body first so CI's `synchronize` read can never see a stale token. A\n // valid token in the PR body is proof this gated flow ran + passed on this exact commit, which CI\n // (`wp-check-pr`) recomputes. We reach here only after the build gate + every BLOCK checklist\n // passed, so minting is legitimate. Nothing about HMAC(salt, HEAD) needs the remote to have it.\n const gateSalt = loadAndValidate(repoRoot).prGate.gateSalt;\n const headSha = this.gitOut(['rev-parse', 'HEAD']);\n const body = this.dashboard.renderDashboard(input) + this.gateTokenBody(gateSalt, headSha);\n const result = this.upsertPr(repoRoot, base, body, title, input);\n // Publish each reviewer's full output as ONE combined PR comment (idempotent, opt-out-aware). Never\n // fatal — the PR is already up by now, so a comment failure only warns.\n this.postChecklistComment(repoRoot, result.prNumber, input.checklists, provenanceVerified);\n const prNum = result.prNumber;\n\n process.stdout.write(\n '\\n' + SEP + '✅ PR finished — here is exactly what I did\\n' + SEP + '\\n' +\n ` 1. validated the build gate (authoritative)\\n` +\n ` 2. ${prNum ? `wrote the gated body to PR #${prNum}` : 'composed the gated PR body'} titled: \"${title}\"\\n` +\n ` 3. force-pushed your work to origin/${base} (after the body, so CI reads the right token)\\n` +\n ` 4. ${result.merge.message}\\n` +\n ` You are on ${base} — same name as the remote branch and the PR head.\\n\\n`,\n );\n }\n\n // Validate + commit + finalize a 3-point merge the AI resolved, if one is in progress. Finalizing here\n // does NOT push (pushRemote=false): this command pushes exactly ONCE, from GatedPrPublisher, and only\n // after review.json + every BLOCK checklist + the build gate pass and the gated PR body is written.\n private async finalizeAnyInProgressMerge(repoRoot: string): Promise<void> {\n const home = this.mergeState.mergeDirFor(repoRoot, this.aiBranchName.getFeatureName());\n const activeDir = this.mergeState.findActiveMergeRunDir(home);\n const marker = activeDir ? this.mergeState.readMergeMarker(activeDir) : null;\n if (!activeDir || !marker || marker.validated) return;\n await this.mergeEnd.mergeEnd(\n repoRoot, 'wp-finish-upsert-pr', activeDir,\n new MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber),\n new MergeEndOptions(marker.conflictedFiles, false),\n );\n }\n\n private gitOut(args: string[]): string {\n const result = spawnSync('git', args, { encoding: 'utf8' });\n return result.status === 0 ? (result.stdout ?? '').trim() : '';\n }\n\n // The user-facing PR title: the AI-authored review.title, or — if omitted — a readable fallback\n // derived from the stable feature name (NEVER the internal `Squash merge of <branch>` commit subject).\n private prTitleFrom(review: ReviewJson): string {\n if (review.title !== '') return review.title;\n return this.aiBranchName.getFeatureName().replace(/[-/]+/g, ' ').trim();\n }\n\n // eslint-disable-next-line @typescript-eslint/max-params\n private computeDashboardInput(repoRoot: string, buildPassed: boolean, review: ReviewJson, title: string, required: readonly RequiredChecklist[]): DashboardInput {\n const config = loadAndValidate(repoRoot).prGate;\n const forkPoint = this.gitOut(['merge-base', 'origin/main', 'HEAD']);\n const featureHead = this.gitOut(['rev-parse', 'HEAD']);\n const mainHead = this.gitOut(['rev-parse', 'origin/main']);\n const range = `${forkPoint}..${featureHead}`;\n const changedFiles = this.gitOut(['diff', range, '--name-only']).split('\\n').filter((f: string): boolean => f.trim() !== '');\n const patch = this.gitOut(['diff', range]);\n\n const gateResults = this.dashboard.computeGateResults(config.gates, changedFiles);\n const disables = this.dashboard.countAddedDisables(patch);\n const rows = this.checklistRows(required, review);\n return new DashboardInput(title, gateResults, disables, buildPassed, forkPoint, featureHead, mainHead, review, rows);\n }\n\n // Pair each matched checklist with its resolved verdict for the dashboard. (A checklist reaching this\n // point is always PASS/OVERRIDDEN — loadReviewJson already threw on FAIL/MISSING.)\n private checklistRows(required: readonly RequiredChecklist[], review: ReviewJson): ChecklistRow[] {\n return required.map((req: RequiredChecklist): ChecklistRow => {\n const verdict = this.reviewJsonService.resolveVerdict(req, review.results);\n return new ChecklistRow(req.id, verdict.status, verdict.detail);\n });\n }\n\n // Hidden HMAC gate-token marker (with a leading blank line) to append to the PR body, or '' when the\n // repo sets no gateSalt (byte-identical body to before this feature). Bound to the pushed HEAD sha.\n private gateTokenBody(gateSalt: string, headSha: string): string {\n const marker = this.gateTokenService.gateTokenMarker(gateSalt, headSha);\n return marker === '' ? '' : `\\n\\n${marker}\\n`;\n }\n\n // Enforce that EACH matched checklist was reviewed by its OWN named subagent, as a DISTINCT run —\n // the coding agent may not self-certify, and one reviewer may not stand in for several. A verified set\n // passes silently; no session id warns but passes; any missing reviewer throws so the PR does not open.\n private enforceProvenance(required: readonly RequiredChecklist[], branch: string): boolean {\n const errors: string[] = [];\n let verified = true; // no reviewers to verify ⇒ vacuously true\n const subagents = required.map((r: RequiredChecklist): string => r.subagent.trim()).filter((s: string): boolean => s !== '');\n if (subagents.length > 0) {\n const result = this.provenance.verifyDistinct(subagents, branch);\n verified = result.status === PROVENANCE_OK;\n if (result.status === PROVENANCE_MISSING) {\n errors.push(result.detail);\n } else if (result.status === PROVENANCE_SKIPPED) {\n process.stderr.write(`⚠️ ${result.detail}\\n`);\n }\n }\n if (errors.length > 0) {\n throw new InformAiError(\n `${errors.length} checklist(s) require an independent reviewer subagent that did not run — fix, then re-run pnpm wp-finish-upsert-pr:\\n\\n` +\n errors.map((e: string): string => ` • ${e}`).join('\\n') +\n `\\n\\nSpawn the named reviewer subagent to review the checklist on THIS branch, then re-run.`,\n );\n }\n return verified;\n }\n\n // Publish every reviewer's full `output` as ONE combined PR comment, idempotently (find the marker\n // comment → PATCH it, else POST). No-op when there is no PR number or no matched checklists. Never\n // fatal: by here the PR is already created/updated, so a `gh` failure only warns.\n private postChecklistComment(repoRoot: string, prNumber: string, rows: readonly ChecklistRow[], provenanceVerified: boolean): void {\n if (prNumber === '' || rows.length === 0) return;\n if (!loadAndValidate(repoRoot).prGate.checklistComments) return;\n const body = this.dashboard.renderChecklistComment(rows, provenanceVerified);\n const prDir = prDirFor(repoRoot, this.aiBranchName.getFeatureName());\n fs.mkdirSync(prDir, { recursive: true });\n const payload = path.join(prDir, 'checklist-comment.json');\n fs.writeFileSync(payload, JSON.stringify({ body }));\n const commentId = this.findChecklistCommentId(prNumber);\n const args = commentId !== ''\n ? ['api', '--method', 'PATCH', `repos/{owner}/{repo}/issues/comments/${commentId}`, '--input', payload]\n : ['api', '--method', 'POST', `repos/{owner}/{repo}/issues/${prNumber}/comments`, '--input', payload];\n const res = spawnSync('gh', args, { encoding: 'utf8' });\n if (res.status !== 0) {\n process.stderr.write('⚠️ Could not post the checklist review comment (non-fatal — the PR is already up).\\n');\n } else {\n process.stdout.write(` ${commentId !== '' ? 'updated' : 'posted'} the checklist review comment ✓\\n`);\n }\n }\n\n // The id of THIS tool's existing checklist comment on the PR (by the hidden marker), or '' if none.\n private findChecklistCommentId(prNumber: string): string {\n const res = spawnSync('gh', [\n 'api', '--paginate', `repos/{owner}/{repo}/issues/${prNumber}/comments`,\n '--jq', `.[] | select(.body | contains(\"${CHECKLIST_COMMENT_MARKER}\")) | .id`,\n ], { encoding: 'utf8' });\n if (res.status !== 0) return '';\n return (res.stdout ?? '').trim().split('\\n')[0] ?? '';\n }\n\n // The PR, the remote branch, and the local branch all share the one stable feature name. Look up /\n // create / merge against `baseBranch` (baseBranchName tolerates a leftover `…wpN` mid-transition).\n // GatedPrPublisher owns the edit/push/create half and its ORDERING — the gated body goes up before\n // the push, so CI's `synchronize` read cannot see the previous run's token.\n private upsertPr(repoRoot: string, baseBranch: string, body: string, title: string, input: DashboardInput): UpsertResult {\n const prDir = prDirFor(repoRoot, this.aiBranchName.getFeatureName());\n fs.mkdirSync(prDir, { recursive: true });\n const bodyFile = path.join(prDir, 'pr-body.md');\n fs.writeFileSync(bodyFile, body + '\\n');\n\n const published = this.publisher.publish(baseBranch, title, bodyFile);\n if (published.createFailed) {\n process.stderr.write('⚠️ gh pr create failed — create the PR manually with the body in:\\n ' + bodyFile + '\\n');\n return new UpsertResult('', new MergeOutcome(false, false,\n '⚠️ did NOT merge — there is no PR to merge (gh pr create failed above)'));\n }\n const num = published.number;\n\n // Set the squash-merge SUBJECT to the PR title (+ the `(#N)` GitHub normally appends, which an\n // explicit --subject would otherwise drop) and the BODY to the compact commit summary, so main's\n // history carries the PR title + risk/flags/link — NOT the internal `Squash merge of <branch>`\n // subject GitHub would inherit from the single squash commit on the branch.\n const ref = this.prRef(baseBranch);\n const subject = ref.number !== '' ? `${title} (#${ref.number})` : title;\n const mergeBodyFile = path.join(prDir, 'merge-commit-body.md');\n fs.writeFileSync(mergeBodyFile, this.dashboard.renderCommitBody(input, ref.url) + '\\n');\n // PrMerger owns the direct-merge / auto-merge-fallback decision AND checks every gh status, so a\n // merge that did not happen is reported as such instead of being swallowed (see pr-merger.ts).\n // REQUIRED config — no default here on purpose. A missing value (an older published\n // rules-config that has no such field) reaches PrMerger as '' and is treated as \"do not merge\".\n const mergeMode = loadAndValidate(repoRoot).prGate.mergeMode ?? '';\n const outcome = this.prMerger.merge(baseBranch, subject, mergeBodyFile, mergeMode);\n return new UpsertResult(ref.number !== '' ? ref.number : num, outcome);\n }\n\n // The PR's number + web URL (for the merge subject `(#N)` and the commit-body back-link). Both ''\n // if it can't be resolved. Rendered via jq into one tab-separated line so no JSON parsing is needed.\n private prRef(baseBranch: string): PrRef {\n const result = spawnSync(\n 'gh', ['pr', 'view', baseBranch, '--json', 'number,url', '--jq', '\"\\\\(.number)\\\\t\\\\(.url)\"'],\n { encoding: 'utf8' },\n );\n if (result.status !== 0) {\n return new PrRef('', '');\n }\n const parts = (result.stdout ?? '').trim().split('\\t');\n return new PrRef(parts[0] ?? '', parts[1] ?? '');\n }\n}\n"]}
|
|
@@ -28,7 +28,9 @@ let StartUpdateCommand = class StartUpdateCommand {
|
|
|
28
28
|
// An open PR means the PR flow must be used — a 3-point update would strand the PR on the old
|
|
29
29
|
// branch generation. Fails fast if GitHub can't be reached (never guesses "no PR").
|
|
30
30
|
this.assertNoOpenPr(repoRoot);
|
|
31
|
-
|
|
31
|
+
// pushRemote=true: this is the END of the update-only flow — no later command pushes, so not
|
|
32
|
+
// pushing here would silently strand the squash rewrite locally. (The PR flow passes false.)
|
|
33
|
+
const outcome = await this.runUpdate.runUpdateFromMain(repoRoot, 'wp-start-update', 'wp-finish-update', true);
|
|
32
34
|
if (outcome === 'conflict') {
|
|
33
35
|
// Context + marker + merge process doc already written by merge-start; hand back to the AI.
|
|
34
36
|
throw new rules_config_1.CliExitError(2, '');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start-update-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/start-update-command.ts"],"names":[],"mappings":";;;;AAAA,iDAAyC;AACzC,0DAAsF;AACtF,yCAA2D;AAC3D,6DAAwD;AACxD,uDAAmD;AAEnD,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAEvE,8FAA8F;AAC9F,sGAAsG;AACtG,sGAAsG;AACtG,kDAAkD;AAE3C,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;IAEN;IACA;IACA;IAHrB,YACqB,cAA8B,EAC9B,WAAwB,EACxB,SAAoB;QAFpB,mBAAc,GAAd,cAAc,CAAgB;QAC9B,gBAAW,GAAX,WAAW,CAAa;QACxB,cAAS,GAAT,SAAS,CAAW;IACtC,CAAC;IAEJ,KAAK,CAAC,GAAG;QACL,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACpE,gGAAgG;QAChG,IAAA,4BAAa,EAAC,QAAQ,EAAE,2BAA2B,CAAC,CAAC;QAErD,8FAA8F;QAC9F,oFAAoF;QACpF,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QAE9B,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"start-update-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/start-update-command.ts"],"names":[],"mappings":";;;;AAAA,iDAAyC;AACzC,0DAAsF;AACtF,yCAA2D;AAC3D,6DAAwD;AACxD,uDAAmD;AAEnD,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAEvE,8FAA8F;AAC9F,sGAAsG;AACtG,sGAAsG;AACtG,kDAAkD;AAE3C,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;IAEN;IACA;IACA;IAHrB,YACqB,cAA8B,EAC9B,WAAwB,EACxB,SAAoB;QAFpB,mBAAc,GAAd,cAAc,CAAgB;QAC9B,gBAAW,GAAX,WAAW,CAAa;QACxB,cAAS,GAAT,SAAS,CAAW;IACtC,CAAC;IAEJ,KAAK,CAAC,GAAG;QACL,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACpE,gGAAgG;QAChG,IAAA,4BAAa,EAAC,QAAQ,EAAE,2BAA2B,CAAC,CAAC;QAErD,8FAA8F;QAC9F,oFAAoF;QACpF,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QAE9B,6FAA6F;QAC7F,6FAA6F;QAC7F,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,IAAI,CAAC,CAAC;QAC9G,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;YACzB,4FAA4F;YAC5F,MAAM,IAAI,2BAAY,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAClC,CAAC;QACD,IAAI,OAAO,KAAK,mBAAmB,EAAE,CAAC;YAClC,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,IAAI,GAAG,GAAG,GAAG,6CAA6C,GAAG,GAAG,GAAG,IAAI;gBACvE,kEAAkE;gBAClE,2BAA2B,CAC9B,CAAC;QACN,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oEAAoE,CAAC,CAAC;IAC/F,CAAC;IAED,2FAA2F;IAC3F,mGAAmG;IACnG,kGAAkG;IAClG,wDAAwD;IACxD,cAAc,CAAC,QAAgB;QAC3B,MAAM,MAAM,GAAG,IAAA,wBAAQ,EAAC,iCAAiC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACvG,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAClE,IAAI,MAAM,KAAK,EAAE;YAAE,OAAO;QAC1B,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,IAAI,GAAG,GAAG,GAAG,kBAAkB,MAAM,gCAAgC,GAAG,GAAG,GAAG,IAAI;YAClF,kGAAkG;YAClG,+BAA+B,MAAM,6DAA6D;YAClG,wDAAwD;YACxD,gCAAgC;YAChC,wCAAwC;YACxC,iCAAiC,CACpC,CAAC;IACN,CAAC;CACJ,CAAA;AAnDY,gDAAkB;6BAAlB,kBAAkB;IAD9B,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAGA,6BAAc;QACjB,2BAAW;QACb,sBAAS;GAJhC,kBAAkB,CAmD9B","sourcesContent":["import { execSync } from 'child_process';\nimport { writeTemplate, CliExitError, RepoRootFinder } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { OpenPrCheck } from '../workflow/open-pr-check';\nimport { RunUpdate } from '../workflow/run-update';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// wp-start-update: the \"sync this feature branch from main\" entry (the redirect target of the\n// redirect-how-to-merge-main guard). Runs the FULL 3-point squash-update via RunUpdate: a CLEAN merge\n// finalizes everything; CONFLICT writes the 3-point context + merge process doc and hands back. NEVER\n// creates a PR (that is the wp-*-upsert-pr flow).\n@injectable(bindingScopeValues.Singleton)\nexport class StartUpdateCommand {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly openPrCheck: OpenPrCheck,\n private readonly runUpdate: RunUpdate,\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\n // An open PR means the PR flow must be used — a 3-point update would strand the PR on the old\n // branch generation. Fails fast if GitHub can't be reached (never guesses \"no PR\").\n this.assertNoOpenPr(repoRoot);\n\n // pushRemote=true: this is the END of the update-only flow — no later command pushes, so not\n // pushing here would silently strand the squash rewrite locally. (The PR flow passes false.)\n const outcome = await this.runUpdate.runUpdateFromMain(repoRoot, 'wp-start-update', 'wp-finish-update', true);\n if (outcome === 'conflict') {\n // Context + marker + merge process doc already written by merge-start; hand back to the AI.\n throw new CliExitError(2, '');\n }\n if (outcome === 'unvalidatedResume') {\n throw new CliExitError(1,\n '\\n' + SEP + '⏸️ Merge in progress — not yet validated\\n' + SEP + '\\n' +\n 'Resolve the remaining conflicts in the working tree, then run:\\n' +\n ' pnpm wp-finish-update\\n',\n );\n }\n process.stdout.write('\\n✅ Updated from main — clean. No need to call wp-finish-update.\\n');\n }\n\n // If a PR is already OPEN for this branch, wp-start-update must refuse. The 3-point update\n // re-squashes your branch onto main and force-pushes it to origin/<feature> — the PR head — but it\n // does NOT refresh the PR body/review or run the authoritative build gate. Fail fast and steer to\n // the PR flow, which re-merges main AND updates the PR.\n assertNoOpenPr(repoRoot: string): void {\n const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: repoRoot, encoding: 'utf8' }).trim();\n const openPr = this.openPrCheck.openPrForBranch(repoRoot, branch);\n if (openPr === '') return;\n throw new CliExitError(2,\n '\\n' + SEP + `⛔ An open PR (#${openPr}) already tracks this branch\\n` + SEP + '\\n' +\n 'wp-start-update re-squashes your branch onto main and force-pushes it, but it does NOT refresh\\n' +\n `the PR body/review — so PR #${openPr} would get new code without an updated review. Use the PR\\n` +\n 'flow instead — it re-merges main AND updates the PR:\\n' +\n ' 1. pnpm wp-start-upsert-pr\\n' +\n ' 2. /wp-merge (only if conflicts)\\n' +\n ' 3. pnpm wp-finish-upsert-pr\\n',\n );\n }\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { RepoRootFinder, ChecklistManifestService, ReviewJsonService, DiffScope } from '@webpieces/rules-config';
|
|
2
|
+
import { ChecklistNotice } from '../workflow/checklist-notice';
|
|
2
3
|
import { AiBranchName } from '../workflow/git-readAiBranchName';
|
|
3
4
|
import { BranchNaming } from '../workflow/branch-naming';
|
|
4
5
|
import { BuildAffected } from '../workflow/build-affected';
|
|
@@ -16,11 +17,14 @@ export declare class StartUpsertPrCommand {
|
|
|
16
17
|
private readonly manifestService;
|
|
17
18
|
private readonly reviewJsonService;
|
|
18
19
|
private readonly diffScope;
|
|
19
|
-
|
|
20
|
+
private readonly checklistNotice;
|
|
21
|
+
constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, branchNaming: BranchNaming, buildAffected: BuildAffected, gitExec: GitExec, runUpdate: RunUpdate, checklistDetector: ChecklistDetector, manifestService: ChecklistManifestService, reviewJsonService: ReviewJsonService, diffScope: DiffScope, checklistNotice: ChecklistNotice);
|
|
20
22
|
run(): Promise<void>;
|
|
23
|
+
private handOffToReview;
|
|
21
24
|
private writePrContext;
|
|
22
25
|
private alreadyReviewed;
|
|
23
26
|
private printChecklistPlan;
|
|
27
|
+
private printEmptyChecklistNotice;
|
|
24
28
|
private scaffoldCiWorkflow;
|
|
25
29
|
private updateBranchFromMain;
|
|
26
30
|
}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.StartUpsertPrCommand = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
const child_process_1 = require("child_process");
|
|
6
5
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
6
|
+
const checklist_notice_1 = require("../workflow/checklist-notice");
|
|
7
7
|
const inversify_1 = require("inversify");
|
|
8
8
|
const git_readAiBranchName_1 = require("../workflow/git-readAiBranchName");
|
|
9
9
|
const branch_naming_1 = require("../workflow/branch-naming");
|
|
@@ -12,9 +12,10 @@ const checklist_detector_1 = require("../workflow/checklist-detector");
|
|
|
12
12
|
const git_exec_1 = require("../workflow/git-exec");
|
|
13
13
|
const run_update_1 = require("../workflow/run-update");
|
|
14
14
|
const SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
|
|
15
|
-
// START of the AI-first PR flow: the deterministic setup — update from main,
|
|
16
|
-
//
|
|
17
|
-
//
|
|
15
|
+
// START of the AI-first PR flow: the deterministic setup — update from main, run the advisory build gate
|
|
16
|
+
// — then hand the AI instructions to WRITE review.json and run `wp-finish-upsert-pr` (which reads it and
|
|
17
|
+
// posts the PR). This command NEVER creates/updates a PR and NEVER pushes: all `gh` posting and the ONE
|
|
18
|
+
// push live in finish, behind review.json + the checklists + the authoritative build gate.
|
|
18
19
|
let StartUpsertPrCommand = class StartUpsertPrCommand {
|
|
19
20
|
repoRootFinder;
|
|
20
21
|
aiBranchName;
|
|
@@ -26,7 +27,8 @@ let StartUpsertPrCommand = class StartUpsertPrCommand {
|
|
|
26
27
|
manifestService;
|
|
27
28
|
reviewJsonService;
|
|
28
29
|
diffScope;
|
|
29
|
-
|
|
30
|
+
checklistNotice;
|
|
31
|
+
constructor(repoRootFinder, aiBranchName, branchNaming, buildAffected, gitExec, runUpdate, checklistDetector, manifestService, reviewJsonService, diffScope, checklistNotice) {
|
|
30
32
|
this.repoRootFinder = repoRootFinder;
|
|
31
33
|
this.aiBranchName = aiBranchName;
|
|
32
34
|
this.branchNaming = branchNaming;
|
|
@@ -37,6 +39,7 @@ let StartUpsertPrCommand = class StartUpsertPrCommand {
|
|
|
37
39
|
this.manifestService = manifestService;
|
|
38
40
|
this.reviewJsonService = reviewJsonService;
|
|
39
41
|
this.diffScope = diffScope;
|
|
42
|
+
this.checklistNotice = checklistNotice;
|
|
40
43
|
}
|
|
41
44
|
async run() {
|
|
42
45
|
const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
|
|
@@ -47,19 +50,24 @@ let StartUpsertPrCommand = class StartUpsertPrCommand {
|
|
|
47
50
|
// before the clean-tree check) and tell the human to copy + require it. IfMissing so it is written
|
|
48
51
|
// once and never clobbers a customized copy.
|
|
49
52
|
this.scaffoldCiWorkflow(repoRoot);
|
|
50
|
-
// Precondition: a fully-committed tree. This flow updates
|
|
51
|
-
// must not commit your work for you, and
|
|
52
|
-
//
|
|
53
|
+
// Precondition: a fully-committed tree. This flow squash-updates the branch and builds it — the
|
|
54
|
+
// tooling must not commit your work for you, and building a dirty working tree would let an
|
|
55
|
+
// uncommitted change build green over a different commit than the one that ships. Fail if dirty.
|
|
53
56
|
this.gitExec.assertCleanTree(repoRoot);
|
|
57
|
+
// Nothing here pushes. This command reviews; wp-finish-upsert-pr pushes ONCE, after review.json,
|
|
58
|
+
// every BLOCK checklist, and the authoritative build gate — so no unreviewed commit reaches the
|
|
59
|
+
// remote, and there is no early `synchronize` firing against a PR body with a stale gate token.
|
|
54
60
|
await this.updateBranchFromMain(repoRoot);
|
|
55
|
-
// Local branch, remote branch, and PR share the one stable feature name.
|
|
56
|
-
this.gitExec.ensurePushed(this.branchNaming.baseBranchName((0, child_process_1.execSync)('git branch --show-current', { encoding: 'utf8' }).trim()));
|
|
57
61
|
// Advisory build gate — early feedback before the AI writes review.json. wp-finish-upsert-pr
|
|
58
62
|
// runs the authoritative one. Both go through the same runBuildGate (only the framing differs).
|
|
59
63
|
this.buildAffected.runBuildGate(repoRoot, new build_affected_1.BuildGateOptions('② Build gate (nx affected)', 'pnpm wp-start-upsert-pr', 'Build failed — fix it before reviewing.'));
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
this.handOffToReview(repoRoot);
|
|
65
|
+
}
|
|
66
|
+
// Hand the AI its next step: write review.json, then run finish (which posts the PR). Computes the
|
|
67
|
+
// checklists this diff MATCHED (from the manifest doc) so the hint names each reviewer subagent to
|
|
68
|
+
// spawn BEFORE review.json is written — and, when NOTHING matched, says so instead of staying silent
|
|
69
|
+
// (zero checklists is a supported state, never a blocker — see ChecklistNotice).
|
|
70
|
+
handOffToReview(repoRoot) {
|
|
63
71
|
const defs = this.manifestService.load(repoRoot, (0, rules_config_1.loadAndValidate)(repoRoot).prGate.checklistDoc);
|
|
64
72
|
const triggered = this.checklistDetector.detectForRepo(repoRoot, defs);
|
|
65
73
|
const required = this.checklistDetector.toRequired(triggered);
|
|
@@ -74,9 +82,10 @@ let StartUpsertPrCommand = class StartUpsertPrCommand {
|
|
|
74
82
|
// Persist the PR diff context (base sha + changed files) so reviewer subagents can `git diff` for
|
|
75
83
|
// content instead of the tooling matching on regexes. Written whenever a base resolves.
|
|
76
84
|
this.writePrContext(repoRoot);
|
|
77
|
-
this.printChecklistPlan(triggered, toReview);
|
|
85
|
+
this.printChecklistPlan(repoRoot, defs, triggered, toReview);
|
|
78
86
|
process.stdout.write('\n' + SEP + '③ Review the PR, then finish\n' + SEP + '\n');
|
|
79
|
-
process.stdout.write(`Branch is updated
|
|
87
|
+
process.stdout.write(`Branch is updated and the build gate passed (nothing pushed yet — finish does the one push).\n` +
|
|
88
|
+
`Now review your own changes and\n` +
|
|
80
89
|
`${(0, rules_config_1.reviewJsonSchemaHint)(reviewPath, toReview)}\n\n` +
|
|
81
90
|
`Then run: pnpm wp-finish-upsert-pr\n` +
|
|
82
91
|
`(It re-validates the build, renders the dashboard with your risk/violations, and creates/updates the PR.)\n\n`);
|
|
@@ -102,9 +111,11 @@ let StartUpsertPrCommand = class StartUpsertPrCommand {
|
|
|
102
111
|
}
|
|
103
112
|
// Show which matched checklists still need a reviewer subagent (spawn each as a distinct one) and which
|
|
104
113
|
// are already reviewed (reused, not re-run) — so a second cycle re-instructs nothing.
|
|
105
|
-
printChecklistPlan(triggered, toReview) {
|
|
106
|
-
if (triggered.length === 0)
|
|
114
|
+
printChecklistPlan(repoRoot, defs, triggered, toReview) {
|
|
115
|
+
if (triggered.length === 0) {
|
|
116
|
+
this.printEmptyChecklistNotice(repoRoot, defs);
|
|
107
117
|
return;
|
|
118
|
+
}
|
|
108
119
|
process.stdout.write('\n' + SEP + '📋 Review checklists\n' + SEP + '\n');
|
|
109
120
|
const toReviewIds = new Set(toReview.map((r) => r.id));
|
|
110
121
|
const reused = triggered.filter((t) => !toReviewIds.has(t.def.id));
|
|
@@ -124,6 +135,16 @@ let StartUpsertPrCommand = class StartUpsertPrCommand {
|
|
|
124
135
|
}
|
|
125
136
|
process.stdout.write('See .webpieces/instruct-ai/webpieces.review-checklists.md for the review-<id>.json format each must write.\n');
|
|
126
137
|
}
|
|
138
|
+
// ZERO checklists matched. Say so — this used to print nothing at all, which reads exactly like "the
|
|
139
|
+
// checklist ran and passed". Purely informational: 0 is a supported state and never blocks finishing.
|
|
140
|
+
// validate() is called ONLY here (the runtime load() is deliberately tolerant), so a missing/malformed
|
|
141
|
+
// checklist doc stops being a silent no-op and gets reported.
|
|
142
|
+
printEmptyChecklistNotice(repoRoot, defs) {
|
|
143
|
+
const docRel = (0, rules_config_1.loadAndValidate)(repoRoot).prGate.checklistDoc;
|
|
144
|
+
const errors = this.manifestService.validate(repoRoot, docRel);
|
|
145
|
+
process.stdout.write('\n' + SEP + '📋 Review checklists\n' + SEP + '\n');
|
|
146
|
+
process.stdout.write(this.checklistNotice.build(docRel, errors, defs.length, 'wp-finish-upsert-pr'));
|
|
147
|
+
}
|
|
127
148
|
// Scaffold the server-side CI check when (and only when) this repo set a gateSalt. Written to the
|
|
128
149
|
// gitignored instruct-ai dir so it never dirties the tree; the human copies it to .github/workflows
|
|
129
150
|
// and marks it required (webpieces can't set branch protection). No-op for repos with no gateSalt.
|
|
@@ -139,7 +160,8 @@ let StartUpsertPrCommand = class StartUpsertPrCommand {
|
|
|
139
160
|
// merge process doc it writes names `wp-finish-upsert-pr` as the finish command.
|
|
140
161
|
async updateBranchFromMain(repoRoot) {
|
|
141
162
|
process.stdout.write('\n' + SEP + '① Updating branch from main\n' + SEP + '\n');
|
|
142
|
-
|
|
163
|
+
// pushRemote=false — finish owns the single push (see MergeEndOptions).
|
|
164
|
+
const outcome = await this.runUpdate.runUpdateFromMain(repoRoot, 'wp-start-upsert-pr', 'wp-finish-upsert-pr', false);
|
|
143
165
|
if (outcome === 'conflict' || outcome === 'unvalidatedResume') {
|
|
144
166
|
throw new rules_config_1.CliExitError(2, '\n⏸️ Conflicts — resolve them, then run pnpm wp-finish-upsert-pr (it validates the merge AND finishes the PR).');
|
|
145
167
|
}
|
|
@@ -157,6 +179,7 @@ exports.StartUpsertPrCommand = StartUpsertPrCommand = tslib_1.__decorate([
|
|
|
157
179
|
checklist_detector_1.ChecklistDetector,
|
|
158
180
|
rules_config_1.ChecklistManifestService,
|
|
159
181
|
rules_config_1.ReviewJsonService,
|
|
160
|
-
rules_config_1.DiffScope
|
|
182
|
+
rules_config_1.DiffScope,
|
|
183
|
+
checklist_notice_1.ChecklistNotice])
|
|
161
184
|
], StartUpsertPrCommand);
|
|
162
185
|
//# 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, ChecklistManifestService, ReviewJsonService, DiffScope, ChangedFilesOptions, PrContext, RequiredChecklist, ChecklistResult, CK_PASS, CK_OVERRIDDEN } from '@webpieces/rules-config';\nimport { TriggeredChecklist } from '../workflow/checklist-detector';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BranchNaming } from '../workflow/branch-naming';\nimport { BuildAffected, BuildGateOptions } from '../workflow/build-affected';\nimport { ChecklistDetector } from '../workflow/checklist-detector';\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, push, run the advisory\n// build gate — then hand the AI instructions to WRITE review.json and run `wp-finish-upsert-pr` (which\n// reads it and posts the PR). This command NEVER creates/updates a PR; all `gh` posting lives in finish.\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 buildAffected: BuildAffected,\n private readonly gitExec: GitExec,\n private readonly runUpdate: RunUpdate,\n private readonly checklistDetector: ChecklistDetector,\n private readonly manifestService: ChecklistManifestService,\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 updates, pushes HEAD, and builds — the tooling\n // must not commit your work for you, and pushing HEAD while building the working tree would let\n // an uncommitted change build green yet push a stale commit. Fail early if dirty.\n this.gitExec.assertCleanTree(repoRoot);\n\n await this.updateBranchFromMain(repoRoot);\n // Local branch, remote branch, and PR share the one stable feature name.\n this.gitExec.ensurePushed(this.branchNaming.baseBranchName(execSync('git branch --show-current', { encoding: 'utf8' }).trim()));\n\n // Advisory build gate — early feedback before the AI writes review.json. wp-finish-upsert-pr\n // runs the authoritative one. Both go through the same runBuildGate (only the framing differs).\n this.buildAffected.runBuildGate(repoRoot, new BuildGateOptions(\n '② Build gate (nx affected)', 'pnpm wp-start-upsert-pr', 'Build failed — fix it before reviewing.',\n ));\n\n // Hand the AI its next step: write review.json, then run finish (which posts the PR). Compute the\n // checklists this diff MATCHED (from the manifest doc) so the hint names each reviewer subagent to\n // spawn BEFORE review.json is written (empty for repos with no checklist doc ⇒ the hint is unchanged).\n const defs = this.manifestService.load(repoRoot, loadAndValidate(repoRoot).prGate.checklistDoc);\n const triggered = this.checklistDetector.detectForRepo(repoRoot, defs);\n const required = this.checklistDetector.toRequired(triggered);\n const reviewPath = reviewJsonPath(repoRoot, this.aiBranchName.getFeatureName());\n // REVIEW ONCE PER BRANCH: a checklist that already has a passing/overridden review-<id>.json from an\n // earlier cycle is NOT re-reviewed — only the ones still needing a verdict are handed to the AI. The\n // review files persist in .webpieces, so a second wp-start/wp-finish cycle re-instructs nothing.\n const results = this.reviewJsonService.loadChecklistResults(reviewPath, required);\n const toReview = required.filter((req: RequiredChecklist): boolean => !this.alreadyReviewed(req, results));\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 + changed files) so reviewer subagents can `git diff` for\n // content instead of the tooling matching on regexes. Written whenever a base resolves.\n this.writePrContext(repoRoot);\n this.printChecklistPlan(triggered, toReview);\n process.stdout.write('\\n' + SEP + '③ Review the PR, then finish\\n' + SEP + '\\n');\n process.stdout.write(\n `Branch is updated, pushed, and the build gate passed. Now review your own changes and\\n` +\n `${reviewJsonSchemaHint(reviewPath, toReview)}\\n\\n` +\n `Then run: pnpm wp-finish-upsert-pr\\n` +\n `(It re-validates the build, renders the dashboard with your risk/violations, and creates/updates the PR.)\\n\\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 private writePrContext(repoRoot: string): void {\n const range = this.diffScope.resolveBase(repoRoot);\n if (!range.base) return;\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 process.stdout.write(`\\n📂 Wrote PR diff context (${changed.length} changed file(s)) → ${p}\\n`);\n }\n\n // A checklist already reviewed on this branch — its review-<id>.json resolves to PASS or OVERRIDDEN —\n // so it is NOT handed back to the AI to re-review (review once per branch).\n private alreadyReviewed(req: RequiredChecklist, results: readonly ChecklistResult[]): boolean {\n const status = this.reviewJsonService.resolveVerdict(req, results).status;\n return status === CK_PASS || status === CK_OVERRIDDEN;\n }\n\n // Show which matched checklists still need a reviewer subagent (spawn each as a distinct one) and which\n // are already reviewed (reused, not re-run) — so a second cycle re-instructs nothing.\n private printChecklistPlan(triggered: readonly TriggeredChecklist[], toReview: readonly RequiredChecklist[]): void {\n if (triggered.length === 0) return;\n process.stdout.write('\\n' + SEP + '📋 Review checklists\\n' + SEP + '\\n');\n const toReviewIds = new Set(toReview.map((r: RequiredChecklist): string => r.id));\n const reused = triggered.filter((t: TriggeredChecklist): boolean => !toReviewIds.has(t.def.id));\n for (const t of reused) {\n process.stdout.write(` ✓ ${t.def.subagent} — already reviewed on this branch (reusing its review-${t.def.id}.json)\\n`);\n }\n if (toReview.length === 0) {\n process.stdout.write('All matched checklists are already reviewed — nothing to re-run. Just write review.json and finish.\\n');\n return;\n }\n process.stdout.write('Spawn EACH of these as a SEPARATE subagent (a different one per checklist — do not self-certify):\\n');\n for (const t of triggered.filter((x: TriggeredChecklist): boolean => toReviewIds.has(x.def.id))) {\n process.stdout.write(` • subagent \"${t.def.subagent}\"`);\n if (t.def.doc.trim() !== '') process.stdout.write(` — reads ${t.def.doc}`);\n process.stdout.write(` (matched: ${t.matchedFiles.slice(0, 4).join(', ')})\\n`);\n }\n process.stdout.write('See .webpieces/instruct-ai/webpieces.review-checklists.md for the review-<id>.json format each must write.\\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 const outcome = await this.runUpdate.runUpdateFromMain(repoRoot, 'wp-start-upsert-pr', 'wp-finish-upsert-pr');\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, ChecklistManifestService, ChecklistDefinition, ReviewJsonService, DiffScope, ChangedFilesOptions, PrContext, RequiredChecklist, ChecklistResult, CK_PASS, CK_OVERRIDDEN } from '@webpieces/rules-config';\nimport { ChecklistNotice } from '../workflow/checklist-notice';\nimport { TriggeredChecklist } from '../workflow/checklist-detector';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BranchNaming } from '../workflow/branch-naming';\nimport { BuildAffected, BuildGateOptions } from '../workflow/build-affected';\nimport { ChecklistDetector } from '../workflow/checklist-detector';\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, run the advisory build gate\n// — then hand the AI instructions to WRITE review.json and run `wp-finish-upsert-pr` (which reads it and\n// posts the PR). This command NEVER creates/updates a PR and NEVER pushes: all `gh` posting and the ONE\n// 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 buildAffected: BuildAffected,\n private readonly gitExec: GitExec,\n private readonly runUpdate: RunUpdate,\n private readonly checklistDetector: ChecklistDetector,\n private readonly manifestService: ChecklistManifestService,\n private readonly reviewJsonService: ReviewJsonService,\n private readonly diffScope: DiffScope,\n private readonly checklistNotice: ChecklistNotice,\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 // Advisory build gate — early feedback before the AI writes review.json. wp-finish-upsert-pr\n // runs the authoritative one. Both go through the same runBuildGate (only the framing differs).\n this.buildAffected.runBuildGate(repoRoot, new BuildGateOptions(\n '② Build gate (nx affected)', 'pnpm wp-start-upsert-pr', 'Build failed — fix it before reviewing.',\n ));\n\n this.handOffToReview(repoRoot);\n }\n\n // Hand the AI its next step: write review.json, then run finish (which posts the PR). Computes the\n // checklists this diff MATCHED (from the manifest doc) so the hint names each reviewer subagent to\n // spawn BEFORE review.json is written — and, when NOTHING matched, says so instead of staying silent\n // (zero checklists is a supported state, never a blocker — see ChecklistNotice).\n private handOffToReview(repoRoot: string): void {\n const defs = this.manifestService.load(repoRoot, loadAndValidate(repoRoot).prGate.checklistDoc);\n const triggered = this.checklistDetector.detectForRepo(repoRoot, defs);\n const required = this.checklistDetector.toRequired(triggered);\n const reviewPath = reviewJsonPath(repoRoot, this.aiBranchName.getFeatureName());\n // REVIEW ONCE PER BRANCH: a checklist that already has a passing/overridden review-<id>.json from an\n // earlier cycle is NOT re-reviewed — only the ones still needing a verdict are handed to the AI. The\n // review files persist in .webpieces, so a second wp-start/wp-finish cycle re-instructs nothing.\n const results = this.reviewJsonService.loadChecklistResults(reviewPath, required);\n const toReview = required.filter((req: RequiredChecklist): boolean => !this.alreadyReviewed(req, results));\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 + changed files) so reviewer subagents can `git diff` for\n // content instead of the tooling matching on regexes. Written whenever a base resolves.\n this.writePrContext(repoRoot);\n this.printChecklistPlan(repoRoot, defs, triggered, toReview);\n process.stdout.write('\\n' + SEP + '③ Review the PR, then finish\\n' + SEP + '\\n');\n process.stdout.write(\n `Branch is updated and the build gate passed (nothing pushed yet — finish does the one push).\\n` +\n `Now review your own changes and\\n` +\n `${reviewJsonSchemaHint(reviewPath, toReview)}\\n\\n` +\n `Then run: pnpm wp-finish-upsert-pr\\n` +\n `(It re-validates the build, renders the dashboard with your risk/violations, and creates/updates the PR.)\\n\\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 private writePrContext(repoRoot: string): void {\n const range = this.diffScope.resolveBase(repoRoot);\n if (!range.base) return;\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 process.stdout.write(`\\n📂 Wrote PR diff context (${changed.length} changed file(s)) → ${p}\\n`);\n }\n\n // A checklist already reviewed on this branch — its review-<id>.json resolves to PASS or OVERRIDDEN —\n // so it is NOT handed back to the AI to re-review (review once per branch).\n private alreadyReviewed(req: RequiredChecklist, results: readonly ChecklistResult[]): boolean {\n const status = this.reviewJsonService.resolveVerdict(req, results).status;\n return status === CK_PASS || status === CK_OVERRIDDEN;\n }\n\n // Show which matched checklists still need a reviewer subagent (spawn each as a distinct one) and which\n // are already reviewed (reused, not re-run) — so a second cycle re-instructs nothing.\n private printChecklistPlan(repoRoot: string, defs: readonly ChecklistDefinition[], triggered: readonly TriggeredChecklist[], toReview: readonly RequiredChecklist[]): void {\n if (triggered.length === 0) {\n this.printEmptyChecklistNotice(repoRoot, defs);\n return;\n }\n process.stdout.write('\\n' + SEP + '📋 Review checklists\\n' + SEP + '\\n');\n const toReviewIds = new Set(toReview.map((r: RequiredChecklist): string => r.id));\n const reused = triggered.filter((t: TriggeredChecklist): boolean => !toReviewIds.has(t.def.id));\n for (const t of reused) {\n process.stdout.write(` ✓ ${t.def.subagent} — already reviewed on this branch (reusing its review-${t.def.id}.json)\\n`);\n }\n if (toReview.length === 0) {\n process.stdout.write('All matched checklists are already reviewed — nothing to re-run. Just write review.json and finish.\\n');\n return;\n }\n process.stdout.write('Spawn EACH of these as a SEPARATE subagent (a different one per checklist — do not self-certify):\\n');\n for (const t of triggered.filter((x: TriggeredChecklist): boolean => toReviewIds.has(x.def.id))) {\n process.stdout.write(` • subagent \"${t.def.subagent}\"`);\n if (t.def.doc.trim() !== '') process.stdout.write(` — reads ${t.def.doc}`);\n process.stdout.write(` (matched: ${t.matchedFiles.slice(0, 4).join(', ')})\\n`);\n }\n process.stdout.write('See .webpieces/instruct-ai/webpieces.review-checklists.md for the review-<id>.json format each must write.\\n');\n }\n\n // ZERO checklists matched. Say so — this used to print nothing at all, which reads exactly like \"the\n // checklist ran and passed\". Purely informational: 0 is a supported state and never blocks finishing.\n // validate() is called ONLY here (the runtime load() is deliberately tolerant), so a missing/malformed\n // checklist doc stops being a silent no-op and gets reported.\n private printEmptyChecklistNotice(repoRoot: string, defs: readonly ChecklistDefinition[]): void {\n const docRel = loadAndValidate(repoRoot).prGate.checklistDoc;\n const errors = this.manifestService.validate(repoRoot, docRel);\n process.stdout.write('\\n' + SEP + '📋 Review checklists\\n' + SEP + '\\n');\n process.stdout.write(this.checklistNotice.build(docRel, errors, defs.length, 'wp-finish-upsert-pr'));\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,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the message `wp-start-upsert-pr` prints when a diff matched ZERO review checklists.
|
|
3
|
+
*
|
|
4
|
+
* ZERO IS A VALID, SUPPORTED STATE — it is NOT an error and NEVER blocks the flow. This exists only
|
|
5
|
+
* because the previous behavior was to print nothing at all, which is indistinguishable from "the
|
|
6
|
+
* checklist ran and passed". Silence is the bug; refusing would be a worse one.
|
|
7
|
+
*
|
|
8
|
+
* The three empty cases need different fixes, so they get different text:
|
|
9
|
+
* - NONE CONFIGURED — the repo has no checklists.doc. Perfectly fine; mention that a human MAY add
|
|
10
|
+
* checklist *.md docs if they want reviews, and move on.
|
|
11
|
+
* - MISCONFIGURED — checklists.doc is set but missing/malformed. This one is worth shouting about:
|
|
12
|
+
* the tolerant loader returns [] for it, so a broken doc silently enforces
|
|
13
|
+
* NOTHING while looking configured.
|
|
14
|
+
* - NONE MATCHED — checklists exist and are valid, but none of their patterns hit this diff.
|
|
15
|
+
* Also fine; report the count so a human can judge whether that is expected.
|
|
16
|
+
*
|
|
17
|
+
* Pure string building, no I/O, so it is unit-testable without git or a repo.
|
|
18
|
+
* `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.
|
|
19
|
+
*/
|
|
20
|
+
export declare class ChecklistNotice {
|
|
21
|
+
/**
|
|
22
|
+
* @param docRel `prGate.checklistDoc` ('' when the repo configured none)
|
|
23
|
+
* @param manifestErrors `ChecklistManifestService.validate()` output ([] when valid or unconfigured)
|
|
24
|
+
* @param definedCount how many checklists the manifest defines
|
|
25
|
+
* @param finishCommand the command to continue with, named in every branch
|
|
26
|
+
*/
|
|
27
|
+
build(docRel: string, manifestErrors: readonly string[], definedCount: number, finishCommand: string): string;
|
|
28
|
+
private reason;
|
|
29
|
+
private continueLine;
|
|
30
|
+
private noneConfigured;
|
|
31
|
+
private emptyManifest;
|
|
32
|
+
private misconfigured;
|
|
33
|
+
private noneMatched;
|
|
34
|
+
}
|