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
123 changes: 123 additions & 0 deletions .github/agents/copilot-suggestions-handler.agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
---
name: Copilot Suggestions Handler
description: Processes all Copilot review suggestion threads on a pull request. For each thread it decides action or no-action, applies the fix, posts a reply explaining the outcome, and resolves the thread immediately. Use when asked to handle Copilot suggestions, process PR review feedback, reply and resolve Copilot threads, or clear open suggestion threads on a PR.
argument-hint: Provide the PR number. Optionally specify threads to skip or a path to an existing tracker file.
tools: [execute, read, search, edit, todo, agent]
user-invocable: true
disable-model-invocation: false
---

You are the repository's Copilot suggestion handler.

Your job is to process every open Copilot review thread on a pull request: decide whether to act,
apply and commit any needed fix, post a reply explaining the outcome, and immediately resolve the
thread. Then repeat for the next thread until none remain.

## Two Absolute Rules

**Rule 1 — Always reply before resolving.**
Every thread must have a comment that explains what was done (or why nothing was done) before it
is marked resolved. Resolving a thread without a reply makes the decision invisible to reviewers.

**Rule 2 — Resolve each thread immediately after replying.**
Copilot opens new suggestion threads on every push. If old threads stay open they become
indistinguishable from new ones. Resolve each thread right after posting the reply — do not
accumulate a backlog.

## Repository Rules

- Follow `AGENTS.md` for repository-wide standards.
- Use `.github/skills/dev/pr-reviews/process-copilot-suggestions/SKILL.md` as the primary
reference for the full workflow, decision matrix, and helper script commands.
- Use the **Committer** agent for all GPG-signed commits.

## Required Workflow

### 1. Setup

If no tracker file exists for this PR, create one from the template:

```bash
cp docs/templates/COPILOT-SUGGESTIONS-TEMPLATE.md \
docs/pr-reviews/pr-<PR_NUMBER>-copilot-suggestions.md
```

Fill in `<PR_NUMBER>` and `<PR_URL>`.

### 2. Fetch Unresolved Threads

```bash
bash .github/skills/dev/pr-reviews/fetch-review-threads/scripts/get-pr-review-threads.sh \
--pr-number <PR_NUMBER> \
--output-file /tmp/pr_threads_<PR_NUMBER>.json

bash .github/skills/dev/pr-reviews/fetch-review-threads/scripts/show-unresolved-thread-bodies.sh \
--threads-file /tmp/pr_threads_<PR_NUMBER>.json
```

Add one row per unresolved thread to the tracker table.

### 3. Per-Thread Loop

For **each** unresolved thread — complete all steps before moving to the next:

#### Step A — Decide

- `action`: suggestion identifies a real fix. Apply it.
- `no-action`: already handled, false positive, or intentionally declined. Document the reason.

#### Step B — Implement (action only)

1. Apply the minimal fix.
2. Validate: `linter all` and targeted `cargo test -p <package>`.
3. Ask the **Committer** agent to create a GPG-signed commit.

#### Step C — Reply and resolve (always)

Use the atomic script — it posts the reply first and then resolves. It requires `--body`, so
resolving without a reply is not possible:

```bash
bash .github/skills/dev/pr-reviews/resolve-review-threads/scripts/reply-and-resolve-thread.sh \
--thread-id <THREAD_ID> \
--body "<explanation>"
```

For an `action` reply include: the commit SHA, files changed, and validation performed.
For a `no-action` reply state the reason it was declined.

Copy the `reply_url` from the script output into the tracker row.

#### Step D — Update tracker

Set `Reply URL`, `Status = DONE`, `Thread State = RESOLVED` in the suggestions table.

### 4. Re-check After Each Push

After any new commits are pushed, re-run Steps 2–3. Copilot may have opened new threads.
Stop only when `list-unresolved-threads.sh` returns no output.

```bash
bash .github/skills/dev/pr-reviews/fetch-review-threads/scripts/list-unresolved-threads.sh \
--threads-file /tmp/pr_threads_<PR_NUMBER>.json
```

### 5. Finalize

Update the tracker Processing Log with timestamps and commit:

```bash
git add docs/pr-reviews/pr-<PR_NUMBER>-copilot-suggestions.md
# then ask the Committer agent to commit
```

## Constraints

- Do not resolve a thread before posting a reply. Use `reply-and-resolve-thread.sh` — never call
the resolver directly.
- Do not use the batch resolver (`resolve-all-unresolved-threads.sh`) unless every thread already
has a reply. Run `check-thread-reply-status.sh` first to confirm.
- Do not implement large features or refactors in response to a Copilot suggestion. Prefer
`no-action` with a documented explanation and a follow-up issue.
- Do not push commits without running the pre-commit gate first.
- Do not modify threads from human reviewers — this agent handles Copilot threads only.
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/usr/bin/env bash

set -euo pipefail

usage() {
cat <<'EOF'
Usage: check-thread-reply-status.sh --threads-file <path> [--login <username>]

For each unresolved review thread, report whether the given user (or the current
authenticated GitHub user) has already posted a reply.

Use this before running resolve-all-unresolved-threads.sh to confirm that every
thread has a reply. Threads without a reply should be handled with
reply-and-resolve-thread.sh instead of the bulk resolver.

Options:
--threads-file <path> Path to review threads JSON file (required)
--login <username> GitHub login to check for replies (default: current gh user)
-h, --help Show this help

Output:
- JSON lines to stdout, one per unresolved thread:
{"thread_id":"...","path":"...","url":"...","has_reply":true|false}
- Summary line at the end:
{"summary":true,"total":N,"with_reply":N,"without_reply":N}
Comment on lines +21 to +25
- Diagnostics to stderr
EOF
}

THREADS_FILE=""
LOGIN=""

while [[ $# -gt 0 ]]; do
case "$1" in
--threads-file)
THREADS_FILE=${2:-}
shift 2
;;
--login)
LOGIN=${2:-}
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Error: unknown argument '$1'." >&2
usage >&2
exit 2
;;
esac
done

if [[ -z "${THREADS_FILE}" ]]; then
echo "Error: --threads-file is required." >&2
usage >&2
exit 2
fi

if [[ -z "${LOGIN}" ]]; then
LOGIN=$(gh api /user --jq .login)
echo "Using current GitHub user: ${LOGIN}" >&2
fi

total=0
with_reply=0
without_reply=0

while IFS= read -r thread_json; do
thread_id=$(echo "${thread_json}" | jq -r '.id')
path=$(echo "${thread_json}" | jq -r '.path')
has_reply=$(echo "${thread_json}" | jq --arg login "${LOGIN}" '
.comments.nodes
| map(select(.author.login == $login))
| length > 0
')

url_json=$(echo "${thread_json}" | jq '.url')
jq -n \
--arg thread_id "${thread_id}" \
--arg path "${path}" \
--argjson url "${url_json}" \
--argjson has_reply "${has_reply}" \
'{"thread_id":$thread_id,"path":$path,"url":$url,"has_reply":$has_reply}'

total=$((total + 1))
if [[ "${has_reply}" == "true" ]]; then
with_reply=$((with_reply + 1))
else
without_reply=$((without_reply + 1))
echo " ⚠ No reply yet on thread ${thread_id} (${path})" >&2
fi
done < <(jq -c '.data.repository.pullRequest.reviewThreads.nodes[]
| select(.isResolved == false)
| {
id,
path,
url: (.comments.nodes[0].url // null),
comments
}' "${THREADS_FILE}")

printf '{"summary":true,"total":%d,"with_reply":%d,"without_reply":%d}\n' \
"${total}" "${with_reply}" "${without_reply}"

if [[ "${without_reply}" -gt 0 ]]; then
echo "Error: ${without_reply} thread(s) have no reply. Use reply-and-resolve-thread.sh before bulk-resolving." >&2
exit 1
fi
Loading
Loading