@webpieces/pr-gate 0.3.292 → 0.3.293
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/scripts/git-finishUpsertPr.js +7 -5
- package/src/scripts/git-finishUpsertPr.js.map +1 -1
- package/src/scripts/workflow/branch-naming.d.ts +9 -6
- package/src/scripts/workflow/branch-naming.js +17 -14
- package/src/scripts/workflow/branch-naming.js.map +1 -1
- package/src/scripts/workflow/merge-end.js +28 -10
- package/src/scripts/workflow/merge-end.js.map +1 -1
- package/src/scripts/workflow/merge-start.d.ts +3 -2
- package/src/scripts/workflow/merge-start.js +40 -13
- package/src/scripts/workflow/merge-start.js.map +1 -1
- package/src/scripts/workflow/merge-state.d.ts +2 -0
- package/src/scripts/workflow/merge-state.js +32 -0
- package/src/scripts/workflow/merge-state.js.map +1 -1
- package/src/scripts/workflow/run-update.js +12 -13
- package/src/scripts/workflow/run-update.js.map +1 -1
- package/src/scripts/wp-update-end.js +6 -4
- package/src/scripts/wp-update-end.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/pr-gate",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.293",
|
|
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.
|
|
26
|
+
"@webpieces/rules-config": "0.3.293"
|
|
27
27
|
},
|
|
28
28
|
"publishConfig": {
|
|
29
29
|
"access": "public"
|
|
@@ -77,12 +77,14 @@ async function main() {
|
|
|
77
77
|
const repoRoot = (0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
|
|
78
78
|
// Refresh the AI-facing workflow doc so it's present + current for any failure message to cite.
|
|
79
79
|
(0, rules_config_1.writeTemplate)(repoRoot, 'webpieces.git-workflow.md');
|
|
80
|
-
const
|
|
80
|
+
const home = (0, merge_state_1.mergeDirFor)(repoRoot, (0, git_readAiBranchName_1.getFeatureName)());
|
|
81
81
|
// 1. Finish any in-progress conflict resolution: validate + commit + finalize the branch swap.
|
|
82
|
-
// No
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
// No active run dir (or its marker already validated) => no merge in progress => nothing to do
|
|
83
|
+
// (the common case). The active merge is the `merge-<n>/` run dir holding a marker.
|
|
84
|
+
const activeDir = (0, merge_state_1.findActiveMergeRunDir)(home);
|
|
85
|
+
const marker = activeDir ? (0, merge_state_1.readMergeMarker)(activeDir) : null;
|
|
86
|
+
if (activeDir && marker && !marker.validated) {
|
|
87
|
+
await (0, merge_end_1.mergeEnd)(repoRoot, 'wp-finish-upsert-pr', activeDir, new merge_start_1.MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber), marker.conflictedFiles);
|
|
86
88
|
}
|
|
87
89
|
// 2. REQUIRE the AI-authored review.json (throws InformAiError with the schema if missing/invalid).
|
|
88
90
|
const review = (0, rules_config_1.loadReviewJson)((0, rules_config_1.reviewJsonPath)(repoRoot, (0, git_readAiBranchName_1.getFeatureName)()));
|
|
@@ -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\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
|
+
{"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, findActiveMergeRunDir } 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 home = mergeDirFor(repoRoot, getFeatureName());\n\n // 1. Finish any in-progress conflict resolution: validate + commit + finalize the branch swap.\n // No active run dir (or its marker already validated) => no merge in progress => nothing to do\n // (the common case). The active merge is the `merge-<n>/` run dir holding a marker.\n const activeDir = findActiveMergeRunDir(home);\n const marker = activeDir ? readMergeMarker(activeDir) : null;\n if (activeDir && marker && !marker.validated) {\n await mergeEnd(\n repoRoot, 'wp-finish-upsert-pr', activeDir,\n new MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber),\n marker.conflictedFiles,\n );\n }\n\n // 2. REQUIRE the AI-authored review.json (throws InformAiError with the schema if missing/invalid).\n const review = loadReviewJson(reviewJsonPath(repoRoot, 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,9 +1,12 @@
|
|
|
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
|
-
/** Pre-merge snapshot name for slot `n
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
/** Pre-merge snapshot name for slot `n`, ALWAYS numbered from 1: `<branch>PreMerge1`,
|
|
5
|
+
* `<branch>PreMerge2`, … The same `n` also names the paired conflict-context folder
|
|
6
|
+
* (`merge-info/<slug>/merge-<n>/`, see merge-state.mergeRunDirFor), so the branch and its context
|
|
7
|
+
* share one number. Clean syncs delete their snapshot at finalize; only conflict syncs leave one. */
|
|
8
|
+
export declare function preMergeBackupName(branch: string, n: number): string;
|
|
9
|
+
/** First free PreMerge slot NUMBER for `branch`, probing 1, 2, 3, … via the `exists` predicate. Pure
|
|
10
|
+
* (the branch-existence check is injected) so it unit-tests without touching git. This integer is the
|
|
11
|
+
* single source of truth for both the backup branch name and its paired `merge-<n>` context folder. */
|
|
12
|
+
export declare function nextFreePreMergeNumber(branch: string, exists: (name: string) => boolean): number;
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
// generation number survives a sync — the old scheme numbered the LOCAL branch up (base → basewp2 →
|
|
9
9
|
// basewp3) while the remote/PR stayed on `base`, which read as "the PR moved" when it never did.
|
|
10
10
|
//
|
|
11
|
-
// The pre-merge safety snapshot is a NUMBERED trail — `<branch>
|
|
12
|
-
// `<branch>PreMerge3`, …
|
|
13
|
-
//
|
|
11
|
+
// The pre-merge safety snapshot is a NUMBERED trail from 1 — `<branch>PreMerge1`, `<branch>PreMerge2`,
|
|
12
|
+
// `<branch>PreMerge3`, … Each snapshot is paired 1:1 with a conflict-context folder `merge-<n>/` under
|
|
13
|
+
// the same number (see merge-state). A CLEAN sync deletes its snapshot at finalize (no undo point
|
|
14
|
+
// needed); only CONFLICT syncs leave a snapshot + its `merge-<n>/` behind. See nextFreePreMergeNumber.
|
|
14
15
|
//
|
|
15
16
|
// `baseBranchName` still strips a trailing `Squash` (the transient temp-branch suffix) AND a trailing
|
|
16
17
|
// `wp<digits>` — the latter only for BACKWARD COMPATIBILITY, so a consumer sitting on a leftover
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
23
|
exports.baseBranchName = baseBranchName;
|
|
23
24
|
exports.preMergeBackupName = preMergeBackupName;
|
|
24
|
-
exports.
|
|
25
|
+
exports.nextFreePreMergeNumber = nextFreePreMergeNumber;
|
|
25
26
|
const GENERATION_RE = /^(.*)wp(\d+)$/;
|
|
26
27
|
class Generation {
|
|
27
28
|
base;
|
|
@@ -44,18 +45,20 @@ function parseGeneration(branch) {
|
|
|
44
45
|
function baseBranchName(branch) {
|
|
45
46
|
return parseGeneration(branch).base;
|
|
46
47
|
}
|
|
47
|
-
/** Pre-merge snapshot name for slot `n
|
|
48
|
-
*
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
/** Pre-merge snapshot name for slot `n`, ALWAYS numbered from 1: `<branch>PreMerge1`,
|
|
49
|
+
* `<branch>PreMerge2`, … The same `n` also names the paired conflict-context folder
|
|
50
|
+
* (`merge-info/<slug>/merge-<n>/`, see merge-state.mergeRunDirFor), so the branch and its context
|
|
51
|
+
* share one number. Clean syncs delete their snapshot at finalize; only conflict syncs leave one. */
|
|
52
|
+
function preMergeBackupName(branch, n) {
|
|
53
|
+
return `${branch}PreMerge${n}`;
|
|
51
54
|
}
|
|
52
|
-
/** First free PreMerge slot for `branch`, probing 1, 2, 3, … via the `exists` predicate. Pure
|
|
53
|
-
* branch-existence check is injected) so it unit-tests without touching git.
|
|
54
|
-
|
|
55
|
+
/** First free PreMerge slot NUMBER for `branch`, probing 1, 2, 3, … via the `exists` predicate. Pure
|
|
56
|
+
* (the branch-existence check is injected) so it unit-tests without touching git. This integer is the
|
|
57
|
+
* single source of truth for both the backup branch name and its paired `merge-<n>` context folder. */
|
|
58
|
+
function nextFreePreMergeNumber(branch, exists) {
|
|
55
59
|
for (let n = 1;; n++) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return name;
|
|
60
|
+
if (!exists(preMergeBackupName(branch, n)))
|
|
61
|
+
return n;
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
64
|
//# 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,6CAA6C;AAC7C,EAAE;AACF,sGAAsG;AACtG,oGAAoG;AACpG,gGAAgG;AAChG,kGAAkG;AAClG,oGAAoG;AACpG,iGAAiG;AACjG,EAAE;AACF
|
|
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,uGAAuG;AACvG,uGAAuG;AACvG,kGAAkG;AAClG,uGAAuG;AACvG,EAAE;AACF,sGAAsG;AACtG,iGAAiG;AACjG,iGAAiG;AACjG,oGAAoG;AACpG,sGAAsG;AACtG,6FAA6F;;AAyB7F,wCAEC;AAMD,gDAEC;AAKD,wDAIC;AA1CD,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;;;sGAGsG;AACtG,SAAgB,kBAAkB,CAAC,MAAc,EAAE,CAAS;IACxD,OAAO,GAAG,MAAM,WAAW,CAAC,EAAE,CAAC;AACnC,CAAC;AAED;;wGAEwG;AACxG,SAAgB,sBAAsB,CAAC,MAAc,EAAE,MAAiC;IACpF,KAAK,IAAI,CAAC,GAAG,CAAC,GAAI,CAAC,EAAE,EAAE,CAAC;QACpB,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC;IACzD,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 from 1 — `<branch>PreMerge1`, `<branch>PreMerge2`,\n// `<branch>PreMerge3`, … Each snapshot is paired 1:1 with a conflict-context folder `merge-<n>/` under\n// the same number (see merge-state). A CLEAN sync deletes its snapshot at finalize (no undo point\n// needed); only CONFLICT syncs leave a snapshot + its `merge-<n>/` behind. See nextFreePreMergeNumber.\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`, ALWAYS numbered from 1: `<branch>PreMerge1`,\n * `<branch>PreMerge2`, … The same `n` also names the paired conflict-context folder\n * (`merge-info/<slug>/merge-<n>/`, see merge-state.mergeRunDirFor), so the branch and its context\n * share one number. Clean syncs delete their snapshot at finalize; only conflict syncs leave one. */\nexport function preMergeBackupName(branch: string, n: number): string {\n return `${branch}PreMerge${n}`;\n}\n\n/** First free PreMerge slot NUMBER for `branch`, probing 1, 2, 3, … via the `exists` predicate. Pure\n * (the branch-existence check is injected) so it unit-tests without touching git. This integer is the\n * single source of truth for both the backup branch name and its paired `merge-<n>` context folder. */\nexport function nextFreePreMergeNumber(branch: string, exists: (name: string) => boolean): number {\n for (let n = 1; ; n++) {\n if (!exists(preMergeBackupName(branch, n))) return n;\n }\n}\n"]}
|
|
@@ -56,9 +56,10 @@ function localBranchExists(name) {
|
|
|
56
56
|
}
|
|
57
57
|
// Force-push the squash branch to the stable feature branch (where the single PR lives), then RENAME
|
|
58
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).
|
|
60
|
-
//
|
|
61
|
-
|
|
59
|
+
// and the PR head are all one name (no `wpN` divergence). On a CLEAN sync (`!hadConflict`) the
|
|
60
|
+
// pre-merge snapshot is disposable, so it's deleted at the very end; a CONFLICT sync keeps it (paired
|
|
61
|
+
// with its `merge-<n>/` context). Ends with an explicit "here is exactly what I did" recap.
|
|
62
|
+
function finalizeBranch(repoRoot, verb, ctx, hadConflict) {
|
|
62
63
|
process.stdout.write('\n' + SEP + '🗑️ Finalizing\n' + SEP + '\n');
|
|
63
64
|
const base = (0, branch_naming_1.baseBranchName)(ctx.currentBranch);
|
|
64
65
|
(0, git_exec_1.runGitChecked)(['branch', '-D', ctx.currentBranch], 'Failed to delete old feature branch');
|
|
@@ -84,28 +85,41 @@ function finalizeBranch(repoRoot, verb, ctx) {
|
|
|
84
85
|
// Branch now contains origin/main — stamp a clean main-sync status so the feature-branch-guard
|
|
85
86
|
// unblocks edits immediately (no wait for the async refresher).
|
|
86
87
|
(0, rules_config_1.stampCleanMainSyncStatus)((0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim());
|
|
88
|
+
// Clean sync → the pre-merge snapshot was never needed; delete it (LAST, so any earlier finalize
|
|
89
|
+
// failure above keeps it). Conflict sync → keep it as the undo point + trail.
|
|
90
|
+
const backupKept = hadConflict;
|
|
91
|
+
if (!backupKept && localBranchExists(ctx.backupBranch)) {
|
|
92
|
+
(0, git_exec_1.runGitChecked)(['branch', '-D', ctx.backupBranch], 'Failed to delete clean-merge backup');
|
|
93
|
+
}
|
|
87
94
|
const finalizeEvent = new rules_config_1.BranchMutationEvent(verb, 'FINALIZE');
|
|
88
95
|
finalizeEvent.fromBranch = ctx.currentBranch;
|
|
89
96
|
finalizeEvent.toBranch = base;
|
|
90
97
|
finalizeEvent.outcome = 'finalized';
|
|
91
|
-
finalizeEvent.artifacts = [`backup=${ctx.backupBranch}`, `remotePR=${base}`];
|
|
98
|
+
finalizeEvent.artifacts = [backupKept ? `backup=${ctx.backupBranch}` : `backupDeleted=${ctx.backupBranch}`, `remotePR=${base}`];
|
|
92
99
|
(0, rules_config_1.logBranchMutation)(repoRoot, finalizeEvent);
|
|
93
|
-
printSyncRecap(base, ctx.backupBranch, ctx.prNumber, remoteExists);
|
|
100
|
+
printSyncRecap(base, ctx.backupBranch, ctx.prNumber, remoteExists, backupKept);
|
|
94
101
|
}
|
|
95
102
|
// The explicit, numbered "here is exactly what I did" recap the AI (and human) reads after a sync.
|
|
96
103
|
// The whole point of the branch-name change is that step 4 can now say "same name as remote/PR" —
|
|
97
104
|
// there is no confusing local-vs-remote divergence left to explain.
|
|
98
|
-
function printSyncRecap(feature, backupBranch, prNumber, pushed) {
|
|
105
|
+
function printSyncRecap(feature, backupBranch, prNumber, pushed, backupKept) {
|
|
99
106
|
const remoteLine = pushed
|
|
100
107
|
? `landed back on ${feature} (== origin/${feature}${prNumber ? ` == PR #${prNumber}` : ''} — names match)`
|
|
101
108
|
: `landed back on ${feature} (local only — no remote branch yet)`;
|
|
109
|
+
const step1 = backupKept
|
|
110
|
+
? `snapshotted your pre-merge state → ${backupBranch} (kept — this merge had conflicts)`
|
|
111
|
+
: `snapshotted your pre-merge state → ${backupBranch} (auto-removed — clean merge, no undo needed)`;
|
|
112
|
+
const trailer = backupKept
|
|
113
|
+
? ` Pre-merge snapshot trail: git branch --list '${feature}PreMerge*'\n` +
|
|
114
|
+
` Its conflict context lives in the paired merge-<n>/ folder under .webpieces/merge-info/\n` +
|
|
115
|
+
` Prune this run's snapshot when safe: git branch -D ${backupBranch}\n\n`
|
|
116
|
+
: '\n';
|
|
102
117
|
process.stdout.write('\n' + SEP + '✅ Sync complete — here is exactly what I did\n' + SEP + '\n' +
|
|
103
|
-
` 1.
|
|
118
|
+
` 1. ${step1}\n` +
|
|
104
119
|
` 2. pulled origin/main\n` +
|
|
105
120
|
` 3. squash-merged your work onto main\n` +
|
|
106
121
|
` 4. ${remoteLine}\n\n` +
|
|
107
|
-
|
|
108
|
-
` Prune this run's snapshot when safe: git branch -D ${backupBranch}\n\n`);
|
|
122
|
+
trailer);
|
|
109
123
|
}
|
|
110
124
|
/**
|
|
111
125
|
* Complete a 3-point squash merge. `conflictedFiles` non-null means a conflict was resolved by the AI
|
|
@@ -128,7 +142,11 @@ async function mergeEnd(repoRoot, verb, mergeDir, ctx, conflictedFiles) {
|
|
|
128
142
|
fs.writeFileSync(path.join(mergeDir, 'conflicts-resolved'), '');
|
|
129
143
|
process.stdout.write('\n✅ Merge validated and committed.\n');
|
|
130
144
|
}
|
|
131
|
-
|
|
145
|
+
// A marker in THIS run dir means the sync hit conflicts (marker is written only on hand-back) —
|
|
146
|
+
// correct even for the validated-resume path (conflictedFiles=null yet a conflict). Read it BEFORE
|
|
147
|
+
// clearMergeMarker so finalize knows whether to keep the pre-merge snapshot.
|
|
148
|
+
const hadConflict = (0, merge_state_1.readMergeMarker)(mergeDir) !== null;
|
|
149
|
+
finalizeBranch(repoRoot, verb, ctx, hadConflict);
|
|
132
150
|
(0, merge_state_1.clearMergeMarker)(mergeDir);
|
|
133
151
|
await (0, cleanTmp_1.cleanTmp)();
|
|
134
152
|
}
|
|
@@ -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 } 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"]}
|
|
1
|
+
{"version":3,"file":"merge-end.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/merge-end.ts"],"names":[],"mappings":";;AAqJA,4BA8BC;;AAnLD,iDAAoD;AACpD,+CAAyB;AACzB,mDAA6B;AAC7B,0DAGiC;AACjC,mDAAiD;AACjD,yCAAsC;AACtC,yCAA8D;AAE9D,+CAAiI;AAEjI,sGAAsG;AACtG,oGAAoG;AACpG,kGAAkG;AAClG,gGAAgG;AAChG,2HAA2H;AAC3H,kGAAkG;AAClG,yFAAyF;AAEzF,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAEvE,6FAA6F;AAC7F,2FAA2F;AAC3F,+FAA+F;AAC/F,SAAS,kBAAkB,CAAC,QAAgB,EAAE,QAAgB,EAAE,eAAyB;IACrF,0FAA0F;IAC1F,MAAM,IAAI,GAAG,IAAA,iCAAmB,EAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;IAC5D,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QACd,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,0EAA0E;YAC1E,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,IAAY,EAAU,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YAC7E,yDAAyD,CAC5D,CAAC;IACN,CAAC;IAED,0DAA0D;IAC1D,MAAM,QAAQ,GAAG,IAAA,wBAAQ,EAAC,sCAAsC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/F,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC;QAClB,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,uCAAuC,GAAG,QAAQ;YAClD,uEAAuE,CAC1E,CAAC;IACN,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAEnE,6FAA6F;IAC7F,8FAA8F;IAC9F,8FAA8F;IAC9F,MAAM,YAAY,GAAG,IAAA,mCAAqB,EAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;IACtE,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QACtB,MAAM,OAAO,GAAG,YAAY,CAAC,gBAAgB;aACxC,GAAG,CAAC,CAAC,IAAY,EAAU,EAAE,CAAC,OAAO,IAAI,aAAa,IAAI,CAAC,IAAI,CAAC,IAAA,+BAAiB,EAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,qCAAsB,CAAC,EAAE,CAAC;aAC7H,IAAI,CAAC,IAAI,CAAC,CAAC;QAChB,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,sCAAsC,qCAAsB,UAAU;YACtE,OAAO;YACP,4FAA4F;YAC5F,uCAAuC,CAC1C,CAAC;IACN,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC;AACnF,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAY;IACnC,OAAO,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AACpG,CAAC;AAED,qGAAqG;AACrG,mGAAmG;AACnG,+FAA+F;AAC/F,sGAAsG;AACtG,4FAA4F;AAC5F,SAAS,cAAc,CAAC,QAAgB,EAAE,IAAkB,EAAE,GAAiB,EAAE,WAAoB;IACjG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,mBAAmB,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;IACpE,MAAM,IAAI,GAAG,IAAA,8BAAc,EAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC/C,IAAA,wBAAa,EAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,aAAa,CAAC,EAAE,qCAAqC,CAAC,CAAC;IAE1F,MAAM,YAAY,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;IAC5G,IAAI,YAAY,EAAE,CAAC;QACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,gBAAgB,GAAG,CAAC,QAAQ,0BAA0B,CAAC,CAAC,CAAC,gDAAgD,CAAC,CAAC;QAC/I,IAAA,wBAAa,EAAC,CAAC,MAAM,EAAE,IAAI,EAAE,oBAAoB,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC,YAAY,IAAI,IAAI,EAAE,CAAC,EAAE,0BAA0B,CAAC,CAAC;IAC7H,CAAC;SAAM,CAAC;QACJ,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;IAC7D,CAAC;IACD,IAAA,wBAAa,EAAC,CAAC,UAAU,EAAE,GAAG,CAAC,YAAY,CAAC,EAAE,kCAAkC,CAAC,CAAC;IAClF,oGAAoG;IACpG,kGAAkG;IAClG,IAAI,IAAI,KAAK,GAAG,CAAC,aAAa,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;QACxD,IAAA,wBAAa,EAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,oCAAoC,CAAC,CAAC;IAChF,CAAC;IACD,IAAA,wBAAa,EAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,oDAAoD,CAAC,CAAC;IAC5F,MAAM,WAAW,GAAG,IAAI,kCAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC5D,WAAW,CAAC,UAAU,GAAG,GAAG,CAAC,aAAa,CAAC;IAC3C,WAAW,CAAC,QAAQ,GAAG,IAAI,CAAC;IAC5B,IAAA,gCAAiB,EAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAEzC,+FAA+F;IAC/F,gEAAgE;IAChE,IAAA,uCAAwB,EAAC,IAAA,wBAAQ,EAAC,+BAA+B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAEjG,iGAAiG;IACjG,8EAA8E;IAC9E,MAAM,UAAU,GAAG,WAAW,CAAC;IAC/B,IAAI,CAAC,UAAU,IAAI,iBAAiB,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;QACrD,IAAA,wBAAa,EAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,YAAY,CAAC,EAAE,qCAAqC,CAAC,CAAC;IAC7F,CAAC;IAED,MAAM,aAAa,GAAG,IAAI,kCAAmB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAChE,aAAa,CAAC,UAAU,GAAG,GAAG,CAAC,aAAa,CAAC;IAC7C,aAAa,CAAC,QAAQ,GAAG,IAAI,CAAC;IAC9B,aAAa,CAAC,OAAO,GAAG,WAAW,CAAC;IACpC,aAAa,CAAC,SAAS,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,iBAAiB,GAAG,CAAC,YAAY,EAAE,EAAE,YAAY,IAAI,EAAE,CAAC,CAAC;IAChI,IAAA,gCAAiB,EAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;IAE3C,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,QAAQ,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC;AACnF,CAAC;AAED,mGAAmG;AACnG,kGAAkG;AAClG,oEAAoE;AACpE,SAAS,cAAc,CAAC,OAAe,EAAE,YAAoB,EAAE,QAAgB,EAAE,MAAe,EAAE,UAAmB;IACjH,MAAM,UAAU,GAAG,MAAM;QACrB,CAAC,CAAC,mBAAmB,OAAO,iBAAiB,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,WAAW,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,iBAAiB;QAC7G,CAAC,CAAC,mBAAmB,OAAO,wCAAwC,CAAC;IACzE,MAAM,KAAK,GAAG,UAAU;QACpB,CAAC,CAAC,sCAAsC,YAAY,oCAAoC;QACxF,CAAC,CAAC,sCAAsC,YAAY,+CAA+C,CAAC;IACxG,MAAM,OAAO,GAAG,UAAU;QACtB,CAAC,CAAC,oDAAoD,OAAO,cAAc;YACzE,gGAAgG;YAChG,0DAA0D,YAAY,MAAM;QAC9E,CAAC,CAAC,IAAI,CAAC;IACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,IAAI,GAAG,GAAG,GAAG,gDAAgD,GAAG,GAAG,GAAG,IAAI;QAC1E,SAAS,KAAK,IAAI;QAClB,4BAA4B;QAC5B,2CAA2C;QAC3C,SAAS,UAAU,MAAM;QACzB,OAAO,CACV,CAAC;AACN,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,QAAQ,CAC1B,QAAgB,EAAE,IAAkB,EAAE,QAAgB,EAAE,GAAiB,EAAE,eAAgC;IAE3G,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;QAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,kCAAkC,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;QACnF,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;QACxD,4FAA4F;QAC5F,4FAA4F;QAC5F,yFAAyF;QACzF,IAAA,4BAAiB,EAAC,QAAQ,CAAC,CAAC;QAC5B,IAAA,wBAAa,EAAC,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,gCAAgC,CAAC,CAAC;QAE/D,MAAM,aAAa,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;QACzG,IAAI,CAAC,aAAa,EAAE,CAAC;YACjB,IAAA,wBAAa,EACT,CAAC,QAAQ,EAAE,IAAI,EAAE,mBAAmB,GAAG,CAAC,aAAa,uBAAuB,CAAC,EAC7E,iCAAiC,CACpC,CAAC;QACN,CAAC;QACD,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,oBAAoB,CAAC,EAAE,EAAE,CAAC,CAAC;QAChE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;IACjE,CAAC;IAED,gGAAgG;IAChG,mGAAmG;IACnG,6EAA6E;IAC7E,MAAM,WAAW,GAAG,IAAA,6BAAe,EAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;IACvD,cAAc,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;IACjD,IAAA,8BAAgB,EAAC,QAAQ,CAAC,CAAC;IAC3B,MAAM,IAAA,mBAAQ,GAAE,CAAC;AACrB,CAAC","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, readMergeMarker, 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). On a CLEAN sync (`!hadConflict`) the\n// pre-merge snapshot is disposable, so it's deleted at the very end; a CONFLICT sync keeps it (paired\n// with its `merge-<n>/` context). Ends with an explicit \"here is exactly what I did\" recap.\nfunction finalizeBranch(repoRoot: string, verb: MutationVerb, ctx: MergeContext, hadConflict: boolean): 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 // Clean sync → the pre-merge snapshot was never needed; delete it (LAST, so any earlier finalize\n // failure above keeps it). Conflict sync → keep it as the undo point + trail.\n const backupKept = hadConflict;\n if (!backupKept && localBranchExists(ctx.backupBranch)) {\n runGitChecked(['branch', '-D', ctx.backupBranch], 'Failed to delete clean-merge backup');\n }\n\n const finalizeEvent = new BranchMutationEvent(verb, 'FINALIZE');\n finalizeEvent.fromBranch = ctx.currentBranch;\n finalizeEvent.toBranch = base;\n finalizeEvent.outcome = 'finalized';\n finalizeEvent.artifacts = [backupKept ? `backup=${ctx.backupBranch}` : `backupDeleted=${ctx.backupBranch}`, `remotePR=${base}`];\n logBranchMutation(repoRoot, finalizeEvent);\n\n printSyncRecap(base, ctx.backupBranch, ctx.prNumber, remoteExists, backupKept);\n}\n\n// The explicit, numbered \"here is exactly what I did\" recap the AI (and human) reads after a sync.\n// 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, backupKept: boolean): void {\n const remoteLine = pushed\n ? `landed back on ${feature} (== origin/${feature}${prNumber ? ` == PR #${prNumber}` : ''} — names match)`\n : `landed back on ${feature} (local only — no remote branch yet)`;\n const step1 = backupKept\n ? `snapshotted your pre-merge state → ${backupBranch} (kept — this merge had conflicts)`\n : `snapshotted your pre-merge state → ${backupBranch} (auto-removed — clean merge, no undo needed)`;\n const trailer = backupKept\n ? ` Pre-merge snapshot trail: git branch --list '${feature}PreMerge*'\\n` +\n ` Its conflict context lives in the paired merge-<n>/ folder under .webpieces/merge-info/\\n` +\n ` Prune this run's snapshot when safe: git branch -D ${backupBranch}\\n\\n`\n : '\\n';\n process.stdout.write(\n '\\n' + SEP + '✅ Sync complete — here is exactly what I did\\n' + SEP + '\\n' +\n ` 1. ${step1}\\n` +\n ` 2. pulled origin/main\\n` +\n ` 3. squash-merged your work onto main\\n` +\n ` 4. ${remoteLine}\\n\\n` +\n trailer,\n );\n}\n\n/**\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 // A marker in THIS run dir means the sync hit conflicts (marker is written only on hand-back) —\n // correct even for the validated-resume path (conflictedFiles=null yet a conflict). Read it BEFORE\n // clearMergeMarker so finalize knows whether to keep the pre-merge snapshot.\n const hadConflict = readMergeMarker(mergeDir) !== null;\n finalizeBranch(repoRoot, verb, ctx, hadConflict);\n clearMergeMarker(mergeDir);\n await cleanTmp();\n}\n"]}
|
|
@@ -9,6 +9,7 @@ export declare class MergeContext {
|
|
|
9
9
|
export declare class MergeStartResult {
|
|
10
10
|
status: 'clean' | 'conflict';
|
|
11
11
|
context: MergeContext | null;
|
|
12
|
-
|
|
12
|
+
runDir: string;
|
|
13
|
+
constructor(status: 'clean' | 'conflict', context: MergeContext | null, runDir: string);
|
|
13
14
|
}
|
|
14
|
-
export declare function mergeStart(repoRoot: string, verb: MutationVerb,
|
|
15
|
+
export declare function mergeStart(repoRoot: string, verb: MutationVerb, home: string, finishCommand: string): Promise<MergeStartResult>;
|
|
@@ -39,9 +39,11 @@ exports.MergeContext = MergeContext;
|
|
|
39
39
|
class MergeStartResult {
|
|
40
40
|
status;
|
|
41
41
|
context;
|
|
42
|
-
|
|
42
|
+
runDir; // this sync's numbered `merge-<n>/` dir — passed to merge-END so it reads THIS marker
|
|
43
|
+
constructor(status, context, runDir) {
|
|
43
44
|
this.status = status;
|
|
44
45
|
this.context = context;
|
|
46
|
+
this.runDir = runDir;
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
49
|
exports.MergeStartResult = MergeStartResult;
|
|
@@ -51,17 +53,34 @@ function detectPr(baseBranch) {
|
|
|
51
53
|
const result = (0, child_process_1.spawnSync)('gh', ['pr', 'list', '--head', baseBranch, '--json', 'number', '--jq', '.[0].number'], { encoding: 'utf8' });
|
|
52
54
|
return result.status === 0 ? (result.stdout ?? '').trim() : '';
|
|
53
55
|
}
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
// The one number `n` for a sync and the two things it names: the pre-merge backup branch and its
|
|
57
|
+
// paired conflict-context run dir.
|
|
58
|
+
class SyncSlot {
|
|
59
|
+
backupBranch;
|
|
60
|
+
runDir;
|
|
61
|
+
constructor(backupBranch, runDir) {
|
|
62
|
+
this.backupBranch = backupBranch;
|
|
63
|
+
this.runDir = runDir;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Pick the first free `<branch>PreMerge<n>` slot number, then WIPE the paired `<home>/merge-<n>/` run
|
|
67
|
+
// dir — a fresh sync means no merge is in progress, so any leftover `merge-<n>` is stale (its own sync
|
|
68
|
+
// ended, or its branch was deleted) and MUST NOT leak its old per-file merge-explanation.md into this
|
|
69
|
+
// merge's validation. Returns the backup branch name + the (now-empty) run dir path, both keyed on `n`.
|
|
70
|
+
function chooseSyncSlot(home, currentBranch) {
|
|
71
|
+
const n = (0, branch_naming_1.nextFreePreMergeNumber)(currentBranch, (name) => (0, child_process_1.spawnSync)('git', ['show-ref', '--verify', '--quiet', `refs/heads/${name}`]).status === 0);
|
|
72
|
+
const runDir = (0, merge_state_1.mergeRunDirFor)(home, n);
|
|
73
|
+
fs.rmSync(runDir, { recursive: true, force: true });
|
|
74
|
+
return new SyncSlot((0, branch_naming_1.preMergeBackupName)(currentBranch, n), runDir);
|
|
75
|
+
}
|
|
76
|
+
// Snapshot the pre-merge state onto the caller-chosen `backupBranch` (`<currentBranch>PreMerge<n>`),
|
|
77
|
+
// never overwriting. The slot number `n` is picked once in mergeStart and shared with the paired
|
|
78
|
+
// `merge-<n>/` context dir. A clean sync deletes this snapshot at finalize; only conflict syncs keep it.
|
|
79
|
+
function createBackup(currentBranch, backupBranch) {
|
|
59
80
|
process.stdout.write('\n' + SEP + '💾 Creating Pre-Merge Backup\n' + SEP + '\n');
|
|
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);
|
|
61
81
|
(0, git_exec_1.runGitChecked)(['checkout', '-b', backupBranch], 'Failed to create backup branch');
|
|
62
82
|
(0, git_exec_1.runGitChecked)(['checkout', currentBranch], 'Failed to return to feature branch');
|
|
63
83
|
process.stdout.write(`✅ Backup created: ${backupBranch}\n\n`);
|
|
64
|
-
return backupBranch;
|
|
65
84
|
}
|
|
66
85
|
function saveConflictContext(conflictedFiles, mergeDir, forkPoint, featureHead, mainHead) {
|
|
67
86
|
for (const file of conflictedFiles) {
|
|
@@ -158,7 +177,7 @@ pnpm {{FINISH_COMMAND}}
|
|
|
158
177
|
|
|
159
178
|
## If you need to bail out
|
|
160
179
|
|
|
161
|
-
A backup branch was created (e.g. \`<feature>
|
|
180
|
+
A numbered backup branch was created (e.g. \`<feature>PreMerge1\`). To abandon:
|
|
162
181
|
|
|
163
182
|
\`\`\`
|
|
164
183
|
git merge --abort 2>/dev/null; git checkout <feature> ; git branch -D {{SQUASH_BRANCH}}
|
|
@@ -207,7 +226,11 @@ function printConflictHandback(docPath, mergeDir, squashBranch, conflictedFiles,
|
|
|
207
226
|
function handleConflictsHandback(repoRoot, mergeDir, currentBranch, squashBranch, backupBranch, prNumber, hashes, finishCommand) {
|
|
208
227
|
const raw = (0, child_process_1.execSync)('git diff --name-only --diff-filter=U', { encoding: 'utf8' }).trim();
|
|
209
228
|
const conflictedFiles = raw.split('\n').filter((f) => f.trim() !== '');
|
|
229
|
+
fs.mkdirSync(mergeDir, { recursive: true }); // run dir was wiped at sync start — create it fresh
|
|
210
230
|
fs.writeFileSync(path.join(mergeDir, 'updatemain-conflicted-files.txt'), raw + '\n');
|
|
231
|
+
// Copy the A/B/C hashes into THIS run dir so the merge-process doc's `MERGE_DIR/updatemain-hashes.json`
|
|
232
|
+
// pointer is accurate (gatherInfo writes the source copy into the feature home before the slot is known).
|
|
233
|
+
fs.writeFileSync(path.join(mergeDir, 'updatemain-hashes.json'), JSON.stringify(hashes, null, 2) + '\n');
|
|
211
234
|
saveConflictContext(conflictedFiles, mergeDir, hashes.hashForkPoint, hashes.hashFeatureHead, hashes.hashMainHead);
|
|
212
235
|
const marker = new merge_state_1.MergeMarker(currentBranch, squashBranch, backupBranch, prNumber, conflictedFiles, hashes.hashForkPoint, hashes.hashFeatureHead, hashes.hashMainHead, false);
|
|
213
236
|
(0, merge_state_1.writeMergeMarker)(mergeDir, marker);
|
|
@@ -235,11 +258,15 @@ function logConflict(repoRoot, verb, mergeDir) {
|
|
|
235
258
|
];
|
|
236
259
|
(0, rules_config_1.logBranchMutation)(repoRoot, event);
|
|
237
260
|
}
|
|
238
|
-
async function mergeStart(repoRoot, verb,
|
|
261
|
+
async function mergeStart(repoRoot, verb, home, finishCommand) {
|
|
239
262
|
const currentBranch = (0, child_process_1.execSync)('git branch --show-current', { encoding: 'utf8' }).trim();
|
|
240
263
|
if (currentBranch.endsWith('Squash')) {
|
|
241
264
|
throw new rules_config_1.CliExitError(1, `❌ On a leftover ${currentBranch} branch with no merge marker. Clean up: git branch -D ${currentBranch}`);
|
|
242
265
|
}
|
|
266
|
+
// One number `n` for this sync drives BOTH the backup branch and its `merge-<n>/` context dir.
|
|
267
|
+
const slot = chooseSyncSlot(home, currentBranch);
|
|
268
|
+
const backupBranch = slot.backupBranch;
|
|
269
|
+
const mergeDir = slot.runDir;
|
|
243
270
|
process.stdout.write('\n' + SEP + '🔄 Squash-Merge Update from Main\n' + SEP + '\n');
|
|
244
271
|
// gatherInfo RETURNS (never exits): an already-even-with-main branch is NOT special-cased here —
|
|
245
272
|
// we flow straight on. The squash merge below becomes a no-op that the `nothingStaged` path
|
|
@@ -252,7 +279,7 @@ async function mergeStart(repoRoot, verb, mergeDir, finishCommand) {
|
|
|
252
279
|
}
|
|
253
280
|
const prNumber = detectPr((0, branch_naming_1.baseBranchName)(currentBranch));
|
|
254
281
|
process.stdout.write(prNumber ? `Existing PR #${prNumber} will be updated.\n` : 'No existing PR (one can be created later).\n');
|
|
255
|
-
|
|
282
|
+
createBackup(currentBranch, backupBranch);
|
|
256
283
|
const backupEvent = new rules_config_1.BranchMutationEvent(verb, 'BACKUP');
|
|
257
284
|
backupEvent.fromBranch = currentBranch;
|
|
258
285
|
backupEvent.toBranch = backupBranch;
|
|
@@ -275,7 +302,7 @@ async function mergeStart(repoRoot, verb, mergeDir, finishCommand) {
|
|
|
275
302
|
if (merge.status !== 0) {
|
|
276
303
|
handleConflictsHandback(repoRoot, mergeDir, currentBranch, squashBranch, backupBranch, prNumber, hashes, finishCommand);
|
|
277
304
|
logConflict(repoRoot, verb, mergeDir);
|
|
278
|
-
return new MergeStartResult('conflict', null);
|
|
305
|
+
return new MergeStartResult('conflict', null, mergeDir);
|
|
279
306
|
}
|
|
280
307
|
(0, rules_config_1.logBranchMutation)(repoRoot, new rules_config_1.BranchMutationEvent(verb, 'SQUASH'));
|
|
281
308
|
const nothingStaged = (0, child_process_1.spawnSync)('git', ['diff-index', '--quiet', '--cached', 'HEAD', '--']).status === 0;
|
|
@@ -285,6 +312,6 @@ async function mergeStart(repoRoot, verb, mergeDir, finishCommand) {
|
|
|
285
312
|
else {
|
|
286
313
|
(0, git_exec_1.runGitChecked)(['commit', '-m', `Squash merge of ${currentBranch}`], 'Failed to commit squash merge');
|
|
287
314
|
}
|
|
288
|
-
return new MergeStartResult('clean', new MergeContext(currentBranch, squashBranch, backupBranch, prNumber));
|
|
315
|
+
return new MergeStartResult('clean', new MergeContext(currentBranch, squashBranch, backupBranch, prNumber), mergeDir);
|
|
289
316
|
}
|
|
290
317
|
//# sourceMappingURL=merge-start.js.map
|
|
@@ -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, 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"]}
|
|
1
|
+
{"version":3,"file":"merge-start.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/merge-start.ts"],"names":[],"mappings":";;;AA4SA,gCA8DC;;AA1WD,iDAAoD;AACpD,+CAAyB;AACzB,mDAA6B;AAC7B,0DAGiC;AACjC,sDAA+C;AAC/C,mDAA6F;AAC7F,yCAA2C;AAC3C,+CAAiG;AAEjG,qGAAqG;AACrG,oGAAoG;AACpG,qGAAqG;AACrG,+FAA+F;AAC/F,uFAAuF;AACvF,mGAAmG;AACnG,mGAAmG;AACnG,0EAA0E;AAE1E,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAQvE,mGAAmG;AACnG,MAAa,YAAY;IACrB,aAAa,CAAS;IACtB,YAAY,CAAS;IACrB,YAAY,CAAS;IACrB,QAAQ,CAAS;IAEjB,YAAY,aAAqB,EAAE,YAAoB,EAAE,YAAoB,EAAE,QAAgB;QAC3F,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC7B,CAAC;CACJ;AAZD,oCAYC;AAED,sGAAsG;AACtG,0FAA0F;AAC1F,MAAa,gBAAgB;IACzB,MAAM,CAAuB;IAC7B,OAAO,CAAsB;IAC7B,MAAM,CAAS,CAAC,sFAAsF;IAEtG,YAAY,MAA4B,EAAE,OAA4B,EAAE,MAAc;QAClF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACzB,CAAC;CACJ;AAVD,4CAUC;AAED,gGAAgG;AAChG,6EAA6E;AAC7E,SAAS,QAAQ,CAAC,UAAkB;IAChC,MAAM,MAAM,GAAG,IAAA,yBAAS,EACpB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,CAAC,EACrF,EAAE,QAAQ,EAAE,MAAM,EAAE,CACvB,CAAC;IACF,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACnE,CAAC;AAED,iGAAiG;AACjG,mCAAmC;AACnC,MAAM,QAAQ;IACV,YAAY,CAAS;IACrB,MAAM,CAAS;IAEf,YAAY,YAAoB,EAAE,MAAc;QAC5C,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACzB,CAAC;CACJ;AAED,sGAAsG;AACtG,uGAAuG;AACvG,sGAAsG;AACtG,wGAAwG;AACxG,SAAS,cAAc,CAAC,IAAY,EAAE,aAAqB;IACvD,MAAM,CAAC,GAAG,IAAA,sCAAsB,EAC5B,aAAa,EACb,CAAC,IAAY,EAAW,EAAE,CAAC,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CACtH,CAAC;IACF,MAAM,MAAM,GAAG,IAAA,4BAAc,EAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACvC,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,OAAO,IAAI,QAAQ,CAAC,IAAA,kCAAkB,EAAC,aAAa,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AACtE,CAAC;AAED,qGAAqG;AACrG,iGAAiG;AACjG,yGAAyG;AACzG,SAAS,YAAY,CAAC,aAAqB,EAAE,YAAoB;IAC7D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,gCAAgC,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;IACjF,IAAA,wBAAa,EAAC,CAAC,UAAU,EAAE,IAAI,EAAE,YAAY,CAAC,EAAE,gCAAgC,CAAC,CAAC;IAClF,IAAA,wBAAa,EAAC,CAAC,UAAU,EAAE,aAAa,CAAC,EAAE,oCAAoC,CAAC,CAAC;IACjF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,YAAY,MAAM,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,mBAAmB,CACxB,eAAyB,EAAE,QAAgB,EAAE,SAAiB,EAAE,WAAmB,EAAE,QAAgB;IAErG,KAAK,MAAM,IAAI,IAAI,eAAe,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,IAAA,+BAAiB,EAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAClD,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAE3C,MAAM,IAAI,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,SAAS,IAAI,IAAI,EAAE,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QACtF,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,iBAAiB,CAAC,EAAE,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC;QAC5H,MAAM,OAAO,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,WAAW,IAAI,IAAI,EAAE,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3F,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC;QAChI,MAAM,IAAI,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,QAAQ,IAAI,IAAI,EAAE,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QACrF,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,EAAE,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC;QAEvH,MAAM,EAAE,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAChG,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;QAClE,MAAM,EAAE,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAC7F,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IACtE,CAAC;AACL,CAAC;AAED,8FAA8F;AAC9F,gGAAgG;AAChG,iGAAiG;AACjG,0FAA0F;AAC1F,MAAM,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkF9B,CAAC;AAEF,SAAS,eAAe,CAAC,QAAgB,EAAE,YAAoB,EAAE,eAAyB,EAAE,aAAqB;IAC7G,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,CAAS,EAAU,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrF,OAAO,sBAAsB;SACxB,OAAO,CAAC,wBAAwB,EAAE,YAAY,CAAC;SAC/C,OAAO,CAAC,oBAAoB,EAAE,QAAQ,CAAC;SACvC,OAAO,CAAC,2BAA2B,EAAE,qCAAsB,CAAC;SAC5D,OAAO,CAAC,yBAAyB,EAAE,aAAa,CAAC;SACjD,OAAO,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC;AACjD,CAAC;AAED,gDAAgD;AAChD,SAAS,oBAAoB,CAAC,QAAgB,EAAE,QAAgB,EAAE,YAAoB,EAAE,eAAyB,EAAE,aAAqB;IACpI,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,gCAAiB,EAAE,aAAa,CAAC,CAAC;IACrE,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC;IAC/D,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,eAAe,CAAC,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC,CAAC;IACnG,OAAO,OAAO,CAAC;AACnB,CAAC;AAED,gGAAgG;AAChG,gGAAgG;AAChG,SAAS,qBAAqB,CAC1B,OAAe,EAAE,QAAgB,EAAE,YAAoB,EAAE,eAAyB,EAAE,aAAqB;IAEzG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,oBAAoB,eAAe,CAAC,MAAM,0CAA0C,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;IACrI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAChF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IACtC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,+DAA+D,CAAC,CAAC;IACtF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gEAAgE,CAAC,CAAC;IACvF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8DAA8D,YAAY,MAAM,CAAC,CAAC;IACvG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAC/C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sCAAsC,OAAO,IAAI,CAAC,CAAC;IACxE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sFAAsF,CAAC,CAAC;IAC7G,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,QAAQ,oEAAoE,CAAC,CAAC;IAC5G,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mBAAmB,aAAa,oFAAoF,CAAC,CAAC;IAC3I,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAC5C,KAAK,MAAM,IAAI,IAAI,eAAe;QAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC;IAC1E,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;AACrC,CAAC;AAED,mGAAmG;AACnG,qEAAqE;AACrE,SAAS,uBAAuB,CAC5B,QAAgB,EAAE,QAAgB,EAAE,aAAqB,EAAE,YAAoB,EAC/E,YAAoB,EAAE,QAAgB,EAAE,MAAkB,EAAE,aAAqB;IAEjF,MAAM,GAAG,GAAG,IAAA,wBAAQ,EAAC,sCAAsC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1F,MAAM,eAAe,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAS,EAAW,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACxF,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,oDAAoD;IACjG,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,iCAAiC,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC,CAAC;IACrF,wGAAwG;IACxG,0GAA0G;IAC1G,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,wBAAwB,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACxG,mBAAmB,CAAC,eAAe,EAAE,QAAQ,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;IAElH,MAAM,MAAM,GAAG,IAAI,yBAAW,CAC1B,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,eAAe,EACpE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,YAAY,EAAE,KAAK,CAC3E,CAAC;IACF,IAAA,8BAAgB,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC;IACvG,qBAAqB,CAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC;AAC3F,CAAC;AAED,2FAA2F;AAC3F,8BAA8B;AAC9B,SAAS,QAAQ,CAAC,GAAW;IACzB,MAAM,MAAM,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IACrF,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACnE,CAAC;AAED,mGAAmG;AACnG,sGAAsG;AACtG,kEAAkE;AAClE,SAAS,WAAW,CAAC,QAAgB,EAAE,IAAkB,EAAE,QAAgB;IACvE,MAAM,GAAG,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,iBAAiB,CAAC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9G,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAS,EAAW,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IACrJ,MAAM,KAAK,GAAG,IAAI,kCAAmB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IACxD,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC;IAC5B,KAAK,CAAC,SAAS,GAAG;QACd,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,mBAAmB,CAAC;QACxC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,gCAAiB,EAAE,aAAa,EAAE,2BAA2B,CAAC;KACrF,CAAC;IACF,IAAA,gCAAiB,EAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;AACvC,CAAC;AAEM,KAAK,UAAU,UAAU,CAAC,QAAgB,EAAE,IAAkB,EAAE,IAAY,EAAE,aAAqB;IACtG,MAAM,aAAa,GAAG,IAAA,wBAAQ,EAAC,2BAA2B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACzF,IAAI,aAAa,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,2BAAY,CAAC,CAAC,EAAE,mBAAmB,aAAa,yDAAyD,aAAa,EAAE,CAAC,CAAC;IACxI,CAAC;IAED,+FAA+F;IAC/F,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;IACjD,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC;IAE7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,oCAAoC,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;IACrF,iGAAiG;IACjG,4FAA4F;IAC5F,+FAA+F;IAC/F,0FAA0F;IAC1F,MAAM,IAAI,GAAG,MAAM,IAAA,2BAAU,GAAE,CAAC;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3B,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kFAAkF,CAAC,CAAC;IAC7G,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAA,8BAAc,EAAC,aAAa,CAAC,CAAC,CAAC;IACzD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,gBAAgB,QAAQ,qBAAqB,CAAC,CAAC,CAAC,8CAA8C,CAAC,CAAC;IAEhI,YAAY,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;IAC1C,MAAM,WAAW,GAAG,IAAI,kCAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC5D,WAAW,CAAC,UAAU,GAAG,aAAa,CAAC;IACvC,WAAW,CAAC,QAAQ,GAAG,YAAY,CAAC;IACpC,IAAA,gCAAiB,EAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAEzC,MAAM,YAAY,GAAG,GAAG,aAAa,QAAQ,CAAC;IAC9C,IAAI,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,YAAY,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnG,MAAM,IAAI,2BAAY,CAAC,CAAC,EAAE,WAAW,YAAY,kDAAkD,YAAY,EAAE,CAAC,CAAC;IACvH,CAAC;IAED,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxC,IAAA,wBAAa,EAAC,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE,yBAAyB,CAAC,CAAC;IAC/D,IAAA,gCAAiB,EAAC,QAAQ,EAAE,IAAI,kCAAmB,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC;IAC5E,IAAA,wBAAa,EAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,4BAA4B,CAAC,CAAC;IACxE,MAAM,SAAS,GAAG,IAAI,kCAAmB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACxD,SAAS,CAAC,OAAO,GAAG,cAAc,CAAC;IACnC,SAAS,CAAC,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IACrC,IAAA,gCAAiB,EAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACvC,IAAA,wBAAa,EAAC,CAAC,UAAU,EAAE,IAAI,EAAE,YAAY,CAAC,EAAE,gCAAgC,CAAC,CAAC;IAElF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,qBAAqB,aAAa,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;IACvF,MAAM,KAAK,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,aAAa,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAC3F,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;QACxH,WAAW,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QACtC,OAAO,IAAI,gBAAgB,CAAC,UAAU,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC5D,CAAC;IACD,IAAA,gCAAiB,EAAC,QAAQ,EAAE,IAAI,kCAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAErE,MAAM,aAAa,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;IACzG,IAAI,aAAa,EAAE,CAAC;QAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC;IACnF,CAAC;SAAM,CAAC;QACJ,IAAA,wBAAa,EAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,mBAAmB,aAAa,EAAE,CAAC,EAAE,+BAA+B,CAAC,CAAC;IACzG,CAAC;IACD,OAAO,IAAI,gBAAgB,CAAC,OAAO,EAAE,IAAI,YAAY,CAAC,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC;AAC1H,CAAC","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, nextFreePreMergeNumber, preMergeBackupName } from './branch-naming';\nimport { runGitChecked } from './git-exec';\nimport { MergeMarker, perFileContextDir, writeMergeMarker, mergeRunDirFor } 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 runDir: string; // this sync's numbered `merge-<n>/` dir — passed to merge-END so it reads THIS marker\n\n constructor(status: 'clean' | 'conflict', context: MergeContext | null, runDir: string) {\n this.status = status;\n this.context = context;\n this.runDir = runDir;\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// The one number `n` for a sync and the two things it names: the pre-merge backup branch and its\n// paired conflict-context run dir.\nclass SyncSlot {\n backupBranch: string;\n runDir: string;\n\n constructor(backupBranch: string, runDir: string) {\n this.backupBranch = backupBranch;\n this.runDir = runDir;\n }\n}\n\n// Pick the first free `<branch>PreMerge<n>` slot number, then WIPE the paired `<home>/merge-<n>/` run\n// dir — a fresh sync means no merge is in progress, so any leftover `merge-<n>` is stale (its own sync\n// ended, or its branch was deleted) and MUST NOT leak its old per-file merge-explanation.md into this\n// merge's validation. Returns the backup branch name + the (now-empty) run dir path, both keyed on `n`.\nfunction chooseSyncSlot(home: string, currentBranch: string): SyncSlot {\n const n = nextFreePreMergeNumber(\n currentBranch,\n (name: string): boolean => spawnSync('git', ['show-ref', '--verify', '--quiet', `refs/heads/${name}`]).status === 0,\n );\n const runDir = mergeRunDirFor(home, n);\n fs.rmSync(runDir, { recursive: true, force: true });\n return new SyncSlot(preMergeBackupName(currentBranch, n), runDir);\n}\n\n// Snapshot the pre-merge state onto the caller-chosen `backupBranch` (`<currentBranch>PreMerge<n>`),\n// never overwriting. The slot number `n` is picked once in mergeStart and shared with the paired\n// `merge-<n>/` context dir. A clean sync deletes this snapshot at finalize; only conflict syncs keep it.\nfunction createBackup(currentBranch: string, backupBranch: string): void {\n process.stdout.write('\\n' + SEP + '💾 Creating Pre-Merge Backup\\n' + SEP + '\\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}\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 numbered backup branch was created (e.g. \\`<feature>PreMerge1\\`). 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.mkdirSync(mergeDir, { recursive: true }); // run dir was wiped at sync start — create it fresh\n fs.writeFileSync(path.join(mergeDir, 'updatemain-conflicted-files.txt'), raw + '\\n');\n // Copy the A/B/C hashes into THIS run dir so the merge-process doc's `MERGE_DIR/updatemain-hashes.json`\n // pointer is accurate (gatherInfo writes the source copy into the feature home before the slot is known).\n fs.writeFileSync(path.join(mergeDir, 'updatemain-hashes.json'), JSON.stringify(hashes, null, 2) + '\\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, home: 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 // One number `n` for this sync drives BOTH the backup branch and its `merge-<n>/` context dir.\n const slot = chooseSyncSlot(home, currentBranch);\n const backupBranch = slot.backupBranch;\n const mergeDir = slot.runDir;\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 createBackup(currentBranch, backupBranch);\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, mergeDir);\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), mergeDir);\n}\n"]}
|
|
@@ -16,6 +16,8 @@ export declare class MarkerScanResult {
|
|
|
16
16
|
constructor(clean: boolean, filesWithMarkers: string[]);
|
|
17
17
|
}
|
|
18
18
|
export declare function mergeDirFor(repoRoot: string, featureName: string): string;
|
|
19
|
+
export declare function mergeRunDirFor(home: string, n: number): string;
|
|
20
|
+
export declare function findActiveMergeRunDir(home: string): string | null;
|
|
19
21
|
export declare function perFileContextDir(mergeDir: string, file: string): string;
|
|
20
22
|
export declare function markerPath(mergeDir: string): string;
|
|
21
23
|
export declare function readMergeMarker(mergeDir: string): MergeMarker | null;
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MarkerScanResult = exports.MergeMarker = void 0;
|
|
4
4
|
exports.mergeDirFor = mergeDirFor;
|
|
5
|
+
exports.mergeRunDirFor = mergeRunDirFor;
|
|
6
|
+
exports.findActiveMergeRunDir = findActiveMergeRunDir;
|
|
5
7
|
exports.perFileContextDir = perFileContextDir;
|
|
6
8
|
exports.markerPath = markerPath;
|
|
7
9
|
exports.readMergeMarker = readMergeMarker;
|
|
@@ -49,9 +51,39 @@ class MarkerScanResult {
|
|
|
49
51
|
}
|
|
50
52
|
exports.MarkerScanResult = MarkerScanResult;
|
|
51
53
|
const CONFLICT_MARKER_RE = /^(<{7}|={7}|>{7})/m;
|
|
54
|
+
// The per-feature "home" dir `.webpieces/merge-info/<slug>/`. It no longer holds the marker/context
|
|
55
|
+
// directly — each sync gets its own numbered `merge-<n>/` run dir underneath (mergeRunDirFor), paired
|
|
56
|
+
// with the sync's `<feature>PreMerge<n>` backup branch. This keeps merge N from ever reusing merge
|
|
57
|
+
// N-1's stale per-file context / merge-explanation.md.
|
|
52
58
|
function mergeDirFor(repoRoot, featureName) {
|
|
53
59
|
return path.join(repoRoot, rules_config_1.WEBPIECES_TMP_DIR, rules_config_1.MERGE_INFO_DIR, featureName);
|
|
54
60
|
}
|
|
61
|
+
// The run dir for sync number `n`: `<home>/merge-<n>/`. Holds this sync's marker + per-file
|
|
62
|
+
// `updatemain-<file>/` context. Numbered to match the sync's `<feature>PreMerge<n>` backup branch.
|
|
63
|
+
function mergeRunDirFor(home, n) {
|
|
64
|
+
return path.join(home, `merge-${n}`);
|
|
65
|
+
}
|
|
66
|
+
// Locate the in-progress merge's run dir: the `<home>/merge-*/` subdir holding a marker. There is at
|
|
67
|
+
// most one (a fresh sync can't start while a merge is in progress); if more than one somehow exists,
|
|
68
|
+
// prefer an UNVALIDATED marker (the live conflict), else return the first found. Null when none.
|
|
69
|
+
function findActiveMergeRunDir(home) {
|
|
70
|
+
if (!fs.existsSync(home))
|
|
71
|
+
return null;
|
|
72
|
+
let fallback = null;
|
|
73
|
+
for (const entry of fs.readdirSync(home)) {
|
|
74
|
+
if (!entry.startsWith('merge-'))
|
|
75
|
+
continue;
|
|
76
|
+
const dir = path.join(home, entry);
|
|
77
|
+
const marker = readMergeMarker(dir);
|
|
78
|
+
if (marker === null)
|
|
79
|
+
continue;
|
|
80
|
+
if (!marker.validated)
|
|
81
|
+
return dir;
|
|
82
|
+
if (fallback === null)
|
|
83
|
+
fallback = dir;
|
|
84
|
+
}
|
|
85
|
+
return fallback;
|
|
86
|
+
}
|
|
55
87
|
// Per-conflicted-file context dir holding A-forkpoint.txt / B-feature.txt / C-main.txt /
|
|
56
88
|
// B-A.diff / C-A.diff (and the AI's merge-explanation.md). Shared so the writer
|
|
57
89
|
// (saveConflictContext) and the reader (the explanation gate) agree on the layout: the conflict
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge-state.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/merge-state.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport {\n WEBPIECES_TMP_DIR,\n MERGE_INFO_DIR,\n MERGE_IN_PROGRESS_FILE,\n MERGE_EXPLANATION_FILE,\n} from '@webpieces/rules-config';\n\n// Proof-obligation marker written when a 3-point squash-merge hits conflicts. Its mere\n// presence (with validated=false) is what the merge-in-progress-guard hook uses to block\n// commit/push/PR until `wp-finish-upsert-pr` validates the resolution and flips it true.\nexport class MergeMarker {\n currentBranch: string;\n squashBranch: string;\n backupBranch: string;\n prNumber: string;\n conflictedFiles: string[];\n forkPoint: string;\n featureHead: string;\n mainHead: string;\n validated: boolean;\n\n constructor(\n currentBranch: string,\n squashBranch: string,\n backupBranch: string,\n prNumber: string,\n conflictedFiles: string[],\n forkPoint: string,\n featureHead: string,\n mainHead: string,\n validated: boolean,\n ) {\n this.currentBranch = currentBranch;\n this.squashBranch = squashBranch;\n this.backupBranch = backupBranch;\n this.prNumber = prNumber;\n this.conflictedFiles = conflictedFiles;\n this.forkPoint = forkPoint;\n this.featureHead = featureHead;\n this.mainHead = mainHead;\n this.validated = validated;\n }\n}\n\nexport class MarkerScanResult {\n clean: boolean;\n filesWithMarkers: string[];\n\n constructor(clean: boolean, filesWithMarkers: string[]) {\n this.clean = clean;\n this.filesWithMarkers = filesWithMarkers;\n }\n}\n\nconst CONFLICT_MARKER_RE = /^(<{7}|={7}|>{7})/m;\n\nexport function mergeDirFor(repoRoot: string, featureName: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, MERGE_INFO_DIR, featureName);\n}\n\n// Per-conflicted-file context dir holding A-forkpoint.txt / B-feature.txt / C-main.txt /\n// B-A.diff / C-A.diff (and the AI's merge-explanation.md). Shared so the writer\n// (saveConflictContext) and the reader (the explanation gate) agree on the layout: the conflict\n// file path with `/` → `__`, prefixed `updatemain-`.\nexport function perFileContextDir(mergeDir: string, file: string): string {\n return path.join(mergeDir, `updatemain-${file.replace(/\\//g, '__')}`);\n}\n\nexport function markerPath(mergeDir: string): string {\n return path.join(mergeDir, MERGE_IN_PROGRESS_FILE);\n}\n\nexport function readMergeMarker(mergeDir: string): MergeMarker | null {\n const filePath = markerPath(mergeDir);\n if (!fs.existsSync(filePath)) return null;\n const raw = JSON.parse(fs.readFileSync(filePath, 'utf8')) as MergeMarker;\n return new MergeMarker(\n raw.currentBranch,\n raw.squashBranch,\n raw.backupBranch,\n raw.prNumber,\n raw.conflictedFiles ?? [],\n raw.forkPoint,\n raw.featureHead,\n raw.mainHead,\n raw.validated === true,\n );\n}\n\nexport function writeMergeMarker(mergeDir: string, marker: MergeMarker): void {\n fs.mkdirSync(mergeDir, { recursive: true });\n fs.writeFileSync(markerPath(mergeDir), JSON.stringify(marker, null, 2) + '\\n');\n}\n\nexport function clearMergeMarker(mergeDir: string): void {\n const filePath = markerPath(mergeDir);\n if (fs.existsSync(filePath)) fs.rmSync(filePath);\n}\n\n/**\n * Scoped conflict-marker scan: reads ONLY the given conflicted files (relative to repo\n * root), never the whole repo — stays O(conflicts) regardless of monorepo size.\n */\nexport function scanConflictMarkers(repoRoot: string, files: string[]): MarkerScanResult {\n const filesWithMarkers: string[] = [];\n for (const file of files) {\n const abs = path.join(repoRoot, file);\n if (!fs.existsSync(abs)) continue;\n const content = fs.readFileSync(abs, 'utf8');\n if (CONFLICT_MARKER_RE.test(content)) filesWithMarkers.push(file);\n }\n return new MarkerScanResult(filesWithMarkers.length === 0, filesWithMarkers);\n}\n\n/**\n * Explanation scan: every conflicted file the AI resolved must have a non-empty\n * MERGE_EXPLANATION_FILE sitting in its per-file context dir (next to the diffs), proving the AI\n * deliberately 3-point merged it and recording HOW. Returns the files whose explanation is\n * missing or empty. Works for every conflicted file regardless of type — including comment-less\n * files (JSON) and files resolved by deletion (no working-tree file to inspect).\n */\nexport function scanMergeExplanations(mergeDir: string, files: string[]): MarkerScanResult {\n const filesMissingExplanation: string[] = [];\n for (const file of files) {\n const explPath = path.join(perFileContextDir(mergeDir, file), MERGE_EXPLANATION_FILE);\n const present = fs.existsSync(explPath) && fs.readFileSync(explPath, 'utf8').trim() !== '';\n if (!present) filesMissingExplanation.push(file);\n }\n return new MarkerScanResult(filesMissingExplanation.length === 0, filesMissingExplanation);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"merge-state.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/merge-state.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport {\n WEBPIECES_TMP_DIR,\n MERGE_INFO_DIR,\n MERGE_IN_PROGRESS_FILE,\n MERGE_EXPLANATION_FILE,\n} from '@webpieces/rules-config';\n\n// Proof-obligation marker written when a 3-point squash-merge hits conflicts. Its mere\n// presence (with validated=false) is what the merge-in-progress-guard hook uses to block\n// commit/push/PR until `wp-finish-upsert-pr` validates the resolution and flips it true.\nexport class MergeMarker {\n currentBranch: string;\n squashBranch: string;\n backupBranch: string;\n prNumber: string;\n conflictedFiles: string[];\n forkPoint: string;\n featureHead: string;\n mainHead: string;\n validated: boolean;\n\n constructor(\n currentBranch: string,\n squashBranch: string,\n backupBranch: string,\n prNumber: string,\n conflictedFiles: string[],\n forkPoint: string,\n featureHead: string,\n mainHead: string,\n validated: boolean,\n ) {\n this.currentBranch = currentBranch;\n this.squashBranch = squashBranch;\n this.backupBranch = backupBranch;\n this.prNumber = prNumber;\n this.conflictedFiles = conflictedFiles;\n this.forkPoint = forkPoint;\n this.featureHead = featureHead;\n this.mainHead = mainHead;\n this.validated = validated;\n }\n}\n\nexport class MarkerScanResult {\n clean: boolean;\n filesWithMarkers: string[];\n\n constructor(clean: boolean, filesWithMarkers: string[]) {\n this.clean = clean;\n this.filesWithMarkers = filesWithMarkers;\n }\n}\n\nconst CONFLICT_MARKER_RE = /^(<{7}|={7}|>{7})/m;\n\n// The per-feature \"home\" dir `.webpieces/merge-info/<slug>/`. It no longer holds the marker/context\n// directly — each sync gets its own numbered `merge-<n>/` run dir underneath (mergeRunDirFor), paired\n// with the sync's `<feature>PreMerge<n>` backup branch. This keeps merge N from ever reusing merge\n// N-1's stale per-file context / merge-explanation.md.\nexport function mergeDirFor(repoRoot: string, featureName: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, MERGE_INFO_DIR, featureName);\n}\n\n// The run dir for sync number `n`: `<home>/merge-<n>/`. Holds this sync's marker + per-file\n// `updatemain-<file>/` context. Numbered to match the sync's `<feature>PreMerge<n>` backup branch.\nexport function mergeRunDirFor(home: string, n: number): string {\n return path.join(home, `merge-${n}`);\n}\n\n// Locate the in-progress merge's run dir: the `<home>/merge-*/` subdir holding a marker. There is at\n// most one (a fresh sync can't start while a merge is in progress); if more than one somehow exists,\n// prefer an UNVALIDATED marker (the live conflict), else return the first found. Null when none.\nexport function findActiveMergeRunDir(home: string): string | null {\n if (!fs.existsSync(home)) return null;\n let fallback: string | null = null;\n for (const entry of fs.readdirSync(home)) {\n if (!entry.startsWith('merge-')) continue;\n const dir = path.join(home, entry);\n const marker = readMergeMarker(dir);\n if (marker === null) continue;\n if (!marker.validated) return dir;\n if (fallback === null) fallback = dir;\n }\n return fallback;\n}\n\n// Per-conflicted-file context dir holding A-forkpoint.txt / B-feature.txt / C-main.txt /\n// B-A.diff / C-A.diff (and the AI's merge-explanation.md). Shared so the writer\n// (saveConflictContext) and the reader (the explanation gate) agree on the layout: the conflict\n// file path with `/` → `__`, prefixed `updatemain-`.\nexport function perFileContextDir(mergeDir: string, file: string): string {\n return path.join(mergeDir, `updatemain-${file.replace(/\\//g, '__')}`);\n}\n\nexport function markerPath(mergeDir: string): string {\n return path.join(mergeDir, MERGE_IN_PROGRESS_FILE);\n}\n\nexport function readMergeMarker(mergeDir: string): MergeMarker | null {\n const filePath = markerPath(mergeDir);\n if (!fs.existsSync(filePath)) return null;\n const raw = JSON.parse(fs.readFileSync(filePath, 'utf8')) as MergeMarker;\n return new MergeMarker(\n raw.currentBranch,\n raw.squashBranch,\n raw.backupBranch,\n raw.prNumber,\n raw.conflictedFiles ?? [],\n raw.forkPoint,\n raw.featureHead,\n raw.mainHead,\n raw.validated === true,\n );\n}\n\nexport function writeMergeMarker(mergeDir: string, marker: MergeMarker): void {\n fs.mkdirSync(mergeDir, { recursive: true });\n fs.writeFileSync(markerPath(mergeDir), JSON.stringify(marker, null, 2) + '\\n');\n}\n\nexport function clearMergeMarker(mergeDir: string): void {\n const filePath = markerPath(mergeDir);\n if (fs.existsSync(filePath)) fs.rmSync(filePath);\n}\n\n/**\n * Scoped conflict-marker scan: reads ONLY the given conflicted files (relative to repo\n * root), never the whole repo — stays O(conflicts) regardless of monorepo size.\n */\nexport function scanConflictMarkers(repoRoot: string, files: string[]): MarkerScanResult {\n const filesWithMarkers: string[] = [];\n for (const file of files) {\n const abs = path.join(repoRoot, file);\n if (!fs.existsSync(abs)) continue;\n const content = fs.readFileSync(abs, 'utf8');\n if (CONFLICT_MARKER_RE.test(content)) filesWithMarkers.push(file);\n }\n return new MarkerScanResult(filesWithMarkers.length === 0, filesWithMarkers);\n}\n\n/**\n * Explanation scan: every conflicted file the AI resolved must have a non-empty\n * MERGE_EXPLANATION_FILE sitting in its per-file context dir (next to the diffs), proving the AI\n * deliberately 3-point merged it and recording HOW. Returns the files whose explanation is\n * missing or empty. Works for every conflicted file regardless of type — including comment-less\n * files (JSON) and files resolved by deletion (no working-tree file to inspect).\n */\nexport function scanMergeExplanations(mergeDir: string, files: string[]): MarkerScanResult {\n const filesMissingExplanation: string[] = [];\n for (const file of files) {\n const explPath = path.join(perFileContextDir(mergeDir, file), MERGE_EXPLANATION_FILE);\n const present = fs.existsSync(explPath) && fs.readFileSync(explPath, 'utf8').trim() !== '';\n if (!present) filesMissingExplanation.push(file);\n }\n return new MarkerScanResult(filesMissingExplanation.length === 0, filesMissingExplanation);\n}\n"]}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runUpdateFromMain = runUpdateFromMain;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const fs = tslib_1.__importStar(require("fs"));
|
|
6
4
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
7
5
|
const git_readAiBranchName_1 = require("./git-readAiBranchName");
|
|
8
6
|
const merge_state_1 = require("./merge-state");
|
|
@@ -17,23 +15,24 @@ async function runUpdateFromMain(repoRoot, verb, finishCommand) {
|
|
|
17
15
|
return outcome;
|
|
18
16
|
}
|
|
19
17
|
async function runUpdate(repoRoot, verb, finishCommand) {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (!existing.validated)
|
|
18
|
+
const home = (0, merge_state_1.mergeDirFor)(repoRoot, (0, git_readAiBranchName_1.getFeatureName)());
|
|
19
|
+
// Resume path: an in-progress merge is the `merge-<n>/` run dir that holds a marker.
|
|
20
|
+
const activeDir = (0, merge_state_1.findActiveMergeRunDir)(home);
|
|
21
|
+
if (activeDir) {
|
|
22
|
+
const existing = (0, merge_state_1.readMergeMarker)(activeDir);
|
|
23
|
+
if (existing === null || !existing.validated)
|
|
26
24
|
return 'unvalidatedResume';
|
|
27
|
-
// Already validated → just finalize the branch swap.
|
|
28
|
-
await (0, merge_end_1.mergeEnd)(repoRoot, verb,
|
|
25
|
+
// Already validated → just finalize the branch swap (reads THIS run dir's marker).
|
|
26
|
+
await (0, merge_end_1.mergeEnd)(repoRoot, verb, activeDir, new merge_start_1.MergeContext(existing.currentBranch, existing.squashBranch, existing.backupBranch, existing.prNumber), null);
|
|
29
27
|
return 'finalized';
|
|
30
28
|
}
|
|
31
|
-
// Fresh update:
|
|
32
|
-
|
|
29
|
+
// Fresh update: mergeStart picks the slot number, creates its own `merge-<n>/` run dir, and
|
|
30
|
+
// returns that path for merge-END to finalize against.
|
|
31
|
+
const result = await (0, merge_start_1.mergeStart)(repoRoot, verb, home, finishCommand);
|
|
33
32
|
if (result.status === 'conflict' || result.context === null) {
|
|
34
33
|
return 'conflict';
|
|
35
34
|
}
|
|
36
|
-
await (0, merge_end_1.mergeEnd)(repoRoot, verb,
|
|
35
|
+
await (0, merge_end_1.mergeEnd)(repoRoot, verb, result.runDir, result.context, null);
|
|
37
36
|
return 'finalized';
|
|
38
37
|
}
|
|
39
38
|
//# sourceMappingURL=run-update.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-update.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/run-update.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"run-update.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/run-update.ts"],"names":[],"mappings":";;AAqBA,8CAOC;AA5BD,0DAA+F;AAC/F,iEAAwD;AACxD,+CAAoF;AACpF,+CAAyD;AACzD,2CAAuC;AAiBhC,KAAK,UAAU,iBAAiB,CAAC,QAAgB,EAAE,IAAkB,EAAE,aAAqB;IAC/F,IAAA,gCAAiB,EAAC,QAAQ,EAAE,IAAI,kCAAmB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IACpE,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;IAC/D,MAAM,GAAG,GAAG,IAAI,kCAAmB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACjD,GAAG,CAAC,OAAO,GAAG,OAAO,CAAC;IACtB,IAAA,gCAAiB,EAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACjC,OAAO,OAAO,CAAC;AACnB,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,QAAgB,EAAE,IAAkB,EAAE,aAAqB;IAChF,MAAM,IAAI,GAAG,IAAA,yBAAW,EAAC,QAAQ,EAAE,IAAA,qCAAc,GAAE,CAAC,CAAC;IAErD,qFAAqF;IACrF,MAAM,SAAS,GAAG,IAAA,mCAAqB,EAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,SAAS,EAAE,CAAC;QACZ,MAAM,QAAQ,GAAG,IAAA,6BAAe,EAAC,SAAS,CAAC,CAAC;QAC5C,IAAI,QAAQ,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS;YAAE,OAAO,mBAAmB,CAAC;QACzE,mFAAmF;QACnF,MAAM,IAAA,oBAAQ,EACV,QAAQ,EAAE,IAAI,EAAE,SAAS,EACzB,IAAI,0BAAY,CAAC,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,QAAQ,CAAC,EACzG,IAAI,CACP,CAAC;QACF,OAAO,WAAW,CAAC;IACvB,CAAC;IAED,4FAA4F;IAC5F,uDAAuD;IACvD,MAAM,MAAM,GAAG,MAAM,IAAA,wBAAU,EAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;IACrE,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QAC1D,OAAO,UAAU,CAAC;IACtB,CAAC;IACD,MAAM,IAAA,oBAAQ,EAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACpE,OAAO,WAAW,CAAC;AACvB,CAAC","sourcesContent":["import { MutationVerb, BranchMutationEvent, logBranchMutation } from '@webpieces/rules-config';\nimport { getFeatureName } from './git-readAiBranchName';\nimport { mergeDirFor, readMergeMarker, findActiveMergeRunDir } from './merge-state';\nimport { mergeStart, MergeContext } from './merge-start';\nimport { mergeEnd } from './merge-end';\n\n// The shared \"sync this feature branch from main\" engine — a thin composition of the two lifecycle\n// primitives (merge-START brings main in, merge-END finalizes). It is an INTERNAL function, not a\n// bin: `wp-update-start` (standalone) and `wp-start-upsert-pr` (PR flow) both call it. It NEVER\n// creates a PR and — unlike the old wp-git-update bin — it does NOT process.exit; it RETURNS an\n// outcome so each caller can print the handoff that fits its context.\n//\n// `finishCommand` is the command the AI is told to run after resolving conflicts — the standalone\n// caller passes `wp-update-end`, the PR flow passes `wp-finish-upsert-pr`.\n//\n// `verb` is the invoking bin, threaded through purely so every branch mutation (backup, checkout,\n// pull, squash, rename) is recorded in `.webpieces/hooks/branch-mutations.log` under the command the\n// user actually ran — the audit trail that used to exist only in `git reflog`.\n\nexport type UpdateOutcome = 'finalized' | 'conflict' | 'unvalidatedResume';\n\nexport async function runUpdateFromMain(repoRoot: string, verb: MutationVerb, finishCommand: string): Promise<UpdateOutcome> {\n logBranchMutation(repoRoot, new BranchMutationEvent(verb, 'START'));\n const outcome = await runUpdate(repoRoot, verb, finishCommand);\n const end = new BranchMutationEvent(verb, 'END');\n end.outcome = outcome;\n logBranchMutation(repoRoot, end);\n return outcome;\n}\n\nasync function runUpdate(repoRoot: string, verb: MutationVerb, finishCommand: string): Promise<UpdateOutcome> {\n const home = mergeDirFor(repoRoot, getFeatureName());\n\n // Resume path: an in-progress merge is the `merge-<n>/` run dir that holds a marker.\n const activeDir = findActiveMergeRunDir(home);\n if (activeDir) {\n const existing = readMergeMarker(activeDir);\n if (existing === null || !existing.validated) return 'unvalidatedResume';\n // Already validated → just finalize the branch swap (reads THIS run dir's marker).\n await mergeEnd(\n repoRoot, verb, activeDir,\n new MergeContext(existing.currentBranch, existing.squashBranch, existing.backupBranch, existing.prNumber),\n null,\n );\n return 'finalized';\n }\n\n // Fresh update: mergeStart picks the slot number, creates its own `merge-<n>/` run dir, and\n // returns that path for merge-END to finalize against.\n const result = await mergeStart(repoRoot, verb, home, finishCommand);\n if (result.status === 'conflict' || result.context === null) {\n return 'conflict';\n }\n await mergeEnd(repoRoot, verb, result.runDir, result.context, null);\n return 'finalized';\n}\n"]}
|
|
@@ -16,16 +16,18 @@ const merge_start_1 = require("./workflow/merge-start");
|
|
|
16
16
|
// progress (a clean `wp-update-start` finalizes on its own — there is nothing left for this to do).
|
|
17
17
|
async function main() {
|
|
18
18
|
const repoRoot = (0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
const home = (0, merge_state_1.mergeDirFor)(repoRoot, (0, git_readAiBranchName_1.getFeatureName)());
|
|
20
|
+
// The in-progress merge is the `merge-<n>/` run dir holding a marker.
|
|
21
|
+
const activeDir = (0, merge_state_1.findActiveMergeRunDir)(home);
|
|
22
|
+
const marker = activeDir ? (0, merge_state_1.readMergeMarker)(activeDir) : null;
|
|
23
|
+
if (!activeDir || !marker) {
|
|
22
24
|
throw new rules_config_1.CliExitError(1, '❌ No merge in progress (no marker) — nothing to finalize.\n' +
|
|
23
25
|
'Start one with: pnpm wp-update-start (a clean update finalizes itself).');
|
|
24
26
|
}
|
|
25
27
|
// Not yet validated => a conflict resolution the AI owns, so validate + commit it first; already
|
|
26
28
|
// validated => clean merge (or previously validated) => finalize only.
|
|
27
29
|
const conflictedFiles = marker.validated ? null : marker.conflictedFiles;
|
|
28
|
-
await (0, merge_end_1.mergeEnd)(repoRoot, 'wp-update-end',
|
|
30
|
+
await (0, merge_end_1.mergeEnd)(repoRoot, 'wp-update-end', activeDir, new merge_start_1.MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber), conflictedFiles);
|
|
29
31
|
process.stdout.write('\n✅ Merge finalized on ' + marker.currentBranch + '.\n');
|
|
30
32
|
}
|
|
31
33
|
if (require.main === module)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wp-update-end.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/wp-update-end.ts"],"names":[],"mappings":";;;AAeA,
|
|
1
|
+
{"version":3,"file":"wp-update-end.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/wp-update-end.ts"],"names":[],"mappings":";;;AAeA,oBAuBC;AArCD,iDAAyC;AACzC,0DAAgE;AAChE,0EAAiE;AACjE,wDAA6F;AAC7F,oDAAgD;AAChD,wDAAsD;AAEtD,sGAAsG;AACtG,gGAAgG;AAChG,qGAAqG;AACrG,qGAAqG;AACrG,sGAAsG;AACtG,oGAAoG;AAE7F,KAAK,UAAU,IAAI;IACtB,MAAM,QAAQ,GAAG,IAAA,wBAAQ,EAAC,+BAA+B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACxF,MAAM,IAAI,GAAG,IAAA,yBAAW,EAAC,QAAQ,EAAE,IAAA,qCAAc,GAAE,CAAC,CAAC;IAErD,sEAAsE;IACtE,MAAM,SAAS,GAAG,IAAA,mCAAqB,EAAC,IAAI,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,IAAA,6BAAe,EAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7D,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,EAAE,CAAC;QACxB,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,6DAA6D;YAC7D,2EAA2E,CAC9E,CAAC;IACN,CAAC;IAED,iGAAiG;IACjG,uEAAuE;IACvE,MAAM,eAAe,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC;IACzE,MAAM,IAAA,oBAAQ,EACV,QAAQ,EAAE,eAAe,EAAE,SAAS,EACpC,IAAI,0BAAY,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,EACjG,eAAe,CAClB,CAAC;IACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,GAAG,MAAM,CAAC,aAAa,GAAG,KAAK,CAAC,CAAC;AACnF,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 { CliExitError, runMain } from '@webpieces/rules-config';\nimport { getFeatureName } from './workflow/git-readAiBranchName';\nimport { mergeDirFor, readMergeMarker, findActiveMergeRunDir } from './workflow/merge-state';\nimport { mergeEnd } from './workflow/merge-end';\nimport { MergeContext } from './workflow/merge-start';\n\n// wp-update-end — finalize half of the 3-point squash-merge lifecycle. Run it AFTER `wp-update-start`\n// handed back conflicts and you resolved them. Given an in-progress merge marker it validates +\n// commits the AI's resolution (when the marker is not yet validated) and ALWAYS finalizes the branch\n// swap (squash→feature, push, stamp clean, clear marker). It does NOT run the build gate / dashboard\n// / PR — that is wp-finish-upsert-pr (the PR flow's finish). Refuses to run when there is no merge in\n// progress (a clean `wp-update-start` finalizes on its own — there is nothing left for this to do).\n\nexport async function main(): Promise<void> {\n const repoRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();\n const home = mergeDirFor(repoRoot, getFeatureName());\n\n // The in-progress merge is the `merge-<n>/` run dir holding a marker.\n const activeDir = findActiveMergeRunDir(home);\n const marker = activeDir ? readMergeMarker(activeDir) : null;\n if (!activeDir || !marker) {\n throw new CliExitError(1,\n '❌ No merge in progress (no marker) — nothing to finalize.\\n' +\n 'Start one with: pnpm wp-update-start (a clean update finalizes itself).',\n );\n }\n\n // Not yet validated => a conflict resolution the AI owns, so validate + commit it first; already\n // validated => clean merge (or previously validated) => finalize only.\n const conflictedFiles = marker.validated ? null : marker.conflictedFiles;\n await mergeEnd(\n repoRoot, 'wp-update-end', activeDir,\n new MergeContext(marker.currentBranch, marker.squashBranch, marker.backupBranch, marker.prNumber),\n conflictedFiles,\n );\n process.stdout.write('\\n✅ Merge finalized on ' + marker.currentBranch + '.\\n');\n}\n\nif (require.main === module) runMain(main);\n"]}
|