diff --git a/extensions/git/scripts/powershell/create-new-feature-branch.ps1 b/extensions/git/scripts/powershell/create-new-feature-branch.ps1 index 65358df0ba..6a4417f8b9 100644 --- a/extensions/git/scripts/powershell/create-new-feature-branch.ps1 +++ b/extensions/git/scripts/powershell/create-new-feature-branch.ps1 @@ -252,7 +252,10 @@ function Get-BranchName { if ($stopWords -contains $word) { continue } if ($word.Length -ge 3) { $meaningfulWords += $word - } elseif ($Description -match "\b$($word.ToUpper())\b") { + } elseif ($Description -cmatch "\b$($word.ToUpper())\b") { + # Case-sensitive (-cmatch) to mirror the bash twin's `grep -qw -- "${word^^}"`: + # keep a short word only when its UPPERCASE form appears in the original + # (an acronym). -match is case-insensitive and would keep every short word. $meaningfulWords += $word } } diff --git a/scripts/powershell/create-new-feature.ps1 b/scripts/powershell/create-new-feature.ps1 index 8627caa6e7..8ad0ed822a 100644 --- a/scripts/powershell/create-new-feature.ps1 +++ b/scripts/powershell/create-new-feature.ps1 @@ -111,8 +111,11 @@ function Get-BranchName { # Keep words that are length >= 3 OR appear as uppercase in original (likely acronyms) if ($word.Length -ge 3) { $meaningfulWords += $word - } elseif ($Description -match "\b$($word.ToUpper())\b") { - # Keep short words if they appear as uppercase in original (likely acronyms) + } elseif ($Description -cmatch "\b$($word.ToUpper())\b") { + # Keep short words if they appear as uppercase in original (likely acronyms). + # Use -cmatch (case-sensitive) so this matches the bash twin's + # case-sensitive `grep "\b${word^^}\b"`; -match would also match the + # lowercased word and keep every short word regardless of case. $meaningfulWords += $word } } diff --git a/tests/extensions/git/test_git_extension.py b/tests/extensions/git/test_git_extension.py index 3d40aef4ee..2017dae627 100644 --- a/tests/extensions/git/test_git_extension.py +++ b/tests/extensions/git/test_git_extension.py @@ -298,6 +298,24 @@ def test_creates_branch_sequential(self, tmp_path: Path): assert data["BRANCH_NAME"] == "001-user-auth" assert data["FEATURE_NUM"] == "001" + def test_branch_name_short_word_case_sensitivity(self, tmp_path: Path): + """A short word is dropped from the derived branch name unless it appears + as an acronym in UPPERCASE in the description (case-sensitive, must match the + PowerShell twin).""" + project = _setup_project(tmp_path) + # lowercase "go" (<3 chars, not an uppercase acronym) is dropped + r1 = _run_bash( + "create-new-feature-branch.sh", project, "--json", "--dry-run", "Add go support", + ) + assert r1.returncode == 0, r1.stderr + assert json.loads(r1.stdout)["BRANCH_NAME"] == "001-support" + # uppercase "GO" is kept as an acronym + r2 = _run_bash( + "create-new-feature-branch.sh", project, "--json", "--dry-run", "Use GO now", + ) + assert r2.returncode == 0, r2.stderr + assert json.loads(r2.stdout)["BRANCH_NAME"] == "001-use-go-now" + def test_creates_branch_timestamp(self, tmp_path: Path): """Extension create-new-feature-branch.sh creates timestamp branch.""" project = _setup_project(tmp_path) @@ -426,6 +444,21 @@ def test_creates_branch_sequential(self, tmp_path: Path): data = json.loads(result.stdout) assert data["BRANCH_NAME"] == "001-user-auth" + def test_branch_name_short_word_case_sensitivity(self, tmp_path: Path): + """PowerShell must match the bash twin: a short word is dropped unless it + appears as an acronym in UPPERCASE (case-sensitive -cmatch, not -match).""" + project = _setup_project(tmp_path) + r1 = _run_pwsh( + "create-new-feature-branch.ps1", project, "-Json", "-DryRun", "Add go support", + ) + assert r1.returncode == 0, r1.stderr + assert json.loads(r1.stdout)["BRANCH_NAME"] == "001-support" + r2 = _run_pwsh( + "create-new-feature-branch.ps1", project, "-Json", "-DryRun", "Use GO now", + ) + assert r2.returncode == 0, r2.stderr + assert json.loads(r2.stdout)["BRANCH_NAME"] == "001-use-go-now" + def test_dry_run_counts_branches_checked_out_in_worktrees(self, tmp_path: Path): """Branches checked out in sibling worktrees still reserve their prefix.""" project = _setup_project(tmp_path / "project") diff --git a/tests/test_timestamp_branches.py b/tests/test_timestamp_branches.py index 1856afb972..14032030ed 100644 --- a/tests/test_timestamp_branches.py +++ b/tests/test_timestamp_branches.py @@ -240,6 +240,17 @@ def test_sequential_default_with_existing_specs(self, git_repo: Path): assert branch is not None assert re.match(r"^\d{3,}-new-feat$", branch), f"unexpected branch: {branch}" + def test_branch_name_short_word_case_sensitivity(self, git_repo: Path): + """A short word is dropped from the derived branch name unless it appears + as an acronym in UPPERCASE in the description. The PowerShell twin must use + case-sensitive -cmatch to produce the same result.""" + r1 = run_script(git_repo, "--json", "--dry-run", "Add go support") + assert r1.returncode == 0, r1.stderr + assert json.loads(r1.stdout)["BRANCH_NAME"] == "001-support" + r2 = run_script(git_repo, "--json", "--dry-run", "Use GO now") + assert r2.returncode == 0, r2.stderr + assert json.loads(r2.stdout)["BRANCH_NAME"] == "001-use-go-now" + def test_sequential_ignores_timestamp_dirs(self, git_repo: Path): """Sequential numbering skips timestamp dirs when computing next number.""" (git_repo / "specs" / "002-first-feat").mkdir(parents=True) @@ -272,6 +283,25 @@ def test_powershell_scanner_uses_long_tryparse_for_large_prefixes(self): assert "[long]::TryParse($matches[1], [ref]$num)" in content assert "$num = [int]$matches[1]" not in content + @pytest.mark.skipif(not _has_pwsh(), reason="pwsh not installed") + def test_branch_name_short_word_case_sensitivity(self, ps_git_repo: Path): + """Core create-new-feature.ps1 must drop a short word unless it appears as + an acronym in UPPERCASE (case-sensitive -cmatch), matching the bash twin.""" + script = ps_git_repo / "scripts" / "powershell" / "create-new-feature.ps1" + + def _run(desc: str) -> subprocess.CompletedProcess: + return subprocess.run( + ["pwsh", "-NoProfile", "-File", str(script), "-Json", "-DryRun", desc], + cwd=ps_git_repo, capture_output=True, text=True, + ) + + r1 = _run("Add go support") + assert r1.returncode == 0, r1.stderr + assert json.loads(r1.stdout)["BRANCH_NAME"] == "001-support" + r2 = _run("Use GO now") + assert r2.returncode == 0, r2.stderr + assert json.loads(r2.stdout)["BRANCH_NAME"] == "001-use-go-now" + # ── check_feature_branch Tests ───────────────────────────────────────────────