# Worktree

> Use when work needs isolation from the current workspace — when /execute (mode=solo) or the /dispatch funnel must run a phase on its own branch without touching the main working tree. Detects existing isolation, creates a fork via `doctrine worktree fork` (the sole creation+provision+bind verb; coordination tier excluded), runs the commit-before-spawn / branch-point / baseline guards, and hands back the fork branch.

- Skill: `davidlee/worktree` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add davidlee/worktree`
- Raw SKILL.md: https://api.skillmd.com/api/skills/davidlee/worktree/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: davidlee (https://skillmd.com/u/davidlee)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/davidlee/worktree

---


# Worktree

Drive the lifecycle of an isolated git worktree: detect existing isolation,
create a fork through **`doctrine worktree fork`** (the verb that adds the
worktree, provisions it, and binds it to its phase when it can — all with
compensating rollback; the fork builds into its own in-tree `target/`), run the
spawn guards, and verify a green baseline before handing off.

**Announce at start:** "Using the worktree skill to set up an isolated workspace."

**This is a sub-skill.** `/execute` (mode=solo) invokes it for opt-in isolation;
the dispatch spawn script calls `fork` for every worker, whatever the harness;
the `/dispatch` router invokes the **coordination** path (`worktree coordinate`,
below) for the per-run `dispatch/<slice>` tree. It is not a `/route` destination
— reach it through the caller.

## Mode contract

The skill is parameterised so the funnel reuses it without inheriting solo
semantics (design §5, the OQ-1 split seam).

**Inputs:**
- `mode = solo | worker`
- `allow_work_in_place: bool` — **true only for `solo`**
- requested branch / path
- `base` — the ref the fork is created from. **`solo` defaults to `HEAD`;
  `worker` REQUIRES an explicit base** (the orchestrator's coordination HEAD `B`),
  because the session HEAD is **not** `B` — the orchestrator drives from the
  coordination branch while the session repo may sit on `main`. Forking from the
  implicit session HEAD imports a divergent base and corrupts the `B..S` delta.

**Behaviour:**
- `solo` — MAY degrade to the work-in-place rung on sandbox denial (no fork; the
  blessed trunk path).
- `worker` — **MUST NOT degrade.** A worker with no real fork is a hard failure;
  the funnel's isolation is mandatory. `fork --worker --base <B>` pins the base
  explicitly — never the implicit session HEAD, which for a worker is *not* `B`
  and breaks `S.parent == B`. Beyond creation, the worker half runs a constrained
  edit→verify→**commit-one-`S`-to-fork** loop; see
  [Worker mode](#worker-mode-the-funnel-half) (SL-031 §5.2).

**Outputs:** `{ fork_path, branch, head_sha, provision_report, baseline_result }`.
`worker` adds `{ fork_branch, head_sha_after }` once it has committed its single
delta `S` — what the orchestrator imports `B..head_sha_after` from.

## Coordination mode — `doctrine worktree coordinate` (SL-064, a THIRD path)

`solo`/`worker` above both go through `fork`. The **dispatch coordination worktree**
is a separate creation verb, **not** a `fork` mode — the `/dispatch` router calls it
once per run, before batch 1:

```sh
doctrine worktree coordinate --slice <N> --dir <path>
```

It creates (or resumes) `dispatch/<slice>` in its own worktree off the **resolved
trunk** — the funnel's sole write target (design §2; ADR-012). It differs from
`fork` on every axis that matters:

- **The orchestrator's own tree.** The coordination tree **is** the orchestrator
  (worker-mode OFF — worker-ness is a property of the process, carried by
  `DOCTRINE_WORKER` in the worker's jail, never of a directory).
  Orchestrator-classed, refused under worker-mode like the rest of the verb class.
- **Create-or-resume, never a second branch.** A live worktree already on
  `dispatch/<slice>` is **refused** (`coordination-live` — concurrent same-slice
  dispatch is illegitimate). A branch that exists with **no** live worktree
  **resumes** (reattach) — so a fresh orchestrator after `/handover` picks up the
  same branch (resume-stable; a per-run discriminator would *break* resume).
- **Regenerates the runtime phase sheets** from the committed `plan.toml` (the
  SL-056 provision axis), via the sole copier — no coordination-tier copy.

Not a worker delta path: there is no `S` and no import. Lifecycle is
worktree-life < branch-life — the directory is removed at conclude, but
`dispatch/<slice>` + the projected `review`/`phase` refs are **kept** as deliverables
(see `/dispatch`). Solo/worker creation below is unchanged.

## Detection (adapt, don't re-create — D1)

If the CWD is already inside a linked worktree, adapt to it; do not create another.

```bash
git rev-parse --git-dir          # differs from common-dir ⇒ linked worktree
git rev-parse --git-common-dir
```

`--git-dir` ≠ `--git-common-dir` ⇒ already in a linked worktree → skip creation.

**Submodule guard.** A submodule *also* trips that inequality. Disambiguate:
- the `.git` gitdir resolves under `…/worktrees/` ⇒ worktree; under `…/modules/`
  ⇒ submodule; **or**
- a non-empty `git rev-parse --show-superproject-working-tree` ⇒ submodule.

A submodule is **not** the isolation we want — treat it as "not yet forked".

## Creation — `doctrine worktree fork` (the controlled path — D5/F1)

Pick the fork directory, run the commit-before-spawn guard, then call the verb:

```bash
doctrine worktree fork --base <B> --branch <name> --dir <path> [--worker]
```

`fork` is the **single create+provision+(mark)+emit verb**, atomic via
compensating rollback (any failure after `git worktree add` triggers a best-effort
`worktree remove --force` / `branch -D` / dir reap; a rollback that itself fails
**names the leftover and exits non-zero** — never a silent half-rollback). It:

1. `git worktree add -b <branch> <dir> <base>` — refuses if `<dir>`/`<branch>`
   exist or `<base>` is not a commit. Pass `<base>` explicitly: `HEAD` for solo,
   `B` for a worker — the explicit base is *why* this path is controlled.
2. **provisions** the fork (the sole copier — coordination/runtime tier withheld
   at the copy seam, D9; see [Provisioning](#provisioning-d9)).
3. with `--worker` — plus both `--slice` and `--phase`, and a
   `<coord>/.worktrees/<name>` dir — **binds the fork** to its `(slice, phase)`.
   Any of those missing and the fork is simply *unbound*, which downstream verbs
   report honestly rather than guess around (solo omits `--worker`).
4. reports **human status to stderr**; stdout stays empty (machine-clean). The fork
   builds into its own in-tree `<dir>/target` — no env contract is emitted (SL-156:
   the platform exited the build-env business).

`fork` is **Orchestrator-classed — refused under worker-mode** (a worker never
forks; the orchestrator/solo, worker-mode OFF, calls it).

**Creation backend ladder — degrade in order:**
1. **Existing isolation** (detection above) → skip creation, adopt the fork.
2. **Harness-native creation** if present and invocable **creation-only** (no
   auto-copy) — opportunistic only. (Claude Code's `WorktreeCreate` hook is the
   instance, but its payload lacks base/path/type, so doctrine cannot drive it
   and dispatch does not: it forks explicitly instead.) **Design around it;
   never depend on it.**
3. **`doctrine worktree fork`** — the **blessed tested default**. Prefer this.
4. **Work-in-place** (`solo` only, `allow_work_in_place=true`) on sandbox denial —
   no fork, the trunk path. `worker` aborts instead.

### Worktree directory selection

Pick the fork directory before calling `fork`, in priority order
(`superpowers:using-git-worktrees`):

1. **Reuse an existing dir** — `.worktrees` wins over `worktrees` if both exist.
2. **Check CLAUDE.md** for a stated preference — use it without asking.
3. **Ask the user** if no directory exists and no preference is stated.

The fork path is `<dir>/<branch>`. **Safety:** the dir MUST be gitignored
(`git check-ignore -q .worktrees`) before creating, else the fork contents get
tracked — if not ignored, add the line to `.gitignore` and commit first. (A
global dir outside the project needs no such check.)

## Provisioning (D9)

`fork` provisions automatically; **never run a second copier.** `doctrine
worktree provision <fork>` exists as the standalone sole copier (run from the
**source root**, never inside the fork) for the rare adopt-existing path that
skipped `fork`. It reads `.worktreeinclude`, fails closed on a tier-naming
pattern, and copies only allowlisted gitignored files — **withholding the
coordination/runtime tier at the copy seam even under a broad `**` pattern.**

### Honest invariant framing (F7 — do not overstate)

The copy-seam guarantee holds **because provision is the only copier.**

- `doctrine worktree check-allowlist` is a **static smell test** — green means no
  pattern *names* the tier; it is **NOT** completeness. `select_copies` (inside
  provision) is the actual guarantee.
- **If a harness force-copies on creation** and cannot be run creation-only,
  doctrine cannot prevent that copy — the guarantee degrades to `check-allowlist`
  only, and the project must keep `.worktreeinclude` precise. This is why the
  `fork` verb (fully controlled) is the default and the harness-native rung is
  never depended on.

## Guards

Run these around the spawn. A guard that fails **aborts** — report, do not
improvise past it.

- **Commit-before-spawn (D5).** Before `fork`, the source tree must be clean so
  the fork sees only committed HEAD: `git status --porcelain -z`. **Abort** on any
  dirty tracked file **or** any untracked non-ignored file (it would be silently
  absent from the fork). Ignored files are fine — provision handles the
  allowlisted subset.
- **Branch-point check (D5).** `fork` lands the worktree at `<base>` by
  construction; the **concurrency extension** is the batch-commit boundary:
  `doctrine worktree branch-point-check --base <B>` exits 0 iff coordination HEAD
  still equals the orchestrator's pre-spawn `B`, 1 otherwise (→ re-dispatch). It is
  the **orchestrator's** guard at import time, worker-mode OFF — not the worker's.
  A HEAD-stationarity compare, not a merge-base (C-V).
- **Baseline-verify (D9).** After creation, run the project's regenerate-and-verify
  command **inside the fork** and gate handoff on green (this repo: `doctrine
  check gate`). The command is **project-provided** — never a hardcoded `cargo …`. An unbuildable
  fork is fixed in provisioning, **never handed off**.

## Worker mode (the funnel half)

`mode=worker` is the path a dispatch worker runs inside its `fork --worker`
worktree. Creation, provision, and the guards above run first **unchanged**; this
is what happens *after* a green baseline, in place of solo handoff. The worker is
a constrained writer: one importable delta, then return, never touching the
coordination/runtime tier (the fork already withholds it, D9).

**Armed by the jail, not by cooperation.** `DOCTRINE_WORKER=1` is set inside the
confinement namespace by the spawn script, so the doctrine guard is armed before
the worker's first command: any doctrine-mediated authored write (`slice`,
`memory record`, `backlog`, minting) **refuses**. Worker-ness is a property of
the process, not of the directory. Outside a dispatch spawn — a hand-driven
worker turn — export it yourself first.

**The constrained loop:**

1. **Mutate source only.** Edit source files in the fork. Do **not** write
   `.doctrine/` authored trees, runtime state, or memory — an import touching them
   is rejected (report+halt).
2. **Verify.** Run the **orchestrator-supplied** verify command (passed in the
   worker prompt — not assumed `doctrine check gate`). A red verify is reported
   back; the worker does not hand back a red delta as if it were green.
3. **Leave the delta uncommitted.** A confined worker **cannot** commit — the
   linked worktree's real git dir is read-only inside the jail, so there is no
   self-commit path and none is wanted. The importable unit is the working tree
   itself, which the orchestrator gathers with `worktree import --from-worktree`.
   Never `reset`, `checkout --`, `stash` or `clean` your own work away — that
   destroys the only copy. Stay within your declared file set; straying breaks
   the file-disjoint batch.

**MUST NOT degrade to work-in-place.** A worker with no real fork is a **hard
abort**, never a silent in-tree edit (contrast `solo` rung 4). If creation failed,
report and stop.

**Return** a structured report (held in orchestrator context, never a doctrine
artifact): what changed, the verify result, memory-worthy notes, and the fork
branch. Knowledge trails the orchestrator's confirmed
commit, not the fork (record-on-trunk, below).

## Squash-orphan caveat (record-on-trunk)

Memory recorded inside a worktree branch is **orphaned by a squash-merge** (the
content survives but the git anchor points at a commit that never lands; SL-008
staleness fires). When durable memory must outlive the fork, **record it on
trunk**, not inside the worktree branch.

## The `.worktreeinclude` template (F2 — project-owned, not installed)

Doctrine has no secrets / irreducible local files → its own default is **nothing
to copy**, and the installer ships **no** `.worktreeinclude` (a root-file install
would clobber a consuming project's file). Provision tolerates its absence (copies
nothing, exit 0).

A project that needs to carry gitignored local files into forks may adopt a
repo-root `.worktreeinclude`: blank lines, `#` comments, literal repo-relative
paths, simple `glob` patterns (`*`, `**`, `?`). **No `!` negation, no anchoring**
(the parser rejects them). The coordination/runtime tier is withheld regardless.
Validate statically (smell test, NOT completeness): `doctrine worktree
check-allowlist`.

## Quick Reference

| Situation | Action |
|---|---|
| `--git-dir` ≠ `--git-common-dir`, not a submodule | Already forked → adopt, skip creation |
| Submodule (`modules/` gitdir / superproject) | Not isolation → treat as not-forked |
| Dispatch coordination tree (per run) | `doctrine worktree coordinate --slice <N> --dir <path>` — create-or-resume, off resolved trunk; NOT a `fork` mode |
| Default fork (solo or dispatch worker) | `doctrine worktree fork --base <B> --branch <name> --dir <path> [--worker]` |
| Worker fork base | `--base <B>` explicit, never the implicit session HEAD (it is not `B`) |
| Worktree dir not ignored | Add to `.gitignore` + commit before `fork` |
| Sandbox denies fork, `mode=solo` | Work-in-place (no fork) |
| Sandbox denies fork, `mode=worker` | **Abort** (isolation mandatory) |
| Adopted a fork that skipped `fork` | `doctrine worktree provision <fork>` from the source root (sole copier) |
| Tree dirty / untracked-non-ignored | **Abort** commit-before-spawn |
| Batch-commit boundary | `doctrine worktree branch-point-check --base <B>` → 1 = re-dispatch |
| Baseline red in fork | Fix in provisioning; **never hand off** |
| `worker` start | `export DOCTRINE_WORKER=1` (self-arm, fail-open) |
| `worker` verify green | Commit ONE non-merge `S`; return `{fork_branch, head_sha_after}` |
| `worker` >1 commit / merge / rebased fork | **Contract violation** — orchestrator rejects pre-import |
| `worker` verify red | Report; **do not** commit a red delta |

## Red Flags

**Never:**
- Copy gitignored files into a fork by any path other than `fork` / `provision`
  (the sole copiers — the exclusion guarantee depends on them).
- Run a second copier after `fork`, or run `provision` from inside the fork.
- Imply `check-allowlist` green means the allowlist is complete (it is a smell test).
- Let a `worker` degrade to work-in-place.
- In `worker` mode: write `.doctrine/` authored trees, skip `export
  DOCTRINE_WORKER=1`, or land more than one non-merge commit `S`.
- Fork from a dirty tree or hand off a red baseline.
- Fork a `worker` from the implicit session HEAD instead of `--base <B>` — for a
  worker the session HEAD is not `B`, a divergent base that breaks `S.parent == B`.
- Author or edit this skill in `.doctrine/skills/` (the gitignored install copy);
  the source of truth is here under `plugins/`.

**Always:**
- Detect before creating; adapt to existing isolation.
- Prefer the `fork` verb; treat the harness-native rung as opportunistic.
- Run all three guards; verify a green baseline before handoff.
- Record durable memory on trunk, not inside the fork branch.

## Outcome

Report the outputs: `fork_path`, `branch`, `head_sha`, the provision report
(copied / withheld files), and the baseline result. The fork branch is the
deliverable handed back to the caller.

<!-- Attribution: see NOTICE.md (adapted from superpowers:using-git-worktrees, MIT). -->

