Skip to content

Commit 9fe1c4c

Browse files
jawwad-aliclaude
andauthored
fix(scripts): keep PowerShell branch-name acronym match case-sensitive (parity with bash) (#3129)
* fix(scripts): keep PowerShell branch-name acronym match case-sensitive Get-BranchName keeps a sub-3-character word only when it appears as an UPPERCASE acronym in the description. The bash twin checks this case-sensitively (grep "\b${word^^}\b" / grep -qw -- "${word^^}"), but the PowerShell twin used -match, which is case-INSENSITIVE, so it kept EVERY short word regardless of case -- contradicting its own comment and diverging from bash. The same description then produced different spec-directory and branch names on Windows/PowerShell vs macOS/Linux (e.g. "Add go support" -> 001-go-support instead of 001-support), desyncing specs/, feature.json, and git branches across a mixed-OS team. Use the case-sensitive -cmatch so a short word is kept only for a genuine uppercase acronym, matching bash. Applied to both the core scripts/powershell/create-new-feature.ps1 and the git extension's create-new-feature-branch.ps1. Add bash + PowerShell regression tests (core and git-extension) asserting a lowercase short word is dropped while an uppercase acronym is kept. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: fix article grammar in branch-name docstrings Address review: 'an UPPERCASE acronym' -> 'an acronym in UPPERCASE' across the four branch-name case-sensitivity test docstrings (the indefinite article reads cleanly before 'acronym'). Docstring-only; no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent bb37b18 commit 9fe1c4c

3 files changed

Lines changed: 67 additions & 1 deletion

File tree

β€Žextensions/git/scripts/powershell/create-new-feature-branch.ps1β€Ž

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,10 @@ function Get-BranchName {
252252
if ($stopWords -contains $word) { continue }
253253
if ($word.Length -ge 3) {
254254
$meaningfulWords += $word
255-
} elseif ($Description -match "\b$($word.ToUpper())\b") {
255+
} elseif ($Description -cmatch "\b$($word.ToUpper())\b") {
256+
# Case-sensitive (-cmatch) to mirror the bash twin's `grep -qw -- "${word^^}"`:
257+
# keep a short word only when its UPPERCASE form appears in the original
258+
# (an acronym). -match is case-insensitive and would keep every short word.
256259
$meaningfulWords += $word
257260
}
258261
}

β€Žtests/extensions/git/test_git_extension.pyβ€Ž

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,24 @@ def test_creates_branch_sequential(self, tmp_path: Path):
298298
assert data["BRANCH_NAME"] == "001-user-auth"
299299
assert data["FEATURE_NUM"] == "001"
300300

301+
def test_branch_name_short_word_case_sensitivity(self, tmp_path: Path):
302+
"""A short word is dropped from the derived branch name unless it appears
303+
as an acronym in UPPERCASE in the description (case-sensitive, must match the
304+
PowerShell twin)."""
305+
project = _setup_project(tmp_path)
306+
# lowercase "go" (<3 chars, not an uppercase acronym) is dropped
307+
r1 = _run_bash(
308+
"create-new-feature-branch.sh", project, "--json", "--dry-run", "Add go support",
309+
)
310+
assert r1.returncode == 0, r1.stderr
311+
assert json.loads(r1.stdout)["BRANCH_NAME"] == "001-support"
312+
# uppercase "GO" is kept as an acronym
313+
r2 = _run_bash(
314+
"create-new-feature-branch.sh", project, "--json", "--dry-run", "Use GO now",
315+
)
316+
assert r2.returncode == 0, r2.stderr
317+
assert json.loads(r2.stdout)["BRANCH_NAME"] == "001-use-go-now"
318+
301319
def test_creates_branch_timestamp(self, tmp_path: Path):
302320
"""Extension create-new-feature-branch.sh creates timestamp branch."""
303321
project = _setup_project(tmp_path)
@@ -426,6 +444,21 @@ def test_creates_branch_sequential(self, tmp_path: Path):
426444
data = json.loads(result.stdout)
427445
assert data["BRANCH_NAME"] == "001-user-auth"
428446

447+
def test_branch_name_short_word_case_sensitivity(self, tmp_path: Path):
448+
"""PowerShell must match the bash twin: a short word is dropped unless it
449+
appears as an acronym in UPPERCASE (case-sensitive -cmatch, not -match)."""
450+
project = _setup_project(tmp_path)
451+
r1 = _run_pwsh(
452+
"create-new-feature-branch.ps1", project, "-Json", "-DryRun", "Add go support",
453+
)
454+
assert r1.returncode == 0, r1.stderr
455+
assert json.loads(r1.stdout)["BRANCH_NAME"] == "001-support"
456+
r2 = _run_pwsh(
457+
"create-new-feature-branch.ps1", project, "-Json", "-DryRun", "Use GO now",
458+
)
459+
assert r2.returncode == 0, r2.stderr
460+
assert json.loads(r2.stdout)["BRANCH_NAME"] == "001-use-go-now"
461+
429462
def test_dry_run_counts_branches_checked_out_in_worktrees(self, tmp_path: Path):
430463
"""Branches checked out in sibling worktrees still reserve their prefix."""
431464
project = _setup_project(tmp_path / "project")

β€Žtests/test_timestamp_branches.pyβ€Ž

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,17 @@ def test_sequential_default_with_existing_specs(self, git_repo: Path):
240240
assert branch is not None
241241
assert re.match(r"^\d{3,}-new-feat$", branch), f"unexpected branch: {branch}"
242242

243+
def test_branch_name_short_word_case_sensitivity(self, git_repo: Path):
244+
"""A short word is dropped from the derived branch name unless it appears
245+
as an acronym in UPPERCASE in the description. The PowerShell twin must use
246+
case-sensitive -cmatch to produce the same result."""
247+
r1 = run_script(git_repo, "--json", "--dry-run", "Add go support")
248+
assert r1.returncode == 0, r1.stderr
249+
assert json.loads(r1.stdout)["BRANCH_NAME"] == "001-support"
250+
r2 = run_script(git_repo, "--json", "--dry-run", "Use GO now")
251+
assert r2.returncode == 0, r2.stderr
252+
assert json.loads(r2.stdout)["BRANCH_NAME"] == "001-use-go-now"
253+
243254
def test_sequential_ignores_timestamp_dirs(self, git_repo: Path):
244255
"""Sequential numbering skips timestamp dirs when computing next number."""
245256
(git_repo / "specs" / "002-first-feat").mkdir(parents=True)
@@ -272,6 +283,25 @@ def test_powershell_scanner_uses_long_tryparse_for_large_prefixes(self):
272283
assert "[long]::TryParse($matches[1], [ref]$num)" in content
273284
assert "$num = [int]$matches[1]" not in content
274285

286+
@pytest.mark.skipif(not _has_pwsh(), reason="pwsh not installed")
287+
def test_branch_name_short_word_case_sensitivity(self, ps_git_repo: Path):
288+
"""Core create-new-feature.ps1 must drop a short word unless it appears as
289+
an acronym in UPPERCASE (case-sensitive -cmatch), matching the bash twin."""
290+
script = ps_git_repo / "scripts" / "powershell" / "create-new-feature.ps1"
291+
292+
def _run(desc: str) -> subprocess.CompletedProcess:
293+
return subprocess.run(
294+
["pwsh", "-NoProfile", "-File", str(script), "-Json", "-DryRun", desc],
295+
cwd=ps_git_repo, capture_output=True, text=True,
296+
)
297+
298+
r1 = _run("Add go support")
299+
assert r1.returncode == 0, r1.stderr
300+
assert json.loads(r1.stdout)["BRANCH_NAME"] == "001-support"
301+
r2 = _run("Use GO now")
302+
assert r2.returncode == 0, r2.stderr
303+
assert json.loads(r2.stdout)["BRANCH_NAME"] == "001-use-go-now"
304+
275305

276306
# ── check_feature_branch Tests ───────────────────────────────────────────────
277307

0 commit comments

Comments
Β (0)