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,609 @@
1
+ """
2
+ QE Roles - Agentic detection roles for quality engineering.
3
+
4
+ Implements the PRD role model:
5
+
6
+ Execution-only roles (deterministic, run existing tests):
7
+ - smoke_tester: Fast critical path validation
8
+ - sanity_tester: Quick functionality check
9
+ - regression_tester: Full test suite execution
10
+
11
+ Agentic detection roles (AI-powered, discover issues):
12
+ - api_tester: API contract and security testing
13
+ - unit_tester: Function/class unit testing
14
+ - e2e_tester: End-to-end workflow testing
15
+ - security_tester: Security vulnerability detection
16
+ - performance_tester: Performance bottleneck detection
17
+
18
+ Heuristic role:
19
+ - fullstack: Senior QE/Tech Lead comprehensive review
20
+ - lint_tester: Run linters across the codebase
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from abc import ABC, abstractmethod
26
+ from dataclasses import dataclass, field
27
+ from enum import Enum
28
+ from pathlib import Path
29
+ from typing import Any, Dict, List, Optional, Callable
30
+ import logging
31
+ import asyncio
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ class RoleType(Enum):
37
+ """Type of QE role."""
38
+
39
+ EXECUTION = "execution" # Run existing tests only
40
+ DETECTION = "detection" # AI-driven issue detection
41
+ HEURISTIC = "heuristic" # Senior QE review
42
+
43
+
44
+ @dataclass
45
+ class RoleConfig:
46
+ """Configuration for a QE role."""
47
+
48
+ name: str
49
+ role_type: RoleType
50
+ description: str
51
+
52
+ # Execution role settings
53
+ test_pattern: str = ""
54
+ fail_fast: bool = False
55
+ detect_flakes: bool = False
56
+
57
+ # Detection role settings
58
+ focus_areas: List[str] = field(default_factory=list)
59
+ max_findings: int = 50
60
+ min_confidence: float = 0.7
61
+
62
+ # Agent settings (for detection roles)
63
+ provider: Optional[str] = None
64
+ model: Optional[str] = None
65
+ timeout_seconds: int = 300
66
+
67
+ # System prompt components
68
+ job_description: str = ""
69
+ forbidden_actions: List[str] = field(default_factory=list)
70
+
71
+
72
+ @dataclass
73
+ class RoleResult:
74
+ """Result from a QE role execution."""
75
+
76
+ role_name: str
77
+ role_type: RoleType
78
+ success: bool
79
+
80
+ # Test results (for execution roles)
81
+ tests_run: int = 0
82
+ tests_passed: int = 0
83
+ tests_failed: int = 0
84
+ tests_skipped: int = 0
85
+
86
+ # Findings (for detection roles)
87
+ findings: List[Dict[str, Any]] = field(default_factory=list)
88
+
89
+ # Artifacts generated
90
+ patches_generated: int = 0
91
+ tests_generated: int = 0
92
+
93
+ # Timing
94
+ duration_seconds: float = 0.0
95
+
96
+ # Errors
97
+ errors: List[str] = field(default_factory=list)
98
+
99
+
100
+ class QERole(ABC):
101
+ """Base class for QE roles."""
102
+
103
+ def __init__(
104
+ self,
105
+ config: RoleConfig,
106
+ project_root: Path,
107
+ allow_suggestions: bool = False,
108
+ ):
109
+ self.config = config
110
+ self.project_root = project_root
111
+ self.allow_suggestions = allow_suggestions
112
+
113
+ @property
114
+ def name(self) -> str:
115
+ return self.config.name
116
+
117
+ @property
118
+ def role_type(self) -> RoleType:
119
+ return self.config.role_type
120
+
121
+ @abstractmethod
122
+ async def run(self) -> RoleResult:
123
+ """Execute the role and return results."""
124
+ pass
125
+
126
+ def get_system_prompt(self) -> str:
127
+ """Get the system prompt for this role (detection roles only)."""
128
+ return self.config.job_description
129
+
130
+
131
+ # =============================================================================
132
+ # Execution Roles (Deterministic, run existing tests)
133
+ # =============================================================================
134
+
135
+
136
+ class SmokeTestRole(QERole):
137
+ """
138
+ Smoke Test Role - Fast critical path validation.
139
+
140
+ Runs existing smoke tests to verify critical functionality.
141
+ Fail-fast behavior: stops on first failure.
142
+ """
143
+
144
+ async def run(self) -> RoleResult:
145
+ from superqode.execution.runner import SmokeRunner
146
+
147
+ runner = SmokeRunner(
148
+ self.project_root,
149
+ test_pattern=self.config.test_pattern or "**/test_smoke*.py",
150
+ timeout_seconds=min(60, self.config.timeout_seconds),
151
+ )
152
+
153
+ suite_result = await runner.run()
154
+
155
+ return RoleResult(
156
+ role_name=self.name,
157
+ role_type=self.role_type,
158
+ success=suite_result.success,
159
+ tests_run=suite_result.total_tests,
160
+ tests_passed=suite_result.passed,
161
+ tests_failed=suite_result.failed,
162
+ tests_skipped=suite_result.skipped,
163
+ duration_seconds=suite_result.duration_seconds,
164
+ errors=suite_result.errors if hasattr(suite_result, "errors") else [],
165
+ )
166
+
167
+
168
+ class SanityTestRole(QERole):
169
+ """
170
+ Sanity Test Role - Quick functionality verification.
171
+
172
+ Runs sanity tests for basic functionality checks.
173
+ """
174
+
175
+ async def run(self) -> RoleResult:
176
+ from superqode.execution.runner import SanityRunner
177
+
178
+ runner = SanityRunner(
179
+ self.project_root,
180
+ test_pattern=self.config.test_pattern or "**/test_sanity*.py",
181
+ timeout_seconds=min(120, self.config.timeout_seconds),
182
+ )
183
+
184
+ suite_result = await runner.run()
185
+
186
+ return RoleResult(
187
+ role_name=self.name,
188
+ role_type=self.role_type,
189
+ success=suite_result.success,
190
+ tests_run=suite_result.total_tests,
191
+ tests_passed=suite_result.passed,
192
+ tests_failed=suite_result.failed,
193
+ tests_skipped=suite_result.skipped,
194
+ duration_seconds=suite_result.duration_seconds,
195
+ errors=suite_result.errors if hasattr(suite_result, "errors") else [],
196
+ )
197
+
198
+
199
+ class RegressionTestRole(QERole):
200
+ """
201
+ Regression Test Role - Full test suite execution.
202
+
203
+ Runs all tests including flake detection.
204
+ """
205
+
206
+ async def run(self) -> RoleResult:
207
+ from superqode.execution.runner import RegressionRunner
208
+
209
+ runner = RegressionRunner(
210
+ self.project_root,
211
+ test_pattern=self.config.test_pattern or "**/test_*.py",
212
+ timeout_seconds=self.config.timeout_seconds,
213
+ detect_flakes=self.config.detect_flakes,
214
+ )
215
+
216
+ suite_result = await runner.run()
217
+
218
+ return RoleResult(
219
+ role_name=self.name,
220
+ role_type=self.role_type,
221
+ success=suite_result.success,
222
+ tests_run=suite_result.total_tests,
223
+ tests_passed=suite_result.passed,
224
+ tests_failed=suite_result.failed,
225
+ tests_skipped=suite_result.skipped,
226
+ duration_seconds=suite_result.duration_seconds,
227
+ errors=suite_result.errors if hasattr(suite_result, "errors") else [],
228
+ )
229
+
230
+
231
+ class LintTestRole(QERole):
232
+ """
233
+ Lint Test Role - Run fast linters for detected languages.
234
+
235
+ Executes local linters (ruff, eslint/biome, golangci-lint, clippy, etc.)
236
+ and reports findings without failing the QE session.
237
+ """
238
+
239
+ async def run(self) -> RoleResult:
240
+ from superqode.execution.linter import LinterRunner
241
+
242
+ runner = LinterRunner(self.project_root, timeout_seconds=self.config.timeout_seconds)
243
+ lint_result = await runner.run()
244
+
245
+ return RoleResult(
246
+ role_name=self.name,
247
+ role_type=self.role_type,
248
+ success=True,
249
+ findings=lint_result.findings,
250
+ duration_seconds=0.0,
251
+ errors=lint_result.errors,
252
+ )
253
+
254
+
255
+ # =============================================================================
256
+ # Detection Roles (AI-powered, discover issues)
257
+ # =============================================================================
258
+
259
+
260
+ async def _run_acp_role(
261
+ role: QERole,
262
+ role_name: str,
263
+ allow_suggestions: bool = False,
264
+ ) -> RoleResult:
265
+ """
266
+ Helper function to run a QE role using ACP agent.
267
+
268
+ This is shared by all detection roles to reduce code duplication.
269
+
270
+ Args:
271
+ role: The QE role to run
272
+ role_name: Name of the role for prompt selection
273
+ allow_suggestions: If True, ask agent to generate and verify fixes
274
+ """
275
+ from .acp_runner import ACPQERunner, ACPRunnerConfig, get_qe_prompt
276
+
277
+ logger.info(f"Running {role_name} role with ACP agent (suggestions={allow_suggestions})")
278
+
279
+ # Create ACP runner with suggestion mode settings
280
+ runner_config = ACPRunnerConfig(
281
+ timeout_seconds=role.config.timeout_seconds,
282
+ verbose=False,
283
+ allow_suggestions=allow_suggestions,
284
+ )
285
+ runner = ACPQERunner(role.project_root, runner_config)
286
+
287
+ # Get the QE prompt for this role (enhanced if suggestions enabled)
288
+ prompt = get_qe_prompt(role_name, allow_suggestions=allow_suggestions)
289
+
290
+ # Run the analysis
291
+ result = await runner.run(prompt, role_name)
292
+
293
+ # Check if the runner encountered errors (common in OSS when agents aren't installed)
294
+ if result.errors and any("Failed to start ACP agent" in error for error in result.errors):
295
+ logger.warning(f"ACP agent not available for {role_name}, providing graceful degradation")
296
+ # Create a graceful finding instead of failing
297
+ from .acp_runner import ACPFinding
298
+
299
+ graceful_finding = ACPFinding(
300
+ id=f"{role_name}-oss-info",
301
+ severity="info",
302
+ title=f"{role_name.replace('_', ' ').title()} - Agent Not Available",
303
+ description=f"This QE role requires ACP-compatible coding agents (OpenCode, Claude Code, etc.). Install coding agents to enable full analysis capabilities.",
304
+ file_path=None,
305
+ line_number=None,
306
+ evidence="ACP agent connection failed - this is normal in OSS environments without coding agents installed",
307
+ suggested_fix="Install OpenCode (npm i -g opencode-ai) or other ACP-compatible coding agents",
308
+ confidence=0.0,
309
+ category="infrastructure",
310
+ )
311
+ result.findings = [graceful_finding]
312
+ result.errors = [] # Clear the errors since we're handling them gracefully
313
+
314
+ # Convert findings to dict format
315
+ findings = []
316
+ for f in result.findings:
317
+ finding_dict = {
318
+ "id": f.id,
319
+ "severity": f.severity,
320
+ "title": f.title,
321
+ "description": f.description,
322
+ "file_path": f.file_path,
323
+ "line_number": f.line_number,
324
+ "evidence": f.evidence,
325
+ "suggested_fix": f.suggested_fix,
326
+ "confidence": f.confidence,
327
+ "category": f.category,
328
+ }
329
+
330
+ # Include fix verification data if available
331
+ if f.fix_verification:
332
+ finding_dict["fix_verification"] = {
333
+ "fix_applied": f.fix_verification.fix_applied,
334
+ "tests_passed": f.fix_verification.tests_passed,
335
+ "tests_total": f.fix_verification.tests_total,
336
+ "fix_verified": f.fix_verification.fix_verified,
337
+ "is_improvement": f.fix_verification.is_improvement,
338
+ "outcome": f.fix_verification.outcome,
339
+ }
340
+
341
+ findings.append(finding_dict)
342
+
343
+ critical_count = len([f for f in findings if f.get("severity") == "critical"])
344
+
345
+ return RoleResult(
346
+ role_name=role.name,
347
+ role_type=role.role_type,
348
+ success=result.success and critical_count == 0,
349
+ findings=findings,
350
+ duration_seconds=result.duration_seconds,
351
+ errors=result.errors,
352
+ )
353
+
354
+
355
+ class APITestRole(QERole):
356
+ """
357
+ API Test Role - API contract and security testing.
358
+
359
+ Uses ACP agent (OpenCode) to:
360
+ - Discover API endpoints
361
+ - Test API contracts
362
+ - Find security vulnerabilities in APIs
363
+ - Generate API test cases
364
+ """
365
+
366
+ DEFAULT_JOB_DESCRIPTION = ""
367
+
368
+ async def run(self) -> RoleResult:
369
+ return await _run_acp_role(self, "api_tester", self.allow_suggestions)
370
+
371
+ def get_system_prompt(self) -> str:
372
+ return self.config.job_description
373
+
374
+
375
+ class UnitTestRole(QERole):
376
+ """
377
+ Unit Test Role - Function/class unit testing.
378
+
379
+ Uses ACP agent (OpenCode) to:
380
+ - Identify functions lacking tests
381
+ - Generate unit tests for uncovered code
382
+ - Find edge cases and error conditions
383
+ """
384
+
385
+ DEFAULT_JOB_DESCRIPTION = ""
386
+
387
+ async def run(self) -> RoleResult:
388
+ return await _run_acp_role(self, "unit_tester", self.allow_suggestions)
389
+
390
+ def get_system_prompt(self) -> str:
391
+ return self.config.job_description
392
+
393
+
394
+ class E2ETestRole(QERole):
395
+ """
396
+ E2E Test Role - End-to-end workflow testing.
397
+
398
+ Uses ACP agent (OpenCode) to:
399
+ - Map user workflows
400
+ - Generate E2E test scenarios
401
+ - Test complete user journeys
402
+ - Verify integration points
403
+ """
404
+
405
+ DEFAULT_JOB_DESCRIPTION = ""
406
+
407
+ async def run(self) -> RoleResult:
408
+ return await _run_acp_role(self, "e2e_tester", self.allow_suggestions)
409
+
410
+ def get_system_prompt(self) -> str:
411
+ return self.config.job_description
412
+
413
+
414
+ class SecurityTestRole(QERole):
415
+ """
416
+ Security Test Role - Security vulnerability detection.
417
+
418
+ Uses ACP agent (OpenCode) to:
419
+ - Find common vulnerabilities (OWASP Top 10)
420
+ - Analyze authentication/authorization
421
+ - Check for injection vulnerabilities
422
+ - Review secrets/credentials handling
423
+ """
424
+
425
+ DEFAULT_JOB_DESCRIPTION = ""
426
+
427
+ async def run(self) -> RoleResult:
428
+ return await _run_acp_role(self, "security_tester", self.allow_suggestions)
429
+
430
+ def get_system_prompt(self) -> str:
431
+ return self.config.job_description
432
+
433
+
434
+ class PerformanceTestRole(QERole):
435
+ """
436
+ Performance Test Role - Performance bottleneck detection.
437
+
438
+ Uses ACP agent (OpenCode) to:
439
+ - Identify performance bottlenecks
440
+ - Find N+1 queries
441
+ - Detect memory leaks
442
+ - Analyze algorithm complexity
443
+ """
444
+
445
+ DEFAULT_JOB_DESCRIPTION = ""
446
+
447
+ async def run(self) -> RoleResult:
448
+ return await _run_acp_role(self, "performance_tester", self.allow_suggestions)
449
+
450
+ def get_system_prompt(self) -> str:
451
+ return self.config.job_description
452
+
453
+
454
+ # =============================================================================
455
+ # Heuristic Role (Senior QE Review)
456
+ # =============================================================================
457
+
458
+
459
+ class FullstackQERole(QERole):
460
+ """
461
+ Fullstack QE Role - Senior QE/Tech Lead comprehensive review.
462
+
463
+ The heuristic role that combines all detection capabilities using ACP:
464
+ - Reviews code like a senior QE engineer
465
+ - Prioritizes findings by business impact
466
+ - Makes judgment calls on edge cases
467
+ - Provides actionable recommendations
468
+ """
469
+
470
+ DEFAULT_JOB_DESCRIPTION = ""
471
+
472
+ async def run(self) -> RoleResult:
473
+ return await _run_acp_role(self, "fullstack", self.allow_suggestions)
474
+
475
+ def get_system_prompt(self) -> str:
476
+ return self.config.job_description
477
+
478
+
479
+ # =============================================================================
480
+ # Role Registry and Factory
481
+ # =============================================================================
482
+
483
+ # Registry of all available roles
484
+ ROLE_REGISTRY: Dict[str, type] = {
485
+ "smoke_tester": SmokeTestRole,
486
+ "sanity_tester": SanityTestRole,
487
+ "regression_tester": RegressionTestRole,
488
+ "lint_tester": LintTestRole,
489
+ "api_tester": APITestRole,
490
+ "unit_tester": UnitTestRole,
491
+ "e2e_tester": E2ETestRole,
492
+ "security_tester": SecurityTestRole,
493
+ "performance_tester": PerformanceTestRole,
494
+ "fullstack": FullstackQERole,
495
+ }
496
+
497
+
498
+ def _load_yaml_data(project_root: Path) -> Dict[str, Any]:
499
+ """Load raw YAML config for the current project."""
500
+ from superqode.config.loader import find_config_file, load_config_from_file
501
+
502
+ config_path = find_config_file()
503
+ if config_path is None:
504
+ candidate = project_root / "superqode.yaml"
505
+ if not candidate.exists():
506
+ # Fall back to packaged template
507
+ template_path = Path(__file__).parent.parent / "data" / "superqode-template.yaml"
508
+ if template_path.exists():
509
+ return load_config_from_file(template_path)
510
+ return {}
511
+ config_path = candidate
512
+
513
+ return load_config_from_file(config_path)
514
+
515
+
516
+ def _get_qe_role_map(data: Dict[str, Any]) -> Dict[str, Any]:
517
+ """Return QE role definitions from the raw YAML structure."""
518
+ team = data.get("team", {})
519
+ if "modes" in team:
520
+ qe_mode = team.get("modes", {}).get("qe", {})
521
+ else:
522
+ qe_mode = team.get("qe", {})
523
+ roles = qe_mode.get("roles", {})
524
+ return roles if isinstance(roles, dict) else {}
525
+
526
+
527
+ def load_role_config_from_yaml(role_name: str, project_root: Path) -> Optional[Dict[str, Any]]:
528
+ """Load role configuration from superqode.yaml."""
529
+ data = _load_yaml_data(project_root)
530
+ role_data = _get_qe_role_map(data).get(role_name)
531
+ return role_data if isinstance(role_data, dict) else None
532
+
533
+
534
+ def _build_role_config(role_name: str, data: Dict[str, Any]) -> RoleConfig:
535
+ """Build a RoleConfig from YAML data."""
536
+ role_type_raw = data.get("role_type")
537
+ if not role_type_raw:
538
+ raise ValueError(f"Role '{role_name}' missing role_type in superqode.yaml")
539
+
540
+ try:
541
+ role_type = RoleType(role_type_raw)
542
+ except ValueError as exc:
543
+ raise ValueError(f"Invalid role_type '{role_type_raw}' for role '{role_name}'") from exc
544
+
545
+ return RoleConfig(
546
+ name=role_name,
547
+ role_type=role_type,
548
+ description=data.get("description", ""),
549
+ test_pattern=data.get("test_pattern", ""),
550
+ fail_fast=data.get("fail_fast", False),
551
+ detect_flakes=data.get("detect_flakes", False),
552
+ focus_areas=data.get("focus_areas", []) or [],
553
+ max_findings=int(data.get("max_findings", 50)),
554
+ min_confidence=float(data.get("min_confidence", 0.7)),
555
+ provider=data.get("provider"),
556
+ model=data.get("model"),
557
+ timeout_seconds=int(data.get("timeout_seconds", 300)),
558
+ job_description=data.get("job_description", ""),
559
+ forbidden_actions=data.get("forbidden_actions", []) or [],
560
+ )
561
+
562
+
563
+ def get_role(
564
+ role_name: str,
565
+ project_root: Path,
566
+ allow_suggestions: bool = False,
567
+ config_overrides: Optional[Dict[str, Any]] = None,
568
+ ) -> QERole:
569
+ """Get a QE role instance from YAML configuration."""
570
+ if role_name not in ROLE_REGISTRY:
571
+ raise ValueError(f"Unknown role: {role_name}. Available: {list(ROLE_REGISTRY.keys())}")
572
+
573
+ yaml_config_data = load_role_config_from_yaml(role_name, project_root)
574
+ if not yaml_config_data:
575
+ raise ValueError(
576
+ f"Role '{role_name}' is not configured in superqode.yaml. "
577
+ "Define it under team.qe.roles."
578
+ )
579
+
580
+ config = _build_role_config(role_name, yaml_config_data)
581
+
582
+ if config_overrides:
583
+ for key, value in config_overrides.items():
584
+ if hasattr(config, key):
585
+ setattr(config, key, value)
586
+
587
+ role_class = ROLE_REGISTRY[role_name]
588
+ return role_class(config, project_root, allow_suggestions=allow_suggestions)
589
+
590
+
591
+ def list_roles(project_root: Optional[Path] = None) -> List[Dict[str, Any]]:
592
+ """List QE roles configured in superqode.yaml."""
593
+ project_root = project_root or Path.cwd()
594
+ data = _load_yaml_data(project_root)
595
+ roles_data = _get_qe_role_map(data)
596
+
597
+ roles = []
598
+ for name, cfg in roles_data.items():
599
+ if not isinstance(cfg, dict):
600
+ continue
601
+ roles.append(
602
+ {
603
+ "name": name,
604
+ "type": cfg.get("role_type", ""),
605
+ "description": cfg.get("description", ""),
606
+ "focus_areas": cfg.get("focus_areas", []),
607
+ }
608
+ )
609
+ return roles