summaryrefslogtreecommitdiff
path: root/makima/src/llm/phase_guidance.rs
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/llm/phase_guidance.rs')
-rw-r--r--makima/src/llm/phase_guidance.rs643
1 files changed, 553 insertions, 90 deletions
diff --git a/makima/src/llm/phase_guidance.rs b/makima/src/llm/phase_guidance.rs
index 0d4bb3d..03f7c76 100644
--- a/makima/src/llm/phase_guidance.rs
+++ b/makima/src/llm/phase_guidance.rs
@@ -2,6 +2,22 @@
//!
//! 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;
@@ -109,8 +125,86 @@ pub struct TaskInfo {
pub status: String,
}
-/// Get phase deliverables configuration
+/// 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
+///
+/// ## Contract Types
+///
+/// ### Simple
+/// - Plan: Only "Plan" deliverable (required)
+/// - Execute: Only "PR" deliverable (required)
+///
+/// ### Specification
+/// - Research: Only "Research Notes" deliverable (required)
+/// - Specify: Only "Requirements Document" deliverable (required)
+/// - Plan: Only "Plan" deliverable (required)
+/// - Execute: Only "PR" deliverable (required)
+/// - Review: Only "Release Notes" deliverable (required)
+///
+/// ### Execute
+/// - Execute: No deliverables at all
+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 deliverables for 'simple' contract type
+/// - Plan phase: Only "Plan" deliverable (required)
+/// - Execute phase: Only "PR" deliverable (required)
+fn get_simple_type_deliverables(phase: &str) -> PhaseDeliverables {
+ match phase {
+ "plan" => PhaseDeliverables {
+ phase: "plan".to_string(),
+ recommended_files: vec![
+ RecommendedFile {
+ template_id: "plan".to_string(),
+ name_suggestion: "Plan".to_string(),
+ priority: FilePriority::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(),
+ recommended_files: vec![
+ RecommendedFile {
+ template_id: "pr".to_string(),
+ name_suggestion: "PR".to_string(),
+ priority: FilePriority::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(),
+ recommended_files: vec![],
+ requires_repository: false,
+ requires_tasks: false,
+ guidance: "Unknown phase for simple contract type".to_string(),
+ },
+ }
+}
+
+/// Get deliverables for 'specification' contract type
+/// - Research: Only "Research Notes" deliverable (required)
+/// - Specify: Only "Requirements Document" deliverable (required)
+/// - Plan: Only "Plan" deliverable (required)
+/// - Execute: Only "PR" deliverable (required)
+/// - Review: Only "Release Notes" deliverable (required)
+fn get_specification_type_deliverables(phase: &str) -> PhaseDeliverables {
match phase {
"research" => PhaseDeliverables {
phase: "research".to_string(),
@@ -118,25 +212,13 @@ pub fn get_phase_deliverables(phase: &str) -> PhaseDeliverables {
RecommendedFile {
template_id: "research-notes".to_string(),
name_suggestion: "Research Notes".to_string(),
- priority: FilePriority::Recommended,
+ priority: FilePriority::Required,
description: "Document findings and insights during research".to_string(),
},
- RecommendedFile {
- template_id: "competitor-analysis".to_string(),
- name_suggestion: "Competitor Analysis".to_string(),
- priority: FilePriority::Recommended,
- description: "Analyze competitors and market positioning".to_string(),
- },
- RecommendedFile {
- template_id: "user-research".to_string(),
- name_suggestion: "User Research".to_string(),
- priority: FilePriority::Optional,
- description: "Document user interviews and persona insights".to_string(),
- },
],
requires_repository: false,
requires_tasks: false,
- guidance: "Focus on understanding the problem space, gathering information, and documenting findings. Create at least one research document before moving to Specify phase.".to_string(),
+ 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(),
@@ -147,74 +229,38 @@ pub fn get_phase_deliverables(phase: &str) -> PhaseDeliverables {
priority: FilePriority::Required,
description: "Define functional and non-functional requirements".to_string(),
},
- RecommendedFile {
- template_id: "user-stories".to_string(),
- name_suggestion: "User Stories".to_string(),
- priority: FilePriority::Recommended,
- description: "Define features from the user's perspective".to_string(),
- },
- RecommendedFile {
- template_id: "acceptance-criteria".to_string(),
- name_suggestion: "Acceptance Criteria".to_string(),
- priority: FilePriority::Recommended,
- description: "Define testable conditions for completion".to_string(),
- },
],
requires_repository: false,
requires_tasks: false,
- guidance: "Define what needs to be built with clear requirements and acceptance criteria. Ensure specifications are detailed enough for planning.".to_string(),
+ 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(),
recommended_files: vec![
RecommendedFile {
- template_id: "architecture".to_string(),
- name_suggestion: "Architecture Document".to_string(),
- priority: FilePriority::Recommended,
- description: "Document system architecture and design decisions".to_string(),
- },
- RecommendedFile {
- template_id: "task-breakdown".to_string(),
- name_suggestion: "Task Breakdown".to_string(),
+ template_id: "plan".to_string(),
+ name_suggestion: "Plan".to_string(),
priority: FilePriority::Required,
- description: "Break down work into implementable tasks".to_string(),
- },
- RecommendedFile {
- template_id: "technical-design".to_string(),
- name_suggestion: "Technical Design".to_string(),
- priority: FilePriority::Optional,
- description: "Detailed technical specification".to_string(),
+ description: "Implementation plan detailing the approach and tasks".to_string(),
},
],
requires_repository: true,
requires_tasks: false,
- guidance: "Design the solution and break down work into tasks. A repository must be configured before moving to Execute phase.".to_string(),
+ 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(),
recommended_files: vec![
RecommendedFile {
- template_id: "dev-notes".to_string(),
- name_suggestion: "Development Notes".to_string(),
- priority: FilePriority::Recommended,
- description: "Track implementation details and decisions".to_string(),
- },
- RecommendedFile {
- template_id: "test-plan".to_string(),
- name_suggestion: "Test Plan".to_string(),
- priority: FilePriority::Optional,
- description: "Document testing strategy and test cases".to_string(),
- },
- RecommendedFile {
- template_id: "implementation-log".to_string(),
- name_suggestion: "Implementation Log".to_string(),
- priority: FilePriority::Optional,
- description: "Chronological log of implementation progress".to_string(),
+ template_id: "pr".to_string(),
+ name_suggestion: "PR".to_string(),
+ priority: FilePriority::Required,
+ description: "Pull request with the implemented changes".to_string(),
},
],
requires_repository: true,
requires_tasks: true,
- guidance: "Execute the planned tasks, implement features, and track progress. Complete all tasks before moving to Review phase.".to_string(),
+ 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(),
@@ -225,41 +271,61 @@ pub fn get_phase_deliverables(phase: &str) -> PhaseDeliverables {
priority: FilePriority::Required,
description: "Document changes for release communication".to_string(),
},
- RecommendedFile {
- template_id: "review-checklist".to_string(),
- name_suggestion: "Review Checklist".to_string(),
- priority: FilePriority::Recommended,
- description: "Comprehensive checklist for code and feature review".to_string(),
- },
- RecommendedFile {
- template_id: "retrospective".to_string(),
- name_suggestion: "Retrospective".to_string(),
- priority: FilePriority::Optional,
- description: "Reflect on the project and capture learnings".to_string(),
- },
],
requires_repository: false,
requires_tasks: false,
- guidance: "Review completed work, document the release, and conduct a retrospective. The contract can be completed after review.".to_string(),
+ 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(),
+ recommended_files: vec![],
+ requires_repository: false,
+ requires_tasks: false,
+ guidance: "Unknown phase for specification contract type".to_string(),
+ },
+ }
+}
+
+/// Get deliverables for 'execute' contract type
+/// - Execute phase only: No deliverables at all
+fn get_execute_type_deliverables(phase: &str) -> PhaseDeliverables {
+ match phase {
+ "execute" => PhaseDeliverables {
+ phase: "execute".to_string(),
+ recommended_files: 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(),
recommended_files: vec![],
requires_repository: false,
requires_tasks: false,
- guidance: "Unknown phase".to_string(),
+ guidance: "The 'execute' contract type only supports the 'execute' phase.".to_string(),
},
}
}
-/// Build a phase checklist comparing expected vs actual deliverables
+/// Build a phase checklist comparing expected vs actual deliverables (legacy, defaults to "simple")
pub fn get_phase_checklist(
phase: &str,
files: &[FileInfo],
tasks: &[TaskInfo],
has_repository: bool,
) -> PhaseChecklist {
- let deliverables = get_phase_deliverables(phase);
+ get_phase_checklist_for_type(phase, files, tasks, has_repository, "simple")
+}
+
+/// Build a phase checklist comparing expected vs actual deliverables for a specific contract type
+pub fn get_phase_checklist_for_type(
+ phase: &str,
+ files: &[FileInfo],
+ tasks: &[TaskInfo],
+ has_repository: bool,
+ contract_type: &str,
+) -> PhaseChecklist {
+ let deliverables = get_phase_deliverables_for_type(phase, contract_type);
// Match files to expected deliverables
let file_deliverables: Vec<DeliverableStatus> = deliverables
@@ -475,14 +541,25 @@ fn generate_phase_summary(
}
}
-/// Check if phase targets are met for transition
+/// Check if phase targets are met for transition (legacy, defaults to "simple")
pub fn check_phase_completion(
phase: &str,
files: &[FileInfo],
tasks: &[TaskInfo],
has_repository: bool,
) -> bool {
- let checklist = get_phase_checklist(phase, files, tasks, has_repository);
+ check_phase_completion_for_type(phase, files, tasks, has_repository, "simple")
+}
+
+/// Check if phase targets are met for transition for a specific contract type
+pub fn check_phase_completion_for_type(
+ phase: &str,
+ files: &[FileInfo],
+ tasks: &[TaskInfo],
+ has_repository: bool,
+ contract_type: &str,
+) -> bool {
+ let checklist = get_phase_checklist_for_type(phase, files, tasks, has_repository, contract_type);
// Check required files are complete
let required_files_complete = checklist.file_deliverables.iter()
@@ -502,6 +579,302 @@ pub fn check_phase_completion(
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));
@@ -572,26 +945,93 @@ mod tests {
use super::*;
#[test]
- fn test_get_phase_deliverables() {
- let research = get_phase_deliverables("research");
+ fn test_get_phase_deliverables_simple() {
+ // Simple contract type: Plan phase has only "Plan" deliverable
+ let plan = get_phase_deliverables_for_type("plan", "simple");
+ assert_eq!(plan.phase, "plan");
+ assert!(plan.requires_repository);
+ assert_eq!(plan.recommended_files.len(), 1);
+ assert_eq!(plan.recommended_files[0].template_id, "plan");
+ assert_eq!(plan.recommended_files[0].priority, FilePriority::Required);
+
+ // Simple contract type: Execute phase has only "PR" deliverable
+ 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.recommended_files.len(), 1);
+ assert_eq!(execute.recommended_files[0].template_id, "pr");
+ assert_eq!(execute.recommended_files[0].priority, FilePriority::Required);
+ }
+
+ #[test]
+ fn test_get_phase_deliverables_specification() {
+ // Specification: Research phase has only "Research Notes" deliverable
+ let research = get_phase_deliverables_for_type("research", "specification");
assert_eq!(research.phase, "research");
assert!(!research.requires_repository);
- assert_eq!(research.recommended_files.len(), 3);
+ assert_eq!(research.recommended_files.len(), 1);
+ assert_eq!(research.recommended_files[0].template_id, "research-notes");
+ assert_eq!(research.recommended_files[0].priority, FilePriority::Required);
- let plan = get_phase_deliverables("plan");
- assert!(plan.requires_repository);
- assert!(plan.recommended_files.iter().any(|f| f.template_id == "task-breakdown"));
+ // Specification: Specify phase has only "Requirements Document" deliverable
+ let specify = get_phase_deliverables_for_type("specify", "specification");
+ assert_eq!(specify.phase, "specify");
+ assert_eq!(specify.recommended_files.len(), 1);
+ assert_eq!(specify.recommended_files[0].template_id, "requirements");
+ assert_eq!(specify.recommended_files[0].priority, FilePriority::Required);
+
+ // Specification: Plan phase has only "Plan" deliverable
+ let plan = get_phase_deliverables_for_type("plan", "specification");
+ assert_eq!(plan.phase, "plan");
+ assert_eq!(plan.recommended_files.len(), 1);
+ assert_eq!(plan.recommended_files[0].template_id, "plan");
+
+ // Specification: Execute phase has only "PR" deliverable
+ let execute = get_phase_deliverables_for_type("execute", "specification");
+ assert_eq!(execute.phase, "execute");
+ assert_eq!(execute.recommended_files.len(), 1);
+ assert_eq!(execute.recommended_files[0].template_id, "pr");
+
+ // Specification: Review phase has only "Release Notes" deliverable
+ let review = get_phase_deliverables_for_type("review", "specification");
+ assert_eq!(review.phase, "review");
+ assert_eq!(review.recommended_files.len(), 1);
+ assert_eq!(review.recommended_files[0].template_id, "release-notes");
+ assert_eq!(review.recommended_files[0].priority, FilePriority::Required);
}
#[test]
- fn test_phase_checklist_empty() {
- let checklist = get_phase_checklist("research", &[], &[], false);
+ fn test_get_phase_deliverables_execute_type() {
+ // Execute contract type: Only execute phase, NO deliverables
+ let execute = get_phase_deliverables_for_type("execute", "execute");
+ assert_eq!(execute.phase, "execute");
+ assert!(execute.requires_repository);
+ assert!(execute.requires_tasks);
+ assert!(execute.recommended_files.is_empty()); // NO deliverables
+
+ // Execute contract type: Other phases should return empty deliverables
+ let plan = get_phase_deliverables_for_type("plan", "execute");
+ assert!(plan.recommended_files.is_empty());
+ }
+
+ #[test]
+ fn test_phase_checklist_empty_simple() {
+ let checklist = get_phase_checklist_for_type("plan", &[], &[], false, "simple");
assert_eq!(checklist.completion_percentage, 0);
assert!(!checklist.suggestions.is_empty());
}
#[test]
- fn test_check_phase_completion() {
+ fn test_phase_checklist_execute_type_no_deliverables() {
+ // Execute contract type with no file deliverables
+ let checklist = get_phase_checklist_for_type("execute", &[], &[], true, "execute");
+ // Should have no file deliverables
+ assert!(checklist.file_deliverables.is_empty());
+ }
+
+ #[test]
+ fn test_check_phase_completion_specification() {
let files = vec![
FileInfo {
id: Uuid::new_v4(),
@@ -600,8 +1040,31 @@ mod tests {
},
];
- // Specify phase has required file
- let complete = check_phase_completion("specify", &files, &[], false);
+ // Specify phase has required file for specification contract type
+ let complete = check_phase_completion_for_type("specify", &files, &[], false, "specification");
assert!(complete);
}
+
+ #[test]
+ fn test_check_phase_completion_simple() {
+ let files = vec![
+ FileInfo {
+ id: Uuid::new_v4(),
+ name: "Plan".to_string(),
+ contract_phase: Some("plan".to_string()),
+ },
+ ];
+
+ // Plan phase has required "Plan" file for simple contract type
+ let complete = check_phase_completion_for_type("plan", &files, &[], true, "simple");
+ assert!(complete);
+ }
+
+ #[test]
+ fn test_legacy_functions_default_to_simple() {
+ // Legacy get_phase_deliverables defaults to simple
+ let plan = get_phase_deliverables("plan");
+ assert_eq!(plan.recommended_files.len(), 1);
+ assert_eq!(plan.recommended_files[0].template_id, "plan");
+ }
}