[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7560Z26ATKC3RZP2YNGFKB8":3,"skill-same:01M7560Z26ATKC3RZP2YNGFKB8":57},{"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":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":29,"commitSha":30,"skillDirectory":31,"forksCount":32,"repositoryHtmlUrl":33,"repositoryHomepageUrl":6,"ownerHtmlUrl":34,"ownerBlogUrl":35,"ownerDescription":36,"updatedAt":37,"install":38,"resources":50},"01M7560Z26ATKC3RZP2YNGFKB8","x-watch-pr-ci",null,"Polls a PR's CI checks and Copilot review status, blocking until checks complete or timeout. Returns one of 8 stable exit codes (SUCCESS=0, CI_PENDING_PROCEED=10, CI_FAILED=20, TIMEOUT=30, PR_ALREADY_MERGED=40, NO_CI_CONFIGURED=50, PR_CLOSED=60, PR_NOT_FOUND=70). Writes a versioned state-file for session resume.","review-bench\u002Federcnj_ia-dev-environment","review-bench","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F286778065?v=4","01M7517F9NTEPDZDAXJBR30FWT","ReviewBench","01M7517FAC9F4P7TGWCNJ8VYVW",0,"MIT","https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment\u002Fblob\u002Fmain\u002Fsrc\u002Ftest\u002Fresources\u002Fgolden\u002Fjava-spring-neo4j\u002F.claude\u002Fskills\u002Fx-watch-pr-ci\u002FSKILL.md",[],[],"2026-11-12 13:55:43",false,"---\nname: x-watch-pr-ci\ndescription: \"Polls a PR's CI checks and Copilot review status, blocking until checks complete or timeout. Returns one of 8 stable exit codes (SUCCESS=0, CI_PENDING_PROCEED=10, CI_FAILED=20, TIMEOUT=30, PR_ALREADY_MERGED=40, NO_CI_CONFIGURED=50, PR_CLOSED=60, PR_NOT_FOUND=70). Writes a versioned state-file for session resume.\"\nuser-invocable: true\nallowed-tools: Bash\nargument-hint: \"--pr-number \u003CN> [--timeout-seconds 1800] [--poll-interval-seconds 60] [--require-copilot-review true] [--require-checks-passing true] [--copilot-review-timeout 900] [--state-file \u003Cpath>] [--no-state-file]\"\ncontext-budget: medium\nrequires-capabilities: []\n---\n\n## Global Output Policy\n\n- **Language**: English ONLY.\n- **Tone**: Technical, Direct, and Concise.\n- **Efficiency**: Remove all conversational fillers and greetings to save tokens.\n\n# Skill: x-watch-pr-ci — CI Watch for PR Approval Gates\n\n## Purpose\n\nPolls a pull request's CI checks and Copilot review until checks complete (or timeout), producing a stable exit code that orchestrators use to build the interactive gate menu (EPIC-0043).\n\nSolves the gap identified in `spec-ci-watch.md §2`: when `x-fix-pr` is invoked from the `FIX-PR` slot it finds zero comments because Copilot hasn't posted yet (review typically takes 30–180s). `x-watch-pr-ci` encapsulates the wait so every caller receives real feedback before presenting a decision gate.\n\n## Triggers\n\n- `\u002Fx-watch-pr-ci --pr-number 42` — watch PR #42 with defaults\n- `\u002Fx-watch-pr-ci --pr-number 42 --timeout-seconds 600` — custom timeout\n- `\u002Fx-watch-pr-ci --pr-number 42 --require-copilot-review false` — skip Copilot wait\n- `\u002Fx-watch-pr-ci --pr-number 42 --no-state-file` — fire-and-forget (no state persistence)\n\n## Parameters\n\n| Parameter | Type | Default | Bounds | Description |\n|-----------|------|---------|--------|-------------|\n| `--pr-number \u003CN>` | int | — (required) | >0 | PR number to monitor. |\n| `--timeout-seconds \u003CN>` | int | 1800 | 60–7200 | Global timeout. |\n| `--poll-interval-seconds \u003CN>` | int | 60 | 15–300 | Sleep between polls. |\n| `--require-copilot-review` | boolean | true | — | Wait for `copilot-pull-request-reviewer[bot]`. |\n| `--require-checks-passing` | boolean | true | — | Require all checks `success` (not just neutral\u002Fskipped). |\n| `--copilot-review-timeout \u003CN>` | int | 900 | 60–timeout | Copilot-specific sub-timeout. |\n| `--state-file \u003Cpath>` | path | `.claude\u002Fstate\u002Fpr-watch-\u003CN>.json` | 512 chars | State file for resume. |\n| `--no-state-file` | flag | — | — | Disable state persistence. |\n\n## Exit Codes (Stable Public Contract — RULE-045-05)\n\n| Code | Name | Condition |\n|------|------|-----------|\n| 0 | `SUCCESS` | All checks green + Copilot review present (or `--require-copilot-review=false`). |\n| 10 | `CI_PENDING_PROCEED` | Checks green + Copilot timeout elapsed without review. Proceed with caution. |\n| 20 | `CI_FAILED` | A check concluded with `failure`, `timed_out`, `cancelled`, or `action_required`. |\n| 30 | `TIMEOUT` | Global timeout elapsed with checks still pending. |\n| 40 | `PR_ALREADY_MERGED` | PR was already merged — idempotent exit. |\n| 50 | `NO_CI_CONFIGURED` | `statusCheckRollup` is empty — no CI configured. |\n| 60 | `PR_CLOSED` | PR closed without merge. |\n| 70 | `PR_NOT_FOUND` | PR does not exist or caller lacks permission. |\n\nThese codes are a **public contract**. Adding a new code = MINOR bump; changing semantics = MAJOR bump (Rule 08 — SemVer).\n\n## Invocation by Orchestrators (Rule 13 INLINE-SKILL)\n\n```markdown\nSkill(skill: \"x-watch-pr-ci\", args: \"--pr-number 42\")\n```\n\nOrchestrators MUST use this Pattern 1 INLINE-SKILL form. Bare-slash (`\u002Fx-watch-pr-ci`) is forbidden in delegation contexts (Rule 13 §Forbidden).\n\n## Workflow\n\n```\n1. VALIDATE   -> Parse and validate arguments; reject out-of-bounds values\n2. RESUME     -> Load state-file if present (skip elapsed time already consumed)\n3. POLL LOOP  -> while elapsed \u003C timeout:\n   a. gh pr view \u003CN> --json state,mergedAt,statusCheckRollup\n   b. gh api repos\u002F{owner}\u002F{repo}\u002Fpulls\u002F{N}\u002Freviews\n   c. Classify (classify checks + copilot + prState + elapsed)\n   d. Write state-file (atomic: .tmp + rename)\n   e. If terminal condition → emit JSON + exit with code\n   f. Sleep poll-interval-seconds\n4. TIMEOUT    -> emit JSON + exit 30\n```\n\n## Step 1 — Argument Validation\n\n```bash\n# Validate --pr-number\nif [[ -z \"$PR_NUMBER\" || \"$PR_NUMBER\" -le 0 ]]; then\n  echo \"ERROR: --pr-number is required and must be > 0\" >&2\n  exit 70\nfi\n\n# Validate --timeout-seconds (60..7200)\nif [[ \"$TIMEOUT_SECONDS\" -lt 60 || \"$TIMEOUT_SECONDS\" -gt 7200 ]]; then\n  echo \"ERROR: timeout-seconds must be in range 60..7200\" >&2\n  exit 1\nfi\n\n# Validate --poll-interval-seconds (15..300)\nif [[ \"$POLL_INTERVAL\" -lt 15 || \"$POLL_INTERVAL\" -gt 300 ]]; then\n  echo \"ERROR: poll-interval-seconds must be in range 15..300\" >&2\n  exit 1\nfi\n\n# Validate --copilot-review-timeout (60..timeout)\nif [[ \"$COPILOT_TIMEOUT\" -lt 60 || \"$COPILOT_TIMEOUT\" -gt \"$TIMEOUT_SECONDS\" ]]; then\n  echo \"ERROR: copilot-review-timeout must be in range 60..${TIMEOUT_SECONDS}\" >&2\n  exit 1\nfi\n```\n\n## Step 2 — Resume from State File\n\n```bash\nSTATE_FILE=\"${STATE_FILE:-.claude\u002Fstate\u002Fpr-watch-${PR_NUMBER}.json}\"\nELAPSED_OFFSET=0\n\nif [[ -z \"$NO_STATE_FILE\" && -f \"$STATE_FILE\" ]]; then\n  # Load previous elapsed from state-file (resume after session restart)\n  PREV_STARTED=$(jq -r '.startedAt' \"$STATE_FILE\" 2>\u002Fdev\u002Fnull)\n  if [[ \"$PREV_STARTED\" != \"null\" && -n \"$PREV_STARTED\" ]]; then\n    ELAPSED_OFFSET=$(( $(date +%s) - $(date -d \"$PREV_STARTED\" +%s 2>\u002Fdev\u002Fnull || date -j -f \"%Y-%m-%dT%H:%M:%SZ\" \"$PREV_STARTED\" +%s) ))\n    echo \"[resume] Loaded state from $STATE_FILE; offset=${ELAPSED_OFFSET}s\" >&2\n  fi\nfi\n\nmkdir -p \"$(dirname \"$STATE_FILE\")\"\nSTART_EPOCH=$(date +%s)\nSTARTED_AT=$(date -u +\"%Y-%m-%dT%H:%M:%SZ\")\n```\n\n## Step 3 — Polling Loop\n\n```bash\nCOPILOT_LOGIN=\"copilot-pull-request-reviewer[bot]\"\nRATE_LIMIT_RETRIES=0\nMAX_RATE_LIMIT_RETRIES=3\n\nwhile true; do\n  ELAPSED=$(( $(date +%s) - START_EPOCH + ELAPSED_OFFSET ))\n\n  # ── Fetch PR state ──────────────────────────────────────────────────────\n  PR_JSON=$(gh pr view \"$PR_NUMBER\" \\\n    --json state,mergedAt,statusCheckRollup 2>&1)\n  GH_EXIT=$?\n\n  if [[ $GH_EXIT -ne 0 ]]; then\n    if echo \"$PR_JSON\" | grep -qi \"Could not resolve\"; then\n      emit_json \"PR_NOT_FOUND\" \"$ELAPSED\" \"[]\" '{\"present\":false}' && exit 70\n    fi\n    if echo \"$PR_JSON\" | grep -qi \"rate limit\\|secondary rate\"; then\n      RATE_LIMIT_RETRIES=$((RATE_LIMIT_RETRIES + 1))\n      if [[ $RATE_LIMIT_RETRIES -gt $MAX_RATE_LIMIT_RETRIES ]]; then\n        emit_json \"TIMEOUT\" \"$ELAPSED\" \"[]\" '{\"present\":false}' && exit 30\n      fi\n      BACKOFF=$(( 30 * (2 ** (RATE_LIMIT_RETRIES - 1)) ))  # 30s, 60s, 120s\n      echo \"[rate-limit] retry ${RATE_LIMIT_RETRIES}\u002F${MAX_RATE_LIMIT_RETRIES}, sleeping ${BACKOFF}s\" >&2\n      sleep \"$BACKOFF\"\n      continue\n    fi\n    echo \"[warn] gh pr view failed (exit $GH_EXIT): $PR_JSON\" >&2\n  fi\n\n  RATE_LIMIT_RETRIES=0  # reset on success\n\n  PR_STATE=$(echo \"$PR_JSON\" | jq -r '.state \u002F\u002F \"UNKNOWN\"' 2>\u002Fdev\u002Fnull)\n  MERGED_AT=$(echo \"$PR_JSON\" | jq -r '.mergedAt \u002F\u002F \"null\"' 2>\u002Fdev\u002Fnull)\n  CHECKS_JSON=$(echo \"$PR_JSON\" | jq -c '[.statusCheckRollup[]? | {name: .name, conclusion: (.conclusion \u002F\u002F .status \u002F\u002F \"pending\")}]' 2>\u002Fdev\u002Fnull || echo \"[]\")\n\n  # ── Classify early-exit conditions ─────────────────────────────────────\n  if [[ \"$PR_STATE\" == \"MERGED\" || (\"$MERGED_AT\" != \"null\" && -n \"$MERGED_AT\") ]]; then\n    emit_json \"PR_ALREADY_MERGED\" \"$ELAPSED\" \"$CHECKS_JSON\" '{\"present\":false}' && exit 40\n  fi\n\n  if [[ \"$PR_STATE\" == \"CLOSED\" ]]; then\n    emit_json \"PR_CLOSED\" \"$ELAPSED\" \"$CHECKS_JSON\" '{\"present\":false}' && exit 60\n  fi\n\n  # ── Fetch Copilot review ────────────────────────────────────────────────\n  OWNER_REPO=$(gh repo view --json nameWithOwner -q '.nameWithOwner' 2>\u002Fdev\u002Fnull)\n  REVIEWS_JSON=$(gh api \"repos\u002F${OWNER_REPO}\u002Fpulls\u002F${PR_NUMBER}\u002Freviews\" 2>\u002Fdev\u002Fnull || echo \"[]\")\n  COPILOT_REVIEW=$(echo \"$REVIEWS_JSON\" | jq --arg login \"$COPILOT_LOGIN\" \\\n    'map(select(.user.login == $login)) | if length > 0 then {present: true, reviewId: .[0].id} else {present: false} end' 2>\u002Fdev\u002Fnull \\\n    || echo '{\"present\":false}')\n  COPILOT_PRESENT=$(echo \"$COPILOT_REVIEW\" | jq -r '.present' 2>\u002Fdev\u002Fnull || echo \"false\")\n\n  # ── Check empty CI (NO_CI_CONFIGURED) ──────────────────────────────────\n  CHECK_COUNT=$(echo \"$CHECKS_JSON\" | jq 'length' 2>\u002Fdev\u002Fnull || echo \"0\")\n  if [[ \"$CHECK_COUNT\" -eq 0 ]]; then\n    emit_json \"NO_CI_CONFIGURED\" \"$ELAPSED\" \"[]\" \"$COPILOT_REVIEW\" && exit 50\n  fi\n\n  # ── Detect failing checks ───────────────────────────────────────────────\n  FAILING=$(echo \"$CHECKS_JSON\" | jq '[.[] | select(.conclusion | IN(\"failure\",\"timed_out\",\"cancelled\",\"action_required\"))] | length' 2>\u002Fdev\u002Fnull || echo \"0\")\n  if [[ \"$FAILING\" -gt 0 ]]; then\n    emit_json \"CI_FAILED\" \"$ELAPSED\" \"$CHECKS_JSON\" \"$COPILOT_REVIEW\" && exit 20\n  fi\n\n  # ── Check all-green ─────────────────────────────────────────────────────\n  NON_GREEN=$(echo \"$CHECKS_JSON\" | jq '[.[] | select(.conclusion | IN(\"success\",\"neutral\",\"skipped\") | not)] | length' 2>\u002Fdev\u002Fnull || echo \"1\")\n  ALL_GREEN=$(( NON_GREEN == 0 ))\n\n  if [[ \"$ALL_GREEN\" -eq 1 ]]; then\n    if [[ \"$REQUIRE_COPILOT_REVIEW\" == \"false\" || \"$COPILOT_PRESENT\" == \"true\" ]]; then\n      emit_json \"SUCCESS\" \"$ELAPSED\" \"$CHECKS_JSON\" \"$COPILOT_REVIEW\" && exit 0\n    fi\n    COPILOT_ELAPSED=$(( $(date +%s) - START_EPOCH ))\n    if [[ \"$COPILOT_ELAPSED\" -ge \"$COPILOT_REVIEW_TIMEOUT\" ]]; then\n      emit_json \"CI_PENDING_PROCEED\" \"$ELAPSED\" \"$CHECKS_JSON\" \"$COPILOT_REVIEW\" && exit 10\n    fi\n  fi\n\n  # ── Global timeout check ────────────────────────────────────────────────\n  if [[ \"$ELAPSED\" -ge \"$TIMEOUT_SECONDS\" ]]; then\n    emit_json \"TIMEOUT\" \"$ELAPSED\" \"$CHECKS_JSON\" \"$COPILOT_REVIEW\" && exit 30\n  fi\n\n  # ── Write state-file (atomic: .tmp + rename) ────────────────────────────\n  if [[ -z \"$NO_STATE_FILE\" ]]; then\n    POLL_COUNT=$(( ${POLL_COUNT:-0} + 1 ))\n    LAST_POLL_AT=$(date -u +\"%Y-%m-%dT%H:%M:%SZ\")\n    jq -n \\\n      --argjson prNumber \"$PR_NUMBER\" \\\n      --arg startedAt \"$STARTED_AT\" \\\n      --arg lastPollAt \"$LAST_POLL_AT\" \\\n      --argjson pollCount \"$POLL_COUNT\" \\\n      --argjson checksSnapshot \"$CHECKS_JSON\" \\\n      --argjson copilotReview \"$COPILOT_REVIEW\" \\\n      --arg schemaVersion \"1.0\" \\\n      '{\n        prNumber: $prNumber,\n        startedAt: $startedAt,\n        lastPollAt: $lastPollAt,\n        pollCount: $pollCount,\n        checksSnapshot: $checksSnapshot,\n        copilotReview: $copilotReview,\n        schemaVersion: $schemaVersion\n      }' > \"${STATE_FILE}.tmp\" 2>\u002Fdev\u002Fnull \\\n    && mv \"${STATE_FILE}.tmp\" \"$STATE_FILE\"\n  fi\n\n  echo \"[poll ${POLL_COUNT}] elapsed=${ELAPSED}s allGreen=${ALL_GREEN} copilot=${COPILOT_PRESENT}\" >&2\n  sleep \"$POLL_INTERVAL\"\ndone\n```\n\n## Helper: emit_json\n\n```bash\nemit_json() {\n  local status=\"$1\" elapsed=\"$2\" checks=\"$3\" copilot=\"$4\"\n  jq -n \\\n    --arg status \"$status\" \\\n    --argjson prNumber \"$PR_NUMBER\" \\\n    --argjson checks \"$checks\" \\\n    --argjson copilotReview \"$copilot\" \\\n    --argjson elapsedSeconds \"$elapsed\" \\\n    '{\n      status: $status,\n      prNumber: $prNumber,\n      checks: $checks,\n      copilotReview: $copilotReview,\n      elapsedSeconds: $elapsedSeconds\n    }'\n}\n```\n\n## State File Schema (RULE-045-03)\n\nLocation: `.claude\u002Fstate\u002Fpr-watch-\u003CN>.json`\n\n```json\n{\n  \"prNumber\": 42,\n  \"startedAt\": \"2026-04-20T10:00:00Z\",\n  \"lastPollAt\": \"2026-04-20T10:01:05Z\",\n  \"pollCount\": 2,\n  \"checksSnapshot\": [\n    {\"name\": \"build\", \"conclusion\": \"success\"},\n    {\"name\": \"test\", \"conclusion\": \"success\"}\n  ],\n  \"copilotReview\": {\"present\": true, \"reviewId\": 12345678},\n  \"schemaVersion\": \"1.0\"\n}\n```\n\nWrite is atomic: write to `\u003Cpath>.tmp`, then rename. On corrupted state: log warning and restart from zero.\n\n## Stdout Contract\n\nProgress logs go to **stderr**. The final JSON summary is the **last line** on **stdout**:\n\n```json\n{\n  \"status\": \"SUCCESS\",\n  \"prNumber\": 42,\n  \"checks\": [\n    {\"name\": \"build\", \"conclusion\": \"success\"},\n    {\"name\": \"test\",  \"conclusion\": \"success\"}\n  ],\n  \"copilotReview\": {\"present\": true, \"reviewId\": 12345678},\n  \"elapsedSeconds\": 87\n}\n```\n\n## Edge Cases\n\n| Scenario | Behaviour |\n|----------|-----------|\n| Rate limit | Exponential backoff 3× (30s, 60s, 120s); after 3rd failure exit 30 (TIMEOUT) |\n| Copilot not configured in repo | Detected after 1st poll (absent from requested_reviewers); downgrade to CI_PENDING_PROCEED |\n| State file corrupted | Log warning, reset poll state, continue from elapsed=0 |\n| PR closed during polling | Exit 60 immediately |\n| PR merged during polling | Exit 40 immediately |\n\n## Rule Compliance\n\n- **Rule 13**: orchestrators invoke via `Skill(skill: \"x-watch-pr-ci\", args: \"...\")` — no bare-slash in delegation\n- **Rule 14**: skill does NOT create worktrees (sequential, runs in caller's working tree)\n- **RULE-045-03**: state-file atomic write + resume\n- **RULE-045-04**: Copilot identified by exact login `copilot-pull-request-reviewer[bot]`\n- **RULE-045-05**: exit codes 0\u002F10\u002F20\u002F30\u002F40\u002F50\u002F60\u002F70 are stable public contract\n- **RULE-045-06**: orchestrators use Rule 13 Pattern 1 INLINE-SKILL\n","## Global Output Policy\n\n- **Language**: English ONLY.\n- **Tone**: Technical, Direct, and Concise.\n- **Efficiency**: Remove all conversational fillers and greetings to save tokens.\n\n# Skill: x-watch-pr-ci — CI Watch for PR Approval Gates\n\n## Purpose\n\nPolls a pull request's CI checks and Copilot review until checks complete (or timeout), producing a stable exit code that orchestrators use to build the interactive gate menu (EPIC-0043).\n\nSolves the gap identified in `spec-ci-watch.md §2`: when `x-fix-pr` is invoked from the `FIX-PR` slot it finds zero comments because Copilot hasn't posted yet (review typically takes 30–180s). `x-watch-pr-ci` encapsulates the wait so every caller receives real feedback before presenting a decision gate.\n\n## Triggers\n\n- `\u002Fx-watch-pr-ci --pr-number 42` — watch PR #42 with defaults\n- `\u002Fx-watch-pr-ci --pr-number 42 --timeout-seconds 600` — custom timeout\n- `\u002Fx-watch-pr-ci --pr-number 42 --require-copilot-review false` — skip Copilot wait\n- `\u002Fx-watch-pr-ci --pr-number 42 --no-state-file` — fire-and-forget (no state persistence)\n\n## Parameters\n\n| Parameter | Type | Default | Bounds | Description |\n|-----------|------|---------|--------|-------------|\n| `--pr-number \u003CN>` | int | — (required) | >0 | PR number to monitor. |\n| `--timeout-seconds \u003CN>` | int | 1800 | 60–7200 | Global timeout. |\n| `--poll-interval-seconds \u003CN>` | int | 60 | 15–300 | Sleep between polls. |\n| `--require-copilot-review` | boolean | true | — | Wait for `copilot-pull-request-reviewer[bot]`. |\n| `--require-checks-passing` | boolean | true | — | Require all checks `success` (not just neutral\u002Fskipped). |\n| `--copilot-review-timeout \u003CN>` | int | 900 | 60–timeout | Copilot-specific sub-timeout. |\n| `--state-file \u003Cpath>` | path | `.claude\u002Fstate\u002Fpr-watch-\u003CN>.json` | 512 chars | State file for resume. |\n| `--no-state-file` | flag | — | — | Disable state persistence. |\n\n## Exit Codes (Stable Public Contract — RULE-045-05)\n\n| Code | Name | Condition |\n|------|------|-----------|\n| 0 | `SUCCESS` | All checks green + Copilot review present (or `--require-copilot-review=false`). |\n| 10 | `CI_PENDING_PROCEED` | Checks green + Copilot timeout elapsed without review. Proceed with caution. |\n| 20 | `CI_FAILED` | A check concluded with `failure`, `timed_out`, `cancelled`, or `action_required`. |\n| 30 | `TIMEOUT` | Global timeout elapsed with checks still pending. |\n| 40 | `PR_ALREADY_MERGED` | PR was already merged — idempotent exit. |\n| 50 | `NO_CI_CONFIGURED` | `statusCheckRollup` is empty — no CI configured. |\n| 60 | `PR_CLOSED` | PR closed without merge. |\n| 70 | `PR_NOT_FOUND` | PR does not exist or caller lacks permission. |\n\nThese codes are a **public contract**. Adding a new code = MINOR bump; changing semantics = MAJOR bump (Rule 08 — SemVer).\n\n## Invocation by Orchestrators (Rule 13 INLINE-SKILL)\n\n```markdown\nSkill(skill: \"x-watch-pr-ci\", args: \"--pr-number 42\")\n```\n\nOrchestrators MUST use this Pattern 1 INLINE-SKILL form. Bare-slash (`\u002Fx-watch-pr-ci`) is forbidden in delegation contexts (Rule 13 §Forbidden).\n\n## Workflow\n\n```\n1. VALIDATE   -> Parse and validate arguments; reject out-of-bounds values\n2. RESUME     -> Load state-file if present (skip elapsed time already consumed)\n3. POLL LOOP  -> while elapsed \u003C timeout:\n   a. gh pr view \u003CN> --json state,mergedAt,statusCheckRollup\n   b. gh api repos\u002F{owner}\u002F{repo}\u002Fpulls\u002F{N}\u002Freviews\n   c. Classify (classify checks + copilot + prState + elapsed)\n   d. Write state-file (atomic: .tmp + rename)\n   e. If terminal condition → emit JSON + exit with code\n   f. Sleep poll-interval-seconds\n4. TIMEOUT    -> emit JSON + exit 30\n```\n\n## Step 1 — Argument Validation\n\n```bash\n# Validate --pr-number\nif [[ -z \"$PR_NUMBER\" || \"$PR_NUMBER\" -le 0 ]]; then\n  echo \"ERROR: --pr-number is required and must be > 0\" >&2\n  exit 70\nfi\n\n# Validate --timeout-seconds (60..7200)\nif [[ \"$TIMEOUT_SECONDS\" -lt 60 || \"$TIMEOUT_SECONDS\" -gt 7200 ]]; then\n  echo \"ERROR: timeout-seconds must be in range 60..7200\" >&2\n  exit 1\nfi\n\n# Validate --poll-interval-seconds (15..300)\nif [[ \"$POLL_INTERVAL\" -lt 15 || \"$POLL_INTERVAL\" -gt 300 ]]; then\n  echo \"ERROR: poll-interval-seconds must be in range 15..300\" >&2\n  exit 1\nfi\n\n# Validate --copilot-review-timeout (60..timeout)\nif [[ \"$COPILOT_TIMEOUT\" -lt 60 || \"$COPILOT_TIMEOUT\" -gt \"$TIMEOUT_SECONDS\" ]]; then\n  echo \"ERROR: copilot-review-timeout must be in range 60..${TIMEOUT_SECONDS}\" >&2\n  exit 1\nfi\n```\n\n## Step 2 — Resume from State File\n\n```bash\nSTATE_FILE=\"${STATE_FILE:-.claude\u002Fstate\u002Fpr-watch-${PR_NUMBER}.json}\"\nELAPSED_OFFSET=0\n\nif [[ -z \"$NO_STATE_FILE\" && -f \"$STATE_FILE\" ]]; then\n  # Load previous elapsed from state-file (resume after session restart)\n  PREV_STARTED=$(jq -r '.startedAt' \"$STATE_FILE\" 2>\u002Fdev\u002Fnull)\n  if [[ \"$PREV_STARTED\" != \"null\" && -n \"$PREV_STARTED\" ]]; then\n    ELAPSED_OFFSET=$(( $(date +%s) - $(date -d \"$PREV_STARTED\" +%s 2>\u002Fdev\u002Fnull || date -j -f \"%Y-%m-%dT%H:%M:%SZ\" \"$PREV_STARTED\" +%s) ))\n    echo \"[resume] Loaded state from $STATE_FILE; offset=${ELAPSED_OFFSET}s\" >&2\n  fi\nfi\n\nmkdir -p \"$(dirname \"$STATE_FILE\")\"\nSTART_EPOCH=$(date +%s)\nSTARTED_AT=$(date -u +\"%Y-%m-%dT%H:%M:%SZ\")\n```\n\n## Step 3 — Polling Loop\n\n```bash\nCOPILOT_LOGIN=\"copilot-pull-request-reviewer[bot]\"\nRATE_LIMIT_RETRIES=0\nMAX_RATE_LIMIT_RETRIES=3\n\nwhile true; do\n  ELAPSED=$(( $(date +%s) - START_EPOCH + ELAPSED_OFFSET ))\n\n  # ── Fetch PR state ──────────────────────────────────────────────────────\n  PR_JSON=$(gh pr view \"$PR_NUMBER\" \\\n    --json state,mergedAt,statusCheckRollup 2>&1)\n  GH_EXIT=$?\n\n  if [[ $GH_EXIT -ne 0 ]]; then\n    if echo \"$PR_JSON\" | grep -qi \"Could not resolve\"; then\n      emit_json \"PR_NOT_FOUND\" \"$ELAPSED\" \"[]\" '{\"present\":false}' && exit 70\n    fi\n    if echo \"$PR_JSON\" | grep -qi \"rate limit\\|secondary rate\"; then\n      RATE_LIMIT_RETRIES=$((RATE_LIMIT_RETRIES + 1))\n      if [[ $RATE_LIMIT_RETRIES -gt $MAX_RATE_LIMIT_RETRIES ]]; then\n        emit_json \"TIMEOUT\" \"$ELAPSED\" \"[]\" '{\"present\":false}' && exit 30\n      fi\n      BACKOFF=$(( 30 * (2 ** (RATE_LIMIT_RETRIES - 1)) ))  # 30s, 60s, 120s\n      echo \"[rate-limit] retry ${RATE_LIMIT_RETRIES}\u002F${MAX_RATE_LIMIT_RETRIES}, sleeping ${BACKOFF}s\" >&2\n      sleep \"$BACKOFF\"\n      continue\n    fi\n    echo \"[warn] gh pr view failed (exit $GH_EXIT): $PR_JSON\" >&2\n  fi\n\n  RATE_LIMIT_RETRIES=0  # reset on success\n\n  PR_STATE=$(echo \"$PR_JSON\" | jq -r '.state \u002F\u002F \"UNKNOWN\"' 2>\u002Fdev\u002Fnull)\n  MERGED_AT=$(echo \"$PR_JSON\" | jq -r '.mergedAt \u002F\u002F \"null\"' 2>\u002Fdev\u002Fnull)\n  CHECKS_JSON=$(echo \"$PR_JSON\" | jq -c '[.statusCheckRollup[]? | {name: .name, conclusion: (.conclusion \u002F\u002F .status \u002F\u002F \"pending\")}]' 2>\u002Fdev\u002Fnull || echo \"[]\")\n\n  # ── Classify early-exit conditions ─────────────────────────────────────\n  if [[ \"$PR_STATE\" == \"MERGED\" || (\"$MERGED_AT\" != \"null\" && -n \"$MERGED_AT\") ]]; then\n    emit_json \"PR_ALREADY_MERGED\" \"$ELAPSED\" \"$CHECKS_JSON\" '{\"present\":false}' && exit 40\n  fi\n\n  if [[ \"$PR_STATE\" == \"CLOSED\" ]]; then\n    emit_json \"PR_CLOSED\" \"$ELAPSED\" \"$CHECKS_JSON\" '{\"present\":false}' && exit 60\n  fi\n\n  # ── Fetch Copilot review ────────────────────────────────────────────────\n  OWNER_REPO=$(gh repo view --json nameWithOwner -q '.nameWithOwner' 2>\u002Fdev\u002Fnull)\n  REVIEWS_JSON=$(gh api \"repos\u002F${OWNER_REPO}\u002Fpulls\u002F${PR_NUMBER}\u002Freviews\" 2>\u002Fdev\u002Fnull || echo \"[]\")\n  COPILOT_REVIEW=$(echo \"$REVIEWS_JSON\" | jq --arg login \"$COPILOT_LOGIN\" \\\n    'map(select(.user.login == $login)) | if length > 0 then {present: true, reviewId: .[0].id} else {present: false} end' 2>\u002Fdev\u002Fnull \\\n    || echo '{\"present\":false}')\n  COPILOT_PRESENT=$(echo \"$COPILOT_REVIEW\" | jq -r '.present' 2>\u002Fdev\u002Fnull || echo \"false\")\n\n  # ── Check empty CI (NO_CI_CONFIGURED) ──────────────────────────────────\n  CHECK_COUNT=$(echo \"$CHECKS_JSON\" | jq 'length' 2>\u002Fdev\u002Fnull || echo \"0\")\n  if [[ \"$CHECK_COUNT\" -eq 0 ]]; then\n    emit_json \"NO_CI_CONFIGURED\" \"$ELAPSED\" \"[]\" \"$COPILOT_REVIEW\" && exit 50\n  fi\n\n  # ── Detect failing checks ───────────────────────────────────────────────\n  FAILING=$(echo \"$CHECKS_JSON\" | jq '[.[] | select(.conclusion | IN(\"failure\",\"timed_out\",\"cancelled\",\"action_required\"))] | length' 2>\u002Fdev\u002Fnull || echo \"0\")\n  if [[ \"$FAILING\" -gt 0 ]]; then\n    emit_json \"CI_FAILED\" \"$ELAPSED\" \"$CHECKS_JSON\" \"$COPILOT_REVIEW\" && exit 20\n  fi\n\n  # ── Check all-green ─────────────────────────────────────────────────────\n  NON_GREEN=$(echo \"$CHECKS_JSON\" | jq '[.[] | select(.conclusion | IN(\"success\",\"neutral\",\"skipped\") | not)] | length' 2>\u002Fdev\u002Fnull || echo \"1\")\n  ALL_GREEN=$(( NON_GREEN == 0 ))\n\n  if [[ \"$ALL_GREEN\" -eq 1 ]]; then\n    if [[ \"$REQUIRE_COPILOT_REVIEW\" == \"false\" || \"$COPILOT_PRESENT\" == \"true\" ]]; then\n      emit_json \"SUCCESS\" \"$ELAPSED\" \"$CHECKS_JSON\" \"$COPILOT_REVIEW\" && exit 0\n    fi\n    COPILOT_ELAPSED=$(( $(date +%s) - START_EPOCH ))\n    if [[ \"$COPILOT_ELAPSED\" -ge \"$COPILOT_REVIEW_TIMEOUT\" ]]; then\n      emit_json \"CI_PENDING_PROCEED\" \"$ELAPSED\" \"$CHECKS_JSON\" \"$COPILOT_REVIEW\" && exit 10\n    fi\n  fi\n\n  # ── Global timeout check ────────────────────────────────────────────────\n  if [[ \"$ELAPSED\" -ge \"$TIMEOUT_SECONDS\" ]]; then\n    emit_json \"TIMEOUT\" \"$ELAPSED\" \"$CHECKS_JSON\" \"$COPILOT_REVIEW\" && exit 30\n  fi\n\n  # ── Write state-file (atomic: .tmp + rename) ────────────────────────────\n  if [[ -z \"$NO_STATE_FILE\" ]]; then\n    POLL_COUNT=$(( ${POLL_COUNT:-0} + 1 ))\n    LAST_POLL_AT=$(date -u +\"%Y-%m-%dT%H:%M:%SZ\")\n    jq -n \\\n      --argjson prNumber \"$PR_NUMBER\" \\\n      --arg startedAt \"$STARTED_AT\" \\\n      --arg lastPollAt \"$LAST_POLL_AT\" \\\n      --argjson pollCount \"$POLL_COUNT\" \\\n      --argjson checksSnapshot \"$CHECKS_JSON\" \\\n      --argjson copilotReview \"$COPILOT_REVIEW\" \\\n      --arg schemaVersion \"1.0\" \\\n      '{\n        prNumber: $prNumber,\n        startedAt: $startedAt,\n        lastPollAt: $lastPollAt,\n        pollCount: $pollCount,\n        checksSnapshot: $checksSnapshot,\n        copilotReview: $copilotReview,\n        schemaVersion: $schemaVersion\n      }' > \"${STATE_FILE}.tmp\" 2>\u002Fdev\u002Fnull \\\n    && mv \"${STATE_FILE}.tmp\" \"$STATE_FILE\"\n  fi\n\n  echo \"[poll ${POLL_COUNT}] elapsed=${ELAPSED}s allGreen=${ALL_GREEN} copilot=${COPILOT_PRESENT}\" >&2\n  sleep \"$POLL_INTERVAL\"\ndone\n```\n\n## Helper: emit_json\n\n```bash\nemit_json() {\n  local status=\"$1\" elapsed=\"$2\" checks=\"$3\" copilot=\"$4\"\n  jq -n \\\n    --arg status \"$status\" \\\n    --argjson prNumber \"$PR_NUMBER\" \\\n    --argjson checks \"$checks\" \\\n    --argjson copilotReview \"$copilot\" \\\n    --argjson elapsedSeconds \"$elapsed\" \\\n    '{\n      status: $status,\n      prNumber: $prNumber,\n      checks: $checks,\n      copilotReview: $copilotReview,\n      elapsedSeconds: $elapsedSeconds\n    }'\n}\n```\n\n## State File Schema (RULE-045-03)\n\nLocation: `.claude\u002Fstate\u002Fpr-watch-\u003CN>.json`\n\n```json\n{\n  \"prNumber\": 42,\n  \"startedAt\": \"2026-04-20T10:00:00Z\",\n  \"lastPollAt\": \"2026-04-20T10:01:05Z\",\n  \"pollCount\": 2,\n  \"checksSnapshot\": [\n    {\"name\": \"build\", \"conclusion\": \"success\"},\n    {\"name\": \"test\", \"conclusion\": \"success\"}\n  ],\n  \"copilotReview\": {\"present\": true, \"reviewId\": 12345678},\n  \"schemaVersion\": \"1.0\"\n}\n```\n\nWrite is atomic: write to `\u003Cpath>.tmp`, then rename. On corrupted state: log warning and restart from zero.\n\n## Stdout Contract\n\nProgress logs go to **stderr**. The final JSON summary is the **last line** on **stdout**:\n\n```json\n{\n  \"status\": \"SUCCESS\",\n  \"prNumber\": 42,\n  \"checks\": [\n    {\"name\": \"build\", \"conclusion\": \"success\"},\n    {\"name\": \"test\",  \"conclusion\": \"success\"}\n  ],\n  \"copilotReview\": {\"present\": true, \"reviewId\": 12345678},\n  \"elapsedSeconds\": 87\n}\n```\n\n## Edge Cases\n\n| Scenario | Behaviour |\n|----------|-----------|\n| Rate limit | Exponential backoff 3× (30s, 60s, 120s); after 3rd failure exit 30 (TIMEOUT) |\n| Copilot not configured in repo | Detected after 1st poll (absent from requested_reviewers); downgrade to CI_PENDING_PROCEED |\n| State file corrupted | Log warning, reset poll state, continue from elapsed=0 |\n| PR closed during polling | Exit 60 immediately |\n| PR merged during polling | Exit 40 immediately |\n\n## Rule Compliance\n\n- **Rule 13**: orchestrators invoke via `Skill(skill: \"x-watch-pr-ci\", args: \"...\")` — no bare-slash in delegation\n- **Rule 14**: skill does NOT create worktrees (sequential, runs in caller's working tree)\n- **RULE-045-03**: state-file atomic write + resume\n- **RULE-045-04**: Copilot identified by exact login `copilot-pull-request-reviewer[bot]`\n- **RULE-045-05**: exit codes 0\u002F10\u002F20\u002F30\u002F40\u002F50\u002F60\u002F70 are stable public contract\n- **RULE-045-06**: orchestrators use Rule 13 Pattern 1 INLINE-SKILL\n",{"name":5,"description":7,"allowed-tools":24,"argument-hint":25,"context-budget":26,"user-invocable":27,"requires-capabilities":28},"Bash","--pr-number \u003CN> [--timeout-seconds 1800] [--poll-interval-seconds 60] [--require-copilot-review true] [--require-checks-passing true] [--copilot-review-timeout 900] [--state-file \u003Cpath>] [--no-state-file]","medium",true,[],"main","952a630c6f38d193a349d86fa18db1de8843e837","src\u002Ftest\u002Fresources\u002Fgolden\u002Fjava-spring-neo4j\u002F.claude\u002Fskills\u002Fx-watch-pr-ci",1,"https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment","https:\u002F\u002Fgithub.com\u002Freview-bench","https:\u002F\u002Freview-bench.ai","An open benchmark for AI code review.","2026-10-27 06:56:37",{"source":8,"skillName":5,"commands":39,"prompt":49,"zipAvailable":27},[40,43,46],{"tool":41,"command":42},"npx","npx skills add review-bench\u002Federcnj_ia-dev-environment --skill x-watch-pr-ci",{"tool":44,"command":45},"bunx","bunx skills add review-bench\u002Federcnj_ia-dev-environment --skill x-watch-pr-ci",{"tool":47,"command":48},"pnpm","pnpm dlx skills add review-bench\u002Federcnj_ia-dev-environment --skill x-watch-pr-ci","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment\u002Fblob\u002Fmain\u002Fsrc\u002Ftest\u002Fresources\u002Fgolden\u002Fjava-spring-neo4j\u002F.claude\u002Fskills\u002Fx-watch-pr-ci\u002FSKILL.md",[51],{"path":52,"resourceType":53,"resourceTypeDesc":54,"sizeBytes":55,"sourceHtmlUrl":56},"README.md","REFERENCE","参考文档",3565,"https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment\u002Fblob\u002Fmain\u002Fsrc\u002Ftest\u002Fresources\u002Fgolden\u002Fjava-spring-neo4j\u002F.claude\u002Fskills\u002Fx-watch-pr-ci\u002FREADME.md",[58,65,72,77,84,91],{"skillNo":59,"name":60,"nameZh":6,"description":61,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":62,"categories":63,"keywords":64,"publishedAt":19},"01M757M8J48TQ1Y3X25GSJBXAS","x-parallel-eval","Detects and classifies file-collision risks between work units (tasks, stories, or epic phases) before parallel execution. Reads ## File Footprint blocks produced by x-task-plan \u002F x-story-plan, applies the parallelism-heuristics knowledge pack (hard \u002F regen \u002F soft categories + hotspot overrides), and emits a collision matrix + serialization recommendation in Markdown (default) or JSON.","https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment\u002Fblob\u002Fmain\u002Fsrc\u002Ftest\u002Fresources\u002Fgolden\u002Fx-parallel-eval\u002FSKILL.md",[],[],{"skillNo":66,"name":67,"nameZh":6,"description":68,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":69,"categories":70,"keywords":71,"publishedAt":19},"01M757M57XMR9546S69466HDWT","parallelism-heuristics","Canonical catalog of file-collision heuristics for parallel task, story, and epic execution. Defines the File Footprint block format, the three Conflict Categories (hard \u002F regen \u002F soft), a hotspot list of high-contention paths, and the degrade-with-warning policy. Consumed by x-task-plan, x-story-plan, x-epic-map, x-parallel-eval, and x-dev-*-implement.","https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment\u002Fblob\u002Fmain\u002Fsrc\u002Ftest\u002Fresources\u002Fgolden\u002Fparallelism-heuristics\u002FSKILL.md",[],[],{"skillNo":73,"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":74,"categories":75,"keywords":76,"publishedAt":19},"01M757M11SZ8P1VCQABT08QVYA","https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment\u002Fblob\u002Fmain\u002Fsrc\u002Ftest\u002Fresources\u002Fgolden\u002Fjava-spring\u002Fplatform-claude-code\u002F.claude\u002Fskills\u002Fx-watch-pr-ci\u002FSKILL.md",[],[],{"skillNo":78,"name":79,"nameZh":6,"description":80,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":81,"categories":82,"keywords":83,"publishedAt":19},"01M757KW53K8KFPGADFB4PH949","x-validate-docs","Documentation freshness gate: validates 6 dimensions (readme, api-specs, grpc-proto, adr, skill-docs, system-architecture) against code changes in a PR. Stack-aware — only validates targets declared\u002Fauto-detected from ProjectConfig.documentation.targets. Returns exit non-zero on staleness; produces structured report.","https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment\u002Fblob\u002Fmain\u002Fsrc\u002Ftest\u002Fresources\u002Fgolden\u002Fjava-spring\u002Fplatform-claude-code\u002F.claude\u002Fskills\u002Fx-validate-docs\u002FSKILL.md",[],[],{"skillNo":85,"name":86,"nameZh":6,"description":87,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":88,"categories":89,"keywords":90,"publishedAt":19},"01M757KQZHSA10MKC0CR571D60","x-update-system-architecture","Incrementally updates docs\u002Farchitecture\u002Fsystem.md after an epic completes. Appends new entries to the Decision Log (§11) using x-internal-write-report --append (dedup by ## ID: marker) and surgically inserts component\u002Fintegration changes into sections 1-10 via Edit. Idempotent: re-running with the same epic produces a byte-identical system.md.","https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment\u002Fblob\u002Fmain\u002Fsrc\u002Ftest\u002Fresources\u002Fgolden\u002Fjava-spring\u002Fplatform-claude-code\u002F.claude\u002Fskills\u002Fx-update-system-architecture\u002FSKILL.md",[],[],{"skillNo":92,"name":93,"nameZh":6,"description":94,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":95,"categories":96,"keywords":97,"publishedAt":19},"01M757KKBT5Q1SVF8DMJBVF46M","x-update-architecture","Incrementally updates the service architecture document with changes from architecture plans. Adds new components, integrations, flows, and ADR references without rewriting existing content. Use after implementation to keep architecture documentation current.","https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment\u002Fblob\u002Fmain\u002Fsrc\u002Ftest\u002Fresources\u002Fgolden\u002Fjava-spring\u002Fplatform-claude-code\u002F.claude\u002Fskills\u002Fx-update-architecture\u002FSKILL.md",[],[]]