Spaces:
Running
Running
File size: 3,648 Bytes
4b445f6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | """
Linter Tool (Ruff)
===================
Ruff is an extremely fast Python linter written in Rust. It replaces
flake8, isort, pycodestyle, and dozens of other tools in a single binary.
It runs 10-100x faster than traditional Python linters.
What Ruff catches:
- Unused imports (F401)
- Undefined names (F821)
- Unused variables (F841)
- Import ordering issues (I001)
- Unnecessary f-strings (F541)
- Bare except clauses (E722)
- And 800+ other rules
We run Ruff on the changed files and feed the output to the Style Agent
as additional context. The LLM then combines Ruff's mechanical findings
with its own understanding of readability and maintainability.
"""
from __future__ import annotations
import json
import subprocess
import tempfile
from pathlib import Path
import structlog
logger = structlog.get_logger()
async def run_ruff(file_contents: dict[str, str]) -> str:
"""
Run Ruff linter on Python files.
Returns a formatted string of linting issues.
"""
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_ruff_") 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 ruff check with JSON output
# --output-format json: machine-parseable output
# --select ALL: enable all rules (we want comprehensive feedback)
# --ignore E501: skip line-length (too noisy, not actionable)
result = subprocess.run(
[
"ruff", "check",
str(tmpdir_path),
"--output-format", "json",
"--select", "F,E,W,I,N,UP,B,A,SIM,RET,ARG",
"--ignore", "E501,E402",
],
capture_output=True,
text=True,
timeout=30,
)
# Ruff exit code 1 means issues found (not an error)
if not result.stdout.strip() or result.stdout.strip() == "[]":
return ""
issues = json.loads(result.stdout)
if not issues:
return ""
# Format findings
summary_lines = [f"Ruff linter found {len(issues)} issue(s):\n"]
for issue in issues[:20]: # Cap at 20 to avoid prompt bloat
code = issue.get("code", "?")
message = issue.get("message", "")
filename = issue.get("filename", "")
line = issue.get("location", {}).get("row", 0)
try:
relative = str(Path(filename).relative_to(tmpdir)).replace("\\", "/")
except ValueError:
relative = Path(filename).name
summary_lines.append(f"- [{code}] {relative}:{line} — {message}")
if len(issues) > 20:
summary_lines.append(f" ... and {len(issues) - 20} more issues")
summary = "\n".join(summary_lines)
logger.info("Ruff analysis complete", issues_count=len(issues))
return summary
except FileNotFoundError:
logger.warning("ruff not found in PATH — skipping lint analysis")
return ""
except Exception as e:
logger.warning("Ruff analysis failed", error=str(e))
return ""
|