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,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
QIR Templates for different QE modes and scenarios.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Dict, List
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class QRTemplate(Enum):
|
|
10
|
+
"""Pre-defined QIR templates for different scenarios."""
|
|
11
|
+
|
|
12
|
+
QUICK_SCAN = "quick_scan"
|
|
13
|
+
DEEP_QE = "deep_qe"
|
|
14
|
+
SECURITY_AUDIT = "security_audit"
|
|
15
|
+
PERFORMANCE_REVIEW = "performance_review"
|
|
16
|
+
REGRESSION_CHECK = "regression_check"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Template configurations
|
|
20
|
+
TEMPLATES: Dict[QRTemplate, Dict] = {
|
|
21
|
+
QRTemplate.QUICK_SCAN: {
|
|
22
|
+
"name": "Quick Scan Report",
|
|
23
|
+
"sections": ["executive_summary", "findings", "recommendations"],
|
|
24
|
+
"findings_limit": 10,
|
|
25
|
+
"include_evidence": False,
|
|
26
|
+
"include_patches": False,
|
|
27
|
+
"methodology_notes": [
|
|
28
|
+
"Time-boxed shallow analysis",
|
|
29
|
+
"Focus on high-risk paths",
|
|
30
|
+
"Static analysis and linting",
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
QRTemplate.DEEP_QE: {
|
|
34
|
+
"name": "Deep QE Investigation Report",
|
|
35
|
+
"sections": [
|
|
36
|
+
"executive_summary",
|
|
37
|
+
"scope",
|
|
38
|
+
"methodology",
|
|
39
|
+
"findings",
|
|
40
|
+
"root_cause",
|
|
41
|
+
"suggested_fixes",
|
|
42
|
+
"generated_tests",
|
|
43
|
+
"benchmarks",
|
|
44
|
+
"recommendations",
|
|
45
|
+
"appendix",
|
|
46
|
+
],
|
|
47
|
+
"findings_limit": None,
|
|
48
|
+
"include_evidence": True,
|
|
49
|
+
"include_patches": True,
|
|
50
|
+
"methodology_notes": [
|
|
51
|
+
"Full codebase exploration",
|
|
52
|
+
"Destructive testing enabled",
|
|
53
|
+
"Test generation for uncovered code",
|
|
54
|
+
"Security vulnerability scanning",
|
|
55
|
+
"Performance profiling",
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
QRTemplate.SECURITY_AUDIT: {
|
|
59
|
+
"name": "Security Audit Report",
|
|
60
|
+
"sections": [
|
|
61
|
+
"executive_summary",
|
|
62
|
+
"scope",
|
|
63
|
+
"methodology",
|
|
64
|
+
"findings",
|
|
65
|
+
"suggested_fixes",
|
|
66
|
+
"recommendations",
|
|
67
|
+
"appendix",
|
|
68
|
+
],
|
|
69
|
+
"findings_limit": None,
|
|
70
|
+
"include_evidence": True,
|
|
71
|
+
"include_patches": True,
|
|
72
|
+
"methodology_notes": [
|
|
73
|
+
"OWASP Top 10 vulnerability check",
|
|
74
|
+
"Dependency vulnerability scan",
|
|
75
|
+
"Authentication/authorization review",
|
|
76
|
+
"Input validation analysis",
|
|
77
|
+
"Secrets detection",
|
|
78
|
+
],
|
|
79
|
+
"severity_filter": ["critical", "high"], # Focus on security-relevant
|
|
80
|
+
},
|
|
81
|
+
QRTemplate.PERFORMANCE_REVIEW: {
|
|
82
|
+
"name": "Performance Review Report",
|
|
83
|
+
"sections": [
|
|
84
|
+
"executive_summary",
|
|
85
|
+
"scope",
|
|
86
|
+
"methodology",
|
|
87
|
+
"benchmarks",
|
|
88
|
+
"findings",
|
|
89
|
+
"recommendations",
|
|
90
|
+
],
|
|
91
|
+
"findings_limit": None,
|
|
92
|
+
"include_evidence": True,
|
|
93
|
+
"include_patches": False,
|
|
94
|
+
"methodology_notes": [
|
|
95
|
+
"Load testing",
|
|
96
|
+
"Memory profiling",
|
|
97
|
+
"CPU profiling",
|
|
98
|
+
"Database query analysis",
|
|
99
|
+
"Network latency measurement",
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
QRTemplate.REGRESSION_CHECK: {
|
|
103
|
+
"name": "Regression Check Report",
|
|
104
|
+
"sections": ["executive_summary", "findings", "recommendations"],
|
|
105
|
+
"findings_limit": None,
|
|
106
|
+
"include_evidence": True,
|
|
107
|
+
"include_patches": False,
|
|
108
|
+
"methodology_notes": [
|
|
109
|
+
"Existing test suite execution",
|
|
110
|
+
"Flaky test detection",
|
|
111
|
+
"Coverage comparison",
|
|
112
|
+
"Performance regression detection",
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def get_template(template: QRTemplate) -> Dict:
|
|
119
|
+
"""Get configuration for a QIR template."""
|
|
120
|
+
return TEMPLATES.get(template, TEMPLATES[QRTemplate.DEEP_QE])
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def get_template_by_mode(mode: str) -> Dict:
|
|
124
|
+
"""Get template configuration by mode name."""
|
|
125
|
+
mode_map = {
|
|
126
|
+
"quick_scan": QRTemplate.QUICK_SCAN,
|
|
127
|
+
"quick": QRTemplate.QUICK_SCAN,
|
|
128
|
+
"deep_qe": QRTemplate.DEEP_QE,
|
|
129
|
+
"deep": QRTemplate.DEEP_QE,
|
|
130
|
+
"security": QRTemplate.SECURITY_AUDIT,
|
|
131
|
+
"performance": QRTemplate.PERFORMANCE_REVIEW,
|
|
132
|
+
"regression": QRTemplate.REGRESSION_CHECK,
|
|
133
|
+
}
|
|
134
|
+
template = mode_map.get(mode.lower(), QRTemplate.DEEP_QE)
|
|
135
|
+
return TEMPLATES[template]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Safety warning system for SuperQode.
|
|
3
|
+
|
|
4
|
+
Provides warnings about destructive QE actions, token consumption,
|
|
5
|
+
and sandbox environment recommendations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .warnings import (
|
|
9
|
+
SafetyWarning,
|
|
10
|
+
WarningType,
|
|
11
|
+
WarningSeverity,
|
|
12
|
+
get_safety_warnings,
|
|
13
|
+
get_production_warnings,
|
|
14
|
+
show_safety_warnings,
|
|
15
|
+
get_warning_acknowledgment,
|
|
16
|
+
should_skip_warnings,
|
|
17
|
+
mark_warnings_acknowledged,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from .sandbox import (
|
|
21
|
+
SandboxDetector,
|
|
22
|
+
SandboxStatus,
|
|
23
|
+
detect_sandbox_environment,
|
|
24
|
+
get_sandbox_recommendations,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
# Warning system
|
|
29
|
+
"SafetyWarning",
|
|
30
|
+
"WarningType",
|
|
31
|
+
"WarningSeverity",
|
|
32
|
+
"show_safety_warnings",
|
|
33
|
+
"get_warning_acknowledgment",
|
|
34
|
+
"should_skip_warnings",
|
|
35
|
+
"mark_warnings_acknowledged",
|
|
36
|
+
# Sandbox detection
|
|
37
|
+
"SandboxDetector",
|
|
38
|
+
"SandboxStatus",
|
|
39
|
+
"detect_sandbox_environment",
|
|
40
|
+
"get_sandbox_recommendations",
|
|
41
|
+
]
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sandbox environment detection for SuperQode.
|
|
3
|
+
|
|
4
|
+
Detects whether QE sessions are being run in isolated, safe environments
|
|
5
|
+
and provides recommendations for safe execution.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
import platform
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from typing import List, Dict, Any, Optional
|
|
14
|
+
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
|
|
17
|
+
_console = Console()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SandboxStatus(Enum):
|
|
21
|
+
"""Status of sandbox environment detection."""
|
|
22
|
+
|
|
23
|
+
SAFE = "safe"
|
|
24
|
+
WARNING = "warning"
|
|
25
|
+
DANGEROUS = "dangerous"
|
|
26
|
+
UNKNOWN = "unknown"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SandboxDetector:
|
|
30
|
+
"""Detects sandbox environment characteristics."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, target_path: Optional[Path] = None):
|
|
33
|
+
self.target_path = target_path or Path.cwd()
|
|
34
|
+
self.detections = {}
|
|
35
|
+
|
|
36
|
+
def detect_all(self) -> Dict[str, Any]:
|
|
37
|
+
"""Run all sandbox detection checks."""
|
|
38
|
+
self.detections = {
|
|
39
|
+
"git_status": self._check_git_status(),
|
|
40
|
+
"container": self._check_container_environment(),
|
|
41
|
+
"virtual_env": self._check_virtual_environment(),
|
|
42
|
+
"filesystem": self._check_filesystem_safety(),
|
|
43
|
+
"system_load": self._check_system_load(),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return self.detections
|
|
47
|
+
|
|
48
|
+
def get_overall_status(self) -> SandboxStatus:
|
|
49
|
+
"""Get overall sandbox safety status."""
|
|
50
|
+
if not self.detections:
|
|
51
|
+
self.detect_all()
|
|
52
|
+
|
|
53
|
+
# Critical indicators of danger
|
|
54
|
+
if self.detections.get("git_status", {}).get("has_uncommitted_changes"):
|
|
55
|
+
return SandboxStatus.DANGEROUS
|
|
56
|
+
|
|
57
|
+
if self.detections.get("filesystem", {}).get("is_production_like"):
|
|
58
|
+
return SandboxStatus.DANGEROUS
|
|
59
|
+
|
|
60
|
+
# Warning indicators
|
|
61
|
+
if not self.detections.get("container", {}).get("is_container"):
|
|
62
|
+
return SandboxStatus.WARNING
|
|
63
|
+
|
|
64
|
+
if not self.detections.get("virtual_env", {}).get("is_venv"):
|
|
65
|
+
return SandboxStatus.WARNING
|
|
66
|
+
|
|
67
|
+
# Safe indicators
|
|
68
|
+
if self.detections.get("container", {}).get("is_container") and not self.detections.get(
|
|
69
|
+
"git_status", {}
|
|
70
|
+
).get("has_uncommitted_changes"):
|
|
71
|
+
return SandboxStatus.SAFE
|
|
72
|
+
|
|
73
|
+
return SandboxStatus.UNKNOWN
|
|
74
|
+
|
|
75
|
+
def _check_git_status(self) -> Dict[str, Any]:
|
|
76
|
+
"""Check git repository status."""
|
|
77
|
+
try:
|
|
78
|
+
# Check if we're in a git repository
|
|
79
|
+
result = subprocess.run(
|
|
80
|
+
["git", "rev-parse", "--git-dir"],
|
|
81
|
+
cwd=self.target_path,
|
|
82
|
+
capture_output=True,
|
|
83
|
+
text=True,
|
|
84
|
+
timeout=5,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if result.returncode != 0:
|
|
88
|
+
return {"is_git_repo": False, "has_uncommitted_changes": False, "is_clean": False}
|
|
89
|
+
|
|
90
|
+
# Check for uncommitted changes
|
|
91
|
+
status_result = subprocess.run(
|
|
92
|
+
["git", "status", "--porcelain"],
|
|
93
|
+
cwd=self.target_path,
|
|
94
|
+
capture_output=True,
|
|
95
|
+
text=True,
|
|
96
|
+
timeout=5,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
has_changes = bool(status_result.stdout.strip())
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
"is_git_repo": True,
|
|
103
|
+
"has_uncommitted_changes": has_changes,
|
|
104
|
+
"is_clean": not has_changes,
|
|
105
|
+
"changes_count": len(status_result.stdout.strip().split("\n"))
|
|
106
|
+
if has_changes
|
|
107
|
+
else 0,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
except (subprocess.TimeoutExpired, subprocess.SubprocessError, FileNotFoundError):
|
|
111
|
+
return {
|
|
112
|
+
"is_git_repo": False,
|
|
113
|
+
"has_uncommitted_changes": False,
|
|
114
|
+
"is_clean": False,
|
|
115
|
+
"error": "git not available",
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
def _check_container_environment(self) -> Dict[str, Any]:
|
|
119
|
+
"""Check if running in a container environment."""
|
|
120
|
+
indicators = {
|
|
121
|
+
"docker": self._check_docker_container(),
|
|
122
|
+
"podman": self._check_podman_container(),
|
|
123
|
+
"kubernetes": self._check_kubernetes_pod(),
|
|
124
|
+
"wsl": self._check_wsl_environment(),
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
is_container = any(indicators.values())
|
|
128
|
+
|
|
129
|
+
return {"is_container": is_container, "indicators": indicators}
|
|
130
|
+
|
|
131
|
+
def _check_docker_container(self) -> bool:
|
|
132
|
+
"""Check if running in Docker container."""
|
|
133
|
+
try:
|
|
134
|
+
# Check for Docker-specific files
|
|
135
|
+
if Path("/.dockerenv").exists():
|
|
136
|
+
return True
|
|
137
|
+
|
|
138
|
+
# Check cgroup for docker
|
|
139
|
+
if Path("/proc/1/cgroup").exists():
|
|
140
|
+
with open("/proc/1/cgroup", "r") as f:
|
|
141
|
+
content = f.read()
|
|
142
|
+
if "docker" in content.lower():
|
|
143
|
+
return True
|
|
144
|
+
|
|
145
|
+
return False
|
|
146
|
+
|
|
147
|
+
except (OSError, IOError):
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
def _check_podman_container(self) -> bool:
|
|
151
|
+
"""Check if running in Podman container."""
|
|
152
|
+
try:
|
|
153
|
+
if Path("/run/.containerenv").exists():
|
|
154
|
+
return True
|
|
155
|
+
|
|
156
|
+
if Path("/proc/1/cgroup").exists():
|
|
157
|
+
with open("/proc/1/cgroup", "r") as f:
|
|
158
|
+
content = f.read()
|
|
159
|
+
if "podman" in content.lower():
|
|
160
|
+
return True
|
|
161
|
+
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
except (OSError, IOError):
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
def _check_kubernetes_pod(self) -> bool:
|
|
168
|
+
"""Check if running in Kubernetes pod."""
|
|
169
|
+
try:
|
|
170
|
+
# Check for Kubernetes service account token
|
|
171
|
+
if Path("/var/run/secrets/kubernetes.io/serviceaccount/token").exists():
|
|
172
|
+
return True
|
|
173
|
+
|
|
174
|
+
# Check environment variables
|
|
175
|
+
if os.environ.get("KUBERNETES_SERVICE_HOST"):
|
|
176
|
+
return True
|
|
177
|
+
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
except OSError:
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
def _check_wsl_environment(self) -> bool:
|
|
184
|
+
"""Check if running in WSL environment."""
|
|
185
|
+
try:
|
|
186
|
+
# Check for WSL-specific files
|
|
187
|
+
if Path("/proc/version").exists():
|
|
188
|
+
with open("/proc/version", "r") as f:
|
|
189
|
+
content = f.read()
|
|
190
|
+
if "microsoft" in content.lower() or "wsl" in content.lower():
|
|
191
|
+
return True
|
|
192
|
+
|
|
193
|
+
# Check uname
|
|
194
|
+
result = subprocess.run(["uname", "-r"], capture_output=True, text=True, timeout=2)
|
|
195
|
+
|
|
196
|
+
if "microsoft" in result.stdout.lower() or "wsl" in result.stdout.lower():
|
|
197
|
+
return True
|
|
198
|
+
|
|
199
|
+
return False
|
|
200
|
+
|
|
201
|
+
except (OSError, IOError, subprocess.SubprocessError):
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
def _check_virtual_environment(self) -> Dict[str, Any]:
|
|
205
|
+
"""Check if running in a virtual environment."""
|
|
206
|
+
is_venv = os.environ.get("VIRTUAL_ENV") is not None
|
|
207
|
+
is_conda = os.environ.get("CONDA_DEFAULT_ENV") is not None
|
|
208
|
+
is_poetry = os.environ.get("POETRY_ACTIVE") is not None
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
"is_venv": is_venv,
|
|
212
|
+
"is_conda": is_conda,
|
|
213
|
+
"is_poetry": is_poetry,
|
|
214
|
+
"has_any_venv": is_venv or is_conda or is_poetry,
|
|
215
|
+
"venv_path": os.environ.get("VIRTUAL_ENV"),
|
|
216
|
+
"conda_env": os.environ.get("CONDA_DEFAULT_ENV"),
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
def _check_filesystem_safety(self) -> Dict[str, Any]:
|
|
220
|
+
"""Check filesystem safety indicators."""
|
|
221
|
+
path = self.target_path
|
|
222
|
+
|
|
223
|
+
# Check for production-like indicators
|
|
224
|
+
production_indicators = [
|
|
225
|
+
"production" in str(path).lower(),
|
|
226
|
+
"prod" in str(path).lower(),
|
|
227
|
+
"live" in str(path).lower(),
|
|
228
|
+
"main" in str(path).lower() and "master" in str(path).lower(),
|
|
229
|
+
path == Path.home(), # Running in home directory
|
|
230
|
+
Path(path / "node_modules").exists(), # Large npm project
|
|
231
|
+
Path(path / ".git").exists()
|
|
232
|
+
and any(
|
|
233
|
+
f.suffix in [".py", ".js", ".ts", ".java", ".cpp", ".c"]
|
|
234
|
+
for f in path.glob("*")
|
|
235
|
+
if f.is_file()
|
|
236
|
+
), # Looks like active development
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
is_production_like = any(production_indicators)
|
|
240
|
+
|
|
241
|
+
# Check write permissions
|
|
242
|
+
try:
|
|
243
|
+
test_file = path / ".superqode_safety_test"
|
|
244
|
+
test_file.write_text("test")
|
|
245
|
+
test_file.unlink()
|
|
246
|
+
has_write_permission = True
|
|
247
|
+
except (OSError, IOError):
|
|
248
|
+
has_write_permission = False
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
"is_production_like": is_production_like,
|
|
252
|
+
"production_indicators": production_indicators,
|
|
253
|
+
"has_write_permission": has_write_permission,
|
|
254
|
+
"target_path": str(path),
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
def _check_system_load(self) -> Dict[str, Any]:
|
|
258
|
+
"""Check system load and resources."""
|
|
259
|
+
try:
|
|
260
|
+
# Get system info
|
|
261
|
+
system = platform.system().lower()
|
|
262
|
+
|
|
263
|
+
if system == "linux":
|
|
264
|
+
# Check load average
|
|
265
|
+
with open("/proc/loadavg", "r") as f:
|
|
266
|
+
loadavg = f.read().strip().split()
|
|
267
|
+
load_1min = float(loadavg[0])
|
|
268
|
+
load_5min = float(loadavg[1])
|
|
269
|
+
load_15min = float(loadavg[2])
|
|
270
|
+
|
|
271
|
+
# Get CPU count
|
|
272
|
+
cpu_count = os.cpu_count() or 1
|
|
273
|
+
high_load = load_1min > cpu_count * 0.8
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
"system": system,
|
|
277
|
+
"load_1min": load_1min,
|
|
278
|
+
"load_5min": load_5min,
|
|
279
|
+
"load_15min": load_15min,
|
|
280
|
+
"cpu_count": cpu_count,
|
|
281
|
+
"high_load": high_load,
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
else:
|
|
285
|
+
return {"system": system, "load_info": "not available"}
|
|
286
|
+
|
|
287
|
+
except (OSError, IOError, ValueError):
|
|
288
|
+
return {"system": platform.system().lower(), "load_info": "error reading system info"}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def detect_sandbox_environment(target_path: Optional[Path] = None) -> Dict[str, Any]:
|
|
292
|
+
"""Convenience function to detect sandbox environment."""
|
|
293
|
+
detector = SandboxDetector(target_path)
|
|
294
|
+
return detector.detect_all()
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def get_sandbox_recommendations(detections: Dict[str, Any]) -> List[str]:
|
|
298
|
+
"""Get recommendations based on sandbox detection results."""
|
|
299
|
+
recommendations = []
|
|
300
|
+
|
|
301
|
+
# Git status recommendations
|
|
302
|
+
git_status = detections.get("git_status", {})
|
|
303
|
+
if git_status.get("has_uncommitted_changes"):
|
|
304
|
+
recommendations.append(
|
|
305
|
+
"⚠️ Git repository has uncommitted changes. Consider committing or stashing before QE."
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
if not git_status.get("is_git_repo"):
|
|
309
|
+
recommendations.append(
|
|
310
|
+
"💡 Consider initializing a git repository for better change tracking during QE."
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Container recommendations
|
|
314
|
+
container = detections.get("container", {})
|
|
315
|
+
if not container.get("is_container"):
|
|
316
|
+
recommendations.append("🐳 Consider running QE in a Docker container for better isolation.")
|
|
317
|
+
|
|
318
|
+
# Virtual environment recommendations
|
|
319
|
+
venv = detections.get("virtual_env", {})
|
|
320
|
+
if not venv.get("has_any_venv"):
|
|
321
|
+
recommendations.append(
|
|
322
|
+
"📦 Consider using a virtual environment (venv, conda) for dependency isolation."
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
# Filesystem recommendations
|
|
326
|
+
fs = detections.get("filesystem", {})
|
|
327
|
+
if fs.get("is_production_like"):
|
|
328
|
+
recommendations.append(
|
|
329
|
+
"🚨 Production-like environment detected. Use sandbox environments for QE testing."
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# System load recommendations
|
|
333
|
+
sys_load = detections.get("system_load", {})
|
|
334
|
+
if sys_load.get("high_load"):
|
|
335
|
+
recommendations.append("⚡ System load is high. QE sessions may impact system performance.")
|
|
336
|
+
|
|
337
|
+
# Always include general recommendations
|
|
338
|
+
if not recommendations:
|
|
339
|
+
recommendations.extend(
|
|
340
|
+
[
|
|
341
|
+
"✅ Environment looks suitable for QE testing.",
|
|
342
|
+
"💡 For maximum safety, consider using git worktrees or Docker containers.",
|
|
343
|
+
]
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
return recommendations
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def display_sandbox_status(detections: Dict[str, Any], console: Optional[Console] = None) -> None:
|
|
350
|
+
"""Display sandbox detection results."""
|
|
351
|
+
if console is None:
|
|
352
|
+
console = _console
|
|
353
|
+
|
|
354
|
+
from rich.table import Table
|
|
355
|
+
from rich.panel import Panel
|
|
356
|
+
|
|
357
|
+
# Create status table
|
|
358
|
+
table = Table(title="Sandbox Environment Detection")
|
|
359
|
+
table.add_column("Check", style="cyan", no_wrap=True)
|
|
360
|
+
table.add_column("Status", style="green")
|
|
361
|
+
table.add_column("Details", style="white")
|
|
362
|
+
|
|
363
|
+
# Git status
|
|
364
|
+
git = detections.get("git_status", {})
|
|
365
|
+
git_status = (
|
|
366
|
+
"✅ Clean"
|
|
367
|
+
if git.get("is_clean")
|
|
368
|
+
else "⚠️ Has Changes"
|
|
369
|
+
if git.get("has_uncommitted_changes")
|
|
370
|
+
else "❓ Not a Git Repo"
|
|
371
|
+
)
|
|
372
|
+
git_details = (
|
|
373
|
+
f"Repository: {git.get('is_git_repo', False)}, Changes: {git.get('changes_count', 0)}"
|
|
374
|
+
)
|
|
375
|
+
table.add_row("Git Status", git_status, git_details)
|
|
376
|
+
|
|
377
|
+
# Container
|
|
378
|
+
container = detections.get("container", {})
|
|
379
|
+
container_status = "✅ Container" if container.get("is_container") else "⚠️ Host System"
|
|
380
|
+
container_details = ", ".join([k for k, v in container.get("indicators", {}).items() if v])
|
|
381
|
+
if not container_details:
|
|
382
|
+
container_details = "Not detected"
|
|
383
|
+
table.add_row("Container", container_status, container_details)
|
|
384
|
+
|
|
385
|
+
# Virtual Environment
|
|
386
|
+
venv = detections.get("virtual_env", {})
|
|
387
|
+
venv_status = "✅ Virtual Env" if venv.get("has_any_venv") else "⚠️ System Python"
|
|
388
|
+
venv_details = []
|
|
389
|
+
if venv.get("is_venv"):
|
|
390
|
+
venv_details.append("venv")
|
|
391
|
+
if venv.get("is_conda"):
|
|
392
|
+
venv_details.append("conda")
|
|
393
|
+
if venv.get("is_poetry"):
|
|
394
|
+
venv_details.append("poetry")
|
|
395
|
+
venv_details = ", ".join(venv_details) if venv_details else "None detected"
|
|
396
|
+
table.add_row("Virtual Env", venv_status, venv_details)
|
|
397
|
+
|
|
398
|
+
# Filesystem
|
|
399
|
+
fs = detections.get("filesystem", {})
|
|
400
|
+
fs_status = "🚨 Production Risk" if fs.get("is_production_like") else "✅ Development Safe"
|
|
401
|
+
fs_details = f"Path: {fs.get('target_path', 'unknown')}"
|
|
402
|
+
table.add_row("Filesystem", fs_status, fs_details)
|
|
403
|
+
|
|
404
|
+
console.print(table)
|
|
405
|
+
|
|
406
|
+
# Show recommendations
|
|
407
|
+
recommendations = get_sandbox_recommendations(detections)
|
|
408
|
+
if recommendations:
|
|
409
|
+
rec_panel = Panel(
|
|
410
|
+
"\n".join(recommendations), title="💡 Recommendations", border_style="blue"
|
|
411
|
+
)
|
|
412
|
+
console.print()
|
|
413
|
+
console.print(rec_panel)
|