@webpieces/pr-gate 0.3.369 → 0.3.371

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/pr-gate",
3
- "version": "0.3.369",
3
+ "version": "0.3.371",
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",
@@ -21,7 +21,7 @@
21
21
  "directory": "packages/tooling/pr-gate"
22
22
  },
23
23
  "dependencies": {
24
- "@webpieces/rules-config": "0.3.369",
24
+ "@webpieces/rules-config": "0.3.371",
25
25
  "@inversifyjs/binding-decorators": "1.1.5",
26
26
  "inversify": "7.10.4",
27
27
  "reflect-metadata": "0.2.2"
@@ -1,10 +1,12 @@
1
+ import { RepoRootFinder } from '@webpieces/rules-config';
1
2
  import { AiBranchName } from '../workflow/git-readAiBranchName';
2
3
  import { MergeState } from '../workflow/merge-state';
3
4
  import { MergeEnd } from '../workflow/merge-end';
4
5
  export declare class FinishUpdateCommand {
6
+ private readonly repoRootFinder;
5
7
  private readonly aiBranchName;
6
8
  private readonly mergeState;
7
9
  private readonly mergeEnd;
8
- constructor(aiBranchName: AiBranchName, mergeState: MergeState, mergeEnd: MergeEnd);
10
+ constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, mergeState: MergeState, mergeEnd: MergeEnd);
9
11
  run(): Promise<void>;
10
12
  }
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FinishUpdateCommand = 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");
7
6
  const rules_config_2 = require("@webpieces/rules-config");
8
7
  const inversify_1 = require("inversify");
@@ -15,16 +14,18 @@ const merge_start_1 = require("../workflow/merge-start");
15
14
  // AI's resolution (when not yet validated) and ALWAYS finalizes the branch swap. It does NOT run the
16
15
  // build gate / dashboard / PR — that is wp-finish-upsert-pr. Refuses when no merge is in progress.
17
16
  let FinishUpdateCommand = class FinishUpdateCommand {
17
+ repoRootFinder;
18
18
  aiBranchName;
19
19
  mergeState;
20
20
  mergeEnd;
21
- constructor(aiBranchName, mergeState, mergeEnd) {
21
+ constructor(repoRootFinder, aiBranchName, mergeState, mergeEnd) {
22
+ this.repoRootFinder = repoRootFinder;
22
23
  this.aiBranchName = aiBranchName;
23
24
  this.mergeState = mergeState;
24
25
  this.mergeEnd = mergeEnd;
25
26
  }
26
27
  async run() {
27
- const repoRoot = (0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
28
+ const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
28
29
  const home = this.mergeState.mergeDirFor(repoRoot, this.aiBranchName.getFeatureName());
29
30
  // The in-progress merge is the `merge-<n>/` run dir holding a marker.
30
31
  const activeDir = this.mergeState.findActiveMergeRunDir(home);
@@ -44,7 +45,8 @@ exports.FinishUpdateCommand = FinishUpdateCommand;
44
45
  exports.FinishUpdateCommand = FinishUpdateCommand = tslib_1.__decorate([
45
46
  (0, rules_config_2.provideSingleton)(),
46
47
  (0, inversify_1.injectable)(),
47
- tslib_1.__metadata("design:paramtypes", [git_readAiBranchName_1.AiBranchName,
48
+ tslib_1.__metadata("design:paramtypes", [rules_config_1.RepoRootFinder,
49
+ git_readAiBranchName_1.AiBranchName,
48
50
  merge_state_1.MergeState,
49
51
  merge_end_1.MergeEnd])
50
52
  ], FinishUpdateCommand);
@@ -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,iDAAyC;AACzC,0DAAuD;AACvD,0DAA2D;AAC3D,yCAAuC;AACvC,2EAAgE;AAChE,yDAAqD;AACrD,qDAAiD;AACjD,yDAAuD;AAEvD,wGAAwG;AACxG,sGAAsG;AACtG,qGAAqG;AACrG,mGAAmG;AAG5F,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAEP;IACA;IACA;IAHrB,YACqB,YAA0B,EAC1B,UAAsB,EACtB,QAAkB;QAFlB,iBAAY,GAAZ,YAAY,CAAc;QAC1B,eAAU,GAAV,UAAU,CAAY;QACtB,aAAQ,GAAR,QAAQ,CAAU;IACpC,CAAC;IAEJ,KAAK,CAAC,GAAG;QACL,MAAM,QAAQ,GAAG,IAAA,wBAAQ,EAAC,+BAA+B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACxF,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,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,eAAe,CAClB,CAAC;QACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,GAAG,MAAM,CAAC,aAAa,GAAG,KAAK,CAAC,CAAC;IACnF,CAAC;CACJ,CAAA;AA/BY,kDAAmB;8BAAnB,mBAAmB;IAF/B,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;6CAG0B,mCAAY;QACd,wBAAU;QACZ,oBAAQ;GAJ9B,mBAAmB,CA+B/B","sourcesContent":["import { execSync } from 'child_process';\nimport { CliExitError } from '@webpieces/rules-config';\nimport { provideSingleton } from '@webpieces/rules-config';\nimport { injectable } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { MergeState } from '../workflow/merge-state';\nimport { MergeEnd } 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@provideSingleton()\n@injectable()\nexport class FinishUpdateCommand {\n constructor(\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 = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();\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 await this.mergeEnd.mergeEnd(\n repoRoot, 'wp-finish-update', activeDir,\n new MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber),\n conflictedFiles,\n );\n process.stdout.write('\\n✅ Merge finalized on ' + marker.currentBranch + '.\\n');\n }\n}\n"]}
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,0DAA2D;AAC3D,yCAAuC;AACvC,2EAAgE;AAChE,yDAAqD;AACrD,qDAAiD;AACjD,yDAAuD;AAEvD,wGAAwG;AACxG,sGAAsG;AACtG,qGAAqG;AACrG,mGAAmG;AAG5F,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,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,eAAe,CAClB,CAAC;QACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,GAAG,MAAM,CAAC,aAAa,GAAG,KAAK,CAAC,CAAC;IACnF,CAAC;CACJ,CAAA;AAhCY,kDAAmB;8BAAnB,mBAAmB;IAF/B,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;6CAG4B,6BAAc;QAChB,mCAAY;QACd,wBAAU;QACZ,oBAAQ;GAL9B,mBAAmB,CAgC/B","sourcesContent":["import { CliExitError, RepoRootFinder } from '@webpieces/rules-config';\nimport { provideSingleton } from '@webpieces/rules-config';\nimport { injectable } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { MergeState } from '../workflow/merge-state';\nimport { MergeEnd } 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@provideSingleton()\n@injectable()\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 await this.mergeEnd.mergeEnd(\n repoRoot, 'wp-finish-update', activeDir,\n new MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber),\n conflictedFiles,\n );\n process.stdout.write('\\n✅ Merge finalized on ' + marker.currentBranch + '.\\n');\n }\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import { RepoRootFinder } from '@webpieces/rules-config';
1
2
  import { AiBranchName } from '../workflow/git-readAiBranchName';
2
3
  import { BranchNaming } from '../workflow/branch-naming';
3
4
  import { GitExec } from '../workflow/git-exec';
@@ -6,6 +7,7 @@ import { MergeState } from '../workflow/merge-state';
6
7
  import { MergeEnd } from '../workflow/merge-end';
7
8
  import { Dashboard } from '../../dashboard/dashboard';
8
9
  export declare class FinishUpsertPrCommand {
10
+ private readonly repoRootFinder;
9
11
  private readonly aiBranchName;
10
12
  private readonly branchNaming;
11
13
  private readonly gitExec;
@@ -13,7 +15,7 @@ export declare class FinishUpsertPrCommand {
13
15
  private readonly mergeState;
14
16
  private readonly mergeEnd;
15
17
  private readonly dashboard;
16
- constructor(aiBranchName: AiBranchName, branchNaming: BranchNaming, gitExec: GitExec, buildAffected: BuildAffected, mergeState: MergeState, mergeEnd: MergeEnd, dashboard: Dashboard);
18
+ constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, branchNaming: BranchNaming, gitExec: GitExec, buildAffected: BuildAffected, mergeState: MergeState, mergeEnd: MergeEnd, dashboard: Dashboard);
17
19
  run(): Promise<void>;
18
20
  private gitOut;
19
21
  private prTitleFrom;
@@ -22,6 +22,7 @@ const SEP = '━━━━━━━━━━━━━━━━━━━━━━
22
22
  // authoritative build gate; (4) render the dashboard; (5) create/update the PR via `gh`. The ONLY
23
23
  // command that posts PRs.
24
24
  let FinishUpsertPrCommand = class FinishUpsertPrCommand {
25
+ repoRootFinder;
25
26
  aiBranchName;
26
27
  branchNaming;
27
28
  gitExec;
@@ -29,7 +30,8 @@ let FinishUpsertPrCommand = class FinishUpsertPrCommand {
29
30
  mergeState;
30
31
  mergeEnd;
31
32
  dashboard;
32
- constructor(aiBranchName, branchNaming, gitExec, buildAffected, mergeState, mergeEnd, dashboard) {
33
+ constructor(repoRootFinder, aiBranchName, branchNaming, gitExec, buildAffected, mergeState, mergeEnd, dashboard) {
34
+ this.repoRootFinder = repoRootFinder;
33
35
  this.aiBranchName = aiBranchName;
34
36
  this.branchNaming = branchNaming;
35
37
  this.gitExec = gitExec;
@@ -39,7 +41,7 @@ let FinishUpsertPrCommand = class FinishUpsertPrCommand {
39
41
  this.dashboard = dashboard;
40
42
  }
41
43
  async run() {
42
- const repoRoot = (0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
44
+ const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
43
45
  // Refresh the AI-facing workflow doc so it's present + current for any failure message to cite.
44
46
  (0, rules_config_1.writeTemplate)(repoRoot, 'webpieces.git-workflow.md');
45
47
  const home = this.mergeState.mergeDirFor(repoRoot, this.aiBranchName.getFeatureName());
@@ -120,7 +122,8 @@ exports.FinishUpsertPrCommand = FinishUpsertPrCommand;
120
122
  exports.FinishUpsertPrCommand = FinishUpsertPrCommand = tslib_1.__decorate([
121
123
  (0, rules_config_2.provideSingleton)(),
122
124
  (0, inversify_1.injectable)(),
123
- tslib_1.__metadata("design:paramtypes", [git_readAiBranchName_1.AiBranchName,
125
+ tslib_1.__metadata("design:paramtypes", [rules_config_1.RepoRootFinder,
126
+ git_readAiBranchName_1.AiBranchName,
124
127
  branch_naming_1.BranchNaming,
125
128
  git_exec_1.GitExec,
126
129
  build_affected_1.BuildAffected,
@@ -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, loadReviewJson, prDirFor, reviewJsonPath, ReviewJson, writeTemplate,\n} from '@webpieces/rules-config';\nimport { provideSingleton } from '@webpieces/rules-config';\nimport { injectable } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BranchNaming } from '../workflow/branch-naming';\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 { Dashboard, DashboardInput } from '../../dashboard/dashboard';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\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@provideSingleton()\n@injectable()\nexport class FinishUpsertPrCommand {\n constructor(\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 dashboard: Dashboard,\n ) {}\n\n async run(): Promise<void> {\n const repoRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();\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 const review = loadReviewJson(reviewJsonPath(repoRoot, this.aiBranchName.getFeatureName()));\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 push, then post.\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 this.gitExec.ensurePushed(base);\n\n process.stdout.write('\\n' + SEP + '📋 Dashboard + PR\\n' + SEP + '\\n');\n const title = this.prTitleFrom(review);\n const body = this.buildDashboard(repoRoot, true, review, title);\n const prNum = this.upsertPr(repoRoot, base, body, title);\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. force-pushed your work to origin/${base}\\n` +\n ` 3. ${prNum ? `updated/created PR #${prNum}` : 'created the PR'} titled: \"${title}\"\\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 private buildDashboard(repoRoot: string, buildPassed: boolean, review: ReviewJson, title: string): string {\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 input = new DashboardInput(title, gateResults, disables, buildPassed, forkPoint, featureHead, mainHead, review);\n return this.dashboard.renderDashboard(input);\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 private upsertPr(repoRoot: string, baseBranch: string, body: string, title: string): string {\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 prNumber = spawnSync(\n 'gh', ['pr', 'list', '--head', baseBranch, '--json', 'number', '--jq', '.[0].number'],\n { encoding: 'utf8' },\n );\n const num = prNumber.status === 0 ? (prNumber.stdout ?? '').trim() : '';\n\n if (num === '') {\n process.stdout.write('Creating PR...\\n');\n const create = spawnSync('gh', ['pr', 'create', '--head', baseBranch, '--base', 'main', '--title', title, '--body-file', bodyFile], { stdio: 'inherit' });\n if (create.status !== 0) {\n process.stderr.write('⚠️ gh pr create failed — create the PR manually with the body in:\\n ' + bodyFile + '\\n');\n return '';\n }\n } else {\n process.stdout.write(`Updating PR #${num}...\\n`);\n spawnSync('gh', ['pr', 'edit', num, '--title', title, '--body-file', bodyFile], { stdio: 'inherit' });\n }\n spawnSync('gh', ['pr', 'merge', baseBranch, '--auto', '--squash'], { stdio: 'inherit' });\n return num;\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, loadReviewJson, prDirFor, reviewJsonPath, ReviewJson, writeTemplate, RepoRootFinder,\n} from '@webpieces/rules-config';\nimport { provideSingleton } from '@webpieces/rules-config';\nimport { injectable } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BranchNaming } from '../workflow/branch-naming';\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 { Dashboard, DashboardInput } from '../../dashboard/dashboard';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\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@provideSingleton()\n@injectable()\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 dashboard: Dashboard,\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 const review = loadReviewJson(reviewJsonPath(repoRoot, this.aiBranchName.getFeatureName()));\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 push, then post.\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 this.gitExec.ensurePushed(base);\n\n process.stdout.write('\\n' + SEP + '📋 Dashboard + PR\\n' + SEP + '\\n');\n const title = this.prTitleFrom(review);\n const body = this.buildDashboard(repoRoot, true, review, title);\n const prNum = this.upsertPr(repoRoot, base, body, title);\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. force-pushed your work to origin/${base}\\n` +\n ` 3. ${prNum ? `updated/created PR #${prNum}` : 'created the PR'} titled: \"${title}\"\\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 private buildDashboard(repoRoot: string, buildPassed: boolean, review: ReviewJson, title: string): string {\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 input = new DashboardInput(title, gateResults, disables, buildPassed, forkPoint, featureHead, mainHead, review);\n return this.dashboard.renderDashboard(input);\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 private upsertPr(repoRoot: string, baseBranch: string, body: string, title: string): string {\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 prNumber = spawnSync(\n 'gh', ['pr', 'list', '--head', baseBranch, '--json', 'number', '--jq', '.[0].number'],\n { encoding: 'utf8' },\n );\n const num = prNumber.status === 0 ? (prNumber.stdout ?? '').trim() : '';\n\n if (num === '') {\n process.stdout.write('Creating PR...\\n');\n const create = spawnSync('gh', ['pr', 'create', '--head', baseBranch, '--base', 'main', '--title', title, '--body-file', bodyFile], { stdio: 'inherit' });\n if (create.status !== 0) {\n process.stderr.write('⚠️ gh pr create failed — create the PR manually with the body in:\\n ' + bodyFile + '\\n');\n return '';\n }\n } else {\n process.stdout.write(`Updating PR #${num}...\\n`);\n spawnSync('gh', ['pr', 'edit', num, '--title', title, '--body-file', bodyFile], { stdio: 'inherit' });\n }\n spawnSync('gh', ['pr', 'merge', baseBranch, '--auto', '--squash'], { stdio: 'inherit' });\n return num;\n }\n}\n"]}
@@ -1,9 +1,11 @@
1
+ import { RepoRootFinder } from '@webpieces/rules-config';
1
2
  import { OpenPrCheck } from '../workflow/open-pr-check';
2
3
  import { RunUpdate } from '../workflow/run-update';
3
4
  export declare class StartUpdateCommand {
5
+ private readonly repoRootFinder;
4
6
  private readonly openPrCheck;
5
7
  private readonly runUpdate;
6
- constructor(openPrCheck: OpenPrCheck, runUpdate: RunUpdate);
8
+ constructor(repoRootFinder: RepoRootFinder, openPrCheck: OpenPrCheck, runUpdate: RunUpdate);
7
9
  run(): Promise<void>;
8
10
  assertNoOpenPr(repoRoot: string): void;
9
11
  }
@@ -14,14 +14,16 @@ const SEP = '━━━━━━━━━━━━━━━━━━━━━━
14
14
  // finalizes everything; CONFLICT writes the 3-point context + merge process doc and hands back. NEVER
15
15
  // creates a PR (that is the wp-*-upsert-pr flow).
16
16
  let StartUpdateCommand = class StartUpdateCommand {
17
+ repoRootFinder;
17
18
  openPrCheck;
18
19
  runUpdate;
19
- constructor(openPrCheck, runUpdate) {
20
+ constructor(repoRootFinder, openPrCheck, runUpdate) {
21
+ this.repoRootFinder = repoRootFinder;
20
22
  this.openPrCheck = openPrCheck;
21
23
  this.runUpdate = runUpdate;
22
24
  }
23
25
  async run() {
24
- const repoRoot = (0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
26
+ const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
25
27
  // Refresh the AI-facing workflow doc so it's present + current for any failure message to cite.
26
28
  (0, rules_config_1.writeTemplate)(repoRoot, 'webpieces.git-workflow.md');
27
29
  // An open PR means the PR flow must be used — a 3-point update would strand the PR on the old
@@ -61,7 +63,8 @@ exports.StartUpdateCommand = StartUpdateCommand;
61
63
  exports.StartUpdateCommand = StartUpdateCommand = tslib_1.__decorate([
62
64
  (0, rules_config_2.provideSingleton)(),
63
65
  (0, inversify_1.injectable)(),
64
- tslib_1.__metadata("design:paramtypes", [open_pr_check_1.OpenPrCheck,
66
+ tslib_1.__metadata("design:paramtypes", [rules_config_1.RepoRootFinder,
67
+ open_pr_check_1.OpenPrCheck,
65
68
  run_update_1.RunUpdate])
66
69
  ], StartUpdateCommand);
67
70
  //# sourceMappingURL=start-update-command.js.map
@@ -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,0DAAsE;AACtE,0DAA2D;AAC3D,yCAAuC;AACvC,6DAAwD;AACxD,uDAAmD;AAEnD,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAEvE,8FAA8F;AAC9F,sGAAsG;AACtG,sGAAsG;AACtG,kDAAkD;AAG3C,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;IAEN;IACA;IAFrB,YACqB,WAAwB,EACxB,SAAoB;QADpB,gBAAW,GAAX,WAAW,CAAa;QACxB,cAAS,GAAT,SAAS,CAAW;IACtC,CAAC;IAEJ,KAAK,CAAC,GAAG;QACL,MAAM,QAAQ,GAAG,IAAA,wBAAQ,EAAC,+BAA+B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACxF,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;QACxG,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;AAhDY,gDAAkB;6BAAlB,kBAAkB;IAF9B,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;6CAGyB,2BAAW;QACb,sBAAS;GAHhC,kBAAkB,CAgD9B","sourcesContent":["import { execSync } from 'child_process';\nimport { writeTemplate, CliExitError } from '@webpieces/rules-config';\nimport { provideSingleton } from '@webpieces/rules-config';\nimport { injectable } 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@provideSingleton()\n@injectable()\nexport class StartUpdateCommand {\n constructor(\n private readonly openPrCheck: OpenPrCheck,\n private readonly runUpdate: RunUpdate,\n ) {}\n\n async run(): Promise<void> {\n const repoRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();\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 const outcome = await this.runUpdate.runUpdateFromMain(repoRoot, 'wp-start-update', 'wp-finish-update');\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
+ {"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,0DAA2D;AAC3D,yCAAuC;AACvC,6DAAwD;AACxD,uDAAmD;AAEnD,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAEvE,8FAA8F;AAC9F,sGAAsG;AACtG,sGAAsG;AACtG,kDAAkD;AAG3C,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;QACxG,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;AAjDY,gDAAkB;6BAAlB,kBAAkB;IAF9B,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;6CAG4B,6BAAc;QACjB,2BAAW;QACb,sBAAS;GAJhC,kBAAkB,CAiD9B","sourcesContent":["import { execSync } from 'child_process';\nimport { writeTemplate, CliExitError, RepoRootFinder } from '@webpieces/rules-config';\nimport { provideSingleton } from '@webpieces/rules-config';\nimport { injectable } 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@provideSingleton()\n@injectable()\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 const outcome = await this.runUpdate.runUpdateFromMain(repoRoot, 'wp-start-update', 'wp-finish-update');\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,15 +1,17 @@
1
+ import { RepoRootFinder } from '@webpieces/rules-config';
1
2
  import { AiBranchName } from '../workflow/git-readAiBranchName';
2
3
  import { BranchNaming } from '../workflow/branch-naming';
3
4
  import { BuildAffected } from '../workflow/build-affected';
4
5
  import { GitExec } from '../workflow/git-exec';
5
6
  import { RunUpdate } from '../workflow/run-update';
6
7
  export declare class StartUpsertPrCommand {
8
+ private readonly repoRootFinder;
7
9
  private readonly aiBranchName;
8
10
  private readonly branchNaming;
9
11
  private readonly buildAffected;
10
12
  private readonly gitExec;
11
13
  private readonly runUpdate;
12
- constructor(aiBranchName: AiBranchName, branchNaming: BranchNaming, buildAffected: BuildAffected, gitExec: GitExec, runUpdate: RunUpdate);
14
+ constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, branchNaming: BranchNaming, buildAffected: BuildAffected, gitExec: GitExec, runUpdate: RunUpdate);
13
15
  run(): Promise<void>;
14
16
  private updateBranchFromMain;
15
17
  }
@@ -16,12 +16,14 @@ const SEP = '━━━━━━━━━━━━━━━━━━━━━━
16
16
  // build gate — then hand the AI instructions to WRITE review.json and run `wp-finish-upsert-pr` (which
17
17
  // reads it and posts the PR). This command NEVER creates/updates a PR; all `gh` posting lives in finish.
18
18
  let StartUpsertPrCommand = class StartUpsertPrCommand {
19
+ repoRootFinder;
19
20
  aiBranchName;
20
21
  branchNaming;
21
22
  buildAffected;
22
23
  gitExec;
23
24
  runUpdate;
24
- constructor(aiBranchName, branchNaming, buildAffected, gitExec, runUpdate) {
25
+ constructor(repoRootFinder, aiBranchName, branchNaming, buildAffected, gitExec, runUpdate) {
26
+ this.repoRootFinder = repoRootFinder;
25
27
  this.aiBranchName = aiBranchName;
26
28
  this.branchNaming = branchNaming;
27
29
  this.buildAffected = buildAffected;
@@ -29,7 +31,7 @@ let StartUpsertPrCommand = class StartUpsertPrCommand {
29
31
  this.runUpdate = runUpdate;
30
32
  }
31
33
  async run() {
32
- const repoRoot = (0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
34
+ const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
33
35
  // Refresh the AI-facing workflow doc so it's present + current for any failure message to cite.
34
36
  (0, rules_config_1.writeTemplate)(repoRoot, 'webpieces.git-workflow.md');
35
37
  // Precondition: a fully-committed tree. This flow updates, pushes HEAD, and builds — the tooling
@@ -64,7 +66,8 @@ exports.StartUpsertPrCommand = StartUpsertPrCommand;
64
66
  exports.StartUpsertPrCommand = StartUpsertPrCommand = tslib_1.__decorate([
65
67
  (0, rules_config_2.provideSingleton)(),
66
68
  (0, inversify_1.injectable)(),
67
- tslib_1.__metadata("design:paramtypes", [git_readAiBranchName_1.AiBranchName,
69
+ tslib_1.__metadata("design:paramtypes", [rules_config_1.RepoRootFinder,
70
+ git_readAiBranchName_1.AiBranchName,
68
71
  branch_naming_1.BranchNaming,
69
72
  build_affected_1.BuildAffected,
70
73
  git_exec_1.GitExec,
@@ -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":";;;;AAAA,iDAAyC;AACzC,0DAA4G;AAC5G,0DAA2D;AAC3D,yCAAuC;AACvC,2EAAgE;AAChE,6DAAyD;AACzD,+DAA6E;AAC7E,mDAA+C;AAC/C,uDAAmD;AAEnD,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAEvE,oGAAoG;AACpG,uGAAuG;AACvG,yGAAyG;AAGlG,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IAER;IACA;IACA;IACA;IACA;IALrB,YACqB,YAA0B,EAC1B,YAA0B,EAC1B,aAA4B,EAC5B,OAAgB,EAChB,SAAoB;QAJpB,iBAAY,GAAZ,YAAY,CAAc;QAC1B,iBAAY,GAAZ,YAAY,CAAc;QAC1B,kBAAa,GAAb,aAAa,CAAe;QAC5B,YAAO,GAAP,OAAO,CAAS;QAChB,cAAS,GAAT,SAAS,CAAW;IACtC,CAAC;IAEJ,KAAK,CAAC,GAAG;QACL,MAAM,QAAQ,GAAG,IAAA,wBAAQ,EAAC,+BAA+B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACxF,gGAAgG;QAChG,IAAA,4BAAa,EAAC,QAAQ,EAAE,2BAA2B,CAAC,CAAC;QAErD,iGAAiG;QACjG,gGAAgG;QAChG,kFAAkF;QAClF,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAEvC,MAAM,IAAI,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QAC1C,yEAAyE;QACzE,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,IAAA,wBAAQ,EAAC,2BAA2B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAEhI,6FAA6F;QAC7F,gGAAgG;QAChG,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,QAAQ,EAAE,IAAI,iCAAgB,CAC1D,4BAA4B,EAAE,yBAAyB,EAAE,yCAAyC,CACrG,CAAC,CAAC;QAEH,sFAAsF;QACtF,MAAM,UAAU,GAAG,IAAA,6BAAc,EAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC,CAAC;QAChF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,gCAAgC,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;QACjF,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,yFAAyF;YACzF,GAAG,IAAA,mCAAoB,EAAC,UAAU,CAAC,MAAM;YACzC,uCAAuC;YACvC,+GAA+G,CAClH,CAAC;IACN,CAAC;IAED,oGAAoG;IACpG,iFAAiF;IACzE,KAAK,CAAC,oBAAoB,CAAC,QAAgB;QAC/C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,+BAA+B,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;QAChF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,QAAQ,EAAE,oBAAoB,EAAE,qBAAqB,CAAC,CAAC;QAC9G,IAAI,OAAO,KAAK,UAAU,IAAI,OAAO,KAAK,mBAAmB,EAAE,CAAC;YAC5D,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,iHAAiH,CACpH,CAAC;QACN,CAAC;IACL,CAAC;CACJ,CAAA;AAnDY,oDAAoB;+BAApB,oBAAoB;IAFhC,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;6CAG0B,mCAAY;QACZ,4BAAY;QACX,8BAAa;QACnB,kBAAO;QACL,sBAAS;GANhC,oBAAoB,CAmDhC","sourcesContent":["import { execSync } from 'child_process';\nimport { reviewJsonPath, reviewJsonSchemaHint, writeTemplate, CliExitError } from '@webpieces/rules-config';\nimport { provideSingleton } from '@webpieces/rules-config';\nimport { injectable } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BranchNaming } from '../workflow/branch-naming';\nimport { BuildAffected, BuildGateOptions } from '../workflow/build-affected';\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@provideSingleton()\n@injectable()\nexport class StartUpsertPrCommand {\n constructor(\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 ) {}\n\n async run(): Promise<void> {\n const repoRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();\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 // 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).\n const reviewPath = reviewJsonPath(repoRoot, this.aiBranchName.getFeatureName());\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)}\\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 // 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":";;;;AAAA,iDAAyC;AACzC,0DAA4H;AAC5H,0DAA2D;AAC3D,yCAAuC;AACvC,2EAAgE;AAChE,6DAAyD;AACzD,+DAA6E;AAC7E,mDAA+C;AAC/C,uDAAmD;AAEnD,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAEvE,oGAAoG;AACpG,uGAAuG;AACvG,yGAAyG;AAGlG,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IAER;IACA;IACA;IACA;IACA;IACA;IANrB,YACqB,cAA8B,EAC9B,YAA0B,EAC1B,YAA0B,EAC1B,aAA4B,EAC5B,OAAgB,EAChB,SAAoB;QALpB,mBAAc,GAAd,cAAc,CAAgB;QAC9B,iBAAY,GAAZ,YAAY,CAAc;QAC1B,iBAAY,GAAZ,YAAY,CAAc;QAC1B,kBAAa,GAAb,aAAa,CAAe;QAC5B,YAAO,GAAP,OAAO,CAAS;QAChB,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,iGAAiG;QACjG,gGAAgG;QAChG,kFAAkF;QAClF,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAEvC,MAAM,IAAI,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QAC1C,yEAAyE;QACzE,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,IAAA,wBAAQ,EAAC,2BAA2B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAEhI,6FAA6F;QAC7F,gGAAgG;QAChG,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,QAAQ,EAAE,IAAI,iCAAgB,CAC1D,4BAA4B,EAAE,yBAAyB,EAAE,yCAAyC,CACrG,CAAC,CAAC;QAEH,sFAAsF;QACtF,MAAM,UAAU,GAAG,IAAA,6BAAc,EAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC,CAAC;QAChF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,gCAAgC,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;QACjF,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,yFAAyF;YACzF,GAAG,IAAA,mCAAoB,EAAC,UAAU,CAAC,MAAM;YACzC,uCAAuC;YACvC,+GAA+G,CAClH,CAAC;IACN,CAAC;IAED,oGAAoG;IACpG,iFAAiF;IACzE,KAAK,CAAC,oBAAoB,CAAC,QAAgB;QAC/C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,+BAA+B,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;QAChF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,QAAQ,EAAE,oBAAoB,EAAE,qBAAqB,CAAC,CAAC;QAC9G,IAAI,OAAO,KAAK,UAAU,IAAI,OAAO,KAAK,mBAAmB,EAAE,CAAC;YAC5D,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,iHAAiH,CACpH,CAAC;QACN,CAAC;IACL,CAAC;CACJ,CAAA;AApDY,oDAAoB;+BAApB,oBAAoB;IAFhC,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;6CAG4B,6BAAc;QAChB,mCAAY;QACZ,4BAAY;QACX,8BAAa;QACnB,kBAAO;QACL,sBAAS;GAPhC,oBAAoB,CAoDhC","sourcesContent":["import { execSync } from 'child_process';\nimport { reviewJsonPath, reviewJsonSchemaHint, writeTemplate, CliExitError, RepoRootFinder } from '@webpieces/rules-config';\nimport { provideSingleton } from '@webpieces/rules-config';\nimport { injectable } from 'inversify';\nimport { AiBranchName } from '../workflow/git-readAiBranchName';\nimport { BranchNaming } from '../workflow/branch-naming';\nimport { BuildAffected, BuildGateOptions } from '../workflow/build-affected';\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@provideSingleton()\n@injectable()\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 ) {}\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 // 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).\n const reviewPath = reviewJsonPath(repoRoot, this.aiBranchName.getFeatureName());\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)}\\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 // 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,3 +1,4 @@
1
+ import { RepoRootFinder } from '@webpieces/rules-config';
1
2
  import { AiBranchName } from './workflow/git-readAiBranchName';
2
3
  import { ForkPoint } from './workflow/git-findForkPoint';
3
4
  import { MergeState } from './workflow/merge-state';
@@ -14,11 +15,12 @@ export declare class GatherInfoResult {
14
15
  }
15
16
  /** Gathers the 3-point merge context (fetch main, find fork point, write hashes) for merge-start. */
16
17
  export declare class GatherInfo {
18
+ private readonly repoRootFinder;
17
19
  private readonly aiBranchName;
18
20
  private readonly forkPoint;
19
21
  private readonly mergeState;
20
22
  private readonly gitExec;
21
- constructor(aiBranchName: AiBranchName, forkPoint: ForkPoint, mergeState: MergeState, gitExec: GitExec);
23
+ constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, forkPoint: ForkPoint, mergeState: MergeState, gitExec: GitExec);
22
24
  gatherInfo(): Promise<GatherInfoResult>;
23
25
  private validateCleanTree;
24
26
  private printHashPoints;
@@ -27,11 +27,13 @@ class GatherInfoResult {
27
27
  exports.GatherInfoResult = GatherInfoResult;
28
28
  /** Gathers the 3-point merge context (fetch main, find fork point, write hashes) for merge-start. */
29
29
  let GatherInfo = class GatherInfo {
30
+ repoRootFinder;
30
31
  aiBranchName;
31
32
  forkPoint;
32
33
  mergeState;
33
34
  gitExec;
34
- constructor(aiBranchName, forkPoint, mergeState, gitExec) {
35
+ constructor(repoRootFinder, aiBranchName, forkPoint, mergeState, gitExec) {
36
+ this.repoRootFinder = repoRootFinder;
35
37
  this.aiBranchName = aiBranchName;
36
38
  this.forkPoint = forkPoint;
37
39
  this.mergeState = mergeState;
@@ -42,7 +44,7 @@ let GatherInfo = class GatherInfo {
42
44
  async gatherInfo() {
43
45
  const currentBranch = (0, child_process_1.execSync)('git branch --show-current', { encoding: 'utf8' }).trim();
44
46
  const featureName = this.aiBranchName.getFeatureName();
45
- const repoRoot = (0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
47
+ const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
46
48
  const mergeDir = this.mergeState.mergeDirFor(repoRoot, featureName);
47
49
  fs.mkdirSync(mergeDir, { recursive: true });
48
50
  this.validateCleanTree(currentBranch, repoRoot);
@@ -102,7 +104,8 @@ exports.GatherInfo = GatherInfo;
102
104
  exports.GatherInfo = GatherInfo = tslib_1.__decorate([
103
105
  (0, rules_config_2.provideSingleton)(),
104
106
  (0, inversify_1.injectable)(),
105
- tslib_1.__metadata("design:paramtypes", [git_readAiBranchName_1.AiBranchName,
107
+ tslib_1.__metadata("design:paramtypes", [rules_config_1.RepoRootFinder,
108
+ git_readAiBranchName_1.AiBranchName,
106
109
  git_findForkPoint_1.ForkPoint,
107
110
  merge_state_1.MergeState,
108
111
  git_exec_1.GitExec])
@@ -1 +1 @@
1
- {"version":3,"file":"git-gatherInfo.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/git-gatherInfo.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 { CliExitError } from '@webpieces/rules-config';\nimport { provideSingleton } from '@webpieces/rules-config';\nimport { injectable } from 'inversify';\nimport { AiBranchName } from './workflow/git-readAiBranchName';\nimport { ForkPoint } from './workflow/git-findForkPoint';\nimport { MergeState } from './workflow/merge-state';\nimport { GitExec } from './workflow/git-exec';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\ninterface HashPoints {\n hashForkPoint: string;\n hashFeatureHead: string;\n hashMainHead: string;\n}\n\n// Result of gathering merge context: whether the branch is already even with main (fork point == main\n// HEAD, so there is nothing to merge) plus the 3 hash points. A class (not an object literal) per the\n// codebase's data-structure convention. gatherInfo RETURNS this instead of calling process.exit.\nexport class GatherInfoResult {\n alreadyUpToDate: boolean;\n hashes: HashPoints;\n\n constructor(alreadyUpToDate: boolean, hashes: HashPoints) {\n this.alreadyUpToDate = alreadyUpToDate;\n this.hashes = hashes;\n }\n}\n\n/** Gathers the 3-point merge context (fetch main, find fork point, write hashes) for merge-start. */\n@provideSingleton()\n@injectable()\nexport class GatherInfo {\n constructor(\n private readonly aiBranchName: AiBranchName,\n private readonly forkPoint: ForkPoint,\n private readonly mergeState: MergeState,\n private readonly gitExec: GitExec,\n ) {}\n\n // Gather the 3-point merge context for the current feature branch and RETURN whether the branch is\n // already even with main. NEVER calls process.exit: it is called as a library (from merge-start).\n async gatherInfo(): Promise<GatherInfoResult> {\n const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();\n const featureName = this.aiBranchName.getFeatureName();\n const repoRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();\n const mergeDir = this.mergeState.mergeDirFor(repoRoot, featureName);\n fs.mkdirSync(mergeDir, { recursive: true });\n\n this.validateCleanTree(currentBranch, repoRoot);\n\n process.stderr.write('\\n');\n process.stderr.write(SEP);\n process.stderr.write('📍 Gathering Merge Context\\n');\n process.stderr.write(SEP);\n process.stderr.write('\\n');\n process.stderr.write('Fetching latest changes from origin/main...\\n');\n spawnSync('git', ['fetch', 'origin', 'main'], { stdio: 'inherit' });\n\n await this.forkPoint.findForkPoint('merge');\n\n const hashesFile = path.join(mergeDir, 'updatemain-hashes.json');\n const hashes = JSON.parse(fs.readFileSync(hashesFile, 'utf8')) as HashPoints;\n\n this.printHashPoints(hashes, currentBranch, mergeDir);\n\n if (hashes.hashForkPoint === hashes.hashMainHead) {\n process.stderr.write(SEP);\n process.stderr.write('✅ Already up to date with main!\\n');\n process.stderr.write(SEP);\n process.stderr.write('\\n');\n process.stderr.write('Your branch has not diverged from main.\\n');\n process.stderr.write('There are no new changes from main to merge.\\n');\n process.stderr.write('\\n');\n process.stderr.write(SEP);\n return new GatherInfoResult(true, hashes);\n }\n\n process.stderr.write('Main has advanced. Merge will be needed.\\n');\n process.stderr.write('\\n');\n process.stderr.write(SEP);\n process.stderr.write('\\n');\n return new GatherInfoResult(false, hashes);\n }\n\n private validateCleanTree(currentBranch: string, repoRoot: string): void {\n if (currentBranch === 'main') {\n throw new CliExitError(1, '❌ Error: Already on main branch. No need to update from main.');\n }\n process.stderr.write(`Current branch: ${currentBranch}\\n`);\n // Require a fully-committed tree (tracked AND untracked). assertCleanTree uses\n // `git status --porcelain` (untracked-aware, gitignore-respecting) and aborts with instructions.\n this.gitExec.assertCleanTree(repoRoot);\n }\n\n private printHashPoints(hashes: HashPoints, currentBranch: string, mergeDir: string): void {\n process.stderr.write('📍 The 3 Hash Points:\\n');\n process.stderr.write(` 1. Fork point (A): ${hashes.hashForkPoint}\\n`);\n process.stderr.write(` (where ${currentBranch} diverged from main)\\n`);\n process.stderr.write('\\n');\n process.stderr.write(` 2. Feature HEAD (B): ${hashes.hashFeatureHead}\\n`);\n process.stderr.write(` (tip of ${currentBranch})\\n`);\n process.stderr.write('\\n');\n process.stderr.write(` 3. Main HEAD (C): ${hashes.hashMainHead}\\n`);\n process.stderr.write(' (current origin/main)\\n');\n process.stderr.write('\\n');\n process.stderr.write(`Merge directory: ${mergeDir}\\n`);\n process.stderr.write('\\n');\n }\n}\n"]}
1
+ {"version":3,"file":"git-gatherInfo.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/git-gatherInfo.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 { CliExitError, RepoRootFinder } from '@webpieces/rules-config';\nimport { provideSingleton } from '@webpieces/rules-config';\nimport { injectable } from 'inversify';\nimport { AiBranchName } from './workflow/git-readAiBranchName';\nimport { ForkPoint } from './workflow/git-findForkPoint';\nimport { MergeState } from './workflow/merge-state';\nimport { GitExec } from './workflow/git-exec';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\ninterface HashPoints {\n hashForkPoint: string;\n hashFeatureHead: string;\n hashMainHead: string;\n}\n\n// Result of gathering merge context: whether the branch is already even with main (fork point == main\n// HEAD, so there is nothing to merge) plus the 3 hash points. A class (not an object literal) per the\n// codebase's data-structure convention. gatherInfo RETURNS this instead of calling process.exit.\nexport class GatherInfoResult {\n alreadyUpToDate: boolean;\n hashes: HashPoints;\n\n constructor(alreadyUpToDate: boolean, hashes: HashPoints) {\n this.alreadyUpToDate = alreadyUpToDate;\n this.hashes = hashes;\n }\n}\n\n/** Gathers the 3-point merge context (fetch main, find fork point, write hashes) for merge-start. */\n@provideSingleton()\n@injectable()\nexport class GatherInfo {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly aiBranchName: AiBranchName,\n private readonly forkPoint: ForkPoint,\n private readonly mergeState: MergeState,\n private readonly gitExec: GitExec,\n ) {}\n\n // Gather the 3-point merge context for the current feature branch and RETURN whether the branch is\n // already even with main. NEVER calls process.exit: it is called as a library (from merge-start).\n async gatherInfo(): Promise<GatherInfoResult> {\n const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();\n const featureName = this.aiBranchName.getFeatureName();\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n const mergeDir = this.mergeState.mergeDirFor(repoRoot, featureName);\n fs.mkdirSync(mergeDir, { recursive: true });\n\n this.validateCleanTree(currentBranch, repoRoot);\n\n process.stderr.write('\\n');\n process.stderr.write(SEP);\n process.stderr.write('📍 Gathering Merge Context\\n');\n process.stderr.write(SEP);\n process.stderr.write('\\n');\n process.stderr.write('Fetching latest changes from origin/main...\\n');\n spawnSync('git', ['fetch', 'origin', 'main'], { stdio: 'inherit' });\n\n await this.forkPoint.findForkPoint('merge');\n\n const hashesFile = path.join(mergeDir, 'updatemain-hashes.json');\n const hashes = JSON.parse(fs.readFileSync(hashesFile, 'utf8')) as HashPoints;\n\n this.printHashPoints(hashes, currentBranch, mergeDir);\n\n if (hashes.hashForkPoint === hashes.hashMainHead) {\n process.stderr.write(SEP);\n process.stderr.write('✅ Already up to date with main!\\n');\n process.stderr.write(SEP);\n process.stderr.write('\\n');\n process.stderr.write('Your branch has not diverged from main.\\n');\n process.stderr.write('There are no new changes from main to merge.\\n');\n process.stderr.write('\\n');\n process.stderr.write(SEP);\n return new GatherInfoResult(true, hashes);\n }\n\n process.stderr.write('Main has advanced. Merge will be needed.\\n');\n process.stderr.write('\\n');\n process.stderr.write(SEP);\n process.stderr.write('\\n');\n return new GatherInfoResult(false, hashes);\n }\n\n private validateCleanTree(currentBranch: string, repoRoot: string): void {\n if (currentBranch === 'main') {\n throw new CliExitError(1, '❌ Error: Already on main branch. No need to update from main.');\n }\n process.stderr.write(`Current branch: ${currentBranch}\\n`);\n // Require a fully-committed tree (tracked AND untracked). assertCleanTree uses\n // `git status --porcelain` (untracked-aware, gitignore-respecting) and aborts with instructions.\n this.gitExec.assertCleanTree(repoRoot);\n }\n\n private printHashPoints(hashes: HashPoints, currentBranch: string, mergeDir: string): void {\n process.stderr.write('📍 The 3 Hash Points:\\n');\n process.stderr.write(` 1. Fork point (A): ${hashes.hashForkPoint}\\n`);\n process.stderr.write(` (where ${currentBranch} diverged from main)\\n`);\n process.stderr.write('\\n');\n process.stderr.write(` 2. Feature HEAD (B): ${hashes.hashFeatureHead}\\n`);\n process.stderr.write(` (tip of ${currentBranch})\\n`);\n process.stderr.write('\\n');\n process.stderr.write(` 3. Main HEAD (C): ${hashes.hashMainHead}\\n`);\n process.stderr.write(' (current origin/main)\\n');\n process.stderr.write('\\n');\n process.stderr.write(`Merge directory: ${mergeDir}\\n`);\n process.stderr.write('\\n');\n }\n}\n"]}
@@ -1,5 +1,8 @@
1
+ import { RepoRootFinder } from '@webpieces/rules-config';
1
2
  /** 30-day cleanup of stale per-feature merge/pr-review dirs (and the legacy flat layout). */
2
3
  export declare class CleanTmp {
4
+ private readonly repoRootFinder;
5
+ constructor(repoRootFinder: RepoRootFinder);
3
6
  cleanTmp(): Promise<void>;
4
7
  private cleanStaleSubdirs;
5
8
  private cleanLegacyTopLevel;
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CleanTmp = void 0;
4
4
  const tslib_1 = require("tslib");
5
- const child_process_1 = require("child_process");
6
5
  const fs = tslib_1.__importStar(require("fs"));
7
6
  const path = tslib_1.__importStar(require("path"));
8
7
  const rules_config_1 = require("@webpieces/rules-config");
@@ -17,8 +16,12 @@ const SEP = '━━━━━━━━━━━━━━━━━━━━━━
17
16
  const LEGACY_PREFIXES = ['merge-', 'review-', 'pr-'];
18
17
  /** 30-day cleanup of stale per-feature merge/pr-review dirs (and the legacy flat layout). */
19
18
  let CleanTmp = class CleanTmp {
19
+ repoRootFinder;
20
+ constructor(repoRootFinder) {
21
+ this.repoRootFinder = repoRootFinder;
22
+ }
20
23
  async cleanTmp() {
21
- const repoRoot = (0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
24
+ const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
22
25
  const tmpBase = path.join(repoRoot, rules_config_1.WEBPIECES_TMP_DIR);
23
26
  if (!fs.existsSync(tmpBase)) {
24
27
  return;
@@ -93,6 +96,7 @@ let CleanTmp = class CleanTmp {
93
96
  exports.CleanTmp = CleanTmp;
94
97
  exports.CleanTmp = CleanTmp = tslib_1.__decorate([
95
98
  (0, rules_config_2.provideSingleton)(),
96
- (0, inversify_1.injectable)()
99
+ (0, inversify_1.injectable)(),
100
+ tslib_1.__metadata("design:paramtypes", [rules_config_1.RepoRootFinder])
97
101
  ], CleanTmp);
98
102
  //# sourceMappingURL=cleanTmp.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cleanTmp.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/cleanTmp.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { WEBPIECES_TMP_DIR, MERGE_INFO_DIR, PR_REVIEW_DIR } from '@webpieces/rules-config';\nimport { provideSingleton } from '@webpieces/rules-config';\nimport { injectable } from 'inversify';\n\nconst CUTOFF_DAYS = 30;\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// Per-feature workflow dirs live under `.webpieces/merge-info/<feature>` and `.webpieces/pr-review/\n// <feature>`; those two homes are permanent (like hooks/ and instruct-ai/) and only their stale\n// subdirs are cleaned. `LEGACY_PREFIXES` sweeps the old flat top-level layout so it self-clears — which\n// also reaps the retired `pr-info/` home (it matches `pr-` and is no longer the active PR home).\nconst LEGACY_PREFIXES = ['merge-', 'review-', 'pr-'];\n\n/** 30-day cleanup of stale per-feature merge/pr-review dirs (and the legacy flat layout). */\n@provideSingleton()\n@injectable()\nexport class CleanTmp {\n async cleanTmp(): Promise<void> {\n const repoRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();\n const tmpBase = path.join(repoRoot, WEBPIECES_TMP_DIR);\n\n if (!fs.existsSync(tmpBase)) {\n return;\n }\n\n process.stdout.write('\\n');\n process.stdout.write(SEP);\n process.stdout.write('🧹 Cleaning Old Temporary Directories\\n');\n process.stdout.write(SEP);\n process.stdout.write('\\n');\n process.stdout.write(`Location: ${tmpBase}\\n`);\n process.stdout.write(`Retention: ${CUTOFF_DAYS} days\\n`);\n process.stdout.write('\\n');\n\n const cutoffMs = CUTOFF_DAYS * 24 * 60 * 60 * 1000;\n const now = Date.now();\n let deletedCount = 0;\n\n // Stale per-feature subdirs under each permanent home.\n deletedCount += this.cleanStaleSubdirs(path.join(tmpBase, MERGE_INFO_DIR), MERGE_INFO_DIR, now, cutoffMs);\n deletedCount += this.cleanStaleSubdirs(path.join(tmpBase, PR_REVIEW_DIR), PR_REVIEW_DIR, now, cutoffMs);\n // Legacy flat top-level dirs from before the merge-info/pr-review nesting (also reaps old pr-info/).\n deletedCount += this.cleanLegacyTopLevel(tmpBase, now, cutoffMs);\n\n if (deletedCount === 0) {\n process.stdout.write(` ✅ No directories older than ${CUTOFF_DAYS} days found\\n`);\n } else {\n process.stdout.write('\\n');\n process.stdout.write(` ✅ Deleted ${deletedCount} old director${deletedCount === 1 ? 'y' : 'ies'}\\n`);\n }\n\n process.stdout.write('\\n');\n process.stdout.write(SEP);\n process.stdout.write('\\n');\n }\n\n // Delete immediate subdirs of `parentDir` whose mtime is older than the cutoff. No-op if absent.\n private cleanStaleSubdirs(parentDir: string, label: string, now: number, cutoffMs: number): number {\n if (!fs.existsSync(parentDir)) return 0;\n let count = 0;\n for (const entry of fs.readdirSync(parentDir)) {\n const fullPath = path.join(parentDir, entry);\n const stat = fs.statSync(fullPath);\n if (!stat.isDirectory()) continue;\n if (now - stat.mtimeMs < cutoffMs) continue;\n process.stdout.write(` 🗑️ Deleting: ${label}/${entry}\\n`);\n fs.rmSync(fullPath, { recursive: true, force: true });\n count += 1;\n }\n return count;\n }\n\n // Sweep the pre-nesting flat layout (`merge-<feature>` etc.), skipping the current homes so this\n // never deletes merge-info/ or pr-review/ themselves. Retired `pr-info/` IS swept (matches `pr-`).\n private cleanLegacyTopLevel(tmpBase: string, now: number, cutoffMs: number): number {\n let count = 0;\n for (const entry of fs.readdirSync(tmpBase)) {\n if (entry === MERGE_INFO_DIR || entry === PR_REVIEW_DIR) continue;\n if (!LEGACY_PREFIXES.some((prefix: string): boolean => entry.startsWith(prefix))) continue;\n const fullPath = path.join(tmpBase, entry);\n const stat = fs.statSync(fullPath);\n if (!stat.isDirectory()) continue;\n if (now - stat.mtimeMs < cutoffMs) continue;\n process.stdout.write(` 🗑️ Deleting: ${entry}\\n`);\n fs.rmSync(fullPath, { recursive: true, force: true });\n count += 1;\n }\n return count;\n }\n}\n"]}
1
+ {"version":3,"file":"cleanTmp.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/cleanTmp.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { WEBPIECES_TMP_DIR, MERGE_INFO_DIR, PR_REVIEW_DIR, RepoRootFinder } from '@webpieces/rules-config';\nimport { provideSingleton } from '@webpieces/rules-config';\nimport { injectable } from 'inversify';\n\nconst CUTOFF_DAYS = 30;\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// Per-feature workflow dirs live under `.webpieces/merge-info/<feature>` and `.webpieces/pr-review/\n// <feature>`; those two homes are permanent (like hooks/ and instruct-ai/) and only their stale\n// subdirs are cleaned. `LEGACY_PREFIXES` sweeps the old flat top-level layout so it self-clears — which\n// also reaps the retired `pr-info/` home (it matches `pr-` and is no longer the active PR home).\nconst LEGACY_PREFIXES = ['merge-', 'review-', 'pr-'];\n\n/** 30-day cleanup of stale per-feature merge/pr-review dirs (and the legacy flat layout). */\n@provideSingleton()\n@injectable()\nexport class CleanTmp {\n constructor(private readonly repoRootFinder: RepoRootFinder) {}\n\n async cleanTmp(): Promise<void> {\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n const tmpBase = path.join(repoRoot, WEBPIECES_TMP_DIR);\n\n if (!fs.existsSync(tmpBase)) {\n return;\n }\n\n process.stdout.write('\\n');\n process.stdout.write(SEP);\n process.stdout.write('🧹 Cleaning Old Temporary Directories\\n');\n process.stdout.write(SEP);\n process.stdout.write('\\n');\n process.stdout.write(`Location: ${tmpBase}\\n`);\n process.stdout.write(`Retention: ${CUTOFF_DAYS} days\\n`);\n process.stdout.write('\\n');\n\n const cutoffMs = CUTOFF_DAYS * 24 * 60 * 60 * 1000;\n const now = Date.now();\n let deletedCount = 0;\n\n // Stale per-feature subdirs under each permanent home.\n deletedCount += this.cleanStaleSubdirs(path.join(tmpBase, MERGE_INFO_DIR), MERGE_INFO_DIR, now, cutoffMs);\n deletedCount += this.cleanStaleSubdirs(path.join(tmpBase, PR_REVIEW_DIR), PR_REVIEW_DIR, now, cutoffMs);\n // Legacy flat top-level dirs from before the merge-info/pr-review nesting (also reaps old pr-info/).\n deletedCount += this.cleanLegacyTopLevel(tmpBase, now, cutoffMs);\n\n if (deletedCount === 0) {\n process.stdout.write(` ✅ No directories older than ${CUTOFF_DAYS} days found\\n`);\n } else {\n process.stdout.write('\\n');\n process.stdout.write(` ✅ Deleted ${deletedCount} old director${deletedCount === 1 ? 'y' : 'ies'}\\n`);\n }\n\n process.stdout.write('\\n');\n process.stdout.write(SEP);\n process.stdout.write('\\n');\n }\n\n // Delete immediate subdirs of `parentDir` whose mtime is older than the cutoff. No-op if absent.\n private cleanStaleSubdirs(parentDir: string, label: string, now: number, cutoffMs: number): number {\n if (!fs.existsSync(parentDir)) return 0;\n let count = 0;\n for (const entry of fs.readdirSync(parentDir)) {\n const fullPath = path.join(parentDir, entry);\n const stat = fs.statSync(fullPath);\n if (!stat.isDirectory()) continue;\n if (now - stat.mtimeMs < cutoffMs) continue;\n process.stdout.write(` 🗑️ Deleting: ${label}/${entry}\\n`);\n fs.rmSync(fullPath, { recursive: true, force: true });\n count += 1;\n }\n return count;\n }\n\n // Sweep the pre-nesting flat layout (`merge-<feature>` etc.), skipping the current homes so this\n // never deletes merge-info/ or pr-review/ themselves. Retired `pr-info/` IS swept (matches `pr-`).\n private cleanLegacyTopLevel(tmpBase: string, now: number, cutoffMs: number): number {\n let count = 0;\n for (const entry of fs.readdirSync(tmpBase)) {\n if (entry === MERGE_INFO_DIR || entry === PR_REVIEW_DIR) continue;\n if (!LEGACY_PREFIXES.some((prefix: string): boolean => entry.startsWith(prefix))) continue;\n const fullPath = path.join(tmpBase, entry);\n const stat = fs.statSync(fullPath);\n if (!stat.isDirectory()) continue;\n if (now - stat.mtimeMs < cutoffMs) continue;\n process.stdout.write(` 🗑️ Deleting: ${entry}\\n`);\n fs.rmSync(fullPath, { recursive: true, force: true });\n count += 1;\n }\n return count;\n }\n}\n"]}
@@ -1,10 +1,12 @@
1
+ import { RepoRootFinder } from '@webpieces/rules-config';
1
2
  import { AiBranchName } from './git-readAiBranchName';
2
3
  import { MergeState } from './merge-state';
3
4
  /** Computes the 3-point fork point and guards against improper merges-from-main. */
4
5
  export declare class ForkPoint {
6
+ private readonly repoRootFinder;
5
7
  private readonly aiBranchName;
6
8
  private readonly mergeState;
7
- constructor(aiBranchName: AiBranchName, mergeState: MergeState);
9
+ constructor(repoRootFinder: RepoRootFinder, aiBranchName: AiBranchName, mergeState: MergeState);
8
10
  forkPointOutputDir(repoRoot: string, featureName: string, workflow: string): string;
9
11
  findForkPoint(workflow: string): Promise<void>;
10
12
  private checkMergeCommits;
@@ -13,9 +13,11 @@ const merge_state_1 = require("./merge-state");
13
13
  const SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
14
14
  /** Computes the 3-point fork point and guards against improper merges-from-main. */
15
15
  let ForkPoint = class ForkPoint {
16
+ repoRootFinder;
16
17
  aiBranchName;
17
18
  mergeState;
18
- constructor(aiBranchName, mergeState) {
19
+ constructor(repoRootFinder, aiBranchName, mergeState) {
20
+ this.repoRootFinder = repoRootFinder;
19
21
  this.aiBranchName = aiBranchName;
20
22
  this.mergeState = mergeState;
21
23
  }
@@ -34,7 +36,7 @@ let ForkPoint = class ForkPoint {
34
36
  }
35
37
  const featureName = this.aiBranchName.getFeatureName();
36
38
  const currentBranch = (0, child_process_1.execSync)('git branch --show-current', { encoding: 'utf8' }).trim();
37
- const repoRoot = (0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
39
+ const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
38
40
  const outputDir = this.forkPointOutputDir(repoRoot, featureName, workflow);
39
41
  const prefix = workflow === 'review' ? 'review-' : 'updatemain-';
40
42
  fs.mkdirSync(outputDir, { recursive: true });
@@ -126,7 +128,8 @@ exports.ForkPoint = ForkPoint;
126
128
  exports.ForkPoint = ForkPoint = tslib_1.__decorate([
127
129
  (0, rules_config_2.provideSingleton)(),
128
130
  (0, inversify_1.injectable)(),
129
- tslib_1.__metadata("design:paramtypes", [git_readAiBranchName_1.AiBranchName,
131
+ tslib_1.__metadata("design:paramtypes", [rules_config_1.RepoRootFinder,
132
+ git_readAiBranchName_1.AiBranchName,
130
133
  merge_state_1.MergeState])
131
134
  ], ForkPoint);
132
135
  //# sourceMappingURL=git-findForkPoint.js.map
@@ -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 } from '@webpieces/rules-config';\nimport { provideSingleton } from '@webpieces/rules-config';\nimport { injectable } 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@provideSingleton()\n@injectable()\nexport class ForkPoint {\n constructor(\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 = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();\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 pnpm wp-start-update\\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:\\n');\n process.stderr.write(` git merge --squash ${currentBranch}\\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 { provideSingleton } from '@webpieces/rules-config';\nimport { injectable } 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@provideSingleton()\n@injectable()\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 pnpm wp-start-update\\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:\\n');\n process.stderr.write(` git merge --squash ${currentBranch}\\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"]}
@@ -120,7 +120,7 @@ let MergeEnd = class MergeEnd {
120
120
  (0, rules_config_1.logBranchMutation)(repoRoot, renameEvent);
121
121
  // Branch now contains origin/main — stamp a clean main-sync status so the feature-branch-guard
122
122
  // unblocks edits immediately (no wait for the async refresher).
123
- (0, rules_config_1.stampCleanMainSyncStatus)((0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim());
123
+ (0, rules_config_1.stampCleanMainSyncStatus)(repoRoot);
124
124
  // Clean sync → the pre-merge snapshot was never needed; delete it LAST. Conflict sync → keep it.
125
125
  const backupKept = hadConflict;
126
126
  if (!backupKept && this.localBranchExists(ctx.backupBranch)) {
@@ -1 +1 @@
1
- {"version":3,"file":"merge-end.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/merge-end.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 MERGE_EXPLANATION_FILE, stampCleanMainSyncStatus, CliExitError,\n MutationVerb, BranchMutationEvent, logBranchMutation,\n} from '@webpieces/rules-config';\nimport { provideSingleton } from '@webpieces/rules-config';\nimport { injectable } from 'inversify';\nimport { BranchNaming } from './branch-naming';\nimport { CleanTmp } from './cleanTmp';\nimport { GitExec } from './git-exec';\nimport { MergeContext } from './merge-start';\nimport { MergeState } from './merge-state';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// merge-END: the second half of the 3-point squash-merge lifecycle, symmetric with merge-START. Given\n// the branch context, it (optionally) validates + commits the AI's conflict resolution, then ALWAYS\n// finalizes — force-pushes `<branch>Squash` to the stable feature branch and renames it BACK to that\n// same feature name (local == remote == PR head), stamps a clean main-sync status, clears the marker,\n// and sweeps stale tmp. RunUpdate (clean path / validated resume), wp-finish-update, and\n// wp-finish-upsert-pr (conflict resolution) all call THIS, so finalization happens in exactly one place.\n@provideSingleton()\n@injectable()\nexport class MergeEnd {\n constructor(\n private readonly branchNaming: BranchNaming,\n private readonly cleanTmpService: CleanTmp,\n private readonly gitExec: GitExec,\n private readonly mergeState: MergeState,\n ) {}\n\n /**\n * Complete a 3-point squash merge. `conflictedFiles` non-null means a conflict was resolved by the\n * AI and must be validated + committed before finalizing; null means a clean merge that merge-START\n * already committed (finalize only). Either way the merge ends fully finalized on the feature branch.\n */\n async mergeEnd(\n repoRoot: string, verb: MutationVerb, mergeDir: string, ctx: MergeContext, conflictedFiles: string[] | null,\n ): Promise<void> {\n if (conflictedFiles !== null) {\n process.stdout.write('\\n' + SEP + '🔎 Validating Merge Resolution\\n' + SEP + '\\n');\n this.validateResolution(repoRoot, mergeDir, conflictedFiles);\n // Stage the AI's resolved conflicts, but NEVER sweep untracked files into the squash commit.\n // Fail on untracked so the AI commits or deletes them explicitly; then `git add -u` stages\n // tracked resolutions only.\n this.gitExec.assertNoUntracked(repoRoot);\n this.gitExec.runGitChecked(['add', '-u'], 'Failed to stage resolved files');\n\n const nothingStaged = spawnSync('git', ['diff-index', '--quiet', '--cached', 'HEAD', '--']).status === 0;\n if (!nothingStaged) {\n this.gitExec.runGitChecked(\n ['commit', '-m', `Squash merge of ${ctx.currentBranch} (conflicts resolved)`],\n 'Failed to commit resolved merge',\n );\n }\n fs.writeFileSync(path.join(mergeDir, 'conflicts-resolved'), '');\n process.stdout.write('\\n✅ Merge validated and committed.\\n');\n }\n\n // A marker in THIS run dir means the sync hit conflicts (marker is written only on hand-back).\n // Read it BEFORE clearMergeMarker so finalize knows whether to keep the pre-merge snapshot.\n const hadConflict = this.mergeState.readMergeMarker(mergeDir) !== null;\n this.finalizeBranch(repoRoot, verb, ctx, hadConflict);\n this.mergeState.clearMergeMarker(mergeDir);\n await this.cleanTmpService.cleanTmp();\n }\n\n // Validate the AI's resolution of the conflicted files. Throws CliExitError with a fix instruction\n // on any failure; returns only when all three checks pass.\n private validateResolution(repoRoot: string, mergeDir: string, conflictedFiles: string[]): void {\n // 1. Scoped conflict-marker scan (only the conflicted files — O(conflicts), not O(repo)).\n const scan = this.mergeState.scanConflictMarkers(repoRoot, conflictedFiles);\n if (!scan.clean) {\n throw new CliExitError(1,\n '❌ Unresolved conflict markers (<<<<<<< / ======= / >>>>>>>) remain in:\\n' +\n scan.filesWithMarkers.map((file: string): string => ` - ${file}`).join('\\n') +\n '\\n\\nResolve them, then re-run: pnpm wp-finish-upsert-pr',\n );\n }\n\n // 2. Ensure git itself has no remaining unmerged entries.\n const unmerged = execSync('git diff --name-only --diff-filter=U', { encoding: 'utf8' }).trim();\n if (unmerged !== '') {\n throw new CliExitError(1,\n '❌ Git still reports unmerged files:\\n' + unmerged +\n '\\n\\nResolve and `git add` them, then re-run: pnpm wp-finish-upsert-pr',\n );\n }\n process.stdout.write('✅ No conflict markers in resolved files.\\n');\n\n // 3. Explanation check — every conflicted file must have a non-empty merge-explanation.md in its\n // per-file context dir, proving the AI deliberately 3-point merged it (and recording how).\n const explanations = this.mergeState.scanMergeExplanations(mergeDir, conflictedFiles);\n if (!explanations.clean) {\n const missing = explanations.filesWithMarkers\n .map((file: string): string => ` - ${file}\\n → ${path.join(this.mergeState.perFileContextDir(mergeDir, file), MERGE_EXPLANATION_FILE)}`)\n .join('\\n');\n throw new CliExitError(1,\n `❌ Missing/empty merge explanation (${MERGE_EXPLANATION_FILE}) for:\\n` +\n missing +\n '\\n\\nWrite a few sentences on how you resolved each (which side, what you combined, why),\\n' +\n 'then re-run: pnpm wp-finish-upsert-pr',\n );\n }\n process.stdout.write('✅ Merge explanations present for all resolved files.\\n');\n }\n\n private localBranchExists(name: string): boolean {\n return spawnSync('git', ['show-ref', '--verify', '--quiet', `refs/heads/${name}`]).status === 0;\n }\n\n // Force-push the squash branch to the stable feature branch (where the single PR lives), then RENAME\n // the local squash branch back to that SAME feature name. On a CLEAN sync the pre-merge snapshot is\n // disposable, so it's deleted at the very end; a CONFLICT sync keeps it.\n private finalizeBranch(repoRoot: string, verb: MutationVerb, ctx: MergeContext, hadConflict: boolean): void {\n process.stdout.write('\\n' + SEP + '🗑️ Finalizing\\n' + SEP + '\\n');\n const base = this.branchNaming.baseBranchName(ctx.currentBranch);\n this.gitExec.runGitChecked(['branch', '-D', ctx.currentBranch], 'Failed to delete old feature branch');\n\n const remoteExists = spawnSync('git', ['ls-remote', '--exit-code', '--heads', 'origin', base]).status === 0;\n if (remoteExists) {\n process.stdout.write(ctx.prNumber ? `Updating PR #${ctx.prNumber} (force-with-lease)...\\n` : 'Updating remote branch (force-with-lease)...\\n');\n this.gitExec.runGitChecked(['push', '-u', '--force-with-lease', 'origin', `${ctx.squashBranch}:${base}`], 'Failed to push to origin');\n } else {\n process.stdout.write('No remote branch — local only.\\n');\n }\n this.gitExec.runGitChecked(['checkout', ctx.squashBranch], 'Failed to checkout squash branch');\n // Free the rename target: `base` is normally the branch we just deleted, but a backward-compat\n // sync from a leftover `…wpN` can leave a separate stale `base` lingering — drop it too.\n if (base !== ctx.currentBranch && this.localBranchExists(base)) {\n this.gitExec.runGitChecked(['branch', '-D', base], 'Failed to delete stale base branch');\n }\n this.gitExec.runGitChecked(['branch', '-m', base], 'Failed to rename squash branch to the feature name');\n const renameEvent = new BranchMutationEvent(verb, 'RENAME');\n renameEvent.fromBranch = ctx.currentBranch;\n renameEvent.toBranch = base;\n logBranchMutation(repoRoot, renameEvent);\n\n // Branch now contains origin/main — stamp a clean main-sync status so the feature-branch-guard\n // unblocks edits immediately (no wait for the async refresher).\n stampCleanMainSyncStatus(execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim());\n\n // Clean sync → the pre-merge snapshot was never needed; delete it LAST. Conflict sync → keep it.\n const backupKept = hadConflict;\n if (!backupKept && this.localBranchExists(ctx.backupBranch)) {\n this.gitExec.runGitChecked(['branch', '-D', ctx.backupBranch], 'Failed to delete clean-merge backup');\n }\n\n const finalizeEvent = new BranchMutationEvent(verb, 'FINALIZE');\n finalizeEvent.fromBranch = ctx.currentBranch;\n finalizeEvent.toBranch = base;\n finalizeEvent.outcome = 'finalized';\n finalizeEvent.artifacts = [backupKept ? `backup=${ctx.backupBranch}` : `backupDeleted=${ctx.backupBranch}`, `remotePR=${base}`];\n logBranchMutation(repoRoot, finalizeEvent);\n\n this.printSyncRecap(base, ctx.backupBranch, ctx.prNumber, remoteExists, backupKept);\n }\n\n // The explicit, numbered \"here is exactly what I did\" recap the AI (and human) reads after a sync.\n private printSyncRecap(feature: string, backupBranch: string, prNumber: string, pushed: boolean, backupKept: boolean): void {\n const remoteLine = pushed\n ? `landed back on ${feature} (== origin/${feature}${prNumber ? ` == PR #${prNumber}` : ''} — names match)`\n : `landed back on ${feature} (local only — no remote branch yet)`;\n const step1 = backupKept\n ? `snapshotted your pre-merge state → ${backupBranch} (kept — this merge had conflicts)`\n : `snapshotted your pre-merge state → ${backupBranch} (auto-removed — clean merge, no undo needed)`;\n const trailer = backupKept\n ? ` Pre-merge snapshot trail: git branch --list '${feature}PreMerge*'\\n` +\n ` Its conflict context lives in the paired merge-<n>/ folder under .webpieces/merge-info/\\n` +\n ` Prune this run's snapshot when safe: git branch -D ${backupBranch}\\n\\n`\n : '\\n';\n process.stdout.write(\n '\\n' + SEP + '✅ Sync complete — here is exactly what I did\\n' + SEP + '\\n' +\n ` 1. ${step1}\\n` +\n ` 2. pulled origin/main\\n` +\n ` 3. squash-merged your work onto main\\n` +\n ` 4. ${remoteLine}\\n\\n` +\n trailer,\n );\n }\n}\n"]}
1
+ {"version":3,"file":"merge-end.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/merge-end.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 MERGE_EXPLANATION_FILE, stampCleanMainSyncStatus, CliExitError,\n MutationVerb, BranchMutationEvent, logBranchMutation,\n} from '@webpieces/rules-config';\nimport { provideSingleton } from '@webpieces/rules-config';\nimport { injectable } from 'inversify';\nimport { BranchNaming } from './branch-naming';\nimport { CleanTmp } from './cleanTmp';\nimport { GitExec } from './git-exec';\nimport { MergeContext } from './merge-start';\nimport { MergeState } from './merge-state';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// merge-END: the second half of the 3-point squash-merge lifecycle, symmetric with merge-START. Given\n// the branch context, it (optionally) validates + commits the AI's conflict resolution, then ALWAYS\n// finalizes — force-pushes `<branch>Squash` to the stable feature branch and renames it BACK to that\n// same feature name (local == remote == PR head), stamps a clean main-sync status, clears the marker,\n// and sweeps stale tmp. RunUpdate (clean path / validated resume), wp-finish-update, and\n// wp-finish-upsert-pr (conflict resolution) all call THIS, so finalization happens in exactly one place.\n@provideSingleton()\n@injectable()\nexport class MergeEnd {\n constructor(\n private readonly branchNaming: BranchNaming,\n private readonly cleanTmpService: CleanTmp,\n private readonly gitExec: GitExec,\n private readonly mergeState: MergeState,\n ) {}\n\n /**\n * Complete a 3-point squash merge. `conflictedFiles` non-null means a conflict was resolved by the\n * AI and must be validated + committed before finalizing; null means a clean merge that merge-START\n * already committed (finalize only). Either way the merge ends fully finalized on the feature branch.\n */\n async mergeEnd(\n repoRoot: string, verb: MutationVerb, mergeDir: string, ctx: MergeContext, conflictedFiles: string[] | null,\n ): Promise<void> {\n if (conflictedFiles !== null) {\n process.stdout.write('\\n' + SEP + '🔎 Validating Merge Resolution\\n' + SEP + '\\n');\n this.validateResolution(repoRoot, mergeDir, conflictedFiles);\n // Stage the AI's resolved conflicts, but NEVER sweep untracked files into the squash commit.\n // Fail on untracked so the AI commits or deletes them explicitly; then `git add -u` stages\n // tracked resolutions only.\n this.gitExec.assertNoUntracked(repoRoot);\n this.gitExec.runGitChecked(['add', '-u'], 'Failed to stage resolved files');\n\n const nothingStaged = spawnSync('git', ['diff-index', '--quiet', '--cached', 'HEAD', '--']).status === 0;\n if (!nothingStaged) {\n this.gitExec.runGitChecked(\n ['commit', '-m', `Squash merge of ${ctx.currentBranch} (conflicts resolved)`],\n 'Failed to commit resolved merge',\n );\n }\n fs.writeFileSync(path.join(mergeDir, 'conflicts-resolved'), '');\n process.stdout.write('\\n✅ Merge validated and committed.\\n');\n }\n\n // A marker in THIS run dir means the sync hit conflicts (marker is written only on hand-back).\n // Read it BEFORE clearMergeMarker so finalize knows whether to keep the pre-merge snapshot.\n const hadConflict = this.mergeState.readMergeMarker(mergeDir) !== null;\n this.finalizeBranch(repoRoot, verb, ctx, hadConflict);\n this.mergeState.clearMergeMarker(mergeDir);\n await this.cleanTmpService.cleanTmp();\n }\n\n // Validate the AI's resolution of the conflicted files. Throws CliExitError with a fix instruction\n // on any failure; returns only when all three checks pass.\n private validateResolution(repoRoot: string, mergeDir: string, conflictedFiles: string[]): void {\n // 1. Scoped conflict-marker scan (only the conflicted files — O(conflicts), not O(repo)).\n const scan = this.mergeState.scanConflictMarkers(repoRoot, conflictedFiles);\n if (!scan.clean) {\n throw new CliExitError(1,\n '❌ Unresolved conflict markers (<<<<<<< / ======= / >>>>>>>) remain in:\\n' +\n scan.filesWithMarkers.map((file: string): string => ` - ${file}`).join('\\n') +\n '\\n\\nResolve them, then re-run: pnpm wp-finish-upsert-pr',\n );\n }\n\n // 2. Ensure git itself has no remaining unmerged entries.\n const unmerged = execSync('git diff --name-only --diff-filter=U', { encoding: 'utf8' }).trim();\n if (unmerged !== '') {\n throw new CliExitError(1,\n '❌ Git still reports unmerged files:\\n' + unmerged +\n '\\n\\nResolve and `git add` them, then re-run: pnpm wp-finish-upsert-pr',\n );\n }\n process.stdout.write('✅ No conflict markers in resolved files.\\n');\n\n // 3. Explanation check — every conflicted file must have a non-empty merge-explanation.md in its\n // per-file context dir, proving the AI deliberately 3-point merged it (and recording how).\n const explanations = this.mergeState.scanMergeExplanations(mergeDir, conflictedFiles);\n if (!explanations.clean) {\n const missing = explanations.filesWithMarkers\n .map((file: string): string => ` - ${file}\\n → ${path.join(this.mergeState.perFileContextDir(mergeDir, file), MERGE_EXPLANATION_FILE)}`)\n .join('\\n');\n throw new CliExitError(1,\n `❌ Missing/empty merge explanation (${MERGE_EXPLANATION_FILE}) for:\\n` +\n missing +\n '\\n\\nWrite a few sentences on how you resolved each (which side, what you combined, why),\\n' +\n 'then re-run: pnpm wp-finish-upsert-pr',\n );\n }\n process.stdout.write('✅ Merge explanations present for all resolved files.\\n');\n }\n\n private localBranchExists(name: string): boolean {\n return spawnSync('git', ['show-ref', '--verify', '--quiet', `refs/heads/${name}`]).status === 0;\n }\n\n // Force-push the squash branch to the stable feature branch (where the single PR lives), then RENAME\n // the local squash branch back to that SAME feature name. On a CLEAN sync the pre-merge snapshot is\n // disposable, so it's deleted at the very end; a CONFLICT sync keeps it.\n private finalizeBranch(repoRoot: string, verb: MutationVerb, ctx: MergeContext, hadConflict: boolean): void {\n process.stdout.write('\\n' + SEP + '🗑️ Finalizing\\n' + SEP + '\\n');\n const base = this.branchNaming.baseBranchName(ctx.currentBranch);\n this.gitExec.runGitChecked(['branch', '-D', ctx.currentBranch], 'Failed to delete old feature branch');\n\n const remoteExists = spawnSync('git', ['ls-remote', '--exit-code', '--heads', 'origin', base]).status === 0;\n if (remoteExists) {\n process.stdout.write(ctx.prNumber ? `Updating PR #${ctx.prNumber} (force-with-lease)...\\n` : 'Updating remote branch (force-with-lease)...\\n');\n this.gitExec.runGitChecked(['push', '-u', '--force-with-lease', 'origin', `${ctx.squashBranch}:${base}`], 'Failed to push to origin');\n } else {\n process.stdout.write('No remote branch — local only.\\n');\n }\n this.gitExec.runGitChecked(['checkout', ctx.squashBranch], 'Failed to checkout squash branch');\n // Free the rename target: `base` is normally the branch we just deleted, but a backward-compat\n // sync from a leftover `…wpN` can leave a separate stale `base` lingering — drop it too.\n if (base !== ctx.currentBranch && this.localBranchExists(base)) {\n this.gitExec.runGitChecked(['branch', '-D', base], 'Failed to delete stale base branch');\n }\n this.gitExec.runGitChecked(['branch', '-m', base], 'Failed to rename squash branch to the feature name');\n const renameEvent = new BranchMutationEvent(verb, 'RENAME');\n renameEvent.fromBranch = ctx.currentBranch;\n renameEvent.toBranch = base;\n logBranchMutation(repoRoot, renameEvent);\n\n // Branch now contains origin/main — stamp a clean main-sync status so the feature-branch-guard\n // unblocks edits immediately (no wait for the async refresher).\n stampCleanMainSyncStatus(repoRoot);\n\n // Clean sync → the pre-merge snapshot was never needed; delete it LAST. Conflict sync → keep it.\n const backupKept = hadConflict;\n if (!backupKept && this.localBranchExists(ctx.backupBranch)) {\n this.gitExec.runGitChecked(['branch', '-D', ctx.backupBranch], 'Failed to delete clean-merge backup');\n }\n\n const finalizeEvent = new BranchMutationEvent(verb, 'FINALIZE');\n finalizeEvent.fromBranch = ctx.currentBranch;\n finalizeEvent.toBranch = base;\n finalizeEvent.outcome = 'finalized';\n finalizeEvent.artifacts = [backupKept ? `backup=${ctx.backupBranch}` : `backupDeleted=${ctx.backupBranch}`, `remotePR=${base}`];\n logBranchMutation(repoRoot, finalizeEvent);\n\n this.printSyncRecap(base, ctx.backupBranch, ctx.prNumber, remoteExists, backupKept);\n }\n\n // The explicit, numbered \"here is exactly what I did\" recap the AI (and human) reads after a sync.\n private printSyncRecap(feature: string, backupBranch: string, prNumber: string, pushed: boolean, backupKept: boolean): void {\n const remoteLine = pushed\n ? `landed back on ${feature} (== origin/${feature}${prNumber ? ` == PR #${prNumber}` : ''} — names match)`\n : `landed back on ${feature} (local only — no remote branch yet)`;\n const step1 = backupKept\n ? `snapshotted your pre-merge state → ${backupBranch} (kept — this merge had conflicts)`\n : `snapshotted your pre-merge state → ${backupBranch} (auto-removed — clean merge, no undo needed)`;\n const trailer = backupKept\n ? ` Pre-merge snapshot trail: git branch --list '${feature}PreMerge*'\\n` +\n ` Its conflict context lives in the paired merge-<n>/ folder under .webpieces/merge-info/\\n` +\n ` Prune this run's snapshot when safe: git branch -D ${backupBranch}\\n\\n`\n : '\\n';\n process.stdout.write(\n '\\n' + SEP + '✅ Sync complete — here is exactly what I did\\n' + SEP + '\\n' +\n ` 1. ${step1}\\n` +\n ` 2. pulled origin/main\\n` +\n ` 3. squash-merged your work onto main\\n` +\n ` 4. ${remoteLine}\\n\\n` +\n trailer,\n );\n }\n}\n"]}