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 | |
| parent | 139be135c2086d725e4f040e744bb25acd436549 (diff) | |
| download | soryu-1b692b8cde4a888c8a35af69231f181b57bf5619.tar.gz soryu-1b692b8cde4a888c8a35af69231f181b57bf5619.zip | |
Fix: Cleanup old chain code
Diffstat (limited to 'makima/src/orchestration')
| -rw-r--r-- | makima/src/orchestration/engine.rs | 2 | ||||
| -rw-r--r-- | makima/src/orchestration/mod.rs | 4 | ||||
| -rw-r--r-- | makima/src/orchestration/planner.rs | 120 | ||||
| -rw-r--r-- | makima/src/orchestration/verifier.rs | 27 |
4 files changed, 143 insertions, 10 deletions
diff --git a/makima/src/orchestration/engine.rs b/makima/src/orchestration/engine.rs index c794156..470db40 100644 --- a/makima/src/orchestration/engine.rs +++ b/makima/src/orchestration/engine.rs @@ -110,8 +110,8 @@ impl DirectiveEngine { /// Create a new directive engine. pub fn new(pool: PgPool) -> Self { Self { + planner: ChainPlanner::new(pool.clone()), pool, - planner: ChainPlanner::new(), event_tx: None, } } diff --git a/makima/src/orchestration/mod.rs b/makima/src/orchestration/mod.rs index 41913ca..8c21089 100644 --- a/makima/src/orchestration/mod.rs +++ b/makima/src/orchestration/mod.rs @@ -19,8 +19,8 @@ mod planner; mod verifier; pub use engine::{DirectiveEngine, EngineError}; -pub use planner::{ChainPlanner, PlannerError}; +pub use planner::{ChainPlanner, GeneratedSpec, PlannerError}; pub use verifier::{ auto_detect_verifiers, CompositeEvaluator, ConfidenceLevel, EvaluationResult, Verifier, - VerifierError, VerifierResult, VerifierType, + VerifierError, VerifierInfo, VerifierResult, VerifierType, }; 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(); diff --git a/makima/src/orchestration/verifier.rs b/makima/src/orchestration/verifier.rs index e98da50..bc29e47 100644 --- a/makima/src/orchestration/verifier.rs +++ b/makima/src/orchestration/verifier.rs @@ -290,6 +290,18 @@ pub enum VerifierError { Io(#[from] std::io::Error), } +/// Information about a verifier for serialization and database storage. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct VerifierInfo { + pub name: String, + pub verifier_type: String, + pub command: String, + pub working_directory: Option<String>, + pub detect_files: Vec<String>, + pub weight: f64, + pub required: bool, +} + /// Verifier trait for pluggable verification implementations. #[async_trait] pub trait Verifier: Send + Sync { @@ -299,6 +311,9 @@ pub trait Verifier: Send + Sync { /// Get the type of this verifier. fn verifier_type(&self) -> VerifierType; + /// Get serializable info about this verifier. + fn info(&self) -> VerifierInfo; + /// Check if this verifier is applicable to the given repository. async fn is_applicable(&self, repo_path: &Path) -> bool; @@ -393,6 +408,18 @@ impl Verifier for CommandVerifier { self.verifier_type.clone() } + fn info(&self) -> VerifierInfo { + VerifierInfo { + name: self.name.clone(), + verifier_type: self.verifier_type.as_str().to_string(), + command: self.command.clone(), + working_directory: self.working_dir.clone(), + detect_files: self.applicable_patterns.clone(), + weight: 1.0, + required: self.required, + } + } + async fn is_applicable(&self, repo_path: &Path) -> bool { if self.applicable_patterns.is_empty() { return true; |
