pr-status · v1.12.0 · 2026-07-29 · sha256 632c832f929c3f67

pr-status v1.12.0A

Immutable. This exact content is served forever at /api/v1/blob/632c832f929c3f67.

---
name: pr-status
description: Show enriched status of your open PRs — CI checks, approvals, unresolved review threads, and linked Jira discussion, with transition-driven suggested next actions.
allowed-tools: "Bash(~/.agents/skills/pr-status/scripts/gh-pr-list-open.sh:*), Bash(~/.agents/skills/pr-status/scripts/gh-pr-list-closed.sh:*), Bash(~/.agents/skills/pr-status/scripts/gh-pr-details.sh:*), Bash(~/.agents/skills/pr-status/scripts/gh-pr-checks.sh:*), Bash(~/.agents/skills/pr-status/scripts/gh-pr-reviews.sh:*), Bash(~/.agents/skills/pr-status/scripts/gh-pr-threads.sh:*), Bash(~/.agents/skills/pr-status/scripts/gh-pr-merge-state.sh:*), Bash(gh pr list:*), Bash(gh pr checks:*), Bash(gh pr view:*), Bash(gh api:*), Bash(gh search:*), Bash(date:*), mcp__jira__jira_get"
model-tier: standard
model: sonnet
effort: medium
version: "1.12.0"
author: "flurdy"
---

# PR Status

Show enriched status for all open PRs created by you across your GitHub org: CI checks, approvals, unresolved review threads, and linked Jira discussion. Also shows recently closed PRs.

The GitHub org is auto-detected from the current repo's `origin` remote, falling back to the most common org across a multi-repo workspace's member repos when the cwd repo has no GitHub remote of its own (a workspace root is often deliberately local-only). Override via the `PR_STATUS_ORG` env var or a first positional argument. When nothing resolves the scripts exit non-zero with an explanatory message rather than returning an empty list.

**The output of this skill is the rendered dashboard** (steps 3–6). Fetching without rendering is not a valid run.

## Usage

```
/pr-status
```

## Instructions

Re-run the fetch scripts and `date` on every invocation, even seconds after the last one — never reuse earlier output or extrapolate the timestamp.

### 1. Fetch

Open PRs (org-wide), one JSON object per line `{number, title, owner, repo}`:

```bash
~/.agents/skills/pr-status/scripts/gh-pr-list-open.sh
```

Recently closed PRs (3-day lookback, 4 on Tuesdays; optional second arg overrides), one JSON object per line `{number, title, owner, repo, closedAt}`:

```bash
~/.agents/skills/pr-status/scripts/gh-pr-list-closed.sh
```

### 2. Fetch PR details

Group the PRs (open and closed) by `owner/repo`; one batch GraphQL call per group:

```bash
~/.agents/skills/pr-status/scripts/gh-pr-details.sh {owner} {repo} {number1} {number2} ...
```

Output is a JSON array, one object per PR:

```json
[
  {
    "number": 123,
    "base": "main",
    "isDraft": false,
    "mergeState": "CLEAN",
    "reviewDecision": "APPROVED",
    "approvers": ["alice"],
    "unresolvedThreads": 2,
    "checksState": "SUCCESS",
    "lastPush": "2026-04-15T09:30:00Z",
    "mergeCommitSha": "abc123...",
    "mergeCommitAt": "2026-04-15T10:00:00Z",
    "mainChecksState": "SUCCESS",
    "readyAt": "2026-04-14T15:32:11Z"
  }
]
```

Map `checksState` values: `SUCCESS` → ✅ / `FAILURE` or `ERROR` → ❌ / `PENDING` or `EXPECTED` → ⏳ / null → `—`

If the batch script is unavailable, the per-PR scripts in the same directory (`gh-pr-checks.sh`, `gh-pr-reviews.sh`, `gh-pr-threads.sh`, `gh-pr-merge-state.sh`) or plain `gh pr view/checks` cover the same fields.

### 3. Fetch linked Jira discussion

For every distinct Jira key found while rendering (open PRs: branch first, then title; recently closed PRs: title; using `/[A-Z]+-\d+/`), fetch its newest Jira comment. Include both open and recently closed PRs when they carry a ticket key; fetch each key once, even if multiple PRs use it. Calls may run in parallel:

```
mcp__jira__jira_get
  path: /rest/api/3/issue/{key}/comment
  queryParams:
    orderBy: -created
    maxResults: 1
  jq: '{total: .total, latest: (.comments[0] // null | if . == null then null else {author: .author.displayName, accountId: .author.accountId, created: .created} end)}'
```

For a key with comments, render `💬 {total} · {latest author first name or @accountId} · {relative age}`. Render `—` for zero comments and `?` if its lookup fails. Do not show comment bodies in this status dashboard. Jira comment failures are non-fatal: render all PR data and add `_Some linked Jira discussion could not be fetched._` beneath the affected table(s).

### 4. Render as tables

Before the tables, output a timestamp line: `_Checked at HH:MM:SS_` in **local** time, 24h (`date '+%H:%M:%S'`, not `date -u`). Relative-time math against `lastPush` / `readyAt` / `closedAt` works in UTC since those are `Z`-suffixed; only the displayed timestamp is local.

**Recently closed** — render first, one table with a **Repo** column. Skip the section if the closed list is empty.

#### Recently closed

| PR | Repo | Ticket | Jira 💬 | Title | Status | CI | Ready | Wait | Closed |
|----|------|--------|---------|-------|--------|----|-------|------|--------|

- **PR**: markdown link `[#123](https://github.com/{owner}/{repo}/pull/123)`
- **Repo**: repository name
- **Ticket**: Jira ID matched by `/[A-Z]+-\d+/` against the PR title, or `—`
- **Status**: 🔀 merged / 🗑️ closed unmerged — emoji only
- **CI**: post-merge checks on the merge commit (`mainChecksState`), only if merged and `mergeCommitAt` is within the last 2 days — otherwise `—`
- **Ready**: relative time since `readyAt` (short units: `Nm`, `Nh`, `Nd`)
- **Wait**: `closedAt - readyAt` — how long the PR waited for review/merge
- **Closed**: relative time since close

**Open PRs** — render after closed, grouped by repo: heading `#### Open — {repo}` then a table per repo that has open PRs.

| PR | Ticket | Jira 💬 | Title | Branch | Target | Ready | Push | Sync | CI | Approved | Threads | LGTM |
|----|--------|---------|-------|--------|--------|-------|------|------|----|----------|---------|------|

- **PR**: markdown link as above
- **Ticket**: Jira ID matched against branch name first, then title, or `—`
- **Branch**: head branch minus conventional-commit prefix and ticket prefix (`feat/AB-1107-cta-clicked-event` → `cta-clicked-event`); truncate past ~30 chars with `…`
- **Target**: base branch. `main`/`master` are default branches — plain text. Anything else is a stacked PR: prefix with 📌
- **Sync**: from `mergeState` when base is `main`/`master`: `CLEAN`/`UNSTABLE` → ✅, `BEHIND` → ⚠️ behind, `DIRTY` → 💥 conflict, other → `—`. Stacked PRs (📌) → `—`
- **CI**: ✅ / ❌ / ⏳ — emoji only
- **Ready**: 🚧 if `isDraft`, else relative time since `readyAt`
- **Push**: relative time since `lastPush`
- **Approved**: one ✅ per approver when `reviewDecision` is `APPROVED`. `REVIEW_REQUIRED` with approvers → stale approvals, one ☑️ each. `CHANGES_REQUESTED` → 👎. No approvers, no changes requested → 🔔 (awaiting review; `—` for drafts)
- **Threads**: `💬 N` if N > 0, else `—`
- **LGTM**: 🚀 if not draft, `APPROVED`, CI `SUCCESS`, sync ✅, zero threads, and `mergeState` `CLEAN`; else 🚧

Truncate titles: ~50 chars in the closed table, ~25 in the open table (13 columns — wide rows break Claude Code's table renderer).

### 5. Summarise changes

After the tables, list deltas since the last check in this session as bullets (e.g. `#6142 CI: ❌ → ✅`). Treat an increase in a linked ticket's comment total as a delta, e.g. `AB-649 Jira discussion: 2 → 3 (Jane, 40m)`. Do not report a count decrease as a discussion update. Otherwise say "No changes." Render both tables in full either way — the point of repeated checks is current state at a glance.

### 6. Suggest next actions (transition-driven)

Surface a **Suggested actions** bullet list — copy-pasteable commands for PRs that *just became* actionable this tick. Read-only: point at commands, never run them.

Fire on transitions, not standing state: on the first check of a session list the currently-actionable PRs as baseline; on later ticks list a PR only when it crosses into an actionable state.

| Transition this tick | Suggested command |
|---|---|
| → 🚀 LGTM (newly mergeable) | `/ready-to-merge {n}` |
| unresolved threads increased, or → 👎 changes requested | `/review-comments {n}` |
| → ⚠️ behind (fell behind base) | `/rebase-main` (on that PR's branch) |
| → 🔔 awaiting review (no longer draft, still no reviewers) | `/request-review` |

Order most actionable first (🚀 → 💬 → ⚠️ → 🔔); omit the section when no PR changed state.

For a PR whose unresolved-thread count increased this tick (only those), fetch who commented and a ~80-char gist for the bullet:

```bash
gh api graphql -f query='
query($owner:String!,$repo:String!,$pr:Int!){
  repository(owner:$owner,name:$repo){
    pullRequest(number:$pr){
      reviewThreads(last:10){
        nodes{ isResolved comments(last:1){ nodes{ author{login} body } } }
      }
    }
  }
}' -f owner="{owner}" -f repo="{repo}" -F pr={number} \
  --jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false) | .comments.nodes[0] | {author: .author.login, gist: (.body | .[0:80])}]'
```

If the fetch fails, fall back to the bare count (`💬 N new`).

### 7. Next-tick recommendation

End with one cadence line for `/watch-prs` to pace from (harmless on a one-shot run):

```
next-tick: {hot|warm|cold} (~{N}s) — {reason}
```

- **hot (~180s)** — CI ⏳ on any open non-draft PR, a push in the last ~5 min, or a transition this tick
- **warm (~600s)** — open non-draft PRs awaiting review or carrying threads, nothing in flight
- **cold (1200 → 1800s)** — nothing actionable soon; escalate 1200 → 1500 → 1800 across consecutive cold ticks, reset on any non-cold tick

If the fetch failed, emit `next-tick: warm (~600s) — incomplete fetch`. Keep the reason to a few words; no other pacing commentary.

If the invoking prompt asks you to reschedule via `ScheduleWakeup`, do that only after everything above is printed — the turn ends when it returns.