diff options
Diffstat (limited to 'makima/src/llm')
| -rw-r--r-- | makima/src/llm/mod.rs | 4 | ||||
| -rw-r--r-- | makima/src/llm/phase_guidance.rs | 296 |
2 files changed, 299 insertions, 1 deletions
diff --git a/makima/src/llm/mod.rs b/makima/src/llm/mod.rs index 6167a42..fc3802b 100644 --- a/makima/src/llm/mod.rs +++ b/makima/src/llm/mod.rs @@ -19,8 +19,10 @@ pub use contract_tools::{ pub use groq::GroqClient; pub use mesh_tools::{parse_mesh_tool_call, MeshToolExecutionResult, MeshToolRequest, MESH_TOOLS}; pub use phase_guidance::{ - check_phase_completion, check_phase_completion_for_type, format_checklist_markdown, + check_deliverables_met, check_phase_completion, check_phase_completion_for_type, + format_checklist_markdown, generate_deliverable_prompt_guidance, get_next_phase_for_contract, get_phase_checklist, get_phase_checklist_for_type, get_phase_deliverables, get_phase_deliverables_for_type, + should_auto_progress, AutoProgressAction, AutoProgressDecision, DeliverableCheckResult, DeliverableItem, DeliverableStatus, FileInfo, FilePriority, PhaseChecklist, PhaseDeliverables, RecommendedFile, TaskInfo, TaskStats, }; diff --git a/makima/src/llm/phase_guidance.rs b/makima/src/llm/phase_guidance.rs index df7bd24..03f7c76 100644 --- a/makima/src/llm/phase_guidance.rs +++ b/makima/src/llm/phase_guidance.rs @@ -579,6 +579,302 @@ pub fn check_phase_completion_for_type( required_files_complete && repository_ok && tasks_ok } +/// Result of checking if deliverables are met for the current phase +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct DeliverableCheckResult { + /// Whether all required deliverables are met + pub deliverables_met: bool, + /// Whether the phase is ready to advance (includes all readiness checks) + pub ready_to_advance: bool, + /// Current phase + pub phase: String, + /// Next phase (if available) + pub next_phase: Option<String>, + /// List of required deliverables and their status + pub required_deliverables: Vec<DeliverableItem>, + /// List of what's missing (if any) + pub missing: Vec<String>, + /// Human-readable summary + pub summary: String, + /// Whether auto-progress is recommended + pub auto_progress_recommended: bool, +} + +/// A single deliverable item status +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct DeliverableItem { + /// Name of the deliverable + pub name: String, + /// Type: "file", "repository", "pr", "tasks" + pub deliverable_type: String, + /// Whether it's met + pub met: bool, + /// Additional details + pub details: Option<String>, +} + +/// Check if all required deliverables for the current phase are met +/// This is used for both prompts and the check_deliverables_met tool +pub fn check_deliverables_met( + phase: &str, + contract_type: &str, + files: &[FileInfo], + tasks: &[TaskInfo], + has_repository: bool, + pr_url: Option<&str>, +) -> DeliverableCheckResult { + let mut required_deliverables = Vec::new(); + let mut missing = Vec::new(); + + // Get the deliverables for this contract type and phase + let deliverables = get_phase_deliverables_for_type(phase, contract_type); + + // Check required files for this phase + for rec in &deliverables.recommended_files { + if rec.priority == FilePriority::Required { + let matched = files.iter().any(|f| { + f.contract_phase.as_deref() == Some(phase) && + (f.name.to_lowercase().contains(&rec.name_suggestion.to_lowercase()) || + rec.name_suggestion.to_lowercase().contains(&f.name.to_lowercase()) || + f.name.to_lowercase().contains(&rec.template_id.replace("-", " "))) + }); + + required_deliverables.push(DeliverableItem { + name: rec.name_suggestion.clone(), + deliverable_type: "file".to_string(), + met: matched, + details: if matched { + Some("Document exists".to_string()) + } else { + None + }, + }); + + if !matched { + missing.push(format!("Create {} (required)", rec.name_suggestion)); + } + } + } + + // Check repository for phases that require it + if deliverables.requires_repository { + required_deliverables.push(DeliverableItem { + name: "Repository".to_string(), + deliverable_type: "repository".to_string(), + met: has_repository, + details: if has_repository { + Some("Repository configured".to_string()) + } else { + None + }, + }); + + if !has_repository { + missing.push("Configure a repository".to_string()); + } + } + + // Check tasks for execute phase + if deliverables.requires_tasks { + let total_tasks = tasks.len(); + let done_tasks = tasks.iter().filter(|t| t.status == "done").count(); + let tasks_complete = total_tasks > 0 && done_tasks == total_tasks; + + required_deliverables.push(DeliverableItem { + name: "Tasks Completed".to_string(), + deliverable_type: "tasks".to_string(), + met: tasks_complete, + details: Some(format!("{}/{} tasks done", done_tasks, total_tasks)), + }); + + if !tasks_complete { + if total_tasks == 0 { + missing.push("Create and complete tasks".to_string()); + } else { + missing.push(format!("Complete remaining {} task(s)", total_tasks - done_tasks)); + } + } + } + + // For simple/specification contracts in execute phase, PR is a key deliverable + if (contract_type == "simple" || contract_type == "specification") && phase == "execute" { + let has_pr = pr_url.is_some() && !pr_url.unwrap_or("").is_empty(); + required_deliverables.push(DeliverableItem { + name: "Pull Request".to_string(), + deliverable_type: "pr".to_string(), + met: has_pr, + details: pr_url.map(|u| format!("PR: {}", u)), + }); + + if !has_pr { + missing.push("Create a Pull Request for the completed work".to_string()); + } + } + + let deliverables_met = required_deliverables.iter().all(|d| d.met); + let next_phase = get_next_phase_for_contract(contract_type, phase); + let ready_to_advance = deliverables_met && next_phase.is_some(); + + let summary = if deliverables_met { + if let Some(ref next) = next_phase { + format!("All deliverables met for {} phase. Ready to advance to {} phase.", phase, next) + } else { + format!("All deliverables met for {} phase. This is the final phase.", phase) + } + } else { + format!("{} deliverable(s) still needed for {} phase.", missing.len(), phase) + }; + + DeliverableCheckResult { + deliverables_met, + ready_to_advance, + phase: phase.to_string(), + next_phase, + required_deliverables, + missing, + summary, + auto_progress_recommended: deliverables_met && ready_to_advance, + } +} + +/// Get the next phase based on contract type +pub fn get_next_phase_for_contract(contract_type: &str, current_phase: &str) -> Option<String> { + match contract_type { + "simple" => match current_phase { + "plan" => Some("execute".to_string()), + "execute" => None, // Terminal phase for simple contracts + _ => None, + }, + "execute" => None, // Execute-only contracts don't have phase transitions + "specification" | _ => match current_phase { + "research" => Some("specify".to_string()), + "specify" => Some("plan".to_string()), + "plan" => Some("execute".to_string()), + "execute" => Some("review".to_string()), + "review" => None, // Final phase + _ => None, + }, + } +} + +/// Determine if the contract should auto-progress to the next phase +/// This is called when deliverables are met and autonomous_loop is enabled +pub fn should_auto_progress( + phase: &str, + contract_type: &str, + files: &[FileInfo], + tasks: &[TaskInfo], + has_repository: bool, + pr_url: Option<&str>, + autonomous_loop: bool, +) -> AutoProgressDecision { + let check = check_deliverables_met(phase, contract_type, files, tasks, has_repository, pr_url); + + if !check.deliverables_met { + return AutoProgressDecision { + should_progress: false, + next_phase: None, + reason: format!("Deliverables not met: {}", check.missing.join(", ")), + action: AutoProgressAction::WaitForDeliverables, + }; + } + + if check.next_phase.is_none() { + return AutoProgressDecision { + should_progress: false, + next_phase: None, + reason: "This is the terminal phase. Contract can be completed.".to_string(), + action: AutoProgressAction::CompleteContract, + }; + } + + if autonomous_loop { + AutoProgressDecision { + should_progress: true, + next_phase: check.next_phase, + reason: "All deliverables met and autonomous_loop is enabled.".to_string(), + action: AutoProgressAction::AdvancePhase, + } + } else { + AutoProgressDecision { + should_progress: false, + next_phase: check.next_phase, + reason: "All deliverables met. Suggest advancing to next phase.".to_string(), + action: AutoProgressAction::SuggestAdvance, + } + } +} + +/// Result of auto-progress decision +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AutoProgressDecision { + /// Whether to automatically progress + pub should_progress: bool, + /// The next phase to progress to + pub next_phase: Option<String>, + /// Reason for the decision + pub reason: String, + /// Recommended action + pub action: AutoProgressAction, +} + +/// Actions that can be taken based on auto-progress decision +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum AutoProgressAction { + /// Wait for required deliverables + WaitForDeliverables, + /// Automatically advance to next phase + AdvancePhase, + /// Suggest user to advance (when not autonomous) + SuggestAdvance, + /// Contract is complete, mark as done + CompleteContract, +} + +/// Generate enhanced prompt guidance for deliverable checking +pub fn generate_deliverable_prompt_guidance( + phase: &str, + contract_type: &str, + check_result: &DeliverableCheckResult, +) -> String { + let mut guidance = String::new(); + + guidance.push_str("\n## Phase Deliverables Status\n\n"); + guidance.push_str(&format!("**Current Phase**: {} | **Contract Type**: {}\n\n", + capitalize(phase), contract_type)); + + // Show required deliverables checklist + guidance.push_str("### Required Deliverables Checklist\n"); + for item in &check_result.required_deliverables { + let status = if item.met { "[x]" } else { "[ ]" }; + let details = item.details.as_ref().map(|d| format!(" - {}", d)).unwrap_or_default(); + guidance.push_str(&format!("{} **{}** ({}){}\n", status, item.name, item.deliverable_type, details)); + } + + // Show status and next actions + guidance.push_str("\n### Status\n"); + if check_result.deliverables_met { + guidance.push_str("**All deliverables are met.**\n\n"); + if let Some(ref next) = check_result.next_phase { + guidance.push_str(&format!("Ready to advance to **{}** phase.\n", next)); + if check_result.auto_progress_recommended { + guidance.push_str(&format!("\n**ACTION REQUIRED**: Since all deliverables are met, you should call `advance_phase` with `new_phase=\"{}\"` to progress the contract.\n", next)); + } + } else { + guidance.push_str("This is the terminal phase. The contract can be marked as completed.\n"); + } + } else { + guidance.push_str("**Deliverables not yet met.**\n\n"); + guidance.push_str("Missing:\n"); + for item in &check_result.missing { + guidance.push_str(&format!("- {}\n", item)); + } + guidance.push_str("\nComplete the missing deliverables before advancing to the next phase.\n"); + } + + guidance +} + /// Format checklist as markdown for LLM context pub fn format_checklist_markdown(checklist: &PhaseChecklist) -> String { let mut md = format!("## Phase Progress ({} Phase)\n\n", capitalize(&checklist.phase)); |
