diff options
| author | soryu <soryu@soryu.co> | 2026-02-06 20:06:30 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-02-06 20:15:27 +0000 |
| commit | 1b692b8cde4a888c8a35af69231f181b57bf5619 (patch) | |
| tree | 74ce25ce6ee5fb4536b53404e1a0ae923e85c30d /makima/src/orchestration/planner.rs | |
| parent | 139be135c2086d725e4f040e744bb25acd436549 (diff) | |
| download | soryu-1b692b8cde4a888c8a35af69231f181b57bf5619.tar.gz soryu-1b692b8cde4a888c8a35af69231f181b57bf5619.zip | |
Fix: Cleanup old chain code
Diffstat (limited to 'makima/src/orchestration/planner.rs')
| -rw-r--r-- | makima/src/orchestration/planner.rs | 120 |
1 files changed, 113 insertions, 7 deletions
diff --git a/makima/src/orchestration/planner.rs b/makima/src/orchestration/planner.rs index cdca8a0..aec2e48 100644 --- a/makima/src/orchestration/planner.rs +++ b/makima/src/orchestration/planner.rs @@ -93,22 +93,38 @@ pub struct GeneratedChain { pub steps: Vec<GeneratedStep>, } +/// Generated specification from LLM. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GeneratedSpec { + /// Generated title (if improved from goal) + pub title: Option<String>, + /// Structured requirements + pub requirements: serde_json::Value, + /// Structured acceptance criteria + pub acceptance_criteria: serde_json::Value, + /// Constraints extracted from goal + pub constraints: Option<serde_json::Value>, +} + /// Chain planner for LLM-based plan generation. pub struct ChainPlanner { /// Default step types to suggest (reserved for future use) #[allow(dead_code)] default_step_types: Vec<String>, + /// Database pool for persistence + #[allow(dead_code)] + pool: Option<sqlx::PgPool>, } impl Default for ChainPlanner { fn default() -> Self { - Self::new() + Self::without_pool() } } impl ChainPlanner { - /// Create a new chain planner. - pub fn new() -> Self { + /// Create a new chain planner without database pool. + pub fn without_pool() -> Self { Self { default_step_types: vec![ "research".to_string(), @@ -118,9 +134,82 @@ impl ChainPlanner { "review".to_string(), "document".to_string(), ], + pool: None, } } + /// Create a new chain planner (backwards compatible). + pub fn new(pool: sqlx::PgPool) -> Self { + Self { + default_step_types: vec![ + "research".to_string(), + "design".to_string(), + "implement".to_string(), + "test".to_string(), + "review".to_string(), + "document".to_string(), + ], + pool: Some(pool), + } + } + + /// Generate a specification from a directive's goal. + /// + /// Analyzes the goal text to produce structured requirements and + /// acceptance criteria. In production, this would call an LLM for + /// richer spec generation. + pub async fn generate_spec( + &self, + directive: &Directive, + ) -> Result<GeneratedSpec, PlannerError> { + // Build a prompt for spec generation + let prompt = format!( + r#"Analyze this goal and generate structured requirements and acceptance criteria. + +Goal: {} + +Generate a JSON response with: +- title: A concise title +- requirements: Array of {{id, title, description, priority, category}} +- acceptance_criteria: Array of {{id, requirementIds, description, testable, verificationMethod}} +- constraints: Array of constraint strings"#, + directive.goal + ); + + // For now, generate a basic spec from the goal text. + // When LLM integration is available, this will call the LLM with the prompt. + let _prompt = prompt; // Will be used when LLM is wired up + + let title = generate_title_from_goal(&directive.goal); + + let requirements = serde_json::json!([ + { + "id": "REQ-001", + "title": title, + "description": directive.goal, + "priority": "required", + "category": "core" + } + ]); + + let acceptance_criteria = serde_json::json!([ + { + "id": "AC-001", + "requirementIds": ["REQ-001"], + "description": format!("Goal is achieved: {}", directive.goal), + "testable": true, + "verificationMethod": "manual" + } + ]); + + Ok(GeneratedSpec { + title: Some(title), + requirements, + acceptance_criteria, + constraints: None, + }) + } + /// Build a planning prompt for the LLM. pub fn build_planning_prompt(&self, directive: &Directive) -> String { let requirements: Vec<String> = directive @@ -578,6 +667,23 @@ Use the same JSON format as before. Do not include already completed steps."#, } } +/// Generate a concise title from a goal string. +fn generate_title_from_goal(goal: &str) -> String { + // Take the first sentence or first 80 chars + let title = if let Some(pos) = goal.find('.') { + if pos < 100 { + &goal[..pos] + } else { + &goal[..80.min(goal.len())] + } + } else if goal.len() > 80 { + &goal[..80] + } else { + goal + }; + title.trim().to_string() +} + /// Extract JSON from LLM response (handles markdown code blocks). fn extract_json_from_response(response: &str) -> Result<String, PlannerError> { // Try to find JSON in code block @@ -650,14 +756,14 @@ mod tests { #[test] fn test_validate_chain_valid() { - let planner = ChainPlanner::new(); + let planner = ChainPlanner::without_pool(); let chain = make_test_chain(); assert!(planner.validate_chain(&chain).is_ok()); } #[test] fn test_validate_chain_invalid_dependency() { - let planner = ChainPlanner::new(); + let planner = ChainPlanner::without_pool(); let mut chain = make_test_chain(); chain.steps[1].depends_on = vec!["nonexistent".to_string()]; @@ -667,7 +773,7 @@ mod tests { #[test] fn test_validate_chain_cycle() { - let planner = ChainPlanner::new(); + let planner = ChainPlanner::without_pool(); let chain = GeneratedChain { name: "cyclic".to_string(), description: "Has cycle".to_string(), @@ -705,7 +811,7 @@ mod tests { #[test] fn test_topological_sort() { - let planner = ChainPlanner::new(); + let planner = ChainPlanner::without_pool(); let chain = make_test_chain(); let order = planner.topological_sort(&chain).unwrap(); |
