288 lines
11 KiB
Markdown
288 lines
11 KiB
Markdown
# 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 worktree` commands 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`
|
|
|
|
```typescript
|
|
{
|
|
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`, use `git worktree add -b <path> <branch>` to create a new branch.
|
|
- **Error handling**:
|
|
- If path already exists: refuse with "Path '<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`
|
|
|
|
```typescript
|
|
{
|
|
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 list` to 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`
|
|
|
|
```typescript
|
|
{
|
|
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 `target` to 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 '<path>' has uncommitted changes. Stash them first?" (no auto-stashing).
|
|
- Change pi's cwd to the target path using `ctx.cwd` or equivalent mechanism.
|
|
- Trigger session integration hooks (see below).
|
|
|
|
#### `git_worktree_remove`
|
|
|
|
```typescript
|
|
{
|
|
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 '<path>' 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
|
|
|
|
```typescript
|
|
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_switch` with the resolved path.
|
|
- After switch, trigger session integration hooks.
|
|
|
|
### Session Integration Hooks
|
|
|
|
#### Update Branch Display
|
|
|
|
When switching worktrees:
|
|
1. Notify pi that the cwd has changed so it refreshes git branch tracking.
|
|
2. 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:
|
|
1. Ask user if they want to create a new pi session for this worktree context.
|
|
2. If yes, call `ctx.newSession()` with an appropriate setup message.
|
|
3. If no, stay in current session (but branch display is updated).
|
|
|
|
**Implementation**:
|
|
```typescript
|
|
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
|
|
|
|
1. **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.
|
|
|
|
2. **Detached HEAD states**: If a worktree is on a detached HEAD, `git worktree list` still works but branch display might show "detached". Tools should handle this gracefully (e.g., use path instead of branch name for identification).
|
|
|
|
3. **Untracked files in worktrees**: `git worktree remove` will refuse if there are untracked files. Error message should suggest removing or moving them first.
|
|
|
|
4. **Worktrees on the same branch**: Git allows multiple worktrees on the same branch (with `-f` force 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 list` is 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)
|
|
|
|
1. Implement the four tools: create, list, switch, remove.
|
|
2. Register error handling with clear messages and suggested fixes.
|
|
3. Test each tool individually with various scenarios.
|
|
|
|
### Phase 2: Interactive Command (v1)
|
|
|
|
4. Implement `/worktree` command with interactive picker.
|
|
5. Wire up branch-first display format.
|
|
6. Add back-switching behavior (auto-restore previous session).
|
|
|
|
### Phase 3: Session Integration (v1)
|
|
|
|
7. Add session integration hooks to switch operations.
|
|
8. Update pi's git branch display on cwd change.
|
|
9. Offer new session creation after switching worktrees.
|
|
|
|
### Phase 4: Polish and Documentation (v1)
|
|
|
|
10. Write README.md with usage examples.
|
|
11. Add error handling for edge cases discovered during testing.
|
|
12. 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.
|