Skip to content

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

Open
gabrielhmsantos wants to merge 3 commits intogithub:mainfrom
gabrielhmsantos:feature/support-single-prefix-feature-branches
Open

feat(scripts): optional single-segment branch prefix for gitflow#2202
gabrielhmsantos wants to merge 3 commits intogithub:mainfrom
gabrielhmsantos:feature/support-single-prefix-feature-branches

Conversation

@gabrielhmsantos
Copy link
Copy Markdown
Contributor

@gabrielhmsantos gabrielhmsantos commented Apr 13, 2026

Description

Teams using gitflow (or similar) often use branch names like feat/001-my-feature or hotfix/20260319-143022-fix, while Spec Kit expects the feature segment to start with a sequential number or a YYYYMMDD-HHMMSS- timestamp. That caused check_feature_branch / prerequisite scripts to reject valid workflows and made PowerShell fall back to a literal specs/<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 and specs/ prefix lookup use only the second segment. Otherwise behavior is unchanged. CURRENT_BRANCH / exported values stay the raw Git (or SPECIFY_FEATURE) name; error messages show the raw name.

  • Bash: spec_kit_effective_branch_name, wired into check_feature_branch and find_feature_dir_by_prefix in scripts/bash/common.sh.
  • PowerShell: Get-SpecKitEffectiveBranchName, Test-FeatureBranch, new Find-FeatureDirByPrefix (parity with bash), and Get-FeaturePathsEnv fallbacks in scripts/powershell/common.ps1.
  • Git extension: git-common bash/ps1 aligned with core validation after normalization.
  • Removed unused get_feature_dir / Get-FeatureDir.

Testing

  • uv run pytest tests/ — 1247 passed, 20 skipped (pwsh-skipped where unavailable).

  • Tested locally with uv run specify --help

  • Ran existing tests with uv sync && uv run pytest

  • Tested with a sample project (if applicable)

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (describe below)

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

- 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
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 updates Get-FeaturePathsEnv to 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>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Collaborator

@mnriem mnriem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@mnriem mnriem requested a review from Copilot April 13, 2026 16:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-common scripts 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

Comment on lines +233 to +239
$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
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copilot uses AI. Check for mistakes.
@mnriem mnriem requested review from Copilot and mnriem April 13, 2026 16:32
Copy link
Copy Markdown
Collaborator

@mnriem mnriem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address Copilot feedback

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-common bash/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

Comment on lines +190 to +200
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
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines 29 to +36

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
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
@mnriem mnriem self-requested a review April 13, 2026 16:38
Copy link
Copy Markdown
Collaborator

@mnriem mnriem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address Copilot feedback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants