How to Analyze GitHub PR Metadata and Community Impact with Graphify's `prs` Command
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. This analysis explains how Graphify-Labs/graphify implements this pipeline in 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) 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).
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), 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). 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) 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). 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 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). 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). 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).
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).
Rendering the Interactive Dashboard
The render_dashboard function (lines 8-36) 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), giving maintainers an immediate sense of review queue health.
Detecting Community Conflicts
The render_conflicts function (lines 12-19) 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), 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) 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
# Show high-level dashboard of open PRs (default base = v8 or repo default)
graphify prs
# Inspect single PR with full graph impact (requires graph.json)
graphify prs 42
# Rank review queue using OpenAI backend
GRAPHIFY_TRIAGE_BACKEND=openai graphify prs --triage
# Display local worktree mappings
graphify prs --worktrees
# Identify PRs sharing graph communities (merge-order risk)
graphify prs --conflicts
# Filter to main branch and hide wrong-base PRs
graphify prs --base=main --wrong-base
Summary
- Graphify aggregates GitHub PR metadata via the
ghCLI wrapper_ghandfetch_prs, storing results in thePRInfodataclass. - Classification logic in
_classifyapplies deterministic ordering to label PRs with statuses likeCI-FAIL,CHANGES-REQ, orREADY, visualized through_status_color. - Worktree mapping via
fetch_worktreeslinks remote branches to local filesystem paths. - Graph impact calculation uses
attach_graph_impactto indexgraph.json, fetch PR files concurrently, and computecommunities_touchedandnodes_affectedvia_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_backendto 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). Additionally, the check_graph_file_size_cap function in graphify/security.py enforces size limits on 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, 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).
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →