s9n-devops-agent 1.6.2 → 1.7.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,403 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Weekly Branch Consolidator
5
+ * Automatically consolidates daily branches into weekly branches to prevent branch proliferation
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const { execSync } = require('child_process');
11
+
12
+ // Configuration
13
+ const CONFIG = {
14
+ colors: {
15
+ reset: '\x1b[0m',
16
+ bright: '\x1b[1m',
17
+ red: '\x1b[31m',
18
+ green: '\x1b[32m',
19
+ yellow: '\x1b[33m',
20
+ blue: '\x1b[34m',
21
+ cyan: '\x1b[36m',
22
+ dim: '\x1b[2m'
23
+ }
24
+ };
25
+
26
+ class WeeklyConsolidator {
27
+ constructor() {
28
+ this.repoRoot = this.getRepoRoot();
29
+ this.projectSettingsPath = path.join(this.repoRoot, 'local_deploy', 'project-settings.json');
30
+ this.projectSettings = this.loadProjectSettings();
31
+ }
32
+
33
+ getRepoRoot() {
34
+ try {
35
+ return execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
36
+ } catch (error) {
37
+ console.error(`${CONFIG.colors.red}Error: Not in a git repository${CONFIG.colors.reset}`);
38
+ process.exit(1);
39
+ }
40
+ }
41
+
42
+ loadProjectSettings() {
43
+ try {
44
+ if (fs.existsSync(this.projectSettingsPath)) {
45
+ return JSON.parse(fs.readFileSync(this.projectSettingsPath, 'utf8'));
46
+ }
47
+ } catch (error) {
48
+ console.warn(`${CONFIG.colors.yellow}Warning: Could not load project settings${CONFIG.colors.reset}`);
49
+ }
50
+ return {};
51
+ }
52
+
53
+ /**
54
+ * Get all branches from remote
55
+ */
56
+ getAllBranches() {
57
+ try {
58
+ const output = execSync('git branch -r --format="%(refname:short)"', { encoding: 'utf8' });
59
+ return output.split('\n')
60
+ .filter(branch => branch.trim())
61
+ .map(branch => branch.replace('origin/', '').trim())
62
+ .filter(branch => !branch.includes('HEAD'));
63
+ } catch (error) {
64
+ console.error(`${CONFIG.colors.red}Error getting branches: ${error.message}${CONFIG.colors.reset}`);
65
+ return [];
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Get daily branches from the past week
71
+ */
72
+ getLastWeekDailyBranches() {
73
+ const allBranches = this.getAllBranches();
74
+ const dailyBranches = allBranches.filter(branch => branch.startsWith('daily/'));
75
+
76
+ // Calculate date range for last week (Monday to Sunday)
77
+ const now = new Date();
78
+ const lastSunday = new Date(now);
79
+ lastSunday.setDate(now.getDate() - now.getDay()); // Last Sunday
80
+ lastSunday.setHours(0, 0, 0, 0);
81
+
82
+ const weekAgo = new Date(lastSunday);
83
+ weekAgo.setDate(lastSunday.getDate() - 7); // Previous Sunday
84
+
85
+ console.log(`${CONFIG.colors.dim}Looking for daily branches between ${weekAgo.toISOString().split('T')[0]} and ${lastSunday.toISOString().split('T')[0]}${CONFIG.colors.reset}`);
86
+
87
+ const lastWeekBranches = dailyBranches.filter(branch => {
88
+ const dateStr = branch.replace('daily/', '');
89
+ const branchDate = new Date(dateStr + 'T00:00:00Z');
90
+ return branchDate >= weekAgo && branchDate < lastSunday;
91
+ }).sort();
92
+
93
+ return lastWeekBranches;
94
+ }
95
+
96
+ /**
97
+ * Generate weekly branch name from daily branches
98
+ */
99
+ generateWeeklyBranchName(dailyBranches) {
100
+ if (dailyBranches.length === 0) {
101
+ return null;
102
+ }
103
+
104
+ const firstDate = dailyBranches[0].replace('daily/', '');
105
+ const lastDate = dailyBranches[dailyBranches.length - 1].replace('daily/', '');
106
+
107
+ return `weekly/${firstDate}_to_${lastDate}`;
108
+ }
109
+
110
+ /**
111
+ * Check if a branch exists locally or remotely
112
+ */
113
+ async branchExists(branchName) {
114
+ try {
115
+ // Check local first
116
+ execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, { stdio: 'ignore' });
117
+ return true;
118
+ } catch {
119
+ try {
120
+ // Check remote
121
+ execSync(`git show-ref --verify --quiet refs/remotes/origin/${branchName}`, { stdio: 'ignore' });
122
+ return true;
123
+ } catch {
124
+ return false;
125
+ }
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Create a new branch from a base branch
131
+ */
132
+ async createBranch(branchName, baseBranch) {
133
+ try {
134
+ console.log(`${CONFIG.colors.blue}Creating branch: ${branchName} from ${baseBranch}${CONFIG.colors.reset}`);
135
+
136
+ // Fetch latest changes
137
+ execSync('git fetch --all --prune', { stdio: 'ignore' });
138
+
139
+ // Create and checkout new branch
140
+ execSync(`git checkout -b ${branchName} origin/${baseBranch}`, { stdio: 'ignore' });
141
+
142
+ // Push to remote
143
+ execSync(`git push -u origin ${branchName}`, { stdio: 'ignore' });
144
+
145
+ return true;
146
+ } catch (error) {
147
+ console.error(`${CONFIG.colors.red}Failed to create branch ${branchName}: ${error.message}${CONFIG.colors.reset}`);
148
+ return false;
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Merge one branch into another
154
+ */
155
+ async mergeBranch(sourceBranch, targetBranch) {
156
+ try {
157
+ console.log(`${CONFIG.colors.blue}Merging ${sourceBranch} → ${targetBranch}${CONFIG.colors.reset}`);
158
+
159
+ // Checkout target branch
160
+ execSync(`git checkout ${targetBranch}`, { stdio: 'ignore' });
161
+
162
+ // Pull latest changes
163
+ execSync(`git pull origin ${targetBranch}`, { stdio: 'ignore' });
164
+
165
+ // Merge source branch
166
+ const mergeMessage = `Merge daily branch ${sourceBranch} into weekly consolidation`;
167
+ execSync(`git merge --no-ff origin/${sourceBranch} -m "${mergeMessage}"`, { stdio: 'ignore' });
168
+
169
+ // Push the merge
170
+ execSync(`git push origin ${targetBranch}`, { stdio: 'ignore' });
171
+
172
+ console.log(`${CONFIG.colors.green}✓ Successfully merged ${sourceBranch} → ${targetBranch}${CONFIG.colors.reset}`);
173
+ return true;
174
+ } catch (error) {
175
+ console.error(`${CONFIG.colors.red}✗ Failed to merge ${sourceBranch} → ${targetBranch}${CONFIG.colors.reset}`);
176
+ console.error(`${CONFIG.colors.dim}Error: ${error.message}${CONFIG.colors.reset}`);
177
+
178
+ // Reset any partial merge state
179
+ try {
180
+ execSync('git merge --abort', { stdio: 'ignore' });
181
+ } catch {}
182
+
183
+ return false;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Delete branches after successful consolidation
189
+ */
190
+ async cleanupDailyBranches(dailyBranches) {
191
+ console.log(`\n${CONFIG.colors.bright}Cleaning up consolidated daily branches...${CONFIG.colors.reset}`);
192
+
193
+ for (const branch of dailyBranches) {
194
+ try {
195
+ console.log(`${CONFIG.colors.blue}Deleting branch: ${branch}${CONFIG.colors.reset}`);
196
+
197
+ // Delete local branch if it exists
198
+ try {
199
+ execSync(`git branch -D ${branch}`, { stdio: 'ignore' });
200
+ } catch {}
201
+
202
+ // Delete remote branch
203
+ execSync(`git push origin --delete ${branch}`, { stdio: 'ignore' });
204
+
205
+ console.log(`${CONFIG.colors.green}✓ Deleted ${branch}${CONFIG.colors.reset}`);
206
+ } catch (error) {
207
+ console.error(`${CONFIG.colors.red}✗ Failed to delete ${branch}: ${error.message}${CONFIG.colors.reset}`);
208
+ }
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Perform dual merge to target branch if enabled
214
+ */
215
+ async performDualMergeToTarget(weeklyBranch) {
216
+ const enableDualMerge = this.projectSettings?.branchManagement?.enableDualMerge;
217
+ const targetBranch = this.projectSettings?.branchManagement?.defaultMergeTarget;
218
+
219
+ if (!enableDualMerge || !targetBranch) {
220
+ console.log(`${CONFIG.colors.dim}Dual merge not enabled or target branch not configured${CONFIG.colors.reset}`);
221
+ return true;
222
+ }
223
+
224
+ console.log(`\n${CONFIG.colors.bright}Performing dual merge to target branch...${CONFIG.colors.reset}`);
225
+
226
+ try {
227
+ const success = await this.mergeBranch(weeklyBranch, targetBranch);
228
+ if (success) {
229
+ console.log(`${CONFIG.colors.green}✓ Weekly branch merged to target: ${weeklyBranch} → ${targetBranch}${CONFIG.colors.reset}`);
230
+ }
231
+ return success;
232
+ } catch (error) {
233
+ console.error(`${CONFIG.colors.red}✗ Failed to merge weekly branch to target${CONFIG.colors.reset}`);
234
+ return false;
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Main consolidation process
240
+ */
241
+ async consolidateWeeklyBranches() {
242
+ console.log(`\n${CONFIG.colors.bright}${CONFIG.colors.blue}Weekly Branch Consolidation${CONFIG.colors.reset}`);
243
+ console.log(`${CONFIG.colors.dim}Repository: ${this.repoRoot}${CONFIG.colors.reset}\n`);
244
+
245
+ // Check if weekly consolidation is enabled
246
+ const enableWeeklyConsolidation = this.projectSettings?.branchManagement?.enableWeeklyConsolidation;
247
+ if (enableWeeklyConsolidation === false) {
248
+ console.log(`${CONFIG.colors.yellow}Weekly consolidation is disabled in project settings${CONFIG.colors.reset}`);
249
+ return;
250
+ }
251
+
252
+ // Get daily branches from last week
253
+ const lastWeekDailies = this.getLastWeekDailyBranches();
254
+
255
+ if (lastWeekDailies.length === 0) {
256
+ console.log(`${CONFIG.colors.yellow}No daily branches found for consolidation${CONFIG.colors.reset}`);
257
+ return;
258
+ }
259
+
260
+ console.log(`${CONFIG.colors.bright}Found ${lastWeekDailies.length} daily branches to consolidate:${CONFIG.colors.reset}`);
261
+ lastWeekDailies.forEach(branch => {
262
+ console.log(` • ${branch}`);
263
+ });
264
+
265
+ // Generate weekly branch name
266
+ const weeklyBranchName = this.generateWeeklyBranchName(lastWeekDailies);
267
+ console.log(`\n${CONFIG.colors.bright}Creating weekly branch: ${weeklyBranchName}${CONFIG.colors.reset}`);
268
+
269
+ // Check if weekly branch already exists
270
+ if (await this.branchExists(weeklyBranchName)) {
271
+ console.log(`${CONFIG.colors.yellow}Weekly branch ${weeklyBranchName} already exists, skipping consolidation${CONFIG.colors.reset}`);
272
+ return;
273
+ }
274
+
275
+ try {
276
+ // Create weekly branch from first daily branch
277
+ const success = await this.createBranch(weeklyBranchName, lastWeekDailies[0]);
278
+ if (!success) {
279
+ throw new Error('Failed to create weekly branch');
280
+ }
281
+
282
+ // Merge remaining daily branches into weekly branch
283
+ let allMergesSuccessful = true;
284
+ for (let i = 1; i < lastWeekDailies.length; i++) {
285
+ const mergeSuccess = await this.mergeBranch(lastWeekDailies[i], weeklyBranchName);
286
+ if (!mergeSuccess) {
287
+ allMergesSuccessful = false;
288
+ console.error(`${CONFIG.colors.red}Stopping consolidation due to merge failure${CONFIG.colors.reset}`);
289
+ break;
290
+ }
291
+ }
292
+
293
+ if (!allMergesSuccessful) {
294
+ console.error(`${CONFIG.colors.red}❌ Weekly consolidation failed due to merge conflicts${CONFIG.colors.reset}`);
295
+ console.log(`${CONFIG.colors.dim}Weekly branch ${weeklyBranchName} created but not all dailies were merged${CONFIG.colors.reset}`);
296
+ console.log(`${CONFIG.colors.dim}Manual intervention required to resolve conflicts${CONFIG.colors.reset}`);
297
+ return;
298
+ }
299
+
300
+ // Perform dual merge to target branch if enabled
301
+ await this.performDualMergeToTarget(weeklyBranchName);
302
+
303
+ // Clean up daily branches after successful consolidation
304
+ await this.cleanupDailyBranches(lastWeekDailies);
305
+
306
+ console.log(`\n${CONFIG.colors.green}✅ Weekly consolidation completed successfully${CONFIG.colors.reset}`);
307
+ console.log(`${CONFIG.colors.bright}Weekly branch: ${weeklyBranchName}${CONFIG.colors.reset}`);
308
+ console.log(`${CONFIG.colors.dim}Consolidated ${lastWeekDailies.length} daily branches${CONFIG.colors.reset}`);
309
+
310
+ } catch (error) {
311
+ console.error(`\n${CONFIG.colors.red}❌ Weekly consolidation failed: ${error.message}${CONFIG.colors.reset}`);
312
+ throw error;
313
+ }
314
+ }
315
+
316
+ /**
317
+ * List existing weekly branches
318
+ */
319
+ listWeeklyBranches() {
320
+ const allBranches = this.getAllBranches();
321
+ const weeklyBranches = allBranches.filter(branch => branch.startsWith('weekly/')).sort();
322
+
323
+ if (weeklyBranches.length === 0) {
324
+ console.log(`${CONFIG.colors.yellow}No weekly branches found${CONFIG.colors.reset}`);
325
+ return;
326
+ }
327
+
328
+ console.log(`\n${CONFIG.colors.bright}Existing Weekly Branches:${CONFIG.colors.reset}`);
329
+ weeklyBranches.forEach(branch => {
330
+ console.log(` • ${branch}`);
331
+ });
332
+ }
333
+
334
+ /**
335
+ * Clean up old weekly branches (keep only recent ones)
336
+ */
337
+ async cleanupOldWeeklyBranches() {
338
+ const retainWeeks = this.projectSettings?.cleanup?.retainWeeklyBranches || 12;
339
+ const allBranches = this.getAllBranches();
340
+ const weeklyBranches = allBranches.filter(branch => branch.startsWith('weekly/')).sort();
341
+
342
+ if (weeklyBranches.length <= retainWeeks) {
343
+ console.log(`${CONFIG.colors.green}No old weekly branches to clean up (${weeklyBranches.length} <= ${retainWeeks})${CONFIG.colors.reset}`);
344
+ return;
345
+ }
346
+
347
+ const branchesToDelete = weeklyBranches.slice(0, weeklyBranches.length - retainWeeks);
348
+
349
+ console.log(`\n${CONFIG.colors.bright}Cleaning up old weekly branches (keeping ${retainWeeks} most recent):${CONFIG.colors.reset}`);
350
+
351
+ for (const branch of branchesToDelete) {
352
+ try {
353
+ console.log(`${CONFIG.colors.blue}Deleting old weekly branch: ${branch}${CONFIG.colors.reset}`);
354
+
355
+ // Delete local branch if it exists
356
+ try {
357
+ execSync(`git branch -D ${branch}`, { stdio: 'ignore' });
358
+ } catch {}
359
+
360
+ // Delete remote branch
361
+ execSync(`git push origin --delete ${branch}`, { stdio: 'ignore' });
362
+
363
+ console.log(`${CONFIG.colors.green}✓ Deleted ${branch}${CONFIG.colors.reset}`);
364
+ } catch (error) {
365
+ console.error(`${CONFIG.colors.red}✗ Failed to delete ${branch}: ${error.message}${CONFIG.colors.reset}`);
366
+ }
367
+ }
368
+ }
369
+
370
+ /**
371
+ * Main execution function
372
+ */
373
+ async run(command = 'consolidate') {
374
+ try {
375
+ switch (command) {
376
+ case 'consolidate':
377
+ await this.consolidateWeeklyBranches();
378
+ break;
379
+ case 'list':
380
+ this.listWeeklyBranches();
381
+ break;
382
+ case 'cleanup':
383
+ await this.cleanupOldWeeklyBranches();
384
+ break;
385
+ default:
386
+ console.log(`${CONFIG.colors.red}Unknown command: ${command}${CONFIG.colors.reset}`);
387
+ console.log('Available commands: consolidate, list, cleanup');
388
+ }
389
+ } catch (error) {
390
+ console.error(`${CONFIG.colors.red}❌ Operation failed: ${error.message}${CONFIG.colors.reset}`);
391
+ process.exit(1);
392
+ }
393
+ }
394
+ }
395
+
396
+ // CLI execution
397
+ if (require.main === module) {
398
+ const command = process.argv[2] || 'consolidate';
399
+ const consolidator = new WeeklyConsolidator();
400
+ consolidator.run(command);
401
+ }
402
+
403
+ module.exports = WeeklyConsolidator;
@@ -41,7 +41,7 @@ show_copyright() {
41
41
  echo "======================================================================"
42
42
  echo
43
43
  echo " CS_DevOpsAgent - Intelligent Git Automation System"
44
- echo " Version 1.6.2 | Build 20251009.14"
44
+ echo " Version 1.7.0 | Build 20251010.01"
45
45
  echo " "
46
46
  echo " Copyright (c) 2024 SecondBrain Labs"
47
47
  echo " Author: Sachin Dev Duggal"