feat(scripts): optional single-segment branch prefix for gitflow (#2202)

* feat(scripts): optional single-segment branch prefix for gitflow

- Add spec_kit_effective_branch_name / Get-SpecKitEffectiveBranchName:
  when branch matches prefix/rest with exactly one slash, validate and
  resolve specs/ using only the rest (e.g. feat/001-my-feature).
- Wire into check_feature_branch, find_feature_dir_by_prefix (bash) and
  Test-FeatureBranch, Find-FeatureDirByPrefix + Get-FeaturePathsEnv (PS).
- Align git extension git-common with core validation; remove unused
  get_feature_dir / Get-FeatureDir helpers.
- Extend tests in test_timestamp_branches.py and test_git_extension.py.

Made-with: Cursor

* Update scripts/powershell/common.ps1

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix(ps): align feature-dir resolution errors with bash (no throw under Stop)

Find-FeatureDirByPrefix: on ambiguous prefix matches, write errors to stderr
and return $null instead of throwing, matching find_feature_dir_by_prefix.

Get-FeaturePathsEnv: narrow try/catch to ConvertFrom-Json only; add
Get-FeatureDirFromBranchPrefixOrExit to mirror bash get_feature_paths
(stderr + exit 1) when prefix lookup fails, avoiding unhandled terminating
errors under $ErrorActionPreference = 'Stop' in check-prerequisites,
setup-plan, and update-agent-context.

Made-with: Cursor

* Update tests/test_timestamp_branches.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update extensions/git/scripts/powershell/git-common.ps1

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update scripts/powershell/common.ps1

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Gabriel Henrique
2026-04-13 20:11:17 -03:00
committed by GitHub
parent de93528fad
commit 4687c33b0f
6 changed files with 269 additions and 63 deletions

View File

@@ -11,10 +11,22 @@ has_git() {
git -C "$repo_root" rev-parse --is-inside-work-tree >/dev/null 2>&1
}
# Strip a single optional path segment (e.g. gitflow "feat/004-name" -> "004-name").
# Only when the full name is exactly two slash-free segments; otherwise returns the raw name.
spec_kit_effective_branch_name() {
local raw="$1"
if [[ "$raw" =~ ^([^/]+)/([^/]+)$ ]]; then
printf '%s\n' "${BASH_REMATCH[2]}"
else
printf '%s\n' "$raw"
fi
}
# Validate that a branch name matches the expected feature branch pattern.
# Accepts sequential (###-* with >=3 digits) or timestamp (YYYYMMDD-HHMMSS-*) formats.
# Logic aligned with scripts/bash/common.sh check_feature_branch after effective-name normalization.
check_feature_branch() {
local branch="$1"
local raw="$1"
local has_git_repo="$2"
# For non-git repos, we can't enforce branch naming but still provide output
@@ -23,19 +35,20 @@ check_feature_branch() {
return 0
fi
# Reject malformed timestamps (7-digit date, 8-digit date without trailing slug, or 7-digit with slug)
if [[ "$branch" =~ ^[0-9]{7}-[0-9]{6} ]] || [[ "$branch" =~ ^[0-9]{8}-[0-9]{6}$ ]]; then
echo "ERROR: Not on a feature branch. Current branch: $branch" >&2
echo "Feature branches should be named like: 001-feature-name or 20260319-143022-feature-name" >&2
local branch
branch=$(spec_kit_effective_branch_name "$raw")
# Accept sequential prefix (3+ digits) but exclude malformed timestamps
# Malformed: 7-or-8 digit date + 6-digit time with no trailing slug (e.g. "2026031-143022" or "20260319-143022")
local is_sequential=false
if [[ "$branch" =~ ^[0-9]{3,}- ]] && [[ ! "$branch" =~ ^[0-9]{7}-[0-9]{6}- ]] && [[ ! "$branch" =~ ^[0-9]{7,8}-[0-9]{6}$ ]]; then
is_sequential=true
fi
if [[ "$is_sequential" != "true" ]] && [[ ! "$branch" =~ ^[0-9]{8}-[0-9]{6}- ]]; then
echo "ERROR: Not on a feature branch. Current branch: $raw" >&2
echo "Feature branches should be named like: 001-feature-name, 1234-feature-name, or 20260319-143022-feature-name" >&2
return 1
fi
# Accept sequential (>=3 digits followed by hyphen) or timestamp (YYYYMMDD-HHMMSS-*)
if [[ "$branch" =~ ^[0-9]{3,}- ]] || [[ "$branch" =~ ^[0-9]{8}-[0-9]{6}- ]]; then
return 0
fi
echo "ERROR: Not on a feature branch. Current branch: $branch" >&2
echo "Feature branches should be named like: 001-feature-name or 20260319-143022-feature-name" >&2
return 1
return 0
}

View File

@@ -15,6 +15,14 @@ function Test-HasGit {
}
}
function Get-SpecKitEffectiveBranchName {
param([string]$Branch)
if ($Branch -match '^([^/]+)/([^/]+)$') {
return $Matches[2]
}
return $Branch
}
function Test-FeatureBranch {
param(
[string]$Branch,
@@ -27,24 +35,17 @@ function Test-FeatureBranch {
return $true
}
# Reject malformed timestamps (7-digit date or no trailing slug)
$hasMalformedTimestamp = ($Branch -match '^[0-9]{7}-[0-9]{6}-') -or
($Branch -match '^(?:\d{7}|\d{8})-\d{6}$')
if ($hasMalformedTimestamp) {
Write-Output "ERROR: Not on a feature branch. Current branch: $Branch"
Write-Output "Feature branches should be named like: 001-feature-name or 20260319-143022-feature-name"
$raw = $Branch
$Branch = Get-SpecKitEffectiveBranchName $raw
# Accept sequential prefix (3+ digits) but exclude malformed timestamps
# Malformed: 7-or-8 digit date + 6-digit time with no trailing slug (e.g. "2026031-143022" or "20260319-143022")
$hasMalformedTimestamp = ($Branch -match '^[0-9]{7}-[0-9]{6}-') -or ($Branch -match '^(?:\d{7}|\d{8})-\d{6}$')
$isSequential = ($Branch -match '^[0-9]{3,}-') -and (-not $hasMalformedTimestamp)
if (-not $isSequential -and $Branch -notmatch '^\d{8}-\d{6}-') {
[Console]::Error.WriteLine("ERROR: Not on a feature branch. Current branch: $raw")
[Console]::Error.WriteLine("Feature branches should be named like: 001-feature-name, 1234-feature-name, or 20260319-143022-feature-name")
return $false
}
# Accept sequential (>=3 digits followed by hyphen) or timestamp (YYYYMMDD-HHMMSS-*)
$isSequential = ($Branch -match '^[0-9]{3,}-') -and (-not $hasMalformedTimestamp)
$isTimestamp = $Branch -match '^\d{8}-\d{6}-'
if ($isSequential -or $isTimestamp) {
return $true
}
Write-Output "ERROR: Not on a feature branch. Current branch: $Branch"
Write-Output "Feature branches should be named like: 001-feature-name or 20260319-143022-feature-name"
return $false
return $true
}