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.
Files changed (55) hide show
  1. ai_code_assistant/__init__.py +14 -0
  2. ai_code_assistant/agent/__init__.py +63 -0
  3. ai_code_assistant/agent/code_agent.py +461 -0
  4. ai_code_assistant/agent/code_generator.py +388 -0
  5. ai_code_assistant/agent/code_reviewer.py +365 -0
  6. ai_code_assistant/agent/diff_engine.py +308 -0
  7. ai_code_assistant/agent/file_manager.py +300 -0
  8. ai_code_assistant/agent/intent_classifier.py +284 -0
  9. ai_code_assistant/chat/__init__.py +11 -0
  10. ai_code_assistant/chat/agent_session.py +156 -0
  11. ai_code_assistant/chat/session.py +165 -0
  12. ai_code_assistant/cli.py +1571 -0
  13. ai_code_assistant/config.py +149 -0
  14. ai_code_assistant/editor/__init__.py +8 -0
  15. ai_code_assistant/editor/diff_handler.py +270 -0
  16. ai_code_assistant/editor/file_editor.py +350 -0
  17. ai_code_assistant/editor/prompts.py +146 -0
  18. ai_code_assistant/generator/__init__.py +7 -0
  19. ai_code_assistant/generator/code_gen.py +265 -0
  20. ai_code_assistant/generator/prompts.py +114 -0
  21. ai_code_assistant/git/__init__.py +6 -0
  22. ai_code_assistant/git/commit_generator.py +130 -0
  23. ai_code_assistant/git/manager.py +203 -0
  24. ai_code_assistant/llm.py +111 -0
  25. ai_code_assistant/providers/__init__.py +23 -0
  26. ai_code_assistant/providers/base.py +124 -0
  27. ai_code_assistant/providers/cerebras.py +97 -0
  28. ai_code_assistant/providers/factory.py +148 -0
  29. ai_code_assistant/providers/google.py +103 -0
  30. ai_code_assistant/providers/groq.py +111 -0
  31. ai_code_assistant/providers/ollama.py +86 -0
  32. ai_code_assistant/providers/openai.py +114 -0
  33. ai_code_assistant/providers/openrouter.py +130 -0
  34. ai_code_assistant/py.typed +0 -0
  35. ai_code_assistant/refactor/__init__.py +20 -0
  36. ai_code_assistant/refactor/analyzer.py +189 -0
  37. ai_code_assistant/refactor/change_plan.py +172 -0
  38. ai_code_assistant/refactor/multi_file_editor.py +346 -0
  39. ai_code_assistant/refactor/prompts.py +175 -0
  40. ai_code_assistant/retrieval/__init__.py +19 -0
  41. ai_code_assistant/retrieval/chunker.py +215 -0
  42. ai_code_assistant/retrieval/indexer.py +236 -0
  43. ai_code_assistant/retrieval/search.py +239 -0
  44. ai_code_assistant/reviewer/__init__.py +7 -0
  45. ai_code_assistant/reviewer/analyzer.py +278 -0
  46. ai_code_assistant/reviewer/prompts.py +113 -0
  47. ai_code_assistant/utils/__init__.py +18 -0
  48. ai_code_assistant/utils/file_handler.py +155 -0
  49. ai_code_assistant/utils/formatters.py +259 -0
  50. cognify_code-0.2.0.dist-info/METADATA +383 -0
  51. cognify_code-0.2.0.dist-info/RECORD +55 -0
  52. cognify_code-0.2.0.dist-info/WHEEL +5 -0
  53. cognify_code-0.2.0.dist-info/entry_points.txt +3 -0
  54. cognify_code-0.2.0.dist-info/licenses/LICENSE +22 -0
  55. cognify_code-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,14 @@
1
+ """
2
+ Cognify AI - Your Local AI-Powered Code Assistant.
3
+
4
+ Review, generate, search, and refactor code with an intelligent AI agent.
5
+ All running locally with complete privacy using Ollama.
6
+ """
7
+
8
+ __version__ = "0.2.0"
9
+ __author__ = "Ashok Kumar"
10
+
11
+ from ai_code_assistant.config import Config, load_config
12
+ from ai_code_assistant.llm import LLMManager
13
+
14
+ __all__ = ["Config", "load_config", "LLMManager", "__version__"]
@@ -0,0 +1,63 @@
1
+ """AI Code Agent - Intelligent code generation, review, and editing."""
2
+
3
+ from ai_code_assistant.agent.file_manager import (
4
+ FileContextManager,
5
+ FileInfo,
6
+ ProjectContext,
7
+ )
8
+ from ai_code_assistant.agent.intent_classifier import (
9
+ IntentClassifier,
10
+ Intent,
11
+ IntentType,
12
+ )
13
+ from ai_code_assistant.agent.code_generator import (
14
+ CodeGenerator,
15
+ CodeGenerationRequest,
16
+ GeneratedCode,
17
+ )
18
+ from ai_code_assistant.agent.diff_engine import (
19
+ DiffEngine,
20
+ ChangeSet,
21
+ FileDiff,
22
+ ChangeType,
23
+ )
24
+ from ai_code_assistant.agent.code_reviewer import (
25
+ CodeReviewer,
26
+ CodeIssue,
27
+ ReviewResult,
28
+ IssueSeverity,
29
+ IssueCategory,
30
+ )
31
+ from ai_code_assistant.agent.code_agent import (
32
+ CodeAgent,
33
+ AgentResponse,
34
+ )
35
+
36
+ __all__ = [
37
+ # File Manager
38
+ "FileContextManager",
39
+ "FileInfo",
40
+ "ProjectContext",
41
+ # Intent Classifier
42
+ "IntentClassifier",
43
+ "Intent",
44
+ "IntentType",
45
+ # Code Generator
46
+ "CodeGenerator",
47
+ "CodeGenerationRequest",
48
+ "GeneratedCode",
49
+ # Diff Engine
50
+ "DiffEngine",
51
+ "ChangeSet",
52
+ "FileDiff",
53
+ "ChangeType",
54
+ # Code Reviewer
55
+ "CodeReviewer",
56
+ "CodeIssue",
57
+ "ReviewResult",
58
+ "IssueSeverity",
59
+ "IssueCategory",
60
+ # Code Agent
61
+ "CodeAgent",
62
+ "AgentResponse",
63
+ ]
@@ -0,0 +1,461 @@
1
+ """Code Agent - Main orchestrator for code generation, review, and editing."""
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ from typing import Callable, List, Optional, Tuple
6
+
7
+ from ai_code_assistant.agent.file_manager import FileContextManager
8
+ from ai_code_assistant.agent.intent_classifier import IntentClassifier, Intent, IntentType
9
+ from ai_code_assistant.agent.code_generator import CodeGenerator, CodeGenerationRequest, GeneratedCode
10
+ from ai_code_assistant.agent.diff_engine import DiffEngine, ChangeSet, FileDiff
11
+ from ai_code_assistant.agent.code_reviewer import CodeReviewer, ReviewResult
12
+
13
+
14
+ @dataclass
15
+ class AgentResponse:
16
+ """Response from the code agent."""
17
+ message: str
18
+ intent: Optional[Intent] = None
19
+ generated_code: Optional[GeneratedCode] = None
20
+ review_result: Optional[ReviewResult] = None
21
+ changeset: Optional[ChangeSet] = None
22
+ requires_confirmation: bool = False
23
+ action_callback: Optional[Callable] = None
24
+
25
+ @property
26
+ def has_changes(self) -> bool:
27
+ return self.changeset is not None and self.changeset.files_changed > 0
28
+
29
+
30
+ class CodeAgent:
31
+ """Main agent that orchestrates code operations based on user intent."""
32
+
33
+ def __init__(self, llm_manager, root_path: Optional[Path] = None):
34
+ self.llm = llm_manager
35
+ self.file_manager = FileContextManager(root_path)
36
+ self.intent_classifier = IntentClassifier(llm_manager)
37
+ self.code_generator = CodeGenerator(llm_manager, self.file_manager)
38
+ self.diff_engine = DiffEngine(self.file_manager)
39
+ self.code_reviewer = CodeReviewer(llm_manager, self.file_manager)
40
+
41
+ # Pending changes awaiting confirmation
42
+ self._pending_changeset: Optional[ChangeSet] = None
43
+
44
+ def process(self, message: str, use_llm_classification: bool = True) -> AgentResponse:
45
+ """Process a user message and return appropriate response."""
46
+ # Classify intent
47
+ if use_llm_classification:
48
+ intent = self.intent_classifier.classify_with_llm(message)
49
+ else:
50
+ intent = self.intent_classifier.classify(message)
51
+
52
+ # Route to appropriate handler
53
+ handlers = {
54
+ IntentType.CODE_GENERATE: self._handle_generate,
55
+ IntentType.CODE_EDIT: self._handle_edit,
56
+ IntentType.CODE_REVIEW: self._handle_review,
57
+ IntentType.CODE_EXPLAIN: self._handle_explain,
58
+ IntentType.CODE_REFACTOR: self._handle_refactor,
59
+ IntentType.TEST_GENERATE: self._handle_test_generate,
60
+ IntentType.FILE_CREATE: self._handle_file_create,
61
+ IntentType.FILE_DELETE: self._handle_file_delete,
62
+ IntentType.PROJECT_INFO: self._handle_project_info,
63
+ IntentType.GENERAL_CHAT: self._handle_general_chat,
64
+ }
65
+
66
+ handler = handlers.get(intent.type, self._handle_general_chat)
67
+ return handler(message, intent)
68
+
69
+ def confirm_changes(self) -> Tuple[bool, str]:
70
+ """Apply pending changes after user confirmation."""
71
+ if not self._pending_changeset:
72
+ return False, "No pending changes to apply."
73
+
74
+ successful, failed = self.diff_engine.apply_changeset(self._pending_changeset)
75
+ self._pending_changeset = None
76
+
77
+ if failed == 0:
78
+ return True, f"✓ Successfully applied changes to {successful} file(s)."
79
+ else:
80
+ return False, f"Applied {successful} file(s), {failed} failed."
81
+
82
+ def reject_changes(self) -> str:
83
+ """Reject pending changes."""
84
+ self._pending_changeset = None
85
+ return "Changes discarded."
86
+
87
+ def _handle_generate(self, message: str, intent: Intent) -> AgentResponse:
88
+ """Handle code generation requests."""
89
+ request = CodeGenerationRequest(
90
+ description=message,
91
+ language=intent.language,
92
+ file_path=intent.file_paths[0] if intent.file_paths else None,
93
+ )
94
+
95
+ generated = self.code_generator.generate(request)
96
+
97
+ # Create changeset
98
+ changeset = ChangeSet(description=f"Generate: {message[:50]}...")
99
+ diff = self.diff_engine.create_file_diff(generated.file_path, generated.code)
100
+ changeset.diffs.append(diff)
101
+
102
+ self._pending_changeset = changeset
103
+
104
+ # Format response
105
+ preview = self.diff_engine.format_diff_simple(diff)
106
+
107
+ return AgentResponse(
108
+ message=f"I'll create the following code:\n\n{preview}",
109
+ intent=intent,
110
+ generated_code=generated,
111
+ changeset=changeset,
112
+ requires_confirmation=True,
113
+ )
114
+
115
+ def _handle_edit(self, message: str, intent: Intent) -> AgentResponse:
116
+ """Handle code editing requests."""
117
+ if not intent.file_paths:
118
+ return AgentResponse(
119
+ message="Please specify which file you want to edit. For example: 'Edit src/main.py to add error handling'",
120
+ intent=intent,
121
+ )
122
+
123
+ file_path = intent.file_paths[0]
124
+ original = self.file_manager.read_file(file_path)
125
+
126
+ if not original:
127
+ return AgentResponse(
128
+ message=f"Cannot find file: {file_path}",
129
+ intent=intent,
130
+ )
131
+
132
+ # Generate edited code
133
+ prompt = f"""Edit the following code according to the user's request.
134
+
135
+ ## Original Code ({file_path})
136
+ ```
137
+ {original[:5000]}
138
+ ```
139
+
140
+ ## User Request
141
+ {message}
142
+
143
+ ## Instructions
144
+ 1. Make the requested changes
145
+ 2. Keep the rest of the code unchanged
146
+ 3. Return the COMPLETE modified file
147
+
148
+ ```
149
+ """
150
+
151
+ response = self.llm.invoke(prompt)
152
+ new_code = self._extract_code(response)
153
+
154
+ # Create changeset
155
+ changeset = ChangeSet(description=f"Edit: {message[:50]}...")
156
+ diff = self.diff_engine.create_diff(original, new_code, file_path)
157
+ changeset.diffs.append(diff)
158
+
159
+ self._pending_changeset = changeset
160
+
161
+ preview = self.diff_engine.format_diff_simple(diff)
162
+
163
+ return AgentResponse(
164
+ message=f"Here are the proposed changes:\n\n{preview}",
165
+ intent=intent,
166
+ changeset=changeset,
167
+ requires_confirmation=True,
168
+ )
169
+
170
+ def _handle_review(self, message: str, intent: Intent) -> AgentResponse:
171
+ """Handle code review requests."""
172
+ if not intent.file_paths:
173
+ # Try to find files to review
174
+ context = self.file_manager.get_project_context()
175
+ py_files = [f.relative_path for f in context.files if f.extension == ".py"][:5]
176
+
177
+ if py_files:
178
+ return AgentResponse(
179
+ message=f"Which file would you like me to review? Found these Python files:\n" +
180
+ "\n".join(f" • {f}" for f in py_files),
181
+ intent=intent,
182
+ )
183
+ return AgentResponse(
184
+ message="Please specify which file you want me to review.",
185
+ intent=intent,
186
+ )
187
+
188
+ file_path = intent.file_paths[0]
189
+
190
+ try:
191
+ result = self.code_reviewer.review_file(file_path)
192
+ except ValueError as e:
193
+ return AgentResponse(message=str(e), intent=intent)
194
+
195
+ # Format review output
196
+ lines = [result.format_summary()]
197
+
198
+ if result.issues:
199
+ lines.append("Issues found:\n")
200
+ for issue in result.issues[:10]:
201
+ lines.append(issue.format())
202
+ lines.append("")
203
+
204
+ if len(result.issues) > 10:
205
+ lines.append(f"... and {len(result.issues) - 10} more issues")
206
+
207
+ return AgentResponse(
208
+ message="\n".join(lines),
209
+ intent=intent,
210
+ review_result=result,
211
+ )
212
+
213
+ def _handle_explain(self, message: str, intent: Intent) -> AgentResponse:
214
+ """Handle code explanation requests."""
215
+ if not intent.file_paths:
216
+ return AgentResponse(
217
+ message="Please specify which file or code you want me to explain.",
218
+ intent=intent,
219
+ )
220
+
221
+ file_path = intent.file_paths[0]
222
+ content = self.file_manager.read_file(file_path)
223
+
224
+ if not content:
225
+ return AgentResponse(
226
+ message=f"Cannot find file: {file_path}",
227
+ intent=intent,
228
+ )
229
+
230
+ prompt = f"""Explain the following code in a clear, educational way.
231
+
232
+ ## Code ({file_path})
233
+ ```
234
+ {content[:5000]}
235
+ ```
236
+
237
+ ## Instructions
238
+ 1. Start with a high-level overview
239
+ 2. Explain the main components/functions
240
+ 3. Describe the flow of execution
241
+ 4. Note any important patterns or techniques used
242
+ 5. Keep the explanation concise but thorough
243
+ """
244
+
245
+ explanation = self.llm.invoke(prompt)
246
+
247
+ return AgentResponse(
248
+ message=f"📖 **Explanation of {file_path}**\n\n{explanation}",
249
+ intent=intent,
250
+ )
251
+
252
+ def _handle_refactor(self, message: str, intent: Intent) -> AgentResponse:
253
+ """Handle code refactoring requests."""
254
+ if not intent.file_paths:
255
+ return AgentResponse(
256
+ message="Please specify which file you want to refactor.",
257
+ intent=intent,
258
+ )
259
+
260
+ file_path = intent.file_paths[0]
261
+ original = self.file_manager.read_file(file_path)
262
+
263
+ if not original:
264
+ return AgentResponse(
265
+ message=f"Cannot find file: {file_path}",
266
+ intent=intent,
267
+ )
268
+
269
+ prompt = f"""Refactor the following code to improve its quality.
270
+
271
+ ## Original Code ({file_path})
272
+ ```
273
+ {original[:5000]}
274
+ ```
275
+
276
+ ## User Request
277
+ {message}
278
+
279
+ ## Refactoring Goals
280
+ 1. Improve readability
281
+ 2. Reduce complexity
282
+ 3. Follow best practices
283
+ 4. Improve performance where possible
284
+ 5. Add/improve documentation
285
+
286
+ Return the COMPLETE refactored file.
287
+
288
+ ```
289
+ """
290
+
291
+ response = self.llm.invoke(prompt)
292
+ new_code = self._extract_code(response)
293
+
294
+ # Create changeset
295
+ changeset = ChangeSet(description=f"Refactor: {file_path}")
296
+ diff = self.diff_engine.create_diff(original, new_code, file_path)
297
+ changeset.diffs.append(diff)
298
+
299
+ self._pending_changeset = changeset
300
+
301
+ preview = self.diff_engine.format_diff_simple(diff)
302
+
303
+ return AgentResponse(
304
+ message=f"Here's the refactored code:\n\n{preview}",
305
+ intent=intent,
306
+ changeset=changeset,
307
+ requires_confirmation=True,
308
+ )
309
+
310
+ def _handle_test_generate(self, message: str, intent: Intent) -> AgentResponse:
311
+ """Handle test generation requests."""
312
+ if not intent.file_paths:
313
+ return AgentResponse(
314
+ message="Please specify which file you want to generate tests for.",
315
+ intent=intent,
316
+ )
317
+
318
+ file_path = intent.file_paths[0]
319
+
320
+ try:
321
+ generated = self.code_generator.generate_test(file_path)
322
+ except ValueError as e:
323
+ return AgentResponse(message=str(e), intent=intent)
324
+
325
+ # Create changeset
326
+ changeset = ChangeSet(description=f"Generate tests for {file_path}")
327
+ diff = self.diff_engine.create_file_diff(generated.file_path, generated.code)
328
+ changeset.diffs.append(diff)
329
+
330
+ self._pending_changeset = changeset
331
+
332
+ preview = self.diff_engine.format_diff_simple(diff)
333
+
334
+ return AgentResponse(
335
+ message=f"I'll create tests at {generated.file_path}:\n\n{preview}",
336
+ intent=intent,
337
+ generated_code=generated,
338
+ changeset=changeset,
339
+ requires_confirmation=True,
340
+ )
341
+
342
+ def _handle_file_create(self, message: str, intent: Intent) -> AgentResponse:
343
+ """Handle file creation requests."""
344
+ # Extract file path from message or intent
345
+ file_path = intent.file_paths[0] if intent.file_paths else None
346
+
347
+ if not file_path:
348
+ return AgentResponse(
349
+ message="Please specify the file path to create.",
350
+ intent=intent,
351
+ )
352
+
353
+ # Generate initial content based on file type
354
+ request = CodeGenerationRequest(
355
+ description=f"Create a new file: {message}",
356
+ file_path=file_path,
357
+ )
358
+
359
+ generated = self.code_generator.generate(request)
360
+
361
+ changeset = ChangeSet(description=f"Create file: {file_path}")
362
+ diff = self.diff_engine.create_file_diff(file_path, generated.code)
363
+ changeset.diffs.append(diff)
364
+
365
+ self._pending_changeset = changeset
366
+
367
+ preview = self.diff_engine.format_diff_simple(diff)
368
+
369
+ return AgentResponse(
370
+ message=f"I'll create {file_path}:\n\n{preview}",
371
+ intent=intent,
372
+ generated_code=generated,
373
+ changeset=changeset,
374
+ requires_confirmation=True,
375
+ )
376
+
377
+ def _handle_file_delete(self, message: str, intent: Intent) -> AgentResponse:
378
+ """Handle file deletion requests."""
379
+ if not intent.file_paths:
380
+ return AgentResponse(
381
+ message="Please specify which file to delete.",
382
+ intent=intent,
383
+ )
384
+
385
+ file_path = intent.file_paths[0]
386
+
387
+ if not self.file_manager.file_exists(file_path):
388
+ return AgentResponse(
389
+ message=f"File not found: {file_path}",
390
+ intent=intent,
391
+ )
392
+
393
+ content = self.file_manager.read_file(file_path) or ""
394
+
395
+ changeset = ChangeSet(description=f"Delete file: {file_path}")
396
+ diff = self.diff_engine.create_diff(content, "", file_path)
397
+ changeset.diffs.append(diff)
398
+
399
+ self._pending_changeset = changeset
400
+
401
+ return AgentResponse(
402
+ message=f"⚠️ This will delete {file_path} ({len(content)} bytes). Are you sure?",
403
+ intent=intent,
404
+ changeset=changeset,
405
+ requires_confirmation=True,
406
+ )
407
+
408
+ def _handle_project_info(self, message: str, intent: Intent) -> AgentResponse:
409
+ """Handle project information requests."""
410
+ context = self.file_manager.get_project_context()
411
+ structure = self.file_manager.get_structure_summary(max_depth=3)
412
+
413
+ info = f"""📁 **Project: {context.root_path.name}**
414
+
415
+ **Statistics:**
416
+ • Total files: {context.total_files}
417
+ • Code files: {context.total_code_files}
418
+ • Languages: {', '.join(sorted(context.languages)) or 'None detected'}
419
+
420
+ **Structure:**
421
+ {structure}
422
+ """
423
+
424
+ return AgentResponse(message=info, intent=intent)
425
+
426
+ def _handle_general_chat(self, message: str, intent: Intent) -> AgentResponse:
427
+ """Handle general chat/questions."""
428
+ # Get project context for better responses
429
+ context = self.file_manager.get_project_context()
430
+
431
+ prompt = f"""You are a helpful coding assistant. Answer the user's question.
432
+
433
+ Project context:
434
+ - Root: {context.root_path.name}
435
+ - Languages: {', '.join(context.languages)}
436
+ - Files: {context.total_code_files} code files
437
+
438
+ User: {message}
439
+
440
+ Provide a helpful, concise response. If the question is about code, you can suggest using specific commands like:
441
+ - "Create a function that..." for code generation
442
+ - "Review src/file.py" for code review
443
+ - "Explain src/file.py" for explanations
444
+ """
445
+
446
+ response = self.llm.invoke(prompt)
447
+
448
+ return AgentResponse(message=response, intent=intent)
449
+
450
+ def _extract_code(self, response: str) -> str:
451
+ """Extract code from LLM response."""
452
+ import re
453
+
454
+ # Try to find code block
455
+ pattern = r"```(?:\w+)?\s*\n(.*?)```"
456
+ match = re.search(pattern, response, re.DOTALL)
457
+
458
+ if match:
459
+ return match.group(1).strip()
460
+
461
+ return response.strip()