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.
Files changed (89) hide show
  1. ai_coding_assistant-0.5.0.dist-info/METADATA +226 -0
  2. ai_coding_assistant-0.5.0.dist-info/RECORD +89 -0
  3. ai_coding_assistant-0.5.0.dist-info/WHEEL +4 -0
  4. ai_coding_assistant-0.5.0.dist-info/entry_points.txt +3 -0
  5. ai_coding_assistant-0.5.0.dist-info/licenses/LICENSE +21 -0
  6. coding_assistant/__init__.py +3 -0
  7. coding_assistant/__main__.py +19 -0
  8. coding_assistant/cli/__init__.py +1 -0
  9. coding_assistant/cli/app.py +158 -0
  10. coding_assistant/cli/commands/__init__.py +19 -0
  11. coding_assistant/cli/commands/ask.py +178 -0
  12. coding_assistant/cli/commands/config.py +438 -0
  13. coding_assistant/cli/commands/diagram.py +267 -0
  14. coding_assistant/cli/commands/document.py +410 -0
  15. coding_assistant/cli/commands/explain.py +192 -0
  16. coding_assistant/cli/commands/fix.py +249 -0
  17. coding_assistant/cli/commands/index.py +162 -0
  18. coding_assistant/cli/commands/refactor.py +245 -0
  19. coding_assistant/cli/commands/search.py +182 -0
  20. coding_assistant/cli/commands/serve_docs.py +128 -0
  21. coding_assistant/cli/repl.py +381 -0
  22. coding_assistant/cli/theme.py +90 -0
  23. coding_assistant/codebase/__init__.py +1 -0
  24. coding_assistant/codebase/crawler.py +93 -0
  25. coding_assistant/codebase/parser.py +266 -0
  26. coding_assistant/config/__init__.py +25 -0
  27. coding_assistant/config/config_manager.py +615 -0
  28. coding_assistant/config/settings.py +82 -0
  29. coding_assistant/context/__init__.py +19 -0
  30. coding_assistant/context/chunker.py +443 -0
  31. coding_assistant/context/enhanced_retriever.py +322 -0
  32. coding_assistant/context/hybrid_search.py +311 -0
  33. coding_assistant/context/ranker.py +355 -0
  34. coding_assistant/context/retriever.py +119 -0
  35. coding_assistant/context/window.py +362 -0
  36. coding_assistant/documentation/__init__.py +23 -0
  37. coding_assistant/documentation/agents/__init__.py +27 -0
  38. coding_assistant/documentation/agents/coordinator.py +510 -0
  39. coding_assistant/documentation/agents/module_documenter.py +111 -0
  40. coding_assistant/documentation/agents/synthesizer.py +139 -0
  41. coding_assistant/documentation/agents/task_delegator.py +100 -0
  42. coding_assistant/documentation/decomposition/__init__.py +21 -0
  43. coding_assistant/documentation/decomposition/context_preserver.py +477 -0
  44. coding_assistant/documentation/decomposition/module_detector.py +302 -0
  45. coding_assistant/documentation/decomposition/partitioner.py +621 -0
  46. coding_assistant/documentation/generators/__init__.py +14 -0
  47. coding_assistant/documentation/generators/dataflow_generator.py +440 -0
  48. coding_assistant/documentation/generators/diagram_generator.py +511 -0
  49. coding_assistant/documentation/graph/__init__.py +13 -0
  50. coding_assistant/documentation/graph/dependency_builder.py +468 -0
  51. coding_assistant/documentation/graph/module_analyzer.py +475 -0
  52. coding_assistant/documentation/writers/__init__.py +11 -0
  53. coding_assistant/documentation/writers/markdown_writer.py +322 -0
  54. coding_assistant/embeddings/__init__.py +0 -0
  55. coding_assistant/embeddings/generator.py +89 -0
  56. coding_assistant/embeddings/store.py +187 -0
  57. coding_assistant/exceptions/__init__.py +50 -0
  58. coding_assistant/exceptions/base.py +110 -0
  59. coding_assistant/exceptions/llm.py +249 -0
  60. coding_assistant/exceptions/recovery.py +263 -0
  61. coding_assistant/exceptions/storage.py +213 -0
  62. coding_assistant/exceptions/validation.py +230 -0
  63. coding_assistant/llm/__init__.py +1 -0
  64. coding_assistant/llm/client.py +277 -0
  65. coding_assistant/llm/gemini_client.py +181 -0
  66. coding_assistant/llm/groq_client.py +160 -0
  67. coding_assistant/llm/prompts.py +98 -0
  68. coding_assistant/llm/together_client.py +160 -0
  69. coding_assistant/operations/__init__.py +13 -0
  70. coding_assistant/operations/differ.py +369 -0
  71. coding_assistant/operations/generator.py +347 -0
  72. coding_assistant/operations/linter.py +430 -0
  73. coding_assistant/operations/validator.py +406 -0
  74. coding_assistant/storage/__init__.py +9 -0
  75. coding_assistant/storage/database.py +363 -0
  76. coding_assistant/storage/session.py +231 -0
  77. coding_assistant/utils/__init__.py +31 -0
  78. coding_assistant/utils/cache.py +477 -0
  79. coding_assistant/utils/hardware.py +132 -0
  80. coding_assistant/utils/keystore.py +206 -0
  81. coding_assistant/utils/logger.py +32 -0
  82. coding_assistant/utils/progress.py +311 -0
  83. coding_assistant/validation/__init__.py +13 -0
  84. coding_assistant/validation/files.py +305 -0
  85. coding_assistant/validation/inputs.py +335 -0
  86. coding_assistant/validation/params.py +280 -0
  87. coding_assistant/validation/sanitizers.py +243 -0
  88. coding_assistant/vcs/__init__.py +5 -0
  89. 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
+ }