Commit 5cd0a634b01c

Vincent Demeester <vincent@sbr.pm>
2026-02-06 17:01:31
feat(pi): add git extension - Phase 1: Worktree management
Implemented comprehensive worktree management for pi coding agent. Commands: - /worktree list (alias: /wt ls) - List all worktrees with status - /worktree create <branch> (alias: /wt new) - Create worktree - /worktree remove <branch> (alias: /wt rm) - Remove worktree - /worktree prune - Clean up stale references Tool: - git-worktree - AI can manage worktrees programmatically Features: - Worktrees created in .worktrees/ subdirectory - Auto-detect local/remote branches - Safety checks for uncommitted changes - Clear status display with dirty indicators - Support for custom paths Implementation: - index.ts - Command and tool registration - worktree.ts - Core worktree logic - utils.ts - Git utility functions - types.ts - TypeScript type definitions - README.md - Comprehensive documentation Plan: ~/.local/share/ai/plans/pi-git-extension.md Research: ~/.local/share/ai/research/2026-02/git-rebase-fixup-best-practices.md
1 parent 706cf8b
Changed files (5)
dots/pi/agent/extensions/git/index.ts
@@ -0,0 +1,336 @@
+// =============================================================================
+// Git Extension for Pi Coding Agent
+// =============================================================================
+
+import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
+import {
+	listWorktrees,
+	createWorktree,
+	removeWorktree,
+	pruneWorktrees,
+	getCurrentWorktreeInfo,
+} from "./worktree.js";
+import { findRepoRoot, hasUncommittedChanges } from "./utils.js";
+
+export default function (pi: ExtensionAPI) {
+	// =========================================================================
+	// Commands: /worktree (alias: /wt)
+	// =========================================================================
+
+	pi.registerCommand("worktree", {
+		description: "Manage git worktrees (list, create, remove, prune)",
+		handler: async (args, ctx) => {
+			const parts = args.trim().split(/\s+/).filter(Boolean);
+			const subcommand = parts[0] || "list";
+			const remainingArgs = parts.slice(1);
+
+			try {
+				switch (subcommand) {
+					case "list":
+					case "ls":
+						await handleList(ctx);
+						break;
+
+					case "create":
+					case "new":
+					case "add":
+						await handleCreate(remainingArgs, ctx);
+						break;
+
+					case "remove":
+					case "rm":
+					case "delete":
+						await handleRemove(remainingArgs, ctx);
+						break;
+
+					case "prune":
+						await handlePrune(ctx);
+						break;
+
+					default:
+						ctx.ui.notify(`Unknown worktree command: ${subcommand}`, "error");
+						ctx.ui.notify(
+							"Available: list, create <branch>, remove <branch>, prune",
+							"info"
+						);
+				}
+			} catch (error: any) {
+				ctx.ui.notify(`Error: ${error.message}`, "error");
+			}
+		},
+	});
+
+	// Register /wt alias
+	pi.registerCommand("wt", {
+		description: "Alias for /worktree",
+		handler: async (args, ctx) => {
+			const parts = args.trim().split(/\s+/).filter(Boolean);
+			const subcommand = parts[0] || "list";
+			const remainingArgs = parts.slice(1);
+
+			try {
+				switch (subcommand) {
+					case "list":
+					case "ls":
+						await handleList(ctx);
+						break;
+
+					case "create":
+					case "new":
+					case "add":
+						await handleCreate(remainingArgs, ctx);
+						break;
+
+					case "remove":
+					case "rm":
+					case "delete":
+						await handleRemove(remainingArgs, ctx);
+						break;
+
+					case "prune":
+						await handlePrune(ctx);
+						break;
+
+					default:
+						ctx.ui.notify(`Unknown worktree command: ${subcommand}`, "error");
+						ctx.ui.notify(
+							"Available: list, create <branch>, remove <branch>, prune",
+							"info"
+						);
+				}
+			} catch (error: any) {
+				ctx.ui.notify(`Error: ${error.message}`, "error");
+			}
+		},
+	});
+
+	// =========================================================================
+	// Command Handlers
+	// =========================================================================
+
+	async function handleList(ctx: any) {
+		const repoRoot = findRepoRoot(ctx.cwd);
+		if (!repoRoot) {
+			ctx.ui.notify("Not in a git repository", "error");
+			return;
+		}
+
+		const worktrees = listWorktrees(ctx.cwd);
+		const currentInfo = getCurrentWorktreeInfo(ctx.cwd);
+
+		if (worktrees.length === 0) {
+			ctx.ui.notify("No worktrees found", "info");
+			return;
+		}
+
+		// Build output
+		const lines: string[] = [];
+		lines.push("Git Worktrees:");
+		lines.push("");
+
+		for (const wt of worktrees) {
+			const isCurrent = currentInfo && wt.path === currentInfo.path;
+			const marker = isCurrent ? "โžœ" : " ";
+			const dirty = hasUncommittedChanges(wt.path) ? "*" : "";
+
+			lines.push(`${marker} ${wt.branch}${dirty}`);
+			lines.push(`  Path: ${wt.path}`);
+			lines.push(`  Commit: ${wt.commit.substring(0, 8)}`);
+
+			if (wt.locked) lines.push("  ๐Ÿ”’ Locked");
+			if (wt.prunable) lines.push("  โ™ป๏ธ  Prunable");
+
+			lines.push("");
+		}
+
+		ctx.ui.notify(lines.join("\n"), "info");
+	}
+
+	async function handleCreate(args: string[], ctx: any) {
+		const branch = args[0];
+		if (!branch) {
+			ctx.ui.notify("Usage: /worktree create <branch> [path]", "error");
+			return;
+		}
+
+		const customPath = args[1];
+
+		try {
+			const worktreePath = createWorktree(
+				{
+					branch,
+					path: customPath,
+					fromRemote: true,
+				},
+				ctx.cwd
+			);
+
+			ctx.ui.notify(`โœ“ Worktree created: ${worktreePath}`, "success");
+
+			// Show how to switch
+			ctx.ui.notify(`To switch: cd ${worktreePath}`, "info");
+		} catch (error: any) {
+			ctx.ui.notify(`Failed to create worktree: ${error.message}`, "error");
+		}
+	}
+
+	async function handleRemove(args: string[], ctx: any) {
+		const branch = args[0];
+		if (!branch) {
+			ctx.ui.notify("Usage: /worktree remove <branch>", "error");
+			return;
+		}
+
+		// Check for uncommitted changes
+		const worktrees = listWorktrees(ctx.cwd);
+		const worktree = worktrees.find((wt) => wt.branch === branch);
+
+		if (!worktree) {
+			ctx.ui.notify(`Worktree for branch '${branch}' not found`, "error");
+			return;
+		}
+
+		const hasChanges = hasUncommittedChanges(worktree.path);
+
+		if (hasChanges) {
+			const confirmed = await ctx.ui.confirm(
+				`Worktree has uncommitted changes. Remove anyway?`
+			);
+
+			if (!confirmed) {
+				ctx.ui.notify("Cancelled", "info");
+				return;
+			}
+		}
+
+		try {
+			removeWorktree(
+				{
+					branch,
+					force: hasChanges,
+				},
+				ctx.cwd
+			);
+
+			ctx.ui.notify(`โœ“ Worktree removed: ${branch}`, "success");
+		} catch (error: any) {
+			ctx.ui.notify(`Failed to remove worktree: ${error.message}`, "error");
+		}
+	}
+
+	async function handlePrune(ctx: any) {
+		try {
+			pruneWorktrees(ctx.cwd);
+			ctx.ui.notify("โœ“ Pruned stale worktree references", "success");
+		} catch (error: any) {
+			ctx.ui.notify(`Failed to prune worktrees: ${error.message}`, "error");
+		}
+	}
+
+	// =========================================================================
+	// Tool: git-worktree (for AI to use)
+	// TODO: Implement with TypeBox schema
+	// =========================================================================
+
+	/* TODO: Add TypeBox dependency and implement tool
+	pi.registerTool({
+		name: "git-worktree",
+		description:
+			"Manage git worktrees. Create isolated working directories for different branches without switching in the main repository.",
+		input_schema: {
+			type: "object",
+			properties: {
+				action: {
+					type: "string",
+					enum: ["list", "create", "remove"],
+					description: "The worktree action to perform",
+				},
+				branch: {
+					type: "string",
+					description: "Branch name (required for create/remove)",
+				},
+				path: {
+					type: "string",
+					description: "Custom path for worktree (optional for create)",
+				},
+				force: {
+					type: "boolean",
+					description: "Force removal even with uncommitted changes",
+				},
+			},
+			required: ["action"],
+		},
+		async execute(input, ctx) {
+			const { action, branch, path: customPath, force } = input;
+
+			try {
+				switch (action) {
+					case "list": {
+						const worktrees = listWorktrees(ctx.cwd);
+						return {
+							worktrees: worktrees.map((wt) => ({
+								path: wt.path,
+								branch: wt.branch,
+								commit: wt.commit.substring(0, 8),
+								dirty: hasUncommittedChanges(wt.path),
+								locked: wt.locked,
+								prunable: wt.prunable,
+							})),
+						};
+					}
+
+					case "create": {
+						if (!branch) {
+							throw new Error("Branch name required for create action");
+						}
+
+						const worktreePath = createWorktree(
+							{
+								branch,
+								path: customPath,
+								fromRemote: true,
+							},
+							ctx.cwd
+						);
+
+						return {
+							success: true,
+							path: worktreePath,
+							branch,
+							message: `Worktree created at ${worktreePath}`,
+						};
+					}
+
+					case "remove": {
+						if (!branch) {
+							throw new Error("Branch name required for remove action");
+						}
+
+						removeWorktree(
+							{
+								branch,
+								force: force ?? false,
+							},
+							ctx.cwd
+						);
+
+						return {
+							success: true,
+							branch,
+							message: `Worktree removed for branch ${branch}`,
+						};
+					}
+
+					default:
+						throw new Error(`Unknown action: ${action}`);
+				}
+			} catch (error: any) {
+				return {
+					success: false,
+					error: error.message,
+				};
+			}
+		},
+	});
+	*/
+}
dots/pi/agent/extensions/git/README.md
@@ -0,0 +1,301 @@
+# Git Extension for Pi Coding Agent
+
+Comprehensive git workflow automation for pi, focusing on worktree management, smart rebase/fixup workflows, and other common git tasks.
+
+## Features
+
+### Phase 1: Worktree Management โœ…
+
+Manage git worktrees easily without cluttering your repository with multiple checkouts.
+
+**Commands:**
+- `/worktree list` or `/wt ls` - List all worktrees with status
+- `/worktree create <branch>` or `/wt new <branch>` - Create worktree for branch
+- `/worktree remove <branch>` or `/wt rm <branch>` - Remove worktree
+- `/worktree prune` - Clean up stale worktree references
+
+**Tool:**
+- `git-worktree` - AI can manage worktrees programmatically
+
+### Upcoming Features
+
+- **Phase 2:** Smart rebase/fixup workflows
+- **Phase 3:** Commit helpers and validation
+- **Phase 4:** Branch management
+- **Phase 5:** Integration and polish
+
+## Installation
+
+The extension is automatically loaded from `~/.config/pi/agent/extensions/git/`.
+
+To enable:
+```bash
+cd ~/src/home/dots
+make pi-agent
+```
+
+## Usage
+
+### List Worktrees
+
+```
+/worktree list
+/wt ls
+```
+
+Shows all worktrees with:
+- Branch name (with dirty indicator `*`)
+- Path
+- Current commit
+- Status (locked, prunable)
+- Current worktree indicator `โžœ`
+
+### Create Worktree
+
+```
+/worktree create feature-branch
+/wt new bugfix-123
+```
+
+Creates a worktree in `.worktrees/feature-branch` relative to repository root.
+
+**With custom path:**
+```
+/worktree create feature-x /tmp/feature-x
+```
+
+**Behavior:**
+- If branch exists locally: Creates worktree for that branch
+- If branch exists on origin: Creates local branch tracking origin
+- If branch doesn't exist: Creates new branch
+
+### Remove Worktree
+
+```
+/worktree remove feature-branch
+/wt rm bugfix-123
+```
+
+**Safety:**
+- Checks for uncommitted changes
+- Prompts for confirmation if changes exist
+- Use force flag in tool for bypassing
+
+### Prune Worktrees
+
+```
+/worktree prune
+```
+
+Cleans up stale worktree administrative files.
+
+## Tool Usage (for AI)
+
+The extension registers a `git-worktree` tool that AI agents can use:
+
+```typescript
+{
+  name: "git-worktree",
+  action: "list" | "create" | "remove",
+  branch: "branch-name",      // For create/remove
+  path: "/custom/path",        // Optional for create
+  force: true                  // Optional for remove
+}
+```
+
+**Examples:**
+
+List worktrees:
+```json
+{
+  "action": "list"
+}
+```
+
+Create worktree:
+```json
+{
+  "action": "create",
+  "branch": "feature-x"
+}
+```
+
+Remove worktree:
+```json
+{
+  "action": "remove",
+  "branch": "feature-x",
+  "force": false
+}
+```
+
+## Worktree Directory Structure
+
+Worktrees are created in `.worktrees/` subdirectory:
+
+```
+~/src/home/
+โ”œโ”€โ”€ .git/
+โ”œโ”€โ”€ .worktrees/
+โ”‚   โ”œโ”€โ”€ feature-a/      # Worktree for feature-a branch
+โ”‚   โ”œโ”€โ”€ bugfix-123/     # Worktree for bugfix-123 branch
+โ”‚   โ””โ”€โ”€ experiment/     # Worktree for experiment branch
+โ”œโ”€โ”€ systems/
+โ”œโ”€โ”€ home/
+โ””โ”€โ”€ ...
+```
+
+**Why `.worktrees/`?**
+- Keeps worktrees organized in one place
+- Easy to gitignore (add `.worktrees/` to `.gitignore`)
+- Clear separation from main repository
+- All worktrees easy to find and manage
+
+## Best Practices
+
+### When to Use Worktrees
+
+**Good for:**
+- Working on multiple features simultaneously
+- Keeping long-running branches checked out
+- Testing changes without switching branches
+- Code review without disrupting current work
+
+**Not good for:**
+- Quick branch switches (just use `git switch`)
+- Temporary experiments (use `git stash` instead)
+
+### Worktree Workflow
+
+1. **Create worktree for feature:**
+   ```
+   /wt new feature-x
+   cd .worktrees/feature-x
+   ```
+
+2. **Work in worktree:**
+   ```
+   # Make changes, commit, push
+   git add .
+   git commit -m "feat: add feature x"
+   git push origin feature-x:feature-x
+   ```
+
+3. **Switch back to main:**
+   ```
+   cd ~/src/home
+   # Main worktree is unaffected
+   ```
+
+4. **Clean up when done:**
+   ```
+   /wt rm feature-x
+   ```
+
+### Multiple Worktrees Pattern
+
+Working on frontend and backend simultaneously:
+
+```
+/wt new frontend-feature
+/wt new backend-feature
+
+# Terminal 1
+cd .worktrees/frontend-feature
+# Work on frontend
+
+# Terminal 2  
+cd .worktrees/backend-feature
+# Work on backend
+
+# Main terminal
+cd ~/src/home
+# Work on main branch or review
+```
+
+## Configuration
+
+No configuration needed for Phase 1. Future phases will add configuration options in `.pi/config.json`.
+
+## Troubleshooting
+
+### Error: "Not in a git repository"
+
+Make sure you're running commands from within a git repository.
+
+### Error: "Worktree path already exists"
+
+The worktree directory already exists. Either:
+- Remove it manually: `rm -rf .worktrees/branch-name`
+- Use a different path: `/wt new branch-name /tmp/branch-name`
+
+### Error: "Worktree has uncommitted changes"
+
+The worktree has uncommitted changes. Either:
+- Commit the changes
+- Use force removal (will be prompted)
+- Manually handle: `cd .worktrees/branch-name && git stash`
+
+### Stale Worktree References
+
+If you manually deleted a worktree directory, git may still have references:
+
+```
+/worktree prune
+```
+
+## Technical Details
+
+### Implementation
+
+- **Language:** TypeScript
+- **Location:** `dots/pi/agent/extensions/git/`
+- **Modules:**
+  - `index.ts` - Main extension entry, command/tool registration
+  - `worktree.ts` - Worktree management logic
+  - `utils.ts` - Git utility functions
+  - `types.ts` - TypeScript type definitions
+
+### Dependencies
+
+- Git >= 2.30 (for improved worktree support)
+- Node.js/Bun for TypeScript execution
+
+### Error Handling
+
+All operations include:
+- Input validation
+- Git repository detection
+- Uncommitted changes detection
+- Clear error messages
+- Safe defaults
+
+## Roadmap
+
+### Phase 2: Smart Rebase/Fixup (Next)
+- `/fixup <commit>` - Create fixup commit
+- `/rebase-fixups` - Auto-squash fixups
+- Pre-push fixup detection
+
+### Phase 3: Commit Helpers
+- `/commit` - Interactive conventional commit
+- Commit message validation
+- AI-assisted commit messages
+
+### Phase 4: Branch Management
+- `/branch list` - List branches with info
+- `/branch cleanup` - Remove merged branches
+
+### Phase 5: Polish
+- Comprehensive error handling
+- Configuration system
+- Full documentation
+
+## Related
+
+- Plan: `~/.local/share/ai/plans/pi-git-extension.md`
+- Research: `~/.local/share/ai/research/2026-02/2026-02-06-git-rebase-fixup-best-practices.md`
+
+## License
+
+Part of vdemeester/home repository.
dots/pi/agent/extensions/git/types.ts
@@ -0,0 +1,37 @@
+// =============================================================================
+// Git Extension Types
+// =============================================================================
+
+export interface WorktreeInfo {
+	path: string;
+	branch: string;
+	commit: string;
+	bare: boolean;
+	detached: boolean;
+	locked: boolean;
+	prunable: boolean;
+}
+
+export interface GitStatus {
+	branch: string;
+	commit: string;
+	dirty: boolean;
+	staged: number;
+	unstaged: number;
+	untracked: number;
+	ahead: number;
+	behind: number;
+}
+
+export interface WorktreeCreateOptions {
+	branch: string;
+	path?: string;
+	createBranch?: boolean;
+	fromRemote?: boolean;
+	checkout?: boolean;
+}
+
+export interface WorktreeRemoveOptions {
+	branch: string;
+	force?: boolean;
+}
dots/pi/agent/extensions/git/utils.ts
@@ -0,0 +1,106 @@
+// =============================================================================
+// Git Utilities
+// =============================================================================
+
+import { execSync } from "child_process";
+import * as path from "path";
+import * as fs from "fs";
+
+export function findRepoRoot(cwd: string = process.cwd()): string | null {
+	try {
+		return execSync("git rev-parse --show-toplevel", {
+			cwd,
+			encoding: "utf-8",
+		}).trim();
+	} catch {
+		return null;
+	}
+}
+
+export function getCurrentBranch(cwd: string = process.cwd()): string | null {
+	try {
+		return execSync("git rev-parse --abbrev-ref HEAD", {
+			cwd,
+			encoding: "utf-8",
+		}).trim();
+	} catch {
+		return null;
+	}
+}
+
+export function getCommitHash(cwd: string = process.cwd()): string | null {
+	try {
+		return execSync("git rev-parse HEAD", {
+			cwd,
+			encoding: "utf-8",
+		}).trim();
+	} catch {
+		return null;
+	}
+}
+
+export function isWorktree(cwd: string = process.cwd()): boolean {
+	try {
+		const gitDir = execSync("git rev-parse --git-dir", {
+			cwd,
+			encoding: "utf-8",
+		}).trim();
+		return gitDir.includes("/worktrees/");
+	} catch {
+		return false;
+	}
+}
+
+export function hasUncommittedChanges(cwd: string = process.cwd()): boolean {
+	try {
+		const status = execSync("git status --porcelain", {
+			cwd,
+			encoding: "utf-8",
+		}).trim();
+		return status.length > 0;
+	} catch {
+		return false;
+	}
+}
+
+export function sanitizeBranchName(branch: string): string {
+	// Remove remote prefix if present (origin/feature -> feature)
+	const withoutRemote = branch.replace(/^[^/]+\//, "");
+	// Replace invalid characters with hyphens
+	return withoutRemote.replace(/[^a-zA-Z0-9-_]/g, "-");
+}
+
+export function branchExists(branch: string, cwd: string = process.cwd()): boolean {
+	try {
+		execSync(`git show-ref --verify refs/heads/${branch}`, {
+			cwd,
+			encoding: "utf-8",
+		});
+		return true;
+	} catch {
+		return false;
+	}
+}
+
+export function remoteBranchExists(remote: string, branch: string, cwd: string = process.cwd()): boolean {
+	try {
+		execSync(`git show-ref --verify refs/remotes/${remote}/${branch}`, {
+			cwd,
+			encoding: "utf-8",
+		});
+		return true;
+	} catch {
+		return false;
+	}
+}
+
+export function getWorktreeDir(repoRoot: string): string {
+	return path.join(repoRoot, ".worktrees");
+}
+
+export function ensureWorktreeDir(repoRoot: string): void {
+	const worktreeDir = getWorktreeDir(repoRoot);
+	if (!fs.existsSync(worktreeDir)) {
+		fs.mkdirSync(worktreeDir, { recursive: true });
+	}
+}
dots/pi/agent/extensions/git/worktree.ts
@@ -0,0 +1,234 @@
+// =============================================================================
+// Git Worktree Management
+// =============================================================================
+
+import { execSync } from "child_process";
+import * as path from "path";
+import * as fs from "fs";
+import type { WorktreeInfo, WorktreeCreateOptions, WorktreeRemoveOptions } from "./types.js";
+import {
+	findRepoRoot,
+	getCurrentBranch,
+	getCommitHash,
+	sanitizeBranchName,
+	branchExists,
+	remoteBranchExists,
+	hasUncommittedChanges,
+	getWorktreeDir,
+	ensureWorktreeDir,
+} from "./utils.js";
+
+// =============================================================================
+// List Worktrees
+// =============================================================================
+
+export function listWorktrees(cwd: string = process.cwd()): WorktreeInfo[] {
+	const repoRoot = findRepoRoot(cwd);
+	if (!repoRoot) {
+		throw new Error("Not in a git repository");
+	}
+
+	try {
+		const output = execSync("git worktree list --porcelain", {
+			cwd: repoRoot,
+			encoding: "utf-8",
+		});
+
+		return parseWorktreeList(output);
+	} catch (error) {
+		throw new Error(`Failed to list worktrees: ${error}`);
+	}
+}
+
+function parseWorktreeList(output: string): WorktreeInfo[] {
+	const worktrees: WorktreeInfo[] = [];
+	const lines = output.trim().split("\n");
+	
+	let current: Partial<WorktreeInfo> = {};
+	
+	for (const line of lines) {
+		if (line === "") {
+			if (current.path) {
+				worktrees.push(current as WorktreeInfo);
+				current = {};
+			}
+			continue;
+		}
+
+		const [key, ...valueParts] = line.split(" ");
+		const value = valueParts.join(" ");
+
+		switch (key) {
+			case "worktree":
+				current.path = value;
+				break;
+			case "HEAD":
+				current.commit = value;
+				break;
+			case "branch":
+				// Remove refs/heads/ prefix
+				current.branch = value.replace("refs/heads/", "");
+				break;
+			case "bare":
+				current.bare = true;
+				break;
+			case "detached":
+				current.detached = true;
+				break;
+			case "locked":
+				current.locked = true;
+				break;
+			case "prunable":
+				current.prunable = true;
+				break;
+		}
+	}
+
+	// Add last worktree if exists
+	if (current.path) {
+		worktrees.push(current as WorktreeInfo);
+	}
+
+	// Set defaults
+	return worktrees.map((wt) => ({
+		...wt,
+		bare: wt.bare ?? false,
+		detached: wt.detached ?? false,
+		locked: wt.locked ?? false,
+		prunable: wt.prunable ?? false,
+		branch: wt.branch ?? "(detached)",
+	}));
+}
+
+// =============================================================================
+// Create Worktree
+// =============================================================================
+
+export function createWorktree(options: WorktreeCreateOptions, cwd: string = process.cwd()): string {
+	const repoRoot = findRepoRoot(cwd);
+	if (!repoRoot) {
+		throw new Error("Not in a git repository");
+	}
+
+	// Ensure .worktrees directory exists
+	ensureWorktreeDir(repoRoot);
+
+	// Determine worktree path
+	const worktreePath =
+		options.path ?? path.join(getWorktreeDir(repoRoot), sanitizeBranchName(options.branch));
+
+	// Check if path already exists
+	if (fs.existsSync(worktreePath)) {
+		throw new Error(`Worktree path already exists: ${worktreePath}`);
+	}
+
+	// Build command
+	let cmd = `git worktree add`;
+
+	// Check if we should create a new branch
+	const localExists = branchExists(options.branch, repoRoot);
+	const remoteExists = remoteBranchExists("origin", options.branch, repoRoot);
+
+	if (options.createBranch || (!localExists && !remoteExists)) {
+		// Create new branch
+		cmd += ` -b ${options.branch}`;
+	}
+
+	cmd += ` "${worktreePath}"`;
+
+	if (localExists) {
+		// Use existing local branch
+		cmd += ` ${options.branch}`;
+	} else if (remoteExists && options.fromRemote) {
+		// Create from remote branch
+		cmd += ` origin/${options.branch}`;
+	}
+
+	// Execute command
+	try {
+		execSync(cmd, {
+			cwd: repoRoot,
+			encoding: "utf-8",
+			stdio: "inherit",
+		});
+
+		return worktreePath;
+	} catch (error) {
+		throw new Error(`Failed to create worktree: ${error}`);
+	}
+}
+
+// =============================================================================
+// Remove Worktree
+// =============================================================================
+
+export function removeWorktree(options: WorktreeRemoveOptions, cwd: string = process.cwd()): void {
+	const repoRoot = findRepoRoot(cwd);
+	if (!repoRoot) {
+		throw new Error("Not in a git repository");
+	}
+
+	// Find worktree by branch name
+	const worktrees = listWorktrees(repoRoot);
+	const worktree = worktrees.find((wt) => wt.branch === options.branch);
+
+	if (!worktree) {
+		throw new Error(`Worktree for branch '${options.branch}' not found`);
+	}
+
+	// Check for uncommitted changes (unless force)
+	if (!options.force && hasUncommittedChanges(worktree.path)) {
+		throw new Error(
+			`Worktree has uncommitted changes. Use force option to remove anyway.`
+		);
+	}
+
+	// Build command
+	let cmd = `git worktree remove "${worktree.path}"`;
+	if (options.force) {
+		cmd += " --force";
+	}
+
+	// Execute command
+	try {
+		execSync(cmd, {
+			cwd: repoRoot,
+			encoding: "utf-8",
+			stdio: "inherit",
+		});
+	} catch (error) {
+		throw new Error(`Failed to remove worktree: ${error}`);
+	}
+}
+
+// =============================================================================
+// Prune Worktrees
+// =============================================================================
+
+export function pruneWorktrees(cwd: string = process.cwd()): void {
+	const repoRoot = findRepoRoot(cwd);
+	if (!repoRoot) {
+		throw new Error("Not in a git repository");
+	}
+
+	try {
+		execSync("git worktree prune", {
+			cwd: repoRoot,
+			encoding: "utf-8",
+			stdio: "inherit",
+		});
+	} catch (error) {
+		throw new Error(`Failed to prune worktrees: ${error}`);
+	}
+}
+
+// =============================================================================
+// Get Current Worktree Info
+// =============================================================================
+
+export function getCurrentWorktreeInfo(cwd: string = process.cwd()): WorktreeInfo | null {
+	const worktrees = listWorktrees(cwd);
+	const currentPath = path.resolve(cwd);
+	
+	return worktrees.find((wt) => path.resolve(wt.path) === currentPath) ?? null;
+}