Files
LLeMbas-CLI/src/git/worktree.ts
T
HomerandClaude Opus 5.5 f9bad01ed7
ci / check (push) Waiting to run
LLeMbas CLI 1.0.0
The first public release of LLeMbas CLI: a terminal coding agent and project manager for any LLM
API, with permission modes, git snapshots, memory and skills, knowledge bases, MCP, voice, and a
link to a LLeMbas instance whose web UI can work its sessions too. Signed Linux binaries for x64
and arm64.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:59:03 +00:00

58 lines
2.8 KiB
TypeScript

// Worktrees for subagents: a subagent that changes files can work in a checkout of its own, on a
// branch of its own, so two of them (or one and you) never edit the same files at once. When it
// is done its changes are committed on that branch and the checkout goes; the branch stays for
// you to merge or delete. A worktree starts from HEAD: uncommitted work is not in it.
import { mkdirSync } from "node:fs"
import { join } from "node:path"
import { createHash } from "node:crypto"
import { paths } from "../config/paths.ts"
import { git } from "./run.ts"
export interface Worktree {
dir: string
branch: string
base: string
}
export function createWorktree(repo: string, label: string): Worktree {
const id = Math.random().toString(36).slice(2, 8)
const slug = label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 30) || "agent"
const branch = `lembas/${slug}-${id}`
const dir = join(paths.data, "worktrees", createHash("sha256").update(repo).digest("hex").slice(0, 12), `${slug}-${id}`)
mkdirSync(join(dir, ".."), { recursive: true })
// Records of checkouts whose directories are gone (a crash, a manual rm) go first.
git(repo, ["worktree", "prune"])
const base = git(repo, ["rev-parse", "HEAD"]).out
if (!base) throw new Error("the repository has no commit yet, so there is nothing to branch from")
const r = git(repo, ["worktree", "add", "-q", "-b", branch, dir, base])
if (r.code) throw new Error(`git worktree add: ${r.err}`)
return { dir, branch, base }
}
export interface WorktreeResult {
/** What to tell the model and the user. */
summary: string
kept: boolean
}
/** Commit what the subagent left, remove the checkout, and keep the branch only if it has work. */
export function finishWorktree(repo: string, wt: Worktree, message: string): WorktreeResult {
// The checkout is the subagent's own, so everything in it is its work.
git(wt.dir, ["add", "-A"])
if (git(wt.dir, ["diff", "--cached", "--quiet"]).code !== 0) {
const c = git(wt.dir, ["commit", "-q", "-m", message])
if (c.code) return { summary: `Its changes could not be committed (${c.err || c.out}); they are still in ${wt.dir} on branch ${wt.branch}.`, kept: true }
}
const commits = Number(git(repo, ["rev-list", "--count", `${wt.base}..${wt.branch}`]).out || "0")
git(repo, ["worktree", "remove", "--force", wt.dir])
if (!commits) {
git(repo, ["branch", "-D", wt.branch])
return { summary: "It changed nothing, so its branch was removed.", kept: false }
}
const stat = git(repo, ["diff", "--shortstat", wt.base, wt.branch]).out.trim()
return {
summary: `Its work is on branch ${wt.branch} (${commits} commit${commits > 1 ? "s" : ""}: ${stat}), not in your working tree. Look with \`git diff HEAD...${wt.branch}\`; take it with \`git merge ${wt.branch}\`; drop it with \`git branch -D ${wt.branch}\`.`,
kept: true,
}
}