Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 95 additions & 13 deletions script/beta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import { $ } from "bun"

const model = "opencode/gpt-5.3-codex"

interface PR {
number: number
title: string
Expand Down Expand Up @@ -50,20 +52,35 @@ async function cleanup() {
} catch {}
}

function lines(prs: PR[]) {
return prs.map((x) => `- #${x.number}: ${x.title}`).join("\n") || "(none)"
}

async function typecheck() {
try {
await $`bun typecheck`.cwd("packages/opencode")
return true
} catch (err) {
console.log(`Typecheck failed: ${err}`)
return false
}
}

async function build() {
try {
await $`./script/build.ts --single`.cwd("packages/opencode")
return true
} catch (err) {
console.log(`Build failed: ${err}`)
return false
}
}

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 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 done = lines(prs.filter((x) => applied.includes(x.number)))
const next = lines(prs.slice(idx + 1))

const prompt = [
`Resolve the current git merge conflicts while merging PR #${pr.number} into the beta branch.`,
Expand All @@ -76,12 +93,14 @@ async function fix(pr: PR, files: string[], prs: PR[], applied: number[], idx: n
"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.",
"After resolving the conflicts, run `bun typecheck` in `packages/opencode`.",
"Fix any merge-caused typecheck errors before finishing.",
"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.",
"When done, leave the working tree with no unmerged files and a passing typecheck.",
].join("\n")

try {
await $`opencode run -m opencode/gpt-5.3-codex ${prompt}`
await $`opencode run -m ${model} ${prompt}`
} catch (err) {
console.log(` opencode failed: ${err}`)
return false
Expand All @@ -93,10 +112,66 @@ async function fix(pr: PR, files: string[], prs: PR[], applied: number[], idx: n
return false
}

if (!(await typecheck())) return false

console.log(" Conflicts resolved with opencode")
return true
}

async function smoke(prs: PR[], applied: number[]) {
console.log("\nRunning final smoke check with opencode...")

const done = lines(prs.filter((x) => applied.includes(x.number)))
const prompt = [
"The beta merge batch is complete.",
`Merged PRs on HEAD:\n${done}`,
"Run `bun typecheck` in `packages/opencode`.",
"Run `./script/build.ts --single` in `packages/opencode`.",
"Fix any merge-caused issues until both commands pass.",
"Do not create a commit.",
].join("\n")

try {
await $`opencode run -m ${model} ${prompt}`
} catch (err) {
console.log(`Smoke fix failed: ${err}`)
return false
}

if (!(await typecheck())) {
return false
}

if (!(await build())) {
return false
}

const out = await $`git status --porcelain`.text()
if (!out.trim()) {
console.log("Smoke check passed")
return true
}

try {
await $`git add -A`
Comment on lines +155 to +156
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The smoke check auto-commits any dirty working tree via git add -A / git commit. As written, this can accidentally include files outside the intended smoke-fix scope (e.g., untracked/generated files created during the smoke commands). Consider restricting what gets staged (limit to known paths) or failing with a clear message listing git status --porcelain entries so a human can review, rather than committing everything blindly.

Suggested change
try {
await $`git add -A`
// Only allow auto-committing changes within packages/opencode.
// If there are changes elsewhere, list them and abort so a human can review.
const statusLines = out.trim().split("\n")
const unsafeChanges = statusLines.filter((line) => {
// Porcelain format: "XY path" (at least 3 chars; path may contain spaces but starts at index 3).
const path = line.length > 3 ? line.slice(3).trim() : ""
return !(path === "packages/opencode" || path.startsWith("packages/opencode/"))
})
if (unsafeChanges.length > 0) {
console.log("Smoke check produced changes outside packages/opencode; refusing to auto-commit.")
console.log("Please review and handle these changes manually:")
console.log(unsafeChanges.join("\n"))
return false
}
try {
await $`git add packages/opencode`

Copilot uses AI. Check for mistakes.
await $`git commit -m "Fix beta integration"`
} catch (err) {
console.log(`Failed to commit smoke fixes: ${err}`)
return false
}

if (!(await typecheck())) {
return false
}

if (!(await build())) {
return false
}

console.log("Smoke check passed")
return true
}

async function main() {
console.log("Fetching open PRs with beta label...")

Expand Down Expand Up @@ -195,6 +270,13 @@ async function main() {
throw new Error(`${failed.length} PR(s) failed to merge`)
}

if (applied.length > 0) {
const ok = await smoke(prs, applied)
if (!ok) {
throw new Error("Final smoke check failed")
}
}

console.log("\nChecking if beta branch has changes...")
await $`git fetch origin beta`

Expand Down
Loading