diff options
| author | soryu <soryu@soryu.co> | 2026-01-25 02:19:01 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-01-25 02:19:01 +0000 |
| commit | a4c5e9a601b49d08e5ef3d7a36cdd29372ce2003 (patch) | |
| tree | 061a880c6ea2cd3bee2fa80137a2e7e3bf3ec6fb /makima/src/server/handlers/contract_chat.rs | |
| parent | 1f223e55be79805bb1061213db4351925bc0b368 (diff) | |
| parent | 2003544969e5b7248ecd242b5cec50b324fa751b (diff) | |
| download | soryu-makima/files-under-contracts-combined.tar.gz soryu-makima/files-under-contracts-combined.zip | |
Merge origin/master into makima/files-under-contracts-combined - resolve import conflictsmakima/files-under-contracts-combined
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'makima/src/server/handlers/contract_chat.rs')
| -rw-r--r-- | makima/src/server/handlers/contract_chat.rs | 168 |
1 files changed, 161 insertions, 7 deletions
diff --git a/makima/src/server/handlers/contract_chat.rs b/makima/src/server/handlers/contract_chat.rs index e2adb72..28c3436 100644 --- a/makima/src/server/handlers/contract_chat.rs +++ b/makima/src/server/handlers/contract_chat.rs @@ -20,7 +20,7 @@ use crate::db::{ }; use crate::llm::{ all_templates, analyze_task_output, body_to_markdown, format_checklist_markdown, - format_parsed_tasks, get_phase_checklist, parse_tasks_from_breakdown, + format_parsed_tasks, parse_tasks_from_breakdown, claude::{self, ClaudeClient, ClaudeError, ClaudeModel}, groq::{GroqClient, GroqError, Message, ToolCallResponse}, parse_contract_tool_call, templates_for_phase, ContractToolRequest, FileInfo, @@ -433,8 +433,8 @@ When a new contract is created or the user seems unsure: fn build_contract_context(contract: &crate::db::models::ContractWithRelations) -> String { let c = &contract.contract; let mut context = format!( - "Name: {}\nID: {}\nPhase: {}\nStatus: {}\n", - c.name, c.id, c.phase, c.status + "Name: {}\nID: {}\nPhase: {}\nStatus: {}\nContract Type: {}\nAutonomous Loop: {}\n", + c.name, c.id, c.phase, c.status, c.contract_type, c.autonomous_loop ); if let Some(ref desc) = c.description { @@ -455,12 +455,31 @@ fn build_contract_context(contract: &crate::db::models::ContractWithRelations) - }).collect(); let has_repository = !contract.repositories.is_empty(); - let phase_checklist = get_phase_checklist(&c.phase, &file_infos, &task_infos, has_repository); + let phase_checklist = crate::llm::get_phase_checklist_for_type(&c.phase, &file_infos, &task_infos, has_repository, &c.contract_type); // Add phase checklist to context context.push_str("\n"); context.push_str(&format_checklist_markdown(&phase_checklist)); + // Add deliverable check result for phase transition readiness + // Note: pr_url is not available in TaskSummary, so we pass None here + // Full PR checking should be done via the check_deliverables_met tool + let deliverable_check = crate::llm::check_deliverables_met( + &c.phase, + &c.contract_type, + &file_infos, + &task_infos, + has_repository, + None, // pr_url not available in TaskSummary + ); + + // Add deliverable prompt guidance + context.push_str(&crate::llm::generate_deliverable_prompt_guidance( + &c.phase, + &c.contract_type, + &deliverable_check, + )); + // Files summary context.push_str(&format!("\n### Files ({} total)\n", contract.files.len())); if !contract.files.is_empty() { @@ -1732,6 +1751,65 @@ async fn handle_contract_request( }; } + // Check if deliverables are met before allowing transition + let cwr = match get_contract_with_relations(pool, contract_id, owner_id).await { + Ok(Some(c)) => c, + Ok(None) | Err(_) => { + // Fall through - we'll just skip the deliverables check + return ContractRequestResult { + success: false, + message: "Failed to load contract for deliverables check".to_string(), + data: None, + }; + } + }; + + let file_infos: Vec<FileInfo> = cwr.files.iter().map(|f| FileInfo { + id: f.id, + name: f.name.clone(), + contract_phase: f.contract_phase.clone(), + }).collect(); + + let task_infos: Vec<TaskInfo> = cwr.tasks.iter().map(|t| TaskInfo { + id: t.id, + name: t.name.clone(), + status: t.status.clone(), + }).collect(); + + let has_repository = !cwr.repositories.is_empty(); + // Note: pr_url is not available in TaskSummary, so we skip PR checking here + // For simple contracts, the PR deliverable check will need to be done + // by fetching full task details if needed + + let check_result = crate::llm::check_deliverables_met( + current_phase, + &contract.contract_type, + &file_infos, + &task_infos, + has_repository, + None, // pr_url not available in TaskSummary + ); + + // Block transition if deliverables are not met + if !check_result.deliverables_met { + return ContractRequestResult { + success: false, + message: format!( + "Cannot advance to '{}' phase: deliverables not met. {}", + new_phase, check_result.summary + ), + data: Some(json!({ + "status": "deliverables_not_met", + "currentPhase": current_phase, + "requestedPhase": new_phase, + "deliverablesMet": false, + "requiredDeliverables": check_result.required_deliverables, + "missing": check_result.missing, + "action": "Complete the missing deliverables before advancing to the next phase" + })), + }; + } + // Check if phase_guard is enabled if contract.phase_guard { // If user provided feedback, return it for the task to address @@ -1816,8 +1894,8 @@ async fn handle_contract_request( // Update phase (either phase_guard is disabled, or user confirmed) match repository::change_contract_phase_for_owner(pool, contract_id, owner_id, &new_phase).await { Ok(Some(updated)) => { - // Get deliverables for the new phase - let deliverables = crate::llm::get_phase_deliverables(&new_phase); + // Get deliverables for the new phase (using contract type) + let deliverables = crate::llm::get_phase_deliverables_for_type(&new_phase, &contract.contract_type); // Build suggested files list let suggested_files: Vec<serde_json::Value> = deliverables @@ -1963,7 +2041,7 @@ async fn handle_contract_request( }).collect(); let has_repository = !cwr.repositories.is_empty(); - let checklist = get_phase_checklist(&cwr.contract.phase, &file_infos, &task_infos, has_repository); + let checklist = crate::llm::get_phase_checklist_for_type(&cwr.contract.phase, &file_infos, &task_infos, has_repository, &cwr.contract.contract_type); ContractRequestResult { success: true, @@ -1993,6 +2071,82 @@ async fn handle_contract_request( } } + ContractToolRequest::CheckDeliverablesMet => { + match get_contract_with_relations(pool, contract_id, owner_id).await { + Ok(Some(cwr)) => { + let file_infos: Vec<FileInfo> = cwr.files.iter().map(|f| FileInfo { + id: f.id, + name: f.name.clone(), + contract_phase: f.contract_phase.clone(), + }).collect(); + + let task_infos: Vec<TaskInfo> = cwr.tasks.iter().map(|t| TaskInfo { + id: t.id, + name: t.name.clone(), + status: t.status.clone(), + }).collect(); + + let has_repository = !cwr.repositories.is_empty(); + + // Note: pr_url is not available in TaskSummary + // For simple contracts needing PR checking, full task details would need to be fetched + // For now, we pass None and the LLM can guide the user to ensure a PR exists + + let check_result = crate::llm::check_deliverables_met( + &cwr.contract.phase, + &cwr.contract.contract_type, + &file_infos, + &task_infos, + has_repository, + None, // pr_url not available in TaskSummary + ); + + // Check if we should auto-progress + let auto_progress = crate::llm::should_auto_progress( + &cwr.contract.phase, + &cwr.contract.contract_type, + &file_infos, + &task_infos, + has_repository, + None, // pr_url not available in TaskSummary + cwr.contract.autonomous_loop, + ); + + ContractRequestResult { + success: true, + message: check_result.summary.clone(), + data: Some(json!({ + "deliverablesMet": check_result.deliverables_met, + "readyToAdvance": check_result.ready_to_advance, + "phase": check_result.phase, + "nextPhase": check_result.next_phase, + "requiredDeliverables": check_result.required_deliverables, + "missing": check_result.missing, + "summary": check_result.summary, + "autoProgressRecommended": check_result.auto_progress_recommended, + "autoProgress": { + "shouldProgress": auto_progress.should_progress, + "nextPhase": auto_progress.next_phase, + "reason": auto_progress.reason, + "action": format!("{:?}", auto_progress.action), + }, + "autonomousLoop": cwr.contract.autonomous_loop, + })), + } + } + Ok(None) => ContractRequestResult { + success: false, + message: "Contract not found".to_string(), + data: None, + }, + Err(e) => ContractRequestResult { + success: false, + message: format!("Database error: {}", e), + data: None, + }, + } + } + // ============================================================================= // Task Derivation Handlers // ============================================================================= |
