
Review Pr
Run a structured GitHub pull request review that pulls linked issues and checks the change against approved or repo spec context before you merge.
Install
npx skills add https://github.com/warpdotdev/common-skills --skill review-prWhat is this skill?
- Calls GitHub REST and GraphQL APIs to load PR metadata, timeline, and issue linkage
- Paginates closingIssuesReferences and CONNECTED_EVENT timeline items for full issue context
- Surfaces when no approved or repository spec context exists for the PR (explicit guard message)
- Resolves repo root from OZ_REPO_ROOT or script ancestry for monorepo-aware review runs
- Python CLI-oriented helpers suitable for agent-driven review workflows in Warp common-skills
Adoption & trust: 3.5k installs on skills.sh; 18 GitHub stars; 3/3 security scanners passed (skills.sh audits).
Recommended Skills
Improve Codebase Architecturemattpocock/skills
Zoom Outmattpocock/skills
Caveman Reviewjuliusbrussee/caveman
Requesting Code Reviewobra/superpowers
Receiving Code Reviewobra/superpowers
Request Refactor Planmattpocock/skills
Journey fit
Primary fit
Pull request review is a pre-merge quality gate in the solo builder journey, which maps to the Ship phase rather than Build or Launch. The skill’s core job is reviewing diffs, context, and issue links on an open PR—the canonical Review subphase under Ship.
Common Questions / FAQ
Is Review Pr safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.
SKILL.md
READMESKILL.md - Review Pr
from __future__ import annotations import argparse import base64 import json import os import re import urllib.error import urllib.parse import urllib.request from datetime import datetime, timezone from pathlib import Path from typing import Any API_ROOT = "https://api.github.com" GRAPHQL_ROOT = f"{API_ROOT}/graphql" NO_SPEC_CONTEXT_MESSAGE = "No approved or repository spec context was found for this PR." REPO_ROOT = Path( (os.environ.get("OZ_REPO_ROOT") or "").strip() or Path(__file__).resolve().parents[4] ) _CLOSING_ISSUES_QUERY = ( "query($owner: String!, $name: String!, $number: Int!, $after: String) {" " repository(owner: $owner, name: $name) {" " pullRequest(number: $number) {" " closingIssuesReferences(first: 100, after: $after) {" " pageInfo { hasNextPage endCursor }" " nodes {" " number" " repository { owner { login } name }" " }" " }" " }" " }" " }" ) _MANUAL_LINKED_ISSUES_QUERY = ( "query($owner: String!, $name: String!, $number: Int!, $after: String) {" " repository(owner: $owner, name: $name) {" " pullRequest(number: $number) {" " timelineItems(first: 100, after: $after, itemTypes: [CONNECTED_EVENT, DISCONNECTED_EVENT]) {" " pageInfo { hasNextPage endCursor }" " nodes {" " __typename" " ... on ConnectedEvent {" " subject {" " __typename" " ... on Issue {" " number" " repository { owner { login } name }" " }" " }" " }" " ... on DisconnectedEvent {" " subject {" " __typename" " ... on Issue {" " number" " repository { owner { login } name }" " }" " }" " }" " }" " }" " }" " }" " }" ) def _resolve_token() -> str: token = (os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN") or "").strip() if not token: raise SystemExit( "GH_TOKEN or GITHUB_TOKEN must be set to resolve PR spec context." ) return token def _parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Resolve approved or repository spec context for a pull request." ) parser.add_argument( "--repo", required=True, help="Repository slug in OWNER/REPO format.", ) parser.add_argument( "--pr", type=int, required=True, help="Pull request number to resolve spec context for.", ) return parser.parse_args() def _gh_request( path_or_url: str, *, token: str, accept: str = "application/vnd.github+json", params: dict[str, str] | None = None, method: str = "GET", payload: bytes | None = None, allow_http_error: bool = False, ) -> tuple[int, bytes, dict[str, str]]: url = path_or_url if path_or_url.startswith("https://") else f"{API_ROOT}{path_or_url}" if params: url = f"{url}?{urllib.parse.urlencode(params)}" request = urllib.request.Request(url, data=payload, method=method) # noqa: S310 request.add_header("Authorization", f"Bearer {token}") request.add_header("Accept", accept) request.add_header("X-GitHub-Api-Version", "2022-11-28") request.add_header("User-Agent", "oz-resolve-review-spec-context") if payload is not None: request.add_header("Content-Type", "application/json") try: with urllib.request.urlopen(request) as response: # noqa: S310 return response.status, response.read(), dict(response.headers) except urllib.error.HTTPError as exc: body = exc.read() if exc.fp is not None else b"" if allow_http_error: return exc.code, body, dict(exc.headers or {}) detail = body.decode("utf-8", errors="replace")[:500] raise SystemExit( f"GitHub API request failed ({exc.code}) for {path_or_url}: {detail}" ) from exc def _gh_json( path: str, *, token: str, params: dict[str, str] | None = None, allow_http_error: bool = False, ) -> tuple[int,