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,1207 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * ============================================================================
5
+ * SESSION COORDINATOR - Foolproof Claude/Agent Handshake System
6
+ * ============================================================================
7
+ *
8
+ * This coordinator ensures Claude/Cline and DevOps agents work in sync.
9
+ * It generates instructions for Claude and manages session allocation.
10
+ *
11
+ * WORKFLOW:
12
+ * 1. Start DevOps agent → generates session & instructions
13
+ * 2. Copy instructions to Claude/Cline
14
+ * 3. Claude follows instructions to use correct worktree
15
+ * 4. Agent monitors that worktree for changes
16
+ *
17
+ * ============================================================================
18
+ */
19
+
20
+ import fs from 'fs';
21
+ import path from 'path';
22
+ import { execSync, spawn, fork } from 'child_process';
23
+ import { fileURLToPath } from 'url';
24
+ import { dirname } from 'path';
25
+ import crypto from 'crypto';
26
+ import readline from 'readline';
27
+
28
+ const __filename = fileURLToPath(import.meta.url);
29
+ const __dirname = dirname(__filename);
30
+
31
+ // ============================================================================
32
+ // CONFIGURATION
33
+ // ============================================================================
34
+
35
+ const CONFIG = {
36
+ sessionsDir: 'local_deploy/sessions',
37
+ locksDir: 'local_deploy/session-locks',
38
+ worktreesDir: 'local_deploy/worktrees',
39
+ instructionsDir: 'local_deploy/instructions',
40
+ colors: {
41
+ reset: '\x1b[0m',
42
+ bright: '\x1b[1m',
43
+ dim: '\x1b[2m',
44
+ green: '\x1b[32m',
45
+ yellow: '\x1b[33m',
46
+ blue: '\x1b[36m',
47
+ red: '\x1b[31m',
48
+ magenta: '\x1b[35m',
49
+ bgBlue: '\x1b[44m',
50
+ bgGreen: '\x1b[42m',
51
+ bgYellow: '\x1b[43m'
52
+ }
53
+ };
54
+
55
+ // ============================================================================
56
+ // SESSION COORDINATOR CLASS
57
+ // ============================================================================
58
+
59
+ class SessionCoordinator {
60
+ constructor() {
61
+ this.repoRoot = this.getRepoRoot();
62
+ this.sessionsPath = path.join(this.repoRoot, CONFIG.sessionsDir);
63
+ this.locksPath = path.join(this.repoRoot, CONFIG.locksDir);
64
+ this.worktreesPath = path.join(this.repoRoot, CONFIG.worktreesDir);
65
+ this.instructionsPath = path.join(this.repoRoot, CONFIG.instructionsDir);
66
+
67
+ // Store user settings in home directory for cross-project usage
68
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
69
+ this.globalSettingsDir = path.join(homeDir, '.devops-agent');
70
+ this.globalSettingsPath = path.join(this.globalSettingsDir, 'settings.json');
71
+
72
+ // Store project-specific settings in local_deploy
73
+ this.projectSettingsPath = path.join(this.repoRoot, 'local_deploy', 'project-settings.json');
74
+
75
+ this.ensureDirectories();
76
+ this.cleanupStaleLocks();
77
+ this.ensureSettingsFile();
78
+ // DO NOT call ensureDeveloperInitials here - it should only be called when creating new sessions
79
+ }
80
+
81
+ getRepoRoot() {
82
+ try {
83
+ return execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
84
+ } catch (error) {
85
+ console.error('Error: Not in a git repository');
86
+ process.exit(1);
87
+ }
88
+ }
89
+
90
+ ensureDirectories() {
91
+ // Ensure local project directories
92
+ [this.sessionsPath, this.locksPath, this.worktreesPath, this.instructionsPath].forEach(dir => {
93
+ if (!fs.existsSync(dir)) {
94
+ fs.mkdirSync(dir, { recursive: true });
95
+ }
96
+ });
97
+
98
+ // Ensure global settings directory in home folder
99
+ if (!fs.existsSync(this.globalSettingsDir)) {
100
+ fs.mkdirSync(this.globalSettingsDir, { recursive: true });
101
+ }
102
+ }
103
+
104
+ cleanupStaleLocks() {
105
+ // Clean up locks older than 1 hour
106
+ const oneHourAgo = Date.now() - 3600000;
107
+
108
+ if (fs.existsSync(this.locksPath)) {
109
+ const locks = fs.readdirSync(this.locksPath);
110
+ locks.forEach(lockFile => {
111
+ const lockPath = path.join(this.locksPath, lockFile);
112
+ const stats = fs.statSync(lockPath);
113
+ if (stats.mtimeMs < oneHourAgo) {
114
+ fs.unlinkSync(lockPath);
115
+ console.log(`${CONFIG.colors.dim}Cleaned stale lock: ${lockFile}${CONFIG.colors.reset}`);
116
+ }
117
+ });
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Ensure developer initials are configured globally
123
+ */
124
+ async ensureGlobalSetup() {
125
+ const globalSettings = this.loadGlobalSettings();
126
+
127
+ // Check if global setup is needed (developer initials)
128
+ if (!globalSettings.developerInitials || !globalSettings.configured) {
129
+ console.log(`\n${CONFIG.colors.yellow}First-time DevOps Agent setup!${CONFIG.colors.reset}`);
130
+ console.log(`${CONFIG.colors.bright}Please enter your 3-letter developer initials${CONFIG.colors.reset}`);
131
+ console.log(`${CONFIG.colors.dim}(These will be used in branch names across ALL projects)${CONFIG.colors.reset}`);
132
+
133
+ const initials = await this.promptForInitials();
134
+ globalSettings.developerInitials = initials.toLowerCase();
135
+ globalSettings.configured = true;
136
+
137
+ this.saveGlobalSettings(globalSettings);
138
+
139
+ console.log(`${CONFIG.colors.green}✓${CONFIG.colors.reset} Developer initials saved globally: ${CONFIG.colors.bright}${initials}${CONFIG.colors.reset}`);
140
+ console.log(`${CONFIG.colors.dim}Your initials are saved in ~/.devops-agent/settings.json${CONFIG.colors.reset}`);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Ensure project-specific version settings are configured
146
+ */
147
+ async ensureProjectSetup() {
148
+ const projectSettings = this.loadProjectSettings();
149
+
150
+ // Check if project setup is needed (version strategy)
151
+ if (!projectSettings.versioningStrategy || !projectSettings.versioningStrategy.configured) {
152
+ console.log(`\n${CONFIG.colors.yellow}First-time project setup for this repository!${CONFIG.colors.reset}`);
153
+ console.log(`${CONFIG.colors.dim}Let's configure the versioning strategy for this project${CONFIG.colors.reset}`);
154
+
155
+ const versionInfo = await this.promptForStartingVersion();
156
+ projectSettings.versioningStrategy = {
157
+ prefix: versionInfo.prefix,
158
+ startMinor: versionInfo.startMinor,
159
+ dailyIncrement: versionInfo.dailyIncrement || 1,
160
+ configured: true
161
+ };
162
+
163
+ this.saveProjectSettings(projectSettings);
164
+
165
+ // Set environment variables for the current session
166
+ process.env.AC_VERSION_PREFIX = versionInfo.prefix;
167
+ process.env.AC_VERSION_START_MINOR = versionInfo.startMinor.toString();
168
+ process.env.AC_VERSION_INCREMENT = versionInfo.dailyIncrement.toString();
169
+
170
+ const incrementDisplay = (versionInfo.dailyIncrement / 100).toFixed(2);
171
+ console.log(`${CONFIG.colors.green}✓${CONFIG.colors.reset} Project versioning configured:`);
172
+ console.log(` Starting: ${CONFIG.colors.bright}${versionInfo.prefix}${versionInfo.startMinor}${CONFIG.colors.reset}`);
173
+ console.log(` Daily increment: ${CONFIG.colors.bright}${incrementDisplay}${CONFIG.colors.reset}`);
174
+ console.log(`${CONFIG.colors.dim}Settings saved in local_deploy/project-settings.json${CONFIG.colors.reset}`);
175
+ } else {
176
+ // Project already configured, set environment variables
177
+ process.env.AC_VERSION_PREFIX = projectSettings.versioningStrategy.prefix;
178
+ process.env.AC_VERSION_START_MINOR = projectSettings.versioningStrategy.startMinor.toString();
179
+ process.env.AC_VERSION_INCREMENT = (projectSettings.versioningStrategy.dailyIncrement || 1).toString();
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Get developer initials from settings (no prompting)
185
+ */
186
+ getDeveloperInitials() {
187
+ const settings = this.loadSettings();
188
+ // Never prompt here, just return default if not configured
189
+ return settings.developerInitials || 'dev';
190
+ }
191
+
192
+ /**
193
+ * Ensure settings files exist
194
+ */
195
+ ensureSettingsFile() {
196
+ // Create global settings if not exists
197
+ if (!fs.existsSync(this.globalSettingsPath)) {
198
+ const defaultGlobalSettings = {
199
+ developerInitials: "",
200
+ email: "",
201
+ preferences: {
202
+ defaultTargetBranch: "main",
203
+ pushOnCommit: true,
204
+ verboseLogging: false
205
+ },
206
+ configured: false
207
+ };
208
+ fs.writeFileSync(this.globalSettingsPath, JSON.stringify(defaultGlobalSettings, null, 2));
209
+ console.log(`${CONFIG.colors.dim}Created global settings at ~/.devops-agent/settings.json${CONFIG.colors.reset}`);
210
+ }
211
+
212
+ // Create project settings if not exists
213
+ if (!fs.existsSync(this.projectSettingsPath)) {
214
+ const defaultProjectSettings = {
215
+ versioningStrategy: {
216
+ prefix: "v0.",
217
+ startMinor: 20,
218
+ configured: false
219
+ },
220
+ autoMergeConfig: {
221
+ enabled: false,
222
+ targetBranch: "main",
223
+ strategy: "pull-request"
224
+ }
225
+ };
226
+ const projectDir = path.dirname(this.projectSettingsPath);
227
+ if (!fs.existsSync(projectDir)) {
228
+ fs.mkdirSync(projectDir, { recursive: true });
229
+ }
230
+ fs.writeFileSync(this.projectSettingsPath, JSON.stringify(defaultProjectSettings, null, 2));
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Load global settings (user-specific)
236
+ */
237
+ loadGlobalSettings() {
238
+ if (fs.existsSync(this.globalSettingsPath)) {
239
+ return JSON.parse(fs.readFileSync(this.globalSettingsPath, 'utf8'));
240
+ }
241
+ return {
242
+ developerInitials: "",
243
+ email: "",
244
+ preferences: {},
245
+ configured: false
246
+ };
247
+ }
248
+
249
+ /**
250
+ * Load project settings
251
+ */
252
+ loadProjectSettings() {
253
+ if (fs.existsSync(this.projectSettingsPath)) {
254
+ return JSON.parse(fs.readFileSync(this.projectSettingsPath, 'utf8'));
255
+ }
256
+ return {
257
+ versioningStrategy: {
258
+ prefix: "v0.",
259
+ startMinor: 20,
260
+ configured: false
261
+ },
262
+ autoMergeConfig: {}
263
+ };
264
+ }
265
+
266
+ /**
267
+ * Combined settings loader for compatibility
268
+ */
269
+ loadSettings() {
270
+ const global = this.loadGlobalSettings();
271
+ const project = this.loadProjectSettings();
272
+ return {
273
+ ...global,
274
+ ...project,
275
+ developerInitials: global.developerInitials,
276
+ configured: global.configured
277
+ };
278
+ }
279
+
280
+ /**
281
+ * Save global settings
282
+ */
283
+ saveGlobalSettings(settings) {
284
+ fs.writeFileSync(this.globalSettingsPath, JSON.stringify(settings, null, 2));
285
+ console.log(`${CONFIG.colors.dim}Global settings saved to ~/.devops-agent/settings.json${CONFIG.colors.reset}`);
286
+ }
287
+
288
+ /**
289
+ * Save project settings
290
+ */
291
+ saveProjectSettings(settings) {
292
+ const projectDir = path.dirname(this.projectSettingsPath);
293
+ if (!fs.existsSync(projectDir)) {
294
+ fs.mkdirSync(projectDir, { recursive: true });
295
+ }
296
+ fs.writeFileSync(this.projectSettingsPath, JSON.stringify(settings, null, 2));
297
+ console.log(`${CONFIG.colors.dim}Project settings saved to local_deploy/project-settings.json${CONFIG.colors.reset}`);
298
+ }
299
+
300
+ /**
301
+ * Save settings (splits between global and project)
302
+ */
303
+ saveSettings(settings) {
304
+ // Split settings into global and project
305
+ const globalSettings = {
306
+ developerInitials: settings.developerInitials,
307
+ email: settings.email || "",
308
+ preferences: settings.preferences || {},
309
+ configured: settings.configured
310
+ };
311
+
312
+ const projectSettings = {
313
+ versioningStrategy: settings.versioningStrategy,
314
+ autoMergeConfig: settings.autoMergeConfig || {}
315
+ };
316
+
317
+ this.saveGlobalSettings(globalSettings);
318
+ this.saveProjectSettings(projectSettings);
319
+ }
320
+
321
+ /**
322
+ * Prompt for developer initials
323
+ */
324
+ promptForInitials() {
325
+ const rl = readline.createInterface({
326
+ input: process.stdin,
327
+ output: process.stdout
328
+ });
329
+
330
+ return new Promise((resolve) => {
331
+ const askInitials = () => {
332
+ rl.question('Developer initials (3 letters): ', (answer) => {
333
+ const initials = answer.trim();
334
+ if (initials.length !== 3) {
335
+ console.log(`${CONFIG.colors.red}Please enter exactly 3 letters${CONFIG.colors.reset}`);
336
+ askInitials();
337
+ } else if (!/^[a-zA-Z]+$/.test(initials)) {
338
+ console.log(`${CONFIG.colors.red}Please use only letters${CONFIG.colors.reset}`);
339
+ askInitials();
340
+ } else {
341
+ rl.close();
342
+ resolve(initials);
343
+ }
344
+ });
345
+ };
346
+ askInitials();
347
+ });
348
+ }
349
+
350
+ /**
351
+ * Get list of available branches
352
+ */
353
+ getAvailableBranches() {
354
+ try {
355
+ const result = execSync('git branch -a --format="%(refname:short)"', {
356
+ cwd: this.repoRoot,
357
+ encoding: 'utf8'
358
+ });
359
+
360
+ return result.split('\n')
361
+ .filter(branch => branch.trim())
362
+ .filter(branch => !branch.includes('HEAD'))
363
+ .map(branch => branch.replace('origin/', ''));
364
+ } catch (error) {
365
+ return ['main', 'develop', 'master'];
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Prompt for merge configuration
371
+ */
372
+ async promptForMergeConfig() {
373
+ const rl = readline.createInterface({
374
+ input: process.stdin,
375
+ output: process.stdout
376
+ });
377
+
378
+ console.log(`\n${CONFIG.colors.yellow}═══ Auto-merge Configuration ═══${CONFIG.colors.reset}`);
379
+ console.log(`${CONFIG.colors.dim}(Automatically merge today's work into a target branch)${CONFIG.colors.reset}`);
380
+
381
+ // Ask if they want auto-merge
382
+ const autoMerge = await new Promise((resolve) => {
383
+ rl.question('\nEnable auto-merge at end of day? (y/N): ', (answer) => {
384
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
385
+ });
386
+ });
387
+
388
+ if (!autoMerge) {
389
+ rl.close();
390
+ console.log(`${CONFIG.colors.dim}Auto-merge disabled. You'll need to manually merge your work.${CONFIG.colors.reset}`);
391
+ return { autoMerge: false };
392
+ }
393
+
394
+ // Get available branches
395
+ const branches = this.getAvailableBranches();
396
+ const uniqueBranches = [...new Set(branches)].slice(0, 10); // Show max 10 branches
397
+
398
+ console.log(`\n${CONFIG.colors.bright}Which branch should today's work be merged INTO?${CONFIG.colors.reset}`);
399
+ console.log(`${CONFIG.colors.dim}(e.g., main, develop, v2.0, feature/xyz)${CONFIG.colors.reset}\n`);
400
+
401
+ console.log(`${CONFIG.colors.bright}Available branches:${CONFIG.colors.reset}`);
402
+ uniqueBranches.forEach((branch, index) => {
403
+ const isDefault = branch === 'main' || branch === 'master' || branch === 'develop';
404
+ const marker = isDefault ? ` ${CONFIG.colors.green}⭐ (recommended)${CONFIG.colors.reset}` : '';
405
+ console.log(` ${index + 1}) ${branch}${marker}`);
406
+ });
407
+ console.log(` 0) Enter a different branch name`);
408
+
409
+ // Ask for target branch
410
+ const targetBranch = await new Promise((resolve) => {
411
+ rl.question(`\nSelect target branch to merge INTO (1-${uniqueBranches.length}, or 0): `, async (answer) => {
412
+ const choice = parseInt(answer);
413
+ if (choice === 0) {
414
+ rl.question('Enter custom branch name: ', (customBranch) => {
415
+ resolve(customBranch.trim());
416
+ });
417
+ } else if (choice >= 1 && choice <= uniqueBranches.length) {
418
+ resolve(uniqueBranches[choice - 1]);
419
+ } else {
420
+ resolve('main'); // Default to main if invalid choice
421
+ }
422
+ });
423
+ });
424
+
425
+ // Ask for merge strategy
426
+ console.log(`\n${CONFIG.colors.bright}Merge strategy:${CONFIG.colors.reset}`);
427
+ console.log(` 1) Create pull request (recommended)`);
428
+ console.log(` 2) Direct merge (when tests pass)`);
429
+ console.log(` 3) Squash and merge`);
430
+
431
+ const strategy = await new Promise((resolve) => {
432
+ rl.question('Select merge strategy (1-3) [1]: ', (answer) => {
433
+ const choice = parseInt(answer) || 1;
434
+ switch(choice) {
435
+ case 2:
436
+ resolve('direct');
437
+ break;
438
+ case 3:
439
+ resolve('squash');
440
+ break;
441
+ default:
442
+ resolve('pull-request');
443
+ }
444
+ });
445
+ });
446
+
447
+ rl.close();
448
+
449
+ const config = {
450
+ autoMerge: true,
451
+ targetBranch,
452
+ strategy,
453
+ requireTests: strategy !== 'pull-request'
454
+ };
455
+
456
+ console.log(`\n${CONFIG.colors.green}✓${CONFIG.colors.reset} Auto-merge configuration saved:`);
457
+ console.log(` ${CONFIG.colors.bright}Today's work${CONFIG.colors.reset} → ${CONFIG.colors.bright}${targetBranch}${CONFIG.colors.reset}`);
458
+ console.log(` Strategy: ${CONFIG.colors.bright}${strategy}${CONFIG.colors.reset}`);
459
+ console.log(`${CONFIG.colors.dim} (Daily branches will be merged into ${targetBranch} at end of day)${CONFIG.colors.reset}`);
460
+
461
+ return config;
462
+ }
463
+
464
+ /**
465
+ * Prompt for starting version configuration
466
+ */
467
+ async promptForStartingVersion() {
468
+ const rl = readline.createInterface({
469
+ input: process.stdin,
470
+ output: process.stdout
471
+ });
472
+
473
+ console.log(`\n${CONFIG.colors.yellow}Version Configuration${CONFIG.colors.reset}`);
474
+ console.log(`${CONFIG.colors.dim}Set the starting version for this codebase${CONFIG.colors.reset}`);
475
+
476
+ // Ask if inheriting existing codebase
477
+ const isInherited = await new Promise((resolve) => {
478
+ rl.question('\nIs this an existing/inherited codebase? (y/N): ', (answer) => {
479
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
480
+ });
481
+ });
482
+
483
+ let prefix = 'v0.';
484
+ let startMinor = 20; // Default v0.20
485
+ let dailyIncrement = 1; // Default 0.01 per day
486
+
487
+ if (isInherited) {
488
+ console.log(`\n${CONFIG.colors.bright}Current Version Examples:${CONFIG.colors.reset}`);
489
+ console.log(' v1.5 → Enter: v1. and 50');
490
+ console.log(' v2.3 → Enter: v2. and 30');
491
+ console.log(' v0.8 → Enter: v0. and 80');
492
+ console.log(' v3.12 → Enter: v3. and 120');
493
+
494
+ // Get version prefix
495
+ prefix = await new Promise((resolve) => {
496
+ rl.question('\nEnter version prefix (e.g., v1., v2., v0.) [v0.]: ', (answer) => {
497
+ const cleaned = answer.trim() || 'v0.';
498
+ // Ensure it ends with a dot
499
+ resolve(cleaned.endsWith('.') ? cleaned : cleaned + '.');
500
+ });
501
+ });
502
+
503
+ // Get starting minor version
504
+ const currentVersion = await new Promise((resolve) => {
505
+ rl.question(`Current version number (e.g., for ${prefix}5 enter 50, for ${prefix}12 enter 120) [20]: `, (answer) => {
506
+ const num = parseInt(answer.trim());
507
+ resolve(isNaN(num) ? 20 : num);
508
+ });
509
+ });
510
+
511
+ // Next version will be current + 1
512
+ startMinor = currentVersion + 1;
513
+
514
+ console.log(`\n${CONFIG.colors.green}✓${CONFIG.colors.reset} Next version will be: ${CONFIG.colors.bright}${prefix}${startMinor}${CONFIG.colors.reset}`);
515
+ console.log(`${CONFIG.colors.dim}(This represents ${prefix}${(startMinor/100).toFixed(2)} in semantic versioning)${CONFIG.colors.reset}`);
516
+ } else {
517
+ // New project
518
+ console.log(`\n${CONFIG.colors.green}✓${CONFIG.colors.reset} Starting new project at: ${CONFIG.colors.bright}v0.20${CONFIG.colors.reset}`);
519
+ }
520
+
521
+ // Ask for daily increment preference
522
+ console.log(`\n${CONFIG.colors.yellow}Daily Version Increment${CONFIG.colors.reset}`);
523
+ console.log(`${CONFIG.colors.dim}How much should the version increment each day?${CONFIG.colors.reset}`);
524
+ console.log(' 1) 0.01 per day (v0.20 → v0.21 → v0.22) [default]');
525
+ console.log(' 2) 0.1 per day (v0.20 → v0.30 → v0.40)');
526
+ console.log(' 3) 0.2 per day (v0.20 → v0.40 → v0.60)');
527
+ console.log(' 4) Custom increment');
528
+
529
+ const incrementChoice = await new Promise((resolve) => {
530
+ rl.question('\nSelect increment (1-4) [1]: ', (answer) => {
531
+ const choice = parseInt(answer.trim()) || 1;
532
+ resolve(choice);
533
+ });
534
+ });
535
+
536
+ switch (incrementChoice) {
537
+ case 2:
538
+ dailyIncrement = 10; // 0.1
539
+ break;
540
+ case 3:
541
+ dailyIncrement = 20; // 0.2
542
+ break;
543
+ case 4:
544
+ dailyIncrement = await new Promise((resolve) => {
545
+ rl.question('Enter increment value (e.g., 5 for 0.05, 25 for 0.25): ', (answer) => {
546
+ const value = parseInt(answer.trim());
547
+ resolve(isNaN(value) || value <= 0 ? 1 : value);
548
+ });
549
+ });
550
+ break;
551
+ default:
552
+ dailyIncrement = 1; // 0.01
553
+ }
554
+
555
+ const incrementDisplay = (dailyIncrement / 100).toFixed(2);
556
+ console.log(`\n${CONFIG.colors.green}✓${CONFIG.colors.reset} Daily increment set to: ${CONFIG.colors.bright}${incrementDisplay}${CONFIG.colors.reset}`);
557
+ console.log(`${CONFIG.colors.dim}(${prefix}${startMinor} → ${prefix}${startMinor + dailyIncrement} → ${prefix}${startMinor + dailyIncrement * 2}...)${CONFIG.colors.reset}`);
558
+
559
+ rl.close();
560
+
561
+ return {
562
+ prefix,
563
+ startMinor,
564
+ dailyIncrement
565
+ };
566
+ }
567
+
568
+ generateSessionId() {
569
+ const timestamp = Date.now().toString(36).slice(-4);
570
+ const random = crypto.randomBytes(2).toString('hex');
571
+ return `${timestamp}-${random}`;
572
+ }
573
+
574
+ /**
575
+ * Create a new session and generate Claude instructions
576
+ */
577
+ async createSession(options = {}) {
578
+ // Ensure both global and project setup are complete
579
+ await this.ensureGlobalSetup(); // Developer initials (once per user)
580
+ await this.ensureProjectSetup(); // Version strategy (once per project)
581
+
582
+ const sessionId = this.generateSessionId();
583
+ const task = options.task || 'development';
584
+ const agentType = options.agent || 'claude';
585
+ const devInitials = this.getDeveloperInitials();
586
+
587
+ console.log(`\n${CONFIG.colors.bgBlue}${CONFIG.colors.bright} Creating New Session ${CONFIG.colors.reset}`);
588
+ console.log(`${CONFIG.colors.blue}Session ID:${CONFIG.colors.reset} ${CONFIG.colors.bright}${sessionId}${CONFIG.colors.reset}`);
589
+ console.log(`${CONFIG.colors.blue}Task:${CONFIG.colors.reset} ${task}`);
590
+ console.log(`${CONFIG.colors.blue}Agent:${CONFIG.colors.reset} ${agentType}`);
591
+ console.log(`${CONFIG.colors.blue}Developer:${CONFIG.colors.reset} ${devInitials}`);
592
+
593
+ // Ask for auto-merge configuration
594
+ const mergeConfig = await this.promptForMergeConfig();
595
+
596
+ // Create worktree with developer initials in the name
597
+ const worktreeName = `${agentType}-${devInitials}-${sessionId}-${task.replace(/\s+/g, '-')}`;
598
+ const worktreePath = path.join(this.worktreesPath, worktreeName);
599
+ const branchName = `${agentType}/${devInitials}/${sessionId}/${task.replace(/\s+/g, '-')}`;
600
+
601
+ try {
602
+ // Create worktree
603
+ console.log(`\n${CONFIG.colors.yellow}Creating worktree...${CONFIG.colors.reset}`);
604
+ execSync(`git worktree add -b ${branchName} "${worktreePath}" HEAD`, { stdio: 'pipe' });
605
+ console.log(`${CONFIG.colors.green}✓${CONFIG.colors.reset} Worktree created at: ${worktreePath}`);
606
+
607
+ // Create session lock
608
+ const lockData = {
609
+ sessionId,
610
+ agentType,
611
+ task,
612
+ worktreePath,
613
+ branchName,
614
+ created: new Date().toISOString(),
615
+ status: 'active',
616
+ pid: process.pid,
617
+ developerInitials: devInitials,
618
+ mergeConfig: mergeConfig
619
+ };
620
+
621
+ const lockFile = path.join(this.locksPath, `${sessionId}.lock`);
622
+ fs.writeFileSync(lockFile, JSON.stringify(lockData, null, 2));
623
+
624
+ // Generate Claude instructions
625
+ const instructions = this.generateClaudeInstructions(lockData);
626
+
627
+ // Save instructions to file
628
+ const instructionsFile = path.join(this.instructionsPath, `${sessionId}.md`);
629
+ fs.writeFileSync(instructionsFile, instructions.markdown);
630
+
631
+ // Display instructions
632
+ this.displayInstructions(instructions, sessionId, task);
633
+
634
+ // Create session config in worktree
635
+ this.createWorktreeConfig(worktreePath, lockData);
636
+
637
+ return {
638
+ sessionId,
639
+ worktreePath,
640
+ branchName,
641
+ lockFile,
642
+ instructionsFile,
643
+ instructions: instructions.plaintext
644
+ };
645
+
646
+ } catch (error) {
647
+ console.error(`${CONFIG.colors.red}Failed to create session: ${error.message}${CONFIG.colors.reset}`);
648
+ process.exit(1);
649
+ }
650
+ }
651
+
652
+ /**
653
+ * Generate instructions for Claude/Cline
654
+ */
655
+ generateClaudeInstructions(sessionData) {
656
+ const { sessionId, worktreePath, branchName, task } = sessionData;
657
+
658
+ const plaintext = `
659
+ SESSION_ID: ${sessionId}
660
+ WORKTREE: ${worktreePath}
661
+ BRANCH: ${branchName}
662
+ TASK: ${task}
663
+
664
+ INSTRUCTIONS:
665
+ 1. Change to worktree directory: cd "${worktreePath}"
666
+ 2. Verify branch: git branch --show-current
667
+ 3. Make your changes for: ${task}
668
+ 4. Write commit message to: .devops-commit-${sessionId}.msg
669
+ 5. The DevOps agent will auto-commit and push your changes
670
+ `;
671
+
672
+ const markdown = `# DevOps Session Instructions
673
+
674
+ ## Session Information
675
+ - **Session ID:** \`${sessionId}\`
676
+ - **Task:** ${task}
677
+ - **Worktree Path:** \`${worktreePath}\`
678
+ - **Branch:** \`${branchName}\`
679
+
680
+ ## Instructions for Claude/Cline
681
+
682
+ ### Step 1: Navigate to Your Worktree
683
+ \`\`\`bash
684
+ cd "${worktreePath}"
685
+ \`\`\`
686
+
687
+ ### Step 2: Verify You're on the Correct Branch
688
+ \`\`\`bash
689
+ git branch --show-current
690
+ # Should output: ${branchName}
691
+ \`\`\`
692
+
693
+ ### Step 3: Work on Your Task
694
+ Make changes for: **${task}**
695
+
696
+ ### Step 4: Commit Your Changes
697
+ Write your commit message to the session-specific file:
698
+ \`\`\`bash
699
+ echo "feat: your commit message here" > .devops-commit-${sessionId}.msg
700
+ \`\`\`
701
+
702
+ ### Step 5: Automatic Processing
703
+ The DevOps agent will automatically:
704
+ - Detect your changes
705
+ - Read your commit message
706
+ - Commit and push to the remote repository
707
+ - Clear the message file
708
+
709
+ ## Session Status
710
+ - Created: ${new Date().toISOString()}
711
+ - Status: Active
712
+ - Agent: Monitoring
713
+
714
+ ## Important Notes
715
+ - All changes should be made in the worktree directory
716
+ - Do not switch branches manually
717
+ - The agent is watching for changes in this specific worktree
718
+ `;
719
+
720
+ const shellCommand = `cd "${worktreePath}" && echo "Session ${sessionId} ready"`;
721
+
722
+ return {
723
+ plaintext,
724
+ markdown,
725
+ shellCommand,
726
+ worktreePath,
727
+ sessionId
728
+ };
729
+ }
730
+
731
+ /**
732
+ * Display instructions in a user-friendly format
733
+ */
734
+ displayInstructions(instructions, sessionId, task) {
735
+ console.log(`\n${CONFIG.colors.bgGreen}${CONFIG.colors.bright} Instructions for Claude/Cline ${CONFIG.colors.reset}\n`);
736
+
737
+ // Clean separator
738
+ console.log(`${CONFIG.colors.yellow}══════════════════════════════════════════════════════════════${CONFIG.colors.reset}`);
739
+ console.log(`${CONFIG.colors.bright}COPY AND PASTE THIS ENTIRE BLOCK INTO CLAUDE BEFORE YOUR PROMPT:${CONFIG.colors.reset}`);
740
+ console.log(`${CONFIG.colors.yellow}──────────────────────────────────────────────────────────────${CONFIG.colors.reset}`);
741
+ console.log();
742
+
743
+ // The actual copyable content - no colors inside
744
+ console.log(`I'm working in a DevOps-managed session with the following setup:`);
745
+ console.log(`- Session ID: ${sessionId}`);
746
+ console.log(`- Working Directory: ${instructions.worktreePath}`);
747
+ console.log(`- Task: ${task || 'development'}`);
748
+ console.log(``);
749
+ console.log(`Please switch to this directory before making any changes:`);
750
+ console.log(`cd "${instructions.worktreePath}"`);
751
+ console.log(``);
752
+ console.log(`Write commit messages to: .devops-commit-${sessionId}.msg`);
753
+ console.log(`The DevOps agent will automatically commit and push changes.`);
754
+ console.log();
755
+
756
+ console.log(`${CONFIG.colors.yellow}══════════════════════════════════════════════════════════════${CONFIG.colors.reset}`);
757
+
758
+ // Status info
759
+ console.log(`\n${CONFIG.colors.green}✓ DevOps agent is starting...${CONFIG.colors.reset}`);
760
+ console.log(`${CONFIG.colors.dim}Full instructions saved to: ${CONFIG.instructionsDir}/${sessionId}.md${CONFIG.colors.reset}`);
761
+ }
762
+
763
+ /**
764
+ * Create configuration in the worktree
765
+ */
766
+ createWorktreeConfig(worktreePath, sessionData) {
767
+ // Session config file
768
+ const configPath = path.join(worktreePath, '.devops-session.json');
769
+ fs.writeFileSync(configPath, JSON.stringify(sessionData, null, 2));
770
+
771
+ // Commit message file
772
+ const msgFile = path.join(worktreePath, `.devops-commit-${sessionData.sessionId}.msg`);
773
+ fs.writeFileSync(msgFile, '');
774
+
775
+ // VS Code settings
776
+ const vscodeDir = path.join(worktreePath, '.vscode');
777
+ if (!fs.existsSync(vscodeDir)) {
778
+ fs.mkdirSync(vscodeDir, { recursive: true });
779
+ }
780
+
781
+ const settings = {
782
+ 'window.title': `${sessionData.agentType.toUpperCase()} Session ${sessionData.sessionId} - ${sessionData.task}`,
783
+ 'terminal.integrated.env.osx': {
784
+ 'DEVOPS_SESSION_ID': sessionData.sessionId,
785
+ 'DEVOPS_WORKTREE': path.basename(worktreePath),
786
+ 'DEVOPS_BRANCH': sessionData.branchName,
787
+ 'AC_MSG_FILE': `.devops-commit-${sessionData.sessionId}.msg`,
788
+ 'AC_BRANCH_PREFIX': `${sessionData.agentType}_${sessionData.sessionId}_`
789
+ }
790
+ };
791
+
792
+ fs.writeFileSync(
793
+ path.join(vscodeDir, 'settings.json'),
794
+ JSON.stringify(settings, null, 2)
795
+ );
796
+
797
+ // Create a README for the session
798
+ const readme = `# DevOps Session: ${sessionData.sessionId}
799
+
800
+ ## Task
801
+ ${sessionData.task}
802
+
803
+ ## Session Details
804
+ - **Session ID:** ${sessionData.sessionId}
805
+ - **Branch:** ${sessionData.branchName}
806
+ - **Created:** ${sessionData.created}
807
+ - **Agent Type:** ${sessionData.agentType}
808
+
809
+ ## How to Use
810
+ 1. Make your changes in this directory
811
+ 2. Write commit message to: \`.devops-commit-${sessionData.sessionId}.msg\`
812
+ 3. The DevOps agent will handle the rest
813
+
814
+ ## Status
815
+ The DevOps agent is monitoring this worktree for changes.
816
+ `;
817
+
818
+ fs.writeFileSync(path.join(worktreePath, 'SESSION_README.md'), readme);
819
+
820
+ // Update .gitignore in the worktree to exclude session files
821
+ const gitignorePath = path.join(worktreePath, '.gitignore');
822
+ let gitignoreContent = '';
823
+
824
+ // Read existing gitignore if it exists
825
+ if (fs.existsSync(gitignorePath)) {
826
+ gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
827
+ }
828
+
829
+ // Session file patterns to ignore
830
+ const sessionPatterns = [
831
+ '# DevOps session management files',
832
+ '.devops-commit-*.msg',
833
+ '.devops-session.json',
834
+ 'SESSION_README.md',
835
+ '.session-cleanup-requested',
836
+ '.worktree-session',
837
+ '.agent-config',
838
+ '.session-*',
839
+ '.devops-command-*'
840
+ ];
841
+
842
+ // Check if we need to add patterns
843
+ let needsUpdate = false;
844
+ for (const pattern of sessionPatterns) {
845
+ if (!gitignoreContent.includes(pattern)) {
846
+ needsUpdate = true;
847
+ break;
848
+ }
849
+ }
850
+
851
+ if (needsUpdate) {
852
+ // Add session patterns to gitignore
853
+ if (!gitignoreContent.endsWith('\n') && gitignoreContent.length > 0) {
854
+ gitignoreContent += '\n';
855
+ }
856
+ gitignoreContent += '\n' + sessionPatterns.join('\n') + '\n';
857
+ fs.writeFileSync(gitignorePath, gitignoreContent);
858
+ console.log(`${CONFIG.colors.green}✓${CONFIG.colors.reset} Updated .gitignore to exclude session files`);
859
+ }
860
+
861
+ console.log(`${CONFIG.colors.dim}Session files created but not committed (they are gitignored)${CONFIG.colors.reset}`);
862
+
863
+ // Note: We do NOT commit these files - they're for session management only
864
+ // This prevents the "uncommitted changes" issue when starting sessions
865
+ }
866
+
867
+ /**
868
+ * Request a session (for Claude to call)
869
+ */
870
+ async requestSession(agentName = 'claude') {
871
+ console.log(`\n${CONFIG.colors.magenta}[${agentName.toUpperCase()}]${CONFIG.colors.reset} Requesting session...`);
872
+
873
+ // Check for available unlocked sessions
874
+ const availableSession = this.findAvailableSession();
875
+
876
+ if (availableSession) {
877
+ console.log(`${CONFIG.colors.green}✓${CONFIG.colors.reset} Found available session: ${availableSession.sessionId}`);
878
+ return this.claimSession(availableSession, agentName);
879
+ } else {
880
+ console.log(`${CONFIG.colors.yellow}No available sessions. Creating new one...${CONFIG.colors.reset}`);
881
+ const task = await this.promptForTask();
882
+ return this.createSession({ task, agent: agentName });
883
+ }
884
+ }
885
+
886
+ /**
887
+ * Find an available unclaimed session
888
+ */
889
+ findAvailableSession() {
890
+ if (!fs.existsSync(this.locksPath)) {
891
+ return null;
892
+ }
893
+
894
+ const locks = fs.readdirSync(this.locksPath);
895
+
896
+ for (const lockFile of locks) {
897
+ const lockPath = path.join(this.locksPath, lockFile);
898
+ const lockData = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
899
+
900
+ // Check if session is available (not claimed)
901
+ if (lockData.status === 'waiting' && !lockData.claimedBy) {
902
+ return lockData;
903
+ }
904
+ }
905
+
906
+ return null;
907
+ }
908
+
909
+ /**
910
+ * Claim a session for an agent
911
+ */
912
+ claimSession(session, agentName) {
913
+ session.claimedBy = agentName;
914
+ session.claimedAt = new Date().toISOString();
915
+ session.status = 'active';
916
+
917
+ const lockFile = path.join(this.locksPath, `${session.sessionId}.lock`);
918
+ fs.writeFileSync(lockFile, JSON.stringify(session, null, 2));
919
+
920
+ const instructions = this.generateClaudeInstructions(session);
921
+ this.displayInstructions(instructions, session.sessionId, session.task);
922
+
923
+ return {
924
+ ...session,
925
+ instructions: instructions.plaintext
926
+ };
927
+ }
928
+
929
+ /**
930
+ * Start the DevOps agent for a session
931
+ */
932
+ async startAgent(sessionId, options = {}) {
933
+ const lockFile = path.join(this.locksPath, `${sessionId}.lock`);
934
+
935
+ if (!fs.existsSync(lockFile)) {
936
+ console.error(`${CONFIG.colors.red}Session not found: ${sessionId}${CONFIG.colors.reset}`);
937
+ return;
938
+ }
939
+
940
+ const sessionData = JSON.parse(fs.readFileSync(lockFile, 'utf8'));
941
+
942
+ console.log(`\n${CONFIG.colors.bgYellow}${CONFIG.colors.bright} Starting DevOps Agent ${CONFIG.colors.reset}`);
943
+ console.log(`${CONFIG.colors.blue}Session:${CONFIG.colors.reset} ${sessionId}`);
944
+ console.log(`${CONFIG.colors.blue}Worktree:${CONFIG.colors.reset} ${sessionData.worktreePath}`);
945
+ console.log(`${CONFIG.colors.blue}Branch:${CONFIG.colors.reset} ${sessionData.branchName}`);
946
+
947
+ // Update session status
948
+ sessionData.agentStarted = new Date().toISOString();
949
+ sessionData.agentPid = process.pid;
950
+ fs.writeFileSync(lockFile, JSON.stringify(sessionData, null, 2));
951
+
952
+ // Get developer initials from session data or settings (NO PROMPTING HERE)
953
+ const devInitials = sessionData.developerInitials || this.getDeveloperInitials() || 'dev';
954
+ const settings = this.loadSettings();
955
+ const projectSettings = this.loadProjectSettings();
956
+
957
+ // Start the agent
958
+ const env = {
959
+ ...process.env,
960
+ DEVOPS_SESSION_ID: sessionId,
961
+ AC_MSG_FILE: `.devops-commit-${sessionId}.msg`,
962
+ AC_BRANCH_PREFIX: `${sessionData.agentType}_${devInitials}_${sessionId}_`,
963
+ AC_WORKING_DIR: sessionData.worktreePath,
964
+ // Don't set AC_BRANCH - let the agent create daily branches within the worktree
965
+ // AC_BRANCH would force a static branch, preventing daily/weekly rollover
966
+ AC_PUSH: 'true', // Enable auto-push for session branches
967
+ AC_DAILY_PREFIX: `${sessionData.agentType}_${devInitials}_${sessionId}_`, // Daily branches with dev initials
968
+ AC_TZ: process.env.AC_TZ || 'Asia/Dubai', // Preserve timezone for daily branches
969
+ AC_DATE_STYLE: process.env.AC_DATE_STYLE || 'dash', // Preserve date style
970
+ // Apply version configuration if set
971
+ ...(projectSettings.versioningStrategy?.prefix && { AC_VERSION_PREFIX: projectSettings.versioningStrategy.prefix }),
972
+ ...(projectSettings.versioningStrategy?.startMinor && { AC_VERSION_START_MINOR: projectSettings.versioningStrategy.startMinor.toString() })
973
+ };
974
+
975
+ const agentScript = path.join(__dirname, 'cs-devops-agent-worker.js');
976
+
977
+ console.log(`\n${CONFIG.colors.green}Agent starting...${CONFIG.colors.reset}`);
978
+ console.log(`${CONFIG.colors.dim}Monitoring: ${sessionData.worktreePath}${CONFIG.colors.reset}`);
979
+ console.log(`${CONFIG.colors.dim}Message file: .devops-commit-${sessionId}.msg${CONFIG.colors.reset}`);
980
+
981
+ // Use fork for better Node.js script handling
982
+ // Fork automatically uses the same node executable and handles paths better
983
+ const child = fork(agentScript, [], {
984
+ cwd: sessionData.worktreePath,
985
+ env,
986
+ stdio: 'inherit',
987
+ silent: false
988
+ });
989
+
990
+ child.on('exit', (code) => {
991
+ console.log(`${CONFIG.colors.yellow}Agent exited with code: ${code}${CONFIG.colors.reset}`);
992
+
993
+ // Update session status
994
+ sessionData.agentStopped = new Date().toISOString();
995
+ sessionData.status = 'stopped';
996
+ fs.writeFileSync(lockFile, JSON.stringify(sessionData, null, 2));
997
+ });
998
+
999
+ // Handle graceful shutdown
1000
+ process.on('SIGINT', () => {
1001
+ console.log(`\n${CONFIG.colors.yellow}Stopping agent...${CONFIG.colors.reset}`);
1002
+ child.kill('SIGINT');
1003
+ setTimeout(() => process.exit(0), 1000);
1004
+ });
1005
+ }
1006
+
1007
+ /**
1008
+ * List all sessions
1009
+ */
1010
+ listSessions() {
1011
+ console.log(`\n${CONFIG.colors.bright}Active Sessions:${CONFIG.colors.reset}`);
1012
+
1013
+ if (!fs.existsSync(this.locksPath)) {
1014
+ console.log('No active sessions');
1015
+ return;
1016
+ }
1017
+
1018
+ const locks = fs.readdirSync(this.locksPath);
1019
+
1020
+ if (locks.length === 0) {
1021
+ console.log('No active sessions');
1022
+ return;
1023
+ }
1024
+
1025
+ locks.forEach(lockFile => {
1026
+ const lockPath = path.join(this.locksPath, lockFile);
1027
+ const session = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
1028
+
1029
+ const status = session.status === 'active' ?
1030
+ `${CONFIG.colors.green}●${CONFIG.colors.reset}` :
1031
+ `${CONFIG.colors.yellow}○${CONFIG.colors.reset}`;
1032
+
1033
+ console.log(`\n${status} ${CONFIG.colors.bright}${session.sessionId}${CONFIG.colors.reset}`);
1034
+ console.log(` Task: ${session.task}`);
1035
+ console.log(` Agent: ${session.agentType}`);
1036
+ console.log(` Branch: ${session.branchName}`);
1037
+ console.log(` Status: ${session.status}`);
1038
+
1039
+ if (session.claimedBy) {
1040
+ console.log(` Claimed by: ${session.claimedBy}`);
1041
+ }
1042
+
1043
+ if (session.agentPid) {
1044
+ console.log(` Agent PID: ${session.agentPid}`);
1045
+ }
1046
+ });
1047
+ }
1048
+
1049
+ /**
1050
+ * Prompt for task name
1051
+ */
1052
+ promptForTask() {
1053
+ const rl = readline.createInterface({
1054
+ input: process.stdin,
1055
+ output: process.stdout
1056
+ });
1057
+
1058
+ return new Promise((resolve) => {
1059
+ rl.question('Enter task name: ', (answer) => {
1060
+ rl.close();
1061
+ resolve(answer || 'development');
1062
+ });
1063
+ });
1064
+ }
1065
+
1066
+ /**
1067
+ * Create a combined session (both create and start agent)
1068
+ */
1069
+ async createAndStart(options = {}) {
1070
+ const session = await this.createSession(options);
1071
+
1072
+ console.log(`\n${CONFIG.colors.yellow}Starting agent for session ${session.sessionId}...${CONFIG.colors.reset}`);
1073
+
1074
+ // Wait a moment for user to see instructions
1075
+ await new Promise(resolve => setTimeout(resolve, 2000));
1076
+
1077
+ await this.startAgent(session.sessionId);
1078
+
1079
+ return session;
1080
+ }
1081
+ }
1082
+
1083
+ // ============================================================================
1084
+ // CLI INTERFACE
1085
+ // ============================================================================
1086
+
1087
+ async function main() {
1088
+ // Display copyright and license information immediately
1089
+ console.log();
1090
+ console.log("=".repeat(70));
1091
+ console.log();
1092
+ console.log(" CS_DevOpsAgent - Intelligent Git Automation System");
1093
+ console.log(" Version 2.4.0 | Build 20240930.1");
1094
+ console.log(" ");
1095
+ console.log(" Copyright (c) 2024 SecondBrain Labs");
1096
+ console.log(" Author: Sachin Dev Duggal");
1097
+ console.log(" ");
1098
+ console.log(" Licensed under the MIT License");
1099
+ console.log(" This software is provided 'as-is' without any warranty.");
1100
+ console.log(" See LICENSE file for full license text.");
1101
+ console.log("=".repeat(70));
1102
+ console.log();
1103
+
1104
+ const args = process.argv.slice(2);
1105
+ const command = args[0] || 'help';
1106
+
1107
+ const coordinator = new SessionCoordinator();
1108
+
1109
+ switch (command) {
1110
+ case 'create': {
1111
+ // Create a new session
1112
+ const task = args.includes('--task') ?
1113
+ args[args.indexOf('--task') + 1] :
1114
+ await coordinator.promptForTask();
1115
+
1116
+ const agent = args.includes('--agent') ?
1117
+ args[args.indexOf('--agent') + 1] :
1118
+ 'claude';
1119
+
1120
+ await coordinator.createSession({ task, agent });
1121
+ break;
1122
+ }
1123
+
1124
+ case 'start': {
1125
+ // Start agent for a session
1126
+ const sessionId = args[1];
1127
+ if (!sessionId) {
1128
+ console.error('Usage: start <session-id>');
1129
+ process.exit(1);
1130
+ }
1131
+ await coordinator.startAgent(sessionId);
1132
+ break;
1133
+ }
1134
+
1135
+ case 'create-and-start': {
1136
+ // Create session and immediately start agent
1137
+ const task = args.includes('--task') ?
1138
+ args[args.indexOf('--task') + 1] :
1139
+ await coordinator.promptForTask();
1140
+
1141
+ const agent = args.includes('--agent') ?
1142
+ args[args.indexOf('--agent') + 1] :
1143
+ 'claude';
1144
+
1145
+ await coordinator.createAndStart({ task, agent });
1146
+ break;
1147
+ }
1148
+
1149
+ case 'request': {
1150
+ // Request a session (for Claude to call)
1151
+ const agent = args[1] || 'claude';
1152
+ await coordinator.requestSession(agent);
1153
+ break;
1154
+ }
1155
+
1156
+ case 'list': {
1157
+ coordinator.listSessions();
1158
+ break;
1159
+ }
1160
+
1161
+ case 'help':
1162
+ default: {
1163
+ console.log(`
1164
+ ${CONFIG.colors.bright}DevOps Session Coordinator${CONFIG.colors.reset}
1165
+
1166
+ ${CONFIG.colors.blue}Usage:${CONFIG.colors.reset}
1167
+ node session-coordinator.js <command> [options]
1168
+
1169
+ ${CONFIG.colors.blue}Commands:${CONFIG.colors.reset}
1170
+ ${CONFIG.colors.green}create${CONFIG.colors.reset} Create a new session and show instructions
1171
+ ${CONFIG.colors.green}start <id>${CONFIG.colors.reset} Start DevOps agent for a session
1172
+ ${CONFIG.colors.green}create-and-start${CONFIG.colors.reset} Create session and start agent (all-in-one)
1173
+ ${CONFIG.colors.green}request [agent]${CONFIG.colors.reset} Request a session (for Claude to call)
1174
+ ${CONFIG.colors.green}list${CONFIG.colors.reset} List all active sessions
1175
+ ${CONFIG.colors.green}help${CONFIG.colors.reset} Show this help
1176
+
1177
+ ${CONFIG.colors.blue}Options:${CONFIG.colors.reset}
1178
+ --task <name> Task or feature name
1179
+ --agent <type> Agent type (claude, cline, copilot, etc.)
1180
+
1181
+ ${CONFIG.colors.blue}Examples:${CONFIG.colors.reset}
1182
+ ${CONFIG.colors.dim}# Workflow 1: Manual coordination${CONFIG.colors.reset}
1183
+ node session-coordinator.js create --task "auth-feature"
1184
+ ${CONFIG.colors.dim}# Copy instructions to Claude${CONFIG.colors.reset}
1185
+ node session-coordinator.js start <session-id>
1186
+
1187
+ ${CONFIG.colors.dim}# Workflow 2: All-in-one${CONFIG.colors.reset}
1188
+ node session-coordinator.js create-and-start --task "api-endpoints"
1189
+
1190
+ ${CONFIG.colors.dim}# Workflow 3: Claude requests a session${CONFIG.colors.reset}
1191
+ node session-coordinator.js request claude
1192
+
1193
+ ${CONFIG.colors.yellow}Typical Workflow:${CONFIG.colors.reset}
1194
+ 1. Run: ${CONFIG.colors.green}node session-coordinator.js create-and-start${CONFIG.colors.reset}
1195
+ 2. Copy the displayed instructions to Claude/Cline
1196
+ 3. Claude navigates to the worktree and starts working
1197
+ 4. Agent automatically commits and pushes changes
1198
+ `);
1199
+ }
1200
+ }
1201
+ }
1202
+
1203
+ // Run the CLI
1204
+ main().catch(err => {
1205
+ console.error(`${CONFIG.colors.red}Error: ${err.message}${CONFIG.colors.reset}`);
1206
+ process.exit(1);
1207
+ });