Skip to content

Commit 36383b4

Browse files
committed
Update script logic
1 parent 3e476c2 commit 36383b4

3 files changed

Lines changed: 228 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,29 @@ All notable changes to the Specify CLI and templates are documented here.
77
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
88
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
99

10+
## [0.0.20] - 2025-10-14
11+
12+
### Added
13+
14+
- **Intelligent Branch Naming**: `create-new-feature` scripts now support `--short-name` parameter for custom branch names
15+
- When `--short-name` provided: Uses the custom name directly (cleaned and formatted)
16+
- When omitted: Automatically generates meaningful names using stop word filtering and length-based filtering
17+
- Filters out common stop words (I, want, to, the, for, etc.)
18+
- Removes words shorter than 3 characters (unless they're uppercase acronyms)
19+
- Takes 3-4 most meaningful words from the description
20+
- **Enforces GitHub's 244-byte branch name limit** with automatic truncation and warnings
21+
- Examples:
22+
- "I want to create user authentication" → `001-create-user-authentication`
23+
- "Implement OAuth2 integration for API" → `001-implement-oauth2-integration-api`
24+
- "Fix payment processing bug" → `001-fix-payment-processing`
25+
- Very long descriptions are automatically truncated at word boundaries to stay within limits
26+
- Designed for AI agents to provide semantic short names while maintaining standalone usability
27+
28+
### Changed
29+
30+
- Enhanced help documentation for `create-new-feature.sh` and `create-new-feature.ps1` scripts with examples
31+
- Branch names now validated against GitHub's 244-byte limit with automatic truncation if needed
32+
1033
## [0.0.19] - 2025-10-10
1134

1235
### Added

scripts/bash/create-new-feature.sh

Lines changed: 107 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,42 @@
33
set -e
44

55
JSON_MODE=false
6+
SHORT_NAME=""
67
ARGS=()
7-
for arg in "$@"; do
8+
i=0
9+
while [ $i -lt $# ]; do
10+
arg="${!i}"
811
case "$arg" in
9-
--json) JSON_MODE=true ;;
10-
--help|-h) echo "Usage: $0 [--json] <feature_description>"; exit 0 ;;
11-
*) ARGS+=("$arg") ;;
12+
--json)
13+
JSON_MODE=true
14+
;;
15+
--short-name)
16+
i=$((i + 1))
17+
SHORT_NAME="${!i}"
18+
;;
19+
--help|-h)
20+
echo "Usage: $0 [--json] [--short-name <name>] <feature_description>"
21+
echo ""
22+
echo "Options:"
23+
echo " --json Output in JSON format"
24+
echo " --short-name <name> Provide a custom short name (2-4 words) for the branch"
25+
echo " --help, -h Show this help message"
26+
echo ""
27+
echo "Examples:"
28+
echo " $0 'Add user authentication system' --short-name 'user-auth'"
29+
echo " $0 'Implement OAuth2 integration for API'"
30+
exit 0
31+
;;
32+
*)
33+
ARGS+=("$arg")
34+
;;
1235
esac
36+
i=$((i + 1))
1337
done
1438

1539
FEATURE_DESCRIPTION="${ARGS[*]}"
1640
if [ -z "$FEATURE_DESCRIPTION" ]; then
17-
echo "Usage: $0 [--json] <feature_description>" >&2
41+
echo "Usage: $0 [--json] [--short-name <name>] <feature_description>" >&2
1842
exit 1
1943
fi
2044

@@ -67,9 +91,84 @@ fi
6791
NEXT=$((HIGHEST + 1))
6892
FEATURE_NUM=$(printf "%03d" "$NEXT")
6993

70-
BRANCH_NAME=$(echo "$FEATURE_DESCRIPTION" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//')
71-
WORDS=$(echo "$BRANCH_NAME" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//')
72-
BRANCH_NAME="${FEATURE_NUM}-${WORDS}"
94+
# Function to generate branch name with stop word filtering and length filtering
95+
generate_branch_name() {
96+
local description="$1"
97+
98+
# Common stop words to filter out
99+
local stop_words="^(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)$"
100+
101+
# Convert to lowercase and split into words
102+
local clean_name=$(echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g')
103+
104+
# Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original)
105+
local meaningful_words=()
106+
for word in $clean_name; do
107+
# Skip empty words
108+
[ -z "$word" ] && continue
109+
110+
# Keep words that are NOT stop words AND (length >= 3 OR are potential acronyms)
111+
if ! echo "$word" | grep -qiE "$stop_words"; then
112+
if [ ${#word} -ge 3 ]; then
113+
meaningful_words+=("$word")
114+
elif echo "$description" | grep -q "\b${word^^}\b"; then
115+
# Keep short words if they appear as uppercase in original (likely acronyms)
116+
meaningful_words+=("$word")
117+
fi
118+
fi
119+
done
120+
121+
# If we have meaningful words, use first 3-4 of them
122+
if [ ${#meaningful_words[@]} -gt 0 ]; then
123+
local max_words=3
124+
if [ ${#meaningful_words[@]} -eq 4 ]; then max_words=4; fi
125+
126+
local result=""
127+
local count=0
128+
for word in "${meaningful_words[@]}"; do
129+
if [ $count -ge $max_words ]; then break; fi
130+
if [ -n "$result" ]; then result="$result-"; fi
131+
result="$result$word"
132+
count=$((count + 1))
133+
done
134+
echo "$result"
135+
else
136+
# Fallback to original logic if no meaningful words found
137+
echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//' | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//'
138+
fi
139+
}
140+
141+
# Generate branch name
142+
if [ -n "$SHORT_NAME" ]; then
143+
# Use provided short name, just clean it up
144+
BRANCH_SUFFIX=$(echo "$SHORT_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//')
145+
else
146+
# Generate from description with smart filtering
147+
BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION")
148+
fi
149+
150+
BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"
151+
152+
# GitHub enforces a 244-byte limit on branch names
153+
# Validate and truncate if necessary
154+
MAX_BRANCH_LENGTH=244
155+
if [ ${#BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then
156+
# Calculate how much we need to trim from suffix
157+
# Account for: feature number (3) + hyphen (1) = 4 chars
158+
MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - 4))
159+
160+
# Truncate suffix at word boundary if possible
161+
TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH)
162+
# Remove trailing hyphen if truncation created one
163+
TRUNCATED_SUFFIX=$(echo "$TRUNCATED_SUFFIX" | sed 's/-$//')
164+
165+
ORIGINAL_BRANCH_NAME="$BRANCH_NAME"
166+
BRANCH_NAME="${FEATURE_NUM}-${TRUNCATED_SUFFIX}"
167+
168+
>&2 echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit"
169+
>&2 echo "[specify] Original: $ORIGINAL_BRANCH_NAME (${#ORIGINAL_BRANCH_NAME} bytes)"
170+
>&2 echo "[specify] Truncated to: $BRANCH_NAME (${#BRANCH_NAME} bytes)"
171+
fi
73172

74173
if [ "$HAS_GIT" = true ]; then
75174
git checkout -b "$BRANCH_NAME"

scripts/powershell/create-new-feature.ps1

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,34 @@
33
[CmdletBinding()]
44
param(
55
[switch]$Json,
6+
[string]$ShortName,
67
[Parameter(ValueFromRemainingArguments = $true)]
78
[string[]]$FeatureDescription
89
)
910
$ErrorActionPreference = 'Stop'
1011

11-
if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) {
12-
Write-Error "Usage: ./create-new-feature.ps1 [-Json] <feature description>"
13-
exit 1
12+
# Show help if requested or no description provided
13+
if (($FeatureDescription -contains '--help') -or ($FeatureDescription -contains '-h') -or
14+
(-not $FeatureDescription) -or ($FeatureDescription.Count -eq 0)) {
15+
16+
if (($FeatureDescription -contains '--help') -or ($FeatureDescription -contains '-h')) {
17+
Write-Host "Usage: ./create-new-feature.ps1 [-Json] [-ShortName <name>] <feature description>"
18+
Write-Host ""
19+
Write-Host "Options:"
20+
Write-Host " -Json Output in JSON format"
21+
Write-Host " -ShortName <name> Provide a custom short name (2-4 words) for the branch"
22+
Write-Host " -h, --help Show this help message"
23+
Write-Host ""
24+
Write-Host "Examples:"
25+
Write-Host " ./create-new-feature.ps1 'Add user authentication system' -ShortName 'user-auth'"
26+
Write-Host " ./create-new-feature.ps1 'Implement OAuth2 integration for API'"
27+
exit 0
28+
} else {
29+
Write-Error "Usage: ./create-new-feature.ps1 [-Json] [-ShortName <name>] <feature description>"
30+
exit 1
31+
}
1432
}
33+
1534
$featureDesc = ($FeatureDescription -join ' ').Trim()
1635

1736
# Resolve repository root. Prefer git information when available, but fall back
@@ -72,9 +91,82 @@ if (Test-Path $specsDir) {
7291
$next = $highest + 1
7392
$featureNum = ('{0:000}' -f $next)
7493

75-
$branchName = $featureDesc.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', ''
76-
$words = ($branchName -split '-') | Where-Object { $_ } | Select-Object -First 3
77-
$branchName = "$featureNum-$([string]::Join('-', $words))"
94+
# Function to generate branch name with stop word filtering and length filtering
95+
function Get-BranchName {
96+
param([string]$Description)
97+
98+
# Common stop words to filter out
99+
$stopWords = @(
100+
'i', 'a', 'an', 'the', 'to', 'for', 'of', 'in', 'on', 'at', 'by', 'with', 'from',
101+
'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had',
102+
'do', 'does', 'did', 'will', 'would', 'should', 'could', 'can', 'may', 'might', 'must', 'shall',
103+
'this', 'that', 'these', 'those', 'my', 'your', 'our', 'their',
104+
'want', 'need', 'add', 'get', 'set'
105+
)
106+
107+
# Convert to lowercase and extract words (alphanumeric only)
108+
$cleanName = $Description.ToLower() -replace '[^a-z0-9\s]', ' '
109+
$words = $cleanName -split '\s+' | Where-Object { $_ }
110+
111+
# Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original)
112+
$meaningfulWords = @()
113+
foreach ($word in $words) {
114+
# Skip stop words
115+
if ($stopWords -contains $word) { continue }
116+
117+
# Keep words that are length >= 3 OR appear as uppercase in original (likely acronyms)
118+
if ($word.Length -ge 3) {
119+
$meaningfulWords += $word
120+
} elseif ($Description -match "\b$($word.ToUpper())\b") {
121+
# Keep short words if they appear as uppercase in original (likely acronyms)
122+
$meaningfulWords += $word
123+
}
124+
}
125+
126+
# If we have meaningful words, use first 3-4 of them
127+
if ($meaningfulWords.Count -gt 0) {
128+
$maxWords = if ($meaningfulWords.Count -eq 4) { 4 } else { 3 }
129+
$result = ($meaningfulWords | Select-Object -First $maxWords) -join '-'
130+
return $result
131+
} else {
132+
# Fallback to original logic if no meaningful words found
133+
$result = $Description.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', ''
134+
$fallbackWords = ($result -split '-') | Where-Object { $_ } | Select-Object -First 3
135+
return [string]::Join('-', $fallbackWords)
136+
}
137+
}
138+
139+
# Generate branch name
140+
if ($ShortName) {
141+
# Use provided short name, just clean it up
142+
$branchSuffix = $ShortName.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', ''
143+
} else {
144+
# Generate from description with smart filtering
145+
$branchSuffix = Get-BranchName -Description $featureDesc
146+
}
147+
148+
$branchName = "$featureNum-$branchSuffix"
149+
150+
# GitHub enforces a 244-byte limit on branch names
151+
# Validate and truncate if necessary
152+
$maxBranchLength = 244
153+
if ($branchName.Length -gt $maxBranchLength) {
154+
# Calculate how much we need to trim from suffix
155+
# Account for: feature number (3) + hyphen (1) = 4 chars
156+
$maxSuffixLength = $maxBranchLength - 4
157+
158+
# Truncate suffix
159+
$truncatedSuffix = $branchSuffix.Substring(0, [Math]::Min($branchSuffix.Length, $maxSuffixLength))
160+
# Remove trailing hyphen if truncation created one
161+
$truncatedSuffix = $truncatedSuffix -replace '-$', ''
162+
163+
$originalBranchName = $branchName
164+
$branchName = "$featureNum-$truncatedSuffix"
165+
166+
Write-Warning "[specify] Branch name exceeded GitHub's 244-byte limit"
167+
Write-Warning "[specify] Original: $originalBranchName ($($originalBranchName.Length) bytes)"
168+
Write-Warning "[specify] Truncated to: $branchName ($($branchName.Length) bytes)"
169+
}
78170

79171
if ($hasGit) {
80172
try {

0 commit comments

Comments
 (0)