diff --git a/tools/verify_tool.py b/tools/verify_tool.py new file mode 100644 index 000000000..adfe59f1a --- /dev/null +++ b/tools/verify_tool.py @@ -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="🛡️" +) + \ No newline at end of file