diff --git a/.github/workflows/pr-review.yml b/.github/workflows/pr-review.yml index 6386453b1..5bdcaeecb 100644 --- a/.github/workflows/pr-review.yml +++ b/.github/workflows/pr-review.yml @@ -39,6 +39,12 @@ jobs: # To go live: gh variable set LIVE_MODE --body true --repo don-petry/self DRY_RUN: ${{ inputs.dry_run || (vars.LIVE_MODE == 'true' && 'false' || 'true') }} PR_URL_OVERRIDE: ${{ inputs.pr_url }} + # Comma-separated list of GitHub orgs where Claude App is installed. + # When findings exist, the agent tags @claude to fix them autonomously. + # gh variable set CLAUDE_ORGS --body "petry-projects,don-petry" --repo don-petry/self + CLAUDE_ORGS: ${{ vars.CLAUDE_ORGS || '' }} + # Max review cycles before stopping @claude delegation and escalating to human. + MAX_REVIEW_CYCLES: ${{ vars.MAX_REVIEW_CYCLES || '3' }} steps: - name: Checkout agent repo diff --git a/AGENT.md b/AGENT.md index 3091561e8..5f46f74c0 100644 --- a/AGENT.md +++ b/AGENT.md @@ -30,7 +30,22 @@ quality gates, and escalates high-risk or gated PRs for human review. - If escalated: `gh pr review --comment`, re-requests don-petry as a reviewer, and adds the `needs-human-review` label. -4. **Idempotency + iterative review cycles** — every posted review starts with +4. **Post-review actions** — after the review is posted, the synthesizer takes + additional actions depending on the decision: + - **If approved:** enables auto-merge (`gh pr merge --auto --squash`), + rebases the branch if behind base, and removes the `needs-human-review` + label. GitHub merges automatically once all required checks pass. + - **If escalated + Claude App available:** posts a follow-up comment tagging + `@claude` with specific fix instructions derived from the council findings. + Claude pushes fixes → next cron tick detects new SHA → council re-reviews + → approve + auto-merge when clean. This creates an autonomous fix loop. + - **If escalated + no Claude App (or max cycles reached):** labels + `needs-human-review` and re-requests don-petry as reviewer. + - **Cycle guard:** after `MAX_REVIEW_CYCLES` (default 3) rounds of @claude + delegation without resolution, the agent stops delegating and escalates + to human to prevent infinite loops. + +5. **Idempotency + iterative review cycles** — every posted review starts with an HTML marker on line 1: ``` @@ -128,6 +143,12 @@ gh workflow run pr-review.yml --repo don-petry/self -f dry_run=false - **Cron frequency** — change the `cron:` line in the workflow file. - **Scope** — edit `scripts/list-prs.sh` to add/remove queries (e.g. to include PRs from a specific org, or to exclude certain repos). +- **Claude delegation** — set `CLAUDE_ORGS` to a comma-separated list of GitHub + orgs where the Claude App is installed: + `gh variable set CLAUDE_ORGS --body "petry-projects,don-petry" --repo don-petry/self` +- **Max review cycles** — how many times the agent tags @claude before + escalating to human (default 3): + `gh variable set MAX_REVIEW_CYCLES --body 5 --repo don-petry/self` - **Max PRs per run** — defaults to 10 per cron tick to stay within the 60-min job timeout (~5 min per PR with 3 council members). Override: `gh variable set MAX_PRS --body 15 --repo don-petry/self` diff --git a/prompts/synthesize.md b/prompts/synthesize.md index 91a780fcf..d7cde4aa6 100644 --- a/prompts/synthesize.md +++ b/prompts/synthesize.md @@ -16,7 +16,15 @@ and (if `$DRY_RUN` is `false`) post a PR review. - `$PR_URL` — the PR to act on. - `$PR_HEAD_SHA` — the commit SHA the council reviewed. - `$DRY_RUN` — `true` or `false`. If `true`, do not call any `gh pr review`, - `gh pr edit`, or `gh api -X POST` commands. Print what you WOULD post. + `gh pr edit`, `gh pr merge`, or `gh api -X POST` commands. Print what you + WOULD do. +- `$CLAUDE_ENABLED` — `true` or `false`. Whether the PR's repo org has the + Claude GitHub App installed. +- `$REVIEW_CYCLE` — integer. How many previous review cycles exist on this PR + (count of our markers in existing comments). Used to prevent infinite + delegation loops. +- `$MAX_REVIEW_CYCLES` — integer (default 3). If `$REVIEW_CYCLE >= $MAX_REVIEW_CYCLES`, + do NOT delegate to @claude — escalate to human instead. ## Steps @@ -45,17 +53,83 @@ and (if `$DRY_RUN` is `false`) post a PR review. MANDATORY and must be the first line of the body. 7. **Act**: - If `$DRY_RUN` is `true`: print the composed body to stdout, prefixed - with `--- WOULD POST ---`, and exit. - - If `final_decision` is `approve`: `gh pr review "$PR_URL" --approve --body "$BODY"`. - - If `final_decision` is `escalate`: `gh pr review "$PR_URL" --comment --body "$BODY"`, - then attempt to add the `needs-human-review` label - (`gh pr edit "$PR_URL" --add-label needs-human-review`; if that fails, - create the label first via `gh label create needs-human-review --repo --color FBCA04 --description "Flagged by automated PR review agent"` and retry), - then re-request don-petry as a reviewer - (`gh api -X POST "repos///pulls//requested_reviewers" -f reviewers[]=don-petry`, - swallowing errors if don-petry is the author or already requested). -8. After acting, print a single-line JSON status to stdout: - `{"pr":"","sha":"","risk":"","decision":"","posted":true|false}` + with `--- WOULD POST ---`, and print what follow-up actions you WOULD + take (delegation, merge, rebase). Then exit. + - If `final_decision` is `approve`: + 1. `gh pr review "$PR_URL" --approve --body "$BODY"` + 2. Proceed to step 8 (post-approval actions). + - If `final_decision` is `escalate`: + 1. `gh pr review "$PR_URL" --comment --body "$BODY"` + 2. Proceed to step 9 (delegation to Claude or human). +8. **Post-approval actions** (only when approving, `$DRY_RUN` is `false`): + 1. **Rebase if needed**: fetch `mergeStateStatus` from step 5's data. If it + is `BEHIND` (base branch has advanced), update the PR branch: + `gh api -X PUT "repos///pulls//update-branch" -f expected_head_sha="$PR_HEAD_SHA"` + Swallow errors (conflicts will be caught on next cycle). + 2. **Enable auto-merge**: `gh pr merge "$PR_URL" --auto --squash` + This tells GitHub to merge the PR once all required checks pass. + Swallow errors if auto-merge is already enabled or not allowed. + 3. Remove the `needs-human-review` label if present: + `gh pr edit "$PR_URL" --remove-label needs-human-review` (swallow errors). +9. **Delegation** (only when escalating, `$DRY_RUN` is `false`): + Decide whether to delegate to Claude or to a human: + - If `$CLAUDE_ENABLED` is `true` AND `$REVIEW_CYCLE` < `$MAX_REVIEW_CYCLES` + AND `final_risk` is NOT `HIGH`: + → **Delegate to Claude** (step 9a). + - Otherwise: + → **Escalate to human** (step 9b). + 9a. **Delegate to Claude**: post a SEPARATE issue comment (NOT a review, + use `gh api -X POST "repos///issues//comments"`) + with actionable fix instructions. The repo has a Claude workflow + trigger that listens to all non-Claude comments, so Claude will + automatically pick up the comment — no `@claude` tag needed. + + The comment MUST be clearly structured so Claude can act on it: + - Lists EVERY finding from the council that has severity `minor`, + `major`, or `critical` — with exact file paths, line numbers, and + what needs to change. + - Instructs Claude to also resolve any unresolved review comments from + other reviewers (CodeRabbit, Copilot, humans). + - Instructs Claude to ensure all CI checks pass after the fix. + - Instructs Claude to rebase on the base branch if needed. + - Ends with: "Once all fixes are pushed, the review council will + automatically re-review on the next cycle." + - The comment must follow this template: + + ``` + ## Review council — fix requested (cycle /) + + The automated review council identified the following issues. Please address each one: + + ### Findings to fix + + + - **[]** `:` — + + + ### Additional tasks + + 1. Resolve all unresolved review thread comments from other reviewers (CodeRabbit, Copilot, etc.) + 2. Ensure all CI checks pass after your changes + 3. Rebase on `` if the branch is behind + 4. Do NOT modify files unrelated to the findings above + + _The review council will automatically re-review after new commits are pushed._ + ``` + + After posting, do NOT add `needs-human-review` label (Claude is handling it). + Do NOT re-request don-petry as reviewer. + 9b. **Escalate to human**: add the `needs-human-review` label + (`gh pr edit "$PR_URL" --add-label needs-human-review`; create it first + if needed via `gh label create needs-human-review --repo --color FBCA04 --description "Flagged by automated PR review agent"`), + then re-request don-petry as a reviewer + (`gh api -X POST "repos///pulls//requested_reviewers" -f reviewers[]=don-petry`, + swallowing errors). + If `$CLAUDE_ENABLED` is `true` but `$REVIEW_CYCLE` >= `$MAX_REVIEW_CYCLES`, + add a note in the escalation: "Claude delegation exhausted after + $REVIEW_CYCLE cycles — human review required." +10. After all actions, print a single-line JSON status to stdout: + `{"pr":"","sha":"","risk":"","decision":"","delegated_to":"claude|human|none","posted":true|false}` ## Review body template diff --git a/scripts/review-one-pr.sh b/scripts/review-one-pr.sh index d028bfb2a..e00d45dee 100755 --- a/scripts/review-one-pr.sh +++ b/scripts/review-one-pr.sh @@ -51,6 +51,32 @@ if [ -n "${EXISTING_MARKER_SHA:-}" ]; then echo " re-review: prior marker was $EXISTING_MARKER_SHA, head is $PR_HEAD_SHA" fi +# Count how many review cycles we've already done on this PR (number of distinct markers). +# This prevents infinite @claude delegation loops. +REVIEW_CYCLE=$( + gh pr view "$PR_URL" --json reviews,comments \ + --jq '((.reviews // []) + (.comments // [])) | .[].body | select(. != null)' 2>/dev/null \ + | grep -cE '