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.
Files changed (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. 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.