22// Git & Branch Name Services
33// ============================================================================
44
5+ import { nanoid } from 'nanoid' ;
56import { formatShellError , type ShellError } from '../utils' ;
67
78export interface RepoInfo {
@@ -41,14 +42,22 @@ export async function getRepoInfo(): Promise<RepoInfo> {
4142 } ;
4243}
4344
44- function isValidBranchName ( name : string ) : boolean {
45+ function isValidBranchName ( name : string ) : [ boolean , string ] {
4546 // Must start with letter, contain only lowercase letters, numbers, hyphens
4647 // Must end with letter or number, max 50 chars
47- if ( name . length === 0 || name . length > 50 ) return false ;
48- if ( ! / ^ [ a - z ] [ a - z 0 - 9 - ] * [ a - z 0 - 9 ] $ / . test ( name ) && ! / ^ [ a - z ] $ / . test ( name ) )
49- return false ;
50- if ( name . includes ( '--' ) ) return false ; // No double hyphens
51- return true ;
48+ if ( ! name || name . length < 5 ) {
49+ return [ false , 'too short' ] ;
50+ }
51+ if ( name . length > 50 ) {
52+ return [ false , 'too long' ] ;
53+ }
54+ if ( ! / ^ [ a - z ] [ a - z 0 - 9 - ] * [ a - z 0 - 9 ] $ / . test ( name ) && ! / ^ [ a - z ] $ / . test ( name ) ) {
55+ return [ false , 'invalid characters' ] ;
56+ }
57+ if ( name . includes ( '--' ) ) {
58+ return [ false , 'double hyphens not allowed' ] ;
59+ }
60+ return [ true , '' ] ;
5261}
5362
5463async function getExistingBranches ( ) : Promise < string [ ] > {
@@ -112,14 +121,21 @@ export async function generateBranchName(
112121 let lastAttempt = '' ;
113122
114123 for ( let attempt = 1 ; attempt <= maxRetries ; attempt ++ ) {
115- let claudePrompt = `Generate a git branch name for the following task: ${ prompt }
124+ let claudePrompt = `Generate a git branch name for the following task:
125+
126+ <task>
127+ \`\`\`markdown
128+ ${ prompt . replace ( / ` ` ` / g, '\\`\\`\\`' ) }
129+ \`\`\`
130+ </task>
116131
117132Requirements:
118- - Output ONLY the branch name, nothing else
119133- Lowercase letters, numbers, and hyphens only
120134- No special characters, spaces, or underscores
121135- Keep it concise (2-4 words max)
122- - Example format: add-user-auth, fix-login-bug` ;
136+ - Example format: add-user-auth, fix-login-bug
137+
138+ CRITICAL: Output ONLY the branch name, nothing else` ;
123139
124140 if ( allExistingNames . size > 0 ) {
125141 claudePrompt += `\n\nIMPORTANT: Do NOT use any of these names (they already exist):
@@ -140,13 +156,12 @@ ${[...allExistingNames].join(', ')}`;
140156 const branchName = result . trim ( ) . toLowerCase ( ) ;
141157
142158 // Clean up any quotes or extra whitespace
143- const cleaned = branchName . replace ( / [ ' " ] / g, '' ) . trim ( ) ;
159+ const cleaned = branchName . replace ( / [ ' " \n ] / g, '' ) . trim ( ) ;
144160
145- if ( ! isValidBranchName ( cleaned ) ) {
146- console . log (
147- ` Attempt ${ attempt } : '${ cleaned } ' is not a valid branch name` ,
148- ) ;
149- lastAttempt = cleaned ;
161+ const [ isValid , reason ] = isValidBranchName ( cleaned ) ;
162+ if ( ! isValid ) {
163+ console . log ( ` Attempt ${ attempt } is invalid (${ reason } )` ) ;
164+ lastAttempt = cleaned . slice ( 0 , 100 ) ;
150165 continue ;
151166 }
152167
@@ -160,7 +175,12 @@ ${[...allExistingNames].join(', ')}`;
160175 return cleaned ;
161176 }
162177
163- throw new Error (
164- `Failed to generate valid branch name after ${ maxRetries } attempts` ,
165- ) ;
178+ console . log ( ' Failed to generate a valid branch name, using a random name.' ) ;
179+ // Fallback: use a generic name with random suffix
180+ let fallbackName : string ;
181+ do {
182+ const randomSuffix = nanoid ( 12 ) . toLowerCase ( ) ;
183+ fallbackName = `hermes-branch-${ randomSuffix } ` ;
184+ } while ( allExistingNames . has ( fallbackName ) ) ;
185+ return fallbackName ;
166186}
0 commit comments