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,591 @@
1
+ """
2
+ JSONL Event Streaming - CI-friendly event output for QE sessions.
3
+
4
+ Code `code exec --json` mode.
5
+
6
+ Event types:
7
+ - qe.started / qe.completed / qe.failed
8
+ - test.suite.started / test.suite.completed
9
+ - test.started / test.completed / test.failed
10
+ - finding.detected
11
+ - artifact.generated
12
+ - agent.started / agent.completed
13
+ - workspace.snapshot / workspace.reverted
14
+
15
+ Usage:
16
+ # Stream to stdout
17
+ emitter = QEEventEmitter(sys.stdout)
18
+ emitter.emit_qe_started(session_id, mode)
19
+
20
+ # Stream to file
21
+ with open("events.jsonl", "w") as f:
22
+ emitter = QEEventEmitter(f)
23
+ ...
24
+
25
+ Output format (JSONL):
26
+ {"type":"qe.started","session_id":"qe-001","mode":"quick","timestamp":"..."}
27
+ {"type":"test.completed","name":"test_auth","status":"passed","duration":0.5}
28
+ {"type":"finding.detected","id":"F001","severity":"high","title":"SQL injection"}
29
+ {"type":"qe.completed","verdict":"pass","findings_count":0,"duration":45.2}
30
+ """
31
+
32
+ import json
33
+ import sys
34
+ from dataclasses import dataclass, field, asdict
35
+ from datetime import datetime
36
+ from enum import Enum
37
+ from io import TextIOBase
38
+ from pathlib import Path
39
+ from typing import Any, Callable, Dict, List, Optional, TextIO, Union
40
+ import logging
41
+
42
+ logger = logging.getLogger(__name__)
43
+
44
+
45
+ class EventType(Enum):
46
+ """QE event types."""
47
+
48
+ # Session lifecycle
49
+ QE_STARTED = "qe.started"
50
+ QE_COMPLETED = "qe.completed"
51
+ QE_FAILED = "qe.failed"
52
+
53
+ # Turn/Phase
54
+ TURN_STARTED = "turn.started"
55
+ TURN_COMPLETED = "turn.completed"
56
+
57
+ # Tests
58
+ TEST_SUITE_STARTED = "test.suite.started"
59
+ TEST_SUITE_COMPLETED = "test.suite.completed"
60
+ TEST_STARTED = "test.started"
61
+ TEST_COMPLETED = "test.completed"
62
+ TEST_FAILED = "test.failed"
63
+ TEST_SKIPPED = "test.skipped"
64
+
65
+ # Findings
66
+ FINDING_DETECTED = "finding.detected"
67
+ FINDING_UPDATED = "finding.updated"
68
+
69
+ # Artifacts
70
+ ARTIFACT_GENERATED = "artifact.generated"
71
+ PATCH_CREATED = "patch.created"
72
+ TEST_GENERATED = "test.generated"
73
+
74
+ # Agents
75
+ AGENT_STARTED = "agent.started"
76
+ AGENT_COMPLETED = "agent.completed"
77
+ AGENT_FAILED = "agent.failed"
78
+
79
+ # Workspace
80
+ WORKSPACE_SNAPSHOT = "workspace.snapshot"
81
+ WORKSPACE_REVERTED = "workspace.reverted"
82
+ WORKSPACE_CHANGE = "workspace.change"
83
+
84
+ # Git operations
85
+ GIT_BLOCKED = "git.blocked"
86
+
87
+ # Progress
88
+ PROGRESS = "progress"
89
+ MESSAGE = "message"
90
+
91
+
92
+ @dataclass
93
+ class QEEvent:
94
+ """A QE event for JSONL streaming."""
95
+
96
+ type: str
97
+ timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
98
+ data: Dict[str, Any] = field(default_factory=dict)
99
+
100
+ def to_json(self) -> str:
101
+ """Convert to JSON string."""
102
+ output = {"type": self.type, "timestamp": self.timestamp}
103
+ output.update(self.data)
104
+ return json.dumps(output, default=str)
105
+
106
+ @classmethod
107
+ def create(cls, event_type: EventType, **kwargs) -> "QEEvent":
108
+ """Create an event with the given type and data."""
109
+ return cls(type=event_type.value, data=kwargs)
110
+
111
+
112
+ class QEEventEmitter:
113
+ """
114
+ Emits JSONL events for QE sessions.
115
+
116
+ Provides CI-friendly streaming output that can be:
117
+ - Piped to other tools
118
+ - Parsed for test reporting
119
+ - Used for real-time monitoring
120
+ """
121
+
122
+ def __init__(
123
+ self,
124
+ output: Optional[TextIO] = None,
125
+ enabled: bool = True,
126
+ min_level: str = "info",
127
+ ):
128
+ """
129
+ Initialize the event emitter.
130
+
131
+ Args:
132
+ output: Output stream (default: sys.stdout)
133
+ enabled: Whether to emit events
134
+ min_level: Minimum event level to emit ("debug", "info", "warning", "error")
135
+ """
136
+ self.output = output or sys.stdout
137
+ self.enabled = enabled
138
+ self.min_level = min_level
139
+ self._handlers: List[Callable[[QEEvent], None]] = []
140
+
141
+ def emit(self, event: QEEvent) -> None:
142
+ """Emit an event."""
143
+ if not self.enabled:
144
+ return
145
+
146
+ try:
147
+ self.output.write(event.to_json() + "\n")
148
+ self.output.flush()
149
+ except Exception as e:
150
+ logger.warning(f"Failed to emit event: {e}")
151
+
152
+ # Call registered handlers
153
+ for handler in self._handlers:
154
+ try:
155
+ handler(event)
156
+ except Exception as e:
157
+ logger.warning(f"Event handler failed: {e}")
158
+
159
+ def add_handler(self, handler: Callable[[QEEvent], None]) -> None:
160
+ """Add an event handler."""
161
+ self._handlers.append(handler)
162
+
163
+ def remove_handler(self, handler: Callable[[QEEvent], None]) -> None:
164
+ """Remove an event handler."""
165
+ if handler in self._handlers:
166
+ self._handlers.remove(handler)
167
+
168
+ # =========================================================================
169
+ # Session Lifecycle Events
170
+ # =========================================================================
171
+
172
+ def emit_qe_started(
173
+ self,
174
+ session_id: str,
175
+ mode: str,
176
+ project_root: Optional[str] = None,
177
+ roles: Optional[List[str]] = None,
178
+ ) -> None:
179
+ """Emit QE session started event."""
180
+ self.emit(
181
+ QEEvent.create(
182
+ EventType.QE_STARTED,
183
+ session_id=session_id,
184
+ mode=mode,
185
+ project_root=project_root,
186
+ roles=roles or [],
187
+ )
188
+ )
189
+
190
+ def emit_qe_completed(
191
+ self,
192
+ session_id: str,
193
+ verdict: str,
194
+ findings_count: int,
195
+ duration_seconds: float,
196
+ tests_generated: int = 0,
197
+ patches_generated: int = 0,
198
+ ) -> None:
199
+ """Emit QE session completed event."""
200
+ self.emit(
201
+ QEEvent.create(
202
+ EventType.QE_COMPLETED,
203
+ session_id=session_id,
204
+ verdict=verdict,
205
+ findings_count=findings_count,
206
+ duration_seconds=duration_seconds,
207
+ tests_generated=tests_generated,
208
+ patches_generated=patches_generated,
209
+ )
210
+ )
211
+
212
+ def emit_qe_failed(
213
+ self,
214
+ session_id: str,
215
+ error: str,
216
+ duration_seconds: float,
217
+ ) -> None:
218
+ """Emit QE session failed event."""
219
+ self.emit(
220
+ QEEvent.create(
221
+ EventType.QE_FAILED,
222
+ session_id=session_id,
223
+ error=error,
224
+ duration_seconds=duration_seconds,
225
+ )
226
+ )
227
+
228
+ # =========================================================================
229
+ # Test Events
230
+ # =========================================================================
231
+
232
+ def emit_test_suite_started(
233
+ self,
234
+ suite_name: str,
235
+ test_count: Optional[int] = None,
236
+ ) -> None:
237
+ """Emit test suite started event."""
238
+ self.emit(
239
+ QEEvent.create(
240
+ EventType.TEST_SUITE_STARTED,
241
+ suite=suite_name,
242
+ test_count=test_count,
243
+ )
244
+ )
245
+
246
+ def emit_test_suite_completed(
247
+ self,
248
+ suite_name: str,
249
+ passed: int,
250
+ failed: int,
251
+ skipped: int,
252
+ duration_seconds: float,
253
+ ) -> None:
254
+ """Emit test suite completed event."""
255
+ self.emit(
256
+ QEEvent.create(
257
+ EventType.TEST_SUITE_COMPLETED,
258
+ suite=suite_name,
259
+ passed=passed,
260
+ failed=failed,
261
+ skipped=skipped,
262
+ duration_seconds=duration_seconds,
263
+ )
264
+ )
265
+
266
+ def emit_test_completed(
267
+ self,
268
+ name: str,
269
+ status: str, # "passed", "failed", "skipped", "error"
270
+ duration_seconds: float,
271
+ message: Optional[str] = None,
272
+ ) -> None:
273
+ """Emit individual test completed event."""
274
+ event_type = {
275
+ "passed": EventType.TEST_COMPLETED,
276
+ "failed": EventType.TEST_FAILED,
277
+ "skipped": EventType.TEST_SKIPPED,
278
+ "error": EventType.TEST_FAILED,
279
+ }.get(status, EventType.TEST_COMPLETED)
280
+
281
+ self.emit(
282
+ QEEvent.create(
283
+ event_type,
284
+ name=name,
285
+ status=status,
286
+ duration_seconds=duration_seconds,
287
+ message=message,
288
+ )
289
+ )
290
+
291
+ # =========================================================================
292
+ # Finding Events
293
+ # =========================================================================
294
+
295
+ def emit_finding_detected(
296
+ self,
297
+ finding_id: str,
298
+ severity: str,
299
+ priority: int,
300
+ title: str,
301
+ location: Optional[str] = None,
302
+ confidence: float = 1.0,
303
+ category: Optional[str] = None,
304
+ found_by: Optional[str] = None,
305
+ ) -> None:
306
+ """Emit finding detected event."""
307
+ self.emit(
308
+ QEEvent.create(
309
+ EventType.FINDING_DETECTED,
310
+ id=finding_id,
311
+ severity=severity,
312
+ priority=priority,
313
+ title=title,
314
+ location=location,
315
+ confidence_score=confidence,
316
+ category=category,
317
+ found_by=found_by,
318
+ )
319
+ )
320
+
321
+ # =========================================================================
322
+ # Artifact Events
323
+ # =========================================================================
324
+
325
+ def emit_artifact_generated(
326
+ self,
327
+ artifact_type: str,
328
+ filename: str,
329
+ description: Optional[str] = None,
330
+ ) -> None:
331
+ """Emit artifact generated event."""
332
+ self.emit(
333
+ QEEvent.create(
334
+ EventType.ARTIFACT_GENERATED,
335
+ artifact_type=artifact_type,
336
+ filename=filename,
337
+ description=description,
338
+ )
339
+ )
340
+
341
+ def emit_patch_created(
342
+ self,
343
+ patch_id: str,
344
+ filename: str,
345
+ target_file: str,
346
+ lines_added: int,
347
+ lines_removed: int,
348
+ ) -> None:
349
+ """Emit patch created event."""
350
+ self.emit(
351
+ QEEvent.create(
352
+ EventType.PATCH_CREATED,
353
+ patch_id=patch_id,
354
+ filename=filename,
355
+ target_file=target_file,
356
+ lines_added=lines_added,
357
+ lines_removed=lines_removed,
358
+ )
359
+ )
360
+
361
+ def emit_test_generated(
362
+ self,
363
+ test_id: str,
364
+ filename: str,
365
+ test_type: str,
366
+ target_file: Optional[str] = None,
367
+ ) -> None:
368
+ """Emit test generated event."""
369
+ self.emit(
370
+ QEEvent.create(
371
+ EventType.TEST_GENERATED,
372
+ test_id=test_id,
373
+ filename=filename,
374
+ test_type=test_type,
375
+ target_file=target_file,
376
+ )
377
+ )
378
+
379
+ # =========================================================================
380
+ # Agent Events
381
+ # =========================================================================
382
+
383
+ def emit_agent_started(
384
+ self,
385
+ agent_id: str,
386
+ role: str,
387
+ model: Optional[str] = None,
388
+ ) -> None:
389
+ """Emit agent started event."""
390
+ self.emit(
391
+ QEEvent.create(
392
+ EventType.AGENT_STARTED,
393
+ agent_id=agent_id,
394
+ role=role,
395
+ model=model,
396
+ )
397
+ )
398
+
399
+ def emit_agent_completed(
400
+ self,
401
+ agent_id: str,
402
+ role: str,
403
+ findings_count: int,
404
+ duration_seconds: float,
405
+ ) -> None:
406
+ """Emit agent completed event."""
407
+ self.emit(
408
+ QEEvent.create(
409
+ EventType.AGENT_COMPLETED,
410
+ agent_id=agent_id,
411
+ role=role,
412
+ findings_count=findings_count,
413
+ duration_seconds=duration_seconds,
414
+ )
415
+ )
416
+
417
+ # =========================================================================
418
+ # Workspace Events
419
+ # =========================================================================
420
+
421
+ def emit_workspace_snapshot(
422
+ self,
423
+ session_id: str,
424
+ files_count: int,
425
+ snapshot_type: str = "full", # "full", "incremental"
426
+ ) -> None:
427
+ """Emit workspace snapshot event."""
428
+ self.emit(
429
+ QEEvent.create(
430
+ EventType.WORKSPACE_SNAPSHOT,
431
+ session_id=session_id,
432
+ files_count=files_count,
433
+ snapshot_type=snapshot_type,
434
+ )
435
+ )
436
+
437
+ def emit_workspace_reverted(
438
+ self,
439
+ session_id: str,
440
+ files_restored: int,
441
+ files_deleted: int,
442
+ ) -> None:
443
+ """Emit workspace reverted event."""
444
+ self.emit(
445
+ QEEvent.create(
446
+ EventType.WORKSPACE_REVERTED,
447
+ session_id=session_id,
448
+ files_restored=files_restored,
449
+ files_deleted=files_deleted,
450
+ )
451
+ )
452
+
453
+ def emit_git_blocked(
454
+ self,
455
+ command: str,
456
+ reason: str,
457
+ ) -> None:
458
+ """Emit git operation blocked event."""
459
+ self.emit(
460
+ QEEvent.create(
461
+ EventType.GIT_BLOCKED,
462
+ command=command,
463
+ reason=reason,
464
+ )
465
+ )
466
+
467
+ # =========================================================================
468
+ # Progress Events
469
+ # =========================================================================
470
+
471
+ def emit_progress(
472
+ self,
473
+ phase: str,
474
+ current: int,
475
+ total: int,
476
+ message: Optional[str] = None,
477
+ ) -> None:
478
+ """Emit progress event."""
479
+ self.emit(
480
+ QEEvent.create(
481
+ EventType.PROGRESS,
482
+ phase=phase,
483
+ current=current,
484
+ total=total,
485
+ percentage=round(current / total * 100, 1) if total > 0 else 0,
486
+ message=message,
487
+ )
488
+ )
489
+
490
+ def emit_message(
491
+ self,
492
+ level: str, # "debug", "info", "warning", "error"
493
+ message: str,
494
+ context: Optional[Dict[str, Any]] = None,
495
+ ) -> None:
496
+ """Emit a log message event."""
497
+ self.emit(
498
+ QEEvent.create(
499
+ EventType.MESSAGE,
500
+ level=level,
501
+ message=message,
502
+ context=context,
503
+ )
504
+ )
505
+
506
+
507
+ class QEEventCollector:
508
+ """
509
+ Collects events in memory for later processing.
510
+
511
+ Useful for generating summary reports after QE completion.
512
+ """
513
+
514
+ def __init__(self):
515
+ self.events: List[QEEvent] = []
516
+
517
+ def collect(self, event: QEEvent) -> None:
518
+ """Add event to collection."""
519
+ self.events.append(event)
520
+
521
+ def get_findings(self) -> List[Dict[str, Any]]:
522
+ """Get all finding events."""
523
+ return [e.data for e in self.events if e.type == EventType.FINDING_DETECTED.value]
524
+
525
+ def get_tests(self) -> List[Dict[str, Any]]:
526
+ """Get all test events."""
527
+ return [
528
+ e.data
529
+ for e in self.events
530
+ if e.type
531
+ in (
532
+ EventType.TEST_COMPLETED.value,
533
+ EventType.TEST_FAILED.value,
534
+ EventType.TEST_SKIPPED.value,
535
+ )
536
+ ]
537
+
538
+ def get_summary(self) -> Dict[str, Any]:
539
+ """Get summary statistics from collected events."""
540
+ tests = self.get_tests()
541
+ findings = self.get_findings()
542
+
543
+ return {
544
+ "total_events": len(self.events),
545
+ "tests": {
546
+ "total": len(tests),
547
+ "passed": sum(1 for t in tests if t.get("status") == "passed"),
548
+ "failed": sum(1 for t in tests if t.get("status") == "failed"),
549
+ "skipped": sum(1 for t in tests if t.get("status") == "skipped"),
550
+ },
551
+ "findings": {
552
+ "total": len(findings),
553
+ "by_severity": {
554
+ severity: sum(1 for f in findings if f.get("severity") == severity)
555
+ for severity in ["critical", "high", "medium", "low", "info"]
556
+ },
557
+ },
558
+ }
559
+
560
+ def to_jsonl(self) -> str:
561
+ """Export all events as JSONL string."""
562
+ return "\n".join(e.to_json() for e in self.events)
563
+
564
+ def save(self, path: Path) -> None:
565
+ """Save events to JSONL file."""
566
+ path.write_text(self.to_jsonl())
567
+
568
+
569
+ # =============================================================================
570
+ # Global Event Emitter
571
+ # =============================================================================
572
+
573
+ _global_emitter: Optional[QEEventEmitter] = None
574
+
575
+
576
+ def get_event_emitter() -> Optional[QEEventEmitter]:
577
+ """Get the global event emitter."""
578
+ return _global_emitter
579
+
580
+
581
+ def set_event_emitter(emitter: QEEventEmitter) -> None:
582
+ """Set the global event emitter."""
583
+ global _global_emitter
584
+ _global_emitter = emitter
585
+
586
+
587
+ def emit_event(event_type: EventType, **kwargs) -> None:
588
+ """Emit an event using the global emitter if set."""
589
+ emitter = get_event_emitter()
590
+ if emitter:
591
+ emitter.emit(QEEvent.create(event_type, **kwargs))
@@ -0,0 +1,65 @@
1
+ """
2
+ Multi-Framework Test Execution Module.
3
+
4
+ Supports running tests across multiple frameworks:
5
+ - Python: pytest, unittest, nose2
6
+ - JavaScript: Jest, Mocha, Vitest, Jasmine, AVA
7
+ - E2E: Cypress, Playwright
8
+ - Performance: k6, JMeter
9
+
10
+ Provides unified interface for:
11
+ - Test discovery
12
+ - Parallel execution (10,000+ concurrent tests)
13
+ - Result aggregation
14
+ - Framework detection
15
+ """
16
+
17
+ from .base import (
18
+ TestFramework,
19
+ FrameworkConfig,
20
+ TestResult,
21
+ TestSuite,
22
+ ExecutionResult,
23
+ )
24
+ from .registry import (
25
+ FrameworkRegistry,
26
+ get_framework,
27
+ detect_framework,
28
+ list_frameworks,
29
+ )
30
+ from .executor import (
31
+ MultiFrameworkExecutor,
32
+ ExecutorConfig,
33
+ execute_tests,
34
+ )
35
+
36
+ # Framework implementations
37
+ from .python import PytestFramework, UnittestFramework
38
+ from .javascript import JestFramework, MochaFramework, VitestFramework
39
+ from .e2e import CypressFramework, PlaywrightFramework
40
+
41
+ __all__ = [
42
+ # Base
43
+ "TestFramework",
44
+ "FrameworkConfig",
45
+ "TestResult",
46
+ "TestSuite",
47
+ "ExecutionResult",
48
+ # Registry
49
+ "FrameworkRegistry",
50
+ "get_framework",
51
+ "detect_framework",
52
+ "list_frameworks",
53
+ # Executor
54
+ "MultiFrameworkExecutor",
55
+ "ExecutorConfig",
56
+ "execute_tests",
57
+ # Implementations
58
+ "PytestFramework",
59
+ "UnittestFramework",
60
+ "JestFramework",
61
+ "MochaFramework",
62
+ "VitestFramework",
63
+ "CypressFramework",
64
+ "PlaywrightFramework",
65
+ ]