@wundr.io/cli 1.0.11 → 1.0.12

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.
Files changed (269) hide show
  1. package/bin/wundr.js +8 -4
  2. package/package.json +23 -23
  3. package/src/ai/ai-service.ts +16 -17
  4. package/src/ai/claude-client.ts +16 -16
  5. package/src/ai/conversation-manager.ts +29 -29
  6. package/src/cli.ts +4 -4
  7. package/src/commands/ai.ts +246 -78
  8. package/src/commands/alignment.ts +74 -74
  9. package/src/commands/analyze-optimized.ts +111 -78
  10. package/src/commands/analyze.ts +14 -14
  11. package/src/commands/batch.ts +179 -42
  12. package/src/commands/chat.ts +37 -30
  13. package/src/commands/claude-init.ts +41 -45
  14. package/src/commands/claude-setup.ts +204 -119
  15. package/src/commands/computer-setup.ts +85 -43
  16. package/src/commands/create-command.ts +4 -4
  17. package/src/commands/create.ts +27 -27
  18. package/src/commands/dashboard.ts +24 -24
  19. package/src/commands/govern.ts +25 -25
  20. package/src/commands/governance.ts +34 -34
  21. package/src/commands/guardian.ts +56 -56
  22. package/src/commands/init.ts +25 -22
  23. package/src/commands/orchestrator.ts +68 -41
  24. package/src/commands/performance-optimizer.ts +34 -35
  25. package/src/commands/plugins.ts +27 -27
  26. package/src/commands/project-update.ts +175 -72
  27. package/src/commands/rag.ts +185 -78
  28. package/src/commands/session.ts +35 -35
  29. package/src/commands/setup.ts +40 -344
  30. package/src/commands/test-init.ts +3 -3
  31. package/src/commands/test.ts +4 -4
  32. package/src/commands/watch.ts +28 -29
  33. package/src/commands/worktree.ts +49 -49
  34. package/src/context/context-manager.ts +10 -10
  35. package/src/context/session-manager.ts +41 -41
  36. package/src/framework/command-interface.ts +520 -0
  37. package/src/framework/command-registry.ts +942 -0
  38. package/src/framework/completion-exporter.ts +383 -0
  39. package/src/framework/debug-logger.ts +519 -0
  40. package/src/framework/error-handler.ts +867 -0
  41. package/src/framework/help-generator.ts +540 -0
  42. package/src/framework/index.ts +169 -0
  43. package/src/framework/interactive-repl.ts +703 -0
  44. package/src/framework/output-formatter.ts +834 -0
  45. package/src/framework/progress-manager.ts +539 -0
  46. package/src/index.ts +4 -4
  47. package/src/interactive/interactive-mode.ts +16 -16
  48. package/src/lib/conflict-resolution.ts +799 -9
  49. package/src/lib/merge-strategy.ts +529 -7
  50. package/src/lib/safety-mechanisms.ts +422 -18
  51. package/src/lib/state-detection.ts +1015 -13
  52. package/src/nlp/command-mapper.ts +29 -29
  53. package/src/nlp/command-parser.ts +17 -17
  54. package/src/nlp/intent-classifier.ts +7 -7
  55. package/src/nlp/intent-parser.ts +54 -52
  56. package/src/plugins/plugin-manager.ts +61 -39
  57. package/src/tests/computer-setup-integration.test.ts +46 -15
  58. package/src/types/modules.d.ts +424 -1
  59. package/src/utils/backup-rollback-manager.ts +11 -8
  60. package/src/utils/config-manager.ts +3 -3
  61. package/src/utils/error-handler.ts +2 -2
  62. package/src/utils/logger.ts +22 -22
  63. package/templates/batch/ci-cd.yaml +7 -7
  64. package/test-suites/api/health.spec.ts +20 -23
  65. package/test-suites/helpers/test-config.ts +14 -13
  66. package/test-suites/ui/accessibility.spec.ts +27 -22
  67. package/test-suites/ui/smoke.spec.ts +26 -21
  68. package/LICENSE +0 -21
  69. package/dist/ai/ai-service.d.ts +0 -152
  70. package/dist/ai/ai-service.d.ts.map +0 -1
  71. package/dist/ai/ai-service.js +0 -430
  72. package/dist/ai/ai-service.js.map +0 -1
  73. package/dist/ai/claude-client.d.ts +0 -130
  74. package/dist/ai/claude-client.d.ts.map +0 -1
  75. package/dist/ai/claude-client.js +0 -340
  76. package/dist/ai/claude-client.js.map +0 -1
  77. package/dist/ai/conversation-manager.d.ts +0 -164
  78. package/dist/ai/conversation-manager.d.ts.map +0 -1
  79. package/dist/ai/conversation-manager.js +0 -614
  80. package/dist/ai/conversation-manager.js.map +0 -1
  81. package/dist/ai/index.d.ts +0 -5
  82. package/dist/ai/index.d.ts.map +0 -1
  83. package/dist/ai/index.js +0 -8
  84. package/dist/ai/index.js.map +0 -1
  85. package/dist/cli.d.ts +0 -36
  86. package/dist/cli.d.ts.map +0 -1
  87. package/dist/cli.js +0 -192
  88. package/dist/cli.js.map +0 -1
  89. package/dist/commands/ai.d.ts +0 -89
  90. package/dist/commands/ai.d.ts.map +0 -1
  91. package/dist/commands/ai.js +0 -799
  92. package/dist/commands/ai.js.map +0 -1
  93. package/dist/commands/alignment.d.ts +0 -78
  94. package/dist/commands/alignment.d.ts.map +0 -1
  95. package/dist/commands/alignment.js +0 -817
  96. package/dist/commands/alignment.js.map +0 -1
  97. package/dist/commands/analyze-optimized.d.ts +0 -14
  98. package/dist/commands/analyze-optimized.d.ts.map +0 -1
  99. package/dist/commands/analyze-optimized.js +0 -600
  100. package/dist/commands/analyze-optimized.js.map +0 -1
  101. package/dist/commands/analyze.d.ts +0 -65
  102. package/dist/commands/analyze.d.ts.map +0 -1
  103. package/dist/commands/analyze.js +0 -435
  104. package/dist/commands/analyze.js.map +0 -1
  105. package/dist/commands/batch.d.ts +0 -71
  106. package/dist/commands/batch.d.ts.map +0 -1
  107. package/dist/commands/batch.js +0 -738
  108. package/dist/commands/batch.js.map +0 -1
  109. package/dist/commands/chat.d.ts +0 -71
  110. package/dist/commands/chat.d.ts.map +0 -1
  111. package/dist/commands/chat.js +0 -674
  112. package/dist/commands/chat.js.map +0 -1
  113. package/dist/commands/claude-init.d.ts +0 -28
  114. package/dist/commands/claude-init.d.ts.map +0 -1
  115. package/dist/commands/claude-init.js +0 -591
  116. package/dist/commands/claude-init.js.map +0 -1
  117. package/dist/commands/claude-setup.d.ts +0 -119
  118. package/dist/commands/claude-setup.d.ts.map +0 -1
  119. package/dist/commands/claude-setup.js +0 -1073
  120. package/dist/commands/claude-setup.js.map +0 -1
  121. package/dist/commands/computer-setup-commands.d.ts +0 -53
  122. package/dist/commands/computer-setup-commands.d.ts.map +0 -1
  123. package/dist/commands/computer-setup-commands.js +0 -705
  124. package/dist/commands/computer-setup-commands.js.map +0 -1
  125. package/dist/commands/computer-setup.d.ts +0 -7
  126. package/dist/commands/computer-setup.d.ts.map +0 -1
  127. package/dist/commands/computer-setup.js +0 -849
  128. package/dist/commands/computer-setup.js.map +0 -1
  129. package/dist/commands/create-command.d.ts +0 -7
  130. package/dist/commands/create-command.d.ts.map +0 -1
  131. package/dist/commands/create-command.js +0 -158
  132. package/dist/commands/create-command.js.map +0 -1
  133. package/dist/commands/create.d.ts +0 -74
  134. package/dist/commands/create.d.ts.map +0 -1
  135. package/dist/commands/create.js +0 -556
  136. package/dist/commands/create.js.map +0 -1
  137. package/dist/commands/dashboard.d.ts +0 -91
  138. package/dist/commands/dashboard.d.ts.map +0 -1
  139. package/dist/commands/dashboard.js +0 -538
  140. package/dist/commands/dashboard.js.map +0 -1
  141. package/dist/commands/govern.d.ts +0 -70
  142. package/dist/commands/govern.d.ts.map +0 -1
  143. package/dist/commands/govern.js +0 -481
  144. package/dist/commands/govern.js.map +0 -1
  145. package/dist/commands/governance.d.ts +0 -17
  146. package/dist/commands/governance.d.ts.map +0 -1
  147. package/dist/commands/governance.js +0 -703
  148. package/dist/commands/governance.js.map +0 -1
  149. package/dist/commands/guardian.d.ts +0 -20
  150. package/dist/commands/guardian.d.ts.map +0 -1
  151. package/dist/commands/guardian.js +0 -597
  152. package/dist/commands/guardian.js.map +0 -1
  153. package/dist/commands/init.d.ts +0 -59
  154. package/dist/commands/init.d.ts.map +0 -1
  155. package/dist/commands/init.js +0 -650
  156. package/dist/commands/init.js.map +0 -1
  157. package/dist/commands/orchestrator.d.ts +0 -7
  158. package/dist/commands/orchestrator.d.ts.map +0 -1
  159. package/dist/commands/orchestrator.js +0 -571
  160. package/dist/commands/orchestrator.js.map +0 -1
  161. package/dist/commands/performance-optimizer.d.ts +0 -30
  162. package/dist/commands/performance-optimizer.d.ts.map +0 -1
  163. package/dist/commands/performance-optimizer.js +0 -650
  164. package/dist/commands/performance-optimizer.js.map +0 -1
  165. package/dist/commands/plugins.d.ts +0 -87
  166. package/dist/commands/plugins.d.ts.map +0 -1
  167. package/dist/commands/plugins.js +0 -685
  168. package/dist/commands/plugins.js.map +0 -1
  169. package/dist/commands/rag.d.ts +0 -7
  170. package/dist/commands/rag.d.ts.map +0 -1
  171. package/dist/commands/rag.js +0 -748
  172. package/dist/commands/rag.js.map +0 -1
  173. package/dist/commands/session.d.ts +0 -41
  174. package/dist/commands/session.d.ts.map +0 -1
  175. package/dist/commands/session.js +0 -441
  176. package/dist/commands/session.js.map +0 -1
  177. package/dist/commands/setup.d.ts +0 -29
  178. package/dist/commands/setup.d.ts.map +0 -1
  179. package/dist/commands/setup.js +0 -397
  180. package/dist/commands/setup.js.map +0 -1
  181. package/dist/commands/test-init.d.ts +0 -9
  182. package/dist/commands/test-init.d.ts.map +0 -1
  183. package/dist/commands/test-init.js +0 -222
  184. package/dist/commands/test-init.js.map +0 -1
  185. package/dist/commands/test.d.ts +0 -25
  186. package/dist/commands/test.d.ts.map +0 -1
  187. package/dist/commands/test.js +0 -217
  188. package/dist/commands/test.js.map +0 -1
  189. package/dist/commands/vp.d.ts +0 -7
  190. package/dist/commands/vp.d.ts.map +0 -1
  191. package/dist/commands/vp.js +0 -571
  192. package/dist/commands/vp.js.map +0 -1
  193. package/dist/commands/watch.d.ts +0 -76
  194. package/dist/commands/watch.d.ts.map +0 -1
  195. package/dist/commands/watch.js +0 -613
  196. package/dist/commands/watch.js.map +0 -1
  197. package/dist/commands/worktree.d.ts +0 -63
  198. package/dist/commands/worktree.d.ts.map +0 -1
  199. package/dist/commands/worktree.js +0 -774
  200. package/dist/commands/worktree.js.map +0 -1
  201. package/dist/context/context-manager.d.ts +0 -155
  202. package/dist/context/context-manager.d.ts.map +0 -1
  203. package/dist/context/context-manager.js +0 -383
  204. package/dist/context/context-manager.js.map +0 -1
  205. package/dist/context/index.d.ts +0 -3
  206. package/dist/context/index.d.ts.map +0 -1
  207. package/dist/context/index.js +0 -6
  208. package/dist/context/index.js.map +0 -1
  209. package/dist/context/session-manager.d.ts +0 -207
  210. package/dist/context/session-manager.d.ts.map +0 -1
  211. package/dist/context/session-manager.js +0 -686
  212. package/dist/context/session-manager.js.map +0 -1
  213. package/dist/index.d.ts +0 -8
  214. package/dist/index.d.ts.map +0 -1
  215. package/dist/index.js +0 -51
  216. package/dist/index.js.map +0 -1
  217. package/dist/interactive/interactive-mode.d.ts +0 -76
  218. package/dist/interactive/interactive-mode.d.ts.map +0 -1
  219. package/dist/interactive/interactive-mode.js +0 -732
  220. package/dist/interactive/interactive-mode.js.map +0 -1
  221. package/dist/nlp/command-mapper.d.ts +0 -174
  222. package/dist/nlp/command-mapper.d.ts.map +0 -1
  223. package/dist/nlp/command-mapper.js +0 -624
  224. package/dist/nlp/command-mapper.js.map +0 -1
  225. package/dist/nlp/command-parser.d.ts +0 -106
  226. package/dist/nlp/command-parser.d.ts.map +0 -1
  227. package/dist/nlp/command-parser.js +0 -417
  228. package/dist/nlp/command-parser.js.map +0 -1
  229. package/dist/nlp/index.d.ts +0 -5
  230. package/dist/nlp/index.d.ts.map +0 -1
  231. package/dist/nlp/index.js +0 -8
  232. package/dist/nlp/index.js.map +0 -1
  233. package/dist/nlp/intent-classifier.d.ts +0 -59
  234. package/dist/nlp/intent-classifier.d.ts.map +0 -1
  235. package/dist/nlp/intent-classifier.js +0 -384
  236. package/dist/nlp/intent-classifier.js.map +0 -1
  237. package/dist/nlp/intent-parser.d.ts +0 -152
  238. package/dist/nlp/intent-parser.d.ts.map +0 -1
  239. package/dist/nlp/intent-parser.js +0 -744
  240. package/dist/nlp/intent-parser.js.map +0 -1
  241. package/dist/plugins/plugin-manager.d.ts +0 -120
  242. package/dist/plugins/plugin-manager.d.ts.map +0 -1
  243. package/dist/plugins/plugin-manager.js +0 -595
  244. package/dist/plugins/plugin-manager.js.map +0 -1
  245. package/dist/types/index.d.ts +0 -224
  246. package/dist/types/index.d.ts.map +0 -1
  247. package/dist/types/index.js +0 -3
  248. package/dist/types/index.js.map +0 -1
  249. package/dist/utils/backup-rollback-manager.d.ts +0 -72
  250. package/dist/utils/backup-rollback-manager.d.ts.map +0 -1
  251. package/dist/utils/backup-rollback-manager.js +0 -289
  252. package/dist/utils/backup-rollback-manager.js.map +0 -1
  253. package/dist/utils/claude-config-installer.d.ts +0 -98
  254. package/dist/utils/claude-config-installer.d.ts.map +0 -1
  255. package/dist/utils/claude-config-installer.js +0 -678
  256. package/dist/utils/claude-config-installer.js.map +0 -1
  257. package/dist/utils/config-manager.d.ts +0 -73
  258. package/dist/utils/config-manager.d.ts.map +0 -1
  259. package/dist/utils/config-manager.js +0 -339
  260. package/dist/utils/config-manager.js.map +0 -1
  261. package/dist/utils/error-handler.d.ts +0 -46
  262. package/dist/utils/error-handler.d.ts.map +0 -1
  263. package/dist/utils/error-handler.js +0 -169
  264. package/dist/utils/error-handler.js.map +0 -1
  265. package/dist/utils/logger.d.ts +0 -25
  266. package/dist/utils/logger.d.ts.map +0 -1
  267. package/dist/utils/logger.js +0 -105
  268. package/dist/utils/logger.js.map +0 -1
  269. package/src/commands/computer-setup-commands.ts +0 -872
@@ -1,774 +0,0 @@
1
- "use strict";
2
- /**
3
- * Worktree Management CLI Commands
4
- *
5
- * Manages git worktrees for multi-agent development including
6
- * listing, creating, switching, cleanup, and synchronization.
7
- *
8
- * @module commands/worktree
9
- */
10
- Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.createWorktreeCommand = createWorktreeCommand;
12
- const tslib_1 = require("tslib");
13
- const child_process_1 = require("child_process");
14
- const fs = tslib_1.__importStar(require("fs/promises"));
15
- const os = tslib_1.__importStar(require("os"));
16
- const path = tslib_1.__importStar(require("path"));
17
- const util_1 = require("util");
18
- const chalk_1 = tslib_1.__importDefault(require("chalk"));
19
- const commander_1 = require("commander");
20
- const ora_1 = tslib_1.__importDefault(require("ora"));
21
- const execAsync = (0, util_1.promisify)(child_process_1.exec);
22
- // Constants
23
- const WORKTREE_STATE_DIR = path.join(os.homedir(), '.wundr', 'worktrees');
24
- const WORKTREE_STATE_FILE = path.join(WORKTREE_STATE_DIR, 'state.json');
25
- // Utility functions
26
- function getTimestamp() {
27
- return new Date().toISOString();
28
- }
29
- function padRight(str, length) {
30
- return str.length >= length
31
- ? str.substring(0, length)
32
- : str + ' '.repeat(length - str.length);
33
- }
34
- function truncate(str, length) {
35
- if (str.length <= length) {
36
- return str;
37
- }
38
- return str.substring(0, length - 3) + '...';
39
- }
40
- function formatAge(dateStr) {
41
- const date = new Date(dateStr);
42
- const now = Date.now();
43
- const diffMs = now - date.getTime();
44
- const diffMins = Math.floor(diffMs / (1000 * 60));
45
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
46
- const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
47
- if (diffDays > 0) {
48
- return `${diffDays}d ago`;
49
- }
50
- if (diffHours > 0) {
51
- return `${diffHours}h ago`;
52
- }
53
- if (diffMins > 0) {
54
- return `${diffMins}m ago`;
55
- }
56
- return 'just now';
57
- }
58
- function generateSessionId() {
59
- const timestamp = Date.now().toString(36);
60
- const random = Math.random().toString(36).substring(2, 8);
61
- return `session-${timestamp}-${random}`;
62
- }
63
- function generateBranchName(taskId) {
64
- const sanitized = taskId
65
- .toLowerCase()
66
- .replace(/[^a-z0-9-]/g, '-')
67
- .replace(/-+/g, '-')
68
- .replace(/^-|-$/g, '');
69
- return `task/${sanitized}`;
70
- }
71
- async function ensureStateDir() {
72
- await fs.mkdir(WORKTREE_STATE_DIR, { recursive: true });
73
- }
74
- async function loadWorktreeState() {
75
- try {
76
- await ensureStateDir();
77
- const content = await fs.readFile(WORKTREE_STATE_FILE, 'utf-8');
78
- return JSON.parse(content);
79
- }
80
- catch {
81
- // Return empty state if file doesn't exist
82
- return {
83
- version: '1.0.0',
84
- lastUpdated: getTimestamp(),
85
- repoPath: process.cwd(),
86
- worktrees: [],
87
- };
88
- }
89
- }
90
- async function saveWorktreeState(state) {
91
- await ensureStateDir();
92
- state.lastUpdated = getTimestamp();
93
- await fs.writeFile(WORKTREE_STATE_FILE, JSON.stringify(state, null, 2));
94
- }
95
- async function getGitRepoRoot() {
96
- try {
97
- const { stdout } = await execAsync('git rev-parse --show-toplevel', {
98
- timeout: 10000,
99
- });
100
- return stdout.trim();
101
- }
102
- catch {
103
- return process.cwd();
104
- }
105
- }
106
- async function executeGitCommand(command, args, cwd) {
107
- const fullCommand = `git ${command} ${args.join(' ')}`;
108
- try {
109
- const { stdout } = await execAsync(fullCommand, { cwd, timeout: 60000 });
110
- return stdout.trim();
111
- }
112
- catch (error) {
113
- const errorMessage = error instanceof Error ? error.message : String(error);
114
- throw new Error(`Git command failed: ${fullCommand}\n${errorMessage}`);
115
- }
116
- }
117
- async function listGitWorktrees(repoPath) {
118
- const worktrees = [];
119
- try {
120
- const output = await executeGitCommand('worktree', ['list', '--porcelain'], repoPath);
121
- const entries = output.split('\n\n').filter(Boolean);
122
- for (const entry of entries) {
123
- const lines = entry.split('\n');
124
- const info = {
125
- isMain: false,
126
- isLocked: false,
127
- };
128
- for (const line of lines) {
129
- if (line.startsWith('worktree ')) {
130
- info.path = line.substring('worktree '.length).trim();
131
- }
132
- else if (line.startsWith('HEAD ')) {
133
- info.commit = line.substring('HEAD '.length).trim();
134
- }
135
- else if (line.startsWith('branch ')) {
136
- info.branch = line
137
- .substring('branch '.length)
138
- .trim()
139
- .replace('refs/heads/', '');
140
- }
141
- else if (line === 'bare') {
142
- info.isMain = true;
143
- }
144
- else if (line === 'locked') {
145
- info.isLocked = true;
146
- }
147
- }
148
- if (info.path) {
149
- // Check if this is the main worktree
150
- if (info.path === repoPath) {
151
- info.isMain = true;
152
- }
153
- // Get last modified time
154
- try {
155
- const stats = await fs.stat(info.path);
156
- info.lastModified = stats.mtime;
157
- }
158
- catch {
159
- // Path might not exist for orphaned worktrees
160
- }
161
- worktrees.push(info);
162
- }
163
- }
164
- }
165
- catch {
166
- // Return empty list if git command fails
167
- }
168
- return worktrees;
169
- }
170
- async function hasUncommittedChanges(worktreePath) {
171
- try {
172
- const { stdout } = await execAsync('git status --porcelain', {
173
- cwd: worktreePath,
174
- timeout: 10000,
175
- });
176
- return stdout.trim().length > 0;
177
- }
178
- catch {
179
- return false;
180
- }
181
- }
182
- function getStatusColor(status) {
183
- switch (status) {
184
- case 'active':
185
- return chalk_1.default.green;
186
- case 'paused':
187
- return chalk_1.default.yellow;
188
- case 'syncing':
189
- return chalk_1.default.blue;
190
- case 'creating':
191
- return chalk_1.default.cyan;
192
- case 'cleanup':
193
- return chalk_1.default.magenta;
194
- case 'error':
195
- return chalk_1.default.red;
196
- case 'destroyed':
197
- return chalk_1.default.gray;
198
- case 'pending':
199
- return chalk_1.default.white;
200
- default:
201
- return chalk_1.default.white;
202
- }
203
- }
204
- function getStatusIcon(status) {
205
- switch (status) {
206
- case 'active':
207
- return '[ACTIVE]';
208
- case 'paused':
209
- return '[PAUSED]';
210
- case 'syncing':
211
- return '[SYNCING]';
212
- case 'creating':
213
- return '[CREATING]';
214
- case 'cleanup':
215
- return '[CLEANUP]';
216
- case 'error':
217
- return '[ERROR]';
218
- case 'destroyed':
219
- return '[DESTROYED]';
220
- case 'pending':
221
- return '[PENDING]';
222
- default:
223
- return '[UNKNOWN]';
224
- }
225
- }
226
- /**
227
- * Create the worktree command with all subcommands
228
- */
229
- function createWorktreeCommand() {
230
- const command = new commander_1.Command('worktree')
231
- .alias('wt')
232
- .description('Manage git worktrees for multi-agent development')
233
- .addHelpText('after', chalk_1.default.gray(`
234
- Examples:
235
- ${chalk_1.default.green('wundr worktree list')} List all worktrees
236
- ${chalk_1.default.green('wundr wt list --session <id>')} List worktrees for a session
237
- ${chalk_1.default.green('wundr worktree create <taskId>')} Create new worktree for a task
238
- ${chalk_1.default.green('wundr worktree switch <taskId>')} Switch to an existing worktree
239
- ${chalk_1.default.green('wundr worktree cleanup --dry-run')} Preview what would be cleaned up
240
- ${chalk_1.default.green('wundr worktree sync')} Sync all worktrees from remote
241
- ${chalk_1.default.green('wundr worktree sync <taskId>')} Sync specific worktree
242
- `));
243
- // List command (default)
244
- command
245
- .command('list', { isDefault: true })
246
- .description('List all worktrees')
247
- .option('-s, --session <id>', 'Filter by session ID')
248
- .option('--status <status>', 'Filter by status (active, paused, syncing, error, etc.)')
249
- .option('-f, --format <format>', 'Output format (table, json)', 'table')
250
- .action(async (options) => {
251
- await listWorktrees(options);
252
- });
253
- // Create command (ccswitch create)
254
- command
255
- .command('create <taskId>')
256
- .description('Create a new worktree for a task')
257
- .option('-b, --base <branch>', 'Base branch to create from', 'main')
258
- .action(async (taskId, options) => {
259
- await createWorktree(taskId, options);
260
- });
261
- // Switch command (ccswitch switch)
262
- command
263
- .command('switch <taskId>')
264
- .description('Switch to an existing worktree')
265
- .action(async (taskId) => {
266
- await switchWorktree(taskId);
267
- });
268
- // Cleanup command
269
- command
270
- .command('cleanup')
271
- .description('Clean up stale worktrees')
272
- .option('--dry-run', 'Preview what would be cleaned up without making changes')
273
- .option('--force', 'Force cleanup even with uncommitted changes')
274
- .option('--age <days>', 'Clean up worktrees older than specified days', '7')
275
- .action(async (options) => {
276
- await cleanupWorktrees(options);
277
- });
278
- // Sync command
279
- command
280
- .command('sync [taskId]')
281
- .description('Sync worktree(s) from remote')
282
- .option('--all', 'Sync all worktrees')
283
- .action(async (taskId, options) => {
284
- await syncWorktrees(taskId, options);
285
- });
286
- return command;
287
- }
288
- // Command implementations
289
- async function listWorktrees(options) {
290
- const spinner = (0, ora_1.default)('Loading worktrees...').start();
291
- try {
292
- const state = await loadWorktreeState();
293
- let worktrees = state.worktrees;
294
- // Filter by session ID if provided
295
- if (options.session) {
296
- worktrees = worktrees.filter(wt => wt.sessionId === options.session);
297
- }
298
- // Filter by status if provided
299
- if (options.status) {
300
- worktrees = worktrees.filter(wt => wt.status === options.status);
301
- }
302
- spinner.stop();
303
- if (options.format === 'json') {
304
- console.log(JSON.stringify({
305
- timestamp: getTimestamp(),
306
- count: worktrees.length,
307
- worktrees: worktrees,
308
- }, null, 2));
309
- return;
310
- }
311
- console.log(chalk_1.default.cyan('\nWorktree List'));
312
- console.log(chalk_1.default.gray('='.repeat(110)));
313
- if (worktrees.length === 0) {
314
- console.log(chalk_1.default.yellow('\nNo worktrees found.'));
315
- if (options.session) {
316
- console.log(chalk_1.default.gray(`No worktrees for session: ${options.session}`));
317
- }
318
- console.log('');
319
- return;
320
- }
321
- // Table header
322
- console.log(chalk_1.default.cyan(padRight('Task ID', 20) +
323
- padRight('Branch', 25) +
324
- padRight('Status', 12) +
325
- padRight('Created', 12) +
326
- padRight('Path', 40)));
327
- console.log(chalk_1.default.gray('-'.repeat(110)));
328
- // Table rows
329
- for (const wt of worktrees) {
330
- const statusColor = getStatusColor(wt.status);
331
- const createdAge = formatAge(wt.createdAt);
332
- const worktreePath = truncate(wt.worktreePath, 38);
333
- const branchName = truncate(wt.branchName, 23);
334
- console.log(padRight(wt.taskId, 20) +
335
- chalk_1.default.blue(padRight(branchName, 25)) +
336
- statusColor(padRight(getStatusIcon(wt.status), 12)) +
337
- padRight(createdAge, 12) +
338
- chalk_1.default.gray(padRight(worktreePath, 40)));
339
- }
340
- console.log(chalk_1.default.gray('-'.repeat(110)));
341
- console.log(chalk_1.default.gray(`Total: ${worktrees.length} worktree(s)`));
342
- console.log('');
343
- }
344
- catch (error) {
345
- spinner.fail('Failed to load worktrees');
346
- console.error(chalk_1.default.red(error instanceof Error ? error.message : String(error)));
347
- }
348
- }
349
- async function createWorktree(taskId, options) {
350
- const spinner = (0, ora_1.default)(`Creating worktree for task: ${taskId}...`).start();
351
- try {
352
- const state = await loadWorktreeState();
353
- const repoPath = await getGitRepoRoot();
354
- state.repoPath = repoPath;
355
- // Check if worktree already exists
356
- const existing = state.worktrees.find(wt => wt.taskId === taskId);
357
- if (existing) {
358
- spinner.fail(`Worktree already exists for task: ${taskId}`);
359
- console.log(chalk_1.default.yellow(`Use "wundr worktree switch ${taskId}" to switch to it.`));
360
- return;
361
- }
362
- const baseBranch = options.base || 'main';
363
- const branchName = generateBranchName(taskId);
364
- const sessionId = generateSessionId();
365
- const worktreeRoot = path.join(repoPath, '.git-worktrees', 'sessions');
366
- const worktreePath = path.join(worktreeRoot, taskId);
367
- // Ensure worktree root exists
368
- await fs.mkdir(worktreeRoot, { recursive: true });
369
- // Fetch latest from remote
370
- try {
371
- await executeGitCommand('fetch', ['origin', baseBranch], repoPath);
372
- }
373
- catch {
374
- // Continue even if fetch fails (might be offline)
375
- }
376
- // Create the worktree with a new branch
377
- await executeGitCommand('worktree', ['add', '-b', branchName, worktreePath, `origin/${baseBranch}`], repoPath);
378
- // Save to state
379
- const entry = {
380
- taskId,
381
- branchName,
382
- worktreePath,
383
- sessionId,
384
- status: 'active',
385
- createdAt: getTimestamp(),
386
- lastAccessedAt: getTimestamp(),
387
- };
388
- state.worktrees.push(entry);
389
- await saveWorktreeState(state);
390
- spinner.succeed(`Worktree created for task: ${taskId}`);
391
- console.log('');
392
- console.log(chalk_1.default.white(' Task ID: ') + chalk_1.default.green(taskId));
393
- console.log(chalk_1.default.white(' Branch: ') + chalk_1.default.blue(branchName));
394
- console.log(chalk_1.default.white(' Path: ') + chalk_1.default.gray(worktreePath));
395
- console.log('');
396
- console.log(chalk_1.default.gray(`Use "wundr worktree switch ${taskId}" to start working.`));
397
- console.log('');
398
- }
399
- catch (error) {
400
- spinner.fail('Failed to create worktree');
401
- console.error(chalk_1.default.red(error instanceof Error ? error.message : String(error)));
402
- }
403
- }
404
- async function switchWorktree(taskId) {
405
- const spinner = (0, ora_1.default)(`Switching to worktree: ${taskId}...`).start();
406
- try {
407
- const state = await loadWorktreeState();
408
- // Find the worktree
409
- const worktree = state.worktrees.find(wt => wt.taskId === taskId);
410
- if (!worktree) {
411
- spinner.fail(`Worktree not found: ${taskId}`);
412
- console.log(chalk_1.default.yellow(`Use "wundr worktree create ${taskId}" to create it.`));
413
- return;
414
- }
415
- // Check if worktree path exists
416
- try {
417
- await fs.access(worktree.worktreePath);
418
- }
419
- catch {
420
- spinner.fail(`Worktree directory not found: ${worktree.worktreePath}`);
421
- console.log(chalk_1.default.yellow('The worktree may have been deleted. Consider running cleanup.'));
422
- return;
423
- }
424
- // Update status
425
- worktree.status = 'active';
426
- worktree.lastAccessedAt = getTimestamp();
427
- await saveWorktreeState(state);
428
- spinner.succeed(`Switched to worktree: ${taskId}`);
429
- console.log('');
430
- console.log(chalk_1.default.white(' Task ID: ') + chalk_1.default.green(taskId));
431
- console.log(chalk_1.default.white(' Branch: ') + chalk_1.default.blue(worktree.branchName));
432
- console.log(chalk_1.default.white(' Path: ') + chalk_1.default.gray(worktree.worktreePath));
433
- console.log('');
434
- console.log(chalk_1.default.cyan('To navigate to this worktree, run:'));
435
- console.log(chalk_1.default.white(` cd ${worktree.worktreePath}`));
436
- console.log('');
437
- }
438
- catch (error) {
439
- spinner.fail('Failed to switch worktree');
440
- console.error(chalk_1.default.red(error instanceof Error ? error.message : String(error)));
441
- }
442
- }
443
- async function cleanupWorktrees(options) {
444
- const maxAgeDays = parseInt(options.age || '7', 10);
445
- const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1000;
446
- const spinner = (0, ora_1.default)(options.dryRun ? 'Analyzing worktrees...' : 'Cleaning up worktrees...').start();
447
- try {
448
- const state = await loadWorktreeState();
449
- const repoPath = state.repoPath || (await getGitRepoRoot());
450
- const now = Date.now();
451
- // Get actual git worktrees
452
- const gitWorktrees = await listGitWorktrees(repoPath);
453
- // Categorize worktrees for cleanup
454
- const toRemove = [];
455
- const toSkip = [];
456
- // Check state entries
457
- for (const entry of state.worktrees) {
458
- const createdAt = new Date(entry.createdAt).getTime();
459
- const age = now - createdAt;
460
- const gitInfo = gitWorktrees.find(g => g.path === entry.worktreePath);
461
- // Check if worktree still exists
462
- try {
463
- await fs.access(entry.worktreePath);
464
- }
465
- catch {
466
- toRemove.push({ entry, reason: 'Directory no longer exists' });
467
- continue;
468
- }
469
- // Check if too old
470
- if (age > maxAgeMs) {
471
- // Check for uncommitted changes
472
- const hasChanges = await hasUncommittedChanges(entry.worktreePath);
473
- if (hasChanges && !options.force) {
474
- toSkip.push({ entry, gitInfo, reason: 'Has uncommitted changes' });
475
- }
476
- else if (gitInfo?.isLocked && !options.force) {
477
- toSkip.push({ entry, gitInfo, reason: 'Worktree is locked' });
478
- }
479
- else {
480
- toRemove.push({
481
- entry,
482
- gitInfo,
483
- reason: `Older than ${maxAgeDays} days`,
484
- });
485
- }
486
- }
487
- // Check error status
488
- if (entry.status === 'error' && !options.force) {
489
- toRemove.push({ entry, gitInfo, reason: 'In error state' });
490
- }
491
- }
492
- spinner.stop();
493
- if (options.dryRun) {
494
- // Show preview
495
- console.log(chalk_1.default.cyan('\nCleanup Preview (Dry Run)'));
496
- console.log(chalk_1.default.gray('='.repeat(80)));
497
- if (toRemove.length === 0) {
498
- console.log(chalk_1.default.green('\nNo worktrees need to be cleaned up.'));
499
- console.log('');
500
- return;
501
- }
502
- console.log(chalk_1.default.yellow(`\nWorktrees to be removed: ${toRemove.length}`));
503
- console.log(chalk_1.default.gray('-'.repeat(80)));
504
- for (const { entry, reason } of toRemove) {
505
- if (entry) {
506
- console.log(chalk_1.default.white(' Task ID: ') + chalk_1.default.green(entry.taskId));
507
- console.log(chalk_1.default.white(' Path: ') + chalk_1.default.gray(entry.worktreePath));
508
- console.log(chalk_1.default.white(' Branch: ') + chalk_1.default.blue(entry.branchName));
509
- console.log(chalk_1.default.white(' Reason: ') + chalk_1.default.yellow(reason));
510
- console.log('');
511
- }
512
- }
513
- if (toSkip.length > 0) {
514
- console.log(chalk_1.default.yellow(`\nWorktrees to be skipped: ${toSkip.length}`));
515
- console.log(chalk_1.default.gray('-'.repeat(80)));
516
- for (const { entry, reason } of toSkip) {
517
- if (entry) {
518
- console.log(chalk_1.default.white(' Task ID: ') + chalk_1.default.green(entry.taskId));
519
- console.log(chalk_1.default.white(' Path: ') + chalk_1.default.gray(entry.worktreePath));
520
- console.log(chalk_1.default.white(' Reason: ') + chalk_1.default.gray(reason));
521
- console.log('');
522
- }
523
- }
524
- }
525
- console.log(chalk_1.default.gray('-'.repeat(80)));
526
- console.log(chalk_1.default.gray('Run without --dry-run to perform actual cleanup.'));
527
- console.log('');
528
- }
529
- else {
530
- // Perform actual cleanup
531
- const result = {
532
- success: true,
533
- removedCount: 0,
534
- removedPaths: [],
535
- skippedPaths: [],
536
- skipReasons: new Map(),
537
- errors: [],
538
- };
539
- for (const { entry, reason } of toRemove) {
540
- if (!entry) {
541
- continue;
542
- }
543
- try {
544
- // Remove the git worktree
545
- const forceFlag = options.force ? '--force' : '';
546
- await executeGitCommand('worktree', ['remove', forceFlag, entry.worktreePath].filter(Boolean), repoPath);
547
- result.removedCount++;
548
- result.removedPaths.push(entry.worktreePath);
549
- }
550
- catch (error) {
551
- // Try manual removal if git worktree remove fails
552
- if (options.force) {
553
- try {
554
- await fs.rm(entry.worktreePath, { recursive: true, force: true });
555
- await executeGitCommand('worktree', ['prune'], repoPath);
556
- result.removedCount++;
557
- result.removedPaths.push(entry.worktreePath);
558
- }
559
- catch (rmError) {
560
- result.errors.push({
561
- path: entry.worktreePath,
562
- message: rmError instanceof Error ? rmError.message : String(rmError),
563
- });
564
- }
565
- }
566
- else {
567
- result.errors.push({
568
- path: entry.worktreePath,
569
- message: error instanceof Error ? error.message : String(error),
570
- });
571
- }
572
- }
573
- }
574
- for (const { entry, reason } of toSkip) {
575
- if (entry) {
576
- result.skippedPaths.push(entry.worktreePath);
577
- result.skipReasons.set(entry.worktreePath, reason);
578
- }
579
- }
580
- // Update state file to remove cleaned worktrees
581
- const removedPaths = new Set(result.removedPaths);
582
- state.worktrees = state.worktrees.filter(wt => !removedPaths.has(wt.worktreePath));
583
- await saveWorktreeState(state);
584
- // Show results
585
- console.log(chalk_1.default.cyan('\nCleanup Results'));
586
- console.log(chalk_1.default.gray('='.repeat(80)));
587
- if (result.removedCount === 0 && result.errors.length === 0) {
588
- console.log(chalk_1.default.green('\nNo worktrees needed to be cleaned up.'));
589
- console.log('');
590
- return;
591
- }
592
- if (result.removedCount > 0) {
593
- console.log(chalk_1.default.green(`\nRemoved ${result.removedCount} worktree(s):`));
594
- for (const removedPath of result.removedPaths) {
595
- console.log(chalk_1.default.gray(` - ${removedPath}`));
596
- }
597
- }
598
- if (result.skippedPaths.length > 0) {
599
- console.log(chalk_1.default.yellow(`\nSkipped ${result.skippedPaths.length} worktree(s):`));
600
- for (const skippedPath of result.skippedPaths) {
601
- const reason = result.skipReasons.get(skippedPath) || 'unknown';
602
- console.log(chalk_1.default.gray(` - ${skippedPath}: ${reason}`));
603
- }
604
- }
605
- if (result.errors.length > 0) {
606
- console.log(chalk_1.default.red(`\nErrors (${result.errors.length}):`));
607
- for (const error of result.errors) {
608
- console.log(chalk_1.default.red(` - ${error.path}: ${error.message}`));
609
- }
610
- }
611
- console.log('');
612
- }
613
- }
614
- catch (error) {
615
- spinner.fail('Failed to cleanup worktrees');
616
- console.error(chalk_1.default.red(error instanceof Error ? error.message : String(error)));
617
- }
618
- }
619
- async function syncWorktrees(taskId, options) {
620
- const spinner = (0, ora_1.default)(taskId ? `Syncing worktree: ${taskId}...` : 'Syncing worktrees...').start();
621
- try {
622
- const state = await loadWorktreeState();
623
- const repoPath = state.repoPath || (await getGitRepoRoot());
624
- const syncSingleWorktree = async (worktree) => {
625
- const timestamp = new Date();
626
- let stashedChanges = false;
627
- try {
628
- // Check for uncommitted changes
629
- const hasChanges = await hasUncommittedChanges(worktree.worktreePath);
630
- if (hasChanges) {
631
- // Stash changes
632
- await executeGitCommand('stash', ['push', '-m', 'auto-stash before sync'], worktree.worktreePath);
633
- stashedChanges = true;
634
- }
635
- // Fetch from remote
636
- await executeGitCommand('fetch', ['origin'], worktree.worktreePath);
637
- // Get current branch
638
- const branchName = await executeGitCommand('rev-parse', ['--abbrev-ref', 'HEAD'], worktree.worktreePath);
639
- // Try fast-forward pull
640
- try {
641
- await executeGitCommand('pull', ['--ff-only', 'origin', branchName], worktree.worktreePath);
642
- }
643
- catch {
644
- // Fast-forward not possible, try rebase
645
- await executeGitCommand('rebase', [`origin/${branchName}`], worktree.worktreePath);
646
- }
647
- // Count commits updated (approximate)
648
- let commitsUpdated = 0;
649
- try {
650
- const { stdout } = await execAsync(`git rev-list --count origin/${branchName}..HEAD`, { cwd: worktree.worktreePath, timeout: 10000 });
651
- commitsUpdated = parseInt(stdout.trim(), 10) || 0;
652
- }
653
- catch {
654
- // Ignore count errors
655
- }
656
- // Restore stashed changes
657
- if (stashedChanges) {
658
- try {
659
- await executeGitCommand('stash', ['pop'], worktree.worktreePath);
660
- }
661
- catch {
662
- // Stash pop might have conflicts
663
- }
664
- }
665
- return {
666
- success: true,
667
- message: 'Successfully synced',
668
- worktreePath: worktree.worktreePath,
669
- branchName,
670
- stashedChanges,
671
- commitsUpdated,
672
- timestamp,
673
- };
674
- }
675
- catch (error) {
676
- // Try to restore stash on failure
677
- if (stashedChanges) {
678
- try {
679
- await executeGitCommand('stash', ['pop'], worktree.worktreePath);
680
- }
681
- catch {
682
- // Ignore
683
- }
684
- }
685
- return {
686
- success: false,
687
- message: error instanceof Error ? error.message : String(error),
688
- worktreePath: worktree.worktreePath,
689
- stashedChanges,
690
- timestamp,
691
- error: error instanceof Error ? error : new Error(String(error)),
692
- };
693
- }
694
- };
695
- if (taskId) {
696
- // Sync specific worktree
697
- const worktree = state.worktrees.find(wt => wt.taskId === taskId);
698
- if (!worktree) {
699
- spinner.fail(`Worktree not found: ${taskId}`);
700
- return;
701
- }
702
- // Update status to syncing
703
- worktree.status = 'syncing';
704
- await saveWorktreeState(state);
705
- const result = await syncSingleWorktree(worktree);
706
- // Update status based on result
707
- worktree.status = result.success ? 'active' : 'error';
708
- worktree.lastAccessedAt = getTimestamp();
709
- await saveWorktreeState(state);
710
- spinner.stop();
711
- if (result.success) {
712
- console.log(chalk_1.default.green(`\nSuccessfully synced worktree: ${taskId}`));
713
- console.log(chalk_1.default.white(' Branch: ') +
714
- chalk_1.default.blue(result.branchName || 'unknown'));
715
- console.log(chalk_1.default.white(' Commits: ') +
716
- chalk_1.default.cyan(`${result.commitsUpdated || 0} updated`));
717
- if (result.stashedChanges) {
718
- console.log(chalk_1.default.yellow(' Note: Local changes were stashed and restored.'));
719
- }
720
- }
721
- else {
722
- console.log(chalk_1.default.red(`\nFailed to sync worktree: ${taskId}`));
723
- console.log(chalk_1.default.red(` Error: ${result.message}`));
724
- }
725
- console.log('');
726
- }
727
- else if (options.all || !taskId) {
728
- // Sync all active worktrees
729
- const activeWorktrees = state.worktrees.filter(wt => wt.status === 'active' || wt.status === 'paused');
730
- if (activeWorktrees.length === 0) {
731
- spinner.stop();
732
- console.log(chalk_1.default.yellow('\nNo active worktrees to sync.'));
733
- console.log('');
734
- return;
735
- }
736
- spinner.text = `Syncing ${activeWorktrees.length} worktree(s)...`;
737
- const results = [];
738
- for (const wt of activeWorktrees) {
739
- wt.status = 'syncing';
740
- await saveWorktreeState(state);
741
- const result = await syncSingleWorktree(wt);
742
- wt.status = result.success ? 'active' : 'error';
743
- wt.lastAccessedAt = getTimestamp();
744
- results.push({ taskId: wt.taskId, result });
745
- }
746
- await saveWorktreeState(state);
747
- spinner.stop();
748
- console.log(chalk_1.default.cyan('\nSync Results'));
749
- console.log(chalk_1.default.gray('='.repeat(80)));
750
- const successful = results.filter(r => r.result.success);
751
- const failed = results.filter(r => !r.result.success);
752
- if (successful.length > 0) {
753
- console.log(chalk_1.default.green(`\nSuccessfully synced: ${successful.length}`));
754
- for (const { taskId: tid, result } of successful) {
755
- console.log(chalk_1.default.gray(` - ${tid}: ${result.commitsUpdated || 0} commit(s) updated`));
756
- }
757
- }
758
- if (failed.length > 0) {
759
- console.log(chalk_1.default.red(`\nFailed to sync: ${failed.length}`));
760
- for (const { taskId: tid, result } of failed) {
761
- console.log(chalk_1.default.red(` - ${tid}: ${result.message}`));
762
- }
763
- }
764
- console.log('');
765
- }
766
- }
767
- catch (error) {
768
- spinner.fail('Failed to sync worktrees');
769
- console.error(chalk_1.default.red(error instanceof Error ? error.message : String(error)));
770
- }
771
- }
772
- // Export for registration
773
- exports.default = createWorktreeCommand;
774
- //# sourceMappingURL=worktree.js.map