Skip to content
26 changes: 24 additions & 2 deletions scripts/ci/pr_governance_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,26 @@ THREADS_JSON="$(gh api graphql \
-F owner="$OWNER" \
-F repo="$REPO" \
-F number="$PR_NUMBER" \
-f query='query($owner:String!, $repo:String!, $number:Int!) { repository(owner:$owner, name:$repo) { pullRequest(number:$number) { headRefOid mergeStateStatus reviewThreads(first:100) { nodes { id isResolved isOutdated } } } } }')"
-f query='query($owner:String!, $repo:String!, $number:Int!) { repository(owner:$owner, name:$repo) { pullRequest(number:$number) { headRefOid mergeStateStatus reviewThreads(first:100) { pageInfo { hasNextPage } nodes { id isResolved isOutdated comments(first:100) { pageInfo { hasNextPage } nodes { databaseId } } } } } } }')"
THREAD_METADATA_TRUNCATED="$(printf '%s' "$THREADS_JSON" | jq '
(.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage // false)
or any(
.data.repository.pullRequest.reviewThreads.nodes[]?;
(.comments.pageInfo.hasNextPage // false)
)'
)"
if [ "$THREAD_METADATA_TRUNCATED" = "true" ]; then
add_blocker 'Review thread metadata was truncated; current resolution state could not be proven.'
fi
UNRESOLVED_THREADS="$(printf '%s' "$THREADS_JSON" | jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false and .isOutdated == false)] | length')"
UNRESOLVED_REVIEW_COMMENT_IDS_JSON="$(printf '%s' "$THREADS_JSON" | jq '
[.data.repository.pullRequest.reviewThreads.nodes[]
| select(.isResolved == false and .isOutdated == false)
| .comments.nodes[]?
| .databaseId
| select(. != null)]
| unique'
)"
if [ "$UNRESOLVED_THREADS" != "0" ]; then
add_blocker "${UNRESOLVED_THREADS} unresolved current review thread(s) remain."
fi
Expand Down Expand Up @@ -442,10 +460,14 @@ if ! REVIEW_COMMENTS_JSON="$(gh api --paginate "repos/${GITHUB_REPOSITORY}/pulls
printf '%s\n' "$(<"$REVIEW_COMMENTS_ERROR_FILE")" | sed 's/^/ /'
add_blocker 'PR review comments could not be read; see the workflow run log.'
else
CODERABBIT_REVIEW_BLOCKERS="$(printf '%s' "$REVIEW_COMMENTS_JSON" | jq -s --arg head_sha "$HEAD_SHA" --arg pattern "$CODERABBIT_BLOCKING_PATTERN" '
CODERABBIT_REVIEW_BLOCKERS="$(printf '%s' "$REVIEW_COMMENTS_JSON" | jq -s \
--arg head_sha "$HEAD_SHA" \
--arg pattern "$CODERABBIT_BLOCKING_PATTERN" \
--argjson unresolved_comment_ids "$UNRESOLVED_REVIEW_COMMENT_IDS_JSON" '
[.[][]
| select((.user.login // "") | test("'"$REVIEW_BOT_LOGIN_PATTERN"'"; "i"))
| select((.body // "") | test($pattern; "i"))
| select(.id as $comment_id | ($unresolved_comment_ids | index($comment_id)) != null)
| select(((.commit_id // "") == $head_sha) or ((.original_commit_id // "") == $head_sha) or ((.body // "") | contains($head_sha)))]
| length'
)"
Expand Down
39 changes: 38 additions & 1 deletion scripts/ci/test_pr_governance_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,18 @@ if [ "$1" = "api" ] && [ "$2" = "graphql" ]; then
printf 'GraphQL request failed\n' >&2
exit 1
fi
if [ "${GH_SCENARIO:-pass}" = "coderabbit_resolved_current_review_comment" ]; then
printf '{"data":{"repository":{"pullRequest":{"headRefOid":"%s","mergeStateStatus":"CLEAN","reviewThreads":{"nodes":[{"id":"thread-888","isResolved":true,"isOutdated":false,"comments":{"nodes":[{"databaseId":888}]}}]}}}}}' "$head_sha"
exit 0
fi
if [ "${GH_SCENARIO:-pass}" = "coderabbit_current_review_comment" ] || [ "${GH_SCENARIO:-pass}" = "github_code_quality_current_review_comment" ]; then
printf '{"data":{"repository":{"pullRequest":{"headRefOid":"%s","mergeStateStatus":"CLEAN","reviewThreads":{"nodes":[{"id":"thread-888","isResolved":false,"isOutdated":false,"comments":{"nodes":[{"databaseId":888}]}}]}}}}}' "$head_sha"
exit 0
fi
if [ "${GH_SCENARIO:-pass}" = "review_threads_truncated" ]; then
printf '{"data":{"repository":{"pullRequest":{"headRefOid":"%s","mergeStateStatus":"CLEAN","reviewThreads":{"pageInfo":{"hasNextPage":true},"nodes":[]}}}}}' "$head_sha"
exit 0
fi
if [ "${GH_SCENARIO:-pass}" = "persistent_unknown" ]; then
printf '{"data":{"repository":{"pullRequest":{"headRefOid":"%s","mergeStateStatus":"UNKNOWN","reviewThreads":{"nodes":[]}}}}}' "$head_sha"
exit 0
Expand Down Expand Up @@ -248,7 +260,7 @@ fi

if [ "$1" = "api" ] && [[ "$args" == *repos/*/pulls/42/comments* ]]; then
case "${GH_SCENARIO:-pass}" in
coderabbit_current_review_comment)
coderabbit_current_review_comment|coderabbit_resolved_current_review_comment)
printf '[{"id":888,"user":{"login":"coderabbitai[bot]"},"commit_id":"0123456789abcdef0123456789abcdef01234567","original_commit_id":"old","created_at":"2026-05-19T00:01:00Z","body":"Potential issue on current head"}]'
;;
github_code_quality_current_review_comment)
Expand Down Expand Up @@ -757,6 +769,29 @@ assert_coderabbit_current_review_comment_blocks() {
assert_not_in_file '^pr merge' "$temp_dir/gh.log"
}

assert_coderabbit_resolved_current_review_comment_does_not_block() {
local temp_dir
temp_dir="$(mktemp -d)"
run_gate coderabbit_resolved_current_review_comment "$temp_dir"

assert_exit_code 0 "$temp_dir"
assert_in_file 'PR governance metadata gate is ready' "$temp_dir/output.txt"
assert_not_in_file 'Current-head CodeRabbit review comment' "$temp_dir/output.txt"
assert_not_in_file 'Current-head CodeRabbit review comment' "$temp_dir/gh.log"
assert_not_in_file '^pr merge' "$temp_dir/gh.log"
}

assert_truncated_review_thread_metadata_blocks() {
local temp_dir
temp_dir="$(mktemp -d)"
run_gate review_threads_truncated "$temp_dir"

assert_exit_code 0 "$temp_dir"
assert_in_file 'Review thread metadata was truncated; current resolution state could not be proven.' "$temp_dir/gh.log"
assert_in_file '<!-- pr-governance:metadata-gate -->' "$temp_dir/gh.log"
assert_not_in_file '^pr merge' "$temp_dir/gh.log"
}

assert_github_code_quality_current_review_comment_blocks() {
local temp_dir
temp_dir="$(mktemp -d)"
Expand Down Expand Up @@ -900,6 +935,8 @@ assert_coderabbit_review_limit_issue_comment_does_not_block
assert_coderabbit_no_actionable_summary_does_not_block
assert_coderabbit_no_actionable_summary_with_blocker_still_blocks
assert_coderabbit_current_review_comment_blocks
assert_coderabbit_resolved_current_review_comment_does_not_block
assert_truncated_review_thread_metadata_blocks
assert_github_code_quality_current_review_comment_blocks
assert_coderabbit_stale_review_comment_does_not_block
assert_changes_requested_creates_marker_comment
Expand Down
Loading