# 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

```
<repoRoot>/                       ← user's primary checkout (untouched)
<repoRoot>/.git/worktrees/<wt>/   ← git's per-worktree metadata (automatic)

<repoRoot>/.nucleic/worktrees/<sessionSlug>/   ← 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
  `<repoRoot>/.nucleic/worktrees/`; user-configurable per project via the `worktreeBase`
  override (which keeps the `<override>/<repoSlug>/` layout for a shared external dir).
- **Branch:** `nucleic/<sessionSlug>` 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 <base>`), stored,
and used as the diff anchor for the session's lifetime.

- **Everything-vs-base (tracked):** `git diff --no-color <baseSHA>` 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 <target>...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/<slug>` (preserves the session as a merge).
- `.rebase` — `git rebase <target>` 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 <remote> nucleic/<slug>` then `gh pr create --base <target> --head nucleic/<slug>
--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 <path>` + `git branch -D nucleic/<slug>`.

---

## 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?
