import * as git from "../git.ts" import * as vm from "../vm.ts" import { spinner } from "../spinner.ts" import { die } from "../fmt.ts" import { requireSession } from "./helpers.ts" export async function action(branch: string) { const { session } = await requireSession(branch) const worktree = session.worktree if (await git.isDirty(worktree)) { die(`Branch "${branch}" has unsaved changes. Run "sandlot save ${branch}" first.`) } const main = await git.mainBranch(worktree) if (!await git.hasNewCommits(worktree)) { die(`Branch "${branch}" has no commits beyond ${main}.`) } const base = await git.mergeBase(main, "HEAD", worktree) const originalHead = await git.headRef(worktree) const spin = spinner("Squashing", branch) try { await git.resetSoft(base, worktree) spin.text = "Starting container" await vm.ensure((msg) => { spin.text = msg }) spin.text = "Generating commit message" const diff = await git.diffStaged(worktree) if (!diff.trim()) { await git.resetSoft(originalHead, worktree) spin.fail("No changes after squash") return } const gen = await vm.claudePipe( diff, "Write a commit message summarizing all changes. Subject line: imperative mood, max 72 characters, no period. Add a blank line then a concise body with the key changes as bullet points. Output only the raw commit message, no quotes or markdown.", ) const msg = (gen.exitCode === 0 && gen.stdout.trim()) || `squash ${branch}` await git.commit(msg, worktree) spin.succeed(`Squashed ${branch} into a single commit`) } catch (err) { const restored = await git.resetSoft(originalHead, worktree).then(() => true).catch(() => false) const message = err instanceof Error ? err.message : String(err) spin.fail(restored ? `Squash failed, changes restored: ${message}` : `Squash failed and rollback failed — check "git reflog" in the worktree: ${message}`) process.exit(1) } }