diff options
Diffstat (limited to 'makima/src/server')
| -rw-r--r-- | makima/src/server/handlers/mesh_daemon.rs | 108 |
1 files changed, 104 insertions, 4 deletions
diff --git a/makima/src/server/handlers/mesh_daemon.rs b/makima/src/server/handlers/mesh_daemon.rs index 60de2e6..743a1ca 100644 --- a/makima/src/server/handlers/mesh_daemon.rs +++ b/makima/src/server/handlers/mesh_daemon.rs @@ -626,10 +626,7 @@ async fn compute_action_directive( if all_tasks_done && !pr_deliverable_complete { let done_count = task_infos.len(); return Some(format!( - "[ACTION REQUIRED] All {} task(s) completed successfully.\n\ - You MUST now create a PR and mark the 'pull-request' deliverable as complete:\n\ - 1. Ensure all changes are merged to your branch\n\ - 2. Create PR and then call mark_deliverable_complete with deliverable_id='pull-request'", + "[INFO] All {} task(s) completed. System is auto-creating PR.", done_count )); } @@ -638,6 +635,98 @@ async fn compute_action_directive( None } +/// Automatically create a PR when all non-supervisor tasks for a contract are done. +/// Only applies to remote-repo contracts in the "execute" phase. +/// Fires as a best-effort operation — errors are logged but not propagated. +async fn auto_create_pr_if_ready( + pool: &sqlx::PgPool, + state: &SharedState, + contract_id: Uuid, + owner_id: Uuid, +) { + // 1. Load contract — must be remote (not local_only) and in execute phase + let contract = match repository::get_contract_for_owner(pool, contract_id, owner_id).await { + Ok(Some(c)) => c, + _ => return, + }; + if contract.local_only || contract.phase != "execute" { + return; + } + + // 2. Load non-supervisor tasks — all must be done + let tasks = match repository::list_tasks_by_contract(pool, contract_id, owner_id).await { + Ok(t) => t, + _ => return, + }; + let non_supervisor_tasks: Vec<_> = tasks.iter().filter(|t| !t.is_supervisor).collect(); + if non_supervisor_tasks.is_empty() || !non_supervisor_tasks.iter().all(|t| t.status == "done") { + return; + } + + // 3. Check pull-request deliverable not already complete + let completed_deliverables = contract.get_completed_deliverables(&contract.phase); + if completed_deliverables.contains(&"pull-request".to_string()) { + return; + } + + // 4. Check at least one repository has a remote URL + let repos = match repository::list_contract_repositories(pool, contract_id).await { + Ok(r) => r, + _ => return, + }; + if !repos.iter().any(|r| r.repository_url.is_some()) { + return; + } + + // 5. Load supervisor task + let supervisor = match repository::get_contract_supervisor_task(pool, contract_id).await { + Ok(Some(s)) => s, + _ => return, + }; + + // Need supervisor's daemon_id to send command + let daemon_id = match supervisor.daemon_id { + Some(id) => id, + None => return, + }; + + // 6. Construct branch name + let sanitized_name: String = supervisor + .name + .chars() + .map(|c| if c.is_alphanumeric() || c == '-' || c == '_' { c } else { '-' }) + .collect::<String>() + .to_lowercase(); + let short_id = &supervisor.id.to_string()[..8]; + let branch = format!("makima/{}-{}", sanitized_name, short_id); + + // 7. Send CreatePR command to supervisor's daemon + let command = DaemonCommand::CreatePR { + task_id: supervisor.id, + title: contract.name.clone(), + body: contract.description.clone(), + base_branch: supervisor.base_branch.clone(), + branch, + }; + + match state.send_daemon_command(daemon_id, command).await { + Ok(()) => { + tracing::info!( + contract_id = %contract_id, + supervisor_id = %supervisor.id, + "Auto-PR: sent CreatePR command to supervisor daemon" + ); + } + Err(e) => { + tracing::warn!( + contract_id = %contract_id, + error = %e, + "Auto-PR: failed to send CreatePR command" + ); + } + } +} + /// Validate an API key and return (user_id, owner_id). async fn validate_daemon_api_key(pool: &sqlx::PgPool, key: &str) -> Result<DaemonAuthResult, String> { let key_hash = hash_api_key(key); @@ -1286,6 +1375,17 @@ async fn handle_daemon_connection(socket: WebSocket, state: SharedState, auth_re } } + // Auto-create PR if all tasks are done and repo is remote + if updated_task.status == "done" { + if let Some(contract_id) = updated_task.contract_id { + let pool_c = pool.clone(); + let state_c = state.clone(); + tokio::spawn(async move { + auto_create_pr_if_ready(&pool_c, &state_c, contract_id, owner_id).await; + }); + } + } + // Record history event for task completion let subtype = if updated_task.status == "done" { "completed" } else { "failed" }; let _ = repository::record_history_event( |
