-
Notifications
You must be signed in to change notification settings - Fork 5
feat(planning): Add planning phase automation and task approval #205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a2c0682
d5cf22e
433ae1e
a00ea39
6038b81
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,24 +4,31 @@ | |
| - Task creation | ||
| - Task updates | ||
| - Task status management | ||
| - Task approval (for planning phase automation) | ||
| """ | ||
|
|
||
| import logging | ||
| from typing import Optional | ||
| from typing import List, Optional | ||
|
|
||
| from fastapi import APIRouter, Depends, HTTPException | ||
| from pydantic import BaseModel, Field | ||
|
|
||
| from codeframe.core.models import Task, TaskStatus | ||
| from codeframe.core.phase_manager import PhaseManager | ||
| from codeframe.persistence.database import Database | ||
| from codeframe.ui.dependencies import get_db | ||
| from codeframe.ui.shared import manager | ||
| from codeframe.ui.websocket_broadcasts import broadcast_development_started | ||
| from codeframe.auth.dependencies import get_current_user | ||
| from codeframe.auth.models import User | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| router = APIRouter(prefix="/api/tasks", tags=["tasks"]) | ||
|
|
||
| # Also register under project-scoped prefix for task approval | ||
| project_router = APIRouter(prefix="/api/projects", tags=["tasks"]) | ||
|
|
||
|
|
||
| class TaskCreateRequest(BaseModel): | ||
| """Request model for creating a task.""" | ||
|
|
@@ -103,3 +110,135 @@ async def create_task( | |
| except Exception as e: | ||
| logger.error(f"Error creating task: {e}", exc_info=True) | ||
| raise HTTPException(status_code=500, detail="Error creating task") | ||
|
|
||
|
|
||
| # ============================================================================ | ||
| # Task Approval Models and Endpoint (Feature: 016-planning-phase-automation) | ||
| # ============================================================================ | ||
|
|
||
|
|
||
| class TaskApprovalRequest(BaseModel): | ||
| """Request model for task approval.""" | ||
| approved: bool | ||
| excluded_task_ids: List[int] = Field(default_factory=list) | ||
|
|
||
|
|
||
| class TaskApprovalResponse(BaseModel): | ||
| """Response model for task approval.""" | ||
| success: bool | ||
| phase: str | ||
| approved_count: int | ||
| excluded_count: int | ||
| message: str | ||
|
|
||
|
|
||
| @project_router.post("/{project_id}/tasks/approve") | ||
| async def approve_tasks( | ||
| project_id: int, | ||
| request: TaskApprovalRequest, | ||
| db: Database = Depends(get_db), | ||
| current_user: User = Depends(get_current_user), | ||
| ) -> TaskApprovalResponse: | ||
| """Approve tasks and transition project to development phase. | ||
|
|
||
| This endpoint allows users to approve generated tasks after reviewing them. | ||
| Approved tasks are updated to 'pending' status and the project phase | ||
| transitions to 'active' (development). | ||
|
|
||
| Args: | ||
| project_id: Project ID | ||
| request: Approval request with approved flag and optional exclusions | ||
| db: Database connection | ||
| current_user: Authenticated user | ||
|
|
||
| Returns: | ||
| TaskApprovalResponse with summary of approval | ||
|
|
||
| Raises: | ||
| HTTPException: | ||
| - 400: Project not in planning phase | ||
| - 403: Access denied | ||
| - 404: Project or tasks not found | ||
| """ | ||
| # Verify project exists | ||
| project = db.get_project(project_id) | ||
| if not project: | ||
| raise HTTPException( | ||
| status_code=404, | ||
| detail=f"Project {project_id} not found" | ||
| ) | ||
|
|
||
| # Authorization check | ||
| if not db.user_has_project_access(current_user.id, project_id): | ||
| raise HTTPException(status_code=403, detail="Access denied") | ||
|
|
||
| # Check if user is rejecting | ||
| if not request.approved: | ||
| return TaskApprovalResponse( | ||
| success=False, | ||
| phase=project.get("phase", "planning"), | ||
| approved_count=0, | ||
| excluded_count=0, | ||
| message="Tasks were not approved. Please review and modify tasks before approving." | ||
| ) | ||
|
|
||
| # Validate project is in planning phase | ||
| current_phase = project.get("phase", "discovery") | ||
| if current_phase != "planning": | ||
| raise HTTPException( | ||
| status_code=400, | ||
| detail=f"Project must be in planning phase to approve tasks. Current phase: {current_phase}" | ||
| ) | ||
|
|
||
| # Get all tasks for the project | ||
| tasks = db.get_project_tasks(project_id) | ||
| if not tasks: | ||
| raise HTTPException( | ||
| status_code=404, | ||
| detail="No tasks found for this project. Generate tasks before approving." | ||
| ) | ||
|
|
||
| # Separate approved and excluded tasks | ||
| # Note: Excluded tasks remain unchanged in the database for audit trail. | ||
| # They are not deleted or modified - users can re-include them later if needed. | ||
| excluded_ids = set(request.excluded_task_ids) | ||
| approved_tasks = [t for t in tasks if t.id not in excluded_ids] | ||
| excluded_tasks = [t for t in tasks if t.id in excluded_ids] | ||
|
|
||
| # Transition project phase to active FIRST (fails early before modifying tasks) | ||
| # This ensures we don't leave tasks in pending status if phase transition fails | ||
| try: | ||
| PhaseManager.transition(project_id, "active", db) | ||
| except HTTPException: | ||
| raise | ||
| except Exception as e: | ||
| logger.error(f"Failed to transition phase for project {project_id}: {e}", exc_info=True) | ||
| raise HTTPException( | ||
| status_code=500, | ||
| detail="Failed to transition project to development phase" | ||
| ) | ||
|
|
||
| # Update approved tasks to pending status (after phase transition succeeds) | ||
| for task in approved_tasks: | ||
| db.update_task(task.id, {"status": "pending"}) | ||
|
Comment on lines
+204
to
+223
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check TaskStatus enum definition and status patterns
echo "=== Searching for TaskStatus enum ==="
ast-grep --pattern 'class TaskStatus'
echo -e "\n=== Searching for status string definitions ==="
rg -n 'status.*=.*["\']' codeframe/ --type py -A2 | head -50
echo -e "\n=== Checking for excluded/rejected status handling ==="
rg -nP -C3 '\b(excluded|rejected|skip)\b' codeframe/ --type py | head -80Repository: frankbria/codeframe Length of output: 795 🏁 Script executed: #!/bin/bash
# Check for transaction patterns and db.update_task usage
echo "=== Looking for transaction patterns ==="
rg -n 'transaction|atomic|begin|commit|rollback' codeframe/ --type py | head -30
echo -e "\n=== Check db.update_task implementation ==="
rg -n 'def update_task' codeframe/ --type py -A10Repository: frankbria/codeframe Length of output: 7292 🏁 Script executed: #!/bin/bash
# Check the full approve_tasks function to understand context
echo "=== Finding approve_tasks function ==="
ast-grep --pattern 'def approve_tasks'Repository: frankbria/codeframe Length of output: 7500 🏁 Script executed: #!/bin/bash
# Get full update_task implementation
echo "=== Full update_task implementation ==="
sed -n '97,130p' codeframe/persistence/repositories/task_repository.py
echo -e "\n=== Check how db uses SQLite connection ==="
rg -n 'self.conn.execute|cursor.execute' codeframe/persistence/repositories/task_repository.py | head -20Repository: frankbria/codeframe Length of output: 2033 🏁 Script executed: #!/bin/bash
# Check if there's any error handling around task updates in approve_tasks or similar patterns
echo "=== Check for error handling patterns in task updates ==="
rg -B3 -A3 'for.*task.*in.*tasks' codeframe/ui/routers/tasks.py
echo -e "\n=== Check PhaseManager.transition to understand phase change ==="
ast-grep --pattern 'def transition'Repository: frankbria/codeframe Length of output: 2980 No action needed for excluded task handling; add error handling to task status update loop. The approach of transitioning the project phase before updating tasks is correct (deliberate fail-fast pattern). However, two clarifications:
🤖 Prompt for AI Agents |
||
|
|
||
| # Broadcast development started event | ||
| await broadcast_development_started( | ||
| manager=manager, | ||
| project_id=project_id, | ||
| approved_count=len(approved_tasks), | ||
| excluded_count=len(excluded_tasks), | ||
| ) | ||
|
|
||
| logger.info( | ||
| f"Tasks approved for project {project_id}: " | ||
| f"{len(approved_tasks)} approved, {len(excluded_tasks)} excluded" | ||
| ) | ||
|
|
||
| return TaskApprovalResponse( | ||
| success=True, | ||
| phase="active", | ||
| approved_count=len(approved_tasks), | ||
| excluded_count=len(excluded_tasks), | ||
| message=f"Successfully approved {len(approved_tasks)} tasks. Development phase started." | ||
| ) | ||
Uh oh!
There was an error while loading. Please reload this page.