@voria/cli 0.0.2

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 (67) hide show
  1. package/README.md +439 -0
  2. package/bin/voria +730 -0
  3. package/docs/ARCHITECTURE.md +419 -0
  4. package/docs/CHANGELOG.md +189 -0
  5. package/docs/CONTRIBUTING.md +447 -0
  6. package/docs/DESIGN_DECISIONS.md +380 -0
  7. package/docs/DEVELOPMENT.md +535 -0
  8. package/docs/EXAMPLES.md +434 -0
  9. package/docs/INSTALL.md +335 -0
  10. package/docs/IPC_PROTOCOL.md +310 -0
  11. package/docs/LLM_INTEGRATION.md +416 -0
  12. package/docs/MODULES.md +470 -0
  13. package/docs/PERFORMANCE.md +346 -0
  14. package/docs/PLUGINS.md +432 -0
  15. package/docs/QUICKSTART.md +184 -0
  16. package/docs/README.md +133 -0
  17. package/docs/ROADMAP.md +346 -0
  18. package/docs/SECURITY.md +334 -0
  19. package/docs/TROUBLESHOOTING.md +565 -0
  20. package/docs/USER_GUIDE.md +700 -0
  21. package/package.json +63 -0
  22. package/python/voria/__init__.py +8 -0
  23. package/python/voria/__pycache__/__init__.cpython-312.pyc +0 -0
  24. package/python/voria/__pycache__/engine.cpython-312.pyc +0 -0
  25. package/python/voria/core/__init__.py +1 -0
  26. package/python/voria/core/__pycache__/__init__.cpython-312.pyc +0 -0
  27. package/python/voria/core/__pycache__/setup.cpython-312.pyc +0 -0
  28. package/python/voria/core/agent/__init__.py +9 -0
  29. package/python/voria/core/agent/__pycache__/__init__.cpython-312.pyc +0 -0
  30. package/python/voria/core/agent/__pycache__/loop.cpython-312.pyc +0 -0
  31. package/python/voria/core/agent/loop.py +343 -0
  32. package/python/voria/core/executor/__init__.py +19 -0
  33. package/python/voria/core/executor/__pycache__/__init__.cpython-312.pyc +0 -0
  34. package/python/voria/core/executor/__pycache__/executor.cpython-312.pyc +0 -0
  35. package/python/voria/core/executor/executor.py +431 -0
  36. package/python/voria/core/github/__init__.py +33 -0
  37. package/python/voria/core/github/__pycache__/__init__.cpython-312.pyc +0 -0
  38. package/python/voria/core/github/__pycache__/client.cpython-312.pyc +0 -0
  39. package/python/voria/core/github/client.py +438 -0
  40. package/python/voria/core/llm/__init__.py +55 -0
  41. package/python/voria/core/llm/__pycache__/__init__.cpython-312.pyc +0 -0
  42. package/python/voria/core/llm/__pycache__/base.cpython-312.pyc +0 -0
  43. package/python/voria/core/llm/__pycache__/claude_provider.cpython-312.pyc +0 -0
  44. package/python/voria/core/llm/__pycache__/gemini_provider.cpython-312.pyc +0 -0
  45. package/python/voria/core/llm/__pycache__/modal_provider.cpython-312.pyc +0 -0
  46. package/python/voria/core/llm/__pycache__/model_discovery.cpython-312.pyc +0 -0
  47. package/python/voria/core/llm/__pycache__/openai_provider.cpython-312.pyc +0 -0
  48. package/python/voria/core/llm/base.py +152 -0
  49. package/python/voria/core/llm/claude_provider.py +188 -0
  50. package/python/voria/core/llm/gemini_provider.py +148 -0
  51. package/python/voria/core/llm/modal_provider.py +228 -0
  52. package/python/voria/core/llm/model_discovery.py +289 -0
  53. package/python/voria/core/llm/openai_provider.py +146 -0
  54. package/python/voria/core/patcher/__init__.py +9 -0
  55. package/python/voria/core/patcher/__pycache__/__init__.cpython-312.pyc +0 -0
  56. package/python/voria/core/patcher/__pycache__/patcher.cpython-312.pyc +0 -0
  57. package/python/voria/core/patcher/patcher.py +375 -0
  58. package/python/voria/core/planner/__init__.py +1 -0
  59. package/python/voria/core/setup.py +201 -0
  60. package/python/voria/core/token_manager/__init__.py +29 -0
  61. package/python/voria/core/token_manager/__pycache__/__init__.cpython-312.pyc +0 -0
  62. package/python/voria/core/token_manager/__pycache__/manager.cpython-312.pyc +0 -0
  63. package/python/voria/core/token_manager/manager.py +241 -0
  64. package/python/voria/engine.py +1185 -0
  65. package/python/voria/plugins/__init__.py +1 -0
  66. package/python/voria/plugins/python/__init__.py +1 -0
  67. package/python/voria/plugins/typescript/__init__.py +1 -0
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@voria/cli",
3
+ "version": "0.0.2",
4
+ "description": "AI-powered CLI tool for automated bug fixing - initialize with voria --init",
5
+ "main": "bin/voria",
6
+ "type": "module",
7
+ "preferGlobal": true,
8
+ "homepage": "https://github.com/Srizdebnath/voria",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/Srizdebnath/voria.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/Srizdebnath/voria/issues"
15
+ },
16
+ "authors": [
17
+ {
18
+ "name": "Srizdebnath",
19
+ "url": "https://github.com/Srizdebnath"
20
+ }
21
+ ],
22
+ "license": "MIT",
23
+ "keywords": [
24
+ "ai",
25
+ "automation",
26
+ "bug-fixing",
27
+ "code-generation",
28
+ "llm",
29
+ "github",
30
+ "pull-request",
31
+ "testing",
32
+ "development",
33
+ "cli",
34
+ "tool"
35
+ ],
36
+ "bin": {
37
+ "voria": "./bin/voria"
38
+ },
39
+ "scripts": {
40
+ "build": "echo 'Build complete - Node.js CLI ready'",
41
+ "test": "node bin/voria --help && node bin/voria --version",
42
+ "test:local": "npm run test",
43
+ "start": "node bin/voria"
44
+ },
45
+ "engines": {
46
+ "node": ">=14.0.0"
47
+ },
48
+ "files": [
49
+ "bin/",
50
+ "python/voria",
51
+ "docs/",
52
+ "README.md",
53
+ "LICENSE",
54
+ "package.json"
55
+ ],
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "os": [
60
+ "darwin",
61
+ "linux"
62
+ ]
63
+ }
@@ -0,0 +1,8 @@
1
+ """voria: AI-powered CLI tool for open source contributors."""
2
+
3
+ __version__ = "0.1.0"
4
+ __author__ = "voria Contributors"
5
+
6
+ from . import engine
7
+
8
+ __all__ = ["engine"]
@@ -0,0 +1 @@
1
+ """voria core modules."""
@@ -0,0 +1,9 @@
1
+ """Agent Loop Module - Orchestrates full voria workflow"""
2
+
3
+ from .loop import AgentLoop, LoopState, LoopAction
4
+
5
+ __all__ = [
6
+ "AgentLoop",
7
+ "LoopState",
8
+ "LoopAction",
9
+ ]
@@ -0,0 +1,343 @@
1
+ """
2
+ Agent Loop - Orchestrates the full voria workflow
3
+
4
+ Workflow:
5
+ 1. Plan: Get fix strategy from LLM
6
+ 2. Patch: Generate code patch from plan
7
+ 3. Apply: Send patch to Rust for file operations
8
+ 4. Test: Run test suite
9
+ 5. Analyze: Check results, decide next action
10
+ 6. Iterate: Repeat up to max iterations
11
+ """
12
+
13
+ import asyncio
14
+ import logging
15
+ from typing import Dict, List, Optional, Any
16
+ from dataclasses import dataclass, field
17
+ from enum import Enum
18
+
19
+ from voria.core.llm import LLMProviderFactory, Message
20
+ from voria.core.executor import TestExecutor, TestSuiteResult
21
+ from voria.core.patcher import CodePatcher
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class LoopAction(Enum):
27
+ """Actions the agent loop can take"""
28
+
29
+ PLAN = "plan"
30
+ PATCH = "patch"
31
+ APPLY = "apply"
32
+ TEST = "test"
33
+ ANALYZE = "analyze"
34
+ SUCCESS = "success"
35
+ FAILURE = "failure"
36
+ ITERATE = "iterate"
37
+
38
+
39
+ @dataclass
40
+ class LoopState:
41
+ """Current state of agent loop"""
42
+
43
+ iteration: int = 0
44
+ issue_id: int = 0
45
+ issue_description: str = ""
46
+ repo_path: str = "."
47
+
48
+ plan: str = ""
49
+ patch: str = ""
50
+ test_results: Optional[TestSuiteResult] = None
51
+ errors: List[str] = field(default_factory=list)
52
+
53
+ def to_dict(self) -> Dict[str, Any]:
54
+ """Convert to dictionary"""
55
+ return {
56
+ "iteration": self.iteration,
57
+ "issue_id": self.issue_id,
58
+ "plan": self.plan[:500] if self.plan else "", # Truncate for logging
59
+ "has_patch": bool(self.patch),
60
+ "test_passed": self.test_results.failed == 0 if self.test_results else None,
61
+ "errors": self.errors[-3:], # Last 3 errors
62
+ }
63
+
64
+
65
+ class AgentLoop:
66
+ """Main agent loop orchestrator"""
67
+
68
+ MAX_ITERATIONS = 5
69
+
70
+ def __init__(self, provider_name: str, api_key: str, repo_path: str = "."):
71
+ """
72
+ Initialize agent loop
73
+
74
+ Args:
75
+ provider_name: LLM provider (modal, openai, gemini, claude)
76
+ api_key: Provider API key
77
+ repo_path: Path to git repository
78
+ """
79
+ self.provider_name = provider_name
80
+ self.api_key = api_key
81
+ self.repo_path = repo_path
82
+
83
+ # Initialize components
84
+ self.provider = None
85
+ self.patcher = CodePatcher(repo_path)
86
+ self.executor = TestExecutor(repo_path)
87
+
88
+ async def initialize(self, model: str):
89
+ """Initialize LLM provider with specific model"""
90
+ self.provider = LLMProviderFactory.create(
91
+ provider_name=self.provider_name, api_key=self.api_key, model=model
92
+ )
93
+ logger.info(f"Initialized {self.provider_name} with model {model}")
94
+
95
+ async def run(self, issue_id: int, issue_description: str) -> Dict[str, Any]:
96
+ """
97
+ Run full agent loop on issue
98
+
99
+ Args:
100
+ issue_id: GitHub issue number
101
+ issue_description: Full issue description/context
102
+
103
+ Returns:
104
+ Dict with final status and results
105
+ """
106
+
107
+ state = LoopState(
108
+ issue_id=issue_id,
109
+ issue_description=issue_description,
110
+ repo_path=self.repo_path,
111
+ )
112
+
113
+ logger.info(f"Starting agent loop for issue #{issue_id}")
114
+
115
+ for iteration in range(1, self.MAX_ITERATIONS + 1):
116
+ state.iteration = iteration
117
+
118
+ logger.info(f"Iteration {iteration}/{self.MAX_ITERATIONS}")
119
+
120
+ try:
121
+ # Step 1: Plan
122
+ action = await self._step_plan(state)
123
+ if action == LoopAction.FAILURE:
124
+ return self._format_result(state, "failed_plan")
125
+
126
+ # Step 2: Generate Patch
127
+ action = await self._step_patch(state)
128
+ if action == LoopAction.FAILURE:
129
+ return self._format_result(state, "failed_patch")
130
+
131
+ # Step 3: Apply Patch (send via NDJSON to Rust)
132
+ action = await self._step_apply(state)
133
+ if action == LoopAction.FAILURE:
134
+ logger.error("Patch application failed, rolling back...")
135
+ await self._rollback()
136
+ # Continue to next iteration
137
+ continue
138
+
139
+ # Step 4: Run Tests
140
+ action = await self._step_test(state)
141
+
142
+ # Step 5: Analyze Results
143
+ if action == LoopAction.SUCCESS:
144
+ logger.info("✅ Issue resolved! Tests passing.")
145
+ return self._format_result(state, "success")
146
+
147
+ elif action == LoopAction.FAILURE:
148
+ # Analysis step will generate next plan
149
+ logger.info(
150
+ f"Tests failed, analyzing for iteration {iteration + 1}..."
151
+ )
152
+ await self._analyze_failure(state)
153
+
154
+ if iteration < self.MAX_ITERATIONS:
155
+ logger.info("Continuing to next iteration...")
156
+ continue
157
+ else:
158
+ logger.warning("Max iterations reached")
159
+ return self._format_result(state, "max_iterations")
160
+
161
+ except Exception as e:
162
+ logger.error(f"Iteration {iteration} failed: {e}")
163
+ state.errors.append(str(e))
164
+
165
+ return self._format_result(state, "timeout")
166
+
167
+ async def _step_plan(self, state: LoopState) -> LoopAction:
168
+ """Step 1: Generate fix plan"""
169
+
170
+ logger.info("Step 1: Planning fix strategy...")
171
+
172
+ try:
173
+ if state.iteration == 1:
174
+ # Initial plan from issue
175
+ prompt = state.issue_description
176
+ else:
177
+ # Refined plan based on previous failures
178
+ prompt = f"""{state.issue_description}
179
+
180
+ Previous attempt failed with:
181
+ {state.test_results.stdout if state.test_results else "Unknown"}
182
+
183
+ Please refine your approach and generate an improved fix."""
184
+
185
+ state.plan = await self.provider.plan(prompt)
186
+
187
+ logger.info(f"Plan generated ({len(state.plan)} chars)")
188
+ return LoopAction.PLAN
189
+
190
+ except Exception as e:
191
+ logger.error(f"Planning failed: {e}")
192
+ state.errors.append(f"Planning: {e}")
193
+ return LoopAction.FAILURE
194
+
195
+ async def _step_patch(self, state: LoopState) -> LoopAction:
196
+ """Step 2: Generate code patch"""
197
+
198
+ logger.info("Step 2: Generating code patch...")
199
+
200
+ try:
201
+ # Get context files (stub - in reality would scan repo)
202
+ context_files = {}
203
+
204
+ state.patch = await self.provider.generate_patch(
205
+ issue_description=state.issue_description,
206
+ context_files=context_files,
207
+ previous_errors=None,
208
+ )
209
+
210
+ logger.info(f"Patch generated ({len(state.patch)} chars)")
211
+ return LoopAction.PATCH
212
+
213
+ except Exception as e:
214
+ logger.error(f"Patch generation failed: {e}")
215
+ state.errors.append(f"Patch generation: {e}")
216
+ return LoopAction.FAILURE
217
+
218
+ async def _step_apply(self, state: LoopState) -> LoopAction:
219
+ """Step 3: Apply patch to files"""
220
+
221
+ logger.info("Step 3: Applying patch...")
222
+
223
+ try:
224
+ result = await self.patcher.apply_patch(state.patch)
225
+
226
+ if result["status"] == "success" or result["status"] == "partial":
227
+ logger.info(f"Patch applied: {result['message']}")
228
+ return LoopAction.APPLY
229
+ else:
230
+ logger.error(f"Patch application failed: {result['message']}")
231
+ state.errors.append(result["message"])
232
+ return LoopAction.FAILURE
233
+
234
+ except Exception as e:
235
+ logger.error(f"Patch application error: {e}")
236
+ state.errors.append(f"Patch application: {e}")
237
+ return LoopAction.FAILURE
238
+
239
+ async def _step_test(self, state: LoopState) -> LoopAction:
240
+ """Step 4: Run tests"""
241
+
242
+ logger.info("Step 4: Running tests...")
243
+
244
+ try:
245
+ state.test_results = await self.executor.run_tests()
246
+
247
+ logger.info(
248
+ f"Tests complete: {state.test_results.passed}/{state.test_results.total} passed"
249
+ )
250
+
251
+ if state.test_results.failed == 0:
252
+ return LoopAction.SUCCESS
253
+ else:
254
+ return LoopAction.FAILURE
255
+
256
+ except Exception as e:
257
+ logger.error(f"Test execution failed: {e}")
258
+ state.errors.append(f"Test execution: {e}")
259
+ return LoopAction.FAILURE
260
+
261
+ async def _analyze_failure(self, state: LoopState):
262
+ """Analyze test failure and suggest improvements"""
263
+
264
+ logger.info("Analyzing test failure...")
265
+
266
+ try:
267
+ if state.test_results:
268
+ analysis = await self.provider.analyze_test_failure(
269
+ test_output=state.test_results.stdout, code_context=state.patch
270
+ )
271
+
272
+ logger.info(
273
+ f"Analysis complete: {analysis.get('suggestions', '')[:200]}"
274
+ )
275
+
276
+ # Update issue description for next iteration
277
+ state.issue_description = f"""{state.issue_description}
278
+
279
+ Failed attempt #{state.iteration}:
280
+ {analysis.get('issue', 'Unknown issue')}
281
+
282
+ Suggested fix:
283
+ {analysis.get('suggestions', 'Try different approach')}"""
284
+
285
+ except Exception as e:
286
+ logger.error(f"Failure analysis failed: {e}")
287
+ state.errors.append(f"Failure analysis: {e}")
288
+
289
+ async def _rollback(self):
290
+ """Rollback applied patches"""
291
+
292
+ logger.info("Rolling back applied patches...")
293
+ # Patcher will automatically restore backups
294
+
295
+ def _format_result(self, state: LoopState, status: str) -> Dict[str, Any]:
296
+ """Format final result"""
297
+
298
+ return {
299
+ "status": status,
300
+ "issue_id": state.issue_id,
301
+ "iterations": state.iteration,
302
+ "plan": state.plan[:200] if state.plan else None,
303
+ "test_results": (
304
+ {
305
+ "total": state.test_results.total if state.test_results else 0,
306
+ "passed": state.test_results.passed if state.test_results else 0,
307
+ "failed": state.test_results.failed if state.test_results else 0,
308
+ }
309
+ if state.test_results
310
+ else None
311
+ ),
312
+ "errors": state.errors,
313
+ "final_state": state.to_dict(),
314
+ }
315
+
316
+
317
+ async def demo_agent_loop():
318
+ """Demo agent loop"""
319
+
320
+ # Example: use Modal free token
321
+ loop = AgentLoop(
322
+ provider_name="modal",
323
+ api_key="your-modal-token", # Would come from env var
324
+ repo_path=".",
325
+ )
326
+
327
+ # Initialize with GLM-5.1-FP8
328
+ await loop.initialize("zai-org/GLM-5.1-FP8")
329
+
330
+ # Run on an issue
331
+ result = await loop.run(
332
+ issue_id=123, issue_description="Fix Unicode parsing in parser.py"
333
+ )
334
+
335
+ print(f"Final Status: {result['status']}")
336
+ print(f"Iterations: {result['iterations']}")
337
+ print(
338
+ f"Tests Passed: {result['test_results']['passed']}/{result['test_results']['total']}"
339
+ )
340
+
341
+
342
+ if __name__ == "__main__":
343
+ asyncio.run(demo_agent_loop())
@@ -0,0 +1,19 @@
1
+ """Test Executor Module - Run tests and parse results"""
2
+
3
+ from .executor import (
4
+ TestExecutor,
5
+ TestSuiteResult,
6
+ TestResult,
7
+ TestStatus,
8
+ PytestParser,
9
+ JestParser,
10
+ )
11
+
12
+ __all__ = [
13
+ "TestExecutor",
14
+ "TestSuiteResult",
15
+ "TestResult",
16
+ "TestStatus",
17
+ "PytestParser",
18
+ "JestParser",
19
+ ]