takt worktreeのBugFix
This commit is contained in:
parent
91731981d3
commit
fabf4bcd27
185
src/__tests__/it-worktree-delete.test.ts
Normal file
185
src/__tests__/it-worktree-delete.test.ts
Normal file
@ -0,0 +1,185 @@
|
||||
/**
|
||||
* Integration test for worktree branch deletion
|
||||
*
|
||||
* Tests that worktree branches can be properly deleted,
|
||||
* including cleanup of worktree directory and session file.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdirSync, writeFileSync, rmSync, existsSync } from 'node:fs';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { listTaktBranches } from '../infra/task/branchList.js';
|
||||
import { deleteBranch } from '../features/tasks/list/taskActions.js';
|
||||
|
||||
describe('worktree branch deletion', () => {
|
||||
let testDir: string;
|
||||
let worktreeDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
// Create temporary git repository
|
||||
testDir = join(tmpdir(), `takt-test-${Date.now()}`);
|
||||
mkdirSync(testDir, { recursive: true });
|
||||
|
||||
// Initialize git repo
|
||||
execFileSync('git', ['init'], { cwd: testDir });
|
||||
execFileSync('git', ['config', 'user.name', 'Test User'], { cwd: testDir });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: testDir });
|
||||
|
||||
// Create initial commit
|
||||
writeFileSync(join(testDir, 'README.md'), '# Test');
|
||||
execFileSync('git', ['add', '.'], { cwd: testDir });
|
||||
execFileSync('git', ['commit', '-m', 'Initial commit'], { cwd: testDir });
|
||||
|
||||
// Create .takt directory structure
|
||||
const taktDir = join(testDir, '.takt');
|
||||
mkdirSync(taktDir, { recursive: true });
|
||||
mkdirSync(join(taktDir, 'worktree-sessions'), { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Cleanup
|
||||
if (worktreeDir && existsSync(worktreeDir)) {
|
||||
rmSync(worktreeDir, { recursive: true, force: true });
|
||||
}
|
||||
if (existsSync(testDir)) {
|
||||
rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('should delete worktree branch and cleanup files', () => {
|
||||
// Create worktree
|
||||
const branchSlug = '20260203T1000-test-deletion';
|
||||
worktreeDir = join(tmpdir(), branchSlug);
|
||||
execFileSync('git', ['clone', '--shared', testDir, worktreeDir]);
|
||||
|
||||
const branchName = `takt/${branchSlug}`;
|
||||
execFileSync('git', ['checkout', '-b', branchName], { cwd: worktreeDir });
|
||||
|
||||
// Make a change
|
||||
writeFileSync(join(worktreeDir, 'test.txt'), 'test content');
|
||||
execFileSync('git', ['add', 'test.txt'], { cwd: worktreeDir });
|
||||
execFileSync('git', ['commit', '-m', 'Test change'], { cwd: worktreeDir });
|
||||
|
||||
// Create worktree-session file
|
||||
const resolvedPath = resolve(worktreeDir);
|
||||
const sessionFilename = resolvedPath.replace(/[/\\:]/g, '-') + '.json';
|
||||
const sessionPath = join(testDir, '.takt', 'worktree-sessions', sessionFilename);
|
||||
const sessionData = {
|
||||
agentSessions: {},
|
||||
updatedAt: new Date().toISOString(),
|
||||
provider: 'claude',
|
||||
};
|
||||
writeFileSync(sessionPath, JSON.stringify(sessionData, null, 2));
|
||||
|
||||
// Verify branch is listed
|
||||
const branchesBefore = listTaktBranches(testDir);
|
||||
const foundBefore = branchesBefore.find(b => b.branch === branchName);
|
||||
expect(foundBefore).toBeDefined();
|
||||
expect(foundBefore?.worktreePath).toBe(worktreeDir);
|
||||
|
||||
// Verify worktree directory and session file exist
|
||||
expect(existsSync(worktreeDir)).toBe(true);
|
||||
expect(existsSync(sessionPath)).toBe(true);
|
||||
|
||||
// Delete branch
|
||||
const result = deleteBranch(testDir, {
|
||||
info: foundBefore!,
|
||||
filesChanged: 1,
|
||||
taskSlug: branchSlug,
|
||||
originalInstruction: 'Test instruction',
|
||||
});
|
||||
|
||||
// Verify deletion succeeded
|
||||
expect(result).toBe(true);
|
||||
|
||||
// Verify worktree directory was removed
|
||||
expect(existsSync(worktreeDir)).toBe(false);
|
||||
|
||||
// Verify session file was removed
|
||||
expect(existsSync(sessionPath)).toBe(false);
|
||||
|
||||
// Verify branch is no longer listed
|
||||
const branchesAfter = listTaktBranches(testDir);
|
||||
const foundAfter = branchesAfter.find(b => b.branch === branchName);
|
||||
expect(foundAfter).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle deletion when worktree directory is already deleted', () => {
|
||||
// Create worktree
|
||||
const branchSlug = '20260203T1001-already-deleted';
|
||||
worktreeDir = join(tmpdir(), branchSlug);
|
||||
execFileSync('git', ['clone', '--shared', testDir, worktreeDir]);
|
||||
|
||||
const branchName = `takt/${branchSlug}`;
|
||||
execFileSync('git', ['checkout', '-b', branchName], { cwd: worktreeDir });
|
||||
|
||||
// Create worktree-session file
|
||||
const resolvedPath = resolve(worktreeDir);
|
||||
const sessionFilename = resolvedPath.replace(/[/\\:]/g, '-') + '.json';
|
||||
const sessionPath = join(testDir, '.takt', 'worktree-sessions', sessionFilename);
|
||||
const sessionData = {
|
||||
agentSessions: {},
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
writeFileSync(sessionPath, JSON.stringify(sessionData, null, 2));
|
||||
|
||||
// Manually delete worktree directory before deletion
|
||||
rmSync(worktreeDir, { recursive: true, force: true });
|
||||
|
||||
// Delete branch (should not fail even though worktree is gone)
|
||||
const result = deleteBranch(testDir, {
|
||||
info: {
|
||||
branch: branchName,
|
||||
commit: 'worktree',
|
||||
worktreePath: worktreeDir,
|
||||
},
|
||||
filesChanged: 0,
|
||||
taskSlug: branchSlug,
|
||||
originalInstruction: 'Test instruction',
|
||||
});
|
||||
|
||||
// Verify deletion succeeded
|
||||
expect(result).toBe(true);
|
||||
|
||||
// Verify session file was still removed
|
||||
expect(existsSync(sessionPath)).toBe(false);
|
||||
});
|
||||
|
||||
it('should delete regular (non-worktree) branches normally', () => {
|
||||
// Create a regular local branch
|
||||
const branchName = 'takt/20260203T1002-regular-branch';
|
||||
execFileSync('git', ['checkout', '-b', branchName], { cwd: testDir });
|
||||
|
||||
// Make a change
|
||||
writeFileSync(join(testDir, 'test.txt'), 'test content');
|
||||
execFileSync('git', ['add', 'test.txt'], { cwd: testDir });
|
||||
execFileSync('git', ['commit', '-m', 'Test change'], { cwd: testDir });
|
||||
|
||||
// Switch back to main
|
||||
execFileSync('git', ['checkout', 'master'], { cwd: testDir });
|
||||
|
||||
// Verify branch exists
|
||||
const branchesBefore = listTaktBranches(testDir);
|
||||
const foundBefore = branchesBefore.find(b => b.branch === branchName);
|
||||
expect(foundBefore).toBeDefined();
|
||||
expect(foundBefore?.worktreePath).toBeUndefined();
|
||||
|
||||
// Delete branch
|
||||
const result = deleteBranch(testDir, {
|
||||
info: foundBefore!,
|
||||
filesChanged: 1,
|
||||
taskSlug: '20260203T1002-regular-branch',
|
||||
originalInstruction: 'Test instruction',
|
||||
});
|
||||
|
||||
// Verify deletion succeeded
|
||||
expect(result).toBe(true);
|
||||
|
||||
// Verify branch is no longer listed
|
||||
const branchesAfter = listTaktBranches(testDir);
|
||||
const foundAfter = branchesAfter.find(b => b.branch === branchName);
|
||||
expect(foundAfter).toBeUndefined();
|
||||
});
|
||||
});
|
||||
131
src/__tests__/it-worktree-sessions.test.ts
Normal file
131
src/__tests__/it-worktree-sessions.test.ts
Normal file
@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Integration test for worktree-sessions recognition in takt list
|
||||
*
|
||||
* Tests that branches created in isolated worktrees (shared clones)
|
||||
* are properly recognized by `takt list` through worktree-sessions tracking.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdirSync, writeFileSync, rmSync, existsSync } from 'node:fs';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { listTaktBranches } from '../infra/task/branchList.js';
|
||||
|
||||
describe('worktree-sessions recognition', () => {
|
||||
let testDir: string;
|
||||
let worktreeDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
// Create temporary git repository
|
||||
testDir = join(tmpdir(), `takt-test-${Date.now()}`);
|
||||
mkdirSync(testDir, { recursive: true });
|
||||
|
||||
// Initialize git repo
|
||||
execFileSync('git', ['init'], { cwd: testDir });
|
||||
execFileSync('git', ['config', 'user.name', 'Test User'], { cwd: testDir });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: testDir });
|
||||
|
||||
// Create initial commit
|
||||
writeFileSync(join(testDir, 'README.md'), '# Test');
|
||||
execFileSync('git', ['add', '.'], { cwd: testDir });
|
||||
execFileSync('git', ['commit', '-m', 'Initial commit'], { cwd: testDir });
|
||||
|
||||
// Create .takt directory structure
|
||||
const taktDir = join(testDir, '.takt');
|
||||
mkdirSync(taktDir, { recursive: true });
|
||||
mkdirSync(join(taktDir, 'worktree-sessions'), { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Cleanup
|
||||
if (worktreeDir && existsSync(worktreeDir)) {
|
||||
rmSync(worktreeDir, { recursive: true, force: true });
|
||||
}
|
||||
if (existsSync(testDir)) {
|
||||
rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('should recognize branches from worktree-sessions', () => {
|
||||
// Simulate worktree creation (directory name includes timestamp-slug)
|
||||
const branchSlug = '20260203T0900-test-feature';
|
||||
worktreeDir = join(tmpdir(), branchSlug);
|
||||
|
||||
// Create shared clone
|
||||
execFileSync('git', ['clone', '--shared', testDir, worktreeDir]);
|
||||
|
||||
// Create and checkout takt branch in worktree
|
||||
const branchName = `takt/${branchSlug}`;
|
||||
execFileSync('git', ['checkout', '-b', branchName], { cwd: worktreeDir });
|
||||
|
||||
// Make a change
|
||||
writeFileSync(join(worktreeDir, 'test.txt'), 'test content');
|
||||
execFileSync('git', ['add', 'test.txt'], { cwd: worktreeDir });
|
||||
execFileSync('git', ['commit', '-m', 'Test change'], { cwd: worktreeDir });
|
||||
|
||||
// Create worktree-session file (using same encoding as encodeWorktreePath)
|
||||
const resolvedPath = resolve(worktreeDir);
|
||||
const sessionFilename = resolvedPath.replace(/[/\\:]/g, '-') + '.json';
|
||||
const sessionPath = join(testDir, '.takt', 'worktree-sessions', sessionFilename);
|
||||
const sessionData = {
|
||||
agentSessions: {},
|
||||
updatedAt: new Date().toISOString(),
|
||||
provider: 'claude',
|
||||
};
|
||||
writeFileSync(sessionPath, JSON.stringify(sessionData, null, 2));
|
||||
|
||||
// Test: listTaktBranches should find the worktree branch
|
||||
const branches = listTaktBranches(testDir);
|
||||
|
||||
expect(branches.length).toBeGreaterThan(0);
|
||||
const found = branches.find(b => b.branch === branchName);
|
||||
expect(found).toBeDefined();
|
||||
expect(found?.worktreePath).toBe(worktreeDir);
|
||||
});
|
||||
|
||||
it('should skip worktree-sessions when worktree directory is deleted', () => {
|
||||
// Create worktree-session file for non-existent directory
|
||||
worktreeDir = '/nonexistent/path/20260203T0900-test';
|
||||
const resolvedPath = resolve(worktreeDir);
|
||||
const sessionFilename = resolvedPath.replace(/[/\\:]/g, '-') + '.json';
|
||||
const sessionPath = join(testDir, '.takt', 'worktree-sessions', sessionFilename);
|
||||
const sessionData = {
|
||||
agentSessions: {},
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
writeFileSync(sessionPath, JSON.stringify(sessionData, null, 2));
|
||||
|
||||
// Test: listTaktBranches should not include the non-existent worktree
|
||||
const branches = listTaktBranches(testDir);
|
||||
|
||||
const found = branches.find(b => b.worktreePath === worktreeDir);
|
||||
expect(found).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should extract correct branch name from session filename', () => {
|
||||
// Create worktree (directory name includes timestamp-slug)
|
||||
const branchSlug = '20260203T0851-unify-debug-log';
|
||||
worktreeDir = join(tmpdir(), branchSlug);
|
||||
execFileSync('git', ['clone', '--shared', testDir, worktreeDir]);
|
||||
|
||||
const branchName = `takt/${branchSlug}`;
|
||||
execFileSync('git', ['checkout', '-b', branchName], { cwd: worktreeDir });
|
||||
|
||||
// Create session file with proper path encoding
|
||||
const resolvedPath = resolve(worktreeDir);
|
||||
const sessionFilename = resolvedPath.replace(/[/\\:]/g, '-') + '.json';
|
||||
const sessionPath = join(testDir, '.takt', 'worktree-sessions', sessionFilename);
|
||||
const sessionData = {
|
||||
agentSessions: {},
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
writeFileSync(sessionPath, JSON.stringify(sessionData, null, 2));
|
||||
|
||||
const branches = listTaktBranches(testDir);
|
||||
|
||||
const found = branches.find(b => b.branch === branchName);
|
||||
expect(found).toBeDefined();
|
||||
expect(found?.worktreePath).toBe(worktreeDir);
|
||||
});
|
||||
});
|
||||
@ -6,6 +6,8 @@
|
||||
*/
|
||||
|
||||
import { execFileSync, spawnSync } from 'node:child_process';
|
||||
import { rmSync, existsSync, unlinkSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import chalk from 'chalk';
|
||||
import {
|
||||
createTempCloneForBranch,
|
||||
@ -25,6 +27,7 @@ import { executeTask } from '../execute/taskExecution.js';
|
||||
import type { TaskExecutionOptions } from '../execute/types.js';
|
||||
import { listWorkflows, getCurrentWorkflow } from '../../../infra/config/index.js';
|
||||
import { DEFAULT_WORKFLOW_NAME } from '../../../shared/constants.js';
|
||||
import { encodeWorktreePath } from '../../../infra/config/project/sessionStore.js';
|
||||
|
||||
const log = createLogger('list-tasks');
|
||||
|
||||
@ -178,11 +181,34 @@ export function mergeBranch(projectDir: string, item: BranchListItem): boolean {
|
||||
|
||||
/**
|
||||
* Delete a branch (discard changes).
|
||||
* For worktree branches, removes the worktree directory and session file.
|
||||
*/
|
||||
export function deleteBranch(projectDir: string, item: BranchListItem): boolean {
|
||||
const { branch } = item.info;
|
||||
const { branch, worktreePath } = item.info;
|
||||
|
||||
try {
|
||||
// If this is a worktree branch, remove the worktree directory and session file
|
||||
if (worktreePath) {
|
||||
// Remove worktree directory if it exists
|
||||
if (existsSync(worktreePath)) {
|
||||
rmSync(worktreePath, { recursive: true, force: true });
|
||||
log.info('Removed worktree directory', { worktreePath });
|
||||
}
|
||||
|
||||
// Remove worktree-session file
|
||||
const encodedPath = encodeWorktreePath(worktreePath);
|
||||
const sessionFile = join(projectDir, '.takt', 'worktree-sessions', `${encodedPath}.json`);
|
||||
if (existsSync(sessionFile)) {
|
||||
unlinkSync(sessionFile);
|
||||
log.info('Removed worktree-session file', { sessionFile });
|
||||
}
|
||||
|
||||
success(`Deleted worktree ${branch}`);
|
||||
log.info('Worktree branch deleted', { branch, worktreePath });
|
||||
return true;
|
||||
}
|
||||
|
||||
// For regular branches, use git branch -D
|
||||
execFileSync('git', ['branch', '-D', branch], {
|
||||
cwd: projectDir,
|
||||
encoding: 'utf-8',
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
*/
|
||||
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { readdirSync, existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { createLogger } from '../../shared/utils/index.js';
|
||||
|
||||
import type { BranchInfo, BranchListItem } from './types.js';
|
||||
@ -49,20 +51,105 @@ export class BranchManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** List all takt-managed branches */
|
||||
/** List all takt-managed branches (local + remote + worktree-sessions) */
|
||||
listTaktBranches(projectDir: string): BranchInfo[] {
|
||||
try {
|
||||
const output = execFileSync(
|
||||
// Get local branches
|
||||
const localOutput = execFileSync(
|
||||
'git', ['branch', '--list', 'takt/*', '--format=%(refname:short) %(objectname:short)'],
|
||||
{ cwd: projectDir, encoding: 'utf-8', stdio: 'pipe' },
|
||||
);
|
||||
return BranchManager.parseTaktBranches(output);
|
||||
const localBranches = BranchManager.parseTaktBranches(localOutput);
|
||||
|
||||
// Get remote branches
|
||||
const remoteOutput = execFileSync(
|
||||
'git', ['branch', '-r', '--list', 'origin/takt/*', '--format=%(refname:short) %(objectname:short)'],
|
||||
{ cwd: projectDir, encoding: 'utf-8', stdio: 'pipe' },
|
||||
);
|
||||
const remoteBranches = BranchManager.parseTaktBranches(remoteOutput)
|
||||
.map(info => ({
|
||||
...info,
|
||||
branch: info.branch.replace(/^origin\//, ''), // Strip origin/ prefix
|
||||
}));
|
||||
|
||||
// Get branches from worktree-sessions (for isolated worktrees without remote)
|
||||
const worktreeBranches = this.listWorktreeSessions(projectDir);
|
||||
|
||||
// Merge and deduplicate (local > remote > worktree-sessions)
|
||||
const branchMap = new Map<string, BranchInfo>();
|
||||
for (const info of worktreeBranches) {
|
||||
branchMap.set(info.branch, info);
|
||||
}
|
||||
for (const info of remoteBranches) {
|
||||
branchMap.set(info.branch, info);
|
||||
}
|
||||
for (const info of localBranches) {
|
||||
branchMap.set(info.branch, info);
|
||||
}
|
||||
|
||||
return Array.from(branchMap.values());
|
||||
} catch (err) {
|
||||
log.error('Failed to list takt branches', { error: String(err) });
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** List branches from worktree-sessions directory */
|
||||
private listWorktreeSessions(projectDir: string): BranchInfo[] {
|
||||
const sessionsDir = join(projectDir, '.takt', 'worktree-sessions');
|
||||
if (!existsSync(sessionsDir)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const files = readdirSync(sessionsDir);
|
||||
const branches: BranchInfo[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
if (!file.endsWith('.json')) continue;
|
||||
|
||||
// Extract branch slug from filename using timestamp pattern
|
||||
// Filename format: -path-to-parent-dir-{timestamp-slug}.json
|
||||
const nameWithoutExt = file.slice(0, -5); // Remove .json
|
||||
const match = nameWithoutExt.match(/(\d{8}T\d{4}-.+)$/);
|
||||
if (!match || match.index === undefined || !match[1]) continue;
|
||||
|
||||
const branchSlug = match[1];
|
||||
const branch = `${TAKT_BRANCH_PREFIX}${branchSlug}`;
|
||||
|
||||
// Extract parent directory path (everything before the branch slug)
|
||||
// Remove trailing dash before converting dashes to slashes
|
||||
let encodedPath = nameWithoutExt.slice(0, match.index);
|
||||
if (encodedPath.endsWith('-')) {
|
||||
encodedPath = encodedPath.slice(0, -1);
|
||||
}
|
||||
|
||||
// Decode parent directory path (dashes back to slashes)
|
||||
const parentPath = encodedPath.replace(/-/g, '/');
|
||||
|
||||
// Construct full worktree path
|
||||
const worktreePath = join(parentPath, branchSlug);
|
||||
|
||||
// Check if worktree directory still exists
|
||||
if (!existsSync(worktreePath)) {
|
||||
continue; // Skip if worktree was deleted
|
||||
}
|
||||
|
||||
// Use placeholder commit hash (worktree sessions don't track commit)
|
||||
branches.push({
|
||||
branch,
|
||||
commit: 'worktree',
|
||||
worktreePath,
|
||||
});
|
||||
}
|
||||
|
||||
return branches;
|
||||
} catch (err) {
|
||||
log.error('Failed to list worktree sessions', { error: String(err) });
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** Parse `git branch --list` formatted output into BranchInfo entries */
|
||||
static parseTaktBranches(output: string): BranchInfo[] {
|
||||
const entries: BranchInfo[] = [];
|
||||
@ -87,14 +174,24 @@ export class BranchManager {
|
||||
}
|
||||
|
||||
/** Get the number of files changed between the default branch and a given branch */
|
||||
getFilesChanged(cwd: string, defaultBranch: string, branch: string): number {
|
||||
getFilesChanged(cwd: string, defaultBranch: string, branch: string, worktreePath?: string): number {
|
||||
try {
|
||||
// If worktreePath is provided, use it for git diff (for worktree-sessions branches)
|
||||
const gitCwd = worktreePath && existsSync(worktreePath) ? worktreePath : cwd;
|
||||
|
||||
log.debug('getFilesChanged', { gitCwd, defaultBranch, branch, worktreePath });
|
||||
|
||||
const output = execFileSync(
|
||||
'git', ['diff', '--numstat', `${defaultBranch}...${branch}`],
|
||||
{ cwd, encoding: 'utf-8', stdio: 'pipe' },
|
||||
{ cwd: gitCwd, encoding: 'utf-8', stdio: 'pipe' },
|
||||
);
|
||||
return output.trim().split('\n').filter(l => l.length > 0).length;
|
||||
} catch {
|
||||
|
||||
const fileCount = output.trim().split('\n').filter(l => l.length > 0).length;
|
||||
log.debug('getFilesChanged result', { fileCount, outputLength: output.length });
|
||||
|
||||
return fileCount;
|
||||
} catch (err) {
|
||||
log.error('getFilesChanged failed', { error: String(err), branch, worktreePath });
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -144,7 +241,7 @@ export class BranchManager {
|
||||
): BranchListItem[] {
|
||||
return branches.map(br => ({
|
||||
info: br,
|
||||
filesChanged: this.getFilesChanged(projectDir, defaultBranch, br.branch),
|
||||
filesChanged: this.getFilesChanged(projectDir, defaultBranch, br.branch, br.worktreePath),
|
||||
taskSlug: BranchManager.extractTaskSlug(br.branch),
|
||||
originalInstruction: this.getOriginalInstruction(projectDir, defaultBranch, br.branch),
|
||||
}));
|
||||
|
||||
@ -46,6 +46,7 @@ export interface WorktreeResult {
|
||||
export interface BranchInfo {
|
||||
branch: string;
|
||||
commit: string;
|
||||
worktreePath?: string; // Path to worktree directory (for worktree-sessions branches)
|
||||
}
|
||||
|
||||
/** Branch with list metadata */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user