diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index 0e351e3ad..ab967dec0 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -536,7 +536,7 @@ jobs: if: always() && (github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request_target') runs-on: ubuntu-latest permissions: - actions: read + actions: write checks: read id-token: write contents: read @@ -4446,3 +4446,19 @@ jobs: ;; esac echo "::endgroup::" + + - name: Dispatch merge scheduler after approval + env: + GH_TOKEN: ${{ github.token }} + GH_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.event.inputs.target_repository || github.repository }} + run: | + set -euo pipefail + gh workflow run pr-review-merge-scheduler.yml \ + --repo "$GH_REPOSITORY" \ + --ref main \ + -f dry_run=false \ + -f max_prs=100 \ + -f trigger_reviews=false \ + -f enable_auto_merge=true \ + -f merge_mode=auto \ + -f update_branches=true diff --git a/scripts/ci/pr_review_merge_scheduler.py b/scripts/ci/pr_review_merge_scheduler.py index dbdb99bb6..032f91144 100644 --- a/scripts/ci/pr_review_merge_scheduler.py +++ b/scripts/ci/pr_review_merge_scheduler.py @@ -1565,6 +1565,23 @@ def summarize_action_error(exc: RuntimeError) -> str: def self_test() -> None: """Exercise scheduler invariants without GitHub network access.""" + assert split_repo("owner/name") == ("owner", "name") + assert split_repo("owner/name/extra") == ("owner", "name/extra") + try: + split_repo("owner") + raise AssertionError("expected ValueError") + except ValueError: + pass + try: + split_repo("/name") + raise AssertionError("expected ValueError") + except ValueError: + pass + try: + split_repo("owner/") + raise AssertionError("expected ValueError") + except ValueError: + pass sample = { "number": 1, "headRefOid": "abc", diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index eab00805e..147efa283 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -378,7 +378,7 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" "if: always() && (github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request_target')" "opencode review side effects are limited to manual or required PR events" assert_file_contains "$workflow_file" "opencode-review-target:" "opencode trusted review job owns the required check surface" assert_file_contains "$workflow_file" "Initialize CodeGraph index for OpenCode" "opencode review workflow initializes CodeGraph before review" - assert_file_contains "$workflow_file" "actions: read" "opencode review workflow can read failed Actions logs for GitHub Check diagnosis" + assert_file_contains "$workflow_file" "actions: write" "opencode review workflow can read failed Actions logs and dispatch the merge scheduler after approval" assert_file_contains "$workflow_file" "checks: read" "opencode review workflow can read failed check-run annotations for line-specific findings" assert_file_contains "$workflow_file" "contents: read" "opencode review workflow uses read-only repository contents permission" assert_file_not_contains "$workflow_file" "contents: write" "opencode review workflow must not request repository content write permission" @@ -578,6 +578,10 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" 'ref: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }}' "coverage evidence checks out the requested PR head SHA as data" assert_file_contains "$workflow_file" 'ref: ${{ steps.trusted_source.outputs.ref }}' "OpenCode review checks out central trusted scripts for same-head validation" assert_file_contains "$workflow_file" 'COVERAGE_EVIDENCE_RESULT: ${{ needs.coverage-evidence.result || '\''skipped'\'' }}' "opencode approval receives the coverage-evidence job conclusion" + assert_file_contains "$workflow_file" "Dispatch merge scheduler after approval" "opencode approval wakes the merge scheduler after current-head approval" + assert_file_contains "$workflow_file" "gh workflow run pr-review-merge-scheduler.yml" "opencode approval dispatches the central merge scheduler workflow" + assert_file_contains "$workflow_file" "-f trigger_reviews=false" "opencode post-approval scheduler dispatch avoids duplicate OpenCode review runs" + assert_file_contains "$workflow_file" "-f enable_auto_merge=true" "opencode post-approval scheduler dispatch enables approved-head merge handling" assert_file_contains "$workflow_file" 'build_coverage_evidence_check_failure_body()' "opencode approval can describe a coverage-evidence blocker without publishing a review" assert_file_contains "$workflow_file" 'fail_for_coverage_evidence_without_review' "opencode approval fails the check, not the PR review state, when coverage-evidence did not pass" assert_file_contains "$workflow_file" "leave the PR review unchanged for coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence" "opencode approval does not turn coverage-evidence blocker states into source review findings" @@ -7334,7 +7338,7 @@ run_gate_case "github-models-primary-unavailable-fallback-success" \ "deepseek/deepseek-r1-0528 deepseek/deepseek-v3-0324" \ "1" -run_gate_case "github-models-primary-denied-fallback-success" \ +run_gate_case_allow_provider_signal "github-models-primary-denied-fallback-success" \ "openai/gpt-5" \ "" \ "0" \ diff --git a/tests/test_pr_review_merge_scheduler.py b/tests/test_pr_review_merge_scheduler.py index fedd85acb..f6c2fcb35 100644 --- a/tests/test_pr_review_merge_scheduler.py +++ b/tests/test_pr_review_merge_scheduler.py @@ -1515,6 +1515,19 @@ def test_print_summary_self_test_parse_args_and_main(monkeypatch, capsys): sched.self_test() assert "self-test passed" in capsys.readouterr().out + real_split_repo = sched.split_repo + invalid_inputs = ["owner", "/name", "owner/"] + for accepted_invalid in invalid_inputs: + def fake_split_repo(repo, accepted_invalid=accepted_invalid): + if repo == accepted_invalid: + return ("accepted", "invalid") + return real_split_repo(repo) + + monkeypatch.setattr(sched, "split_repo", fake_split_repo) + with pytest.raises(AssertionError, match="expected ValueError"): + sched.self_test() + monkeypatch.setattr(sched, "split_repo", real_split_repo) + parsed = sched.parse_args( [ "--repo",