From d45875fd0e10d4d8d3191aa0728b0e5ef9995b8f Mon Sep 17 00:00:00 2001 From: Rachael Rose Renk <91027132+rachaelrenk@users.noreply.github.com> Date: Fri, 4 Sep 2026 15:25:16 -0600 Subject: [PATCH] fix(docs): gate agent reviews by check status Co-Authored-By: Warp --- .../publish_review_signal.py | 8 +-- .../stale_review_requests.py | 54 +++++++++++++++++++ .../test_publish_review_signal.py | 12 ++--- .../test_stale_review_requests.py | 53 ++++++++++++++++++ .github/workflows/agent-docs-review.yml | 14 +++++ 5 files changed, 131 insertions(+), 10 deletions(-) create mode 100644 .agents/skills/doc_quality_policy/stale_review_requests.py create mode 100644 .agents/skills/doc_quality_policy/test_stale_review_requests.py diff --git a/.agents/skills/doc_quality_policy/publish_review_signal.py b/.agents/skills/doc_quality_policy/publish_review_signal.py index 6cc8c695..aaac2342 100644 --- a/.agents/skills/doc_quality_policy/publish_review_signal.py +++ b/.agents/skills/doc_quality_policy/publish_review_signal.py @@ -16,10 +16,10 @@ _spec.loader.exec_module(vrs) _REVIEW_EVENTS = { - "approve": "APPROVE", - "approve with nits": "APPROVE", - "approve_with_nits": "APPROVE", - "request changes": "REQUEST_CHANGES", + "approve": "COMMENT", + "approve with nits": "COMMENT", + "approve_with_nits": "COMMENT", + "request changes": "COMMENT", } diff --git a/.agents/skills/doc_quality_policy/stale_review_requests.py b/.agents/skills/doc_quality_policy/stale_review_requests.py new file mode 100644 index 00000000..c8676f5c --- /dev/null +++ b/.agents/skills/doc_quality_policy/stale_review_requests.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Print stale GitHub Actions change-request review IDs, one per line.""" +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path +from typing import Iterable, Mapping, Optional + + +def stale_review_ids( + reviews: Iterable[Mapping[str, object]], + head_sha: str, + reviewer_login: str = "github-actions[bot]", +) -> list[int]: + """Select only prior blocking reviews published by the automation account.""" + stale_ids: list[int] = [] + for review in reviews: + author = review.get("user") + login = author.get("login") if isinstance(author, Mapping) else None + if ( + login == reviewer_login + and review.get("state") == "CHANGES_REQUESTED" + and review.get("commit_id") != head_sha + and isinstance(review.get("id"), int) + ): + stale_ids.append(review["id"]) + return stale_ids + + +def main(argv: Optional[list[str]] = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--reviews", required=True, type=Path) + parser.add_argument("--head-sha", required=True) + parser.add_argument("--reviewer-login", default="github-actions[bot]") + args = parser.parse_args(argv) + try: + reviews = json.loads(args.reviews.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + print(f"error: {exc}", file=sys.stderr) + return 1 + if not isinstance(reviews, list): + print("error: reviews JSON must be an array", file=sys.stderr) + return 1 + for review_id in stale_review_ids( + reviews, args.head_sha, args.reviewer_login + ): + print(review_id) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.agents/skills/doc_quality_policy/test_publish_review_signal.py b/.agents/skills/doc_quality_policy/test_publish_review_signal.py index 6470b779..949d47fc 100644 --- a/.agents/skills/doc_quality_policy/test_publish_review_signal.py +++ b/.agents/skills/doc_quality_policy/test_publish_review_signal.py @@ -23,9 +23,9 @@ def _signal(verdict: str = "Approve") -> str: class TestBuildReviewPayload(unittest.TestCase): - def test_approve_maps_to_github_approval(self): + def test_approve_maps_to_non_blocking_github_comment(self): payload = prs.build_review_payload(_signal(), "1", "sha1", "github-actions[bot]") - self.assertEqual(payload["event"], "APPROVE") + self.assertEqual(payload["event"], "COMMENT") self.assertEqual(payload["commit_id"], "sha1") self.assertIn("## Verdict\nApprove", payload["body"]) self.assertNotIn("## Review signal", payload["body"]) @@ -37,17 +37,17 @@ def test_approve_maps_to_github_approval(self): self.assertEqual(problems, []) self.assertEqual(published_signal["reviewer_login"], "github-actions[bot]") - def test_approve_with_nits_maps_to_github_approval(self): + def test_approve_with_nits_maps_to_non_blocking_github_comment(self): payload = prs.build_review_payload( _signal("Approve with nits"), "1", "sha1", "github-actions[bot]" ) - self.assertEqual(payload["event"], "APPROVE") + self.assertEqual(payload["event"], "COMMENT") - def test_request_changes_maps_to_github_change_request(self): + def test_request_changes_maps_to_non_blocking_github_comment(self): payload = prs.build_review_payload( _signal("Request changes"), "1", "sha1", "github-actions[bot]" ) - self.assertEqual(payload["event"], "REQUEST_CHANGES") + self.assertEqual(payload["event"], "COMMENT") def test_rejects_signal_for_another_head(self): with self.assertRaises(ValueError): diff --git a/.agents/skills/doc_quality_policy/test_stale_review_requests.py b/.agents/skills/doc_quality_policy/test_stale_review_requests.py new file mode 100644 index 00000000..aa313a30 --- /dev/null +++ b/.agents/skills/doc_quality_policy/test_stale_review_requests.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +"""Unit tests for stale_review_requests.py.""" +from __future__ import annotations + +import importlib.util +import sys +import unittest +from pathlib import Path + + +_HERE = Path(__file__).resolve().parent +_spec = importlib.util.spec_from_file_location( + "stale_review_requests", _HERE / "stale_review_requests.py" +) +srr = importlib.util.module_from_spec(_spec) +sys.modules[_spec.name] = srr +_spec.loader.exec_module(srr) + + +class TestStaleReviewIds(unittest.TestCase): + def test_selects_only_prior_automated_change_requests(self): + reviews = [ + { + "id": 1, + "state": "CHANGES_REQUESTED", + "commit_id": "old-sha", + "user": {"login": "github-actions[bot]"}, + }, + { + "id": 2, + "state": "CHANGES_REQUESTED", + "commit_id": "current-sha", + "user": {"login": "github-actions[bot]"}, + }, + { + "id": 3, + "state": "APPROVED", + "commit_id": "old-sha", + "user": {"login": "github-actions[bot]"}, + }, + { + "id": 4, + "state": "CHANGES_REQUESTED", + "commit_id": "old-sha", + "user": {"login": "reviewer"}, + }, + ] + + self.assertEqual(srr.stale_review_ids(reviews, "current-sha"), [1]) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/workflows/agent-docs-review.yml b/.github/workflows/agent-docs-review.yml index 1a968ee0..13a5711a 100644 --- a/.github/workflows/agent-docs-review.yml +++ b/.github/workflows/agent-docs-review.yml @@ -53,6 +53,20 @@ jobs: 4. Emit one [SIGNAL:pr-review] JSON record with this head SHA, verdict, severity counts, and top categories. Set reviewer_login to `github-actions[bot]`, the runner account that will publish the review. + - name: Dismiss stale automated change requests + env: + GH_TOKEN: ${{ github.token }} + run: | + gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews?per_page=100" > /tmp/reviews.json + python3 .agents/skills/doc_quality_policy/stale_review_requests.py \ + --reviews /tmp/reviews.json \ + --head-sha "${{ github.event.pull_request.head.sha }}" | + while IFS= read -r review_id; do + [ -z "$review_id" ] && continue + gh api --method PUT \ + "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews/${review_id}/dismissals" \ + -f "message=Superseded by the current Agent docs review check." + done - name: Publish the independent review env: