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,346 @@
1
+ """Multi-file editor for coordinated refactoring operations."""
2
+
3
+ import re
4
+ import shutil
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+ from typing import Dict, List, Optional, Tuple
8
+
9
+ from ai_code_assistant.config import Config, get_language_by_extension
10
+ from ai_code_assistant.llm import LLMManager
11
+ from ai_code_assistant.editor.diff_handler import DiffHandler
12
+ from ai_code_assistant.refactor.prompts import (
13
+ MULTI_FILE_EDIT_PROMPT,
14
+ RENAME_SYMBOL_PROMPT,
15
+ EXTRACT_TO_FILE_PROMPT,
16
+ )
17
+ from ai_code_assistant.refactor.change_plan import (
18
+ ChangePlan,
19
+ FileChange,
20
+ ChangeType,
21
+ RefactorResult,
22
+ )
23
+ from ai_code_assistant.refactor.analyzer import RefactorAnalyzer
24
+
25
+
26
+ class MultiFileEditor:
27
+ """Editor for coordinated multi-file refactoring."""
28
+
29
+ def __init__(self, config: Config, llm_manager: LLMManager):
30
+ """Initialize the multi-file editor.
31
+
32
+ Args:
33
+ config: Application configuration
34
+ llm_manager: LLM manager for AI interactions
35
+ """
36
+ self.config = config
37
+ self.llm = llm_manager
38
+ self.diff_handler = DiffHandler()
39
+ self.analyzer = RefactorAnalyzer(config, llm_manager)
40
+
41
+ def refactor(
42
+ self,
43
+ instruction: str,
44
+ files: List[Path],
45
+ dry_run: bool = False,
46
+ create_backup: bool = True,
47
+ ) -> RefactorResult:
48
+ """Perform multi-file refactoring.
49
+
50
+ Args:
51
+ instruction: Refactoring instruction
52
+ files: List of files to potentially modify
53
+ dry_run: If True, don't apply changes
54
+ create_backup: If True, create backup before changes
55
+
56
+ Returns:
57
+ RefactorResult with all changes
58
+ """
59
+ # Analyze to create plan
60
+ plan = self.analyzer.analyze(instruction, files)
61
+
62
+ if not plan.changes:
63
+ return RefactorResult(
64
+ plan=plan,
65
+ error="No changes identified",
66
+ )
67
+
68
+ # Read current file contents
69
+ file_contents = self._read_files([Path(c.file_path) for c in plan.changes])
70
+
71
+ # Update plan with original content
72
+ for change in plan.changes:
73
+ if change.file_path in file_contents:
74
+ change.original_content = file_contents[change.file_path]
75
+
76
+ # Generate changes
77
+ result = self._generate_changes(plan, file_contents)
78
+
79
+ if result.error:
80
+ return result
81
+
82
+ # Apply changes if not dry run
83
+ if not dry_run:
84
+ backup_dir = None
85
+ if create_backup:
86
+ backup_dir = self._create_backup(plan)
87
+ result.backup_dir = str(backup_dir) if backup_dir else None
88
+
89
+ self._apply_changes(plan)
90
+ result.applied = True
91
+
92
+ return result
93
+
94
+ def rename_symbol(
95
+ self,
96
+ old_name: str,
97
+ new_name: str,
98
+ symbol_type: str,
99
+ files: List[Path],
100
+ dry_run: bool = False,
101
+ ) -> RefactorResult:
102
+ """Rename a symbol across multiple files.
103
+
104
+ Args:
105
+ old_name: Current symbol name
106
+ new_name: New symbol name
107
+ symbol_type: Type of symbol (function, class, variable, etc.)
108
+ files: Files to search and modify
109
+ dry_run: If True, don't apply changes
110
+
111
+ Returns:
112
+ RefactorResult with all changes
113
+ """
114
+ # Read file contents
115
+ file_contents = self._read_files(files)
116
+
117
+ # Filter to files containing the symbol
118
+ relevant_files = {
119
+ path: content for path, content in file_contents.items()
120
+ if old_name in content
121
+ }
122
+
123
+ if not relevant_files:
124
+ plan = ChangePlan(
125
+ instruction=f"Rename {symbol_type} '{old_name}' to '{new_name}'",
126
+ summary=f"Symbol '{old_name}' not found in any files",
127
+ changes=[],
128
+ )
129
+ return RefactorResult(plan=plan, error="Symbol not found")
130
+
131
+ # Create plan
132
+ plan = ChangePlan(
133
+ instruction=f"Rename {symbol_type} '{old_name}' to '{new_name}'",
134
+ summary=f"Renaming {symbol_type} across {len(relevant_files)} files",
135
+ changes=[
136
+ FileChange(
137
+ file_path=path,
138
+ change_type=ChangeType.MODIFY,
139
+ description=f"Rename '{old_name}' to '{new_name}'",
140
+ original_content=content,
141
+ )
142
+ for path, content in relevant_files.items()
143
+ ],
144
+ )
145
+
146
+ # Generate changes using LLM
147
+ formatted_contents = self._format_file_contents(relevant_files)
148
+
149
+ try:
150
+ response = self.llm.invoke_with_template(
151
+ RENAME_SYMBOL_PROMPT,
152
+ old_name=old_name,
153
+ new_name=new_name,
154
+ symbol_type=symbol_type,
155
+ file_contents=formatted_contents,
156
+ )
157
+
158
+ self._parse_multi_file_response(plan, response)
159
+
160
+ except Exception as e:
161
+ return RefactorResult(plan=plan, error=f"LLM error: {str(e)}")
162
+
163
+ # Apply if not dry run
164
+ if not dry_run:
165
+ self._apply_changes(plan)
166
+ return RefactorResult(plan=plan, applied=True)
167
+
168
+ return RefactorResult(plan=plan)
169
+
170
+ def _read_files(self, files: List[Path]) -> Dict[str, str]:
171
+ """Read contents of files."""
172
+ contents = {}
173
+ for file_path in files:
174
+ if file_path.exists():
175
+ try:
176
+ contents[str(file_path)] = file_path.read_text(encoding="utf-8")
177
+ except Exception:
178
+ pass
179
+ return contents
180
+
181
+ def _format_file_contents(self, contents: Dict[str, str]) -> str:
182
+ """Format file contents for prompt."""
183
+ parts = []
184
+ for file_path, content in contents.items():
185
+ lang = get_language_by_extension(self.config, Path(file_path)) or "text"
186
+ parts.append(f"### {file_path}\n```{lang}\n{content}\n```\n")
187
+ return "\n".join(parts)
188
+
189
+ def _generate_changes(
190
+ self,
191
+ plan: ChangePlan,
192
+ file_contents: Dict[str, str],
193
+ ) -> RefactorResult:
194
+ """Generate actual file changes using LLM.
195
+
196
+ Args:
197
+ plan: The change plan
198
+ file_contents: Current file contents
199
+
200
+ Returns:
201
+ RefactorResult with generated changes
202
+ """
203
+ # Format change plan for prompt
204
+ change_plan_text = "\n".join([
205
+ f"- {c.file_path}: {c.description} ({c.change_type.value})"
206
+ for c in plan.changes
207
+ ])
208
+
209
+ formatted_contents = self._format_file_contents(file_contents)
210
+
211
+ try:
212
+ response = self.llm.invoke_with_template(
213
+ MULTI_FILE_EDIT_PROMPT,
214
+ instruction=plan.instruction,
215
+ change_plan=change_plan_text,
216
+ file_contents=formatted_contents,
217
+ )
218
+
219
+ self._parse_multi_file_response(plan, response)
220
+
221
+ # Generate diffs for each change
222
+ for change in plan.changes:
223
+ if change.new_content and change.original_content:
224
+ change.diff = self.diff_handler.generate_diff(
225
+ original=change.original_content,
226
+ modified=change.new_content,
227
+ filename=change.file_path,
228
+ )
229
+
230
+ return RefactorResult(plan=plan)
231
+
232
+ except Exception as e:
233
+ return RefactorResult(plan=plan, error=f"LLM error: {str(e)}")
234
+
235
+ def _parse_multi_file_response(self, plan: ChangePlan, response: str) -> None:
236
+ """Parse LLM response containing multiple file contents.
237
+
238
+ Args:
239
+ plan: Change plan to update
240
+ response: LLM response with file contents
241
+ """
242
+ # Pattern to match file sections
243
+ pattern = r"###\s*FILE:\s*([^\n]+)\s*\n```\w*\s*\n(.*?)```"
244
+ matches = re.findall(pattern, response, re.DOTALL)
245
+
246
+ # Map new content to changes
247
+ for file_path, content in matches:
248
+ file_path = file_path.strip()
249
+ content = content.strip()
250
+
251
+ # Find matching change in plan
252
+ for change in plan.changes:
253
+ if change.file_path == file_path or file_path.endswith(change.file_path):
254
+ change.new_content = content
255
+ break
256
+
257
+ def _create_backup(self, plan: ChangePlan) -> Optional[Path]:
258
+ """Create backup of all files to be modified.
259
+
260
+ Args:
261
+ plan: Change plan with files to backup
262
+
263
+ Returns:
264
+ Path to backup directory or None
265
+ """
266
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
267
+ backup_dir = Path(f".refactor_backup_{timestamp}")
268
+
269
+ try:
270
+ backup_dir.mkdir(exist_ok=True)
271
+
272
+ for change in plan.changes:
273
+ if change.change_type in (ChangeType.MODIFY, ChangeType.DELETE):
274
+ src = Path(change.file_path)
275
+ if src.exists():
276
+ # Preserve directory structure in backup
277
+ dest = backup_dir / change.file_path
278
+ dest.parent.mkdir(parents=True, exist_ok=True)
279
+ shutil.copy2(src, dest)
280
+
281
+ return backup_dir
282
+
283
+ except Exception:
284
+ return None
285
+
286
+ def _apply_changes(self, plan: ChangePlan) -> None:
287
+ """Apply all changes in the plan.
288
+
289
+ Args:
290
+ plan: Change plan with changes to apply
291
+ """
292
+ for change in plan.get_ordered_changes():
293
+ try:
294
+ path = Path(change.file_path)
295
+
296
+ if change.change_type == ChangeType.DELETE:
297
+ if path.exists():
298
+ path.unlink()
299
+ change.applied = True
300
+
301
+ elif change.change_type == ChangeType.CREATE:
302
+ path.parent.mkdir(parents=True, exist_ok=True)
303
+ path.write_text(change.new_content, encoding="utf-8")
304
+ change.applied = True
305
+
306
+ elif change.change_type == ChangeType.RENAME:
307
+ if path.exists() and change.new_path:
308
+ new_path = Path(change.new_path)
309
+ new_path.parent.mkdir(parents=True, exist_ok=True)
310
+ shutil.move(str(path), str(new_path))
311
+ change.applied = True
312
+
313
+ elif change.change_type == ChangeType.MODIFY:
314
+ if change.new_content:
315
+ path.write_text(change.new_content, encoding="utf-8")
316
+ change.applied = True
317
+ else:
318
+ change.error = "No new content generated"
319
+
320
+ except Exception as e:
321
+ change.error = str(e)
322
+
323
+ def restore_backup(self, backup_dir: Path) -> bool:
324
+ """Restore files from backup.
325
+
326
+ Args:
327
+ backup_dir: Path to backup directory
328
+
329
+ Returns:
330
+ True if successful
331
+ """
332
+ if not backup_dir.exists():
333
+ return False
334
+
335
+ try:
336
+ for backup_file in backup_dir.rglob("*"):
337
+ if backup_file.is_file():
338
+ # Get relative path from backup dir
339
+ rel_path = backup_file.relative_to(backup_dir)
340
+ dest = Path(rel_path)
341
+ dest.parent.mkdir(parents=True, exist_ok=True)
342
+ shutil.copy2(backup_file, dest)
343
+ return True
344
+ except Exception:
345
+ return False
346
+
@@ -0,0 +1,175 @@
1
+ """Prompt templates for multi-file refactoring."""
2
+
3
+ from langchain_core.prompts import ChatPromptTemplate
4
+
5
+ # System prompt for analyzing refactoring scope
6
+ ANALYZE_REFACTOR_SYSTEM = """You are an expert code architect analyzing a codebase for refactoring.
7
+
8
+ Your task is to analyze the provided code files and determine what changes are needed to accomplish the refactoring goal.
9
+
10
+ For each file that needs changes, provide:
11
+ 1. The file path
12
+ 2. The type of change (modify, create, delete, rename)
13
+ 3. A description of what changes are needed
14
+ 4. The priority (high, medium, low)
15
+ 5. Dependencies on other file changes
16
+
17
+ Return your analysis as JSON with this structure:
18
+ {
19
+ "summary": "Brief description of the refactoring plan",
20
+ "affected_files": [
21
+ {
22
+ "file_path": "path/to/file.py",
23
+ "change_type": "modify|create|delete|rename",
24
+ "description": "What changes are needed",
25
+ "priority": "high|medium|low",
26
+ "depends_on": ["other/file.py"]
27
+ }
28
+ ],
29
+ "risks": ["List of potential risks or breaking changes"],
30
+ "estimated_complexity": "low|medium|high"
31
+ }"""
32
+
33
+ ANALYZE_REFACTOR_PROMPT = ChatPromptTemplate.from_messages([
34
+ ("system", ANALYZE_REFACTOR_SYSTEM),
35
+ ("human", """Analyze the following codebase for this refactoring task:
36
+
37
+ **Refactoring Goal:** {instruction}
38
+
39
+ **Files in scope:**
40
+ {file_contents}
41
+
42
+ Analyze what changes are needed and return a JSON plan.""")
43
+ ])
44
+
45
+ # System prompt for generating multi-file changes
46
+ MULTI_FILE_EDIT_SYSTEM = """You are an expert code refactoring assistant performing coordinated changes across multiple files.
47
+
48
+ When making changes:
49
+ 1. Ensure consistency across all files
50
+ 2. Update all imports and references
51
+ 3. Maintain backward compatibility where possible
52
+ 4. Follow existing code style and patterns
53
+ 5. Add appropriate type hints and docstrings
54
+
55
+ For each file, return the COMPLETE modified content wrapped in a code block with the filename as a comment."""
56
+
57
+ MULTI_FILE_EDIT_PROMPT = ChatPromptTemplate.from_messages([
58
+ ("system", MULTI_FILE_EDIT_SYSTEM),
59
+ ("human", """Apply the following refactoring across these files:
60
+
61
+ **Refactoring Goal:** {instruction}
62
+
63
+ **Change Plan:**
64
+ {change_plan}
65
+
66
+ **Current File Contents:**
67
+ {file_contents}
68
+
69
+ For each file that needs changes, return the complete modified content in this format:
70
+
71
+ ### FILE: path/to/file.py
72
+ ```python
73
+ # complete file content here
74
+ ```
75
+
76
+ ### FILE: path/to/another.py
77
+ ```javascript
78
+ // complete file content here
79
+ ```
80
+
81
+ Return ALL files that need changes with their complete content.""")
82
+ ])
83
+
84
+ # Prompt for renaming symbols across files
85
+ RENAME_SYMBOL_PROMPT = ChatPromptTemplate.from_messages([
86
+ ("system", """You are an expert at renaming symbols across a codebase.
87
+
88
+ When renaming:
89
+ 1. Update all occurrences of the symbol
90
+ 2. Update imports and exports
91
+ 3. Update docstrings and comments that reference the symbol
92
+ 4. Preserve all other code exactly as-is
93
+
94
+ Return each modified file with its complete content."""),
95
+ ("human", """Rename the symbol across these files:
96
+
97
+ **Old Name:** {old_name}
98
+ **New Name:** {new_name}
99
+ **Symbol Type:** {symbol_type}
100
+
101
+ **Files:**
102
+ {file_contents}
103
+
104
+ Return each modified file with complete content in this format:
105
+
106
+ ### FILE: path/to/file.py
107
+ ```python
108
+ # complete file content
109
+ ```""")
110
+ ])
111
+
112
+ # Prompt for extracting code to new file
113
+ EXTRACT_TO_FILE_PROMPT = ChatPromptTemplate.from_messages([
114
+ ("system", """You are an expert at extracting code into separate modules.
115
+
116
+ When extracting:
117
+ 1. Move the specified code to a new file
118
+ 2. Add appropriate imports to the new file
119
+ 3. Update the original file to import from the new location
120
+ 4. Ensure all references are updated
121
+
122
+ Return both the new file and modified original file."""),
123
+ ("human", """Extract code to a new file:
124
+
125
+ **What to Extract:** {instruction}
126
+ **New File Path:** {new_file_path}
127
+
128
+ **Original File ({original_file}):**
129
+ ```{language}
130
+ {original_content}
131
+ ```
132
+
133
+ Return both files with complete content:
134
+
135
+ ### FILE: {new_file_path}
136
+ ```{language}
137
+ # new file content
138
+ ```
139
+
140
+ ### FILE: {original_file}
141
+ ```{language}
142
+ # modified original file
143
+ ```""")
144
+ ])
145
+
146
+ # Prompt for adding feature across multiple files
147
+ ADD_FEATURE_MULTI_PROMPT = ChatPromptTemplate.from_messages([
148
+ ("system", """You are an expert at adding features that span multiple files.
149
+
150
+ When adding features:
151
+ 1. Follow existing patterns in the codebase
152
+ 2. Add necessary imports
153
+ 3. Update configuration if needed
154
+ 4. Add appropriate tests structure
155
+ 5. Maintain consistency with existing code style
156
+
157
+ Return all files that need to be created or modified."""),
158
+ ("human", """Add the following feature across the codebase:
159
+
160
+ **Feature Description:** {instruction}
161
+
162
+ **Existing Files:**
163
+ {file_contents}
164
+
165
+ **Files to Create/Modify:**
166
+ {target_files}
167
+
168
+ Return all files with complete content in this format:
169
+
170
+ ### FILE: path/to/file.py
171
+ ```python
172
+ # complete file content
173
+ ```""")
174
+ ])
175
+
@@ -0,0 +1,19 @@
1
+ """
2
+ Codebase retrieval module for semantic search.
3
+
4
+ This module provides functionality to:
5
+ - Index code files into a vector database
6
+ - Search for relevant code using natural language queries
7
+ - Watch for file changes and update the index
8
+ """
9
+
10
+ from .indexer import CodebaseIndexer
11
+ from .search import CodebaseSearch
12
+ from .chunker import CodeChunker
13
+
14
+ __all__ = [
15
+ "CodebaseIndexer",
16
+ "CodebaseSearch",
17
+ "CodeChunker",
18
+ ]
19
+