superqode 0.1.5__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 (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. superqode-0.1.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,373 @@
1
+ """
2
+ Python Test Framework Implementations.
3
+
4
+ Supports:
5
+ - pytest
6
+ - unittest
7
+ - nose2
8
+ """
9
+
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+ from typing import List, Optional
13
+ import json
14
+ import re
15
+
16
+ from .base import (
17
+ TestFramework,
18
+ FrameworkConfig,
19
+ TestResult,
20
+ TestSuite,
21
+ ExecutionResult,
22
+ TestStatus,
23
+ )
24
+
25
+
26
+ class PytestFramework(TestFramework):
27
+ """Pytest test framework."""
28
+
29
+ NAME = "pytest"
30
+ DISPLAY_NAME = "Pytest"
31
+ LANGUAGE = "python"
32
+ FILE_PATTERNS = ["**/test_*.py", "**/*_test.py"]
33
+
34
+ @classmethod
35
+ def detect(cls, project_root: Path) -> bool:
36
+ """Detect if pytest is used."""
37
+ # Check for pytest.ini, pyproject.toml with pytest, or conftest.py
38
+ if (project_root / "pytest.ini").exists():
39
+ return True
40
+ if (project_root / "conftest.py").exists():
41
+ return True
42
+
43
+ pyproject = project_root / "pyproject.toml"
44
+ if pyproject.exists():
45
+ content = pyproject.read_text()
46
+ if "[tool.pytest" in content:
47
+ return True
48
+
49
+ # Check for test files
50
+ for pattern in cls.FILE_PATTERNS:
51
+ if list(project_root.glob(pattern)):
52
+ return True
53
+
54
+ return False
55
+
56
+ async def discover(self) -> List[TestSuite]:
57
+ """Discover pytest tests."""
58
+ command = ["pytest", "--collect-only", "-q", str(self.config.project_root)]
59
+
60
+ exit_code, stdout, stderr = await self.run_command(command, timeout=60)
61
+
62
+ suites = []
63
+ current_file = None
64
+ tests = []
65
+
66
+ for line in stdout.splitlines():
67
+ line = line.strip()
68
+ if not line or line.startswith("="):
69
+ continue
70
+
71
+ # Parse collected test items
72
+ if "::" in line:
73
+ parts = line.split("::")
74
+ file_path = parts[0]
75
+
76
+ if file_path != current_file:
77
+ if current_file and tests:
78
+ suites.append(
79
+ TestSuite(
80
+ name=Path(current_file).stem, file_path=current_file, tests=tests
81
+ )
82
+ )
83
+ current_file = file_path
84
+ tests = []
85
+
86
+ test_name = "::".join(parts[1:])
87
+ tests.append(test_name)
88
+
89
+ # Don't forget the last file
90
+ if current_file and tests:
91
+ suites.append(
92
+ TestSuite(name=Path(current_file).stem, file_path=current_file, tests=tests)
93
+ )
94
+
95
+ return suites
96
+
97
+ async def execute(self, tests: Optional[List[str]] = None, **kwargs) -> ExecutionResult:
98
+ """Execute pytest tests."""
99
+ started_at = datetime.now()
100
+
101
+ command = ["pytest"]
102
+
103
+ # Add options
104
+ if self.config.verbose:
105
+ command.append("-v")
106
+ if self.config.fail_fast:
107
+ command.append("-x")
108
+ if self.config.coverage:
109
+ command.extend(["--cov", "--cov-report=json"])
110
+ if self.config.parallel and self.config.workers > 1:
111
+ command.extend(["-n", str(self.config.workers)])
112
+
113
+ # JSON output for parsing
114
+ command.append("--tb=short")
115
+ command.append("-q")
116
+
117
+ # Add extra args
118
+ command.extend(self.config.extra_args)
119
+
120
+ # Add specific tests
121
+ if tests:
122
+ command.extend(tests)
123
+ else:
124
+ command.append(str(self.config.project_root))
125
+
126
+ exit_code, stdout, stderr = await self.run_command(command)
127
+
128
+ ended_at = datetime.now()
129
+ duration = (ended_at - started_at).total_seconds()
130
+
131
+ # Parse results
132
+ test_results = self.parse_results(stdout)
133
+
134
+ # Count results
135
+ passed = sum(1 for r in test_results if r.status == TestStatus.PASSED)
136
+ failed = sum(1 for r in test_results if r.status == TestStatus.FAILED)
137
+ skipped = sum(1 for r in test_results if r.status == TestStatus.SKIPPED)
138
+ errors = sum(1 for r in test_results if r.status == TestStatus.ERROR)
139
+
140
+ # Get coverage if available
141
+ coverage = None
142
+ if self.config.coverage:
143
+ coverage_file = self.config.project_root / "coverage.json"
144
+ if coverage_file.exists():
145
+ try:
146
+ cov_data = json.loads(coverage_file.read_text())
147
+ coverage = cov_data.get("totals", {}).get("percent_covered", 0)
148
+ except Exception:
149
+ pass
150
+
151
+ return ExecutionResult(
152
+ framework=self.NAME,
153
+ started_at=started_at,
154
+ ended_at=ended_at,
155
+ duration_seconds=duration,
156
+ total=len(test_results),
157
+ passed=passed,
158
+ failed=failed,
159
+ skipped=skipped,
160
+ errors=errors,
161
+ test_results=test_results,
162
+ coverage_percentage=coverage,
163
+ output=stdout,
164
+ error_output=stderr,
165
+ )
166
+
167
+ def parse_results(self, output: str) -> List[TestResult]:
168
+ """Parse pytest output."""
169
+ results = []
170
+
171
+ # Parse test results from output
172
+ # Format: test_file.py::test_name PASSED/FAILED
173
+ test_pattern = re.compile(
174
+ r"(\S+::\S+)\s+(PASSED|FAILED|SKIPPED|ERROR)(?:\s+\[.*?\])?\s*(?:\((.+?)\))?"
175
+ )
176
+
177
+ for line in output.splitlines():
178
+ match = test_pattern.search(line)
179
+ if match:
180
+ name = match.group(1)
181
+ status_str = match.group(2)
182
+ duration_str = match.group(3)
183
+
184
+ status_map = {
185
+ "PASSED": TestStatus.PASSED,
186
+ "FAILED": TestStatus.FAILED,
187
+ "SKIPPED": TestStatus.SKIPPED,
188
+ "ERROR": TestStatus.ERROR,
189
+ }
190
+ status = status_map.get(status_str, TestStatus.ERROR)
191
+
192
+ # Parse duration if available
193
+ duration_ms = 0.0
194
+ if duration_str:
195
+ try:
196
+ if "s" in duration_str:
197
+ duration_ms = float(duration_str.replace("s", "")) * 1000
198
+ except ValueError:
199
+ pass
200
+
201
+ results.append(
202
+ TestResult(
203
+ name=name,
204
+ status=status,
205
+ duration_ms=duration_ms,
206
+ )
207
+ )
208
+
209
+ return results
210
+
211
+ def get_command(self, tests: Optional[List[str]] = None) -> List[str]:
212
+ """Get pytest command."""
213
+ command = ["pytest"]
214
+ if tests:
215
+ command.extend(tests)
216
+ return command
217
+
218
+
219
+ class UnittestFramework(TestFramework):
220
+ """Python unittest framework."""
221
+
222
+ NAME = "unittest"
223
+ DISPLAY_NAME = "Unittest"
224
+ LANGUAGE = "python"
225
+ FILE_PATTERNS = ["**/test_*.py", "**/*_test.py"]
226
+
227
+ @classmethod
228
+ def detect(cls, project_root: Path) -> bool:
229
+ """Detect if unittest is used."""
230
+ # Check for test files with unittest imports
231
+ for pattern in cls.FILE_PATTERNS:
232
+ for test_file in project_root.glob(pattern):
233
+ try:
234
+ content = test_file.read_text()
235
+ if "import unittest" in content or "from unittest" in content:
236
+ return True
237
+ except Exception:
238
+ pass
239
+ return False
240
+
241
+ async def discover(self) -> List[TestSuite]:
242
+ """Discover unittest tests."""
243
+ command = [
244
+ "python",
245
+ "-m",
246
+ "unittest",
247
+ "discover",
248
+ "-s",
249
+ str(self.config.project_root),
250
+ "-p",
251
+ "test_*.py",
252
+ "-v",
253
+ ]
254
+
255
+ # Just collect, don't run
256
+ exit_code, stdout, stderr = await self.run_command(
257
+ [
258
+ "python",
259
+ "-c",
260
+ f"""
261
+ import unittest
262
+ import sys
263
+ loader = unittest.TestLoader()
264
+ suite = loader.discover('{self.config.project_root}', pattern='test_*.py')
265
+ for group in suite:
266
+ for test_group in group:
267
+ for test in test_group:
268
+ print(str(test))
269
+ """,
270
+ ],
271
+ timeout=60,
272
+ )
273
+
274
+ suites_dict = {}
275
+ for line in stdout.splitlines():
276
+ line = line.strip()
277
+ if not line:
278
+ continue
279
+
280
+ # Parse test name: test_method (module.TestClass)
281
+ match = re.match(r"(\w+)\s+\((.+)\)", line)
282
+ if match:
283
+ method = match.group(1)
284
+ module_class = match.group(2)
285
+
286
+ if module_class not in suites_dict:
287
+ suites_dict[module_class] = []
288
+ suites_dict[module_class].append(method)
289
+
290
+ return [
291
+ TestSuite(name=name, file_path=name.replace(".", "/") + ".py", tests=tests)
292
+ for name, tests in suites_dict.items()
293
+ ]
294
+
295
+ async def execute(self, tests: Optional[List[str]] = None, **kwargs) -> ExecutionResult:
296
+ """Execute unittest tests."""
297
+ started_at = datetime.now()
298
+
299
+ command = ["python", "-m", "unittest"]
300
+
301
+ if self.config.verbose:
302
+ command.append("-v")
303
+ if self.config.fail_fast:
304
+ command.append("-f")
305
+
306
+ if tests:
307
+ command.extend(tests)
308
+ else:
309
+ command.extend(["discover", "-s", str(self.config.project_root), "-p", "test_*.py"])
310
+
311
+ exit_code, stdout, stderr = await self.run_command(command)
312
+
313
+ ended_at = datetime.now()
314
+ duration = (ended_at - started_at).total_seconds()
315
+
316
+ test_results = self.parse_results(stdout + stderr)
317
+
318
+ passed = sum(1 for r in test_results if r.status == TestStatus.PASSED)
319
+ failed = sum(1 for r in test_results if r.status == TestStatus.FAILED)
320
+ skipped = sum(1 for r in test_results if r.status == TestStatus.SKIPPED)
321
+ errors = sum(1 for r in test_results if r.status == TestStatus.ERROR)
322
+
323
+ return ExecutionResult(
324
+ framework=self.NAME,
325
+ started_at=started_at,
326
+ ended_at=ended_at,
327
+ duration_seconds=duration,
328
+ total=len(test_results),
329
+ passed=passed,
330
+ failed=failed,
331
+ skipped=skipped,
332
+ errors=errors,
333
+ test_results=test_results,
334
+ output=stdout,
335
+ error_output=stderr,
336
+ )
337
+
338
+ def parse_results(self, output: str) -> List[TestResult]:
339
+ """Parse unittest output."""
340
+ results = []
341
+
342
+ # Parse verbose output: test_name (module.Class) ... ok/FAIL/ERROR
343
+ pattern = re.compile(r"(\w+)\s+\((.+?)\)\s+\.\.\.\s+(ok|FAIL|ERROR|skipped)")
344
+
345
+ for match in pattern.finditer(output):
346
+ method = match.group(1)
347
+ module_class = match.group(2)
348
+ status_str = match.group(3)
349
+
350
+ status_map = {
351
+ "ok": TestStatus.PASSED,
352
+ "FAIL": TestStatus.FAILED,
353
+ "ERROR": TestStatus.ERROR,
354
+ "skipped": TestStatus.SKIPPED,
355
+ }
356
+ status = status_map.get(status_str, TestStatus.ERROR)
357
+
358
+ results.append(
359
+ TestResult(
360
+ name=f"{module_class}.{method}",
361
+ status=status,
362
+ duration_ms=0,
363
+ )
364
+ )
365
+
366
+ return results
367
+
368
+ def get_command(self, tests: Optional[List[str]] = None) -> List[str]:
369
+ """Get unittest command."""
370
+ command = ["python", "-m", "unittest"]
371
+ if tests:
372
+ command.extend(tests)
373
+ return command
@@ -0,0 +1,92 @@
1
+ """
2
+ Framework Registry - Central registry for test frameworks.
3
+
4
+ Provides:
5
+ - Framework registration
6
+ - Auto-detection
7
+ - Framework lookup
8
+ """
9
+
10
+ from pathlib import Path
11
+ from typing import Dict, List, Optional, Type
12
+
13
+ from .base import TestFramework, FrameworkConfig
14
+
15
+
16
+ class FrameworkRegistry:
17
+ """Registry for test frameworks."""
18
+
19
+ _frameworks: Dict[str, Type[TestFramework]] = {}
20
+
21
+ @classmethod
22
+ def register(cls, framework_class: Type[TestFramework]) -> None:
23
+ """Register a framework."""
24
+ cls._frameworks[framework_class.NAME] = framework_class
25
+
26
+ @classmethod
27
+ def get(cls, name: str, config: Optional[FrameworkConfig] = None) -> Optional[TestFramework]:
28
+ """Get a framework by name."""
29
+ framework_class = cls._frameworks.get(name)
30
+ if framework_class:
31
+ return framework_class(config)
32
+ return None
33
+
34
+ @classmethod
35
+ def detect(
36
+ cls, project_root: Path, config: Optional[FrameworkConfig] = None
37
+ ) -> List[TestFramework]:
38
+ """Detect frameworks in a project."""
39
+ detected = []
40
+ for framework_class in cls._frameworks.values():
41
+ if framework_class.detect(project_root):
42
+ cfg = config or FrameworkConfig(project_root=project_root)
43
+ detected.append(framework_class(cfg))
44
+ return detected
45
+
46
+ @classmethod
47
+ def list_all(cls) -> List[Dict[str, str]]:
48
+ """List all registered frameworks."""
49
+ return [
50
+ {
51
+ "name": fw.NAME,
52
+ "display_name": fw.DISPLAY_NAME,
53
+ "language": fw.LANGUAGE,
54
+ }
55
+ for fw in cls._frameworks.values()
56
+ ]
57
+
58
+
59
+ def get_framework(name: str, config: Optional[FrameworkConfig] = None) -> Optional[TestFramework]:
60
+ """Get a framework by name."""
61
+ return FrameworkRegistry.get(name, config)
62
+
63
+
64
+ def detect_framework(
65
+ project_root: Path, config: Optional[FrameworkConfig] = None
66
+ ) -> List[TestFramework]:
67
+ """Detect frameworks in a project."""
68
+ return FrameworkRegistry.detect(project_root, config)
69
+
70
+
71
+ def list_frameworks() -> List[Dict[str, str]]:
72
+ """List all available frameworks."""
73
+ return FrameworkRegistry.list_all()
74
+
75
+
76
+ # Auto-register frameworks when module is imported
77
+ def _register_all_frameworks():
78
+ """Register all built-in frameworks."""
79
+ from .python import PytestFramework, UnittestFramework
80
+ from .javascript import JestFramework, MochaFramework, VitestFramework
81
+ from .e2e import CypressFramework, PlaywrightFramework
82
+
83
+ FrameworkRegistry.register(PytestFramework)
84
+ FrameworkRegistry.register(UnittestFramework)
85
+ FrameworkRegistry.register(JestFramework)
86
+ FrameworkRegistry.register(MochaFramework)
87
+ FrameworkRegistry.register(VitestFramework)
88
+ FrameworkRegistry.register(CypressFramework)
89
+ FrameworkRegistry.register(PlaywrightFramework)
90
+
91
+
92
+ _register_all_frameworks()
@@ -0,0 +1,47 @@
1
+ """
2
+ MCP Tools Module - Model Context Protocol tools with lazy loading.
3
+
4
+ Provides 100+ MCP tools organized by domain with 87% context reduction
5
+ through lazy loading (only load domain tools when needed).
6
+
7
+ Tool Categories:
8
+ - core: Always loaded (14 tools)
9
+ - testing: Test generation, execution, reporting
10
+ - security: Vulnerability scanning, OWASP analysis
11
+ - performance: Benchmarking, bottleneck analysis
12
+ - coverage: Gap detection, coverage analysis
13
+ - quality: Quality gates, deployment readiness
14
+ - flaky: Detection and stabilization
15
+ - accessibility: WCAG compliance, visual regression
16
+ - learning: Pattern management, training
17
+ """
18
+
19
+ from .registry import (
20
+ MCPToolRegistry,
21
+ MCPTool,
22
+ ToolDomain,
23
+ get_registry,
24
+ register_tool,
25
+ get_tool,
26
+ list_tools,
27
+ list_domains,
28
+ load_domain,
29
+ get_loaded_domains,
30
+ )
31
+ from .core_tools import register_core_tools
32
+ from .testing_tools import register_testing_tools
33
+
34
+ __all__ = [
35
+ "MCPToolRegistry",
36
+ "MCPTool",
37
+ "ToolDomain",
38
+ "get_registry",
39
+ "register_tool",
40
+ "get_tool",
41
+ "list_tools",
42
+ "list_domains",
43
+ "load_domain",
44
+ "get_loaded_domains",
45
+ "register_core_tools",
46
+ "register_testing_tools",
47
+ ]