Spaces:
Running
Running
| """ | |
| Radon Complexity Analysis Tool | |
| ================================ | |
| Radon measures cyclomatic complexity — the number of independent execution paths | |
| through a function. Higher complexity = more branches = harder to test and maintain, | |
| AND often correlates with performance issues (deeply nested conditionals often | |
| indicate O(n²) or worse algorithms). | |
| Complexity grades: | |
| A (1-5): Simple, low risk | |
| B (6-10): Moderate complexity | |
| C (11-15): High complexity — consider refactoring | |
| D (16-20): Very high — likely performance and maintenance issues | |
| E (21-25): Extremely complex | |
| F (26+): Unmaintainable | |
| We report functions with complexity grade C or worse (>10) to the Performance Agent. | |
| The agent uses this as a signal to look deeper at those functions for algorithmic issues. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import subprocess | |
| import tempfile | |
| from pathlib import Path | |
| import structlog | |
| logger = structlog.get_logger() | |
| async def run_radon(file_contents: dict[str, str]) -> str: | |
| """ | |
| Run radon cyclomatic complexity analysis on Python files. | |
| Returns a formatted string summarizing high-complexity functions. | |
| """ | |
| python_files = { | |
| path: content | |
| for path, content in file_contents.items() | |
| if path.endswith(".py") | |
| } | |
| if not python_files: | |
| return "" | |
| try: | |
| with tempfile.TemporaryDirectory(prefix="ninjacg_radon_") as tmpdir: | |
| tmpdir_path = Path(tmpdir) | |
| for filepath, content in python_files.items(): | |
| file_path = tmpdir_path / filepath | |
| file_path.parent.mkdir(parents=True, exist_ok=True) | |
| file_path.write_text(content, encoding="utf-8") | |
| # Run radon cc (cyclomatic complexity) with JSON output | |
| # -j: JSON output | |
| # -n C: only show grade C or worse (complexity > 10) | |
| result = subprocess.run( | |
| ["radon", "cc", "-j", "-n", "C", str(tmpdir_path)], | |
| capture_output=True, | |
| text=True, | |
| timeout=30, | |
| ) | |
| if not result.stdout.strip() or result.stdout.strip() == "{}": | |
| return "" | |
| radon_output = json.loads(result.stdout) | |
| # Collect high-complexity functions | |
| findings = [] | |
| for file_path, functions in radon_output.items(): | |
| try: | |
| relative = str(Path(file_path).relative_to(tmpdir)).replace("\\", "/") | |
| except ValueError: | |
| relative = Path(file_path).name | |
| for func in functions: | |
| if not isinstance(func, dict): | |
| continue | |
| name = func.get("name", "unknown") | |
| complexity = func.get("complexity", 0) | |
| rank = func.get("rank", "?") | |
| lineno = func.get("lineno", 0) | |
| findings.append( | |
| f"- {relative}:{lineno} — `{name}()` complexity={complexity} (grade {rank})" | |
| ) | |
| if not findings: | |
| return "" | |
| summary = ( | |
| f"Radon complexity analysis found {len(findings)} high-complexity function(s):\n" | |
| + "\n".join(findings) | |
| ) | |
| logger.info("Radon analysis complete", high_complexity_count=len(findings)) | |
| return summary | |
| except FileNotFoundError: | |
| logger.warning("radon not found in PATH — skipping complexity analysis") | |
| return "" | |
| except Exception as e: | |
| logger.warning("Radon analysis failed", error=str(e)) | |
| return "" | |