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,238 @@
1
+ """
2
+ Codebase Indexer - Builds comprehensive index of the entire codebase
3
+ """
4
+
5
+ import ast
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Dict, List, Optional, Set
9
+ from dataclasses import dataclass, field
10
+
11
+
12
+ @dataclass
13
+ class FileInfo:
14
+ """Information about a source file"""
15
+ path: Path
16
+ relative_path: str
17
+ size: int
18
+ lines: int
19
+ language: str
20
+ content: Optional[str] = None
21
+ functions: List[str] = field(default_factory=list)
22
+ classes: List[str] = field(default_factory=list)
23
+ imports: List[str] = field(default_factory=list)
24
+
25
+
26
+ @dataclass
27
+ class CodebaseIndex:
28
+ """Complete index of the codebase"""
29
+ root_path: Path
30
+ files: Dict[str, FileInfo] = field(default_factory=dict)
31
+ functions: Dict[str, List[str]] = field(default_factory=dict) # function_name -> [file_paths]
32
+ classes: Dict[str, List[str]] = field(default_factory=dict) # class_name -> [file_paths]
33
+ imports: Dict[str, Set[str]] = field(default_factory=dict) # module -> {files_that_import_it}
34
+ total_files: int = 0
35
+ total_lines: int = 0
36
+ total_size: int = 0
37
+
38
+
39
+ class CodebaseIndexer:
40
+ """
41
+ Indexes the entire codebase for the Oracle Agent
42
+
43
+ Extracts:
44
+ - File structure and contents
45
+ - Function and class definitions
46
+ - Import relationships
47
+ - Documentation strings
48
+ """
49
+
50
+ SUPPORTED_EXTENSIONS = {
51
+ '.py': 'python',
52
+ '.js': 'javascript',
53
+ '.ts': 'typescript',
54
+ '.jsx': 'javascript',
55
+ '.tsx': 'typescript',
56
+ '.md': 'markdown',
57
+ '.txt': 'text',
58
+ '.yaml': 'yaml',
59
+ '.yml': 'yaml',
60
+ '.json': 'json',
61
+ '.toml': 'toml',
62
+ }
63
+
64
+ IGNORE_DIRS = {
65
+ '__pycache__', '.git', '.venv', 'venv', 'node_modules',
66
+ 'dist', 'build', '.pytest_cache', '.egg-info', 'rlm_old_backup',
67
+ '.vscode', '.idea', 'logs', 'agent_logs'
68
+ }
69
+
70
+ def __init__(self, root_path: Path):
71
+ """Initialize indexer with repository root"""
72
+ self.root_path = Path(root_path).resolve()
73
+ self.index = CodebaseIndex(root_path=self.root_path)
74
+
75
+ def build_index(self, include_content: bool = True) -> CodebaseIndex:
76
+ """
77
+ Build complete codebase index
78
+
79
+ Args:
80
+ include_content: Whether to include full file contents in index
81
+
82
+ Returns:
83
+ CodebaseIndex with all extracted information
84
+ """
85
+ print(f"🔍 Indexing codebase at: {self.root_path}")
86
+
87
+ # Walk directory tree
88
+ for root, dirs, files in os.walk(self.root_path):
89
+ # Filter out ignored directories
90
+ dirs[:] = [d for d in dirs if d not in self.IGNORE_DIRS]
91
+
92
+ root_path = Path(root)
93
+
94
+ for file in files:
95
+ file_path = root_path / file
96
+ ext = file_path.suffix.lower()
97
+
98
+ if ext in self.SUPPORTED_EXTENSIONS:
99
+ self._index_file(file_path, include_content)
100
+
101
+ print(f"✅ Indexed {self.index.total_files} files ({self.index.total_lines:,} lines)")
102
+ return self.index
103
+
104
+ def _index_file(self, file_path: Path, include_content: bool):
105
+ """Index a single file"""
106
+ try:
107
+ relative_path = str(file_path.relative_to(self.root_path))
108
+
109
+ # Read file content
110
+ content = file_path.read_text(encoding='utf-8', errors='ignore')
111
+ lines = content.count('\n') + 1
112
+ size = file_path.stat().st_size
113
+
114
+ ext = file_path.suffix.lower()
115
+ language = self.SUPPORTED_EXTENSIONS[ext]
116
+
117
+ # Create file info
118
+ file_info = FileInfo(
119
+ path=file_path,
120
+ relative_path=relative_path,
121
+ size=size,
122
+ lines=lines,
123
+ language=language,
124
+ content=content if include_content else None
125
+ )
126
+
127
+ # Extract Python-specific metadata
128
+ if language == 'python':
129
+ self._extract_python_info(file_info, content)
130
+
131
+ # Store in index
132
+ self.index.files[relative_path] = file_info
133
+ self.index.total_files += 1
134
+ self.index.total_lines += lines
135
+ self.index.total_size += size
136
+
137
+ except Exception as e:
138
+ print(f"⚠️ Error indexing {file_path}: {e}")
139
+
140
+ def _extract_python_info(self, file_info: FileInfo, content: str):
141
+ """Extract Python-specific information using AST"""
142
+ try:
143
+ tree = ast.parse(content)
144
+
145
+ for node in ast.walk(tree):
146
+ # Extract function definitions
147
+ if isinstance(node, ast.FunctionDef):
148
+ func_name = node.name
149
+ file_info.functions.append(func_name)
150
+
151
+ if func_name not in self.index.functions:
152
+ self.index.functions[func_name] = []
153
+ self.index.functions[func_name].append(file_info.relative_path)
154
+
155
+ # Extract class definitions
156
+ elif isinstance(node, ast.ClassDef):
157
+ class_name = node.name
158
+ file_info.classes.append(class_name)
159
+
160
+ if class_name not in self.index.classes:
161
+ self.index.classes[class_name] = []
162
+ self.index.classes[class_name].append(file_info.relative_path)
163
+
164
+ # Extract imports
165
+ elif isinstance(node, (ast.Import, ast.ImportFrom)):
166
+ if isinstance(node, ast.Import):
167
+ for alias in node.names:
168
+ module = alias.name
169
+ file_info.imports.append(module)
170
+
171
+ if module not in self.index.imports:
172
+ self.index.imports[module] = set()
173
+ self.index.imports[module].add(file_info.relative_path)
174
+
175
+ elif isinstance(node, ast.ImportFrom):
176
+ module = node.module or ''
177
+ if module:
178
+ file_info.imports.append(module)
179
+
180
+ if module not in self.index.imports:
181
+ self.index.imports[module] = set()
182
+ self.index.imports[module].add(file_info.relative_path)
183
+
184
+ except SyntaxError:
185
+ # Skip files with syntax errors
186
+ pass
187
+
188
+ def get_file_info(self, relative_path: str) -> Optional[FileInfo]:
189
+ """Get information about a specific file"""
190
+ return self.index.files.get(relative_path)
191
+
192
+ def find_function(self, function_name: str) -> List[str]:
193
+ """Find all files containing a function"""
194
+ return self.index.functions.get(function_name, [])
195
+
196
+ def find_class(self, class_name: str) -> List[str]:
197
+ """Find all files containing a class"""
198
+ return self.index.classes.get(class_name, [])
199
+
200
+ def find_importers(self, module_name: str) -> Set[str]:
201
+ """Find all files that import a module"""
202
+ return self.index.imports.get(module_name, set())
203
+
204
+ def search_files(self, pattern: str) -> List[FileInfo]:
205
+ """Search for files matching a pattern"""
206
+ results = []
207
+ for relative_path, file_info in self.index.files.items():
208
+ if pattern.lower() in relative_path.lower():
209
+ results.append(file_info)
210
+ return results
211
+
212
+ def get_summary(self) -> str:
213
+ """Get a summary of the indexed codebase"""
214
+ summary = [
215
+ f"Codebase Summary",
216
+ f"=" * 80,
217
+ f"Root: {self.index.root_path}",
218
+ f"Total Files: {self.index.total_files}",
219
+ f"Total Lines: {self.index.total_lines:,}",
220
+ f"Total Size: {self.index.total_size / 1024:.1f} KB",
221
+ f"",
222
+ f"Functions: {len(self.index.functions)}",
223
+ f"Classes: {len(self.index.classes)}",
224
+ f"Modules: {len(self.index.imports)}",
225
+ ]
226
+
227
+ # Language breakdown
228
+ lang_counts = {}
229
+ for file_info in self.index.files.values():
230
+ lang = file_info.language
231
+ lang_counts[lang] = lang_counts.get(lang, 0) + 1
232
+
233
+ summary.append("")
234
+ summary.append("Files by Language:")
235
+ for lang, count in sorted(lang_counts.items(), key=lambda x: -x[1]):
236
+ summary.append(f" {lang}: {count}")
237
+
238
+ return "\n".join(summary)
@@ -0,0 +1,278 @@
1
+ """
2
+ Oracle Agent - RLM-powered codebase knowledge system
3
+
4
+ The Oracle has unlimited context and knows everything about the codebase.
5
+ """
6
+
7
+ from pathlib import Path
8
+ from typing import Dict, Optional, List, Any
9
+ from dataclasses import dataclass
10
+
11
+ from groknroll.core.rlm import RLM
12
+ from groknroll.oracle.codebase_indexer import CodebaseIndexer, CodebaseIndex
13
+
14
+
15
+ @dataclass
16
+ class OracleResponse:
17
+ """Response from the Oracle"""
18
+ question: str
19
+ answer: str
20
+ confidence: str # high, medium, low
21
+ sources: List[str] # Files referenced
22
+ execution_time: float
23
+
24
+ def __str__(self):
25
+ return f"""
26
+ Oracle Response
27
+ {'=' * 80}
28
+ Question: {self.question}
29
+
30
+ Answer:
31
+ {self.answer}
32
+
33
+ Confidence: {self.confidence}
34
+ Sources: {', '.join(self.sources[:5])}{'...' if len(self.sources) > 5 else ''}
35
+ Time: {self.execution_time:.2f}s
36
+ {'=' * 80}
37
+ """
38
+
39
+
40
+ class OracleAgent:
41
+ """
42
+ Oracle Agent - Knows everything about the codebase
43
+
44
+ Uses RLM's unlimited context to answer questions about:
45
+ - Code structure and architecture
46
+ - Function and class locations
47
+ - Import dependencies
48
+ - Implementation details
49
+ - Best practices in the codebase
50
+ - How to use features
51
+ - Where bugs might be
52
+ - Refactoring suggestions
53
+
54
+ Example:
55
+ oracle = OracleAgent("/path/to/project")
56
+ response = oracle.ask("Where is the RLM class defined?")
57
+ print(response.answer)
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ project_path: Path,
63
+ backend: str = "openai",
64
+ model: str = "gpt-4o-mini",
65
+ verbose: bool = False
66
+ ):
67
+ """
68
+ Initialize Oracle Agent
69
+
70
+ Args:
71
+ project_path: Path to the project root
72
+ backend: LLM backend to use
73
+ model: Model name
74
+ verbose: Whether to show detailed output
75
+ """
76
+ self.project_path = Path(project_path).resolve()
77
+ self.verbose = verbose
78
+
79
+ print(f"🔮 Initializing Oracle Agent for: {self.project_path}")
80
+
81
+ # Build codebase index
82
+ self.indexer = CodebaseIndexer(self.project_path)
83
+ self.index = self.indexer.build_index(include_content=True)
84
+
85
+ # Initialize RLM
86
+ self.rlm = RLM(
87
+ backend=backend,
88
+ backend_kwargs={"model_name": model},
89
+ environment="local",
90
+ max_iterations=10,
91
+ verbose=verbose
92
+ )
93
+
94
+ print(f"✅ Oracle ready - {self.index.total_files} files indexed")
95
+
96
+ def ask(self, question: str, context: Optional[Dict[str, Any]] = None) -> OracleResponse:
97
+ """
98
+ Ask the Oracle a question about the codebase
99
+
100
+ Args:
101
+ question: Question about the codebase
102
+ context: Additional context to provide
103
+
104
+ Returns:
105
+ OracleResponse with answer and metadata
106
+ """
107
+ print(f"\n🔮 Oracle processing: {question[:80]}...")
108
+
109
+ # Build comprehensive context for RLM
110
+ rlm_context = self._build_context(question, context)
111
+
112
+ # Ask RLM
113
+ import time
114
+ start = time.time()
115
+
116
+ result = self.rlm.completion(rlm_context)
117
+
118
+ execution_time = time.time() - start
119
+
120
+ # Extract sources from response
121
+ sources = self._extract_sources(result.response)
122
+
123
+ return OracleResponse(
124
+ question=question,
125
+ answer=result.response,
126
+ confidence="high", # Could be inferred from response
127
+ sources=sources,
128
+ execution_time=execution_time
129
+ )
130
+
131
+ def _build_context(self, question: str, extra_context: Optional[Dict] = None) -> str:
132
+ """Build comprehensive context for RLM"""
133
+
134
+ parts = [
135
+ "You are the Oracle - an all-knowing agent with complete knowledge of this codebase.",
136
+ "You have unlimited context and can examine any file in detail.",
137
+ "",
138
+ "# Codebase Overview",
139
+ self.indexer.get_summary(),
140
+ "",
141
+ "# Available Files",
142
+ ]
143
+
144
+ # Add file listing
145
+ for relative_path in sorted(self.index.files.keys())[:100]: # Top 100 files
146
+ file_info = self.index.files[relative_path]
147
+ parts.append(f"- {relative_path} ({file_info.language}, {file_info.lines} lines)")
148
+
149
+ parts.append("")
150
+ parts.append("# Question")
151
+ parts.append(question)
152
+ parts.append("")
153
+
154
+ # Add relevant context based on question
155
+ parts.append("# Relevant Code")
156
+ parts.append("")
157
+
158
+ # Try to find relevant files
159
+ relevant_files = self._find_relevant_files(question)
160
+
161
+ for file_path in relevant_files[:5]: # Top 5 most relevant
162
+ file_info = self.index.files.get(file_path)
163
+ if file_info and file_info.content:
164
+ parts.append(f"## File: {file_path}")
165
+ parts.append(f"```{file_info.language}")
166
+ parts.append(file_info.content)
167
+ parts.append("```")
168
+ parts.append("")
169
+
170
+ if extra_context:
171
+ parts.append("# Additional Context")
172
+ for key, value in extra_context.items():
173
+ parts.append(f"{key}: {value}")
174
+ parts.append("")
175
+
176
+ parts.append("# Instructions")
177
+ parts.append("Answer the question comprehensively using your knowledge of the codebase.")
178
+ parts.append("Reference specific files, functions, and classes when relevant.")
179
+ parts.append("Provide code examples if helpful.")
180
+ parts.append("Use FINAL_VAR(str(your_answer)) to return your answer.")
181
+
182
+ return "\n".join(parts)
183
+
184
+ def _find_relevant_files(self, question: str) -> List[str]:
185
+ """Find files relevant to the question"""
186
+ relevant = []
187
+ question_lower = question.lower()
188
+
189
+ # Check for specific file mentions
190
+ for file_path in self.index.files.keys():
191
+ file_name = Path(file_path).name.lower()
192
+ if file_name in question_lower or file_path in question_lower:
193
+ relevant.append(file_path)
194
+
195
+ # Check for class/function mentions
196
+ for class_name, file_paths in self.index.classes.items():
197
+ if class_name.lower() in question_lower:
198
+ relevant.extend(file_paths)
199
+
200
+ for func_name, file_paths in self.index.functions.items():
201
+ if func_name.lower() in question_lower:
202
+ relevant.extend(file_paths)
203
+
204
+ # Keywords that suggest core files
205
+ if any(kw in question_lower for kw in ['main', 'core', 'base', 'init']):
206
+ for file_path in self.index.files.keys():
207
+ if any(name in file_path for name in ['__init__.py', 'main.py', 'core/', 'rlm.py']):
208
+ relevant.append(file_path)
209
+
210
+ # Remove duplicates while preserving order
211
+ seen = set()
212
+ unique_relevant = []
213
+ for item in relevant:
214
+ if item not in seen:
215
+ seen.add(item)
216
+ unique_relevant.append(item)
217
+
218
+ return unique_relevant if unique_relevant else list(self.index.files.keys())[:10]
219
+
220
+ def _extract_sources(self, response: str) -> List[str]:
221
+ """Extract file references from response"""
222
+ sources = set()
223
+
224
+ for file_path in self.index.files.keys():
225
+ if file_path in response or Path(file_path).name in response:
226
+ sources.add(file_path)
227
+
228
+ return list(sources)
229
+
230
+ def find_function(self, function_name: str) -> OracleResponse:
231
+ """Ask Oracle where a function is defined"""
232
+ return self.ask(f"Where is the function '{function_name}' defined? Show me the code.")
233
+
234
+ def find_class(self, class_name: str) -> OracleResponse:
235
+ """Ask Oracle where a class is defined"""
236
+ return self.ask(f"Where is the class '{class_name}' defined? Show me the code and explain what it does.")
237
+
238
+ def explain_file(self, file_path: str) -> OracleResponse:
239
+ """Ask Oracle to explain a file"""
240
+ return self.ask(f"Explain what {file_path} does and how it fits into the overall architecture.")
241
+
242
+ def find_usage(self, item_name: str) -> OracleResponse:
243
+ """Ask Oracle how something is used"""
244
+ return self.ask(f"Show me examples of how '{item_name}' is used in the codebase.")
245
+
246
+ def suggest_refactoring(self, description: str) -> OracleResponse:
247
+ """Ask Oracle for refactoring suggestions"""
248
+ return self.ask(f"Suggest how to refactor: {description}")
249
+
250
+ def find_bugs(self, area: Optional[str] = None) -> OracleResponse:
251
+ """Ask Oracle to identify potential bugs"""
252
+ question = f"Identify potential bugs in {area}" if area else "Identify potential bugs in the codebase"
253
+ return self.ask(question)
254
+
255
+ def get_architecture_overview(self) -> OracleResponse:
256
+ """Ask Oracle for architecture overview"""
257
+ return self.ask(
258
+ "Provide a comprehensive overview of the codebase architecture. "
259
+ "Explain the main components, how they interact, and the overall design pattern."
260
+ )
261
+
262
+ def how_to_add_feature(self, feature: str) -> OracleResponse:
263
+ """Ask Oracle how to add a feature"""
264
+ return self.ask(
265
+ f"How would I add this feature: {feature}? "
266
+ f"Which files would I need to modify? Show me the steps."
267
+ )
268
+
269
+ def trace_execution(self, entry_point: str) -> OracleResponse:
270
+ """Ask Oracle to trace execution flow"""
271
+ return self.ask(
272
+ f"Trace the execution flow starting from {entry_point}. "
273
+ f"Show me the call chain and what happens step by step."
274
+ )
275
+
276
+ def explain_imports(self, module: str) -> OracleResponse:
277
+ """Ask Oracle about import relationships"""
278
+ return self.ask(f"Explain the import relationships for {module}. Which files import it and why?")
groknroll/setup.py ADDED
@@ -0,0 +1,34 @@
1
+ """
2
+ groknroll setup
3
+ """
4
+
5
+ from setuptools import setup, find_packages
6
+
7
+ setup(
8
+ name="groknroll",
9
+ version="2.0.0",
10
+ packages=find_packages(),
11
+ python_requires=">=3.9",
12
+ install_requires=[
13
+ "click>=8.1.0",
14
+ "rich>=13.0.0",
15
+ "blessed>=1.20.0",
16
+ "inquirer>=3.1.0",
17
+ "radon>=6.0.0",
18
+ "bandit>=1.7.0",
19
+ "gitpython>=3.1.0",
20
+ "diff-match-patch>=20230430",
21
+ "sqlalchemy>=2.0.0",
22
+ "pyyaml>=6.0.0",
23
+ "python-dotenv>=1.0.0",
24
+ "jinja2>=3.1.0",
25
+ "pytest>=7.4.0",
26
+ "coverage>=7.0.0",
27
+ ],
28
+ entry_points={
29
+ "console_scripts": [
30
+ "groknroll=groknroll.cli.main:main",
31
+ "gknr=groknroll.cli.main:main",
32
+ ],
33
+ },
34
+ )
@@ -0,0 +1,14 @@
1
+ """
2
+ groknroll Storage - SQLite database for project context and history
3
+ """
4
+
5
+ from groknroll.storage.database import Database
6
+ from groknroll.storage.models import (
7
+ Project,
8
+ FileIndex,
9
+ Execution,
10
+ Session,
11
+ Analysis
12
+ )
13
+
14
+ __all__ = ["Database", "Project", "FileIndex", "Execution", "Session", "Analysis"]