Create New Worktree
Creates a new git worktree with:
- Shared assets (symlinked or copied)
- Independent environment per project type
- Proper
.envrcconfiguration for direnv
Arguments
worktree_name(required): Name for the worktree and branchassets_path(optional): Path to external assets directory to symlink from
Cookbook
Setup guide: cookbook/setup.md -- guided walkthrough for creating .worktree.yml (read when no config exists or user requests setup help)
Parallelization
CRITICAL: Use Task tool with model: haiku for parallel execution when:
- Setting up assets in multiple directories
- Installing dependencies in multiple environments
- Any operation that can run independently
Spawn separate haiku agents for each independent task to maximize parallelization.
Example - parallel environment setup:
Task(model: haiku, prompt: "Setup Python venv in /path/to/backend")
Task(model: haiku, prompt: "Run npm install in /path/to/frontend")
Task(model: haiku, prompt: "Create symlinks for assets in /path/to/data")
Send all independent Task calls in a SINGLE message to run them concurrently.
Pre-Flight Checks
Verify git repo root:
git rev-parse --show-toplevelValidate worktree name:
- Must be provided
- No spaces or special chars (except
-and_)
Check for existing worktree/branch:
- If worktree exists: STOP and report
- If branch exists: Ask user to confirm reuse
Check for .worktree.yml:
REPO_ROOT=$(git rev-parse --show-toplevel) find "$REPO_ROOT" -maxdepth 1 -name ".worktree.yml" -type f | head -1- If
.worktree.ymlexists: continue to Asset Discovery (Step 4) normally - If
.worktree.ymldoes NOT exist: prompt the user via AskUserQuestion:No
.worktree.ymlfound in your project. This file configures how worktrees set up assets (symlinks, copies) and environments. Without it, the skill infers configuration from directory structure and.gitignore.Would you like to create a
.worktree.ymlnow? I can guide you through the setup. (You can skip this and the skill will use inference instead.) - If user says yes: read
cookbook/setup.mdand follow the guided creation flow. After creating the config, continue to Step 4 (Asset Discovery) using the new config. - If user says no or skips: continue to Step 4 (Asset Discovery) with inference mode.
- If
Asset Discovery
When assets_path is provided
Use the provided path directly. Mirror its structure into the worktree via symlinks.
When assets_path is NOT provided (inference mode)
Step 1: Check for sibling assets directory
Look for directories matching <repo-name>-assets pattern:
REPO_NAME=$(basename "$(git rev-parse --show-toplevel)")
REPO_PARENT=$(dirname "$(git rev-parse --show-toplevel)")
ASSETS_CANDIDATES=(
"$REPO_PARENT/${REPO_NAME}-assets"
"$REPO_PARENT/assets"
"$REPO_PARENT/.${REPO_NAME}-assets"
)
If found, use it as assets_path.
Step 2: Analyze .gitignore for local assets
If no sibling assets directory found, parse .gitignore to identify:
# Find gitignored directories/files that exist locally
git ls-files --others --ignored --exclude-standard --directory
Filter for:
- Directories containing data files (
inputs/,data/,fixtures/) - Environment files (
.env,.env.local,*.env) - Config files that vary per environment (
config.local.*) - Large binary directories (
models/,weights/,cache/)
Step 3: Classify assets
For each discovered asset, classify:
| Asset Type | Action | Reason |
|---|---|---|
.env files |
Copy | Secrets, may need per-worktree customization |
inputs/, data/ dirs |
Symlink | Large, read-only shared data |
config.*.local |
Copy | May need per-worktree customization |
| Binary/model dirs | Symlink | Large, read-only |
Execution Flow
CRITICAL: AUTONOMOUS EXECUTION REQUIRED
This command MUST execute ALL steps (1-8) in sequence WITHOUT stopping for user input. After the branch skill invocation completes in Step 2, IMMEDIATELY continue to Step 3. Do NOT wait for user confirmation between steps.
Execution pattern:
- Generate unique name
- Invoke
Skill(skill="branch", args="...")-> when it completes, CONTINUE IMMEDIATELY - Create worktree -> CONTINUE
- Setup assets -> CONTINUE
- Setup environment -> CONTINUE
- Enable direnv -> CONTINUE
- Commit changes -> CONTINUE
- Display summary -> DONE
Execution Steps
Step 1: Generate unique name
UUID_PREFIX=$(uuidgen | cut -c1-6 | tr 'A-Z' 'a-z')
FULL_NAME="$UUID_PREFIX-<worktree_name>"
This ensures branch name and worktree directory match exactly.
Step 2: Create branch with spec directory
Invoke the branch skill with the full name:
Skill(skill="branch", args="$FULL_NAME")
Creates AND COMMITS (CRITICAL):
- Branch:
$FULL_NAME(e.g.,a1b2c3-feature-name) - Spec dir:
specs/<YYYY>/<MM>/$FULL_NAME/ - Backlog:
000-backlog.md
Why commit is critical: If spec dir is not committed before git worktree add, the files won't exist in the new worktree (worktree is created from the branch's committed state).
AFTER branch COMPLETES: Do NOT stop. Do NOT show todos. IMMEDIATELY proceed to Step 3.
Step 3: Create worktree
git worktree add "trees/$FULL_NAME" "$FULL_NAME"
Branch and worktree directory now have the same name.
Step 4: Setup assets based on .worktree.yml
4a. Discover all .worktree.yml files:
find "$WORKTREE_PATH" -name ".worktree.yml" -type f
4b. For each config file, process assets with correct path resolution:
for CONFIG_FILE in $(find "$WORKTREE_PATH" -name ".worktree.yml"); do
CONFIG_DIR=$(dirname "$CONFIG_FILE")
CONFIG_RELATIVE=$(realpath --relative-to="$WORKTREE_PATH" "$CONFIG_DIR")
# Read source from config (use yq or grep+sed)
SOURCE_VALUE=$(yq '.assets.source' "$CONFIG_FILE")
# CRITICAL: Resolve source relative to CONFIG_DIR, not CWD
if [[ -n "$SOURCE_VALUE" && "$SOURCE_VALUE" != "null" ]]; then
ASSETS_SOURCE=$(cd "$CONFIG_DIR" && realpath "$SOURCE_VALUE")
else
# Fallback to inference
ASSETS_SOURCE="$INFERRED_ASSETS_PATH/$CONFIG_RELATIVE"
fi
# Process symlinks
for ASSET in $(yq '.assets.symlink[]' "$CONFIG_FILE"); do
SOURCE="$ASSETS_SOURCE/$ASSET"
TARGET="$CONFIG_DIR/$ASSET"
rm -rf "$TARGET"
ln -sf "$SOURCE" "$TARGET"
done
# Process copies
for ASSET in $(yq '.assets.copy[]' "$CONFIG_FILE"); do
SOURCE="$ASSETS_SOURCE/$ASSET"
TARGET="$CONFIG_DIR/$ASSET"
[[ -f "$SOURCE" ]] && cp "$SOURCE" "$TARGET"
done
# Process root_symlinks (gitignored dirs symlinked from repo root)
if ! REPO_ROOT=$(git -C "$WORKTREE_PATH" rev-parse --show-toplevel); then
REPO_ROOT="$WORKTREE_PATH"
fi
# For worktrees, resolve to the main repo root
MAIN_WORKTREE=$(git -C "$WORKTREE_PATH" worktree list --porcelain | head -1 | sed 's/^worktree //')
[[ -n "$MAIN_WORKTREE" ]] && REPO_ROOT="$MAIN_WORKTREE"
ROOT_SYMLINKS=$(yq '.root_symlinks[]' "$CONFIG_FILE" || true)
for ASSET in $ROOT_SYMLINKS; do
[[ -z "$ASSET" || "$ASSET" == "null" ]] && continue
SOURCE="$REPO_ROOT/$ASSET"
TARGET="$WORKTREE_PATH/$ASSET"
if [[ -e "$SOURCE" ]]; then
rm -rf "$TARGET"
ln -sf "$SOURCE" "$TARGET"
else
echo "WARN: root_symlink source not found: $SOURCE"
fi
done
done
If action = COPY:
cp -r "$SOURCE_PATH" "$WORKTREE_PATH/$RELATIVE_PATH"
If action = SYMLINK:
# Remove placeholder if exists (may show as deleted in git status - expected)
rm -rf "$WORKTREE_PATH/$RELATIVE_PATH"
# Create symlink with absolute path to source
ln -sf "$ABSOLUTE_SOURCE_PATH" "$WORKTREE_PATH/$RELATIVE_PATH"
Important: Symlinked paths that replace tracked placeholder files will show as deleted in git status. This is expected and should NOT be committed.
COMMON BUG: Do NOT resolve assets.source from repo root or CWD. Always cd to the config file's directory first.
Step 5: Detect and setup environment
PARALLELIZE: When multiple environments are configured, spawn separate haiku agents for each.
Detect project type by presence of:
| File | Project Type |
|---|---|
pyproject.toml, requirements.txt |
Python |
package.json |
Node.js |
Cargo.toml |
Rust |
go.mod |
Go |
Gemfile |
Ruby |
For each project root (may be multiple in monorepo) - RUN IN PARALLEL:
# Send ALL environment setup tasks in a SINGLE message:
Task(model: haiku, prompt: "Setup Python venv at <path1>: create .venv, install deps")
Task(model: haiku, prompt: "Setup Node at <path2>: npm install")
Task(model: haiku, prompt: "Setup Python venv at <path3>: create .venv, install deps")
Python projects
Create
.envrc:dotenv_if_exists .env source .venv/bin/activateSymlink
.envto worktree root:ln -sf <relative_path_to_root>/.env .envCreate venv and install:
python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt [ -f requirements.dev.txt ] && pip install -r requirements.dev.txt [ -f pyproject.toml ] && pip install -e .
Node.js projects
Create
.envrc:dotenv_if_exists .env PATH_add node_modules/.binInstall dependencies:
npm install # or yarn/pnpm based on lockfile
Other project types
Detect and configure accordingly. Prioritize:
- Linking environment files
- Installing dependencies
- Setting up direnv
Step 6: Enable direnv
For each configured directory:
direnv allow
If direnv not installed, warn but continue.
Step 7: Commit setup changes
CRITICAL: Commit all worktree setup changes to leave a clean state.
cd "$WORKTREE_PATH"
git add -A
git status --short
# Only commit if there are changes
if ! git diff --cached --quiet; then
git commit -m "chore(worktree): setup $WORKTREE_NAME
- Configure asset symlinks
- Setup development environment
- Create .envrc for direnv"
fi
What gets committed:
- New files:
.envrc, copied assets - Deleted placeholders: files replaced by symlinks show as deleted (expected)
What stays untracked (gitignored):
.venv/,node_modules/,.env(secrets)
Step 8: Summary
Display:
Worktree created:
Path: trees/<UUID>-<worktree_name>
Branch: <worktree_name>
Spec: specs/<YYYY>/<MM>/<worktree_name>/
Assets configured:
[COPY] .env
[SYMLINK] data/inputs -> /abs/path/to/assets/data/inputs
[SYMLINK] models/ -> /abs/path/to/assets/models/
Environments setup:
- ./app (Python, venv created)
- ./frontend (Node.js, npm installed)
Error Handling
| Condition | Action |
|---|---|
| Worktree exists | STOP, report path |
| Branch exists | Ask to reuse or abort |
| Assets path not found | STOP if explicit, warn if inferred |
| venv creation fails | Warn, continue with other dirs |
| Dependency install fails | Warn, continue |
| direnv not installed | Warn, continue |
| Symlink target missing | Warn, skip that asset |
Project Configuration (Optional)
Projects can define .worktree.yml at repo root or in subdirectories:
# .worktree.yml
assets:
source: ../my-project-assets # Override default discovery
symlink:
- data/inputs
- models/
copy:
- .env
- config.local.yml
environments:
- path: backend
type: python
- path: frontend
type: node
skip_branch_command: false # Set true to skip /branch integration
# Symlink gitignored directories from repo root into worktrees
# NOT resolved from assets.source — uses main worktree root as base
root_symlinks:
- .specs # External specs git repo (shared across worktrees)
When .worktree.yml exists, use its configuration instead of inference.
Path Resolution Rules (CRITICAL)
All paths in .worktree.yml are resolved relative to the config file's directory, NOT the current working directory or repo root.
When processing .worktree.yml:
Determine config directory:
CONFIG_DIR=$(dirname "$CONFIG_FILE")Resolve
assets.source:# If source is relative path ASSETS_SOURCE=$(cd "$CONFIG_DIR" && realpath "$SOURCE_VALUE")Resolve symlink/copy paths:
- Source:
$ASSETS_SOURCE/<path> - Target:
$WORKTREE_PATH/$CONFIG_RELATIVE_PATH/<path>
- Source:
Example - Monorepo with subproject config
repo/
|-- .worktree.yml # Root config (if any)
`-- services/
`-- api/
`-- .worktree.yml # Subproject config
If services/api/.worktree.yml has:
assets:
source: ../../../repo-assets/services/api
copy:
- .env
Resolution:
CONFIG_DIR="services/api"
# source resolved from CONFIG_DIR:
ASSETS_SOURCE=$(cd "services/api" && realpath "../../../repo-assets/services/api")
# -> /abs/path/repo-assets/services/api
# .env source: $ASSETS_SOURCE/.env
# .env target: $WORKTREE_PATH/services/api/.env
Common Bug: Using CWD instead of CONFIG_DIR
WRONG:
# Resolves source relative to repo root - INCORRECT
ASSETS_SOURCE=$(realpath "$SOURCE_VALUE")
CORRECT:
# Resolves source relative to where .worktree.yml is located
ASSETS_SOURCE=$(cd "$CONFIG_DIR" && realpath "$SOURCE_VALUE")