# Nucleic — Git / Worktree Manager (v0) The Conductor-defining feature: each session runs in an isolated `git worktree` + branch so many agents work the same repo in parallel without colliding. This component owns worktree lifecycle, diffing, and integration (merge / rebase / squash / PR) back to the main line. **Status:** design draft. Git commands are spelled out so the plumbing is reviewable; they're the adapter detail, the Swift surface (§7) is the stable contract. --- ## 1. Invariants 1. **One session ↔ one worktree ↔ one branch.** Never shared, never reused across sessions. 2. **The main working copy is never touched.** All agent activity happens in a worktree; the user's primary checkout stays clean and usable. 3. **Structural git ops are serialized per repo.** `worktree add/remove`, branch create, and integration take a per-repo lock. Working-tree edits inside a worktree are *not* locked — they're isolated by design (each worktree has its own index) and run fully in parallel. 4. **Survivable.** A crash may leave worktrees/branches behind; startup reconciliation makes the DB and `git worktree list` agree again. No silent data loss. 5. **Agent work may be uncommitted.** We treat the base commit as the diff anchor and capture working-tree changes whether or not the agent committed (§4). --- ## 2. Layout & naming ``` / ← user's primary checkout (untouched) /.git/worktrees// ← git's per-worktree metadata (automatic) /.nucleic/worktrees// ← the agent's isolated checkout ``` - **`worktreeBase`** default: inside the project under `.nucleic/worktrees/`, on the **same volume** (fast checkout, no cross-device copies). `.nucleic/` is added to the repo's local `.git/info/exclude` on first worktree creation so the user's primary checkout never reports it as untracked and the tracked `.gitignore` is never modified. Default `/.nucleic/worktrees/`; user-configurable per project via the `worktreeBase` override (which keeps the `//` layout for a shared external dir). - **Branch:** `nucleic/` where `sessionSlug` derives from the session title, lowercased/kebabbed, deduped with a short suffix on collision. - **Worktree dir name** = `sessionSlug`. Both branch and dir collisions are checked under the per-repo lock before creation. --- ## 3. Lifecycle state machine ``` create() setup ok (none) ───────────────▶ provisioning ─────────▶ ready ──▶ active (agent running) │ fail │ ▼ ▼ finalize() failed finalizing │ integrate() / discard() ▼ cleaned ◀── removeWorktree ◀── merged | discarded ◀── integrated | conflicted ``` - **provisioning → ready:** create worktree+branch, run setup script. Any failure rolls back (remove the half-made worktree/branch) and lands in `failed`. - **active:** the `AgentBackend` runs with `cwd = worktree.path` (this is the `WorktreePath` in BACKEND_PROTOCOL `RunSpec`). - **conflicted:** integration hit conflicts; user resolves in the worktree or aborts. --- ## 4. Status & diff (live UI source) The session's **base commit SHA** is captured at creation (`git rev-parse `), stored, and used as the diff anchor for the session's lifetime. - **Everything-vs-base (tracked):** `git diff --no-color ` compares the *working tree* to the base commit, so it captures committed, staged, and unstaged tracked changes in one shot — independent of whether the agent committed. - **Untracked new files:** enumerate via `git status --porcelain=v2 --untracked-files=all`; to render their diffs, use intent-to-add semantics (`git add -N`-style) in a scratch read so new files show as additions. (We do this without mutating the agent's index where possible.) - **Diffstat / file list:** parsed from `git status --porcelain=v2` (rename/copy aware). - **Ahead/behind vs integration target:** `git rev-list --left-right --count ...HEAD` → `(behind, ahead)`. - **Dirty/clean:** porcelain output empty ⇒ clean. Status is recomputed on demand and after each turn completes (cheap; can be debounced). The diff feeds the macOS live-diff pane and the iPhone snapshot `diffStat`. --- ## 5. Setup script Per-project, optional (e.g. `npm install`, `bundle install`): - Runs in the new worktree, `cwd = worktree.path`, with project `extraEnv`, **after** branch creation and **before** the session is marked `ready`. - Stdout/stderr captured and surfaced as synthetic `AgentEvent.raw`/log entries so the user sees provisioning output in the session view. - **Timeout** + failure policy per project: `block` (session can't start) or `warn` (proceed, flag it). Default `block` so agents don't run against a broken environment. --- ## 6. Integration (finish a session) Two phases: **finalize** (make the branch represent the work) then **integrate** (land it). ### 6.1 Finalize — handle uncommitted work `CommitOption`: - `.auto(message)` — stage all (`git add -A`) and commit a snapshot so the branch is clean. Default; message templated from session title. - `.manual` — require the agent/user to have committed; refuse to integrate a dirty tree. - `.none` — leave as-is (only valid for `discard`). ### 6.2 Integrate — land the branch `IntegrationStrategy` into a target ref (default = project default branch): - `.merge` — `git merge --no-ff nucleic/` (preserves the session as a merge). - `.rebase` — `git rebase ` then fast-forward (linear history). - `.squash` — `git merge --squash` + single commit (one tidy commit per session). Performed on the **target's** worktree/checkout context under the per-repo lock. Result: ```swift enum IntegrationResult { case clean(mergedInto: GitRef, commit: String) case conflicted([ConflictedPath]) // → state `conflicted` } ``` **Conflicts:** detected from a non-zero merge/rebase exit + `git status`. We surface the conflicted paths in the UI and offer: (a) resolve in the worktree (optionally hand the conflict back to an agent as a new turn), then continue; or (b) abort (`git merge/rebase --abort`) and leave the branch intact. ### 6.3 PR flow (alternative to local integration) `git push -u nucleic/` then `gh pr create --base --head nucleic/ --title … --body …`. Returns the PR URL. Requires a configured remote + `gh` auth; the local branch/worktree can then be cleaned or kept. ### 6.4 Discard Confirm if the branch has unmerged commits or a dirty tree, then `git worktree remove --force ` + `git branch -D nucleic/`. --- ## 7. Swift surface ```swift protocol WorktreeManager: Sendable { func create(for s: SessionID, in p: Project, base: GitRef, slug: String) async throws -> Worktree func status(of w: Worktree) async throws -> WorktreeStatus func diff(of w: Worktree, _ opts: DiffOptions) async throws -> Diff func finalize(_ w: Worktree, commit: CommitOption) async throws func integrate(_ w: Worktree, into target: GitRef, strategy: IntegrationStrategy) async throws -> IntegrationResult func createPR(_ w: Worktree, base: GitRef, title: String, body: String) async throws -> URL func discard(_ w: Worktree, force: Bool) async throws func reconcile(_ p: Project) async throws -> [Orphan] // startup cleanup (§8) } struct Worktree: Sendable, Codable { let sessionID: SessionID let path: String let branch: String let baseSHA: String let createdAt: Date } struct WorktreeStatus: Sendable, Codable { let isDirty: Bool let ahead: Int let behind: Int let diffStat: DiffStat // files, +adds, −removes let conflicts: [String] // non-empty ⇒ state conflicted } ``` --- ## 8. Reconciliation & cleanup (crash recovery) On app launch, per registered project: 1. `git worktree list --porcelain` ⨉ our DB of expected worktrees. 2. **In git, not in DB** → orphan; offer cleanup (`git worktree remove`). 3. **In DB, not in git** → stale record; mark the session `error`/`cleaned`. 4. `git worktree prune` to clear deleted-dir metadata. 5. Sessions left `active` from a previous run (process gone) → transition to `interrupted`; the worktree is intact and the session is resumable (BACKEND_PROTOCOL §6). Guardrails: configurable **max concurrent worktrees per repo**, a **disk-space preflight** before `create`, and a warning when worktree count or total size crosses a threshold. --- ## 9. Considerations / open questions 1. **`worktreeBase` location** — in-project `.nucleic/worktrees/` (default, same-volume, git-excluded) vs. app-support (centralized but volume-crossing risk). Exposed as a per-project `worktreeBase` override. 2. **Auto-commit cadence** — only at finalize, or periodic snapshot commits during a run so the live diff has a stable base and work is recoverable? (Leaning: finalize-only, with working-tree diff covering interim state.) 3. **Submodules / Git LFS / huge repos** — worktree add can be slow or surprising with submodules and LFS; decide whether to `--no-checkout` + sparse for big repos, and how setup interacts with LFS smudge. 4. **Shared-ref contention** — concurrent commits across many worktrees touch shared refs; confirm the per-repo lock scope is only structural ops (not every commit) to keep parallelism high. 5. **Conflict-to-agent loop** — should an integration conflict be offer-able as a new agent turn ("resolve these conflicts") automatically, closing the loop without leaving the app?