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,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
|