diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 1966a7562..0c8a68ab5 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -1096,6 +1096,18 @@ jobs: ;; esac + if [ -f package-lock.json ] || [ -f npm-shrinkwrap.json ]; then + # An npm lockfile is authoritative: npm owns this project even when a + # vestigial pnpm-lock.yaml or yarn.lock is also committed. This mirrors + # the base materializer (materialize_base_javascript_packages.py), which + # already treats a pnpm-lock.yaml with a sibling package-lock.json and no + # exact packageManager as a vestigial second lockfile. Without this the + # runtime runner selected pnpm/yarn and refused on the missing exact + # packageManager, failing coverage-evidence for npm projects that also + # carry a stray pnpm-lock.yaml. + command -v npm >/dev/null 2>&1 && printf '%s\n' "npm" + return + fi if [ -f pnpm-lock.yaml ]; then ensure_corepack_runner pnpm "$declared_spec" && printf '%s\n' "pnpm" return @@ -3857,7 +3869,7 @@ jobs: # cost-efficient tier, cheaper than the legacy gpt-5 it replaced # ($1/$6 vs $1.25/$10 per 1M tokens) so the org OpenAI budget # stretches further between top-ups. - OPENCODE_MODEL_CANDIDATES: "${{ needs.validate-pr-metadata.outputs.is_private == 'false' && 'opencode-free/nemotron-3-ultra-free opencode-free/deepseek-v4-flash-free opencode-free/north-mini-code-free opencode-free/laguna-s-2.1-free opencode-free/ling-3.0-flash-free opencode-free/big-pickle opencode-free/mimo-v2.5-free ' || '' }}github-models/deepseek/deepseek-v3-0324 openai/gpt-5.6-luna openrouter/deepseek/deepseek-v3.2 openrouter/qwen/qwen3-coder github-models/openai/gpt-4.1 github-models/openai/gpt-5 github-models/openai/gpt-5-chat github-models/openai/o3 github-models/deepseek/deepseek-r1-0528 github-models/deepseek/deepseek-r1" + OPENCODE_MODEL_CANDIDATES: "${{ needs.validate-pr-metadata.outputs.is_private == 'false' && 'opencode-free/nemotron-3-ultra-free opencode-free/deepseek-v4-flash-free opencode-free/north-mini-code-free opencode-free/laguna-s-2.1-free opencode-free/ling-3.0-flash-free opencode-free/big-pickle opencode-free/mimo-v2.5-free ' || '' }}github-models/deepseek/deepseek-v3-0324 openai/gpt-5.6-luna openrouter/deepseek/deepseek-v3.2 openrouter/qwen/qwen3-coder github-models/openai/gpt-4.1 github-models/openai/gpt-5 github-models/openai/gpt-5-chat github-models/openai/o3 github-models/deepseek/deepseek-r1-0528 github-models/deepseek/deepseek-r1 github-models/mistral-ai/mistral-medium-2505 github-models/meta/llama-4-maverick-17b-128e-instruct-fp8 github-models/meta/llama-4-scout-17b-16e-instruct" # One attempt per model, then fall through to the next model. Retrying # the SAME model 5x let a rate-limited/hung leader consume the whole # step, so the pool never reached a healthy fallback model. @@ -6692,6 +6704,29 @@ jobs: return 1 } + hold_for_missing_r_cmd_check() { + local body + body="$(printf '%s\n' \ + "## Pull request overview" \ + "" \ + "OpenCode reviewed the current-head source evidence but R package tests were deferred after bounded package-load-only failures." \ + "" \ + "## Approval hold" \ + "" \ + "### Successful current-head R CMD check evidence is required" \ + "- Problem: coverage-evidence deferred package-load-only testthat failures, but no successful current-head R CMD check was found." \ + "- Root cause: deterministic coverage deferral is safe only when the repository's peer R CMD check installs dependencies and passes on this exact head." \ + "- Fix: add or repair the R CMD check workflow and rerun it successfully on the current head." \ + "- Regression test: Keep deferred R coverage fail-closed unless a successful R CMD check is present." \ + "" \ + "- Result: WAITING_FOR_R_CMD_CHECK" \ + "- Head SHA: \`${HEAD_SHA}\`" \ + "- Workflow run: ${RUN_ID}" \ + "- Workflow attempt: ${RUN_ATTEMPT}" + )" + hold_approval_without_review "WAITING_FOR_R_CMD_CHECK" "$body" + } + stop_without_review_after_model_unavailable() { local body body="$(printf '%s\n' \ @@ -6809,7 +6844,7 @@ jobs: fi if ! require_r_cmd_check_for_deferred_coverage; then - return 1 + hold_for_missing_r_cmd_check fi failed_check_evidence_file="$(mktemp)" @@ -7197,25 +7232,7 @@ jobs: fi fi if ! require_r_cmd_check_for_deferred_coverage; then - body="$(printf '%s\n' \ - "## Pull request overview" \ - "" \ - "OpenCode reviewed the current-head source evidence but R package tests were deferred after bounded package-load-only failures." \ - "" \ - "## Approval hold" \ - "" \ - "### Successful current-head R CMD check evidence is required" \ - "- Problem: coverage-evidence deferred package-load-only testthat failures, but no successful current-head R CMD check was found." \ - "- Root cause: deterministic coverage deferral is safe only when the repository's peer R CMD check installs dependencies and passes on this exact head." \ - "- Fix: add or repair the R CMD check workflow and rerun it successfully on the current head." \ - "- Regression test: Keep deferred R coverage fail-closed unless a successful R CMD check is present." \ - "" \ - "- Result: WAITING_FOR_R_CMD_CHECK" \ - "- Head SHA: \`${HEAD_SHA}\`" \ - "- Workflow run: ${RUN_ID}" \ - "- Workflow attempt: ${RUN_ATTEMPT}" - )" - hold_approval_without_review "WAITING_FOR_R_CMD_CHECK" "$body" + hold_for_missing_r_cmd_check fi unresolved_reviewer_threads_file="$(mktemp)" reviewer_thread_review_body_file="$(mktemp)" diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index bd300a839..381e30ab0 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -144,6 +144,9 @@ def test_opencode_model_pool_sets_high_effort_for_capable_candidates(): ["github-models", "openai/o3"], ["github-models", "deepseek/deepseek-r1-0528"], ["github-models", "deepseek/deepseek-r1"], + ["github-models", "mistral-ai/mistral-medium-2505"], + ["github-models", "meta/llama-4-maverick-17b-128e-instruct-fp8"], + ["github-models", "meta/llama-4-scout-17b-16e-instruct"], ] assert direct_openai_models == ["gpt-5.6-luna"] assert openrouter_models == [ @@ -181,6 +184,12 @@ def test_opencode_model_pool_sets_high_effort_for_capable_candidates(): assert "response_format" not in deepseek_model.get("options", {}) assert north_model["tool_call"] is True assert "response_format" not in north_model["options"] + for model_name, model_config in free_models.items(): + if model_config.get("reasoning") is True: + assert model_config["options"]["reasoningEffort"] == "high", model_name + assert model_config["variants"]["high"]["reasoningEffort"] == "high", ( + model_name + ) assert free_models["laguna-s-2.1-free"]["limit"] == { "context": 256000, "output": 32000, @@ -197,12 +206,6 @@ def test_opencode_model_pool_sets_high_effort_for_capable_candidates(): "context": 200000, "output": 32000, } - for model_name, model_config in free_models.items(): - if model_config.get("reasoning") is True: - assert model_config["options"]["reasoningEffort"] == "high", model_name - assert model_config["variants"]["high"]["reasoningEffort"] == "high", ( - model_name - ) assert github_candidate_models == [ "deepseek/deepseek-v3-0324", "openai/gpt-4.1", @@ -211,6 +214,9 @@ def test_opencode_model_pool_sets_high_effort_for_capable_candidates(): "openai/o3", "deepseek/deepseek-r1-0528", "deepseek/deepseek-r1", + "mistral-ai/mistral-medium-2505", + "meta/llama-4-maverick-17b-128e-instruct-fp8", + "meta/llama-4-scout-17b-16e-instruct", ] banned_review_candidates = { "gpt-5-nano", @@ -735,6 +741,13 @@ def test_opencode_coverage_prefers_preinstalled_declared_pnpm_before_npm(): assert select_function.index("[ -f pnpm-lock.yaml ]") < select_function.rindex( "elif command -v npm" ) + # A committed npm lockfile is authoritative: an npm project that also carries a + # vestigial pnpm-lock.yaml / yarn.lock must resolve to npm rather than refuse on + # a missing exact packageManager (mirrors materialize_base_javascript_packages.py). + # The npm-lockfile check therefore precedes the pnpm/yarn lockfile fallback. + assert select_function.index( + "[ -f package-lock.json ] || [ -f npm-shrinkwrap.json ]" + ) < select_function.index("[ -f pnpm-lock.yaml ]") declared_pnpm_start = select_function.index(" pnpm)") declared_pnpm_end = select_function.index( @@ -2388,6 +2401,7 @@ def test_r_package_load_deferral_requires_current_head_r_cmd_check(): assert marker in workflow assert "require_r_cmd_check_for_deferred_coverage" in workflow assert workflow.count("require_r_cmd_check_for_deferred_coverage") == 3 + assert workflow.count("hold_for_missing_r_cmd_check") == 3 assert "WAITING_FOR_R_CMD_CHECK" in workflow assert "testthat unavailable in coverage runner" not in workflow assert (