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,620 @@
1
+ """
2
+ Git Operations Module
3
+
4
+ Provides git operations with smart commit messages and PR creation.
5
+ """
6
+
7
+ import subprocess
8
+ from pathlib import Path
9
+ from typing import Optional, List, Dict, Any
10
+ from dataclasses import dataclass
11
+ import re
12
+
13
+
14
+ @dataclass
15
+ class GitStatus:
16
+ """Git repository status"""
17
+ branch: str
18
+ staged: List[str]
19
+ unstaged: List[str]
20
+ untracked: List[str]
21
+ is_clean: bool
22
+ ahead: int
23
+ behind: int
24
+
25
+
26
+ @dataclass
27
+ class GitResult:
28
+ """Result of a git operation"""
29
+ success: bool
30
+ message: str
31
+ output: Optional[str] = None
32
+
33
+
34
+ class GitOperations:
35
+ """
36
+ Git operations for groknroll agent
37
+
38
+ Features:
39
+ - Git status and info
40
+ - Stage, commit, push
41
+ - Smart commit message generation
42
+ - PR creation (via gh CLI)
43
+ - Branch management
44
+ - Git history
45
+ """
46
+
47
+ def __init__(self, project_path: Path):
48
+ """
49
+ Initialize git operations
50
+
51
+ Args:
52
+ project_path: Git repository root
53
+ """
54
+ self.project_path = project_path.resolve()
55
+
56
+ # Verify git is available
57
+ if not self._is_git_available():
58
+ raise RuntimeError("Git is not installed or not in PATH")
59
+
60
+ # Verify this is a git repo
61
+ if not self._is_git_repo():
62
+ raise RuntimeError(f"Not a git repository: {project_path}")
63
+
64
+ def status(self) -> GitStatus:
65
+ """
66
+ Get git status
67
+
68
+ Returns:
69
+ GitStatus with current repository state
70
+ """
71
+ # Get current branch
72
+ branch_result = self._run_git(["branch", "--show-current"])
73
+ branch = branch_result.strip() if branch_result else "unknown"
74
+
75
+ # Get status
76
+ status_result = self._run_git(["status", "--porcelain"])
77
+ staged = []
78
+ unstaged = []
79
+ untracked = []
80
+
81
+ for line in status_result.splitlines():
82
+ if not line:
83
+ continue
84
+
85
+ status_code = line[:2]
86
+ filepath = line[3:]
87
+
88
+ if status_code[0] in ("M", "A", "D", "R", "C"):
89
+ staged.append(filepath)
90
+ if status_code[1] in ("M", "D"):
91
+ unstaged.append(filepath)
92
+ if status_code == "??":
93
+ untracked.append(filepath)
94
+
95
+ # Get ahead/behind status
96
+ ahead, behind = self._get_ahead_behind()
97
+
98
+ return GitStatus(
99
+ branch=branch,
100
+ staged=staged,
101
+ unstaged=unstaged,
102
+ untracked=untracked,
103
+ is_clean=not (staged or unstaged or untracked),
104
+ ahead=ahead,
105
+ behind=behind
106
+ )
107
+
108
+ def add(self, files: Optional[List[Path]] = None, all_files: bool = False) -> GitResult:
109
+ """
110
+ Stage files
111
+
112
+ Args:
113
+ files: List of files to stage (or None with all_files=True)
114
+ all_files: Stage all changes
115
+
116
+ Returns:
117
+ GitResult
118
+ """
119
+ try:
120
+ if all_files:
121
+ self._run_git(["add", "-A"])
122
+ return GitResult(
123
+ success=True,
124
+ message="All changes staged"
125
+ )
126
+
127
+ if not files:
128
+ return GitResult(
129
+ success=False,
130
+ message="No files specified and all_files=False"
131
+ )
132
+
133
+ # Stage specific files
134
+ file_paths = [str(f) for f in files]
135
+ self._run_git(["add"] + file_paths)
136
+
137
+ return GitResult(
138
+ success=True,
139
+ message=f"Staged {len(files)} file(s)"
140
+ )
141
+
142
+ except Exception as e:
143
+ return GitResult(
144
+ success=False,
145
+ message=f"Error staging files: {e}"
146
+ )
147
+
148
+ def commit(
149
+ self,
150
+ message: str,
151
+ files: Optional[List[Path]] = None,
152
+ amend: bool = False
153
+ ) -> GitResult:
154
+ """
155
+ Create commit
156
+
157
+ Args:
158
+ message: Commit message
159
+ files: Files to commit (or all staged if None)
160
+ amend: Amend previous commit
161
+
162
+ Returns:
163
+ GitResult
164
+ """
165
+ try:
166
+ # Stage files if provided
167
+ if files:
168
+ self.add(files)
169
+
170
+ # Build commit command
171
+ cmd = ["commit", "-m", message]
172
+ if amend:
173
+ cmd.append("--amend")
174
+
175
+ output = self._run_git(cmd)
176
+
177
+ return GitResult(
178
+ success=True,
179
+ message=f"Commit created: {message[:50]}...",
180
+ output=output
181
+ )
182
+
183
+ except Exception as e:
184
+ return GitResult(
185
+ success=False,
186
+ message=f"Error creating commit: {e}"
187
+ )
188
+
189
+ def push(
190
+ self,
191
+ remote: str = "origin",
192
+ branch: Optional[str] = None,
193
+ set_upstream: bool = False,
194
+ force: bool = False
195
+ ) -> GitResult:
196
+ """
197
+ Push to remote
198
+
199
+ Args:
200
+ remote: Remote name
201
+ branch: Branch name (current branch if None)
202
+ set_upstream: Set upstream tracking
203
+ force: Force push (use with caution!)
204
+
205
+ Returns:
206
+ GitResult
207
+ """
208
+ try:
209
+ # Get current branch if not specified
210
+ if branch is None:
211
+ status = self.status()
212
+ branch = status.branch
213
+
214
+ # Build push command
215
+ cmd = ["push"]
216
+ if set_upstream:
217
+ cmd.extend(["-u", remote, branch])
218
+ else:
219
+ cmd.extend([remote, branch])
220
+
221
+ if force:
222
+ cmd.append("--force")
223
+
224
+ output = self._run_git(cmd)
225
+
226
+ return GitResult(
227
+ success=True,
228
+ message=f"Pushed to {remote}/{branch}",
229
+ output=output
230
+ )
231
+
232
+ except Exception as e:
233
+ return GitResult(
234
+ success=False,
235
+ message=f"Error pushing: {e}"
236
+ )
237
+
238
+ def pull(
239
+ self,
240
+ remote: str = "origin",
241
+ branch: Optional[str] = None,
242
+ rebase: bool = False
243
+ ) -> GitResult:
244
+ """
245
+ Pull from remote
246
+
247
+ Args:
248
+ remote: Remote name
249
+ branch: Branch name
250
+ rebase: Use rebase instead of merge
251
+
252
+ Returns:
253
+ GitResult
254
+ """
255
+ try:
256
+ cmd = ["pull"]
257
+ if rebase:
258
+ cmd.append("--rebase")
259
+ cmd.append(remote)
260
+ if branch:
261
+ cmd.append(branch)
262
+
263
+ output = self._run_git(cmd)
264
+
265
+ return GitResult(
266
+ success=True,
267
+ message=f"Pulled from {remote}",
268
+ output=output
269
+ )
270
+
271
+ except Exception as e:
272
+ return GitResult(
273
+ success=False,
274
+ message=f"Error pulling: {e}"
275
+ )
276
+
277
+ def create_branch(self, branch_name: str, checkout: bool = True) -> GitResult:
278
+ """
279
+ Create new branch
280
+
281
+ Args:
282
+ branch_name: Branch name
283
+ checkout: Checkout after creating
284
+
285
+ Returns:
286
+ GitResult
287
+ """
288
+ try:
289
+ if checkout:
290
+ self._run_git(["checkout", "-b", branch_name])
291
+ message = f"Created and checked out branch: {branch_name}"
292
+ else:
293
+ self._run_git(["branch", branch_name])
294
+ message = f"Created branch: {branch_name}"
295
+
296
+ return GitResult(
297
+ success=True,
298
+ message=message
299
+ )
300
+
301
+ except Exception as e:
302
+ return GitResult(
303
+ success=False,
304
+ message=f"Error creating branch: {e}"
305
+ )
306
+
307
+ def checkout(self, branch_name: str, create: bool = False) -> GitResult:
308
+ """
309
+ Checkout branch
310
+
311
+ Args:
312
+ branch_name: Branch name
313
+ create: Create if doesn't exist
314
+
315
+ Returns:
316
+ GitResult
317
+ """
318
+ try:
319
+ if create:
320
+ self._run_git(["checkout", "-b", branch_name])
321
+ else:
322
+ self._run_git(["checkout", branch_name])
323
+
324
+ return GitResult(
325
+ success=True,
326
+ message=f"Checked out: {branch_name}"
327
+ )
328
+
329
+ except Exception as e:
330
+ return GitResult(
331
+ success=False,
332
+ message=f"Error checking out branch: {e}"
333
+ )
334
+
335
+ def generate_commit_message(self, context: Optional[str] = None) -> str:
336
+ """
337
+ Generate smart commit message based on diff
338
+
339
+ Args:
340
+ context: Additional context for commit message
341
+
342
+ Returns:
343
+ Generated commit message
344
+ """
345
+ try:
346
+ # Get diff
347
+ diff = self._run_git(["diff", "--cached"])
348
+
349
+ if not diff:
350
+ return "chore: update files"
351
+
352
+ # Analyze diff to generate message
353
+ message = self._analyze_diff_for_message(diff, context)
354
+
355
+ return message
356
+
357
+ except Exception as e:
358
+ return f"chore: update files ({e})"
359
+
360
+ def create_pr(
361
+ self,
362
+ title: str,
363
+ body: Optional[str] = None,
364
+ base: str = "main",
365
+ draft: bool = False
366
+ ) -> GitResult:
367
+ """
368
+ Create pull request using GitHub CLI
369
+
370
+ Args:
371
+ title: PR title
372
+ body: PR description
373
+ base: Base branch
374
+ draft: Create as draft
375
+
376
+ Returns:
377
+ GitResult with PR URL
378
+ """
379
+ try:
380
+ # Check if gh CLI is available
381
+ if not self._is_gh_available():
382
+ return GitResult(
383
+ success=False,
384
+ message="GitHub CLI (gh) not installed. Install with: brew install gh"
385
+ )
386
+
387
+ # Build gh command
388
+ cmd = ["gh", "pr", "create", "--title", title, "--base", base]
389
+
390
+ if body:
391
+ cmd.extend(["--body", body])
392
+
393
+ if draft:
394
+ cmd.append("--draft")
395
+
396
+ output = subprocess.run(
397
+ cmd,
398
+ cwd=str(self.project_path),
399
+ capture_output=True,
400
+ text=True,
401
+ check=True
402
+ )
403
+
404
+ # Extract PR URL from output
405
+ pr_url = output.stdout.strip()
406
+
407
+ return GitResult(
408
+ success=True,
409
+ message=f"PR created: {pr_url}",
410
+ output=pr_url
411
+ )
412
+
413
+ except subprocess.CalledProcessError as e:
414
+ return GitResult(
415
+ success=False,
416
+ message=f"Error creating PR: {e.stderr}"
417
+ )
418
+ except Exception as e:
419
+ return GitResult(
420
+ success=False,
421
+ message=f"Error creating PR: {e}"
422
+ )
423
+
424
+ def log(self, limit: int = 10, oneline: bool = False) -> GitResult:
425
+ """
426
+ Get git log
427
+
428
+ Args:
429
+ limit: Number of commits
430
+ oneline: One line per commit
431
+
432
+ Returns:
433
+ GitResult with log output
434
+ """
435
+ try:
436
+ cmd = ["log", f"-{limit}"]
437
+ if oneline:
438
+ cmd.append("--oneline")
439
+
440
+ output = self._run_git(cmd)
441
+
442
+ return GitResult(
443
+ success=True,
444
+ message="Git log retrieved",
445
+ output=output
446
+ )
447
+
448
+ except Exception as e:
449
+ return GitResult(
450
+ success=False,
451
+ message=f"Error getting log: {e}"
452
+ )
453
+
454
+ def diff(self, cached: bool = False, files: Optional[List[Path]] = None) -> GitResult:
455
+ """
456
+ Get git diff
457
+
458
+ Args:
459
+ cached: Show staged changes
460
+ files: Specific files to diff
461
+
462
+ Returns:
463
+ GitResult with diff output
464
+ """
465
+ try:
466
+ cmd = ["diff"]
467
+ if cached:
468
+ cmd.append("--cached")
469
+
470
+ if files:
471
+ cmd.extend([str(f) for f in files])
472
+
473
+ output = self._run_git(cmd)
474
+
475
+ return GitResult(
476
+ success=True,
477
+ message="Diff retrieved",
478
+ output=output
479
+ )
480
+
481
+ except Exception as e:
482
+ return GitResult(
483
+ success=False,
484
+ message=f"Error getting diff: {e}"
485
+ )
486
+
487
+ # =========================================================================
488
+ # Helper Methods
489
+ # =========================================================================
490
+
491
+ def _run_git(self, args: List[str]) -> str:
492
+ """Run git command and return output"""
493
+ cmd = ["git"] + args
494
+ result = subprocess.run(
495
+ cmd,
496
+ cwd=str(self.project_path),
497
+ capture_output=True,
498
+ text=True,
499
+ check=True
500
+ )
501
+ return result.stdout
502
+
503
+ def _is_git_available(self) -> bool:
504
+ """Check if git is installed"""
505
+ try:
506
+ subprocess.run(
507
+ ["git", "--version"],
508
+ capture_output=True,
509
+ check=True
510
+ )
511
+ return True
512
+ except (subprocess.CalledProcessError, FileNotFoundError):
513
+ return False
514
+
515
+ def _is_gh_available(self) -> bool:
516
+ """Check if GitHub CLI is installed"""
517
+ try:
518
+ subprocess.run(
519
+ ["gh", "--version"],
520
+ capture_output=True,
521
+ check=True
522
+ )
523
+ return True
524
+ except (subprocess.CalledProcessError, FileNotFoundError):
525
+ return False
526
+
527
+ def _is_git_repo(self) -> bool:
528
+ """Check if directory is a git repository"""
529
+ try:
530
+ self._run_git(["rev-parse", "--git-dir"])
531
+ return True
532
+ except subprocess.CalledProcessError:
533
+ return False
534
+
535
+ def _get_ahead_behind(self) -> tuple[int, int]:
536
+ """Get ahead/behind commit count"""
537
+ try:
538
+ output = self._run_git(["rev-list", "--left-right", "--count", "@{upstream}...HEAD"])
539
+ behind, ahead = output.strip().split()
540
+ return int(ahead), int(behind)
541
+ except (subprocess.CalledProcessError, ValueError):
542
+ return 0, 0
543
+
544
+ def _analyze_diff_for_message(self, diff: str, context: Optional[str] = None) -> str:
545
+ """
546
+ Analyze diff to generate commit message
547
+
548
+ Args:
549
+ diff: Git diff output
550
+ context: Additional context
551
+
552
+ Returns:
553
+ Generated commit message
554
+ """
555
+ # Count changes
556
+ additions = diff.count("\n+") - diff.count("\n+++")
557
+ deletions = diff.count("\n-") - diff.count("\n---")
558
+
559
+ # Detect change type
560
+ if "def " in diff or "class " in diff:
561
+ change_type = "feat"
562
+ elif "import " in diff:
563
+ change_type = "refactor"
564
+ elif "test_" in diff or "Test" in diff:
565
+ change_type = "test"
566
+ elif "README" in diff or "# " in diff:
567
+ change_type = "docs"
568
+ elif "fix" in diff.lower() or "bug" in diff.lower():
569
+ change_type = "fix"
570
+ else:
571
+ change_type = "chore"
572
+
573
+ # Generate summary
574
+ if additions > deletions * 2:
575
+ summary = "add new functionality"
576
+ elif deletions > additions * 2:
577
+ summary = "remove deprecated code"
578
+ else:
579
+ summary = "update implementation"
580
+
581
+ # Add context if provided
582
+ if context:
583
+ message = f"{change_type}: {context}\n\n+{additions} -{deletions}"
584
+ else:
585
+ message = f"{change_type}: {summary}\n\n+{additions} -{deletions}"
586
+
587
+ return message
588
+
589
+ def get_remote_url(self, remote: str = "origin") -> Optional[str]:
590
+ """Get remote URL"""
591
+ try:
592
+ output = self._run_git(["remote", "get-url", remote])
593
+ return output.strip()
594
+ except subprocess.CalledProcessError:
595
+ return None
596
+
597
+ def get_current_branch(self) -> Optional[str]:
598
+ """Get current branch name"""
599
+ try:
600
+ output = self._run_git(["branch", "--show-current"])
601
+ return output.strip()
602
+ except subprocess.CalledProcessError:
603
+ return None
604
+
605
+ def list_branches(self, all_branches: bool = False) -> List[str]:
606
+ """List branches"""
607
+ try:
608
+ cmd = ["branch"]
609
+ if all_branches:
610
+ cmd.append("-a")
611
+
612
+ output = self._run_git(cmd)
613
+ branches = [
614
+ line.strip().lstrip("* ").strip()
615
+ for line in output.splitlines()
616
+ if line.strip()
617
+ ]
618
+ return branches
619
+ except subprocess.CalledProcessError:
620
+ return []
@@ -0,0 +1,11 @@
1
+ """
2
+ Oracle Agent - RLM-based codebase knowledge system
3
+
4
+ The Oracle knows everything about the codebase and can answer questions
5
+ using RLM's unlimited context capabilities.
6
+ """
7
+
8
+ from groknroll.oracle.oracle_agent import OracleAgent
9
+ from groknroll.oracle.codebase_indexer import CodebaseIndexer
10
+
11
+ __all__ = ["OracleAgent", "CodebaseIndexer"]