Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
davidlee avatar

Worktree

  • 77 installs
  • 1 repo stars
  • Updated August 3, 2026
  • davidlee/doctrine

Isolates work on its own git branch by forking a worktree via doctrine worktree fork, running commit-before-spawn and baseline guards before handing back the fork branch.

About

Creates an isolated git worktree fork so a phase can run on its own branch without touching the main working tree. A developer uses it when execute or dispatch flows need workspace isolation before spawning work.

  • Detects existing isolation and creates a fork via doctrine worktree fork
  • Runs commit-before-spawn, branch-point, and baseline guards before handing back the branch

Worktree by the numbers

  • 77 all-time installs (skills.sh)
  • Ranked #255 of 733 Git & Pull Requests skills by installs in the Skillselion catalog
  • Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/davidlee/doctrine --skill worktree

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs77
repo stars1
Last updatedAugust 3, 2026
Repositorydavidlee/doctrine

What it does

Isolates work on its own git branch by forking a worktree via doctrine worktree fork, running commit-before-spawn and baseline guards before handing back the fork branch.

Files

SKILL.mdMarkdownGitHub ↗

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, optionally stamps the worker marker, and emits the per-worktree env contract — all with compensating rollback), 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; /dispatch-subprocess invokes it for codex/pi workers; the /dispatch router invokes the coordination path (worktree coordinate, below) for the per-run dispatch/<slice> tree. (The claude worker arm, /dispatch-agent, does not use fork — Claude default-creates the worktree and a SubagentStart hook stamps it; see that skill.) 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: booltrue 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).

  • workerMUST 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 (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:

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:

  • Markerless. The coordination tree is the orchestrator (worker-mode OFF),

so — unlike fork --worker — it stamps no worker marker. 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, no import, no marker. 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.

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:

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). 3. with --worker, stamps the worker marker before any spawn window (solo omits --worker). 4. emits the per-worktree env contract on stdout (KEY=value per line; human status to stderr) — the project declares its per-wt env (doctrine-the-repo declares CARGO_TARGET_DIR, §8); generalisable, not a framework primitive.

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 — the claude arm instead default-creates + SubagentStart-stamps, see /dispatch-agent.) 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: just check). 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 codex/pi worker runs inside its fork --worker worktree (claude workers are stamped by the SubagentStart hook instead — see /dispatch-agent). 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).

Self-arm first (D2a, fail-open — C-I). The worker's first act is export DOCTRINE_WORKER=1. This arms the doctrine guard so any doctrine-mediated authored write (slice, memory record, backlog, minting) refuses. Nothing enforces the line — the Agent tool exposes no env seam, so it is a self-armed prompt contract that fails open if omitted (the orchestrator's import-time .doctrine//.claude/-reject belt, not this var, is the real protection). Arm it regardless.

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 just check). A red verify is reported back; the worker does not commit a red delta. 3. Commit exactly one `S`. git add -A && git commit — raw git, not a doctrine verb (so DOCTRINE_WORKER=1 does not refuse it, D2a). S is the importable unit (the orchestrator imports B..S): exactly one non-merge commit on top of B — no multi-commit, merge, or rebase that re-parents off B (each is a contract violation rejected before import). 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, plus { fork_branch, head_sha_after }. 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

SituationAction
--git-dir--git-common-dir, not a submoduleAlready 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> — markerless, create-or-resume, off resolved trunk; NOT a fork mode
Default fork (solo or codex/pi 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 ignoredAdd to .gitignore + commit before fork
Sandbox denies fork, mode=soloWork-in-place (no fork)
Sandbox denies fork, mode=workerAbort (isolation mandatory)
Adopted a fork that skipped forkdoctrine worktree provision <fork> from the source root (sole copier)
Tree dirty / untracked-non-ignoredAbort commit-before-spawn
Batch-commit boundarydoctrine worktree branch-point-check --base <B> → 1 = re-dispatch
Baseline red in forkFix in provisioning; never hand off
worker startexport DOCTRINE_WORKER=1 (self-arm, fail-open)
worker verify greenCommit ONE non-merge S; return {fork_branch, head_sha_after}
worker >1 commit / merge / rebased forkContract violation — orchestrator rejects pre-import
worker verify redReport; 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). -->

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.