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
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,14 @@ Scripts live in [`bin/`](bin) and are added to `PATH` via `zsh/zshrc.symlink`.

#### Automated PR review

These orchestrate Claude Code reviews of pull requests. They power the `review-all-prs` LaunchAgent.
These scripts run pull request reviews through Claude Code or Codex. The `review-all-prs` LaunchAgent uses Codex, reviews only PRs from `team-feature-flags`, starts one PR per hourly run, and allows two attempts per calendar day. Failed reviews count toward the daily limit.

Before starting or reinstalling the LaunchAgent, install the Codex CLI and run `codex login`. Existing service installations that only configured Claude will stop at the runner's authentication check until Codex is available.

| Script | Purpose |
| ------ | ------- |
| [`review-all-prs.sh`](bin/review-all-prs.sh) | Find PRs awaiting your review in a GitHub org using the GraphQL API. Filters out PRs you've already reviewed and sorts by priority: PRs authored by `--priority-team` members, then flags-scoped titles, then the rest. |
| [`run-pr-reviews.sh`](bin/run-pr-reviews.sh) | Take a list of PRs and run `/review-code` against each one in priority order, with per-review timeouts and Claude usage-limit detection. |
| [`review-all-prs.sh`](bin/review-all-prs.sh) | Find PRs awaiting your review in a GitHub org using the GraphQL API. `--author-team` limits every result source to current team members. The script filters out settled reviews and sorts by priority: `--priority-team` authors, flags-scoped titles, then the rest. |
| [`run-pr-reviews.sh`](bin/run-pr-reviews.sh) | Take a list of PRs and run the `review-code` skill through `--engine claude` or `--engine codex`. It supports per-run and daily attempt limits, review timeouts, and engine usage-limit detection. |
| [`review-all-prs-service.sh`](bin/review-all-prs-service.sh) | Manage the `review-all-prs` macOS LaunchAgent (install, start, stop, logs, run). |
| [`recent-reviews.sh`](bin/recent-reviews.sh) | Show recent PR review activity from session state files. |
| [`seed-pr-failures.sh`](bin/seed-pr-failures.sh) | Rebuild the persistent PR-failure ledger from session history. |
Expand Down
65 changes: 62 additions & 3 deletions bin/lib/test-review-search-queries.sh
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,12 @@ if [[ "${1-}" == "api" && "${2-}" == "graphql" ]]; then
fi
done
# PR_FIXTURE_QUERY names a substring; searches matching it return a page of
# two PRs, every other search an empty page. That reproduces a draft only some
# four PRs, every other search an empty page. That reproduces a draft only some
# qualifiers can see. 99001 carries an unsubmitted draft review by "me";
# 99002 carries no review at all, so it is what pending mode has to drop.
# 99002 carries no review at all, so it is what pending mode has to drop;
# 99003 is an outside-team author with a pending review, so strict author-team
# filtering has to remove it even when it came from the pending sweep; 99004
# belongs to a second team for repeatable --author-team coverage.
if [[ -n "${PR_FIXTURE_QUERY-}" && "$query" == *"$PR_FIXTURE_QUERY"* ]]; then
cat <<'NODE'
{"data":{"search":{"pageInfo":{"hasNextPage":false,"endCursor":null},"edges":[{"node":{
Expand All @@ -86,6 +89,24 @@ if [[ "${1-}" == "api" && "${2-}" == "graphql" ]]; then
"updatedAt":"2026-08-24T12:00:00Z",
"reviews":{"nodes":[]},
"commits":{"nodes":[{"commit":{"committedDate":"2026-08-24T11:00:00Z"}}]}
}}, {"node":{
"number":99003,
"title":"fix(flags): fixture PR from outside the team",
"url":"https://github.com/PostHog/posthog/pull/99003",
"repository":{"nameWithOwner":"PostHog/posthog"},
"author":{"login":"outside-dev"},
"updatedAt":"2026-08-24T12:00:00Z",
"reviews":{"nodes":[{"author":{"login":"me"},"state":"PENDING","submittedAt":null}]},
"commits":{"nodes":[{"commit":{"committedDate":"2026-08-24T11:00:00Z"}}]}
}}, {"node":{
"number":99004,
"title":"feat(flags): fixture PR from a second allowed team",
"url":"https://github.com/PostHog/posthog/pull/99004",
"repository":{"nameWithOwner":"PostHog/posthog"},
"author":{"login":"growth-dev"},
"updatedAt":"2026-08-24T12:00:00Z",
"reviews":{"nodes":[]},
"commits":{"nodes":[{"commit":{"committedDate":"2026-08-24T11:00:00Z"}}]}
}}]}}}
NODE
exit 0
Expand All @@ -95,12 +116,16 @@ NODE
fi
case "${2-}" in
user) echo "me" ;;
orgs/*/teams/growth/members*)
if [[ "$*" == *"[.[].login]"* ]]; then echo '["growth-dev"]'
else echo 'growth-dev'; fi
;;
orgs/*/teams/*/members*)
# '[.[].login]' asks for a JSON array, '.[].login' for bare lines.
if [[ "$*" == *"[.[].login]"* ]]; then echo '["dev-one","dev-two"]'
else printf 'dev-one\ndev-two\n'; fi
;;
orgs/*/members*) echo '["dev-one","dev-two"]' ;;
orgs/*/members*) echo '["dev-one","dev-two","growth-dev"]' ;;
*) echo '[]' ;;
esac
SHIM
Expand Down Expand Up @@ -177,6 +202,12 @@ assert "--all folds the priority team into team-review-requested" \
ran_query "team-review-requested:PostHog/flags"
assert "--all sweeps involves:@me for pending drafts" ran_query "involves:@me"

run_queries --all --author-team flags
assert "--all uses --author-team members for its author search" \
ran_query "author:dev-one author:dev-two"
assert_not "--author-team does not add a team review-request query" \
ran_query "team-review-requested:PostHog/flags"

run_queries --org acme --draft
assert "--draft scopes its queries to --org" ran_query "org:acme"

Expand All @@ -197,6 +228,34 @@ out=$(run_with_fixture "involves:@me" --draft --json)
assert "--draft still reports a draft found only through the involves sweep" \
grep -q '"number": 99001' <<< "$out"

# --author-team filters every merged result, unlike --team, which only expands discovery.
out=$(run_with_fixture "review-requested:@me" --author-team flags --json)
assert "--author-team keeps a direct review request authored by a team member" \
grep -q '"number": 99002' <<< "$out"
assert_not "--author-team excludes a direct review request from outside the team" \
grep -q '"number": 99003' <<< "$out"
assert_not "one --author-team excludes authors from a different team" \
grep -q '"number": 99004' <<< "$out"

out=$(run_with_fixture "review-requested:@me" \
--author-team flags --author-team growth --json)
assert "repeated --author-team keeps authors from the first team" \
grep -q '"number": 99002' <<< "$out"
assert "repeated --author-team keeps authors from the second team" \
grep -q '"number": 99004' <<< "$out"
assert_not "repeated --author-team still excludes authors from other teams" \
grep -q '"number": 99003' <<< "$out"

out=$(run_with_fixture "involves:@me" --author-team flags --json)
assert "--author-team keeps a team member's pending review from the involves sweep" \
grep -q '"number": 99001' <<< "$out"
assert_not "--author-team excludes an outside author's pending review from the merged sweep" \
grep -q '"number": 99003' <<< "$out"

out=$(run_with_fixture "review-requested:@me" --team flags --json)
assert "--team keeps its discovery-only semantics for outside authors" \
grep -q '"number": 99003' <<< "$out"

# The hidden count measures the new-commits gate, which pending mode's extra
# PENDING filter makes meaningless, so pending mode must not report one.
out=$(run_with_fixture "review-requested:@me" --draft)
Expand Down
Loading
Loading