summaryrefslogtreecommitdiff
path: root/makima/src/server
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/server')
-rw-r--r--makima/src/server/handlers/directives.rs156
-rw-r--r--makima/src/server/mod.rs2
-rw-r--r--makima/src/server/openapi.rs6
3 files changed, 138 insertions, 26 deletions
diff --git a/makima/src/server/handlers/directives.rs b/makima/src/server/handlers/directives.rs
index b4b438a..56278a8 100644
--- a/makima/src/server/handlers/directives.rs
+++ b/makima/src/server/handlers/directives.rs
@@ -9,14 +9,14 @@ use axum::{
use uuid::Uuid;
use crate::db::models::{
- CleanupTasksResponse, CreateDirectiveRequest, CreateTaskRequest,
+ CleanupResponse, CleanupTasksResponse, CreateDirectiveRequest, CreateTaskRequest,
CreateDirectiveStepRequest, Directive, DirectiveListResponse,
DirectiveStep, DirectiveWithSteps, PickUpOrdersResponse,
UpdateDirectiveRequest, UpdateDirectiveStepRequest, UpdateGoalRequest,
UpdateOrderRequest,
};
use crate::db::repository;
-use crate::orchestration::directive::build_order_pickup_prompt;
+use crate::orchestration::directive::{build_cleanup_prompt, build_order_pickup_prompt};
use crate::server::auth::Authenticated;
use crate::server::messages::ApiError;
use crate::server::state::SharedState;
@@ -872,20 +872,21 @@ pub async fn update_goal(
// =============================================================================
-/// Clean up terminal tasks associated with a directive.
+/// Clean up merged steps for an idle directive by spawning a verification task.
#[utoipa::path(
post,
- path = "/api/v1/directives/{id}/cleanup-tasks",
+ path = "/api/v1/directives/{id}/cleanup",
params(("id" = Uuid, Path, description = "Directive ID")),
responses(
- (status = 200, description = "Tasks cleaned up", body = CleanupTasksResponse),
+ (status = 200, description = "Cleanup task spawned", body = CleanupResponse),
(status = 404, description = "Not found", body = ApiError),
+ (status = 409, description = "Directive is not idle", body = ApiError),
(status = 503, description = "Database not configured", body = ApiError),
),
security(("bearer_auth" = []), ("api_key" = [])),
tag = "Directives"
)]
-pub async fn cleanup_tasks(
+pub async fn cleanup_directive(
State(state): State<SharedState>,
Authenticated(auth): Authenticated,
Path(id): Path<Uuid>,
@@ -898,36 +899,147 @@ pub async fn cleanup_tasks(
.into_response();
};
- // Verify directive ownership
- match repository::get_directive_for_owner(pool, auth.owner_id, id).await {
- Ok(Some(_)) => {}
- Ok(None) => {
+ // Get the directive with steps and verify ownership
+ let (directive, _steps) =
+ match repository::get_directive_with_steps_for_owner(pool, auth.owner_id, id).await {
+ Ok(Some(ds)) => ds,
+ Ok(None) => {
+ return (
+ StatusCode::NOT_FOUND,
+ Json(ApiError::new("NOT_FOUND", "Directive not found")),
+ )
+ .into_response();
+ }
+ Err(e) => {
+ return (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("GET_FAILED", &e.to_string())),
+ )
+ .into_response();
+ }
+ };
+
+ // Verify directive is idle
+ if directive.status != "idle" {
+ return (
+ StatusCode::CONFLICT,
+ Json(ApiError::new(
+ "NOT_IDLE",
+ "Directive must be idle to run cleanup",
+ )),
+ )
+ .into_response();
+ }
+
+ // Get completed step tasks for branch name computation
+ let step_tasks = match repository::get_completed_step_tasks(pool, id).await {
+ Ok(tasks) => tasks,
+ Err(e) => {
+ tracing::error!("Failed to get completed step tasks: {}", e);
return (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Directive not found")),
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("GET_STEPS_FAILED", &e.to_string())),
)
.into_response();
}
+ };
+
+ if step_tasks.is_empty() {
+ return Json(CleanupResponse {
+ message: "No completed steps to clean up".to_string(),
+ task_id: None,
+ })
+ .into_response();
+ }
+
+ let pr_branch = match &directive.pr_branch {
+ Some(b) => b.clone(),
+ None => {
+ return Json(CleanupResponse {
+ message: "No PR branch set — nothing to verify against".to_string(),
+ task_id: None,
+ })
+ .into_response();
+ }
+ };
+
+ let base_branch = directive.base_branch.as_deref().unwrap_or("main");
+
+ // Build the cleanup prompt
+ let prompt = build_cleanup_prompt(&directive, &step_tasks, &pr_branch, base_branch);
+
+ // Create the cleanup task (following pick_up_orders pattern)
+ let req = CreateTaskRequest {
+ contract_id: None,
+ name: format!("Cleanup: {}", directive.title),
+ description: Some("Directive cleanup — verify merged branches and remove merged steps".to_string()),
+ plan: prompt,
+ parent_task_id: None,
+ is_supervisor: false,
+ priority: 0,
+ repository_url: directive.repository_url.clone(),
+ base_branch: directive.base_branch.clone(),
+ target_branch: None,
+ merge_mode: None,
+ target_repo_path: None,
+ completion_action: None,
+ continue_from_task_id: None,
+ copy_files: None,
+ checkpoint_sha: None,
+ branched_from_task_id: None,
+ conversation_history: None,
+ supervisor_worktree_task_id: None,
+ directive_id: Some(directive.id),
+ directive_step_id: None,
+ };
+
+ let task = match repository::create_task_for_owner(pool, auth.owner_id, req).await {
+ Ok(t) => t,
Err(e) => {
+ tracing::error!("Failed to create cleanup task: {}", e);
return (
StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("GET_FAILED", &e.to_string())),
+ Json(ApiError::new("CREATE_TASK_FAILED", &e.to_string())),
)
.into_response();
}
+ };
+
+ // Assign as orchestrator task
+ if let Err(e) = repository::assign_orchestrator_task(pool, id, task.id).await {
+ tracing::error!("Failed to assign orchestrator task: {}", e);
+ return (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("ASSIGN_TASK_FAILED", &e.to_string())),
+ )
+ .into_response();
}
- match repository::cleanup_directive_tasks(pool, auth.owner_id, id).await {
- Ok(deleted) => Json(CleanupTasksResponse { deleted }).into_response(),
- Err(e) => {
- tracing::error!("Failed to cleanup directive tasks: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("CLEANUP_FAILED", &e.to_string())),
- )
- .into_response()
+ // Cancel old planning tasks
+ let cancelled = repository::cancel_old_planning_tasks(pool, id, task.id).await;
+ if let Ok(count) = cancelled {
+ if count > 0 {
+ tracing::info!(
+ directive_id = %id,
+ cancelled_count = count,
+ "Cancelled old planning tasks superseded by cleanup"
+ );
}
}
+
+ // Set directive to active
+ if let Err(e) = repository::set_directive_status(pool, auth.owner_id, id, "active").await {
+ tracing::warn!("Failed to set directive status to active: {}", e);
+ }
+
+ // Advance ready steps
+ let _ = repository::advance_directive_ready_steps(pool, id).await;
+
+ Json(CleanupResponse {
+ message: format!("Cleanup task spawned for {} completed steps", step_tasks.len()),
+ task_id: Some(task.id),
+ })
+ .into_response()
}
// =============================================================================
diff --git a/makima/src/server/mod.rs b/makima/src/server/mod.rs
index 6bd5ae0..2310ba3 100644
--- a/makima/src/server/mod.rs
+++ b/makima/src/server/mod.rs
@@ -237,7 +237,7 @@ pub fn make_router(state: SharedState) -> Router {
.route("/directives/{id}/steps/{step_id}/fail", post(directives::fail_step))
.route("/directives/{id}/steps/{step_id}/skip", post(directives::skip_step))
.route("/directives/{id}/goal", put(directives::update_goal))
- .route("/directives/{id}/cleanup-tasks", post(directives::cleanup_tasks))
+ .route("/directives/{id}/cleanup", post(directives::cleanup_directive))
.route("/directives/{id}/create-pr", post(directives::create_pr))
.route("/directives/{id}/pick-up-orders", post(directives::pick_up_orders))
// Order endpoints
diff --git a/makima/src/server/openapi.rs b/makima/src/server/openapi.rs
index 87ca9c5..6065eeb 100644
--- a/makima/src/server/openapi.rs
+++ b/makima/src/server/openapi.rs
@@ -8,7 +8,7 @@ use crate::db::models::{
ChangePhaseRequest,
Contract, ContractChatHistoryResponse, ContractChatMessageRecord, ContractEvent,
ContractListResponse, ContractRepository, ContractSummary, ContractWithRelations,
- CleanupTasksResponse,
+ CleanupResponse,
CreateContractRequest, CreateDirectiveRequest, CreateDirectiveStepRequest, CreateFileRequest,
CreateManagedRepositoryRequest, CreateOrderRequest, CreateTaskRequest,
Daemon, DaemonDirectoriesResponse,
@@ -128,7 +128,7 @@ use crate::server::messages::{ApiError, AudioEncoding, StartMessage, StopMessage
directives::fail_step,
directives::skip_step,
directives::update_goal,
- directives::cleanup_tasks,
+ directives::cleanup_directive,
directives::create_pr,
// Order endpoints
orders::list_orders,
@@ -234,7 +234,7 @@ use crate::server::messages::{ApiError, AudioEncoding, StartMessage, StopMessage
UpdateGoalRequest,
CreateDirectiveStepRequest,
UpdateDirectiveStepRequest,
- CleanupTasksResponse,
+ CleanupResponse,
// Order schemas
Order,
OrderListResponse,