feat(prompts): implement Phase 6 - Incremental Execution (FR-7, FR-8)
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>
This commit is contained in:
95
markitect/prompts/incremental/metrics.py
Normal file
95
markitect/prompts/incremental/metrics.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user