Skip to content

feat(work-driver): repoRoot is never a dev tree — always-worktree branch mechanization + integration at repoRoot only #287

Description

@randomm

Context

Incident #602: stale repoRoot residue was swept into a merged PR because for N=1 workstreams runBranch sets worktrees = {default: ctx.repoRoot} (work-driver.ts:1274-1277) — development happens directly at repoRoot. The ops branch dispatch also mutates the repoRoot checkout (git fetch/checkout <mainline>/pull --ff-only, inlineBranchPrompt work-driver.ts:4459-4461), and mechanizedCommitPr does git checkout <branch> at repoRoot (:1866) with an in-place stagePorcelainPaths case (:1886). These are the hard serial-only assumptions blocking parallel groups, and the residue cause #283 only detects.

Target architecture: ALL development happens in worktrees (even N=1); repoRoot is the integration point only — worktree patches are applied onto the group's feature branch at repoRoot, committed, pushed, PR'd (mirrors the old PM-driven flow).

Latent bug fixed structurally: inlineLensFixPrompt says "Do NOT commit" (:4696) and nothing pushes after lens-fix — in worktree mode lens-fix edits would never reach the PR.

This work must ship as its own separate PR, independent of any other open issue. Note: #277 touches verifyStepOutcome (:2385-2516); land #277 first and rebase over it.

What to build

A. Mechanized branch setup (runBranch, work-driver.ts:1261-1296, behind mechanizeOpsEnabled() :1769-1771): replace the ops branch dispatch with: git fetch origin <mainline>; baseSha = git rev-parse origin/<mainline> (replacing repoRoot-HEAD read at :1287-1290); branch name = deterministic slug from cached issue title (feature/issue-N-<slug>); create one worktree per workstream via worktreeCreate (worktree.ts:19-27, extended to accept absolute repo path + --detach fromRef, detached at baseSha — no scratch branches). Delete the {default: ctx.repoRoot} N=1 case (:1274-1277): N=1 gets .worktrees/issue-N/default. No git command mutates repoRoot in this step.

B. Integration helper — extract integrate(ctx, patches, opts): preflight git status --porcelain clean at repoRoot (#283's gate relocated) → git checkout -B <branch> <baseSha> (replacing plain checkout at :1866) → per-workstream git -C <worktree> diff --cached/diff HEADgit apply --index at repoRoot (:1891-1901; delete the in-place repoRoot case :1886 — N=1 uses the sibling-patch path too) → commit → push → optional gh pr create. mechanizedCommitPr (:1843-1990) uses it fully.

C. Lens-fix re-integrate: on adversarial-approved from lens-fix (route table :466), before lens-review, apply the worktree's new diff as a follow-up commit + push via integrate. Update inlinePlanPrompt/inlineLensFixPrompt wording accordingly.

D. Read paths off repoRoot HEAD: fetchAllMergedDiffs (:3047-3070) diffs origin/<base>..origin/<branch>; captureWorktreeSnapshot (:3646-3685) snapshots the group's worktrees + git ls-remote origin <branch>. Delete ?? repoRoot cwd fallbacks at :1386, :1611, :2657.

E. Cleanup: runMerged best-effort worktreeRemove(…, force) + git worktree prune.

Schema (additive): pipelineState.integration?: { integratedAt?: number; reintegrations?: number }.
Knobs: rides PI_ENSEMBLE_MECHANIZE_OPS=0; new PI_ENSEMBLE_ALWAYS_WORKTREE=0 restores old N=1 behavior.
Failure routing: worktree-create failure → dispatch-failed on branch → handoff; git apply conflict → cap-hit → handoff with patch path in scratchDir named in the comment.

Acceptance criteria

  • End-to-end runWorkDriver test (injected verifyExecFn recording command+cwd): no git command with cwd === repoRoot between branch and commit-pr.
  • N=1 fixture produces a real .worktrees/issue-N/default path; worktrees[id] can never resolve to repoRoot.
  • checkout -B <branch> <baseSha> ordering asserted; preflight-dirty repoRoot → handoff.
  • Lens-fix approval emits apply + commit + push (re-integrate) before lens-review.
  • fetchAllMergedDiffs/captureWorktreeSnapshot fixtures show no repoRoot-HEAD reads.
  • PI_ENSEMBLE_ALWAYS_WORKTREE=0 restores current N=1 behavior.
  • tsc + biome + smoke tests green; docs (README env-knob table + architecture notes) updated.

Out of scope

Parallel group execution (separate issue); spawn caps/widget multiplexing (separate issue); plan decomposition (separate issue); worktree dep-bootstrap optimization (node_modules hardlinking — follow-up issue); grouping rules R1-R5.

Test plan

Offline: smoke-test fixtures per acceptance criteria via verifyExecFn/dispatchFn injection; N=1 and N=3 workstream fixtures; apply-conflict fixture → handoff. Live on nessie: single-issue /work cycle end-to-end; verify PR contains only worktree-authored changes with a deliberately dirtied repoRoot file left untouched; verify lens-fix commit lands on the PR; verify worktree cleanup after merge.


Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions