Direction:PullRequest → Issue Cardinality: N:M (a PR can close multiple issues, an issue can be closed by multiple PRs)
Example Query
MATCH (pr:PullRequest {repo: "alice/backend"})-[:CLOSES]->(i:Issue)RETURN pr.number, pr.title, collect(i.number) AS closed_issuesORDER BY pr.number DESCLIMIT 10
Use case: List recent PRs and the issues they closed.
-- Repository: unique by full_nameCREATE CONSTRAINT repo_full_name IF NOT EXISTSFOR (r:Repository) REQUIRE r.full_name IS UNIQUE;-- Developer: unique by loginCREATE CONSTRAINT dev_login IF NOT EXISTSFOR (d:Developer) REQUIRE d.login IS UNIQUE;-- PullRequest: unique by (repo, number)CREATE CONSTRAINT pr_repo_number IF NOT EXISTSFOR (pr:PullRequest) REQUIRE (pr.repo, pr.number) IS UNIQUE;-- Issue: unique by (repo, number)CREATE CONSTRAINT issue_repo_number IF NOT EXISTSFOR (i:Issue) REQUIRE (i.repo, i.number) IS UNIQUE;-- File: unique by (repo, path)CREATE CONSTRAINT file_repo_path IF NOT EXISTSFOR (f:File) REQUIRE (f.repo, f.path) IS UNIQUE;
Indexes
-- Index on File.language for faster filteringCREATE INDEX file_language IF NOT EXISTSFOR (f:File) ON (f.language);-- Index on PullRequest.verdict for analyticsCREATE INDEX pr_verdict IF NOT EXISTSFOR (pr:PullRequest) ON (pr.verdict);
Find past PRs that touched the same files (structural similarity).
UNWIND $paths AS pathMATCH (pr:PullRequest {repo: $repo})-[:TOUCHES]->(f:File {repo: $repo, path: path})WHERE ($exclude IS NULL OR pr.number <> $exclude) AND pr.verdict IS NOT NULLWITH pr, count(DISTINCT f) AS overlapORDER BY overlap DESCLIMIT $top_kRETURN pr.number AS number, pr.title AS title, pr.author AS author, pr.verdict AS verdict, overlap
Example (Python)
related = await graph_builder.get_related_prs( repo_full_name="alice/backend", file_paths=["app/auth/jwt_utils.py", "app/auth/dependencies.py"], exclude_pr=42, # Don't include current PR top_k=5,)# [{"number": 38, "title": "Add JWT refresh", "author": "bob", "verdict": "APPROVE", "overlap": 2}, ...]
Find files touched by the most PRs (high churn → high importance or fragility).
MATCH (pr:PullRequest {repo: $repo})-[:TOUCHES]->(f:File)RETURN f.path AS path, f.language AS language, count(pr) AS pr_countORDER BY pr_count DESCLIMIT $limit
Find files repeatedly flagged in PRs with REQUEST_CHANGES verdict.
MATCH (pr:PullRequest {repo: $repo, verdict: "REQUEST_CHANGES"})-[:TOUCHES]->(f:File)RETURN f.path AS path, f.language AS language, count(pr) AS risk_countORDER BY risk_count DESCLIMIT $limit
For each heavily-touched file, who is the dominant contributor?
MATCH (pr:PullRequest {repo: $repo})-[:AUTHORED_BY]->(d:Developer), (pr)-[:TOUCHES]->(f:File)WITH f.path AS path, d.login AS dev, count(*) AS touchesORDER BY path, touches DESCWITH path, collect({dev: dev, touches: touches})[0] AS top_owner, sum(touches) AS total_touchesWHERE total_touches >= 2RETURN path, top_owner.dev AS owner, top_owner.touches AS owner_touches, total_touchesORDER BY total_touches DESCLIMIT $limit