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,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"]
|