Skip to main content

process-pr-review

Process every pull-request review finding, regardless of whether it was authored by Copilot, a person, or another bot. Use when asked to process PR review feedback, resolve review threads, audit review comments, address Copilot and maintainer findings together, triage review feedback submitted after merge, or handle a review left on a closed pull request that another pull request replaced.

Source facts

Repository
torrust/torrust-tracker
Last source activity
October 7, 2026 at 10:59
Detected SKILL.md language
English
Stars
532
Forks
56

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
process-pr-review
description
Process every pull-request review finding, regardless of whether it was authored by Copilot, a person, or another bot. Use when asked to process PR review feedback, resolve review threads, audit review comments, address Copilot and maintainer findings together, triage review feedback submitted after merge, or handle a review left on a closed pull request that another pull request replaced.
metadata
{"author":"torrust","version":"1.5","semantic-links":{"related-artifacts":["docs/issues/closed/2219-2003-unify-pr-review-processing/ISSUE.md","docs/templates/PR-REVIEW-TEMPLATE.md","docs/templates/REVIEW-FINDINGS.md",".github/skills/dev/pr-reviews/fetch-review-threads/SKILL.md",".github/skills/dev/pr-reviews/resolve-review-threads/SKILL.md",".github/skills/dev/pr-reviews/process-pr-review/scripts/validate-audit-record.py"]}}
# Processing Pull-Request Reviews The PR author owns one tracked audit record at `docs/pr-reviews/pr-<PR_NUMBER>-review/PR-REVIEW.md`. Reviewers, including repository review agents, deliver findings through GitHub only and create no repository artifact. Process all authors through this workflow; author classification adds context but never changes the audit or resolution requirements. ## Purpose Process every review finding correctly, efficiently, traceably, and deterministically: handle every actionable suggestion, reply to every resolvable thread, and record each finding's progress through resolution. The normalized audit data also supports continuous improvement: identify recurring errors, review-process patterns, and automation or guardrail candidates so common feedback is prevented earlier and future review processing consumes fewer tokens. ## Prerequisites - Target pull request number and permission to push its branch and reply to its review threads. - GitHub CLI (`gh`) and the `fetch-review-threads` and `resolve-review-threads` helper skills. - Ability to run focused validation for any proposed fix. ## Workflow 1. **Fetch the source of truth.** Use `fetch-review-threads`' GitHub CLI-backed Rust tool to collect all threads, including resolved and outdated threads. Also fetch each submitted review and its review-specific comments by review ID. REST comment responses are supplementary: GraphQL thread data is authoritative for thread identity and resolution state. 2. **Classify and normalize.** Classify `github-copilot[bot]` and `copilot-pull-request-reviewer` as `Copilot`, human accounts as `Human`, and every other bot or unavailable author as `Unknown`. Split each review body into one row per independently actionable assertion; do not make a row for a summary or verdict that contains no request. Preserve the source review ID and URL. Treat Copilot's collapsed `Suppressed comments` as findings only when their full content is retrievable and contains an independently actionable assertion. Otherwise record no row and retain the review summary only as non-actionable context. 3. **Assign and deduplicate findings.** Use a reviewer-provided finding ID when present and it does not collide with an existing audit finding ID. When a later review reuses an existing ID for a different finding, assign the next audit-local `F<ordinal>` and record the reviewer's original ID in the detail entry. Otherwise assign `F<ordinal>` in source-review and source-order order. Assign the immutable repository reference `review-finding:pr-<PR_NUMBER>-<FINDING_ID>`, lowercasing the finding ID in the reference. Before action, compare a new item with all earlier findings against the current tree. A later item requesting the same current-tree change is a re-raise: keep its source row and record `RE_RAISE_OF:<FindingId>`. Every re-raise has its own tracking row and detail entry; never collapse it into the original finding. 4. **Categorize for future analysis.** For every new audit row, assign exactly one primary category: `link-integrity`, `formatting`, `metadata`, `testing`, `correctness`, `documentation`, `maintainability`, `security`, or `other`. Categorize the concern rather than its proposed fix; use `other` only when no listed category fits. Do not backfill or reinterpret historical audit records. 5. **Decide against the current tree.** Re-derive every reply claim from the current tree with file inspection or a focused command. Record one of `FIXED`, `NO_ACTION`, `SUPERSEDED`, or `FOLLOW_UP`, the verification performed, and a concise independent summary. Never close a row with an undocumented disposition. If a Processing Log was rewritten in place, restore its prior entries, append a correction that names the rewritten content, and do not rewrite the record again. 6. **Implement and validate fixes.** Make each independent fix in its own GPG signed Conventional Commit after focused validation. Cite the unique commit subject, not a branch SHA, because the branch can be rebased. 7. **Reply before resolving.** For each resolvable inline thread, reply with the disposition, current-tree verification, and resolution reference, then use `resolve-review-threads` to resolve it. If a code or documentation change fixed the concern, record `Disposition=FIXED` and `Thread state=RESOLVED` even when GitHub marks the original thread outdated after the push. For a duplicate, superseded, or no-change thread, reply exactly `Superseded by <FindingId>: <reason>.`, record `Disposition=NO_ACTION` and `Thread state=SUPERSEDED`, then resolve it. In a post-merge review, record `NO_ACTION` only when the maintainer has approved declining the follow-up work. 8. **Consolidate review bodies.** A PR conversation response may cover multiple review rounds only when it names every review ID and every finding ID with its disposition and resolution reference. Store its durable URL in each related row. 9. **Verify completion.** Run `scripts/validate-audit-record.py` (see [Validation Script](#validation-script)) and fix every reported failure. Refresh GraphQL thread data and show no unresolved actionable thread. Update the audit progressively and commit it separately from product fixes. ## Reviews Submitted After Merge A merged pull request is immutable delivery history. Review feedback submitted after merge is not authorization to create a branch, modify `develop`, or open a follow-up pull request. Feedback submitted before merge but not yet replied to, resolved, or audited when the pull request merged follows the same workflow: the merged branch can no longer carry its fixes or audit record. Record both the review submission time and the merge time during triage. When a review body, inline thread, or comment arrives after the pull request merged, or was still unprocessed when it merged: 1. **Stop after read-only triage.** Verify the merge time, review submission time, current target branch, thread state, and whether each finding remains live on the current target branch. Do not edit, branch, commit, push, reply, or resolve yet. 2. **Ask the maintainer.** Report the post-merge workflow gap and propose explicit dispositions: no action, audit-only recording, an existing issue/branch handoff, or a new follow-up branch and pull request. Obtain approval before choosing one. A pre-merge review-processing request does not authorize post-merge remediation. Record approval as a durable GitHub issue or pull-request comment URL in the original audit before any mutating action; chat-only approval is insufficient as the long-term evidence for this gate. When this rule is introduced after an approved action has already started, a retrospective approval record may be used only if it states the original approval time and the durable comment's creation time. When the merged PR has no audit, the durable comment alone satisfies this gate until the follow-up creates the audit (step 3). 3. **Preserve the original audit.** Normalize every late finding into the merged PR's existing audit, including independently actionable review-body assertions. Use collision-safe audit IDs and retain reviewer-provided IDs in detail entries when reassigned. When the merged PR has no audit, the follow-up branch creates `docs/pr-reviews/pr-<PR_NUMBER>-review/PR-REVIEW.md` with the approval comment URL in its Ownership section. 4. **Implement only the approved follow-up.** Branch from the latest target branch, not from the merged PR head. Keep independent fixes and audit updates in coherent signed commits. Link the follow-up PR to the merged PR and to the issue that owns the remaining work; use issue-closing keywords only when the follow-up fully resolves that issue. 5. **Reply with follow-up evidence.** Reply on the merged PR's resolvable threads with the approved disposition and follow-up PR or merged-commit reference. Do not claim `FIXED` on the target branch before the follow-up merges; record `Disposition=FOLLOW_UP` and `Thread state=OPEN` while it remains open. A review-body finding without a thread remains `NON_RESOLVABLE`. 6. **Close the loop after merge.** After the follow-up merges, update the original audit with the durable merge reference, change completed dispositions to `FIXED`, reply if needed, then resolve addressed threads. Refresh GraphQL and record zero unresolved actionable threads. If the maintainer declines follow-up work, record `NO_ACTION` with the reason only when the maintainer approved that audit update. Do not silently turn a late review into a new project. ## Reviews on a Closed, Superseded Pull Request A pull request can be closed without merging and replaced by another that carries the same changes, for example after its source branch is renamed. A review can still arrive on the closed pull request, or be left unprocessed when it closed. The closed pull request cannot carry fixes or an audit, so its findings are handled on the replacement: 1. **Triage read-only first.** Record the close time, each review's submission time, and the replacement pull request. Check every finding against the replacement's current tree. 2. **Fix on the replacement branch.** Use the normal pre-merge workflow: one signed commit per independent fix. 3. **Audit in the replacement's record.** Add the closed pull request's findings to `docs/pr-reviews/pr-<REPLACEMENT>-review/PR-REVIEW.md`, alongside the replacement's own findings: - Set each detail entry's `PR number` to the pull request where the finding was raised. - Build the reference from that number too, for example `review-finding:pr-<CLOSED>-f1`. - Keep finding IDs unique across the whole audit, assigning new IDs on collision as usual. - Say in the `## Findings` introduction which rows came from the closed pull request and why. 4. **Validate with both comment sets.** The validation script reads review comments for one pull request. Concatenate both pull requests' comments and pass them with `--comments-file`; the script accepts several concatenated JSON arrays: ```bash { gh api repos/torrust/torrust-tracker/pulls/<CLOSED>/comments --paginate gh api repos/torrust/torrust-tracker/pulls/<REPLACEMENT>/comments --paginate } > .tmp/pr-<REPLACEMENT>-review-comments.json python3 .github/skills/dev/pr-reviews/process-pr-review/scripts/validate-audit-record.py \ --pr-number <REPLACEMENT> --comments-file .tmp/pr-<REPLACEMENT>-review-comments.json ``` 5. **Reply and resolve on the closed pull request.** Reply on each of its threads with the disposition, the fixing commit subject, and a link to the replacement pull request, then resolve the thread. A finding that no longer applies to the replacement tree is `NO_ACTION`/`SUPERSEDED` with the prescribed `Superseded by` reply. ## Required Audit Fields Every new normalized finding has the following 19 fields. This list is the canonical roster; the skeleton in `PR-REVIEW-TEMPLATE.md` must match its field names and order exactly. ### Tracking Row Fields - Finding ID - Review finding reference - Author class - Severity - Category - Relationship - Disposition - Thread state ### Detail-Entry Heading Field - Summary ### Detail-Entry Fields - PR number - Source review ID - Reviewer finding ID - Source URL - Concern - Solution - Current-tree verification - Resolution reference - Follow-up PR URL - Reply URL Record a post-merge approval URL once in the audit's Ownership section, rather than repeating it in each detail entry. `Reviewer finding ID` is optional: record the original ID when reassigned; otherwise use `N/A`. Record each finding as one compact tracking row plus one matching detail entry carrying the remaining narrative and source-metadata fields, as laid out in the audit template. The immutable repository reference is `review-finding:pr-<PR_NUMBER>-<FINDING_ID>`, with a lowercase finding ID; use it when another repository artifact needs to cite the finding. GitHub identifiers remain source metadata, not the canonical finding reference. Never change the reference after assigning it. Historical audit records remain unchanged and do not need review-finding references. Author class is `Copilot`, `Human`, or `Unknown`; category is `link-integrity`, `formatting`, `metadata`, `testing`, `correctness`, `documentation`, `maintainability`, `security`, or `other`. Severity is `Blocker`, `Major`, `Minor`, `Nit`, or `Suggestion`; mark a severity inferred from free prose as inferred. `FIXED` resolution references are unique Conventional Commit subjects; when the fix lives outside the repository tree (for example a PR or issue body edit), use the durable URL of the PR conversation response that states it. `NO_ACTION`, `SUPERSEDED`, and `FOLLOW_UP` resolution references are durable reply URLs. Never use a branch SHA. Record a follow-up pull request only in the separate Follow-up PR URL field, using `N/A` when it does not apply. Historical records remain valid without the analysis fields. ## Validation Script `scripts/validate-audit-record.py` mechanically checks the audit record against GitHub review comments and branch history. Run it before every audit commit and before replying to or resolving any thread: ```bash python3 .github/skills/dev/pr-reviews/process-pr-review/scripts/validate-audit-record.py \ --pr-number <PR_NUMBER> ``` It fetches review comments with `gh` unless `--comments-file` is given, and compares commit subjects against `<base>..HEAD` (`--base` defaults to `develop`). It checks every row whose finding ID is audit-local (`F1`) or reviewer-provided (`OPS-001`: uppercase letters and digits in hyphen-separated groups). It fails when: - the `## Findings` section is missing, or one of its table data rows does not parse as a tracking row (for example a padded or lowercase finding ID); - a tracking row has no detail entry, or a detail entry has no tracking row; - a row's `Source review ID` does not match the review that owns its `Source URL`; - a discussion-anchored row cites zero or several reply URLs, cites a reply that does not exist, or cites a reply posted on a different thread than its source comment; - a `Resolution reference` names a commit subject that is not on the branch; - a row's `Severity` differs from the `[Severity]` bracket of its source comment; or - Processing Log entries are not in chronological order. Every failure is a claim in the merged record that the bytes contradict, so treat a non-zero exit as blocking. The script does not verify prose claims such as `Current-tree verification` sentences, nor that a log stamp is later than the commit that carries it; check those by hand. The script is a Python prototype. Like the other Python developer tools in this repository, it is intended to be migrated to Rust; keep its behaviour as the reference when doing so. ## Advisory Reviewer Finding Format The [review finding template](../../../../../docs/templates/REVIEW-FINDINGS.md) is advisory: never reject a review for omitting it. For a formatted inline finding, require the first line `[<Severity>][<FindingId>] <summary>`, with severity limited to `Blocker`, `Major`, `Minor`, `Nit`, or `Suggestion`. Each independently actionable finding gets its own inline thread. A re-raised finding uses the original finding ID and states the re-raise in its body. Review bodies contain only the round verdict or summary; do not duplicate detailed inline findings there. ## Retiring or Replacing Review Workflow Documents When a PR retires, renames, replaces, or supersedes a review-workflow document, do not rely on file movement alone as evidence that the workflow contract survived. Before resolving the review finding, record an inventory of every normative rule in the retiring artifact. For each rule, record one of: - `PRESERVED`: the destination document and section that now owns the rule; - `DROPPED`: the explicit reason the rule is no longer part of the workflow contract. Resolve the finding only after the inventory is checked against the current tree and any preserved rule is present in its named destination. Keep historical audit records unchanged; the inventory guards live workflow behavior, not archival prose. ## Rename Migration Verification When a review finding concerns a rename-only or move-with-limited-edits migration, verify the rename mechanically before accepting the migration as pure. The verification record must name: - the selected merge base or comparison commit; - the old repository path; - the new repository path; - the reviewed expected zero-context patch for that file. Compare the actual zero-context patch with the reviewed expectation and fail the verification when they differ. The approved patch may be empty only when the actual patch is also empty. Use a failure-propagating comparison such as: ```sh actual_patch=$(git diff -U0 "<base>:<old-path>" "HEAD:<new-path>") test "$actual_patch" = "$(cat "<approved-zero-context-patch>")" ``` Record the command, base, paths, expected-patch artifact or inline expectation, and result in the audit detail before resolving the finding. ## Completion Checklist - [ ] GraphQL data collected for all review threads - [ ] Review bodies split into independent findings - [ ] Re-raises mapped to their original finding before action - [ ] Every action verified against the current tree, validated, and committed - [ ] `scripts/validate-audit-record.py` exits `0` against the committed audit - [ ] Every resolvable thread replied to before resolution - [ ] Every superseded thread has the prescribed reply and audit state - [ ] Consolidated responses that cover multiple review rounds name every covered review and finding ID with its disposition and resolution reference, with the durable response URL in each related row - [ ] Final GraphQL fetch reports no unresolved actionable thread - [ ] Audit committed separately from product fixes - [ ] For feedback submitted after merge or unprocessed at merge: maintainer approval recorded before any mutating action - [ ] For an approved post-merge follow-up: branch based on the current target branch and original audit (or the audit the follow-up created) updated through follow-up merge - [ ] For a review on a closed, superseded pull request: fixes and audit on the replacement pull request, and every closed-pull-request thread replied to and resolved
View on GitHub