s9n-devops-agent 1.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.
@@ -0,0 +1,768 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * ============================================================================
5
+ * WORKTREE MANAGER FOR MULTI-AGENT DEVELOPMENT
6
+ * ============================================================================
7
+ *
8
+ * This module manages Git worktrees to enable multiple AI agents to work
9
+ * on the same codebase simultaneously without conflicts.
10
+ *
11
+ * Key Features:
12
+ * - Creates isolated worktrees for each AI agent
13
+ * - Manages agent-specific branches
14
+ * - Coordinates merges between agent work
15
+ * - Prevents conflicts through branch isolation
16
+ *
17
+ * Usage:
18
+ * node worktree-manager.js create --agent claude --task feature-x
19
+ * node worktree-manager.js list
20
+ * node worktree-manager.js merge --agent claude
21
+ * node worktree-manager.js cleanup --agent claude
22
+ * ============================================================================
23
+ */
24
+
25
+ import fs from 'fs';
26
+ import path from 'path';
27
+ import { execSync, spawn } from 'child_process';
28
+ import { fileURLToPath } from 'url';
29
+ import { dirname } from 'path';
30
+ import readline from 'readline';
31
+
32
+ const __filename = fileURLToPath(import.meta.url);
33
+ const __dirname = dirname(__filename);
34
+
35
+ // ============================================================================
36
+ // CONFIGURATION
37
+ // ============================================================================
38
+
39
+ const CONFIG = {
40
+ // Base directory for worktrees (relative to main repo)
41
+ worktreesDir: '.worktrees',
42
+
43
+ // Agent naming patterns
44
+ agentPrefix: 'agent',
45
+
46
+ // Branch naming patterns
47
+ branchPatterns: {
48
+ agent: 'agent/${agentName}/${taskName}',
49
+ daily: 'agent/${agentName}/daily/${date}',
50
+ feature: 'agent/${agentName}/feature/${feature}'
51
+ },
52
+
53
+ // Supported AI agents
54
+ knownAgents: ['claude', 'copilot', 'cursor', 'aider', 'custom'],
55
+
56
+ // Colors for console output
57
+ colors: {
58
+ reset: '\x1b[0m',
59
+ bright: '\x1b[1m',
60
+ green: '\x1b[32m',
61
+ yellow: '\x1b[33m',
62
+ blue: '\x1b[36m',
63
+ red: '\x1b[31m',
64
+ magenta: '\x1b[35m',
65
+ }
66
+ };
67
+
68
+ // ============================================================================
69
+ // UTILITY FUNCTIONS
70
+ // ============================================================================
71
+
72
+ const log = {
73
+ info: (msg) => console.log(`${CONFIG.colors.blue}ℹ${CONFIG.colors.reset} ${msg}`),
74
+ success: (msg) => console.log(`${CONFIG.colors.green}✓${CONFIG.colors.reset} ${msg}`),
75
+ warn: (msg) => console.log(`${CONFIG.colors.yellow}⚠${CONFIG.colors.reset} ${msg}`),
76
+ error: (msg) => console.log(`${CONFIG.colors.red}✗${CONFIG.colors.reset} ${msg}`),
77
+ agent: (agent, msg) => console.log(`${CONFIG.colors.magenta}[${agent}]${CONFIG.colors.reset} ${msg}`),
78
+ header: (msg) => {
79
+ console.log(`\n${CONFIG.colors.bright}${CONFIG.colors.blue}${'='.repeat(60)}${CONFIG.colors.reset}`);
80
+ console.log(`${CONFIG.colors.bright}${msg}${CONFIG.colors.reset}`);
81
+ console.log(`${CONFIG.colors.bright}${CONFIG.colors.blue}${'='.repeat(60)}${CONFIG.colors.reset}\n`);
82
+ }
83
+ };
84
+
85
+ /**
86
+ * Execute a shell command and return the output
87
+ */
88
+ function execCommand(command, options = {}) {
89
+ try {
90
+ const result = execSync(command, {
91
+ encoding: 'utf8',
92
+ stdio: options.silent ? 'pipe' : 'inherit',
93
+ ...options
94
+ });
95
+ return result ? result.trim() : '';
96
+ } catch (error) {
97
+ if (!options.ignoreError) {
98
+ throw error;
99
+ }
100
+ return null;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Check if we're in a git repository
106
+ */
107
+ function checkGitRepo() {
108
+ try {
109
+ execSync('git rev-parse --git-dir', { stdio: 'pipe' });
110
+ return true;
111
+ } catch {
112
+ return false;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Get the root directory of the git repository
118
+ */
119
+ function getRepoRoot() {
120
+ return execCommand('git rev-parse --show-toplevel', { silent: true });
121
+ }
122
+
123
+ /**
124
+ * Get current branch name
125
+ */
126
+ function getCurrentBranch() {
127
+ return execCommand('git branch --show-current', { silent: true });
128
+ }
129
+
130
+ /**
131
+ * Create a directory if it doesn't exist
132
+ */
133
+ function ensureDir(dirPath) {
134
+ if (!fs.existsSync(dirPath)) {
135
+ fs.mkdirSync(dirPath, { recursive: true });
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Format date as YYYY-MM-DD
141
+ */
142
+ function getDateString() {
143
+ const now = new Date();
144
+ return now.toISOString().split('T')[0];
145
+ }
146
+
147
+ /**
148
+ * Generate branch name based on pattern
149
+ */
150
+ function generateBranchName(pattern, vars) {
151
+ let branch = pattern;
152
+ for (const [key, value] of Object.entries(vars)) {
153
+ branch = branch.replace(`\${${key}}`, value);
154
+ }
155
+ return branch;
156
+ }
157
+
158
+ // ============================================================================
159
+ // WORKTREE MANAGEMENT CLASS
160
+ // ============================================================================
161
+
162
+ class WorktreeManager {
163
+ constructor() {
164
+ this.repoRoot = getRepoRoot();
165
+ this.worktreesPath = path.join(this.repoRoot, CONFIG.worktreesDir);
166
+ this.configFile = path.join(this.worktreesPath, 'agents.json');
167
+ this.loadConfig();
168
+ }
169
+
170
+ /**
171
+ * Load or initialize agent configuration
172
+ */
173
+ loadConfig() {
174
+ ensureDir(this.worktreesPath);
175
+
176
+ if (fs.existsSync(this.configFile)) {
177
+ const data = fs.readFileSync(this.configFile, 'utf8');
178
+ this.agents = JSON.parse(data);
179
+ } else {
180
+ this.agents = {};
181
+ this.saveConfig();
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Save agent configuration
187
+ */
188
+ saveConfig() {
189
+ fs.writeFileSync(this.configFile, JSON.stringify(this.agents, null, 2));
190
+ }
191
+
192
+ /**
193
+ * Create a new worktree for an AI agent
194
+ */
195
+ createWorktree(agentName, taskName, options = {}) {
196
+ log.header(`Creating Worktree for Agent: ${agentName}`);
197
+
198
+ // Validate agent name
199
+ if (!agentName || agentName.length < 2) {
200
+ throw new Error('Agent name must be at least 2 characters');
201
+ }
202
+
203
+ // Generate paths and names
204
+ const worktreeName = `${agentName}-${taskName || getDateString()}`;
205
+ const worktreePath = path.join(this.worktreesPath, worktreeName);
206
+ const branchName = generateBranchName(
207
+ options.branchPattern || CONFIG.branchPatterns.agent,
208
+ {
209
+ agentName,
210
+ taskName: taskName || 'main',
211
+ date: getDateString(),
212
+ feature: options.feature || taskName
213
+ }
214
+ );
215
+
216
+ // Check if worktree already exists
217
+ if (fs.existsSync(worktreePath)) {
218
+ log.warn(`Worktree already exists at: ${worktreePath}`);
219
+ return { path: worktreePath, branch: branchName, exists: true };
220
+ }
221
+
222
+ // Create the worktree
223
+ log.info(`Creating worktree at: ${worktreePath}`);
224
+ log.info(`Branch: ${branchName}`);
225
+
226
+ try {
227
+ // Create new branch and worktree
228
+ execCommand(`git worktree add -b ${branchName} "${worktreePath}" HEAD`, { silent: false });
229
+
230
+ // Update agent configuration
231
+ this.agents[agentName] = {
232
+ name: agentName,
233
+ worktrees: [
234
+ ...(this.agents[agentName]?.worktrees || []),
235
+ {
236
+ name: worktreeName,
237
+ path: worktreePath,
238
+ branch: branchName,
239
+ task: taskName,
240
+ created: new Date().toISOString(),
241
+ status: 'active'
242
+ }
243
+ ]
244
+ };
245
+
246
+ this.saveConfig();
247
+
248
+ // Setup agent-specific configuration
249
+ this.setupAgentConfig(agentName, worktreePath, options);
250
+
251
+ log.success(`Worktree created successfully!`);
252
+ log.info(`Agent ${agentName} can now work in: ${worktreePath}`);
253
+
254
+ return { path: worktreePath, branch: branchName, exists: false };
255
+
256
+ } catch (error) {
257
+ log.error(`Failed to create worktree: ${error.message}`);
258
+ throw error;
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Setup agent-specific configuration in the worktree
264
+ */
265
+ setupAgentConfig(agentName, worktreePath, options) {
266
+ log.info('Setting up agent-specific configuration...');
267
+
268
+ // Create .agent-config file
269
+ const agentConfig = {
270
+ agent: agentName,
271
+ worktree: path.basename(worktreePath),
272
+ created: new Date().toISOString(),
273
+ task: options.task || 'general',
274
+ autoCommit: {
275
+ enabled: true,
276
+ prefix: `agent_${agentName}_`,
277
+ messagePrefix: `[${agentName.toUpperCase()}]`,
278
+ pushOnCommit: options.autoPush !== false
279
+ }
280
+ };
281
+
282
+ fs.writeFileSync(
283
+ path.join(worktreePath, '.agent-config'),
284
+ JSON.stringify(agentConfig, null, 2)
285
+ );
286
+
287
+ // Create agent-specific commit message file
288
+ fs.writeFileSync(
289
+ path.join(worktreePath, `.${agentName}-commit-msg`),
290
+ ''
291
+ );
292
+
293
+ // Create agent-specific VS Code settings
294
+ const vscodeDir = path.join(worktreePath, '.vscode');
295
+ ensureDir(vscodeDir);
296
+
297
+ const settings = {
298
+ 'window.title': `${agentName.toUpperCase()} - ${options.task || 'Workspace'}`,
299
+ 'terminal.integrated.env.osx': {
300
+ 'AGENT_NAME': agentName,
301
+ 'AGENT_WORKTREE': path.basename(worktreePath),
302
+ 'AC_BRANCH_PREFIX': `agent_${agentName}_`,
303
+ 'AC_MSG_FILE': `.${agentName}-commit-msg`
304
+ }
305
+ };
306
+
307
+ fs.writeFileSync(
308
+ path.join(vscodeDir, 'settings.json'),
309
+ JSON.stringify(settings, null, 2)
310
+ );
311
+
312
+ log.success('Agent configuration created');
313
+ }
314
+
315
+ /**
316
+ * List all worktrees and their agents
317
+ */
318
+ listWorktrees() {
319
+ log.header('Active Worktrees');
320
+
321
+ // Get git worktree list
322
+ const worktrees = execCommand('git worktree list --porcelain', { silent: true })
323
+ .split('\n\n')
324
+ .filter(w => w)
325
+ .map(w => {
326
+ const lines = w.split('\n');
327
+ const worktreePath = lines[0].replace('worktree ', '');
328
+ const branch = lines[2]?.replace('branch refs/heads/', '') || 'detached';
329
+ return { path: worktreePath, branch };
330
+ });
331
+
332
+ // Display main repository
333
+ const mainWorktree = worktrees.find(w => w.path === this.repoRoot);
334
+ if (mainWorktree) {
335
+ console.log(`${CONFIG.colors.bright}Main Repository:${CONFIG.colors.reset}`);
336
+ console.log(` Path: ${mainWorktree.path}`);
337
+ console.log(` Branch: ${mainWorktree.branch}`);
338
+ console.log('');
339
+ }
340
+
341
+ // Display agent worktrees
342
+ console.log(`${CONFIG.colors.bright}Agent Worktrees:${CONFIG.colors.reset}`);
343
+
344
+ let agentWorktreeCount = 0;
345
+ for (const [agentName, agentData] of Object.entries(this.agents)) {
346
+ const activeWorktrees = (agentData.worktrees || []).filter(w => w.status === 'active');
347
+
348
+ if (activeWorktrees.length > 0) {
349
+ console.log(`\n${CONFIG.colors.magenta}[${agentName}]${CONFIG.colors.reset}`);
350
+
351
+ for (const wt of activeWorktrees) {
352
+ const exists = fs.existsSync(wt.path);
353
+ const status = exists ? CONFIG.colors.green + '✓' : CONFIG.colors.red + '✗';
354
+
355
+ console.log(` ${status}${CONFIG.colors.reset} ${wt.name}`);
356
+ console.log(` Branch: ${wt.branch}`);
357
+ console.log(` Task: ${wt.task || 'N/A'}`);
358
+ console.log(` Created: ${new Date(wt.created).toLocaleDateString()}`);
359
+
360
+ if (!exists) {
361
+ console.log(` ${CONFIG.colors.yellow}(Worktree missing - may need cleanup)${CONFIG.colors.reset}`);
362
+ }
363
+
364
+ agentWorktreeCount++;
365
+ }
366
+ }
367
+ }
368
+
369
+ if (agentWorktreeCount === 0) {
370
+ console.log(' No agent worktrees found');
371
+ }
372
+
373
+ console.log(`\nTotal worktrees: ${worktrees.length}`);
374
+ }
375
+
376
+ /**
377
+ * Merge agent's work back to main branch
378
+ */
379
+ async mergeAgentWork(agentName, options = {}) {
380
+ log.header(`Merging ${agentName}'s Work`);
381
+
382
+ const agentData = this.agents[agentName];
383
+ if (!agentData) {
384
+ log.error(`No worktrees found for agent: ${agentName}`);
385
+ return;
386
+ }
387
+
388
+ const activeWorktrees = (agentData.worktrees || []).filter(w => w.status === 'active');
389
+ if (activeWorktrees.length === 0) {
390
+ log.warn(`No active worktrees for agent: ${agentName}`);
391
+ return;
392
+ }
393
+
394
+ // Let user select which worktree to merge
395
+ console.log('Select worktree to merge:');
396
+ activeWorktrees.forEach((wt, idx) => {
397
+ console.log(` ${idx + 1}. ${wt.name} (${wt.branch})`);
398
+ });
399
+
400
+ const selection = await this.promptUser('Enter number: ');
401
+ const selectedIdx = parseInt(selection) - 1;
402
+
403
+ if (selectedIdx < 0 || selectedIdx >= activeWorktrees.length) {
404
+ log.error('Invalid selection');
405
+ return;
406
+ }
407
+
408
+ const worktree = activeWorktrees[selectedIdx];
409
+ const targetBranch = options.target || 'main';
410
+
411
+ log.info(`Merging ${worktree.branch} into ${targetBranch}...`);
412
+
413
+ try {
414
+ // Save current branch
415
+ const currentBranch = getCurrentBranch();
416
+
417
+ // Checkout target branch
418
+ execCommand(`git checkout ${targetBranch}`);
419
+
420
+ // Merge agent's branch
421
+ const mergeMessage = `Merge ${agentName}'s work: ${worktree.task || 'updates'}`;
422
+ execCommand(`git merge ${worktree.branch} -m "${mergeMessage}"`);
423
+
424
+ log.success(`Successfully merged ${worktree.branch} into ${targetBranch}`);
425
+
426
+ // Ask if should delete the branch
427
+ const shouldDelete = await this.promptUser('Delete merged branch? (y/n): ');
428
+ if (shouldDelete.toLowerCase() === 'y') {
429
+ execCommand(`git branch -d ${worktree.branch}`);
430
+ worktree.status = 'merged';
431
+ this.saveConfig();
432
+ log.success('Branch deleted');
433
+ }
434
+
435
+ // Return to original branch
436
+ if (currentBranch) {
437
+ execCommand(`git checkout ${currentBranch}`);
438
+ }
439
+
440
+ } catch (error) {
441
+ log.error(`Merge failed: ${error.message}`);
442
+ log.info('You may need to resolve conflicts manually');
443
+ }
444
+ }
445
+
446
+ /**
447
+ * Clean up worktrees for an agent
448
+ */
449
+ async cleanupWorktrees(agentName, options = {}) {
450
+ log.header(`Cleaning Up ${agentName}'s Worktrees`);
451
+
452
+ const agentData = this.agents[agentName];
453
+ if (!agentData) {
454
+ log.warn(`No worktrees found for agent: ${agentName}`);
455
+ return;
456
+ }
457
+
458
+ const worktrees = agentData.worktrees || [];
459
+ let cleaned = 0;
460
+
461
+ for (const wt of worktrees) {
462
+ const exists = fs.existsSync(wt.path);
463
+
464
+ if (!exists && !options.force) {
465
+ log.info(`Worktree already removed: ${wt.name}`);
466
+ wt.status = 'removed';
467
+ cleaned++;
468
+ continue;
469
+ }
470
+
471
+ if (options.all || wt.status === 'merged' || !exists) {
472
+ try {
473
+ // Remove worktree
474
+ if (exists) {
475
+ log.info(`Removing worktree: ${wt.name}`);
476
+ execCommand(`git worktree remove "${wt.path}" --force`);
477
+ }
478
+
479
+ // Delete branch if requested
480
+ if (options.deleteBranches) {
481
+ execCommand(`git branch -D ${wt.branch}`, { ignoreError: true });
482
+ log.info(`Deleted branch: ${wt.branch}`);
483
+ }
484
+
485
+ wt.status = 'removed';
486
+ cleaned++;
487
+
488
+ } catch (error) {
489
+ log.error(`Failed to remove ${wt.name}: ${error.message}`);
490
+ }
491
+ }
492
+ }
493
+
494
+ // Clean up agent data if all worktrees removed
495
+ if (worktrees.every(wt => wt.status === 'removed')) {
496
+ delete this.agents[agentName];
497
+ log.info(`Removed agent configuration for: ${agentName}`);
498
+ }
499
+
500
+ this.saveConfig();
501
+ log.success(`Cleaned up ${cleaned} worktree(s)`);
502
+ }
503
+
504
+ /**
505
+ * Run cs-devops-agent worker in a specific worktree
506
+ */
507
+ runCS_DevOpsAgent(agentName, worktreeName) {
508
+ const agentData = this.agents[agentName];
509
+ if (!agentData) {
510
+ log.error(`Agent not found: ${agentName}`);
511
+ return;
512
+ }
513
+
514
+ const worktree = agentData.worktrees.find(w => w.name === worktreeName);
515
+ if (!worktree) {
516
+ log.error(`Worktree not found: ${worktreeName}`);
517
+ return;
518
+ }
519
+
520
+ log.header(`Starting DevOps Agent for ${agentName}`);
521
+ log.info(`Worktree: ${worktree.path}`);
522
+ log.info(`Branch: ${worktree.branch}`);
523
+
524
+ // Set up environment variables
525
+ const env = {
526
+ ...process.env,
527
+ AGENT_NAME: agentName,
528
+ AGENT_WORKTREE: worktreeName,
529
+ AC_BRANCH_PREFIX: `agent_${agentName}_`,
530
+ AC_MSG_FILE: `.${agentName}-commit-msg`,
531
+ AC_WORKING_DIR: worktree.path
532
+ };
533
+
534
+ // Start cs-devops-agent worker
535
+ const autoCommitPath = path.join(this.repoRoot, 'cs-devops-agent-worker.js');
536
+ const child = spawn('node', [autoCommitPath], {
537
+ cwd: worktree.path,
538
+ env,
539
+ stdio: 'inherit'
540
+ });
541
+
542
+ child.on('exit', (code) => {
543
+ log.info(`Auto-commit worker exited with code: ${code}`);
544
+ });
545
+
546
+ // Handle graceful shutdown
547
+ process.on('SIGINT', () => {
548
+ log.info('Stopping cs-devops-agent worker...');
549
+ child.kill('SIGINT');
550
+ process.exit(0);
551
+ });
552
+ }
553
+
554
+ /**
555
+ * Prompt user for input
556
+ */
557
+ promptUser(question) {
558
+ const rl = readline.createInterface({
559
+ input: process.stdin,
560
+ output: process.stdout
561
+ });
562
+
563
+ return new Promise((resolve) => {
564
+ rl.question(question, (answer) => {
565
+ rl.close();
566
+ resolve(answer);
567
+ });
568
+ });
569
+ }
570
+
571
+ /**
572
+ * Create parallel workspace for multiple agents
573
+ */
574
+ async createParallelWorkspace(agents, task) {
575
+ log.header('Creating Parallel Workspace for Multiple Agents');
576
+
577
+ const workspace = {
578
+ id: `parallel-${Date.now()}`,
579
+ task,
580
+ agents: [],
581
+ created: new Date().toISOString()
582
+ };
583
+
584
+ for (const agent of agents) {
585
+ log.info(`Setting up workspace for: ${agent}`);
586
+
587
+ const result = this.createWorktree(agent, task, {
588
+ feature: task,
589
+ autoPush: false
590
+ });
591
+
592
+ workspace.agents.push({
593
+ name: agent,
594
+ ...result
595
+ });
596
+ }
597
+
598
+ // Save workspace configuration
599
+ const workspaceFile = path.join(this.worktreesPath, 'workspaces.json');
600
+ let workspaces = {};
601
+
602
+ if (fs.existsSync(workspaceFile)) {
603
+ workspaces = JSON.parse(fs.readFileSync(workspaceFile, 'utf8'));
604
+ }
605
+
606
+ workspaces[workspace.id] = workspace;
607
+ fs.writeFileSync(workspaceFile, JSON.stringify(workspaces, null, 2));
608
+
609
+ log.success(`Parallel workspace created: ${workspace.id}`);
610
+ log.info(`Agents ready: ${agents.join(', ')}`);
611
+
612
+ return workspace;
613
+ }
614
+ }
615
+
616
+ // ============================================================================
617
+ // CLI INTERFACE
618
+ // ============================================================================
619
+
620
+ async function main() {
621
+ const args = process.argv.slice(2);
622
+ const command = args[0];
623
+
624
+ if (!checkGitRepo()) {
625
+ log.error('Not in a git repository!');
626
+ process.exit(1);
627
+ }
628
+
629
+ const manager = new WorktreeManager();
630
+
631
+ switch (command) {
632
+ case 'create': {
633
+ const agentIdx = args.indexOf('--agent');
634
+ const taskIdx = args.indexOf('--task');
635
+
636
+ if (agentIdx === -1 || !args[agentIdx + 1]) {
637
+ log.error('Usage: worktree-manager create --agent <name> --task <task>');
638
+ process.exit(1);
639
+ }
640
+
641
+ const agentName = args[agentIdx + 1];
642
+ const taskName = taskIdx !== -1 ? args[taskIdx + 1] : null;
643
+
644
+ manager.createWorktree(agentName, taskName);
645
+ break;
646
+ }
647
+
648
+ case 'list': {
649
+ manager.listWorktrees();
650
+ break;
651
+ }
652
+
653
+ case 'merge': {
654
+ const agentIdx = args.indexOf('--agent');
655
+
656
+ if (agentIdx === -1 || !args[agentIdx + 1]) {
657
+ log.error('Usage: worktree-manager merge --agent <name>');
658
+ process.exit(1);
659
+ }
660
+
661
+ const agentName = args[agentIdx + 1];
662
+ await manager.mergeAgentWork(agentName);
663
+ break;
664
+ }
665
+
666
+ case 'cleanup': {
667
+ const agentIdx = args.indexOf('--agent');
668
+
669
+ if (agentIdx === -1 || !args[agentIdx + 1]) {
670
+ log.error('Usage: worktree-manager cleanup --agent <name> [--all] [--delete-branches]');
671
+ process.exit(1);
672
+ }
673
+
674
+ const agentName = args[agentIdx + 1];
675
+ const options = {
676
+ all: args.includes('--all'),
677
+ deleteBranches: args.includes('--delete-branches'),
678
+ force: args.includes('--force')
679
+ };
680
+
681
+ await manager.cleanupWorktrees(agentName, options);
682
+ break;
683
+ }
684
+
685
+ case 'cs-devops-agent': {
686
+ const agentIdx = args.indexOf('--agent');
687
+ const worktreeIdx = args.indexOf('--worktree');
688
+
689
+ if (agentIdx === -1 || !args[agentIdx + 1] || worktreeIdx === -1 || !args[worktreeIdx + 1]) {
690
+ log.error('Usage: worktree-manager cs-devops-agent --agent <name> --worktree <name>');
691
+ process.exit(1);
692
+ }
693
+
694
+ const agentName = args[agentIdx + 1];
695
+ const worktreeName = args[worktreeIdx + 1];
696
+
697
+ manager.runCS_DevOpsAgent(agentName, worktreeName);
698
+ break;
699
+ }
700
+
701
+ case 'parallel': {
702
+ const agentsIdx = args.indexOf('--agents');
703
+ const taskIdx = args.indexOf('--task');
704
+
705
+ if (agentsIdx === -1 || taskIdx === -1) {
706
+ log.error('Usage: worktree-manager parallel --agents agent1,agent2,agent3 --task <task>');
707
+ process.exit(1);
708
+ }
709
+
710
+ const agents = args[agentsIdx + 1].split(',');
711
+ const task = args[taskIdx + 1];
712
+
713
+ await manager.createParallelWorkspace(agents, task);
714
+ break;
715
+ }
716
+
717
+ default: {
718
+ console.log(`
719
+ ${CONFIG.colors.bright}Worktree Manager - Multi-Agent Development System${CONFIG.colors.reset}
720
+
721
+ Commands:
722
+ create Create a new worktree for an AI agent
723
+ worktree-manager create --agent <name> --task <task>
724
+
725
+ list List all active worktrees and agents
726
+ worktree-manager list
727
+
728
+ merge Merge an agent's work back to main
729
+ worktree-manager merge --agent <name>
730
+
731
+ cleanup Clean up worktrees for an agent
732
+ worktree-manager cleanup --agent <name> [--all] [--delete-branches]
733
+
734
+ cs-devops-agent Run cs-devops-agent in an agent's worktree
735
+ worktree-manager cs-devops-agent --agent <name> --worktree <name>
736
+
737
+ parallel Create parallel workspace for multiple agents
738
+ worktree-manager parallel --agents agent1,agent2 --task <task>
739
+
740
+ Examples:
741
+ # Create worktree for Claude to work on authentication
742
+ worktree-manager create --agent claude --task auth-feature
743
+
744
+ # Create parallel workspace for 3 agents
745
+ worktree-manager parallel --agents claude,copilot,cursor --task refactor-api
746
+
747
+ # List all worktrees
748
+ worktree-manager list
749
+
750
+ # Merge Claude's work
751
+ worktree-manager merge --agent claude
752
+
753
+ # Clean up merged worktrees
754
+ worktree-manager cleanup --agent claude --delete-branches
755
+ `);
756
+ }
757
+ }
758
+ }
759
+
760
+ // Run if called directly
761
+ if (import.meta.url === `file://${__filename}`) {
762
+ main().catch(error => {
763
+ log.error(`Fatal error: ${error.message}`);
764
+ process.exit(1);
765
+ });
766
+ }
767
+
768
+ export default WorktreeManager;