ai-coding-assistant 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ai_coding_assistant-0.5.0.dist-info/METADATA +226 -0
- ai_coding_assistant-0.5.0.dist-info/RECORD +89 -0
- ai_coding_assistant-0.5.0.dist-info/WHEEL +4 -0
- ai_coding_assistant-0.5.0.dist-info/entry_points.txt +3 -0
- ai_coding_assistant-0.5.0.dist-info/licenses/LICENSE +21 -0
- coding_assistant/__init__.py +3 -0
- coding_assistant/__main__.py +19 -0
- coding_assistant/cli/__init__.py +1 -0
- coding_assistant/cli/app.py +158 -0
- coding_assistant/cli/commands/__init__.py +19 -0
- coding_assistant/cli/commands/ask.py +178 -0
- coding_assistant/cli/commands/config.py +438 -0
- coding_assistant/cli/commands/diagram.py +267 -0
- coding_assistant/cli/commands/document.py +410 -0
- coding_assistant/cli/commands/explain.py +192 -0
- coding_assistant/cli/commands/fix.py +249 -0
- coding_assistant/cli/commands/index.py +162 -0
- coding_assistant/cli/commands/refactor.py +245 -0
- coding_assistant/cli/commands/search.py +182 -0
- coding_assistant/cli/commands/serve_docs.py +128 -0
- coding_assistant/cli/repl.py +381 -0
- coding_assistant/cli/theme.py +90 -0
- coding_assistant/codebase/__init__.py +1 -0
- coding_assistant/codebase/crawler.py +93 -0
- coding_assistant/codebase/parser.py +266 -0
- coding_assistant/config/__init__.py +25 -0
- coding_assistant/config/config_manager.py +615 -0
- coding_assistant/config/settings.py +82 -0
- coding_assistant/context/__init__.py +19 -0
- coding_assistant/context/chunker.py +443 -0
- coding_assistant/context/enhanced_retriever.py +322 -0
- coding_assistant/context/hybrid_search.py +311 -0
- coding_assistant/context/ranker.py +355 -0
- coding_assistant/context/retriever.py +119 -0
- coding_assistant/context/window.py +362 -0
- coding_assistant/documentation/__init__.py +23 -0
- coding_assistant/documentation/agents/__init__.py +27 -0
- coding_assistant/documentation/agents/coordinator.py +510 -0
- coding_assistant/documentation/agents/module_documenter.py +111 -0
- coding_assistant/documentation/agents/synthesizer.py +139 -0
- coding_assistant/documentation/agents/task_delegator.py +100 -0
- coding_assistant/documentation/decomposition/__init__.py +21 -0
- coding_assistant/documentation/decomposition/context_preserver.py +477 -0
- coding_assistant/documentation/decomposition/module_detector.py +302 -0
- coding_assistant/documentation/decomposition/partitioner.py +621 -0
- coding_assistant/documentation/generators/__init__.py +14 -0
- coding_assistant/documentation/generators/dataflow_generator.py +440 -0
- coding_assistant/documentation/generators/diagram_generator.py +511 -0
- coding_assistant/documentation/graph/__init__.py +13 -0
- coding_assistant/documentation/graph/dependency_builder.py +468 -0
- coding_assistant/documentation/graph/module_analyzer.py +475 -0
- coding_assistant/documentation/writers/__init__.py +11 -0
- coding_assistant/documentation/writers/markdown_writer.py +322 -0
- coding_assistant/embeddings/__init__.py +0 -0
- coding_assistant/embeddings/generator.py +89 -0
- coding_assistant/embeddings/store.py +187 -0
- coding_assistant/exceptions/__init__.py +50 -0
- coding_assistant/exceptions/base.py +110 -0
- coding_assistant/exceptions/llm.py +249 -0
- coding_assistant/exceptions/recovery.py +263 -0
- coding_assistant/exceptions/storage.py +213 -0
- coding_assistant/exceptions/validation.py +230 -0
- coding_assistant/llm/__init__.py +1 -0
- coding_assistant/llm/client.py +277 -0
- coding_assistant/llm/gemini_client.py +181 -0
- coding_assistant/llm/groq_client.py +160 -0
- coding_assistant/llm/prompts.py +98 -0
- coding_assistant/llm/together_client.py +160 -0
- coding_assistant/operations/__init__.py +13 -0
- coding_assistant/operations/differ.py +369 -0
- coding_assistant/operations/generator.py +347 -0
- coding_assistant/operations/linter.py +430 -0
- coding_assistant/operations/validator.py +406 -0
- coding_assistant/storage/__init__.py +9 -0
- coding_assistant/storage/database.py +363 -0
- coding_assistant/storage/session.py +231 -0
- coding_assistant/utils/__init__.py +31 -0
- coding_assistant/utils/cache.py +477 -0
- coding_assistant/utils/hardware.py +132 -0
- coding_assistant/utils/keystore.py +206 -0
- coding_assistant/utils/logger.py +32 -0
- coding_assistant/utils/progress.py +311 -0
- coding_assistant/validation/__init__.py +13 -0
- coding_assistant/validation/files.py +305 -0
- coding_assistant/validation/inputs.py +335 -0
- coding_assistant/validation/params.py +280 -0
- coding_assistant/validation/sanitizers.py +243 -0
- coding_assistant/vcs/__init__.py +5 -0
- coding_assistant/vcs/git.py +269 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
"""Linter integration for code quality checking."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import json
|
|
5
|
+
import tempfile
|
|
6
|
+
import os
|
|
7
|
+
from typing import List, Dict, Optional, Tuple
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class LintIssue:
|
|
17
|
+
"""Represents a single linting issue."""
|
|
18
|
+
file_path: str
|
|
19
|
+
line: int
|
|
20
|
+
column: int
|
|
21
|
+
severity: str # 'error', 'warning', 'info'
|
|
22
|
+
code: str # Error code (e.g., 'E501', 'no-unused-vars')
|
|
23
|
+
message: str
|
|
24
|
+
rule: Optional[str] = None
|
|
25
|
+
fixable: bool = False
|
|
26
|
+
|
|
27
|
+
def __repr__(self):
|
|
28
|
+
return f"{self.file_path}:{self.line}:{self.column} [{self.severity}] {self.code}: {self.message}"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class LinterResult:
|
|
32
|
+
"""Result of linting operation."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, issues: List[LintIssue], fixed_code: Optional[str] = None):
|
|
35
|
+
self.issues = issues
|
|
36
|
+
self.fixed_code = fixed_code
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def has_errors(self) -> bool:
|
|
40
|
+
"""Check if there are any errors."""
|
|
41
|
+
return any(issue.severity == 'error' for issue in self.issues)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def has_warnings(self) -> bool:
|
|
45
|
+
"""Check if there are any warnings."""
|
|
46
|
+
return any(issue.severity == 'warning' for issue in self.issues)
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def error_count(self) -> int:
|
|
50
|
+
"""Count of errors."""
|
|
51
|
+
return sum(1 for issue in self.issues if issue.severity == 'error')
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def warning_count(self) -> int:
|
|
55
|
+
"""Count of warnings."""
|
|
56
|
+
return sum(1 for issue in self.issues if issue.severity == 'warning')
|
|
57
|
+
|
|
58
|
+
def __repr__(self):
|
|
59
|
+
return f"LinterResult(errors={self.error_count}, warnings={self.warning_count})"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class LinterIntegration:
|
|
63
|
+
"""Integration with code linters (ruff for Python, eslint for JS/TS)."""
|
|
64
|
+
|
|
65
|
+
def __init__(self, project_root: Optional[str] = None):
|
|
66
|
+
"""
|
|
67
|
+
Initialize the linter integration.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
project_root: Root directory of the project (for config files)
|
|
71
|
+
"""
|
|
72
|
+
self.project_root = Path(project_root) if project_root else Path.cwd()
|
|
73
|
+
self.console = Console()
|
|
74
|
+
|
|
75
|
+
# Check availability of linters
|
|
76
|
+
self._ruff_available = self._check_command('ruff')
|
|
77
|
+
self._eslint_available = self._check_command('eslint') or self._check_command('npx')
|
|
78
|
+
|
|
79
|
+
def lint(self, code: str, language: str,
|
|
80
|
+
file_path: Optional[str] = None,
|
|
81
|
+
auto_fix: bool = False) -> LinterResult:
|
|
82
|
+
"""
|
|
83
|
+
Lint code for the given language.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
code: The code to lint
|
|
87
|
+
language: Programming language ('python', 'javascript', 'typescript')
|
|
88
|
+
file_path: Optional file path (for better error messages)
|
|
89
|
+
auto_fix: Whether to attempt automatic fixes
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
LinterResult with issues and optionally fixed code
|
|
93
|
+
"""
|
|
94
|
+
language = language.lower()
|
|
95
|
+
|
|
96
|
+
if language == 'python':
|
|
97
|
+
return self._lint_python(code, file_path, auto_fix)
|
|
98
|
+
elif language in ('javascript', 'typescript', 'jsx', 'tsx'):
|
|
99
|
+
return self._lint_javascript(code, file_path, language, auto_fix)
|
|
100
|
+
else:
|
|
101
|
+
# Unsupported language, return empty result
|
|
102
|
+
return LinterResult(issues=[])
|
|
103
|
+
|
|
104
|
+
def _lint_python(self, code: str, file_path: Optional[str] = None,
|
|
105
|
+
auto_fix: bool = False) -> LinterResult:
|
|
106
|
+
"""Lint Python code using ruff."""
|
|
107
|
+
if not self._ruff_available:
|
|
108
|
+
return LinterResult(issues=[])
|
|
109
|
+
|
|
110
|
+
issues = []
|
|
111
|
+
fixed_code = None
|
|
112
|
+
|
|
113
|
+
# Create temp file for linting
|
|
114
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
|
|
115
|
+
f.write(code)
|
|
116
|
+
temp_path = f.name
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
# Run ruff check
|
|
120
|
+
result = subprocess.run(
|
|
121
|
+
['ruff', 'check', '--output-format=json', temp_path],
|
|
122
|
+
capture_output=True,
|
|
123
|
+
text=True,
|
|
124
|
+
timeout=10
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Parse JSON output
|
|
128
|
+
if result.stdout:
|
|
129
|
+
try:
|
|
130
|
+
ruff_output = json.loads(result.stdout)
|
|
131
|
+
for item in ruff_output:
|
|
132
|
+
issues.append(LintIssue(
|
|
133
|
+
file_path=file_path or temp_path,
|
|
134
|
+
line=item.get('location', {}).get('row', 0),
|
|
135
|
+
column=item.get('location', {}).get('column', 0),
|
|
136
|
+
severity='error' if item.get('type') == 'Error' else 'warning',
|
|
137
|
+
code=item.get('code', 'unknown'),
|
|
138
|
+
message=item.get('message', ''),
|
|
139
|
+
rule=item.get('code'),
|
|
140
|
+
fixable=item.get('fix', {}).get('applicability') == 'automatic'
|
|
141
|
+
))
|
|
142
|
+
except json.JSONDecodeError:
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
# Try auto-fix if requested
|
|
146
|
+
if auto_fix and issues:
|
|
147
|
+
fix_result = subprocess.run(
|
|
148
|
+
['ruff', 'check', '--fix', temp_path],
|
|
149
|
+
capture_output=True,
|
|
150
|
+
timeout=10
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if fix_result.returncode == 0:
|
|
154
|
+
# Read fixed code
|
|
155
|
+
with open(temp_path, 'r') as f:
|
|
156
|
+
fixed_code = f.read()
|
|
157
|
+
|
|
158
|
+
# Format with ruff
|
|
159
|
+
format_result = subprocess.run(
|
|
160
|
+
['ruff', 'format', temp_path],
|
|
161
|
+
capture_output=True,
|
|
162
|
+
timeout=10
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if format_result.returncode == 0:
|
|
166
|
+
with open(temp_path, 'r') as f:
|
|
167
|
+
fixed_code = f.read()
|
|
168
|
+
|
|
169
|
+
except subprocess.TimeoutExpired:
|
|
170
|
+
issues.append(LintIssue(
|
|
171
|
+
file_path=file_path or 'unknown',
|
|
172
|
+
line=0,
|
|
173
|
+
column=0,
|
|
174
|
+
severity='error',
|
|
175
|
+
code='timeout',
|
|
176
|
+
message='Linter timed out'
|
|
177
|
+
))
|
|
178
|
+
except Exception as e:
|
|
179
|
+
issues.append(LintIssue(
|
|
180
|
+
file_path=file_path or 'unknown',
|
|
181
|
+
line=0,
|
|
182
|
+
column=0,
|
|
183
|
+
severity='error',
|
|
184
|
+
code='error',
|
|
185
|
+
message=f'Linter error: {str(e)}'
|
|
186
|
+
))
|
|
187
|
+
finally:
|
|
188
|
+
# Clean up temp file
|
|
189
|
+
try:
|
|
190
|
+
os.unlink(temp_path)
|
|
191
|
+
except:
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
return LinterResult(issues=issues, fixed_code=fixed_code)
|
|
195
|
+
|
|
196
|
+
def _lint_javascript(self, code: str, file_path: Optional[str] = None,
|
|
197
|
+
language: str = 'javascript',
|
|
198
|
+
auto_fix: bool = False) -> LinterResult:
|
|
199
|
+
"""Lint JavaScript/TypeScript code using eslint."""
|
|
200
|
+
if not self._eslint_available:
|
|
201
|
+
return LinterResult(issues=[])
|
|
202
|
+
|
|
203
|
+
issues = []
|
|
204
|
+
fixed_code = None
|
|
205
|
+
|
|
206
|
+
# Determine file extension
|
|
207
|
+
extension_map = {
|
|
208
|
+
'javascript': '.js',
|
|
209
|
+
'typescript': '.ts',
|
|
210
|
+
'jsx': '.jsx',
|
|
211
|
+
'tsx': '.tsx'
|
|
212
|
+
}
|
|
213
|
+
extension = extension_map.get(language, '.js')
|
|
214
|
+
|
|
215
|
+
# Create temp file
|
|
216
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix=extension, delete=False) as f:
|
|
217
|
+
f.write(code)
|
|
218
|
+
temp_path = f.name
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
# Build eslint command
|
|
222
|
+
cmd = ['npx', 'eslint', '--format=json']
|
|
223
|
+
if auto_fix:
|
|
224
|
+
cmd.append('--fix')
|
|
225
|
+
cmd.append(temp_path)
|
|
226
|
+
|
|
227
|
+
# Run eslint
|
|
228
|
+
result = subprocess.run(
|
|
229
|
+
cmd,
|
|
230
|
+
capture_output=True,
|
|
231
|
+
text=True,
|
|
232
|
+
cwd=self.project_root,
|
|
233
|
+
timeout=15
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Parse JSON output
|
|
237
|
+
if result.stdout:
|
|
238
|
+
try:
|
|
239
|
+
eslint_output = json.loads(result.stdout)
|
|
240
|
+
for file_result in eslint_output:
|
|
241
|
+
for msg in file_result.get('messages', []):
|
|
242
|
+
issues.append(LintIssue(
|
|
243
|
+
file_path=file_path or temp_path,
|
|
244
|
+
line=msg.get('line', 0),
|
|
245
|
+
column=msg.get('column', 0),
|
|
246
|
+
severity='error' if msg.get('severity') == 2 else 'warning',
|
|
247
|
+
code=msg.get('ruleId', 'unknown'),
|
|
248
|
+
message=msg.get('message', ''),
|
|
249
|
+
rule=msg.get('ruleId'),
|
|
250
|
+
fixable=msg.get('fix') is not None
|
|
251
|
+
))
|
|
252
|
+
except json.JSONDecodeError:
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
# If auto-fix was requested, read the fixed code
|
|
256
|
+
if auto_fix:
|
|
257
|
+
try:
|
|
258
|
+
with open(temp_path, 'r') as f:
|
|
259
|
+
fixed_code = f.read()
|
|
260
|
+
except:
|
|
261
|
+
pass
|
|
262
|
+
|
|
263
|
+
except subprocess.TimeoutExpired:
|
|
264
|
+
issues.append(LintIssue(
|
|
265
|
+
file_path=file_path or 'unknown',
|
|
266
|
+
line=0,
|
|
267
|
+
column=0,
|
|
268
|
+
severity='error',
|
|
269
|
+
code='timeout',
|
|
270
|
+
message='Linter timed out'
|
|
271
|
+
))
|
|
272
|
+
except FileNotFoundError:
|
|
273
|
+
# eslint not found, skip silently
|
|
274
|
+
pass
|
|
275
|
+
except Exception as e:
|
|
276
|
+
# Other errors, add as issue
|
|
277
|
+
issues.append(LintIssue(
|
|
278
|
+
file_path=file_path or 'unknown',
|
|
279
|
+
line=0,
|
|
280
|
+
column=0,
|
|
281
|
+
severity='warning',
|
|
282
|
+
code='linter-error',
|
|
283
|
+
message=f'Linter unavailable: {str(e)}'
|
|
284
|
+
))
|
|
285
|
+
finally:
|
|
286
|
+
# Clean up temp file
|
|
287
|
+
try:
|
|
288
|
+
os.unlink(temp_path)
|
|
289
|
+
except:
|
|
290
|
+
pass
|
|
291
|
+
|
|
292
|
+
return LinterResult(issues=issues, fixed_code=fixed_code)
|
|
293
|
+
|
|
294
|
+
def lint_file(self, file_path: str, auto_fix: bool = False) -> LinterResult:
|
|
295
|
+
"""
|
|
296
|
+
Lint a file based on its extension.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
file_path: Path to the file to lint
|
|
300
|
+
auto_fix: Whether to apply automatic fixes
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
LinterResult
|
|
304
|
+
"""
|
|
305
|
+
path = Path(file_path)
|
|
306
|
+
|
|
307
|
+
if not path.exists():
|
|
308
|
+
return LinterResult(issues=[LintIssue(
|
|
309
|
+
file_path=file_path,
|
|
310
|
+
line=0,
|
|
311
|
+
column=0,
|
|
312
|
+
severity='error',
|
|
313
|
+
code='file-not-found',
|
|
314
|
+
message=f'File not found: {file_path}'
|
|
315
|
+
)])
|
|
316
|
+
|
|
317
|
+
# Determine language from extension
|
|
318
|
+
extension = path.suffix.lower()
|
|
319
|
+
language_map = {
|
|
320
|
+
'.py': 'python',
|
|
321
|
+
'.js': 'javascript',
|
|
322
|
+
'.jsx': 'jsx',
|
|
323
|
+
'.ts': 'typescript',
|
|
324
|
+
'.tsx': 'tsx',
|
|
325
|
+
'.mjs': 'javascript',
|
|
326
|
+
'.cjs': 'javascript',
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
language = language_map.get(extension)
|
|
330
|
+
if not language:
|
|
331
|
+
return LinterResult(issues=[])
|
|
332
|
+
|
|
333
|
+
# Read file and lint
|
|
334
|
+
try:
|
|
335
|
+
code = path.read_text(encoding='utf-8')
|
|
336
|
+
result = self.lint(code, language, str(file_path), auto_fix)
|
|
337
|
+
|
|
338
|
+
# If auto-fix produced fixed code, write it back
|
|
339
|
+
if auto_fix and result.fixed_code:
|
|
340
|
+
path.write_text(result.fixed_code, encoding='utf-8')
|
|
341
|
+
|
|
342
|
+
return result
|
|
343
|
+
|
|
344
|
+
except Exception as e:
|
|
345
|
+
return LinterResult(issues=[LintIssue(
|
|
346
|
+
file_path=file_path,
|
|
347
|
+
line=0,
|
|
348
|
+
column=0,
|
|
349
|
+
severity='error',
|
|
350
|
+
code='read-error',
|
|
351
|
+
message=f'Error reading file: {str(e)}'
|
|
352
|
+
)])
|
|
353
|
+
|
|
354
|
+
def display_issues(self, result: LinterResult, title: Optional[str] = None):
|
|
355
|
+
"""
|
|
356
|
+
Display linting issues in a formatted table.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
result: The LinterResult to display
|
|
360
|
+
title: Optional title for the display
|
|
361
|
+
"""
|
|
362
|
+
if not result.issues:
|
|
363
|
+
self.console.print("[green]✓ No linting issues found[/green]")
|
|
364
|
+
return
|
|
365
|
+
|
|
366
|
+
# Create summary
|
|
367
|
+
summary = f"[red]✗ {result.error_count} errors[/red]"
|
|
368
|
+
if result.warning_count > 0:
|
|
369
|
+
summary += f", [yellow]⚠ {result.warning_count} warnings[/yellow]"
|
|
370
|
+
|
|
371
|
+
self.console.print(f"\n{summary}\n")
|
|
372
|
+
|
|
373
|
+
# Create table
|
|
374
|
+
table = Table(
|
|
375
|
+
title=title or "Linting Issues",
|
|
376
|
+
show_header=True,
|
|
377
|
+
header_style="bold magenta",
|
|
378
|
+
border_style="blue"
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
table.add_column("Severity", width=8)
|
|
382
|
+
table.add_column("Line:Col", width=10)
|
|
383
|
+
table.add_column("Code", width=15)
|
|
384
|
+
table.add_column("Message", width=60)
|
|
385
|
+
|
|
386
|
+
for issue in result.issues:
|
|
387
|
+
# Color code by severity
|
|
388
|
+
if issue.severity == 'error':
|
|
389
|
+
severity_style = "red"
|
|
390
|
+
icon = "✗"
|
|
391
|
+
elif issue.severity == 'warning':
|
|
392
|
+
severity_style = "yellow"
|
|
393
|
+
icon = "⚠"
|
|
394
|
+
else:
|
|
395
|
+
severity_style = "blue"
|
|
396
|
+
icon = "ℹ"
|
|
397
|
+
|
|
398
|
+
table.add_row(
|
|
399
|
+
f"[{severity_style}]{icon} {issue.severity}[/{severity_style}]",
|
|
400
|
+
f"{issue.line}:{issue.column}",
|
|
401
|
+
f"[cyan]{issue.code}[/cyan]",
|
|
402
|
+
issue.message[:60],
|
|
403
|
+
style=severity_style if issue.severity == 'error' else None
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
self.console.print(table)
|
|
407
|
+
|
|
408
|
+
# Show fixable issues count
|
|
409
|
+
fixable_count = sum(1 for issue in result.issues if issue.fixable)
|
|
410
|
+
if fixable_count > 0:
|
|
411
|
+
self.console.print(f"\n[blue]ℹ {fixable_count} issues can be auto-fixed[/blue]")
|
|
412
|
+
|
|
413
|
+
def _check_command(self, command: str) -> bool:
|
|
414
|
+
"""Check if a command is available."""
|
|
415
|
+
try:
|
|
416
|
+
subprocess.run(
|
|
417
|
+
[command, '--version'],
|
|
418
|
+
capture_output=True,
|
|
419
|
+
timeout=5
|
|
420
|
+
)
|
|
421
|
+
return True
|
|
422
|
+
except:
|
|
423
|
+
return False
|
|
424
|
+
|
|
425
|
+
def get_linter_info(self) -> Dict[str, bool]:
|
|
426
|
+
"""Get information about available linters."""
|
|
427
|
+
return {
|
|
428
|
+
'ruff': self._ruff_available,
|
|
429
|
+
'eslint': self._eslint_available
|
|
430
|
+
}
|