diff options
| author | soryu <soryu@soryu.co> | 2026-01-24 15:18:21 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-01-24 16:11:39 +0000 |
| commit | abc5fbed331ea527ccaac0cd4120c4a0650f8bc0 (patch) | |
| tree | ebfaf1e5c76361abcc0423edb90697725d7839a4 /makima/src/llm | |
| parent | 579c983d3efb8f1414ffb45b9e031f741cce5f76 (diff) | |
| download | soryu-abc5fbed331ea527ccaac0cd4120c4a0650f8bc0.tar.gz soryu-abc5fbed331ea527ccaac0cd4120c4a0650f8bc0.zip | |
feat: Simplify phase deliverables and add 'execute' contract type
## Changes
### Phase Deliverables Simplified
- **Simple contract type**:
- Plan phase: Only 'Plan' deliverable (required)
- Execute phase: Only 'PR' deliverable (required)
- **Specification contract type**:
- Research phase: Only 'Research Notes' deliverable (required)
- Specify phase: Only 'Requirements Document' deliverable (required)
- Plan phase: Only 'Plan' deliverable (required)
- Execute phase: Only 'PR' deliverable (required)
- Review phase: Only 'Release Notes' deliverable (required)
### New 'execute' Contract Type
- Only has 'execute' phase (no plan or review phases)
- NO deliverables at all - executes tasks directly
- Added to ContractType enum with proper Display/FromStr implementations
- Added helper methods: `initial_phase()`, `terminal_phase()`
### API Updates
- Added `get_phase_deliverables_for_type()` for contract-type-aware deliverables
- Added `get_phase_checklist_for_type()` for contract-type-aware checklists
- Added `check_phase_completion_for_type()` for contract-type-aware completion checks
- Legacy functions remain for backward compatibility (default to 'simple' type)
### Files Modified
- makima/src/llm/phase_guidance.rs - Core deliverable definitions
- makima/src/db/models.rs - ContractType enum and Contract methods
- makima/src/llm/mod.rs - Export new functions
- makima/src/server/handlers/contract_daemon.rs - Use type-aware functions
- makima/src/server/handlers/contract_chat.rs - Use type-aware functions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'makima/src/llm')
| -rw-r--r-- | makima/src/llm/mod.rs | 3 | ||||
| -rw-r--r-- | makima/src/llm/phase_guidance.rs | 347 |
2 files changed, 259 insertions, 91 deletions
diff --git a/makima/src/llm/mod.rs b/makima/src/llm/mod.rs index c4f8e50..6167a42 100644 --- a/makima/src/llm/mod.rs +++ b/makima/src/llm/mod.rs @@ -19,7 +19,8 @@ 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, format_checklist_markdown, get_phase_checklist, get_phase_deliverables, + check_phase_completion, check_phase_completion_for_type, format_checklist_markdown, + get_phase_checklist, get_phase_checklist_for_type, get_phase_deliverables, get_phase_deliverables_for_type, 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 0d4bb3d..df7bd24 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".to_string(), + guidance: "Unknown phase for specification contract type".to_string(), }, } } -/// Build a phase checklist comparing expected vs actual deliverables +/// 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: "The 'execute' contract type only supports the 'execute' phase.".to_string(), + }, + } +} + +/// 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() @@ -572,26 +649,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); + + // 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); + } - 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_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() { - let checklist = get_phase_checklist("research", &[], &[], false); + 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 +744,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"); + } } |
