It is difficult to know how much of a large software project will break or change when you modify a single piece of code. This makes it hard to gauge the risk of making updates.
It analyzes a file to calculate a 'Blast Radius' score by tracking how far a change spreads through the code and identifying where it hits critical bottlenecks. It provides a clear measurement of the reach of a specific update.
It allows developers to see the ripple effect of their changes before they cause unexpected problems elsewhere.
It was run inside an isolated container with no network access. This is the exact command and the real output it produced — captured process output, not written by a model.
$ python3 blast_radius.py Usage: python blast_radius.py <file_to_analyze.py> [changed_function]
A screenshot of that run.
A clean run proves this does what is shown above, in a CPU-only sandbox. It is a small research demo — not a production tool, and nothing here was published anywhere.
All of it — 102 lines, one file, standard library only.
import ast
from collections import defaultdict, deque
class CallGraph:
def __init__(self):
self.graph = defaultdict(list)
self.reverse_graph = defaultdict(list)
self.functions = {}
self.entry_points = set()
def add_call(self, caller, callee):
self.graph[caller].append(callee)
self.reverse_graph[callee].append(caller)
def parse_file(self, path):
with open(path, "r") as f:
tree = ast.parse(f.read())
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
self.functions[node.name] = node
if node.name in ['main', 'lambda']:
self.entry_points.add(node.name)
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
caller = self._current_scope(node)
callee = node.func.id
if caller and callee:
self.add_call(caller, callee)
def _current_scope(self, node):
# Simplified scope detection
for parent in ast.iter_parents(node):
if isinstance(parent, ast.FunctionDef):
return parent.name
return None
def shortest_path(self, start, end):
# BFS for shortest path
queue = deque([start])
visited = set()
dist = {start: 0}
while queue:
current = queue.popleft()
if current == end:
return dist[current]
visited.add(current)
for neighbor in self.graph.get(current, []):
if neighbor not in visited:
dist[neighbor] = dist[current] + 1
queue.append(neighbor)
return float('inf')
class BlastRadiusCalculator:
def __init__(self, graph):
self.graph = graph
self._calculate_connectivity()
def _calculate_connectivity(self):
self.connectivity = defaultdict(float)
for node in self.graph.graph:
shortest_paths = [self.graph.shortest_path(node, other) for other in self.graph.graph if other != node]
self.connectivity[node] = sum(1 for p in shortest_paths if p != float('inf')) / len(shortest_paths)
def blast_radius(self, changed_function):
# Impact Analysis: Find all reachable nodes from changed function
affected = set()
queue = deque([changed_function])
while queue:
current = queue.popleft()
affected.add(current)
for neighbor in self.graph.graph.get(current, []):
if neighbor not in affected:
queue.append(neighbor)
# Graph-Path Reachability Score: Calculate average shortest path from entry points
entry_paths = [self.graph.shortest_path(ep, changed_function) for ep in self.graph.entry_points]
average_depth = sum(p for p in entry_paths if p != float('inf')) / len([p for p in entry_points if p != float('inf')])
# Blast Radius = Impact Size * Normalized Depth * Connectivity
impact_size = len(affected)
normalized_depth = average_depth / (1 + max(self.graph.graph.values())) if self.graph.graph else 0
return impact_size * normalized_depth * self.connectivity.get(changed_function, 0.5)
if __name__ == '__main__':
import sys
if len(sys.argv) < 2:
print('Usage: python blast_radius.py <file_to_analyze.py> [changed_function]')
sys.exit(1)
graph = CallGraph()
graph.parse_file(sys.argv[1])
changed_function = sys.argv[2] if len(sys.argv) > 2 else 'main'
calculator = BlastRadiusCalculator(graph)
score = calculator.blast_radius(changed_function)
print(f'Blast Radius Score for {changed_function}: {score:.2f}')