Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .claude/commands/worktree-ls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
description: List all active git worktrees with status
argument-hint: ""
---

# List Git Worktrees

## Instructions

### 1. Get Worktree List

Use porcelain format for reliable, machine-parseable output:

```bash
git worktree list --porcelain
```

### 2. Parse and Check Status

Parse the porcelain output into records. Each worktree is a block of lines
separated by a blank line, with fields:
- `worktree <path>` — the absolute path
- `HEAD <sha>` — the full commit hash
- `branch refs/heads/<name>` — the branch (or `detached` if HEAD is detached)

For each worktree path, check for uncommitted changes:

```bash
git -C "$WORKTREE_PATH" status --porcelain | wc -l
```

Quote `$WORKTREE_PATH` in all commands to handle paths with spaces.

### 3. Display Results

Show a table with:
- **Path**
- **Branch**
- **Commit** (short hash — first 7 characters of HEAD)
- **Status**: "clean" or "N uncommitted changes"

If there's only the main worktree, add:
```
No additional worktrees. Use /worktree-new <name> to create one.
```
125 changes: 125 additions & 0 deletions .claude/commands/worktree-new.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
---
description: Create a new git worktree with full dev environment for parallel work
argument-hint: "<name> [base-branch]"
---

# Create Git Worktree

Create an isolated worktree for parallel development. Arguments: $ARGUMENTS

## Instructions

### 1. Parse Arguments

Parse `$ARGUMENTS` to extract:
- **name** (required): First argument — used as both directory suffix and branch name
- **base-ref** (optional): Second argument — existing branch, tag, or ref to branch
from (creates branch `<name>` starting at that ref)

If no name is provided, abort with:
```
Error: Name required. Usage: /worktree-new <name> [base-branch]
Example: /worktree-new feature-bacon-fix
```

Validate that **name** starts with a letter or digit, followed by `[a-zA-Z0-9._-]`.
If it starts with `-` or contains spaces, slashes, or other shell metacharacters, abort:
```
Error: Name must start with a letter or digit and contain only letters, digits, dots, hyphens, and underscores.
Got: <name>
```

If **base-ref** is provided, apply the same character validation (must match
`^[a-zA-Z0-9][a-zA-Z0-9._/-]*$` — slashes are allowed for refs like `origin/main`).
Then verify the ref exists:

```bash
git rev-parse --verify --quiet "$BASE_REF"
```

If verification fails, abort:
```
Error: Ref not found: <base-ref>
Available branches:
<output of: git branch -a --format='%(refname:short)'>
```

### 2. Resolve Paths

Derive paths dynamically (do NOT hardcode the repo name):

```bash
MAIN_ROOT="$(git worktree list --porcelain | head -1 | sed 's/^worktree //')"
REPO_NAME="$(basename "$MAIN_ROOT")"
PARENT_DIR="$(dirname "$MAIN_ROOT")"
WORKTREE_PATH="${PARENT_DIR}/${REPO_NAME}-<name>"
```

Use `$WORKTREE_PATH` (the absolute path) for all subsequent commands.

### 3. Validate

```bash
git worktree list
```

- If a worktree already exists at `$WORKTREE_PATH`, abort with an error.
- If a branch named `<name>` already exists and no base-ref was given:
- First check if the branch is already checked out in a worktree
(parse `git worktree list --porcelain` for a `branch refs/heads/<name>` line).
- If checked out elsewhere, abort:
```
Error: Branch '<name>' is already checked out in worktree at <path>.
Use a different name or remove that worktree first.
```
- Otherwise, ask the user whether to check out that existing branch
or pick a different name. If the user chooses to use it:
```bash
git worktree add -- "$WORKTREE_PATH" "<name>"
```
Then skip step 4 and continue to step 5.

### 4. Create the Worktree

```bash
# If base-ref provided (create new branch <name> starting at base-ref):
git worktree add -b "<name>" -- "$WORKTREE_PATH" "$BASE_REF"

# If no base-ref (create new branch from current HEAD):
git worktree add -b "<name>" -- "$WORKTREE_PATH"
```

### 5. Set Up Python Environment

Note to user: dependency installation may take a moment on a fresh venv.

```bash
python3 -m venv "$WORKTREE_PATH/.venv"
"$WORKTREE_PATH/.venv/bin/pip" install --upgrade pip
"$WORKTREE_PATH/.venv/bin/pip" install -e "$WORKTREE_PATH[dev]"
```

Do NOT use `-q` — let pip output stream so the user sees progress.

### 6. Build Rust Backend (best-effort)

Use `--manifest-path` to avoid changing directories:

```bash
"$WORKTREE_PATH/.venv/bin/maturin" develop --manifest-path "$WORKTREE_PATH/Cargo.toml"
```

If maturin is not installed in the venv or the build fails, note that pure-Python
mode will be used and continue. This is not an error.

### 7. Report

Print:

```
Worktree ready: $WORKTREE_PATH
Branch: <branch>

To start working:
cd $WORKTREE_PATH && source .venv/bin/activate && claude
```
100 changes: 100 additions & 0 deletions .claude/commands/worktree-rm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
description: Remove a git worktree and optionally delete its branch
argument-hint: "<name>"
---

# Remove Git Worktree

Remove an existing worktree. Arguments: $ARGUMENTS

## Instructions

### 1. Parse Arguments

Extract **name** from `$ARGUMENTS`. If empty, abort:
```
Error: Name required. Usage: /worktree-rm <name>
Tip: Run /worktree-ls to see active worktrees.
```

Validate that **name** starts with a letter or digit, followed by `[a-zA-Z0-9._-]`.
If it starts with `-` or contains invalid characters, abort:
```
Error: Name must start with a letter or digit and contain only letters, digits, dots, hyphens, and underscores.
Got: <name>
```

### 2. Resolve Paths

```bash
MAIN_ROOT="$(git worktree list --porcelain | head -1 | sed 's/^worktree //')"
REPO_NAME="$(basename "$MAIN_ROOT")"
PARENT_DIR="$(dirname "$MAIN_ROOT")"
WORKTREE_PATH="${PARENT_DIR}/${REPO_NAME}-<name>"
```

### 3. Validate

```bash
git worktree list
```

If no worktree exists at `$WORKTREE_PATH`, abort:
```
Error: No worktree found at $WORKTREE_PATH
Active worktrees: <list them>
```

### 4. Check for Uncommitted Work

Capture the branch name first (needed for step 6, before the worktree is removed):

```bash
BRANCH=$(git -C "$WORKTREE_PATH" rev-parse --abbrev-ref HEAD)
```

Then check for uncommitted changes:

```bash
git -C "$WORKTREE_PATH" status --porcelain
```

If there are uncommitted changes, warn the user with AskUserQuestion:
- Option 1: "Abort — I have unsaved work"
- Option 2: "Remove anyway — discard changes"

**If the user chooses "Abort", stop immediately. Do NOT continue to step 5.**

### 5. Remove the Worktree

If the worktree had uncommitted changes and the user chose "Remove anyway":
```bash
git worktree remove "$WORKTREE_PATH" --force
```

If the worktree was clean (step 4 found no changes):
```bash
git worktree remove "$WORKTREE_PATH"
```

### 6. Try to Delete the Branch

Only attempt branch deletion if `$BRANCH` equals `<name>` (meaning we created it
via `/worktree-new <name>` without a base-ref). If the branch is something else
(e.g., `feature/existing-branch`), skip deletion — the user didn't create it.

```bash
git branch -d -- "$BRANCH"
```

Let the output print naturally:
- If the branch was merged, it will be deleted and git prints a confirmation.
- If not fully merged, git prints a warning — relay that to the user
(suggest `git branch -D -- "$BRANCH"` if they want to force-delete).

### 7. Report

```
Removed worktree: $WORKTREE_PATH
[Branch status from step 6]
```