[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6MQVSNFCR4G0482KFHRRTJ2":3,"skill-same:01M6MQVSNFCR4G0482KFHRRTJ2":70},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":39,"linkOnly":40,"markdownContent":41,"markdownBody":42,"frontMatter":43,"refName":27,"commitSha":44,"skillDirectory":45,"forksCount":46,"repositoryHtmlUrl":47,"repositoryHomepageUrl":6,"ownerHtmlUrl":48,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":49,"install":50,"resources":63},"01M6MQVSNFCR4G0482KFHRRTJ2","pr-review-loop",null,"Use when a PR against main is open, updated, or failing — the mechanics of running its review loop. Covers reading a Copilot review without missing the suppressed bucket, the API traps that silently report a clean round, the separate-agent substitute when Copilot does not review, and the pointer to delegate-routing for the review rule. Load it when you are actually operating the loop; the trigger and the decision rules are always-on in the repository operating policy.","higherorderfunctor\u002Fnix-agentic-tools","higherorderfunctor","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F34926636?v=4","01M6MQT3TQS16N1CBRZJYR2EMB","Christopher Aubut","01M6MQT419AF9ERC87M87NRXQV",2,"Unlicense","https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools\u002Fblob\u002Fmain\u002Fdev\u002Fskills\u002Fpr-review-loop\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"pr review","loop","review","pr review loop","review loop","pr","failing","running","main","mechanics","open","clean","missing","copilot","decision","policy","delegate","trigger","for","operating","2026-11-10 10:12:30",false,"---\nname: pr-review-loop\ndescription: >-\n  Use when a PR against main is open, updated, or failing — the mechanics of\n  running its review loop. Covers reading a Copilot review without missing the\n  suppressed bucket, the API traps that silently report a clean round, the\n  separate-agent substitute when Copilot does not review, and the pointer to\n  delegate-routing for the review rule. Load it when you are actually operating\n  the loop; the trigger and the decision rules are always-on in the repository\n  operating policy.\n---\n\n# PR review loop — mechanics\n\nThe operating policy owns WHEN to do these things and WHOSE job it is. This\nskill owns HOW, because the how is a pile of API traps that each produce a\nconfident wrong answer, and none of it is needed until a PR actually exists.\n\n## Reading a Copilot review\n\nCopilot writes findings in TWO places and only one creates a thread.\n\n**Bucket 1 — inline comments** become resolvable threads and gate the merge.\nRead them with GraphQL, not the REST comments endpoint:\n\n```bash\ngh api graphql -f query='\nquery { repository(owner:\"OWNER\", name:\"REPO\") {\n  pullRequest(number:N) { reviewThreads(first:100) {\n    nodes { id isResolved path comments(first:1){nodes{author{login} body}} } } } } }' \\\n  --jq '.data.repository.pullRequest.reviewThreads.nodes[]|select(.isResolved==false)'\n```\n\n`first:100` is the maximum page size, and it is a CAP, not a guarantee. A PR\nwith more threads than that returns a truncated set with no error — which is\nthis document's own failure mode, a query that reports a clean round it never\nestablished. Check `pageInfo.hasNextPage` and page through `endCursor` before\nconcluding a PR has no unresolved threads.\n\nREST attributes the same output to two DIFFERENT logins —\n`copilot-pull-request-reviewer[bot]` on `\u002Fpulls\u002FN\u002Freviews`, plain `Copilot` on\n`\u002Fpulls\u002FN\u002Fcomments` — so filtering comments by the bot login returns zero while\ngating threads are open. Measured on PR #614: four unresolved threads invisible,\nand since threads block merge it presented as a PR that would not land. GraphQL\nsidesteps it and returns the `isResolved` and thread id you need anyway.\n\n**Bucket 2 — a `\u003Cdetails>` block in the review BODY.** No thread, nothing to\nresolve, invisible to every thread query.\n\n```bash\ngh api --paginate \"repos\u002FOWNER\u002FREPO\u002Fpulls\u002FN\u002Freviews\" \\\n  --jq '[.[]|select(.user.login==\"copilot-pull-request-reviewer[bot]\")]|last|.body'\n```\n\n**Print the whole body. Never grep it for a heading.** The block's title is not\nstable — `Comments suppressed due to low confidence (N)` and plain\n`Suppressed comments (N)` have both shipped. A phrase grep that returns empty is\nindistinguishable from a clean bucket, which makes the failure silent and\nself-confirming. On PR #568 the suppressed bucket produced 7 genuine findings\nagainst the gating bucket's 2; the confidence signal was inverted.\n\n`--paginate` is load-bearing: the endpoint pages at 30 and `last` on page one is\nan OLD review that reads exactly like a fresh clean one.\n\n**\"generated no new comments\" describes bucket 1 only.** It has appeared in the\nsame body as a suppressed finding.\n\n## Watching a PR\n\nEvery wait on a PR uses one watcher, `scripts\u002Fpr-watch.sh` in this skill's\ndirectory (in this repository, `dev\u002Fskills\u002Fpr-review-loop\u002Fscripts\u002Fpr-watch.sh`).\nRun it in the background (`run_in_background`) for every PR you own or wait on,\nall PRs of a stack in one call, so its exit is the one notification:\n\n```bash\nscripts\u002Fpr-watch.sh [--repo OWNER\u002FREPO] [--interval 30] [--settle 60] [--timeout 21600] PR [PR...]\n```\n\nIt snapshots each PR at start and exits 0 on the first poll that sees a change,\nprinting every change from that poll as `#\u003Cpr> \u003Cevent>`: `merged \u003Csha>`,\n`closed`, `reopened`, `ready`, `draft`, `head-moved \u003Cold>->\u003Cnew>`,\n`base \u003Cold>->\u003Cnew>`, `auto-merge armed \u003Cmethod>`, `auto-merge disarmed`,\n`dirty`, `check-failed \u003Cname> \u003Clink>`, `checks-green`, `rerun-cancelled \u003Crun>`,\n`review \u003Clogin> \u003CSTATE> on \u003Csha>: \u003Cfirst line>` and\n`thread-new \u003Cpath>:\u003Cline> \u003Cfirst line>`. Exit 2 means ten polls in a row failed\nto read GitHub, 3 a timeout and 64 a usage error.\n\nThe baseline is the state at start, so re-arm after each wake once you have\nacted on it, and stop the watcher when the PR is done. Act on each event:\n\n- **`check-failed`**: read that job's log, fix, push and re-arm. It fires on the\n  FIRST failure while slow Darwin legs still run; `gh pr checks --watch` and\n  `--fail-fast` wait for pending checks or ignore cancellations.\n- **`dirty`**: rebase. A conflicted PR gets no checks, so it reads like slow CI.\n- **`review`**: read it as below. Copilot's quota and error replies arrive as\n  `COMMENTED` reviews, and the first line shows which one this is.\n- **`rerun-cancelled`** appears once the other checks are green, even right\n  after re-arming: run `gh run rerun \u003Crun>` so the superseded run's required\n  contexts clear, then re-arm. Runs older than GitHub's 30-day rerun window are\n  never asked for; push or dispatch to get fresh checks instead.\n\nThe guards each close a way a hand-written watcher reported the wrong thing:\n\n- **Checks and reviews belong to the head.** It reads checks for the PR's\n  current head commit, so the previous commit's finished checks never pass for a\n  new push, and a moved head is its own event. A review of an older commit is\n  not an event; every new review of the head is, two from one author included.\n- **No checks is not green.** `checks-green` needs at least one check and none\n  pending. A stacked PR may have none at all; that is silence, not an event.\n- **Cancelled duplicates.** A cancelled check whose Actions run has a newer run\n  of the same workflow on the same head, still running or successful, is not a\n  failure. A failed job, even inside that cancelled run, still wakes you.\n- **A failed read is never an event.** An empty, failed or hung `gh` call is\n  retried next poll; it cannot read as \"head moved\" or \"checks gone\". Each call\n  is cut off after 60 s or at `--timeout`, whichever comes first, and killed 2 s\n  later if it ignores the signal.\n- **Late checks.** Jobs that wait on other jobs create their checks later, so a\n  settled-looking snapshot can cover only the first batch. `checks-green` waits\n  until the checks have stayed green for `--settle` seconds. Checks that were\n  green at the start report `checks-green` only after a non-green poll.\n\n`UNSTABLE` on the PR is not a failure — it means \"not all green yet\" and covers\npending. `DIRTY` is the one that means stop waiting and rebase.\n\nBefore telling anyone a PR is mergeable, read its state: green checks are not\nmergeability, because an armed auto-merge lands the PR the moment they pass.\n\n```bash\ngh pr view \u003CN> --json state,mergedAt,autoMergeRequest\n```\n\nReport \"merged\" when `state` is `MERGED`, and \"green; auto-merge will land it\"\nwhen `autoMergeRequest` is set. Ask for a merge only when the PR is `OPEN` with\n`autoMergeRequest` null.\n\n## Watching a dispatched CI run\n\nA stacked PR (base is another PR's branch) gets no automatic checks, so an empty\ncheck list there is expected, not a failure. After a substantive change, not a\npure rebase or restack, dispatch CI and watch that one run with the script\nbelow, beside `pr-watch.sh`. It is separate because it waits on one Actions run,\nnot on the PR: any other check on the commit, such as Copilot's, would satisfy\n`checks-green` before the dispatched run exists.\n\n```bash\n#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euETo pipefail\nshopt -s inherit_errexit 2>\u002Fdev\u002Fnull || :\nrepo=OWNER\u002FREPO\nbranch=$(git branch --show-current) head=$(git rev-parse HEAD)\ngh() { timeout 60 gh \"$@\"; } # runs the real gh; it sets no HTTP timeout\n# gh prints the new run's URL: watch that run, never one picked from a list.\nurl=$(gh workflow run ci.yml --repo \"$repo\" --ref \"$branch\")\nrun=${url##*\u002F}\n[[ $run =~ ^[0-9]+$ ]] || { echo \"no run URL from gh: $url\"; exit 1; }\nsha=$(gh run view \"$run\" --repo \"$repo\" --json headSha --jq .headSha)\n[ \"$sha\" = \"$head\" ] || { echo \"run $run is for $sha, not $head\"; exit 1; }\ndeadline=$((SECONDS + 3 * 3600))\nwhile [ \"$SECONDS\" -lt \"$deadline\" ]; do\n  view=$(gh run view \"$run\" --repo \"$repo\" --json status,conclusion,jobs)\n  failed=$(jq -r '[.jobs[] | select(.conclusion | IN(\"failure\", \"cancelled\",\n    \"timed_out\", \"action_required\", \"startup_failure\")) | .name] | join(\", \")' \u003C\u003C\u003C\"$view\")\n  [ -z \"$failed\" ] || { echo \"run $run failed: $failed\"; exit 1; }\n  if [ \"$(jq -r .status \u003C\u003C\u003C\"$view\")\" = completed ]; then\n    [ \"$(jq -r .conclusion \u003C\u003C\u003C\"$view\")\" = success ] && exit 0\n    echo \"run $run ended $(jq -r .conclusion \u003C\u003C\u003C\"$view\")\"\n    exit 1\n  fi\n  sleep 30\ndone\necho \"run $run still running after 3 hours\"\nexit 1\n```\n\nIt watches the run `gh workflow run` created, checked against the pushed commit,\nso no other run can pass for it. It stops on the first failed job instead of\nwaiting for the slowest one, and every `gh` call has a time limit.\n\nWhen a stacked PR moves to `main`, two CI runs can start for its head and one of\nthem is cancelled. The cancelled run leaves red contexts on the PR. Wait on the\nlive run with `pr-watch.sh`, then merge once\n`gh pr view \u003Cpr> --json mergeStateStatus --jq .mergeStateStatus` prints `CLEAN`;\nthe watcher reports `dirty`, never `CLEAN`.\n\nSome harness permission classifiers block `gh pr edit`, closing a PR and\nretargeting it, while allowing `gh pr create --draft`, `gh pr ready` and\n`gh workflow run`. Treat PR body and base changes as operator actions unless\npermission is given; before a base PR merges, retarget its open dependent PRs\nwith permission or ask the operator to do so before merging, following\nstack-submit's **Before merging**.\n\n## Deciding whether a review is yours to read\n\nThree states look alike and only one means \"reviewed, nothing found\".\n\n```bash\n# did a review run on THIS commit? gate on commit_id, never a timestamp\ngh api --paginate \"repos\u002FOWNER\u002FREPO\u002Fpulls\u002FN\u002Freviews\" \\\n  --jq '[.[]|select(.user.login==\"copilot-pull-request-reviewer[bot]\")]|last|.commit_id'\n# did the reviewer run at all on the head SHA?\ngh api --paginate \"repos\u002FOWNER\u002FREPO\u002Fcommits\u002F\u003Chead-sha>\u002Fcheck-runs\" \\\n  --jq '.check_runs[]|select(.name==\"copilot-pull-request-reviewer\")|.status'\n```\n\nA review of an OLDER commit still advances `submitted_at`, so a timestamp\nbaseline reports a stale review as fresh. Absent run = nothing ran. `completed`\nplus an error body = it ran and produced nothing.\n\n**A zero-finding round is clean ONLY if the body says the review ran.** Both\n\"Copilot encountered an error and was unable to review\" and the quota refusal\narrive as `state: COMMENTED` with zero threads — every mechanical signal reads\n\"reviewed, nothing found\".\n\n## Re-requesting\n\nOnly ever after a significant change since the last run (the operating policy\ndefines significant). Request the review through REST with the real reviewer\nlogin:\n\n```bash\ngh api --method POST \"repos\u002FOWNER\u002FREPO\u002Fpulls\u002FN\u002Frequested_reviewers\" \\\n  -f 'reviewers[]=copilot-pull-request-reviewer[bot]'\n```\n\nThe request is not the confirmation. The POST returns the entire pull request\nobject on success, so HTTP 200 proves nothing on its own. Poll for the\n`copilot-pull-request-reviewer` check run on the head SHA. A request issued\nwhile a review is in flight is silently dropped.\n\nGraphQL has no `requestCopilotReview` mutation. In a session that actually has\nthe GitHub MCP server configured, its `request_copilot_review` operation is an\nalternative. The identifier shown by the client is PREFIXED and varies by MCP\nclient config, so match on the trailing segment rather than the full name.\n\n## Separate-agent review\n\nThe repository operating policy owns the triggers and scope. For local review,\nfollow the delegate-routing skill's **Work and review** workflow, including its\nSubtractive rubric and its Review step, which sets how many checks a change\ngets. Keep reviewers read-only: forbid mutating git commands because sibling\nworktrees hold live edits. Record dismissed findings by title and count, and\ntheir dispositions in the PR body.\n\nFindings are unbounded, so bound the contest before adding lenses: a 94-line\ndocs PR once produced 28 findings and a 59-agent run.\n","# PR review loop — mechanics\n\nThe operating policy owns WHEN to do these things and WHOSE job it is. This\nskill owns HOW, because the how is a pile of API traps that each produce a\nconfident wrong answer, and none of it is needed until a PR actually exists.\n\n## Reading a Copilot review\n\nCopilot writes findings in TWO places and only one creates a thread.\n\n**Bucket 1 — inline comments** become resolvable threads and gate the merge.\nRead them with GraphQL, not the REST comments endpoint:\n\n```bash\ngh api graphql -f query='\nquery { repository(owner:\"OWNER\", name:\"REPO\") {\n  pullRequest(number:N) { reviewThreads(first:100) {\n    nodes { id isResolved path comments(first:1){nodes{author{login} body}} } } } } }' \\\n  --jq '.data.repository.pullRequest.reviewThreads.nodes[]|select(.isResolved==false)'\n```\n\n`first:100` is the maximum page size, and it is a CAP, not a guarantee. A PR\nwith more threads than that returns a truncated set with no error — which is\nthis document's own failure mode, a query that reports a clean round it never\nestablished. Check `pageInfo.hasNextPage` and page through `endCursor` before\nconcluding a PR has no unresolved threads.\n\nREST attributes the same output to two DIFFERENT logins —\n`copilot-pull-request-reviewer[bot]` on `\u002Fpulls\u002FN\u002Freviews`, plain `Copilot` on\n`\u002Fpulls\u002FN\u002Fcomments` — so filtering comments by the bot login returns zero while\ngating threads are open. Measured on PR #614: four unresolved threads invisible,\nand since threads block merge it presented as a PR that would not land. GraphQL\nsidesteps it and returns the `isResolved` and thread id you need anyway.\n\n**Bucket 2 — a `\u003Cdetails>` block in the review BODY.** No thread, nothing to\nresolve, invisible to every thread query.\n\n```bash\ngh api --paginate \"repos\u002FOWNER\u002FREPO\u002Fpulls\u002FN\u002Freviews\" \\\n  --jq '[.[]|select(.user.login==\"copilot-pull-request-reviewer[bot]\")]|last|.body'\n```\n\n**Print the whole body. Never grep it for a heading.** The block's title is not\nstable — `Comments suppressed due to low confidence (N)` and plain\n`Suppressed comments (N)` have both shipped. A phrase grep that returns empty is\nindistinguishable from a clean bucket, which makes the failure silent and\nself-confirming. On PR #568 the suppressed bucket produced 7 genuine findings\nagainst the gating bucket's 2; the confidence signal was inverted.\n\n`--paginate` is load-bearing: the endpoint pages at 30 and `last` on page one is\nan OLD review that reads exactly like a fresh clean one.\n\n**\"generated no new comments\" describes bucket 1 only.** It has appeared in the\nsame body as a suppressed finding.\n\n## Watching a PR\n\nEvery wait on a PR uses one watcher, `scripts\u002Fpr-watch.sh` in this skill's\ndirectory (in this repository, `dev\u002Fskills\u002Fpr-review-loop\u002Fscripts\u002Fpr-watch.sh`).\nRun it in the background (`run_in_background`) for every PR you own or wait on,\nall PRs of a stack in one call, so its exit is the one notification:\n\n```bash\nscripts\u002Fpr-watch.sh [--repo OWNER\u002FREPO] [--interval 30] [--settle 60] [--timeout 21600] PR [PR...]\n```\n\nIt snapshots each PR at start and exits 0 on the first poll that sees a change,\nprinting every change from that poll as `#\u003Cpr> \u003Cevent>`: `merged \u003Csha>`,\n`closed`, `reopened`, `ready`, `draft`, `head-moved \u003Cold>->\u003Cnew>`,\n`base \u003Cold>->\u003Cnew>`, `auto-merge armed \u003Cmethod>`, `auto-merge disarmed`,\n`dirty`, `check-failed \u003Cname> \u003Clink>`, `checks-green`, `rerun-cancelled \u003Crun>`,\n`review \u003Clogin> \u003CSTATE> on \u003Csha>: \u003Cfirst line>` and\n`thread-new \u003Cpath>:\u003Cline> \u003Cfirst line>`. Exit 2 means ten polls in a row failed\nto read GitHub, 3 a timeout and 64 a usage error.\n\nThe baseline is the state at start, so re-arm after each wake once you have\nacted on it, and stop the watcher when the PR is done. Act on each event:\n\n- **`check-failed`**: read that job's log, fix, push and re-arm. It fires on the\n  FIRST failure while slow Darwin legs still run; `gh pr checks --watch` and\n  `--fail-fast` wait for pending checks or ignore cancellations.\n- **`dirty`**: rebase. A conflicted PR gets no checks, so it reads like slow CI.\n- **`review`**: read it as below. Copilot's quota and error replies arrive as\n  `COMMENTED` reviews, and the first line shows which one this is.\n- **`rerun-cancelled`** appears once the other checks are green, even right\n  after re-arming: run `gh run rerun \u003Crun>` so the superseded run's required\n  contexts clear, then re-arm. Runs older than GitHub's 30-day rerun window are\n  never asked for; push or dispatch to get fresh checks instead.\n\nThe guards each close a way a hand-written watcher reported the wrong thing:\n\n- **Checks and reviews belong to the head.** It reads checks for the PR's\n  current head commit, so the previous commit's finished checks never pass for a\n  new push, and a moved head is its own event. A review of an older commit is\n  not an event; every new review of the head is, two from one author included.\n- **No checks is not green.** `checks-green` needs at least one check and none\n  pending. A stacked PR may have none at all; that is silence, not an event.\n- **Cancelled duplicates.** A cancelled check whose Actions run has a newer run\n  of the same workflow on the same head, still running or successful, is not a\n  failure. A failed job, even inside that cancelled run, still wakes you.\n- **A failed read is never an event.** An empty, failed or hung `gh` call is\n  retried next poll; it cannot read as \"head moved\" or \"checks gone\". Each call\n  is cut off after 60 s or at `--timeout`, whichever comes first, and killed 2 s\n  later if it ignores the signal.\n- **Late checks.** Jobs that wait on other jobs create their checks later, so a\n  settled-looking snapshot can cover only the first batch. `checks-green` waits\n  until the checks have stayed green for `--settle` seconds. Checks that were\n  green at the start report `checks-green` only after a non-green poll.\n\n`UNSTABLE` on the PR is not a failure — it means \"not all green yet\" and covers\npending. `DIRTY` is the one that means stop waiting and rebase.\n\nBefore telling anyone a PR is mergeable, read its state: green checks are not\nmergeability, because an armed auto-merge lands the PR the moment they pass.\n\n```bash\ngh pr view \u003CN> --json state,mergedAt,autoMergeRequest\n```\n\nReport \"merged\" when `state` is `MERGED`, and \"green; auto-merge will land it\"\nwhen `autoMergeRequest` is set. Ask for a merge only when the PR is `OPEN` with\n`autoMergeRequest` null.\n\n## Watching a dispatched CI run\n\nA stacked PR (base is another PR's branch) gets no automatic checks, so an empty\ncheck list there is expected, not a failure. After a substantive change, not a\npure rebase or restack, dispatch CI and watch that one run with the script\nbelow, beside `pr-watch.sh`. It is separate because it waits on one Actions run,\nnot on the PR: any other check on the commit, such as Copilot's, would satisfy\n`checks-green` before the dispatched run exists.\n\n```bash\n#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euETo pipefail\nshopt -s inherit_errexit 2>\u002Fdev\u002Fnull || :\nrepo=OWNER\u002FREPO\nbranch=$(git branch --show-current) head=$(git rev-parse HEAD)\ngh() { timeout 60 gh \"$@\"; } # runs the real gh; it sets no HTTP timeout\n# gh prints the new run's URL: watch that run, never one picked from a list.\nurl=$(gh workflow run ci.yml --repo \"$repo\" --ref \"$branch\")\nrun=${url##*\u002F}\n[[ $run =~ ^[0-9]+$ ]] || { echo \"no run URL from gh: $url\"; exit 1; }\nsha=$(gh run view \"$run\" --repo \"$repo\" --json headSha --jq .headSha)\n[ \"$sha\" = \"$head\" ] || { echo \"run $run is for $sha, not $head\"; exit 1; }\ndeadline=$((SECONDS + 3 * 3600))\nwhile [ \"$SECONDS\" -lt \"$deadline\" ]; do\n  view=$(gh run view \"$run\" --repo \"$repo\" --json status,conclusion,jobs)\n  failed=$(jq -r '[.jobs[] | select(.conclusion | IN(\"failure\", \"cancelled\",\n    \"timed_out\", \"action_required\", \"startup_failure\")) | .name] | join(\", \")' \u003C\u003C\u003C\"$view\")\n  [ -z \"$failed\" ] || { echo \"run $run failed: $failed\"; exit 1; }\n  if [ \"$(jq -r .status \u003C\u003C\u003C\"$view\")\" = completed ]; then\n    [ \"$(jq -r .conclusion \u003C\u003C\u003C\"$view\")\" = success ] && exit 0\n    echo \"run $run ended $(jq -r .conclusion \u003C\u003C\u003C\"$view\")\"\n    exit 1\n  fi\n  sleep 30\ndone\necho \"run $run still running after 3 hours\"\nexit 1\n```\n\nIt watches the run `gh workflow run` created, checked against the pushed commit,\nso no other run can pass for it. It stops on the first failed job instead of\nwaiting for the slowest one, and every `gh` call has a time limit.\n\nWhen a stacked PR moves to `main`, two CI runs can start for its head and one of\nthem is cancelled. The cancelled run leaves red contexts on the PR. Wait on the\nlive run with `pr-watch.sh`, then merge once\n`gh pr view \u003Cpr> --json mergeStateStatus --jq .mergeStateStatus` prints `CLEAN`;\nthe watcher reports `dirty`, never `CLEAN`.\n\nSome harness permission classifiers block `gh pr edit`, closing a PR and\nretargeting it, while allowing `gh pr create --draft`, `gh pr ready` and\n`gh workflow run`. Treat PR body and base changes as operator actions unless\npermission is given; before a base PR merges, retarget its open dependent PRs\nwith permission or ask the operator to do so before merging, following\nstack-submit's **Before merging**.\n\n## Deciding whether a review is yours to read\n\nThree states look alike and only one means \"reviewed, nothing found\".\n\n```bash\n# did a review run on THIS commit? gate on commit_id, never a timestamp\ngh api --paginate \"repos\u002FOWNER\u002FREPO\u002Fpulls\u002FN\u002Freviews\" \\\n  --jq '[.[]|select(.user.login==\"copilot-pull-request-reviewer[bot]\")]|last|.commit_id'\n# did the reviewer run at all on the head SHA?\ngh api --paginate \"repos\u002FOWNER\u002FREPO\u002Fcommits\u002F\u003Chead-sha>\u002Fcheck-runs\" \\\n  --jq '.check_runs[]|select(.name==\"copilot-pull-request-reviewer\")|.status'\n```\n\nA review of an OLDER commit still advances `submitted_at`, so a timestamp\nbaseline reports a stale review as fresh. Absent run = nothing ran. `completed`\nplus an error body = it ran and produced nothing.\n\n**A zero-finding round is clean ONLY if the body says the review ran.** Both\n\"Copilot encountered an error and was unable to review\" and the quota refusal\narrive as `state: COMMENTED` with zero threads — every mechanical signal reads\n\"reviewed, nothing found\".\n\n## Re-requesting\n\nOnly ever after a significant change since the last run (the operating policy\ndefines significant). Request the review through REST with the real reviewer\nlogin:\n\n```bash\ngh api --method POST \"repos\u002FOWNER\u002FREPO\u002Fpulls\u002FN\u002Frequested_reviewers\" \\\n  -f 'reviewers[]=copilot-pull-request-reviewer[bot]'\n```\n\nThe request is not the confirmation. The POST returns the entire pull request\nobject on success, so HTTP 200 proves nothing on its own. Poll for the\n`copilot-pull-request-reviewer` check run on the head SHA. A request issued\nwhile a review is in flight is silently dropped.\n\nGraphQL has no `requestCopilotReview` mutation. In a session that actually has\nthe GitHub MCP server configured, its `request_copilot_review` operation is an\nalternative. The identifier shown by the client is PREFIXED and varies by MCP\nclient config, so match on the trailing segment rather than the full name.\n\n## Separate-agent review\n\nThe repository operating policy owns the triggers and scope. For local review,\nfollow the delegate-routing skill's **Work and review** workflow, including its\nSubtractive rubric and its Review step, which sets how many checks a change\ngets. Keep reviewers read-only: forbid mutating git commands because sibling\nworktrees hold live edits. Record dismissed findings by title and count, and\ntheir dispositions in the PR body.\n\nFindings are unbounded, so bound the contest before adding lenses: a 94-line\ndocs PR once produced 28 findings and a 59-agent run.\n",{"name":5,"description":7},"2a93bbf3c2b149c332ef3e05030c5280c5f3c75b","dev\u002Fskills\u002Fpr-review-loop",0,"https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools","https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor","2026-11-14 03:32:24",{"source":8,"skillName":5,"commands":51,"prompt":61,"zipAvailable":62},[52,55,58],{"tool":53,"command":54},"npx","npx skills add higherorderfunctor\u002Fnix-agentic-tools --skill pr-review-loop",{"tool":56,"command":57},"bunx","bunx skills add higherorderfunctor\u002Fnix-agentic-tools --skill pr-review-loop",{"tool":59,"command":60},"pnpm","pnpm dlx skills add higherorderfunctor\u002Fnix-agentic-tools --skill pr-review-loop","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools\u002Fblob\u002Fmain\u002Fdev\u002Fskills\u002Fpr-review-loop\u002FSKILL.md",true,[64],{"path":65,"resourceType":66,"resourceTypeDesc":67,"sizeBytes":68,"sourceHtmlUrl":69},"scripts\u002Fpr-watch.sh","SCRIPT","脚本",11196,"https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools\u002Fblob\u002Fmain\u002Fdev\u002Fskills\u002Fpr-review-loop\u002Fscripts\u002Fpr-watch.sh",[71,104,129,147,166,181],{"skillNo":72,"name":73,"nameZh":6,"description":74,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":75,"categories":76,"keywords":82,"publishedAt":39},"01M6MQXHSDNFXSN7TT5FFWPF3X","stack-test","Use when you need to run tests or formatters across commits in a stack. Use INSTEAD of manual git test run or looping git checkout + test. Prevents: testing every commit when only the tip has to pass, unbounded parallel jobs exhausting memory, cache misunderstandings.","https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools\u002Fblob\u002Fmain\u002Fpackages\u002Fstacked-workflows\u002Fskills\u002Fstack-test\u002FSKILL.md",[77],{"categoryNo":78,"categoryName":79,"taxonomyType":80,"taxonomyTypeDesc":81},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103],"test","stack","stack test","parallel","TIP","checkout","commits","memory","manual","testing","fix","tests","only","use","false","you","cache","commit","across","command","when",{"skillNo":105,"name":106,"nameZh":6,"description":107,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":108,"categories":109,"keywords":110,"publishedAt":39},"01M6MQXAXVWG1RY9H2116G4SVX","stack-summary","Use when you need to audit, analyze, or review stack quality before restructuring. Produces a structured summary with per-commit classification, philosophy audit, and violation flags. Output feeds directly into \u002Fstack-plan restructure mode. Use INSTEAD of manual git log inspection.","https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools\u002Fblob\u002Fmain\u002Fpackages\u002Fstacked-workflows\u002Fskills\u002Fstack-summary\u002FSKILL.md",[],[84,111,112,113,114,37,91,115,116,96,97,98,117,100,118,21,119,120,121,122,123,103,124,125,126,127,128],"stack summary","summary","feeds","review stack","mode","classification","git log","log","output","analyze","with","philosophy","inspection","plan","worktree","audit","flags","git",{"skillNo":130,"name":131,"nameZh":6,"description":132,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":133,"categories":134,"keywords":135,"publishedAt":39},"01M6MQX62HMQR5CSN4FSC95ZJC","stack-submit","Use when you need to push, submit, or create pull requests for a commit stack. Rebases only the current stack onto upstream main, validates, and pushes INSTEAD of manual git sync + git submit + PR\u002FMR creation. Handles branch creation, stacked PR\u002FMR creation with correct base branches.","https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools\u002Fblob\u002Fmain\u002Fpackages\u002Fstacked-workflows\u002Fskills\u002Fstack-submit\u002FSKILL.md",[],[136,84,137,138,139,140,24,141,37,91,142,143,144,95,27,96,97,145,146,103],"submit","stack submit","pull","branch","push","creation","correct","upstream","branches","git sync","mr",{"skillNo":148,"name":149,"nameZh":6,"description":150,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":151,"categories":152,"keywords":153,"publishedAt":39},"01M6MQX2BNQ4MAV9KCV0RZ734G","stack-split","Use when you need to break up, split, or decompose a large commit into smaller atomic commits. Use INSTEAD of manual git rebase -i + edit or git reset HEAD^. Prevents: non-working intermediate commits, wrong split ordering, missed downstream restack.","https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools\u002Fblob\u002Fmain\u002Fpackages\u002Fstacked-workflows\u002Fskills\u002Fstack-split\u002FSKILL.md",[],[154,84,155,156,89,91,157,96,97,158,98,100,159,160,161,162,163,164,128,165,103],"split","stack split","decompose","up","atomic commits","reset","head","missed","break","atomic","edit","rebase",{"skillNo":167,"name":168,"nameZh":6,"description":169,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":170,"categories":171,"keywords":172,"publishedAt":39},"01M6MQWWM5P7FVQSF7EWHJ70C6","stack-plan","Use when you need to plan commits, restructure a stack, or commit uncommitted work as organized atomic commits. Builds a stack from a description, uncommitted work, or existing commits INSTEAD of dumping everything into one monolithic commit with git add -A && git commit, or manual git rebase -i, git reset --soft, or ad-hoc git move sequences. Prevents: wrong commit ordering, forward references, git move -F panics, untested intermediates.","https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools\u002Fblob\u002Fmain\u002Fpackages\u002Fstacked-workflows\u002Fskills\u002Fstack-plan\u002FSKILL.md",[],[84,124,173,174,175,89,37,176,91,177,96,178,97,158,98,100,179,159,121,180,103],"stack plan","tree","as","move","git commit","Existing","git move","references",{"skillNo":182,"name":183,"nameZh":6,"description":184,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":185,"categories":186,"keywords":187,"publishedAt":39},"01M6MQWP7CKTM5NF72MED1H8NS","stack-fix","Use when you need to fix, correct, or update content in an earlier commit — line-level edits, content moves, or structural changes. Absorbs staged changes into correct stack commits INSTEAD of running git absorb or git commit --fixup via Bash, or hand-running git prev + edit + git amend + git restack --merge. Prevents: missed dry-run preview, leftover staged changes going unnoticed, forgetting to restack. Falls back to guided manual amend when absorb cannot route hunks.","https:\u002F\u002Fgithub.com\u002Fhigherorderfunctor\u002Fnix-agentic-tools\u002Fblob\u002Fmain\u002Fpackages\u002Fstacked-workflows\u002Fskills\u002Fstack-fix\u002FSKILL.md",[],[93,84,188,189,89,91,142,190,177,96,97,98,191,192,100,193,194,195,26,103],"stack fix","back","structural","Bash","preview","hand","dry","route"]