# Git Worktree Sandbox

**Package:** `cleveragents.infrastructure.sandbox.git_worktree`
**Introduced:** Unreleased (issue #4454)

The git worktree sandbox isolates plan modifications in a dedicated git
branch and worktree.  On commit, changes are merged back to the original
branch via `git merge`.  On rollback, the worktree is discarded entirely.
This replaces the previous flat `shutil.copy2` apply strategy for
git-checkout resources.

---

## Purpose

When a plan applies changes to a git-checkout resource, the changes must
be isolated from the working tree until the user approves them.  The git
worktree sandbox achieves this by:

1. Creating a new branch `cleveragents/plan-<plan_id>` from the current HEAD.
2. Creating a temporary git worktree at a system temp directory.
3. Letting the actor write changes inside the worktree.
4. On commit: staging all changes, committing them in the worktree, then
   merging the sandbox branch back into the original branch.
5. On rollback: resetting the worktree (and optionally the original branch)
   to the base commit and discarding the worktree.

Non-git projects fall back to the original flat file copy strategy.

---

## `GitWorktreeSandbox`

```python
from cleveragents.infrastructure.sandbox.git_worktree import GitWorktreeSandbox

sandbox = GitWorktreeSandbox(
    resource_id="01HZ...",
    original_path="/path/to/repo",
)
```

Implements the `Sandbox` protocol.

**Constructor parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `resource_id` | `str` | — | Identifier of the resource being sandboxed |
| `original_path` | `str` | — | Path to the git repository root |
| `git_timeout` | `int` | `30` | Timeout in seconds for git commands |

---

## Lifecycle

```
PENDING ──► create() ──► CREATED ──► get_path() ──► ACTIVE
                                                        │
                                              commit() ─┤─► COMMITTED ──► cleanup() ──► CLEANED_UP
                                                        │
                                            rollback() ─┴─► ROLLED_BACK ──► cleanup() ──► CLEANED_UP
```

### `create(plan_id) → SandboxContext`

Creates the worktree and branch.

```python
ctx = sandbox.create(plan_id="plan-01HZ...")
# ctx.sandbox_path — path to the worktree
# ctx.metadata["branch"] — e.g. "cleveragents/plan-plan-01HZ..."
# ctx.metadata["base_commit"] — HEAD SHA at creation time
```

Raises `SandboxCreationError` if the path is not a git repository root or
if `git worktree add` fails.

### `get_path(resource_path) → str`

Translates a resource-relative path to an absolute path inside the
worktree.  Rejects `..` path traversal attempts.

```python
worktree_file = sandbox.get_path("src/main.py")
# → "/tmp/ca-sandbox-plan-01HZ.../src/main.py"
```

### `commit(message=None) → CommitResult`

Stages all changes in the worktree, commits them, then merges the sandbox
branch into the original branch.

```python
result = sandbox.commit("feat: implement new feature")
# result.success — True on success
# result.commit_ref — SHA of the sandbox commit
# result.changed_files — list of modified file paths
# result.added_files — list of newly added file paths
# result.deleted_files — list of deleted file paths
```

If there are no staged changes, returns a `CommitResult` with empty file
lists and no new commit.

Raises `SandboxCommitError` if the commit or merge fails.

### `rollback()`

Discards all worktree changes.

- From `ACTIVE`: resets the worktree branch to the base commit.
- From `COMMITTED`: also resets the original branch to the pre-merge commit
  (fully undoes the merge).

!!! warning "Multi-worktree safety"
    Rolling back from `COMMITTED` executes `git reset --hard` on the
    original branch.  If other git worktrees or external processes are
    tracking the same branch, they will be affected.

### `cleanup()`

Removes the worktree directory and deletes the sandbox branch.  Idempotent
— safe to call multiple times.

---

## Apply Summary

When `plan apply` uses the git worktree strategy, the CLI displays a
spec-aligned Apply Summary:

```
╭─ Apply Summary ──────────────────────────────────────╮
│ Plan ID:    plan-01HZ...                             │
│ Artifacts:  3 files changed                          │
│ Insertions: +142                                     │
│ Deletions:  -17                                      │
│ Project:    local/my-project                         │
│ Timestamp:  2026-04-09 17:30:00                      │
╰──────────────────────────────────────────────────────╯
╭─ Sandbox Cleanup ────────────────────────────────────╮
│ ✓ Worktree removed                                   │
│ ✓ Branch deleted                                     │
╰──────────────────────────────────────────────────────╯
✓ OK  Changes applied
```

---

## Error Types

| Exception | When Raised |
|-----------|-------------|
| `SandboxCreationError` | `git worktree add` fails or path is not a git root |
| `SandboxCommitError` | `git commit` or `git merge` fails |
| `SandboxRollbackError` | `git reset --hard` fails during rollback |
| `SandboxStateError` | Method called in an invalid lifecycle state |

All exceptions are importable from
`cleveragents.infrastructure.sandbox.protocol`.

---

## Branch Naming

Branch names are sanitised from the plan ID:

- Only alphanumeric characters, hyphens, underscores, slashes, and dots
  are kept.
- Runs of disallowed characters are collapsed into a single hyphen.
- The final branch name has the form `cleveragents/plan-<sanitised_plan_id>`.

---

## Fallback for Non-Git Projects

`PlanLifecycleService` detects whether the resource is a git-checkout type.
If not, it falls back to the original flat file copy strategy
(`shutil.copy2`).  The git worktree sandbox is only used for resources
where `resource_type_name` is `git-checkout` or `git`.

---

## Related

- [Architecture — Plan Lifecycle](../architecture.md#plan-lifecycle)
- [ADR-015 Sandbox & Checkpoint](../adr/ADR-015-sandbox-and-checkpoint.md)
- [ADR-038 Cross-Mechanism Sandbox Coordination](../adr/ADR-038-cross-mechanism-sandbox-coordination.md)
- [`cleveragents.infrastructure.sandbox`](../api/core.md)
