Spaces:
Sleeping
Sleeping
| """ | |
| core/tools.py — Static analysis tools (zero LLM API calls). | |
| """ | |
| import ast | |
| import json | |
| import os | |
| import subprocess | |
| import tempfile | |
| from langchain_core.tools import tool | |
| from radon.complexity import cc_rank, cc_visit | |
| def flake8_tool(code: str) -> str: | |
| """Runs flake8 linter on Python code. Returns violations as JSON.""" | |
| with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: | |
| f.write(code) | |
| tmp_path = f.name | |
| try: | |
| result = subprocess.run( | |
| ["flake8", "--format=%(row)d::%(col)d::%(code)s::%(text)s", tmp_path], | |
| capture_output=True, text=True, | |
| ) | |
| violations = [] | |
| for line in result.stdout.strip().splitlines(): | |
| parts = line.split("::") | |
| if len(parts) == 4: | |
| violations.append({ | |
| "line": int(parts[0].split(":")[-1]), | |
| "col": int(parts[1]), | |
| "code": parts[2], | |
| "message": parts[3], | |
| }) | |
| return json.dumps(violations) | |
| finally: | |
| os.unlink(tmp_path) | |
| def radon_tool(code: str) -> str: | |
| """Analyzes cyclomatic complexity. Returns per-function scores as JSON.""" | |
| try: | |
| return json.dumps([ | |
| { | |
| "name": b.name, | |
| "line": b.lineno, | |
| "complexity": b.complexity, | |
| "rank": cc_rank(b.complexity), | |
| "is_high_risk": b.complexity > 5, | |
| } | |
| for b in cc_visit(code) | |
| ]) | |
| except Exception as exc: | |
| return json.dumps({"error": str(exc)}) | |
| def ast_tool(code: str) -> str: | |
| """Parses Python AST. Returns functions, classes, imports as JSON.""" | |
| try: | |
| tree = ast.parse(code) | |
| functions, classes, imports = [], [], [] | |
| for node in ast.walk(tree): | |
| if isinstance(node, ast.FunctionDef): | |
| functions.append({ | |
| "name": node.name, | |
| "line": node.lineno, | |
| "args": [a.arg for a in node.args.args], | |
| "has_docstring": ast.get_docstring(node) is not None, | |
| }) | |
| elif isinstance(node, ast.ClassDef): | |
| classes.append({ | |
| "name": node.name, | |
| "line": node.lineno, | |
| "has_docstring": ast.get_docstring(node) is not None, | |
| }) | |
| elif isinstance(node, ast.Import): | |
| for alias in node.names: | |
| imports.append(alias.name) | |
| elif isinstance(node, ast.ImportFrom): | |
| imports.append( | |
| f"from {node.module} import {', '.join(a.name for a in node.names)}" | |
| ) | |
| return json.dumps({"functions": functions, "classes": classes, "imports": imports}) | |
| except SyntaxError as exc: | |
| return json.dumps({"error": str(exc)}) | |
| TOOLS = [flake8_tool, radon_tool, ast_tool] | |