diff --git a/apps/server/src/routes/worktree/routes/diffs.ts b/apps/server/src/routes/worktree/routes/diffs.ts index 75f43d7f..314fa8ce 100644 --- a/apps/server/src/routes/worktree/routes/diffs.ts +++ b/apps/server/src/routes/worktree/routes/diffs.ts @@ -39,7 +39,10 @@ export function createDiffsHandler() { } // Git worktrees are stored in project directory - const worktreePath = path.join(projectPath, '.worktrees', featureId); + // Sanitize featureId the same way it's sanitized when creating worktrees + // (see create.ts: branchName.replace(/[^a-zA-Z0-9_-]/g, '-')) + const sanitizedFeatureId = featureId.replace(/[^a-zA-Z0-9_-]/g, '-'); + const worktreePath = path.join(projectPath, '.worktrees', sanitizedFeatureId); try { // Check if worktree exists diff --git a/apps/server/src/routes/worktree/routes/file-diff.ts b/apps/server/src/routes/worktree/routes/file-diff.ts index 4d29eb26..f3d4ed1a 100644 --- a/apps/server/src/routes/worktree/routes/file-diff.ts +++ b/apps/server/src/routes/worktree/routes/file-diff.ts @@ -37,7 +37,10 @@ export function createFileDiffHandler() { } // Git worktrees are stored in project directory - const worktreePath = path.join(projectPath, '.worktrees', featureId); + // Sanitize featureId the same way it's sanitized when creating worktrees + // (see create.ts: branchName.replace(/[^a-zA-Z0-9_-]/g, '-')) + const sanitizedFeatureId = featureId.replace(/[^a-zA-Z0-9_-]/g, '-'); + const worktreePath = path.join(projectPath, '.worktrees', sanitizedFeatureId); try { await secureFs.access(worktreePath); diff --git a/apps/server/src/routes/worktree/routes/info.ts b/apps/server/src/routes/worktree/routes/info.ts index 3d512452..5c2eb808 100644 --- a/apps/server/src/routes/worktree/routes/info.ts +++ b/apps/server/src/routes/worktree/routes/info.ts @@ -28,7 +28,10 @@ export function createInfoHandler() { } // Check if worktree exists (git worktrees are stored in project directory) - const worktreePath = path.join(projectPath, '.worktrees', featureId); + // Sanitize featureId the same way it's sanitized when creating worktrees + // (see create.ts: branchName.replace(/[^a-zA-Z0-9_-]/g, '-')) + const sanitizedFeatureId = featureId.replace(/[^a-zA-Z0-9_-]/g, '-'); + const worktreePath = path.join(projectPath, '.worktrees', sanitizedFeatureId); try { await secureFs.access(worktreePath); const { stdout } = await execAsync('git rev-parse --abbrev-ref HEAD', { diff --git a/apps/server/src/routes/worktree/routes/status.ts b/apps/server/src/routes/worktree/routes/status.ts index f9d6bf88..b44c5ae4 100644 --- a/apps/server/src/routes/worktree/routes/status.ts +++ b/apps/server/src/routes/worktree/routes/status.ts @@ -28,7 +28,10 @@ export function createStatusHandler() { } // Git worktrees are stored in project directory - const worktreePath = path.join(projectPath, '.worktrees', featureId); + // Sanitize featureId the same way it's sanitized when creating worktrees + // (see create.ts: branchName.replace(/[^a-zA-Z0-9_-]/g, '-')) + const sanitizedFeatureId = featureId.replace(/[^a-zA-Z0-9_-]/g, '-'); + const worktreePath = path.join(projectPath, '.worktrees', sanitizedFeatureId); try { await secureFs.access(worktreePath); diff --git a/apps/server/src/services/auto-mode-service.ts b/apps/server/src/services/auto-mode-service.ts index 606660c3..28498829 100644 --- a/apps/server/src/services/auto-mode-service.ts +++ b/apps/server/src/services/auto-mode-service.ts @@ -2060,7 +2060,9 @@ Address the follow-up instructions above. Review the previous work and make the const feature = await this.loadFeature(projectPath, featureId); // Worktrees are in project dir - const worktreePath = path.join(projectPath, '.worktrees', featureId); + // Sanitize featureId the same way it's sanitized when creating worktrees + const sanitizedFeatureId = featureId.replace(/[^a-zA-Z0-9_-]/g, '-'); + const worktreePath = path.join(projectPath, '.worktrees', sanitizedFeatureId); let workDir = projectPath; try { @@ -2143,7 +2145,9 @@ Address the follow-up instructions above. Review the previous work and make the } } else { // Fallback: try to find worktree at legacy location - const legacyWorktreePath = path.join(projectPath, '.worktrees', featureId); + // Sanitize featureId the same way it's sanitized when creating worktrees + const sanitizedFeatureId = featureId.replace(/[^a-zA-Z0-9_-]/g, '-'); + const legacyWorktreePath = path.join(projectPath, '.worktrees', sanitizedFeatureId); try { await secureFs.access(legacyWorktreePath); workDir = legacyWorktreePath; @@ -2429,22 +2433,25 @@ Format your response as a structured markdown document.`; provider?: ModelProvider; title?: string; description?: string; + branchName?: string; }> > { const agents = await Promise.all( Array.from(this.runningFeatures.values()).map(async (rf) => { - // Try to fetch feature data to get title and description + // Try to fetch feature data to get title, description, and branchName let title: string | undefined; let description: string | undefined; + let branchName: string | undefined; try { const feature = await this.featureLoader.get(rf.projectPath, rf.featureId); if (feature) { title = feature.title; description = feature.description; + branchName = feature.branchName; } } catch (error) { - // Silently ignore errors - title/description are optional + // Silently ignore errors - title/description/branchName are optional } return { @@ -2456,6 +2463,7 @@ Format your response as a structured markdown document.`; provider: rf.provider, title, description, + branchName, }; }) ); diff --git a/apps/ui/src/components/views/board-view.tsx b/apps/ui/src/components/views/board-view.tsx index 17d44d2b..c72fc8de 100644 --- a/apps/ui/src/components/views/board-view.tsx +++ b/apps/ui/src/components/views/board-view.tsx @@ -1415,6 +1415,7 @@ export function BoardView() { featureId={outputFeature?.id || ''} featureStatus={outputFeature?.status} onNumberKeyPress={handleOutputModalNumberKeyPress} + branchName={outputFeature?.branchName} /> {/* Archive All Verified Dialog */} diff --git a/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx b/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx index ba78f1c8..cfb34f18 100644 --- a/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx @@ -28,6 +28,8 @@ interface AgentOutputModalProps { onNumberKeyPress?: (key: string) => void; /** Project path - if not provided, falls back to window.__currentProject for backward compatibility */ projectPath?: string; + /** Branch name for the feature worktree - used when viewing changes */ + branchName?: string; } type ViewMode = 'summary' | 'parsed' | 'raw' | 'changes'; @@ -40,6 +42,7 @@ export function AgentOutputModal({ featureStatus, onNumberKeyPress, projectPath: projectPathProp, + branchName, }: AgentOutputModalProps) { const isBacklogPlan = featureId.startsWith('backlog-plan:'); const [output, setOutput] = useState(''); @@ -433,7 +436,7 @@ export function AgentOutputModal({ {projectPath ? ( {/* Backlog Plan Dialog */} diff --git a/apps/ui/src/components/views/running-agents-view.tsx b/apps/ui/src/components/views/running-agents-view.tsx index b77518d0..883609db 100644 --- a/apps/ui/src/components/views/running-agents-view.tsx +++ b/apps/ui/src/components/views/running-agents-view.tsx @@ -280,6 +280,7 @@ export function RunningAgentsView() { } featureId={selectedAgent.featureId} featureStatus="running" + branchName={selectedAgent.branchName} /> )}