Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
93 changes: 81 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,78 @@ 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 a body that:
- Tags `@claude` on the first line so the Claude GitHub App picks it up.
- 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:

```
@claude Please fix the following findings from the automated review council and resolve all outstanding review comments on this PR:

## Council findings to address

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

## Additional instructions

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

Once all fixes are pushed, the review council will automatically re-review on the next cycle.
```

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