Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/pr-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 22 additions & 1 deletion AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

```
Expand Down Expand Up @@ -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`
Expand Down
98 changes: 86 additions & 12 deletions prompts/synthesize.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 <owner/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/<owner>/<repo>/pulls/<num>/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":"<url>","sha":"<sha>","risk":"<r>","decision":"<d>","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/<owner>/<repo>/pulls/<num>/update-branch" -f expected_head_sha="$PR_HEAD_SHA"`
Comment on lines +64 to +67
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/<owner>/<repo>/issues/<num>/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 <REVIEW_CYCLE + 1>/<MAX_REVIEW_CYCLES>)

The automated review council identified the following issues. Please address each one:

### Findings to fix

<for each finding with severity minor/major/critical:>
- **[<severity>]** `<file>:<line>` — <message>
<end for>

### 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 `<baseRefName>` 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 <owner/repo> --color FBCA04 --description "Flagged by automated PR review agent"`),
then re-request don-petry as a reviewer
(`gh api -X POST "repos/<owner>/<repo>/pulls/<num>/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":"<url>","sha":"<sha>","risk":"<r>","decision":"<d>","delegated_to":"claude|human|none","posted":true|false}`

## Review body template

Expand Down
26 changes: 26 additions & 0 deletions scripts/review-one-pr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 '<!-- pr-review-agent v1 sha=[a-f0-9]+' || echo 0
)
export REVIEW_CYCLE
echo " review cycle: $REVIEW_CYCLE (max: ${MAX_REVIEW_CYCLES:-3})"

# Detect if the PR's repo org has Claude App (for @claude delegation).
PR_ORG=$(echo "$PR_URL" | sed -E 's|https://github.com/([^/]+)/.*|\1|')
export PR_ORG
CLAUDE_ENABLED=false
if [ -n "${CLAUDE_ORGS:-}" ]; then
IFS=',' read -ra ORG_LIST <<< "$CLAUDE_ORGS"
for org in "${ORG_LIST[@]}"; do
if [ "$(echo "$org" | tr -d ' ')" = "$PR_ORG" ]; then
CLAUDE_ENABLED=true
break
fi
done
fi
export CLAUDE_ENABLED
echo " claude delegation: $CLAUDE_ENABLED (org: $PR_ORG)"

# 3. Run council in parallel
mkdir -p /tmp/council
rm -f /tmp/council/*.json /tmp/council/*.log
Expand Down