diff --git a/.github/workflows/auto-approve.yml b/.github/workflows/auto-approve.yml new file mode 100644 index 0000000..a612235 --- /dev/null +++ b/.github/workflows/auto-approve.yml @@ -0,0 +1,66 @@ +name: auto-approve + +# Approval bridge for the team's code-review policy (SOC 2 / Vanta "GitHub +# code changes were approved or provided justification"): every PR is reviewed +# automatically by Qodo, findings are advisory, human approval is not +# mandated. Qodo's review lives in a comment, invisible to GitHub's approval +# model — this workflow makes the review visible: when Qodo posts a completed +# review, the PR gets an APPROVED review whose body records Qodo's verdict. +# The approval attests "an automated review happened", NOT "the code is clean". +# A PR merged before Qodo finishes gets no approval. + +on: + issue_comment: + types: [created, edited] + +# Qodo edits its persistent review comment on every re-review; when edits land +# in a burst, only the newest matters. +concurrency: + group: auto-approve-${{ github.event.issue.number }} + cancel-in-progress: true + +jobs: + approve: + name: Approve on Qodo review + # Exact-login match: '[' is illegal in GitHub logins, so the [bot] suffix + # is unforgeable — a prefix match would accept lookalike user accounts. + if: >- + github.event.issue.pull_request && + github.event.comment.user.login == 'qodo-code-review[bot]' && + github.event.comment.user.type == 'Bot' && + contains(github.event.comment.body, 'Code Review by Qodo') + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + pull-requests: write + steps: + # Plain gh, not a marketplace action: both auto-approve actions are + # unusable here (fork paywalls private repos; original fails the org's + # verified-creators actions policy). + - name: Approve with the Qodo verdict + env: + GH_TOKEN: ${{ github.token }} + BODY: ${{ github.event.comment.body }} + COMMENT_URL: ${{ github.event.comment.html_url }} + REPO: ${{ github.repository }} + NUM: ${{ github.event.issue.number }} + run: | + set -euo pipefail + # Dedupe: comment edits re-fire this job; approve each head commit once. + head_sha=$(gh api "repos/$REPO/pulls/$NUM" --jq .head.sha) + dup=$(gh api "repos/$REPO/pulls/$NUM/reviews?per_page=100" --paginate \ + --jq "[.[] | select(.user.login==\"github-actions[bot]\" and .state==\"APPROVED\" and .commit_id==\"$head_sha\")] | length") + if [ "$dup" -gt 0 ]; then + echo "Head $head_sha already carries an auto-approval; nothing to do." + exit 0 + fi + # Quote whatever verdict categories the comment carries, without + # encoding Qodo's taxonomy (it changes between releases). A parse + # miss must be visible, not silently degraded. + verdict=$(printf '%s' "$BODY" | grep -oE '[^<]*\([0-9]+\)' | sed 's/<[^>]*>//g' | paste -sd ', ' -) || true + if [ -z "$verdict" ]; then + echo "::warning::No verdict categories parsed from the Qodo comment — its format may have changed; approving with a link only." + verdict="see the review comment" + fi + gh pr review "$NUM" --repo "$REPO" --approve \ + --body "Auto-approved: Qodo review completed — ${verdict}. Findings are advisory per team policy; verdict source: ${COMMENT_URL} [auto-approve.yml]" diff --git a/.pr_agent.toml b/.pr_agent.toml new file mode 100644 index 0000000..b602638 --- /dev/null +++ b/.pr_agent.toml @@ -0,0 +1,11 @@ +# Qodo configuration. +# +# Review-every-push keeps the review in sync with the latest commits, which the +# auto-approve workflow (.github/workflows/auto-approve.yml) depends on: it +# approves the PR whenever Qodo publishes a completed review, quoting the +# verdict. Self-review checkboxes and the classic pr-agent auto-approval keys +# are deliberately absent: the former needs a hidden manual click, the latter +# are dead on this platform (verified 2026-09-07). + +[github_app] +handle_push_trigger = true diff --git a/Cargo.lock b/Cargo.lock index 6529af9..042a1b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "atomic-waker" @@ -1413,9 +1413,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.38" +version = "0.23.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" dependencies = [ "aws-lc-rs", "once_cell", @@ -1477,9 +1477,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "aws-lc-rs", "ring", @@ -1757,7 +1757,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.61.2",