Route worktree creation into the configured parent directory #322

Closed
opened 2026-11-05 21:42:44 +00:00 by david · 1 comment
Owner

Summary

Make resolveWorkDir create new worktrees under a configured parent directory (worktree.dir), and enable worktree creation for normal clones when that directory is in effect — while the worktree registry keeps priority and a clone without a configured dir keeps running in place.

Background

src/shared/git/services/resolveWorktree.ts exports resolveWorkDir({ originalCwd, runGit, issueKey, issueSummary, provider, ensureWorktreeFn?, findExistingWorktreeFn?, registerWorktreeFn?, fsStat? }) (tests in resolveWorktree.test.ts). Current flow:

  1. isInsideWorktree fast path → return originalCwd (a run started inside a worktree reuses that tree).
  2. Not bare → return originalCwd unchanged (normal clones run in place).
  3. Bare → parentDir = dirname(originalCwd), worktreeName = buildBranchFolderName(issueKey, issueSummary, provider), registry lookup findExistingWorktreeFn({ bareRepoPath, issueKey }) (registry lives at <launchRoot>/.pi-loop/worktrees/<issueKey> via gitWorktree.ts); if found → return the existing path; else resolveDefaultBranch + ensureWorktreeFn({ runGit, bareRepoPath, parentDir, worktreeName, branchRef }) + registerWorktreeFn on creation.

ensureWorktree already joins parentDir/worktreeName and runs git worktree add --detach from bareRepoPath — no change needed there: --detach means no branch is checked out at creation, so a linked worktree created from a normal clone behaves the same as one created from a bare repo.

ADR-022 decisions this step implements:

  • §5: the registry is consulted before ensureWorktree, so a configured dir only affects new worktree creation; a resume must never move a work-in-progress tree.
  • §6: with a configured dir in effect, a normal clone gets a linked worktree under it; without one, a normal clone runs in place (unchanged).
  • §7: state (worktree registry, run artifacts) stays at the launch repo root — for a normal clone, bareRepoPath must be set to originalCwd because the registry lives at the launch root.
  • The parent dir is computed after the registry lookup, so a resume never reads or depends on it.

Depends on: #320 — the value this input carries is parsed there.

Implementation Details

Files: src/shared/git/services/resolveWorktree.ts, src/shared/git/services/resolveWorktree.test.ts.

  1. Add worktreeDir: string | undefined to ResolveWorkDirInput.
  2. Remove the bare-only early return. New gate:
    • inside a worktree → return originalCwd (unchanged);
    • not bare: worktreeDir === undefined → return originalCwd unchanged (regression guard); worktreeDir defined → continue (create a linked worktree);
    • bare → continue (unchanged).
  3. const parentDir = worktreeDir ?? dirname(originalCwd) — computed after the registry lookup.
  4. bareRepoPath = originalCwd for both repo kinds that continue (the registry lives at the launch root), and resolveDefaultBranch runs against it.
  5. Everything downstream (registry lookup, resolveDefaultBranch, ensureWorktree, registerWorktree) is unchanged.
  6. Update the ResolvedWorkDir / resolveWorkDir doc comments: bareRepoPath is the path to the repo holding the worktree registry (the bare repo, or the launch root for a normal clone in worktree mode); the "non-bare repo → original cwd" paragraph must reflect the new gate.

Acceptance Criteria

  • All existing resolveWorktree.test.ts tests pass unchanged when worktreeDir is undefined.
  • Bare repo + worktreeDir: '/srv/wt' → ensureWorktreeFn is called with parentDir: '/srv/wt'.
  • Normal clone + worktreeDir defined → a worktree is created (today it returns originalCwd), and the registry entry is written under originalCwd/.pi-loop/worktrees/<key>.
  • Normal clone + worktreeDir undefined → returns originalCwd, isWorktree: false (regression guard).
  • Registry entry present → ensureWorktreeFn is never called, regardless of worktreeDir; the registered path is returned.
  • npm run lint and npm test pass.

Test Plan

Extend resolveWorktree.test.ts with the cases above (existing tests use injectable ensureWorktreeFn/findExistingWorktreeFn/registerWorktreeFn doubles — follow that style). Run npm test.

## Summary Make `resolveWorkDir` create new worktrees under a configured parent directory (`worktree.dir`), and enable worktree creation for **normal clones** when that directory is in effect — while the worktree registry keeps priority and a clone without a configured dir keeps running in place. ## Background `src/shared/git/services/resolveWorktree.ts` exports `resolveWorkDir({ originalCwd, runGit, issueKey, issueSummary, provider, ensureWorktreeFn?, findExistingWorktreeFn?, registerWorktreeFn?, fsStat? })` (tests in `resolveWorktree.test.ts`). Current flow: 1. `isInsideWorktree` fast path → return `originalCwd` (a run started inside a worktree reuses that tree). 2. Not bare → return `originalCwd` unchanged (normal clones run in place). 3. Bare → `parentDir = dirname(originalCwd)`, `worktreeName = buildBranchFolderName(issueKey, issueSummary, provider)`, registry lookup `findExistingWorktreeFn({ bareRepoPath, issueKey })` (registry lives at `<launchRoot>/.pi-loop/worktrees/<issueKey>` via `gitWorktree.ts`); if found → return the existing path; else `resolveDefaultBranch` + `ensureWorktreeFn({ runGit, bareRepoPath, parentDir, worktreeName, branchRef })` + `registerWorktreeFn` on creation. `ensureWorktree` already joins `parentDir`/`worktreeName` and runs `git worktree add --detach` from `bareRepoPath` — **no change needed there**: `--detach` means no branch is checked out at creation, so a linked worktree created from a normal clone behaves the same as one created from a bare repo. ADR-022 decisions this step implements: - §5: the registry is consulted **before** `ensureWorktree`, so a configured dir only affects **new** worktree creation; a resume must never move a work-in-progress tree. - §6: with a configured dir in effect, a normal clone gets a **linked worktree** under it; without one, a normal clone runs in place (unchanged). - §7: state (worktree registry, run artifacts) stays at the **launch** repo root — for a normal clone, `bareRepoPath` must be set to `originalCwd` because the registry lives at the launch root. - The parent dir is computed **after** the registry lookup, so a resume never reads or depends on it. **Depends on:** #320 — the value this input carries is parsed there. ## Implementation Details Files: `src/shared/git/services/resolveWorktree.ts`, `src/shared/git/services/resolveWorktree.test.ts`. 1. Add `worktreeDir: string | undefined` to `ResolveWorkDirInput`. 2. Remove the bare-only early return. New gate: - inside a worktree → return `originalCwd` (unchanged); - not bare: `worktreeDir === undefined` → return `originalCwd` unchanged (regression guard); `worktreeDir` defined → continue (create a linked worktree); - bare → continue (unchanged). 3. `const parentDir = worktreeDir ?? dirname(originalCwd)` — computed **after** the registry lookup. 4. `bareRepoPath = originalCwd` for both repo kinds that continue (the registry lives at the launch root), and `resolveDefaultBranch` runs against it. 5. Everything downstream (registry lookup, `resolveDefaultBranch`, `ensureWorktree`, `registerWorktree`) is unchanged. 6. Update the `ResolvedWorkDir` / `resolveWorkDir` doc comments: `bareRepoPath` is the path to the repo holding the worktree registry (the bare repo, or the launch root for a normal clone in worktree mode); the "non-bare repo → original cwd" paragraph must reflect the new gate. ## Acceptance Criteria - [ ] All existing `resolveWorktree.test.ts` tests pass unchanged when `worktreeDir` is undefined. - [ ] Bare repo + `worktreeDir: '/srv/wt'` → `ensureWorktreeFn` is called with `parentDir: '/srv/wt'`. - [ ] Normal clone + `worktreeDir` defined → a worktree is created (today it returns `originalCwd`), and the registry entry is written under `originalCwd/.pi-loop/worktrees/<key>`. - [ ] Normal clone + `worktreeDir` undefined → returns `originalCwd`, `isWorktree: false` (regression guard). - [ ] Registry entry present → `ensureWorktreeFn` is never called, regardless of `worktreeDir`; the registered path is returned. - [ ] `npm run lint` and `npm test` pass. ## Test Plan Extend `resolveWorktree.test.ts` with the cases above (existing tests use injectable `ensureWorktreeFn`/`findExistingWorktreeFn`/`registerWorktreeFn` doubles — follow that style). Run `npm test`.
david closed this issue 2026-11-06 02:44:18 +00:00
Author
Owner

pi-loop opened and merged a pull request for this issue: #328

pi-loop opened and merged a pull request for this issue: https://git.excelera.net/david/pi-loop/pulls/328
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
2025-12-31
Dependencies

No dependencies set

Reference
david/pi-loop#322
No description provided.