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,340 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Issue Discovery Timeline Widget.
|
|
3
|
+
|
|
4
|
+
A SuperQode-original widget showing issues discovered during QE sessions
|
|
5
|
+
in a chronological timeline format with severity indicators.
|
|
6
|
+
|
|
7
|
+
Design: Clean, scannable timeline that emphasizes issue severity
|
|
8
|
+
and provides quick access to details.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from typing import List, Optional
|
|
15
|
+
|
|
16
|
+
from rich.console import RenderableType
|
|
17
|
+
from rich.panel import Panel
|
|
18
|
+
from rich.text import Text
|
|
19
|
+
from textual.reactive import reactive
|
|
20
|
+
from textual.widgets import Static
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class IssueSeverity(Enum):
|
|
24
|
+
"""Severity level of an issue."""
|
|
25
|
+
|
|
26
|
+
CRITICAL = "critical" # Red - security, crash
|
|
27
|
+
HIGH = "high" # Orange - bugs, failures
|
|
28
|
+
MEDIUM = "medium" # Yellow - warnings
|
|
29
|
+
LOW = "low" # Green - suggestions
|
|
30
|
+
INFO = "info" # Blue - informational
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class IssueCategory(Enum):
|
|
34
|
+
"""Category of the issue."""
|
|
35
|
+
|
|
36
|
+
BUG = "bug"
|
|
37
|
+
SECURITY = "security"
|
|
38
|
+
PERFORMANCE = "performance"
|
|
39
|
+
COVERAGE = "coverage"
|
|
40
|
+
STYLE = "style"
|
|
41
|
+
COMPLEXITY = "complexity"
|
|
42
|
+
DEPENDENCY = "dependency"
|
|
43
|
+
TEST = "test"
|
|
44
|
+
OTHER = "other"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class DiscoveredIssue:
|
|
49
|
+
"""An issue discovered during QE analysis."""
|
|
50
|
+
|
|
51
|
+
id: str
|
|
52
|
+
severity: IssueSeverity
|
|
53
|
+
category: IssueCategory
|
|
54
|
+
title: str
|
|
55
|
+
file_path: str = ""
|
|
56
|
+
line_number: Optional[int] = None
|
|
57
|
+
description: str = ""
|
|
58
|
+
discovered_at: datetime = field(default_factory=datetime.now)
|
|
59
|
+
discovered_by: str = "" # Agent name
|
|
60
|
+
verified: bool = False
|
|
61
|
+
fixed: bool = False
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def location(self) -> str:
|
|
65
|
+
"""Get formatted location string."""
|
|
66
|
+
if self.line_number:
|
|
67
|
+
return f"{self.file_path}:{self.line_number}"
|
|
68
|
+
return self.file_path
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Severity styling
|
|
72
|
+
SEVERITY_STYLES = {
|
|
73
|
+
IssueSeverity.CRITICAL: {"color": "#ef4444", "icon": "🔴", "label": "CRITICAL"},
|
|
74
|
+
IssueSeverity.HIGH: {"color": "#f97316", "icon": "🟠", "label": "HIGH"},
|
|
75
|
+
IssueSeverity.MEDIUM: {"color": "#eab308", "icon": "🟡", "label": "MEDIUM"},
|
|
76
|
+
IssueSeverity.LOW: {"color": "#22c55e", "icon": "🟢", "label": "LOW"},
|
|
77
|
+
IssueSeverity.INFO: {"color": "#3b82f6", "icon": "🔵", "label": "INFO"},
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
CATEGORY_ICONS = {
|
|
81
|
+
IssueCategory.BUG: "🐛",
|
|
82
|
+
IssueCategory.SECURITY: "🔒",
|
|
83
|
+
IssueCategory.PERFORMANCE: "⚡",
|
|
84
|
+
IssueCategory.COVERAGE: "📊",
|
|
85
|
+
IssueCategory.STYLE: "🎨",
|
|
86
|
+
IssueCategory.COMPLEXITY: "🔄",
|
|
87
|
+
IssueCategory.DEPENDENCY: "📦",
|
|
88
|
+
IssueCategory.TEST: "🧪",
|
|
89
|
+
IssueCategory.OTHER: "📝",
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class IssueTimeline(Static):
|
|
94
|
+
"""Issue Discovery Timeline Widget.
|
|
95
|
+
|
|
96
|
+
Displays issues discovered during QE sessions in chronological order
|
|
97
|
+
with severity indicators and summary statistics.
|
|
98
|
+
|
|
99
|
+
Usage:
|
|
100
|
+
timeline = IssueTimeline()
|
|
101
|
+
timeline.add_issue(DiscoveredIssue(
|
|
102
|
+
id="issue-1",
|
|
103
|
+
severity=IssueSeverity.HIGH,
|
|
104
|
+
category=IssueCategory.BUG,
|
|
105
|
+
title="NullRef in UserService.get()",
|
|
106
|
+
file_path="src/api/user.py",
|
|
107
|
+
line_number=45,
|
|
108
|
+
))
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
DEFAULT_CSS = """
|
|
112
|
+
IssueTimeline {
|
|
113
|
+
height: auto;
|
|
114
|
+
border: solid #3f3f46;
|
|
115
|
+
padding: 0 1;
|
|
116
|
+
margin: 0 0 1 0;
|
|
117
|
+
max-height: 20;
|
|
118
|
+
overflow-y: auto;
|
|
119
|
+
}
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
# Reactive state
|
|
123
|
+
show_verified_only: reactive[bool] = reactive(False)
|
|
124
|
+
show_category: reactive[Optional[IssueCategory]] = reactive(None)
|
|
125
|
+
|
|
126
|
+
def __init__(
|
|
127
|
+
self,
|
|
128
|
+
title: str = "Discovery Timeline",
|
|
129
|
+
max_visible: int = 10,
|
|
130
|
+
compact: bool = False,
|
|
131
|
+
**kwargs,
|
|
132
|
+
):
|
|
133
|
+
super().__init__(**kwargs)
|
|
134
|
+
self.title = title
|
|
135
|
+
self.max_visible = max_visible
|
|
136
|
+
self.compact = compact
|
|
137
|
+
self._issues: List[DiscoveredIssue] = []
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def issues(self) -> List[DiscoveredIssue]:
|
|
141
|
+
"""Get all issues."""
|
|
142
|
+
return self._issues.copy()
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def filtered_issues(self) -> List[DiscoveredIssue]:
|
|
146
|
+
"""Get filtered issues based on current settings."""
|
|
147
|
+
result = self._issues
|
|
148
|
+
|
|
149
|
+
if self.show_verified_only:
|
|
150
|
+
result = [i for i in result if i.verified]
|
|
151
|
+
|
|
152
|
+
if self.show_category:
|
|
153
|
+
result = [i for i in result if i.category == self.show_category]
|
|
154
|
+
|
|
155
|
+
return result
|
|
156
|
+
|
|
157
|
+
def add_issue(self, issue: DiscoveredIssue) -> None:
|
|
158
|
+
"""Add an issue to the timeline."""
|
|
159
|
+
self._issues.append(issue)
|
|
160
|
+
# Sort by time (newest first)
|
|
161
|
+
self._issues.sort(key=lambda i: i.discovered_at, reverse=True)
|
|
162
|
+
self.refresh()
|
|
163
|
+
|
|
164
|
+
def mark_verified(self, issue_id: str) -> None:
|
|
165
|
+
"""Mark an issue as verified."""
|
|
166
|
+
for issue in self._issues:
|
|
167
|
+
if issue.id == issue_id:
|
|
168
|
+
issue.verified = True
|
|
169
|
+
break
|
|
170
|
+
self.refresh()
|
|
171
|
+
|
|
172
|
+
def mark_fixed(self, issue_id: str) -> None:
|
|
173
|
+
"""Mark an issue as fixed."""
|
|
174
|
+
for issue in self._issues:
|
|
175
|
+
if issue.id == issue_id:
|
|
176
|
+
issue.fixed = True
|
|
177
|
+
break
|
|
178
|
+
self.refresh()
|
|
179
|
+
|
|
180
|
+
def remove_issue(self, issue_id: str) -> None:
|
|
181
|
+
"""Remove an issue from the timeline."""
|
|
182
|
+
self._issues = [i for i in self._issues if i.id != issue_id]
|
|
183
|
+
self.refresh()
|
|
184
|
+
|
|
185
|
+
def clear(self) -> None:
|
|
186
|
+
"""Clear all issues."""
|
|
187
|
+
self._issues.clear()
|
|
188
|
+
self.refresh()
|
|
189
|
+
|
|
190
|
+
def get_summary(self) -> dict:
|
|
191
|
+
"""Get summary statistics."""
|
|
192
|
+
counts = {sev: 0 for sev in IssueSeverity}
|
|
193
|
+
for issue in self._issues:
|
|
194
|
+
counts[issue.severity] += 1
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
"total": len(self._issues),
|
|
198
|
+
"verified": sum(1 for i in self._issues if i.verified),
|
|
199
|
+
"fixed": sum(1 for i in self._issues if i.fixed),
|
|
200
|
+
"by_severity": counts,
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
def _render_issue(self, issue: DiscoveredIssue) -> Text:
|
|
204
|
+
"""Render a single issue entry."""
|
|
205
|
+
style = SEVERITY_STYLES[issue.severity]
|
|
206
|
+
cat_icon = CATEGORY_ICONS.get(issue.category, "📝")
|
|
207
|
+
|
|
208
|
+
result = Text()
|
|
209
|
+
|
|
210
|
+
# Timestamp
|
|
211
|
+
time_str = issue.discovered_at.strftime("%H:%M:%S")
|
|
212
|
+
result.append(f" {time_str} ", style="#6b7280")
|
|
213
|
+
|
|
214
|
+
# Severity indicator
|
|
215
|
+
result.append(f"{style['icon']} ", style=style["color"])
|
|
216
|
+
result.append(f"{style['label']:<8}", style=f"bold {style['color']}")
|
|
217
|
+
|
|
218
|
+
# Title (truncate if too long)
|
|
219
|
+
title = issue.title
|
|
220
|
+
if len(title) > 40 and self.compact:
|
|
221
|
+
title = title[:37] + "..."
|
|
222
|
+
result.append(f"{title}", style="#e2e8f0")
|
|
223
|
+
|
|
224
|
+
# Status badges
|
|
225
|
+
if issue.verified:
|
|
226
|
+
result.append(" ✓", style="bold #22c55e")
|
|
227
|
+
if issue.fixed:
|
|
228
|
+
result.append(" ✗", style="bold #3b82f6")
|
|
229
|
+
|
|
230
|
+
return result
|
|
231
|
+
|
|
232
|
+
def _render_summary_bar(self) -> Text:
|
|
233
|
+
"""Render the summary statistics bar."""
|
|
234
|
+
summary = self.get_summary()
|
|
235
|
+
counts = summary["by_severity"]
|
|
236
|
+
|
|
237
|
+
result = Text()
|
|
238
|
+
result.append(" Total: ", style="#6b7280")
|
|
239
|
+
result.append(f"{summary['total']}", style="bold #e2e8f0")
|
|
240
|
+
result.append(" issues", style="#6b7280")
|
|
241
|
+
|
|
242
|
+
# Severity breakdown
|
|
243
|
+
parts = []
|
|
244
|
+
for sev in [
|
|
245
|
+
IssueSeverity.CRITICAL,
|
|
246
|
+
IssueSeverity.HIGH,
|
|
247
|
+
IssueSeverity.MEDIUM,
|
|
248
|
+
IssueSeverity.LOW,
|
|
249
|
+
]:
|
|
250
|
+
if counts[sev] > 0:
|
|
251
|
+
style = SEVERITY_STYLES[sev]
|
|
252
|
+
parts.append(f"[{style['color']}]{counts[sev]} {style['label'].title()}[/]")
|
|
253
|
+
|
|
254
|
+
if parts:
|
|
255
|
+
result.append(" | ", style="#3f3f46")
|
|
256
|
+
result.append_markup(" | ".join(parts))
|
|
257
|
+
|
|
258
|
+
# Verified count
|
|
259
|
+
if summary["verified"] > 0:
|
|
260
|
+
result.append(" | ", style="#3f3f46")
|
|
261
|
+
result.append(f"{summary['verified']} verified", style="#22c55e")
|
|
262
|
+
|
|
263
|
+
return result
|
|
264
|
+
|
|
265
|
+
def render(self) -> RenderableType:
|
|
266
|
+
"""Render the timeline."""
|
|
267
|
+
content = Text()
|
|
268
|
+
|
|
269
|
+
filtered = self.filtered_issues
|
|
270
|
+
|
|
271
|
+
if not filtered:
|
|
272
|
+
content.append("\n No issues discovered yet\n", style="#6b7280")
|
|
273
|
+
else:
|
|
274
|
+
# Show issues (limited by max_visible)
|
|
275
|
+
visible = filtered[: self.max_visible]
|
|
276
|
+
|
|
277
|
+
for issue in visible:
|
|
278
|
+
content.append(self._render_issue(issue))
|
|
279
|
+
content.append("\n")
|
|
280
|
+
|
|
281
|
+
# Show "more" indicator if truncated
|
|
282
|
+
remaining = len(filtered) - len(visible)
|
|
283
|
+
if remaining > 0:
|
|
284
|
+
content.append(f"\n ... and {remaining} more issues", style="#6b7280")
|
|
285
|
+
content.append("\n")
|
|
286
|
+
|
|
287
|
+
# Divider
|
|
288
|
+
content.append("\n")
|
|
289
|
+
content.append(" " + "═" * 50, style="#3f3f46")
|
|
290
|
+
content.append("\n")
|
|
291
|
+
|
|
292
|
+
# Summary bar
|
|
293
|
+
content.append(self._render_summary_bar())
|
|
294
|
+
content.append("\n")
|
|
295
|
+
|
|
296
|
+
return Panel(
|
|
297
|
+
content,
|
|
298
|
+
title=f"[bold #f59e0b]{self.title}[/]",
|
|
299
|
+
border_style="#3f3f46",
|
|
300
|
+
padding=(0, 0),
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class CompactIssueTimeline(IssueTimeline):
|
|
305
|
+
"""Compact version of IssueTimeline for smaller spaces."""
|
|
306
|
+
|
|
307
|
+
DEFAULT_CSS = """
|
|
308
|
+
CompactIssueTimeline {
|
|
309
|
+
height: auto;
|
|
310
|
+
max-height: 8;
|
|
311
|
+
border: solid #3f3f46;
|
|
312
|
+
padding: 0 1;
|
|
313
|
+
}
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
def __init__(self, **kwargs):
|
|
317
|
+
kwargs.setdefault("max_visible", 5)
|
|
318
|
+
kwargs.setdefault("compact", True)
|
|
319
|
+
super().__init__(**kwargs)
|
|
320
|
+
|
|
321
|
+
def _render_issue(self, issue: DiscoveredIssue) -> Text:
|
|
322
|
+
"""Render a single issue in compact format."""
|
|
323
|
+
style = SEVERITY_STYLES[issue.severity]
|
|
324
|
+
|
|
325
|
+
result = Text()
|
|
326
|
+
|
|
327
|
+
# Time (short format)
|
|
328
|
+
time_str = issue.discovered_at.strftime("%H:%M")
|
|
329
|
+
result.append(f"{time_str} ", style="#6b7280")
|
|
330
|
+
|
|
331
|
+
# Severity dot
|
|
332
|
+
result.append(f"{style['icon']} ", style=style["color"])
|
|
333
|
+
|
|
334
|
+
# Title (shorter)
|
|
335
|
+
title = issue.title
|
|
336
|
+
if len(title) > 35:
|
|
337
|
+
title = title[:32] + "..."
|
|
338
|
+
result.append(title, style="#e2e8f0")
|
|
339
|
+
|
|
340
|
+
return result
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SuperQode Leader Key Widget - Ctrl+X prefix shortcuts.
|
|
3
|
+
|
|
4
|
+
Implements leader key shortcuts where pressing Ctrl+X
|
|
5
|
+
shows available actions and waits for a second key.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
Ctrl+X → shows leader key popup
|
|
9
|
+
then press:
|
|
10
|
+
H - Help
|
|
11
|
+
E - Edit (open editor)
|
|
12
|
+
C - Copy response
|
|
13
|
+
S - Select text
|
|
14
|
+
T - Theme picker
|
|
15
|
+
D - Diagnostics
|
|
16
|
+
Q - Quit
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from typing import Callable, Dict, Optional, TYPE_CHECKING
|
|
22
|
+
|
|
23
|
+
from rich.text import Text
|
|
24
|
+
|
|
25
|
+
from textual.widgets import Static
|
|
26
|
+
from textual.containers import Container
|
|
27
|
+
from textual.reactive import reactive
|
|
28
|
+
from textual.message import Message
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from textual.app import App
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ============================================================================
|
|
35
|
+
# DESIGN
|
|
36
|
+
# ============================================================================
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
from superqode.design_system import COLORS as SQ_COLORS
|
|
40
|
+
except ImportError:
|
|
41
|
+
|
|
42
|
+
class SQ_COLORS:
|
|
43
|
+
primary = "#7c3aed"
|
|
44
|
+
primary_light = "#a855f7"
|
|
45
|
+
text_primary = "#fafafa"
|
|
46
|
+
text_secondary = "#e4e4e7"
|
|
47
|
+
text_muted = "#a1a1aa"
|
|
48
|
+
text_dim = "#71717a"
|
|
49
|
+
bg_elevated = "#0a0a0a"
|
|
50
|
+
border_default = "#27272a"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ============================================================================
|
|
54
|
+
# LEADER KEY DEFINITIONS
|
|
55
|
+
# ============================================================================
|
|
56
|
+
|
|
57
|
+
LEADER_KEYS = {
|
|
58
|
+
"h": {
|
|
59
|
+
"label": "Help",
|
|
60
|
+
"description": "Show help",
|
|
61
|
+
"action": "show_help",
|
|
62
|
+
},
|
|
63
|
+
"e": {
|
|
64
|
+
"label": "Edit",
|
|
65
|
+
"description": "Open external editor",
|
|
66
|
+
"action": "open_editor",
|
|
67
|
+
},
|
|
68
|
+
"c": {
|
|
69
|
+
"label": "Copy",
|
|
70
|
+
"description": "Copy last response",
|
|
71
|
+
"action": "copy_response",
|
|
72
|
+
},
|
|
73
|
+
"s": {
|
|
74
|
+
"label": "Select",
|
|
75
|
+
"description": "Open selectable view",
|
|
76
|
+
"action": "show_select",
|
|
77
|
+
},
|
|
78
|
+
"t": {
|
|
79
|
+
"label": "Theme",
|
|
80
|
+
"description": "Change theme",
|
|
81
|
+
"action": "show_theme",
|
|
82
|
+
},
|
|
83
|
+
"d": {
|
|
84
|
+
"label": "Diagnostics",
|
|
85
|
+
"description": "Show diagnostics",
|
|
86
|
+
"action": "show_diagnostics",
|
|
87
|
+
},
|
|
88
|
+
"b": {
|
|
89
|
+
"label": "Sidebar",
|
|
90
|
+
"description": "Toggle sidebar",
|
|
91
|
+
"action": "toggle_sidebar",
|
|
92
|
+
},
|
|
93
|
+
"q": {
|
|
94
|
+
"label": "Quit",
|
|
95
|
+
"description": "Exit application",
|
|
96
|
+
"action": "quit_app",
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ============================================================================
|
|
102
|
+
# LEADER KEY WIDGET
|
|
103
|
+
# ============================================================================
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class LeaderKeyPopup(Static):
|
|
107
|
+
"""
|
|
108
|
+
Popup showing available leader key commands.
|
|
109
|
+
|
|
110
|
+
Appears when user presses Ctrl+X and waits for second key.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
DEFAULT_CSS = """
|
|
114
|
+
LeaderKeyPopup {
|
|
115
|
+
layer: overlay;
|
|
116
|
+
width: auto;
|
|
117
|
+
height: auto;
|
|
118
|
+
background: #0a0a0a;
|
|
119
|
+
border: round #7c3aed;
|
|
120
|
+
padding: 1 2;
|
|
121
|
+
display: none;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
LeaderKeyPopup.visible {
|
|
125
|
+
display: block;
|
|
126
|
+
}
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
class KeyPressed(Message):
|
|
130
|
+
"""Posted when a leader key is pressed."""
|
|
131
|
+
|
|
132
|
+
def __init__(self, key: str, action: str) -> None:
|
|
133
|
+
self.key = key
|
|
134
|
+
self.action = action
|
|
135
|
+
super().__init__()
|
|
136
|
+
|
|
137
|
+
class Cancelled(Message):
|
|
138
|
+
"""Posted when leader mode is cancelled."""
|
|
139
|
+
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
visible: reactive[bool] = reactive(False)
|
|
143
|
+
|
|
144
|
+
def __init__(self, **kwargs):
|
|
145
|
+
super().__init__("", **kwargs)
|
|
146
|
+
|
|
147
|
+
def watch_visible(self, visible: bool) -> None:
|
|
148
|
+
"""Toggle visibility."""
|
|
149
|
+
if visible:
|
|
150
|
+
self.add_class("visible")
|
|
151
|
+
else:
|
|
152
|
+
self.remove_class("visible")
|
|
153
|
+
|
|
154
|
+
def show(self) -> None:
|
|
155
|
+
"""Show the leader key popup."""
|
|
156
|
+
self.visible = True
|
|
157
|
+
self.focus()
|
|
158
|
+
|
|
159
|
+
def hide(self) -> None:
|
|
160
|
+
"""Hide the popup."""
|
|
161
|
+
self.visible = False
|
|
162
|
+
|
|
163
|
+
def render(self) -> Text:
|
|
164
|
+
"""Render the leader key options."""
|
|
165
|
+
t = Text()
|
|
166
|
+
t.append("◈ Leader: Ctrl+X + ...\n", style=f"bold {SQ_COLORS.primary}")
|
|
167
|
+
t.append("\n", style="")
|
|
168
|
+
|
|
169
|
+
for key, info in LEADER_KEYS.items():
|
|
170
|
+
t.append(f" [{key.upper()}]", style=f"bold {SQ_COLORS.primary_light}")
|
|
171
|
+
t.append(f" {info['label']:<12}", style=SQ_COLORS.text_secondary)
|
|
172
|
+
t.append(f" {info['description']}\n", style=SQ_COLORS.text_dim)
|
|
173
|
+
|
|
174
|
+
t.append("\n", style="")
|
|
175
|
+
t.append(" [Esc] Cancel", style=SQ_COLORS.text_muted)
|
|
176
|
+
|
|
177
|
+
return t
|
|
178
|
+
|
|
179
|
+
def on_key(self, event) -> None:
|
|
180
|
+
"""Handle key press in leader mode."""
|
|
181
|
+
key = event.key.lower()
|
|
182
|
+
|
|
183
|
+
if key == "escape":
|
|
184
|
+
self.hide()
|
|
185
|
+
self.post_message(self.Cancelled())
|
|
186
|
+
event.stop()
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
if key in LEADER_KEYS:
|
|
190
|
+
action = LEADER_KEYS[key]["action"]
|
|
191
|
+
self.hide()
|
|
192
|
+
self.post_message(self.KeyPressed(key, action))
|
|
193
|
+
event.stop()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# ============================================================================
|
|
197
|
+
# LEADER KEY MIXIN
|
|
198
|
+
# ============================================================================
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class LeaderKeyMixin:
|
|
202
|
+
"""
|
|
203
|
+
Mixin to add leader key support to an App.
|
|
204
|
+
|
|
205
|
+
Usage:
|
|
206
|
+
class MyApp(App, LeaderKeyMixin):
|
|
207
|
+
def __init__(self):
|
|
208
|
+
super().__init__()
|
|
209
|
+
self._init_leader_key()
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
_leader_mode: bool = False
|
|
213
|
+
_leader_popup: Optional[LeaderKeyPopup] = None
|
|
214
|
+
|
|
215
|
+
def _init_leader_key(self) -> None:
|
|
216
|
+
"""Initialize leader key support."""
|
|
217
|
+
self._leader_mode = False
|
|
218
|
+
|
|
219
|
+
def action_leader_key(self) -> None:
|
|
220
|
+
"""Activate leader key mode (Ctrl+X)."""
|
|
221
|
+
if hasattr(self, "_leader_popup") and self._leader_popup:
|
|
222
|
+
self._leader_popup.show()
|
|
223
|
+
self._leader_mode = True
|
|
224
|
+
|
|
225
|
+
def _handle_leader_action(self, action: str) -> None:
|
|
226
|
+
"""Handle a leader key action."""
|
|
227
|
+
self._leader_mode = False
|
|
228
|
+
|
|
229
|
+
# Map actions to app methods
|
|
230
|
+
action_map = {
|
|
231
|
+
"show_help": "action_show_help",
|
|
232
|
+
"open_editor": "action_open_editor",
|
|
233
|
+
"copy_response": "action_copy_response",
|
|
234
|
+
"show_select": "_show_select",
|
|
235
|
+
"show_theme": "_show_theme",
|
|
236
|
+
"show_diagnostics": "_show_diagnostics",
|
|
237
|
+
"toggle_sidebar": "action_toggle_sidebar",
|
|
238
|
+
"quit_app": "action_quit",
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
method_name = action_map.get(action)
|
|
242
|
+
if method_name and hasattr(self, method_name):
|
|
243
|
+
method = getattr(self, method_name)
|
|
244
|
+
if callable(method):
|
|
245
|
+
method()
|
|
246
|
+
|
|
247
|
+
def on_leader_key_popup_key_pressed(self, event: LeaderKeyPopup.KeyPressed) -> None:
|
|
248
|
+
"""Handle leader key selection."""
|
|
249
|
+
self._handle_leader_action(event.action)
|
|
250
|
+
|
|
251
|
+
def on_leader_key_popup_cancelled(self, event: LeaderKeyPopup.Cancelled) -> None:
|
|
252
|
+
"""Handle leader mode cancelled."""
|
|
253
|
+
self._leader_mode = False
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# ============================================================================
|
|
257
|
+
# EXPORTS
|
|
258
|
+
# ============================================================================
|
|
259
|
+
|
|
260
|
+
__all__ = [
|
|
261
|
+
"LEADER_KEYS",
|
|
262
|
+
"LeaderKeyPopup",
|
|
263
|
+
"LeaderKeyMixin",
|
|
264
|
+
]
|