feat: Autonomous Regression Sentry — verify_impact tool #970
122
tools/verify_tool.py
Normal file
122
tools/verify_tool.py
Normal file
@@ -0,0 +1,122 @@
|
||||
|
||||
#!/usr/bin/env python3
|
||||
"""Impact Analysis Tool - Prevents regressions by identifying affected downstream components."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from tools.registry import registry, tool_error, tool_result
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
VERIFY_IMPACT_SCHEMA = {
|
||||
"name": "verify_impact",
|
||||
"description": "Analyze the impact of your recent changes. Checks for usages of modified functions/classes across the codebase to identify potential regressions. Use this before claiming a task is done to ensure you haven't broken downstream components.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {"type": "string", "description": "Optional: Path to the specific file you want to analyze. If omitted, analyzes all currently staged/modified files in git."},
|
||||
"depth": {"type": "integer", "description": "Search depth for usages (default: 1)", "default": 1}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def analyze_impact(path: str = None, depth: int = 1, task_id: str = "default"):
|
||||
"""Identify downstream usages of modified code elements."""
|
||||
try:
|
||||
# 1. Identify changed files and symbols
|
||||
if path:
|
||||
files_to_check = [path]
|
||||
else:
|
||||
# Use git to find modified files if not specified
|
||||
try:
|
||||
cmd = ["git", "diff", "--name-only", "HEAD"]
|
||||
files_to_check = subprocess.check_output(cmd).decode().splitlines()
|
||||
if not files_to_check:
|
||||
# Try staged files
|
||||
cmd = ["git", "diff", "--cached", "--name-only"]
|
||||
files_to_check = subprocess.check_output(cmd).decode().splitlines()
|
||||
except:
|
||||
return tool_error("Git not available or not a repository. Please specify 'path' explicitly.")
|
||||
|
||||
if not files_to_check:
|
||||
return tool_result(message="No changes detected in git. Try specifying a 'path' if you have uncommitted changes.")
|
||||
|
||||
# 2. Extract potential symbols (functions/classes) from changes
|
||||
# For simplicity, we'll use a heuristic: grep for 'def ' and 'class ' in diffs
|
||||
affected_symbols = set()
|
||||
for f in files_to_check:
|
||||
try:
|
||||
diff_cmd = ["git", "diff", "HEAD", "--", f]
|
||||
diff = subprocess.check_output(diff_cmd).decode()
|
||||
for line in diff.splitlines():
|
||||
if line.startswith("+") and ("def " in line or "class " in line):
|
||||
# Extract name
|
||||
parts = line.split()
|
||||
if len(parts) > 1:
|
||||
name = parts[1].split("(")[0].split(":")[0]
|
||||
affected_symbols.add(name)
|
||||
except:
|
||||
continue
|
||||
|
||||
# 3. Search for these symbols in the codebase (excluding the original files)
|
||||
impact_report = {}
|
||||
for symbol in affected_symbols:
|
||||
if not symbol or len(symbol) < 3: continue
|
||||
|
||||
# Use ripgrep/grep to find usages
|
||||
try:
|
||||
exclude_args = []
|
||||
for f in files_to_check:
|
||||
exclude_args.extend(["--exclude", f])
|
||||
|
||||
# Search for usages
|
||||
search_cmd = ["grep", "-r", "-l", symbol, "."] + exclude_args
|
||||
usages = subprocess.check_output(search_cmd).decode().splitlines()
|
||||
if usages:
|
||||
impact_report[symbol] = usages[:10] # Limit per symbol
|
||||
except subprocess.CalledProcessError:
|
||||
# No matches found
|
||||
continue
|
||||
|
||||
if not impact_report:
|
||||
return tool_result(
|
||||
status="Clean",
|
||||
message="No obvious downstream usages found for modified symbols. Changes appear contained.",
|
||||
files_analyzed=files_to_check
|
||||
)
|
||||
|
||||
summary = (
|
||||
f"IDENTIFIED POTENTIAL IMPACTS:\n"
|
||||
f"You modified {len(affected_symbols)} key symbols. The following files use them and might be affected:\n"
|
||||
)
|
||||
for sym, files in impact_report.items():
|
||||
summary += f"- {sym}: used in {', '.join(files)}\n"
|
||||
|
||||
return tool_result(
|
||||
status="Attention Required",
|
||||
summary=summary,
|
||||
impact_map=impact_report,
|
||||
recommendation="Review the identified files to ensure your changes didn't break their functionality."
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return tool_error(f"Impact analysis failed: {str(e)}")
|
||||
|
||||
def _handle_verify_impact(args, **kw):
|
||||
return analyze_impact(
|
||||
path=args.get("path"),
|
||||
depth=args.get("depth", 1),
|
||||
task_id=kw.get("task_id", "default")
|
||||
)
|
||||
|
||||
registry.register(
|
||||
name="verify_impact",
|
||||
toolset="qa",
|
||||
schema=VERIFY_IMPACT_SCHEMA,
|
||||
handler=_handle_verify_impact,
|
||||
emoji="🛡️"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user