//! Phase guidance and deliverables tracking for contract management. //! //! This module provides structured guidance for each contract phase, tracking //! expected deliverables and completion criteria. //! //! ## Contract Types //! //! ### Simple //! - **Plan phase**: One required deliverable: "Plan" //! - **Execute phase**: One required deliverable: "PR" //! //! ### Specification //! - **Research phase**: One required deliverable: "Research Notes" //! - **Specify phase**: One required deliverable: "Requirements Document" //! - **Plan phase**: One required deliverable: "Plan" //! - **Execute phase**: One required deliverable: "PR" //! - **Review phase**: One required deliverable: "Release Notes" //! //! ### Execute //! - **Execute phase only**: No deliverables at all use serde::{Deserialize, Serialize}; use utoipa::ToSchema; /// Priority level for deliverables #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "lowercase")] pub enum DeliverablePriority { /// Must be completed before advancing phase Required, /// Strongly suggested for phase completion Recommended, /// Nice to have, not blocking Optional, } /// A deliverable for a phase #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct Deliverable { /// Unique identifier for the deliverable pub id: String, /// Display name pub name: String, /// Priority level pub priority: DeliverablePriority, /// Brief description of purpose pub description: String, } /// Expected deliverables for a phase #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct PhaseDeliverables { /// Phase name pub phase: String, /// Deliverables for this phase pub deliverables: Vec, /// Whether a repository is required for this phase pub requires_repository: bool, /// Whether tasks should be completed in this phase pub requires_tasks: bool, /// Guidance text for this phase pub guidance: String, } /// Status of a deliverable #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct DeliverableStatus { /// Deliverable ID pub id: String, /// Display name pub name: String, /// Priority pub priority: DeliverablePriority, /// Whether it has been completed pub completed: bool, } /// Checklist for phase completion #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct PhaseChecklist { /// Current phase pub phase: String, /// Deliverable status list pub deliverables: Vec, /// Whether repository is configured pub has_repository: bool, /// Whether repository was required pub repository_required: bool, /// Task statistics (for execute phase) pub task_stats: Option, /// Overall completion percentage (0-100) pub completion_percentage: u8, /// Summary message pub summary: String, /// Suggestions for next actions pub suggestions: Vec, } /// Task statistics for execute phase #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct TaskStats { pub total: usize, pub pending: usize, pub running: usize, pub done: usize, pub failed: usize, } /// Minimal task info for checklist building pub struct TaskInfo { pub name: String, pub status: String, } use crate::db::models::PhaseConfig; /// Get phase deliverables configuration (legacy, defaults to "simple" contract type) pub fn get_phase_deliverables(phase: &str) -> PhaseDeliverables { get_phase_deliverables_for_type(phase, "simple") } /// Get phase deliverables configuration for a specific contract type pub fn get_phase_deliverables_for_type(phase: &str, contract_type: &str) -> PhaseDeliverables { match contract_type { "execute" => get_execute_type_deliverables(phase), "specification" => get_specification_type_deliverables(phase), "simple" | _ => get_simple_type_deliverables(phase), } } /// Get phase deliverables from a custom PhaseConfig /// This is used for contracts with custom templates pub fn get_phase_deliverables_from_config(phase: &str, config: &PhaseConfig) -> PhaseDeliverables { // Check if this phase exists in the config let phase_exists = config.phases.iter().any(|p| p.id == phase); if !phase_exists { return PhaseDeliverables { phase: phase.to_string(), deliverables: vec![], requires_repository: false, requires_tasks: false, guidance: format!("Phase '{}' is not defined in this contract template", phase), }; } // Get deliverables for this phase from the config let deliverables: Vec = config .deliverables .get(phase) .map(|defs| { defs.iter() .map(|d| Deliverable { id: d.id.clone(), name: d.name.clone(), priority: match d.priority.as_str() { "recommended" => DeliverablePriority::Recommended, "optional" => DeliverablePriority::Optional, _ => DeliverablePriority::Required, }, description: format!("{} deliverable", d.name), }) .collect() }) .unwrap_or_default(); // Determine if repository is required (typically for execute-like phases) let requires_repository = phase == "execute" || phase == "plan"; // Determine if tasks are required (typically for execute phase) let requires_tasks = phase == "execute"; // Find the phase name for better guidance let phase_name = config .phases .iter() .find(|p| p.id == phase) .map(|p| p.name.clone()) .unwrap_or_else(|| phase.to_string()); let guidance = if deliverables.is_empty() { format!("Complete the {} phase. No specific deliverables are required.", phase_name) } else { let deliverable_names: Vec<_> = deliverables.iter().map(|d| d.name.clone()).collect(); format!( "Complete the {} phase by producing the following deliverables: {}", phase_name, deliverable_names.join(", ") ) }; PhaseDeliverables { phase: phase.to_string(), deliverables, requires_repository, requires_tasks, guidance, } } /// Get phase deliverables, checking custom config first, then falling back to built-in types pub fn get_phase_deliverables_with_config( phase: &str, contract_type: &str, phase_config: Option<&PhaseConfig>, ) -> PhaseDeliverables { // If we have a custom phase config, use it if let Some(config) = phase_config { return get_phase_deliverables_from_config(phase, config); } // Otherwise, fall back to built-in contract types get_phase_deliverables_for_type(phase, contract_type) } /// Get deliverables for 'simple' contract type fn get_simple_type_deliverables(phase: &str) -> PhaseDeliverables { match phase { "plan" => PhaseDeliverables { phase: "plan".to_string(), deliverables: vec![Deliverable { id: "plan-document".to_string(), name: "Plan".to_string(), priority: DeliverablePriority::Required, description: "Implementation plan detailing the approach and tasks".to_string(), }], requires_repository: true, requires_tasks: false, guidance: "Create a plan document that outlines the implementation approach. A repository must be configured before moving to Execute phase.".to_string(), }, "execute" => PhaseDeliverables { phase: "execute".to_string(), deliverables: vec![Deliverable { id: "pull-request".to_string(), name: "Pull Request".to_string(), priority: DeliverablePriority::Required, description: "Pull request with the implemented changes".to_string(), }], requires_repository: true, requires_tasks: true, guidance: "Execute the plan and create a PR with the implemented changes. Complete all tasks to finish the contract.".to_string(), }, _ => PhaseDeliverables { phase: phase.to_string(), deliverables: vec![], requires_repository: false, requires_tasks: false, guidance: "Unknown phase for simple contract type".to_string(), }, } } /// Get deliverables for 'specification' contract type fn get_specification_type_deliverables(phase: &str) -> PhaseDeliverables { match phase { "research" => PhaseDeliverables { phase: "research".to_string(), deliverables: vec![Deliverable { id: "research-notes".to_string(), name: "Research Notes".to_string(), priority: DeliverablePriority::Required, description: "Document findings and insights during research".to_string(), }], requires_repository: false, requires_tasks: false, guidance: "Focus on understanding the problem space and document your findings in the Research Notes before moving to Specify phase.".to_string(), }, "specify" => PhaseDeliverables { phase: "specify".to_string(), deliverables: vec![Deliverable { id: "requirements-document".to_string(), name: "Requirements Document".to_string(), priority: DeliverablePriority::Required, description: "Define functional and non-functional requirements".to_string(), }], requires_repository: false, requires_tasks: false, guidance: "Define what needs to be built with clear requirements in the Requirements Document. Ensure specifications are detailed enough for planning.".to_string(), }, "plan" => PhaseDeliverables { phase: "plan".to_string(), deliverables: vec![Deliverable { id: "plan-document".to_string(), name: "Plan".to_string(), priority: DeliverablePriority::Required, description: "Implementation plan detailing the approach and tasks".to_string(), }], requires_repository: true, requires_tasks: false, guidance: "Create a plan document that outlines the implementation approach. A repository must be configured before moving to Execute phase.".to_string(), }, "execute" => PhaseDeliverables { phase: "execute".to_string(), deliverables: vec![Deliverable { id: "pull-request".to_string(), name: "Pull Request".to_string(), priority: DeliverablePriority::Required, description: "Pull request with the implemented changes".to_string(), }], requires_repository: true, requires_tasks: true, guidance: "Execute the plan and create a PR with the implemented changes. Complete all tasks before moving to Review phase.".to_string(), }, "review" => PhaseDeliverables { phase: "review".to_string(), deliverables: vec![Deliverable { id: "release-notes".to_string(), name: "Release Notes".to_string(), priority: DeliverablePriority::Required, description: "Document changes for release communication".to_string(), }], requires_repository: false, requires_tasks: false, guidance: "Review completed work and document the release in the Release Notes. The contract can be completed after review.".to_string(), }, _ => PhaseDeliverables { phase: phase.to_string(), deliverables: vec![], requires_repository: false, requires_tasks: false, guidance: "Unknown phase for specification contract type".to_string(), }, } } /// Get deliverables for 'execute' contract type fn get_execute_type_deliverables(phase: &str) -> PhaseDeliverables { match phase { "execute" => PhaseDeliverables { phase: "execute".to_string(), deliverables: vec![], // No deliverables for execute-only contract type requires_repository: true, requires_tasks: true, guidance: "Execute the tasks directly. No deliverable documents are required for this contract type.".to_string(), }, _ => PhaseDeliverables { phase: phase.to_string(), deliverables: vec![], requires_repository: false, requires_tasks: false, guidance: "The 'execute' contract type only supports the 'execute' phase.".to_string(), }, } } /// Build a phase checklist comparing expected vs actual deliverables pub fn get_phase_checklist_for_type( phase: &str, completed_deliverables: &[String], tasks: &[TaskInfo], has_repository: bool, contract_type: &str, ) -> PhaseChecklist { let phase_config = get_phase_deliverables_for_type(phase, contract_type); // Build deliverable status list let deliverables: Vec = phase_config .deliverables .iter() .map(|d| DeliverableStatus { id: d.id.clone(), name: d.name.clone(), priority: d.priority, completed: completed_deliverables.contains(&d.id), }) .collect(); // Calculate task stats for execute phase let task_stats = if phase == "execute" { let total = tasks.len(); let pending = tasks.iter().filter(|t| t.status == "pending").count(); let running = tasks.iter().filter(|t| t.status == "running").count(); let done = tasks.iter().filter(|t| t.status == "done").count(); let failed = tasks .iter() .filter(|t| t.status == "failed" || t.status == "error") .count(); Some(TaskStats { total, pending, running, done, failed, }) } else { None }; // Calculate completion percentage let mut completed_items = 0; let mut total_items = 0; // Count required and recommended deliverables (not optional) for status in &deliverables { if status.priority != DeliverablePriority::Optional { total_items += 1; if status.completed { completed_items += 1; } } } // Count repository if required if phase_config.requires_repository { total_items += 1; if has_repository { completed_items += 1; } } // Count tasks if in execute phase if let Some(ref stats) = task_stats { if stats.total > 0 { total_items += 1; if stats.done == stats.total && stats.total > 0 { completed_items += 1; } } } let completion_percentage = if total_items > 0 { ((completed_items as f64 / total_items as f64) * 100.0) as u8 } else { 100 // No requirements means complete }; // Generate suggestions let mut suggestions = Vec::new(); // Suggest missing deliverables for status in &deliverables { if !status.completed { match status.priority { DeliverablePriority::Required => { suggestions.push(format!( "Mark '{}' as complete using mark_deliverable_complete (required)", status.name )); } DeliverablePriority::Recommended => { suggestions.push(format!( "Consider completing '{}' (recommended)", status.name )); } DeliverablePriority::Optional => {} } } } // Suggest repository if needed if phase_config.requires_repository && !has_repository { suggestions.push("Configure a repository for task execution".to_string()); } // Suggest task actions for execute phase if let Some(ref stats) = task_stats { if stats.total == 0 { suggestions.push("Create tasks to implement the plan".to_string()); } else if stats.pending > 0 { suggestions.push(format!("Run {} pending task(s)", stats.pending)); } else if stats.running > 0 { suggestions.push(format!( "Wait for {} running task(s) to complete", stats.running )); } else if stats.failed > 0 { suggestions.push(format!("Address {} failed task(s)", stats.failed)); } else if stats.done == stats.total && stats.total > 0 { suggestions.push("All tasks complete. Mark deliverables and advance phase.".to_string()); } } // Generate summary let summary = generate_phase_summary( phase, &deliverables, has_repository, &task_stats, completion_percentage, ); PhaseChecklist { phase: phase.to_string(), deliverables, has_repository, repository_required: phase_config.requires_repository, task_stats, completion_percentage, summary, suggestions, } } fn generate_phase_summary( phase: &str, deliverables: &[DeliverableStatus], has_repository: bool, task_stats: &Option, completion_percentage: u8, ) -> String { let completed_count = deliverables.iter().filter(|d| d.completed).count(); let total_count = deliverables.len(); match phase { "research" => { if completed_count == 0 { "Research phase needs documentation. Mark deliverables complete when ready." .to_string() } else { format!( "{}/{} deliverables complete. Ready to transition to Specify phase.", completed_count, total_count ) } } "specify" => { let has_required = deliverables .iter() .filter(|d| d.priority == DeliverablePriority::Required) .all(|d| d.completed); if !has_required { "Specify phase requires completing the Requirements Document deliverable." .to_string() } else { "Specifications ready. Consider transitioning to Plan phase.".to_string() } } "plan" => { let has_required = deliverables .iter() .filter(|d| d.priority == DeliverablePriority::Required) .all(|d| d.completed); if !has_required { "Plan phase requires completing the Plan deliverable.".to_string() } else if !has_repository { "Repository not configured. Configure a repository before Execute phase." .to_string() } else { "Planning complete. Ready to transition to Execute phase.".to_string() } } "execute" => { if let Some(stats) = task_stats { if stats.total == 0 { "No tasks created. Create tasks to implement the plan.".to_string() } else if stats.done == stats.total { "All tasks complete! Mark deliverables and advance to Review phase (or complete contract).".to_string() } else { format!( "{}/{} tasks completed ({}% done)", stats.done, stats.total, if stats.total > 0 { (stats.done * 100) / stats.total } else { 0 } ) } } else { "Execute phase in progress.".to_string() } } "review" => { let has_required = deliverables .iter() .filter(|d| d.priority == DeliverablePriority::Required) .all(|d| d.completed); if !has_required { "Review phase requires completing the Release Notes deliverable.".to_string() } else { "Review documentation complete. Contract can be marked as done.".to_string() } } _ => format!("Phase {} - {}% complete", phase, completion_percentage), } } /// 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, /// List of required deliverables and their status pub required_deliverables: Vec, /// List of what's missing (if any) pub missing: Vec, /// 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 { /// ID of the deliverable pub id: String, /// Name of the deliverable pub name: String, /// Type: "deliverable", "repository", "tasks" pub deliverable_type: String, /// Whether it's met pub met: bool, /// Additional details pub details: Option, } /// Check if all required deliverables for the current phase are met pub fn check_deliverables_met( phase: &str, contract_type: &str, completed_deliverables: &[String], tasks: &[TaskInfo], has_repository: bool, ) -> DeliverableCheckResult { let mut required_items = Vec::new(); let mut missing = Vec::new(); // Get the deliverables for this contract type and phase let phase_config = get_phase_deliverables_for_type(phase, contract_type); // Check required deliverables for this phase for deliverable in &phase_config.deliverables { if deliverable.priority == DeliverablePriority::Required { let is_complete = completed_deliverables.contains(&deliverable.id); required_items.push(DeliverableItem { id: deliverable.id.clone(), name: deliverable.name.clone(), deliverable_type: "deliverable".to_string(), met: is_complete, details: if is_complete { Some("Marked complete".to_string()) } else { None }, }); if !is_complete { missing.push(format!( "Mark '{}' as complete (required)", deliverable.name )); } } } // Check repository for phases that require it if phase_config.requires_repository { required_items.push(DeliverableItem { id: "repository".to_string(), 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 phase_config.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_items.push(DeliverableItem { id: "tasks".to_string(), 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 )); } } } let deliverables_met = required_items.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: required_items, 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 { 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 pub fn should_auto_progress( phase: &str, contract_type: &str, completed_deliverables: &[String], tasks: &[TaskInfo], has_repository: bool, autonomous_loop: bool, ) -> AutoProgressDecision { let check = check_deliverables_met( phase, contract_type, completed_deliverables, tasks, has_repository, ); 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, /// 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( "\nUse `mark_deliverable_complete` to mark deliverables as complete when ready.\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) ); // Deliverables md.push_str("### Deliverables\n"); for status in &checklist.deliverables { let check = if status.completed { "+" } else { "-" }; let priority_label = match status.priority { DeliverablePriority::Required => " (required)", DeliverablePriority::Recommended => " (recommended)", DeliverablePriority::Optional => " (optional)", }; if status.completed { md.push_str(&format!("[{}] {} - completed\n", check, status.name)); } else { md.push_str(&format!("[{}] {}{}\n", check, status.name, priority_label)); } } // Repository status if checklist.repository_required { let check = if checklist.has_repository { "+" } else { "-" }; md.push_str(&format!("[{}] Repository configured (required)\n", check)); } // Task stats for execute phase if let Some(ref stats) = checklist.task_stats { md.push_str("\n### Task Progress\n"); md.push_str(&format!("- Total: {}\n", stats.total)); md.push_str(&format!("- Done: {}\n", stats.done)); if stats.pending > 0 { md.push_str(&format!("- Pending: {}\n", stats.pending)); } if stats.running > 0 { md.push_str(&format!("- Running: {}\n", stats.running)); } if stats.failed > 0 { md.push_str(&format!("- Failed: {}\n", stats.failed)); } } // Summary md.push_str(&format!( "\n**Status**: {} ({}% complete)\n", checklist.summary, checklist.completion_percentage )); // Suggestions if !checklist.suggestions.is_empty() { md.push_str("\n**Next Steps**:\n"); for suggestion in &checklist.suggestions { md.push_str(&format!("- {}\n", suggestion)); } } md } fn capitalize(s: &str) -> String { let mut chars = s.chars(); match chars.next() { None => String::new(), Some(first) => first.to_uppercase().collect::() + chars.as_str(), } } #[cfg(test)] mod tests { use super::*; #[test] fn test_get_phase_deliverables_simple() { let plan = get_phase_deliverables_for_type("plan", "simple"); assert_eq!(plan.phase, "plan"); assert!(plan.requires_repository); assert_eq!(plan.deliverables.len(), 1); assert_eq!(plan.deliverables[0].id, "plan-document"); assert_eq!(plan.deliverables[0].priority, DeliverablePriority::Required); let execute = get_phase_deliverables_for_type("execute", "simple"); assert_eq!(execute.phase, "execute"); assert!(execute.requires_repository); assert!(execute.requires_tasks); assert_eq!(execute.deliverables.len(), 1); assert_eq!(execute.deliverables[0].id, "pull-request"); } #[test] fn test_get_phase_deliverables_specification() { let research = get_phase_deliverables_for_type("research", "specification"); assert_eq!(research.deliverables.len(), 1); assert_eq!(research.deliverables[0].id, "research-notes"); let specify = get_phase_deliverables_for_type("specify", "specification"); assert_eq!(specify.deliverables.len(), 1); assert_eq!(specify.deliverables[0].id, "requirements-document"); let review = get_phase_deliverables_for_type("review", "specification"); assert_eq!(review.deliverables.len(), 1); assert_eq!(review.deliverables[0].id, "release-notes"); } #[test] fn test_get_phase_deliverables_execute_type() { let execute = get_phase_deliverables_for_type("execute", "execute"); assert!(execute.deliverables.is_empty()); assert!(execute.requires_repository); assert!(execute.requires_tasks); } #[test] fn test_check_deliverables_met() { // No deliverables marked complete let result = check_deliverables_met("plan", "simple", &[], &[], true); assert!(!result.deliverables_met); assert!(!result.missing.is_empty()); // Deliverable marked complete let completed = vec!["plan-document".to_string()]; let result = check_deliverables_met("plan", "simple", &completed, &[], true); assert!(result.deliverables_met); assert!(result.ready_to_advance); } #[test] fn test_phase_checklist() { let completed = vec!["plan-document".to_string()]; let checklist = get_phase_checklist_for_type("plan", &completed, &[], true, "simple"); assert_eq!(checklist.completion_percentage, 100); assert!(checklist.deliverables[0].completed); } }