@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.
- package/README.md +439 -0
- package/bin/voria +730 -0
- package/docs/ARCHITECTURE.md +419 -0
- package/docs/CHANGELOG.md +189 -0
- package/docs/CONTRIBUTING.md +447 -0
- package/docs/DESIGN_DECISIONS.md +380 -0
- package/docs/DEVELOPMENT.md +535 -0
- package/docs/EXAMPLES.md +434 -0
- package/docs/INSTALL.md +335 -0
- package/docs/IPC_PROTOCOL.md +310 -0
- package/docs/LLM_INTEGRATION.md +416 -0
- package/docs/MODULES.md +470 -0
- package/docs/PERFORMANCE.md +346 -0
- package/docs/PLUGINS.md +432 -0
- package/docs/QUICKSTART.md +184 -0
- package/docs/README.md +133 -0
- package/docs/ROADMAP.md +346 -0
- package/docs/SECURITY.md +334 -0
- package/docs/TROUBLESHOOTING.md +565 -0
- package/docs/USER_GUIDE.md +700 -0
- package/package.json +63 -0
- package/python/voria/__init__.py +8 -0
- package/python/voria/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/__pycache__/engine.cpython-312.pyc +0 -0
- package/python/voria/core/__init__.py +1 -0
- package/python/voria/core/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/__pycache__/setup.cpython-312.pyc +0 -0
- package/python/voria/core/agent/__init__.py +9 -0
- package/python/voria/core/agent/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/agent/__pycache__/loop.cpython-312.pyc +0 -0
- package/python/voria/core/agent/loop.py +343 -0
- package/python/voria/core/executor/__init__.py +19 -0
- package/python/voria/core/executor/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/executor/__pycache__/executor.cpython-312.pyc +0 -0
- package/python/voria/core/executor/executor.py +431 -0
- package/python/voria/core/github/__init__.py +33 -0
- package/python/voria/core/github/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/github/__pycache__/client.cpython-312.pyc +0 -0
- package/python/voria/core/github/client.py +438 -0
- package/python/voria/core/llm/__init__.py +55 -0
- package/python/voria/core/llm/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/base.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/claude_provider.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/gemini_provider.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/modal_provider.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/model_discovery.cpython-312.pyc +0 -0
- package/python/voria/core/llm/__pycache__/openai_provider.cpython-312.pyc +0 -0
- package/python/voria/core/llm/base.py +152 -0
- package/python/voria/core/llm/claude_provider.py +188 -0
- package/python/voria/core/llm/gemini_provider.py +148 -0
- package/python/voria/core/llm/modal_provider.py +228 -0
- package/python/voria/core/llm/model_discovery.py +289 -0
- package/python/voria/core/llm/openai_provider.py +146 -0
- package/python/voria/core/patcher/__init__.py +9 -0
- package/python/voria/core/patcher/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/patcher/__pycache__/patcher.cpython-312.pyc +0 -0
- package/python/voria/core/patcher/patcher.py +375 -0
- package/python/voria/core/planner/__init__.py +1 -0
- package/python/voria/core/setup.py +201 -0
- package/python/voria/core/token_manager/__init__.py +29 -0
- package/python/voria/core/token_manager/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/voria/core/token_manager/__pycache__/manager.cpython-312.pyc +0 -0
- package/python/voria/core/token_manager/manager.py +241 -0
- package/python/voria/engine.py +1185 -0
- package/python/voria/plugins/__init__.py +1 -0
- package/python/voria/plugins/python/__init__.py +1 -0
- 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
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""voria core modules."""
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
+
]
|
|
Binary file
|
|
Binary file
|