How detect_changes Computes Risk Scores for Code Modifications
The detect_changes module in tirth8205/code-review-graph calculates risk scores by aggregating six weighted factors—including flow participation, community crossing, test coverage, and security sensitivity—into a normalized 0.0 to 1.0 value using the compute_risk_score function.
The detect_changes functionality within the tirth8205/code-review-graph repository provides automated risk assessment for code reviews by analyzing graph-based relationships between modified functions and their architectural context. When a pull request modifies specific files, the system maps these changes to graph nodes and evaluates how dangerous each modification might be to system stability. This risk scoring mechanism enables reviewers to prioritize their attention on the most critical changes first.
The Core Risk Calculation Logic
The risk scoring implementation resides in code_review_graph/changes.py, specifically within the compute_risk_score function (approximately lines 30–74). This function aggregates multiple architectural and semantic signals, each contributing a capped weighted value to the final score.
Flow Participation Factor
The algorithm first examines whether the modified node participates in critical system flows. It retrieves flow criticalities via store.get_flow_criticalities_for_node:
- If criticality values exist, their sum is added (capped at 0.25)
- If no criticalities are defined, the count of flow memberships is multiplied by 0.05 (also capped at 0.25)
This ensures that nodes touching multiple critical paths receive higher risk weighting without exceeding the quarter-point threshold.
Community Crossing Impact
Crossing architectural boundaries increases risk. The function counts callers belonging to different communities than the target node using store.get_node_community_id and store.get_community_ids_by_qualified_names:
- Each community crossing adds 0.05 to the score
- Maximum contribution capped at 0.15
This penalizes changes that act as integration points between distinct system modules.
Test Coverage Penalty
The system evaluates testing safety nets via store.get_transitive_tests:
- 0 tests: Contributes 0.30 (high risk)
- Each test reduces penalty by 0.05
- 5+ tests: Contributes 0.05 (minimum risk floor)
This inverse relationship ensures untested code receives significantly higher risk scores than well-tested components.
Security Sensitivity Check
The function scans for security-critical identifiers:
- Checks if
node.nameornode.qualified_namecontains keywords fromSECURITY_KEYWORDS - Match adds 0.20 to the total score
This static analysis layer catches modifications to encryption, authentication, or authorization routines regardless of graph topology.
Caller Count Weight
Dependency popularity influences risk through store.get_edges_by_target:
- Contribution equals
min(caller_count / 20, 0.10) - Maximum contribution: 0.10
Widely called utilities receive higher scores than isolated internal functions.
Optional Change Frequency (Churn)
When include_churn=True, the algorithm incorporates historical modification data:
- Looks up commit counts touching the node's file via
churn_counts - Formula:
min(commit_count / _CHURN_SATURATION, 1.0) * _CHURN_WEIGHT - Maximum contribution approximately 0.15
Frequently modified files indicate unstable code requiring additional scrutiny.
Score Normalization and Output
After summing all factors, compute_risk_score clamps and formats the result:
return round(min(max(score, 0.0), 1.0), 4)
This guarantees all scores fall within the 0.0 – 1.0 range with four-decimal precision, enabling consistent comparison across different node types and project sizes.
Practical Usage Examples
Calculate risk for a single modified function:
from code_review_graph.changes import compute_risk_score
from code_review_graph.graph import GraphStore
store = GraphStore.load("/path/to/graph")
node = store.get_node_by_qualified_name("myproject.utils.encrypt_data")
risk = compute_risk_score(store, node)
print(f"Risk score: {risk}") # Output: 0.73
Execute full PR analysis with churn data:
from code_review_graph.changes import analyze_changes
result = analyze_changes(
store,
changed_files=["src/crypto.py", "src/auth.py"],
repo_root="/path/to/repo",
include_churn=True
)
for item in result["review_priorities"]:
print(f"{item['qualified_name']}: {item['risk_score']}")
Integration with the Analysis Pipeline
The analyze_changes function orchestrates the complete workflow defined in code_review_graph/changes.py. It:
- Parses diffs to identify changed nodes
- Invokes
compute_risk_scorefor each modified function/class - Complements scores with flow-impact data from
code_review_graph/flows.pyviaget_affected_flows - Returns the top-10 highest-risk nodes as review priorities
Supporting infrastructure in code_review_graph/graph.py provides the GraphStore APIs—get_flow_criticalities_for_node, get_transitive_tests, and get_edges_by_target—that supply the raw metrics feeding into the risk algorithm.
Summary
- Six weighted factors determine risk: flow participation (max 0.25), community crossing (max 0.15), test coverage (0.05–0.30), security sensitivity (0.20), caller count (max 0.10), and optional churn (max ~0.15)
- Normalization ensures scores always fall between 0.0 and 1.0 with four-decimal precision
- Primary implementation lives in
code_review_graph/changes.pywithin thecompute_risk_scorefunction - Integration occurs through
analyze_changes, which processes PR diffs and returns prioritized review queues - Dependencies on
GraphStoreAPIs provide architectural context for community detection, flow membership, and test coverage analysis
Frequently Asked Questions
What is the maximum possible risk score detect_changes can assign?
The maximum risk score is 1.0. While the sum of all weighted factors could theoretically exceed this value, the compute_risk_score function explicitly clamps the result using min(max(score, 0.0), 1.0) before returning. This normalization ensures consistent scoring boundaries across different codebases and change types.
How does detect_changes penalize code with no test coverage?
Nodes with zero transitive test dependencies receive a 0.30 penalty—the largest single factor in the scoring algorithm. Each detected test reduces this penalty by 0.05, bottoming out at 0.05 for five or more tests. This design reflects the architectural risk of modifying untested code paths.
Where is the risk scoring logic physically implemented?
The core algorithm resides in code_review_graph/changes.py within the compute_risk_score function, specifically between lines 30 and 74. This module also contains the analyze_changes orchestration function and churn calculation utilities that feed data into the scoring system.
Can detect_changes incorporate git history into risk calculations?
Yes, when the include_churn parameter is set to True in analyze_changes, the system looks up file modification counts from churn_counts and adds a weighted factor based on change frequency. This optional churn data adds up to approximately 0.15 additional risk points for frequently modified files, helping identify unstable code hotspots.
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 →