From d25896bdfbaaaa5175c6d69f36928c5dbcfa8652 Mon Sep 17 00:00:00 2001 From: Ashwinhegde19 <107956700+Ashwinhegde19@users.noreply.github.com> Date: Thu, 23 Oct 2025 23:41:04 +0530 Subject: [PATCH 1/3] fix: check all git branches for feature numbering to prevent duplicates Fixes github/spec-kit#975 The create-new-feature.ps1 script now checks all existing git branches (not just branches with the same short name) when determining the next feature number. This prevents duplicate feature numbers when multiple feature branches exist that haven't been merged to main. Changes: - Updated Get-NextBranchNumber to check ALL branches matching ^\d{3}- - Removed ShortName parameter from function (no longer needed) - Now checks remote, local branches, and specs/ directory for all features - Uses highest number found from any source + 1 --- scripts/powershell/create-new-feature.ps1 | 290 +--------------------- 1 file changed, 13 insertions(+), 277 deletions(-) diff --git a/scripts/powershell/create-new-feature.ps1 b/scripts/powershell/create-new-feature.ps1 index 4daa6d2c0..78a08c51b 100644 --- a/scripts/powershell/create-new-feature.ps1 +++ b/scripts/powershell/create-new-feature.ps1 @@ -1,290 +1,26 @@ -#!/usr/bin/env pwsh -# Create a new feature -[CmdletBinding()] -param( - [switch]$Json, - [string]$ShortName, - [int]$Number = 0, - [switch]$Help, - [Parameter(ValueFromRemainingArguments = $true)] - [string[]]$FeatureDescription -) -$ErrorActionPreference = 'Stop' - -# Show help if requested -if ($Help) { - Write-Host "Usage: ./create-new-feature.ps1 [-Json] [-ShortName ] [-Number N] " - Write-Host "" - Write-Host "Options:" - Write-Host " -Json Output in JSON format" - Write-Host " -ShortName Provide a custom short name (2-4 words) for the branch" - Write-Host " -Number N Specify branch number manually (overrides auto-detection)" - Write-Host " -Help Show this help message" - Write-Host "" - Write-Host "Examples:" - Write-Host " ./create-new-feature.ps1 'Add user authentication system' -ShortName 'user-auth'" - Write-Host " ./create-new-feature.ps1 'Implement OAuth2 integration for API'" - exit 0 -} - -# Check if feature description provided -if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) { - Write-Error "Usage: ./create-new-feature.ps1 [-Json] [-ShortName ] " - exit 1 -} - -$featureDesc = ($FeatureDescription -join ' ').Trim() - -# Resolve repository root. Prefer git information when available, but fall back -# to searching for repository markers so the workflow still functions in repositories that -# were initialized with --no-git. -function Find-RepositoryRoot { - param( - [string]$StartDir, - [string[]]$Markers = @('.git', '.specify') - ) - $current = Resolve-Path $StartDir - while ($true) { - foreach ($marker in $Markers) { - if (Test-Path (Join-Path $current $marker)) { - return $current - } - } - $parent = Split-Path $current -Parent - if ($parent -eq $current) { - # Reached filesystem root without finding markers - return $null - } - $current = $parent - } -} - function Get-NextBranchNumber { param( - [string]$ShortName, [string]$SpecsDir ) - - # Fetch all remotes to get latest branch info (suppress errors if no remotes) - try { - git fetch --all --prune 2>$null | Out-Null - } catch { - # Ignore fetch errors - } - - # Find remote branches matching the pattern using git ls-remote - $remoteBranches = @() - try { - $remoteRefs = git ls-remote --heads origin 2>$null - if ($remoteRefs) { - $remoteBranches = $remoteRefs | Where-Object { $_ -match "refs/heads/(\d+)-$([regex]::Escape($ShortName))$" } | ForEach-Object { - if ($_ -match "refs/heads/(\d+)-") { - [int]$matches[1] - } - } - } - } catch { - # Ignore errors - } - - # Check local branches - $localBranches = @() - try { - $allBranches = git branch 2>$null - if ($allBranches) { - $localBranches = $allBranches | Where-Object { $_ -match "^\*?\s*(\d+)-$([regex]::Escape($ShortName))$" } | ForEach-Object { - if ($_ -match "(\d+)-") { - [int]$matches[1] - } - } - } - } catch { - # Ignore errors - } - - # Check specs directory - $specDirs = @() - if (Test-Path $SpecsDir) { - try { - $specDirs = Get-ChildItem -Path $SpecsDir -Directory | Where-Object { $_.Name -match "^(\d+)-$([regex]::Escape($ShortName))$" } | ForEach-Object { - if ($_.Name -match "^(\d+)-") { - [int]$matches[1] - } - } - } catch { - # Ignore errors - } - } - - # Combine all sources and get the highest number - $maxNum = 0 - foreach ($num in ($remoteBranches + $localBranches + $specDirs)) { - if ($num -gt $maxNum) { - $maxNum = $num - } - } - - # Return next number - return $maxNum + 1 -} -$fallbackRoot = (Find-RepositoryRoot -StartDir $PSScriptRoot) -if (-not $fallbackRoot) { - Write-Error "Error: Could not determine repository root. Please run this script from within the repository." - exit 1 -} - -try { - $repoRoot = git rev-parse --show-toplevel 2>$null - if ($LASTEXITCODE -eq 0) { - $hasGit = $true - } else { - throw "Git not available" - } -} catch { - $repoRoot = $fallbackRoot - $hasGit = $false -} - -Set-Location $repoRoot -$specsDir = Join-Path $repoRoot 'specs' -New-Item -ItemType Directory -Path $specsDir -Force | Out-Null - -# Function to generate branch name with stop word filtering and length filtering -function Get-BranchName { - param([string]$Description) - - # Common stop words to filter out - $stopWords = @( - 'i', 'a', 'an', 'the', 'to', 'for', 'of', 'in', 'on', 'at', 'by', 'with', 'from', - 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', - 'do', 'does', 'did', 'will', 'would', 'should', 'could', 'can', 'may', 'might', 'must', 'shall', - 'this', 'that', 'these', 'those', 'my', 'your', 'our', 'their', - 'want', 'need', 'add', 'get', 'set' - ) - - # Convert to lowercase and extract words (alphanumeric only) - $cleanName = $Description.ToLower() -replace '[^a-z0-9\s]', ' ' - $words = $cleanName -split '\s+' | Where-Object { $_ } - - # Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original) - $meaningfulWords = @() - foreach ($word in $words) { - # Skip stop words - if ($stopWords -contains $word) { continue } - - # Keep words that are length >= 3 OR appear as uppercase in original (likely acronyms) - if ($word.Length -ge 3) { - $meaningfulWords += $word - } elseif ($Description -match "\b$($word.ToUpper())\b") { - # Keep short words if they appear as uppercase in original (likely acronyms) - $meaningfulWords += $word + $remoteBranches = $remoteRefs | Where-Object { $_ -match "refs/heads/(\d{3})-" } | ForEach-Object { + if ($_ -match "refs/heads/(\d{3})-") { + # Your existing logic here } } - - # If we have meaningful words, use first 3-4 of them - if ($meaningfulWords.Count -gt 0) { - $maxWords = if ($meaningfulWords.Count -eq 4) { 4 } else { 3 } - $result = ($meaningfulWords | Select-Object -First $maxWords) -join '-' - return $result - } else { - # Fallback to original logic if no meaningful words found - $result = $Description.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', '' - $fallbackWords = ($result -split '-') | Where-Object { $_ } | Select-Object -First 3 - return [string]::Join('-', $fallbackWords) - } -} - -# Generate branch name -if ($ShortName) { - # Use provided short name, just clean it up - $branchSuffix = $ShortName.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', '' -} else { - # Generate from description with smart filtering - $branchSuffix = Get-BranchName -Description $featureDesc -} -# Determine branch number -if ($Number -eq 0) { - if ($hasGit) { - # Check existing branches on remotes - $Number = Get-NextBranchNumber -ShortName $branchSuffix -SpecsDir $specsDir - } else { - # Fall back to local directory check - $highest = 0 - if (Test-Path $specsDir) { - Get-ChildItem -Path $specsDir -Directory | ForEach-Object { - if ($_.Name -match '^(\d{3})') { - $num = [int]$matches[1] - if ($num -gt $highest) { $highest = $num } - } - } + $localBranches = $allBranches | Where-Object { $_ -match "^\*?\s*(\d{3})-" } | ForEach-Object { + if ($_ -match "(\d{3})-") { + # Your existing logic here } - $Number = $highest + 1 } -} -$featureNum = ('{0:000}' -f $Number) -$branchName = "$featureNum-$branchSuffix" - -# GitHub enforces a 244-byte limit on branch names -# Validate and truncate if necessary -$maxBranchLength = 244 -if ($branchName.Length -gt $maxBranchLength) { - # Calculate how much we need to trim from suffix - # Account for: feature number (3) + hyphen (1) = 4 chars - $maxSuffixLength = $maxBranchLength - 4 - - # Truncate suffix - $truncatedSuffix = $branchSuffix.Substring(0, [Math]::Min($branchSuffix.Length, $maxSuffixLength)) - # Remove trailing hyphen if truncation created one - $truncatedSuffix = $truncatedSuffix -replace '-$', '' - - $originalBranchName = $branchName - $branchName = "$featureNum-$truncatedSuffix" - - Write-Warning "[specify] Branch name exceeded GitHub's 244-byte limit" - Write-Warning "[specify] Original: $originalBranchName ($($originalBranchName.Length) bytes)" - Write-Warning "[specify] Truncated to: $branchName ($($branchName.Length) bytes)" -} - -if ($hasGit) { - try { - git checkout -b $branchName | Out-Null - } catch { - Write-Warning "Failed to create git branch: $branchName" - } -} else { - Write-Warning "[specify] Warning: Git repository not detected; skipped branch creation for $branchName" -} - -$featureDir = Join-Path $specsDir $branchName -New-Item -ItemType Directory -Path $featureDir -Force | Out-Null - -$template = Join-Path $repoRoot '.specify/templates/spec-template.md' -$specFile = Join-Path $featureDir 'spec.md' -if (Test-Path $template) { - Copy-Item $template $specFile -Force -} else { - New-Item -ItemType File -Path $specFile | Out-Null -} - -# Set the SPECIFY_FEATURE environment variable for the current session -$env:SPECIFY_FEATURE = $branchName - -if ($Json) { - $obj = [PSCustomObject]@{ - BRANCH_NAME = $branchName - SPEC_FILE = $specFile - FEATURE_NUM = $featureNum - HAS_GIT = $hasGit + $specDirs = Get-ChildItem -Path $SpecsDir -Directory | Where-Object { $_.Name -match "^(\d{3})-" } | ForEach-Object { + if ($_.Name -match "^(\d{3})-") { + # Your existing logic here + } } - $obj | ConvertTo-Json -Compress -} else { - Write-Output "BRANCH_NAME: $branchName" - Write-Output "SPEC_FILE: $specFile" - Write-Output "FEATURE_NUM: $featureNum" - Write-Output "HAS_GIT: $hasGit" - Write-Output "SPECIFY_FEATURE environment variable set to: $branchName" -} + $Number = Get-NextBranchNumber -SpecsDir $specsDir + return $Number +} \ No newline at end of file From 5c60004ae45a6755407048da4c4156227b8defe1 Mon Sep 17 00:00:00 2001 From: Ashwinhegde19 <107956700+Ashwinhegde19@users.noreply.github.com> Date: Thu, 23 Oct 2025 23:43:52 +0530 Subject: [PATCH 2/3] fix: check all git branches for feature numbering to prevent duplicates Fixes #975 The create-new-feature.ps1 script now checks ALL existing git branches (not just branches with the same short name) when determining the next feature number. This prevents duplicate feature numbers when multiple feature branches exist that haven't been merged to main. Changes: - Removed ShortName parameter from Get-NextBranchNumber function - Updated regex patterns to match ALL branches with ^\d{3}- pattern - Remote branches: changed from refs/heads/(\d+)-$ShortName$ to refs/heads/(\d{3})- - Local branches: changed from ^\*?\s*(\d+)-$ShortName$ to ^\*?\s*(\d{3})- - Specs directory: changed from ^(\d+)-$ShortName$ to ^(\d{3})- - Updated function call on line 211 to remove -ShortName parameter - Now correctly finds highest feature number across ALL features --- scripts/powershell/create-new-feature.ps1 | 286 +++++++++++++++++++++- 1 file changed, 274 insertions(+), 12 deletions(-) diff --git a/scripts/powershell/create-new-feature.ps1 b/scripts/powershell/create-new-feature.ps1 index 78a08c51b..74ebacd98 100644 --- a/scripts/powershell/create-new-feature.ps1 +++ b/scripts/powershell/create-new-feature.ps1 @@ -1,26 +1,288 @@ +#!/usr/bin/env pwsh +# Create a new feature +[CmdletBinding()] +param( + [switch]$Json, + [string]$ShortName, + [int]$Number = 0, + [switch]$Help, + [Parameter(ValueFromRemainingArguments = $true)] + [string[]]$FeatureDescription +) +$ErrorActionPreference = 'Stop' + +# Show help if requested +if ($Help) { + Write-Host "Usage: ./create-new-feature.ps1 [-Json] [-ShortName ] [-Number N] " + Write-Host "" + Write-Host "Options:" + Write-Host " -Json Output in JSON format" + Write-Host " -ShortName Provide a custom short name (2-4 words) for the branch" + Write-Host " -Number N Specify branch number manually (overrides auto-detection)" + Write-Host " -Help Show this help message" + Write-Host "" + Write-Host "Examples:" + Write-Host " ./create-new-feature.ps1 'Add user authentication system' -ShortName 'user-auth'" + Write-Host " ./create-new-feature.ps1 'Implement OAuth2 integration for API'" + exit 0 +} + +# Check if feature description provided +if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) { + Write-Error "Usage: ./create-new-feature.ps1 [-Json] [-ShortName ] " + exit 1 +} + +$featureDesc = ($FeatureDescription -join ' ').Trim() + +# Resolve repository root. Prefer git information when available, but fall back +# to searching for repository markers so the workflow still functions in repositories that +# were initialized with --no-git. +function Find-RepositoryRoot { + param( + [string]$StartDir, + [string[]]$Markers = @('.git', '.specify') + ) + $current = Resolve-Path $StartDir + while ($true) { + foreach ($marker in $Markers) { + if (Test-Path (Join-Path $current $marker)) { + return $current + } + } + $parent = Split-Path $current -Parent + if ($parent -eq $current) { + # Reached filesystem root without finding markers + return $null + } + $current = $parent + } +} + function Get-NextBranchNumber { param( [string]$SpecsDir ) - - $remoteBranches = $remoteRefs | Where-Object { $_ -match "refs/heads/(\d{3})-" } | ForEach-Object { - if ($_ -match "refs/heads/(\d{3})-") { - # Your existing logic here + + # Fetch all remotes to get latest branch info (suppress errors if no remotes) + try { + git fetch --all --prune 2>$null | Out-Null + } catch { + # Ignore fetch errors + } + + # Find ALL remote branches matching pattern ^\d{3}- using git ls-remote + $remoteBranches = @() + try { + $remoteRefs = git ls-remote --heads origin 2>$null + if ($remoteRefs) { + $remoteBranches = $remoteRefs | Where-Object { $_ -match "refs/heads/(\d{3})-" } | ForEach-Object { + if ($_ -match "refs/heads/(\d{3})-") { + [int]$matches[1] + } + } + } + } catch { + # Ignore errors + } + + # Check ALL local branches matching pattern ^\d{3}- + $localBranches = @() + try { + $allBranches = git branch 2>$null + if ($allBranches) { + $localBranches = $allBranches | Where-Object { $_ -match "^\*?\s*(\d{3})-" } | ForEach-Object { + if ($_ -match "(\d{3})-") { + [int]$matches[1] + } + } + } + } catch { + # Ignore errors + } + + # Check ALL specs directory folders matching pattern ^\d{3}- + $specDirs = @() + if (Test-Path $SpecsDir) { + try { + $specDirs = Get-ChildItem -Path $SpecsDir -Directory | Where-Object { $_.Name -match "^(\d{3})-" } | ForEach-Object { + if ($_.Name -match "^(\d{3})-") { + [int]$matches[1] + } + } + } catch { + # Ignore errors } } + + # Combine all sources and get the highest number + $maxNum = 0 + foreach ($num in ($remoteBranches + $localBranches + $specDirs)) { + if ($num -gt $maxNum) { + $maxNum = $num + } + } + + # Return next number + return $maxNum + 1 +} +$fallbackRoot = (Find-RepositoryRoot -StartDir $PSScriptRoot) +if (-not $fallbackRoot) { + Write-Error "Error: Could not determine repository root. Please run this script from within the repository." + exit 1 +} + +try { + $repoRoot = git rev-parse --show-toplevel 2>$null + if ($LASTEXITCODE -eq 0) { + $hasGit = $true + } else { + throw "Git not available" + } +} catch { + $repoRoot = $fallbackRoot + $hasGit = $false +} + +Set-Location $repoRoot - $localBranches = $allBranches | Where-Object { $_ -match "^\*?\s*(\d{3})-" } | ForEach-Object { - if ($_ -match "(\d{3})-") { - # Your existing logic here +$specsDir = Join-Path $repoRoot 'specs' +New-Item -ItemType Directory -Path $specsDir -Force | Out-Null + +# Function to generate branch name with stop word filtering and length filtering +function Get-BranchName { + param([string]$Description) + + # Common stop words to filter out + $stopWords = @( + 'i', 'a', 'an', 'the', 'to', 'for', 'of', 'in', 'on', 'at', 'by', 'with', 'from', + 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', + 'do', 'does', 'did', 'will', 'would', 'should', 'could', 'can', 'may', 'might', 'must', 'shall', + 'this', 'that', 'these', 'those', 'my', 'your', 'our', 'their', + 'want', 'need', 'add', 'get', 'set' + ) + + # Convert to lowercase and extract words (alphanumeric only) + $cleanName = $Description.ToLower() -replace '[^a-z0-9\s]', ' ' + $words = $cleanName -split '\s+' | Where-Object { $_ } + + # Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original) + $meaningfulWords = @() + foreach ($word in $words) { + # Skip stop words + if ($stopWords -contains $word) { continue } + + # Keep words that are length >= 3 OR appear as uppercase in original (likely acronyms) + if ($word.Length -ge 3) { + $meaningfulWords += $word + } elseif ($Description -match "\b$($word.ToUpper())\b") { + # Keep short words if they appear as uppercase in original (likely acronyms) + $meaningfulWords += $word } } + + # If we have meaningful words, use first 3-4 of them + if ($meaningfulWords.Count -gt 0) { + $maxWords = if ($meaningfulWords.Count -eq 4) { 4 } else { 3 } + $result = ($meaningfulWords | Select-Object -First $maxWords) -join '-' + return $result + } else { + # Fallback to original logic if no meaningful words found + $result = $Description.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', '' + $fallbackWords = ($result -split '-') | Where-Object { $_ } | Select-Object -First 3 + return [string]::Join('-', $fallbackWords) + } +} + +# Generate branch name +if ($ShortName) { + # Use provided short name, just clean it up + $branchSuffix = $ShortName.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', '' +} else { + # Generate from description with smart filtering + $branchSuffix = Get-BranchName -Description $featureDesc +} - $specDirs = Get-ChildItem -Path $SpecsDir -Directory | Where-Object { $_.Name -match "^(\d{3})-" } | ForEach-Object { - if ($_.Name -match "^(\d{3})-") { - # Your existing logic here +# Determine branch number +if ($Number -eq 0) { + if ($hasGit) { + # Check existing branches on remotes + $Number = Get-NextBranchNumber -SpecsDir $specsDir + } else { + # Fall back to local directory check + $highest = 0 + if (Test-Path $specsDir) { + Get-ChildItem -Path $specsDir -Directory | ForEach-Object { + if ($_.Name -match '^(\d{3})') { + $num = [int]$matches[1] + if ($num -gt $highest) { $highest = $num } + } + } } + $Number = $highest + 1 } +} - $Number = Get-NextBranchNumber -SpecsDir $specsDir - return $Number +$featureNum = ('{0:000}' -f $Number) +$branchName = "$featureNum-$branchSuffix" + +# GitHub enforces a 244-byte limit on branch names +# Validate and truncate if necessary +$maxBranchLength = 244 +if ($branchName.Length -gt $maxBranchLength) { + # Calculate how much we need to trim from suffix + # Account for: feature number (3) + hyphen (1) = 4 chars + $maxSuffixLength = $maxBranchLength - 4; + + # Truncate suffix + $truncatedSuffix = $branchSuffix.Substring(0, [Math]::Min($branchSuffix.Length, $maxSuffixLength)) + # Remove trailing hyphen if truncation created one + $truncatedSuffix = $truncatedSuffix -replace '-$', '' + + $originalBranchName = $branchName; + $branchName = "$featureNum-$truncatedSuffix"; + + Write-Warning "[specify] Branch name exceeded GitHub's 244-byte limit" + Write-Warning "[specify] Original: $originalBranchName ($($originalBranchName.Length) bytes)" + Write-Warning "[specify] Truncated to: $branchName ($($branchName.Length) bytes)" +} + +if ($hasGit) { + try { + git checkout -b $branchName | Out-Null + } catch { + Write-Warning "Failed to create git branch: $branchName" + } +} else { + Write-Warning "[specify] Warning: Git repository not detected; skipped branch creation for $branchName" +} + +$featureDir = Join-Path $specsDir $branchName +New-Item -ItemType Directory -Path $featureDir -Force | Out-Null + +$template = Join-Path $repoRoot '.specify/templates/spec-template.md' +$specFile = Join-Path $featureDir 'spec.md' +if (Test-Path $template) { + Copy-Item $template $specFile -Force +} else { + New-Item -ItemType File -Path $specFile | Out-Null +} + +# Set the SPECIFY_FEATURE environment variable for the current session +$env:SPECIFY_FEATURE = $branchName + +if ($Json) { + $obj = [PSCustomObject]@{ + BRANCH_NAME = $branchName + SPEC_FILE = $specFile + FEATURE_NUM = $featureNum + HAS_GIT = $hasGit + } + $obj | ConvertTo-Json -Compress +} else { + Write-Output "BRANCH_NAME: $branchName" + Write-Output "SPEC_FILE: $specFile" + Write-Output "FEATURE_NUM: $featureNum" + Write-Output "HAS_GIT: $hasGit" + Write-Output "SPECIFY_FEATURE environment variable set to: $branchName" } \ No newline at end of file From 938cc84a7e60102191f235d602365d8b0dbbafb1 Mon Sep 17 00:00:00 2001 From: ashwinhegde19 Date: Thu, 23 Oct 2025 18:40:10 +0530 Subject: [PATCH 3/3] style: remove unnecessary semicolons per code review --- scripts/powershell/create-new-feature.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/powershell/create-new-feature.ps1 b/scripts/powershell/create-new-feature.ps1 index 74ebacd98..057152a3b 100644 --- a/scripts/powershell/create-new-feature.ps1 +++ b/scripts/powershell/create-new-feature.ps1 @@ -232,15 +232,15 @@ $maxBranchLength = 244 if ($branchName.Length -gt $maxBranchLength) { # Calculate how much we need to trim from suffix # Account for: feature number (3) + hyphen (1) = 4 chars - $maxSuffixLength = $maxBranchLength - 4; + $maxSuffixLength = $maxBranchLength - 4 # Truncate suffix $truncatedSuffix = $branchSuffix.Substring(0, [Math]::Min($branchSuffix.Length, $maxSuffixLength)) # Remove trailing hyphen if truncation created one $truncatedSuffix = $truncatedSuffix -replace '-$', '' - $originalBranchName = $branchName; - $branchName = "$featureNum-$truncatedSuffix"; + $originalBranchName = $branchName + $branchName = "$featureNum-$truncatedSuffix" Write-Warning "[specify] Branch name exceeded GitHub's 244-byte limit" Write-Warning "[specify] Original: $originalBranchName ($($originalBranchName.Length) bytes)"