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,360 @@
1
+ """V2 Review router - delegates to core modules.
2
+
3
+ This module provides v2-style API endpoints for code review operations
4
+ that delegate to core modules. It uses the v2 Workspace model.
5
+
6
+ The v1 router (review.py) remains for backwards compatibility.
7
+ """
8
+
9
+ import logging
10
+ from typing import Literal, Optional
11
+
12
+ from fastapi import APIRouter, Depends, HTTPException, Request
13
+ from pydantic import BaseModel, Field
14
+
15
+ from codeframe.core.workspace import Workspace
16
+ from codeframe.lib.rate_limiter import rate_limit_standard
17
+ from codeframe.core import review, git
18
+ from codeframe.ui.dependencies import get_v2_workspace
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ router = APIRouter(prefix="/api/v2/review", tags=["review-v2"])
23
+
24
+
25
+ # ============================================================================
26
+ # Response Models
27
+ # ============================================================================
28
+
29
+
30
+ class ReviewFindingResponse(BaseModel):
31
+ """Response model for a review finding."""
32
+
33
+ category: str
34
+ severity: Literal["critical", "high", "medium", "low", "info"]
35
+ message: str
36
+ file_path: str
37
+ line_number: Optional[int] = None
38
+ suggestion: Optional[str] = None
39
+
40
+
41
+ class ReviewResultResponse(BaseModel):
42
+ """Response model for review result."""
43
+
44
+ status: Literal["approved", "changes_requested", "rejected"]
45
+ overall_score: float
46
+ findings: list[ReviewFindingResponse]
47
+ summary: str
48
+
49
+
50
+ class ReviewSummaryResponse(BaseModel):
51
+ """Response model for review summary."""
52
+
53
+ status: str
54
+ overall_score: float
55
+ total_findings: int
56
+ severity_counts: dict[str, int]
57
+ summary: str
58
+ has_blocking_issues: bool
59
+
60
+
61
+ class ReviewFilesRequest(BaseModel):
62
+ """Request model for reviewing files."""
63
+
64
+ files: list[str] = Field(..., min_length=1, description="Files to review")
65
+
66
+
67
+ class ReviewTaskRequest(BaseModel):
68
+ """Request model for reviewing a task's files."""
69
+
70
+ task_id: str = Field(..., description="Task ID")
71
+ files_modified: list[str] = Field(..., min_length=1, description="Modified files to review")
72
+
73
+
74
+ class FileChangeResponse(BaseModel):
75
+ """Per-file change statistics."""
76
+
77
+ path: str
78
+ change_type: str
79
+ insertions: int
80
+ deletions: int
81
+
82
+
83
+ class DiffStatsResponse(BaseModel):
84
+ """Response model for diff with statistics."""
85
+
86
+ diff: str
87
+ files_changed: int
88
+ insertions: int
89
+ deletions: int
90
+ changed_files: list[FileChangeResponse]
91
+
92
+
93
+ class PatchResponse(BaseModel):
94
+ """Response model for patch export."""
95
+
96
+ patch: str
97
+ filename: str
98
+
99
+
100
+ class CommitMessageResponse(BaseModel):
101
+ """Response model for generated commit message."""
102
+
103
+ message: str
104
+
105
+
106
+ # ============================================================================
107
+ # Review Endpoints
108
+ # ============================================================================
109
+
110
+
111
+ @router.post("/files", response_model=ReviewResultResponse)
112
+ @rate_limit_standard()
113
+ async def review_files(
114
+ request: Request,
115
+ body: ReviewFilesRequest,
116
+ workspace: Workspace = Depends(get_v2_workspace),
117
+ ) -> ReviewResultResponse:
118
+ """Run code review on specified files.
119
+
120
+ Performs complexity analysis, security scanning, and OWASP pattern
121
+ detection on the given files.
122
+
123
+ Args:
124
+ request: HTTP request for rate limiting
125
+ body: ReviewFilesRequest with file list
126
+ workspace: v2 Workspace
127
+
128
+ Returns:
129
+ ReviewResultResponse with findings and score
130
+ """
131
+ try:
132
+ result = review.review_files(workspace, body.files)
133
+
134
+ return ReviewResultResponse(
135
+ status=result.status,
136
+ overall_score=result.overall_score,
137
+ findings=[
138
+ ReviewFindingResponse(
139
+ category=f.category,
140
+ severity=f.severity,
141
+ message=f.message,
142
+ file_path=f.file_path,
143
+ line_number=f.line_number,
144
+ suggestion=f.suggestion,
145
+ )
146
+ for f in result.findings
147
+ ],
148
+ summary=result.summary,
149
+ )
150
+
151
+ except Exception as e:
152
+ logger.error(f"Failed to review files: {e}", exc_info=True)
153
+ raise HTTPException(status_code=500, detail=str(e))
154
+
155
+
156
+ @router.post("/task", response_model=ReviewResultResponse)
157
+ @rate_limit_standard()
158
+ async def review_task(
159
+ request: Request,
160
+ body: ReviewTaskRequest,
161
+ workspace: Workspace = Depends(get_v2_workspace),
162
+ ) -> ReviewResultResponse:
163
+ """Run code review for a task's modified files.
164
+
165
+ Convenience endpoint that wraps file review with task context.
166
+
167
+ Args:
168
+ request: HTTP request for rate limiting
169
+ body: ReviewTaskRequest with task ID and modified files
170
+ workspace: v2 Workspace
171
+
172
+ Returns:
173
+ ReviewResultResponse with findings and score
174
+ """
175
+ try:
176
+ result = review.review_task(
177
+ workspace,
178
+ task_id=body.task_id,
179
+ files_modified=body.files_modified,
180
+ )
181
+
182
+ return ReviewResultResponse(
183
+ status=result.status,
184
+ overall_score=result.overall_score,
185
+ findings=[
186
+ ReviewFindingResponse(
187
+ category=f.category,
188
+ severity=f.severity,
189
+ message=f.message,
190
+ file_path=f.file_path,
191
+ line_number=f.line_number,
192
+ suggestion=f.suggestion,
193
+ )
194
+ for f in result.findings
195
+ ],
196
+ summary=result.summary,
197
+ )
198
+
199
+ except Exception as e:
200
+ logger.error(f"Failed to review task: {e}", exc_info=True)
201
+ raise HTTPException(status_code=500, detail=str(e))
202
+
203
+
204
+ @router.post("/files/summary", response_model=ReviewSummaryResponse)
205
+ @rate_limit_standard()
206
+ async def review_files_summary(
207
+ request: Request,
208
+ body: ReviewFilesRequest,
209
+ workspace: Workspace = Depends(get_v2_workspace),
210
+ ) -> ReviewSummaryResponse:
211
+ """Run code review and return summary only.
212
+
213
+ Similar to review_files but returns a condensed summary without
214
+ individual findings. Useful for quick status checks.
215
+
216
+ Args:
217
+ request: HTTP request for rate limiting
218
+ body: ReviewFilesRequest with file list
219
+ workspace: v2 Workspace
220
+
221
+ Returns:
222
+ ReviewSummaryResponse with aggregated metrics
223
+ """
224
+ try:
225
+ result = review.review_files(workspace, body.files)
226
+ summary = review.get_review_summary(result)
227
+
228
+ return ReviewSummaryResponse(
229
+ status=summary["status"],
230
+ overall_score=summary["overall_score"],
231
+ total_findings=summary["total_findings"],
232
+ severity_counts=summary["severity_counts"],
233
+ summary=summary["summary"],
234
+ has_blocking_issues=summary["has_blocking_issues"],
235
+ )
236
+
237
+ except Exception as e:
238
+ logger.error(f"Failed to get review summary: {e}", exc_info=True)
239
+ raise HTTPException(status_code=500, detail=str(e))
240
+
241
+
242
+ # ============================================================================
243
+ # Diff & Patch Endpoints (for Review & Commit View)
244
+ # ============================================================================
245
+
246
+
247
+ @router.get("/diff", response_model=DiffStatsResponse)
248
+ @rate_limit_standard()
249
+ async def get_review_diff(
250
+ request: Request,
251
+ staged: bool = False,
252
+ workspace: Workspace = Depends(get_v2_workspace),
253
+ ) -> DiffStatsResponse:
254
+ """Get unified diff with parsed statistics.
255
+
256
+ Returns the raw diff plus per-file change counts for display
257
+ in the Review & Commit View.
258
+
259
+ Args:
260
+ request: HTTP request for rate limiting
261
+ staged: If True, show staged changes; if False, show unstaged
262
+ workspace: v2 Workspace
263
+
264
+ Returns:
265
+ DiffStatsResponse with diff text and statistics
266
+ """
267
+ try:
268
+ stats = git.get_diff_stats(workspace, staged=staged)
269
+
270
+ return DiffStatsResponse(
271
+ diff=stats.diff,
272
+ files_changed=stats.files_changed,
273
+ insertions=stats.insertions,
274
+ deletions=stats.deletions,
275
+ changed_files=[
276
+ FileChangeResponse(
277
+ path=f.path,
278
+ change_type=f.change_type,
279
+ insertions=f.insertions,
280
+ deletions=f.deletions,
281
+ )
282
+ for f in stats.changed_files
283
+ ],
284
+ )
285
+
286
+ except ValueError as e:
287
+ logger.error(f"Get review diff failed: {e}")
288
+ raise HTTPException(status_code=400, detail=str(e))
289
+ except Exception as e:
290
+ logger.error(f"Failed to get review diff: {e}", exc_info=True)
291
+ raise HTTPException(status_code=500, detail=str(e))
292
+
293
+
294
+ @router.get("/patch", response_model=PatchResponse)
295
+ @rate_limit_standard()
296
+ async def get_review_patch(
297
+ request: Request,
298
+ staged: bool = False,
299
+ workspace: Workspace = Depends(get_v2_workspace),
300
+ ) -> PatchResponse:
301
+ """Get patch-formatted diff for export.
302
+
303
+ Returns the diff in patch format suitable for `git apply`.
304
+
305
+ Args:
306
+ request: HTTP request for rate limiting
307
+ staged: If True, show staged changes; if False, show unstaged
308
+ workspace: v2 Workspace
309
+
310
+ Returns:
311
+ PatchResponse with patch content and suggested filename
312
+ """
313
+ try:
314
+ patch_content = git.get_patch(workspace, staged=staged)
315
+ branch = git.get_current_branch(workspace)
316
+ filename = f"{branch.replace('/', '-')}.patch"
317
+
318
+ return PatchResponse(
319
+ patch=patch_content,
320
+ filename=filename,
321
+ )
322
+
323
+ except ValueError as e:
324
+ logger.error(f"Get patch failed: {e}")
325
+ raise HTTPException(status_code=400, detail=str(e))
326
+ except Exception as e:
327
+ logger.error(f"Failed to get patch: {e}", exc_info=True)
328
+ raise HTTPException(status_code=500, detail=str(e))
329
+
330
+
331
+ @router.post("/commit-message", response_model=CommitMessageResponse)
332
+ @rate_limit_standard()
333
+ async def generate_commit_message(
334
+ request: Request,
335
+ staged: bool = False,
336
+ workspace: Workspace = Depends(get_v2_workspace),
337
+ ) -> CommitMessageResponse:
338
+ """Generate a commit message from the current diff.
339
+
340
+ Analyzes changed files to suggest a conventional commit message.
341
+
342
+ Args:
343
+ request: HTTP request for rate limiting
344
+ staged: If True, analyze staged changes; if False, unstaged
345
+ workspace: v2 Workspace
346
+
347
+ Returns:
348
+ CommitMessageResponse with suggested message
349
+ """
350
+ try:
351
+ message = git.generate_commit_message(workspace, staged=staged)
352
+
353
+ return CommitMessageResponse(message=message)
354
+
355
+ except ValueError as e:
356
+ logger.error(f"Generate commit message failed: {e}")
357
+ raise HTTPException(status_code=400, detail=str(e))
358
+ except Exception as e:
359
+ logger.error(f"Failed to generate commit message: {e}", exc_info=True)
360
+ raise HTTPException(status_code=500, detail=str(e))
@@ -0,0 +1,214 @@
1
+ """V2 Schedule router - delegates to core modules.
2
+
3
+ This module provides v2-style API endpoints for schedule management that
4
+ delegate to core/schedule.py. It uses the v2 Workspace model.
5
+
6
+ The v1 router (schedule.py) remains for backwards compatibility.
7
+ """
8
+
9
+ import logging
10
+ from typing import Optional
11
+
12
+ from fastapi import APIRouter, Depends, HTTPException, Query, Request
13
+ from pydantic import BaseModel
14
+
15
+ from codeframe.core.workspace import Workspace
16
+ from codeframe.lib.rate_limiter import rate_limit_standard
17
+ from codeframe.core import schedule
18
+ from codeframe.ui.dependencies import get_v2_workspace
19
+ from codeframe.ui.response_models import api_error, ErrorCodes
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ router = APIRouter(prefix="/api/v2/schedule", tags=["schedule-v2"])
24
+
25
+
26
+ # ============================================================================
27
+ # Request/Response Models
28
+ # ============================================================================
29
+
30
+
31
+ class TaskAssignmentResponse(BaseModel):
32
+ """Response model for a single task assignment."""
33
+
34
+ task_id: str
35
+ title: str
36
+ start_time: float
37
+ end_time: float
38
+ assigned_agent: Optional[int] = None
39
+
40
+
41
+ class ScheduleResponse(BaseModel):
42
+ """Response model for project schedule."""
43
+
44
+ task_assignments: list[TaskAssignmentResponse]
45
+ total_duration: float
46
+ agents_used: int
47
+
48
+
49
+ class CompletionPredictionResponse(BaseModel):
50
+ """Response model for completion prediction."""
51
+
52
+ predicted_date: str
53
+ remaining_hours: float
54
+ completed_percentage: float
55
+ confidence_interval: dict[str, str]
56
+
57
+
58
+ class BottleneckResponse(BaseModel):
59
+ """Response model for a scheduling bottleneck."""
60
+
61
+ task_id: str
62
+ task_title: str
63
+ bottleneck_type: str
64
+ impact_hours: float
65
+ recommendation: str
66
+
67
+
68
+ # ============================================================================
69
+ # Schedule Endpoints
70
+ # ============================================================================
71
+
72
+
73
+ @router.get("", response_model=ScheduleResponse)
74
+ @rate_limit_standard()
75
+ async def get_schedule(
76
+ request: Request,
77
+ agents: int = Query(1, ge=1, le=10, description="Number of parallel agents/workers"),
78
+ workspace: Workspace = Depends(get_v2_workspace),
79
+ ) -> ScheduleResponse:
80
+ """Get the schedule for a workspace.
81
+
82
+ Uses Critical Path Method to assign start/end times while
83
+ respecting dependency constraints and agent availability.
84
+
85
+ This is the v2 equivalent of `cf schedule show`.
86
+
87
+ Args:
88
+ agents: Number of parallel agents/workers (default: 1)
89
+ workspace: v2 Workspace
90
+
91
+ Returns:
92
+ Schedule with task assignments
93
+ """
94
+ try:
95
+ result = schedule.get_schedule(workspace, agents=agents)
96
+
97
+ return ScheduleResponse(
98
+ task_assignments=[
99
+ TaskAssignmentResponse(
100
+ task_id=a.task_id,
101
+ title=a.title,
102
+ start_time=a.start_time,
103
+ end_time=a.end_time,
104
+ assigned_agent=a.assigned_agent,
105
+ )
106
+ for a in result.task_assignments
107
+ ],
108
+ total_duration=result.total_duration,
109
+ agents_used=result.agents_used,
110
+ )
111
+
112
+ except ValueError as e:
113
+ raise HTTPException(
114
+ status_code=404,
115
+ detail=api_error("Schedule not found", ErrorCodes.NOT_FOUND, str(e)),
116
+ )
117
+ except Exception as e:
118
+ logger.error(f"Failed to get schedule: {e}", exc_info=True)
119
+ raise HTTPException(
120
+ status_code=500,
121
+ detail=api_error("Failed to get schedule", ErrorCodes.INTERNAL_ERROR, str(e)),
122
+ )
123
+
124
+
125
+ @router.get("/predict", response_model=CompletionPredictionResponse)
126
+ @rate_limit_standard()
127
+ async def predict_completion(
128
+ request: Request,
129
+ hours_per_day: float = Query(8.0, gt=0, le=24, description="Working hours per day"),
130
+ workspace: Workspace = Depends(get_v2_workspace),
131
+ ) -> CompletionPredictionResponse:
132
+ """Predict project completion date.
133
+
134
+ This is the v2 equivalent of `cf schedule predict`.
135
+
136
+ Args:
137
+ hours_per_day: Working hours per day (default: 8)
138
+ workspace: v2 Workspace
139
+
140
+ Returns:
141
+ Completion prediction with confidence interval
142
+ """
143
+ try:
144
+ result = schedule.predict_completion(workspace, hours_per_day=hours_per_day)
145
+
146
+ return CompletionPredictionResponse(
147
+ predicted_date=result.predicted_date.isoformat(),
148
+ remaining_hours=result.remaining_hours,
149
+ completed_percentage=result.completed_percentage,
150
+ confidence_interval={
151
+ "early": result.confidence_early.isoformat(),
152
+ "late": result.confidence_late.isoformat(),
153
+ },
154
+ )
155
+
156
+ except ValueError as e:
157
+ raise HTTPException(
158
+ status_code=404,
159
+ detail=api_error("Prediction failed", ErrorCodes.NOT_FOUND, str(e)),
160
+ )
161
+ except Exception as e:
162
+ logger.error(f"Failed to predict completion: {e}", exc_info=True)
163
+ raise HTTPException(
164
+ status_code=500,
165
+ detail=api_error("Failed to predict completion", ErrorCodes.INTERNAL_ERROR, str(e)),
166
+ )
167
+
168
+
169
+ @router.get("/bottlenecks", response_model=list[BottleneckResponse])
170
+ @rate_limit_standard()
171
+ async def get_bottlenecks(
172
+ request: Request,
173
+ workspace: Workspace = Depends(get_v2_workspace),
174
+ ) -> list[BottleneckResponse]:
175
+ """Identify scheduling bottlenecks for a workspace.
176
+
177
+ Identifies:
178
+ - Long duration tasks on critical path
179
+ - Tasks with many dependents causing delays
180
+ - Resource constraints limiting parallelization
181
+
182
+ This is the v2 equivalent of `cf schedule bottlenecks`.
183
+
184
+ Args:
185
+ workspace: v2 Workspace
186
+
187
+ Returns:
188
+ List of identified bottlenecks with recommendations
189
+ """
190
+ try:
191
+ bottlenecks = schedule.get_bottlenecks(workspace)
192
+
193
+ return [
194
+ BottleneckResponse(
195
+ task_id=bn.task_id,
196
+ task_title=bn.task_title,
197
+ bottleneck_type=bn.bottleneck_type,
198
+ impact_hours=bn.impact_hours,
199
+ recommendation=bn.recommendation,
200
+ )
201
+ for bn in bottlenecks
202
+ ]
203
+
204
+ except ValueError as e:
205
+ raise HTTPException(
206
+ status_code=404,
207
+ detail=api_error("Bottlenecks not found", ErrorCodes.NOT_FOUND, str(e)),
208
+ )
209
+ except Exception as e:
210
+ logger.error(f"Failed to get bottlenecks: {e}", exc_info=True)
211
+ raise HTTPException(
212
+ status_code=500,
213
+ detail=api_error("Failed to get bottlenecks", ErrorCodes.INTERNAL_ERROR, str(e)),
214
+ )