codeframe-ai 0.9.0__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 (197) hide show
  1. codeframe/__init__.py +11 -0
  2. codeframe/__main__.py +20 -0
  3. codeframe/adapters/__init__.py +5 -0
  4. codeframe/adapters/e2b/__init__.py +13 -0
  5. codeframe/adapters/e2b/adapter.py +342 -0
  6. codeframe/adapters/e2b/budget.py +71 -0
  7. codeframe/adapters/e2b/credential_scanner.py +134 -0
  8. codeframe/adapters/llm/__init__.py +92 -0
  9. codeframe/adapters/llm/anthropic.py +414 -0
  10. codeframe/adapters/llm/base.py +444 -0
  11. codeframe/adapters/llm/mock.py +281 -0
  12. codeframe/adapters/llm/openai.py +483 -0
  13. codeframe/agents/__init__.py +8 -0
  14. codeframe/agents/dependency_resolver.py +714 -0
  15. codeframe/auth/__init__.py +16 -0
  16. codeframe/auth/api_key_router.py +238 -0
  17. codeframe/auth/api_keys.py +156 -0
  18. codeframe/auth/dependencies.py +358 -0
  19. codeframe/auth/manager.py +178 -0
  20. codeframe/auth/models.py +30 -0
  21. codeframe/auth/router.py +93 -0
  22. codeframe/auth/schemas.py +15 -0
  23. codeframe/auth/scopes.py +53 -0
  24. codeframe/cli/__init__.py +12 -0
  25. codeframe/cli/__main__.py +20 -0
  26. codeframe/cli/api_client.py +275 -0
  27. codeframe/cli/app.py +5688 -0
  28. codeframe/cli/auth.py +122 -0
  29. codeframe/cli/auth_commands.py +958 -0
  30. codeframe/cli/commands/__init__.py +5 -0
  31. codeframe/cli/config_commands.py +79 -0
  32. codeframe/cli/dashboard_commands.py +67 -0
  33. codeframe/cli/engines_commands.py +205 -0
  34. codeframe/cli/env_commands.py +409 -0
  35. codeframe/cli/helpers.py +56 -0
  36. codeframe/cli/hooks_commands.py +208 -0
  37. codeframe/cli/import_commands.py +129 -0
  38. codeframe/cli/pr_commands.py +549 -0
  39. codeframe/cli/proof_commands.py +415 -0
  40. codeframe/cli/stats_commands.py +311 -0
  41. codeframe/cli/telemetry_runtime.py +153 -0
  42. codeframe/cli/validators.py +123 -0
  43. codeframe/config/rate_limits.py +165 -0
  44. codeframe/core/__init__.py +15 -0
  45. codeframe/core/adapters/__init__.py +43 -0
  46. codeframe/core/adapters/agent_adapter.py +114 -0
  47. codeframe/core/adapters/builtin.py +326 -0
  48. codeframe/core/adapters/claude_code.py +62 -0
  49. codeframe/core/adapters/codex.py +393 -0
  50. codeframe/core/adapters/git_utils.py +40 -0
  51. codeframe/core/adapters/kilocode.py +126 -0
  52. codeframe/core/adapters/opencode.py +48 -0
  53. codeframe/core/adapters/streaming_chat.py +483 -0
  54. codeframe/core/adapters/subprocess_adapter.py +213 -0
  55. codeframe/core/adapters/verification_wrapper.py +269 -0
  56. codeframe/core/agent.py +2183 -0
  57. codeframe/core/agents_config.py +569 -0
  58. codeframe/core/api_key_service.py +211 -0
  59. codeframe/core/artifacts.py +428 -0
  60. codeframe/core/blocker_detection.py +218 -0
  61. codeframe/core/blockers.py +433 -0
  62. codeframe/core/checkpoints.py +481 -0
  63. codeframe/core/conductor.py +2255 -0
  64. codeframe/core/config.py +827 -0
  65. codeframe/core/config_watcher.py +268 -0
  66. codeframe/core/context.py +542 -0
  67. codeframe/core/context_packager.py +234 -0
  68. codeframe/core/credentials.py +735 -0
  69. codeframe/core/dependency_analyzer.py +229 -0
  70. codeframe/core/dependency_graph.py +290 -0
  71. codeframe/core/diagnostic_agent.py +712 -0
  72. codeframe/core/diagnostics.py +616 -0
  73. codeframe/core/editor.py +556 -0
  74. codeframe/core/engine_registry.py +256 -0
  75. codeframe/core/engine_stats.py +231 -0
  76. codeframe/core/environment.py +697 -0
  77. codeframe/core/events.py +375 -0
  78. codeframe/core/executor.py +1005 -0
  79. codeframe/core/fix_tracker.py +480 -0
  80. codeframe/core/gates.py +1322 -0
  81. codeframe/core/git.py +477 -0
  82. codeframe/core/github_connect_service.py +178 -0
  83. codeframe/core/github_integration_config.py +118 -0
  84. codeframe/core/github_issues_service.py +449 -0
  85. codeframe/core/hooks.py +184 -0
  86. codeframe/core/importers/__init__.py +1 -0
  87. codeframe/core/importers/ralph.py +540 -0
  88. codeframe/core/installer.py +650 -0
  89. codeframe/core/models.py +1026 -0
  90. codeframe/core/notifications_config.py +183 -0
  91. codeframe/core/planner.py +437 -0
  92. codeframe/core/prd.py +670 -0
  93. codeframe/core/prd_discovery.py +1118 -0
  94. codeframe/core/prd_stress_test.py +499 -0
  95. codeframe/core/progress.py +126 -0
  96. codeframe/core/proof/__init__.py +34 -0
  97. codeframe/core/proof/capture.py +79 -0
  98. codeframe/core/proof/evidence.py +56 -0
  99. codeframe/core/proof/ledger.py +574 -0
  100. codeframe/core/proof/models.py +162 -0
  101. codeframe/core/proof/obligations.py +103 -0
  102. codeframe/core/proof/runner.py +233 -0
  103. codeframe/core/proof/scope.py +81 -0
  104. codeframe/core/proof/stubs.py +156 -0
  105. codeframe/core/quick_fixes.py +558 -0
  106. codeframe/core/react_agent.py +1650 -0
  107. codeframe/core/reconciliation.py +183 -0
  108. codeframe/core/replay.py +788 -0
  109. codeframe/core/review.py +285 -0
  110. codeframe/core/runtime.py +1134 -0
  111. codeframe/core/sandbox/__init__.py +27 -0
  112. codeframe/core/sandbox/context.py +98 -0
  113. codeframe/core/sandbox/worktree.py +20 -0
  114. codeframe/core/schedule.py +396 -0
  115. codeframe/core/stall_detector.py +71 -0
  116. codeframe/core/stall_monitor.py +134 -0
  117. codeframe/core/state_machine.py +121 -0
  118. codeframe/core/streaming.py +502 -0
  119. codeframe/core/task_tree.py +400 -0
  120. codeframe/core/tasks.py +1022 -0
  121. codeframe/core/telemetry.py +232 -0
  122. codeframe/core/templates.py +221 -0
  123. codeframe/core/tools.py +942 -0
  124. codeframe/core/workspace.py +887 -0
  125. codeframe/core/worktrees.py +276 -0
  126. codeframe/git/__init__.py +5 -0
  127. codeframe/git/github_integration.py +505 -0
  128. codeframe/lib/__init__.py +0 -0
  129. codeframe/lib/audit_logger.py +248 -0
  130. codeframe/lib/metrics_tracker.py +800 -0
  131. codeframe/lib/quality/__init__.py +7 -0
  132. codeframe/lib/quality/complexity_analyzer.py +316 -0
  133. codeframe/lib/quality/owasp_patterns.py +284 -0
  134. codeframe/lib/quality/security_scanner.py +250 -0
  135. codeframe/lib/rate_limiter.py +312 -0
  136. codeframe/notifications/__init__.py +0 -0
  137. codeframe/notifications/webhook.py +380 -0
  138. codeframe/planning/__init__.py +30 -0
  139. codeframe/planning/issue_generator.py +219 -0
  140. codeframe/planning/prd_template_functions.py +137 -0
  141. codeframe/planning/prd_templates.py +975 -0
  142. codeframe/planning/task_scheduler.py +511 -0
  143. codeframe/planning/task_templates.py +533 -0
  144. codeframe/platform_store/__init__.py +5 -0
  145. codeframe/platform_store/database.py +277 -0
  146. codeframe/platform_store/repositories/__init__.py +24 -0
  147. codeframe/platform_store/repositories/api_key_repository.py +245 -0
  148. codeframe/platform_store/repositories/audit_repository.py +67 -0
  149. codeframe/platform_store/repositories/base.py +295 -0
  150. codeframe/platform_store/repositories/interactive_sessions.py +165 -0
  151. codeframe/platform_store/repositories/token_repository.py +598 -0
  152. codeframe/platform_store/repositories/workspace_registry_repository.py +175 -0
  153. codeframe/platform_store/schema_manager.py +321 -0
  154. codeframe/templates/AGENTS.md.default +94 -0
  155. codeframe/tui/__init__.py +5 -0
  156. codeframe/tui/app.py +256 -0
  157. codeframe/tui/data_service.py +103 -0
  158. codeframe/ui/__init__.py +0 -0
  159. codeframe/ui/dependencies.py +103 -0
  160. codeframe/ui/models.py +999 -0
  161. codeframe/ui/response_models.py +201 -0
  162. codeframe/ui/routers/__init__.py +5 -0
  163. codeframe/ui/routers/_helpers.py +29 -0
  164. codeframe/ui/routers/batches_v2.py +315 -0
  165. codeframe/ui/routers/blockers_v2.py +320 -0
  166. codeframe/ui/routers/checkpoints_v2.py +310 -0
  167. codeframe/ui/routers/costs_v2.py +322 -0
  168. codeframe/ui/routers/diagnose_v2.py +225 -0
  169. codeframe/ui/routers/discovery_v2.py +417 -0
  170. codeframe/ui/routers/environment_v2.py +284 -0
  171. codeframe/ui/routers/events_v2.py +75 -0
  172. codeframe/ui/routers/gates_v2.py +166 -0
  173. codeframe/ui/routers/git_v2.py +284 -0
  174. codeframe/ui/routers/github_integrations_v2.py +532 -0
  175. codeframe/ui/routers/interactive_sessions_v2.py +238 -0
  176. codeframe/ui/routers/pr_v2.py +709 -0
  177. codeframe/ui/routers/prd_v2.py +695 -0
  178. codeframe/ui/routers/proof_v2.py +755 -0
  179. codeframe/ui/routers/review_v2.py +360 -0
  180. codeframe/ui/routers/schedule_v2.py +214 -0
  181. codeframe/ui/routers/session_chat_ws.py +354 -0
  182. codeframe/ui/routers/settings_v2.py +562 -0
  183. codeframe/ui/routers/streaming_v2.py +155 -0
  184. codeframe/ui/routers/tasks_v2.py +1098 -0
  185. codeframe/ui/routers/templates_v2.py +232 -0
  186. codeframe/ui/routers/terminal_ws.py +267 -0
  187. codeframe/ui/routers/workspace_v2.py +527 -0
  188. codeframe/ui/server.py +568 -0
  189. codeframe/ui/shared.py +241 -0
  190. codeframe/workspace/__init__.py +5 -0
  191. codeframe/workspace/manager.py +249 -0
  192. codeframe_ai-0.9.0.dist-info/METADATA +517 -0
  193. codeframe_ai-0.9.0.dist-info/RECORD +197 -0
  194. codeframe_ai-0.9.0.dist-info/WHEEL +5 -0
  195. codeframe_ai-0.9.0.dist-info/entry_points.txt +3 -0
  196. codeframe_ai-0.9.0.dist-info/licenses/LICENSE +661 -0
  197. codeframe_ai-0.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,276 @@
1
+ """Worktree-per-task isolation for parallel batch execution.
2
+
3
+ Creates isolated git worktrees so parallel agents don't modify files in the
4
+ same working directory. Each task gets its own branch and working tree,
5
+ then merges back to the base branch on completion.
6
+
7
+ Lifecycle:
8
+ 1. create(workspace_path, task_id) → worktree path
9
+ 2. Agent runs with cwd set to worktree
10
+ 3. merge_back(workspace_path, task_id) → MergeResult
11
+ 4. cleanup(workspace_path, task_id)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import logging
18
+ import os
19
+ import subprocess
20
+ import threading
21
+ from dataclasses import dataclass
22
+ from datetime import datetime, timezone
23
+ from pathlib import Path
24
+ from typing import Optional
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+ WORKTREE_DIR = ".codeframe/worktrees"
29
+ _REGISTRY_FILE = ".codeframe/worktrees.json"
30
+ _registry_lock = threading.Lock()
31
+
32
+
33
+ @dataclass
34
+ class MergeResult:
35
+ """Result from merging a worktree branch back to base."""
36
+
37
+ task_id: str
38
+ success: bool
39
+ conflict_details: str
40
+ merge_commit: Optional[str]
41
+
42
+
43
+ class TaskWorktree:
44
+ """Manages git worktrees for isolated parallel task execution."""
45
+
46
+ def create(
47
+ self,
48
+ workspace_path: Path,
49
+ task_id: str,
50
+ base_branch: str = "main",
51
+ ) -> Path:
52
+ """Create an isolated worktree for a task.
53
+
54
+ Args:
55
+ workspace_path: Root of the git repository
56
+ task_id: Task identifier (used for branch and directory name)
57
+ base_branch: Branch to base the worktree on
58
+
59
+ Returns:
60
+ Path to the created worktree directory
61
+
62
+ Raises:
63
+ subprocess.CalledProcessError: If git worktree creation fails
64
+ """
65
+ worktree_path = workspace_path / WORKTREE_DIR / task_id
66
+ worktree_path.parent.mkdir(parents=True, exist_ok=True)
67
+ branch_name = f"cf/{task_id}"
68
+
69
+ subprocess.run(
70
+ ["git", "worktree", "add", str(worktree_path), "-b", branch_name, base_branch],
71
+ cwd=str(workspace_path),
72
+ capture_output=True,
73
+ text=True,
74
+ check=True,
75
+ )
76
+
77
+ logger.info("Created worktree for %s at %s", task_id, worktree_path)
78
+ return worktree_path
79
+
80
+ def merge_back(
81
+ self,
82
+ workspace_path: Path,
83
+ task_id: str,
84
+ base_branch: str = "main",
85
+ ) -> MergeResult:
86
+ """Merge worktree branch back to base branch.
87
+
88
+ Args:
89
+ workspace_path: Root of the git repository
90
+ task_id: Task identifier
91
+ base_branch: Branch to merge into
92
+
93
+ Returns:
94
+ MergeResult with success status and optional conflict details
95
+ """
96
+ branch_name = f"cf/{task_id}"
97
+
98
+ # Checkout base branch
99
+ subprocess.run(
100
+ ["git", "checkout", base_branch],
101
+ cwd=str(workspace_path),
102
+ capture_output=True,
103
+ text=True,
104
+ check=True,
105
+ )
106
+
107
+ # Attempt merge
108
+ result = subprocess.run(
109
+ ["git", "merge", branch_name, "--no-ff", "-m", f"Merge {branch_name} into {base_branch}"],
110
+ cwd=str(workspace_path),
111
+ capture_output=True,
112
+ text=True,
113
+ )
114
+
115
+ if result.returncode == 0:
116
+ # Get merge commit hash
117
+ head = subprocess.run(
118
+ ["git", "rev-parse", "HEAD"],
119
+ cwd=str(workspace_path),
120
+ capture_output=True,
121
+ text=True,
122
+ )
123
+ merge_commit = head.stdout.strip() if head.returncode == 0 else None
124
+
125
+ logger.info("Merged %s back to %s", branch_name, base_branch)
126
+ return MergeResult(
127
+ task_id=task_id,
128
+ success=True,
129
+ conflict_details="",
130
+ merge_commit=merge_commit,
131
+ )
132
+ else:
133
+ # Merge conflict — abort and report
134
+ conflict_output = result.stdout + result.stderr
135
+ subprocess.run(
136
+ ["git", "merge", "--abort"],
137
+ cwd=str(workspace_path),
138
+ capture_output=True,
139
+ )
140
+
141
+ logger.warning("Merge conflict for %s: %s", branch_name, conflict_output[:200])
142
+ return MergeResult(
143
+ task_id=task_id,
144
+ success=False,
145
+ conflict_details=conflict_output[:2000],
146
+ merge_commit=None,
147
+ )
148
+
149
+ def cleanup(
150
+ self,
151
+ workspace_path: Path,
152
+ task_id: str,
153
+ ) -> None:
154
+ """Remove worktree and delete task branch.
155
+
156
+ Never raises — cleanup failures are logged as warnings.
157
+ """
158
+ worktree_path = workspace_path / WORKTREE_DIR / task_id
159
+ branch_name = f"cf/{task_id}"
160
+
161
+ # Remove worktree
162
+ try:
163
+ subprocess.run(
164
+ ["git", "worktree", "remove", str(worktree_path), "--force"],
165
+ cwd=str(workspace_path),
166
+ capture_output=True,
167
+ text=True,
168
+ )
169
+ except Exception as exc:
170
+ logger.warning("Failed to remove worktree for %s: %s", task_id, exc)
171
+
172
+ # Delete branch
173
+ try:
174
+ subprocess.run(
175
+ ["git", "branch", "-D", branch_name],
176
+ cwd=str(workspace_path),
177
+ capture_output=True,
178
+ text=True,
179
+ )
180
+ except Exception as exc:
181
+ logger.warning("Failed to delete branch %s: %s", branch_name, exc)
182
+
183
+
184
+ def get_base_branch(workspace_path: Path) -> str:
185
+ """Return the current HEAD branch name, defaulting to 'main' on failure.
186
+
187
+ Returns 'main' when git is unavailable, the directory is not a repo,
188
+ or HEAD is detached (rev-parse returns 'HEAD' literally).
189
+ """
190
+ result = subprocess.run(
191
+ ["git", "rev-parse", "--abbrev-ref", "HEAD"],
192
+ cwd=str(workspace_path),
193
+ capture_output=True,
194
+ text=True,
195
+ )
196
+ branch = result.stdout.strip()
197
+ if result.returncode != 0 or not branch or branch == "HEAD":
198
+ return "main"
199
+ return branch
200
+
201
+
202
+ def list_worktrees(workspace_path: Path) -> list[dict]:
203
+ """Return all entries in the worktree registry, or [] if absent/corrupt/malformed."""
204
+ registry_file = workspace_path / _REGISTRY_FILE
205
+ if not registry_file.exists():
206
+ return []
207
+ try:
208
+ data = json.loads(registry_file.read_text())
209
+ return data if isinstance(data, list) else []
210
+ except Exception:
211
+ return []
212
+
213
+
214
+ class WorktreeRegistry:
215
+ """Atomic registry of active worktrees for orphan detection.
216
+
217
+ Stores entries in ``.codeframe/worktrees.json``. All mutations are
218
+ protected by a module-level threading.Lock and written atomically
219
+ (write to .tmp then os.replace).
220
+ """
221
+
222
+ def register(self, workspace_path: Path, task_id: str, batch_id: str) -> None:
223
+ """Add or refresh a worktree entry for task_id."""
224
+ with _registry_lock:
225
+ entries = list_worktrees(workspace_path)
226
+ # Remove any existing entry for this task_id (idempotent)
227
+ entries = [e for e in entries if e["task_id"] != task_id]
228
+ entries.append({
229
+ "task_id": task_id,
230
+ "batch_id": batch_id,
231
+ "created_at": datetime.now(timezone.utc).isoformat(),
232
+ "pid": os.getpid(),
233
+ })
234
+ self._write(workspace_path, entries)
235
+
236
+ def unregister(self, workspace_path: Path, task_id: str) -> None:
237
+ """Remove the worktree entry for task_id. Safe if absent."""
238
+ with _registry_lock:
239
+ entries = list_worktrees(workspace_path)
240
+ entries = [e for e in entries if e["task_id"] != task_id]
241
+ self._write(workspace_path, entries)
242
+
243
+ def list_stale(self, workspace_path: Path) -> list[dict]:
244
+ """Return entries whose registered PID is no longer alive."""
245
+ stale = []
246
+ for entry in list_worktrees(workspace_path):
247
+ pid = entry.get("pid")
248
+ if pid is None:
249
+ continue
250
+ try:
251
+ os.kill(pid, 0)
252
+ except ProcessLookupError:
253
+ stale.append(entry)
254
+ except PermissionError:
255
+ pass # Process is alive but owned by another user — not stale
256
+ return stale
257
+
258
+ def cleanup_stale(self, workspace_path: Path) -> None:
259
+ """Remove stale worktree directories and unregister their entries."""
260
+ stale = self.list_stale(workspace_path)
261
+ if not stale:
262
+ return
263
+ wt = TaskWorktree()
264
+ for entry in stale:
265
+ task_id = entry["task_id"]
266
+ logger.info("Cleaning up orphaned worktree for task %s (pid %s)", task_id, entry.get("pid"))
267
+ wt.cleanup(workspace_path, task_id)
268
+ self.unregister(workspace_path, task_id)
269
+
270
+ @staticmethod
271
+ def _write(workspace_path: Path, entries: list[dict]) -> None:
272
+ registry_file = workspace_path / _REGISTRY_FILE
273
+ registry_file.parent.mkdir(parents=True, exist_ok=True)
274
+ tmp_file = registry_file.with_suffix(".json.tmp")
275
+ tmp_file.write_text(json.dumps(entries, indent=2))
276
+ os.replace(tmp_file, registry_file)
@@ -0,0 +1,5 @@
1
+ """Git integration for CodeFRAME.
2
+
3
+ The GitHub API client lives in :mod:`codeframe.git.github_integration` and is
4
+ imported directly where needed (``cf pr`` commands, the ``pr_v2`` router).
5
+ """