autofix-branch.js
1 #!/usr/bin/env node 2 3 /** 4 * Auto-Fix Branch Manager 5 * 6 * All automated maintenance tasks (sage, lint, deps, security, docs) commit 7 * directly to main. The separate autofix branch workflow has been retired. 8 * 9 * ensureAutofixBranch() now simply ensures we are on main. 10 */ 11 12 import { execSync } from 'child_process'; 13 import { existsSync, readFileSync } from 'fs'; 14 import { join } from 'path'; 15 import { fileURLToPath } from 'url'; 16 import { dirname } from 'path'; 17 18 const __filename = fileURLToPath(import.meta.url); 19 const __dirname = dirname(__filename); 20 const projectRoot = join(__dirname, '..'); 21 22 const BRANCH_NAME = 'main'; 23 24 /** 25 * Check if we're in a git repository 26 */ 27 export function isGitRepo() { 28 try { 29 execSync('git rev-parse --git-dir', { cwd: projectRoot, stdio: 'ignore' }); 30 return true; 31 } catch { 32 return false; 33 } 34 } 35 36 /** 37 * Get current git branch 38 */ 39 export function getCurrentBranch() { 40 try { 41 return execSync('git branch --show-current', { 42 cwd: projectRoot, 43 encoding: 'utf-8', 44 }).trim(); 45 } catch { 46 return null; 47 } 48 } 49 50 /** 51 * Check if branch exists 52 */ 53 export function branchExists(branchName) { 54 try { 55 execSync(`git rev-parse --verify ${branchName}`, { cwd: projectRoot, stdio: 'ignore' }); 56 return true; 57 } catch { 58 return false; 59 } 60 } 61 62 /** 63 * Get the main branch name (main or master) 64 */ 65 export function getMainBranch() { 66 try { 67 // Check if 'main' exists 68 execSync('git rev-parse --verify main', { cwd: projectRoot, stdio: 'ignore' }); 69 return 'main'; 70 } catch { 71 try { 72 // Fall back to 'master' 73 execSync('git rev-parse --verify master', { cwd: projectRoot, stdio: 'ignore' }); 74 return 'master'; 75 } catch { 76 return 'main'; // Default fallback 77 } 78 } 79 } 80 81 /** 82 * Ensure we're on main, ready for automated commits. 83 * 84 * @returns {Object} - { branch: string, switched: boolean } 85 */ 86 export function ensureAutofixBranch() { 87 if (!isGitRepo()) { 88 throw new Error('Not a git repository'); 89 } 90 91 const currentBranch = getCurrentBranch(); 92 const mainBranch = getMainBranch(); 93 94 if (currentBranch !== mainBranch) { 95 execSync(`git checkout ${mainBranch}`, { 96 cwd: projectRoot, 97 stdio: 'inherit', 98 }); 99 } 100 101 return { 102 branch: mainBranch, 103 switched: currentBranch !== mainBranch, 104 message: `On ${mainBranch} â ready for automated commits`, 105 }; 106 } 107 108 /** 109 * Commit changes with a standardized format 110 * 111 * @param {string} type - Type of fix (sage, deps, lint, security, docs, etc.) 112 * @param {string} summary - Brief summary of changes 113 * @param {Object} details - Additional details to include in commit body 114 */ 115 export function commitAutofix(type, summary, details = {}) { 116 // Check if there are changes to commit 117 try { 118 const status = execSync('git status --porcelain', { 119 cwd: projectRoot, 120 encoding: 'utf-8', 121 }); 122 123 if (!status.trim()) { 124 return { 125 committed: false, 126 message: 'No changes to commit', 127 }; 128 } 129 } catch (error) { 130 throw new Error(`Failed to check git status: ${error.message}`); 131 } 132 133 // Stage all changes 134 execSync('git add -A', { cwd: projectRoot }); 135 136 // Build commit message 137 const detailsText = Object.entries(details) 138 .map(([key, value]) => `${key}: ${value}`) 139 .join('\n'); 140 141 const commitMessage = `fix(${type}): ${summary} 142 143 ${detailsText} 144 145 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>`; 146 147 // Commit 148 execSync(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, { 149 cwd: projectRoot, 150 stdio: 'inherit', 151 }); 152 153 return { 154 committed: true, 155 message: `Committed ${type} fixes to ${getMainBranch()} branch`, 156 }; 157 } 158 159 /** 160 * Get summary of recent commits on main (autofix branch no longer used). 161 */ 162 export function getAutofixSummary() { 163 if (!isGitRepo()) { 164 return { exists: false, commits: [], filesChanged: 0 }; 165 } 166 167 const mainBranch = getMainBranch(); 168 169 try { 170 const commits = execSync( 171 `git log ${mainBranch} --pretty=format:"%h|%s|%ar" --no-merges -20`, 172 { cwd: projectRoot, encoding: 'utf-8' } 173 ) 174 .trim() 175 .split('\n') 176 .filter(Boolean) 177 .map(line => { 178 const [hash, subject, date] = line.split('|'); 179 return { hash, subject, date }; 180 }); 181 182 return { exists: true, branch: mainBranch, commits }; 183 } catch (error) { 184 return { exists: true, error: error.message }; 185 } 186 } 187 188 /** 189 * No-op: autofix branch no longer used. Commits go directly to main. 190 */ 191 export function deleteAutofixBranch() { 192 return { deleted: false, message: 'autofix branch retired â commits go directly to main' }; 193 } 194 195 // CLI commands 196 if (import.meta.url === `file://${process.argv[1]}`) { 197 const command = process.argv[2]; 198 199 try { 200 switch (command) { 201 case 'ensure': { 202 console.log('đ Ensuring we are on main...'); 203 const result = ensureAutofixBranch(); 204 console.log(`â ${result.message}`); 205 break; 206 } 207 208 case 'summary': { 209 console.log('đ Recent Commits on main\n'); 210 const summary = getAutofixSummary(); 211 if (!summary.exists) { 212 console.log('âšī¸ Not a git repo'); 213 } else if (summary.error) { 214 console.error(`â Error: ${summary.error}`); 215 } else { 216 console.log(`Branch: ${summary.branch}`); 217 if (summary.commits.length > 0) { 218 console.log('Recent Commits:'); 219 summary.commits.forEach(commit => { 220 console.log(` ${commit.hash} - ${commit.subject} (${commit.date})`); 221 }); 222 } 223 } 224 break; 225 } 226 227 case 'delete': { 228 const deleteResult = deleteAutofixBranch(); 229 console.log(`âšī¸ ${deleteResult.message}`); 230 break; 231 } 232 233 case 'help': 234 default: 235 console.log(` 236 Usage: node scripts/autofix-branch.js <command> 237 238 Commands: 239 ensure - Ensure we are on main (autofix branch retired) 240 summary - Show recent commits on main 241 delete - No-op (autofix branch retired) 242 help - Show this help message 243 244 Examples: 245 node scripts/autofix-branch.js ensure 246 node scripts/autofix-branch.js summary 247 `); 248 } 249 } catch (error) { 250 console.error(`â Error: ${error.message}`); 251 process.exit(1); 252 } 253 }