# Goal: per-branch sub-worktree primitives (SPEC §6.10.1)

SPEC §6.10 specifies that each branch in a parallel region runs in
its own git worktree off the fork point, backed by a first-class
branch ref in the run's namespace. This task implements the
**`WorktreeManager` primitives** needed for that — without wiring
them into the engine yet.

After this task, `WorktreeManager` can create / list / remove
per-branch worktrees and refs; nothing calls these methods yet
(engine traversal lands in the next chunk). The new API is
exercised exclusively by unit tests for now.

## Files to touch

- `src/attractor/worktree/manager.py` — add `create_branch`,
  `list_branches`, `remove_branch` methods; new `RunBranchWorktree`
  dataclass.
- `src/attractor/worktree/__init__.py` — re-export the new types.
- `tests/test_worktree/test_manager.py` (or a sibling
  `test_branch_manager.py`) — positive and negative tests for the
  new methods.
- `traceable-reqs.toml` — promote
  `REQ-EXEC-PARALLEL-WORKTREE.required_stages` from `["doc"]` to
  `["doc", "impl", "unit"]`.

## API to implement

### `RunBranchWorktree` (new dataclass, frozen)

Fields, mirroring the existing `RunWorktree`:

- `run_id: str`
- `branch_name: str`
- `path: Path` — `.attractor/worktrees/<run-id>/branch/<name>/`
- `branch_ref: str` — `refs/heads/attractor/run/<run-id>/branch/<name>`

### `WorktreeManager.create_branch(run_id, branch_name, fork_point_oid) -> RunBranchWorktree`

- `run_id` and `branch_name` must be non-empty;
  `branch_name` must match `[a-z0-9][a-z0-9_-]*` (same surface as a
  DOT node id).
- `fork_point_oid` is a string OID of an existing commit in the
  repo — typically the main run worktree's HEAD at the moment a
  `component` is entered.
- Creates `refs/heads/attractor/run/<run-id>/branch/<branch_name>`
  pointing at `fork_point_oid`.
- Creates a worktree directory at
  `.attractor/worktrees/<run-id>/branch/<branch_name>/` checked out
  to that ref via `git worktree add`.
- Returns the `RunBranchWorktree`.
- Raises a descriptive error if the branch ref or directory
  already exists (do not silently overwrite).

### `WorktreeManager.list_branches(run_id) -> list[RunBranchWorktree]`

- Enumerate live branch worktrees for a given run, in
  declaration order (filesystem dirent or `git worktree list`
  order is fine — just be deterministic).
- Empty list if the run has no branch worktrees.

### `WorktreeManager.remove_branch(branch_worktree) -> None`

- Remove the worktree directory via `git worktree remove --force`.
- Do NOT delete the branch ref (matches `RunWorktree` semantics —
  refs are durable artifacts kept until `prune`).
- Idempotent: removing a non-existent dir succeeds silently.

## Definition of done

- All three `WorktreeManager` methods implemented, each carrying
  `# [impl->REQ-EXEC-PARALLEL-WORKTREE]` on or directly above the
  method.
- `RunBranchWorktree` dataclass implemented and re-exported.
- Tests carry `# [unit->REQ-EXEC-PARALLEL-WORKTREE]` and cover at
  minimum:
  - Creating one branch worktree off the run's main worktree HEAD;
    the dir exists and the ref resolves to the fork-point OID.
  - Creating two branch worktrees for the same run; they coexist.
  - `list_branches` returns the created worktrees.
  - `remove_branch` removes the dir but the ref persists.
  - Creating the same `(run_id, branch_name)` twice raises.
  - Bad `branch_name` (empty, contains `/`, contains uppercase)
    raises with a clear message.
  - `list_branches` for an unknown run returns an empty list (no
    error).
- Existing `WorktreeManager.list()` includes branch worktrees as
  well as the main one, OR documents that branch worktrees have a
  separate enumeration path (decide and pick one — note in a
  docstring).
- `attractor prune` already removes everything under
  `refs/heads/attractor/run/<id>/`, so branch refs are swept
  automatically. Add a regression test asserting this still
  holds with branch refs present.
- `uv run pytest && uv run ruff check src tests && uv run pyright`
  all pass.
- `uv run traceable-reqs check --json` reports
  `REQ-EXEC-PARALLEL-WORKTREE` complete on `doc`, `impl`, and
  `unit` stages.

## References

- SPEC.md §6.7 (existing run-isolation model — read first for context)
- SPEC.md §6.10.1 (per-branch sub-worktrees and refs — load-bearing)
- SPEC.md §7.2 (ref namespace — branch refs live alongside
  `state` and `worktree` under `attractor/run/<id>/`)
- SPEC.md §14.1 (prune semantics — confirms branch refs are swept)
- `src/attractor/worktree/manager.py` — existing `WorktreeManager`,
  `RunWorktree`, `_run_git` helper for subprocess git calls
- `tests/test_worktree/test_manager.py` — existing test style
- `traceable-reqs.toml` — REQ-EXEC-PARALLEL-WORKTREE (doc-only;
  promote in this PR)

## Out of scope

- Engine integration. The new methods are unused by `Engine`
  until the next chunk lands. Do not modify `engine.py`.
- Journal `branch_name` field. Belongs with the engine chunk.
- Resume logic for in-flight regions. Separate chunk after
  engine traversal lands.
- `attractor prune` selector additions for branch refs. The
  existing ref-prefix sweep already covers them; just verify
  via test.

After this task lands, `WorktreeManager` has the API the engine
will call when fan-out fires — but the engine itself doesn't yet
know how to call it. That wire-up is the next chunk.
