From 33df8976ca714a6c3e772c4c6814756ade33ec19 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Sun, 23 Nov 2025 16:01:56 +0000 Subject: [PATCH 1/6] fix: use global maximum for branch numbering to prevent collisions The check_existing_branches (bash) and Get-NextBranchNumber (PowerShell) functions were only looking for branches/specs matching the SAME short name when determining the next feature number. This caused collisions where multiple features could be assigned the same number if they had different short names. For example, if feature 023-ci-optimization existed, creating a new feature with a different short name would incorrectly use 001 instead of 024. This fix changes both functions to: 1. Use get_highest_from_branches() / Get-HighestNumberFromBranches to find the highest number across ALL branches globally 2. Use get_highest_from_specs() / Get-HighestNumberFromSpecs to find the highest number across ALL spec directories globally 3. Return the maximum of both + 1 The helper functions already existed but were not being used. This fix properly utilizes them to ensure features are numbered sequentially regardless of their short names. Issue: Branch number collisions when creating features with different names Impact: Prevents multiple features from sharing the same number prefix --- scripts/bash/create-new-feature.sh | 34 +++++------- scripts/powershell/create-new-feature.ps1 | 65 ++++------------------- 2 files changed, 24 insertions(+), 75 deletions(-) diff --git a/scripts/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh index 592dab2ea3..9e57cc6ec3 100644 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -130,30 +130,22 @@ get_highest_from_branches() { check_existing_branches() { local short_name="$1" local specs_dir="$2" - + # Fetch all remotes to get latest branch info (suppress errors if no remotes) git fetch --all --prune 2>/dev/null || true - - # Find all branches matching the pattern using git ls-remote (more reliable) - local remote_branches=$(git ls-remote --heads origin 2>/dev/null | grep -E "refs/heads/[0-9]+-${short_name}$" | sed 's/.*\/\([0-9]*\)-.*/\1/' | sort -n) - - # Also check local branches - local local_branches=$(git branch 2>/dev/null | grep -E "^[* ]*[0-9]+-${short_name}$" | sed 's/^[* ]*//' | sed 's/-.*//' | sort -n) - - # Check specs directory as well - local spec_dirs="" - if [ -d "$specs_dir" ]; then - spec_dirs=$(find "$specs_dir" -maxdepth 1 -type d -name "[0-9]*-${short_name}" 2>/dev/null | xargs -n1 basename 2>/dev/null | sed 's/-.*//' | sort -n) + + # Get highest number from ALL branches (not just matching short name) + local highest_branch=$(get_highest_from_branches) + + # Get highest number from ALL specs (not just matching short name) + local highest_spec=$(get_highest_from_specs "$specs_dir") + + # Take the maximum of both + local max_num=$highest_branch + if [ "$highest_spec" -gt "$max_num" ]; then + max_num=$highest_spec fi - - # Combine all sources and get the highest number - local max_num=0 - for num in $remote_branches $local_branches $spec_dirs; do - if [ "$num" -gt "$max_num" ]; then - max_num=$num - fi - done - + # Return next number echo $((max_num + 1)) } diff --git a/scripts/powershell/create-new-feature.ps1 b/scripts/powershell/create-new-feature.ps1 index 351f4e9e7e..80e989c37c 100644 --- a/scripts/powershell/create-new-feature.ps1 +++ b/scripts/powershell/create-new-feature.ps1 @@ -104,66 +104,23 @@ function Get-NextBranchNumber { [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 - } - } - + + # Get highest number from ALL branches (not just matching short name) + $highestBranch = Get-HighestNumberFromBranches + + # Get highest number from ALL specs (not just matching short name) + $highestSpec = Get-HighestNumberFromSpecs -SpecsDir $SpecsDir + + # Take the maximum of both + $maxNum = [Math]::Max($highestBranch, $highestSpec) + # Return next number return $maxNum + 1 } From bf5ae42085460d9d834ceeca4045b0ba298c6f54 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Sun, 23 Nov 2025 16:12:11 +0000 Subject: [PATCH 2/6] Update scripts/bash/create-new-feature.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/bash/create-new-feature.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh index 9e57cc6ec3..a632c58841 100644 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -128,8 +128,7 @@ get_highest_from_branches() { # Function to check existing branches (local and remote) and return next available number check_existing_branches() { - local short_name="$1" - local specs_dir="$2" + local specs_dir="$1" # Fetch all remotes to get latest branch info (suppress errors if no remotes) git fetch --all --prune 2>/dev/null || true From a0ca101aa4c0a11bd3b4ba145c86c0a0b659a274 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Sun, 23 Nov 2025 16:12:28 +0000 Subject: [PATCH 3/6] Update scripts/powershell/create-new-feature.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/powershell/create-new-feature.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/powershell/create-new-feature.ps1 b/scripts/powershell/create-new-feature.ps1 index 80e989c37c..fce6b92fac 100644 --- a/scripts/powershell/create-new-feature.ps1 +++ b/scripts/powershell/create-new-feature.ps1 @@ -101,7 +101,6 @@ function Get-HighestNumberFromBranches { function Get-NextBranchNumber { param( - [string]$ShortName, [string]$SpecsDir ) From f65bf6ccb7833d5e98d6088e7ca5b3d7e17431b5 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Sun, 23 Nov 2025 16:21:22 +0000 Subject: [PATCH 4/6] fix: remove unused short_name parameter from branch numbering functions The check_existing_branches (bash) and Get-NextBranchNumber (PowerShell) functions no longer use the short_name parameter since they now find the global maximum across ALL features. This commit: 1. Removes the unused parameter from function signatures 2. Updates all call sites to not pass the parameter This prevents the scripts from failing when the function is called with the wrong number of arguments. --- scripts/bash/create-new-feature.sh | 2 +- scripts/powershell/create-new-feature.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh index a632c58841..fe6d73999b 100644 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -238,7 +238,7 @@ fi if [ -z "$BRANCH_NUMBER" ]; then if [ "$HAS_GIT" = true ]; then # Check existing branches on remotes - BRANCH_NUMBER=$(check_existing_branches "$BRANCH_SUFFIX" "$SPECS_DIR") + BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR") else # Fall back to local directory check HIGHEST=$(get_highest_from_specs "$SPECS_DIR") diff --git a/scripts/powershell/create-new-feature.ps1 b/scripts/powershell/create-new-feature.ps1 index fce6b92fac..2f0172e35d 100644 --- a/scripts/powershell/create-new-feature.ps1 +++ b/scripts/powershell/create-new-feature.ps1 @@ -210,7 +210,7 @@ if ($ShortName) { if ($Number -eq 0) { if ($hasGit) { # Check existing branches on remotes - $Number = Get-NextBranchNumber -ShortName $branchSuffix -SpecsDir $specsDir + $Number = Get-NextBranchNumber -SpecsDir $specsDir } else { # Fall back to local directory check $Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1 From b4e1c07817005abef023da3833edd993cb7ca74b Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 25 Nov 2025 13:50:25 +0000 Subject: [PATCH 5/6] fix(scripts): prevent octal interpretation in feature number parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When --number 027 was passed, printf '%03d' interpreted it as octal, converting 027 (octal) to 23 (decimal). Now forces base-10 with 10# prefix. Bug: User passes --number 027, gets feature 023 instead of 027 Root cause: printf %03d treats leading zeros as octal notation Fix: Use $((10#$BRANCH_NUMBER)) to force decimal interpretation Example: - Before: --number 027 → octal 027 → decimal 23 → feature 023 - After: --number 027 → decimal 27 → feature 027 Note: PowerShell version does not have this bug because [int] type conversion in PowerShell does not treat leading zeros as octal. --- scripts/bash/create-new-feature.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh index fe6d73999b..640ba73cb3 100644 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -246,7 +246,8 @@ if [ -z "$BRANCH_NUMBER" ]; then fi fi -FEATURE_NUM=$(printf "%03d" "$BRANCH_NUMBER") +# Force base-10 interpretation to prevent octal conversion (027 → 23) +FEATURE_NUM=$(printf "%03d" "$((10#$BRANCH_NUMBER))") BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}" # GitHub enforces a 244-byte limit on branch names From dadda123f06226886fa709721cb05401752f3e0e Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 25 Nov 2025 13:54:29 +0000 Subject: [PATCH 6/6] Update scripts/bash/create-new-feature.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/bash/create-new-feature.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh index 640ba73cb3..c40cfd77f0 100644 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -246,7 +246,7 @@ if [ -z "$BRANCH_NUMBER" ]; then fi fi -# Force base-10 interpretation to prevent octal conversion (027 → 23) +# Force base-10 interpretation to prevent octal conversion (e.g., 010 → 8 in octal, but should be 10 in decimal) FEATURE_NUM=$(printf "%03d" "$((10#$BRANCH_NUMBER))") BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"