diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index 6115e1781..11ce9e39a 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -4518,6 +4518,73 @@ jobs: return 0 } + approve_previously_reviewed_merge_update_after_model_exhaustion() { + local parents first_parent second_parent reviews_json approved_parent_count body + + if [ -z "${OPENCODE_SOURCE_WORKDIR:-}" ] || [ ! -d "$OPENCODE_SOURCE_WORKDIR/.git" ]; then + return 1 + fi + if ! parents="$(git -C "$OPENCODE_SOURCE_WORKDIR" show -s --format='%P' "$HEAD_SHA" 2>/dev/null)"; then + return 1 + fi + # shellcheck disable=SC2086 + set -- $parents + if [ "$#" -ne 2 ]; then + return 1 + fi + first_parent="$1" + second_parent="$2" + + if [ -z "${PR_BASE_SHA:-}" ]; then + return 1 + fi + if [ "$second_parent" != "$PR_BASE_SHA" ]; then + if ! git -C "$OPENCODE_SOURCE_WORKDIR" merge-base --is-ancestor "$second_parent" "$PR_BASE_SHA" 2>/dev/null; then + return 1 + fi + fi + + if ! reviews_json="$(gh api -X GET "repos/${GH_REPOSITORY}/pulls/${PR_NUMBER}/reviews" --paginate | jq -s 'add')"; then + return 1 + fi + approved_parent_count="$( + jq -r --arg sha "$first_parent" ' + [ + .[] + | select(.commit_id == $sha) + | select(.user.login == "opencode-agent[bot]" or .user.login == "opencode-agent") + | select(.state == "APPROVED") + ] + | length + ' <<<"$reviews_json" + )" + if [ "${approved_parent_count:-0}" -lt 1 ]; then + return 1 + fi + + body="$(printf '%s\n' \ + "## Pull request overview" \ + "" \ + "OpenCode 모델 풀이 현재 merge-update head에서 usable control block을 만들지 못했지만, 중앙 리뷰 게이트가 이전 승인과 현재 head evidence를 함께 확인했습니다." \ + "" \ + "## Approval evidence" \ + "" \ + "- Result: APPROVE" \ + "- Reason: 현재 head는 이미 OpenCode가 승인한 PR parent \`${first_parent}\`와 base parent \`${second_parent}\`를 결합한 2-parent merge update입니다." \ + "- Prior review: OpenCode approval exists on first parent \`${first_parent}\`." \ + "- Base evidence: second parent is the current base SHA or an ancestor of it." \ + "- Peer GitHub Checks: completed without failures before this fallback approval." \ + "- Human review threads: unresolved blocking threads were not present." \ + "- Model outcome: model_pool=${OPENCODE_MODEL_POOL_OUTCOME:-unknown}; selected_model=${OPENCODE_MODEL_POOL_MODEL:-none}." \ + "- Head SHA: \`${HEAD_SHA}\`" \ + "- Workflow run: ${RUN_ID}" \ + "- Workflow attempt: ${RUN_ATTEMPT}" \ + "" \ + "이 fallback은 이미 승인된 PR head를 최신 base에 병합한 merge-update commit에만 적용됩니다. 이전 승인 없는 source changes, single-parent commits, stale base parents, failed checks, unresolved human threads에는 적용되지 않습니다.")" + create_pull_review "APPROVE" "$body" + return 0 + } + request_changes_for_merge_conflict_if_present() { local pr_json merge_state mergeable base_ref head_ref body change_graph @@ -4690,6 +4757,11 @@ jobs: exit 0 fi + if approve_previously_reviewed_merge_update_after_model_exhaustion; then + echo "::endgroup::" + exit 0 + fi + request_changes_after_model_exhaustion \ "$pending_checks_file" \ "$failed_checks_file" \ diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 64fd585dd..08cff4e51 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -178,6 +178,12 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): assert "changed_file_is_low_risk_review_fallback" in workflow assert "production source 또는 package manifest 변경이 없습니다" in workflow assert "Source, workflow, config, package, migration, generated artifact 변경은 모델 기반 review 없이 승인하지 않습니다" in workflow + assert "approve_previously_reviewed_merge_update_after_model_exhaustion" in workflow + assert "2-parent merge update" in workflow + assert "Prior review: OpenCode approval exists on first parent" in workflow + assert 'reviews" --paginate | jq -s' in workflow + assert 'select(.state == "APPROVED")' in workflow + assert "이 fallback은 이미 승인된 PR head를 최신 base에 병합한 merge-update commit에만 적용됩니다" in workflow assert 'timeout-minutes: 310' in workflow assert 'OPENCODE_MODEL_ATTEMPTS: "3"' in workflow assert 'OPENCODE_RUN_TIMEOUT_SECONDS: "900"' in workflow