Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion scripts/bash/create-new-feature.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ set -e
JSON_MODE=false
SHORT_NAME=""
BRANCH_NUMBER=""
NUMBER_EXPLICIT=false # true only when --number was explicitly passed by the caller
ARGS=()
i=1
while [ $i -le $# ]; do
Expand Down Expand Up @@ -39,14 +40,26 @@ while [ $i -le $# ]; do
exit 1
fi
BRANCH_NUMBER="$next_arg"
# Validate --number is an integer in [1,999]; downstream tooling
# expects a 3-digit prefix (^[0-9]{3}-) so out-of-range values
# produce malformed branch names.
if ! [[ "$next_arg" =~ ^[0-9]+$ ]]; then
>&2 echo 'Error: --number must be an integer between 1 and 999'
exit 1
fi
if (( next_arg < 1 || next_arg > 999 )); then
>&2 echo 'Error: --number must be an integer between 1 and 999'
exit 1
fi
NUMBER_EXPLICIT=true
;;
--help|-h)
echo "Usage: $0 [--json] [--short-name <name>] [--number N] <feature_description>"
echo ""
echo "Options:"
echo " --json Output in JSON format"
echo " --short-name <name> Provide a custom short name (2-4 words) for the branch"
echo " --number N Specify branch number manually (overrides auto-detection)"
echo " --number N Preferred branch number (auto-corrected if prefix already exists in specs or branches)"
echo " --help, -h Show this help message"
echo ""
echo "Examples:"
Expand Down Expand Up @@ -266,6 +279,53 @@ fi

# 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))")

# ── Guardrail: auto-correct if --number was explicitly passed and the prefix
# already exists in specs/ or as a git branch. Only fires on explicit --number
# to preserve the existing auto-detection contract.
# Fetches remotes once here; subsequent max-finding reuses local branch data
# to avoid a second network round-trip.
if [ "$NUMBER_EXPLICIT" = true ]; then
# Check for conflict in spec directories (directories only)
SPEC_CONFLICT=false
while IFS= read -r spec_path; do
if [ -d "$spec_path" ]; then
SPEC_CONFLICT=true
break
fi
done < <(compgen -G "$SPECS_DIR/${FEATURE_NUM}-*" 2>/dev/null)

# Check for conflict in git branches (local and remote)
# Fetch once here so both conflict detection and recalculation use
# up-to-date remote info without a second network call.
BRANCH_CONFLICT=false
if [ "$HAS_GIT" = true ]; then
git fetch --all --prune >/dev/null 2>&1 || true
if git branch -a 2>/dev/null | grep -qE "(^|[[:space:]])(remotes/[^/]+/)?${FEATURE_NUM}-"; then
BRANCH_CONFLICT=true
fi
fi

if [ "$SPEC_CONFLICT" = true ] || [ "$BRANCH_CONFLICT" = true ]; then
REQUESTED_NUM="$FEATURE_NUM"
# Inline the max-finding using already-fetched local branch data
# to avoid a second network round-trip.
if [ "$HAS_GIT" = true ]; then
HIGHEST_BRANCH=$(get_highest_from_branches)
else
HIGHEST_BRANCH=0
fi
HIGHEST_SPEC=$(get_highest_from_specs "$SPECS_DIR")
if [ "$HIGHEST_SPEC" -gt "$HIGHEST_BRANCH" ]; then
BRANCH_NUMBER=$((HIGHEST_SPEC + 1))
else
Comment on lines +302 to +321
BRANCH_NUMBER=$((HIGHEST_BRANCH + 1))
fi
FEATURE_NUM=$(printf "%03d" "$((10#$BRANCH_NUMBER))")
>&2 echo "⚠️ [specify] --number $REQUESTED_NUM conflicts with an existing spec dir or branch. Auto-corrected to $FEATURE_NUM."
fi
fi

BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"

# GitHub enforces a 244-byte limit on branch names
Expand Down
44 changes: 43 additions & 1 deletion scripts/powershell/create-new-feature.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
param(
[switch]$Json,
[string]$ShortName,
[ValidateRange(1, 999)]
[int]$Number = 0,
[switch]$Help,
Comment on lines +7 to 9
[Parameter(ValueFromRemainingArguments = $true)]
Expand All @@ -18,7 +19,7 @@ if ($Help) {
Write-Host "Options:"
Write-Host " -Json Output in JSON format"
Write-Host " -ShortName <name> Provide a custom short name (2-4 words) for the branch"
Write-Host " -Number N Specify branch number manually (overrides auto-detection)"
Write-Host " -Number N Preferred branch number (auto-corrected if prefix already exists in specs or branches)"
Write-Host " -Help Show this help message"
Write-Host ""
Write-Host "Examples:"
Expand Down Expand Up @@ -213,6 +214,10 @@ if ($ShortName) {
}

# Determine branch number
# Track whether the caller explicitly passed a non-zero -Number so the guardrail
# below only fires for explicit overrides, not for auto-detected numbers.
# Exclude -Number 0 since 0 is the auto-detect sentinel and produces prefix "000".
$numberExplicit = $PSBoundParameters.ContainsKey('Number') -and $Number -ne 0
if ($Number -eq 0) {
if ($hasGit) {
# Check existing branches on remotes
Expand All @@ -224,6 +229,43 @@ if ($Number -eq 0) {
}

$featureNum = ('{0:000}' -f $Number)

# ── Guardrail: auto-correct if -Number was explicitly passed and the prefix
# already exists in specs/ or as a git branch. Only fires on explicit -Number
# to preserve the existing auto-detection contract.
# Reuses Get-NextBranchNumber, which fetches remotes and checks both
# specs directories and all local/remote branches.
if ($numberExplicit) {
$requestedNum = $featureNum

# Check for conflict in spec directories
$specConflict = (Get-ChildItem -Path $specsDir -Directory -ErrorAction SilentlyContinue |`
Where-Object { $_.Name -match "^$featureNum-" }).Count -gt 0

# Check for conflict in git branches (local and remote)
# Note: we do NOT fetch here — if a conflict is found, Get-NextBranchNumber
# below will fetch exactly once before computing the corrected number.
$branchConflict = $false
if ($hasGit) {
$allBranches = git branch -a 2>$null
if ($LASTEXITCODE -eq 0) {
$branchConflict = ($allBranches | Where-Object { $_ -match "(^|\s)(remotes/[^/]+/)?$featureNum-" }).Count -gt 0
}
}

if ($specConflict -or $branchConflict) {
# Delegate to Get-NextBranchNumber, which fetches and computes
# max(all specs, all branches) + 1 — same logic used by auto-detection.
if ($hasGit) {
$Number = Get-NextBranchNumber -SpecsDir $specsDir
} else {
Comment on lines +249 to +261
$Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1
}
$featureNum = ('{0:000}' -f $Number)
Write-Warning "[specify] -Number $requestedNum conflicts with an existing spec dir or branch. Auto-corrected to $featureNum."
}
}

$branchName = "$featureNum-$branchSuffix"

# GitHub enforces a 244-byte limit on branch names
Expand Down
Loading