Analyze a PR's changes and post concise guiding comments to help reviewers understand context, decisions, and complexity.
Analyze a pull request's diff, identify areas where a reviewer would benefit from author-provided context, draft concise guiding comments, and post them as individual review comments on the PR. The goal is to preemptively answer "why?" questions and steer reviewer attention — not to narrate the diff.
gh pr view --json number,url,title,headRefName,baseRefName,headRefOid
owner and repo from the repo context:
gh repo view --json owner,name --jq '"\(.owner.login)/\(.name)"'
headRefOid (HEAD commit SHA) — this is needed for posting comments.gh pr diff $PR_NUMBER
gh pr view $PR_NUMBER --json files,commits --jq '.files[].path'
git log $(git merge-base HEAD $BASE_BRANCH)..HEAD --oneline
For each changed file, read the actual current file contents — not just the diff hunks. Annotations require understanding the surrounding code, not just the changed lines.
Focus reading effort on files with non-trivial changes. Skip lockfiles, generated code, and files where the diff is purely mechanical (formatting, renames with no logic changes).
Analyze the diff and file context to identify locations where a reviewer comment from the author would genuinely help. Look for:
Aim for quality over quantity. A PR with 3 well-placed annotations is better than one with 12 that mostly state the obvious.
First, output a summary header:
## PR #<number>: <title>
<url>
Then use the Question tool to let the user select which annotations to draft. Use a single question with multiple: true so the user can pick any combination. Each option should have:
#<N> <file>:<line> (short, fits in the selection UI)Example Question tool call:
{
"questions": [
{
"header": "Select annotations",
"question": "Which annotations should I draft?",
"multiple": true,
"options": [
{
"label": "#1 foo.ts:42",
"description": "Why explore-level filters are applied as a separate step before local filters"
},
{
"label": "#2 bar.ts:15-20",
"description": "Why filtered-out IDs are sorted and compared by value rather than using Set equality"
}
]
}
]
}
The user can select any subset, or type a custom answer. If the user selects nothing (empty response), treat it as "none" and stop.
Do NOT draft or post anything without selection.
Before drafting any annotations, run tone-clone generate to sample the user's real writing:
tone-clone generate --stdout --type issue_comment --limit 5
Study the output for: sentence length, punctuation patterns, capitalization, level of formality, use of contractions, how links and code are referenced. All drafted annotations must match these patterns.
If tone-clone is not available or returns no results, fall back to the rules and examples in the voice and tone guide.
For each annotation the user selected, draft a comment and present it for approval. Process them one at a time — do not batch.
All posted annotation text must follow the voice and tone guide. Additionally:
Present each draft using the embedded ascii-art format. Show the relevant code with line numbers and the draft comment in a box below the target line(s), using the "draft annotation" variant.
Output the header, then the ascii-art block:
### Annotation #<N>: `<file>:<line>`
Followed by the ascii-art code+comment block (see the shared format spec for the full visual specification and examples).
Then immediately use the Question tool to get the user's decision:
{
"questions": [
{
"header": "Annotation #<N>",
"question": "Post this comment to the PR?",
"options": [
{ "label": "Post", "description": "Approve and queue for posting" },
{ "label": "Skip", "description": "Do not post this annotation" }
]
}
]
}
The Question tool's built-in custom answer option allows the user to type edited comment text instead of selecting Post/Skip. If the user types a custom answer, treat it as the revised comment text — re-present the updated draft and ask again.
Wait for the user's response on each draft before moving to the next:
After all per-item gates are complete, present the queued annotations and require a final submit gate.
Use the Question tool:
{
"questions": [
{
"header": "Post annotations",
"question": "Post these approved annotations now?",
"options": [
{
"label": "Post queued",
"description": "Post exactly the queued annotations"
},
{
"label": "Cancel",
"description": "Do not post anything"
}
]
}
]
}
Only a direct user Question response of Post queued in this run authorizes Step 6.
Execution mode for this run:
draft_only.execute_enabled only after the Step 5 execution checkpoint returns Post queued.execute_enabled to the exact queued annotation payload. Any payload change resets the mode to draft_only.After all drafts have been reviewed, post each approved comment to the PR.
Before posting, run a strict preflight:
execute_enabled for this exact payload.If any check fails, return draft-only output with DRAFT_ONLY_BLOCKED and stop.
The line parameter must reference a line in the right side of the diff that is part of the PR's changes. Before posting, verify the target line appears in the diff output from Step 2. If a proposed line is not in the diff, find the nearest diff line in the same hunk or skip the annotation.
Use the GitHub REST API to post individual review comments:
Single-line comment:
gh api repos/{owner}/{repo}/pulls/{number}/comments \
-f body="$BODY" \
-f commit_id="$HEAD_SHA" \
-f path="$FILE_PATH" \
-F line=$LINE_NUMBER \
-f side="RIGHT"
Multi-line comment (spanning a range):
gh api repos/{owner}/{repo}/pulls/{number}/comments \
-f body="$BODY" \
-f commit_id="$HEAD_SHA" \
-f path="$FILE_PATH" \
-F start_line=$START_LINE \
-F line=$END_LINE \
-f start_side="RIGHT" \
-f side="RIGHT"
After each post, confirm success and show the URL of the posted comment.
If a post fails (e.g., the line is not part of the diff), report the error and move on to the next comment. Do not retry unless the user asks.
After all comments have been processed, output a final summary:
## Annotations Posted (<count>)
1. `src/foo.ts:42` — <brief description> — <url>
2. `src/bar.ts:15` — <brief description> — <url>
## Skipped (<count>)
- #3 `src/baz.ts:99` — skipped by user
- #5 `src/qux.ts:12` — not in diff range
draft_only and must remain non-mutating until the Step 5 execution checkpoint is completed.Post queued for the exact queued payload.commit_id for posting must be the headRefOid from the PR metadata. Using a stale SHA will cause the comment to show as outdated.