Skip to main content

worktrees

Use when work is about to become commits and needs isolation first — check the tree state, then branch or cut a git worktree off a clean base (native EnterWorktree first, git fallback) so implement never runs on main. NOT what may run concurrently inside those workspaces (that is `parallel`), NOT the merge/cleanup after (that is `ship`).

Ir a la instalación

Datos de origen

Repositorio
ericrisco/rsc-harness
Última actividad en el origen
18 de septiembre de 2026 a las 12:00
Idioma detectado de SKILL.md
inglés
Estrellas
110
Forks
9

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Explorador de archivos
3 archivos

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
worktrees
description
Use when work is about to become commits and needs isolation first — check the tree state, then branch or cut a git worktree off a clean base (native EnterWorktree first, git fallback) so implement never runs on main. NOT what may run concurrently inside those workspaces (that is `parallel`), NOT the merge/cleanup after (that is `ship`).
tags
["git","worktree","isolation"]
recommends
[]
profiles
["core","full"]
origin
risco
# worktrees — isolate the work before you touch it Before you execute a plan you guarantee one thing: **the work happens somewhere that can be thrown away without harming the main checkout.** A feature branch keeps history clean; a *worktree* goes further — a second working directory on its own branch, so you can build, run, and test the feature without disturbing the files you (or another agent, or a running dev server) have open on the default branch. `worktrees` is an **on-demand** step in the SDD chain — not a numbered phase, but the gate that `implement` calls when it finds itself on `main`. It does exactly one job: confirm an isolated workspace exists, create one if it doesn't, and hand back. It writes no runtime code and leaves no artifact under `02-DOCS`; isolation is plumbing, not knowledge. ```text constitution → specify → clarify → plan → tasks → analyze → [ worktrees ] → implement → verify → review → ship on-demand, called before the first commit ``` ## The one rule that defines this step **Never run `implement` on the default branch.** The moment a plan is about to become commits, the work belongs in isolation. If `git rev-parse --abbrev-ref HEAD` says `main` or `master`, you stop and create the isolated workspace *first* — before the first edit, not after the diff already exists on the wrong branch. Recovering a half-built feature off `main` is strictly more expensive than branching one command earlier. ## The decision: branch vs. worktree Both isolate history. A worktree also isolates the *filesystem*. Pick by what's actually in play. | Situation | Use | Why | | --- | --- | --- | | Solo, single task, clean tree, no parallel work | **branch** (`git switch -c`) | Cheapest isolation; nothing else needs the main checkout right now. | | A dev server, build watcher, or editor is live on the current files | **worktree** | A branch switch yanks files out from under the running process; a worktree leaves them put. | | You'll run two streams of work at once (see the parallel pattern) | **worktree** | Each stream gets its own directory; no stash-juggling, no checkout thrash. | | The current tree is dirty and the user wants both the WIP and the new work | **worktree** | Stash-and-switch risks losing the WIP; a worktree sidesteps the stash entirely. | | You're an agent that may keep the main session running elsewhere | **worktree** | Filesystem isolation is the whole point — the parent session keeps its files. | When in doubt, prefer a **worktree**: it is the strictly stronger isolation and the cost is one extra directory. The rest of this skill assumes a worktree; the branch path is the degenerate case (skip the directory, keep the branch). ## Before you create anything — the pre-flight gate Run this in order. Each check prevents a class of "lost work" you can't easily undo. 1. **Read the accompaniment dial** — `02-DOCS/wiki/harness/user-profile.md` gives the technical + accompaniment level (L0..L3); set your volume from the table below. No profile yet → assume non-technical, explain what a worktree is in one plain sentence before making one. No `02-DOCS/` at all (a worktree can be created in any git repo with no rsc harness present) → skip the dial, assume non-technical, and proceed. 2. **Confirm you're in a git repo.** `git rev-parse --is-inside-work-tree`. If not, there's nothing to isolate with git — tell the user; don't fabricate a worktree. 3. **Check the current branch.** `git rev-parse --abbrev-ref HEAD`. On `main`/`master` with work about to start → this skill is exactly right. Already on a feature branch with a clean tree → isolation may already be sufficient; say so and don't create a redundant one. 4. **Check the tree state.** `git status --short`. A **dirty tree is a fork in the road**, not a thing to bulldoze: - Uncommitted changes that belong to the *new* feature → a worktree built from a clean base means those changes stay on the old branch. Surface this; ask whether to commit/stash them first or carry them over. Never silently leave a user's WIP behind. - Unrelated WIP → that's the textbook reason to use a worktree (isolate the new work, leave the WIP exactly where it sits). - A dirty tree you don't understand is a hard stop: surface every modified path and ask before branching or stashing. Lost WIP is the one unrecoverable failure in this step. 5. **Pick the base ref.** Branch from an up-to-date default branch unless the user wants to build on local HEAD. Stale base = predictable merge pain later. Default: fresh from `origin/<default>`. 6. **Choose a name** tied to the feature slug — the same `<slug>` the spec and plan use (`feat/<slug>`), so the branch, the spec at `02-DOCS/wiki/sdd/specs/<slug>.md`, and the plan at `02-DOCS/wiki/sdd/plans/<slug>.md` all line up and are trivially traceable. A change that is not a feature takes the type that fits — `fix/<slug>`, `docs/<slug>`, `chore/<slug>` — from the same Conventional Commits vocabulary the commit guard already enforces here. **The prefix is load bearing**: the automatic cleanup uses it, with the location, as the two signals that say a worktree is ours to remove. A name outside that vocabulary is never swept for you. Only once the tree state is understood and the user's WIP is accounted for do you create anything. ## Creating the workspace — native first, git fallback **Prefer the native worktree tool when it's available in your environment.** An `EnterWorktree`-style tool typically creates an isolated worktree (often under something like `.claude/worktrees/`, though the exact location is tool-dependent), switches the session into it, and tracks it for clean exit — which is exactly the lifecycle this skill wants, with no manual path management. The git path is the universal fallback and is exactly what the native tool does under the hood. | You have… | Create | Leave intact | Discard | | --- | --- | --- | --- | | A native worktree tool (`EnterWorktree`-style) | enter, named for the feature slug; the session's working directory moves into the isolated checkout | exit with `keep` — worktree and branch stay on disk for later | exit with `remove` — deletes both when the work is done or abandoned | | Plain git only | `git worktree add -b feat/<slug> .worktrees/<slug>` | leave the dir; it persists until its work lands | `git worktree remove` + `git branch -d` | Removing a worktree that holds uncommitted or unmerged work must be an explicit, confirmed choice, never a silent cleanup: refuse the silent path and confirm the discard with the user, quoting what would be lost. **You do not have to remember to clean up.** Once the work lands on the trunk, a `post-merge` hook retires the worktree and its branch on its own — it removes only what the reaper already classifies as safe, and anything holding unsaved work is refused exactly as it is above. Nothing to run, and nothing to remember: the previous version of this skill asked an agent to remember, and it was skipped on both features that reached that point. ```bash # from the repo root, default branch up to date git fetch origin git worktree add -b feat/<slug> .worktrees/<slug> origin/<default-branch> # work happens in .worktrees/<slug>; the main checkout is untouched ``` **Inside the repo, always — never a sibling.** `.worktrees/` is already gitignored by the harness, the reaper already recognises it as its own, and the difference matters after the work lands: a sibling directory sits outside the tree, so it survives every cleanup of the repository and stops being visible to whoever would retire it. Two of them accumulated here before this was written down. Deleting the repo should delete its worktrees with it. ```bash # branch-only path (the degenerate case: no separate directory needed) git switch -c feat/<slug> origin/<default-branch> ``` ```bash # when the work has landed — the cleanup is the default, and it is a command, not a judgement call npx @ericrisco/rsc worktrees reap ``` Either way, the contract is identical: **after this step, the cwd is an isolated branch off a clean base, and the default-branch checkout is exactly as it was.** Confirm that out loud (at the dial's level) before handing to `implement`. ### Cleanup is the default, and it is not yours to judge A worktree whose work is already in the trunk is retired **by default** — you do not decide that by reading the situation, because "is this safe to delete?" is a deterministic question and a wrong answer costs a directory that cannot be brought back. Ask the command: ```bash npx @ericrisco/rsc worktrees # every landed worktree, with a verdict and a reason npx @ericrisco/rsc worktrees reap # retire the ones that are safe with nothing to lose npx @ericrisco/rsc worktrees reap <path> # retire one the user has just confirmed ``` Three things have to hold before a worktree is retired without asking, and each fails towards **keeping** it: - **rsc created it** — location *and* branch shape both match. Only one matches → it might be the user's, so it is asked about, never removed silently. Neither → foreign; not touched, not offered. - **The branch adds nothing the trunk does not have** — asked about *content*, so a squash-merged pull request counts as landed. Its branch is kept even so: git will not delete it safely, and while it exists the commits survive a wrong call. - **Nothing inside would be lost** — not just "no pending changes": untracked and ignored files count, because a worktree that git calls clean can still hold the only copy of a `.env` or a local database. Installed dependencies and build output are the one carve-out. Anything else is reported with the reason and the way out. A refusal you cannot act on is not safety. **The native tool owns its own lifecycle.** If you isolated via a native `EnterWorktree`-style tool, exit through that tool's `remove`/`keep`; the reaper is for worktrees git alone knows about. **When the command is unavailable** (no rsc in this project), the manual equivalent is: confirm it is a linked worktree (`git rev-parse --git-dir` ≠ `--git-common-dir`), rule out a submodule (`git rev-parse --show-superproject-working-tree` must be empty), `cd` to the main working tree, then `git worktree remove` and `git worktree prune`. Never `-D` a branch to make the cleanup succeed. ## Model tier — `light` (opt-in routing) This phase's default model tier is **`light`** — isolating the workspace is mechanical git work. Routing is **off** unless `models.enabled: true` in `02-DOCS/wiki/sdd/config.yaml`. When on: resolve this phase's tier (`models.overrides` wins over `models.phases`), map it to a model via `models.tiers`, and apply per `../sdd/references/model-routing.md` — announce the switch per the accompaniment dial when it differs from the session model, and dispatch any `Task`/`parallel` subagents on that model. Routing off or no profile → honor the session model silently. Never fake a switch a tool can't make; skip routing on a one-line change. ## Adapting to the dial The isolation is identical at every level; only the talking changes. | Level | How `worktrees` behaves | | --- | --- | | **L0** | Create the isolated workspace, state the branch/dir in one line, hand to `implement`. No explanation. | | **L1** | Same, plus one line of *why* a worktree over a branch (or vice-versa) for this case. | | **L2** | Justify the base-ref choice and the branch-vs-worktree call; surface the dirty-tree decision explicitly. | | **L3** | Explain in plain language what a worktree is and why isolation protects their work; ask before touching any uncommitted changes; confirm the base ref. | At every level, a **dirty tree with the user's uncommitted work is a hard stop for a confirmation** — the dial controls verbosity, never whether you check before risking someone's WIP. ## Anti-patterns → STOP | Rationalization | Reality / Fix | | --- | --- | | "I'll just implement on `main`, it's a small change" | Small changes become commits on the wrong branch. Branch/worktree first — always, before the first edit. | | "There's uncommitted WIP, I'll stash it and switch" | A worktree avoids the stash entirely and can't drop it. Use a worktree; if you must stash, confirm with the user and name the stash. | | "I'll branch off local HEAD, fetching is slow" | Stale base = merge conflicts you pay for later. Branch off an up-to-date `origin/<default>` unless the user wants HEAD. | | "The worktree's dirty but I'll `remove --force` to clean up" | Force-removing a dirty/unmerged worktree throws away work irreversibly. Resolve or confirm explicitly first; never silent-force. | | "I'll name it `wip` / `temp` / `branch2`" | An untraceable name divorces the branch from its spec/plan, and the automatic cleanup will not claim it. Use `<type>/<slug>` — `feat/` for a feature, `fix/`, `docs/`, `chore/` for the rest. | | "I'll create the worktree AND start writing code right here" | This skill only isolates. Hand a clean isolated workspace to `implement`; don't blur the two steps. | | "Already on a feature branch, I'll make a worktree anyway" | Redundant isolation is just clutter. If the current branch is already isolated and clean, say so and proceed. | | "I'll record the worktree path into 02-DOCS so it's tracked" | Isolation is plumbing, not knowledge. No artifact; the branch name traces it. Don't pollute the wiki. | **Next:** with a clean isolated workspace in hand, hand to `../implement/SKILL.md` — walk the task list test-first, one commit per task, on this branch. Neighbours: `../tasks/SKILL.md` slices the plan before you get here; `../parallel/SKILL.md` decides *what may run concurrently* inside these workspaces — it leans on this skill (each independent stream gets its own worktree, so the streams never fight over files) but the partition-then-gather call is its own; `../ship/SKILL.md` closes the branch afterwards — merge, PR, or discard, with Eric-only git authorship. This skill only *opens* the isolation, and it touches git state only: no runtime code, nothing under `02-DOCS`.
Ver en GitHub