434 lines
15 KiB
Markdown
434 lines
15 KiB
Markdown
# pi-worktree Implementation Plan
|
|
|
|
## Overview
|
|
|
|
Step-by-step engineering plan for building the `pi-worktree` extension. Breaks down into discrete implementable units with milestones, sequencing, dependencies, and validation tasks.
|
|
|
|
## Milestones
|
|
|
|
### Milestone 1: Foundation (Day 1-2)
|
|
|
|
**Goal**: Set up extension structure and register basic tools.
|
|
|
|
#### Task 1.1: Create Extension File
|
|
|
|
```bash
|
|
mkdir -p .pi/extensions
|
|
touch .pi/extensions/pi-worktree.ts
|
|
```
|
|
|
|
**Deliverable**: Empty extension file with correct imports and structure.
|
|
|
|
**Validation**: Extension loads without errors when pi starts.
|
|
|
|
---
|
|
|
|
#### Task 1.2: Register Tool Scaffolding
|
|
|
|
Register all four tools with minimal implementations (return placeholder responses).
|
|
|
|
```typescript
|
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
import { Type } from "typebox";
|
|
|
|
export default function (pi: ExtensionAPI) {
|
|
// Register tools here
|
|
}
|
|
```
|
|
|
|
**Deliverable**: Four tool registrations with correct signatures but placeholder execute functions.
|
|
|
|
**Validation**:
|
|
- `pi.getAllTools()` includes all four tools after loading extension.
|
|
- Calling each tool returns a response (even if just "not implemented").
|
|
|
|
---
|
|
|
|
#### Task 1.3: Implement `git_worktree_list`
|
|
|
|
**Implementation**:
|
|
```typescript
|
|
async function executeGitWorktreeList(toolCallId, params, signal, onUpdate, ctx) {
|
|
const result = await pi.exec("git", ["worktree", "list"], { cwd: ctx.cwd });
|
|
|
|
if (result.exitCode !== 0) {
|
|
return {
|
|
content: [{ type: "text", text: `Error listing worktrees: ${result.stdout}` }],
|
|
isError: true
|
|
};
|
|
}
|
|
|
|
// Parse output into structured format
|
|
const lines = result.stdout.trim().split("\n");
|
|
const worktrees = lines.map(line => {
|
|
const parts = line.split(/\s+/);
|
|
return {
|
|
path: parts[0],
|
|
branch: parts[1] || "detached",
|
|
isCurrent: line.includes("(current)")
|
|
};
|
|
});
|
|
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: worktrees.map(w => `${w.branch} — ${w.path}${w.isCurrent ? " (current)" : ""}`).join("\n")
|
|
}],
|
|
details: { worktrees }
|
|
};
|
|
}
|
|
```
|
|
|
|
**Deliverable**: Tool that lists all linked worktrees with branch and path.
|
|
|
|
**Validation**:
|
|
- Run in a repo with multiple worktrees — verify output matches `git worktree list`.
|
|
- Test error case (no git repo) — should return clear error message.
|
|
|
|
---
|
|
|
|
### Milestone 2: Create and Remove Operations (Day 3-5)
|
|
|
|
**Goal**: Implement create and remove operations with error handling.
|
|
|
|
#### Task 2.1: Implement `git_worktree_create`
|
|
|
|
**Implementation Steps**:
|
|
1. Validate input parameters.
|
|
2. Check if path already exists — refuse if so.
|
|
3. Determine whether to use `-b` flag (create new branch) or not.
|
|
4. Run `git worktree add <path> [branch]`.
|
|
5. Handle errors with clear messages.
|
|
|
|
**Error Handling**:
|
|
- Path exists: "Path '<path>' already exists. Choose a different location."
|
|
- Branch is current: "Can't create worktree on the current branch — switch first."
|
|
- Git error: Surface git's message and suggest fixes (e.g., "Git reported 'fatal: ...'. Check if you have permissions or if there's a naming conflict.").
|
|
|
|
**Deliverable**: Tool that creates linked worktrees with proper validation.
|
|
|
|
**Validation**:
|
|
- Create worktree on new branch — verify it appears in `git worktree list`.
|
|
- Try to create at existing path — should refuse with clear error.
|
|
- Try to create on current branch — should refuse with clear error.
|
|
|
|
---
|
|
|
|
#### Task 2.2: Implement `git_worktree_remove`
|
|
|
|
**Implementation Steps**:
|
|
1. Validate input parameters.
|
|
2. Check if target is the main repo — refuse if so (suggest `git worktree prune`).
|
|
3. Check for uncommitted changes — refuse with message if found.
|
|
4. Run `git worktree remove <path>`.
|
|
5. Handle errors with clear messages.
|
|
|
|
**Error Handling**:
|
|
- Main repo: "Can't remove the main repository. Use 'git worktree prune' instead."
|
|
- Uncommitted changes: "Worktree '<path>' has uncommitted changes. Stash or commit them first?"
|
|
- Git error: Surface git's message and suggest fixes (e.g., "Git reported 'fatal: ...'. Check if you have permissions or if there are open files.").
|
|
|
|
**Deliverable**: Tool that removes linked worktrees with proper validation.
|
|
|
|
**Validation**:
|
|
- Remove a worktree with no changes — verify it disappears from `git worktree list`.
|
|
- Try to remove main repo — should refuse with clear error.
|
|
- Try to remove worktree with uncommitted changes — should refuse with clear error.
|
|
|
|
---
|
|
|
|
### Milestone 3: Switch Operation (Day 6-7)
|
|
|
|
**Goal**: Implement switch operation with cwd change and session integration hooks.
|
|
|
|
#### Task 3.1: Implement `git_worktree_switch`
|
|
|
|
**Implementation Steps**:
|
|
1. Validate input parameters.
|
|
2. Resolve target to a path (if it's a branch name, look up the worktree path).
|
|
3. Check if already in that worktree — refuse if so.
|
|
4. Check for uncommitted changes in target — fail fast with message if switching away from it.
|
|
5. Change pi's cwd to the target path.
|
|
6. Trigger session integration hooks (see Milestone 4).
|
|
|
|
**Error Handling**:
|
|
- Already there: "Already in this worktree."
|
|
- Uncommitted changes in source: "Worktree '<path>' has uncommitted changes. Stash them first?"
|
|
- Target not found: "No worktree found at '<target>'. Run 'git_worktree_list' to see available worktrees."
|
|
|
|
**Deliverable**: Tool that switches pi's working directory to a linked worktree.
|
|
|
|
**Validation**:
|
|
- Switch to an existing worktree — verify cwd changes and branch display updates.
|
|
- Try to switch to non-existent target — should refuse with clear error.
|
|
- Verify session integration hooks fire after successful switch.
|
|
|
|
---
|
|
|
|
### Milestone 4: Session Integration (Day 8-9)
|
|
|
|
**Goal**: Wire up session integration hooks for switch operations.
|
|
|
|
#### Task 4.1: Update Branch Display on Cwd Change
|
|
|
|
**Implementation**:
|
|
```typescript
|
|
// In git_worktree_switch execute function, after changing cwd:
|
|
ctx.ui.notify(`Switched to worktree at ${targetPath}`, "info");
|
|
// Pi's footer data provider will pick up the new HEAD file automatically
|
|
// when it detects the cwd change. No additional code needed for branch display.
|
|
```
|
|
|
|
**Deliverable**: Branch display updates automatically after switching worktrees.
|
|
|
|
**Validation**:
|
|
- Switch to a worktree on `feature-x` — verify pi's footer shows `feature-x`.
|
|
- Switch back to main — verify pi's footer shows `main` (or whatever the main branch is).
|
|
|
|
---
|
|
|
|
#### Task 4.2: Offer New Session Creation
|
|
|
|
**Implementation**:
|
|
```typescript
|
|
// In git_worktree_switch execute function, after successful switch:
|
|
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 ${branchName}. Context from previous session...`
|
|
}],
|
|
timestamp: Date.now()
|
|
});
|
|
},
|
|
withSession: async (newCtx) => {
|
|
newCtx.ui.notify("New session created for this worktree.", "info");
|
|
}
|
|
});
|
|
}
|
|
```
|
|
|
|
**Deliverable**: After switching worktrees, user is offered the option to create a new pi session.
|
|
|
|
**Validation**:
|
|
- Switch to a worktree and choose "Yes" — verify new session is created with appropriate context message.
|
|
- Switch to a worktree and choose "No" — verify current session continues unchanged (except branch display).
|
|
|
|
---
|
|
|
|
#### Task 4.3: Implement Back-Switching Behavior
|
|
|
|
**Implementation**:
|
|
```typescript
|
|
// Track previous sessions in memory during pi process lifetime:
|
|
const worktreeSessionMap = new Map<string, string>(); // worktree path -> session file
|
|
let mainRepoSessionFile: string | undefined;
|
|
|
|
// When switching TO a worktree:
|
|
worktreeSessionMap.set(targetPath, ctx.sessionManager.getSessionFile());
|
|
|
|
// When switching TO the main repo (detected by checking if target is the original cwd):
|
|
if (mainRepoSessionFile) {
|
|
await ctx.switchSession(mainRepoSessionFile);
|
|
} else {
|
|
// No previous session tracked — stay in current session
|
|
mainRepoSessionFile = ctx.sessionManager.getSessionFile();
|
|
}
|
|
```
|
|
|
|
**Deliverable**: Switching back to the main repo auto-restores the previous session.
|
|
|
|
**Validation**:
|
|
- Start a session, switch to a worktree (create new session), then switch back to main — verify pi returns to the original session.
|
|
- Verify that the `worktreeSessionMap` is cleared on pi restart (stateless tools).
|
|
|
|
---
|
|
|
|
### Milestone 5: Interactive Command (Day 10)
|
|
|
|
**Goal**: Implement `/worktree` command with interactive picker.
|
|
|
|
#### Task 5.1: Register and Implement `/worktree` Command
|
|
|
|
**Implementation**:
|
|
```typescript
|
|
pi.registerCommand("worktree", {
|
|
description: "Switch between linked worktrees interactively.",
|
|
handler: async (args, ctx) => {
|
|
// 1. List all worktrees
|
|
const listResult = await pi.callTool("git_worktree_list");
|
|
if (listResult.isError) {
|
|
ctx.ui.notify(`Error listing worktrees: ${listResult.content}`, "error");
|
|
return;
|
|
}
|
|
|
|
// 2. Parse the structured data and display as picker
|
|
const worktrees = listResult.details.worktrees as Array<{
|
|
path: string;
|
|
branch: string;
|
|
isCurrent: boolean;
|
|
}>;
|
|
|
|
if (worktrees.length === 0) {
|
|
ctx.ui.notify("No linked worktrees found.", "info");
|
|
return;
|
|
}
|
|
|
|
// 3. Show interactive picker
|
|
const choice = await ctx.ui.select("Pick a worktree:",
|
|
worktrees.map(w => `${w.branch} — ${w.path}${w.isCurrent ? " (current)" : ""}`)
|
|
);
|
|
|
|
if (!choice) return;
|
|
|
|
// 4. Find the selected worktree and switch to it
|
|
const selected = worktrees.find(w =>
|
|
choice.startsWith(`${w.branch} — ${w.path}`)
|
|
);
|
|
|
|
if (selected) {
|
|
await pi.callTool("git_worktree_switch", { target: selected.path });
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
**Deliverable**: `/worktree` command shows an interactive picker and switches to the selected worktree.
|
|
|
|
**Validation**:
|
|
- Run `/worktree` in a repo with multiple worktrees — verify picker shows all options with correct format.
|
|
- Select a worktree — verify pi switches to it and branch display updates.
|
|
- Cancel the picker (Esc) — verify no switch occurs.
|
|
|
|
---
|
|
|
|
### Milestone 6: Polish and Documentation (Day 11-12)
|
|
|
|
**Goal**: Add final polish, edge case handling, and documentation.
|
|
|
|
#### Task 6.1: Handle Edge Cases
|
|
|
|
Add error handling for:
|
|
- Nested worktrees (git will error — surface the error clearly).
|
|
- Detached HEAD states in worktrees (use path instead of branch name for identification).
|
|
- Untracked files preventing removal (suggest removing or moving them first).
|
|
- Worktrees on the same branch (let git handle it, surface the error if it fails).
|
|
|
|
**Deliverable**: Robust error handling for all common edge cases.
|
|
|
|
**Validation**:
|
|
- Test each edge case scenario manually and verify appropriate error messages.
|
|
|
|
---
|
|
|
|
#### Task 6.2: Write README.md
|
|
|
|
Create `.pi/extensions/README.md` (or update existing) with:
|
|
- Extension name and purpose.
|
|
- Installation instructions (project-local auto-discovery).
|
|
- Tool descriptions with examples for each of the four tools.
|
|
- Command description with example usage.
|
|
- Session integration behavior.
|
|
- Error handling strategy.
|
|
|
|
**Deliverable**: Complete documentation for users and developers.
|
|
|
|
**Validation**:
|
|
- Review README for clarity, completeness, and accuracy.
|
|
- Verify all examples work as documented.
|
|
|
|
---
|
|
|
|
#### Task 6.3: Final Testing and Cleanup
|
|
|
|
- Test the complete workflow end-to-end: create → switch → remove.
|
|
- Verify error messages are clear and actionable.
|
|
- Check that session integration works correctly in all scenarios.
|
|
- Clean up any debug logging or TODO comments.
|
|
|
|
**Deliverable**: Production-ready extension with no known issues.
|
|
|
|
**Validation**:
|
|
- Run through the complete workflow multiple times to catch any regressions.
|
|
- Review code for quality, readability, and maintainability.
|
|
|
|
---
|
|
|
|
## Dependencies
|
|
|
|
### External Dependencies
|
|
|
|
- **Node.js built-ins**: `child_process` (for exec), `fs`, `path`. No external npm packages needed in v1.
|
|
- **Git**: Requires git 2.x with worktree support (available in all modern git versions).
|
|
- **TypeBox**: Already available as a pi dependency for tool parameter schemas.
|
|
|
|
### Internal Dependencies
|
|
|
|
- Pi's extension API (`ExtensionAPI`, `ExtensionContext`).
|
|
- Pi's session management (`ctx.sessionManager`, `ctx.newSession()`, `ctx.switchSession()`).
|
|
- Pi's UI methods (`ctx.ui.select()`, `ctx.ui.notify()`, etc.).
|
|
|
|
---
|
|
|
|
## Validation Strategy
|
|
|
|
### Unit Testing (Manual)
|
|
|
|
Test each tool in isolation with various inputs:
|
|
- Valid inputs — verify correct behavior.
|
|
- Invalid inputs (missing params, non-existent paths/branches) — verify clear error messages.
|
|
- Edge cases (existing path, current branch, uncommitted changes) — verify appropriate refusals.
|
|
|
|
### Integration Testing (Manual)
|
|
|
|
Test the complete workflow:
|
|
1. Create a worktree on a new branch.
|
|
2. Switch to it via `/worktree` command or `git_worktree_switch`.
|
|
3. Verify branch display updates correctly.
|
|
4. Choose "Yes" when offered a new session — verify new session is created.
|
|
5. Switch back to main — verify auto-restore of previous session.
|
|
6. Remove the worktree — verify it disappears from list.
|
|
|
|
### User Acceptance Testing (Manual)
|
|
|
|
Have early users run through typical workflows:
|
|
- Feature branch development cycle (create → use → remove).
|
|
- Switching between multiple worktrees frequently.
|
|
- Handling errors and edge cases.
|
|
|
|
Gather feedback on clarity, usability, and any missing features or confusing behavior.
|
|
|
|
---
|
|
|
|
## Timeline Summary
|
|
|
|
| Milestone | Days | Deliverables |
|
|
|-----------|------|--------------|
|
|
| 1: Foundation | Day 1-2 | Extension structure, tool scaffolding, `git_worktree_list` implemented |
|
|
| 2: Create/Remove | Day 3-5 | `git_worktree_create` and `git_worktree_remove` with error handling |
|
|
| 3: Switch Operation | Day 6-7 | `git_worktree_switch` with cwd change |
|
|
| 4: Session Integration | Day 8-9 | Branch display updates, new session offering, back-switching behavior |
|
|
| 5: Interactive Command | Day 10 | `/worktree` command with interactive picker |
|
|
| 6: Polish and Documentation | Day 11-12 | Edge case handling, README.md, final testing |
|
|
|
|
**Total Estimated Time**: ~12 days (assuming part-time development on top of other work).
|
|
|
|
---
|
|
|
|
## Success Criteria
|
|
|
|
The extension is ready for release when:
|
|
- All four tools (`create`, `list`, `switch`, `remove`) work correctly with clear error messages.
|
|
- `/worktree` command provides an intuitive interactive picker with branch-first display.
|
|
- Session integration works: branch display updates on switch, new session offered after switch, auto-restore of previous session when returning to main.
|
|
- Edge cases (existing path, current branch, uncommitted changes) are handled gracefully with helpful error messages.
|
|
- Documentation is complete and accurate.
|
|
- No known regressions or critical bugs remain.
|