# How to Analyze GitHub PR Metadata and Community Impact with Graphify's `prs` Command

> Analyze GitHub PR metadata and community impact with Graphify's prs command. Discover blast radius, check CI status, map worktrees, and detect merge conflicts efficiently.

- Repository: [Graphify Labs/graphify](https://github.com/Graphify-Labs/graphify)
- Tags: how-to-guide
- Published: 2026-07-17

---

**The `graphify prs` command aggregates open pull requests from GitHub, enriches them with CI status and local worktree mappings, and cross-references them against a knowledge graph to calculate community-level blast radius and detect merge conflicts.**

The Graphify open-source tool provides a terminal-native dashboard for reviewing GitHub pull requests through the lens of code architecture. Using the `prs` command, developers can fetch live PR metadata, classify review readiness, and quantify each change's impact on graph communities defined in [`graph.json`](https://github.com/Graphify-Labs/graphify/blob/main/graph.json). This analysis explains how Graphify-Labs/graphify implements this pipeline in [`graphify/prs.py`](https://github.com/Graphify-Labs/graphify/blob/main/graphify/prs.py).

## Fetching PR Metadata from GitHub

Graphify relies on the GitHub CLI (`gh`) rather than direct REST API calls, wrapping it in Python helper functions to ensure consistent JSON output and error handling.

### The `_gh` Helper and `fetch_prs` Implementation

The internal `_gh` function ([lines 41-50](https://github.com/Graphify-Labs/graphify/blob/v8/graphify/prs.py#L41-L50)) executes shell commands with timeout protection and structured logging. Building on this, `fetch_prs` constructs a `gh pr list` query that requests specific JSON fields: `number`, `title`, `headRefName`, `baseRefName`, `author`, `isDraft`, `reviewDecision`, `statusCheckRollup`, and `updatedAt` ([lines 95-124](https://github.com/Graphify-Labs/graphify/blob/v8/graphify/prs.py#L95-L124)).

The command filters for open PRs targeting a specified base branch (defaulting to the repository’s default branch via `_detect_default_branch`), ensuring the dashboard only surfaces actionable items.

### The `PRInfo` Dataclass Structure

Each PR is modeled as a `PRInfo` instance that stores raw metadata alongside derived attributes. The dataclass initializes an `expected_base` field to detect branch mismatches ([lines 154-176](https://github.com/Graphify-Labs/graphify/blob/v8/graphify/prs.py#L154-L176)), which later feeds into the `WRONG-BASE` classification status.

## Classifying Pull Request Status

Beyond raw metadata, Graphify applies business logic to determine review priority through a deterministic classification system.

### Deterministic Status Ordering with `_classify`

The `PRInfo.status` property invokes `_classify`, which evaluates PR attributes against a priority tuple called `_STATUS_ORDER` ([lines 100-119](https://github.com/Graphify-Labs/graphify/blob/v8/graphify/prs.py#L100-L119)). The function checks:

- Branch target alignment (`WRONG-BASE`)
- CI rollup state (`CI-FAIL`)
- Review decisions (`CHANGES-REQ`, `APPROVED`)
- Draft flag (`DRAFT`)
- Age thresholds (`STALE`)
- Pending states

This produces one of eight canonical statuses: `WRONG-BASE`, `CI-FAIL`, `CHANGES-REQ`, `DRAFT`, `STALE`, `PENDING`, `APPROVED`, or `READY`.

### Visual Status Indicators

The `_status_color` function ([lines 122-132](https://github.com/Graphify-Labs/graphify/blob/v8/graphify/prs.py#L122-L132)) maps these classifications to terminal colors for the dashboard, while `_ci_icon` provides Unicode indicators for quick visual scanning of CI health.

## Mapping Local Worktrees to Remote Branches

To bridge the gap between remote PRs and local development environments, `fetch_worktrees` executes `git worktree list --porcelain` and parses the output into a dictionary mapping `branch → worktree_path` ([lines 98-119](https://github.com/Graphify-Labs/graphify/blob/v8/graphify/prs.py#L98-L119)). This mapping is attached to each `PRInfo` instance, allowing the dashboard to display where a PR’s branch exists on the local filesystem.

## Calculating Graph Community Impact

When a [`graph.json`](https://github.com/Graphify-Labs/graphify/blob/main/graph.json) file exists (produced by Graphify’s graph generation commands), the `prs` command can calculate structural blast radius to analyze community impact.

### Loading and Indexing the Graph

The `attach_graph_impact` function first loads the graph JSON via `_load_graph_json`, then builds two indices: `file_to_communities` and `file_to_node_count` ([lines 55-70](https://github.com/Graphify-Labs/graphify/blob/v8/graphify/prs.py#L55-L70)). These mappings allow O(1) lookup of which code communities a file belongs to and how many nodes each file represents.

### Async File Fetching and Path Matching

To avoid blocking on `gh pr diff` calls, `fetch_pr_files` dispatches requests across a thread pool ([lines 71-88](https://github.com/Graphify-Labs/graphify/blob/v8/graphify/prs.py#L71-L88)). For each PR, changed files are matched against the graph index using `_path_match`, which handles substring and relative path resolution.

The `compute_pr_impact` function aggregates these matches into `communities_touched` (unique community IDs) and `nodes_affected` (total node count) ([lines 86-98](https://github.com/Graphify-Labs/graphify/blob/v8/graphify/prs.py#L86-L98)).

### Building Community Labels

Human-readable community names are extracted via `build_community_labels`, which selects the top-N labels (defaulting to 4) from the graph nodes associated with touched communities ([lines 35-45](https://github.com/Graphify-Labs/graphify/blob/v8/graphify/prs.py#L35-L45)).

## Rendering the Interactive Dashboard

The `render_dashboard` function ([lines 8-36](https://github.com/Graphify-Labs/graphify/blob/v8/graphify/prs.py#L8-L36)) prints a concise table of actionable PRs, including:

- Color-coded status indicators
- CI icons
- Age since last update
- Blast radius summary (`nodes_affected / communities_touched`)

A summary footer aggregates counts per status ([lines 38-50](https://github.com/Graphify-Labs/graphify/blob/v8/graphify/prs.py#L38-L50)), giving maintainers an immediate sense of review queue health.

## Detecting Community Conflicts

The `render_conflicts` function ([lines 12-19](https://github.com/Graphify-Labs/graphify/blob/v8/graphify/prs.py#L12-L19)) groups PRs by the communities they touch. When multiple PRs affect the same community, the dashboard flags them as potential merge-order conflicts, displaying the human-readable labels built earlier to clarify which architectural domains are at risk.

## LLM-Powered Triage

When invoked with `--triage`, the command calls `triage_with_opus` to prioritize the review queue. The function constructs a prompt containing each actionable PR’s metadata and blast-radius data, then dispatches it to a configured backend.

Backend resolution follows `_resolve_triage_backend` ([lines 59-81](https://github.com/Graphify-Labs/graphify/blob/v8/graphify/prs.py#L59-L81)), which supports Claude, Kimi, OpenAI, Gemini, Ollama, and local `claude-cli` via environment variables and API key discovery.

## Command Entry Point and CLI Dispatch

The `cmd_prs` function ([lines 75-87](https://github.com/Graphify-Labs/graphify/blob/v8/graphify/prs.py#L75-L87)) serves as the Click command entry point, parsing flags like `--triage`, `--worktrees`, `--conflicts`, `--base`, and `--repo`. It orchestrates the data pipeline: fetching PRs, attaching worktrees, optionally computing graph impact, and dispatching to the appropriate renderer.

## Practical CLI Usage Examples

```bash

# Show high-level dashboard of open PRs (default base = v8 or repo default)

graphify prs

```

```bash

# Inspect single PR with full graph impact (requires graph.json)

graphify prs 42

```

```bash

# Rank review queue using OpenAI backend

GRAPHIFY_TRIAGE_BACKEND=openai graphify prs --triage

```

```bash

# Display local worktree mappings

graphify prs --worktrees

```

```bash

# Identify PRs sharing graph communities (merge-order risk)

graphify prs --conflicts

```

```bash

# Filter to main branch and hide wrong-base PRs

graphify prs --base=main --wrong-base

```

## Summary

- **Graphify** aggregates GitHub PR metadata via the `gh` CLI wrapper `_gh` and `fetch_prs`, storing results in the `PRInfo` dataclass.
- **Classification logic** in `_classify` applies deterministic ordering to label PRs with statuses like `CI-FAIL`, `CHANGES-REQ`, or `READY`, visualized through `_status_color`.
- **Worktree mapping** via `fetch_worktrees` links remote branches to local filesystem paths.
- **Graph impact** calculation uses `attach_graph_impact` to index [`graph.json`](https://github.com/Graphify-Labs/graphify/blob/main/graph.json), fetch PR files concurrently, and compute `communities_touched` and `nodes_affected` via `_path_match`.
- **Conflict detection** groups PRs by shared communities in `render_conflicts`, highlighting merge-order risks.
- **LLM triage** integrates with multiple backends through `_resolve_triage_backend` to prioritize reviews based on blast radius.

## Frequently Asked Questions

### How does Graphify handle large repositories with many open PRs?

Graphify uses thread pools in `fetch_pr_files` to parallelize `gh pr diff` calls, preventing network bottlenecks when analyzing file changes across dozens of pull requests ([lines 71-88](https://github.com/Graphify-Labs/graphify/blob/v8/graphify/prs.py#L71-L88)). Additionally, the `check_graph_file_size_cap` function in [`graphify/security.py`](https://github.com/Graphify-Labs/graphify/blob/main/graphify/security.py) enforces size limits on [`graph.json`](https://github.com/Graphify-Labs/graphify/blob/main/graph.json) to prevent memory issues during impact analysis.

### What GitHub permissions are required for the `prs` command?

The command requires the `gh` CLI to be authenticated with permissions to read pull requests and repository contents. For private repositories, the token needs `repo` scope to access PR metadata, review decisions, and CI status check rollups via the `gh pr list` and `gh pr diff` commands.

### Can I use the `prs` command without a generated graph?

Yes. The dashboard functions fully without [`graph.json`](https://github.com/Graphify-Labs/graphify/blob/main/graph.json), displaying PR metadata, CI status, and worktree mappings. Graph community impact analysis, conflict detection, and blast radius calculations only activate when a valid graph file exists and the user requests deep inspection via specific flags or single-PR lookup.

### How does the classification system determine if a PR is "STALE"?

The `_classify` function checks the `updatedAt` timestamp against configurable age thresholds within the `PRInfo` logic. If a PR exceeds the staleness threshold (typically days since last update) and hasn't reached a terminal state like `APPROVED` or `MERGED`, it receives the `STALE` status to alert maintainers to potentially abandoned work ([lines 100-119](https://github.com/Graphify-Labs/graphify/blob/v8/graphify/prs.py#L100-L119)).