import { createWorktree, type Worktree } from "./manager.js"; import { assertDisjointWriterScopes, assertOwnedPaths } from "./ownership.js"; import { worktreeChangedPaths } from "./integration.js"; export interface ParallelWriterPlan { nodeId: string; scope: string[]; } export interface ParallelWriterResult { nodeId: string; scope: string[]; worktree: string; baseCommit: string; changedPaths: string[]; } export const PARALLEL_WRITER_LIMIT = 4; export function assertParallelWriterPlans(plans: readonly ParallelWriterPlan[]): void { if (plans.length < 2) throw new Error("Parallel writers require at least two plans"); if (plans.length > PARALLEL_WRITER_LIMIT) throw new Error(`Parallel writers are capped at ${PARALLEL_WRITER_LIMIT}`); const ids = new Set(); for (const plan of plans) { if (!/^[\w-]+$/.test(plan.nodeId)) throw new Error(`Invalid parallel writer node id ${plan.nodeId}`); if (ids.has(plan.nodeId)) throw new Error(`Duplicate parallel writer node id ${plan.nodeId}`); ids.add(plan.nodeId); if (!plan.scope.length) throw new Error(`Parallel writer ${plan.nodeId} requires a declared scope`); } assertDisjointWriterScopes(plans.map((plan) => plan.scope)); } export async function createParallelWorktrees(repo: string, root: string, runId: string, plans: readonly ParallelWriterPlan[]): Promise> { assertParallelWriterPlans(plans); const created: Array = []; for (const plan of plans) created.push({ ...plan, worktree: await createWorktree(repo, root, runId, plan.nodeId) }); return created; } export async function collectParallelWriterResults(writers: ReadonlyArray): Promise { const results: ParallelWriterResult[] = []; const claimed = new Map(); for (const writer of writers) { const changedPaths = await worktreeChangedPaths(writer.worktree.path); assertOwnedPaths(changedPaths, writer.scope); for (const path of changedPaths) { const owner = claimed.get(path); if (owner && owner !== writer.nodeId) throw new Error(`Parallel writers ${owner} and ${writer.nodeId} both changed ${path}`); claimed.set(path, writer.nodeId); } results.push({ nodeId: writer.nodeId, scope: writer.scope, worktree: writer.worktree.path, baseCommit: writer.worktree.baseCommit, changedPaths }); } return results; } export function parallelWriterHandoff(results: readonly ParallelWriterResult[]): string { const lines = results.map((result) => `${result.nodeId}\n worktree: ${result.worktree}\n base: ${result.baseCommit}\n changed: ${result.changedPaths.join(", ") || "nothing"}`); return [ "Parallel writers finished in separate worktrees. Nothing was merged.", "Review each worktree and integrate the ones you accept; the controller does not combine them.", ...lines, ].join("\n"); }