Skip to content

Commit 8e14ab1

Browse files
t-sakodaclaude
andauthored
fix: support feature branch numbers with 4+ digits (#2040)
* fix: support feature branch numbers with 4+ digits in common.sh and common.ps1 The sequential feature number pattern was hardcoded to exactly 3 digits (`{3}`), causing branches like `1234-feature-name` to be rejected. Changed to `{3,}` (3 or more digits) to support growing projects. Also added a guard to exclude malformed timestamp patterns from being accepted as sequential prefixes. Closes #344 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: narrow timestamp guard and use [long] to prevent overflow - Change [int] to [long] in PowerShell Get-CurrentBranch to avoid overflow for large feature numbers (>2,147,483,647) - Narrow malformed-timestamp exclusion from ^[0-9]+-[0-9]{6}- to ^[0-9]{7}-[0-9]{6}- so valid sequential branches like 004-123456-fix-bug are not rejected Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add regression tests for 4+ digit feature branch support Cover check_feature_branch and find_feature_dir_by_prefix with 4-digit sequential prefixes, as requested in PR review #2040. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: reject timestamp-like branches without trailing slug Branches like "20260319-143022" (no "-<name>" suffix) were incorrectly accepted as sequential prefixes. Add explicit rejection for 7-or-8 digit date + 6-digit time patterns with no trailing slug, in both common.sh and common.ps1. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0945df9 commit 8e14ab1

3 files changed

Lines changed: 42 additions & 8 deletions

File tree

scripts/bash/common.sh

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ get_current_branch() {
7878
latest_timestamp="$ts"
7979
latest_feature=$dirname
8080
fi
81-
elif [[ "$dirname" =~ ^([0-9]{3})- ]]; then
81+
elif [[ "$dirname" =~ ^([0-9]{3,})- ]]; then
8282
local number=${BASH_REMATCH[1]}
8383
number=$((10#$number))
8484
if [[ "$number" -gt "$highest" ]]; then
@@ -124,9 +124,15 @@ check_feature_branch() {
124124
return 0
125125
fi
126126

127-
if [[ ! "$branch" =~ ^[0-9]{3}- ]] && [[ ! "$branch" =~ ^[0-9]{8}-[0-9]{6}- ]]; then
127+
# Accept sequential prefix (3+ digits) but exclude malformed timestamps
128+
# Malformed: 7-or-8 digit date + 6-digit time with no trailing slug (e.g. "2026031-143022" or "20260319-143022")
129+
local is_sequential=false
130+
if [[ "$branch" =~ ^[0-9]{3,}- ]] && [[ ! "$branch" =~ ^[0-9]{7}-[0-9]{6}- ]] && [[ ! "$branch" =~ ^[0-9]{7,8}-[0-9]{6}$ ]]; then
131+
is_sequential=true
132+
fi
133+
if [[ "$is_sequential" != "true" ]] && [[ ! "$branch" =~ ^[0-9]{8}-[0-9]{6}- ]]; then
128134
echo "ERROR: Not on a feature branch. Current branch: $branch" >&2
129-
echo "Feature branches should be named like: 001-feature-name or 20260319-143022-feature-name" >&2
135+
echo "Feature branches should be named like: 001-feature-name, 1234-feature-name, or 20260319-143022-feature-name" >&2
130136
return 1
131137
fi
132138

@@ -146,7 +152,7 @@ find_feature_dir_by_prefix() {
146152
local prefix=""
147153
if [[ "$branch_name" =~ ^([0-9]{8}-[0-9]{6})- ]]; then
148154
prefix="${BASH_REMATCH[1]}"
149-
elif [[ "$branch_name" =~ ^([0-9]{3})- ]]; then
155+
elif [[ "$branch_name" =~ ^([0-9]{3,})- ]]; then
150156
prefix="${BASH_REMATCH[1]}"
151157
else
152158
# If branch doesn't have a recognized prefix, fall back to exact match

scripts/powershell/common.ps1

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ function Get-CurrentBranch {
8383
$latestTimestamp = $ts
8484
$latestFeature = $_.Name
8585
}
86-
} elseif ($_.Name -match '^(\d{3})-') {
87-
$num = [int]$matches[1]
86+
} elseif ($_.Name -match '^(\d{3,})-') {
87+
$num = [long]$matches[1]
8888
if ($num -gt $highest) {
8989
$highest = $num
9090
# Only update if no timestamp branch found yet
@@ -139,9 +139,13 @@ function Test-FeatureBranch {
139139
return $true
140140
}
141141

142-
if ($Branch -notmatch '^[0-9]{3}-' -and $Branch -notmatch '^\d{8}-\d{6}-') {
142+
# Accept sequential prefix (3+ digits) but exclude malformed timestamps
143+
# Malformed: 7-or-8 digit date + 6-digit time with no trailing slug (e.g. "2026031-143022" or "20260319-143022")
144+
$hasMalformedTimestamp = ($Branch -match '^[0-9]{7}-[0-9]{6}-') -or ($Branch -match '^(?:\d{7}|\d{8})-\d{6}$')
145+
$isSequential = ($Branch -match '^[0-9]{3,}-') -and (-not $hasMalformedTimestamp)
146+
if (-not $isSequential -and $Branch -notmatch '^\d{8}-\d{6}-') {
143147
Write-Output "ERROR: Not on a feature branch. Current branch: $Branch"
144-
Write-Output "Feature branches should be named like: 001-feature-name or 20260319-143022-feature-name"
148+
Write-Output "Feature branches should be named like: 001-feature-name, 1234-feature-name, or 20260319-143022-feature-name"
145149
return $false
146150
}
147151
return $true

tests/test_timestamp_branches.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,26 @@ def test_rejects_main(self):
186186
result = source_and_call('check_feature_branch "main" "true"')
187187
assert result.returncode != 0
188188

189+
def test_accepts_four_digit_sequential_branch(self):
190+
"""check_feature_branch accepts 4+ digit sequential branch."""
191+
result = source_and_call('check_feature_branch "1234-feat" "true"')
192+
assert result.returncode == 0
193+
189194
def test_rejects_partial_timestamp(self):
190195
"""Test 9: check_feature_branch rejects 7-digit date."""
191196
result = source_and_call('check_feature_branch "2026031-143022-feat" "true"')
192197
assert result.returncode != 0
193198

199+
def test_rejects_timestamp_without_slug(self):
200+
"""check_feature_branch rejects timestamp-like branch missing trailing slug."""
201+
result = source_and_call('check_feature_branch "20260319-143022" "true"')
202+
assert result.returncode != 0
203+
204+
def test_rejects_7digit_timestamp_without_slug(self):
205+
"""check_feature_branch rejects 7-digit date + 6-digit time without slug."""
206+
result = source_and_call('check_feature_branch "2026031-143022" "true"')
207+
assert result.returncode != 0
208+
194209

195210
# ── find_feature_dir_by_prefix Tests ─────────────────────────────────────────
196211

@@ -214,6 +229,15 @@ def test_cross_branch_prefix(self, tmp_path: Path):
214229
assert result.returncode == 0
215230
assert result.stdout.strip() == f"{tmp_path}/specs/20260319-143022-original-feat"
216231

232+
def test_four_digit_sequential_prefix(self, tmp_path: Path):
233+
"""find_feature_dir_by_prefix resolves 4+ digit sequential prefix."""
234+
(tmp_path / "specs" / "1000-original-feat").mkdir(parents=True)
235+
result = source_and_call(
236+
f'find_feature_dir_by_prefix "{tmp_path}" "1000-different-name"'
237+
)
238+
assert result.returncode == 0
239+
assert result.stdout.strip() == f"{tmp_path}/specs/1000-original-feat"
240+
217241

218242
# ── get_current_branch Tests ─────────────────────────────────────────────────
219243

0 commit comments

Comments
 (0)