From 4038d370bfbd7360aeb1beb293427f7eba418b38 Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Tue, 30 Jun 2026 06:43:48 -0500 Subject: [PATCH] chore: align CI Python matrix with devguide lifecycle + fix bash 3.2 portability (#3244) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: align CI Python matrix with devguide release lifecycle Run the pytest matrix only on the bugfix (maintenance) releases — 3.13 and 3.14 — instead of 3.11/3.12/3.13, and point the ruff lint job at the latest interpreter (3.14). The supported floor stays at requires-python >= 3.11 (oldest non-EOL security release): older security versions are supported by claim and fixed reactively rather than gated on a wide per-commit matrix. Also add macos-latest to the OS matrix so macOS regressions are caught. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: make bash scripts portable to bash 3.2 (macOS system /bin/bash) Adding macos-latest to the CI matrix surfaced two pre-existing bash 3.2 incompatibilities (macOS ships bash 3.2 as /bin/bash): 1. update-agent-context.sh embedded Python heredocs inside $(...) command substitution. bash 3.2 mis-parses an apostrophe in a heredoc body nested in $(...), failing with "unexpected EOF while looking for matching `''". Removed the apostrophes from the affected $()-nested heredoc body and documented the constraint to prevent regressions. 2. create-new-feature-branch.sh and create-new-feature.sh used the bash 4+ ${word^^} uppercase parameter expansion, which errors as a "bad substitution" on bash 3.2 and caused short uppercase acronyms (e.g. "GO") to be dropped from derived branch names. Replaced with a portable `tr '[:lower:]' '[:upper:]'` pipeline. Verified the full test suite passes under bash 3.2.57 and shellcheck (--severity=error) is clean. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address review feedback on bash 3.2 portability changes - create-new-feature.sh: replace the non-portable `\b...\b` grep word-boundary (BSD grep treats `\b` as a backspace, so the acronym branch could silently fail) with `grep -qw`, matching its twin create-new-feature-branch.sh, and pipe the description via `printf '%s'` instead of `echo`. - create-new-feature-branch.sh: switch the acronym check to `printf '%s'` as well so both twins are identical and avoid `echo` on user-provided text. - update-agent-context.sh: reword the apostrophe-free self-seeding comment to be clearer and less easy to misread. Verified under bash 3.2.57 (full bash-script suite green) and shellcheck --severity=error. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/test.yml | 6 +++--- .../scripts/bash/update-agent-context.sh | 21 ++++++++++++------- .../scripts/bash/create-new-feature-branch.sh | 2 +- scripts/bash/create-new-feature.sh | 2 +- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f8dde1963..be3caa784 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,7 @@ jobs: - name: Set up Python uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 with: - python-version: "3.13" + python-version: "3.14" - name: Run ruff check run: uvx ruff check src/ @@ -30,8 +30,8 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, windows-latest] - python-version: ["3.11", "3.12", "3.13"] + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ["3.13", "3.14"] steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 diff --git a/extensions/agent-context/scripts/bash/update-agent-context.sh b/extensions/agent-context/scripts/bash/update-agent-context.sh index c3e5c2020..b7121a2f6 100755 --- a/extensions/agent-context/scripts/bash/update-agent-context.sh +++ b/extensions/agent-context/scripts/bash/update-agent-context.sh @@ -59,6 +59,13 @@ case "$(uname -s 2>/dev/null || true)" in esac # Parse extension config once; emit context files as JSON, followed by marker strings. +# +# NOTE (bash 3.2 / macOS portability): the embedded Python heredocs below run +# inside $(...) command substitution. bash 3.2 (the system /bin/bash on macOS) +# mis-parses a single-quote/apostrophe in a heredoc body nested in $(...), +# failing with "unexpected EOF while looking for matching `''". Keep these +# $(...)-nested heredoc bodies free of apostrophes (use double quotes in Python +# string literals and avoid contractions in comments). if ! _raw_opts="$("$_python" - "$EXT_CONFIG" "$_case_insensitive_context_files" "$PROJECT_ROOT" <<'PY' import json import sys @@ -113,11 +120,11 @@ if isinstance(raw_files, list): if not context_files: add_context_file(get_str(data, "context_file")) if not context_files: - # Self-seed: the agent-context extension owns its lifecycle, so when its - # own config declares no target it derives one from the active integration - # recorded in init-options.json, using the extension's OWN bundled mapping - # (agent-context-defaults.json). This is independent of the Specify CLI by - # design — nothing here imports specify_cli. + # Self-seed: the agent-context extension manages its own lifecycle, so when + # its config declares no target, it derives one from the active integration + # recorded in init-options.json, mapped through the bundled + # agent-context-defaults.json file. This is independent of the Specify CLI + # by design; nothing here imports specify_cli. project_root = sys.argv[3] if len(sys.argv) > 3 else "." integration_key = "" try: @@ -144,7 +151,7 @@ if not context_files: except Exception: print( "agent-context: unable to read %s; cannot self-seed the context " - "file. Set 'context_file' in the extension config." % defaults_path, + "file. Set context_file in the extension config." % defaults_path, file=sys.stderr, ) mapping = {} @@ -152,7 +159,7 @@ if not context_files: if not context_files: print( "agent-context: no default context file is known for integration " - "'%s'. Set 'context_file' in the extension config to choose one." + "%s. Set context_file in the extension config to choose one." % integration_key, file=sys.stderr, ) diff --git a/extensions/git/scripts/bash/create-new-feature-branch.sh b/extensions/git/scripts/bash/create-new-feature-branch.sh index d638b048c..a13e49812 100755 --- a/extensions/git/scripts/bash/create-new-feature-branch.sh +++ b/extensions/git/scripts/bash/create-new-feature-branch.sh @@ -288,7 +288,7 @@ 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 + elif printf '%s' "$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 c9609764f..746e88a2d 100644 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -152,7 +152,7 @@ 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 + elif printf '%s' "$description" | grep -qw -- "$(printf '%s' "$word" | tr '[:lower:]' '[:upper:]')"; then # Keep short words if they appear as uppercase in original (likely acronyms) meaningful_words+=("$word") fi