Merge main into refactor/frontend

Merge latest features from main including:
- PR #161 (worktree-confusion): Clarified branch handling in dialogs
- PR #160 (speckits-rebase): Planning mode functionality

Resolved conflicts:
- add-feature-dialog.tsx: Combined TanStack Router navigation with branch selection state
- worktree-integration.spec.ts: Updated tests for new worktree behavior (created at execution time)
- package-lock.json: Regenerated after merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Kacper
2025-12-18 12:00:45 +01:00
71 changed files with 10632 additions and 1192 deletions

View File

@@ -8,6 +8,7 @@
import type { Request, Response } from "express";
import { exec } from "child_process";
import { promisify } from "util";
import { existsSync } from "fs";
import { isGitRepo, getErrorMessage, logError, normalizePath } from "../common.js";
const execAsync = promisify(exec);
@@ -58,10 +59,12 @@ export function createListHandler() {
});
const worktrees: WorktreeInfo[] = [];
const removedWorktrees: Array<{ path: string; branch: string }> = [];
const lines = stdout.split("\n");
let current: { path?: string; branch?: string } = {};
let isFirst = true;
// First pass: detect removed worktrees
for (const line of lines) {
if (line.startsWith("worktree ")) {
current.path = normalizePath(line.slice(9));
@@ -69,19 +72,40 @@ export function createListHandler() {
current.branch = line.slice(7).replace("refs/heads/", "");
} else if (line === "") {
if (current.path && current.branch) {
worktrees.push({
path: current.path,
branch: current.branch,
isMain: isFirst,
isCurrent: current.branch === currentBranch,
hasWorktree: true,
});
isFirst = false;
const isMainWorktree = isFirst;
// Check if the worktree directory actually exists
// Skip checking/pruning the main worktree (projectPath itself)
if (!isMainWorktree && !existsSync(current.path)) {
// Worktree directory doesn't exist - it was manually deleted
removedWorktrees.push({
path: current.path,
branch: current.branch,
});
} else {
// Worktree exists (or is main worktree), add it to the list
worktrees.push({
path: current.path,
branch: current.branch,
isMain: isMainWorktree,
isCurrent: current.branch === currentBranch,
hasWorktree: true,
});
isFirst = false;
}
}
current = {};
}
}
// Prune removed worktrees from git (only if any were detected)
if (removedWorktrees.length > 0) {
try {
await execAsync("git worktree prune", { cwd: projectPath });
} catch {
// Prune failed, but we'll still report the removed worktrees
}
}
// If includeDetails is requested, fetch change status for each worktree
if (includeDetails) {
for (const worktree of worktrees) {
@@ -103,7 +127,11 @@ export function createListHandler() {
}
}
res.json({ success: true, worktrees });
res.json({
success: true,
worktrees,
removedWorktrees: removedWorktrees.length > 0 ? removedWorktrees : undefined,
});
} catch (error) {
logError(error, "List worktrees failed");
res.status(500).json({ success: false, error: getErrorMessage(error) });