Submatter/apps/worker/src/git/worktree.ts
Randall Stillwell 6ccb060bea feat(second-brain): open self-hosted Obsidian for crews — harness-agnostic v0.1
- MCP create-backlog-item for natural language Plan/Epic/Task authoring
- Worker non-code: optional worktree + per-harness allowlist + doc_review→published
- Harness registry: harness_providers/role_harness_routes, WorkerAdapter (minimal|crewai|langgraph|remote-ag-ui), AG-UI POST /api/ag-ui, Settings → Harnesses with Test
- Plans tree: backlog.listTree + createPlan/Epic/Task + Start→Convoy, web CRUD without CLI
- Fleet: /fleet rollup across workspaces + SSE /api/fleet/events

Decision: docs/decisions/2026-08-25-second-brain-os-pivot.md
Convoy: .convoys/second-brain-os-v01

Co-authored-by: orchestrator
2026-08-25 16:20:53 -05:00

100 lines
2.5 KiB
TypeScript

import { access } from "node:fs/promises";
import { mkdir } from "node:fs/promises";
import { join } from "node:path";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
export type WorktreeContext = {
repoRoot: string;
worktreePath: string;
branch: string;
};
async function pathExists(path: string): Promise<boolean> {
try {
await access(path);
return true;
} catch {
return false;
}
}
/** Reuse an existing worktree directory when present (crash resume without re-clone). */
export async function reuseWorktreeIfPresent(
repoRoot: string,
worktreePath: string,
branch: string | null,
): Promise<WorktreeContext | null> {
if (!(await pathExists(worktreePath))) {
return null;
}
try {
const { stdout } = await execFileAsync("git", ["rev-parse", "--show-toplevel"], {
cwd: worktreePath,
});
if (stdout.trim() !== worktreePath && !(await pathExists(join(worktreePath, ".git")))) {
return null;
}
} catch {
return null;
}
let resolvedBranch = branch ?? "HEAD";
if (!branch) {
try {
const { stdout } = await execFileAsync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
cwd: worktreePath,
});
resolvedBranch = stdout.trim() || "HEAD";
} catch {
resolvedBranch = "HEAD";
}
}
return { repoRoot, worktreePath, branch: resolvedBranch };
}
export async function ensureWorktree(
repoRoot: string,
worktreeBase: string,
worktreeKey: string,
branchSuffix: string,
opts?: { needsWorktree?: boolean },
): Promise<WorktreeContext | null> {
if (opts?.needsWorktree === false) {
return null;
}
const branch = `worker/${branchSuffix}`.replace(/[^a-zA-Z0-9/_-]/g, "-");
const worktreePath = join(worktreeBase, worktreeKey);
await mkdir(worktreeBase, { recursive: true });
const existing = await reuseWorktreeIfPresent(repoRoot, worktreePath, branch);
if (existing) {
return existing;
}
try {
await execFileAsync("git", ["worktree", "add", "-B", branch, worktreePath], {
cwd: repoRoot,
});
} catch {
await execFileAsync("git", ["worktree", "add", worktreePath, branch], {
cwd: repoRoot,
});
}
return { repoRoot, worktreePath, branch };
}
export async function removeWorktree(repoRoot: string, worktreePath: string): Promise<void> {
try {
await execFileAsync("git", ["worktree", "remove", "--force", worktreePath], {
cwd: repoRoot,
});
} catch {
// Best-effort cleanup; operator can prune stale worktrees manually.
}
}