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.
- codeframe/__init__.py +11 -0
- codeframe/__main__.py +20 -0
- codeframe/adapters/__init__.py +5 -0
- codeframe/adapters/e2b/__init__.py +13 -0
- codeframe/adapters/e2b/adapter.py +342 -0
- codeframe/adapters/e2b/budget.py +71 -0
- codeframe/adapters/e2b/credential_scanner.py +134 -0
- codeframe/adapters/llm/__init__.py +92 -0
- codeframe/adapters/llm/anthropic.py +414 -0
- codeframe/adapters/llm/base.py +444 -0
- codeframe/adapters/llm/mock.py +281 -0
- codeframe/adapters/llm/openai.py +483 -0
- codeframe/agents/__init__.py +8 -0
- codeframe/agents/dependency_resolver.py +714 -0
- codeframe/auth/__init__.py +16 -0
- codeframe/auth/api_key_router.py +238 -0
- codeframe/auth/api_keys.py +156 -0
- codeframe/auth/dependencies.py +358 -0
- codeframe/auth/manager.py +178 -0
- codeframe/auth/models.py +30 -0
- codeframe/auth/router.py +93 -0
- codeframe/auth/schemas.py +15 -0
- codeframe/auth/scopes.py +53 -0
- codeframe/cli/__init__.py +12 -0
- codeframe/cli/__main__.py +20 -0
- codeframe/cli/api_client.py +275 -0
- codeframe/cli/app.py +5688 -0
- codeframe/cli/auth.py +122 -0
- codeframe/cli/auth_commands.py +958 -0
- codeframe/cli/commands/__init__.py +5 -0
- codeframe/cli/config_commands.py +79 -0
- codeframe/cli/dashboard_commands.py +67 -0
- codeframe/cli/engines_commands.py +205 -0
- codeframe/cli/env_commands.py +409 -0
- codeframe/cli/helpers.py +56 -0
- codeframe/cli/hooks_commands.py +208 -0
- codeframe/cli/import_commands.py +129 -0
- codeframe/cli/pr_commands.py +549 -0
- codeframe/cli/proof_commands.py +415 -0
- codeframe/cli/stats_commands.py +311 -0
- codeframe/cli/telemetry_runtime.py +153 -0
- codeframe/cli/validators.py +123 -0
- codeframe/config/rate_limits.py +165 -0
- codeframe/core/__init__.py +15 -0
- codeframe/core/adapters/__init__.py +43 -0
- codeframe/core/adapters/agent_adapter.py +114 -0
- codeframe/core/adapters/builtin.py +326 -0
- codeframe/core/adapters/claude_code.py +62 -0
- codeframe/core/adapters/codex.py +393 -0
- codeframe/core/adapters/git_utils.py +40 -0
- codeframe/core/adapters/kilocode.py +126 -0
- codeframe/core/adapters/opencode.py +48 -0
- codeframe/core/adapters/streaming_chat.py +483 -0
- codeframe/core/adapters/subprocess_adapter.py +213 -0
- codeframe/core/adapters/verification_wrapper.py +269 -0
- codeframe/core/agent.py +2183 -0
- codeframe/core/agents_config.py +569 -0
- codeframe/core/api_key_service.py +211 -0
- codeframe/core/artifacts.py +428 -0
- codeframe/core/blocker_detection.py +218 -0
- codeframe/core/blockers.py +433 -0
- codeframe/core/checkpoints.py +481 -0
- codeframe/core/conductor.py +2255 -0
- codeframe/core/config.py +827 -0
- codeframe/core/config_watcher.py +268 -0
- codeframe/core/context.py +542 -0
- codeframe/core/context_packager.py +234 -0
- codeframe/core/credentials.py +735 -0
- codeframe/core/dependency_analyzer.py +229 -0
- codeframe/core/dependency_graph.py +290 -0
- codeframe/core/diagnostic_agent.py +712 -0
- codeframe/core/diagnostics.py +616 -0
- codeframe/core/editor.py +556 -0
- codeframe/core/engine_registry.py +256 -0
- codeframe/core/engine_stats.py +231 -0
- codeframe/core/environment.py +697 -0
- codeframe/core/events.py +375 -0
- codeframe/core/executor.py +1005 -0
- codeframe/core/fix_tracker.py +480 -0
- codeframe/core/gates.py +1322 -0
- codeframe/core/git.py +477 -0
- codeframe/core/github_connect_service.py +178 -0
- codeframe/core/github_integration_config.py +118 -0
- codeframe/core/github_issues_service.py +449 -0
- codeframe/core/hooks.py +184 -0
- codeframe/core/importers/__init__.py +1 -0
- codeframe/core/importers/ralph.py +540 -0
- codeframe/core/installer.py +650 -0
- codeframe/core/models.py +1026 -0
- codeframe/core/notifications_config.py +183 -0
- codeframe/core/planner.py +437 -0
- codeframe/core/prd.py +670 -0
- codeframe/core/prd_discovery.py +1118 -0
- codeframe/core/prd_stress_test.py +499 -0
- codeframe/core/progress.py +126 -0
- codeframe/core/proof/__init__.py +34 -0
- codeframe/core/proof/capture.py +79 -0
- codeframe/core/proof/evidence.py +56 -0
- codeframe/core/proof/ledger.py +574 -0
- codeframe/core/proof/models.py +162 -0
- codeframe/core/proof/obligations.py +103 -0
- codeframe/core/proof/runner.py +233 -0
- codeframe/core/proof/scope.py +81 -0
- codeframe/core/proof/stubs.py +156 -0
- codeframe/core/quick_fixes.py +558 -0
- codeframe/core/react_agent.py +1650 -0
- codeframe/core/reconciliation.py +183 -0
- codeframe/core/replay.py +788 -0
- codeframe/core/review.py +285 -0
- codeframe/core/runtime.py +1134 -0
- codeframe/core/sandbox/__init__.py +27 -0
- codeframe/core/sandbox/context.py +98 -0
- codeframe/core/sandbox/worktree.py +20 -0
- codeframe/core/schedule.py +396 -0
- codeframe/core/stall_detector.py +71 -0
- codeframe/core/stall_monitor.py +134 -0
- codeframe/core/state_machine.py +121 -0
- codeframe/core/streaming.py +502 -0
- codeframe/core/task_tree.py +400 -0
- codeframe/core/tasks.py +1022 -0
- codeframe/core/telemetry.py +232 -0
- codeframe/core/templates.py +221 -0
- codeframe/core/tools.py +942 -0
- codeframe/core/workspace.py +887 -0
- codeframe/core/worktrees.py +276 -0
- codeframe/git/__init__.py +5 -0
- codeframe/git/github_integration.py +505 -0
- codeframe/lib/__init__.py +0 -0
- codeframe/lib/audit_logger.py +248 -0
- codeframe/lib/metrics_tracker.py +800 -0
- codeframe/lib/quality/__init__.py +7 -0
- codeframe/lib/quality/complexity_analyzer.py +316 -0
- codeframe/lib/quality/owasp_patterns.py +284 -0
- codeframe/lib/quality/security_scanner.py +250 -0
- codeframe/lib/rate_limiter.py +312 -0
- codeframe/notifications/__init__.py +0 -0
- codeframe/notifications/webhook.py +380 -0
- codeframe/planning/__init__.py +30 -0
- codeframe/planning/issue_generator.py +219 -0
- codeframe/planning/prd_template_functions.py +137 -0
- codeframe/planning/prd_templates.py +975 -0
- codeframe/planning/task_scheduler.py +511 -0
- codeframe/planning/task_templates.py +533 -0
- codeframe/platform_store/__init__.py +5 -0
- codeframe/platform_store/database.py +277 -0
- codeframe/platform_store/repositories/__init__.py +24 -0
- codeframe/platform_store/repositories/api_key_repository.py +245 -0
- codeframe/platform_store/repositories/audit_repository.py +67 -0
- codeframe/platform_store/repositories/base.py +295 -0
- codeframe/platform_store/repositories/interactive_sessions.py +165 -0
- codeframe/platform_store/repositories/token_repository.py +598 -0
- codeframe/platform_store/repositories/workspace_registry_repository.py +175 -0
- codeframe/platform_store/schema_manager.py +321 -0
- codeframe/templates/AGENTS.md.default +94 -0
- codeframe/tui/__init__.py +5 -0
- codeframe/tui/app.py +256 -0
- codeframe/tui/data_service.py +103 -0
- codeframe/ui/__init__.py +0 -0
- codeframe/ui/dependencies.py +103 -0
- codeframe/ui/models.py +999 -0
- codeframe/ui/response_models.py +201 -0
- codeframe/ui/routers/__init__.py +5 -0
- codeframe/ui/routers/_helpers.py +29 -0
- codeframe/ui/routers/batches_v2.py +315 -0
- codeframe/ui/routers/blockers_v2.py +320 -0
- codeframe/ui/routers/checkpoints_v2.py +310 -0
- codeframe/ui/routers/costs_v2.py +322 -0
- codeframe/ui/routers/diagnose_v2.py +225 -0
- codeframe/ui/routers/discovery_v2.py +417 -0
- codeframe/ui/routers/environment_v2.py +284 -0
- codeframe/ui/routers/events_v2.py +75 -0
- codeframe/ui/routers/gates_v2.py +166 -0
- codeframe/ui/routers/git_v2.py +284 -0
- codeframe/ui/routers/github_integrations_v2.py +532 -0
- codeframe/ui/routers/interactive_sessions_v2.py +238 -0
- codeframe/ui/routers/pr_v2.py +709 -0
- codeframe/ui/routers/prd_v2.py +695 -0
- codeframe/ui/routers/proof_v2.py +755 -0
- codeframe/ui/routers/review_v2.py +360 -0
- codeframe/ui/routers/schedule_v2.py +214 -0
- codeframe/ui/routers/session_chat_ws.py +354 -0
- codeframe/ui/routers/settings_v2.py +562 -0
- codeframe/ui/routers/streaming_v2.py +155 -0
- codeframe/ui/routers/tasks_v2.py +1098 -0
- codeframe/ui/routers/templates_v2.py +232 -0
- codeframe/ui/routers/terminal_ws.py +267 -0
- codeframe/ui/routers/workspace_v2.py +527 -0
- codeframe/ui/server.py +568 -0
- codeframe/ui/shared.py +241 -0
- codeframe/workspace/__init__.py +5 -0
- codeframe/workspace/manager.py +249 -0
- codeframe_ai-0.9.0.dist-info/METADATA +517 -0
- codeframe_ai-0.9.0.dist-info/RECORD +197 -0
- codeframe_ai-0.9.0.dist-info/WHEEL +5 -0
- codeframe_ai-0.9.0.dist-info/entry_points.txt +3 -0
- codeframe_ai-0.9.0.dist-info/licenses/LICENSE +661 -0
- 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))
|