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,440 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Git Worktree Manager - Isolated testing environments using git worktrees.
|
|
3
|
+
|
|
4
|
+
Inspired by EveryCode's git_worktree.rs implementation.
|
|
5
|
+
|
|
6
|
+
Benefits over file snapshots:
|
|
7
|
+
- Git handles all the complexity
|
|
8
|
+
- Preserves build caches (target/, node_modules/, __pycache__/)
|
|
9
|
+
- Can test specific commits
|
|
10
|
+
- Multiple worktrees for parallel QE
|
|
11
|
+
- Native git integration
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
manager = GitWorktreeManager(project_root)
|
|
15
|
+
|
|
16
|
+
# Create QE worktree
|
|
17
|
+
worktree = await manager.create_qe_worktree(
|
|
18
|
+
session_id="qe-20260108",
|
|
19
|
+
base_ref="HEAD",
|
|
20
|
+
copy_uncommitted=True,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Run QE in worktree...
|
|
24
|
+
|
|
25
|
+
# Cleanup
|
|
26
|
+
await manager.remove_worktree(worktree)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import asyncio
|
|
30
|
+
import hashlib
|
|
31
|
+
import json
|
|
32
|
+
import os
|
|
33
|
+
import shutil
|
|
34
|
+
from dataclasses import dataclass, field
|
|
35
|
+
from datetime import datetime
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
from typing import Any, Dict, List, Optional
|
|
38
|
+
import logging
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class WorktreeInfo:
|
|
45
|
+
"""Information about a QE worktree."""
|
|
46
|
+
|
|
47
|
+
path: Path
|
|
48
|
+
session_id: str
|
|
49
|
+
base_ref: str
|
|
50
|
+
base_commit: str
|
|
51
|
+
created_at: datetime
|
|
52
|
+
repo_root: Path
|
|
53
|
+
|
|
54
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
55
|
+
return {
|
|
56
|
+
"path": str(self.path),
|
|
57
|
+
"session_id": self.session_id,
|
|
58
|
+
"base_ref": self.base_ref,
|
|
59
|
+
"base_commit": self.base_commit,
|
|
60
|
+
"created_at": self.created_at.isoformat(),
|
|
61
|
+
"repo_root": str(self.repo_root),
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class GitWorktreeManager:
|
|
66
|
+
"""
|
|
67
|
+
Manage git worktrees for QE sessions.
|
|
68
|
+
|
|
69
|
+
Creates isolated worktrees for QE analysis while:
|
|
70
|
+
- Preserving build caches for faster test runs
|
|
71
|
+
- Supporting multiple parallel QE sessions
|
|
72
|
+
- Enabling testing of specific commits
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
# Global worktree location
|
|
76
|
+
WORKTREE_ROOT = Path.home() / ".superqode" / "working"
|
|
77
|
+
SESSION_REGISTRY = Path.home() / ".superqode" / "working" / "_sessions"
|
|
78
|
+
|
|
79
|
+
def __init__(self, project_root: Path):
|
|
80
|
+
self.project_root = project_root.resolve()
|
|
81
|
+
self._git_root: Optional[Path] = None
|
|
82
|
+
self._repo_name: Optional[str] = None
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def git_root(self) -> Path:
|
|
86
|
+
"""Get the git repository root."""
|
|
87
|
+
if self._git_root is None:
|
|
88
|
+
self._git_root = self._find_git_root()
|
|
89
|
+
return self._git_root
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def repo_name(self) -> str:
|
|
93
|
+
"""Get a unique name for this repository."""
|
|
94
|
+
if self._repo_name is None:
|
|
95
|
+
# Use repo directory name + hash of path for uniqueness
|
|
96
|
+
name = self.git_root.name
|
|
97
|
+
path_hash = hashlib.md5(str(self.git_root).encode()).hexdigest()[:8]
|
|
98
|
+
self._repo_name = f"{name}-{path_hash}"
|
|
99
|
+
return self._repo_name
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def worktree_base(self) -> Path:
|
|
103
|
+
"""Base directory for this repo's worktrees."""
|
|
104
|
+
return self.WORKTREE_ROOT / self.repo_name / "qe"
|
|
105
|
+
|
|
106
|
+
def _find_git_root(self) -> Path:
|
|
107
|
+
"""Find the git repository root."""
|
|
108
|
+
current = self.project_root
|
|
109
|
+
while current != current.parent:
|
|
110
|
+
if (current / ".git").exists():
|
|
111
|
+
return current
|
|
112
|
+
current = current.parent
|
|
113
|
+
|
|
114
|
+
# Not a git repo - use project root
|
|
115
|
+
logger.warning(f"Not a git repository: {self.project_root}")
|
|
116
|
+
return self.project_root
|
|
117
|
+
|
|
118
|
+
async def _run_git(
|
|
119
|
+
self,
|
|
120
|
+
args: List[str],
|
|
121
|
+
cwd: Optional[Path] = None,
|
|
122
|
+
check: bool = True,
|
|
123
|
+
) -> asyncio.subprocess.Process:
|
|
124
|
+
"""Run a git command."""
|
|
125
|
+
cwd = cwd or self.git_root
|
|
126
|
+
|
|
127
|
+
process = await asyncio.create_subprocess_exec(
|
|
128
|
+
"git",
|
|
129
|
+
*args,
|
|
130
|
+
cwd=str(cwd),
|
|
131
|
+
stdout=asyncio.subprocess.PIPE,
|
|
132
|
+
stderr=asyncio.subprocess.PIPE,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
stdout, stderr = await process.communicate()
|
|
136
|
+
|
|
137
|
+
if check and process.returncode != 0:
|
|
138
|
+
error_msg = stderr.decode().strip()
|
|
139
|
+
raise RuntimeError(f"Git command failed: git {' '.join(args)}\n{error_msg}")
|
|
140
|
+
|
|
141
|
+
return process
|
|
142
|
+
|
|
143
|
+
async def _get_git_output(self, args: List[str], cwd: Optional[Path] = None) -> str:
|
|
144
|
+
"""Run git command and return stdout."""
|
|
145
|
+
cwd = cwd or self.git_root
|
|
146
|
+
|
|
147
|
+
process = await asyncio.create_subprocess_exec(
|
|
148
|
+
"git",
|
|
149
|
+
*args,
|
|
150
|
+
cwd=str(cwd),
|
|
151
|
+
stdout=asyncio.subprocess.PIPE,
|
|
152
|
+
stderr=asyncio.subprocess.PIPE,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
stdout, _ = await process.communicate()
|
|
156
|
+
return stdout.decode().strip()
|
|
157
|
+
|
|
158
|
+
async def is_git_repo(self) -> bool:
|
|
159
|
+
"""Check if project is a git repository."""
|
|
160
|
+
try:
|
|
161
|
+
await self._run_git(["rev-parse", "--git-dir"])
|
|
162
|
+
return True
|
|
163
|
+
except RuntimeError:
|
|
164
|
+
return False
|
|
165
|
+
|
|
166
|
+
async def get_current_head(self) -> str:
|
|
167
|
+
"""Get the current HEAD commit."""
|
|
168
|
+
return await self._get_git_output(["rev-parse", "HEAD"])
|
|
169
|
+
|
|
170
|
+
async def create_qe_worktree(
|
|
171
|
+
self,
|
|
172
|
+
session_id: str,
|
|
173
|
+
base_ref: str = "HEAD",
|
|
174
|
+
copy_uncommitted: bool = True,
|
|
175
|
+
keep_gitignored: bool = True,
|
|
176
|
+
) -> WorktreeInfo:
|
|
177
|
+
"""
|
|
178
|
+
Create an isolated worktree for a QE session.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
session_id: Unique session identifier
|
|
182
|
+
base_ref: Git ref to base the worktree on (commit, branch, tag)
|
|
183
|
+
copy_uncommitted: Whether to copy uncommitted changes
|
|
184
|
+
keep_gitignored: Whether to preserve gitignored files (caches)
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
WorktreeInfo with worktree details
|
|
188
|
+
"""
|
|
189
|
+
if not await self.is_git_repo():
|
|
190
|
+
raise RuntimeError("Not a git repository - cannot create worktree")
|
|
191
|
+
|
|
192
|
+
# Ensure base directory exists
|
|
193
|
+
self.worktree_base.mkdir(parents=True, exist_ok=True)
|
|
194
|
+
|
|
195
|
+
worktree_path = self.worktree_base / session_id
|
|
196
|
+
|
|
197
|
+
# Resolve the base commit
|
|
198
|
+
base_commit = await self._get_git_output(["rev-parse", base_ref])
|
|
199
|
+
|
|
200
|
+
# Check if worktree already exists
|
|
201
|
+
if worktree_path.exists():
|
|
202
|
+
logger.info(f"Reusing existing worktree: {worktree_path}")
|
|
203
|
+
# Reset to base commit
|
|
204
|
+
await self._reset_worktree(worktree_path, base_commit, keep_gitignored)
|
|
205
|
+
else:
|
|
206
|
+
# Create new detached worktree
|
|
207
|
+
await self._create_worktree(worktree_path, base_commit)
|
|
208
|
+
|
|
209
|
+
# Copy uncommitted changes if requested
|
|
210
|
+
if copy_uncommitted:
|
|
211
|
+
await self._copy_uncommitted_changes(worktree_path)
|
|
212
|
+
|
|
213
|
+
# Create worktree info
|
|
214
|
+
info = WorktreeInfo(
|
|
215
|
+
path=worktree_path,
|
|
216
|
+
session_id=session_id,
|
|
217
|
+
base_ref=base_ref,
|
|
218
|
+
base_commit=base_commit,
|
|
219
|
+
created_at=datetime.now(),
|
|
220
|
+
repo_root=self.git_root,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Register worktree
|
|
224
|
+
await self._register_worktree(info)
|
|
225
|
+
|
|
226
|
+
logger.info(f"Created QE worktree: {worktree_path} @ {base_commit[:8]}")
|
|
227
|
+
|
|
228
|
+
return info
|
|
229
|
+
|
|
230
|
+
async def _create_worktree(self, worktree_path: Path, commit: str) -> None:
|
|
231
|
+
"""Create a new detached worktree."""
|
|
232
|
+
try:
|
|
233
|
+
await self._run_git(
|
|
234
|
+
[
|
|
235
|
+
"worktree",
|
|
236
|
+
"add",
|
|
237
|
+
"--detach",
|
|
238
|
+
str(worktree_path),
|
|
239
|
+
commit,
|
|
240
|
+
]
|
|
241
|
+
)
|
|
242
|
+
except RuntimeError as e:
|
|
243
|
+
error_str = str(e)
|
|
244
|
+
|
|
245
|
+
# Handle "already registered" error
|
|
246
|
+
if "already registered" in error_str or "already used by" in error_str:
|
|
247
|
+
logger.info("Pruning stale worktrees...")
|
|
248
|
+
await self._run_git(["worktree", "prune"])
|
|
249
|
+
|
|
250
|
+
# Retry
|
|
251
|
+
await self._run_git(
|
|
252
|
+
[
|
|
253
|
+
"worktree",
|
|
254
|
+
"add",
|
|
255
|
+
"--detach",
|
|
256
|
+
str(worktree_path),
|
|
257
|
+
commit,
|
|
258
|
+
]
|
|
259
|
+
)
|
|
260
|
+
else:
|
|
261
|
+
raise
|
|
262
|
+
|
|
263
|
+
async def _reset_worktree(
|
|
264
|
+
self,
|
|
265
|
+
worktree_path: Path,
|
|
266
|
+
commit: str,
|
|
267
|
+
keep_gitignored: bool,
|
|
268
|
+
) -> None:
|
|
269
|
+
"""Reset existing worktree to a specific commit."""
|
|
270
|
+
# Hard reset to commit
|
|
271
|
+
await self._run_git(["reset", "--hard", commit], cwd=worktree_path)
|
|
272
|
+
|
|
273
|
+
# Clean tracked files
|
|
274
|
+
clean_args = ["clean", "-fd"]
|
|
275
|
+
if not keep_gitignored:
|
|
276
|
+
clean_args.append("-x") # Also remove gitignored files
|
|
277
|
+
|
|
278
|
+
await self._run_git(clean_args, cwd=worktree_path)
|
|
279
|
+
|
|
280
|
+
async def _copy_uncommitted_changes(self, worktree_path: Path) -> int:
|
|
281
|
+
"""
|
|
282
|
+
Copy uncommitted (modified + untracked) files to worktree.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
Number of files copied
|
|
286
|
+
"""
|
|
287
|
+
# List modified and untracked files
|
|
288
|
+
output = await self._get_git_output(["ls-files", "-om", "--exclude-standard", "-z"])
|
|
289
|
+
|
|
290
|
+
copied = 0
|
|
291
|
+
for file_path in output.split("\0"):
|
|
292
|
+
if not file_path or file_path.startswith(".git/"):
|
|
293
|
+
continue
|
|
294
|
+
|
|
295
|
+
src = self.git_root / file_path
|
|
296
|
+
dest = worktree_path / file_path
|
|
297
|
+
|
|
298
|
+
if not src.exists() or not src.is_file():
|
|
299
|
+
continue
|
|
300
|
+
|
|
301
|
+
# Create parent directories
|
|
302
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
303
|
+
|
|
304
|
+
# Copy file
|
|
305
|
+
shutil.copy2(src, dest)
|
|
306
|
+
copied += 1
|
|
307
|
+
|
|
308
|
+
# Also handle deletions - remove files in worktree that were deleted locally
|
|
309
|
+
deleted_output = await self._get_git_output(["ls-files", "-d", "-z"])
|
|
310
|
+
|
|
311
|
+
for file_path in deleted_output.split("\0"):
|
|
312
|
+
if not file_path or file_path.startswith(".git/"):
|
|
313
|
+
continue
|
|
314
|
+
|
|
315
|
+
target = worktree_path / file_path
|
|
316
|
+
if target.exists():
|
|
317
|
+
target.unlink()
|
|
318
|
+
copied += 1
|
|
319
|
+
|
|
320
|
+
logger.debug(f"Copied {copied} uncommitted files to worktree")
|
|
321
|
+
return copied
|
|
322
|
+
|
|
323
|
+
async def remove_worktree(self, worktree: WorktreeInfo, force: bool = False) -> None:
|
|
324
|
+
"""Remove a QE worktree."""
|
|
325
|
+
if not worktree.path.exists():
|
|
326
|
+
logger.debug(f"Worktree already removed: {worktree.path}")
|
|
327
|
+
return
|
|
328
|
+
|
|
329
|
+
args = ["worktree", "remove"]
|
|
330
|
+
if force:
|
|
331
|
+
args.append("--force")
|
|
332
|
+
args.append(str(worktree.path))
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
await self._run_git(args)
|
|
336
|
+
except RuntimeError as e:
|
|
337
|
+
if force:
|
|
338
|
+
# Force remove directory manually
|
|
339
|
+
shutil.rmtree(worktree.path, ignore_errors=True)
|
|
340
|
+
else:
|
|
341
|
+
raise
|
|
342
|
+
|
|
343
|
+
# Unregister
|
|
344
|
+
await self._unregister_worktree(worktree.session_id)
|
|
345
|
+
|
|
346
|
+
logger.info(f"Removed worktree: {worktree.path}")
|
|
347
|
+
|
|
348
|
+
async def list_worktrees(self) -> List[WorktreeInfo]:
|
|
349
|
+
"""List all QE worktrees for this repository."""
|
|
350
|
+
worktrees = []
|
|
351
|
+
|
|
352
|
+
registry_file = self.SESSION_REGISTRY / f"{self.repo_name}.json"
|
|
353
|
+
if not registry_file.exists():
|
|
354
|
+
return worktrees
|
|
355
|
+
|
|
356
|
+
try:
|
|
357
|
+
data = json.loads(registry_file.read_text())
|
|
358
|
+
for entry in data.get("worktrees", []):
|
|
359
|
+
worktrees.append(
|
|
360
|
+
WorktreeInfo(
|
|
361
|
+
path=Path(entry["path"]),
|
|
362
|
+
session_id=entry["session_id"],
|
|
363
|
+
base_ref=entry["base_ref"],
|
|
364
|
+
base_commit=entry["base_commit"],
|
|
365
|
+
created_at=datetime.fromisoformat(entry["created_at"]),
|
|
366
|
+
repo_root=Path(entry["repo_root"]),
|
|
367
|
+
)
|
|
368
|
+
)
|
|
369
|
+
except (json.JSONDecodeError, KeyError) as e:
|
|
370
|
+
logger.warning(f"Failed to read worktree registry: {e}")
|
|
371
|
+
|
|
372
|
+
return worktrees
|
|
373
|
+
|
|
374
|
+
async def cleanup_stale_worktrees(self, max_age_hours: int = 24) -> int:
|
|
375
|
+
"""Remove worktrees older than max_age_hours."""
|
|
376
|
+
removed = 0
|
|
377
|
+
now = datetime.now()
|
|
378
|
+
|
|
379
|
+
for worktree in await self.list_worktrees():
|
|
380
|
+
age = now - worktree.created_at
|
|
381
|
+
if age.total_seconds() > max_age_hours * 3600:
|
|
382
|
+
await self.remove_worktree(worktree, force=True)
|
|
383
|
+
removed += 1
|
|
384
|
+
|
|
385
|
+
return removed
|
|
386
|
+
|
|
387
|
+
async def _register_worktree(self, info: WorktreeInfo) -> None:
|
|
388
|
+
"""Register worktree in session registry."""
|
|
389
|
+
self.SESSION_REGISTRY.mkdir(parents=True, exist_ok=True)
|
|
390
|
+
registry_file = self.SESSION_REGISTRY / f"{self.repo_name}.json"
|
|
391
|
+
|
|
392
|
+
data = {"worktrees": []}
|
|
393
|
+
if registry_file.exists():
|
|
394
|
+
try:
|
|
395
|
+
data = json.loads(registry_file.read_text())
|
|
396
|
+
except json.JSONDecodeError:
|
|
397
|
+
pass
|
|
398
|
+
|
|
399
|
+
# Add or update entry
|
|
400
|
+
data["worktrees"] = [
|
|
401
|
+
w for w in data.get("worktrees", []) if w.get("session_id") != info.session_id
|
|
402
|
+
]
|
|
403
|
+
data["worktrees"].append(info.to_dict())
|
|
404
|
+
|
|
405
|
+
registry_file.write_text(json.dumps(data, indent=2))
|
|
406
|
+
|
|
407
|
+
async def _unregister_worktree(self, session_id: str) -> None:
|
|
408
|
+
"""Remove worktree from session registry."""
|
|
409
|
+
registry_file = self.SESSION_REGISTRY / f"{self.repo_name}.json"
|
|
410
|
+
|
|
411
|
+
if not registry_file.exists():
|
|
412
|
+
return
|
|
413
|
+
|
|
414
|
+
try:
|
|
415
|
+
data = json.loads(registry_file.read_text())
|
|
416
|
+
data["worktrees"] = [
|
|
417
|
+
w for w in data.get("worktrees", []) if w.get("session_id") != session_id
|
|
418
|
+
]
|
|
419
|
+
registry_file.write_text(json.dumps(data, indent=2))
|
|
420
|
+
except json.JSONDecodeError:
|
|
421
|
+
pass
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
async def prepare_qe_worktree(
|
|
425
|
+
project_root: Path,
|
|
426
|
+
session_id: str,
|
|
427
|
+
base_ref: str = "HEAD",
|
|
428
|
+
) -> WorktreeInfo:
|
|
429
|
+
"""
|
|
430
|
+
Convenience function to prepare a QE worktree.
|
|
431
|
+
|
|
432
|
+
Creates or reuses a worktree pinned to base_ref with uncommitted changes.
|
|
433
|
+
"""
|
|
434
|
+
manager = GitWorktreeManager(project_root)
|
|
435
|
+
return await manager.create_qe_worktree(
|
|
436
|
+
session_id=session_id,
|
|
437
|
+
base_ref=base_ref,
|
|
438
|
+
copy_uncommitted=True,
|
|
439
|
+
keep_gitignored=True,
|
|
440
|
+
)
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: superqode
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: SuperQode: Super Quality Engineering for Agentic Coding Teams
|
|
5
|
+
Author-email: Shashi Jagtap <info@super-agentic.ai>
|
|
6
|
+
Maintainer-email: Shashi Jagtap <info@super-agentic.ai>
|
|
7
|
+
Project-URL: Repository, https://github.com/SuperagenticAI/superqode
|
|
8
|
+
Project-URL: Documentation, https://superagenticai.github.io/superqode/
|
|
9
|
+
Project-URL: Issues, https://github.com/SuperagenticAI/superqode/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/SuperagenticAI/superqode/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: ai,coding-agents,multi-agent,orchestration,sdlc,automation,mcp,kubernetes,superqode
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
19
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: pyyaml>=6.0
|
|
24
|
+
Requires-Dist: click>=8.0.0
|
|
25
|
+
Requires-Dist: litellm>=1.80.11
|
|
26
|
+
Requires-Dist: agent-client-protocol>=0.7.0
|
|
27
|
+
Requires-Dist: prompt_toolkit>=3.0.0
|
|
28
|
+
Requires-Dist: rich>=13.0.0
|
|
29
|
+
Requires-Dist: textual>=0.47.0
|
|
30
|
+
Requires-Dist: mcp>=1.25.0
|
|
31
|
+
Requires-Dist: anyio>=4.0.0
|
|
32
|
+
Requires-Dist: httpx>=0.24.0
|
|
33
|
+
Requires-Dist: httpx-sse>=0.4.0
|
|
34
|
+
Requires-Dist: codeoptix>=0.1.0
|
|
35
|
+
Requires-Dist: superopt>=0.1.1
|
|
36
|
+
Provides-Extra: mlx
|
|
37
|
+
Requires-Dist: mlx-lm<0.30.0,>=0.28.3; python_version < "3.14" and extra == "mlx"
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
41
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
42
|
+
Requires-Dist: coverage>=7.0.0; extra == "dev"
|
|
43
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
44
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
45
|
+
Requires-Dist: pre-commit>=3.0.0; extra == "dev"
|
|
46
|
+
Provides-Extra: testing
|
|
47
|
+
Requires-Dist: pytest>=7.0.0; extra == "testing"
|
|
48
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "testing"
|
|
49
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "testing"
|
|
50
|
+
Requires-Dist: coverage>=7.0.0; extra == "testing"
|
|
51
|
+
Requires-Dist: bandit>=1.7.0; extra == "testing"
|
|
52
|
+
Requires-Dist: httpx>=0.24.0; extra == "testing"
|
|
53
|
+
Provides-Extra: linters
|
|
54
|
+
Requires-Dist: bandit>=1.7.5; extra == "linters"
|
|
55
|
+
Requires-Dist: pylint>=3.0.0; extra == "linters"
|
|
56
|
+
Requires-Dist: flake8>=6.1.0; extra == "linters"
|
|
57
|
+
Requires-Dist: safety>=2.3.0; extra == "linters"
|
|
58
|
+
Requires-Dist: pip-audit>=2.6.0; extra == "linters"
|
|
59
|
+
Provides-Extra: ui-testing
|
|
60
|
+
Requires-Dist: selenium>=4.0.0; extra == "ui-testing"
|
|
61
|
+
Requires-Dist: playwright>=1.0.0; extra == "ui-testing"
|
|
62
|
+
Provides-Extra: performance
|
|
63
|
+
Requires-Dist: locust>=2.0.0; extra == "performance"
|
|
64
|
+
Provides-Extra: docs
|
|
65
|
+
Requires-Dist: mkdocs>=1.5.0; extra == "docs"
|
|
66
|
+
Requires-Dist: mkdocs-material>=9.4.0; extra == "docs"
|
|
67
|
+
Requires-Dist: pymdown-extensions>=10.0.0; extra == "docs"
|
|
68
|
+
Requires-Dist: mkdocs-minify-plugin>=0.7.0; extra == "docs"
|
|
69
|
+
Dynamic: license-file
|
|
70
|
+
|
|
71
|
+
<p align="center">
|
|
72
|
+
<img src="https://raw.githubusercontent.com/SuperagenticAI/superqode/main/assets/super-qode-header.png" alt="SuperQode Banner">
|
|
73
|
+
</p>
|
|
74
|
+
|
|
75
|
+
<p align="center">
|
|
76
|
+
<img src="https://raw.githubusercontent.com/SuperagenticAI/superqode/main/assets/superqode-logo.png" alt="SuperQode Logo" width="200">
|
|
77
|
+
</p>
|
|
78
|
+
|
|
79
|
+
<h1 align="center">SuperQode</h1>
|
|
80
|
+
|
|
81
|
+
<p align="center">
|
|
82
|
+
<strong>Superior Quality-Oriented Agentic Software Development</strong><br>
|
|
83
|
+
<em>Orchestrate, Validate, and Deploy Agentic Software with Unshakable Confidence.</em><br>
|
|
84
|
+
<strong>Let agents break the code. Prove the fix. Ship with confidence.</strong>
|
|
85
|
+
</p>
|
|
86
|
+
|
|
87
|
+
<p align="center">
|
|
88
|
+
<a href="https://pypi.org/project/superqode/"><img src="https://img.shields.io/pypi/v/superqode?style=flat-square&color=blue" alt="PyPI"></a>
|
|
89
|
+
<a href="https://pypi.org/project/superqode/"><img src="https://img.shields.io/pypi/pyversions/superqode?style=flat-square" alt="Python"></a>
|
|
90
|
+
<a href="https://github.com/SuperagenticAI/superqode/actions"><img src="https://img.shields.io/github/actions/workflow/status/SuperagenticAI/superqode/superqe.yml?style=flat-square&label=CI" alt="CI"></a>
|
|
91
|
+
<a href="https://github.com/SuperagenticAI/superqode/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-AGPL--3.0-green?style=flat-square" alt="License"></a>
|
|
92
|
+
</p>
|
|
93
|
+
|
|
94
|
+
<p align="center">
|
|
95
|
+
<a href="https://github.com/SuperagenticAI/superqode/stargazers"><img src="https://img.shields.io/github/stars/SuperagenticAI/superqode?style=flat-square" alt="Stars"></a>
|
|
96
|
+
<a href="https://github.com/SuperagenticAI/superqode/network/members"><img src="https://img.shields.io/github/forks/SuperagenticAI/superqode?style=flat-square" alt="Forks"></a>
|
|
97
|
+
<a href="https://github.com/SuperagenticAI/superqode/issues"><img src="https://img.shields.io/github/issues/SuperagenticAI/superqode?style=flat-square" alt="Issues"></a>
|
|
98
|
+
<a href="https://github.com/SuperagenticAI/superqode/pulls"><img src="https://img.shields.io/github/issues-pr/SuperagenticAI/superqode?style=flat-square" alt="PRs"></a>
|
|
99
|
+
</p>
|
|
100
|
+
|
|
101
|
+
<p align="center">
|
|
102
|
+
<a href="https://superagenticai.github.io/superqode/">📚 Documentation</a> •
|
|
103
|
+
<a href="https://github.com/SuperagenticAI/superqode/issues">🐛 Report Bug</a> •
|
|
104
|
+
<a href="https://github.com/SuperagenticAI/superqode/discussions">💬 Discussions</a>
|
|
105
|
+
</p>
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## What is SuperQode and SuperQE?
|
|
110
|
+
|
|
111
|
+
**SuperQE** is the quality paradigm and automation CLI: Super Quality Engineering for Agentic AI. It uses QE coding agents to break and validate code written by coding agents. SuperQE can spawn a team of QE agents with different testing personas in a multi-agent setup to stress your code from many angles.
|
|
112
|
+
|
|
113
|
+
**SuperQode** is the agentic coding harness designed to drive the SuperQE process. It delivers a Superior and Quality Optimized Developer Experience as a TUI for interactive development, debugging, and exploratory QE. SuperQode can also be used as a general development harness beyond QE.
|
|
114
|
+
|
|
115
|
+
**Note (Enterprise):** Enterprise adds powerful automation, deep evaluation testing, and enterprise integrations (Moltbot first; more bot integrations coming).
|
|
116
|
+
|
|
117
|
+
## Quick Start
|
|
118
|
+
|
|
119
|
+
### Installation
|
|
120
|
+
|
|
121
|
+
**Primary (Recommended)**
|
|
122
|
+
```bash
|
|
123
|
+
# Using uv (best performance)
|
|
124
|
+
uv tool install superqode
|
|
125
|
+
|
|
126
|
+
# Or using pip
|
|
127
|
+
pip install superqode
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Alternate (No Python Required)**
|
|
131
|
+
```bash
|
|
132
|
+
# Using Homebrew (macOS/Linux)
|
|
133
|
+
brew install SuperagenticAI/superqode/superqode
|
|
134
|
+
|
|
135
|
+
# Using Curl script
|
|
136
|
+
curl -fsSL https://super-agentic.ai/install.sh | bash
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Run SuperQode
|
|
140
|
+
|
|
141
|
+
**Interactive TUI (Explore)**
|
|
142
|
+
```bash
|
|
143
|
+
cd your-project
|
|
144
|
+
superqode
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
<p align="center">
|
|
148
|
+
<img src="https://raw.githubusercontent.com/SuperagenticAI/superqode/main/assets/superqode.png" alt="SuperQode TUI">
|
|
149
|
+
</p>
|
|
150
|
+
|
|
151
|
+
**Automated QE (CI/CD)**
|
|
152
|
+
```bash
|
|
153
|
+
cd your-project
|
|
154
|
+
superqe init
|
|
155
|
+
superqe run . --mode quick
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
## Key Features
|
|
161
|
+
|
|
162
|
+
| Feature | Description |
|
|
163
|
+
|---------|-------------|
|
|
164
|
+
| 🎯 **Quality-First** | Breaks and validates code, not generates it |
|
|
165
|
+
| 🛡️ **Sandbox Execution** | Destructive testing without production risk |
|
|
166
|
+
| 🤖 **Multi-Agent QE** | Cross-validation from multiple AI perspectives |
|
|
167
|
+
| 📋 **Quality Reports** | Forensic artifacts documenting findings |
|
|
168
|
+
| 👥 **Human-in-the-Loop** | All fixes are suggestions for human review |
|
|
169
|
+
| 🏠 **Self-Hosted** | BYOK, privacy-first, no SaaS dependency |
|
|
170
|
+
|
|
171
|
+
## How It Works
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
QE SESSION LIFECYCLE
|
|
175
|
+
━━━━━━━━━━━━━━━━━━━━
|
|
176
|
+
1. SNAPSHOT → Original code preserved
|
|
177
|
+
2. QE SANDBOX → Agents modify, test, break freely
|
|
178
|
+
3. REPORT → Document findings and fixes
|
|
179
|
+
4. REVERT → All changes removed automatically
|
|
180
|
+
5. ARTIFACTS → QRs and patches preserved
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Your original code is ALWAYS restored.**
|
|
184
|
+
|
|
185
|
+
## Documentation
|
|
186
|
+
|
|
187
|
+
For complete guides, configuration options, and API reference:
|
|
188
|
+
|
|
189
|
+
**[📚 View Full Documentation →](https://superagenticai.github.io/superqode/)**
|
|
190
|
+
|
|
191
|
+
## Contributing
|
|
192
|
+
|
|
193
|
+
We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
git clone https://github.com/SuperagenticAI/superqode
|
|
197
|
+
cd superqode
|
|
198
|
+
uv pip install -e ".[dev]"
|
|
199
|
+
pytest
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## License
|
|
203
|
+
|
|
204
|
+
[AGPL-3.0](LICENSE) — Built by [Superagentic AI](https://super-agentic.ai/) for developers who care about code quality.
|