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,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Skill Registry - Central registry for QE skills.
|
|
3
|
+
|
|
4
|
+
Provides skill lookup, registration, and discovery.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, List, Optional, Type
|
|
8
|
+
|
|
9
|
+
from .base import Skill, SkillConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SkillRegistry:
|
|
13
|
+
"""Registry for QE skills."""
|
|
14
|
+
|
|
15
|
+
_skills: Dict[str, Type[Skill]] = {}
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def register(cls, skill_class: Type[Skill]) -> None:
|
|
19
|
+
"""Register a skill."""
|
|
20
|
+
cls._skills[skill_class.NAME] = skill_class
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def get(cls, name: str, config: Optional[SkillConfig] = None) -> Optional[Skill]:
|
|
24
|
+
"""Get a skill by name."""
|
|
25
|
+
skill_class = cls._skills.get(name)
|
|
26
|
+
if skill_class:
|
|
27
|
+
return skill_class(config)
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def list_all(cls) -> List[Dict[str, str]]:
|
|
32
|
+
"""List all registered skills."""
|
|
33
|
+
return [skill().get_info() for skill in cls._skills.values()]
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def get_by_category(cls, category: str) -> List[Type[Skill]]:
|
|
37
|
+
"""Get skills by category."""
|
|
38
|
+
return [skill for skill in cls._skills.values() if skill.CATEGORY == category]
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def get_categories(cls) -> List[str]:
|
|
42
|
+
"""Get all skill categories."""
|
|
43
|
+
return list(set(skill.CATEGORY for skill in cls._skills.values()))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_skill(name: str, config: Optional[SkillConfig] = None) -> Optional[Skill]:
|
|
47
|
+
"""Get a skill by name."""
|
|
48
|
+
return SkillRegistry.get(name, config)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def list_skills() -> List[Dict[str, str]]:
|
|
52
|
+
"""List all skills."""
|
|
53
|
+
return SkillRegistry.list_all()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def register_skill(skill_class: Type[Skill]) -> None:
|
|
57
|
+
"""Register a skill."""
|
|
58
|
+
SkillRegistry.register(skill_class)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_skills_by_category(category: str) -> List[Type[Skill]]:
|
|
62
|
+
"""Get skills by category."""
|
|
63
|
+
return SkillRegistry.get_by_category(category)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# Auto-register skills when module is imported
|
|
67
|
+
def _register_all_skills():
|
|
68
|
+
"""Register all built-in skills."""
|
|
69
|
+
from .core_skills import (
|
|
70
|
+
TestabilityScoring,
|
|
71
|
+
TDDLondonChicago,
|
|
72
|
+
APITestingPatterns,
|
|
73
|
+
AccessibilityTesting,
|
|
74
|
+
ShiftLeftTesting,
|
|
75
|
+
ChaosEngineeringResilience,
|
|
76
|
+
VisualTestingAdvanced,
|
|
77
|
+
ComplianceTesting,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
SkillRegistry.register(TestabilityScoring)
|
|
81
|
+
SkillRegistry.register(TDDLondonChicago)
|
|
82
|
+
SkillRegistry.register(APITestingPatterns)
|
|
83
|
+
SkillRegistry.register(AccessibilityTesting)
|
|
84
|
+
SkillRegistry.register(ShiftLeftTesting)
|
|
85
|
+
SkillRegistry.register(ChaosEngineeringResilience)
|
|
86
|
+
SkillRegistry.register(VisualTestingAdvanced)
|
|
87
|
+
SkillRegistry.register(ComplianceTesting)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
_register_all_skills()
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Minimal fix verifier for OSS (no automation)."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VerificationStatus(Enum):
|
|
10
|
+
"""Status of fix verification."""
|
|
11
|
+
|
|
12
|
+
SKIPPED = "skipped"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class TestMetrics:
|
|
17
|
+
total_tests: int = 0
|
|
18
|
+
passed: int = 0
|
|
19
|
+
failed: int = 0
|
|
20
|
+
skipped: int = 0
|
|
21
|
+
errors: int = 0
|
|
22
|
+
duration_ms: int = 0
|
|
23
|
+
coverage_percent: Optional[float] = None
|
|
24
|
+
|
|
25
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
26
|
+
return {
|
|
27
|
+
"total_tests": self.total_tests,
|
|
28
|
+
"passed": self.passed,
|
|
29
|
+
"failed": self.failed,
|
|
30
|
+
"skipped": self.skipped,
|
|
31
|
+
"errors": self.errors,
|
|
32
|
+
"duration_ms": self.duration_ms,
|
|
33
|
+
"coverage_percent": self.coverage_percent,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class VerificationResult:
|
|
39
|
+
status: VerificationStatus
|
|
40
|
+
finding_id: str
|
|
41
|
+
before_metrics: Optional[TestMetrics] = None
|
|
42
|
+
after_metrics: Optional[TestMetrics] = None
|
|
43
|
+
tests_fixed: int = 0
|
|
44
|
+
tests_broken: int = 0
|
|
45
|
+
coverage_delta: float = 0.0
|
|
46
|
+
evidence: List[str] = field(default_factory=list)
|
|
47
|
+
patch_file: Optional[str] = None
|
|
48
|
+
verification_duration_ms: int = 0
|
|
49
|
+
error_message: Optional[str] = None
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def is_improvement(self) -> bool:
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def confidence_score(self) -> float:
|
|
57
|
+
return 0.0
|
|
58
|
+
|
|
59
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
60
|
+
return {
|
|
61
|
+
"status": self.status.value,
|
|
62
|
+
"finding_id": self.finding_id,
|
|
63
|
+
"before_metrics": self.before_metrics.to_dict() if self.before_metrics else None,
|
|
64
|
+
"after_metrics": self.after_metrics.to_dict() if self.after_metrics else None,
|
|
65
|
+
"tests_fixed": self.tests_fixed,
|
|
66
|
+
"tests_broken": self.tests_broken,
|
|
67
|
+
"coverage_delta": self.coverage_delta,
|
|
68
|
+
"is_improvement": self.is_improvement,
|
|
69
|
+
"confidence_score": self.confidence_score,
|
|
70
|
+
"evidence": self.evidence,
|
|
71
|
+
"patch_file": self.patch_file,
|
|
72
|
+
"verification_duration_ms": self.verification_duration_ms,
|
|
73
|
+
"error_message": self.error_message,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class FixVerifierConfig:
|
|
79
|
+
test_command: str = "pytest"
|
|
80
|
+
test_args: List[str] = field(default_factory=list)
|
|
81
|
+
coverage_command: Optional[str] = None
|
|
82
|
+
timeout_seconds: int = 300
|
|
83
|
+
require_no_regressions: bool = True
|
|
84
|
+
min_improvement_threshold: float = 0.0
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class FixVerifier:
|
|
88
|
+
"""OSS placeholder verifier."""
|
|
89
|
+
|
|
90
|
+
def __init__(self, project_root: Path, config: Optional[FixVerifierConfig] = None):
|
|
91
|
+
self.project_root = Path(project_root)
|
|
92
|
+
self.config = config or FixVerifierConfig()
|
|
93
|
+
|
|
94
|
+
def verify_fix(
|
|
95
|
+
self,
|
|
96
|
+
finding_id: str,
|
|
97
|
+
patch_content: str,
|
|
98
|
+
target_file: Optional[Path] = None,
|
|
99
|
+
apply_patch_fn=None,
|
|
100
|
+
) -> VerificationResult:
|
|
101
|
+
return VerificationResult(status=VerificationStatus.SKIPPED, finding_id=finding_id)
|
superqode/superqe_cli.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""SuperQE CLI entrypoint.
|
|
2
|
+
|
|
3
|
+
Exposes the QE automation commands as a dedicated CLI while keeping
|
|
4
|
+
SuperQode focused on the developer TUI experience.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
from superqode import __version__
|
|
12
|
+
import shutil
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from superqode.commands.qe import qe as qe_group
|
|
16
|
+
from superqode.commands.superqe import superqe as advanced_group
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@click.group()
|
|
20
|
+
@click.version_option(version=__version__)
|
|
21
|
+
def superqe() -> None:
|
|
22
|
+
"""SuperQE - Quality Engineering automation CLI.
|
|
23
|
+
|
|
24
|
+
Use `superqode` for the interactive developer TUI.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _attach_commands(target: click.Group, source: click.Group) -> None:
|
|
29
|
+
for name, command in source.commands.items():
|
|
30
|
+
target.add_command(command, name=name)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
_attach_commands(superqe, qe_group)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@superqe.command("init")
|
|
37
|
+
@click.argument("path", type=click.Path(), default=".")
|
|
38
|
+
@click.option("--force", "-f", is_flag=True, help="Overwrite existing configuration")
|
|
39
|
+
@click.option("--guided", "-g", is_flag=True, help="Run guided setup wizard")
|
|
40
|
+
def init_command(path: str, force: bool, guided: bool) -> None:
|
|
41
|
+
"""Initialize superqode.yaml using the full template (non-interactive)."""
|
|
42
|
+
if guided:
|
|
43
|
+
from superqode.commands.init import init as guided_init
|
|
44
|
+
|
|
45
|
+
guided_init(path=path, force=force, minimal=False, guided=True)
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
project_root = Path(path).resolve()
|
|
49
|
+
config_path = project_root / "superqode.yaml"
|
|
50
|
+
|
|
51
|
+
if config_path.exists() and not force:
|
|
52
|
+
click.echo(f"Configuration already exists at {config_path}")
|
|
53
|
+
click.echo("Use --force to overwrite")
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
template_path = Path(__file__).resolve().parents[2] / "superqode-template.yaml"
|
|
57
|
+
if template_path.exists():
|
|
58
|
+
shutil.copy2(template_path, config_path)
|
|
59
|
+
click.echo(f"✓ Created {config_path} with all roles available")
|
|
60
|
+
else:
|
|
61
|
+
click.echo("Template not found; falling back to guided setup.")
|
|
62
|
+
from superqode.commands.init import init as guided_init
|
|
63
|
+
|
|
64
|
+
guided_init(path=path, force=force, minimal=False, guided=True)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
superqe.add_command(advanced_group, name="advanced")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def main() -> None:
|
|
71
|
+
"""Run the SuperQE CLI."""
|
|
72
|
+
superqe()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
main()
|
superqode/tool_call.py
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SuperQode Tool Call Display - Agent Tool Execution Visualization
|
|
3
|
+
|
|
4
|
+
Beautiful display for agent tool calls with:
|
|
5
|
+
- Expandable/collapsible content
|
|
6
|
+
- Status indicators
|
|
7
|
+
- Embedded diffs
|
|
8
|
+
- Syntax highlighting
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import List, Optional, Any, Dict
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
from rich.panel import Panel
|
|
20
|
+
from rich.text import Text
|
|
21
|
+
from rich.syntax import Syntax
|
|
22
|
+
from rich.markdown import Markdown
|
|
23
|
+
from rich.box import ROUNDED, SIMPLE
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ToolStatus(Enum):
|
|
27
|
+
"""Tool call status."""
|
|
28
|
+
|
|
29
|
+
PENDING = "pending"
|
|
30
|
+
IN_PROGRESS = "in_progress"
|
|
31
|
+
COMPLETED = "completed"
|
|
32
|
+
FAILED = "failed"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ToolKind(Enum):
|
|
36
|
+
"""Type of tool operation."""
|
|
37
|
+
|
|
38
|
+
READ = "read"
|
|
39
|
+
WRITE = "write"
|
|
40
|
+
EDIT = "edit"
|
|
41
|
+
SHELL = "shell"
|
|
42
|
+
SEARCH = "search"
|
|
43
|
+
OTHER = "other"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class ToolCallContent:
|
|
48
|
+
"""Content from a tool call."""
|
|
49
|
+
|
|
50
|
+
content_type: str # "text", "diff", "code", "markdown"
|
|
51
|
+
data: Any
|
|
52
|
+
language: Optional[str] = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class ToolCall:
|
|
57
|
+
"""A tool call from the agent."""
|
|
58
|
+
|
|
59
|
+
id: str
|
|
60
|
+
name: str
|
|
61
|
+
title: str
|
|
62
|
+
kind: ToolKind = ToolKind.OTHER
|
|
63
|
+
status: ToolStatus = ToolStatus.PENDING
|
|
64
|
+
content: List[ToolCallContent] = field(default_factory=list)
|
|
65
|
+
arguments: Dict[str, Any] = field(default_factory=dict)
|
|
66
|
+
result: Optional[str] = None
|
|
67
|
+
error: Optional[str] = None
|
|
68
|
+
started_at: Optional[datetime] = None
|
|
69
|
+
completed_at: Optional[datetime] = None
|
|
70
|
+
expanded: bool = False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# SuperQode tool call colors
|
|
74
|
+
TOOL_COLORS = {
|
|
75
|
+
# Status colors
|
|
76
|
+
"pending": "#71717a",
|
|
77
|
+
"in_progress": "#06b6d4",
|
|
78
|
+
"completed": "#22c55e",
|
|
79
|
+
"failed": "#ef4444",
|
|
80
|
+
# Kind colors
|
|
81
|
+
"read": "#3b82f6",
|
|
82
|
+
"write": "#f97316",
|
|
83
|
+
"edit": "#eab308",
|
|
84
|
+
"shell": "#8b5cf6",
|
|
85
|
+
"search": "#06b6d4",
|
|
86
|
+
"other": "#71717a",
|
|
87
|
+
# UI colors
|
|
88
|
+
"header": "#a855f7",
|
|
89
|
+
"border": "#2a2a2a",
|
|
90
|
+
"content_bg": "#111111",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# Status icons
|
|
94
|
+
STATUS_ICONS = {
|
|
95
|
+
ToolStatus.PENDING: "⏳",
|
|
96
|
+
ToolStatus.IN_PROGRESS: "🔄",
|
|
97
|
+
ToolStatus.COMPLETED: "✅",
|
|
98
|
+
ToolStatus.FAILED: "❌",
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Kind icons
|
|
102
|
+
KIND_ICONS = {
|
|
103
|
+
ToolKind.READ: "📖",
|
|
104
|
+
ToolKind.WRITE: "✏️",
|
|
105
|
+
ToolKind.EDIT: "📝",
|
|
106
|
+
ToolKind.SHELL: "💻",
|
|
107
|
+
ToolKind.SEARCH: "🔍",
|
|
108
|
+
ToolKind.OTHER: "🔧",
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class ToolCallManager:
|
|
113
|
+
"""Manages tool call display and tracking."""
|
|
114
|
+
|
|
115
|
+
def __init__(self):
|
|
116
|
+
self.calls: List[ToolCall] = []
|
|
117
|
+
self.auto_expand: str = "both" # "always", "never", "success", "fail", "both"
|
|
118
|
+
self._call_counter = 0
|
|
119
|
+
|
|
120
|
+
def _generate_id(self) -> str:
|
|
121
|
+
"""Generate a unique tool call ID."""
|
|
122
|
+
self._call_counter += 1
|
|
123
|
+
return f"tool_{self._call_counter}"
|
|
124
|
+
|
|
125
|
+
def add_call(
|
|
126
|
+
self,
|
|
127
|
+
name: str,
|
|
128
|
+
title: str,
|
|
129
|
+
kind: ToolKind = ToolKind.OTHER,
|
|
130
|
+
arguments: Optional[Dict[str, Any]] = None,
|
|
131
|
+
) -> ToolCall:
|
|
132
|
+
"""Add a new tool call."""
|
|
133
|
+
call = ToolCall(
|
|
134
|
+
id=self._generate_id(),
|
|
135
|
+
name=name,
|
|
136
|
+
title=title,
|
|
137
|
+
kind=kind,
|
|
138
|
+
arguments=arguments or {},
|
|
139
|
+
started_at=datetime.now(),
|
|
140
|
+
)
|
|
141
|
+
self.calls.append(call)
|
|
142
|
+
return call
|
|
143
|
+
|
|
144
|
+
def update_status(self, call_id: str, status: ToolStatus) -> bool:
|
|
145
|
+
"""Update a tool call's status."""
|
|
146
|
+
for call in self.calls:
|
|
147
|
+
if call.id == call_id:
|
|
148
|
+
call.status = status
|
|
149
|
+
if status in (ToolStatus.COMPLETED, ToolStatus.FAILED):
|
|
150
|
+
call.completed_at = datetime.now()
|
|
151
|
+
# Auto-expand based on settings
|
|
152
|
+
call.expanded = self._should_expand(call)
|
|
153
|
+
return True
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
def _should_expand(self, call: ToolCall) -> bool:
|
|
157
|
+
"""Determine if a call should auto-expand."""
|
|
158
|
+
if self.auto_expand == "always":
|
|
159
|
+
return True
|
|
160
|
+
if self.auto_expand == "never":
|
|
161
|
+
return False
|
|
162
|
+
if self.auto_expand == "success":
|
|
163
|
+
return call.status == ToolStatus.COMPLETED
|
|
164
|
+
if self.auto_expand == "fail":
|
|
165
|
+
return call.status == ToolStatus.FAILED
|
|
166
|
+
if self.auto_expand == "both":
|
|
167
|
+
return call.status in (ToolStatus.COMPLETED, ToolStatus.FAILED)
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
def add_content(
|
|
171
|
+
self, call_id: str, content_type: str, data: Any, language: Optional[str] = None
|
|
172
|
+
) -> bool:
|
|
173
|
+
"""Add content to a tool call."""
|
|
174
|
+
for call in self.calls:
|
|
175
|
+
if call.id == call_id:
|
|
176
|
+
call.content.append(
|
|
177
|
+
ToolCallContent(content_type=content_type, data=data, language=language)
|
|
178
|
+
)
|
|
179
|
+
return True
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
def complete(self, call_id: str, result: Optional[str] = None) -> bool:
|
|
183
|
+
"""Mark a tool call as completed."""
|
|
184
|
+
for call in self.calls:
|
|
185
|
+
if call.id == call_id:
|
|
186
|
+
call.status = ToolStatus.COMPLETED
|
|
187
|
+
call.result = result
|
|
188
|
+
call.completed_at = datetime.now()
|
|
189
|
+
call.expanded = self._should_expand(call)
|
|
190
|
+
return True
|
|
191
|
+
return False
|
|
192
|
+
|
|
193
|
+
def fail(self, call_id: str, error: str) -> bool:
|
|
194
|
+
"""Mark a tool call as failed."""
|
|
195
|
+
for call in self.calls:
|
|
196
|
+
if call.id == call_id:
|
|
197
|
+
call.status = ToolStatus.FAILED
|
|
198
|
+
call.error = error
|
|
199
|
+
call.completed_at = datetime.now()
|
|
200
|
+
call.expanded = self._should_expand(call)
|
|
201
|
+
return True
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
def toggle_expand(self, call_id: str) -> bool:
|
|
205
|
+
"""Toggle expansion state of a tool call."""
|
|
206
|
+
for call in self.calls:
|
|
207
|
+
if call.id == call_id:
|
|
208
|
+
call.expanded = not call.expanded
|
|
209
|
+
return True
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
def get_recent(self, count: int = 10) -> List[ToolCall]:
|
|
213
|
+
"""Get the most recent tool calls."""
|
|
214
|
+
return self.calls[-count:]
|
|
215
|
+
|
|
216
|
+
def clear(self) -> None:
|
|
217
|
+
"""Clear all tool calls."""
|
|
218
|
+
self.calls.clear()
|
|
219
|
+
self._call_counter = 0
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def render_tool_call(call: ToolCall, console: Console, show_content: bool = True) -> None:
|
|
223
|
+
"""Render a single tool call."""
|
|
224
|
+
status_icon = STATUS_ICONS.get(call.status, "🔧")
|
|
225
|
+
kind_icon = KIND_ICONS.get(call.kind, "🔧")
|
|
226
|
+
status_color = TOOL_COLORS.get(call.status.value, TOOL_COLORS["pending"])
|
|
227
|
+
kind_color = TOOL_COLORS.get(call.kind.value, TOOL_COLORS["other"])
|
|
228
|
+
|
|
229
|
+
# Header line
|
|
230
|
+
header = Text()
|
|
231
|
+
|
|
232
|
+
# Expand indicator
|
|
233
|
+
if call.content:
|
|
234
|
+
expand_icon = "▼" if call.expanded else "▶"
|
|
235
|
+
header.append(f"{expand_icon} ", style="dim")
|
|
236
|
+
else:
|
|
237
|
+
header.append(" ", style="")
|
|
238
|
+
|
|
239
|
+
# Kind icon and title
|
|
240
|
+
header.append(f"{kind_icon} ", style=kind_color)
|
|
241
|
+
header.append(call.title, style=f"bold {status_color}")
|
|
242
|
+
|
|
243
|
+
# Status indicator
|
|
244
|
+
header.append(" ", style="")
|
|
245
|
+
if call.status == ToolStatus.PENDING:
|
|
246
|
+
header.append("⏳", style=status_color)
|
|
247
|
+
elif call.status == ToolStatus.IN_PROGRESS:
|
|
248
|
+
header.append("🔄", style=status_color)
|
|
249
|
+
elif call.status == ToolStatus.COMPLETED:
|
|
250
|
+
header.append("✔", style=status_color)
|
|
251
|
+
elif call.status == ToolStatus.FAILED:
|
|
252
|
+
header.append("✗", style=status_color)
|
|
253
|
+
|
|
254
|
+
# Duration
|
|
255
|
+
if call.completed_at and call.started_at:
|
|
256
|
+
duration = (call.completed_at - call.started_at).total_seconds()
|
|
257
|
+
header.append(f" ({duration:.2f}s)", style="dim")
|
|
258
|
+
|
|
259
|
+
console.print(header)
|
|
260
|
+
|
|
261
|
+
# Content (if expanded)
|
|
262
|
+
if show_content and call.expanded and call.content:
|
|
263
|
+
render_tool_content(call, console)
|
|
264
|
+
|
|
265
|
+
# Error message
|
|
266
|
+
if call.error:
|
|
267
|
+
console.print(f" [red]Error: {call.error}[/red]")
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def render_tool_content(call: ToolCall, console: Console) -> None:
|
|
271
|
+
"""Render the content of a tool call."""
|
|
272
|
+
for content in call.content:
|
|
273
|
+
if content.content_type == "text":
|
|
274
|
+
# Plain text
|
|
275
|
+
text = str(content.data)
|
|
276
|
+
if len(text) > 500:
|
|
277
|
+
text = text[:500] + "..."
|
|
278
|
+
console.print(f" [dim]{text}[/dim]")
|
|
279
|
+
|
|
280
|
+
elif content.content_type == "code":
|
|
281
|
+
# Syntax highlighted code
|
|
282
|
+
lang = content.language or "text"
|
|
283
|
+
syntax = Syntax(
|
|
284
|
+
str(content.data),
|
|
285
|
+
lang,
|
|
286
|
+
theme="monokai",
|
|
287
|
+
line_numbers=True,
|
|
288
|
+
word_wrap=True,
|
|
289
|
+
background_color="#000000",
|
|
290
|
+
)
|
|
291
|
+
console.print(
|
|
292
|
+
Panel(syntax, border_style=TOOL_COLORS["border"], box=SIMPLE, padding=(0, 1))
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
elif content.content_type == "diff":
|
|
296
|
+
# Diff display
|
|
297
|
+
from superqode.diff_view import compute_diff, render_diff_unified
|
|
298
|
+
|
|
299
|
+
if isinstance(content.data, dict):
|
|
300
|
+
diff = compute_diff(
|
|
301
|
+
content.data.get("old", ""),
|
|
302
|
+
content.data.get("new", ""),
|
|
303
|
+
content.data.get("path", "file"),
|
|
304
|
+
)
|
|
305
|
+
render_diff_unified(diff, console)
|
|
306
|
+
|
|
307
|
+
elif content.content_type == "markdown":
|
|
308
|
+
# Markdown content
|
|
309
|
+
md = Markdown(str(content.data))
|
|
310
|
+
console.print(Panel(md, border_style=TOOL_COLORS["border"], box=SIMPLE, padding=(0, 1)))
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def render_tool_calls(manager: ToolCallManager, console: Console, limit: int = 10) -> None:
|
|
314
|
+
"""Render recent tool calls."""
|
|
315
|
+
calls = manager.get_recent(limit)
|
|
316
|
+
|
|
317
|
+
if not calls:
|
|
318
|
+
console.print(" [dim]No tool calls yet[/dim]")
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
# Header
|
|
322
|
+
header = Text()
|
|
323
|
+
header.append(" 🔧 ", style="bold")
|
|
324
|
+
header.append("Tool Calls", style="bold white")
|
|
325
|
+
header.append(f" ({len(calls)})", style="dim")
|
|
326
|
+
|
|
327
|
+
console.print(Panel(header, border_style=TOOL_COLORS["header"], box=ROUNDED, padding=(0, 1)))
|
|
328
|
+
|
|
329
|
+
# Render each call
|
|
330
|
+
for call in calls:
|
|
331
|
+
render_tool_call(call, console)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def render_tool_summary(manager: ToolCallManager, console: Console) -> None:
|
|
335
|
+
"""Render a compact summary of tool calls."""
|
|
336
|
+
if not manager.calls:
|
|
337
|
+
return
|
|
338
|
+
|
|
339
|
+
completed = sum(1 for c in manager.calls if c.status == ToolStatus.COMPLETED)
|
|
340
|
+
failed = sum(1 for c in manager.calls if c.status == ToolStatus.FAILED)
|
|
341
|
+
pending = sum(
|
|
342
|
+
1 for c in manager.calls if c.status in (ToolStatus.PENDING, ToolStatus.IN_PROGRESS)
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
line = Text()
|
|
346
|
+
line.append("🔧 ", style="")
|
|
347
|
+
line.append(f"{completed}", style=f"bold {TOOL_COLORS['completed']}")
|
|
348
|
+
line.append("✔ ", style=TOOL_COLORS["completed"])
|
|
349
|
+
|
|
350
|
+
if failed:
|
|
351
|
+
line.append(f"{failed}", style=f"bold {TOOL_COLORS['failed']}")
|
|
352
|
+
line.append("✗ ", style=TOOL_COLORS["failed"])
|
|
353
|
+
|
|
354
|
+
if pending:
|
|
355
|
+
line.append(f"{pending}", style=f"bold {TOOL_COLORS['pending']}")
|
|
356
|
+
line.append("⏳", style=TOOL_COLORS["pending"])
|
|
357
|
+
|
|
358
|
+
console.print(line)
|