Skip to content

V025 Multi-Worktree Workflow

Problem

A V025 checkout is not a single working directory. It is several git worktrees sharing one repository, each playing a distinct role — but those roles are nowhere documented. Every new contributor and every Claude session re-derives them from git error messages and history:

  • worktree-manager.sh create fails because main is checked out in some other worktree.
  • A merge into main requires a "dance" — you cd into a different worktree to run the merge, because you can't check main out where you are.
  • The active workspace carries a long-lived branch whose name has drifted from its content, and the fix (rename in place) is an unwritten convention.
  • Spec numbers assigned on unmerged branches collide with numbers the current worktree can't see.

Each symptom has its own surface explanation, so the shared root cause stays invisible. The knowledge lives in lived practice and in per-user MEMORY.md, but memory is the wrong home: it truncates, drifts as state changes, and is per-user not per-repo. These are repo-level toolkit conventions and belong in the toolkit's documentation surface. This doc is that home.

The three roles

V025 uses three worktree roles, each with a distinct lifecycle. A worktree's role is defined by the branch it holds, not by its path.

Role Branch it holds Lifecycle
Main-Holder main The worktree that currently holds mainoften none. main is frequently checked out nowhere; it only needs a holder at merge time, and that holder is usually thrown away afterward. Not a standing role — a transient state. See merge into main and EC-1 below.
Active-Workspace a long-lived chore/* or docs/* branch The repo-root checkout where day-to-day tooling commands run and commit (intake-qualify routings, spec births, doc-sync updates). Accumulates commits between merges to main. When its branch name no longer reflects its content, it is renamed in place, not deleted — it is the only checkout this worktree has. See rename the active-workspace branch.
Feature feature/spec-NNN-* One per active spec, created from main via worktree-manager.sh create, living under .worktrees/feature/. Built through the spec workflow (/vt-d-2-plan/vt-d-3-build → …), then merged back into main after /vt-d-complete. Removed once merged.

Why "Main-Holder" is a transient state, not a standing role. Because main is usually held nowhere, there is normally no Main-Holder to find. Merges are performed by temporarily giving some worktree main (or spinning up a throwaway holder), merging, then releasing it. If you go looking for a permanent main-holder worktree, you often won't find one — and that is correct, not a broken state.

Edge cases referenced in this doc

Four recurring situations are labeled EC-1 / EC-2 / EC-3 / EC-4 throughout the procedures below:

  • EC-1 — main is held nowhere. The normal resting state: no worktree has main checked out, so a merge into main needs a throwaway holder. See merge into main.
  • EC-2 — the Active-Workspace branch name has drifted from what the branch now contains and needs an in-place rename (not a delete). See rename the active-workspace branch.
  • EC-3 — a feature branch forked from main before a recent merge and is now behind main. See sync a fresh feature branch after a main-merge.
  • EC-4 — a forked phase must never prompt. 4-review and 5-finalize declare context: fork; an AskUserQuestion inside one is swallowed and the sub-agent dies silently. See running several worktrees in parallel.

Standard procedures

Create a feature worktree

From the Active-Workspace, always use the manager script (never raw git worktree add — the script copies .env* files, manages .gitignore, and keeps the directory layout consistent):

bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh create feature/spec-NNN-slug

The script creates the branch from main and handles the held-main case gracefully (fixed at commit b53fed62). /vt-d-activate Step 6.7 offers to do this for you at activation time.

Side effect to undo when main is held nowhere (EC-1). The script's currency step runs git checkout main in the Active-Workspace before adding the worktree. When main is held elsewhere that checkout fails harmlessly and is skipped; but when main is held nowhere it succeeds and leaves the Active-Workspace sitting on main — HEAD is never restored. After the command returns, confirm with git -C "<active-workspace-path>" branch --show-current and, if it now reads main, restore it: git -C "<active-workspace-path>" checkout <active-workspace-branch>.

Merge into main

Never check out main in the Active-Workspace to merge. If main is held in another worktree you'll get a already used by worktree error (see anti-patterns); if it's held nowhere you could check it out, but the convention is to keep the Active-Workspace on its own long-lived branch. Instead:

  1. Find who holds main, if anyone:
    git worktree list --porcelain | awk '
      /^worktree /{p=$0; sub(/^worktree /,"",p)} /^branch refs\/heads\/main$/{print p}'
    
  2. If a holder path is printed (an absolute path from step 1), merge the source branch in that worktree without changing your own cwd:
    git -C "<main-holder-path>" merge --no-ff <source-branch>
    
  3. If nothing is printed (main held nowhere — the common case, EC-1), create a throwaway holder worktree on main, merge in it, then remove it:
    # Anchor every path on the MAIN worktree root (the first entry git lists), so these
    # steps work unchanged from any cwd — repo root OR a feature worktree.
    root="$(git worktree list --porcelain | awk 'NR==1{sub(/^worktree /,"",$0);print;exit}')"
    
    # Attach the EXISTING main branch with raw `git worktree add` — the one documented
    # exception to "always use the manager script". The script can't do this: it always
    # passes `-b` (→ `fatal: a branch named 'main' already exists`) and its currency step
    # would `git checkout main` in the Active-Workspace, stranding it on main.
    git worktree add "$root/.worktrees/main-holder" main
    git -C "$root/.worktrees/main-holder" merge --no-ff <source-branch>
    git worktree remove "$root/.worktrees/main-holder"
    

The pre-commit hook has a MERGE_HEAD exception, so --no-ff merges into main need no --no-verify. This is a local ephemeral-branch workflow — main is not pushed by default.

Sync a fresh feature branch after a main-merge

A feature worktree created from main before the day's Active-Workspace commits were merged is now behind. Bring it current from inside the feature worktree (EC-3):

git merge main

Do this right after creation if main moved between when the feature branch forked and when you started work.

Rename the active-workspace branch

When the Active-Workspace branch name no longer fits what's on it (EC-2), rename in place rather than deleting — the branch is this worktree's only checkout:

git branch -m <old-name> <new-name>

(Real example: docs/doc-sync-2026-05-18chore/scratch once the branch had accumulated more than doc-sync work.)

Anti-patterns & error signatures

You see / you do What it means What to do instead
fatal: '<branch>' is already used by worktree '<path>' The branch (often main) is checked out in the worktree at <path>. Git allows a branch in only one worktree at a time. Don't check it out here. cd "<path>" and work there, or use a throwaway holder. See merge into main.
Checking out main in the Active-Workspace to merge Breaks the role separation; fails outright if main is held elsewhere. Merge from the Main-Holder (or a throwaway holder), never from the Active-Workspace.
Deleting the Active-Workspace branch because its name is stale Loses the worktree's only checkout and its accumulated pre-merge commits. git branch -m to rename in place.
New feature worktree's first build sees stale main state The branch forked from main before a recent merge. git merge main inside the feature worktree (EC-3).
spec-from-requirements proposes a SPEC number that already exists elsewhere Pre-SPEC-165 behaviour: the tool saw only the current worktree's registry. Should no longer happen — the allocator owns this. See spec-numbering across branches.

Parallel-work hazards and guardrails

The roles and procedures above describe how to run several worktrees. This section describes what running several worktrees has actually cost this repo, and the guardrail that follows from each. Every hazard here is one we have already paid for at least once, with the commit that paid it cited. They are documented as hazards to avoid re-paying — not as arguments against working in parallel.

Hazard (a) — the registry keeps saying specified after teardown

The gap. The Feature role's lifecycle ends "Removed once merged" (see the three roles). Nothing in that step reconciles the two places a spec's status lives:

Where Written by Authoritative for
specs/[N]-feature/state.yaml the workflow phases (/vt-d-2-plan, /vt-d-complete, …) the spec's real status
.design-state.yamlspecs_status.SPEC-NNN.status hand edits and some skills the dashboard, wave computation, /vt-d-activate

A spec can therefore be built, finalized, merged and its worktree removed while the registry still advertises it as specified — and the worktree that knew better no longer exists.

What it cost. SPEC-158 was built, finalized (GO, v3.51.0) and merged at edea6444, while .design-state.yaml still marked it specified; its own state.yaml already read completed. Repairing one field took a dedicated branch, commit 3dae31ad, and merge commit 611dd10f — a one-line diff that cost a full branch-and-merge cycle because it was found long after the fact.

Sibling failure, same family. /vt-d-complete never clears .active-spec; the pointer is only overwritten by the next activation, so a completed spec stays falsely "active" until something else happens to replace it (bugs/017-complete-active-spec-stale/report.md, which cites the earlier incident the report cites as 931ad01 — work on SPEC-29 ran while .active-spec still named the completed SPEC-23. ⚠ That SHA does not resolve in this repository; it is quoted from the bug report rather than independently verifiable, so treat the incident as reported, not as a lookup). Completion propagating to one record but not the others is the shape to watch for.

Guardrail — reconcile before you remove. git worktree remove is the point of no return: it deletes the copy of state.yaml that would have told you the truth. Before removing a feature worktree, confirm the registry agrees with the spec's own state.yaml, and fix it if not.

Run from the repo root; it compares every registry entry against its per-spec state.yaml and prints only the mismatches:

python3 - <<'PY'
import os, yaml
reg = yaml.safe_load(open('.design-state.yaml')).get('specs_status') or {}
drift = compared = 0
for sid, meta in sorted(reg.items()):
    meta = meta or {}
    # BOTH key names are live: the registry carries `specs_dir` on most entries and `dir` on the
    # rest. Reading only one silently skips the majority and prints a confident all-clear.
    d = meta.get('dir') or meta.get('specs_dir')
    if not d:
        continue
    p = os.path.join(d.rstrip('/'), 'state.yaml')   # specs_dir values carry a trailing slash
    if not os.path.exists(p):
        continue
    live, shown = (yaml.safe_load(open(p)) or {}).get('status'), meta.get('status')
    if shown is None or live is None:
        continue
    compared += 1
    if shown != live:
        print('%s registry=%s state.yaml=%s' % (sid, shown, live)); drift += 1
print('compared: %d  drift: %d' % (compared, drift))
PY

The compared: count is deliberate — read it before you trust drift: 0. A check of this shape once shipped here reading only dir, which compared 43 of 174 entries and reported a clean tree while a real mismatch sat in the other 131.

drift: 0 is the clean state. Any line above it names a spec whose registry entry lies; the per-spec state.yaml is the one to believe, so fix .design-state.yaml to match it.

Three things the check cannot tell you. Entries with no status: key at all are skipped rather than flagged — the newest spec births do not write one, so absence is normal and is not drift. Entries with neither dir nor specs_dir are skipped too. And the check compares only what the current worktree can see: a stale branch shows you its own old registry, not main's. Merge main first (EC-3) if you want the comparison to mean anything repo-wide.

Hazard (b) — two branches claim the same version, and git says nothing

The gap. Version numbers and the count mirrors beside them are edited on parallel branches, but git merges them as text. It has no notion of "these two branches both claimed 3.49.0". Whether you find out depends entirely on which of three shapes the edit took:

What the branches did What git does How you find out
Both wrote the same new value Merges clean — identical text on both sides is not a conflict You don't. Two changes ship under one version number
Wrote different values on the same line Conflicts — you resolve it by hand Immediately. This is the safe shape
Branch never touched the version files Merges clean; main's value is retained You don't. The bump the branch intended silently never happens

Only the middle row is loud. The other two are the hazard: one loses a version number, the other loses a bump.

What it cost. Four reconciliations, all in one week:

  • SPEC-160 and SPEC-162 both finalized 3.49.0. SPEC-162 reached main first (1bd6fa78), so SPEC-160 was rebased on merge to 3.49.03.50.0, taking main's skill count in every mirror.
  • BUG-003 and BUG-004 both claimed 3.50.1. BUG-003 was re-based to 3.50.2 — its ledger entry still records it, "reconciled onto main after BUG-004 independently claimed 3.50.1".
  • SPEC-146 and SPEC-159 never touched the version files at all. Main's value was retained on merge and the intended bump had to be applied afterwards, on the merged tree. SPEC-159's finalize gate flagged a collision hazard that the merge itself then resolved invisibly.

Note what BUG-004's entry says about the code: "Main never touched persona-select.sh, so the code fix merged conflict-free." The code was never the problem. The metadata beside it was.

Guardrail — ask what main already claimed, before you finalize a bump. A version number is not yours until it is merged. Check before you write it, not after:

# Substitute the plugin you are bumping.
p=vt-product-dev
# Paths go in as ARGUMENTS, never interpolated into the python source — a value containing a quote
# would otherwise break out of the string literal.
printf 'branch=%s  main=%s\n' \
  "$(python3 -c "import json,sys;print(json.load(open(sys.argv[1]))['version'])" \
      "plugins/$p/.claude-plugin/plugin.json")" \
  "$(git show "main:plugins/$p/.claude-plugin/plugin.json" | python3 -c "import json,sys;print(json.load(sys.stdin)['version'])")"

If main has moved past where your branch forked, merge main first (EC-3) and re-derive the bump on the merged tree — that is what SPEC-146 and SPEC-159 ended up doing the slow way.

Second detection signal — the count mirrors. A skill added on a parallel branch leaves components.skills and the manifest disagreeing. Derive both; never quote either:

for p in plugins/*/.claude-plugin/plugin.json; do
  d=$(dirname "$p"); m="$d/skill-symlinks.manifest"; [ -f "$m" ] || continue
  live=$(grep -v '^#' "$m" | grep -c ' -> ')
  shown=$(python3 -c "import json,sys;print(json.load(open(sys.argv[1])).get('components',{}).get('skills','-'))" "$p")
  [ "$live" = "$shown" ] || echo "DRIFT $d: plugin.json=$shown manifest=$live"
done

Silence means every mirror agrees. Two traps in that one-liner, both already paid for: the grep -v '^#' is required because each manifest's header comment contains a literal -> and a naive count overcounts by one per file; and the count must be derived per plugin, because since the plugin split each manifest carries its own prefix.

The general form and the mirror list live elsewhere — do not duplicate them here. docs/solutions/patterns/count-reconciliation-single-source.md is the general rule (reconcile to ONE source, pin ALL N mirrors); docs/solutions/patterns/critical-patterns.md P-003 carries the per-bump checklist of which fields move together. That checklist's detection signal used to hardcode grep -c "^vt-c-", which returned 0 for every plugin but vt-base and read as drift-free; it was corrected on 2026-08-13 (BUG-067) and now carries the same derivation used above, pinned by scripts/tests/setup/57-plugin-component-counts-agree.sh. Either place is safe to follow.

Hazard (c) — running setup.sh from a worktree (fixed; guard in place)

setup.sh derives TOOLKIT_ROOT from BASH_SOURCE and writes it as the absolute symlink target for every skill, command, agent and hook. Deploying from a feature worktree therefore points your live ~/.claude at a directory that disappears when that worktree is removed.

This is fixed. assert_deployable_root() (scripts/setup.sh:966-1043) refuses to deploy from a linked worktree; --allow-worktree (parsed at :3316, declared at :60-61, honoured by the guard's early return at :967) is the deliberate opt-out. The incident record — two occurrences, and the finding that a worktree deploy does not merely defer the damage but silently downgrades live content — is in bugs/009-setup-worktree-path-baking/report.md. Read it there; it is not repeated here.

Hazard (d) — resolving the merge, once you are in it

Merge into main above covers how to run the merge — finding the holder, the throwaway-holder recipe, the MERGE_HEAD pre-commit exception. This is the other half: which files to reconcile, and how, once git has stopped and handed you conflicts. Do not look for these rules in the procedure section; they are not there.

The distinction that decides every case: is this file a ledger, or a mirror?

File Kind Rule
.design-state.yaml (specs_status, bugchen_status) ledger — each branch registers its own spec/bug Keep BOTH sides. The entries are additive, not competing. Dropping one un-registers a spec that exists
docs/change-ledger.md ledger — each branch appends its own entry Keep BOTH sides, in date order
plugins/*/CHANGELOG.md ledger Keep BOTH sides
plugin.json version, components.*, README/docs skill counts mirror — one correct value Take main's, then re-derive on the merged tree. See hazard (b)

Keeping both sides of a ledger and taking one side of a mirror is the whole rule. The failure is applying a mirror's instinct ("pick the right one") to a ledger, which silently deletes a real record.

Worked example, from the SPEC-160 reconciliation (docs/change-ledger.md, SPEC-160 entry): main's skill count was taken in every mirror, while both entries were kept in the CHANGELOG and the ledger. Same merge, two opposite resolutions, chosen by file kind.

Historical note — the --theirs gate-file rule no longer applies. That same entry records --theirs on the ephemeral .review-gate.md / .test-gate.md. Both files are now gitignored (.gitignore:44-47) and untracked — de032495 (BUG-014) deleted the tracked copies, and scripts/tests/setup/48-ephemeral-state-is-not-tracked.sh keeps them that way. Untracked files cannot produce a merge conflict, so the rule is recorded here only so that reading the older ledger entries does not mislead you. Do not go looking for a gate-file conflict to resolve.

How many at once — and what it costs you

The working ceiling is 4–6 concurrent feature worktrees. Treat that as a target to plan against, not a measurement: it comes from SPEC-167's user story, which is where the number was proposed. No throughput study has been run in this repo, and none is planned — see the honest status below.

What bounds it is human attention, not machine capacity. Git will happily give you twenty worktrees. Each one in flight needs a person to read its review findings, attest its DoR/DoD items, resolve its merge, and decide what to do about it — and git shortlog -sne --all shows that review attention concentrated in effectively one reviewer: two identities carry essentially all of it (rolfsterhh-visitrans and the pm-team address), against single- and low-double-digit counts for everyone else. Run it yourself before relying on the number. The async phases (context: fork review and finalize) genuinely run without you; everything on either side of them does not. The ceiling is therefore a statement about one person's review bandwidth, and it moves if that changes.

The costs are the four hazards above. They are not incidental — they are the tax:

Cost Where it lands
Registry and pointer drift hazard (a) — every extra worktree is another record that can go stale after teardown
Version and count collisions hazard (b) — collisions scale with the number of branches finalizing near each other, and two of the three shapes are silent
Merge reconciliation hazard (d) — every parallel branch eventually pays it
Staleness EC-3 — a branch forked early drifts behind main while you work elsewhere, and the drift is invisible until you merge

Beyond those, one cost stated as reasoning, not measurement — the same standard this section applies to the benefit: context-switching raises overhead and lowers deep flow. Steering several tasks means holding several problem-states, and re-entering one you left hours ago costs real time. That is a trade, not a free lunch, and it is the reason this section states a ceiling at all.

Honest status of the benefit. The claim that parallel work is faster — that the async phases amortise the ceremony across idle gaps — is an unvalidated hypothesis, not a finding. docs/evaluations/vtd-vs-v025-comparison.md labels it verbatim "The hypothesis (to validate, not assert)", and the trial that would have tested it was deliberately cut from SPEC-167 as an organizational experiment rather than a toolkit concern (specs/167-*/decisions.md, Decision 1). So: this doc tells you how to run several worktrees without re-paying the hazards above. It does not tell you that doing so is faster, because nobody here has measured that.

Serial work remains fully supported. Nothing in this doc deprecates working one spec at a time, and the ceiling is an upper bound, not a quota.

Running several worktrees in parallel

The loop below composes parts that already exist — it introduces no new machinery. Each step links to the procedure that owns it rather than repeating it.

Per task, once:

  1. Create the worktreecreate a feature worktree. Use the manager script, and remember its EC-1 side effect: when main is held nowhere the script can leave the Active-Workspace sitting on main, so check and restore HEAD afterwards.
  2. Sync it if main has movedgit merge main inside the new worktree (EC-3). Do this at creation, not at merge time; a branch that forks early and drifts is the cheapest hazard to avoid and the most annoying to discover late.
  3. Activate the spec there/vt-d-activate SPEC-NNN from inside that worktree, so the spec's state.yaml and the branch agree.

Then, steering several at once:

  1. Work the synchronous phases yourself/vt-d-2-plan, /vt-d-3-build. These need you.
  2. Hand off the asynchronous ones/vt-d-4-review and /vt-d-5-finalize both declare context: fork in their SKILL.md frontmatter and run without you. This is the only place parallelism actually buys idle time, and it is where you switch to another worktree.
  3. Mergemerge into main for the mechanics, then hazard (d) for which files to reconcile.
  4. Reconcile before teardown — run the drift check from hazard (a) before git worktree remove. After removal the evidence is gone.

EC-4 — a forked phase must never prompt. Because 4-review and 5-finalize run forked, they MUST NOT call AskUserQuestion. The skills state the consequence plainly: "The question will be swallowed, the sub-agent will die, and the parent conversation receives no output at all" — see the ## Error Handling section of both workflow-5-finalize/SKILL.md and workflow-4-review/SKILL.md (cited by section, not line: these files run past 1400 lines and the numbers drift). A forked phase reports; it does not ask. If you are extending either skill, that constraint is load-bearing — a prompt there does not degrade, it silently produces nothing.

What does not parallelise. Steps 1–4 and 6–7 are yours. Only step 5 runs while you are elsewhere, which is why the ceiling in the previous section is set by your review bandwidth rather than by how many worktrees git will create.

Spec-numbering across branches

This procedure is retired (SPEC-165, vt-base 3.53.0). It existed because /vt-d-spec-from-requirements read only the current worktree's .design-state.yaml, so in a fragmented multi-worktree checkout the number it proposed could collide — and it carried its own retirement clause: "if spec-from-requirements is ever fixed to scan all branches, this procedure can be retired." That fix shipped. Do not hand-compute SPEC IDs.

Both skills that mint IDs (/vt-d-spec-from-requirements, /vt-d-specs-from-prd) now call allocate_spec_id.py, which claims the number under a lock on a store shared by every worktree of the clone and takes the maximum of the working tree, every local and remote ref, and the stored counter. It always exits 0; on any failure it warns on stderr and falls back to a local scan that still includes the all-refs floor.

Both failure modes this section used to enumerate by hand are covered:

Old mode What it was Covered by
A — fragmented-ahead IDs on unmerged feature branches sit higher than main's registry, including spec directories that are committed but not yet registered The all-refs scan: registry blobs and ls-tree spec-directory basenames, across refs/heads + refs/remotes
B — stale-behind-main The current branch forked from main long ago, so its registry misses IDs main has since assigned Same scan — main is just another ref; no divergence check needed
(neither, and the real defect) An ID allocated but not yet committed in another worktree is invisible to any ref-based scan The shared counter, which reserves the number the moment it is handed out

To see what it decided, ask it rather than reconstructing the answer:

python3 ~/.claude/skills/vt-d-spec-from-requirements/scripts/allocate_spec_id.py --show

--show prints the resolved store path, each scanner's maximum, the chosen ID, the claim cost and any named diagnostic. Note it allocates: every call reserves its number, --show included, so use it when you actually want an ID, not as a read-only probe.

Illustrative layout example

This is an illustrative example of the role pattern at one point in time — your checkout will differ. It shows the shape, it is not a maintained inventory. Run git worktree list yourself for the current picture.

A git worktree list might look like this:

V025-claude-toolkit                         [chore/scratch]        ← Active-Workspace (repo root)
.worktrees/feature/spec-149-...             [feature/spec-149-...] ← Feature
.worktrees/feature/spec-151-...             [feature/spec-151-...] ← Feature
V025-wt-ims-workflow-gates                  [docs/doc-sync-...]    ← a sibling worktree (not currently holding main)

Note what's absent: no line shows [main]. In this snapshot main is held nowhere — the normal resting state (EC-1). A Main-Holder appears only transiently, at merge time.

Where this came from

  • SPEC-143 activation session (2026-05-28) surfaced the three symptoms — a worktree-manager.sh failure under held main, the cross-worktree merge dance, and the implicit branch-rename — and recognized them as one missing convention doc. Source proposal: intake/processed/from-projects/2026-05-28-v025-multi-worktree-workflow-conventions.md.
  • The 2026-07-04 spec-numbering incident is the worked example for Mode B: specs were created on chore/scratch, which had forked from main and was ~60 commits behind, so the tool mis-numbered them; the fix was a surgical re-apply of only the genuinely-new specs onto current main. This very SPEC-151 session then hit the fragmented-ahead hazard it documents — a scan of all branches surfaced a dropped SPEC-152 still living on chore/scratch, higher than main's registry.
  • The surface bug behind symptom 1 was fixed separately at commit b53fed62 (intake/processed/bugs/2026-05-28-resolved-worktree-manager-fails-when-from-branch-checked-out-elsewhere.md).
  • SPEC-167 (2026-08) added everything from parallel-work hazards and guardrails onward — the four hazards, the 4–6 ceiling, and the parallel-run procedure. Those sections came from incidents this repo had already paid for but had recorded only in commit messages and per-entry ledger notes; SPEC-167 generalised them into rules. The sections above that heading are SPEC-151's original doc and were not modified.
  • plugins/vt-base/skills/using-git-worktrees/SKILL.md — how to create an isolated worktree
  • plugins/vt-base/skills/git-worktree/SKILL.md — the worktree-manager.sh wrapper
  • plugins/vt-product-dev/skills/activate/SKILL.md — Step 6.7 detects the held-main case at activation
  • plugins/vt-product-dev/skills/statusline-spec-phase/SKILL.md — opt-in statusline showing the active spec and phase for the session you are in (single-session by design; it cannot see other worktrees, so it complements this doc rather than replacing git worktree list)
  • docs/solutions/patterns/cross-repo-spec-tracking.md — the sibling "one repo, one spec namespace" rule
  • intake/projects.yaml — registry of projects for cross-project awareness