ccmanager 3.12.6 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,317 +1,155 @@
1
- import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
2
- import { Effect } from 'effect';
3
- import { WorktreeService, setWorktreeLastOpened } from './worktreeService.js';
4
- import { execSync } from 'child_process';
5
- // We need to keep a reference to the original Map to clear it between tests
6
- // Module-level state needs to be reset for isolated tests
7
- // Mock child_process module
8
- vi.mock('child_process');
9
- // Mock fs module
10
- vi.mock('fs');
11
- // Mock worktreeConfigManager
12
- vi.mock('./worktreeConfigManager.js', () => ({
13
- worktreeConfigManager: {
14
- initialize: vi.fn(),
15
- isAvailable: vi.fn(() => true),
16
- reset: vi.fn(),
1
+ import { describe, it, expect } from 'vitest';
2
+ import { prepareSessionItems } from '../utils/worktreeUtils.js';
3
+ const makeWorktree = (path, branch) => ({
4
+ path,
5
+ branch,
6
+ isMainWorktree: path.endsWith('/main'),
7
+ hasSession: false,
8
+ });
9
+ const makeSession = (id, worktreePath, number, lastAccessedAt, name) => ({
10
+ id,
11
+ worktreePath,
12
+ sessionNumber: number,
13
+ sessionName: name,
14
+ lastAccessedAt,
15
+ stateMutex: {
16
+ getSnapshot: () => ({
17
+ state: 'idle',
18
+ backgroundTaskCount: 0,
19
+ teamMemberCount: 0,
20
+ }),
17
21
  },
18
- }));
19
- // Mock configReader (still needed for getWorktreeHooks in createWorktreeEffect)
20
- vi.mock('./config/configReader.js', () => ({
21
- configReader: {
22
- getWorktreeConfig: vi.fn(() => ({
23
- autoDirectory: false,
24
- copySessionData: true,
22
+ });
23
+ describe('prepareSessionItems - sortByLastSession', () => {
24
+ it('should not sort worktrees when sortByLastSession is false', () => {
25
+ const worktrees = [
26
+ makeWorktree('/repo', 'main'),
27
+ makeWorktree('/repo/feature-a', 'feature-a'),
28
+ makeWorktree('/repo/feature-b', 'feature-b'),
29
+ ];
30
+ const sessions = [
31
+ makeSession('s1', '/repo', 1, 1000),
32
+ makeSession('s2', '/repo/feature-a', 1, 3000),
33
+ ];
34
+ const items = prepareSessionItems(worktrees, sessions, {
25
35
  sortByLastSession: false,
26
- })),
27
- getWorktreeHooks: vi.fn(() => ({})),
28
- },
29
- }));
30
- // Mock HookExecutor
31
- vi.mock('../utils/hookExecutor.js', () => ({
32
- executeWorktreePostCreationHook: vi.fn(),
33
- }));
34
- // Get the mocked functions with proper typing
35
- const mockedExecSync = vi.mocked(execSync);
36
- // Helper to clear worktree last opened state by setting all known paths to undefined time
37
- // Since we can't clear the Map directly, we'll set timestamps to 0 for cleanup
38
- const clearWorktreeTimestamps = () => {
39
- // This is a workaround since we can't access the internal Map
40
- // Tests should use unique paths or set their own timestamps
41
- };
42
- describe('WorktreeService - Sorting', () => {
43
- let service;
44
- beforeEach(() => {
45
- vi.clearAllMocks();
46
- // Mock git rev-parse --git-common-dir to return a predictable path
47
- mockedExecSync.mockImplementation((cmd, _options) => {
48
- if (typeof cmd === 'string' && cmd === 'git rev-parse --git-common-dir') {
49
- return '/test/repo/.git\n';
50
- }
51
- throw new Error('Command not mocked: ' + cmd);
52
36
  });
53
- // Create service instance
54
- service = new WorktreeService('/test/repo');
55
- });
56
- afterEach(() => {
57
- clearWorktreeTimestamps();
37
+ expect(items[0]?.worktree.path).toBe('/repo');
38
+ expect(items[1]?.worktree.path).toBe('/repo/feature-a');
39
+ expect(items[2]?.worktree.path).toBe('/repo/feature-b');
58
40
  });
59
- describe('getWorktreesEffect with sortByLastSession', () => {
60
- it('should not sort worktrees when sortByLastSession is false', async () => {
61
- // Setup mock git output
62
- const gitOutput = `worktree /test/repo
63
- branch refs/heads/main
64
-
65
- worktree /test/repo/feature-a
66
- branch refs/heads/feature-a
67
-
68
- worktree /test/repo/feature-b
69
- branch refs/heads/feature-b
70
- `;
71
- mockedExecSync.mockReturnValue(gitOutput);
72
- // Execute
73
- const result = await Effect.runPromise(service.getWorktreesEffect({ sortByLastSession: false }));
74
- // Verify order is unchanged (as returned by git)
75
- expect(result).toHaveLength(3);
76
- expect(result[0]?.path).toBe('/test/repo');
77
- expect(result[1]?.path).toBe('/test/repo/feature-a');
78
- expect(result[2]?.path).toBe('/test/repo/feature-b');
79
- });
80
- it('should not sort worktrees when sortByLastSession is undefined', async () => {
81
- // Setup mock git output
82
- const gitOutput = `worktree /test/repo
83
- branch refs/heads/main
84
-
85
- worktree /test/repo/feature-a
86
- branch refs/heads/feature-a
87
-
88
- worktree /test/repo/feature-b
89
- branch refs/heads/feature-b
90
- `;
91
- mockedExecSync.mockReturnValue(gitOutput);
92
- // Execute without options
93
- const result = await Effect.runPromise(service.getWorktreesEffect());
94
- // Verify order is unchanged (as returned by git)
95
- expect(result).toHaveLength(3);
96
- expect(result[0]?.path).toBe('/test/repo');
97
- expect(result[1]?.path).toBe('/test/repo/feature-a');
98
- expect(result[2]?.path).toBe('/test/repo/feature-b');
99
- });
100
- it('should sort worktrees by last opened timestamp in descending order', async () => {
101
- // Setup mock git output
102
- const gitOutput = `worktree /test/repo
103
- branch refs/heads/main
104
-
105
- worktree /test/repo/feature-a
106
- branch refs/heads/feature-a
107
-
108
- worktree /test/repo/feature-b
109
- branch refs/heads/feature-b
110
- `;
111
- mockedExecSync.mockReturnValue(gitOutput);
112
- // Setup timestamps - feature-b was opened most recently, then main, then feature-a
113
- setWorktreeLastOpened('/test/repo', 2000);
114
- setWorktreeLastOpened('/test/repo/feature-a', 1000);
115
- setWorktreeLastOpened('/test/repo/feature-b', 3000);
116
- // Execute
117
- const result = await Effect.runPromise(service.getWorktreesEffect({ sortByLastSession: true }));
118
- // Verify sorted order (most recent first)
119
- expect(result).toHaveLength(3);
120
- expect(result[0]?.path).toBe('/test/repo/feature-b'); // 3000
121
- expect(result[1]?.path).toBe('/test/repo'); // 2000
122
- expect(result[2]?.path).toBe('/test/repo/feature-a'); // 1000
123
- });
124
- it('should place worktrees without timestamps at the end', async () => {
125
- // Setup mock git output - use unique paths to avoid state pollution
126
- const gitOutput = `worktree /test/repo-no-ts/main
127
- branch refs/heads/main
128
-
129
- worktree /test/repo-no-ts/feature-a
130
- branch refs/heads/feature-a
131
-
132
- worktree /test/repo-no-ts/feature-b
133
- branch refs/heads/feature-b
134
-
135
- worktree /test/repo-no-ts/feature-c
136
- branch refs/heads/feature-c
137
- `;
138
- mockedExecSync.mockReturnValue(gitOutput);
139
- // Setup timestamps - only feature-a and feature-b have timestamps
140
- // main and feature-c have no timestamps set
141
- setWorktreeLastOpened('/test/repo-no-ts/feature-a', 1000);
142
- setWorktreeLastOpened('/test/repo-no-ts/feature-b', 2000);
143
- // Execute
144
- const result = await Effect.runPromise(service.getWorktreesEffect({ sortByLastSession: true }));
145
- // Verify sorted order
146
- expect(result).toHaveLength(4);
147
- expect(result[0]?.path).toBe('/test/repo-no-ts/feature-b'); // 2000
148
- expect(result[1]?.path).toBe('/test/repo-no-ts/feature-a'); // 1000
149
- // main and feature-c at the end with timestamp 0 (original order preserved)
150
- expect(result[2]?.path).toBe('/test/repo-no-ts/main'); // undefined -> 0
151
- expect(result[3]?.path).toBe('/test/repo-no-ts/feature-c'); // undefined -> 0
152
- });
153
- it('should handle empty worktree list', async () => {
154
- // Setup empty git output
155
- mockedExecSync.mockReturnValue('');
156
- // Execute
157
- const result = await Effect.runPromise(service.getWorktreesEffect({ sortByLastSession: true }));
158
- // Verify empty result
159
- expect(result).toHaveLength(0);
41
+ it('should sort worktrees by most recent session lastAccessedAt', () => {
42
+ const worktrees = [
43
+ makeWorktree('/repo', 'main'),
44
+ makeWorktree('/repo/feature-a', 'feature-a'),
45
+ makeWorktree('/repo/feature-b', 'feature-b'),
46
+ ];
47
+ const sessions = [
48
+ makeSession('s1', '/repo', 1, 2000),
49
+ makeSession('s2', '/repo/feature-a', 1, 1000),
50
+ makeSession('s3', '/repo/feature-b', 1, 3000),
51
+ ];
52
+ const items = prepareSessionItems(worktrees, sessions, {
53
+ sortByLastSession: true,
160
54
  });
161
- it('should handle single worktree', async () => {
162
- // Setup mock git output with single worktree - unique path
163
- const gitOutput = `worktree /test/repo-single
164
- branch refs/heads/main
165
- `;
166
- mockedExecSync.mockReturnValue(gitOutput);
167
- setWorktreeLastOpened('/test/repo-single', 1000);
168
- // Execute
169
- const result = await Effect.runPromise(service.getWorktreesEffect({ sortByLastSession: true }));
170
- // Verify single result
171
- expect(result).toHaveLength(1);
172
- expect(result[0]?.path).toBe('/test/repo-single');
173
- });
174
- it('should maintain stable sort for worktrees with same timestamp', async () => {
175
- // Setup mock git output - unique paths
176
- const gitOutput = `worktree /test/repo-stable/feature-a
177
- branch refs/heads/feature-a
178
-
179
- worktree /test/repo-stable/feature-b
180
- branch refs/heads/feature-b
181
-
182
- worktree /test/repo-stable/feature-c
183
- branch refs/heads/feature-c
184
- `;
185
- mockedExecSync.mockReturnValue(gitOutput);
186
- // All have the same timestamp
187
- setWorktreeLastOpened('/test/repo-stable/feature-a', 1000);
188
- setWorktreeLastOpened('/test/repo-stable/feature-b', 1000);
189
- setWorktreeLastOpened('/test/repo-stable/feature-c', 1000);
190
- // Execute
191
- const result = await Effect.runPromise(service.getWorktreesEffect({ sortByLastSession: true }));
192
- // Verify original order is maintained (stable sort)
193
- expect(result).toHaveLength(3);
194
- expect(result[0]?.path).toBe('/test/repo-stable/feature-a');
195
- expect(result[1]?.path).toBe('/test/repo-stable/feature-b');
196
- expect(result[2]?.path).toBe('/test/repo-stable/feature-c');
55
+ expect(items[0]?.worktree.path).toBe('/repo/feature-b'); // 3000
56
+ expect(items[1]?.worktree.path).toBe('/repo'); // 2000
57
+ expect(items[2]?.worktree.path).toBe('/repo/feature-a'); // 1000
58
+ });
59
+ it('should use the max lastAccessedAt across multiple sessions in one worktree', () => {
60
+ const worktrees = [
61
+ makeWorktree('/repo/wt-a', 'a'),
62
+ makeWorktree('/repo/wt-b', 'b'),
63
+ ];
64
+ const sessions = [
65
+ makeSession('s1', '/repo/wt-a', 1, 1000),
66
+ makeSession('s2', '/repo/wt-a', 2, 5000),
67
+ makeSession('s3', '/repo/wt-b', 1, 3000),
68
+ ];
69
+ const items = prepareSessionItems(worktrees, sessions, {
70
+ sortByLastSession: true,
197
71
  });
198
- it('should sort correctly with mixed timestamps including zero', async () => {
199
- // Setup mock git output - unique paths
200
- const gitOutput = `worktree /test/repo-zero/zero-timestamp
201
- branch refs/heads/zero-timestamp
202
-
203
- worktree /test/repo-zero/recent
204
- branch refs/heads/recent
205
-
206
- worktree /test/repo-zero/older
207
- branch refs/heads/older
208
- `;
209
- mockedExecSync.mockReturnValue(gitOutput);
210
- // Setup timestamps including explicit zero
211
- setWorktreeLastOpened('/test/repo-zero/zero-timestamp', 0);
212
- setWorktreeLastOpened('/test/repo-zero/recent', 3000);
213
- setWorktreeLastOpened('/test/repo-zero/older', 1000);
214
- // Execute
215
- const result = await Effect.runPromise(service.getWorktreesEffect({ sortByLastSession: true }));
216
- // Verify sorted order
217
- expect(result).toHaveLength(3);
218
- expect(result[0]?.path).toBe('/test/repo-zero/recent'); // 3000
219
- expect(result[1]?.path).toBe('/test/repo-zero/older'); // 1000
220
- expect(result[2]?.path).toBe('/test/repo-zero/zero-timestamp'); // 0
72
+ // wt-a has max 5000, wt-b has 3000
73
+ expect(items[0]?.worktree.path).toBe('/repo/wt-a');
74
+ expect(items[1]?.worktree.path).toBe('/repo/wt-a');
75
+ expect(items[2]?.worktree.path).toBe('/repo/wt-b');
76
+ });
77
+ it('should place worktrees without sessions at the end', () => {
78
+ const worktrees = [
79
+ makeWorktree('/repo/no-session', 'no-session'),
80
+ makeWorktree('/repo/has-session', 'has-session'),
81
+ ];
82
+ const sessions = [makeSession('s1', '/repo/has-session', 1, 1000)];
83
+ const items = prepareSessionItems(worktrees, sessions, {
84
+ sortByLastSession: true,
221
85
  });
222
- it('should preserve worktree properties after sorting', async () => {
223
- // Setup mock git output - unique paths
224
- const gitOutput = `worktree /test/repo-props
225
- branch refs/heads/main
226
- bare
227
-
228
- worktree /test/repo-props/feature-a
229
- branch refs/heads/feature-a
230
- `;
231
- mockedExecSync.mockReturnValue(gitOutput);
232
- setWorktreeLastOpened('/test/repo-props', 1000);
233
- setWorktreeLastOpened('/test/repo-props/feature-a', 2000);
234
- // Execute
235
- const result = await Effect.runPromise(service.getWorktreesEffect({ sortByLastSession: true }));
236
- // Verify properties are preserved
237
- expect(result).toHaveLength(2);
238
- expect(result[0]?.path).toBe('/test/repo-props/feature-a');
239
- expect(result[0]?.branch).toBe('feature-a');
240
- expect(result[0]?.isMainWorktree).toBe(false);
241
- expect(result[1]?.path).toBe('/test/repo-props');
242
- expect(result[1]?.branch).toBe('main');
243
- expect(result[1]?.isMainWorktree).toBe(true);
86
+ expect(items[0]?.worktree.path).toBe('/repo/has-session');
87
+ expect(items[1]?.worktree.path).toBe('/repo/no-session');
88
+ });
89
+ it('should sort sessions within a worktree by lastAccessedAt', () => {
90
+ const worktrees = [makeWorktree('/repo/wt', 'wt')];
91
+ const sessions = [
92
+ makeSession('s1', '/repo/wt', 1, 1000),
93
+ makeSession('s2', '/repo/wt', 2, 3000),
94
+ makeSession('s3', '/repo/wt', 3, 2000),
95
+ ];
96
+ const items = prepareSessionItems(worktrees, sessions, {
97
+ sortByLastSession: true,
244
98
  });
245
- it('should handle very large timestamps', async () => {
246
- // Setup mock git output - unique paths
247
- const gitOutput = `worktree /test/repo-large/old
248
- branch refs/heads/old
249
-
250
- worktree /test/repo-large/new
251
- branch refs/heads/new
252
- `;
253
- mockedExecSync.mockReturnValue(gitOutput);
254
- // Use actual Date.now() values
255
- const now = Date.now();
256
- const yesterday = now - 24 * 60 * 60 * 1000;
257
- setWorktreeLastOpened('/test/repo-large/old', yesterday);
258
- setWorktreeLastOpened('/test/repo-large/new', now);
259
- // Execute
260
- const result = await Effect.runPromise(service.getWorktreesEffect({ sortByLastSession: true }));
261
- // Verify sorted order
262
- expect(result).toHaveLength(2);
263
- expect(result[0]?.path).toBe('/test/repo-large/new');
264
- expect(result[1]?.path).toBe('/test/repo-large/old');
99
+ expect(items).toHaveLength(3);
100
+ expect(items[0]?.session?.id).toBe('s2'); // 3000
101
+ expect(items[1]?.session?.id).toBe('s3'); // 2000
102
+ expect(items[2]?.session?.id).toBe('s1'); // 1000
103
+ });
104
+ it('should handle empty worktree list', () => {
105
+ const items = prepareSessionItems([], [], { sortByLastSession: true });
106
+ expect(items).toHaveLength(0);
107
+ });
108
+ it('should preserve worktree properties after sorting', () => {
109
+ const worktrees = [
110
+ makeWorktree('/repo', 'main'),
111
+ makeWorktree('/repo/feature', 'feature'),
112
+ ];
113
+ const sessions = [
114
+ makeSession('s1', '/repo/feature', 1, 2000),
115
+ makeSession('s2', '/repo', 1, 1000),
116
+ ];
117
+ const items = prepareSessionItems(worktrees, sessions, {
118
+ sortByLastSession: true,
265
119
  });
120
+ expect(items[0]?.worktree.path).toBe('/repo/feature');
121
+ expect(items[0]?.worktree.branch).toBe('feature');
122
+ expect(items[0]?.worktree.isMainWorktree).toBe(false);
123
+ expect(items[1]?.worktree.path).toBe('/repo');
124
+ expect(items[1]?.worktree.branch).toBe('main');
266
125
  });
267
- describe('getWorktreesEffect error handling with sorting', () => {
268
- it('should sort correctly when sortByLastSession is true', async () => {
269
- // Setup mock git output - unique paths
270
- const gitOutput = `worktree /test/repo-sort
271
- branch refs/heads/main
272
-
273
- worktree /test/repo-sort/feature-a
274
- branch refs/heads/feature-a
275
-
276
- worktree /test/repo-sort/feature-b
277
- branch refs/heads/feature-b
278
- `;
279
- mockedExecSync.mockReturnValue(gitOutput);
280
- // Set timestamps to verify sorting works
281
- setWorktreeLastOpened('/test/repo-sort', 1000);
282
- setWorktreeLastOpened('/test/repo-sort/feature-a', 3000);
283
- setWorktreeLastOpened('/test/repo-sort/feature-b', 2000);
284
- // Execute with sorting
285
- const result = await Effect.runPromise(service.getWorktreesEffect({ sortByLastSession: true }));
286
- // Verify sorting happened correctly (implicitly means getWorktreeLastOpenedTime was called)
287
- expect(result).toHaveLength(3);
288
- expect(result[0]?.path).toBe('/test/repo-sort/feature-a'); // 3000
289
- expect(result[1]?.path).toBe('/test/repo-sort/feature-b'); // 2000
290
- expect(result[2]?.path).toBe('/test/repo-sort'); // 1000
126
+ it('should maintain stable order for worktrees with same timestamp', () => {
127
+ const worktrees = [
128
+ makeWorktree('/repo/a', 'a'),
129
+ makeWorktree('/repo/b', 'b'),
130
+ makeWorktree('/repo/c', 'c'),
131
+ ];
132
+ const sessions = [
133
+ makeSession('s1', '/repo/a', 1, 1000),
134
+ makeSession('s2', '/repo/b', 1, 1000),
135
+ makeSession('s3', '/repo/c', 1, 1000),
136
+ ];
137
+ const items = prepareSessionItems(worktrees, sessions, {
138
+ sortByLastSession: true,
291
139
  });
292
- it('should not sort when sortByLastSession is false', async () => {
293
- // Setup mock git output - unique paths
294
- const gitOutput = `worktree /test/repo-nosort
295
- branch refs/heads/main
296
-
297
- worktree /test/repo-nosort/feature-a
298
- branch refs/heads/feature-a
299
-
300
- worktree /test/repo-nosort/feature-b
301
- branch refs/heads/feature-b
302
- `;
303
- mockedExecSync.mockReturnValue(gitOutput);
304
- // Set timestamps that would cause reordering if sorting was applied
305
- setWorktreeLastOpened('/test/repo-nosort', 1000);
306
- setWorktreeLastOpened('/test/repo-nosort/feature-a', 3000);
307
- setWorktreeLastOpened('/test/repo-nosort/feature-b', 2000);
308
- // Execute without sorting
309
- const result = await Effect.runPromise(service.getWorktreesEffect({ sortByLastSession: false }));
310
- // Verify original order is preserved
311
- expect(result).toHaveLength(3);
312
- expect(result[0]?.path).toBe('/test/repo-nosort');
313
- expect(result[1]?.path).toBe('/test/repo-nosort/feature-a');
314
- expect(result[2]?.path).toBe('/test/repo-nosort/feature-b');
140
+ expect(items[0]?.worktree.path).toBe('/repo/a');
141
+ expect(items[1]?.worktree.path).toBe('/repo/b');
142
+ expect(items[2]?.worktree.path).toBe('/repo/c');
143
+ });
144
+ it('should not sort when no sessions exist', () => {
145
+ const worktrees = [
146
+ makeWorktree('/repo', 'main'),
147
+ makeWorktree('/repo/feature', 'feature'),
148
+ ];
149
+ const items = prepareSessionItems(worktrees, [], {
150
+ sortByLastSession: true,
315
151
  });
152
+ expect(items[0]?.worktree.path).toBe('/repo');
153
+ expect(items[1]?.worktree.path).toBe('/repo/feature');
316
154
  });
317
155
  });
@@ -18,6 +18,9 @@ export interface Worktree {
18
18
  export interface Session {
19
19
  id: string;
20
20
  worktreePath: string;
21
+ sessionNumber: number;
22
+ sessionName?: string;
23
+ lastAccessedAt: number;
21
24
  process: IPty;
22
25
  output: string[];
23
26
  outputHistory: Buffer[];
@@ -44,12 +47,42 @@ export interface AutoApprovalResponse {
44
47
  needsPermission: boolean;
45
48
  reason?: string;
46
49
  }
50
+ export type MenuAction = {
51
+ type: 'selectWorktree';
52
+ worktree: Worktree;
53
+ session?: Session;
54
+ } | {
55
+ type: 'newWorktree';
56
+ } | {
57
+ type: 'newSession';
58
+ worktreePath: string;
59
+ } | {
60
+ type: 'renameSession';
61
+ session: Session;
62
+ } | {
63
+ type: 'killSession';
64
+ sessionId: string;
65
+ } | {
66
+ type: 'sessionActions';
67
+ session: Session;
68
+ worktreePath: string;
69
+ } | {
70
+ type: 'deleteWorktree';
71
+ } | {
72
+ type: 'mergeWorktree';
73
+ } | {
74
+ type: 'configuration';
75
+ scope: ConfigScope;
76
+ } | {
77
+ type: 'exit';
78
+ };
47
79
  export interface SessionManager {
48
80
  sessions: Map<string, Session>;
49
- getSession(worktreePath: string): Session | undefined;
50
- destroySession(worktreePath: string): void;
81
+ getSessionById(id: string): Session | undefined;
82
+ getSessionsForWorktree(worktreePath: string): Session[];
83
+ destroySession(sessionId: string): void;
51
84
  getAllSessions(): Session[];
52
- cancelAutoApproval(worktreePath: string, reason?: string): void;
85
+ cancelAutoApproval(sessionId: string, reason?: string): void;
53
86
  }
54
87
  export interface ShortcutKey {
55
88
  ctrl?: boolean;
@@ -210,9 +243,7 @@ export declare class AmbiguousBranchError extends Error {
210
243
  constructor(branchName: string, matches: RemoteBranchMatch[]);
211
244
  }
212
245
  export interface IWorktreeService {
213
- getWorktreesEffect(options?: {
214
- sortByLastSession?: boolean;
215
- }): import('effect').Effect.Effect<Worktree[], import('../types/errors.js').GitError, never>;
246
+ getWorktreesEffect(): import('effect').Effect.Effect<Worktree[], import('../types/errors.js').GitError, never>;
216
247
  getGitRootPath(): string;
217
248
  createWorktreeEffect(worktreePath: string, branch: string, baseBranch: string, copySessionData?: boolean, copyClaudeDirectory?: boolean): import('effect').Effect.Effect<Worktree, import('../types/errors.js').GitError | import('../types/errors.js').FileSystemError | import('../types/errors.js').ProcessError, never>;
218
249
  deleteWorktreeEffect(worktreePath: string, options?: {
@@ -376,6 +376,8 @@ describe('hookExecutor Integration Tests', () => {
376
376
  const mockSession = {
377
377
  id: 'test-session-123',
378
378
  worktreePath: tmpDir, // Use tmpDir as the worktree path
379
+ sessionNumber: 1,
380
+ lastAccessedAt: Date.now(),
379
381
  process: {},
380
382
  terminal: {},
381
383
  output: [],
@@ -430,6 +432,8 @@ describe('hookExecutor Integration Tests', () => {
430
432
  const mockSession = {
431
433
  id: 'test-session-456',
432
434
  worktreePath: tmpDir, // Use tmpDir as the worktree path
435
+ sessionNumber: 1,
436
+ lastAccessedAt: Date.now(),
433
437
  process: {},
434
438
  terminal: {},
435
439
  output: [],
@@ -482,6 +486,8 @@ describe('hookExecutor Integration Tests', () => {
482
486
  const mockSession = {
483
487
  id: 'test-session-789',
484
488
  worktreePath: tmpDir, // Use tmpDir as the worktree path
489
+ sessionNumber: 1,
490
+ lastAccessedAt: Date.now(),
485
491
  process: {},
486
492
  terminal: {},
487
493
  output: [],
@@ -536,6 +542,8 @@ describe('hookExecutor Integration Tests', () => {
536
542
  const mockSession = {
537
543
  id: 'test-session-failure',
538
544
  worktreePath: tmpDir,
545
+ sessionNumber: 1,
546
+ lastAccessedAt: Date.now(),
539
547
  process: {},
540
548
  terminal: {},
541
549
  output: [],
@@ -1,8 +1,8 @@
1
1
  import { Worktree, Session } from '../types/index.js';
2
2
  /**
3
- * Worktree item with formatted content for display.
3
+ * One menu row: worktree metadata plus optional session (for multi-session worktrees).
4
4
  */
5
- export interface WorktreeItem {
5
+ export interface SessionItem {
6
6
  worktree: Worktree;
7
7
  session?: Session;
8
8
  baseLabel: string;
@@ -30,13 +30,19 @@ export declare function extractBranchParts(branchName: string): {
30
30
  name: string;
31
31
  };
32
32
  /**
33
- * Prepares worktree content for display with plain and colored versions.
33
+ * Prepares session items for display.
34
+ * Supports multiple sessions per worktree.
35
+ * When sortByLastSession is true, worktrees are sorted by the most recent
36
+ * session lastAccessedAt timestamp (descending), and sessions within each
37
+ * worktree are also sorted by lastAccessedAt.
34
38
  */
35
- export declare function prepareWorktreeItems(worktrees: Worktree[], sessions: Session[]): WorktreeItem[];
39
+ export declare function prepareSessionItems(worktrees: Worktree[], sessions: Session[], options?: {
40
+ sortByLastSession?: boolean;
41
+ }): SessionItem[];
36
42
  /**
37
43
  * Calculates column positions based on content widths.
38
44
  */
39
- export declare function calculateColumnPositions(items: WorktreeItem[]): {
45
+ export declare function calculateColumnPositions(items: SessionItem[]): {
40
46
  fileChanges: number;
41
47
  aheadBehind: number;
42
48
  parentBranch: number;
@@ -45,4 +51,4 @@ export declare function calculateColumnPositions(items: WorktreeItem[]): {
45
51
  /**
46
52
  * Assembles the final worktree label with proper column alignment
47
53
  */
48
- export declare function assembleWorktreeLabel(item: WorktreeItem, columns: ReturnType<typeof calculateColumnPositions>): string;
54
+ export declare function assembleSessionLabel(item: SessionItem, columns: ReturnType<typeof calculateColumnPositions>): string;