Automate isolated development environments using git worktrees and tmux sessions with Claude Code. Enables parallel work across multiple tasks, projects, and repositories.
Commands
| review <ref>
| PR review session
| /psm review omc#123
| fix <ref>
| Issue fix session
| /psm fix omc#42
| feature <proj> <name>
| Feature development
| /psm feature omc add-webhooks
| list [project]
| List active sessions
| /psm list
| attach <session>
| Attach to session
| /psm attach omc:pr-123
| kill <session>
| Kill session
| /psm kill omc:pr-123
| cleanup
| Clean merged/closed
| /psm cleanup
| status
| Current session info
| /psm status
Project References
Supported formats:
-
Alias:
omc#123(requires~/.psm/projects.json) -
Full:
owner/repo#123 -
URL:
https://github.com/owner/repo/pull/123 -
Current:
#123(uses current directory's repo)
Configuration
Project Aliases (~/.psm/projects.json)
{
"aliases": {
"omc": {
"repo": "Yeachan-Heo/oh-my-claudecode",
"local": "~/Workspace/oh-my-claudecode",
"default_base": "main"
}
},
"defaults": {
"worktree_root": "~/.psm/worktrees",
"cleanup_after_days": 14
}
}
Directory Structure
~/.psm/
├── projects.json # Project aliases
├── sessions.json # Active session registry
└── worktrees/ # Worktree storage
└── <project>/
└── <type>-<id>/
Session Naming
| PR Review
| psm:omc:pr-123
| ~/.psm/worktrees/omc/pr-123
| Issue Fix
| psm:omc:issue-42
| ~/.psm/worktrees/omc/issue-42
| Feature
| psm:omc:feat-auth
| ~/.psm/worktrees/omc/feat-auth
Implementation Protocol
When the user invokes a PSM command, follow this protocol:
Parse Arguments
Parse {{ARGUMENTS}} to determine:
-
Subcommand: review, fix, feature, list, attach, kill, cleanup, status
-
Reference: project#number, URL, or session ID
-
Options: --branch, --base, --no-claude, --no-tmux, etc.
Subcommand: review <ref>
Purpose: Create PR review session
Steps:
- Resolve reference:
# Read project aliases
cat ~/.psm/projects.json 2>/dev/null || echo '{"aliases":{}}'
# Parse ref format: alias#num, owner/repo#num, or URL
# Extract: project_alias, repo (owner/repo), pr_number, local_path
- Fetch PR info:
gh pr view <pr_number> --repo <repo> --json number,title,author,headRefName,baseRefName,body,files,url
- Ensure local repo exists:
# If local path doesn't exist, clone
if [[ ! -d "$local_path" ]]; then
git clone "https://github.com/$repo.git" "$local_path"
fi
- Create worktree:
worktree_path="$HOME/.psm/worktrees/$project_alias/pr-$pr_number"
# Fetch PR branch
cd "$local_path"
git fetch origin "pull/$pr_number/head:pr-$pr_number-review"
# Create worktree
git worktree add "$worktree_path" "pr-$pr_number-review"
- Create session metadata:
cat > "$worktree_path/.psm-session.json" << EOF
{
"id": "$project_alias:pr-$pr_number",
"type": "review",
"project": "$project_alias",
"ref": "pr-$pr_number",
"branch": "<head_branch>",
"base": "<base_branch>",
"created_at": "$(date -Iseconds)",
"tmux_session": "psm:$project_alias:pr-$pr_number",
"worktree_path": "$worktree_path",
"source_repo": "$local_path",
"github": {
"pr_number": $pr_number,
"pr_title": "<title>",
"pr_author": "<author>",
"pr_url": "<url>"
},
"state": "active"
}
EOF
- Update sessions registry:
# Add to ~/.psm/sessions.json
- Create tmux session:
tmux new-session -d -s "psm:$project_alias:pr-$pr_number" -c "$worktree_path"
- Launch Claude Code (unless --no-claude):
tmux send-keys -t "psm:$project_alias:pr-$pr_number" "claude" Enter
- Output session info:
Session ready!
ID: omc:pr-123
Worktree: ~/.psm/worktrees/omc/pr-123
Tmux: psm:omc:pr-123
To attach: tmux attach -t psm:omc:pr-123
Subcommand: fix <ref>
Purpose: Create issue fix session
Steps:
-
Resolve reference (same as review)
-
Fetch issue info:
gh issue view <issue_number> --repo <repo> --json number,title,body,labels,url
- Create feature branch:
cd "$local_path"
git fetch origin main
branch_name="fix/$issue_number-$(echo "$title" | tr ' ' '-' | tr '[:upper:]' '[:lower:]' | head -c 30)"
git checkout -b "$branch_name" origin/main
- Create worktree:
worktree_path="$HOME/.psm/worktrees/$project_alias/issue-$issue_number"
git worktree add "$worktree_path" "$branch_name"
-
Create session metadata (similar to review, type="fix")
-
Update registry, create tmux, launch claude (same as review)
Subcommand: feature <project> <name>
Purpose: Start feature development
Steps:
-
Resolve project (from alias or path)
-
Create feature branch:
cd "$local_path"
git fetch origin main
branch_name="feature/$feature_name"
git checkout -b "$branch_name" origin/main
- Create worktree:
worktree_path="$HOME/.psm/worktrees/$project_alias/feat-$feature_name"
git worktree add "$worktree_path" "$branch_name"
- Create session, tmux, launch claude (same pattern)
Subcommand: list [project]
Purpose: List active sessions
Steps:
- Read sessions registry:
cat ~/.psm/sessions.json 2>/dev/null || echo '{"sessions":{}}'
- Check tmux sessions:
tmux list-sessions -F "#{session_name}" 2>/dev/null | grep "^psm:"
- Check worktrees:
ls -la ~/.psm/worktrees/*/ 2>/dev/null
- Format output:
Active PSM Sessions:
ID | Type | Status | Worktree
-------------------|---------|----------|---------------------------
omc:pr-123 | review | active | ~/.psm/worktrees/omc/pr-123
omc:issue-42 | fix | detached | ~/.psm/worktrees/omc/issue-42
Subcommand: attach <session>
Purpose: Attach to existing session
Steps:
-
Parse session ID:
project:type-number -
Verify session exists:
tmux has-session -t "psm:$session_id" 2>/dev/null
- Attach:
tmux attach -t "psm:$session_id"
Subcommand: kill <session>
Purpose: Kill session and cleanup
Steps:
- Kill tmux session:
tmux kill-session -t "psm:$session_id" 2>/dev/null
- Remove worktree:
worktree_path=$(jq -r ".sessions[\"$session_id\"].worktree" ~/.psm/sessions.json)
source_repo=$(jq -r ".sessions[\"$session_id\"].source_repo" ~/.psm/sessions.json)
cd "$source_repo"
git worktree remove "$worktree_path" --force
- Update registry:
# Remove from sessions.json
Subcommand: cleanup
Purpose: Clean up merged PRs and closed issues
Steps:
-
Read all sessions
-
For each PR session, check if merged:
gh pr view <pr_number> --repo <repo> --json merged,state
- For each issue session, check if closed:
gh issue view <issue_number> --repo <repo> --json closed,state
- Clean up merged/closed sessions:
Kill tmux session
-
Remove worktree
-
Update registry
-
Report:
Cleanup complete:
Removed: omc:pr-123 (merged)
Removed: omc:issue-42 (closed)
Kept: omc:feat-auth (active)
Subcommand: status
Purpose: Show current session info
Steps:
- Detect current session from tmux or cwd:
tmux display-message -p "#{session_name}" 2>/dev/null
# or check if cwd is inside a worktree
- Read session metadata:
cat .psm-session.json 2>/dev/null
- Show status:
Current Session: omc:pr-123
Type: review
PR: #123 - Add webhook support
Branch: feature/webhooks
Created: 2 hours ago
Error Handling
| Worktree exists | Offer: attach, recreate, or abort
| PR not found | Verify URL/number, check permissions
| No tmux | Warn and skip session creation
| No gh CLI | Error with install instructions
Requirements
-
gitwith worktree support (v2.5+) -
ghCLI (authenticated) -
tmux -
jqfor JSON parsing
Initialization
On first run, create default config:
mkdir -p ~/.psm/worktrees ~/.psm/logs
# Create default projects.json if not exists
if [[ ! -f ~/.psm/projects.json ]]; then
cat > ~/.psm/projects.json << 'EOF'
{
"aliases": {
"omc": {
"repo": "Yeachan-Heo/oh-my-claudecode",
"local": "~/Workspace/oh-my-claudecode",
"default_base": "main"
}
},
"defaults": {
"worktree_root": "~/.psm/worktrees",
"cleanup_after_days": 14,
"auto_cleanup_merged": true
}
}
EOF
fi
# Create sessions.json if not exists
if [[ ! -f ~/.psm/sessions.json ]]; then
echo '{"version":1,"sessions":{},"stats":{"total_created":0,"total_cleaned":0}}' > ~/.psm/sessions.json
fi