groknroll 2.0.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 (62) hide show
  1. groknroll/__init__.py +36 -0
  2. groknroll/__main__.py +9 -0
  3. groknroll/agents/__init__.py +18 -0
  4. groknroll/agents/agent_manager.py +187 -0
  5. groknroll/agents/base_agent.py +118 -0
  6. groknroll/agents/build_agent.py +231 -0
  7. groknroll/agents/plan_agent.py +215 -0
  8. groknroll/cli/__init__.py +7 -0
  9. groknroll/cli/enhanced_cli.py +372 -0
  10. groknroll/cli/large_codebase_cli.py +413 -0
  11. groknroll/cli/main.py +331 -0
  12. groknroll/cli/rlm_commands.py +258 -0
  13. groknroll/clients/__init__.py +63 -0
  14. groknroll/clients/anthropic.py +112 -0
  15. groknroll/clients/azure_openai.py +142 -0
  16. groknroll/clients/base_lm.py +33 -0
  17. groknroll/clients/gemini.py +162 -0
  18. groknroll/clients/litellm.py +105 -0
  19. groknroll/clients/openai.py +129 -0
  20. groknroll/clients/portkey.py +94 -0
  21. groknroll/core/__init__.py +9 -0
  22. groknroll/core/agent.py +339 -0
  23. groknroll/core/comms_utils.py +264 -0
  24. groknroll/core/context.py +251 -0
  25. groknroll/core/exceptions.py +181 -0
  26. groknroll/core/large_codebase.py +564 -0
  27. groknroll/core/lm_handler.py +206 -0
  28. groknroll/core/rlm.py +446 -0
  29. groknroll/core/rlm_codebase.py +448 -0
  30. groknroll/core/rlm_integration.py +256 -0
  31. groknroll/core/types.py +276 -0
  32. groknroll/environments/__init__.py +34 -0
  33. groknroll/environments/base_env.py +182 -0
  34. groknroll/environments/constants.py +32 -0
  35. groknroll/environments/docker_repl.py +336 -0
  36. groknroll/environments/local_repl.py +388 -0
  37. groknroll/environments/modal_repl.py +502 -0
  38. groknroll/environments/prime_repl.py +588 -0
  39. groknroll/logger/__init__.py +4 -0
  40. groknroll/logger/rlm_logger.py +63 -0
  41. groknroll/logger/verbose.py +393 -0
  42. groknroll/operations/__init__.py +15 -0
  43. groknroll/operations/bash_ops.py +447 -0
  44. groknroll/operations/file_ops.py +473 -0
  45. groknroll/operations/git_ops.py +620 -0
  46. groknroll/oracle/__init__.py +11 -0
  47. groknroll/oracle/codebase_indexer.py +238 -0
  48. groknroll/oracle/oracle_agent.py +278 -0
  49. groknroll/setup.py +34 -0
  50. groknroll/storage/__init__.py +14 -0
  51. groknroll/storage/database.py +272 -0
  52. groknroll/storage/models.py +128 -0
  53. groknroll/utils/__init__.py +0 -0
  54. groknroll/utils/parsing.py +168 -0
  55. groknroll/utils/prompts.py +146 -0
  56. groknroll/utils/rlm_utils.py +19 -0
  57. groknroll-2.0.0.dist-info/METADATA +246 -0
  58. groknroll-2.0.0.dist-info/RECORD +62 -0
  59. groknroll-2.0.0.dist-info/WHEEL +5 -0
  60. groknroll-2.0.0.dist-info/entry_points.txt +3 -0
  61. groknroll-2.0.0.dist-info/licenses/LICENSE +21 -0
  62. groknroll-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,251 @@
1
+ """
2
+ Project Context Management
3
+
4
+ Maintains comprehensive project state including:
5
+ - File index with AST metadata
6
+ - Dependency graphs
7
+ - Test coverage
8
+ - Conversation history
9
+ - Active tasks
10
+ """
11
+
12
+ from pathlib import Path
13
+ from typing import Dict, List, Optional, Any
14
+ from datetime import datetime
15
+ import json
16
+
17
+ from groknroll.storage.database import Database
18
+ from groknroll.storage.models import Project, FileIndex
19
+
20
+
21
+ class ProjectContext:
22
+ """
23
+ Project context manager
24
+
25
+ Maintains all project state in SQLite database for instant access.
26
+ """
27
+
28
+ def __init__(self, project_path: Path, database: Optional[Database] = None):
29
+ """
30
+ Initialize project context
31
+
32
+ Args:
33
+ project_path: Path to project root
34
+ database: Database instance (creates new if None)
35
+ """
36
+ self.project_path = project_path.resolve()
37
+ self.db = database or Database()
38
+
39
+ # Get or create project in database
40
+ self.project = self.db.get_or_create_project(self.project_path)
41
+
42
+ def index_project(self, force: bool = False) -> Dict[str, Any]:
43
+ """
44
+ Index entire project
45
+
46
+ Args:
47
+ force: Force re-indexing even if up-to-date
48
+
49
+ Returns:
50
+ Indexing statistics
51
+ """
52
+ indexed_count = 0
53
+ total_lines = 0
54
+ skipped_count = 0
55
+
56
+ # File extensions to index
57
+ code_extensions = {
58
+ '.py', '.js', '.ts', '.jsx', '.tsx',
59
+ '.go', '.rs', '.java', '.c', '.cpp', '.h', '.hpp',
60
+ '.rb', '.php', '.swift', '.kt', '.scala'
61
+ }
62
+
63
+ for file_path in self.project_path.rglob('*'):
64
+ if not file_path.is_file():
65
+ continue
66
+
67
+ if file_path.suffix not in code_extensions:
68
+ continue
69
+
70
+ # Skip common directories
71
+ if any(part.startswith('.') or part in ['node_modules', 'venv', '.venv', 'dist', 'build']
72
+ for part in file_path.parts):
73
+ skipped_count += 1
74
+ continue
75
+
76
+ try:
77
+ # Get file stats
78
+ stat = file_path.stat()
79
+ relative_path = str(file_path.relative_to(self.project_path))
80
+
81
+ # Count lines
82
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
83
+ lines = len(f.readlines())
84
+ total_lines += lines
85
+
86
+ # Determine language
87
+ language = self._detect_language(file_path.suffix)
88
+
89
+ # Index file
90
+ self.db.index_file(
91
+ project_id=self.project.id,
92
+ file_path=file_path,
93
+ relative_path=relative_path,
94
+ language=language,
95
+ size_bytes=stat.st_size,
96
+ lines_of_code=lines,
97
+ last_modified=datetime.fromtimestamp(stat.st_mtime)
98
+ )
99
+
100
+ indexed_count += 1
101
+
102
+ except Exception as e:
103
+ print(f"Error indexing {file_path}: {e}")
104
+ continue
105
+
106
+ # Update project stats
107
+ self.db.update_project_stats(
108
+ project_id=self.project.id,
109
+ total_files=indexed_count,
110
+ total_lines=total_lines
111
+ )
112
+
113
+ return {
114
+ "indexed": indexed_count,
115
+ "skipped": skipped_count,
116
+ "total_lines": total_lines
117
+ }
118
+
119
+ def get_file_context(self, file_path: Path) -> Optional[Dict[str, Any]]:
120
+ """Get context for specific file"""
121
+ files = self.db.get_project_files(self.project.id)
122
+
123
+ for f in files:
124
+ if Path(f.path) == file_path:
125
+ return {
126
+ "path": f.path,
127
+ "relative_path": f.relative_path,
128
+ "language": f.language,
129
+ "lines_of_code": f.lines_of_code,
130
+ "complexity": f.complexity,
131
+ "imports": f.imports,
132
+ "exports": f.exports,
133
+ }
134
+
135
+ return None
136
+
137
+ def search_files(self, query: str, language: Optional[str] = None) -> List[Dict[str, Any]]:
138
+ """Search indexed files"""
139
+ results = self.db.search_files(
140
+ project_id=self.project.id,
141
+ query=query,
142
+ language=language
143
+ )
144
+
145
+ return [
146
+ {
147
+ "path": f.path,
148
+ "relative_path": f.relative_path,
149
+ "language": f.language,
150
+ "lines_of_code": f.lines_of_code,
151
+ }
152
+ for f in results
153
+ ]
154
+
155
+ def get_project_overview(self) -> Dict[str, Any]:
156
+ """Get project overview"""
157
+ files = self.db.get_project_files(self.project.id)
158
+
159
+ # Group by language
160
+ language_stats = {}
161
+ for f in files:
162
+ lang = f.language or "unknown"
163
+ if lang not in language_stats:
164
+ language_stats[lang] = {"files": 0, "lines": 0}
165
+
166
+ language_stats[lang]["files"] += 1
167
+ language_stats[lang]["lines"] += f.lines_of_code or 0
168
+
169
+ return {
170
+ "name": self.project.name,
171
+ "path": self.project.path,
172
+ "total_files": self.project.total_files or 0,
173
+ "total_lines": self.project.total_lines or 0,
174
+ "last_indexed": self.project.last_indexed,
175
+ "languages": language_stats
176
+ }
177
+
178
+ def get_execution_stats(self) -> Dict[str, Any]:
179
+ """Get RLM execution statistics"""
180
+ return self.db.get_execution_stats(self.project.id)
181
+
182
+ def log_execution(
183
+ self,
184
+ task: str,
185
+ response: str,
186
+ **metrics
187
+ ) -> None:
188
+ """Log RLM execution"""
189
+ self.db.log_execution(
190
+ project_id=self.project.id,
191
+ task=task,
192
+ response=response,
193
+ **metrics
194
+ )
195
+
196
+ def save_analysis(
197
+ self,
198
+ analysis_type: str,
199
+ results: Dict[str, Any],
200
+ **metadata
201
+ ) -> None:
202
+ """Save analysis results"""
203
+ self.db.save_analysis(
204
+ project_id=self.project.id,
205
+ analysis_type=analysis_type,
206
+ results=results,
207
+ **metadata
208
+ )
209
+
210
+ def get_latest_analysis(self, analysis_type: str) -> Optional[Dict[str, Any]]:
211
+ """Get latest analysis of given type"""
212
+ analysis = self.db.get_latest_analysis(
213
+ project_id=self.project.id,
214
+ analysis_type=analysis_type
215
+ )
216
+
217
+ if analysis:
218
+ return {
219
+ "type": analysis.analysis_type,
220
+ "results": analysis.results,
221
+ "recommendations": analysis.recommendations,
222
+ "issues": analysis.issues,
223
+ "metrics": analysis.metrics,
224
+ "created_at": analysis.created_at
225
+ }
226
+
227
+ return None
228
+
229
+ def _detect_language(self, extension: str) -> str:
230
+ """Detect language from file extension"""
231
+ mapping = {
232
+ '.py': 'python',
233
+ '.js': 'javascript',
234
+ '.ts': 'typescript',
235
+ '.jsx': 'javascript',
236
+ '.tsx': 'typescript',
237
+ '.go': 'go',
238
+ '.rs': 'rust',
239
+ '.java': 'java',
240
+ '.c': 'c',
241
+ '.cpp': 'cpp',
242
+ '.h': 'c',
243
+ '.hpp': 'cpp',
244
+ '.rb': 'ruby',
245
+ '.php': 'php',
246
+ '.swift': 'swift',
247
+ '.kt': 'kotlin',
248
+ '.scala': 'scala',
249
+ }
250
+
251
+ return mapping.get(extension, 'unknown')
@@ -0,0 +1,181 @@
1
+ """
2
+ Custom exception classes for the RLM system.
3
+
4
+ These exceptions provide better error handling and debugging capabilities
5
+ throughout the RLM pipeline.
6
+ """
7
+
8
+
9
+ class RLMError(Exception):
10
+ """Base exception for all RLM-related errors."""
11
+
12
+ pass
13
+
14
+
15
+ class REPLExecutionError(RLMError):
16
+ """Raised when code execution in the REPL environment fails."""
17
+
18
+ def __init__(self, message: str, code: str | None = None, stderr: str | None = None):
19
+ """Initialize REPLExecutionError.
20
+
21
+ Args:
22
+ message: Human-readable error message
23
+ code: The code that failed to execute
24
+ stderr: Standard error output from the execution
25
+ """
26
+ super().__init__(message)
27
+ self.code = code
28
+ self.stderr = stderr
29
+
30
+
31
+ class LMHandlerError(RLMError):
32
+ """Base exception for LM handler-related errors."""
33
+
34
+ pass
35
+
36
+
37
+ class LMHandlerTimeoutError(LMHandlerError):
38
+ """Raised when an LM handler request times out."""
39
+
40
+ def __init__(self, message: str, timeout_seconds: float | None = None):
41
+ """Initialize LMHandlerTimeoutError.
42
+
43
+ Args:
44
+ message: Human-readable error message
45
+ timeout_seconds: The timeout value that was exceeded
46
+ """
47
+ super().__init__(message)
48
+ self.timeout_seconds = timeout_seconds
49
+
50
+
51
+ class LMHandlerConnectionError(LMHandlerError):
52
+ """Raised when connection to LM handler fails."""
53
+
54
+ pass
55
+
56
+
57
+ class ContextError(RLMError):
58
+ """Base exception for context-related errors."""
59
+
60
+ pass
61
+
62
+
63
+ class ContextTooLargeError(ContextError):
64
+ """Raised when context exceeds reasonable size limits."""
65
+
66
+ def __init__(
67
+ self, message: str, context_size: int | None = None, max_size: int | None = None
68
+ ):
69
+ """Initialize ContextTooLargeError.
70
+
71
+ Args:
72
+ message: Human-readable error message
73
+ context_size: Actual size of the context
74
+ max_size: Maximum allowed context size
75
+ """
76
+ super().__init__(message)
77
+ self.context_size = context_size
78
+ self.max_size = max_size
79
+
80
+
81
+ class CostLimitExceededError(RLMError):
82
+ """Raised when cost limits are exceeded during RLM execution."""
83
+
84
+ def __init__(
85
+ self,
86
+ message: str,
87
+ current_cost: float | None = None,
88
+ cost_limit: float | None = None,
89
+ ):
90
+ """Initialize CostLimitExceededError.
91
+
92
+ Args:
93
+ message: Human-readable error message
94
+ current_cost: Current accumulated cost
95
+ cost_limit: Maximum allowed cost
96
+ """
97
+ super().__init__(message)
98
+ self.current_cost = current_cost
99
+ self.cost_limit = cost_limit
100
+
101
+
102
+ class IterationLimitExceededError(RLMError):
103
+ """Raised when maximum iterations are exceeded."""
104
+
105
+ def __init__(
106
+ self, message: str, max_iterations: int | None = None, current_iteration: int | None = None
107
+ ):
108
+ """Initialize IterationLimitExceededError.
109
+
110
+ Args:
111
+ message: Human-readable error message
112
+ max_iterations: Maximum allowed iterations
113
+ current_iteration: Current iteration number
114
+ """
115
+ super().__init__(message)
116
+ self.max_iterations = max_iterations
117
+ self.current_iteration = current_iteration
118
+
119
+
120
+ class CompletionTimeoutError(RLMError):
121
+ """Raised when a completion times out."""
122
+
123
+ def __init__(
124
+ self, message: str, timeout_seconds: float | None = None, elapsed_seconds: float | None = None
125
+ ):
126
+ """Initialize CompletionTimeoutError.
127
+
128
+ Args:
129
+ message: Human-readable error message
130
+ timeout_seconds: Configured timeout value
131
+ elapsed_seconds: Time elapsed before timeout
132
+ """
133
+ super().__init__(message)
134
+ self.timeout_seconds = timeout_seconds
135
+ self.elapsed_seconds = elapsed_seconds
136
+
137
+
138
+ class EnvironmentError(RLMError):
139
+ """Base exception for environment-related errors."""
140
+
141
+ pass
142
+
143
+
144
+ class EnvironmentSetupError(EnvironmentError):
145
+ """Raised when environment setup fails."""
146
+
147
+ pass
148
+
149
+
150
+ class EnvironmentCleanupError(EnvironmentError):
151
+ """Raised when environment cleanup fails."""
152
+
153
+ pass
154
+
155
+
156
+ class ParsingError(RLMError):
157
+ """Raised when parsing RLM responses fails."""
158
+
159
+ def __init__(self, message: str, response_text: str | None = None):
160
+ """Initialize ParsingError.
161
+
162
+ Args:
163
+ message: Human-readable error message
164
+ response_text: The response text that failed to parse
165
+ """
166
+ super().__init__(message)
167
+ self.response_text = response_text
168
+
169
+
170
+ class FinalAnswerNotFoundError(ParsingError):
171
+ """Raised when no final answer is found after max iterations."""
172
+
173
+ def __init__(self, message: str, iterations_completed: int | None = None):
174
+ """Initialize FinalAnswerNotFoundError.
175
+
176
+ Args:
177
+ message: Human-readable error message
178
+ iterations_completed: Number of iterations that were completed
179
+ """
180
+ super().__init__(message)
181
+ self.iterations_completed = iterations_completed