From pds
Manages git worktrees for isolated parallel development: creates GitHub issue-tied branches via /pds:worktree command, lists worktrees, removes directories, prunes stale refs.
npx claudepluginhub rmzi/portable-dev-system --plugin pdsThis skill uses the workspace's default tool permissions.
Each stream of work gets its own directory. No stashing, no branch switching, no lost context.
Manages Git worktrees for parallel development: create from main with .env copying, list status, switch, cleanup interactively via bash script.
Creates isolated git worktrees for parallel development without disrupting the main workspace. Includes safety verification, .gitignore checks, and directory selection. Use for feature work or PR reviews.
Manages Git worktrees for isolated parallel development on multiple branches. Creates, lists, switches between, and removes worktrees with safety checks for uncommitted changes and unpushed commits.
Share bugs, ideas, or general feedback.
Each stream of work gets its own directory. No stashing, no branch switching, no lost context.
Every meaningful branch should be tied to a GitHub tracking issue so /pds:finish can post a canonical dev diary comment. Branch names must encode the issue number:
<type>/<issue>-<slug>
# examples
feat/89-cg-receive
fix/132-tmux-socket
refactor/108-backpressure
Invoke with an issue number and (optional) type:
/pds:worktree <issue-number> [type]
Protocol:
REPO_ROOT (see Path Resolution below).gh issue view <N> --json title -q .title to fetch the issue title.<type>/<N>-<slug> (default type=feat if omitted; use fix for bug issues, refactor for cleanup, etc.).git worktree add "$REPO_ROOT/.worktrees/<type>-<N>-<slug>" -b <type>/<N>-<slug>
cd into the worktree.Fail fast if gh issue view <N> returns no issue — do not fabricate a branch name from a stale or wrong number.
For spike exploration, PR review, or other work that genuinely has no tracking issue, the raw git worktree add forms below remain supported. /pds:finish will prompt for an issue number at ship time if it encounters a branch without the <type>/<issue>-<slug> pattern.
REPO_ROOT="$(git rev-parse --path-format=absolute --git-common-dir | sed 's|/.git$||')"
git worktree list # List worktrees
git worktree add "$REPO_ROOT/.worktrees/dir" branch # Create from existing branch
git worktree add "$REPO_ROOT/.worktrees/dir" -b new-branch # Create with new branch
git worktree remove "$REPO_ROOT/.worktrees/dir" # Remove worktree
git worktree prune # Clean stale references
Worktrees live inside the repo at .worktrees/:
project/ # main worktree (main/master)
project/.worktrees/feature-auth/ # feature work
project/.worktrees/hotfix-login/ # urgent fix
project/.worktrees/pr-123/ # reviewing a PR
Pattern: project/.worktrees/{branch-with-slashes-as-dashes}
.worktrees/ is auto-added to .gitignore on first use.
When running inside a worktree, relative .worktrees/ paths create nested directories instead of sibling worktrees at the repo root. Always resolve REPO_ROOT first:
REPO_ROOT="$(git rev-parse --path-format=absolute --git-common-dir | sed 's|/.git$||')"
This uses git plumbing to find the shared .git directory (which always lives in the main repo), then strips the /.git suffix. It's idempotent — returns the same path from both the main repo and any worktree. Requires Git 2.13+.
Then use "$REPO_ROOT/.worktrees/" in all commands.
| Alternative | Problem |
|---|---|
git rev-parse --show-toplevel | Returns the current worktree root, not the main repo root — exactly the bug this fixes |
dirname $(git rev-parse --git-common-dir) | Community pattern (anthropics/claude-code#1052) — same idea, but without --path-format=absolute it returns .git (relative) from the main repo, making dirname return . which is fragile |
git worktree list | head -1 | awk '{print $1}' | Works but slower (lists all worktrees), relies on output formatting |
Sibling directories (../project-feature) | Official Claude Code docs pattern — avoids the problem entirely but PDS uses .worktrees/ for organization |
We use --path-format=absolute to guarantee an absolute path regardless of where the command runs, combined with --git-common-dir to always resolve to the main repo's .git directory.
REPO_ROOT="$(git rev-parse --path-format=absolute --git-common-dir | sed 's|/.git$||')"
# Start feature work
git worktree add "$REPO_ROOT/.worktrees/feature-user-profiles" -b feature/user-profiles
cd "$REPO_ROOT/.worktrees/feature-user-profiles"
# Urgent bug — new worktree, no context lost
git worktree add "$REPO_ROOT/.worktrees/hotfix-critical" -b hotfix/critical-fix
cd "$REPO_ROOT/.worktrees/hotfix-critical"
# Fix bug, PR, merge, clean up
git worktree remove "$REPO_ROOT/.worktrees/hotfix-critical"
git branch -d hotfix/critical-fix
REPO_ROOT="$(git rev-parse --path-format=absolute --git-common-dir | sed 's|/.git$||')"
git fetch origin pull/123/head:pr-123
git worktree add "$REPO_ROOT/.worktrees/pr-123" pr-123
# Review, test, done
git worktree remove "$REPO_ROOT/.worktrees/pr-123"
git branch -d pr-123
REPO_ROOT="$(git rev-parse --path-format=absolute --git-common-dir | sed 's|/.git$||')"
git worktree add "$REPO_ROOT/.worktrees/spike-idea" -b spike/crazy-idea
# If good: merge. If bad: remove worktree + delete branch
git worktree remove "$REPO_ROOT/.worktrees/spike-idea"
git branch -D spike/crazy-idea
REPO_ROOT="$(git rev-parse --path-format=absolute --git-common-dir | sed 's|/.git$||')"
git worktree add "$REPO_ROOT/.worktrees/feature-api" -b feature/api
git worktree add "$REPO_ROOT/.worktrees/feature-ui" -b feature/ui
# Work on API in one terminal, UI in another
# Each has its own index, HEAD, and uncommitted changes
REPO_ROOT="$(git rev-parse --path-format=absolute --git-common-dir | sed 's|/.git$||')"
# Create worktrees for multi-agent work (see /swarm)
git worktree add "$REPO_ROOT/.worktrees/task-1-auth" -b task-1/auth
git worktree add "$REPO_ROOT/.worktrees/task-2-api" -b task-2/api
# Each agent gets its own isolated environment
# Use TaskCreate/Task tool for agent dispatch (see /pds:swarm)
Invoke as /pds:worktree gc to detect and remove stale worktrees.
| Type | Detection | Removal |
|---|---|---|
| Orphan | Directory in .worktrees/ not in git worktree list (broken git metadata) | rm -rf <path> (git doesn't know about it) |
| Merged branch | Worktree whose branch is already merged to main | git worktree remove <path> + git branch -d <branch> |
Resolve repo root:
REPO_ROOT="$(git rev-parse --path-format=absolute --git-common-dir | sed 's|/.git$||')"
Find orphans — dirs in .worktrees/ not registered with git:
# Get git-registered worktree paths
git worktree list --porcelain | grep '^worktree ' | sed 's|^worktree ||'
# Compare against .worktrees/*/ on disk
Find merged-branch worktrees:
# Determine main branch
git symbolic-ref refs/remotes/origin/HEAD | sed 's|refs/remotes/origin/||'
# List merged branches
git branch --merged main
# Cross-reference with git worktree list
For each stale worktree, report:
Triage before removal. Classify each stale worktree and handle accordingly:
| State | Detection | Action |
|---|---|---|
| Dirty (uncommitted changes) | git -C <path> status --porcelain is non-empty | Offer /pds:finish before removal — code may be unsaved |
Has artifacts (.claude/swarm/ exists) | test -d <path>/.claude/swarm | Offer extraction: archive *.md to docs/swarm-reports/ and distill 1-2 auto-memory entries |
| Clean stale (no changes, no artifacts) | Neither of the above | Remove directly |
For dirty worktrees: warn the user and offer to run /pds:finish in that worktree first. Do not remove until the user confirms.
For worktrees with artifacts: run the extraction step from /pds:finish Step 0 (archive swarm reports + distill to auto-memory), then proceed with removal.
Ask user for confirmation before removing. List all candidates with their triage classification and wait for approval.
Remove confirmed worktrees:
git worktree remove "$REPO_ROOT/.worktrees/<name>" then git branch -d <branch>rm -rf "$REPO_ROOT/.worktrees/<name>"Clean up: git worktree prune to clear stale git references.
Report summary: N removed, N extracted-then-removed, N skipped (dirty, user declined finish).
PDS detects stale worktrees at session start and warns in context:
STALE WORKTREES: 3 stale worktree(s) in .worktrees/. Run /pds:worktree gc to clean up.
REPO_ROOT="$(git rev-parse --path-format=absolute --git-common-dir | sed 's|/.git$||')"
# Remove a specific worktree and its branch
git worktree remove "$REPO_ROOT/.worktrees/done-feature"
git branch -d done-feature-branch
# Clean stale references (worktree dir already deleted)
git worktree prune
# Find branches already merged to main
git branch --merged main
# Delete merged branches
git branch -d merged-branch-name
.worktrees/, not /tmp or ../git rev-parse --path-format=absolute --git-common-dir | sed 's|/.git$||' before any .worktrees/ path to avoid nested directories when running from a worktree