Files
Gordon Lam d47a6c93b7 Add copilot-pr-autopilot skill (#1944)
* Add copilot-pr-autopilot skill

Skill that drives any GitHub pull request through repeated rounds of
Copilot Code Review until the agent has either resolved every thread
or explicitly escalated it to the human. Triggered via GraphQL (no
@copilot mention needed), triages every open thread with a fix /
decline / escalate rubric, replies and resolves each thread citing
the pushed SHA, then re-triggers until HEAD is reviewed with zero
threads awaiting the agent's reply.

Includes step scripts (01 request-review, 02 check-review-status,
03 list-open-threads, 08 reply-and-resolve, 10 cleanup-outdated),
shared library (_lib.ps1) with gh-CLI wrappers (Invoke-Gh,
Invoke-GhGraphQL, ConvertFrom-GhJson, Assert-GhReady), reply
templates, and reference docs for each step.

Repo-agnostic. Requires gh CLI on PATH and repo Triage/Write for
full autopilot; external PR authors get single-iteration mode with
manual re-trigger via the UI re-request button or a substantive
push.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address review: split per-step references + add recap-gate circuit breaker

- Fix #1: give steps 1/7/10 their own reference files
  (01-request-review.md, 07-commit-push.md, 10-cleanup.md); trim the
  inline bodies out of orchestration.md so it stays cross-cutting only.
- Fix #3: add a recurring round-cap & recap gate to 09-convergence.md —
  default STOP every 10th round, recap all prior rounds, detect drift
  (out-of-scope / over-engineering / wrong-direction / belongs-in-separate-PR)
  with CONTINUE / REVERT-AND-SHIP / HAND-OFF verdicts. Agent reasoning,
  no new script.
- Surface the gate from SKILL.md and orchestration.md; regenerate
  docs/README.skills.md. Markdown-only change; scripts unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(copilot-pr-autopilot): surface recap gate in decision pseudo-code; clarify Copilot+human convergence and round definition

- Inject the round-cap recap gate into the '## Decision: loop back or exit'
  pseudo-code else-branch so an agent following the code block (not just the
  prose) runs the STOP-every-10th-round check before looping.
- Broaden the 'never terminal' paragraph: non-convergence is driven by a
  Copilot finding OR a human review comment (this skill handles both); the
  loop ends only when there are no new comments from either source AND every
  open thread (Copilot or human) has an agent reply/escalation.
- Define a 'round' explicitly as one execution of step 1 (01-request-review),
  i.e. one Copilot-review trigger — the cap counts review rounds, not tool
  calls or fix edits.

Markdown-only; no script changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* copilot-pr-autopilot: make recap-gate round count deterministic

Add 09-review-round.ps1: counts Copilot Code Review submissions straight
from the PR's API history (full GraphQL pagination), so the recap-gate
trigger is a derived number, not a fallible agent mental tally. This
removes the exact failure mode the skill exists to survive — a count
drifting across a long run (the real 156-round case).

The script reports Round + RecapDue (Round % RecapInterval == 0) only;
it never stops the loop or picks the verdict. CONTINUE / REVERT-AND-SHIP
/ HAND-OFF stays agent reasoning. 09-convergence.md updated to reference
the deterministic count while preserving 'no script stops the loop' and
'non-convergence = Copilot finding OR human comment'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Regenerate docs/README.skills.md for copilot-pr-autopilot (add 09-review-round.ps1)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-01 11:09:58 +10:00

98 lines
3.3 KiB
PowerShell

<#
.SYNOPSIS
Post a reply on a Copilot review thread and resolve it.
.DESCRIPTION
Performs the two GraphQL mutations needed to address a Copilot finding:
1. addPullRequestReviewThreadReply — appends a reply comment.
2. resolveReviewThread — marks the thread resolved.
Use this for both accepted-and-fixed findings and for declined-with-
rationale findings. See ../templates/reply-fix.md, reply-decline.md,
reply-drift.md, and reply-partial.md for body patterns.
.PARAMETER ThreadId
The GraphQL node ID of the review thread (e.g. PRRT_kw...).
.PARAMETER Body
The reply body. Markdown is supported.
.PARAMETER NoResolve
If set, posts the reply only and leaves the thread open. Useful when
you want to start a back-and-forth discussion rather than close out the
thread.
.EXAMPLE
pwsh 08-reply-and-resolve.ps1 -ThreadId PRRT_kw... -Body "Fixed in abc1234."
.EXAMPLE
# Decline with rationale, do not resolve yet
pwsh 08-reply-and-resolve.ps1 -ThreadId PRRT_kw... -NoResolve `
-Body "Declining: this would require cross-class plumbing for a hypothetical race."
.NOTES
Reply + resolve are TWO separate GraphQL mutations and the script
is NOT atomic: if the reply succeeds and the resolve then fails
(transient 5xx, rate limit, thread disappeared mid-call), the reply
is already posted. The script is also NOT idempotent on reply —
re-running it with the same -ThreadId and -Body will post a
duplicate reply because -Body is mandatory.
Recommended retry policy when a call fails:
- If "Replied to thread X" did NOT print, the reply step failed;
safe to re-run the whole command.
- If "Replied to thread X" DID print but "Resolved thread X" did
not, ONLY the resolve step failed. Do NOT re-run this script
(it would post a duplicate reply). Instead resolve directly:
gh api graphql `
-f query='mutation($t:ID!){resolveReviewThread(input:{threadId:$t}){thread{id}}}' `
-f t=$ThreadId
This raw call bypasses the Invoke-Gh helper but is safe here
because the mutation contains no embedded double-quote
characters, so the PowerShell-5.1 native-arg splitting bug
documented in references/api-quirks.md does not apply.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$ThreadId,
[Parameter(Mandatory = $true)]
[string]$Body,
[switch]$NoResolve
)
$ErrorActionPreference = 'Stop'
. "$PSScriptRoot/_lib.ps1"
$replyMutation = @'
mutation($tid: ID!, $body: String!) {
addPullRequestReviewThreadReply(input: {
pullRequestReviewThreadId: $tid,
body: $body
}) {
comment { id }
}
}
'@
$replyArgs = @('-f', "query=$replyMutation", '-f', "tid=$ThreadId", '-f', "body=$Body")
Invoke-GhGraphQL -GhArgs $replyArgs -Context "reply to thread $ThreadId" | Out-Null
Write-Output "Replied to thread $ThreadId"
if (-not $NoResolve) {
$resolveMutation = @'
mutation($tid: ID!) {
resolveReviewThread(input: { threadId: $tid }) {
thread { isResolved }
}
}
'@
$resolveArgs = @('-f', "query=$resolveMutation", '-f', "tid=$ThreadId")
Invoke-GhGraphQL -GhArgs $resolveArgs -Context "resolve thread $ThreadId" | Out-Null
Write-Output "Resolved thread $ThreadId"
}