summaryrefslogtreecommitdiff
path: root/makima/src/orchestration
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-06 20:06:30 +0000
committersoryu <soryu@soryu.co>2026-02-06 20:15:27 +0000
commit1b692b8cde4a888c8a35af69231f181b57bf5619 (patch)
tree74ce25ce6ee5fb4536b53404e1a0ae923e85c30d /makima/src/orchestration
parent139be135c2086d725e4f040e744bb25acd436549 (diff)
downloadsoryu-1b692b8cde4a888c8a35af69231f181b57bf5619.tar.gz
soryu-1b692b8cde4a888c8a35af69231f181b57bf5619.zip
Fix: Cleanup old chain code
Diffstat (limited to 'makima/src/orchestration')
-rw-r--r--makima/src/orchestration/engine.rs2
-rw-r--r--makima/src/orchestration/mod.rs4
-rw-r--r--makima/src/orchestration/planner.rs120
-rw-r--r--makima/src/orchestration/verifier.rs27
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;