11 KiB
pi-worktree Extension Design Document
Overview
A pi extension that manages git worktrees through pi's tool/command system, providing full CRUD operations plus interactive switching with tight session integration.
Goals
- Enable users to manage git worktrees without typing
git worktreecommands manually - Provide an intuitive interface for the common feature branch workflow (create → use → remove)
- Keep pi's mental model of the current context in sync when switching worktrees
- Fail fast with clear error messages and suggested fixes
Constraints
- v1 Scope: Full CRUD + switch command only. No auto-worktree creation per branch, no smart defaults, no persistent state between tool calls.
- Error Handling: Refuse dangerous operations with helpful suggestions. No silent auto-correction.
- State Management: Stateless tools — each call works independently based on current git state.
- Integration Depth: Session awareness in v1 (update branch display, offer new sessions) but no automatic worktree creation or session branching logic.
Architecture
Extension Structure
.pi/extensions/pi-worktree.ts
├── Tool implementations
│ ├── git_worktree_create(path, branch?)
│ ├── git_worktree_list()
│ ├── git_worktree_switch(target)
│ └── git_worktree_remove(path)
├── Command registration
│ └── /worktree — interactive picker
└── Session integration hooks
├── Update pi's git branch display on switch
└── Offer new session creation after switch
Tool Signatures
git_worktree_create
{
name: "git_worktree_create",
label: "Create Worktree",
description: "Create a linked worktree for a branch in the current repo.",
parameters: Type.Object({
path: Type.String({ description: "Path for the new worktree" }),
branch?: Type.Optional(Type.String({
description: "Branch name (new or existing). Defaults to 'main'."
})),
create_branch?: Type.Optional(Type.Boolean({
description: "Create a new branch? Defaults to true if branch doesn't exist.",
default: true
}))
}),
async execute(toolCallId, params, signal, onUpdate, ctx) {
// Implementation
}
}
Behavior:
- Run
git worktree add <path> [branch]to create the linked worktree. - If branch doesn't exist and
create_branch: true, usegit worktree add -b <path> <branch>to create a new branch. - Error handling:
- If path already exists: refuse with "Path '' already exists. Choose a different location."
- If branch is current branch: refuse with "Can't create worktree on the current branch — switch first."
- If git fails, surface the error message and suggest fixes.
git_worktree_list
{
name: "git_worktree_list",
label: "List Worktrees",
description: "List all linked worktrees in the current repo.",
parameters: Type.Object({}),
async execute(toolCallId, params, signal, onUpdate, ctx) {
// Implementation
}
}
Behavior:
- Run
git worktree listto get the list. - Parse output and format as "branch — path" (e.g., "feature-x — /home/user/repo/feature-x").
- Mark the current branch/worktree with an indicator (e.g.,
(current)). - Return structured data for the LLM to display or use in other operations.
git_worktree_switch
{
name: "git_worktree_switch",
label: "Switch Worktree",
description: "Switch pi's working directory to a linked worktree.",
parameters: Type.Object({
target: Type.String({
description: "Path or branch name of the target worktree."
})
}),
async execute(toolCallId, params, signal, onUpdate, ctx) {
// Implementation
}
}
Behavior:
- Resolve
targetto a path (if it's a branch name, look up the worktree path). - If target is the current worktree: refuse with "Already in this worktree."
- If target has uncommitted changes and you're switching away: fail fast with "Worktree '' has uncommitted changes. Stash them first?" (no auto-stashing).
- Change pi's cwd to the target path using
ctx.cwdor equivalent mechanism. - Trigger session integration hooks (see below).
git_worktree_remove
{
name: "git_worktree_remove",
label: "Remove Worktree",
description: "Remove a linked worktree.",
parameters: Type.Object({
path: Type.String({
description: "Path of the worktree to remove."
})
}),
async execute(toolCallId, params, signal, onUpdate, ctx) {
// Implementation
}
}
Behavior:
- If target is the main repo: refuse with "Can't remove the main repository. Use 'git worktree prune' instead."
- If target has uncommitted changes: refuse with "Worktree '' has uncommitted changes. Stash or commit them first?"
- Run
git worktree remove <path>to remove it. - If git fails (e.g., due to open files), surface the error and suggest fixes.
Command Registration
/worktree Interactive Picker
pi.registerCommand("worktree", {
description: "Switch between linked worktrees interactively.",
handler: async (_args, ctx) => {
// 1. List all worktrees using git_worktree_list
// 2. Display as a picker (branch-first with path)
// 3. On selection, call git_worktree_switch with the target
// 4. Trigger session integration hooks
}
});
Behavior:
- Use
ctx.ui.select()to show an interactive picker of worktrees. - Display format: "feature-x — /home/user/repo/feature-x (current)" or similar.
- When user selects a target, call
git_worktree_switchwith the resolved path. - After switch, trigger session integration hooks.
Session Integration Hooks
Update Branch Display
When switching worktrees:
- Notify pi that the cwd has changed so it refreshes git branch tracking.
- Update the footer to show the new branch.
Implementation: Use ctx.ui.notify() or similar mechanism to signal cwd change. Pi's existing footer data provider will pick up the new HEAD file and update the branch display automatically when the cwd changes.
Offer New Session Creation
After switching worktrees:
- Ask user if they want to create a new pi session for this worktree context.
- If yes, call
ctx.newSession()with an appropriate setup message. - If no, stay in current session (but branch display is updated).
Implementation:
const choice = await ctx.ui.select("Create a new session?", [
"Yes, start fresh for this worktree",
"No, keep the current session"
]);
if (choice?.startsWith("Yes")) {
await ctx.newSession({
setup: async (sm) => {
sm.appendMessage({
role: "user",
content: [{ type: "text", text: `Switched to worktree for branch <branch>. Context from previous session...` }],
timestamp: Date.now()
});
},
withSession: async (newCtx) => {
newCtx.ui.notify("New session created for this worktree.", "info");
}
});
}
Back-Switching Behavior
When switching back to the main repo from a worktree:
- Auto-restore the previous session that was active before creating/using the worktree.
Implementation: Track which sessions were used with which branches/worktrees in memory (not persisted). When switching back to main, switch pi's session back to the one that was active for main.
Tradeoffs
Stateless vs Stateful Tools
Decision: Stateless tools, but remember recent sessions in memory during a pi process lifetime.
Rationale:
- Stateless tools are simpler and more predictable — each call works independently based on current git state.
- Memory of recent sessions is necessary for back-switching behavior (auto-restore previous session when returning to main).
- This memory is ephemeral (lost on pi restart) and doesn't complicate the tool API.
Auto-Correction vs Fail Fast
Decision: Fail fast with helpful errors, no auto-correction.
Rationale:
- Users want control over their git state — stashing changes should be explicit.
- Auto-correction can hide problems (e.g., silently dropping uncommitted work).
- Clear error messages + suggested fixes give users the information to make informed decisions.
Session Integration Depth in v1
Decision: Update branch display AND offer new session creation, but no automatic worktree creation per branch.
Rationale:
- Branch display update is essential — pi needs to know which branch it's tracking.
- Offering new sessions gives users the choice without forcing a context switch.
- Auto-worktree creation adds complexity and assumptions about workflow that we haven't validated.
Risks
Edge Cases
-
Nested worktrees: Git doesn't support nested linked worktrees (worktrees within worktrees). Our tools will fail with standard git errors — no special handling needed in v1.
-
Detached HEAD states: If a worktree is on a detached HEAD,
git worktree liststill works but branch display might show "detached". Tools should handle this gracefully (e.g., use path instead of branch name for identification). -
Untracked files in worktrees:
git worktree removewill refuse if there are untracked files. Error message should suggest removing or moving them first. -
Worktrees on the same branch: Git allows multiple worktrees on the same branch (with
-fforce flag), but it's dangerous. Our tools won't allow this by default — git will error out, and we'll surface that error clearly.
Performance Considerations
git worktree listis fast and doesn't require network access.- Switching cwd should be nearly instantaneous (just a chdir).
- No background processes or watchers needed in v1.
Rollout Strategy
Phase 1: Core Tools (v1)
- Implement the four tools: create, list, switch, remove.
- Register error handling with clear messages and suggested fixes.
- Test each tool individually with various scenarios.
Phase 2: Interactive Command (v1)
- Implement
/worktreecommand with interactive picker. - Wire up branch-first display format.
- Add back-switching behavior (auto-restore previous session).
Phase 3: Session Integration (v1)
- Add session integration hooks to switch operations.
- Update pi's git branch display on cwd change.
- Offer new session creation after switching worktrees.
Phase 4: Polish and Documentation (v1)
- Write README.md with usage examples.
- Add error handling for edge cases discovered during testing.
- Gather feedback from early users.
Future Considerations (Post-v1)
- Auto-worktree creation: When switching branches in pi, automatically create a worktree if one doesn't exist.
- Smart defaults: Stash changes before switching (with confirmation), offer to delete branch when removing worktree.
- Persistent state: Remember recent worktrees/branches for quick access, even after pi restarts.
- Session branching: Automatically create a new session when entering a worktree on a feature branch, preserving the main repo's session.