chore: making PR review chores deterministic + less token hungry

This commit is contained in:
Gustavo Madeira Santana
2026-02-11 13:20:57 -05:00
parent 93411b74a0
commit 72fbfaa755
15 changed files with 2500 additions and 527 deletions

View File

@@ -0,0 +1,181 @@
# PR Workflow for Maintainers
Please read this in full and do not skip sections.
This is the single source of truth for the maintainer PR workflow.
## Triage order
Process PRs **oldest to newest**. Older PRs are more likely to have merge conflicts and stale dependencies; resolving them first keeps the queue healthy and avoids snowballing rebase pain.
## Working rule
Skills execute workflow. Maintainers provide judgment.
Always pause between skills to evaluate technical direction, not just command success.
These three skills must be used in order:
1. `review-pr` — review only, produce findings
2. `prepare-pr` — rebase, fix, gate, push to PR head branch
3. `merge-pr` — squash-merge, verify MERGED state, clean up
They are necessary, but not sufficient. Maintainers must steer between steps and understand the code before moving forward.
Treat PRs as reports first, code second.
If submitted code is low quality, ignore it and implement the best solution for the problem.
Do not continue if you cannot verify the problem is real or test the fix.
## Coding Agent
Use ChatGPT 5.3 Codex High. Fall back to 5.2 Codex High or 5.3 Codex Medium if necessary.
## PR quality bar
- Do not trust PR code by default.
- Do not merge changes you cannot validate with a reproducible problem and a tested fix.
- Keep types strict. Do not use `any` in implementation code.
- Keep external-input boundaries typed and validated, including CLI input, environment variables, network payloads, and tool output.
- Keep implementations properly scoped. Fix root causes, not local symptoms.
- Identify and reuse canonical sources of truth so behavior does not drift across the codebase.
- Harden changes. Always evaluate security impact and abuse paths.
- Understand the system before changing it. Never make the codebase messier just to clear a PR queue.
## Rebase and conflict resolution
Before any substantive review or prep work, **always rebase the PR branch onto current `main` and resolve merge conflicts first**. A PR that cannot cleanly rebase is not ready for review — fix conflicts before evaluating correctness.
- During `prepare-pr`: rebase onto `main` as the first step, before fixing findings or running gates.
- If conflicts are complex or touch areas you do not understand, stop and escalate.
- Prefer **rebase** for linear history; **squash** when commit history is messy or unhelpful.
## Commit and changelog rules
- Create commits with `scripts/committer "<msg>" <file...>`; avoid manual `git add`/`git commit` so staging stays scoped.
- Follow concise, action-oriented commit messages (e.g., `CLI: add verbose flag to send`).
- During `prepare-pr`, use this commit subject format: `fix: <summary> (openclaw#<PR>) thanks @<pr-author>`.
- Group related changes; avoid bundling unrelated refactors.
- Changelog workflow: keep the latest released version at the top (no `Unreleased`); after publishing, bump the version and start a new top section.
- When working on a PR: add a changelog entry with the PR number and thank the contributor.
- When working on an issue: reference the issue in the changelog entry.
- Pure test additions/fixes generally do **not** need a changelog entry unless they alter user-facing behavior or the user asks for one.
## Co-contributor and clawtributors
- If we squash, add the PR author as a co-contributor in the commit body using a `Co-authored-by:` trailer.
- When maintainer prepares and merges the PR, add the maintainer as an additional `Co-authored-by:` trailer too.
- Avoid `--auto` merges for maintainer landings. Merge only after checks are green so the maintainer account is the actor and attribution is deterministic.
- For squash merges, set `--author-email` to a reviewer-owned email with fallback candidates; if merge fails due to author-email validation, retry once with the next candidate.
- If you review a PR and later do work on it, land via merge/squash (no direct-main commits) and always add the PR author as a co-contributor.
- When merging a PR: leave a PR comment that explains exactly what we did, include the SHA hashes, and record the comment URL in the final report.
- When merging a PR from a new contributor: run `bun scripts/update-clawtributors.ts` to add their avatar to the README "Thanks to all clawtributors" list, then commit the regenerated README.
## Review mode vs landing mode
- **Review mode (PR link only):** read `gh pr view`/`gh pr diff`; **do not** switch branches; **do not** change code.
- **Landing mode (exception path):** use only when normal `review-pr -> prepare-pr -> merge-pr` flow cannot safely preserve attribution or cannot satisfy branch protection. Create an integration branch from `main`, bring in PR commits (**prefer rebase** for linear history; **merge allowed** when complexity/conflicts make it safer), apply fixes, add changelog (+ thanks + PR #), run full gate **locally before committing** (`pnpm build && pnpm check && pnpm test`), commit, merge back to `main`, then `git switch main` (never stay on a topic branch after landing). Important: the contributor needs to be in the git graph after this!
## Pre-review safety checks
- Before starting a review when a GH Issue/PR is pasted: use an isolated `.worktrees/pr-<PR>` checkout from `origin/main`. Do not require a clean main checkout, and do not run `git pull` in a dirty main checkout.
- PR review calls: prefer a single `gh pr view --json ...` to batch metadata/comments; run `gh pr diff` only when needed.
- PRs should summarize scope, note testing performed, and mention any user-facing changes or new flags.
- Read `docs/help/submitting-a-pr.md` ([Submitting a PR](https://docs.openclaw.ai/help/submitting-a-pr)) for what we expect from contributors.
## Unified workflow
Entry criteria:
- PR URL/number is known.
- Problem statement is clear enough to attempt reproduction.
- A realistic verification path exists (tests, integration checks, or explicit manual validation).
### 1) `review-pr`
Purpose:
- Review only: correctness, value, security risk, tests, docs, and changelog impact.
- Produce structured findings and a recommendation.
Expected output:
- Recommendation: ready, needs work, needs discussion, or close.
- `.local/review.md` with actionable findings.
Maintainer checkpoint before `prepare-pr`:
```
What problem are they trying to solve?
What is the most optimal implementation?
Can we fix up everything?
Do we have any questions?
```
Stop and escalate instead of continuing if:
- The problem cannot be reproduced or confirmed.
- The proposed PR scope does not match the stated problem.
- The design introduces unresolved security or trust-boundary concerns.
### 2) `prepare-pr`
Purpose:
- Make the PR merge-ready on its head branch.
- Rebase onto current `main` first, then fix blocker/important findings, then run gates.
- In fresh worktrees, bootstrap dependencies before local gates (`pnpm install --frozen-lockfile`).
Expected output:
- Updated code and tests on the PR head branch.
- `.local/prep.md` with changes, verification, and current HEAD SHA.
- Final status: `PR is ready for /mergepr`.
Maintainer checkpoint before `merge-pr`:
```
Is this the most optimal implementation?
Is the code properly scoped?
Is the code properly reusing existing logic in the codebase?
Is the code properly typed?
Is the code hardened?
Do we have enough tests?
Do we need regression tests?
Are tests using fake timers where appropriate? (e.g., debounce/throttle, retry backoff, timeout branches, delayed callbacks, polling loops)
Do not add performative tests, ensure tests are real and there are no regressions.
Do you see any follow-up refactors we should do?
Take your time, fix it properly, refactor if necessary.
Did any changes introduce any potential security vulnerabilities?
```
Stop and escalate instead of continuing if:
- You cannot verify behavior changes with meaningful tests or validation.
- Fixing findings requires broad architecture changes outside safe PR scope.
- Security hardening requirements remain unresolved.
### 3) `merge-pr`
Purpose:
- Merge only after review and prep artifacts are present and checks are green.
- Use deterministic squash merge flow (`--match-head-commit` + explicit subject/body with co-author trailer), then verify the PR ends in `MERGED` state.
- If no required checks are configured on the PR, treat that as acceptable and continue after branch-up-to-date validation.
Go or no-go checklist before merge:
- All BLOCKER and IMPORTANT findings are resolved.
- Verification is meaningful and regression risk is acceptably low.
- Docs and changelog are updated when required.
- Required CI checks are green and the branch is not behind `main`.
Expected output:
- Successful merge commit and recorded merge SHA.
- Worktree cleanup after successful merge.
- Comment on PR indicating merge was successful.
Maintainer checkpoint after merge:
- Were any refactors intentionally deferred and now need follow-up issue(s)?
- Did this reveal broader architecture or test gaps we should address?
- Run `bun scripts/update-clawtributors.ts` if the contributor is new.

View File

@@ -0,0 +1,304 @@
---
name: merge-pr
description: Merge a GitHub PR via squash after /prepare-pr. Use when asked to merge a ready PR. Do not push to main or modify code. Ensure the PR ends in MERGED state and clean up worktrees after success.
---
# Merge PR
## Overview
Merge a prepared PR via deterministic squash merge (`--match-head-commit` + explicit co-author trailer), then clean up the worktree after success.
## Inputs
- Ask for PR number or URL.
- If missing, use `.local/prep.env` from the worktree if present.
- If ambiguous, ask.
## Safety
- Use `gh pr merge --squash` as the only path to `main`.
- Do not run `git push` at all during merge.
- Do not use `gh pr merge --auto` for maintainer landings.
- Do not run gateway stop commands. Do not kill processes. Do not touch port 18792.
## Execution Rule
- Execute the workflow. Do not stop after printing the TODO checklist.
- If delegating, require the delegate to run commands and capture outputs.
## Known Footguns
- If you see "fatal: not a git repository", you are in the wrong directory. Move to the repo root and retry.
- Read `.local/review.md`, `.local/prep.md`, and `.local/prep.env` in the worktree. Do not skip.
- Always merge with `--match-head-commit "$PREP_HEAD_SHA"` to prevent racing stale or changed heads.
- Clean up `.worktrees/pr-<PR>` only after confirmed `MERGED`.
## Completion Criteria
- Ensure `gh pr merge` succeeds.
- Ensure PR state is `MERGED`, never `CLOSED`.
- Record the merge SHA.
- Leave a PR comment with merge SHA and prepared head SHA, and capture the comment URL.
- Run cleanup only after merge success.
## First: Create a TODO Checklist
Create a checklist of all merge steps, print it, then continue and execute the commands.
## Setup: Use a Worktree
Use an isolated worktree for all merge work.
```sh
repo_root=$(git rev-parse --show-toplevel)
cd "$repo_root"
gh auth status
WORKTREE_DIR=".worktrees/pr-<PR>"
cd "$WORKTREE_DIR"
```
Run all commands inside the worktree directory.
## Load Local Artifacts (Mandatory)
Expect these files from earlier steps:
- `.local/review.md` from `/review-pr`
- `.local/prep.md` from `/prepare-pr`
- `.local/prep.env` from `/prepare-pr`
```sh
ls -la .local || true
for required in .local/review.md .local/prep.md .local/prep.env; do
if [ ! -f "$required" ]; then
echo "Missing $required. Stop and run /review-pr then /prepare-pr."
exit 1
fi
done
sed -n '1,120p' .local/review.md
sed -n '1,120p' .local/prep.md
source .local/prep.env
```
## Steps
1. Identify PR meta and verify prepared SHA still matches
```sh
pr_meta_json=$(gh pr view <PR> --json number,title,state,isDraft,author,headRefName,headRefOid,baseRefName,headRepository,body)
printf '%s\n' "$pr_meta_json" | jq '{number,title,state,isDraft,author:.author.login,head:.headRefName,headSha:.headRefOid,base:.baseRefName,headRepo:.headRepository.nameWithOwner,body}'
pr_title=$(printf '%s\n' "$pr_meta_json" | jq -r .title)
pr_number=$(printf '%s\n' "$pr_meta_json" | jq -r .number)
pr_head_sha=$(printf '%s\n' "$pr_meta_json" | jq -r .headRefOid)
contrib=$(printf '%s\n' "$pr_meta_json" | jq -r .author.login)
is_draft=$(printf '%s\n' "$pr_meta_json" | jq -r .isDraft)
if [ "$is_draft" = "true" ]; then
echo "ERROR: PR is draft. Stop and run /prepare-pr after draft is cleared."
exit 1
fi
if [ "$pr_head_sha" != "$PREP_HEAD_SHA" ]; then
echo "ERROR: PR head changed after /prepare-pr (expected $PREP_HEAD_SHA, got $pr_head_sha). Re-run /prepare-pr."
exit 1
fi
```
2. Run sanity checks
Stop if any are true:
- PR is a draft.
- Required checks are failing.
- Branch is behind main.
If checks are pending, wait for completion before merging. Do not use `--auto`.
If no required checks are configured, continue.
```sh
gh pr checks <PR> --required --watch --fail-fast || true
checks_json=$(gh pr checks <PR> --required --json name,bucket,state 2>/tmp/gh-checks.err || true)
if [ -z "$checks_json" ]; then
checks_json='[]'
fi
required_count=$(printf '%s\n' "$checks_json" | jq 'length')
if [ "$required_count" -eq 0 ]; then
echo "No required checks configured for this PR."
fi
printf '%s\n' "$checks_json" | jq -r '.[] | "\(.bucket)\t\(.name)\t\(.state)"'
failed_required=$(printf '%s\n' "$checks_json" | jq '[.[] | select(.bucket=="fail")] | length')
pending_required=$(printf '%s\n' "$checks_json" | jq '[.[] | select(.bucket=="pending")] | length')
if [ "$failed_required" -gt 0 ]; then
echo "Required checks are failing, run /prepare-pr."
exit 1
fi
if [ "$pending_required" -gt 0 ]; then
echo "Required checks are still pending, retry /merge-pr when green."
exit 1
fi
git fetch origin main
git fetch origin pull/<PR>/head:pr-<PR> --force
git merge-base --is-ancestor origin/main pr-<PR> || (echo "PR branch is behind main, run /prepare-pr" && exit 1)
```
If anything is failing or behind, stop and say to run `/prepare-pr`.
3. Merge PR with explicit attribution metadata
```sh
reviewer=$(gh api user --jq .login)
reviewer_id=$(gh api user --jq .id)
coauthor_email=${COAUTHOR_EMAIL:-"$contrib@users.noreply.github.com"}
if [ -z "$coauthor_email" ] || [ "$coauthor_email" = "null" ]; then
contrib_id=$(gh api users/$contrib --jq .id)
coauthor_email="${contrib_id}+${contrib}@users.noreply.github.com"
fi
gh_email=$(gh api user --jq '.email // ""' || true)
git_email=$(git config user.email || true)
mapfile -t reviewer_email_candidates < <(
printf '%s\n' \
"$gh_email" \
"$git_email" \
"${reviewer_id}+${reviewer}@users.noreply.github.com" \
"${reviewer}@users.noreply.github.com" | awk 'NF && !seen[$0]++'
)
[ "${#reviewer_email_candidates[@]}" -gt 0 ] || { echo "ERROR: could not resolve reviewer author email"; exit 1; }
reviewer_email="${reviewer_email_candidates[0]}"
cat > .local/merge-body.txt <<EOF
Merged via /review-pr -> /prepare-pr -> /merge-pr.
Prepared head SHA: $PREP_HEAD_SHA
Co-authored-by: $contrib <$coauthor_email>
Co-authored-by: $reviewer <$reviewer_email>
Reviewed-by: @$reviewer
EOF
run_merge() {
local email="$1"
local stderr_file
stderr_file=$(mktemp)
if gh pr merge <PR> \
--squash \
--delete-branch \
--match-head-commit "$PREP_HEAD_SHA" \
--author-email "$email" \
--subject "$pr_title (#$pr_number)" \
--body-file .local/merge-body.txt \
2> >(tee "$stderr_file" >&2)
then
rm -f "$stderr_file"
return 0
fi
merge_err=$(cat "$stderr_file")
rm -f "$stderr_file"
return 1
}
merge_err=""
selected_merge_author_email="$reviewer_email"
if ! run_merge "$selected_merge_author_email"; then
if printf '%s\n' "$merge_err" | rg -qi 'author.?email|email.*associated|associated.*email|invalid.*email' && [ "${#reviewer_email_candidates[@]}" -ge 2 ]; then
selected_merge_author_email="${reviewer_email_candidates[1]}"
echo "Retrying once with fallback author email: $selected_merge_author_email"
run_merge "$selected_merge_author_email" || { echo "ERROR: merge failed after fallback retry"; exit 1; }
else
echo "ERROR: merge failed"
exit 1
fi
fi
```
Retry is allowed exactly once when the error is clearly author-email validation.
4. Verify PR state and capture merge SHA
```sh
state=$(gh pr view <PR> --json state --jq .state)
if [ "$state" != "MERGED" ]; then
echo "Merge not finalized yet (state=$state), waiting up to 15 minutes..."
for _ in $(seq 1 90); do
sleep 10
state=$(gh pr view <PR> --json state --jq .state)
if [ "$state" = "MERGED" ]; then
break
fi
done
fi
if [ "$state" != "MERGED" ]; then
echo "ERROR: PR state is $state after waiting. Leave worktree and retry /merge-pr later."
exit 1
fi
merge_sha=$(gh pr view <PR> --json mergeCommit --jq '.mergeCommit.oid')
if [ -z "$merge_sha" ] || [ "$merge_sha" = "null" ]; then
echo "ERROR: merge commit SHA missing."
exit 1
fi
commit_body=$(gh api repos/:owner/:repo/commits/$merge_sha --jq .commit.message)
contrib=${contrib:-$(gh pr view <PR> --json author --jq .author.login)}
reviewer=${reviewer:-$(gh api user --jq .login)}
printf '%s\n' "$commit_body" | rg -q "^Co-authored-by: $contrib <" || { echo "ERROR: missing PR author co-author trailer"; exit 1; }
printf '%s\n' "$commit_body" | rg -q "^Co-authored-by: $reviewer <" || { echo "ERROR: missing reviewer co-author trailer"; exit 1; }
echo "merge_sha=$merge_sha"
```
5. PR comment
Use a multiline heredoc with interpolation enabled.
```sh
ok=0
comment_output=""
for _ in 1 2 3; do
if comment_output=$(gh pr comment <PR> -F - <<EOF
Merged via squash.
- Prepared head SHA: $PREP_HEAD_SHA
- Merge commit: $merge_sha
Thanks @$contrib!
EOF
); then
ok=1
break
fi
sleep 2
done
[ "$ok" -eq 1 ] || { echo "ERROR: failed to post PR comment after retries"; exit 1; }
comment_url=$(printf '%s\n' "$comment_output" | rg -o 'https://github.com/[^ ]+/pull/[0-9]+#issuecomment-[0-9]+' -m1 || true)
[ -n "$comment_url" ] || comment_url="unresolved"
echo "comment_url=$comment_url"
```
6. Clean up worktree only on success
Run cleanup only if step 4 returned `MERGED`.
```sh
cd "$repo_root"
git worktree remove ".worktrees/pr-<PR>" --force
git branch -D temp/pr-<PR> 2>/dev/null || true
git branch -D pr-<PR> 2>/dev/null || true
git branch -D pr-<PR>-prep 2>/dev/null || true
```
## Guardrails
- Worktree only.
- Do not close PRs.
- End in MERGED state.
- Clean up only after merge success.
- Never push to main. Use `gh pr merge --squash` only.
- Do not run `git push` at all in this command.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "Merge PR"
short_description: "Merge GitHub PRs via squash"
default_prompt: "Use $merge-pr to merge a GitHub PR via squash after preparation."

View File

@@ -0,0 +1,336 @@
---
name: prepare-pr
description: Prepare a GitHub PR for merge by rebasing onto main, fixing review findings, running gates, committing fixes, and pushing to the PR head branch. Use after /review-pr. Never merge or push to main.
---
# Prepare PR
## Overview
Prepare a PR head branch for merge with review fixes, green gates, and deterministic merge handoff artifacts.
## Inputs
- Ask for PR number or URL.
- If missing, use `.local/pr-meta.env` from the PR worktree if present.
- If ambiguous, ask.
## Safety
- Never push to `main` or `origin/main`. Push only to the PR head branch.
- Never run `git push` without explicit remote and branch. Do not run bare `git push`.
- Do not run gateway stop commands. Do not kill processes. Do not touch port 18792.
- Do not run `git clean -fdx`.
- Do not run `git add -A` or `git add .`.
## Execution Rule
- Execute the workflow. Do not stop after printing the TODO checklist.
- If delegating, require the delegate to run commands and capture outputs.
## Completion Criteria
- Rebase PR commits onto `origin/main`.
- Fix all BLOCKER and IMPORTANT items from `.local/review.md`.
- Commit prep changes with required subject format.
- Run required gates and pass (`pnpm test` may be skipped only for high-confidence docs-only changes).
- Push the updated HEAD back to the PR head branch.
- Write `.local/prep.md` and `.local/prep.env`.
- Output exactly: `PR is ready for /mergepr`.
## First: Create a TODO Checklist
Create a checklist of all prep steps, print it, then continue and execute the commands.
## Setup: Use a Worktree
Use an isolated worktree for all prep work.
```sh
repo_root=$(git rev-parse --show-toplevel)
cd "$repo_root"
gh auth status
WORKTREE_DIR=".worktrees/pr-<PR>"
if [ ! -d "$WORKTREE_DIR" ]; then
git fetch origin main
git worktree add "$WORKTREE_DIR" -b temp/pr-<PR> origin/main
fi
cd "$WORKTREE_DIR"
mkdir -p .local
```
Run all commands inside the worktree directory.
## Load Review Artifacts (Mandatory)
```sh
if [ ! -f .local/review.md ]; then
echo "Missing .local/review.md. Run /review-pr first and save findings."
exit 1
fi
if [ ! -f .local/pr-meta.env ]; then
echo "Missing .local/pr-meta.env. Run /review-pr first and save metadata."
exit 1
fi
sed -n '1,220p' .local/review.md
source .local/pr-meta.env
```
## Steps
1. Identify PR meta with one API call
```sh
pr_meta_json=$(gh pr view <PR> --json number,title,author,headRefName,headRefOid,baseRefName,headRepository,headRepositoryOwner,body)
printf '%s\n' "$pr_meta_json" | jq '{number,title,author:.author.login,head:.headRefName,headSha:.headRefOid,base:.baseRefName,headRepo:.headRepository.nameWithOwner,headRepoOwner:.headRepositoryOwner.login,headRepoName:.headRepository.name,body}'
pr_number=$(printf '%s\n' "$pr_meta_json" | jq -r .number)
contrib=$(printf '%s\n' "$pr_meta_json" | jq -r .author.login)
head=$(printf '%s\n' "$pr_meta_json" | jq -r .headRefName)
pr_head_sha_before=$(printf '%s\n' "$pr_meta_json" | jq -r .headRefOid)
head_owner=$(printf '%s\n' "$pr_meta_json" | jq -r '.headRepositoryOwner.login // empty')
head_repo_name=$(printf '%s\n' "$pr_meta_json" | jq -r '.headRepository.name // empty')
head_repo_url=$(printf '%s\n' "$pr_meta_json" | jq -r '.headRepository.url // empty')
if [ -n "${PR_HEAD:-}" ] && [ "$head" != "$PR_HEAD" ]; then
echo "ERROR: PR head branch changed from $PR_HEAD to $head. Re-run /review-pr."
exit 1
fi
```
2. Fetch PR head and rebase on latest `origin/main`
```sh
git fetch origin pull/<PR>/head:pr-<PR> --force
git checkout -B pr-<PR>-prep pr-<PR>
git fetch origin main
git rebase origin/main
```
If conflicts happen:
- Resolve each conflicted file.
- Run `git add <resolved_file>` for each file.
- Run `git rebase --continue`.
If the rebase gets confusing or you resolve conflicts 3 or more times, stop and report.
3. Fix issues from `.local/review.md`
- Fix all BLOCKER and IMPORTANT items.
- NITs are optional.
- Keep scope tight.
Keep a running log in `.local/prep.md`:
- List which review items you fixed.
- List which files you touched.
- Note behavior changes.
4. Optional quick feedback tests before full gates
Targeted tests are optional quick feedback, not a substitute for full gates.
If running targeted tests in a fresh worktree:
```sh
if [ ! -x node_modules/.bin/vitest ]; then
pnpm install --frozen-lockfile
fi
```
5. Commit prep fixes with required subject format
Use `scripts/committer` with explicit file paths.
Required subject format:
- `fix: <summary> (openclaw#<PR>) thanks @<author>`
```sh
commit_msg="fix: <summary> (openclaw#$pr_number) thanks @$contrib"
scripts/committer "$commit_msg" <changed file 1> <changed file 2> ...
```
If there are no local changes, do not create a no-op commit.
Post-commit validation (mandatory):
```sh
subject=$(git log -1 --pretty=%s)
echo "$subject" | rg -q "openclaw#$pr_number" || { echo "ERROR: commit subject missing openclaw#$pr_number"; exit 1; }
echo "$subject" | rg -q "thanks @$contrib" || { echo "ERROR: commit subject missing thanks @$contrib"; exit 1; }
```
6. Decide verification mode and run required gates before pushing
If you are highly confident the change is docs-only, you may skip `pnpm test`.
High-confidence docs-only criteria (all must be true):
- Every changed file is documentation-only (`docs/**`, `README*.md`, `CHANGELOG.md`, `*.md`, `*.mdx`, `mintlify.json`, `docs.json`).
- No code, runtime, test, dependency, or build config files changed (`src/**`, `extensions/**`, `apps/**`, `package.json`, lockfiles, TS/JS config, test files, scripts).
- `.local/review.md` does not call for non-doc behavior fixes.
Suggested check:
```sh
changed_files=$(git diff --name-only origin/main...HEAD)
non_docs=$(printf "%s\n" "$changed_files" | grep -Ev '^(docs/|README.*\.md$|CHANGELOG\.md$|.*\.md$|.*\.mdx$|mintlify\.json$|docs\.json$)' || true)
docs_only=false
if [ -n "$changed_files" ] && [ -z "$non_docs" ]; then
docs_only=true
fi
echo "docs_only=$docs_only"
```
Bootstrap dependencies in a fresh worktree before gates:
```sh
if [ ! -d node_modules ]; then
pnpm install --frozen-lockfile
fi
```
Run required gates:
```sh
pnpm build
pnpm check
if [ "$docs_only" = "true" ]; then
echo "Docs-only change detected with high confidence; skipping pnpm test." | tee -a .local/prep.md
else
pnpm test
fi
```
Require all required gates to pass. If something fails, fix, commit, and rerun. Allow at most 3 fix-and-rerun cycles.
7. Push safely to the PR head branch
Build `prhead` from owner/name first, then validate remote branch SHA before push.
```sh
if [ -n "$head_owner" ] && [ -n "$head_repo_name" ]; then
head_repo_push_url="https://github.com/$head_owner/$head_repo_name.git"
elif [ -n "$head_repo_url" ] && [ "$head_repo_url" != "null" ]; then
case "$head_repo_url" in
*.git) head_repo_push_url="$head_repo_url" ;;
*) head_repo_push_url="$head_repo_url.git" ;;
esac
else
echo "ERROR: unable to determine PR head repo push URL"
exit 1
fi
git remote add prhead "$head_repo_push_url" 2>/dev/null || git remote set-url prhead "$head_repo_push_url"
echo "Pushing to branch: $head"
if [ "$head" = "main" ] || [ "$head" = "master" ]; then
echo "ERROR: head branch is main/master. This is wrong. Stopping."
exit 1
fi
remote_sha=$(git ls-remote prhead "refs/heads/$head" | awk '{print $1}')
if [ -z "$remote_sha" ]; then
echo "ERROR: remote branch refs/heads/$head not found on prhead"
exit 1
fi
if [ "$remote_sha" != "$pr_head_sha_before" ]; then
echo "ERROR: expected remote SHA $pr_head_sha_before, got $remote_sha. Re-fetch metadata and rebase first."
exit 1
fi
git push --force-with-lease=refs/heads/$head:$pr_head_sha_before prhead HEAD:$head || push_failed=1
```
If lease push fails because head moved, perform one automatic retry:
```sh
if [ "${push_failed:-0}" = "1" ]; then
echo "Lease push failed, retrying once with fresh PR head..."
pr_head_sha_before=$(gh pr view <PR> --json headRefOid --jq .headRefOid)
git fetch origin pull/<PR>/head:pr-<PR>-latest --force
git rebase pr-<PR>-latest
pnpm build
pnpm check
if [ "$docs_only" != "true" ]; then
pnpm test
fi
git push --force-with-lease=refs/heads/$head:$pr_head_sha_before prhead HEAD:$head
fi
```
8. Verify PR head and base relation (Mandatory)
```sh
prep_head_sha=$(git rev-parse HEAD)
pr_head_sha_after=$(gh pr view <PR> --json headRefOid --jq .headRefOid)
if [ "$prep_head_sha" != "$pr_head_sha_after" ]; then
echo "ERROR: pushed head SHA does not match PR head SHA."
exit 1
fi
git fetch origin main
git fetch origin pull/<PR>/head:pr-<PR>-verify --force
git merge-base --is-ancestor origin/main pr-<PR>-verify && echo "PR is up to date with main" || (echo "ERROR: PR is still behind main, rebase again" && exit 1)
git branch -D pr-<PR>-verify 2>/dev/null || true
```
9. Write prep summary artifacts (Mandatory)
Write `.local/prep.md` and `.local/prep.env` for merge handoff.
```sh
contrib_id=$(gh api users/$contrib --jq .id)
coauthor_email="${contrib_id}+${contrib}@users.noreply.github.com"
cat > .local/prep.env <<EOF_ENV
PR_NUMBER=$pr_number
PR_AUTHOR=$contrib
PR_HEAD=$head
PR_HEAD_SHA_BEFORE=$pr_head_sha_before
PREP_HEAD_SHA=$prep_head_sha
COAUTHOR_EMAIL=$coauthor_email
EOF_ENV
ls -la .local/prep.md .local/prep.env
wc -l .local/prep.md .local/prep.env
```
10. Output
Include a diff stat summary:
```sh
git diff --stat origin/main..HEAD
git diff --shortstat origin/main..HEAD
```
Report totals: X files changed, Y insertions(+), Z deletions(-).
If gates passed and push succeeded, print exactly:
```
PR is ready for /mergepr
```
Otherwise, list remaining failures and stop.
## Guardrails
- Worktree only.
- Do not delete the worktree on success. `/mergepr` may reuse it.
- Do not run `gh pr merge`.
- Never push to main. Only push to the PR head branch.
- Run and pass all required gates before pushing. `pnpm test` may be skipped only for high-confidence docs-only changes, and the skip must be explicitly recorded in `.local/prep.md`.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "Prepare PR"
short_description: "Prepare GitHub PRs for merge"
default_prompt: "Use $prepare-pr to prep a GitHub PR for merge without merging."

View File

@@ -0,0 +1,253 @@
---
name: review-pr
description: Review-only GitHub pull request analysis with the gh CLI. Use when asked to review a PR, provide structured feedback, or assess readiness to land. Do not merge, push, or make code changes you intend to keep.
---
# Review PR
## Overview
Perform a thorough review-only PR assessment and return a structured recommendation on readiness for /prepare-pr.
## Inputs
- Ask for PR number or URL.
- If missing, always ask. Never auto-detect from conversation.
- If ambiguous, ask.
## Safety
- Never push to `main` or `origin/main`, not during review, not ever.
- Do not run `git push` at all during review. Treat review as read only.
- Do not stop or kill the gateway. Do not run gateway stop commands. Do not kill processes on port 18792.
## Execution Rule
- Execute the workflow. Do not stop after printing the TODO checklist.
- If delegating, require the delegate to run commands and capture outputs, not a plan.
## Known Failure Modes
- If you see "fatal: not a git repository", you are in the wrong directory. Move to the repository root and retry.
- Do not stop after printing the checklist. That is not completion.
## Writing Style for Output
- Write casual and direct.
- Avoid em dashes and en dashes. Use commas or separate sentences.
## Completion Criteria
- Run the commands in the worktree and inspect the PR directly.
- Produce the structured review sections A through J.
- Save the full review to `.local/review.md` inside the worktree.
- Save PR metadata handoff to `.local/pr-meta.env` inside the worktree.
## First: Create a TODO Checklist
Create a checklist of all review steps, print it, then continue and execute the commands.
## Setup: Use a Worktree
Use an isolated worktree for all review work.
```sh
repo_root=$(git rev-parse --show-toplevel)
cd "$repo_root"
gh auth status
WORKTREE_DIR=".worktrees/pr-<PR>"
git fetch origin main
# Reuse existing worktree if it exists, otherwise create new
if [ -d "$WORKTREE_DIR" ]; then
git worktree list
cd "$WORKTREE_DIR"
git fetch origin main
git checkout -B temp/pr-<PR> origin/main
else
git worktree add "$WORKTREE_DIR" -b temp/pr-<PR> origin/main
cd "$WORKTREE_DIR"
fi
# Create local scratch space that persists across /review-pr to /prepare-pr to /merge-pr
mkdir -p .local
```
Run all commands inside the worktree directory.
Start on `origin/main` so you can check for existing implementations before looking at PR code.
## Steps
1. Identify PR meta and context
```sh
pr_meta_json=$(gh pr view <PR> --json number,title,state,isDraft,author,baseRefName,headRefName,headRefOid,headRepository,url,body,labels,assignees,reviewRequests,files,additions,deletions,statusCheckRollup)
printf '%s\n' "$pr_meta_json" | jq '{number,title,url,state,isDraft,author:.author.login,base:.baseRefName,head:.headRefName,headSha:.headRefOid,headRepo:.headRepository.nameWithOwner,additions,deletions,files:(.files|length),body}'
cat > .local/pr-meta.env <<EOF
PR_NUMBER=$(printf '%s\n' "$pr_meta_json" | jq -r .number)
PR_URL=$(printf '%s\n' "$pr_meta_json" | jq -r .url)
PR_AUTHOR=$(printf '%s\n' "$pr_meta_json" | jq -r .author.login)
PR_BASE=$(printf '%s\n' "$pr_meta_json" | jq -r .baseRefName)
PR_HEAD=$(printf '%s\n' "$pr_meta_json" | jq -r .headRefName)
PR_HEAD_SHA=$(printf '%s\n' "$pr_meta_json" | jq -r .headRefOid)
PR_HEAD_REPO=$(printf '%s\n' "$pr_meta_json" | jq -r .headRepository.nameWithOwner)
EOF
ls -la .local/pr-meta.env
```
2. Check if this already exists in main before looking at the PR branch
- Identify the core feature or fix from the PR title and description.
- Search for existing implementations using keywords from the PR title, changed file paths, and function or component names from the diff.
```sh
# Use keywords from the PR title and changed files
rg -n "<keyword_from_pr_title>" -S src packages apps ui || true
rg -n "<function_or_component_name>" -S src packages apps ui || true
git log --oneline --all --grep="<keyword_from_pr_title>" | head -20
```
If it already exists, call it out as a BLOCKER or at least IMPORTANT.
3. Claim the PR
Assign yourself so others know someone is reviewing. Skip if the PR looks like spam or is a draft you plan to recommend closing.
```sh
gh_user=$(gh api user --jq .login)
gh pr edit <PR> --add-assignee "$gh_user" || echo "Could not assign reviewer, continuing"
```
4. Read the PR description carefully
Use the body from step 1. Summarize goal, scope, and missing context.
5. Read the diff thoroughly
Minimum:
```sh
gh pr diff <PR>
```
If you need full code context locally, fetch the PR head to a local ref and diff it. Do not create a merge commit.
```sh
git fetch origin pull/<PR>/head:pr-<PR> --force
mb=$(git merge-base origin/main pr-<PR>)
# Show only this PR patch relative to merge-base, not total branch drift
git diff --stat "$mb"..pr-<PR>
git diff "$mb"..pr-<PR>
```
If you want to browse the PR version of files directly, temporarily check out `pr-<PR>` in the worktree. Do not commit or push. Return to `temp/pr-<PR>` and reset to `origin/main` afterward.
```sh
# Use only if needed
# git checkout pr-<PR>
# git branch --show-current
# ...inspect files...
git checkout temp/pr-<PR>
git checkout -B temp/pr-<PR> origin/main
git branch --show-current
```
6. Validate the change is needed and valuable
Be honest. Call out low value AI slop.
7. Evaluate implementation quality
Review correctness, design, performance, and ergonomics.
8. Perform a security review
Assume OpenClaw subagents run with full disk access, including git, gh, and shell. Check auth, input validation, secrets, dependencies, tool safety, and privacy.
9. Review tests and verification
Identify what exists, what is missing, and what would be a minimal regression test.
If you run local tests in the worktree, bootstrap dependencies first:
```sh
if [ ! -x node_modules/.bin/vitest ]; then
pnpm install --frozen-lockfile
fi
```
10. Check docs
Check if the PR touches code with related documentation such as README, docs, inline API docs, or config examples.
- If docs exist for the changed area and the PR does not update them, flag as IMPORTANT.
- If the PR adds a new feature or config option with no docs, flag as IMPORTANT.
- If the change is purely internal with no user-facing impact, skip this.
11. Check changelog
Check if `CHANGELOG.md` exists and whether the PR warrants an entry.
- If the project has a changelog and the PR is user-facing, flag missing entry as IMPORTANT.
- Leave the change for /prepare-pr, only flag it here.
12. Answer the key question
Decide if /prepare-pr can fix issues or the contributor must update the PR.
13. Save findings to the worktree
Write the full structured review sections A through J to `.local/review.md`.
Create or overwrite the file and verify it exists and is non-empty.
```sh
ls -la .local/review.md
wc -l .local/review.md
```
14. Output the structured review
Produce a review that matches what you saved to `.local/review.md`.
A) TL;DR recommendation
- One of: READY FOR /prepare-pr | NEEDS WORK | NEEDS DISCUSSION | NOT USEFUL (CLOSE)
- 1 to 3 sentences.
B) What changed
C) What is good
D) Security findings
E) Concerns or questions (actionable)
- Numbered list.
- Mark each item as BLOCKER, IMPORTANT, or NIT.
- For each, point to file or area and propose a concrete fix.
F) Tests
G) Docs status
- State if related docs are up to date, missing, or not applicable.
H) Changelog
- State if `CHANGELOG.md` needs an entry and which category.
I) Follow ups (optional)
J) Suggested PR comment (optional)
## Guardrails
- Worktree only.
- Do not delete the worktree after review.
- Review only, do not merge, do not push.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "Review PR"
short_description: "Review GitHub PRs without merging"
default_prompt: "Use $review-pr to perform a thorough, review-only GitHub PR review."