From 73a84a6508779d2de593d62fc4e170492de747b1 Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:28:57 +1000 Subject: [PATCH 1/6] fix: provide merge context to beta conflict resolver When the opencode conflict resolver runs during beta merges, it now knows which PRs have already been applied and which are still pending. This prevents it from resolving conflicts in ways that break already-merged PRs (e.g. re-introducing deleted files or stale imports). --- script/beta.ts | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/script/beta.ts b/script/beta.ts index 61f9cf86200e..61c35151ebec 100755 --- a/script/beta.ts +++ b/script/beta.ts @@ -50,11 +50,38 @@ async function cleanup() { } catch {} } -async function fix(pr: PR, files: string[]) { +async function fix(pr: PR, files: string[], prs: PR[], applied: number[]) { console.log(` Trying to auto-resolve ${files.length} conflict(s) with opencode...`) + + const appliedList = + applied.length > 0 + ? applied + .map((n) => { + const p = prs.find((x) => x.number === n) + return `- #${n}: ${p?.title ?? ""}` + }) + .join("\n") + : "(none yet)" + + const remaining = prs + .filter((p) => p.number !== pr.number && !applied.includes(p.number)) + .map((p) => `- #${p.number}: ${p.title}`) + .join("\n") + const prompt = [ `Resolve the current git merge conflicts while merging PR #${pr.number} into the beta branch.`, + `PR #${pr.number}: ${pr.title}`, `Only touch these files: ${files.join(", ")}.`, + "", + "PRs already merged into beta (their changes are on HEAD):", + appliedList, + "", + "PRs still pending after this one:", + remaining || "(none)", + "", + "IMPORTANT: The conflict resolution must be consistent with already-merged PRs.", + "If a PR already deleted a file/directory, do not re-add it.", + "If a PR already changed an import, keep that change.", "Keep the merge in progress, do not abort the merge, and do not create a commit.", "When done, leave the working tree with no unmerged files.", ].join("\n") @@ -119,7 +146,7 @@ async function main() { const files = await conflicts() if (files.length > 0) { console.log(" Failed to merge (conflicts)") - if (!(await fix(pr, files))) { + if (!(await fix(pr, files, prs, applied))) { await cleanup() failed.push({ number: pr.number, title: pr.title, reason: "Merge conflicts" }) await commentOnPR(pr.number, "Merge conflicts with dev branch") From 5655613e60bf7769dd64b2b754fc20d39f7de396 Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:31:57 +1000 Subject: [PATCH 2/6] fix: exclude processed PRs from resolver context --- script/beta.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/script/beta.ts b/script/beta.ts index 61c35151ebec..0f81c3ff0e8b 100755 --- a/script/beta.ts +++ b/script/beta.ts @@ -50,7 +50,7 @@ async function cleanup() { } catch {} } -async function fix(pr: PR, files: string[], prs: PR[], applied: number[]) { +async function fix(pr: PR, files: string[], prs: PR[], applied: number[], idx: number) { console.log(` Trying to auto-resolve ${files.length} conflict(s) with opencode...`) const appliedList = @@ -64,7 +64,8 @@ async function fix(pr: PR, files: string[], prs: PR[], applied: number[]) { : "(none yet)" const remaining = prs - .filter((p) => p.number !== pr.number && !applied.includes(p.number)) + .slice(idx + 1) + .filter((p) => !applied.includes(p.number)) .map((p) => `- #${p.number}: ${p.title}`) .join("\n") @@ -126,7 +127,7 @@ async function main() { const applied: number[] = [] const failed: FailedPR[] = [] - for (const pr of prs) { + for (const [idx, pr] of prs.entries()) { console.log(`\nProcessing PR #${pr.number}: ${pr.title}`) console.log(" Fetching PR head...") @@ -146,7 +147,7 @@ async function main() { const files = await conflicts() if (files.length > 0) { console.log(" Failed to merge (conflicts)") - if (!(await fix(pr, files, prs, applied))) { + if (!(await fix(pr, files, prs, applied, idx))) { await cleanup() failed.push({ number: pr.number, title: pr.title, reason: "Merge conflicts" }) await commentOnPR(pr.number, "Merge conflicts with dev branch") From eddd9e0f3664b456c8eb79559142efad05232cf2 Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:32:46 +1000 Subject: [PATCH 3/6] refactor: simplify beta resolver prompt assembly --- script/beta.ts | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/script/beta.ts b/script/beta.ts index 0f81c3ff0e8b..3f565f1bdc70 100755 --- a/script/beta.ts +++ b/script/beta.ts @@ -53,33 +53,24 @@ async function cleanup() { async function fix(pr: PR, files: string[], prs: PR[], applied: number[], idx: number) { console.log(` Trying to auto-resolve ${files.length} conflict(s) with opencode...`) - const appliedList = - applied.length > 0 - ? applied - .map((n) => { - const p = prs.find((x) => x.number === n) - return `- #${n}: ${p?.title ?? ""}` - }) - .join("\n") - : "(none yet)" - - const remaining = prs - .slice(idx + 1) - .filter((p) => !applied.includes(p.number)) - .map((p) => `- #${p.number}: ${p.title}`) - .join("\n") + const done = + prs + .filter((x) => applied.includes(x.number)) + .map((x) => `- #${x.number}: ${x.title}`) + .join("\n") || "(none yet)" + + const next = + prs + .slice(idx + 1) + .map((x) => `- #${x.number}: ${x.title}`) + .join("\n") || "(none)" const prompt = [ `Resolve the current git merge conflicts while merging PR #${pr.number} into the beta branch.`, `PR #${pr.number}: ${pr.title}`, `Only touch these files: ${files.join(", ")}.`, - "", - "PRs already merged into beta (their changes are on HEAD):", - appliedList, - "", - "PRs still pending after this one:", - remaining || "(none)", - "", + `Merged PRs on HEAD:\n${done}`, + `Pending PRs after this one:\n${next}`, "IMPORTANT: The conflict resolution must be consistent with already-merged PRs.", "If a PR already deleted a file/directory, do not re-add it.", "If a PR already changed an import, keep that change.", From 828069108c285b3205352d0eecfea093f3a53460 Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:36:42 +1000 Subject: [PATCH 4/6] Update beta.ts --- script/beta.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/script/beta.ts b/script/beta.ts index 3f565f1bdc70..8bbf65f14c85 100755 --- a/script/beta.ts +++ b/script/beta.ts @@ -72,6 +72,8 @@ async function fix(pr: PR, files: string[], prs: PR[], applied: number[], idx: n `Merged PRs on HEAD:\n${done}`, `Pending PRs after this one:\n${next}`, "IMPORTANT: The conflict resolution must be consistent with already-merged PRs.", + "Stacked work should be preserved, and may cause conflicts.", + "Ensure merged and pending PRs are prefered over the work in the base branch.", "If a PR already deleted a file/directory, do not re-add it.", "If a PR already changed an import, keep that change.", "Keep the merge in progress, do not abort the merge, and do not create a commit.", From b320a7b5a7740418e3c0103062cdc446aa464d3b Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:37:10 +1000 Subject: [PATCH 5/6] Update beta.ts --- script/beta.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/beta.ts b/script/beta.ts index 8bbf65f14c85..63d2cbd9f6ea 100755 --- a/script/beta.ts +++ b/script/beta.ts @@ -74,7 +74,7 @@ async function fix(pr: PR, files: string[], prs: PR[], applied: number[], idx: n "IMPORTANT: The conflict resolution must be consistent with already-merged PRs.", "Stacked work should be preserved, and may cause conflicts.", "Ensure merged and pending PRs are prefered over the work in the base branch.", - "If a PR already deleted a file/directory, do not re-add it.", + "If a PR already deleted a file/directory, do not re-add it, instead apply changes in the new semantic location.", "If a PR already changed an import, keep that change.", "Keep the merge in progress, do not abort the merge, and do not create a commit.", "When done, leave the working tree with no unmerged files.", From 4cfe2b967ce4777cc5d417c9ea487e61d5b438a2 Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:38:29 +1000 Subject: [PATCH 6/6] fix: treat pending beta PRs as context only --- script/beta.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/script/beta.ts b/script/beta.ts index 63d2cbd9f6ea..2c3ed88b0c81 100755 --- a/script/beta.ts +++ b/script/beta.ts @@ -70,10 +70,10 @@ async function fix(pr: PR, files: string[], prs: PR[], applied: number[], idx: n `PR #${pr.number}: ${pr.title}`, `Only touch these files: ${files.join(", ")}.`, `Merged PRs on HEAD:\n${done}`, - `Pending PRs after this one:\n${next}`, + `Pending PRs after this one (context only):\n${next}`, "IMPORTANT: The conflict resolution must be consistent with already-merged PRs.", - "Stacked work should be preserved, and may cause conflicts.", - "Ensure merged and pending PRs are prefered over the work in the base branch.", + "Pending PRs are context only; do not introduce their changes unless they are already present on HEAD.", + "Prefer already-merged PRs over the base branch when resolving stacked conflicts.", "If a PR already deleted a file/directory, do not re-add it, instead apply changes in the new semantic location.", "If a PR already changed an import, keep that change.", "Keep the merge in progress, do not abort the merge, and do not create a commit.",