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,347 @@
|
|
|
1
|
+
"""Code generation with extraction and validation."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import List, Dict, Optional, Tuple
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from coding_assistant.operations.validator import SyntaxValidator, ValidationResult
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class CodeBlock:
|
|
11
|
+
"""Represents a code block extracted from text."""
|
|
12
|
+
language: str
|
|
13
|
+
code: str
|
|
14
|
+
start_line: int
|
|
15
|
+
end_line: int
|
|
16
|
+
file_path: Optional[str] = None
|
|
17
|
+
is_valid: bool = False
|
|
18
|
+
validation_error: Optional[str] = None
|
|
19
|
+
|
|
20
|
+
def __repr__(self):
|
|
21
|
+
status = "✓ valid" if self.is_valid else f"✗ invalid: {self.validation_error}"
|
|
22
|
+
return f"CodeBlock({self.language}, lines {self.start_line}-{self.end_line}, {status})"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CodeGenerator:
|
|
26
|
+
"""Generate and validate code from LLM responses."""
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
"""Initialize the code generator."""
|
|
30
|
+
self.validator = SyntaxValidator()
|
|
31
|
+
|
|
32
|
+
def extract_code_blocks(self, text: str, validate: bool = True) -> List[CodeBlock]:
|
|
33
|
+
"""
|
|
34
|
+
Extract code blocks from markdown-formatted text.
|
|
35
|
+
|
|
36
|
+
Supports fenced code blocks with language specifiers:
|
|
37
|
+
```python
|
|
38
|
+
code here
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
text: The text containing code blocks (typically LLM response)
|
|
43
|
+
validate: Whether to validate extracted code blocks
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
List of CodeBlock objects
|
|
47
|
+
"""
|
|
48
|
+
code_blocks = []
|
|
49
|
+
|
|
50
|
+
# Pattern for fenced code blocks: ```language\ncode\n```
|
|
51
|
+
pattern = r'```(\w+)?\n(.*?)```'
|
|
52
|
+
|
|
53
|
+
lines = text.split('\n')
|
|
54
|
+
current_line = 0
|
|
55
|
+
|
|
56
|
+
# Find all code blocks
|
|
57
|
+
for match in re.finditer(pattern, text, re.DOTALL):
|
|
58
|
+
language = match.group(1) or 'text'
|
|
59
|
+
code = match.group(2)
|
|
60
|
+
|
|
61
|
+
# Calculate line numbers
|
|
62
|
+
start_pos = match.start()
|
|
63
|
+
start_line = text[:start_pos].count('\n') + 1
|
|
64
|
+
end_line = start_line + code.count('\n')
|
|
65
|
+
|
|
66
|
+
# Skip non-code languages
|
|
67
|
+
if language.lower() in ('text', 'plaintext', 'output', 'bash', 'shell', 'sh'):
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
# Normalize language names
|
|
71
|
+
language = self._normalize_language(language)
|
|
72
|
+
|
|
73
|
+
# Create code block
|
|
74
|
+
block = CodeBlock(
|
|
75
|
+
language=language,
|
|
76
|
+
code=code.strip(),
|
|
77
|
+
start_line=start_line,
|
|
78
|
+
end_line=end_line
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Validate if requested
|
|
82
|
+
if validate and language in self.validator.SUPPORTED_LANGUAGES:
|
|
83
|
+
validation_result = self.validator.validate(block.code, language)
|
|
84
|
+
block.is_valid = validation_result.is_valid
|
|
85
|
+
if not validation_result.is_valid:
|
|
86
|
+
block.validation_error = validation_result.error_message
|
|
87
|
+
|
|
88
|
+
code_blocks.append(block)
|
|
89
|
+
|
|
90
|
+
return code_blocks
|
|
91
|
+
|
|
92
|
+
def extract_with_context(self, text: str) -> List[Dict]:
|
|
93
|
+
"""
|
|
94
|
+
Extract code blocks with surrounding context.
|
|
95
|
+
|
|
96
|
+
Returns code blocks along with explanatory text before/after them.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
text: The text containing code and explanations
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
List of dicts with 'type' (code/text), 'content', and metadata
|
|
103
|
+
"""
|
|
104
|
+
sections = []
|
|
105
|
+
|
|
106
|
+
# Split by code blocks but keep them
|
|
107
|
+
pattern = r'(```\w*\n.*?```)'
|
|
108
|
+
parts = re.split(pattern, text, flags=re.DOTALL)
|
|
109
|
+
|
|
110
|
+
for part in parts:
|
|
111
|
+
if part.strip():
|
|
112
|
+
if part.startswith('```'):
|
|
113
|
+
# It's a code block
|
|
114
|
+
blocks = self.extract_code_blocks(part)
|
|
115
|
+
for block in blocks:
|
|
116
|
+
sections.append({
|
|
117
|
+
'type': 'code',
|
|
118
|
+
'language': block.language,
|
|
119
|
+
'content': block.code,
|
|
120
|
+
'is_valid': block.is_valid,
|
|
121
|
+
'validation_error': block.validation_error
|
|
122
|
+
})
|
|
123
|
+
else:
|
|
124
|
+
# It's text/explanation
|
|
125
|
+
sections.append({
|
|
126
|
+
'type': 'text',
|
|
127
|
+
'content': part.strip()
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
return sections
|
|
131
|
+
|
|
132
|
+
def generate_from_specification(self, specification: str,
|
|
133
|
+
context: Optional[List[Dict]] = None,
|
|
134
|
+
llm_client = None) -> List[CodeBlock]:
|
|
135
|
+
"""
|
|
136
|
+
Generate code from a specification using an LLM.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
specification: Description of what code to generate
|
|
140
|
+
context: Optional context (similar code, documentation, etc.)
|
|
141
|
+
llm_client: LLM client to use for generation
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
List of generated and validated CodeBlock objects
|
|
145
|
+
|
|
146
|
+
Raises:
|
|
147
|
+
ValueError: If no LLM client provided
|
|
148
|
+
"""
|
|
149
|
+
if not llm_client:
|
|
150
|
+
raise ValueError("LLM client required for code generation")
|
|
151
|
+
|
|
152
|
+
# Build prompt
|
|
153
|
+
prompt = self._build_generation_prompt(specification, context)
|
|
154
|
+
|
|
155
|
+
# Generate code
|
|
156
|
+
response = llm_client.generate(prompt, stream=False)
|
|
157
|
+
|
|
158
|
+
# Extract and validate code blocks
|
|
159
|
+
code_blocks = self.extract_code_blocks(response, validate=True)
|
|
160
|
+
|
|
161
|
+
return code_blocks
|
|
162
|
+
|
|
163
|
+
def validate_code_block(self, block: CodeBlock) -> ValidationResult:
|
|
164
|
+
"""
|
|
165
|
+
Validate a code block.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
block: The CodeBlock to validate
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
ValidationResult
|
|
172
|
+
"""
|
|
173
|
+
return self.validator.validate(block.code, block.language, block.file_path)
|
|
174
|
+
|
|
175
|
+
def attempt_fix(self, block: CodeBlock, llm_client=None) -> Optional[CodeBlock]:
|
|
176
|
+
"""
|
|
177
|
+
Attempt to fix invalid code using LLM.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
block: The invalid CodeBlock to fix
|
|
181
|
+
llm_client: LLM client for generating fixes
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Fixed CodeBlock if successful, None otherwise
|
|
185
|
+
"""
|
|
186
|
+
if block.is_valid:
|
|
187
|
+
return block
|
|
188
|
+
|
|
189
|
+
if not llm_client:
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
# Build fix prompt
|
|
193
|
+
prompt = f"""The following {block.language} code has a syntax error:
|
|
194
|
+
|
|
195
|
+
```{block.language}
|
|
196
|
+
{block.code}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Error: {block.validation_error}
|
|
200
|
+
|
|
201
|
+
Please fix the syntax error and provide only the corrected code in a code block.
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
response = llm_client.generate(prompt, stream=False)
|
|
206
|
+
fixed_blocks = self.extract_code_blocks(response, validate=True)
|
|
207
|
+
|
|
208
|
+
if fixed_blocks and fixed_blocks[0].is_valid:
|
|
209
|
+
return fixed_blocks[0]
|
|
210
|
+
except Exception:
|
|
211
|
+
pass
|
|
212
|
+
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
def _normalize_language(self, language: str) -> str:
|
|
216
|
+
"""Normalize language identifiers."""
|
|
217
|
+
language = language.lower()
|
|
218
|
+
|
|
219
|
+
# Map common aliases
|
|
220
|
+
alias_map = {
|
|
221
|
+
'py': 'python',
|
|
222
|
+
'js': 'javascript',
|
|
223
|
+
'ts': 'typescript',
|
|
224
|
+
'jsx': 'jsx',
|
|
225
|
+
'tsx': 'tsx',
|
|
226
|
+
'javascript': 'javascript',
|
|
227
|
+
'typescript': 'typescript',
|
|
228
|
+
'python': 'python',
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return alias_map.get(language, language)
|
|
232
|
+
|
|
233
|
+
def _build_generation_prompt(self, specification: str,
|
|
234
|
+
context: Optional[List[Dict]] = None) -> str:
|
|
235
|
+
"""Build a prompt for code generation."""
|
|
236
|
+
prompt = "Generate code based on the following specification:\n\n"
|
|
237
|
+
prompt += f"{specification}\n\n"
|
|
238
|
+
|
|
239
|
+
if context:
|
|
240
|
+
prompt += "Context (related code from the project):\n\n"
|
|
241
|
+
for ctx in context:
|
|
242
|
+
if 'file_path' in ctx:
|
|
243
|
+
prompt += f"## {ctx['file_path']}\n"
|
|
244
|
+
prompt += f"```{ctx.get('language', 'python')}\n"
|
|
245
|
+
prompt += f"{ctx.get('content', '')}\n"
|
|
246
|
+
prompt += "```\n\n"
|
|
247
|
+
|
|
248
|
+
prompt += "\nProvide only the code in properly formatted code blocks with language specifiers."
|
|
249
|
+
|
|
250
|
+
return prompt
|
|
251
|
+
|
|
252
|
+
def extract_file_operations(self, text: str) -> List[Dict]:
|
|
253
|
+
"""
|
|
254
|
+
Extract file operations from LLM response.
|
|
255
|
+
|
|
256
|
+
Looks for patterns like:
|
|
257
|
+
- "Create file: path/to/file.py"
|
|
258
|
+
- "Modify file: path/to/file.py"
|
|
259
|
+
- "Delete file: path/to/file.py"
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
List of file operations with type, path, and code
|
|
263
|
+
"""
|
|
264
|
+
operations = []
|
|
265
|
+
|
|
266
|
+
# Pattern for file operations
|
|
267
|
+
file_op_pattern = r'(Create|Modify|Update|Delete)\s+(?:file:?\s+)?`?([^\s`]+\.(?:py|js|ts|jsx|tsx))`?'
|
|
268
|
+
|
|
269
|
+
# Find all file operations
|
|
270
|
+
matches = re.finditer(file_op_pattern, text, re.IGNORECASE)
|
|
271
|
+
|
|
272
|
+
# Also extract code blocks
|
|
273
|
+
code_blocks = self.extract_code_blocks(text, validate=True)
|
|
274
|
+
code_block_idx = 0
|
|
275
|
+
|
|
276
|
+
for match in matches:
|
|
277
|
+
operation_type = match.group(1).lower()
|
|
278
|
+
file_path = match.group(2)
|
|
279
|
+
|
|
280
|
+
# Map operation types
|
|
281
|
+
if operation_type in ('create', 'modify', 'update'):
|
|
282
|
+
# Try to find associated code block
|
|
283
|
+
code = None
|
|
284
|
+
if code_block_idx < len(code_blocks):
|
|
285
|
+
code = code_blocks[code_block_idx].code
|
|
286
|
+
code_block_idx += 1
|
|
287
|
+
|
|
288
|
+
operations.append({
|
|
289
|
+
'type': 'create' if operation_type == 'create' else 'modify',
|
|
290
|
+
'file_path': file_path,
|
|
291
|
+
'code': code,
|
|
292
|
+
'validated': code_blocks[code_block_idx - 1].is_valid if code else False
|
|
293
|
+
})
|
|
294
|
+
elif operation_type == 'delete':
|
|
295
|
+
operations.append({
|
|
296
|
+
'type': 'delete',
|
|
297
|
+
'file_path': file_path
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
return operations
|
|
301
|
+
|
|
302
|
+
def format_code(self, code: str, language: str) -> str:
|
|
303
|
+
"""
|
|
304
|
+
Format code according to language conventions.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
code: The code to format
|
|
308
|
+
language: The programming language
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Formatted code
|
|
312
|
+
"""
|
|
313
|
+
# For Python, could use black
|
|
314
|
+
# For JS/TS, could use prettier
|
|
315
|
+
# For now, just return as-is
|
|
316
|
+
# This is a placeholder for future formatting integration
|
|
317
|
+
return code
|
|
318
|
+
|
|
319
|
+
def count_lines(self, code: str) -> int:
|
|
320
|
+
"""Count non-empty lines in code."""
|
|
321
|
+
return len([line for line in code.split('\n') if line.strip()])
|
|
322
|
+
|
|
323
|
+
def estimate_complexity(self, code: str, language: str) -> Dict:
|
|
324
|
+
"""
|
|
325
|
+
Estimate code complexity.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Dict with metrics like lines, functions, classes, etc.
|
|
329
|
+
"""
|
|
330
|
+
metrics = {
|
|
331
|
+
'lines': self.count_lines(code),
|
|
332
|
+
'total_lines': len(code.split('\n')),
|
|
333
|
+
'language': language
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if language == 'python':
|
|
337
|
+
# Count Python-specific constructs
|
|
338
|
+
metrics['functions'] = code.count('def ')
|
|
339
|
+
metrics['classes'] = code.count('class ')
|
|
340
|
+
metrics['imports'] = code.count('import ') + code.count('from ')
|
|
341
|
+
elif language in ('javascript', 'typescript', 'jsx', 'tsx'):
|
|
342
|
+
# Count JS/TS-specific constructs
|
|
343
|
+
metrics['functions'] = code.count('function ') + code.count('=> ')
|
|
344
|
+
metrics['classes'] = code.count('class ')
|
|
345
|
+
metrics['imports'] = code.count('import ') + code.count('require(')
|
|
346
|
+
|
|
347
|
+
return metrics
|