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,75 @@
1
+ """Events API router for CodeFRAME v2.
2
+
3
+ Provides endpoints for fetching workspace activity/event history.
4
+ Delegates to codeframe.core.events module.
5
+
6
+ TODO(#336): Add authentication to this router. All v2 routers are
7
+ missing auth enforcement despite documentation stating it's required.
8
+ """
9
+
10
+ from typing import Optional
11
+
12
+ from fastapi import APIRouter, Depends, Query, Request
13
+ from pydantic import BaseModel
14
+
15
+ from codeframe.core.workspace import Workspace
16
+ from codeframe.core import events
17
+ from codeframe.lib.rate_limiter import rate_limit_standard
18
+ from codeframe.ui.dependencies import get_v2_workspace
19
+
20
+ router = APIRouter(prefix="/api/v2/events", tags=["events"])
21
+
22
+
23
+ class EventResponse(BaseModel):
24
+ """Response model for a single event."""
25
+
26
+ id: int
27
+ workspace_id: str
28
+ event_type: str
29
+ payload: dict
30
+ created_at: str # ISO format
31
+
32
+
33
+ class EventListResponse(BaseModel):
34
+ """Response model for event list."""
35
+
36
+ events: list[EventResponse]
37
+ total: int
38
+
39
+
40
+ @router.get("", response_model=EventListResponse)
41
+ @rate_limit_standard()
42
+ async def list_events(
43
+ request: Request, # Required for rate limiting
44
+ workspace: Workspace = Depends(get_v2_workspace),
45
+ limit: int = Query(20, ge=1, le=100, description="Maximum events to return"),
46
+ since_id: Optional[int] = Query(None, description="Only return events after this ID"),
47
+ ):
48
+ """List recent events for a workspace.
49
+
50
+ Returns events in reverse chronological order (newest first).
51
+
52
+ Args:
53
+ request: HTTP request for rate limiting
54
+ workspace: Resolved workspace from workspace_path query param
55
+ limit: Maximum number of events (1-100, default 20)
56
+ since_id: Optional event ID for pagination
57
+
58
+ Returns:
59
+ List of events with total count
60
+ """
61
+ event_list = events.list_recent(workspace, limit=limit, since_id=since_id)
62
+
63
+ return EventListResponse(
64
+ events=[
65
+ EventResponse(
66
+ id=e.id,
67
+ workspace_id=e.workspace_id,
68
+ event_type=e.event_type,
69
+ payload=e.payload,
70
+ created_at=e.created_at.isoformat(),
71
+ )
72
+ for e in event_list
73
+ ],
74
+ total=len(event_list),
75
+ )
@@ -0,0 +1,166 @@
1
+ """V2 Gates router - delegates to core/gates module.
2
+
3
+ This module provides v2-style API endpoints for verification gates.
4
+ Gates are automated checks (tests, lint) that run before code is considered complete.
5
+
6
+ Routes:
7
+ POST /api/v2/gates/run - Run verification gates
8
+ GET /api/v2/gates - List available gates
9
+ """
10
+
11
+ import logging
12
+ from typing import Optional
13
+
14
+ from fastapi import APIRouter, Depends, HTTPException, Request
15
+ from pydantic import BaseModel, Field
16
+
17
+ from codeframe.core.workspace import Workspace
18
+ from codeframe.lib.rate_limiter import rate_limit_standard
19
+ from codeframe.core import gates
20
+ from codeframe.core.gates import GateResult, GateCheck
21
+ from codeframe.ui.dependencies import get_v2_workspace
22
+ from codeframe.ui.response_models import api_error, ErrorCodes
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ router = APIRouter(prefix="/api/v2/gates", tags=["gates-v2"])
27
+
28
+
29
+ # ============================================================================
30
+ # Request/Response Models
31
+ # ============================================================================
32
+
33
+
34
+ class GateCheckResponse(BaseModel):
35
+ """Response for a single gate check."""
36
+
37
+ name: str
38
+ status: str # GateStatus value
39
+ exit_code: Optional[int]
40
+ output: str
41
+ duration_ms: int
42
+
43
+
44
+ class GateResultResponse(BaseModel):
45
+ """Response for gate run results."""
46
+
47
+ passed: bool
48
+ checks: list[GateCheckResponse]
49
+ summary: str
50
+ started_at: Optional[str]
51
+ completed_at: Optional[str]
52
+
53
+
54
+ class RunGatesRequest(BaseModel):
55
+ """Request for running gates."""
56
+
57
+ gates: Optional[list[str]] = Field(None, description="Specific gates to run (None = all)")
58
+ verbose: bool = Field(False, description="Include verbose output")
59
+
60
+
61
+ # ============================================================================
62
+ # Helper Functions
63
+ # ============================================================================
64
+
65
+
66
+ def _check_to_response(check: GateCheck) -> GateCheckResponse:
67
+ """Convert a GateCheck to a GateCheckResponse."""
68
+ return GateCheckResponse(
69
+ name=check.name,
70
+ status=check.status.value,
71
+ exit_code=check.exit_code,
72
+ output=check.output,
73
+ duration_ms=check.duration_ms,
74
+ )
75
+
76
+
77
+ def _result_to_response(result: GateResult) -> GateResultResponse:
78
+ """Convert a GateResult to a GateResultResponse."""
79
+ return GateResultResponse(
80
+ passed=result.passed,
81
+ checks=[_check_to_response(c) for c in result.checks],
82
+ summary=result.summary,
83
+ started_at=result.started_at.isoformat() if result.started_at else None,
84
+ completed_at=result.completed_at.isoformat() if result.completed_at else None,
85
+ )
86
+
87
+
88
+ # ============================================================================
89
+ # Endpoints
90
+ # ============================================================================
91
+
92
+
93
+ @router.post("/run", response_model=GateResultResponse)
94
+ @rate_limit_standard()
95
+ async def run_gates(
96
+ request: Request,
97
+ body: RunGatesRequest = None,
98
+ workspace: Workspace = Depends(get_v2_workspace),
99
+ ) -> GateResultResponse:
100
+ """Run verification gates.
101
+
102
+ Runs automated checks (tests, lint) on the workspace code.
103
+
104
+ Args:
105
+ request: HTTP request for rate limiting
106
+ body: Gate run options
107
+ workspace: v2 Workspace
108
+
109
+ Returns:
110
+ Gate results with pass/fail status for each check
111
+ """
112
+ gate_list = body.gates if body else None
113
+ verbose = body.verbose if body else False
114
+
115
+ try:
116
+ result = gates.run(workspace, gates=gate_list, verbose=verbose)
117
+ return _result_to_response(result)
118
+
119
+ except Exception as e:
120
+ logger.error(f"Failed to run gates: {e}", exc_info=True)
121
+ raise HTTPException(
122
+ status_code=500,
123
+ detail=api_error("Gate execution failed", ErrorCodes.EXECUTION_FAILED, str(e)),
124
+ )
125
+
126
+
127
+ @router.get("", response_model=dict)
128
+ @rate_limit_standard()
129
+ async def list_gates(
130
+ request: Request,
131
+ workspace: Workspace = Depends(get_v2_workspace),
132
+ ) -> dict:
133
+ """List available verification gates.
134
+
135
+ Returns the gates that can be run on this workspace.
136
+
137
+ Args:
138
+ workspace: v2 Workspace
139
+
140
+ Returns:
141
+ List of available gates with descriptions
142
+ """
143
+ # Currently available gates
144
+ available_gates = [
145
+ {
146
+ "name": "pytest",
147
+ "description": "Run Python tests",
148
+ "command": "pytest",
149
+ },
150
+ {
151
+ "name": "ruff",
152
+ "description": "Run ruff linter",
153
+ "command": "ruff check",
154
+ },
155
+ {
156
+ "name": "ruff-fix",
157
+ "description": "Run ruff with auto-fix",
158
+ "command": "ruff check --fix",
159
+ },
160
+ ]
161
+
162
+ return {
163
+ "gates": available_gates,
164
+ "total": len(available_gates),
165
+ "message": "Use POST /api/v2/gates/run to execute gates",
166
+ }
@@ -0,0 +1,284 @@
1
+ """V2 Git router - delegates to core modules.
2
+
3
+ This module provides v2-style API endpoints for git operations
4
+ that delegate to core modules. It uses the v2 Workspace model.
5
+
6
+ The v1 router (git.py) remains for backwards compatibility.
7
+ """
8
+
9
+ import logging
10
+ from typing import 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 git
18
+ from codeframe.ui.dependencies import get_v2_workspace
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ router = APIRouter(prefix="/api/v2/git", tags=["git-v2"])
23
+
24
+
25
+ # ============================================================================
26
+ # Response Models
27
+ # ============================================================================
28
+
29
+
30
+ class GitStatusResponse(BaseModel):
31
+ """Response model for git status."""
32
+
33
+ current_branch: str
34
+ is_dirty: bool
35
+ modified_files: list[str]
36
+ untracked_files: list[str]
37
+ staged_files: list[str]
38
+
39
+
40
+ class CommitInfoResponse(BaseModel):
41
+ """Response model for commit information."""
42
+
43
+ hash: str
44
+ short_hash: str
45
+ message: str
46
+ author: str
47
+ timestamp: str
48
+
49
+
50
+ class CommitListResponse(BaseModel):
51
+ """Response model for commit listing."""
52
+
53
+ commits: list[CommitInfoResponse]
54
+
55
+
56
+ class CommitRequest(BaseModel):
57
+ """Request model for creating a commit."""
58
+
59
+ files: list[str] = Field(..., min_length=1, description="Files to commit")
60
+ message: str = Field(..., min_length=1, description="Commit message")
61
+
62
+
63
+ class CommitResultResponse(BaseModel):
64
+ """Response model for commit result."""
65
+
66
+ commit_hash: str
67
+ commit_message: str
68
+ files_changed: int
69
+
70
+
71
+ class DiffResponse(BaseModel):
72
+ """Response model for git diff."""
73
+
74
+ diff: str
75
+ staged: bool
76
+
77
+
78
+ # ============================================================================
79
+ # Git Endpoints
80
+ # ============================================================================
81
+
82
+
83
+ @router.get("/status", response_model=GitStatusResponse)
84
+ @rate_limit_standard()
85
+ async def get_git_status(
86
+ request: Request,
87
+ workspace: Workspace = Depends(get_v2_workspace),
88
+ ) -> GitStatusResponse:
89
+ """Get git working tree status.
90
+
91
+ Returns current branch, dirty state, and file lists.
92
+
93
+ Args:
94
+ workspace: v2 Workspace
95
+
96
+ Returns:
97
+ GitStatusResponse with status information
98
+ """
99
+ try:
100
+ status = git.get_status(workspace)
101
+
102
+ return GitStatusResponse(
103
+ current_branch=status.current_branch,
104
+ is_dirty=status.is_dirty,
105
+ modified_files=status.modified_files,
106
+ untracked_files=status.untracked_files,
107
+ staged_files=status.staged_files,
108
+ )
109
+
110
+ except ValueError as e:
111
+ logger.error(f"Git status failed: {e}")
112
+ raise HTTPException(status_code=400, detail=str(e))
113
+ except Exception as e:
114
+ logger.error(f"Failed to get git status: {e}", exc_info=True)
115
+ raise HTTPException(status_code=500, detail=str(e))
116
+
117
+
118
+ @router.get("/commits", response_model=CommitListResponse)
119
+ @rate_limit_standard()
120
+ async def list_commits(
121
+ request: Request,
122
+ branch: Optional[str] = None,
123
+ limit: int = 50,
124
+ workspace: Workspace = Depends(get_v2_workspace),
125
+ ) -> CommitListResponse:
126
+ """List git commits.
127
+
128
+ Args:
129
+ branch: Optional branch name (default: current branch)
130
+ limit: Maximum commits to return (default: 50)
131
+ workspace: v2 Workspace
132
+
133
+ Returns:
134
+ CommitListResponse with commit list
135
+ """
136
+ if limit < 1 or limit > 100:
137
+ raise HTTPException(status_code=400, detail="Limit must be between 1 and 100")
138
+
139
+ try:
140
+ commits = git.list_commits(workspace, branch=branch, limit=limit)
141
+
142
+ return CommitListResponse(
143
+ commits=[
144
+ CommitInfoResponse(
145
+ hash=c.hash,
146
+ short_hash=c.short_hash,
147
+ message=c.message,
148
+ author=c.author,
149
+ timestamp=c.timestamp,
150
+ )
151
+ for c in commits
152
+ ]
153
+ )
154
+
155
+ except ValueError as e:
156
+ logger.error(f"List commits failed: {e}")
157
+ raise HTTPException(status_code=400, detail=str(e))
158
+ except Exception as e:
159
+ logger.error(f"Failed to list commits: {e}", exc_info=True)
160
+ raise HTTPException(status_code=500, detail=str(e))
161
+
162
+
163
+ @router.post("/commit", response_model=CommitResultResponse, status_code=201)
164
+ @rate_limit_standard()
165
+ async def create_commit(
166
+ request: Request,
167
+ body: CommitRequest,
168
+ workspace: Workspace = Depends(get_v2_workspace),
169
+ ) -> CommitResultResponse:
170
+ """Create a git commit.
171
+
172
+ Stages the specified files and creates a commit.
173
+
174
+ Args:
175
+ request: HTTP request for rate limiting
176
+ body: CommitRequest with files and message
177
+ workspace: v2 Workspace
178
+
179
+ Returns:
180
+ CommitResultResponse with commit details
181
+ """
182
+ try:
183
+ result = git.create_commit(
184
+ workspace,
185
+ files=body.files,
186
+ message=body.message,
187
+ )
188
+
189
+ return CommitResultResponse(
190
+ commit_hash=result.commit_hash,
191
+ commit_message=result.commit_message,
192
+ files_changed=result.files_changed,
193
+ )
194
+
195
+ except ValueError as e:
196
+ logger.error(f"Create commit failed: {e}")
197
+ raise HTTPException(status_code=400, detail=str(e))
198
+ except Exception as e:
199
+ logger.error(f"Failed to create commit: {e}", exc_info=True)
200
+ raise HTTPException(status_code=500, detail=str(e))
201
+
202
+
203
+ @router.get("/diff", response_model=DiffResponse)
204
+ @rate_limit_standard()
205
+ async def get_diff(
206
+ request: Request,
207
+ staged: bool = False,
208
+ workspace: Workspace = Depends(get_v2_workspace),
209
+ ) -> DiffResponse:
210
+ """Get git diff.
211
+
212
+ Args:
213
+ staged: If True, show staged changes; if False, show unstaged
214
+ workspace: v2 Workspace
215
+
216
+ Returns:
217
+ DiffResponse with diff content
218
+ """
219
+ try:
220
+ diff_content = git.get_diff(workspace, staged=staged)
221
+
222
+ return DiffResponse(
223
+ diff=diff_content,
224
+ staged=staged,
225
+ )
226
+
227
+ except ValueError as e:
228
+ logger.error(f"Get diff failed: {e}")
229
+ raise HTTPException(status_code=400, detail=str(e))
230
+ except Exception as e:
231
+ logger.error(f"Failed to get diff: {e}", exc_info=True)
232
+ raise HTTPException(status_code=500, detail=str(e))
233
+
234
+
235
+ @router.get("/branch")
236
+ @rate_limit_standard()
237
+ async def get_current_branch(
238
+ request: Request,
239
+ workspace: Workspace = Depends(get_v2_workspace),
240
+ ) -> dict:
241
+ """Get current branch name.
242
+
243
+ Args:
244
+ workspace: v2 Workspace
245
+
246
+ Returns:
247
+ Dict with branch name
248
+ """
249
+ try:
250
+ branch = git.get_current_branch(workspace)
251
+ return {"branch": branch}
252
+
253
+ except ValueError as e:
254
+ logger.error(f"Get branch failed: {e}")
255
+ raise HTTPException(status_code=400, detail=str(e))
256
+ except Exception as e:
257
+ logger.error(f"Failed to get current branch: {e}", exc_info=True)
258
+ raise HTTPException(status_code=500, detail=str(e))
259
+
260
+
261
+ @router.get("/clean")
262
+ @rate_limit_standard()
263
+ async def check_clean(
264
+ request: Request,
265
+ workspace: Workspace = Depends(get_v2_workspace),
266
+ ) -> dict:
267
+ """Check if working tree is clean.
268
+
269
+ Args:
270
+ workspace: v2 Workspace
271
+
272
+ Returns:
273
+ Dict with is_clean boolean
274
+ """
275
+ try:
276
+ is_clean = git.is_clean(workspace)
277
+ return {"is_clean": is_clean}
278
+
279
+ except ValueError as e:
280
+ logger.error(f"Check clean failed: {e}")
281
+ raise HTTPException(status_code=400, detail=str(e))
282
+ except Exception as e:
283
+ logger.error(f"Failed to check if clean: {e}", exc_info=True)
284
+ raise HTTPException(status_code=500, detail=str(e))