Add change detection, structural diff-based impact analysis, configurable-depth incremental recomputation with circular suppression, and impact debt tracking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
96 lines
2.8 KiB
Python
96 lines
2.8 KiB
Python
"""
|
|
Pure diff functions for impact analysis.
|
|
|
|
Implements FR-8.2: Structural diff-based impact measurement.
|
|
|
|
Uses difflib.SequenceMatcher for computing change magnitude between
|
|
old and new content versions.
|
|
"""
|
|
|
|
import difflib
|
|
from typing import Optional
|
|
|
|
|
|
def structural_diff_ratio(old_content: str, new_content: str) -> float:
|
|
"""
|
|
Calculate structural similarity ratio between old and new content.
|
|
|
|
Uses SequenceMatcher to compute a ratio of matching blocks.
|
|
Returns the *change* ratio (1.0 - similarity), so higher values
|
|
mean more change.
|
|
|
|
Args:
|
|
old_content: Previous content
|
|
new_content: Current content
|
|
|
|
Returns:
|
|
Change ratio from 0.0 (identical) to 1.0 (completely different)
|
|
"""
|
|
if old_content == new_content:
|
|
return 0.0
|
|
if not old_content and not new_content:
|
|
return 0.0
|
|
if not old_content or not new_content:
|
|
return 1.0
|
|
|
|
matcher = difflib.SequenceMatcher(None, old_content, new_content)
|
|
similarity = matcher.ratio()
|
|
return 1.0 - similarity
|
|
|
|
|
|
def line_diff_ratio(old_content: str, new_content: str) -> float:
|
|
"""
|
|
Calculate line-level change ratio between old and new content.
|
|
|
|
Splits content into lines and computes SequenceMatcher ratio
|
|
at the line level. Returns the change ratio.
|
|
|
|
Args:
|
|
old_content: Previous content
|
|
new_content: Current content
|
|
|
|
Returns:
|
|
Change ratio from 0.0 (identical) to 1.0 (completely different)
|
|
"""
|
|
if old_content == new_content:
|
|
return 0.0
|
|
if not old_content and not new_content:
|
|
return 0.0
|
|
if not old_content or not new_content:
|
|
return 1.0
|
|
|
|
old_lines = old_content.splitlines()
|
|
new_lines = new_content.splitlines()
|
|
matcher = difflib.SequenceMatcher(None, old_lines, new_lines)
|
|
similarity = matcher.ratio()
|
|
return 1.0 - similarity
|
|
|
|
|
|
def calculate_change_magnitude(
|
|
old_content: Optional[str],
|
|
new_content: Optional[str],
|
|
method: str = "structural",
|
|
) -> float:
|
|
"""
|
|
Calculate the magnitude of a change between two content versions.
|
|
|
|
This is the primary entry point for impact measurement (FR-8.2).
|
|
|
|
Args:
|
|
old_content: Previous content (None for created artifacts)
|
|
new_content: Current content (None for deleted artifacts)
|
|
method: Diff method to use ("structural" or "line")
|
|
|
|
Returns:
|
|
Change magnitude from 0.0 (no change) to 1.0 (complete change)
|
|
"""
|
|
# Handle None cases (creation/deletion)
|
|
if old_content is None and new_content is None:
|
|
return 0.0
|
|
if old_content is None or new_content is None:
|
|
return 1.0
|
|
|
|
if method == "line":
|
|
return line_diff_ratio(old_content, new_content)
|
|
return structural_diff_ratio(old_content, new_content)
|