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.
- groknroll/__init__.py +36 -0
- groknroll/__main__.py +9 -0
- groknroll/agents/__init__.py +18 -0
- groknroll/agents/agent_manager.py +187 -0
- groknroll/agents/base_agent.py +118 -0
- groknroll/agents/build_agent.py +231 -0
- groknroll/agents/plan_agent.py +215 -0
- groknroll/cli/__init__.py +7 -0
- groknroll/cli/enhanced_cli.py +372 -0
- groknroll/cli/large_codebase_cli.py +413 -0
- groknroll/cli/main.py +331 -0
- groknroll/cli/rlm_commands.py +258 -0
- groknroll/clients/__init__.py +63 -0
- groknroll/clients/anthropic.py +112 -0
- groknroll/clients/azure_openai.py +142 -0
- groknroll/clients/base_lm.py +33 -0
- groknroll/clients/gemini.py +162 -0
- groknroll/clients/litellm.py +105 -0
- groknroll/clients/openai.py +129 -0
- groknroll/clients/portkey.py +94 -0
- groknroll/core/__init__.py +9 -0
- groknroll/core/agent.py +339 -0
- groknroll/core/comms_utils.py +264 -0
- groknroll/core/context.py +251 -0
- groknroll/core/exceptions.py +181 -0
- groknroll/core/large_codebase.py +564 -0
- groknroll/core/lm_handler.py +206 -0
- groknroll/core/rlm.py +446 -0
- groknroll/core/rlm_codebase.py +448 -0
- groknroll/core/rlm_integration.py +256 -0
- groknroll/core/types.py +276 -0
- groknroll/environments/__init__.py +34 -0
- groknroll/environments/base_env.py +182 -0
- groknroll/environments/constants.py +32 -0
- groknroll/environments/docker_repl.py +336 -0
- groknroll/environments/local_repl.py +388 -0
- groknroll/environments/modal_repl.py +502 -0
- groknroll/environments/prime_repl.py +588 -0
- groknroll/logger/__init__.py +4 -0
- groknroll/logger/rlm_logger.py +63 -0
- groknroll/logger/verbose.py +393 -0
- groknroll/operations/__init__.py +15 -0
- groknroll/operations/bash_ops.py +447 -0
- groknroll/operations/file_ops.py +473 -0
- groknroll/operations/git_ops.py +620 -0
- groknroll/oracle/__init__.py +11 -0
- groknroll/oracle/codebase_indexer.py +238 -0
- groknroll/oracle/oracle_agent.py +278 -0
- groknroll/setup.py +34 -0
- groknroll/storage/__init__.py +14 -0
- groknroll/storage/database.py +272 -0
- groknroll/storage/models.py +128 -0
- groknroll/utils/__init__.py +0 -0
- groknroll/utils/parsing.py +168 -0
- groknroll/utils/prompts.py +146 -0
- groknroll/utils/rlm_utils.py +19 -0
- groknroll-2.0.0.dist-info/METADATA +246 -0
- groknroll-2.0.0.dist-info/RECORD +62 -0
- groknroll-2.0.0.dist-info/WHEEL +5 -0
- groknroll-2.0.0.dist-info/entry_points.txt +3 -0
- groknroll-2.0.0.dist-info/licenses/LICENSE +21 -0
- 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
|