From b86de5863431e56f7fac5d54973ea8dc9339074a Mon Sep 17 00:00:00 2001 From: Pascal Date: Sat, 27 Jun 2026 08:17:32 +0200 Subject: [PATCH 1/2] fix(scripts): portable uppercase for branch-name acronym retention Branch-name generation keeps short uppercase acronyms (e.g. "AI") by re-checking the lowercased word against the original description with ${word^^}. That parameter expansion is bash 4+ only; on macOS's default bash 3.2 it errors with "bad substitution", so the acronym/short-word retention branch never matches and those words are dropped ("go AI now" yields 001-now instead of 001-ai-now). Use tr '[:lower:]' '[:upper:]' instead, which is portable. Applies to both the core create-new-feature.sh and the git extension's create-new-feature-branch.sh. The existing test_branch_name_short_word_case_sensitivity / test_short_word_retention tests cover this and now pass on bash 3.2 (CI runs on bash 4+/Linux, so they passed there already). (Disclosure: an AI coding agent surfaced the failure while running the suite on macOS and pinned the root cause; fix written and reviewed by me.) --- CHANGELOG.md | 2 ++ extensions/git/scripts/bash/create-new-feature-branch.sh | 4 +++- scripts/bash/create-new-feature.sh | 6 ++++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c036a1884..66d886bedf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ +- fix(scripts): use a portable uppercase (`tr`) for branch-name acronym retention instead of `${word^^}`, which is bash 4+ only and broke acronym/short-word retention on macOS's default bash 3.2 + ## [0.11.9] - 2026-06-26 ### Changed diff --git a/extensions/git/scripts/bash/create-new-feature-branch.sh b/extensions/git/scripts/bash/create-new-feature-branch.sh index d638b048c9..fa868c7c34 100755 --- a/extensions/git/scripts/bash/create-new-feature-branch.sh +++ b/extensions/git/scripts/bash/create-new-feature-branch.sh @@ -288,7 +288,9 @@ generate_branch_name() { if ! echo "$word" | grep -qiE "$stop_words"; then if [ ${#word} -ge 3 ]; then meaningful_words+=("$word") - elif echo "$description" | grep -qw -- "${word^^}"; then + # Uppercase via tr (portable) rather than ${word^^}, which is bash 4+ + # only and breaks on macOS's default bash 3.2 ("bad substitution"). + elif echo "$description" | grep -qw -- "$(printf '%s' "$word" | tr '[:lower:]' '[:upper:]')"; then meaningful_words+=("$word") fi fi diff --git a/scripts/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh index c9609764f7..bc1f90f60e 100644 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -152,8 +152,10 @@ generate_branch_name() { if ! echo "$word" | grep -qiE "$stop_words"; then if [ ${#word} -ge 3 ]; then meaningful_words+=("$word") - elif echo "$description" | grep -q "\b${word^^}\b"; then - # Keep short words if they appear as uppercase in original (likely acronyms) + # Keep short words if they appear as uppercase in original (likely acronyms). + # Uppercase via tr (portable) rather than ${word^^}, which is bash 4+ only + # and breaks on macOS's default bash 3.2 ("bad substitution"). + elif echo "$description" | grep -q "\b$(printf '%s' "$word" | tr '[:lower:]' '[:upper:]')\b"; then meaningful_words+=("$word") fi fi From c8db57281107d9135f9196baf53cec9eb536557b Mon Sep 17 00:00:00 2001 From: Pascal Date: Sat, 27 Jun 2026 15:36:36 +0200 Subject: [PATCH 2/2] fix(scripts): portability follow-ups from code review - core create-new-feature.sh: match the acronym with `grep -qw` (POSIX whole-word) instead of `\b...\b` (GNU/BSD-only), matching the git extension and dropping a non-POSIX construct. - lint: add a CI guard rejecting bash 4+ case-modification expansions in *.sh. shellcheck assumes bash 4+ from the shebang and can't flag them, and CI has no bash-3.2 lane, so this prevents silently re-shipping the macOS regression this PR fixes. - update a stale PowerShell extension comment that cited the removed bash idiom. (Disclosure: prompted by an AI code review of the PR; written and reviewed by me.) --- .github/workflows/lint.yml | 12 ++++++++++++ CHANGELOG.md | 2 +- .../git/scripts/bash/create-new-feature-branch.sh | 4 ++-- .../scripts/powershell/create-new-feature-branch.ps1 | 7 ++++--- scripts/bash/create-new-feature.sh | 8 ++++---- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 84074b4791..e85802a53d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -54,3 +54,15 @@ jobs: # (notably SC2155). Tighten in a follow-up after cleanup. - name: Run shellcheck on shell scripts run: git ls-files -z -- '*.sh' | xargs -0 shellcheck --severity=error + + # macOS ships bash 3.2, where ${var^^} / ${var,,} error with "bad + # substitution". shellcheck assumes bash 4+ from the shebang and cannot + # flag these, so guard explicitly; use tr for portable case conversion. + - name: Reject bash 4+ case-modification expansions + run: | + matches=$(git ls-files -z -- '*.sh' | xargs -0 grep -nE '\$\{[A-Za-z_][A-Za-z0-9_]*(\[[^]]*\])?(\^|,)' || true) + if [ -n "$matches" ]; then + echo "Found bash 4+ case-modification expansion(s); use tr for portability (macOS ships bash 3.2):" + echo "$matches" + exit 1 + fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 66d886bedf..9695b5f669 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ -- fix(scripts): use a portable uppercase (`tr`) for branch-name acronym retention instead of `${word^^}`, which is bash 4+ only and broke acronym/short-word retention on macOS's default bash 3.2 +- fix(scripts): make branch-name acronym retention portable (uppercase via `tr` instead of bash-4-only `${word^^}`, whole-word match via `grep -w` instead of GNU/BSD-only `\b`), fixing acronym/short-word retention on macOS's default bash 3.2 ## [0.11.9] - 2026-06-26 diff --git a/extensions/git/scripts/bash/create-new-feature-branch.sh b/extensions/git/scripts/bash/create-new-feature-branch.sh index fa868c7c34..ba1c4a7220 100755 --- a/extensions/git/scripts/bash/create-new-feature-branch.sh +++ b/extensions/git/scripts/bash/create-new-feature-branch.sh @@ -288,8 +288,8 @@ generate_branch_name() { if ! echo "$word" | grep -qiE "$stop_words"; then if [ ${#word} -ge 3 ]; then meaningful_words+=("$word") - # Uppercase via tr (portable) rather than ${word^^}, which is bash 4+ - # only and breaks on macOS's default bash 3.2 ("bad substitution"). + # Uppercase via tr (portable) rather than bash's 4+ "^^" case + # expansion, which breaks on macOS's default bash 3.2 (bad substitution). elif echo "$description" | grep -qw -- "$(printf '%s' "$word" | tr '[:lower:]' '[:upper:]')"; then meaningful_words+=("$word") fi diff --git a/extensions/git/scripts/powershell/create-new-feature-branch.ps1 b/extensions/git/scripts/powershell/create-new-feature-branch.ps1 index 6a4417f8b9..cd41232e3b 100644 --- a/extensions/git/scripts/powershell/create-new-feature-branch.ps1 +++ b/extensions/git/scripts/powershell/create-new-feature-branch.ps1 @@ -253,9 +253,10 @@ function Get-BranchName { if ($word.Length -ge 3) { $meaningfulWords += $word } 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. + # Case-sensitive (-cmatch) to mirror the bash twin's case-sensitive + # whole-word acronym match: 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/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh index bc1f90f60e..8887f39399 100644 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -152,10 +152,10 @@ generate_branch_name() { if ! echo "$word" | grep -qiE "$stop_words"; then if [ ${#word} -ge 3 ]; then meaningful_words+=("$word") - # Keep short words if they appear as uppercase in original (likely acronyms). - # Uppercase via tr (portable) rather than ${word^^}, which is bash 4+ only - # and breaks on macOS's default bash 3.2 ("bad substitution"). - elif echo "$description" | grep -q "\b$(printf '%s' "$word" | tr '[:lower:]' '[:upper:]')\b"; then + # Keep short words that appear as an uppercase acronym in the original. + # Uppercase via tr and match with grep -w (both portable) rather than + # bash's 4+ "^^" case expansion (breaks on macOS bash 3.2) and \b (non-POSIX). + elif echo "$description" | grep -qw -- "$(printf '%s' "$word" | tr '[:lower:]' '[:upper:]')"; then meaningful_words+=("$word") fi fi