feat(scripts): optional single-segment branch prefix for gitflow#2202
feat(scripts): optional single-segment branch prefix for gitflow#2202gabrielhmsantos wants to merge 3 commits intogithub:mainfrom
Conversation
- 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
There was a problem hiding this comment.
Pull request overview
Adds gitflow-style support for feature branches that include a single optional prefix segment (e.g., feat/004-name) by normalizing to an “effective branch name” for validation and specs/ directory resolution, keeping exported branch values and error displays as the raw branch name.
Changes:
- Introduces effective-branch-name normalization in bash and PowerShell, wiring it into feature-branch validation and
specs/directory lookup. - Adds PowerShell parity for prefix-based feature directory resolution (
Find-FeatureDirByPrefix) and updatesGet-FeaturePathsEnvto use it. - Updates core + git extension scripts and extends pytest coverage for prefixed branches.
Show a summary per file
| File | Description |
|---|---|
| tests/test_timestamp_branches.py | Adds regression tests for single-prefix branch validation and prefix-based directory resolution (bash + pwsh skip-when-missing). |
| tests/extensions/git/test_git_extension.py | Adds git extension tests ensuring core/extension parity for single-prefix behavior (bash + pwsh). |
| scripts/powershell/common.ps1 | Adds Get-SpecKitEffectiveBranchName, updates branch validation to use it, adds Find-FeatureDirByPrefix, and switches feature-path resolution to prefix lookup. |
| scripts/bash/common.sh | Adds spec_kit_effective_branch_name and uses it in branch validation and prefix-based feature dir lookup. |
| extensions/git/scripts/powershell/git-common.ps1 | Aligns extension PowerShell branch validation with core effective-name normalization. |
| extensions/git/scripts/bash/git-common.sh | Aligns extension bash branch validation with core effective-name normalization. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comments suppressed due to low confidence (2)
scripts/powershell/common.ps1:201
- Find-FeatureDirByPrefix writes error text and then throws when multiple matches are found. Since Get-FeaturePathsEnv is used by scripts that may expect clean stdout (e.g., JSON mode), consider emitting errors on the error stream (Write-Error/Write-Warning) and returning a non-zero/controlled failure instead of throwing (or throw without first writing to stdout).
$names = ($dirMatches | ForEach-Object { $_.Name }) -join ' '
Write-Output "ERROR: Multiple spec directories found with prefix '$prefix': $names"
Write-Output "Please ensure only one spec directory exists per prefix."
throw "Multiple spec directories for prefix '$prefix'"
scripts/powershell/common.ps1:233
- The try/catch in Get-FeaturePathsEnv wraps both JSON parsing and the Find-FeatureDirByPrefix call. If Find-FeatureDirByPrefix throws (e.g., multiple prefix matches), the catch block just calls it again, which will rethrow and can duplicate error output. Consider narrowing the try/catch to only the JSON parsing/conversion, or handling prefix-resolution errors separately.
} else {
$featureDir = Find-FeatureDirByPrefix -RepoRoot $repoRoot -Branch $currentBranch
}
} catch {
$featureDir = Find-FeatureDirByPrefix -RepoRoot $repoRoot -Branch $currentBranch
- Files reviewed: 6/6 changed files
- Comments generated: 1
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds branch-name normalization to support gitflow-style prefixes (e.g., feat/004-name) while preserving existing behavior for other branch shapes. This improves both feature-branch validation and specs/ directory resolution across Bash, PowerShell, and the bundled git extension.
Changes:
- Introduces an “effective branch name” helper that strips exactly one leading path segment when the branch is exactly two segments (
x/y). - Wires normalization into branch validation and
specs/prefix-based directory lookup (core scripts + git extension scripts). - Expands pytest coverage for single-prefix acceptance/rejection and prefix-based path resolution (including PowerShell when available).
Show a summary per file
| File | Description |
|---|---|
| tests/test_timestamp_branches.py | Adds tests for single-prefix normalization in validation + prefix-based directory lookup; adds a pwsh availability guard. |
| tests/extensions/git/test_git_extension.py | Adds git extension tests ensuring bash/ps1 branch validation matches core behavior after normalization. |
| scripts/powershell/common.ps1 | Adds effective-branch helper; updates validation error messaging; adds prefix-based feature dir resolver and uses it in Get-FeaturePathsEnv. |
| scripts/bash/common.sh | Adds effective-branch helper; applies it to check_feature_branch and find_feature_dir_by_prefix; removes unused get_feature_dir. |
| extensions/git/scripts/powershell/git-common.ps1 | Aligns git extension PowerShell branch validation with core logic + normalization. |
| extensions/git/scripts/bash/git-common.sh | Aligns git extension bash branch validation with core logic + normalization. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 6/6 changed files
- Comments generated: 1
mnriem
left a comment
There was a problem hiding this comment.
Please address Copilot feedback. If not applicable, please explain why
…r 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
There was a problem hiding this comment.
Pull request overview
Adds gitflow-friendly branch-name normalization (optional single-segment prefix stripping) so feature-branch validation and specs/ directory resolution work with branches like feat/001-... while preserving raw branch names in exported values and error messages.
Changes:
- Introduces an “effective branch name” helper in bash and PowerShell and wires it into branch validation + prefix-based
specs/directory lookup. - Aligns the bundled git extension’s
git-commonscripts with the core validation logic after normalization. - Expands pytest coverage for single-prefix branches across bash, PowerShell (when available), and the git extension.
Show a summary per file
| File | Description |
|---|---|
| tests/test_timestamp_branches.py | Adds tests for single-prefix branch validation and prefix-based feature dir resolution (bash + optional pwsh). |
| tests/extensions/git/test_git_extension.py | Adds git extension tests to ensure git-common matches core behavior with single-prefix branches. |
| scripts/powershell/common.ps1 | Adds effective-name normalization, implements prefix-based feature dir resolution, and updates validation + path resolution flow. |
| scripts/bash/common.sh | Adds effective-name normalization and applies it to branch validation and prefix-based feature dir lookup. |
| extensions/git/scripts/powershell/git-common.ps1 | Aligns git extension PowerShell branch validation with core (including normalization). |
| extensions/git/scripts/bash/git-common.sh | Aligns git extension bash branch validation with core (including normalization). |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 6/6 changed files
- Comments generated: 1
| $featureJsonRaw = Get-Content -LiteralPath $featureJson -Raw | ||
| try { | ||
| $featureConfig = Get-Content $featureJson -Raw | ConvertFrom-Json | ||
| if ($featureConfig.feature_directory) { | ||
| $featureDir = $featureConfig.feature_directory | ||
| # Normalize relative paths to absolute under repo root | ||
| if (-not [System.IO.Path]::IsPathRooted($featureDir)) { | ||
| $featureDir = Join-Path $repoRoot $featureDir | ||
| } | ||
| } else { | ||
| $featureDir = Get-FeatureDir -RepoRoot $repoRoot -Branch $currentBranch | ||
| } | ||
| $featureConfig = $featureJsonRaw | ConvertFrom-Json | ||
| } catch { | ||
| $featureDir = Get-FeatureDir -RepoRoot $repoRoot -Branch $currentBranch | ||
| [Console]::Error.WriteLine("ERROR: Failed to parse .specify/feature.json: $_") | ||
| exit 1 | ||
| } |
There was a problem hiding this comment.
In Get-FeaturePathsEnv, a malformed .specify/feature.json now causes an immediate exit 1. This is a behavior change from the previous PowerShell implementation (which fell back) and diverges from the bash get_feature_paths behavior (which tolerates parse errors and falls back to prefix lookup). Consider falling back to branch-based resolution (optionally emitting a warning) instead of exiting; if you do keep failing fast, the error text should use the underlying exception message (current $_ stringification is usually noisy).
See below for a potential fix:
if ($featureConfig.feature_directory) {
$featureDir = $featureConfig.feature_directory
# Normalize relative paths to absolute under repo root
if (-not [System.IO.Path]::IsPathRooted($featureDir)) {
$featureDir = Join-Path $repoRoot $featureDir
}
} else {
$featureDir = Get-FeatureDirFromBranchPrefixOrExit -RepoRoot $repoRoot -CurrentBranch $currentBranch
}
} catch {
[Console]::Error.WriteLine("WARNING: Failed to parse .specify/feature.json: $($_.Exception.Message). Falling back to branch-based feature resolution.")
mnriem
left a comment
There was a problem hiding this comment.
Please address Copilot feedback
There was a problem hiding this comment.
Pull request overview
Adds gitflow-friendly branch name normalization (strip exactly one leading path segment) so feature-branch validation and specs/ prefix directory resolution work with branches like feat/001-x while preserving raw branch names for exports and error messages.
Changes:
- Introduces “effective branch name” normalization in core bash and PowerShell scripts and wires it into branch validation + prefix-based feature dir lookup.
- Aligns git extension
git-commonbash/PowerShell validation logic with core behavior. - Expands test coverage for single-segment prefix branches and cross-branch prefix resolution.
Show a summary per file
| File | Description |
|---|---|
| tests/test_timestamp_branches.py | Adds tests for single-segment prefixed branch validation and prefix-based directory resolution; adds pwsh-dependent integration test. |
| tests/extensions/git/test_git_extension.py | Adds git extension tests ensuring core/extension parity for single-segment prefixed branches (bash + pwsh). |
| scripts/powershell/common.ps1 | Implements effective-branch normalization, updates Test-FeatureBranch, adds prefix-based directory resolution helpers, and updates Get-FeaturePathsEnv fallback behavior. |
| scripts/bash/common.sh | Implements effective-branch normalization and applies it to branch validation and prefix-based feature dir lookup. |
| extensions/git/scripts/powershell/git-common.ps1 | Aligns git extension PowerShell branch validation with core (effective name + malformed timestamp rules). |
| extensions/git/scripts/bash/git-common.sh | Aligns git extension bash branch validation with core (effective name + malformed timestamp rules). |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 6/6 changed files
- Comments generated: 2
| if ($dirMatches.Count -eq 0) { | ||
| return (Join-Path $specsDir $branchName) | ||
| } | ||
| if ($dirMatches.Count -eq 1) { | ||
| return $dirMatches[0].FullName | ||
| } | ||
| $names = ($dirMatches | ForEach-Object { $_.Name }) -join ' ' | ||
| [Console]::Error.WriteLine("ERROR: Multiple spec directories found with prefix '$prefix': $names") | ||
| [Console]::Error.WriteLine('Please ensure only one spec directory exists per prefix.') | ||
| return $null | ||
| } |
There was a problem hiding this comment.
Find-FeatureDirByPrefix has a new multiple-matches error path (prints an error and returns $null). There’s currently no test covering this behavior, so it’s easy for regressions to slip in (e.g., returning one of the matches, or not surfacing the failure cleanly through Get-FeaturePathsEnv). Add a test that creates two specs/<prefix>-* directories and asserts the function fails deterministically (non-zero exit and helpful stderr).
|
|
||
| def _has_pwsh() -> bool: | ||
| """Check if pwsh is available.""" | ||
| try: | ||
| subprocess.run(["pwsh", "--version"], capture_output=True, check=True) | ||
| return True | ||
| except (FileNotFoundError, subprocess.CalledProcessError): | ||
| return False |
There was a problem hiding this comment.
This skip helper runs an external pwsh --version subprocess at import/collection time (because it’s called inside @pytest.mark.skipif(...)). That can noticeably slow test collection and may fail for reasons other than “pwsh not installed”. Consider replacing it with a cheaper availability check (e.g., shutil.which("pwsh") is not None) or caching the result in a module-level constant.
| def _has_pwsh() -> bool: | |
| """Check if pwsh is available.""" | |
| try: | |
| subprocess.run(["pwsh", "--version"], capture_output=True, check=True) | |
| return True | |
| except (FileNotFoundError, subprocess.CalledProcessError): | |
| return False | |
| HAS_PWSH = shutil.which("pwsh") is not None | |
| def _has_pwsh() -> bool: | |
| """Check if pwsh is available.""" | |
| return HAS_PWSH |
mnriem
left a comment
There was a problem hiding this comment.
Please address Copilot feedback
Description
Teams using gitflow (or similar) often use branch names like
feat/001-my-featureorhotfix/20260319-143022-fix, while Spec Kit expects the feature segment to start with a sequential number or aYYYYMMDD-HHMMSS-timestamp. That causedcheck_feature_branch/ prerequisite scripts to reject valid workflows and made PowerShell fall back to a literalspecs/<full-branch>path instead of prefix-based resolution.This PR adds a shared effective branch name step: when the full ref matches exactly two slash-free segments separated by a single
/(^([^/]+)/([^/]+)$), validation andspecs/prefix lookup use only the second segment. Otherwise behavior is unchanged.CURRENT_BRANCH/ exported values stay the raw Git (orSPECIFY_FEATURE) name; error messages show the raw name.spec_kit_effective_branch_name, wired intocheck_feature_branchandfind_feature_dir_by_prefixinscripts/bash/common.sh.Get-SpecKitEffectiveBranchName,Test-FeatureBranch, newFind-FeatureDirByPrefix(parity with bash), andGet-FeaturePathsEnvfallbacks inscripts/powershell/common.ps1.git-commonbash/ps1 aligned with core validation after normalization.get_feature_dir/Get-FeatureDir.Testing
uv run pytest tests/— 1247 passed, 20 skipped (pwsh-skipped where unavailable).Tested locally with
uv run specify --helpRan existing tests with
uv sync && uv run pytestTested with a sample project (if applicable)
AI Disclosure
Implementation and tests were developed with Cursor / AI-assisted editing from an agreed design (single optional path segment, no hardcoded prefix list, parity bash/PowerShell, extension alignment).
Made with Cursor