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.
- superqode/__init__.py +33 -0
- superqode/acp/__init__.py +23 -0
- superqode/acp/client.py +913 -0
- superqode/acp/permission_screen.py +457 -0
- superqode/acp/types.py +480 -0
- superqode/acp_discovery.py +856 -0
- superqode/agent/__init__.py +22 -0
- superqode/agent/edit_strategies.py +334 -0
- superqode/agent/loop.py +892 -0
- superqode/agent/qe_report_templates.py +39 -0
- superqode/agent/system_prompts.py +353 -0
- superqode/agent_output.py +721 -0
- superqode/agent_stream.py +953 -0
- superqode/agents/__init__.py +59 -0
- superqode/agents/acp_registry.py +305 -0
- superqode/agents/client.py +249 -0
- superqode/agents/data/augmentcode.com.toml +51 -0
- superqode/agents/data/cagent.dev.toml +51 -0
- superqode/agents/data/claude.com.toml +60 -0
- superqode/agents/data/codeassistant.dev.toml +51 -0
- superqode/agents/data/codex.openai.com.toml +57 -0
- superqode/agents/data/fastagent.ai.toml +66 -0
- superqode/agents/data/geminicli.com.toml +77 -0
- superqode/agents/data/goose.block.xyz.toml +54 -0
- superqode/agents/data/junie.jetbrains.com.toml +56 -0
- superqode/agents/data/kimi.moonshot.cn.toml +57 -0
- superqode/agents/data/llmlingagent.dev.toml +51 -0
- superqode/agents/data/molt.bot.toml +49 -0
- superqode/agents/data/opencode.ai.toml +60 -0
- superqode/agents/data/stakpak.dev.toml +51 -0
- superqode/agents/data/vtcode.dev.toml +51 -0
- superqode/agents/discovery.py +266 -0
- superqode/agents/messaging.py +160 -0
- superqode/agents/persona.py +166 -0
- superqode/agents/registry.py +421 -0
- superqode/agents/schema.py +72 -0
- superqode/agents/unified.py +367 -0
- superqode/app/__init__.py +111 -0
- superqode/app/constants.py +314 -0
- superqode/app/css.py +366 -0
- superqode/app/models.py +118 -0
- superqode/app/suggester.py +125 -0
- superqode/app/widgets.py +1591 -0
- superqode/app_enhanced.py +399 -0
- superqode/app_main.py +17187 -0
- superqode/approval.py +312 -0
- superqode/atomic.py +296 -0
- superqode/commands/__init__.py +1 -0
- superqode/commands/acp.py +965 -0
- superqode/commands/agents.py +180 -0
- superqode/commands/auth.py +278 -0
- superqode/commands/config.py +374 -0
- superqode/commands/init.py +826 -0
- superqode/commands/providers.py +819 -0
- superqode/commands/qe.py +1145 -0
- superqode/commands/roles.py +380 -0
- superqode/commands/serve.py +172 -0
- superqode/commands/suggestions.py +127 -0
- superqode/commands/superqe.py +460 -0
- superqode/config/__init__.py +51 -0
- superqode/config/loader.py +812 -0
- superqode/config/schema.py +498 -0
- superqode/core/__init__.py +111 -0
- superqode/core/roles.py +281 -0
- superqode/danger.py +386 -0
- superqode/data/superqode-template.yaml +1522 -0
- superqode/design_system.py +1080 -0
- superqode/dialogs/__init__.py +6 -0
- superqode/dialogs/base.py +39 -0
- superqode/dialogs/model.py +130 -0
- superqode/dialogs/provider.py +870 -0
- superqode/diff_view.py +919 -0
- superqode/enterprise.py +21 -0
- superqode/evaluation/__init__.py +25 -0
- superqode/evaluation/adapters.py +93 -0
- superqode/evaluation/behaviors.py +89 -0
- superqode/evaluation/engine.py +209 -0
- superqode/evaluation/scenarios.py +96 -0
- superqode/execution/__init__.py +36 -0
- superqode/execution/linter.py +538 -0
- superqode/execution/modes.py +347 -0
- superqode/execution/resolver.py +283 -0
- superqode/execution/runner.py +642 -0
- superqode/file_explorer.py +811 -0
- superqode/file_viewer.py +471 -0
- superqode/flash.py +183 -0
- superqode/guidance/__init__.py +58 -0
- superqode/guidance/config.py +203 -0
- superqode/guidance/prompts.py +71 -0
- superqode/harness/__init__.py +54 -0
- superqode/harness/accelerator.py +291 -0
- superqode/harness/config.py +319 -0
- superqode/harness/validator.py +147 -0
- superqode/history.py +279 -0
- superqode/integrations/superopt_runner.py +124 -0
- superqode/logging/__init__.py +49 -0
- superqode/logging/adapters.py +219 -0
- superqode/logging/formatter.py +923 -0
- superqode/logging/integration.py +341 -0
- superqode/logging/sinks.py +170 -0
- superqode/logging/unified_log.py +417 -0
- superqode/lsp/__init__.py +26 -0
- superqode/lsp/client.py +544 -0
- superqode/main.py +1069 -0
- superqode/mcp/__init__.py +89 -0
- superqode/mcp/auth_storage.py +380 -0
- superqode/mcp/client.py +1236 -0
- superqode/mcp/config.py +319 -0
- superqode/mcp/integration.py +337 -0
- superqode/mcp/oauth.py +436 -0
- superqode/mcp/oauth_callback.py +385 -0
- superqode/mcp/types.py +290 -0
- superqode/memory/__init__.py +31 -0
- superqode/memory/feedback.py +342 -0
- superqode/memory/store.py +522 -0
- superqode/notifications.py +369 -0
- superqode/optimization/__init__.py +5 -0
- superqode/optimization/config.py +33 -0
- superqode/permissions/__init__.py +25 -0
- superqode/permissions/rules.py +488 -0
- superqode/plan.py +323 -0
- superqode/providers/__init__.py +33 -0
- superqode/providers/gateway/__init__.py +165 -0
- superqode/providers/gateway/base.py +228 -0
- superqode/providers/gateway/litellm_gateway.py +1170 -0
- superqode/providers/gateway/openresponses_gateway.py +436 -0
- superqode/providers/health.py +297 -0
- superqode/providers/huggingface/__init__.py +74 -0
- superqode/providers/huggingface/downloader.py +472 -0
- superqode/providers/huggingface/endpoints.py +442 -0
- superqode/providers/huggingface/hub.py +531 -0
- superqode/providers/huggingface/inference.py +394 -0
- superqode/providers/huggingface/transformers_runner.py +516 -0
- superqode/providers/local/__init__.py +100 -0
- superqode/providers/local/base.py +438 -0
- superqode/providers/local/discovery.py +418 -0
- superqode/providers/local/lmstudio.py +256 -0
- superqode/providers/local/mlx.py +457 -0
- superqode/providers/local/ollama.py +486 -0
- superqode/providers/local/sglang.py +268 -0
- superqode/providers/local/tgi.py +260 -0
- superqode/providers/local/tool_support.py +477 -0
- superqode/providers/local/vllm.py +258 -0
- superqode/providers/manager.py +1338 -0
- superqode/providers/models.py +1016 -0
- superqode/providers/models_dev.py +578 -0
- superqode/providers/openresponses/__init__.py +87 -0
- superqode/providers/openresponses/converters/__init__.py +17 -0
- superqode/providers/openresponses/converters/messages.py +343 -0
- superqode/providers/openresponses/converters/tools.py +268 -0
- superqode/providers/openresponses/schema/__init__.py +56 -0
- superqode/providers/openresponses/schema/models.py +585 -0
- superqode/providers/openresponses/streaming/__init__.py +5 -0
- superqode/providers/openresponses/streaming/parser.py +338 -0
- superqode/providers/openresponses/tools/__init__.py +21 -0
- superqode/providers/openresponses/tools/apply_patch.py +352 -0
- superqode/providers/openresponses/tools/code_interpreter.py +290 -0
- superqode/providers/openresponses/tools/file_search.py +333 -0
- superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
- superqode/providers/registry.py +716 -0
- superqode/providers/usage.py +332 -0
- superqode/pure_mode.py +384 -0
- superqode/qr/__init__.py +23 -0
- superqode/qr/dashboard.py +781 -0
- superqode/qr/generator.py +1018 -0
- superqode/qr/templates.py +135 -0
- superqode/safety/__init__.py +41 -0
- superqode/safety/sandbox.py +413 -0
- superqode/safety/warnings.py +256 -0
- superqode/server/__init__.py +33 -0
- superqode/server/lsp_server.py +775 -0
- superqode/server/web.py +250 -0
- superqode/session/__init__.py +25 -0
- superqode/session/persistence.py +580 -0
- superqode/session/sharing.py +477 -0
- superqode/session.py +475 -0
- superqode/sidebar.py +2991 -0
- superqode/stream_view.py +648 -0
- superqode/styles/__init__.py +3 -0
- superqode/superqe/__init__.py +184 -0
- superqode/superqe/acp_runner.py +1064 -0
- superqode/superqe/constitution/__init__.py +62 -0
- superqode/superqe/constitution/evaluator.py +308 -0
- superqode/superqe/constitution/loader.py +432 -0
- superqode/superqe/constitution/schema.py +250 -0
- superqode/superqe/events.py +591 -0
- superqode/superqe/frameworks/__init__.py +65 -0
- superqode/superqe/frameworks/base.py +234 -0
- superqode/superqe/frameworks/e2e.py +263 -0
- superqode/superqe/frameworks/executor.py +237 -0
- superqode/superqe/frameworks/javascript.py +409 -0
- superqode/superqe/frameworks/python.py +373 -0
- superqode/superqe/frameworks/registry.py +92 -0
- superqode/superqe/mcp_tools/__init__.py +47 -0
- superqode/superqe/mcp_tools/core_tools.py +418 -0
- superqode/superqe/mcp_tools/registry.py +230 -0
- superqode/superqe/mcp_tools/testing_tools.py +167 -0
- superqode/superqe/noise.py +89 -0
- superqode/superqe/orchestrator.py +778 -0
- superqode/superqe/roles.py +609 -0
- superqode/superqe/session.py +713 -0
- superqode/superqe/skills/__init__.py +57 -0
- superqode/superqe/skills/base.py +106 -0
- superqode/superqe/skills/core_skills.py +899 -0
- superqode/superqe/skills/registry.py +90 -0
- superqode/superqe/verifier.py +101 -0
- superqode/superqe_cli.py +76 -0
- superqode/tool_call.py +358 -0
- superqode/tools/__init__.py +93 -0
- superqode/tools/agent_tools.py +496 -0
- superqode/tools/base.py +324 -0
- superqode/tools/batch_tool.py +133 -0
- superqode/tools/diagnostics.py +311 -0
- superqode/tools/edit_tools.py +653 -0
- superqode/tools/enhanced_base.py +515 -0
- superqode/tools/file_tools.py +269 -0
- superqode/tools/file_tracking.py +45 -0
- superqode/tools/lsp_tools.py +610 -0
- superqode/tools/network_tools.py +350 -0
- superqode/tools/permissions.py +400 -0
- superqode/tools/question_tool.py +324 -0
- superqode/tools/search_tools.py +598 -0
- superqode/tools/shell_tools.py +259 -0
- superqode/tools/todo_tools.py +121 -0
- superqode/tools/validation.py +80 -0
- superqode/tools/web_tools.py +639 -0
- superqode/tui.py +1152 -0
- superqode/tui_integration.py +875 -0
- superqode/tui_widgets/__init__.py +27 -0
- superqode/tui_widgets/widgets/__init__.py +18 -0
- superqode/tui_widgets/widgets/progress.py +185 -0
- superqode/tui_widgets/widgets/tool_display.py +188 -0
- superqode/undo_manager.py +574 -0
- superqode/utils/__init__.py +5 -0
- superqode/utils/error_handling.py +323 -0
- superqode/utils/fuzzy.py +257 -0
- superqode/widgets/__init__.py +477 -0
- superqode/widgets/agent_collab.py +390 -0
- superqode/widgets/agent_store.py +936 -0
- superqode/widgets/agent_switcher.py +395 -0
- superqode/widgets/animation_manager.py +284 -0
- superqode/widgets/code_context.py +356 -0
- superqode/widgets/command_palette.py +412 -0
- superqode/widgets/connection_status.py +537 -0
- superqode/widgets/conversation_history.py +470 -0
- superqode/widgets/diff_indicator.py +155 -0
- superqode/widgets/enhanced_status_bar.py +385 -0
- superqode/widgets/enhanced_toast.py +476 -0
- superqode/widgets/file_browser.py +809 -0
- superqode/widgets/file_reference.py +585 -0
- superqode/widgets/issue_timeline.py +340 -0
- superqode/widgets/leader_key.py +264 -0
- superqode/widgets/mode_switcher.py +445 -0
- superqode/widgets/model_picker.py +234 -0
- superqode/widgets/permission_preview.py +1205 -0
- superqode/widgets/prompt.py +358 -0
- superqode/widgets/provider_connect.py +725 -0
- superqode/widgets/pty_shell.py +587 -0
- superqode/widgets/qe_dashboard.py +321 -0
- superqode/widgets/resizable_sidebar.py +377 -0
- superqode/widgets/response_changes.py +218 -0
- superqode/widgets/response_display.py +528 -0
- superqode/widgets/rich_tool_display.py +613 -0
- superqode/widgets/sidebar_panels.py +1180 -0
- superqode/widgets/slash_complete.py +356 -0
- superqode/widgets/split_view.py +612 -0
- superqode/widgets/status_bar.py +273 -0
- superqode/widgets/superqode_display.py +786 -0
- superqode/widgets/thinking_display.py +815 -0
- superqode/widgets/throbber.py +87 -0
- superqode/widgets/toast.py +206 -0
- superqode/widgets/unified_output.py +1073 -0
- superqode/workspace/__init__.py +75 -0
- superqode/workspace/artifacts.py +472 -0
- superqode/workspace/coordinator.py +353 -0
- superqode/workspace/diff_tracker.py +429 -0
- superqode/workspace/git_guard.py +373 -0
- superqode/workspace/git_snapshot.py +526 -0
- superqode/workspace/manager.py +750 -0
- superqode/workspace/snapshot.py +357 -0
- superqode/workspace/watcher.py +535 -0
- superqode/workspace/worktree.py +440 -0
- superqode-0.1.5.dist-info/METADATA +204 -0
- superqode-0.1.5.dist-info/RECORD +288 -0
- superqode-0.1.5.dist-info/WHEEL +5 -0
- superqode-0.1.5.dist-info/entry_points.txt +3 -0
- superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
- 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
|
+
]
|