@webpieces/pr-gate 0.3.290 → 0.3.292

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.290",
3
+ "version": "0.3.292",
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",
@@ -23,7 +23,7 @@
23
23
  "directory": "packages/tooling/pr-gate"
24
24
  },
25
25
  "dependencies": {
26
- "@webpieces/rules-config": "0.3.290"
26
+ "@webpieces/rules-config": "0.3.292"
27
27
  },
28
28
  "publishConfig": {
29
29
  "access": "public"
@@ -26,7 +26,15 @@ function gitOut(args) {
26
26
  const result = (0, child_process_1.spawnSync)('git', args, { encoding: 'utf8' });
27
27
  return result.status === 0 ? (result.stdout ?? '').trim() : '';
28
28
  }
29
- function buildDashboard(repoRoot, buildPassed, review) {
29
+ // The user-facing PR title: the AI-authored review.title, or — if the AI omitted it — a readable
30
+ // fallback derived from the stable feature name (NEVER the internal `Squash merge of <branch>` commit
31
+ // subject, which leaked bookkeeping into the PR title).
32
+ function prTitleFrom(review) {
33
+ if (review.title !== '')
34
+ return review.title;
35
+ return (0, git_readAiBranchName_1.getFeatureName)().replace(/[-/]+/g, ' ').trim();
36
+ }
37
+ function buildDashboard(repoRoot, buildPassed, review, title) {
30
38
  const config = (0, rules_config_1.loadAndValidate)(repoRoot).prGate;
31
39
  const forkPoint = gitOut(['merge-base', 'origin/main', 'HEAD']);
32
40
  const featureHead = gitOut(['rev-parse', 'HEAD']);
@@ -34,36 +42,36 @@ function buildDashboard(repoRoot, buildPassed, review) {
34
42
  const range = `${forkPoint}..${featureHead}`;
35
43
  const changedFiles = gitOut(['diff', range, '--name-only']).split('\n').filter((f) => f.trim() !== '');
36
44
  const patch = gitOut(['diff', range]);
37
- const title = gitOut(['log', '-1', '--format=%s']);
38
45
  const gateResults = (0, dashboard_1.computeGateResults)(config.gates, changedFiles);
39
46
  const disables = (0, dashboard_1.countAddedDisables)(patch);
40
47
  const input = new dashboard_1.DashboardInput(title, gateResults, disables, buildPassed, forkPoint, featureHead, mainHead, review);
41
48
  return (0, dashboard_1.renderDashboard)(input);
42
49
  }
43
- // The PR always lives on the stable base branch (the local branch may be a numbered generation
44
- // base2/base3/…). Look up / create / merge against `baseBranch`, never the numbered branch, or
45
- // generation 2+ would fail to find its PR and open a duplicate.
46
- function upsertPr(repoRoot, baseBranch, body) {
50
+ // The PR, the remote branch, and the local branch all share the one stable feature name now. Look up /
51
+ // create / merge against `baseBranch` (baseBranchName also tolerates a leftover `…wpN` mid-transition),
52
+ // or a resolve from such a leftover could fail to find its PR and open a duplicate.
53
+ function upsertPr(repoRoot, baseBranch, body, title) {
47
54
  const prDir = (0, rules_config_1.prDirFor)(repoRoot, (0, git_readAiBranchName_1.getFeatureName)());
48
55
  fs.mkdirSync(prDir, { recursive: true });
49
56
  const bodyFile = path.join(prDir, 'pr-body.md');
50
57
  fs.writeFileSync(bodyFile, body + '\n');
51
- const existing = gitOut(['log', '-1', '--format=%s']); // title fallback
52
58
  const prNumber = (0, child_process_1.spawnSync)('gh', ['pr', 'list', '--head', baseBranch, '--json', 'number', '--jq', '.[0].number'], { encoding: 'utf8' });
53
59
  const num = prNumber.status === 0 ? (prNumber.stdout ?? '').trim() : '';
54
60
  if (num === '') {
55
61
  process.stdout.write('Creating PR...\n');
56
- const create = (0, child_process_1.spawnSync)('gh', ['pr', 'create', '--head', baseBranch, '--base', 'main', '--title', existing, '--body-file', bodyFile], { stdio: 'inherit' });
62
+ const create = (0, child_process_1.spawnSync)('gh', ['pr', 'create', '--head', baseBranch, '--base', 'main', '--title', title, '--body-file', bodyFile], { stdio: 'inherit' });
57
63
  if (create.status !== 0) {
58
64
  process.stderr.write('⚠️ gh pr create failed — create the PR manually with the body in:\n ' + bodyFile + '\n');
59
- return;
65
+ return '';
60
66
  }
61
67
  }
62
68
  else {
63
69
  process.stdout.write(`Updating PR #${num}...\n`);
64
- (0, child_process_1.spawnSync)('gh', ['pr', 'edit', num, '--body-file', bodyFile], { stdio: 'inherit' });
70
+ // Keep the title in sync with the latest review.title (not just the body).
71
+ (0, child_process_1.spawnSync)('gh', ['pr', 'edit', num, '--title', title, '--body-file', bodyFile], { stdio: 'inherit' });
65
72
  }
66
73
  (0, child_process_1.spawnSync)('gh', ['pr', 'merge', baseBranch, '--auto', '--squash'], { stdio: 'inherit' });
74
+ return num;
67
75
  }
68
76
  async function main() {
69
77
  const repoRoot = (0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
@@ -84,14 +92,20 @@ async function main() {
84
92
  (0, git_exec_1.assertCleanTree)(repoRoot);
85
93
  // 3. Authoritative build gate, then push, then post.
86
94
  (0, build_affected_1.runBuildGate)(repoRoot, new build_affected_1.BuildGateOptions('🛠️ Build gate (authoritative)', 'pnpm wp-finish-upsert-pr', 'Build failed — no PR created/updated.'));
87
- // After finalize the local branch is the numbered generation (base2/…); the remote branch and
88
- // PR live on the stable base name push and upsert against that.
95
+ // After finalize the local branch, the remote branch, and the PR all share the SAME stable name —
96
+ // push and upsert against it. (baseBranchName is a no-op on the already-stable name; it also
97
+ // tolerates a leftover `…wpN` mid-transition.)
89
98
  const base = (0, branch_naming_1.baseBranchName)((0, child_process_1.execSync)('git branch --show-current', { encoding: 'utf8' }).trim());
90
99
  (0, git_exec_1.ensurePushed)(base);
91
100
  process.stdout.write('\n' + SEP + '📋 Dashboard + PR\n' + SEP + '\n');
92
- const body = buildDashboard(repoRoot, true, review);
93
- upsertPr(repoRoot, base, body);
94
- process.stdout.write('\n✅ Done.\n');
101
+ const title = prTitleFrom(review);
102
+ const body = buildDashboard(repoRoot, true, review, title);
103
+ const prNum = upsertPr(repoRoot, base, body, title);
104
+ process.stdout.write('\n' + SEP + '✅ PR finished — here is exactly what I did\n' + SEP + '\n' +
105
+ ` 1. validated the build gate (authoritative)\n` +
106
+ ` 2. force-pushed your work to origin/${base}\n` +
107
+ ` 3. ${prNum ? `updated/created PR #${prNum}` : 'created the PR'} titled: "${title}"\n` +
108
+ ` You are on ${base} — same name as the remote branch and the PR head.\n\n`);
95
109
  }
96
110
  if (require.main === module)
97
111
  (0, rules_config_1.runMain)(main);
@@ -1 +1 @@
1
- {"version":3,"file":"git-finishUpsertPr.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/git-finishUpsertPr.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\nimport { execSync, spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n loadAndValidate,\n loadReviewJson,\n prDirFor,\n reviewJsonPath,\n ReviewJson,\n writeTemplate,\n runMain,\n} from '@webpieces/rules-config';\nimport { getFeatureName } from './workflow/git-readAiBranchName';\nimport { baseBranchName } from './workflow/branch-naming';\nimport { assertCleanTree, ensurePushed } from './workflow/git-exec';\nimport { runBuildGate, BuildGateOptions } from './workflow/build-affected';\nimport { mergeDirFor, readMergeMarker } from './workflow/merge-state';\nimport { mergeEnd } from './workflow/merge-end';\nimport { MergeContext } from './workflow/merge-start';\nimport {\n computeGateResults,\n countAddedDisables,\n renderDashboard,\n DashboardInput,\n} from '../dashboard/dashboard';\n\n// FINISH of the AI-first PR flow. Runs after the AI has written review.json (see wp-start-upsert-pr).\n// Responsibilities, in order: (1) if a 3-point merge was in progress, validate + commit + FINALIZE the\n// AI's resolution via merge-END (so the PR is posted from the finalized feature branch, not the squash\n// branch); (2) REQUIRE review.json (hard-fail with the schema if absent/invalid); (3) run the\n// authoritative build gate; (4) render the dashboard; (5) create/update the PR via `gh`. This is the\n// ONLY command that posts PRs.\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\nfunction gitOut(args: string[]): string {\n const result = spawnSync('git', args, { encoding: 'utf8' });\n return result.status === 0 ? (result.stdout ?? '').trim() : '';\n}\n\nfunction buildDashboard(repoRoot: string, buildPassed: boolean, review: ReviewJson): string {\n const config = loadAndValidate(repoRoot).prGate;\n const forkPoint = gitOut(['merge-base', 'origin/main', 'HEAD']);\n const featureHead = gitOut(['rev-parse', 'HEAD']);\n const mainHead = gitOut(['rev-parse', 'origin/main']);\n const range = `${forkPoint}..${featureHead}`;\n const changedFiles = gitOut(['diff', range, '--name-only']).split('\\n').filter((f: string): boolean => f.trim() !== '');\n const patch = gitOut(['diff', range]);\n const title = gitOut(['log', '-1', '--format=%s']);\n\n const gateResults = computeGateResults(config.gates, changedFiles);\n const disables = countAddedDisables(patch);\n const input = new DashboardInput(title, gateResults, disables, buildPassed, forkPoint, featureHead, mainHead, review);\n return renderDashboard(input);\n}\n\n// The PR always lives on the stable base branch (the local branch may be a numbered generation\n// base2/base3/…). Look up / create / merge against `baseBranch`, never the numbered branch, or\n// generation 2+ would fail to find its PR and open a duplicate.\nfunction upsertPr(repoRoot: string, baseBranch: string, body: string): void {\n const prDir = prDirFor(repoRoot, 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 existing = gitOut(['log', '-1', '--format=%s']); // title fallback\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', existing, '--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, '--body-file', bodyFile], { stdio: 'inherit' });\n }\n spawnSync('gh', ['pr', 'merge', baseBranch, '--auto', '--squash'], { stdio: 'inherit' });\n}\n\nexport async function main(): 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 mergeDir = mergeDirFor(repoRoot, getFeatureName());\n\n // 1. Finish any in-progress conflict resolution: validate + commit + finalize the branch swap.\n // No marker (or already validated) => no merge in progress => nothing to do (the common case).\n const marker = readMergeMarker(mergeDir);\n if (marker && !marker.validated) {\n await mergeEnd(\n repoRoot, 'wp-finish-upsert-pr', mergeDir,\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, getFeatureName()));\n\n // 2b. The build gate validates the WORKING TREE but we push HEAD — so they MUST be identical, or a\n // fix edited after the merge commit builds green yet a stale commit gets pushed (CI then fails on\n // the committed tree). Require a clean tree here; the tooling won't commit your work for you.\n assertCleanTree(repoRoot);\n\n // 3. Authoritative build gate, then push, then post.\n runBuildGate(repoRoot, new BuildGateOptions(\n '🛠️ Build gate (authoritative)', 'pnpm wp-finish-upsert-pr', 'Build failed — no PR created/updated.',\n ));\n // After finalize the local branch is the numbered generation (base2/…); the remote branch and\n // PR live on the stable base name — push and upsert against that.\n const base = baseBranchName(execSync('git branch --show-current', { encoding: 'utf8' }).trim());\n ensurePushed(base);\n\n process.stdout.write('\\n' + SEP + '📋 Dashboard + PR\\n' + SEP + '\\n');\n const body = buildDashboard(repoRoot, true, review);\n upsertPr(repoRoot, base, body);\n process.stdout.write('\\n✅ Done.\\n');\n}\n\nif (require.main === module) runMain(main);\n"]}
1
+ {"version":3,"file":"git-finishUpsertPr.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/git-finishUpsertPr.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\nimport { execSync, spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n loadAndValidate,\n loadReviewJson,\n prDirFor,\n reviewJsonPath,\n ReviewJson,\n writeTemplate,\n runMain,\n} from '@webpieces/rules-config';\nimport { getFeatureName } from './workflow/git-readAiBranchName';\nimport { baseBranchName } from './workflow/branch-naming';\nimport { assertCleanTree, ensurePushed } from './workflow/git-exec';\nimport { runBuildGate, BuildGateOptions } from './workflow/build-affected';\nimport { mergeDirFor, readMergeMarker } from './workflow/merge-state';\nimport { mergeEnd } from './workflow/merge-end';\nimport { MergeContext } from './workflow/merge-start';\nimport {\n computeGateResults,\n countAddedDisables,\n renderDashboard,\n DashboardInput,\n} from '../dashboard/dashboard';\n\n// FINISH of the AI-first PR flow. Runs after the AI has written review.json (see wp-start-upsert-pr).\n// Responsibilities, in order: (1) if a 3-point merge was in progress, validate + commit + FINALIZE the\n// AI's resolution via merge-END (so the PR is posted from the finalized feature branch, not the squash\n// branch); (2) REQUIRE review.json (hard-fail with the schema if absent/invalid); (3) run the\n// authoritative build gate; (4) render the dashboard; (5) create/update the PR via `gh`. This is the\n// ONLY command that posts PRs.\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\nfunction 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 the AI omitted it — a readable\n// fallback derived from the stable feature name (NEVER the internal `Squash merge of <branch>` commit\n// subject, which leaked bookkeeping into the PR title).\nfunction prTitleFrom(review: ReviewJson): string {\n if (review.title !== '') return review.title;\n return getFeatureName().replace(/[-/]+/g, ' ').trim();\n}\n\nfunction buildDashboard(repoRoot: string, buildPassed: boolean, review: ReviewJson, title: string): string {\n const config = loadAndValidate(repoRoot).prGate;\n const forkPoint = gitOut(['merge-base', 'origin/main', 'HEAD']);\n const featureHead = gitOut(['rev-parse', 'HEAD']);\n const mainHead = gitOut(['rev-parse', 'origin/main']);\n const range = `${forkPoint}..${featureHead}`;\n const changedFiles = gitOut(['diff', range, '--name-only']).split('\\n').filter((f: string): boolean => f.trim() !== '');\n const patch = gitOut(['diff', range]);\n\n const gateResults = computeGateResults(config.gates, changedFiles);\n const disables = countAddedDisables(patch);\n const input = new DashboardInput(title, gateResults, disables, buildPassed, forkPoint, featureHead, mainHead, review);\n return renderDashboard(input);\n}\n\n// The PR, the remote branch, and the local branch all share the one stable feature name now. Look up /\n// create / merge against `baseBranch` (baseBranchName also tolerates a leftover `…wpN` mid-transition),\n// or a resolve from such a leftover could fail to find its PR and open a duplicate.\nfunction upsertPr(repoRoot: string, baseBranch: string, body: string, title: string): string {\n const prDir = prDirFor(repoRoot, 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 // Keep the title in sync with the latest review.title (not just the body).\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\nexport async function main(): 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 mergeDir = mergeDirFor(repoRoot, getFeatureName());\n\n // 1. Finish any in-progress conflict resolution: validate + commit + finalize the branch swap.\n // No marker (or already validated) => no merge in progress => nothing to do (the common case).\n const marker = readMergeMarker(mergeDir);\n if (marker && !marker.validated) {\n await mergeEnd(\n repoRoot, 'wp-finish-upsert-pr', mergeDir,\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, getFeatureName()));\n\n // 2b. The build gate validates the WORKING TREE but we push HEAD — so they MUST be identical, or a\n // fix edited after the merge commit builds green yet a stale commit gets pushed (CI then fails on\n // the committed tree). Require a clean tree here; the tooling won't commit your work for you.\n assertCleanTree(repoRoot);\n\n // 3. Authoritative build gate, then push, then post.\n runBuildGate(repoRoot, new BuildGateOptions(\n '🛠️ Build gate (authoritative)', 'pnpm wp-finish-upsert-pr', 'Build failed — no PR created/updated.',\n ));\n // After finalize the local branch, the remote branch, and the PR all share the SAME stable name —\n // push and upsert against it. (baseBranchName is a no-op on the already-stable name; it also\n // tolerates a leftover `…wpN` mid-transition.)\n const base = baseBranchName(execSync('git branch --show-current', { encoding: 'utf8' }).trim());\n ensurePushed(base);\n\n process.stdout.write('\\n' + SEP + '📋 Dashboard + PR\\n' + SEP + '\\n');\n const title = prTitleFrom(review);\n const body = buildDashboard(repoRoot, true, review, title);\n const prNum = 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\nif (require.main === module) runMain(main);\n"]}
@@ -1,8 +1,9 @@
1
1
  /** Stable base identity (remote / PR / feature-name slug source): trailing `Squash` and the
2
2
  * generation marker stripped. `base` → `base`, `basewp2` → `base`, `basewp2Squash` → `base`. */
3
3
  export declare function baseBranchName(branch: string): string;
4
- /** The local branch you land on after this merge: base + `wp` + (gen + 1). `base` → `basewp2`,
5
- * `basewp2` `basewp3`. */
6
- export declare function nextBranchName(branch: string): string;
7
- /** Pre-merge safety snapshot of the given (current) branch. One overwritable slot per branch. */
8
- export declare function preMergeBackupName(branch: string): string;
4
+ /** Pre-merge snapshot name for slot `n`: `n<=1` `<branch>PreMerge`, `n>=2` → `<branch>PreMerge<n>`.
5
+ * Snapshots accumulate one per sync (never overwritten), since the branch name is now constant. */
6
+ export declare function preMergeBackupName(branch: string, n?: number): string;
7
+ /** First free PreMerge slot for `branch`, probing 1, 2, 3, via the `exists` predicate. Pure (the
8
+ * branch-existence check is injected) so it unit-tests without touching git. */
9
+ export declare function nextFreePreMergeSlot(branch: string, exists: (name: string) => boolean): string;
@@ -1,23 +1,27 @@
1
1
  "use strict";
2
- // Branch-naming for the numbered-generation squash-merge scheme.
2
+ // Branch-naming for the squash-merge scheme.
3
3
  //
4
- // A feature branch has a stable *base* identity plus a *generation* marker that bumps every time we
5
- // re-sync from main: base basewp2 basewp3 … (gen 1 carries no marker). The remote branch and
6
- // therefore the single PR always lives on `base`; only the LOCAL branch numbers up, so you can see
7
- // at a glance how many times you've re-merged main. The pre-merge safety snapshot is
8
- // `<currentBranch>PreMerge` (one overwritable slot per generation), replacing the old ever-accumulating
9
- // `<branch>Backup1/Backup2/…`.
4
+ // A feature branch has ONE stable name that never changes: it is the local branch, the remote branch,
5
+ // AND the single PR head all identical, so "which branch is my PR on?" is never ambiguous. A sync
6
+ // (re-merge from main) happens on a transient `<branch>Squash` branch and, when it finishes, is
7
+ // force-pushed and RENAMED BACK to the same feature name (see merge-end.finalizeBranch). No `wpN`
8
+ // generation number survives a sync the old scheme numbered the LOCAL branch up (base → basewp2 →
9
+ // basewp3) while the remote/PR stayed on `base`, which read as "the PR moved" when it never did.
10
10
  //
11
- // The generation marker is `wp<N>` (a literal `wp` prefix, NOT a bare number) so it is unambiguous
12
- // against branch names that naturally end in digits most importantly version-upgrade branches like
13
- // `deanhiller/upgrade-webpieces-0.3.213`, which an earlier bare-digit scheme mangled by stripping the
14
- // `213`. Parsing keys off the `wp` marker: strip a trailing `Squash` (the internal temp-branch suffix),
15
- // then a trailing `wp<digits>`. The branch-creation-guard rejects human branches ending in `wp<digits>`
16
- // so the marker stays reserved for this tool.
11
+ // The pre-merge safety snapshot is a NUMBERED trail `<branch>PreMerge`, `<branch>PreMerge2`,
12
+ // `<branch>PreMerge3`, one per sync, never overwritten (the branch name is constant now, so the
13
+ // snapshot itself carries the generation count that the branch name used to). See nextFreePreMergeSlot.
14
+ //
15
+ // `baseBranchName` still strips a trailing `Squash` (the transient temp-branch suffix) AND a trailing
16
+ // `wp<digits>` the latter only for BACKWARD COMPATIBILITY, so a consumer sitting on a leftover
17
+ // `…wp5` branch from the old scheme still resolves to its stable name during the transition. The
18
+ // generation marker `wp<N>` is a literal `wp` prefix (NOT a bare number) so it never mangles branch
19
+ // names that naturally end in digits, e.g. `deanhiller/upgrade-webpieces-0.3.213`. The tool no longer
20
+ // PRODUCES `wpN`; the branch-creation-guard still reserves the suffix during the transition.
17
21
  Object.defineProperty(exports, "__esModule", { value: true });
18
22
  exports.baseBranchName = baseBranchName;
19
- exports.nextBranchName = nextBranchName;
20
23
  exports.preMergeBackupName = preMergeBackupName;
24
+ exports.nextFreePreMergeSlot = nextFreePreMergeSlot;
21
25
  const GENERATION_RE = /^(.*)wp(\d+)$/;
22
26
  class Generation {
23
27
  base;
@@ -40,14 +44,18 @@ function parseGeneration(branch) {
40
44
  function baseBranchName(branch) {
41
45
  return parseGeneration(branch).base;
42
46
  }
43
- /** The local branch you land on after this merge: base + `wp` + (gen + 1). `base` → `basewp2`,
44
- * `basewp2` `basewp3`. */
45
- function nextBranchName(branch) {
46
- const generation = parseGeneration(branch);
47
- return `${generation.base}wp${generation.gen + 1}`;
47
+ /** Pre-merge snapshot name for slot `n`: `n<=1` `<branch>PreMerge`, `n>=2` → `<branch>PreMerge<n>`.
48
+ * Snapshots accumulate one per sync (never overwritten), since the branch name is now constant. */
49
+ function preMergeBackupName(branch, n = 1) {
50
+ return n <= 1 ? `${branch}PreMerge` : `${branch}PreMerge${n}`;
48
51
  }
49
- /** Pre-merge safety snapshot of the given (current) branch. One overwritable slot per branch. */
50
- function preMergeBackupName(branch) {
51
- return `${branch}PreMerge`;
52
+ /** First free PreMerge slot for `branch`, probing 1, 2, 3, via the `exists` predicate. Pure (the
53
+ * branch-existence check is injected) so it unit-tests without touching git. */
54
+ function nextFreePreMergeSlot(branch, exists) {
55
+ for (let n = 1;; n++) {
56
+ const name = preMergeBackupName(branch, n);
57
+ if (!exists(name))
58
+ return name;
59
+ }
52
60
  }
53
61
  //# sourceMappingURL=branch-naming.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"branch-naming.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/branch-naming.ts"],"names":[],"mappings":";AAAA,iEAAiE;AACjE,EAAE;AACF,oGAAoG;AACpG,qGAAqG;AACrG,qGAAqG;AACrG,qFAAqF;AACrF,wGAAwG;AACxG,+BAA+B;AAC/B,EAAE;AACF,mGAAmG;AACnG,qGAAqG;AACrG,sGAAsG;AACtG,wGAAwG;AACxG,wGAAwG;AACxG,8CAA8C;;AAyB9C,wCAEC;AAID,wCAGC;AAGD,gDAEC;AArCD,MAAM,aAAa,GAAG,eAAe,CAAC;AAEtC,MAAM,UAAU;IACZ,IAAI,CAAS;IACb,GAAG,CAAS;IAEZ,YAAY,IAAY,EAAE,GAAW;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACnB,CAAC;CACJ;AAED,SAAS,eAAe,CAAC,MAAc;IACnC,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IACjD,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;QAC3B,OAAO,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,IAAI,UAAU,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;AAC5C,CAAC;AAED;iGACiG;AACjG,SAAgB,cAAc,CAAC,MAAc;IACzC,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;AACxC,CAAC;AAED;6BAC6B;AAC7B,SAAgB,cAAc,CAAC,MAAc;IACzC,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IAC3C,OAAO,GAAG,UAAU,CAAC,IAAI,KAAK,UAAU,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;AACvD,CAAC;AAED,iGAAiG;AACjG,SAAgB,kBAAkB,CAAC,MAAc;IAC7C,OAAO,GAAG,MAAM,UAAU,CAAC;AAC/B,CAAC","sourcesContent":["// Branch-naming for the numbered-generation squash-merge scheme.\n//\n// A feature branch has a stable *base* identity plus a *generation* marker that bumps every time we\n// re-sync from main: base basewp2 basewp3 … (gen 1 carries no marker). The remote branch and\n// therefore the single PR always lives on `base`; only the LOCAL branch numbers up, so you can see\n// at a glance how many times you've re-merged main. The pre-merge safety snapshot is\n// `<currentBranch>PreMerge` (one overwritable slot per generation), replacing the old ever-accumulating\n// `<branch>Backup1/Backup2/…`.\n//\n// The generation marker is `wp<N>` (a literal `wp` prefix, NOT a bare number) so it is unambiguous\n// against branch names that naturally end in digits most importantly version-upgrade branches like\n// `deanhiller/upgrade-webpieces-0.3.213`, which an earlier bare-digit scheme mangled by stripping the\n// `213`. Parsing keys off the `wp` marker: strip a trailing `Squash` (the internal temp-branch suffix),\n// then a trailing `wp<digits>`. The branch-creation-guard rejects human branches ending in `wp<digits>`\n// so the marker stays reserved for this tool.\n\nconst GENERATION_RE = /^(.*)wp(\\d+)$/;\n\nclass Generation {\n base: string;\n gen: number;\n\n constructor(base: string, gen: number) {\n this.base = base;\n this.gen = gen;\n }\n}\n\nfunction parseGeneration(branch: string): Generation {\n const withoutSquash = branch.replace(/Squash$/, '');\n const match = withoutSquash.match(GENERATION_RE);\n if (match && match[1] !== '') {\n return new Generation(match[1], parseInt(match[2], 10));\n }\n return new Generation(withoutSquash, 1);\n}\n\n/** Stable base identity (remote / PR / feature-name slug source): trailing `Squash` and the\n * generation marker stripped. `base` → `base`, `basewp2` → `base`, `basewp2Squash` → `base`. */\nexport function baseBranchName(branch: string): string {\n return parseGeneration(branch).base;\n}\n\n/** The local branch you land on after this merge: base + `wp` + (gen + 1). `base` → `basewp2`,\n * `basewp2` `basewp3`. */\nexport function nextBranchName(branch: string): string {\n const generation = parseGeneration(branch);\n return `${generation.base}wp${generation.gen + 1}`;\n}\n\n/** Pre-merge safety snapshot of the given (current) branch. One overwritable slot per branch. */\nexport function preMergeBackupName(branch: string): string {\n return `${branch}PreMerge`;\n}\n"]}
1
+ {"version":3,"file":"branch-naming.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/branch-naming.ts"],"names":[],"mappings":";AAAA,6CAA6C;AAC7C,EAAE;AACF,sGAAsG;AACtG,oGAAoG;AACpG,gGAAgG;AAChG,kGAAkG;AAClG,oGAAoG;AACpG,iGAAiG;AACjG,EAAE;AACF,+FAA+F;AAC/F,oGAAoG;AACpG,wGAAwG;AACxG,EAAE;AACF,sGAAsG;AACtG,iGAAiG;AACjG,iGAAiG;AACjG,oGAAoG;AACpG,sGAAsG;AACtG,6FAA6F;;AAyB7F,wCAEC;AAID,gDAEC;AAID,oDAKC;AAxCD,MAAM,aAAa,GAAG,eAAe,CAAC;AAEtC,MAAM,UAAU;IACZ,IAAI,CAAS;IACb,GAAG,CAAS;IAEZ,YAAY,IAAY,EAAE,GAAW;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACnB,CAAC;CACJ;AAED,SAAS,eAAe,CAAC,MAAc;IACnC,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IACjD,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;QAC3B,OAAO,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,IAAI,UAAU,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;AAC5C,CAAC;AAED;iGACiG;AACjG,SAAgB,cAAc,CAAC,MAAc;IACzC,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;AACxC,CAAC;AAED;oGACoG;AACpG,SAAgB,kBAAkB,CAAC,MAAc,EAAE,IAAY,CAAC;IAC5D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,UAAU,CAAC,CAAC,CAAC,GAAG,MAAM,WAAW,CAAC,EAAE,CAAC;AAClE,CAAC;AAED;iFACiF;AACjF,SAAgB,oBAAoB,CAAC,MAAc,EAAE,MAAiC;IAClF,KAAK,IAAI,CAAC,GAAG,CAAC,GAAI,CAAC,EAAE,EAAE,CAAC;QACpB,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IACnC,CAAC;AACL,CAAC","sourcesContent":["// Branch-naming for the squash-merge scheme.\n//\n// A feature branch has ONE stable name that never changes: it is the local branch, the remote branch,\n// AND the single PR head all identical, so \"which branch is my PR on?\" is never ambiguous. A sync\n// (re-merge from main) happens on a transient `<branch>Squash` branch and, when it finishes, is\n// force-pushed and RENAMED BACK to the same feature name (see merge-end.finalizeBranch). No `wpN`\n// generation number survives a sync — the old scheme numbered the LOCAL branch up (base basewp2 →\n// basewp3) while the remote/PR stayed on `base`, which read as \"the PR moved\" when it never did.\n//\n// The pre-merge safety snapshot is a NUMBERED trail — `<branch>PreMerge`, `<branch>PreMerge2`,\n// `<branch>PreMerge3`, one per sync, never overwritten (the branch name is constant now, so the\n// snapshot itself carries the generation count that the branch name used to). See nextFreePreMergeSlot.\n//\n// `baseBranchName` still strips a trailing `Squash` (the transient temp-branch suffix) AND a trailing\n// `wp<digits>` the latter only for BACKWARD COMPATIBILITY, so a consumer sitting on a leftover\n// `…wp5` branch from the old scheme still resolves to its stable name during the transition. The\n// generation marker `wp<N>` is a literal `wp` prefix (NOT a bare number) so it never mangles branch\n// names that naturally end in digits, e.g. `deanhiller/upgrade-webpieces-0.3.213`. The tool no longer\n// PRODUCES `wpN`; the branch-creation-guard still reserves the suffix during the transition.\n\nconst GENERATION_RE = /^(.*)wp(\\d+)$/;\n\nclass Generation {\n base: string;\n gen: number;\n\n constructor(base: string, gen: number) {\n this.base = base;\n this.gen = gen;\n }\n}\n\nfunction parseGeneration(branch: string): Generation {\n const withoutSquash = branch.replace(/Squash$/, '');\n const match = withoutSquash.match(GENERATION_RE);\n if (match && match[1] !== '') {\n return new Generation(match[1], parseInt(match[2], 10));\n }\n return new Generation(withoutSquash, 1);\n}\n\n/** Stable base identity (remote / PR / feature-name slug source): trailing `Squash` and the\n * generation marker stripped. `base` → `base`, `basewp2` → `base`, `basewp2Squash` → `base`. */\nexport function baseBranchName(branch: string): string {\n return parseGeneration(branch).base;\n}\n\n/** Pre-merge snapshot name for slot `n`: `n<=1` `<branch>PreMerge`, `n>=2` → `<branch>PreMerge<n>`.\n * Snapshots accumulate one per sync (never overwritten), since the branch name is now constant. */\nexport function preMergeBackupName(branch: string, n: number = 1): string {\n return n <= 1 ? `${branch}PreMerge` : `${branch}PreMerge${n}`;\n}\n\n/** First free PreMerge slot for `branch`, probing 1, 2, 3, … via the `exists` predicate. Pure (the\n * branch-existence check is injected) so it unit-tests without touching git. */\nexport function nextFreePreMergeSlot(branch: string, exists: (name: string) => boolean): string {\n for (let n = 1; ; n++) {\n const name = preMergeBackupName(branch, n);\n if (!exists(name)) return name;\n }\n}\n"]}
@@ -6,8 +6,8 @@ const child_process_1 = require("child_process");
6
6
  const rules_config_1 = require("@webpieces/rules-config");
7
7
  const branch_naming_1 = require("./branch-naming");
8
8
  // Stable feature identity used to key the merge-context dir and PR-body dir. It MUST stay constant
9
- // across a branch's numbered generations (base base2 → base3) and its transient `Squash` temp, so
10
- // derive it from baseBranchName (strips `Squash` + the generation number) before slugifying.
9
+ // across a sync's transient `<feature>Squash` temp branch (and any leftover `…wpN` from the old
10
+ // scheme), so derive it from baseBranchName (strips `Squash` + a legacy `wpN`) before slugifying.
11
11
  function getFeatureName() {
12
12
  const branch = (0, child_process_1.execSync)('git branch --show-current', { encoding: 'utf8' }).trim();
13
13
  return (0, branch_naming_1.baseBranchName)(branch).replace(/\//g, '-');
@@ -1 +1 @@
1
- {"version":3,"file":"git-readAiBranchName.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/git-readAiBranchName.ts"],"names":[],"mappings":";;AAOA,wCAGC;AAED,oBAEC;AAdD,iDAAyC;AACzC,0DAAkD;AAClD,mDAAiD;AAEjD,mGAAmG;AACnG,oGAAoG;AACpG,6FAA6F;AAC7F,SAAgB,cAAc;IAC1B,MAAM,MAAM,GAAG,IAAA,wBAAQ,EAAC,2BAA2B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAClF,OAAO,IAAA,8BAAc,EAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACtD,CAAC;AAEM,KAAK,UAAU,IAAI;IACtB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,IAAI,CAAC,CAAC;AAClD,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM;IAAE,IAAA,sBAAO,EAAC,IAAI,CAAC,CAAC","sourcesContent":["import { execSync } from 'child_process';\nimport { runMain } from '@webpieces/rules-config';\nimport { baseBranchName } from './branch-naming';\n\n// Stable feature identity used to key the merge-context dir and PR-body dir. It MUST stay constant\n// across a branch's numbered generations (base base2 → base3) and its transient `Squash` temp, so\n// derive it from baseBranchName (strips `Squash` + the generation number) before slugifying.\nexport function getFeatureName(): string {\n const branch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();\n return baseBranchName(branch).replace(/\\//g, '-');\n}\n\nexport async function main(): Promise<void> {\n process.stdout.write(getFeatureName() + '\\n');\n}\n\nif (require.main === module) runMain(main);\n"]}
1
+ {"version":3,"file":"git-readAiBranchName.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/git-readAiBranchName.ts"],"names":[],"mappings":";;AAOA,wCAGC;AAED,oBAEC;AAdD,iDAAyC;AACzC,0DAAkD;AAClD,mDAAiD;AAEjD,mGAAmG;AACnG,gGAAgG;AAChG,kGAAkG;AAClG,SAAgB,cAAc;IAC1B,MAAM,MAAM,GAAG,IAAA,wBAAQ,EAAC,2BAA2B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAClF,OAAO,IAAA,8BAAc,EAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACtD,CAAC;AAEM,KAAK,UAAU,IAAI;IACtB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,IAAI,CAAC,CAAC;AAClD,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM;IAAE,IAAA,sBAAO,EAAC,IAAI,CAAC,CAAC","sourcesContent":["import { execSync } from 'child_process';\nimport { runMain } from '@webpieces/rules-config';\nimport { baseBranchName } from './branch-naming';\n\n// Stable feature identity used to key the merge-context dir and PR-body dir. It MUST stay constant\n// across a sync's transient `<feature>Squash` temp branch (and any leftover `…wpN` from the old\n// scheme), so derive it from baseBranchName (strips `Squash` + a legacy `wpN`) before slugifying.\nexport function getFeatureName(): string {\n const branch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();\n return baseBranchName(branch).replace(/\\//g, '-');\n}\n\nexport async function main(): Promise<void> {\n process.stdout.write(getFeatureName() + '\\n');\n}\n\nif (require.main === module) runMain(main);\n"]}
@@ -12,9 +12,9 @@ const git_exec_1 = require("./git-exec");
12
12
  const merge_state_1 = require("./merge-state");
13
13
  // merge-END: the second half of the 3-point squash-merge lifecycle, symmetric with merge-START. Given
14
14
  // the branch context, it (optionally) validates + commits the AI's conflict resolution, then ALWAYS
15
- // finalizes the merge — promotes `<branch>Squash` to the next numbered generation (base base2),
16
- // force-pushes to the stable base branch, stamps a clean main-sync status, clears the marker and
17
- // sweeps stale tmp. The shared runUpdateFromMain (clean path / validated resume), wp-update-end, and
15
+ // finalizes the merge — force-pushes `<branch>Squash` to the stable feature branch and renames it
16
+ // BACK to that same feature name (local == remote == PR head), stamps a clean main-sync status,
17
+ // clears the marker and sweeps stale tmp. The shared runUpdateFromMain (clean path / validated resume), wp-update-end, and
18
18
  // wp-finish-upsert-pr (conflict resolution) all call THIS, so finalization happens in exactly one
19
19
  // place and the conflict path can no longer post a PR from the un-swapped squash branch.
20
20
  const SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
@@ -51,13 +51,16 @@ function validateResolution(repoRoot, mergeDir, conflictedFiles) {
51
51
  }
52
52
  process.stdout.write('✅ Merge explanations present for all resolved files.\n');
53
53
  }
54
- // Promote the squash branch to the NEXT numbered generation, force-push to the stable base
55
- // branch (where the single PR lives), and stamp clean main-sync. The local branch numbers up
56
- // (base → base2 → base3) so the generation is visible; the remote/PR name stays `base`.
54
+ function localBranchExists(name) {
55
+ return (0, child_process_1.spawnSync)('git', ['show-ref', '--verify', '--quiet', `refs/heads/${name}`]).status === 0;
56
+ }
57
+ // Force-push the squash branch to the stable feature branch (where the single PR lives), then RENAME
58
+ // the local squash branch back to that SAME feature name — so the local branch, the remote branch,
59
+ // and the PR head are all one name (no `wpN` divergence). The pre-merge snapshot is preserved as a
60
+ // numbered PreMerge trail. Ends with an explicit "here is exactly what I did" recap for the AI/human.
57
61
  function finalizeBranch(repoRoot, verb, ctx) {
58
62
  process.stdout.write('\n' + SEP + '🗑️ Finalizing\n' + SEP + '\n');
59
63
  const base = (0, branch_naming_1.baseBranchName)(ctx.currentBranch);
60
- const next = (0, branch_naming_1.nextBranchName)(ctx.currentBranch);
61
64
  (0, git_exec_1.runGitChecked)(['branch', '-D', ctx.currentBranch], 'Failed to delete old feature branch');
62
65
  const remoteExists = (0, child_process_1.spawnSync)('git', ['ls-remote', '--exit-code', '--heads', 'origin', base]).status === 0;
63
66
  if (remoteExists) {
@@ -68,22 +71,41 @@ function finalizeBranch(repoRoot, verb, ctx) {
68
71
  process.stdout.write('No remote branch — local only.\n');
69
72
  }
70
73
  (0, git_exec_1.runGitChecked)(['checkout', ctx.squashBranch], 'Failed to checkout squash branch');
71
- (0, git_exec_1.runGitChecked)(['branch', '-m', next], 'Failed to rename squash branch');
74
+ // Free the rename target: `base` is normally the branch we just deleted (ctx.currentBranch), but on
75
+ // a backward-compat sync from a leftover `…wpN` a separate stale `base` can linger — drop it too.
76
+ if (base !== ctx.currentBranch && localBranchExists(base)) {
77
+ (0, git_exec_1.runGitChecked)(['branch', '-D', base], 'Failed to delete stale base branch');
78
+ }
79
+ (0, git_exec_1.runGitChecked)(['branch', '-m', base], 'Failed to rename squash branch to the feature name');
72
80
  const renameEvent = new rules_config_1.BranchMutationEvent(verb, 'RENAME');
73
81
  renameEvent.fromBranch = ctx.currentBranch;
74
- renameEvent.toBranch = next;
82
+ renameEvent.toBranch = base;
75
83
  (0, rules_config_1.logBranchMutation)(repoRoot, renameEvent);
76
84
  // Branch now contains origin/main — stamp a clean main-sync status so the feature-branch-guard
77
85
  // unblocks edits immediately (no wait for the async refresher).
78
86
  (0, rules_config_1.stampCleanMainSyncStatus)((0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim());
79
87
  const finalizeEvent = new rules_config_1.BranchMutationEvent(verb, 'FINALIZE');
80
88
  finalizeEvent.fromBranch = ctx.currentBranch;
81
- finalizeEvent.toBranch = next;
89
+ finalizeEvent.toBranch = base;
82
90
  finalizeEvent.outcome = 'finalized';
83
91
  finalizeEvent.artifacts = [`backup=${ctx.backupBranch}`, `remotePR=${base}`];
84
92
  (0, rules_config_1.logBranchMutation)(repoRoot, finalizeEvent);
85
- process.stdout.write(`\n✅ Now on ${next} (remote/PR: ${base}), updated from main. Backup: ${ctx.backupBranch}\n`);
86
- process.stdout.write(` Delete backup when safe: git branch -D ${ctx.backupBranch}\n\n`);
93
+ printSyncRecap(base, ctx.backupBranch, ctx.prNumber, remoteExists);
94
+ }
95
+ // The explicit, numbered "here is exactly what I did" recap the AI (and human) reads after a sync.
96
+ // The whole point of the branch-name change is that step 4 can now say "same name as remote/PR" —
97
+ // there is no confusing local-vs-remote divergence left to explain.
98
+ function printSyncRecap(feature, backupBranch, prNumber, pushed) {
99
+ const remoteLine = pushed
100
+ ? `landed back on ${feature} (== origin/${feature}${prNumber ? ` == PR #${prNumber}` : ''} — names match)`
101
+ : `landed back on ${feature} (local only — no remote branch yet)`;
102
+ process.stdout.write('\n' + SEP + '✅ Sync complete — here is exactly what I did\n' + SEP + '\n' +
103
+ ` 1. snapshotted your pre-merge state → ${backupBranch}\n` +
104
+ ` 2. pulled origin/main\n` +
105
+ ` 3. squash-merged your work onto main\n` +
106
+ ` 4. ${remoteLine}\n\n` +
107
+ ` Pre-merge snapshot trail: git branch --list '${feature}PreMerge*'\n` +
108
+ ` Prune this run's snapshot when safe: git branch -D ${backupBranch}\n\n`);
87
109
  }
88
110
  /**
89
111
  * Complete a 3-point squash merge. `conflictedFiles` non-null means a conflict was resolved by the AI
@@ -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 { baseBranchName, nextBranchName } from './branch-naming';\nimport { cleanTmp } from './cleanTmp';\nimport { assertNoUntracked, runGitChecked } from './git-exec';\nimport { MergeContext } from './merge-start';\nimport { clearMergeMarker, perFileContextDir, scanConflictMarkers, scanMergeExplanations } from './merge-state';\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 the merge — promotes `<branch>Squash` to the next numbered generation (base → base2),\n// force-pushes to the stable base branch, stamps a clean main-sync status, clears the marker and\n// sweeps stale tmp. The shared runUpdateFromMain (clean path / validated resume), wp-update-end, and\n// wp-finish-upsert-pr (conflict resolution) all call THIS, so finalization happens in exactly one\n// place and the conflict path can no longer post a PR from the un-swapped squash branch.\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// Validate the AI's resolution of the conflicted files — the part of the process the AI owns\n// (branch creation/finalization is the script's job, so it is not re-checked here). Throws\n// CliExitError with a fix instruction on any failure; returns only when all three checks pass.\nfunction 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 = 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\n // its per-file context dir, proving the AI deliberately 3-point merged it (and recording how)\n // rather than blindly taking one side. A sidecar file works for any type, incl. JSON/deletes.\n const explanations = scanMergeExplanations(mergeDir, conflictedFiles);\n if (!explanations.clean) {\n const missing = explanations.filesWithMarkers\n .map((file: string): string => ` - ${file}\\n → ${path.join(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// Promote the squash branch to the NEXT numbered generation, force-push to the stable base\n// branch (where the single PR lives), and stamp clean main-sync. The local branch numbers up\n// (base → base2 → base3) so the generation is visible; the remote/PR name stays `base`.\nfunction finalizeBranch(repoRoot: string, verb: MutationVerb, ctx: MergeContext): void {\n process.stdout.write('\\n' + SEP + '🗑️ Finalizing\\n' + SEP + '\\n');\n const base = baseBranchName(ctx.currentBranch);\n const next = nextBranchName(ctx.currentBranch);\n 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 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 runGitChecked(['checkout', ctx.squashBranch], 'Failed to checkout squash branch');\n runGitChecked(['branch', '-m', next], 'Failed to rename squash branch');\n const renameEvent = new BranchMutationEvent(verb, 'RENAME');\n renameEvent.fromBranch = ctx.currentBranch;\n renameEvent.toBranch = next;\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 const finalizeEvent = new BranchMutationEvent(verb, 'FINALIZE');\n finalizeEvent.fromBranch = ctx.currentBranch;\n finalizeEvent.toBranch = next;\n finalizeEvent.outcome = 'finalized';\n finalizeEvent.artifacts = [`backup=${ctx.backupBranch}`, `remotePR=${base}`];\n logBranchMutation(repoRoot, finalizeEvent);\n\n process.stdout.write(`\\n✅ Now on ${next} (remote/PR: ${base}), updated from main. Backup: ${ctx.backupBranch}\\n`);\n process.stdout.write(` Delete backup when safe: git branch -D ${ctx.backupBranch}\\n\\n`);\n}\n\n/**\n * Complete a 3-point squash merge. `conflictedFiles` non-null means a conflict was resolved by the AI\n * 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 */\nexport async function 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 validateResolution(repoRoot, mergeDir, conflictedFiles);\n // Stage the AI's resolved conflicts, but NEVER sweep untracked files into the squash commit\n // (a blanket `git add -A` once swept a stale untracked dir in). Fail on untracked so the AI\n // commits or deletes them explicitly; then `git add -u` stages tracked resolutions only.\n assertNoUntracked(repoRoot);\n 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 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 finalizeBranch(repoRoot, verb, ctx);\n clearMergeMarker(mergeDir);\n await cleanTmp();\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 { baseBranchName } from './branch-naming';\nimport { cleanTmp } from './cleanTmp';\nimport { assertNoUntracked, runGitChecked } from './git-exec';\nimport { MergeContext } from './merge-start';\nimport { clearMergeMarker, perFileContextDir, scanConflictMarkers, scanMergeExplanations } from './merge-state';\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 the merge — force-pushes `<branch>Squash` to the stable feature branch and renames it\n// BACK to that same feature name (local == remote == PR head), stamps a clean main-sync status,\n// clears the marker and sweeps stale tmp. The shared runUpdateFromMain (clean path / validated resume), wp-update-end, and\n// wp-finish-upsert-pr (conflict resolution) all call THIS, so finalization happens in exactly one\n// place and the conflict path can no longer post a PR from the un-swapped squash branch.\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// Validate the AI's resolution of the conflicted files — the part of the process the AI owns\n// (branch creation/finalization is the script's job, so it is not re-checked here). Throws\n// CliExitError with a fix instruction on any failure; returns only when all three checks pass.\nfunction 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 = 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\n // its per-file context dir, proving the AI deliberately 3-point merged it (and recording how)\n // rather than blindly taking one side. A sidecar file works for any type, incl. JSON/deletes.\n const explanations = scanMergeExplanations(mergeDir, conflictedFiles);\n if (!explanations.clean) {\n const missing = explanations.filesWithMarkers\n .map((file: string): string => ` - ${file}\\n → ${path.join(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\nfunction 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 — so the local branch, the remote branch,\n// and the PR head are all one name (no `wpN` divergence). The pre-merge snapshot is preserved as a\n// numbered PreMerge trail. Ends with an explicit \"here is exactly what I did\" recap for the AI/human.\nfunction finalizeBranch(repoRoot: string, verb: MutationVerb, ctx: MergeContext): void {\n process.stdout.write('\\n' + SEP + '🗑️ Finalizing\\n' + SEP + '\\n');\n const base = baseBranchName(ctx.currentBranch);\n 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 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 runGitChecked(['checkout', ctx.squashBranch], 'Failed to checkout squash branch');\n // Free the rename target: `base` is normally the branch we just deleted (ctx.currentBranch), but on\n // a backward-compat sync from a leftover `…wpN` a separate stale `base` can linger — drop it too.\n if (base !== ctx.currentBranch && localBranchExists(base)) {\n runGitChecked(['branch', '-D', base], 'Failed to delete stale base branch');\n }\n 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 const finalizeEvent = new BranchMutationEvent(verb, 'FINALIZE');\n finalizeEvent.fromBranch = ctx.currentBranch;\n finalizeEvent.toBranch = base;\n finalizeEvent.outcome = 'finalized';\n finalizeEvent.artifacts = [`backup=${ctx.backupBranch}`, `remotePR=${base}`];\n logBranchMutation(repoRoot, finalizeEvent);\n\n printSyncRecap(base, ctx.backupBranch, ctx.prNumber, remoteExists);\n}\n\n// The explicit, numbered \"here is exactly what I did\" recap the AI (and human) reads after a sync.\n// The whole point of the branch-name change is that step 4 can now say \"same name as remote/PR\" —\n// there is no confusing local-vs-remote divergence left to explain.\nfunction printSyncRecap(feature: string, backupBranch: string, prNumber: string, pushed: 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 process.stdout.write(\n '\\n' + SEP + '✅ Sync complete — here is exactly what I did\\n' + SEP + '\\n' +\n ` 1. snapshotted your pre-merge state → ${backupBranch}\\n` +\n ` 2. pulled origin/main\\n` +\n ` 3. squash-merged your work onto main\\n` +\n ` 4. ${remoteLine}\\n\\n` +\n ` Pre-merge snapshot trail: git branch --list '${feature}PreMerge*'\\n` +\n ` Prune this run's snapshot when safe: git branch -D ${backupBranch}\\n\\n`,\n );\n}\n\n/**\n * Complete a 3-point squash merge. `conflictedFiles` non-null means a conflict was resolved by the AI\n * 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 */\nexport async function 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 validateResolution(repoRoot, mergeDir, conflictedFiles);\n // Stage the AI's resolved conflicts, but NEVER sweep untracked files into the squash commit\n // (a blanket `git add -A` once swept a stale untracked dir in). Fail on untracked so the AI\n // commits or deletes them explicitly; then `git add -u` stages tracked resolutions only.\n assertNoUntracked(repoRoot);\n 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 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 finalizeBranch(repoRoot, verb, ctx);\n clearMergeMarker(mergeDir);\n await cleanTmp();\n}\n"]}
@@ -45,21 +45,19 @@ class MergeStartResult {
45
45
  }
46
46
  }
47
47
  exports.MergeStartResult = MergeStartResult;
48
- // Detect the PR by its STABLE base branch — the PR always lives on `base`, never on the numbered
49
- // generation (base2/base3/…) you may currently be on, so pass baseBranchName(currentBranch).
48
+ // Detect the PR by its STABLE feature branch — pass baseBranchName(currentBranch) so a leftover
49
+ // `…wpN` from the old scheme still resolves to the one name the PR lives on.
50
50
  function detectPr(baseBranch) {
51
51
  const result = (0, child_process_1.spawnSync)('gh', ['pr', 'list', '--head', baseBranch, '--json', 'number', '--jq', '.[0].number'], { encoding: 'utf8' });
52
52
  return result.status === 0 ? (result.stdout ?? '').trim() : '';
53
53
  }
54
- // Snapshot the pre-merge state as `<currentBranch>PreMerge`. One overwritable slot per generation:
55
- // if a stale PreMerge from a crashed rerun of THIS branch exists, delete it first so we never
56
- // accumulate backups (the old scheme minted a fresh Backup1/Backup2/… on every run).
54
+ // Snapshot the pre-merge state into the next free NUMBERED slot `<currentBranch>PreMerge`, then
55
+ // `PreMerge2`, `PreMerge3`, never overwriting an existing one. The feature branch name is now
56
+ // constant across syncs, so the numbered snapshot itself is the trail of "how many times I re-merged
57
+ // main" that the old `wpN` branch name used to carry.
57
58
  function createBackup(currentBranch) {
58
59
  process.stdout.write('\n' + SEP + '💾 Creating Pre-Merge Backup\n' + SEP + '\n');
59
- const backupBranch = (0, branch_naming_1.preMergeBackupName)(currentBranch);
60
- if ((0, child_process_1.spawnSync)('git', ['show-ref', '--verify', '--quiet', `refs/heads/${backupBranch}`]).status === 0) {
61
- (0, git_exec_1.runGitChecked)(['branch', '-D', backupBranch], 'Failed to delete stale pre-merge backup');
62
- }
60
+ const backupBranch = (0, branch_naming_1.nextFreePreMergeSlot)(currentBranch, (name) => (0, child_process_1.spawnSync)('git', ['show-ref', '--verify', '--quiet', `refs/heads/${name}`]).status === 0);
63
61
  (0, git_exec_1.runGitChecked)(['checkout', '-b', backupBranch], 'Failed to create backup branch');
64
62
  (0, git_exec_1.runGitChecked)(['checkout', currentBranch], 'Failed to return to feature branch');
65
63
  process.stdout.write(`✅ Backup created: ${backupBranch}\n\n`);
@@ -185,11 +183,20 @@ function writeMergeProcessDoc(repoRoot, mergeDir, squashBranch, conflictedFiles,
185
183
  fs.writeFileSync(docPath, mergeProcessDoc(mergeDir, squashBranch, conflictedFiles, finishCommand));
186
184
  return docPath;
187
185
  }
188
- function printConflictHandback(docPath, mergeDir, squashBranch, conflictedFiles) {
186
+ // The AI-facing "what just happened / what to do next" recap on the conflict path. Explicit and
187
+ // numbered so an agent (or human) can't miss where the context lives or which command finishes.
188
+ function printConflictHandback(docPath, mergeDir, squashBranch, conflictedFiles, finishCommand) {
189
189
  process.stdout.write('\n' + SEP + `⚠️ Conflicts in ${conflictedFiles.length} file(s) — handing control back to you\n` + SEP + '\n');
190
- process.stdout.write(`You are on branch ${squashBranch} with conflicts in the working tree.\n\n`);
191
- process.stdout.write(`FOLLOW THE MERGE PROCESS: ${docPath}\n`);
192
- process.stdout.write(`3-point context per file in: ${mergeDir}/updatemain-<file>/\n\n`);
190
+ process.stdout.write('Here is exactly what I did and what you need to do:\n\n');
191
+ process.stdout.write('What I did:\n');
192
+ process.stdout.write(' 1. snapshotted your pre-merge state to a PreMerge branch\n');
193
+ process.stdout.write(' 2. pulled origin/main and squash-merged your work onto it\n');
194
+ process.stdout.write(` 3. hit conflicts — you are now on the transient branch ${squashBranch}\n\n`);
195
+ process.stdout.write('What you need to do:\n');
196
+ process.stdout.write(` 1. read the merge process doc: ${docPath}\n`);
197
+ process.stdout.write(` 2. resolve each conflicted file below (its 3-point A/B/C context + diffs are in\n`);
198
+ process.stdout.write(` ${mergeDir}/updatemain-<file>/), and write that file's merge-explanation.md\n`);
199
+ process.stdout.write(` 3. run pnpm ${finishCommand} — it validates, commits, and finalizes (do NOT git add/commit/push yourself)\n\n`);
193
200
  process.stdout.write('Conflicted files:\n');
194
201
  for (const file of conflictedFiles)
195
202
  process.stdout.write(` - ${file}\n`);
@@ -205,7 +212,7 @@ function handleConflictsHandback(repoRoot, mergeDir, currentBranch, squashBranch
205
212
  const marker = new merge_state_1.MergeMarker(currentBranch, squashBranch, backupBranch, prNumber, conflictedFiles, hashes.hashForkPoint, hashes.hashFeatureHead, hashes.hashMainHead, false);
206
213
  (0, merge_state_1.writeMergeMarker)(mergeDir, marker);
207
214
  const docPath = writeMergeProcessDoc(repoRoot, mergeDir, squashBranch, conflictedFiles, finishCommand);
208
- printConflictHandback(docPath, mergeDir, squashBranch, conflictedFiles);
215
+ printConflictHandback(docPath, mergeDir, squashBranch, conflictedFiles, finishCommand);
209
216
  }
210
217
  // Short sha of a ref (best-effort — '' if it can't resolve), for the branch-mutation log's
211
218
  // oldMain→newMain annotation.
@@ -1 +1 @@
1
- {"version":3,"file":"merge-start.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/merge-start.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 WEBPIECES_TMP_DIR, MERGE_EXPLANATION_FILE, CliExitError,\n MutationVerb, BranchMutationEvent, logBranchMutation,\n} from '@webpieces/rules-config';\nimport { gatherInfo } from '../git-gatherInfo';\nimport { baseBranchName, preMergeBackupName } from './branch-naming';\nimport { runGitChecked } from './git-exec';\nimport { MergeMarker, perFileContextDir, writeMergeMarker } from './merge-state';\n\n// merge-START: the first half of the 3-point squash-merge lifecycle. Brings origin/main into a fresh\n// `<branch>Squash`, and on conflict writes the 3-point context files + the unvalidated marker + the\n// process doc, then hands control back to the AI. On a clean merge it commits the squash and returns\n// the branch context so the caller (runUpdateFromMain) can run merge-END to finalize. It NEVER\n// finalizes or posts a PR — that is merge-END's / wp-finish-upsert-pr's job. Shared so\n// runUpdateFromMain (and, via it, wp-update-start + wp-start-upsert-pr) all set up a merge through\n// one code path. `finishCommand` names the command the AI runs to finish after resolving conflicts\n// (standalone update → `wp-update-end`; PR flow → `wp-finish-upsert-pr`).\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\ninterface HashPoints {\n hashForkPoint: string;\n hashFeatureHead: string;\n hashMainHead: string;\n}\n\n// The four branch names merge-END needs to finalize a merge (swap squash→feature, push, clean up).\nexport class MergeContext {\n currentBranch: string;\n squashBranch: string;\n backupBranch: string;\n prNumber: string;\n\n constructor(currentBranch: string, squashBranch: string, backupBranch: string, prNumber: string) {\n this.currentBranch = currentBranch;\n this.squashBranch = squashBranch;\n this.backupBranch = backupBranch;\n this.prNumber = prNumber;\n }\n}\n\n// Outcome of merge-start: 'clean' carries the context for merge-END to finalize; 'conflict' means the\n// marker + context files were written and the caller should hand back to the AI (exit 2).\nexport class MergeStartResult {\n status: 'clean' | 'conflict';\n context: MergeContext | null;\n\n constructor(status: 'clean' | 'conflict', context: MergeContext | null) {\n this.status = status;\n this.context = context;\n }\n}\n\n// Detect the PR by its STABLE base branch — the PR always lives on `base`, never on the numbered\n// generation (base2/base3/…) you may currently be on, so pass baseBranchName(currentBranch).\nfunction detectPr(baseBranch: string): string {\n const result = spawnSync(\n 'gh', ['pr', 'list', '--head', baseBranch, '--json', 'number', '--jq', '.[0].number'],\n { encoding: 'utf8' },\n );\n return result.status === 0 ? (result.stdout ?? '').trim() : '';\n}\n\n// Snapshot the pre-merge state as `<currentBranch>PreMerge`. One overwritable slot per generation:\n// if a stale PreMerge from a crashed rerun of THIS branch exists, delete it first so we never\n// accumulate backups (the old scheme minted a fresh Backup1/Backup2/… on every run).\nfunction createBackup(currentBranch: string): string {\n process.stdout.write('\\n' + SEP + '💾 Creating Pre-Merge Backup\\n' + SEP + '\\n');\n const backupBranch = preMergeBackupName(currentBranch);\n if (spawnSync('git', ['show-ref', '--verify', '--quiet', `refs/heads/${backupBranch}`]).status === 0) {\n runGitChecked(['branch', '-D', backupBranch], 'Failed to delete stale pre-merge backup');\n }\n runGitChecked(['checkout', '-b', backupBranch], 'Failed to create backup branch');\n runGitChecked(['checkout', currentBranch], 'Failed to return to feature branch');\n process.stdout.write(`✅ Backup created: ${backupBranch}\\n\\n`);\n return backupBranch;\n}\n\nfunction saveConflictContext(\n conflictedFiles: string[], mergeDir: string, forkPoint: string, featureHead: string, mainHead: string,\n): void {\n for (const file of conflictedFiles) {\n const fileDir = perFileContextDir(mergeDir, file);\n fs.mkdirSync(fileDir, { recursive: true });\n\n const fork = spawnSync('git', ['show', `${forkPoint}:${file}`], { encoding: 'utf8' });\n fs.writeFileSync(path.join(fileDir, 'A-forkpoint.txt'), fork.status === 0 ? (fork.stdout ?? '') : '(file did not exist)\\n');\n const feature = spawnSync('git', ['show', `${featureHead}:${file}`], { encoding: 'utf8' });\n fs.writeFileSync(path.join(fileDir, 'B-feature.txt'), feature.status === 0 ? (feature.stdout ?? '') : '(file did not exist)\\n');\n const main = spawnSync('git', ['show', `${mainHead}:${file}`], { encoding: 'utf8' });\n fs.writeFileSync(path.join(fileDir, 'C-main.txt'), main.status === 0 ? (main.stdout ?? '') : '(file did not exist)\\n');\n\n const ba = spawnSync('git', ['diff', forkPoint, featureHead, '--', file], { encoding: 'utf8' });\n fs.writeFileSync(path.join(fileDir, 'B-A.diff'), ba.stdout ?? '');\n const ca = spawnSync('git', ['diff', forkPoint, mainHead, '--', file], { encoding: 'utf8' });\n fs.writeFileSync(path.join(fileDir, 'C-A.diff'), ca.stdout ?? '');\n }\n}\n\n// Single source of truth for the merge process. The script WRITES it at conflict time (rather\n// than the AI relying on a separate hand-maintained doc), parameterized with the live MERGE_DIR\n// and conflicted-file list, so the instructions can never drift from the actual layout. The body\n// is a template constant with {{...}} placeholders so this stays a small filler function.\nconst MERGE_PROCESS_TEMPLATE = `# AI-Assisted Squash-Merge Conflict Resolution (generated)\n\nThis file was generated by \\`pnpm wp-update-start\\` when the 3-point squash-merge hit conflicts.\nIt is the single source of truth for the merge process — follow it exactly.\n\nYou are on branch \\`{{SQUASH_BRANCH}}\\` with conflict markers in the working tree.\n\\`MERGE_DIR = {{MERGE_DIR}}\\`\n\n## How the gate works\n\n- Resolve every conflicted file in the working tree.\n- Run **\\`pnpm {{FINISH_COMMAND}}\\`** — the validation + finish gate. It scans for leftover conflict\n markers, checks each conflicted file has a written merge explanation, validates, and commits the\n merge. (In the PR flow this command is \\`wp-finish-upsert-pr\\`, which additionally runs the\n \\`nx affected\\` build, renders the dashboard, and creates/updates the PR.)\n- **Do NOT run \\`git add\\` / \\`git commit\\` / \\`git push\\` / \\`gh pr\\` yourself.** They are blocked by\n the \\`merge-in-progress-guard\\` hook until the gate validates. The gate does the commit.\n\n## STEP 1 — Load the merge context\n\nPer conflicted file, \\`MERGE_DIR/updatemain-<safe_path>/\\` holds (\\`<safe_path>\\` = path with \\`/\\`→\\`__\\`):\n\n\\`\\`\\`\nA-forkpoint.txt # file at fork point (base)\nB-feature.txt # file on your feature branch\nC-main.txt # file on main\nB-A.diff # what your feature changed (B−A)\nC-A.diff # what main changed (C−A)\n\\`\\`\\`\n\n\\`updatemain-hashes.json\\` holds A/B/C commit hashes. To see why main changed:\n\\`git log <A>..<C> --oneline\\`.\n\n## STEP 2 — Resolve each conflicted file\n\nFor each file: read the working-tree file (the markers) and its \\`B-A.diff\\` / \\`C-A.diff\\` (intent),\nthen Edit to the resolved version, removing ALL conflict markers.\n\nStrategies: goals align & non-overlapping → merge both · one side removes what the other modifies\n→ prefer the removal · same lines, simple (imports/format) → merge both · same lines, complex or\nconflicting goals → ask the user · feature re-implements what main already squashed → prefer\nmain's, then re-apply only the genuinely new feature work.\n\n**Then write a merge explanation** for each conflicted file — NOT a comment in the source (that\nbreaks for JSON and deleted files). Write it next to that file's diffs, at:\n\n\\`\\`\\`\nMERGE_DIR/updatemain-<safe_path>/{{EXPLANATION_FILE}}\n\\`\\`\\`\n\n(\\`<safe_path>\\` = the conflict file path with \\`/\\` → \\`__\\`, the same dir that holds its\n\\`A-forkpoint.txt\\` / \\`B-A.diff\\` / \\`C-A.diff\\`.) In it, explain in a few sentences how you resolved\nthis file: which side you took where, what you combined from B-A.diff vs C-A.diff, and why. The\ngate fails if any conflicted file's explanation is missing or empty. Do not paste A/B/C context\nblocks into the source code.\n\n## STEP 3 — Run the gate (validates the merge AND finalizes it)\n\n\\`\\`\\`\npnpm {{FINISH_COMMAND}}\n\\`\\`\\`\n\n- Leftover conflict markers → fix those files and re-run.\n- Missing merge explanation → write it (see STEP 2) and re-run.\n- Build failure → fix the TypeScript/lint errors and re-run (the gate re-stages for you).\n- Missing review.json (PR flow only) → write it in the printed format (your PR review), then re-run.\n- On success it commits and finalizes the merge (in the PR flow it also renders the dashboard and\n creates/updates the PR).\n\n## Conflicted files\n\n{{FILE_LIST}}\n\n## If you need to bail out\n\nA backup branch was created (e.g. \\`<feature>PreMerge\\`). To abandon:\n\n\\`\\`\\`\ngit merge --abort 2>/dev/null; git checkout <feature> ; git branch -D {{SQUASH_BRANCH}}\n\\`\\`\\`\n\nThen delete \\`{{MERGE_DIR}}/\\` for a clean slate.\n`;\n\nfunction mergeProcessDoc(mergeDir: string, squashBranch: string, conflictedFiles: string[], finishCommand: string): string {\n const fileList = conflictedFiles.map((f: string): string => `- \\`${f}\\``).join('\\n');\n return MERGE_PROCESS_TEMPLATE\n .replace(/\\{\\{SQUASH_BRANCH\\}\\}/g, squashBranch)\n .replace(/\\{\\{MERGE_DIR\\}\\}/g, mergeDir)\n .replace(/\\{\\{EXPLANATION_FILE\\}\\}/g, MERGE_EXPLANATION_FILE)\n .replace(/\\{\\{FINISH_COMMAND\\}\\}/g, finishCommand)\n .replace(/\\{\\{FILE_LIST\\}\\}/g, fileList);\n}\n\n// Returns the absolute path of the written doc.\nfunction writeMergeProcessDoc(repoRoot: string, mergeDir: string, squashBranch: string, conflictedFiles: string[], finishCommand: string): string {\n const docDir = path.join(repoRoot, WEBPIECES_TMP_DIR, 'instruct-ai');\n fs.mkdirSync(docDir, { recursive: true });\n const docPath = path.join(docDir, 'webpieces.mergeprocess.md');\n fs.writeFileSync(docPath, mergeProcessDoc(mergeDir, squashBranch, conflictedFiles, finishCommand));\n return docPath;\n}\n\nfunction printConflictHandback(docPath: string, mergeDir: string, squashBranch: string, conflictedFiles: string[]): void {\n process.stdout.write('\\n' + SEP + `⚠️ Conflicts in ${conflictedFiles.length} file(s) — handing control back to you\\n` + SEP + '\\n');\n process.stdout.write(`You are on branch ${squashBranch} with conflicts in the working tree.\\n\\n`);\n process.stdout.write(`FOLLOW THE MERGE PROCESS: ${docPath}\\n`);\n process.stdout.write(`3-point context per file in: ${mergeDir}/updatemain-<file>/\\n\\n`);\n process.stdout.write('Conflicted files:\\n');\n for (const file of conflictedFiles) process.stdout.write(` - ${file}\\n`);\n process.stdout.write('\\n' + SEP);\n}\n\n// Write the conflict context files + the unvalidated marker + the process doc. Does NOT exit — the\n// caller decides (runUpdateFromMain exits 2 to hand back to the AI).\nfunction handleConflictsHandback(\n repoRoot: string, mergeDir: string, currentBranch: string, squashBranch: string,\n backupBranch: string, prNumber: string, hashes: HashPoints, finishCommand: string,\n): void {\n const raw = execSync('git diff --name-only --diff-filter=U', { encoding: 'utf8' }).trim();\n const conflictedFiles = raw.split('\\n').filter((f: string): boolean => f.trim() !== '');\n fs.writeFileSync(path.join(mergeDir, 'updatemain-conflicted-files.txt'), raw + '\\n');\n saveConflictContext(conflictedFiles, mergeDir, hashes.hashForkPoint, hashes.hashFeatureHead, hashes.hashMainHead);\n\n const marker = new MergeMarker(\n currentBranch, squashBranch, backupBranch, prNumber, conflictedFiles,\n hashes.hashForkPoint, hashes.hashFeatureHead, hashes.hashMainHead, false,\n );\n writeMergeMarker(mergeDir, marker);\n const docPath = writeMergeProcessDoc(repoRoot, mergeDir, squashBranch, conflictedFiles, finishCommand);\n printConflictHandback(docPath, mergeDir, squashBranch, conflictedFiles);\n}\n\n// Short sha of a ref (best-effort — '' if it can't resolve), for the branch-mutation log's\n// oldMain→newMain annotation.\nfunction shortSha(ref: string): string {\n const result = spawnSync('git', ['rev-parse', '--short', ref], { encoding: 'utf8' });\n return result.status === 0 ? (result.stdout ?? '').trim() : '';\n}\n\n// Log the CONFLICT phase with the conflicted-file list + the artifact paths a resolver needs — the\n// per-file 3-point context dir and the generated merge-process doc — so the branch-mutation log alone\n// tells the next agent where to look (no reflog/log archaeology).\nfunction logConflict(repoRoot: string, verb: MutationVerb, mergeDir: string): void {\n const raw = spawnSync('git', ['diff', '--name-only', '--diff-filter=U'], { cwd: repoRoot, encoding: 'utf8' });\n const files = (raw.status === 0 ? (raw.stdout ?? '') : '').split('\\n').map((f: string): string => f.trim()).filter((f: string): boolean => f !== '');\n const event = new BranchMutationEvent(verb, 'CONFLICT');\n event.conflict = true;\n event.conflictFiles = files;\n event.artifacts = [\n path.join(mergeDir, 'updatemain-<file>'),\n path.join(repoRoot, WEBPIECES_TMP_DIR, 'instruct-ai', 'webpieces.mergeprocess.md'),\n ];\n logBranchMutation(repoRoot, event);\n}\n\nexport async function mergeStart(repoRoot: string, verb: MutationVerb, mergeDir: string, finishCommand: string): Promise<MergeStartResult> {\n const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();\n if (currentBranch.endsWith('Squash')) {\n throw new CliExitError(1, `❌ On a leftover ${currentBranch} branch with no merge marker. Clean up: git branch -D ${currentBranch}`);\n }\n\n process.stdout.write('\\n' + SEP + '🔄 Squash-Merge Update from Main\\n' + SEP + '\\n');\n // gatherInfo RETURNS (never exits): an already-even-with-main branch is NOT special-cased here —\n // we flow straight on. The squash merge below becomes a no-op that the `nothingStaged` path\n // (further down) handles, so the caller still proceeds to push + build. (Previously gatherInfo\n // process.exit(0)'d on this case, silently killing wp-start-upsert-pr before push/build.)\n const info = await gatherInfo();\n const hashes = info.hashes;\n if (info.alreadyUpToDate) {\n process.stdout.write('ℹ️ Branch already even with main; nothing to merge, continuing to push/build.\\n');\n }\n\n const prNumber = detectPr(baseBranchName(currentBranch));\n process.stdout.write(prNumber ? `Existing PR #${prNumber} will be updated.\\n` : 'No existing PR (one can be created later).\\n');\n\n const backupBranch = createBackup(currentBranch);\n const backupEvent = new BranchMutationEvent(verb, 'BACKUP');\n backupEvent.fromBranch = currentBranch;\n backupEvent.toBranch = backupBranch;\n logBranchMutation(repoRoot, backupEvent);\n\n const squashBranch = `${currentBranch}Squash`;\n if (spawnSync('git', ['show-ref', '--verify', '--quiet', `refs/heads/${squashBranch}`]).status === 0) {\n throw new CliExitError(1, `❌ Stale ${squashBranch} from a previous run. Delete it: git branch -D ${squashBranch}`);\n }\n\n const mainBeforePull = shortSha('main');\n runGitChecked(['checkout', 'main'], 'Failed to checkout main');\n logBranchMutation(repoRoot, new BranchMutationEvent(verb, 'CHECKOUT_MAIN'));\n runGitChecked(['pull', 'origin', 'main'], 'Failed to pull origin/main');\n const pullEvent = new BranchMutationEvent(verb, 'PULL');\n pullEvent.oldMain = mainBeforePull;\n pullEvent.newMain = shortSha('main');\n logBranchMutation(repoRoot, pullEvent);\n runGitChecked(['checkout', '-b', squashBranch], 'Failed to create squash branch');\n\n process.stdout.write('\\n' + SEP + `🔀 Squash merging ${currentBranch}\\n` + SEP + '\\n');\n const merge = spawnSync('git', ['merge', '--squash', currentBranch], { stdio: 'inherit' });\n if (merge.status !== 0) {\n handleConflictsHandback(repoRoot, mergeDir, currentBranch, squashBranch, backupBranch, prNumber, hashes, finishCommand);\n logConflict(repoRoot, verb, mergeDir);\n return new MergeStartResult('conflict', null);\n }\n logBranchMutation(repoRoot, new BranchMutationEvent(verb, 'SQUASH'));\n\n const nothingStaged = spawnSync('git', ['diff-index', '--quiet', '--cached', 'HEAD', '--']).status === 0;\n if (nothingStaged) {\n process.stdout.write('ℹ️ Already up-to-date with main (nothing to merge).\\n');\n } else {\n runGitChecked(['commit', '-m', `Squash merge of ${currentBranch}`], 'Failed to commit squash merge');\n }\n return new MergeStartResult('clean', new MergeContext(currentBranch, squashBranch, backupBranch, prNumber));\n}\n"]}
1
+ {"version":3,"file":"merge-start.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/merge-start.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 WEBPIECES_TMP_DIR, MERGE_EXPLANATION_FILE, CliExitError,\n MutationVerb, BranchMutationEvent, logBranchMutation,\n} from '@webpieces/rules-config';\nimport { gatherInfo } from '../git-gatherInfo';\nimport { baseBranchName, nextFreePreMergeSlot } from './branch-naming';\nimport { runGitChecked } from './git-exec';\nimport { MergeMarker, perFileContextDir, writeMergeMarker } from './merge-state';\n\n// merge-START: the first half of the 3-point squash-merge lifecycle. Brings origin/main into a fresh\n// `<branch>Squash`, and on conflict writes the 3-point context files + the unvalidated marker + the\n// process doc, then hands control back to the AI. On a clean merge it commits the squash and returns\n// the branch context so the caller (runUpdateFromMain) can run merge-END to finalize. It NEVER\n// finalizes or posts a PR — that is merge-END's / wp-finish-upsert-pr's job. Shared so\n// runUpdateFromMain (and, via it, wp-update-start + wp-start-upsert-pr) all set up a merge through\n// one code path. `finishCommand` names the command the AI runs to finish after resolving conflicts\n// (standalone update → `wp-update-end`; PR flow → `wp-finish-upsert-pr`).\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\ninterface HashPoints {\n hashForkPoint: string;\n hashFeatureHead: string;\n hashMainHead: string;\n}\n\n// The four branch names merge-END needs to finalize a merge (swap squash→feature, push, clean up).\nexport class MergeContext {\n currentBranch: string;\n squashBranch: string;\n backupBranch: string;\n prNumber: string;\n\n constructor(currentBranch: string, squashBranch: string, backupBranch: string, prNumber: string) {\n this.currentBranch = currentBranch;\n this.squashBranch = squashBranch;\n this.backupBranch = backupBranch;\n this.prNumber = prNumber;\n }\n}\n\n// Outcome of merge-start: 'clean' carries the context for merge-END to finalize; 'conflict' means the\n// marker + context files were written and the caller should hand back to the AI (exit 2).\nexport class MergeStartResult {\n status: 'clean' | 'conflict';\n context: MergeContext | null;\n\n constructor(status: 'clean' | 'conflict', context: MergeContext | null) {\n this.status = status;\n this.context = context;\n }\n}\n\n// Detect the PR by its STABLE feature branch — pass baseBranchName(currentBranch) so a leftover\n// `…wpN` from the old scheme still resolves to the one name the PR lives on.\nfunction detectPr(baseBranch: string): string {\n const result = spawnSync(\n 'gh', ['pr', 'list', '--head', baseBranch, '--json', 'number', '--jq', '.[0].number'],\n { encoding: 'utf8' },\n );\n return result.status === 0 ? (result.stdout ?? '').trim() : '';\n}\n\n// Snapshot the pre-merge state into the next free NUMBERED slot — `<currentBranch>PreMerge`, then\n// `PreMerge2`, `PreMerge3`, … — never overwriting an existing one. The feature branch name is now\n// constant across syncs, so the numbered snapshot itself is the trail of \"how many times I re-merged\n// main\" that the old `wpN` branch name used to carry.\nfunction createBackup(currentBranch: string): string {\n process.stdout.write('\\n' + SEP + '💾 Creating Pre-Merge Backup\\n' + SEP + '\\n');\n const backupBranch = nextFreePreMergeSlot(\n currentBranch,\n (name: string): boolean => spawnSync('git', ['show-ref', '--verify', '--quiet', `refs/heads/${name}`]).status === 0,\n );\n runGitChecked(['checkout', '-b', backupBranch], 'Failed to create backup branch');\n runGitChecked(['checkout', currentBranch], 'Failed to return to feature branch');\n process.stdout.write(`✅ Backup created: ${backupBranch}\\n\\n`);\n return backupBranch;\n}\n\nfunction saveConflictContext(\n conflictedFiles: string[], mergeDir: string, forkPoint: string, featureHead: string, mainHead: string,\n): void {\n for (const file of conflictedFiles) {\n const fileDir = perFileContextDir(mergeDir, file);\n fs.mkdirSync(fileDir, { recursive: true });\n\n const fork = spawnSync('git', ['show', `${forkPoint}:${file}`], { encoding: 'utf8' });\n fs.writeFileSync(path.join(fileDir, 'A-forkpoint.txt'), fork.status === 0 ? (fork.stdout ?? '') : '(file did not exist)\\n');\n const feature = spawnSync('git', ['show', `${featureHead}:${file}`], { encoding: 'utf8' });\n fs.writeFileSync(path.join(fileDir, 'B-feature.txt'), feature.status === 0 ? (feature.stdout ?? '') : '(file did not exist)\\n');\n const main = spawnSync('git', ['show', `${mainHead}:${file}`], { encoding: 'utf8' });\n fs.writeFileSync(path.join(fileDir, 'C-main.txt'), main.status === 0 ? (main.stdout ?? '') : '(file did not exist)\\n');\n\n const ba = spawnSync('git', ['diff', forkPoint, featureHead, '--', file], { encoding: 'utf8' });\n fs.writeFileSync(path.join(fileDir, 'B-A.diff'), ba.stdout ?? '');\n const ca = spawnSync('git', ['diff', forkPoint, mainHead, '--', file], { encoding: 'utf8' });\n fs.writeFileSync(path.join(fileDir, 'C-A.diff'), ca.stdout ?? '');\n }\n}\n\n// Single source of truth for the merge process. The script WRITES it at conflict time (rather\n// than the AI relying on a separate hand-maintained doc), parameterized with the live MERGE_DIR\n// and conflicted-file list, so the instructions can never drift from the actual layout. The body\n// is a template constant with {{...}} placeholders so this stays a small filler function.\nconst MERGE_PROCESS_TEMPLATE = `# AI-Assisted Squash-Merge Conflict Resolution (generated)\n\nThis file was generated by \\`pnpm wp-update-start\\` when the 3-point squash-merge hit conflicts.\nIt is the single source of truth for the merge process — follow it exactly.\n\nYou are on branch \\`{{SQUASH_BRANCH}}\\` with conflict markers in the working tree.\n\\`MERGE_DIR = {{MERGE_DIR}}\\`\n\n## How the gate works\n\n- Resolve every conflicted file in the working tree.\n- Run **\\`pnpm {{FINISH_COMMAND}}\\`** — the validation + finish gate. It scans for leftover conflict\n markers, checks each conflicted file has a written merge explanation, validates, and commits the\n merge. (In the PR flow this command is \\`wp-finish-upsert-pr\\`, which additionally runs the\n \\`nx affected\\` build, renders the dashboard, and creates/updates the PR.)\n- **Do NOT run \\`git add\\` / \\`git commit\\` / \\`git push\\` / \\`gh pr\\` yourself.** They are blocked by\n the \\`merge-in-progress-guard\\` hook until the gate validates. The gate does the commit.\n\n## STEP 1 — Load the merge context\n\nPer conflicted file, \\`MERGE_DIR/updatemain-<safe_path>/\\` holds (\\`<safe_path>\\` = path with \\`/\\`→\\`__\\`):\n\n\\`\\`\\`\nA-forkpoint.txt # file at fork point (base)\nB-feature.txt # file on your feature branch\nC-main.txt # file on main\nB-A.diff # what your feature changed (B−A)\nC-A.diff # what main changed (C−A)\n\\`\\`\\`\n\n\\`updatemain-hashes.json\\` holds A/B/C commit hashes. To see why main changed:\n\\`git log <A>..<C> --oneline\\`.\n\n## STEP 2 — Resolve each conflicted file\n\nFor each file: read the working-tree file (the markers) and its \\`B-A.diff\\` / \\`C-A.diff\\` (intent),\nthen Edit to the resolved version, removing ALL conflict markers.\n\nStrategies: goals align & non-overlapping → merge both · one side removes what the other modifies\n→ prefer the removal · same lines, simple (imports/format) → merge both · same lines, complex or\nconflicting goals → ask the user · feature re-implements what main already squashed → prefer\nmain's, then re-apply only the genuinely new feature work.\n\n**Then write a merge explanation** for each conflicted file — NOT a comment in the source (that\nbreaks for JSON and deleted files). Write it next to that file's diffs, at:\n\n\\`\\`\\`\nMERGE_DIR/updatemain-<safe_path>/{{EXPLANATION_FILE}}\n\\`\\`\\`\n\n(\\`<safe_path>\\` = the conflict file path with \\`/\\` → \\`__\\`, the same dir that holds its\n\\`A-forkpoint.txt\\` / \\`B-A.diff\\` / \\`C-A.diff\\`.) In it, explain in a few sentences how you resolved\nthis file: which side you took where, what you combined from B-A.diff vs C-A.diff, and why. The\ngate fails if any conflicted file's explanation is missing or empty. Do not paste A/B/C context\nblocks into the source code.\n\n## STEP 3 — Run the gate (validates the merge AND finalizes it)\n\n\\`\\`\\`\npnpm {{FINISH_COMMAND}}\n\\`\\`\\`\n\n- Leftover conflict markers → fix those files and re-run.\n- Missing merge explanation → write it (see STEP 2) and re-run.\n- Build failure → fix the TypeScript/lint errors and re-run (the gate re-stages for you).\n- Missing review.json (PR flow only) → write it in the printed format (your PR review), then re-run.\n- On success it commits and finalizes the merge (in the PR flow it also renders the dashboard and\n creates/updates the PR).\n\n## Conflicted files\n\n{{FILE_LIST}}\n\n## If you need to bail out\n\nA backup branch was created (e.g. \\`<feature>PreMerge\\`). To abandon:\n\n\\`\\`\\`\ngit merge --abort 2>/dev/null; git checkout <feature> ; git branch -D {{SQUASH_BRANCH}}\n\\`\\`\\`\n\nThen delete \\`{{MERGE_DIR}}/\\` for a clean slate.\n`;\n\nfunction mergeProcessDoc(mergeDir: string, squashBranch: string, conflictedFiles: string[], finishCommand: string): string {\n const fileList = conflictedFiles.map((f: string): string => `- \\`${f}\\``).join('\\n');\n return MERGE_PROCESS_TEMPLATE\n .replace(/\\{\\{SQUASH_BRANCH\\}\\}/g, squashBranch)\n .replace(/\\{\\{MERGE_DIR\\}\\}/g, mergeDir)\n .replace(/\\{\\{EXPLANATION_FILE\\}\\}/g, MERGE_EXPLANATION_FILE)\n .replace(/\\{\\{FINISH_COMMAND\\}\\}/g, finishCommand)\n .replace(/\\{\\{FILE_LIST\\}\\}/g, fileList);\n}\n\n// Returns the absolute path of the written doc.\nfunction writeMergeProcessDoc(repoRoot: string, mergeDir: string, squashBranch: string, conflictedFiles: string[], finishCommand: string): string {\n const docDir = path.join(repoRoot, WEBPIECES_TMP_DIR, 'instruct-ai');\n fs.mkdirSync(docDir, { recursive: true });\n const docPath = path.join(docDir, 'webpieces.mergeprocess.md');\n fs.writeFileSync(docPath, mergeProcessDoc(mergeDir, squashBranch, conflictedFiles, finishCommand));\n return docPath;\n}\n\n// The AI-facing \"what just happened / what to do next\" recap on the conflict path. Explicit and\n// numbered so an agent (or human) can't miss where the context lives or which command finishes.\nfunction printConflictHandback(\n docPath: string, mergeDir: string, squashBranch: string, conflictedFiles: string[], finishCommand: string,\n): void {\n process.stdout.write('\\n' + SEP + `⚠️ Conflicts in ${conflictedFiles.length} file(s) — handing control back to you\\n` + SEP + '\\n');\n process.stdout.write('Here is exactly what I did and what you need to do:\\n\\n');\n process.stdout.write('What I did:\\n');\n process.stdout.write(' 1. snapshotted your pre-merge state to a PreMerge branch\\n');\n process.stdout.write(' 2. pulled origin/main and squash-merged your work onto it\\n');\n process.stdout.write(` 3. hit conflicts — you are now on the transient branch ${squashBranch}\\n\\n`);\n process.stdout.write('What you need to do:\\n');\n process.stdout.write(` 1. read the merge process doc: ${docPath}\\n`);\n process.stdout.write(` 2. resolve each conflicted file below (its 3-point A/B/C context + diffs are in\\n`);\n process.stdout.write(` ${mergeDir}/updatemain-<file>/), and write that file's merge-explanation.md\\n`);\n process.stdout.write(` 3. run pnpm ${finishCommand} — it validates, commits, and finalizes (do NOT git add/commit/push yourself)\\n\\n`);\n process.stdout.write('Conflicted files:\\n');\n for (const file of conflictedFiles) process.stdout.write(` - ${file}\\n`);\n process.stdout.write('\\n' + SEP);\n}\n\n// Write the conflict context files + the unvalidated marker + the process doc. Does NOT exit — the\n// caller decides (runUpdateFromMain exits 2 to hand back to the AI).\nfunction handleConflictsHandback(\n repoRoot: string, mergeDir: string, currentBranch: string, squashBranch: string,\n backupBranch: string, prNumber: string, hashes: HashPoints, finishCommand: string,\n): void {\n const raw = execSync('git diff --name-only --diff-filter=U', { encoding: 'utf8' }).trim();\n const conflictedFiles = raw.split('\\n').filter((f: string): boolean => f.trim() !== '');\n fs.writeFileSync(path.join(mergeDir, 'updatemain-conflicted-files.txt'), raw + '\\n');\n saveConflictContext(conflictedFiles, mergeDir, hashes.hashForkPoint, hashes.hashFeatureHead, hashes.hashMainHead);\n\n const marker = new MergeMarker(\n currentBranch, squashBranch, backupBranch, prNumber, conflictedFiles,\n hashes.hashForkPoint, hashes.hashFeatureHead, hashes.hashMainHead, false,\n );\n writeMergeMarker(mergeDir, marker);\n const docPath = writeMergeProcessDoc(repoRoot, mergeDir, squashBranch, conflictedFiles, finishCommand);\n printConflictHandback(docPath, mergeDir, squashBranch, conflictedFiles, finishCommand);\n}\n\n// Short sha of a ref (best-effort — '' if it can't resolve), for the branch-mutation log's\n// oldMain→newMain annotation.\nfunction shortSha(ref: string): string {\n const result = spawnSync('git', ['rev-parse', '--short', ref], { encoding: 'utf8' });\n return result.status === 0 ? (result.stdout ?? '').trim() : '';\n}\n\n// Log the CONFLICT phase with the conflicted-file list + the artifact paths a resolver needs — the\n// per-file 3-point context dir and the generated merge-process doc — so the branch-mutation log alone\n// tells the next agent where to look (no reflog/log archaeology).\nfunction logConflict(repoRoot: string, verb: MutationVerb, mergeDir: string): void {\n const raw = spawnSync('git', ['diff', '--name-only', '--diff-filter=U'], { cwd: repoRoot, encoding: 'utf8' });\n const files = (raw.status === 0 ? (raw.stdout ?? '') : '').split('\\n').map((f: string): string => f.trim()).filter((f: string): boolean => f !== '');\n const event = new BranchMutationEvent(verb, 'CONFLICT');\n event.conflict = true;\n event.conflictFiles = files;\n event.artifacts = [\n path.join(mergeDir, 'updatemain-<file>'),\n path.join(repoRoot, WEBPIECES_TMP_DIR, 'instruct-ai', 'webpieces.mergeprocess.md'),\n ];\n logBranchMutation(repoRoot, event);\n}\n\nexport async function mergeStart(repoRoot: string, verb: MutationVerb, mergeDir: string, finishCommand: string): Promise<MergeStartResult> {\n const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();\n if (currentBranch.endsWith('Squash')) {\n throw new CliExitError(1, `❌ On a leftover ${currentBranch} branch with no merge marker. Clean up: git branch -D ${currentBranch}`);\n }\n\n process.stdout.write('\\n' + SEP + '🔄 Squash-Merge Update from Main\\n' + SEP + '\\n');\n // gatherInfo RETURNS (never exits): an already-even-with-main branch is NOT special-cased here —\n // we flow straight on. The squash merge below becomes a no-op that the `nothingStaged` path\n // (further down) handles, so the caller still proceeds to push + build. (Previously gatherInfo\n // process.exit(0)'d on this case, silently killing wp-start-upsert-pr before push/build.)\n const info = await gatherInfo();\n const hashes = info.hashes;\n if (info.alreadyUpToDate) {\n process.stdout.write('ℹ️ Branch already even with main; nothing to merge, continuing to push/build.\\n');\n }\n\n const prNumber = detectPr(baseBranchName(currentBranch));\n process.stdout.write(prNumber ? `Existing PR #${prNumber} will be updated.\\n` : 'No existing PR (one can be created later).\\n');\n\n const backupBranch = createBackup(currentBranch);\n const backupEvent = new BranchMutationEvent(verb, 'BACKUP');\n backupEvent.fromBranch = currentBranch;\n backupEvent.toBranch = backupBranch;\n logBranchMutation(repoRoot, backupEvent);\n\n const squashBranch = `${currentBranch}Squash`;\n if (spawnSync('git', ['show-ref', '--verify', '--quiet', `refs/heads/${squashBranch}`]).status === 0) {\n throw new CliExitError(1, `❌ Stale ${squashBranch} from a previous run. Delete it: git branch -D ${squashBranch}`);\n }\n\n const mainBeforePull = shortSha('main');\n runGitChecked(['checkout', 'main'], 'Failed to checkout main');\n logBranchMutation(repoRoot, new BranchMutationEvent(verb, 'CHECKOUT_MAIN'));\n runGitChecked(['pull', 'origin', 'main'], 'Failed to pull origin/main');\n const pullEvent = new BranchMutationEvent(verb, 'PULL');\n pullEvent.oldMain = mainBeforePull;\n pullEvent.newMain = shortSha('main');\n logBranchMutation(repoRoot, pullEvent);\n runGitChecked(['checkout', '-b', squashBranch], 'Failed to create squash branch');\n\n process.stdout.write('\\n' + SEP + `🔀 Squash merging ${currentBranch}\\n` + SEP + '\\n');\n const merge = spawnSync('git', ['merge', '--squash', currentBranch], { stdio: 'inherit' });\n if (merge.status !== 0) {\n handleConflictsHandback(repoRoot, mergeDir, currentBranch, squashBranch, backupBranch, prNumber, hashes, finishCommand);\n logConflict(repoRoot, verb, mergeDir);\n return new MergeStartResult('conflict', null);\n }\n logBranchMutation(repoRoot, new BranchMutationEvent(verb, 'SQUASH'));\n\n const nothingStaged = spawnSync('git', ['diff-index', '--quiet', '--cached', 'HEAD', '--']).status === 0;\n if (nothingStaged) {\n process.stdout.write('ℹ️ Already up-to-date with main (nothing to merge).\\n');\n } else {\n runGitChecked(['commit', '-m', `Squash merge of ${currentBranch}`], 'Failed to commit squash merge');\n }\n return new MergeStartResult('clean', new MergeContext(currentBranch, squashBranch, backupBranch, prNumber));\n}\n"]}
@@ -6,7 +6,7 @@ const rules_config_1 = require("@webpieces/rules-config");
6
6
  const branch_naming_1 = require("./branch-naming");
7
7
  const SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
8
8
  // The open PR (if any) tracking a feature branch, looked up by its STABLE base name (the PR always
9
- // lives on `base`, never on the numbered generation basewp2/basewp3/…). Returns '' ONLY when GitHub
9
+ // lives on the stable feature name, and a leftover `…wpN` still resolves there). Returns '' ONLY when GitHub
10
10
  // answered and there is genuinely no open PR.
11
11
  //
12
12
  // This is a HARD GATE, not an advisory hint: it FAILS FAST if `gh` cannot answer (not installed, not
@@ -1 +1 @@
1
- {"version":3,"file":"open-pr-check.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/open-pr-check.ts"],"names":[],"mappings":";;AAeA,0CAkBC;AAjCD,iDAA0C;AAC1C,0DAAuD;AACvD,mDAAiD;AAEjD,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAEvE,mGAAmG;AACnG,oGAAoG;AACpG,8CAA8C;AAC9C,EAAE;AACF,qGAAqG;AACrG,qGAAqG;AACrG,sGAAsG;AACtG,iGAAiG;AACjG,qCAAqC;AACrC,SAAgB,eAAe,CAAC,QAAgB,EAAE,aAAqB;IACnE,MAAM,IAAI,GAAG,IAAA,8BAAc,EAAC,aAAa,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,IAAA,yBAAS,EACpB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,CAAC,EAClG,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,CACtC,CAAC;IACF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC;QAC5G,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,IAAI,GAAG,GAAG,GAAG,oEAAoE,GAAG,GAAG,GAAG,IAAI;YAC9F,8BAA8B,IAAI,MAAM,MAAM,MAAM;YACpD,6FAA6F;YAC7F,8FAA8F;YAC9F,4FAA4F;YAC5F,+EAA+E,CAClF,CAAC;IACN,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AACxC,CAAC","sourcesContent":["import { spawnSync } from 'child_process';\nimport { CliExitError } from '@webpieces/rules-config';\nimport { baseBranchName } from './branch-naming';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// The open PR (if any) tracking a feature branch, looked up by its STABLE base name (the PR always\n// lives on `base`, never on the numbered generation basewp2/basewp3/…). Returns '' ONLY when GitHub\n// answered and there is genuinely no open PR.\n//\n// This is a HARD GATE, not an advisory hint: it FAILS FAST if `gh` cannot answer (not installed, not\n// authenticated, offline). We must NOT degrade \"couldn't ask GitHub\" into \"no PR\" — a 3-point update\n// makes a new branch generation and the existing PR would be left pointing at the OLD branch (stale),\n// so running wp-update-start blind when a PR might exist is exactly the disaster we are guarding\n// against. Refuse rather than guess.\nexport function openPrForBranch(repoRoot: string, currentBranch: string): string {\n const base = baseBranchName(currentBranch);\n const result = spawnSync(\n 'gh', ['pr', 'list', '--head', base, '--state', 'open', '--json', 'number', '--jq', '.[0].number'],\n { cwd: repoRoot, encoding: 'utf8' },\n );\n if (result.status !== 0) {\n const detail = (result.stderr ?? '').trim() || (result.error ? result.error.message : 'gh exited non-zero');\n throw new CliExitError(2,\n '\\n' + SEP + '❌ Could not ask GitHub whether an open PR exists for this branch\\n' + SEP + '\\n' +\n `gh failed for base branch \"${base}\": ${detail}\\n\\n` +\n 'This check must NOT be skipped: a 3-point update creates a NEW branch generation, so if a\\n' +\n 'PR already exists, wp-update-start would leave it pointing at the OLD branch. Fix gh first\\n' +\n '(install / `gh auth login` / restore network), then re-run — or, if a PR does exist, use\\n' +\n 'the PR flow: pnpm wp-start-upsert-pr → /wp-merge → pnpm wp-finish-upsert-pr\\n',\n );\n }\n return (result.stdout ?? '').trim();\n}\n"]}
1
+ {"version":3,"file":"open-pr-check.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/open-pr-check.ts"],"names":[],"mappings":";;AAeA,0CAkBC;AAjCD,iDAA0C;AAC1C,0DAAuD;AACvD,mDAAiD;AAEjD,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAEvE,mGAAmG;AACnG,6GAA6G;AAC7G,8CAA8C;AAC9C,EAAE;AACF,qGAAqG;AACrG,qGAAqG;AACrG,sGAAsG;AACtG,iGAAiG;AACjG,qCAAqC;AACrC,SAAgB,eAAe,CAAC,QAAgB,EAAE,aAAqB;IACnE,MAAM,IAAI,GAAG,IAAA,8BAAc,EAAC,aAAa,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,IAAA,yBAAS,EACpB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,CAAC,EAClG,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,CACtC,CAAC;IACF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC;QAC5G,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,IAAI,GAAG,GAAG,GAAG,oEAAoE,GAAG,GAAG,GAAG,IAAI;YAC9F,8BAA8B,IAAI,MAAM,MAAM,MAAM;YACpD,6FAA6F;YAC7F,8FAA8F;YAC9F,4FAA4F;YAC5F,+EAA+E,CAClF,CAAC;IACN,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AACxC,CAAC","sourcesContent":["import { spawnSync } from 'child_process';\nimport { CliExitError } from '@webpieces/rules-config';\nimport { baseBranchName } from './branch-naming';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// The open PR (if any) tracking a feature branch, looked up by its STABLE base name (the PR always\n// lives on the stable feature name, and a leftover `…wpN` still resolves there). Returns '' ONLY when GitHub\n// answered and there is genuinely no open PR.\n//\n// This is a HARD GATE, not an advisory hint: it FAILS FAST if `gh` cannot answer (not installed, not\n// authenticated, offline). We must NOT degrade \"couldn't ask GitHub\" into \"no PR\" — a 3-point update\n// makes a new branch generation and the existing PR would be left pointing at the OLD branch (stale),\n// so running wp-update-start blind when a PR might exist is exactly the disaster we are guarding\n// against. Refuse rather than guess.\nexport function openPrForBranch(repoRoot: string, currentBranch: string): string {\n const base = baseBranchName(currentBranch);\n const result = spawnSync(\n 'gh', ['pr', 'list', '--head', base, '--state', 'open', '--json', 'number', '--jq', '.[0].number'],\n { cwd: repoRoot, encoding: 'utf8' },\n );\n if (result.status !== 0) {\n const detail = (result.stderr ?? '').trim() || (result.error ? result.error.message : 'gh exited non-zero');\n throw new CliExitError(2,\n '\\n' + SEP + '❌ Could not ask GitHub whether an open PR exists for this branch\\n' + SEP + '\\n' +\n `gh failed for base branch \"${base}\": ${detail}\\n\\n` +\n 'This check must NOT be skipped: a 3-point update creates a NEW branch generation, so if a\\n' +\n 'PR already exists, wp-update-start would leave it pointing at the OLD branch. Fix gh first\\n' +\n '(install / `gh auth login` / restore network), then re-run — or, if a PR does exist, use\\n' +\n 'the PR flow: pnpm wp-start-upsert-pr → /wp-merge → pnpm wp-finish-upsert-pr\\n',\n );\n }\n return (result.stdout ?? '').trim();\n}\n"]}
@@ -33,7 +33,8 @@ async function main() {
33
33
  // uncommitted change build green yet push a stale commit. Fail early with instructions if dirty.
34
34
  (0, git_exec_1.assertCleanTree)(repoRoot);
35
35
  await updateBranchFromMain(repoRoot);
36
- // Local branch may be a numbered generation (base2/…); the remote/PR branch is the stable base.
36
+ // Local branch, remote branch, and PR share the one stable feature name (baseBranchName also
37
+ // tolerates a leftover `…wpN` from the old scheme).
37
38
  (0, git_exec_1.ensurePushed)((0, branch_naming_1.baseBranchName)((0, child_process_1.execSync)('git branch --show-current', { encoding: 'utf8' }).trim()));
38
39
  // Advisory build gate — early feedback before the AI writes review.json. wp-finish-upsert-pr runs
39
40
  // the authoritative one. Both go through the same shared runBuildGate (only the framing differs).
@@ -1 +1 @@
1
- {"version":3,"file":"wp-start-upsert-pr.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/wp-start-upsert-pr.ts"],"names":[],"mappings":";;;AA6BA,oBA6BC;AAzDD,iDAAyC;AACzC,0DAAqH;AACrH,0EAAiE;AACjE,4DAA0D;AAC1D,8DAA2E;AAC3E,kDAAoE;AACpE,sDAA0D;AAE1D,mGAAmG;AACnG,gGAAgG;AAChG,mGAAmG;AACnG,4FAA4F;AAE5F,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAEvE,gGAAgG;AAChG,mGAAmG;AACnG,0EAA0E;AAC1E,KAAK,UAAU,oBAAoB,CAAC,QAAgB;IAChD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,+BAA+B,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;IAChF,MAAM,OAAO,GAAG,MAAM,IAAA,8BAAiB,EAAC,QAAQ,EAAE,oBAAoB,EAAE,qBAAqB,CAAC,CAAC;IAC/F,IAAI,OAAO,KAAK,UAAU,IAAI,OAAO,KAAK,mBAAmB,EAAE,CAAC;QAC5D,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,iHAAiH,CACpH,CAAC;IACN,CAAC;AACL,CAAC;AAEM,KAAK,UAAU,IAAI;IACtB,MAAM,QAAQ,GAAG,IAAA,wBAAQ,EAAC,+BAA+B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACxF,gGAAgG;IAChG,IAAA,4BAAa,EAAC,QAAQ,EAAE,2BAA2B,CAAC,CAAC;IAErD,iGAAiG;IACjG,mGAAmG;IACnG,iGAAiG;IACjG,IAAA,0BAAe,EAAC,QAAQ,CAAC,CAAC;IAE1B,MAAM,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACrC,gGAAgG;IAChG,IAAA,uBAAY,EAAC,IAAA,8BAAc,EAAC,IAAA,wBAAQ,EAAC,2BAA2B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAEjG,kGAAkG;IAClG,kGAAkG;IAClG,IAAA,6BAAY,EAAC,QAAQ,EAAE,IAAI,iCAAgB,CACvC,4BAA4B,EAAE,yBAAyB,EAAE,yCAAyC,CACrG,CAAC,CAAC;IAEH,sFAAsF;IACtF,MAAM,UAAU,GAAG,IAAA,6BAAc,EAAC,QAAQ,EAAE,IAAA,qCAAc,GAAE,CAAC,CAAC;IAC9D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,gCAAgC,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;IACjF,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,yFAAyF;QACzF,GAAG,IAAA,mCAAoB,EAAC,UAAU,CAAC,MAAM;QACzC,uCAAuC;QACvC,+GAA+G,CAClH,CAAC;AACN,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM;IAAE,IAAA,sBAAO,EAAC,IAAI,CAAC,CAAC","sourcesContent":["#!/usr/bin/env node\nimport { execSync } from 'child_process';\nimport { reviewJsonPath, reviewJsonSchemaHint, writeTemplate, CliExitError, runMain } from '@webpieces/rules-config';\nimport { getFeatureName } from './workflow/git-readAiBranchName';\nimport { baseBranchName } from './workflow/branch-naming';\nimport { runBuildGate, BuildGateOptions } from './workflow/build-affected';\nimport { assertCleanTree, ensurePushed } from './workflow/git-exec';\nimport { runUpdateFromMain } from './workflow/run-update';\n\n// START of the AI-first PR flow (webpieces is AI-driven, so we invert trytami's human-first flow):\n// this command does the deterministic setup — update from main, push, run the build gate — then\n// hands the AI instructions to WRITE review.json and run `wp-finish-upsert-pr` (which reads it and\n// posts the PR). This command NEVER creates/updates a PR; all `gh` posting lives in finish.\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// Step A — bring the branch up to date with main via the shared 3-point engine (in-process). On\n// conflict the merge process doc it writes names `wp-finish-upsert-pr` as the finish command (this\n// PR flow's finish, which validates + finalizes + builds + posts the PR).\nasync function updateBranchFromMain(repoRoot: string): Promise<void> {\n process.stdout.write('\\n' + SEP + '① Updating branch from main\\n' + SEP + '\\n');\n const outcome = await 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\nexport async function main(): 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 an\n // uncommitted change build green yet push a stale commit. Fail early with instructions if dirty.\n assertCleanTree(repoRoot);\n\n await updateBranchFromMain(repoRoot);\n // Local branch may be a numbered generation (base2/…); the remote/PR branch is the stable base.\n ensurePushed(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 runs\n // the authoritative one. Both go through the same shared runBuildGate (only the framing differs).\n 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, 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\nif (require.main === module) runMain(main);\n"]}
1
+ {"version":3,"file":"wp-start-upsert-pr.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/wp-start-upsert-pr.ts"],"names":[],"mappings":";;;AA6BA,oBA8BC;AA1DD,iDAAyC;AACzC,0DAAqH;AACrH,0EAAiE;AACjE,4DAA0D;AAC1D,8DAA2E;AAC3E,kDAAoE;AACpE,sDAA0D;AAE1D,mGAAmG;AACnG,gGAAgG;AAChG,mGAAmG;AACnG,4FAA4F;AAE5F,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAEvE,gGAAgG;AAChG,mGAAmG;AACnG,0EAA0E;AAC1E,KAAK,UAAU,oBAAoB,CAAC,QAAgB;IAChD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,+BAA+B,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;IAChF,MAAM,OAAO,GAAG,MAAM,IAAA,8BAAiB,EAAC,QAAQ,EAAE,oBAAoB,EAAE,qBAAqB,CAAC,CAAC;IAC/F,IAAI,OAAO,KAAK,UAAU,IAAI,OAAO,KAAK,mBAAmB,EAAE,CAAC;QAC5D,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,iHAAiH,CACpH,CAAC;IACN,CAAC;AACL,CAAC;AAEM,KAAK,UAAU,IAAI;IACtB,MAAM,QAAQ,GAAG,IAAA,wBAAQ,EAAC,+BAA+B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACxF,gGAAgG;IAChG,IAAA,4BAAa,EAAC,QAAQ,EAAE,2BAA2B,CAAC,CAAC;IAErD,iGAAiG;IACjG,mGAAmG;IACnG,iGAAiG;IACjG,IAAA,0BAAe,EAAC,QAAQ,CAAC,CAAC;IAE1B,MAAM,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACrC,6FAA6F;IAC7F,oDAAoD;IACpD,IAAA,uBAAY,EAAC,IAAA,8BAAc,EAAC,IAAA,wBAAQ,EAAC,2BAA2B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAEjG,kGAAkG;IAClG,kGAAkG;IAClG,IAAA,6BAAY,EAAC,QAAQ,EAAE,IAAI,iCAAgB,CACvC,4BAA4B,EAAE,yBAAyB,EAAE,yCAAyC,CACrG,CAAC,CAAC;IAEH,sFAAsF;IACtF,MAAM,UAAU,GAAG,IAAA,6BAAc,EAAC,QAAQ,EAAE,IAAA,qCAAc,GAAE,CAAC,CAAC;IAC9D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,gCAAgC,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;IACjF,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,yFAAyF;QACzF,GAAG,IAAA,mCAAoB,EAAC,UAAU,CAAC,MAAM;QACzC,uCAAuC;QACvC,+GAA+G,CAClH,CAAC;AACN,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM;IAAE,IAAA,sBAAO,EAAC,IAAI,CAAC,CAAC","sourcesContent":["#!/usr/bin/env node\nimport { execSync } from 'child_process';\nimport { reviewJsonPath, reviewJsonSchemaHint, writeTemplate, CliExitError, runMain } from '@webpieces/rules-config';\nimport { getFeatureName } from './workflow/git-readAiBranchName';\nimport { baseBranchName } from './workflow/branch-naming';\nimport { runBuildGate, BuildGateOptions } from './workflow/build-affected';\nimport { assertCleanTree, ensurePushed } from './workflow/git-exec';\nimport { runUpdateFromMain } from './workflow/run-update';\n\n// START of the AI-first PR flow (webpieces is AI-driven, so we invert trytami's human-first flow):\n// this command does the deterministic setup — update from main, push, run the build gate — then\n// hands the AI instructions to WRITE review.json and run `wp-finish-upsert-pr` (which reads it and\n// posts the PR). This command NEVER creates/updates a PR; all `gh` posting lives in finish.\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// Step A — bring the branch up to date with main via the shared 3-point engine (in-process). On\n// conflict the merge process doc it writes names `wp-finish-upsert-pr` as the finish command (this\n// PR flow's finish, which validates + finalizes + builds + posts the PR).\nasync function updateBranchFromMain(repoRoot: string): Promise<void> {\n process.stdout.write('\\n' + SEP + '① Updating branch from main\\n' + SEP + '\\n');\n const outcome = await 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\nexport async function main(): 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 an\n // uncommitted change build green yet push a stale commit. Fail early with instructions if dirty.\n assertCleanTree(repoRoot);\n\n await updateBranchFromMain(repoRoot);\n // Local branch, remote branch, and PR share the one stable feature name (baseBranchName also\n // tolerates a leftover `…wpN` from the old scheme).\n ensurePushed(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 runs\n // the authoritative one. Both go through the same shared runBuildGate (only the framing differs).\n 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, 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\nif (require.main === module) runMain(main);\n"]}
@@ -13,21 +13,21 @@ const run_update_1 = require("./workflow/run-update");
13
13
  // on CONFLICT it writes the 3-point context + merge process doc and hands back — you resolve, then
14
14
  // run `wp-update-end` to finalize. It NEVER creates a PR (that is the wp-*-upsert-pr flow).
15
15
  const SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
16
- // If a PR is already OPEN for this branch, wp-update-start must refuse. The 3-point update squash-
17
- // merges onto a NEW branch generation (base basewp2), and the existing PR stays pinned to the old
18
- // branch so running the update-only flow would leave the PR pointing at stale, pre-merge code. The
19
- // PR flow (wp-start-upsert-pr → wp-finish-upsert-pr) is the ONLY correct path once a PR exists: it
20
- // re-merges main AND updates the PR. Fail fast and steer there. (openPrForBranch itself fails fast if
21
- // GitHub can't be reached — we never assume "no PR" when we simply couldn't ask.)
16
+ // If a PR is already OPEN for this branch, wp-update-start must refuse. The 3-point update re-squashes
17
+ // your branch onto main and force-pushes it to origin/<feature> the PR head but it does NOT refresh
18
+ // the PR body/review or run the authoritative build gate. So it would push new code onto an open PR
19
+ // without an updated review. The PR flow (wp-start-upsert-pr → wp-finish-upsert-pr) is the ONLY correct
20
+ // path once a PR exists: it re-merges main AND updates the PR. Fail fast and steer there. (openPrForBranch
21
+ // itself fails fast if GitHub can't be reached — we never assume "no PR" when we simply couldn't ask.)
22
22
  function assertNoOpenPr(repoRoot) {
23
23
  const branch = (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', { cwd: repoRoot, encoding: 'utf8' }).trim();
24
24
  const openPr = (0, open_pr_check_1.openPrForBranch)(repoRoot, branch);
25
25
  if (openPr === '')
26
26
  return;
27
27
  throw new rules_config_1.CliExitError(2, '\n' + SEP + `⛔ An open PR (#${openPr}) already tracks this branch\n` + SEP + '\n' +
28
- 'wp-update-start does a 3-point squash-merge onto a NEW branch generation (base basewp2),\n' +
29
- `but PR #${openPr} is pinned to the current branch so it would be left pointing at stale,\n` +
30
- 'pre-merge code. Use the PR flow instead — it re-merges main AND updates the PR:\n' +
28
+ 'wp-update-start re-squashes your branch onto main and force-pushes it, but it does NOT refresh\n' +
29
+ `the PR body/review — so PR #${openPr} would get new code without an updated review. Use the PR\n` +
30
+ 'flow instead — it re-merges main AND updates the PR:\n' +
31
31
  ' 1. pnpm wp-start-upsert-pr\n' +
32
32
  ' 2. /wp-merge (only if conflicts)\n' +
33
33
  ' 3. pnpm wp-finish-upsert-pr\n');
@@ -1 +1 @@
1
- {"version":3,"file":"wp-update-start.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/wp-update-start.ts"],"names":[],"mappings":";;;AAoBA,wCAaC;AAED,oBAsBC;AAxDD,iDAAyC;AACzC,0DAA+E;AAC/E,4DAA2D;AAC3D,sDAA0D;AAE1D,oGAAoG;AACpG,2FAA2F;AAC3F,sGAAsG;AACtG,mGAAmG;AACnG,4FAA4F;AAE5F,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAEvE,mGAAmG;AACnG,oGAAoG;AACpG,qGAAqG;AACrG,mGAAmG;AACnG,sGAAsG;AACtG,kFAAkF;AAClF,SAAgB,cAAc,CAAC,QAAgB;IAC3C,MAAM,MAAM,GAAG,IAAA,wBAAQ,EAAC,iCAAiC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACvG,MAAM,MAAM,GAAG,IAAA,+BAAe,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACjD,IAAI,MAAM,KAAK,EAAE;QAAE,OAAO;IAC1B,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,IAAI,GAAG,GAAG,GAAG,kBAAkB,MAAM,gCAAgC,GAAG,GAAG,GAAG,IAAI;QAClF,8FAA8F;QAC9F,WAAW,MAAM,6EAA6E;QAC9F,mFAAmF;QACnF,gCAAgC;QAChC,wCAAwC;QACxC,iCAAiC,CACpC,CAAC;AACN,CAAC;AAEM,KAAK,UAAU,IAAI;IACtB,MAAM,QAAQ,GAAG,IAAA,wBAAQ,EAAC,+BAA+B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACxF,gGAAgG;IAChG,IAAA,4BAAa,EAAC,QAAQ,EAAE,2BAA2B,CAAC,CAAC;IAErD,8FAA8F;IAC9F,oFAAoF;IACpF,cAAc,CAAC,QAAQ,CAAC,CAAC;IAEzB,MAAM,OAAO,GAAG,MAAM,IAAA,8BAAiB,EAAC,QAAQ,EAAE,iBAAiB,EAAE,eAAe,CAAC,CAAC;IACtF,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;QACzB,4FAA4F;QAC5F,MAAM,IAAI,2BAAY,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAClC,CAAC;IACD,IAAI,OAAO,KAAK,mBAAmB,EAAE,CAAC;QAClC,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,IAAI,GAAG,GAAG,GAAG,6CAA6C,GAAG,GAAG,GAAG,IAAI;YACvE,kEAAkE;YAClE,wBAAwB,CAC3B,CAAC;IACN,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iEAAiE,CAAC,CAAC;AAC5F,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM;IAAE,IAAA,sBAAO,EAAC,IAAI,CAAC,CAAC","sourcesContent":["#!/usr/bin/env node\nimport { execSync } from 'child_process';\nimport { writeTemplate, CliExitError, runMain } from '@webpieces/rules-config';\nimport { openPrForBranch } from './workflow/open-pr-check';\nimport { runUpdateFromMain } from './workflow/run-update';\n\n// wp-update-start: the \"sync this feature branch from main\" entry point (the redirect target of the\n// redirect-how-to-merge-main guard). It runs the FULL 3-point squash-update via the shared\n// runUpdateFromMain engine: on a CLEAN merge it finalizes everything (no need to call wp-update-end);\n// on CONFLICT it writes the 3-point context + merge process doc and hands back — you resolve, then\n// run `wp-update-end` to finalize. It NEVER creates a PR (that is the wp-*-upsert-pr flow).\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// If a PR is already OPEN for this branch, wp-update-start must refuse. The 3-point update squash-\n// merges onto a NEW branch generation (base basewp2), and the existing PR stays pinned to the old\n// branch so running the update-only flow would leave the PR pointing at stale, pre-merge code. The\n// PR flow (wp-start-upsert-pr → wp-finish-upsert-pr) is the ONLY correct path once a PR exists: it\n// re-merges main AND updates the PR. Fail fast and steer there. (openPrForBranch itself fails fast if\n// GitHub can't be reached — we never assume \"no PR\" when we simply couldn't ask.)\nexport function assertNoOpenPr(repoRoot: string): void {\n const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: repoRoot, encoding: 'utf8' }).trim();\n const openPr = 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-update-start does a 3-point squash-merge onto a NEW branch generation (base basewp2),\\n' +\n `but PR #${openPr} is pinned to the current branch so it would be left pointing at stale,\\n` +\n 'pre-merge code. Use the PR 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\nexport async function main(): 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 assertNoOpenPr(repoRoot);\n\n const outcome = await runUpdateFromMain(repoRoot, 'wp-update-start', 'wp-update-end');\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-update-end\\n',\n );\n }\n process.stdout.write('\\n✅ Updated from main — clean. No need to call wp-update-end.\\n');\n}\n\nif (require.main === module) runMain(main);\n"]}
1
+ {"version":3,"file":"wp-update-start.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/wp-update-start.ts"],"names":[],"mappings":";;;AAoBA,wCAaC;AAED,oBAsBC;AAxDD,iDAAyC;AACzC,0DAA+E;AAC/E,4DAA2D;AAC3D,sDAA0D;AAE1D,oGAAoG;AACpG,2FAA2F;AAC3F,sGAAsG;AACtG,mGAAmG;AACnG,4FAA4F;AAE5F,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAEvE,uGAAuG;AACvG,wGAAwG;AACxG,oGAAoG;AACpG,wGAAwG;AACxG,2GAA2G;AAC3G,uGAAuG;AACvG,SAAgB,cAAc,CAAC,QAAgB;IAC3C,MAAM,MAAM,GAAG,IAAA,wBAAQ,EAAC,iCAAiC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACvG,MAAM,MAAM,GAAG,IAAA,+BAAe,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACjD,IAAI,MAAM,KAAK,EAAE;QAAE,OAAO;IAC1B,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,IAAI,GAAG,GAAG,GAAG,kBAAkB,MAAM,gCAAgC,GAAG,GAAG,GAAG,IAAI;QAClF,kGAAkG;QAClG,+BAA+B,MAAM,6DAA6D;QAClG,wDAAwD;QACxD,gCAAgC;QAChC,wCAAwC;QACxC,iCAAiC,CACpC,CAAC;AACN,CAAC;AAEM,KAAK,UAAU,IAAI;IACtB,MAAM,QAAQ,GAAG,IAAA,wBAAQ,EAAC,+BAA+B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACxF,gGAAgG;IAChG,IAAA,4BAAa,EAAC,QAAQ,EAAE,2BAA2B,CAAC,CAAC;IAErD,8FAA8F;IAC9F,oFAAoF;IACpF,cAAc,CAAC,QAAQ,CAAC,CAAC;IAEzB,MAAM,OAAO,GAAG,MAAM,IAAA,8BAAiB,EAAC,QAAQ,EAAE,iBAAiB,EAAE,eAAe,CAAC,CAAC;IACtF,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;QACzB,4FAA4F;QAC5F,MAAM,IAAI,2BAAY,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAClC,CAAC;IACD,IAAI,OAAO,KAAK,mBAAmB,EAAE,CAAC;QAClC,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,IAAI,GAAG,GAAG,GAAG,6CAA6C,GAAG,GAAG,GAAG,IAAI;YACvE,kEAAkE;YAClE,wBAAwB,CAC3B,CAAC;IACN,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iEAAiE,CAAC,CAAC;AAC5F,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM;IAAE,IAAA,sBAAO,EAAC,IAAI,CAAC,CAAC","sourcesContent":["#!/usr/bin/env node\nimport { execSync } from 'child_process';\nimport { writeTemplate, CliExitError, runMain } from '@webpieces/rules-config';\nimport { openPrForBranch } from './workflow/open-pr-check';\nimport { runUpdateFromMain } from './workflow/run-update';\n\n// wp-update-start: the \"sync this feature branch from main\" entry point (the redirect target of the\n// redirect-how-to-merge-main guard). It runs the FULL 3-point squash-update via the shared\n// runUpdateFromMain engine: on a CLEAN merge it finalizes everything (no need to call wp-update-end);\n// on CONFLICT it writes the 3-point context + merge process doc and hands back — you resolve, then\n// run `wp-update-end` to finalize. It NEVER creates a PR (that is the wp-*-upsert-pr flow).\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n// If a PR is already OPEN for this branch, wp-update-start must refuse. The 3-point update re-squashes\n// your branch onto main and force-pushes it to origin/<feature> the PR head but it does NOT refresh\n// the PR body/review or run the authoritative build gate. So it would push new code onto an open PR\n// without an updated review. The PR flow (wp-start-upsert-pr → wp-finish-upsert-pr) is the ONLY correct\n// path once a PR exists: it re-merges main AND updates the PR. Fail fast and steer there. (openPrForBranch\n// itself fails fast if GitHub can't be reached — we never assume \"no PR\" when we simply couldn't ask.)\nexport function assertNoOpenPr(repoRoot: string): void {\n const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: repoRoot, encoding: 'utf8' }).trim();\n const openPr = 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-update-start 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\nexport async function main(): 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 assertNoOpenPr(repoRoot);\n\n const outcome = await runUpdateFromMain(repoRoot, 'wp-update-start', 'wp-update-end');\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-update-end\\n',\n );\n }\n process.stdout.write('\\n✅ Updated from main — clean. No need to call wp-update-end.\\n');\n}\n\nif (require.main === module) runMain(main);\n"]}