From 6a045cd9539cfe0b04ddca68595fd9def4fb45c5 Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:23:58 +0000 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20implement=20issue=20#1210=20?= =?UTF-8?q?=E2=80=94=20[Phase=202]=20Switch=20feature-ideation-reusable.ym?= =?UTF-8?q?l=20model=20default=20to=20the=20opus=20family=20and=20verify?= =?UTF-8?q?=20alias=20resolution?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workflows/feature-ideation-reusable.yml | 8 ++-- .../feature-ideation/model-input.bats | 39 +++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 test/workflows/feature-ideation/model-input.bats diff --git a/.github/workflows/feature-ideation-reusable.yml b/.github/workflows/feature-ideation-reusable.yml index 1fe127627..017613dd6 100644 --- a/.github/workflows/feature-ideation-reusable.yml +++ b/.github/workflows/feature-ideation-reusable.yml @@ -50,10 +50,10 @@ on: default: 'standard' type: string model: - description: > - Model family — `opus` (default, recommended), `sonnet`, or `haiku`. - The resolver/CLI maps the family to the current version ID. An - operator MAY override with a concrete model ID for a one-off pin. + # The Claude Code CLI resolves the family client-side (`--model opus` + # in the analyst step) to the current version ID — never pin a + # version ID here. See standards/agent-standards.md. + description: 'Model family (`opus` recommended - Sonnet produces shallower adversarial passes) or an explicit model id override' required: false default: 'opus' type: string diff --git a/test/workflows/feature-ideation/model-input.bats b/test/workflows/feature-ideation/model-input.bats new file mode 100644 index 000000000..dcfcec2ae --- /dev/null +++ b/test/workflows/feature-ideation/model-input.bats @@ -0,0 +1,39 @@ +#!/usr/bin/env bats +# Tests for the `model` input of the reusable feature-ideation workflow +# (issue #1210). The default must be the model FAMILY (`opus`) so the BMAD +# Analyst auto-tracks the current Opus via the Claude Code CLI's client-side +# alias resolution — a pinned version id must never be the default. + +load 'helpers/setup' + +WF="${TT_REPO_ROOT}/.github/workflows/feature-ideation-reusable.yml" + +@test "model input: default is the opus family" { + run yq -r '.on.workflow_call.inputs.model.default' "$WF" + [ "$status" -eq 0 ] + [ "$output" = "opus" ] +} + +@test "model input: default is not a pinned claude-* version id" { + run yq -r '.on.workflow_call.inputs.model.default' "$WF" + [ "$status" -eq 0 ] + [[ "$output" != claude-* ]] +} + +@test "model input: description recommends the opus family with an id override" { + run yq -r '.on.workflow_call.inputs.model.description' "$WF" + [ "$status" -eq 0 ] + [ "$output" = 'Model family (`opus` recommended - Sonnet produces shallower adversarial passes) or an explicit model id override' ] +} + +@test "analyst step: model is passed via --model so the CLI resolves the family" { + run yq -r '.jobs[].steps[]? | select(.name == "Run Claude Code — BMAD Analyst") | .with.claude_args' "$WF" + [ "$status" -eq 0 ] + echo "$output" | grep -qF -- '--model ${{ inputs.model }}' +} + +@test "analyst step: no ANTHROPIC_MODEL env bypasses the CLI's alias resolver" { + run yq -r '.jobs[].steps[]? | select(.name == "Run Claude Code — BMAD Analyst") | .env | has("ANTHROPIC_MODEL")' "$WF" + [ "$status" -eq 0 ] + [ "$output" = "false" ] +} From 88d912f268ac4e23285f421c367f9d838a522fff Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:28:01 +0000 Subject: [PATCH 2/7] fix(reviews): restore sonnet/haiku in model description; harden model-input bats Test-Change-Justification: model-input.bats is new in this PR (#1245); edits address review threads (gemini yq null-env, bash glob) and the description change cubic flagged on #1245. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/feature-ideation-reusable.yml | 2 +- test/workflows/feature-ideation/model-input.bats | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/feature-ideation-reusable.yml b/.github/workflows/feature-ideation-reusable.yml index 017613dd6..c3cd265a2 100644 --- a/.github/workflows/feature-ideation-reusable.yml +++ b/.github/workflows/feature-ideation-reusable.yml @@ -53,7 +53,7 @@ on: # The Claude Code CLI resolves the family client-side (`--model opus` # in the analyst step) to the current version ID — never pin a # version ID here. See standards/agent-standards.md. - description: 'Model family (`opus` recommended - Sonnet produces shallower adversarial passes) or an explicit model id override' + description: 'Model family (`opus` recommended; `sonnet` and `haiku` also accepted, but Sonnet produces shallower adversarial passes) or an explicit model id override' required: false default: 'opus' type: string diff --git a/test/workflows/feature-ideation/model-input.bats b/test/workflows/feature-ideation/model-input.bats index dcfcec2ae..1c7ceb6e6 100644 --- a/test/workflows/feature-ideation/model-input.bats +++ b/test/workflows/feature-ideation/model-input.bats @@ -23,17 +23,17 @@ WF="${TT_REPO_ROOT}/.github/workflows/feature-ideation-reusable.yml" @test "model input: description recommends the opus family with an id override" { run yq -r '.on.workflow_call.inputs.model.description' "$WF" [ "$status" -eq 0 ] - [ "$output" = 'Model family (`opus` recommended - Sonnet produces shallower adversarial passes) or an explicit model id override' ] + [ "$output" = 'Model family (`opus` recommended; `sonnet` and `haiku` also accepted, but Sonnet produces shallower adversarial passes) or an explicit model id override' ] } @test "analyst step: model is passed via --model so the CLI resolves the family" { run yq -r '.jobs[].steps[]? | select(.name == "Run Claude Code — BMAD Analyst") | .with.claude_args' "$WF" [ "$status" -eq 0 ] - echo "$output" | grep -qF -- '--model ${{ inputs.model }}' + [[ "$output" == *'--model ${{ inputs.model }}'* ]] } @test "analyst step: no ANTHROPIC_MODEL env bypasses the CLI's alias resolver" { - run yq -r '.jobs[].steps[]? | select(.name == "Run Claude Code — BMAD Analyst") | .env | has("ANTHROPIC_MODEL")' "$WF" + run yq -r '.jobs[].steps[]? | select(.name == "Run Claude Code — BMAD Analyst") | .env // {} | has("ANTHROPIC_MODEL")' "$WF" [ "$status" -eq 0 ] [ "$output" = "false" ] } From 2d642c996e36e55bf9774ae298f5c3bf5a49c318 Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Sun, 4 Oct 2026 00:31:45 +0000 Subject: [PATCH 3/7] fix(bot): install yq in feature-ideation test environment Ensures YAML queries in the model-input.bats test suite have the required yq binary available, preventing command-not-found errors. Addresses cubic P2 finding. Co-Authored-By: Claude Haiku 4.5 --- .github/workflows/feature-ideation-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/feature-ideation-tests.yml b/.github/workflows/feature-ideation-tests.yml index f98393e0f..5727f7024 100644 --- a/.github/workflows/feature-ideation-tests.yml +++ b/.github/workflows/feature-ideation-tests.yml @@ -55,11 +55,11 @@ jobs: with: fetch-depth: 1 - - name: Install bats, shellcheck, and jq + - name: Install bats, shellcheck, jq, and yq run: | set -euo pipefail sudo apt-get update -qq - sudo apt-get install -y --no-install-recommends bats shellcheck jq + sudo apt-get install -y --no-install-recommends bats shellcheck jq yq - name: Install Python jsonschema and PyYAML run: 'pip install --quiet --require-hashes --only-binary :all: -r scripts/feature-ideation-tests-requirements.txt' From ce8acb0d878dcca71b142896ebe2f900635cd4d2 Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Tue, 6 Oct 2026 23:09:03 +0000 Subject: [PATCH 4/7] fix(bot): pin mikefarah/yq to v4.44.3 in feature-ideation-tests Replaced apt-get yq installation with a pinned, SHA256-verified download of mikefarah/yq v4.44.3. This ensures the bats test suite uses a consistent YAML query implementation across all runner images and eliminates the risk of different yq implementations (e.g. old Python yq) parsing the fallback syntax (.env // {}) differently. Co-Authored-By: Claude Haiku 4.5 --- .github/workflows/feature-ideation-tests.yml | 27 +++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/.github/workflows/feature-ideation-tests.yml b/.github/workflows/feature-ideation-tests.yml index 5727f7024..1a81553f3 100644 --- a/.github/workflows/feature-ideation-tests.yml +++ b/.github/workflows/feature-ideation-tests.yml @@ -59,7 +59,32 @@ jobs: run: | set -euo pipefail sudo apt-get update -qq - sudo apt-get install -y --no-install-recommends bats shellcheck jq yq + sudo apt-get install -y --no-install-recommends bats shellcheck jq + # Install a pinned mikefarah/yq so the suite is reproducible and not + # dependent on whatever version ships in the runner image. + YQ_VERSION="v4.44.3" + # --proto '=https' --proto-redir '=https' enforces HTTPS on both the + # initial request and any redirects (GitHub releases redirect to + # objects.githubusercontent.com). + # --retry 5 --retry-delay 2 --retry-connrefused --retry-all-errors + # rides out the transient network/5xx blips from that redirect target + # that otherwise flake this suite (#739); the budget stays bounded so a + # genuinely-down mirror still fails the job fast. + curl -sSfL --proto '=https' --proto-redir '=https' --retry 5 --retry-delay 2 --retry-connrefused --retry-all-errors \ + "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" \ + -o /tmp/yq_linux_amd64 + curl -sSfL --proto '=https' --proto-redir '=https' --retry 5 --retry-delay 2 --retry-connrefused --retry-all-errors \ + "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/checksums" \ + -o /tmp/yq_checksums + curl -sSfL --proto '=https' --proto-redir '=https' --retry 5 --retry-delay 2 --retry-connrefused --retry-all-errors \ + "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/checksums_hashes_order" \ + -o /tmp/yq_checksums_order + # The checksums file puts filename first; use checksums_hashes_order to + # find the SHA-256 column position dynamically. + SHA256_COL=$(( $(grep -n "^SHA-256$" /tmp/yq_checksums_order | cut -d: -f1) + 1 )) + EXPECTED_SHA="$(grep "^yq_linux_amd64 " /tmp/yq_checksums | awk -v col="${SHA256_COL}" '{print $col}')" + echo "${EXPECTED_SHA} /tmp/yq_linux_amd64" | sha256sum -c - + sudo install -m 755 /tmp/yq_linux_amd64 /usr/local/bin/yq - name: Install Python jsonschema and PyYAML run: 'pip install --quiet --require-hashes --only-binary :all: -r scripts/feature-ideation-tests-requirements.txt' From 6acab77cad7e38349ee63cac8817b04cf9521330 Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Tue, 6 Oct 2026 23:15:30 +0000 Subject: [PATCH 5/7] fix(reviews): hardcode yq sha256 and add curl timeouts --- .github/workflows/feature-ideation-tests.yml | 23 ++++++++------------ 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/.github/workflows/feature-ideation-tests.yml b/.github/workflows/feature-ideation-tests.yml index 1a81553f3..1a856506e 100644 --- a/.github/workflows/feature-ideation-tests.yml +++ b/.github/workflows/feature-ideation-tests.yml @@ -68,22 +68,17 @@ jobs: # objects.githubusercontent.com). # --retry 5 --retry-delay 2 --retry-connrefused --retry-all-errors # rides out the transient network/5xx blips from that redirect target - # that otherwise flake this suite (#739); the budget stays bounded so a - # genuinely-down mirror still fails the job fast. - curl -sSfL --proto '=https' --proto-redir '=https' --retry 5 --retry-delay 2 --retry-connrefused --retry-all-errors \ + # that otherwise flake this suite (#739); --connect-timeout/--max-time + # bound each attempt so a stalled connection cannot eat the job budget. + curl -sSfL --proto '=https' --proto-redir '=https' --connect-timeout 10 --max-time 60 \ + --retry 5 --retry-delay 2 --retry-connrefused --retry-all-errors \ "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" \ -o /tmp/yq_linux_amd64 - curl -sSfL --proto '=https' --proto-redir '=https' --retry 5 --retry-delay 2 --retry-connrefused --retry-all-errors \ - "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/checksums" \ - -o /tmp/yq_checksums - curl -sSfL --proto '=https' --proto-redir '=https' --retry 5 --retry-delay 2 --retry-connrefused --retry-all-errors \ - "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/checksums_hashes_order" \ - -o /tmp/yq_checksums_order - # The checksums file puts filename first; use checksums_hashes_order to - # find the SHA-256 column position dynamically. - SHA256_COL=$(( $(grep -n "^SHA-256$" /tmp/yq_checksums_order | cut -d: -f1) + 1 )) - EXPECTED_SHA="$(grep "^yq_linux_amd64 " /tmp/yq_checksums | awk -v col="${SHA256_COL}" '{print $col}')" - echo "${EXPECTED_SHA} /tmp/yq_linux_amd64" | sha256sum -c - + # SHA-256 of yq_linux_amd64 for v4.44.3, hardcoded (vetted) rather than + # fetched from the same release, so a swapped release asset is detected. + # Update together with YQ_VERSION. + YQ_SHA256="a2c097180dd884a8d50c956ee16a9cec070f30a7947cf4ebf87d5f36213e9ed7" + echo "${YQ_SHA256} /tmp/yq_linux_amd64" | sha256sum -c - sudo install -m 755 /tmp/yq_linux_amd64 /usr/local/bin/yq - name: Install Python jsonschema and PyYAML From 1a404e9104fe6aa8c476e48f88838768ca50e3ac Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Fri, 9 Oct 2026 22:45:48 +0000 Subject: [PATCH 6/7] fix(reviews): align model input description with #1210 AC2 exact wording Test-Change-Justification: the old assertion encoded a reworded description that does not match the exact text required by issue #1210 AC2 (https://github.com/petry-projects/.github/issues/1210); CodeRabbit's Linked Issues check flagged the mismatch on PR #1245. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/feature-ideation-reusable.yml | 2 +- test/workflows/feature-ideation/model-input.bats | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/feature-ideation-reusable.yml b/.github/workflows/feature-ideation-reusable.yml index 2a11cd736..0c41cc872 100644 --- a/.github/workflows/feature-ideation-reusable.yml +++ b/.github/workflows/feature-ideation-reusable.yml @@ -53,7 +53,7 @@ on: # The Claude Code CLI resolves the family client-side (`--model opus` # in the analyst step) to the current version ID — never pin a # version ID here. See standards/agent-standards.md. - description: 'Model family (`opus` recommended; `sonnet` and `haiku` also accepted, but Sonnet produces shallower adversarial passes) or an explicit model id override' + description: 'Model family (`opus` recommended - Sonnet produces shallower adversarial passes) or an explicit model id override' required: false default: 'opus' type: string diff --git a/test/workflows/feature-ideation/model-input.bats b/test/workflows/feature-ideation/model-input.bats index 1c7ceb6e6..6caac4238 100644 --- a/test/workflows/feature-ideation/model-input.bats +++ b/test/workflows/feature-ideation/model-input.bats @@ -23,7 +23,7 @@ WF="${TT_REPO_ROOT}/.github/workflows/feature-ideation-reusable.yml" @test "model input: description recommends the opus family with an id override" { run yq -r '.on.workflow_call.inputs.model.description' "$WF" [ "$status" -eq 0 ] - [ "$output" = 'Model family (`opus` recommended; `sonnet` and `haiku` also accepted, but Sonnet produces shallower adversarial passes) or an explicit model id override' ] + [ "$output" = 'Model family (`opus` recommended - Sonnet produces shallower adversarial passes) or an explicit model id override' ] } @test "analyst step: model is passed via --model so the CLI resolves the family" { From ea8322e82f3cf5c2b82a34a98c8f2ba6a985875b Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Fri, 9 Oct 2026 23:05:13 +0000 Subject: [PATCH 7/7] fix(reviews): add final period to model input description per #1210 AC2 Test-Change-Justification: #1210 AC2 requires the exact description text ending in a period; CodeRabbit review on PR #1245 flagged the omission, so the old assertion was wrong. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/feature-ideation-reusable.yml | 2 +- test/workflows/feature-ideation/model-input.bats | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/feature-ideation-reusable.yml b/.github/workflows/feature-ideation-reusable.yml index 0c41cc872..826b52aef 100644 --- a/.github/workflows/feature-ideation-reusable.yml +++ b/.github/workflows/feature-ideation-reusable.yml @@ -53,7 +53,7 @@ on: # The Claude Code CLI resolves the family client-side (`--model opus` # in the analyst step) to the current version ID — never pin a # version ID here. See standards/agent-standards.md. - description: 'Model family (`opus` recommended - Sonnet produces shallower adversarial passes) or an explicit model id override' + description: 'Model family (`opus` recommended - Sonnet produces shallower adversarial passes) or an explicit model id override.' required: false default: 'opus' type: string diff --git a/test/workflows/feature-ideation/model-input.bats b/test/workflows/feature-ideation/model-input.bats index 6caac4238..a0dd09763 100644 --- a/test/workflows/feature-ideation/model-input.bats +++ b/test/workflows/feature-ideation/model-input.bats @@ -23,7 +23,7 @@ WF="${TT_REPO_ROOT}/.github/workflows/feature-ideation-reusable.yml" @test "model input: description recommends the opus family with an id override" { run yq -r '.on.workflow_call.inputs.model.description' "$WF" [ "$status" -eq 0 ] - [ "$output" = 'Model family (`opus` recommended - Sonnet produces shallower adversarial passes) or an explicit model id override' ] + [ "$output" = 'Model family (`opus` recommended - Sonnet produces shallower adversarial passes) or an explicit model id override.' ] } @test "analyst step: model is passed via --model so the CLI resolves the family" {