---
name: setup-worktree
description: Symlink shared project state (dependencies, generated code, env files) from the current git worktree to the main working tree. Run after creating a new worktree so tooling can reuse installed deps without reinstalling per worktree. Idempotent — safe to re-run. Do NOT run `npm install` here — only install in the main working tree, and only when a dep error appears or package.json/lockfile changes.
---

# Setup Worktree

> 🔗 **setup-worktree** — linking shared state to the main tree.

Print that line before doing anything else.

## What it does

Creates idempotent symlinks from the current worktree → the main working tree for:

**Dependencies and generated code** (expensive to rebuild per worktree):
- `node_modules/`
- `apps/ui/node_modules/`, `apps/mastra/node_modules/`, `packages/workflow-types/node_modules/`
- `apps/ui/src/generated/prisma/`
- `packages/workflow-types/dist/`
- `apps/ui/.env.local` (required for `next build`; typecheck/tests stub Prisma)

The list is spiralpegasus-specific. Edit the `TARGETS` array below to adjust for other repos.

## Preconditions

- Must be invoked from inside a git worktree (not the main working tree).
- Refuses to run in the main working tree — nothing to link to.
- Refuses if the target path inside the worktree exists AS A REAL FILE/DIR (not a symlink). The script reports what it skipped; the user decides whether to move or delete.

## Behavior

Idempotent rules, per target path:

| Current state in worktree | Action |
|---------------------------|--------|
| symlink → correct target | skip (already linked) |
| symlink → wrong target | replace |
| broken symlink | replace |
| real file/dir, non-empty | **skip with warning** — do not clobber |
| real file/dir, empty | remove and link |
| missing | create link |

If a required target is missing in the main tree (e.g. `node_modules` not yet installed), the script warns and skips that entry — run `npm install` in the main tree, then re-run this skill.

## When to use

- After creating a new worktree (`git worktree add ...`)
- Any time a worktree feels "stale" — re-running is safe

## Do NOT

- **Do not run `npm install` from inside this skill.** The main working tree owns dependency state. If a dep error appears or `package.json`/`package-lock.json` changed, install in the main tree only, then re-run this skill to refresh.
- **Do not symlink git-tracked directories** (e.g. `patches/`, `apps/ui/src/`). Those diverge per branch and must live in each worktree independently.

## How to invoke

Run this script via Bash. It prints one line per target: `linked`, `already-linked`, `replaced`, `skipped (reason)`.

```bash
set -euo pipefail

CURRENT=$(git rev-parse --show-toplevel 2>/dev/null || true)
if [[ -z "$CURRENT" ]]; then
  echo "error: not inside a git repository" >&2
  exit 1
fi

MAIN=$(git worktree list | head -1 | awk '{print $1}')
if [[ -z "$MAIN" || ! -d "$MAIN" ]]; then
  echo "error: could not resolve main working tree from 'git worktree list'" >&2
  exit 1
fi

if [[ "$CURRENT" == "$MAIN" ]]; then
  echo "error: running inside the main working tree ($MAIN) — nothing to link to" >&2
  echo "this skill is meant to be run from a secondary worktree" >&2
  exit 1
fi

echo "main: $MAIN"
echo "worktree: $CURRENT"
echo

TARGETS=(
  "node_modules"
  "apps/ui/node_modules"
  "apps/mastra/node_modules"
  "packages/workflow-types/node_modules"
  "apps/ui/src/generated/prisma"
  "packages/workflow-types/dist"
  "apps/ui/.env.local"
)

link_one() {
  local rel="$1"
  local src="$MAIN/$rel"
  local dest="$CURRENT/$rel"

  if [[ ! -e "$src" && ! -L "$src" ]]; then
    printf "  skipped %-48s (main has no %s — install deps or create it first)\n" "$rel" "$rel"
    return
  fi

  mkdir -p "$(dirname "$dest")"

  if [[ -L "$dest" ]]; then
    local resolved
    resolved=$(readlink "$dest")
    if [[ "$resolved" == "$src" ]]; then
      printf "  already-linked %-41s\n" "$rel"
      return
    fi
    rm "$dest"
    ln -s "$src" "$dest"
    printf "  replaced %-47s (was → %s)\n" "$rel" "$resolved"
    return
  fi

  if [[ -e "$dest" ]]; then
    if [[ -d "$dest" && -z "$(ls -A "$dest" 2>/dev/null)" ]]; then
      rmdir "$dest"
      ln -s "$src" "$dest"
      printf "  linked %-49s (empty dir removed)\n" "$rel"
      return
    fi
    printf "  skipped %-48s (exists as real file/dir — move or delete to link)\n" "$rel"
    return
  fi

  ln -s "$src" "$dest"
  printf "  linked %-49s\n" "$rel"
}

for rel in "${TARGETS[@]}"; do
  link_one "$rel"
done

echo
echo "done. do NOT run npm install in this worktree — install in $MAIN only, then re-run this skill."
```

## Installation

Drop this `SKILL.md` into `~/.claude/skills/setup-worktree/`:

```bash
mkdir -p ~/.claude/skills/setup-worktree
# save this file as ~/.claude/skills/setup-worktree/SKILL.md
```

Then invoke via `/setup-worktree` in Claude Code.
