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,149 @@
1
+ """Configuration management for AI Code Assistant."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ import yaml
7
+ from pydantic import BaseModel, Field
8
+ from pydantic_settings import BaseSettings
9
+
10
+
11
+ class LLMConfig(BaseModel):
12
+ """LLM configuration settings."""
13
+ provider: str = "ollama" # ollama, google, groq, cerebras, openrouter, openai
14
+ model: str = "deepseek-coder:6.7b"
15
+ api_key: Optional[str] = None # Can also use environment variables
16
+ base_url: Optional[str] = None # Optional custom base URL
17
+ temperature: float = 0.1
18
+ max_tokens: int = 4096
19
+ timeout: int = 120
20
+
21
+
22
+ class ReviewConfig(BaseModel):
23
+ """Code review configuration settings."""
24
+ severity_levels: List[str] = Field(default=["critical", "warning", "suggestion"])
25
+ categories: List[str] = Field(
26
+ default=["bugs", "security", "performance", "style", "best_practices"]
27
+ )
28
+ max_file_size_kb: int = 500
29
+ include_line_numbers: bool = True
30
+ include_confidence: bool = True
31
+
32
+
33
+ class GenerationConfig(BaseModel):
34
+ """Code generation configuration settings."""
35
+ include_type_hints: bool = True
36
+ include_docstrings: bool = True
37
+ default_mode: str = "function"
38
+ max_lines: int = 500
39
+
40
+
41
+ class OutputConfig(BaseModel):
42
+ """Output configuration settings."""
43
+ default_format: str = "console"
44
+ use_colors: bool = True
45
+ output_dir: str = "./output"
46
+ verbose: bool = False
47
+
48
+
49
+ class RetrievalConfig(BaseModel):
50
+ """Codebase retrieval configuration settings."""
51
+ embedding_model: str = "all-MiniLM-L6-v2"
52
+ persist_directory: str = ".ai-assistant-index"
53
+ collection_name: str = "codebase"
54
+ chunk_size: int = 50
55
+ chunk_overlap: int = 10
56
+ max_file_size_kb: int = 1024
57
+
58
+
59
+ class EditorConfig(BaseModel):
60
+ """File editing configuration settings."""
61
+ create_backup: bool = True
62
+ show_diff: bool = True
63
+ max_file_size_kb: int = 500
64
+ auto_format: bool = False
65
+
66
+
67
+ class RefactorConfig(BaseModel):
68
+ """Multi-file refactoring configuration settings."""
69
+ max_files: int = 20
70
+ max_file_size_kb: int = 500
71
+ create_backup: bool = True
72
+ require_confirmation: bool = True
73
+ show_plan: bool = True
74
+
75
+
76
+ class LanguageConfig(BaseModel):
77
+ """Language-specific configuration."""
78
+ extensions: List[str]
79
+ comment_style: str
80
+
81
+
82
+ class Config(BaseSettings):
83
+ """Main configuration class."""
84
+ llm: LLMConfig = Field(default_factory=LLMConfig)
85
+ review: ReviewConfig = Field(default_factory=ReviewConfig)
86
+ generation: GenerationConfig = Field(default_factory=GenerationConfig)
87
+ output: OutputConfig = Field(default_factory=OutputConfig)
88
+ retrieval: RetrievalConfig = Field(default_factory=RetrievalConfig)
89
+ editor: EditorConfig = Field(default_factory=EditorConfig)
90
+ refactor: RefactorConfig = Field(default_factory=RefactorConfig)
91
+ languages: Dict[str, LanguageConfig] = Field(default_factory=dict)
92
+
93
+ class Config:
94
+ env_prefix = "AI_ASSIST_"
95
+
96
+
97
+ def find_config_file() -> Optional[Path]:
98
+ """Find configuration file in standard locations."""
99
+ locations = [
100
+ Path.cwd() / "config.yaml",
101
+ Path.cwd() / ".ai-code-assistant.yaml",
102
+ Path.home() / ".ai-code-assistant" / "config.yaml",
103
+ Path.home() / ".config" / "ai-code-assistant" / "config.yaml",
104
+ ]
105
+ for path in locations:
106
+ if path.exists():
107
+ return path
108
+ return None
109
+
110
+
111
+ def load_config(config_path: Optional[Path] = None) -> Config:
112
+ """Load configuration from file or use defaults."""
113
+ if config_path is None:
114
+ config_path = find_config_file()
115
+
116
+ if config_path and config_path.exists():
117
+ with open(config_path) as f:
118
+ data = yaml.safe_load(f)
119
+ return _parse_config(data)
120
+
121
+ return Config()
122
+
123
+
124
+ def _parse_config(data: Dict[str, Any]) -> Config:
125
+ """Parse configuration dictionary into Config object."""
126
+ languages = {}
127
+ if "languages" in data:
128
+ for lang_name, lang_data in data["languages"].items():
129
+ languages[lang_name] = LanguageConfig(**lang_data)
130
+ data["languages"] = languages
131
+
132
+ return Config(**data)
133
+
134
+
135
+ def get_language_by_extension(config: Config, file_path: Path) -> Optional[str]:
136
+ """Detect language from file extension."""
137
+ ext = file_path.suffix.lower()
138
+ for lang_name, lang_config in config.languages.items():
139
+ if ext in lang_config.extensions:
140
+ return lang_name
141
+
142
+ # Fallback detection
143
+ extension_map = {
144
+ ".py": "python", ".pyw": "python",
145
+ ".js": "javascript", ".jsx": "javascript", ".mjs": "javascript",
146
+ ".ts": "typescript", ".tsx": "typescript",
147
+ ".java": "java", ".go": "go", ".rs": "rust",
148
+ }
149
+ return extension_map.get(ext)
@@ -0,0 +1,8 @@
1
+ """File editing module for AI Code Assistant."""
2
+
3
+ from ai_code_assistant.editor.file_editor import FileEditor, EditResult
4
+ from ai_code_assistant.editor.diff_handler import DiffHandler, DiffResult
5
+ from ai_code_assistant.editor.prompts import EDIT_PROMPTS
6
+
7
+ __all__ = ["FileEditor", "EditResult", "DiffHandler", "DiffResult", "EDIT_PROMPTS"]
8
+
@@ -0,0 +1,270 @@
1
+ """Diff generation and application for code editing."""
2
+
3
+ import difflib
4
+ from dataclasses import dataclass, field
5
+ from typing import List, Optional, Tuple
6
+
7
+
8
+ @dataclass
9
+ class DiffLine:
10
+ """Represents a single line in a diff."""
11
+ line_number: int
12
+ content: str
13
+ change_type: str # 'add', 'remove', 'context', 'header'
14
+
15
+ def __str__(self) -> str:
16
+ prefix = {
17
+ 'add': '+',
18
+ 'remove': '-',
19
+ 'context': ' ',
20
+ 'header': '@',
21
+ }.get(self.change_type, ' ')
22
+ return f"{prefix} {self.content}"
23
+
24
+
25
+ @dataclass
26
+ class DiffHunk:
27
+ """Represents a hunk (section) of changes in a diff."""
28
+ old_start: int
29
+ old_count: int
30
+ new_start: int
31
+ new_count: int
32
+ lines: List[DiffLine] = field(default_factory=list)
33
+
34
+ @property
35
+ def header(self) -> str:
36
+ return f"@@ -{self.old_start},{self.old_count} +{self.new_start},{self.new_count} @@"
37
+
38
+
39
+ @dataclass
40
+ class DiffResult:
41
+ """Result of diff generation."""
42
+ original_file: str
43
+ modified_file: str
44
+ hunks: List[DiffHunk] = field(default_factory=list)
45
+ unified_diff: str = ""
46
+ additions: int = 0
47
+ deletions: int = 0
48
+
49
+ @property
50
+ def has_changes(self) -> bool:
51
+ return self.additions > 0 or self.deletions > 0
52
+
53
+ @property
54
+ def summary(self) -> str:
55
+ return f"+{self.additions} -{self.deletions} lines"
56
+
57
+ def to_dict(self) -> dict:
58
+ return {
59
+ "original_file": self.original_file,
60
+ "modified_file": self.modified_file,
61
+ "additions": self.additions,
62
+ "deletions": self.deletions,
63
+ "unified_diff": self.unified_diff,
64
+ "has_changes": self.has_changes,
65
+ }
66
+
67
+
68
+ class DiffHandler:
69
+ """Handles diff generation and application."""
70
+
71
+ def __init__(self, context_lines: int = 3):
72
+ """Initialize diff handler.
73
+
74
+ Args:
75
+ context_lines: Number of context lines around changes
76
+ """
77
+ self.context_lines = context_lines
78
+
79
+ def generate_diff(
80
+ self,
81
+ original: str,
82
+ modified: str,
83
+ filename: str = "file",
84
+ ) -> DiffResult:
85
+ """Generate a unified diff between original and modified content.
86
+
87
+ Args:
88
+ original: Original file content
89
+ modified: Modified file content
90
+ filename: Name of the file for diff headers
91
+
92
+ Returns:
93
+ DiffResult with diff information
94
+ """
95
+ original_lines = original.splitlines(keepends=True)
96
+ modified_lines = modified.splitlines(keepends=True)
97
+
98
+ # Ensure files end with newline for proper diff
99
+ if original_lines and not original_lines[-1].endswith('\n'):
100
+ original_lines[-1] += '\n'
101
+ if modified_lines and not modified_lines[-1].endswith('\n'):
102
+ modified_lines[-1] += '\n'
103
+
104
+ # Generate unified diff
105
+ diff_lines = list(difflib.unified_diff(
106
+ original_lines,
107
+ modified_lines,
108
+ fromfile=f"a/{filename}",
109
+ tofile=f"b/{filename}",
110
+ n=self.context_lines,
111
+ ))
112
+
113
+ unified_diff = ''.join(diff_lines)
114
+
115
+ # Count additions and deletions
116
+ additions = sum(1 for line in diff_lines if line.startswith('+') and not line.startswith('+++'))
117
+ deletions = sum(1 for line in diff_lines if line.startswith('-') and not line.startswith('---'))
118
+
119
+ # Parse hunks
120
+ hunks = self._parse_hunks(diff_lines)
121
+
122
+ return DiffResult(
123
+ original_file=filename,
124
+ modified_file=filename,
125
+ hunks=hunks,
126
+ unified_diff=unified_diff,
127
+ additions=additions,
128
+ deletions=deletions,
129
+ )
130
+
131
+ def _parse_hunks(self, diff_lines: List[str]) -> List[DiffHunk]:
132
+ """Parse diff lines into hunks."""
133
+ hunks = []
134
+ current_hunk = None
135
+
136
+ for line in diff_lines:
137
+ if line.startswith('@@'):
138
+ # Parse hunk header: @@ -start,count +start,count @@
139
+ if current_hunk:
140
+ hunks.append(current_hunk)
141
+
142
+ # Extract numbers from header
143
+ parts = line.split('@@')[1].strip().split()
144
+ old_part = parts[0][1:].split(',') # Remove '-'
145
+ new_part = parts[1][1:].split(',') # Remove '+'
146
+
147
+ old_start = int(old_part[0])
148
+ old_count = int(old_part[1]) if len(old_part) > 1 else 1
149
+ new_start = int(new_part[0])
150
+ new_count = int(new_part[1]) if len(new_part) > 1 else 1
151
+
152
+ current_hunk = DiffHunk(
153
+ old_start=old_start,
154
+ old_count=old_count,
155
+ new_start=new_start,
156
+ new_count=new_count,
157
+ )
158
+ elif current_hunk is not None:
159
+ if line.startswith('+') and not line.startswith('+++'):
160
+ change_type = 'add'
161
+ elif line.startswith('-') and not line.startswith('---'):
162
+ change_type = 'remove'
163
+ elif line.startswith(' '):
164
+ change_type = 'context'
165
+ else:
166
+ continue
167
+
168
+ current_hunk.lines.append(DiffLine(
169
+ line_number=0, # Will be calculated if needed
170
+ content=line[1:].rstrip('\n'),
171
+ change_type=change_type,
172
+ ))
173
+
174
+ if current_hunk:
175
+ hunks.append(current_hunk)
176
+
177
+ return hunks
178
+
179
+ def apply_diff(self, original: str, diff_result: DiffResult) -> str:
180
+ """Apply a diff to original content.
181
+
182
+ Note: This is a simplified implementation. For complex diffs,
183
+ consider using the patch utility or similar tools.
184
+
185
+ Args:
186
+ original: Original file content
187
+ diff_result: DiffResult to apply
188
+
189
+ Returns:
190
+ Modified content after applying diff
191
+ """
192
+ # For simplicity, we return the modified content that was used
193
+ # to generate the diff. In a real implementation, you might
194
+ # want to parse and apply the diff hunks.
195
+ # This is mainly useful for verification.
196
+ return diff_result.modified_file if hasattr(diff_result, '_modified_content') else original
197
+
198
+ def format_for_display(
199
+ self,
200
+ diff_result: DiffResult,
201
+ use_colors: bool = True,
202
+ ) -> str:
203
+ """Format diff for terminal display.
204
+
205
+ Args:
206
+ diff_result: DiffResult to format
207
+ use_colors: Whether to use ANSI colors
208
+
209
+ Returns:
210
+ Formatted diff string
211
+ """
212
+ if not diff_result.has_changes:
213
+ return "No changes detected."
214
+
215
+ lines = []
216
+
217
+ # Header
218
+ lines.append(f"--- a/{diff_result.original_file}")
219
+ lines.append(f"+++ b/{diff_result.modified_file}")
220
+
221
+ for hunk in diff_result.hunks:
222
+ lines.append(hunk.header)
223
+
224
+ for diff_line in hunk.lines:
225
+ if use_colors:
226
+ if diff_line.change_type == 'add':
227
+ lines.append(f"\033[32m+{diff_line.content}\033[0m")
228
+ elif diff_line.change_type == 'remove':
229
+ lines.append(f"\033[31m-{diff_line.content}\033[0m")
230
+ else:
231
+ lines.append(f" {diff_line.content}")
232
+ else:
233
+ prefix = {
234
+ 'add': '+',
235
+ 'remove': '-',
236
+ 'context': ' ',
237
+ }.get(diff_line.change_type, ' ')
238
+ lines.append(f"{prefix}{diff_line.content}")
239
+
240
+ return '\n'.join(lines)
241
+
242
+ def get_changed_lines(self, diff_result: DiffResult) -> Tuple[List[int], List[int]]:
243
+ """Get lists of added and removed line numbers.
244
+
245
+ Args:
246
+ diff_result: DiffResult to analyze
247
+
248
+ Returns:
249
+ Tuple of (added_lines, removed_lines)
250
+ """
251
+ added = []
252
+ removed = []
253
+
254
+ for hunk in diff_result.hunks:
255
+ old_line = hunk.old_start
256
+ new_line = hunk.new_start
257
+
258
+ for diff_line in hunk.lines:
259
+ if diff_line.change_type == 'add':
260
+ added.append(new_line)
261
+ new_line += 1
262
+ elif diff_line.change_type == 'remove':
263
+ removed.append(old_line)
264
+ old_line += 1
265
+ else: # context
266
+ old_line += 1
267
+ new_line += 1
268
+
269
+ return added, removed
270
+