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.rs594
1 files changed, 594 insertions, 0 deletions
diff --git a/makima/src/llm/phase_guidance.rs b/makima/src/llm/phase_guidance.rs
new file mode 100644
index 0000000..e2d6cd8
--- /dev/null
+++ b/makima/src/llm/phase_guidance.rs
@@ -0,0 +1,594 @@
+//! Phase guidance and deliverables tracking for contract management.
+//!
+//! This module provides structured guidance for each contract phase, tracking
+//! expected deliverables and completion criteria.
+
+use serde::{Deserialize, Serialize};
+use utoipa::ToSchema;
+use uuid::Uuid;
+
+/// Priority level for recommended deliverables
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "lowercase")]
+pub enum FilePriority {
+ /// Must exist before advancing phase
+ Required,
+ /// Strongly suggested for phase completion
+ Recommended,
+ /// Nice to have, not blocking
+ Optional,
+}
+
+/// A recommended file for a phase
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct RecommendedFile {
+ /// Template ID to create from
+ pub template_id: String,
+ /// Suggested file name
+ pub name_suggestion: String,
+ /// Priority level
+ pub priority: FilePriority,
+ /// Brief description of purpose
+ pub description: String,
+}
+
+/// Expected deliverables for a phase
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct PhaseDeliverables {
+ /// Phase name
+ pub phase: String,
+ /// Recommended files to create
+ pub recommended_files: Vec<RecommendedFile>,
+ /// Whether a repository is required for this phase
+ pub requires_repository: bool,
+ /// Whether tasks should exist in this phase
+ pub requires_tasks: bool,
+ /// Guidance text for this phase
+ pub guidance: String,
+}
+
+/// Status of a deliverable item
+#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
+pub struct DeliverableStatus {
+ /// Template ID
+ pub template_id: String,
+ /// Expected name
+ pub name: String,
+ /// Priority
+ pub priority: FilePriority,
+ /// Whether it has been created
+ pub completed: bool,
+ /// File ID if created
+ pub file_id: Option<Uuid>,
+ /// Actual file name if created
+ pub actual_name: Option<String>,
+}
+
+/// Checklist for phase completion
+#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
+pub struct PhaseChecklist {
+ /// Current phase
+ pub phase: String,
+ /// File deliverables status
+ pub file_deliverables: Vec<DeliverableStatus>,
+ /// 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<TaskStats>,
+ /// Overall completion percentage (0-100)
+ pub completion_percentage: u8,
+ /// Summary message
+ pub summary: String,
+ /// Suggestions for next actions
+ pub suggestions: Vec<String>,
+}
+
+/// 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 file info for checklist building
+pub struct FileInfo {
+ pub id: Uuid,
+ pub name: String,
+ pub contract_phase: Option<String>,
+}
+
+/// Minimal task info for checklist building
+pub struct TaskInfo {
+ pub id: Uuid,
+ pub name: String,
+ pub status: String,
+}
+
+/// Get phase deliverables configuration
+pub fn get_phase_deliverables(phase: &str) -> PhaseDeliverables {
+ match phase {
+ "research" => PhaseDeliverables {
+ phase: "research".to_string(),
+ recommended_files: vec![
+ RecommendedFile {
+ template_id: "research-notes".to_string(),
+ name_suggestion: "Research Notes".to_string(),
+ priority: FilePriority::Recommended,
+ 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(),
+ },
+ "specify" => PhaseDeliverables {
+ phase: "specify".to_string(),
+ recommended_files: vec![
+ RecommendedFile {
+ template_id: "requirements".to_string(),
+ name_suggestion: "Requirements Document".to_string(),
+ 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(),
+ },
+ "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(),
+ 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(),
+ },
+ ],
+ 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(),
+ },
+ "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(),
+ },
+ ],
+ 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(),
+ },
+ "review" => PhaseDeliverables {
+ phase: "review".to_string(),
+ recommended_files: vec![
+ RecommendedFile {
+ template_id: "release-notes".to_string(),
+ name_suggestion: "Release Notes".to_string(),
+ 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(),
+ },
+ _ => PhaseDeliverables {
+ phase: phase.to_string(),
+ recommended_files: vec![],
+ requires_repository: false,
+ requires_tasks: false,
+ guidance: "Unknown phase".to_string(),
+ },
+ }
+}
+
+/// Build a phase checklist comparing expected vs actual deliverables
+pub fn get_phase_checklist(
+ phase: &str,
+ files: &[FileInfo],
+ tasks: &[TaskInfo],
+ has_repository: bool,
+) -> PhaseChecklist {
+ let deliverables = get_phase_deliverables(phase);
+
+ // Match files to expected deliverables
+ let file_deliverables: Vec<DeliverableStatus> = deliverables
+ .recommended_files
+ .iter()
+ .map(|rec| {
+ // Check if a file with matching template ID or similar name exists
+ let matched_file = files.iter().find(|f| {
+ // Match by phase first
+ f.contract_phase.as_deref() == Some(phase) &&
+ // Then by name similarity (case-insensitive contains)
+ (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("-", " ")))
+ });
+
+ DeliverableStatus {
+ template_id: rec.template_id.clone(),
+ name: rec.name_suggestion.clone(),
+ priority: rec.priority,
+ completed: matched_file.is_some(),
+ file_id: matched_file.map(|f| f.id),
+ actual_name: matched_file.map(|f| f.name.clone()),
+ }
+ })
+ .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 files (not optional)
+ for status in &file_deliverables {
+ if status.priority != FilePriority::Optional {
+ total_items += 1;
+ if status.completed {
+ completed_items += 1;
+ }
+ }
+ }
+
+ // Count repository if required
+ if deliverables.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 required files
+ for status in &file_deliverables {
+ if !status.completed {
+ match status.priority {
+ FilePriority::Required => {
+ suggestions.push(format!("Create {} (required)", status.name));
+ }
+ FilePriority::Recommended => {
+ suggestions.push(format!("Consider creating {} (recommended)", status.name));
+ }
+ FilePriority::Optional => {
+ // Don't suggest optional items
+ }
+ }
+ }
+ }
+
+ // Suggest repository if needed
+ if deliverables.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 from the Task Breakdown document".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));
+ }
+ }
+
+ // Generate summary
+ let summary = generate_phase_summary(phase, &file_deliverables, has_repository, &task_stats, completion_percentage);
+
+ PhaseChecklist {
+ phase: phase.to_string(),
+ file_deliverables,
+ has_repository,
+ repository_required: deliverables.requires_repository,
+ task_stats,
+ completion_percentage,
+ summary,
+ suggestions,
+ }
+}
+
+fn generate_phase_summary(
+ phase: &str,
+ deliverables: &[DeliverableStatus],
+ has_repository: bool,
+ task_stats: &Option<TaskStats>,
+ 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. Create research notes or competitor analysis.".to_string()
+ } else {
+ format!("{}/{} research documents created. Consider transitioning to Specify phase.", completed_count, total_count)
+ }
+ }
+ "specify" => {
+ let has_required = deliverables.iter()
+ .filter(|d| d.priority == FilePriority::Required)
+ .all(|d| d.completed);
+
+ if !has_required {
+ "Specify phase requires a Requirements Document before transitioning.".to_string()
+ } else if completion_percentage >= 66 {
+ "Specifications are ready. Consider transitioning to Plan phase.".to_string()
+ } else {
+ format!("{}/{} specification documents created.", completed_count, total_count)
+ }
+ }
+ "plan" => {
+ let has_task_breakdown = deliverables.iter()
+ .any(|d| d.template_id == "task-breakdown" && d.completed);
+
+ if !has_task_breakdown {
+ "Plan phase requires a Task Breakdown document.".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 from the Task Breakdown document.".to_string()
+ } else if stats.done == stats.total {
+ "All tasks complete! Ready for Review phase.".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_release_notes = deliverables.iter()
+ .any(|d| d.template_id == "release-notes" && d.completed);
+
+ if !has_release_notes {
+ "Review phase requires Release Notes before completion.".to_string()
+ } else {
+ "Review documentation complete. Contract can be marked as done.".to_string()
+ }
+ }
+ _ => format!("Phase {} - {}% complete", phase, completion_percentage),
+ }
+}
+
+/// Check if phase targets are met for transition
+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 required files are complete
+ let required_files_complete = checklist.file_deliverables.iter()
+ .filter(|d| d.priority == FilePriority::Required)
+ .all(|d| d.completed);
+
+ // Check repository if required
+ let repository_ok = !checklist.repository_required || checklist.has_repository;
+
+ // Check tasks if in execute phase
+ let tasks_ok = if let Some(stats) = &checklist.task_stats {
+ stats.total > 0 && stats.done == stats.total
+ } else {
+ true
+ };
+
+ required_files_complete && repository_ok && tasks_ok
+}
+
+/// 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));
+
+ // File deliverables
+ md.push_str("### Deliverables\n");
+ for status in &checklist.file_deliverables {
+ let check = if status.completed { "+" } else { "-" };
+ let priority_label = match status.priority {
+ FilePriority::Required => " (required)",
+ FilePriority::Recommended => " (recommended)",
+ FilePriority::Optional => " (optional)",
+ };
+
+ if status.completed {
+ md.push_str(&format!("[{}] {} - \"{}\"\n", check, status.name, status.actual_name.as_deref().unwrap_or("created")));
+ } 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(&format!("\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::<String>() + chars.as_str(),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_get_phase_deliverables() {
+ let research = get_phase_deliverables("research");
+ assert_eq!(research.phase, "research");
+ assert!(!research.requires_repository);
+ assert_eq!(research.recommended_files.len(), 3);
+
+ let plan = get_phase_deliverables("plan");
+ assert!(plan.requires_repository);
+ assert!(plan.recommended_files.iter().any(|f| f.template_id == "task-breakdown"));
+ }
+
+ #[test]
+ fn test_phase_checklist_empty() {
+ let checklist = get_phase_checklist("research", &[], &[], false);
+ assert_eq!(checklist.completion_percentage, 0);
+ assert!(!checklist.suggestions.is_empty());
+ }
+
+ #[test]
+ fn test_check_phase_completion() {
+ let files = vec![
+ FileInfo {
+ id: Uuid::new_v4(),
+ name: "Requirements Document".to_string(),
+ contract_phase: Some("specify".to_string()),
+ },
+ ];
+
+ // Specify phase has required file
+ let complete = check_phase_completion("specify", &files, &[], false);
+ assert!(complete);
+ }
+}