cognify-code 0.2.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_code_assistant/__init__.py +14 -0
- ai_code_assistant/agent/__init__.py +63 -0
- ai_code_assistant/agent/code_agent.py +461 -0
- ai_code_assistant/agent/code_generator.py +388 -0
- ai_code_assistant/agent/code_reviewer.py +365 -0
- ai_code_assistant/agent/diff_engine.py +308 -0
- ai_code_assistant/agent/file_manager.py +300 -0
- ai_code_assistant/agent/intent_classifier.py +284 -0
- ai_code_assistant/chat/__init__.py +11 -0
- ai_code_assistant/chat/agent_session.py +156 -0
- ai_code_assistant/chat/session.py +165 -0
- ai_code_assistant/cli.py +1571 -0
- ai_code_assistant/config.py +149 -0
- ai_code_assistant/editor/__init__.py +8 -0
- ai_code_assistant/editor/diff_handler.py +270 -0
- ai_code_assistant/editor/file_editor.py +350 -0
- ai_code_assistant/editor/prompts.py +146 -0
- ai_code_assistant/generator/__init__.py +7 -0
- ai_code_assistant/generator/code_gen.py +265 -0
- ai_code_assistant/generator/prompts.py +114 -0
- ai_code_assistant/git/__init__.py +6 -0
- ai_code_assistant/git/commit_generator.py +130 -0
- ai_code_assistant/git/manager.py +203 -0
- ai_code_assistant/llm.py +111 -0
- ai_code_assistant/providers/__init__.py +23 -0
- ai_code_assistant/providers/base.py +124 -0
- ai_code_assistant/providers/cerebras.py +97 -0
- ai_code_assistant/providers/factory.py +148 -0
- ai_code_assistant/providers/google.py +103 -0
- ai_code_assistant/providers/groq.py +111 -0
- ai_code_assistant/providers/ollama.py +86 -0
- ai_code_assistant/providers/openai.py +114 -0
- ai_code_assistant/providers/openrouter.py +130 -0
- ai_code_assistant/py.typed +0 -0
- ai_code_assistant/refactor/__init__.py +20 -0
- ai_code_assistant/refactor/analyzer.py +189 -0
- ai_code_assistant/refactor/change_plan.py +172 -0
- ai_code_assistant/refactor/multi_file_editor.py +346 -0
- ai_code_assistant/refactor/prompts.py +175 -0
- ai_code_assistant/retrieval/__init__.py +19 -0
- ai_code_assistant/retrieval/chunker.py +215 -0
- ai_code_assistant/retrieval/indexer.py +236 -0
- ai_code_assistant/retrieval/search.py +239 -0
- ai_code_assistant/reviewer/__init__.py +7 -0
- ai_code_assistant/reviewer/analyzer.py +278 -0
- ai_code_assistant/reviewer/prompts.py +113 -0
- ai_code_assistant/utils/__init__.py +18 -0
- ai_code_assistant/utils/file_handler.py +155 -0
- ai_code_assistant/utils/formatters.py +259 -0
- cognify_code-0.2.0.dist-info/METADATA +383 -0
- cognify_code-0.2.0.dist-info/RECORD +55 -0
- cognify_code-0.2.0.dist-info/WHEEL +5 -0
- cognify_code-0.2.0.dist-info/entry_points.txt +3 -0
- cognify_code-0.2.0.dist-info/licenses/LICENSE +22 -0
- cognify_code-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
"""Code Generator for AI-powered code generation."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from ai_code_assistant.agent.file_manager import FileContextManager, ProjectContext
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class GeneratedCode:
|
|
11
|
+
"""Represents generated code."""
|
|
12
|
+
code: str
|
|
13
|
+
file_path: str
|
|
14
|
+
language: str
|
|
15
|
+
description: str
|
|
16
|
+
is_new_file: bool = True
|
|
17
|
+
imports: List[str] = field(default_factory=list)
|
|
18
|
+
dependencies: List[str] = field(default_factory=list)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class CodeGenerationRequest:
|
|
23
|
+
"""Request for code generation."""
|
|
24
|
+
description: str
|
|
25
|
+
language: Optional[str] = None
|
|
26
|
+
file_path: Optional[str] = None
|
|
27
|
+
context_files: List[str] = field(default_factory=list)
|
|
28
|
+
style_guide: Optional[str] = None
|
|
29
|
+
framework: Optional[str] = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Language-specific templates
|
|
33
|
+
CODE_TEMPLATES = {
|
|
34
|
+
"python": {
|
|
35
|
+
"function": '''def {name}({params}){return_type}:
|
|
36
|
+
"""{docstring}"""
|
|
37
|
+
{body}
|
|
38
|
+
''',
|
|
39
|
+
"class": '''class {name}{base}:
|
|
40
|
+
"""{docstring}"""
|
|
41
|
+
|
|
42
|
+
def __init__(self{init_params}):
|
|
43
|
+
{init_body}
|
|
44
|
+
|
|
45
|
+
{methods}
|
|
46
|
+
''',
|
|
47
|
+
"api_endpoint": '''@app.{method}("{path}")
|
|
48
|
+
async def {name}({params}){return_type}:
|
|
49
|
+
"""{docstring}"""
|
|
50
|
+
{body}
|
|
51
|
+
''',
|
|
52
|
+
},
|
|
53
|
+
"typescript": {
|
|
54
|
+
"function": '''export function {name}({params}): {return_type} {{
|
|
55
|
+
{body}
|
|
56
|
+
}}
|
|
57
|
+
''',
|
|
58
|
+
"class": '''export class {name}{base} {{
|
|
59
|
+
{properties}
|
|
60
|
+
|
|
61
|
+
constructor({init_params}) {{
|
|
62
|
+
{init_body}
|
|
63
|
+
}}
|
|
64
|
+
|
|
65
|
+
{methods}
|
|
66
|
+
}}
|
|
67
|
+
''',
|
|
68
|
+
"component": '''import React from 'react';
|
|
69
|
+
|
|
70
|
+
interface {name}Props {{
|
|
71
|
+
{props}
|
|
72
|
+
}}
|
|
73
|
+
|
|
74
|
+
export const {name}: React.FC<{name}Props> = ({{ {destructured_props} }}) => {{
|
|
75
|
+
return (
|
|
76
|
+
{jsx}
|
|
77
|
+
);
|
|
78
|
+
}};
|
|
79
|
+
''',
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
GENERATION_PROMPT = '''You are an expert code generator. Generate high-quality, production-ready code based on the user's request.
|
|
85
|
+
|
|
86
|
+
## Project Context
|
|
87
|
+
{project_context}
|
|
88
|
+
|
|
89
|
+
## Existing Code Context
|
|
90
|
+
{code_context}
|
|
91
|
+
|
|
92
|
+
## User Request
|
|
93
|
+
{request}
|
|
94
|
+
|
|
95
|
+
## Requirements
|
|
96
|
+
- Language: {language}
|
|
97
|
+
- File path: {file_path}
|
|
98
|
+
- Framework: {framework}
|
|
99
|
+
|
|
100
|
+
## Instructions
|
|
101
|
+
1. Generate clean, well-documented code
|
|
102
|
+
2. Follow best practices for {language}
|
|
103
|
+
3. Include proper error handling
|
|
104
|
+
4. Add type hints/annotations where applicable
|
|
105
|
+
5. Include necessary imports at the top
|
|
106
|
+
6. Match the style of existing code in the project
|
|
107
|
+
|
|
108
|
+
## Output Format
|
|
109
|
+
Respond with ONLY the code, no explanations. Start with any necessary imports.
|
|
110
|
+
If creating a new file, include the complete file content.
|
|
111
|
+
If modifying existing code, show only the new/changed code.
|
|
112
|
+
|
|
113
|
+
```{language}
|
|
114
|
+
'''
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class CodeGenerator:
|
|
118
|
+
"""Generates code using AI with project context awareness."""
|
|
119
|
+
|
|
120
|
+
def __init__(self, llm_manager, file_manager: Optional[FileContextManager] = None):
|
|
121
|
+
self.llm = llm_manager
|
|
122
|
+
self.file_manager = file_manager or FileContextManager()
|
|
123
|
+
|
|
124
|
+
def generate(self, request: CodeGenerationRequest) -> GeneratedCode:
|
|
125
|
+
"""Generate code based on the request."""
|
|
126
|
+
# Get project context
|
|
127
|
+
project_context = self._build_project_context()
|
|
128
|
+
|
|
129
|
+
# Get code context from related files
|
|
130
|
+
code_context = self._build_code_context(request)
|
|
131
|
+
|
|
132
|
+
# Determine language and file path
|
|
133
|
+
language = request.language or self._detect_language(request)
|
|
134
|
+
file_path = request.file_path or self._suggest_file_path(request, language)
|
|
135
|
+
|
|
136
|
+
# Build prompt
|
|
137
|
+
prompt = GENERATION_PROMPT.format(
|
|
138
|
+
project_context=project_context,
|
|
139
|
+
code_context=code_context,
|
|
140
|
+
request=request.description,
|
|
141
|
+
language=language,
|
|
142
|
+
file_path=file_path,
|
|
143
|
+
framework=request.framework or "standard library",
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Generate code
|
|
147
|
+
response = self.llm.invoke(prompt)
|
|
148
|
+
|
|
149
|
+
# Parse response
|
|
150
|
+
code = self._extract_code(response, language)
|
|
151
|
+
imports = self._extract_imports(code, language)
|
|
152
|
+
|
|
153
|
+
return GeneratedCode(
|
|
154
|
+
code=code,
|
|
155
|
+
file_path=file_path,
|
|
156
|
+
language=language,
|
|
157
|
+
description=request.description,
|
|
158
|
+
is_new_file=not self.file_manager.file_exists(file_path),
|
|
159
|
+
imports=imports,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def generate_function(self, name: str, description: str,
|
|
163
|
+
language: str = "python",
|
|
164
|
+
params: Optional[List[Dict]] = None,
|
|
165
|
+
return_type: Optional[str] = None) -> GeneratedCode:
|
|
166
|
+
"""Generate a function."""
|
|
167
|
+
params_str = self._format_params(params or [], language)
|
|
168
|
+
|
|
169
|
+
request = CodeGenerationRequest(
|
|
170
|
+
description=f"Create a function named '{name}' that {description}. "
|
|
171
|
+
f"Parameters: {params_str}. "
|
|
172
|
+
f"Return type: {return_type or 'appropriate type'}",
|
|
173
|
+
language=language,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
return self.generate(request)
|
|
177
|
+
|
|
178
|
+
def generate_class(self, name: str, description: str,
|
|
179
|
+
language: str = "python",
|
|
180
|
+
methods: Optional[List[str]] = None,
|
|
181
|
+
base_class: Optional[str] = None) -> GeneratedCode:
|
|
182
|
+
"""Generate a class."""
|
|
183
|
+
methods_str = ", ".join(methods) if methods else "appropriate methods"
|
|
184
|
+
|
|
185
|
+
request = CodeGenerationRequest(
|
|
186
|
+
description=f"Create a class named '{name}' that {description}. "
|
|
187
|
+
f"Include methods: {methods_str}. "
|
|
188
|
+
f"Base class: {base_class or 'none'}",
|
|
189
|
+
language=language,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
return self.generate(request)
|
|
193
|
+
|
|
194
|
+
def generate_api_endpoint(self, path: str, method: str,
|
|
195
|
+
description: str,
|
|
196
|
+
framework: str = "fastapi") -> GeneratedCode:
|
|
197
|
+
"""Generate an API endpoint."""
|
|
198
|
+
request = CodeGenerationRequest(
|
|
199
|
+
description=f"Create a {method.upper()} endpoint at '{path}' that {description}",
|
|
200
|
+
language="python",
|
|
201
|
+
framework=framework,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
return self.generate(request)
|
|
205
|
+
|
|
206
|
+
def generate_test(self, target_file: str,
|
|
207
|
+
test_framework: str = "pytest") -> GeneratedCode:
|
|
208
|
+
"""Generate tests for a file."""
|
|
209
|
+
# Read the target file
|
|
210
|
+
content = self.file_manager.read_file(target_file)
|
|
211
|
+
|
|
212
|
+
if not content:
|
|
213
|
+
raise ValueError(f"Cannot read file: {target_file}")
|
|
214
|
+
|
|
215
|
+
request = CodeGenerationRequest(
|
|
216
|
+
description=f"Generate comprehensive unit tests for the following code:\n\n{content[:3000]}",
|
|
217
|
+
language="python",
|
|
218
|
+
framework=test_framework,
|
|
219
|
+
context_files=[target_file],
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
result = self.generate(request)
|
|
223
|
+
|
|
224
|
+
# Suggest test file path
|
|
225
|
+
if target_file.startswith("src/"):
|
|
226
|
+
result.file_path = target_file.replace("src/", "tests/test_")
|
|
227
|
+
else:
|
|
228
|
+
result.file_path = f"tests/test_{target_file.split('/')[-1]}"
|
|
229
|
+
|
|
230
|
+
return result
|
|
231
|
+
|
|
232
|
+
def _build_project_context(self) -> str:
|
|
233
|
+
"""Build project context string."""
|
|
234
|
+
context = self.file_manager.get_project_context()
|
|
235
|
+
|
|
236
|
+
lines = [
|
|
237
|
+
f"Project root: {context.root_path.name}",
|
|
238
|
+
f"Languages: {', '.join(context.languages)}",
|
|
239
|
+
f"Total files: {context.total_code_files} code files",
|
|
240
|
+
"",
|
|
241
|
+
"Project structure (summary):",
|
|
242
|
+
self.file_manager.get_structure_summary(max_depth=2),
|
|
243
|
+
]
|
|
244
|
+
|
|
245
|
+
return "\n".join(lines)
|
|
246
|
+
|
|
247
|
+
def _build_code_context(self, request: CodeGenerationRequest) -> str:
|
|
248
|
+
"""Build code context from related files."""
|
|
249
|
+
context_parts = []
|
|
250
|
+
|
|
251
|
+
# Read explicitly requested context files
|
|
252
|
+
for file_path in request.context_files[:5]: # Limit to 5 files
|
|
253
|
+
content = self.file_manager.read_file(file_path)
|
|
254
|
+
if content:
|
|
255
|
+
# Truncate large files
|
|
256
|
+
if len(content) > 2000:
|
|
257
|
+
content = content[:2000] + "\n... (truncated)"
|
|
258
|
+
context_parts.append(f"### {file_path}\n```\n{content}\n```")
|
|
259
|
+
|
|
260
|
+
# If file_path specified, get related files
|
|
261
|
+
if request.file_path:
|
|
262
|
+
related = self.file_manager.get_related_files(request.file_path)
|
|
263
|
+
for rel_path in related[:3]:
|
|
264
|
+
if rel_path not in request.context_files:
|
|
265
|
+
content = self.file_manager.read_file(rel_path)
|
|
266
|
+
if content and len(content) < 1500:
|
|
267
|
+
context_parts.append(f"### {rel_path} (related)\n```\n{content}\n```")
|
|
268
|
+
|
|
269
|
+
return "\n\n".join(context_parts) if context_parts else "No existing code context."
|
|
270
|
+
|
|
271
|
+
def _detect_language(self, request: CodeGenerationRequest) -> str:
|
|
272
|
+
"""Detect language from request."""
|
|
273
|
+
desc_lower = request.description.lower()
|
|
274
|
+
|
|
275
|
+
language_keywords = {
|
|
276
|
+
"python": ["python", "django", "flask", "fastapi", "pytest"],
|
|
277
|
+
"typescript": ["typescript", "react", "angular", "vue", "tsx"],
|
|
278
|
+
"javascript": ["javascript", "node", "express", "js"],
|
|
279
|
+
"java": ["java", "spring", "maven", "gradle"],
|
|
280
|
+
"go": ["go", "golang", "gin"],
|
|
281
|
+
"rust": ["rust", "cargo"],
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
for lang, keywords in language_keywords.items():
|
|
285
|
+
if any(kw in desc_lower for kw in keywords):
|
|
286
|
+
return lang
|
|
287
|
+
|
|
288
|
+
# Default based on project
|
|
289
|
+
context = self.file_manager.get_project_context()
|
|
290
|
+
if context.languages:
|
|
291
|
+
# Return most common language
|
|
292
|
+
return list(context.languages)[0]
|
|
293
|
+
|
|
294
|
+
return "python"
|
|
295
|
+
|
|
296
|
+
def _suggest_file_path(self, request: CodeGenerationRequest, language: str) -> str:
|
|
297
|
+
"""Suggest a file path for the generated code."""
|
|
298
|
+
ext_map = {
|
|
299
|
+
"python": ".py",
|
|
300
|
+
"typescript": ".ts",
|
|
301
|
+
"javascript": ".js",
|
|
302
|
+
"java": ".java",
|
|
303
|
+
"go": ".go",
|
|
304
|
+
"rust": ".rs",
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
ext = ext_map.get(language, ".txt")
|
|
308
|
+
|
|
309
|
+
# Extract a name from the description
|
|
310
|
+
desc_words = request.description.lower().split()
|
|
311
|
+
name_words = []
|
|
312
|
+
|
|
313
|
+
skip_words = {"create", "write", "generate", "make", "build", "a", "an", "the", "that", "which", "for"}
|
|
314
|
+
|
|
315
|
+
for word in desc_words[:5]:
|
|
316
|
+
word = ''.join(c for c in word if c.isalnum())
|
|
317
|
+
if word and word not in skip_words:
|
|
318
|
+
name_words.append(word)
|
|
319
|
+
if len(name_words) >= 2:
|
|
320
|
+
break
|
|
321
|
+
|
|
322
|
+
name = "_".join(name_words) if name_words else "generated"
|
|
323
|
+
|
|
324
|
+
return f"src/{name}{ext}"
|
|
325
|
+
|
|
326
|
+
def _extract_code(self, response: str, language: str) -> str:
|
|
327
|
+
"""Extract code from LLM response."""
|
|
328
|
+
# Try to find code block
|
|
329
|
+
import re
|
|
330
|
+
|
|
331
|
+
# Match ```language or ``` code blocks
|
|
332
|
+
pattern = rf"```(?:{language})?\s*\n(.*?)```"
|
|
333
|
+
match = re.search(pattern, response, re.DOTALL | re.IGNORECASE)
|
|
334
|
+
|
|
335
|
+
if match:
|
|
336
|
+
return match.group(1).strip()
|
|
337
|
+
|
|
338
|
+
# If no code block, return cleaned response
|
|
339
|
+
lines = response.strip().split("\n")
|
|
340
|
+
|
|
341
|
+
# Remove common non-code prefixes
|
|
342
|
+
cleaned = []
|
|
343
|
+
for line in lines:
|
|
344
|
+
if not line.startswith(("Here", "This", "I'll", "The following", "```")):
|
|
345
|
+
cleaned.append(line)
|
|
346
|
+
|
|
347
|
+
return "\n".join(cleaned).strip()
|
|
348
|
+
|
|
349
|
+
def _extract_imports(self, code: str, language: str) -> List[str]:
|
|
350
|
+
"""Extract import statements from code."""
|
|
351
|
+
imports = []
|
|
352
|
+
|
|
353
|
+
for line in code.split("\n"):
|
|
354
|
+
line = line.strip()
|
|
355
|
+
|
|
356
|
+
if language == "python":
|
|
357
|
+
if line.startswith("import ") or line.startswith("from "):
|
|
358
|
+
imports.append(line)
|
|
359
|
+
elif language in ("typescript", "javascript"):
|
|
360
|
+
if line.startswith("import "):
|
|
361
|
+
imports.append(line)
|
|
362
|
+
elif language == "java":
|
|
363
|
+
if line.startswith("import "):
|
|
364
|
+
imports.append(line)
|
|
365
|
+
elif language == "go":
|
|
366
|
+
if line.startswith("import "):
|
|
367
|
+
imports.append(line)
|
|
368
|
+
|
|
369
|
+
return imports
|
|
370
|
+
|
|
371
|
+
def _format_params(self, params: List[Dict], language: str) -> str:
|
|
372
|
+
"""Format parameters for display."""
|
|
373
|
+
if not params:
|
|
374
|
+
return "none"
|
|
375
|
+
|
|
376
|
+
formatted = []
|
|
377
|
+
for p in params:
|
|
378
|
+
name = p.get("name", "param")
|
|
379
|
+
ptype = p.get("type", "")
|
|
380
|
+
|
|
381
|
+
if language == "python":
|
|
382
|
+
formatted.append(f"{name}: {ptype}" if ptype else name)
|
|
383
|
+
elif language in ("typescript", "javascript"):
|
|
384
|
+
formatted.append(f"{name}: {ptype}" if ptype else name)
|
|
385
|
+
else:
|
|
386
|
+
formatted.append(f"{ptype} {name}" if ptype else name)
|
|
387
|
+
|
|
388
|
+
return ", ".join(formatted)
|