summaryrefslogtreecommitdiff
path: root/makima/src/daemon
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/daemon')
-rw-r--r--makima/src/daemon/api/chain.rs78
-rw-r--r--makima/src/daemon/api/directive.rs285
-rw-r--r--makima/src/daemon/api/mod.rs1
-rw-r--r--makima/src/daemon/chain/dag.rs450
-rw-r--r--makima/src/daemon/chain/mod.rs13
-rw-r--r--makima/src/daemon/chain/parser.rs414
-rw-r--r--makima/src/daemon/chain/runner.rs388
-rw-r--r--makima/src/daemon/cli/chain.rs107
-rw-r--r--makima/src/daemon/cli/mod.rs52
-rw-r--r--makima/src/daemon/mod.rs1
-rw-r--r--makima/src/daemon/skill_installer.rs2
-rw-r--r--makima/src/daemon/skills/chain.md116
-rw-r--r--makima/src/daemon/skills/chain_directive.md224
-rw-r--r--makima/src/daemon/skills/mod.rs4
14 files changed, 286 insertions, 1849 deletions
diff --git a/makima/src/daemon/api/chain.rs b/makima/src/daemon/api/chain.rs
deleted file mode 100644
index c37c980..0000000
--- a/makima/src/daemon/api/chain.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-//! Chain API methods.
-
-use uuid::Uuid;
-
-use super::client::{ApiClient, ApiError};
-use super::supervisor::JsonValue;
-use crate::db::models::CreateChainRequest;
-
-impl ApiClient {
- /// Create a new chain with contracts.
- pub async fn create_chain(&self, req: CreateChainRequest) -> Result<JsonValue, ApiError> {
- self.post("/api/v1/chains", &req).await
- }
-
- /// List all chains for the authenticated user.
- pub async fn list_chains(
- &self,
- status: Option<&str>,
- limit: i32,
- ) -> Result<JsonValue, ApiError> {
- let mut params = Vec::new();
- if let Some(s) = status {
- params.push(format!("status={}", s));
- }
- params.push(format!("limit={}", limit));
- let query_string = format!("?{}", params.join("&"));
- self.get(&format!("/api/v1/chains{}", query_string)).await
- }
-
- /// Get a chain by ID.
- pub async fn get_chain(&self, chain_id: Uuid) -> Result<JsonValue, ApiError> {
- self.get(&format!("/api/v1/chains/{}", chain_id)).await
- }
-
- /// Get contracts in a chain.
- pub async fn get_chain_contracts(&self, chain_id: Uuid) -> Result<JsonValue, ApiError> {
- self.get(&format!("/api/v1/chains/{}/contracts", chain_id))
- .await
- }
-
- /// Get chain DAG structure for visualization.
- pub async fn get_chain_graph(&self, chain_id: Uuid) -> Result<JsonValue, ApiError> {
- self.get(&format!("/api/v1/chains/{}/graph", chain_id))
- .await
- }
-
- /// Archive a chain.
- pub async fn archive_chain(&self, chain_id: Uuid) -> Result<JsonValue, ApiError> {
- self.delete_with_response(&format!("/api/v1/chains/{}", chain_id))
- .await
- }
-
- /// Start a chain (creates root contracts and optionally a supervisor).
- pub async fn start_chain(&self, chain_id: Uuid) -> Result<JsonValue, ApiError> {
- self.post_empty(&format!("/api/v1/chains/{}/start", chain_id))
- .await
- }
-
- /// Start a chain with supervisor enabled.
- pub async fn start_chain_with_supervisor(
- &self,
- chain_id: Uuid,
- repository_url: Option<&str>,
- ) -> Result<JsonValue, ApiError> {
- #[derive(serde::Serialize)]
- #[serde(rename_all = "camelCase")]
- struct StartRequest<'a> {
- with_supervisor: bool,
- repository_url: Option<&'a str>,
- }
- let req = StartRequest {
- with_supervisor: true,
- repository_url,
- };
- self.post(&format!("/api/v1/chains/{}/start", chain_id), &req)
- .await
- }
-}
diff --git a/makima/src/daemon/api/directive.rs b/makima/src/daemon/api/directive.rs
index 5281d21..48762d6 100644
--- a/makima/src/daemon/api/directive.rs
+++ b/makima/src/daemon/api/directive.rs
@@ -159,4 +159,289 @@ impl ApiClient {
)
.await
}
+
+ // =========================================================================
+ // Chain operations
+ // =========================================================================
+
+ /// Force chain regeneration (replan).
+ pub async fn replan_directive_chain(
+ &self,
+ directive_id: Uuid,
+ ) -> Result<JsonValue, ApiError> {
+ self.post_empty(&format!(
+ "/api/v1/directives/{}/chain/replan",
+ directive_id
+ ))
+ .await
+ }
+
+ // =========================================================================
+ // Step management
+ // =========================================================================
+
+ /// Add a step to a directive's chain.
+ pub async fn add_directive_step(
+ &self,
+ directive_id: Uuid,
+ name: &str,
+ description: Option<&str>,
+ step_type: Option<&str>,
+ depends_on: Option<Vec<Uuid>>,
+ ) -> Result<JsonValue, ApiError> {
+ #[derive(serde::Serialize)]
+ #[serde(rename_all = "camelCase")]
+ struct AddStepReq<'a> {
+ name: &'a str,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ description: Option<&'a str>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ step_type: Option<&'a str>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ depends_on: Option<Vec<Uuid>>,
+ }
+ let req = AddStepReq {
+ name,
+ description,
+ step_type,
+ depends_on,
+ };
+ self.post(
+ &format!("/api/v1/directives/{}/chain/steps", directive_id),
+ &req,
+ )
+ .await
+ }
+
+ /// Get a step by ID.
+ pub async fn get_directive_step(
+ &self,
+ directive_id: Uuid,
+ step_id: Uuid,
+ ) -> Result<JsonValue, ApiError> {
+ self.get(&format!(
+ "/api/v1/directives/{}/chain/steps/{}",
+ directive_id, step_id
+ ))
+ .await
+ }
+
+ /// Update a step.
+ pub async fn update_directive_step(
+ &self,
+ directive_id: Uuid,
+ step_id: Uuid,
+ update: serde_json::Value,
+ ) -> Result<JsonValue, ApiError> {
+ self.put(
+ &format!(
+ "/api/v1/directives/{}/chain/steps/{}",
+ directive_id, step_id
+ ),
+ &update,
+ )
+ .await
+ }
+
+ /// Delete a step.
+ pub async fn delete_directive_step(
+ &self,
+ directive_id: Uuid,
+ step_id: Uuid,
+ ) -> Result<(), ApiError> {
+ self.delete(&format!(
+ "/api/v1/directives/{}/chain/steps/{}",
+ directive_id, step_id
+ ))
+ .await
+ }
+
+ /// Skip a step.
+ pub async fn skip_directive_step(
+ &self,
+ directive_id: Uuid,
+ step_id: Uuid,
+ ) -> Result<JsonValue, ApiError> {
+ self.post_empty(&format!(
+ "/api/v1/directives/{}/chain/steps/{}/skip",
+ directive_id, step_id
+ ))
+ .await
+ }
+
+ /// Force re-evaluation of a step.
+ pub async fn evaluate_directive_step(
+ &self,
+ directive_id: Uuid,
+ step_id: Uuid,
+ ) -> Result<JsonValue, ApiError> {
+ self.post_empty(&format!(
+ "/api/v1/directives/{}/chain/steps/{}/evaluate",
+ directive_id, step_id
+ ))
+ .await
+ }
+
+ /// Trigger manual rework for a step.
+ pub async fn rework_directive_step(
+ &self,
+ directive_id: Uuid,
+ step_id: Uuid,
+ instructions: Option<&str>,
+ ) -> Result<JsonValue, ApiError> {
+ #[derive(serde::Serialize)]
+ #[serde(rename_all = "camelCase")]
+ struct ReworkReq<'a> {
+ instructions: Option<&'a str>,
+ }
+ let req = ReworkReq { instructions };
+ self.post(
+ &format!(
+ "/api/v1/directives/{}/chain/steps/{}/rework",
+ directive_id, step_id
+ ),
+ &req,
+ )
+ .await
+ }
+
+ // =========================================================================
+ // Evaluations
+ // =========================================================================
+
+ /// List evaluations for a directive.
+ pub async fn list_directive_evaluations(
+ &self,
+ directive_id: Uuid,
+ ) -> Result<JsonValue, ApiError> {
+ self.get(&format!(
+ "/api/v1/directives/{}/evaluations",
+ directive_id
+ ))
+ .await
+ }
+
+ // =========================================================================
+ // Verifiers
+ // =========================================================================
+
+ /// List verifiers for a directive.
+ pub async fn list_directive_verifiers(
+ &self,
+ directive_id: Uuid,
+ ) -> Result<JsonValue, ApiError> {
+ self.get(&format!("/api/v1/directives/{}/verifiers", directive_id))
+ .await
+ }
+
+ /// Add a verifier to a directive.
+ pub async fn add_directive_verifier(
+ &self,
+ directive_id: Uuid,
+ name: &str,
+ verifier_type: &str,
+ command: Option<&str>,
+ ) -> Result<JsonValue, ApiError> {
+ #[derive(serde::Serialize)]
+ #[serde(rename_all = "camelCase")]
+ struct CreateVerifierReq<'a> {
+ name: &'a str,
+ verifier_type: &'a str,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ command: Option<&'a str>,
+ }
+ let req = CreateVerifierReq {
+ name,
+ verifier_type,
+ command,
+ };
+ self.post(
+ &format!("/api/v1/directives/{}/verifiers", directive_id),
+ &req,
+ )
+ .await
+ }
+
+ /// Update a verifier.
+ pub async fn update_directive_verifier(
+ &self,
+ directive_id: Uuid,
+ verifier_id: Uuid,
+ update: serde_json::Value,
+ ) -> Result<JsonValue, ApiError> {
+ self.put(
+ &format!(
+ "/api/v1/directives/{}/verifiers/{}",
+ directive_id, verifier_id
+ ),
+ &update,
+ )
+ .await
+ }
+
+ /// Auto-detect verifiers based on repository content.
+ pub async fn auto_detect_directive_verifiers(
+ &self,
+ directive_id: Uuid,
+ ) -> Result<JsonValue, ApiError> {
+ self.post_empty(&format!(
+ "/api/v1/directives/{}/verifiers/auto-detect",
+ directive_id
+ ))
+ .await
+ }
+
+ // =========================================================================
+ // Requirements & Spec
+ // =========================================================================
+
+ /// Update directive requirements.
+ pub async fn update_directive_requirements(
+ &self,
+ directive_id: Uuid,
+ requirements: serde_json::Value,
+ ) -> Result<JsonValue, ApiError> {
+ #[derive(serde::Serialize)]
+ #[serde(rename_all = "camelCase")]
+ struct UpdateReq {
+ requirements: serde_json::Value,
+ }
+ let req = UpdateReq { requirements };
+ self.put(
+ &format!("/api/v1/directives/{}/requirements", directive_id),
+ &req,
+ )
+ .await
+ }
+
+ /// Update directive acceptance criteria.
+ pub async fn update_directive_criteria(
+ &self,
+ directive_id: Uuid,
+ acceptance_criteria: serde_json::Value,
+ ) -> Result<JsonValue, ApiError> {
+ #[derive(serde::Serialize)]
+ #[serde(rename_all = "camelCase")]
+ struct UpdateReq {
+ acceptance_criteria: serde_json::Value,
+ }
+ let req = UpdateReq { acceptance_criteria };
+ self.put(
+ &format!("/api/v1/directives/{}/criteria", directive_id),
+ &req,
+ )
+ .await
+ }
+
+ /// Generate a specification from the directive's goal.
+ pub async fn generate_directive_spec(
+ &self,
+ directive_id: Uuid,
+ ) -> Result<JsonValue, ApiError> {
+ self.post_empty(&format!(
+ "/api/v1/directives/{}/generate-spec",
+ directive_id
+ ))
+ .await
+ }
}
diff --git a/makima/src/daemon/api/mod.rs b/makima/src/daemon/api/mod.rs
index f1f52d0..2d1efbf 100644
--- a/makima/src/daemon/api/mod.rs
+++ b/makima/src/daemon/api/mod.rs
@@ -1,6 +1,5 @@
//! HTTP API client for makima CLI commands.
-pub mod chain;
pub mod client;
pub mod contract;
pub mod directive;
diff --git a/makima/src/daemon/chain/dag.rs b/makima/src/daemon/chain/dag.rs
deleted file mode 100644
index 7ba5904..0000000
--- a/makima/src/daemon/chain/dag.rs
+++ /dev/null
@@ -1,450 +0,0 @@
-//! DAG validation and traversal for chain contracts.
-//!
-//! Provides cycle detection and topological sorting for contract dependencies.
-
-use std::collections::{HashMap, HashSet, VecDeque};
-use thiserror::Error;
-
-use super::parser::ChainDefinition;
-
-/// Error type for DAG operations.
-#[derive(Error, Debug)]
-pub enum DagError {
- #[error("Cycle detected in dependency graph: {0}")]
- CycleDetected(String),
-
- #[error("Unknown contract in dependency: {0}")]
- UnknownContract(String),
-}
-
-/// Validates that the chain definition forms a valid DAG (no cycles).
-///
-/// Uses depth-first search with color marking to detect cycles.
-/// Returns Ok(()) if valid, or an error describing the cycle.
-pub fn validate_dag(chain: &ChainDefinition) -> Result<(), DagError> {
- // Build adjacency list from contract dependencies
- let mut adjacency: HashMap<&str, Vec<&str>> = HashMap::new();
- let contract_names: HashSet<&str> = chain.contracts.iter().map(|c| c.name.as_str()).collect();
-
- for contract in &chain.contracts {
- let deps: Vec<&str> = contract
- .depends_on
- .as_ref()
- .map(|d| d.iter().map(|s| s.as_str()).collect())
- .unwrap_or_default();
-
- // Validate all dependencies exist
- for dep in &deps {
- if !contract_names.contains(dep) {
- return Err(DagError::UnknownContract(format!(
- "Contract '{}' depends on unknown contract '{}'",
- contract.name, dep
- )));
- }
- }
-
- adjacency.insert(contract.name.as_str(), deps);
- }
-
- // Color-based DFS for cycle detection
- // White (0): not visited, Gray (1): in progress, Black (2): completed
- let mut color: HashMap<&str, u8> = HashMap::new();
- for name in &contract_names {
- color.insert(name, 0);
- }
-
- // Track path for cycle reporting
- fn dfs<'a>(
- node: &'a str,
- adjacency: &HashMap<&'a str, Vec<&'a str>>,
- color: &mut HashMap<&'a str, u8>,
- path: &mut Vec<&'a str>,
- ) -> Result<(), DagError> {
- color.insert(node, 1); // Mark as in-progress
- path.push(node);
-
- if let Some(deps) = adjacency.get(node) {
- for dep in deps {
- match color.get(dep) {
- Some(1) => {
- // Found cycle - dep is in current path
- let cycle_start = path.iter().position(|&n| n == *dep).unwrap();
- let cycle: Vec<_> = path[cycle_start..].to_vec();
- return Err(DagError::CycleDetected(format!(
- "{} -> {}",
- cycle.join(" -> "),
- dep
- )));
- }
- Some(0) => {
- // Not visited - recurse
- dfs(dep, adjacency, color, path)?;
- }
- _ => {
- // Already completed - skip
- }
- }
- }
- }
-
- color.insert(node, 2); // Mark as completed
- path.pop();
- Ok(())
- }
-
- // Run DFS from each unvisited node
- for name in &contract_names {
- if color.get(name) == Some(&0) {
- let mut path = Vec::new();
- dfs(name, &adjacency, &mut color, &mut path)?;
- }
- }
-
- Ok(())
-}
-
-/// Returns contracts in topological order (dependencies before dependents).
-///
-/// Uses Kahn's algorithm for topological sorting.
-pub fn topological_sort(chain: &ChainDefinition) -> Result<Vec<&str>, DagError> {
- // Validate first
- validate_dag(chain)?;
-
- // Build in-degree map and adjacency list
- let mut in_degree: HashMap<&str, usize> = HashMap::new();
- let mut dependents: HashMap<&str, Vec<&str>> = HashMap::new();
-
- for contract in &chain.contracts {
- in_degree.entry(contract.name.as_str()).or_insert(0);
- dependents.entry(contract.name.as_str()).or_default();
-
- if let Some(deps) = &contract.depends_on {
- for dep in deps {
- *in_degree.entry(contract.name.as_str()).or_insert(0) += 1;
- dependents
- .entry(dep.as_str())
- .or_default()
- .push(contract.name.as_str());
- }
- }
- }
-
- // Kahn's algorithm
- let mut queue: VecDeque<&str> = VecDeque::new();
- let mut result: Vec<&str> = Vec::new();
-
- // Start with nodes that have no dependencies
- for (name, &degree) in &in_degree {
- if degree == 0 {
- queue.push_back(name);
- }
- }
-
- while let Some(node) = queue.pop_front() {
- result.push(node);
-
- if let Some(deps) = dependents.get(node) {
- for dep in deps {
- if let Some(degree) = in_degree.get_mut(dep) {
- *degree -= 1;
- if *degree == 0 {
- queue.push_back(dep);
- }
- }
- }
- }
- }
-
- Ok(result)
-}
-
-/// Returns contracts that are ready to run (have no unmet dependencies).
-///
-/// Takes a set of completed contract names and returns contracts that
-/// can now be started.
-pub fn get_ready_contracts<'a>(
- chain: &'a ChainDefinition,
- completed: &HashSet<&str>,
-) -> Vec<&'a str> {
- chain
- .contracts
- .iter()
- .filter(|c| {
- // Already completed? Skip
- if completed.contains(c.name.as_str()) {
- return false;
- }
-
- // Check if all dependencies are met
- match &c.depends_on {
- None => true, // No dependencies
- Some(deps) => deps.iter().all(|d| completed.contains(d.as_str())),
- }
- })
- .map(|c| c.name.as_str())
- .collect()
-}
-
-/// Get the depth of each contract in the DAG (for layout purposes).
-///
-/// Root nodes (no dependencies) have depth 0.
-/// Each dependent has depth = max(dependency depths) + 1.
-pub fn get_contract_depths(chain: &ChainDefinition) -> HashMap<&str, usize> {
- let mut depths: HashMap<&str, usize> = HashMap::new();
-
- // Multiple passes to handle dependencies
- let max_iterations = chain.contracts.len();
- for _ in 0..max_iterations {
- let mut changed = false;
-
- for contract in &chain.contracts {
- let new_depth = match &contract.depends_on {
- None => 0,
- Some(deps) => {
- if deps.iter().all(|d| depths.contains_key(d.as_str())) {
- deps.iter()
- .filter_map(|d| depths.get(d.as_str()))
- .max()
- .copied()
- .unwrap_or(0)
- + 1
- } else {
- continue; // Dependencies not yet computed
- }
- }
- };
-
- if depths.get(contract.name.as_str()) != Some(&new_depth) {
- depths.insert(contract.name.as_str(), new_depth);
- changed = true;
- }
- }
-
- if !changed {
- break;
- }
- }
-
- depths
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::daemon::chain::parser::parse_chain_yaml;
-
- #[test]
- fn test_valid_dag() {
- let yaml = r#"
-name: Valid DAG
-contracts:
- - name: A
- tasks:
- - name: Task
- plan: "Do A"
- - name: B
- depends_on: [A]
- tasks:
- - name: Task
- plan: "Do B"
- - name: C
- depends_on: [A]
- tasks:
- - name: Task
- plan: "Do C"
- - name: D
- depends_on: [B, C]
- tasks:
- - name: Task
- plan: "Do D"
-"#;
- let chain = parse_chain_yaml(yaml).unwrap();
- assert!(validate_dag(&chain).is_ok());
- }
-
- #[test]
- fn test_simple_cycle() {
- let yaml = r#"
-name: Simple Cycle
-contracts:
- - name: A
- depends_on: [B]
- tasks:
- - name: Task
- plan: "Do A"
- - name: B
- depends_on: [A]
- tasks:
- - name: Task
- plan: "Do B"
-"#;
- let chain = parse_chain_yaml(yaml).unwrap();
- let result = validate_dag(&chain);
- assert!(result.is_err());
- assert!(result.unwrap_err().to_string().contains("Cycle detected"));
- }
-
- #[test]
- fn test_longer_cycle() {
- let yaml = r#"
-name: Longer Cycle
-contracts:
- - name: A
- depends_on: [C]
- tasks:
- - name: Task
- plan: "Do A"
- - name: B
- depends_on: [A]
- tasks:
- - name: Task
- plan: "Do B"
- - name: C
- depends_on: [B]
- tasks:
- - name: Task
- plan: "Do C"
-"#;
- let chain = parse_chain_yaml(yaml).unwrap();
- let result = validate_dag(&chain);
- assert!(result.is_err());
- assert!(result.unwrap_err().to_string().contains("Cycle detected"));
- }
-
- #[test]
- fn test_topological_sort() {
- let yaml = r#"
-name: Topo Test
-contracts:
- - name: A
- tasks:
- - name: Task
- plan: "Do A"
- - name: B
- depends_on: [A]
- tasks:
- - name: Task
- plan: "Do B"
- - name: C
- depends_on: [A]
- tasks:
- - name: Task
- plan: "Do C"
- - name: D
- depends_on: [B, C]
- tasks:
- - name: Task
- plan: "Do D"
-"#;
- let chain = parse_chain_yaml(yaml).unwrap();
- let sorted = topological_sort(&chain).unwrap();
-
- // A must come before B, C; B and C must come before D
- let pos_a = sorted.iter().position(|&n| n == "A").unwrap();
- let pos_b = sorted.iter().position(|&n| n == "B").unwrap();
- let pos_c = sorted.iter().position(|&n| n == "C").unwrap();
- let pos_d = sorted.iter().position(|&n| n == "D").unwrap();
-
- assert!(pos_a < pos_b);
- assert!(pos_a < pos_c);
- assert!(pos_b < pos_d);
- assert!(pos_c < pos_d);
- }
-
- #[test]
- fn test_get_ready_contracts() {
- let yaml = r#"
-name: Ready Test
-contracts:
- - name: A
- tasks:
- - name: Task
- plan: "Do A"
- - name: B
- depends_on: [A]
- tasks:
- - name: Task
- plan: "Do B"
- - name: C
- tasks:
- - name: Task
- plan: "Do C"
-"#;
- let chain = parse_chain_yaml(yaml).unwrap();
-
- // Initially A and C are ready (no dependencies)
- let completed = HashSet::new();
- let mut ready = get_ready_contracts(&chain, &completed);
- ready.sort();
- assert_eq!(ready, vec!["A", "C"]);
-
- // After A completes, B becomes ready
- let mut completed = HashSet::new();
- completed.insert("A");
- let ready = get_ready_contracts(&chain, &completed);
- assert!(ready.contains(&"B"));
- assert!(ready.contains(&"C")); // C still ready if not started
- }
-
- #[test]
- fn test_get_contract_depths() {
- let yaml = r#"
-name: Depth Test
-contracts:
- - name: A
- tasks:
- - name: Task
- plan: "Do A"
- - name: B
- depends_on: [A]
- tasks:
- - name: Task
- plan: "Do B"
- - name: C
- depends_on: [B]
- tasks:
- - name: Task
- plan: "Do C"
-"#;
- let chain = parse_chain_yaml(yaml).unwrap();
- let depths = get_contract_depths(&chain);
-
- assert_eq!(depths.get("A"), Some(&0));
- assert_eq!(depths.get("B"), Some(&1));
- assert_eq!(depths.get("C"), Some(&2));
- }
-
- #[test]
- fn test_diamond_dependency_depths() {
- let yaml = r#"
-name: Diamond Test
-contracts:
- - name: A
- tasks:
- - name: Task
- plan: "Do A"
- - name: B
- depends_on: [A]
- tasks:
- - name: Task
- plan: "Do B"
- - name: C
- depends_on: [A]
- tasks:
- - name: Task
- plan: "Do C"
- - name: D
- depends_on: [B, C]
- tasks:
- - name: Task
- plan: "Do D"
-"#;
- let chain = parse_chain_yaml(yaml).unwrap();
- let depths = get_contract_depths(&chain);
-
- assert_eq!(depths.get("A"), Some(&0));
- assert_eq!(depths.get("B"), Some(&1));
- assert_eq!(depths.get("C"), Some(&1));
- assert_eq!(depths.get("D"), Some(&2));
- }
-}
diff --git a/makima/src/daemon/chain/mod.rs b/makima/src/daemon/chain/mod.rs
deleted file mode 100644
index 5588a27..0000000
--- a/makima/src/daemon/chain/mod.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-//! Chain module - DAG-based multi-contract orchestration.
-//!
-//! Chains are directed acyclic graphs (DAGs) of contracts that work together
-//! to achieve a larger goal. Each contract can depend on others, and contracts
-//! run in parallel when no dependencies exist.
-
-pub mod dag;
-pub mod parser;
-pub mod runner;
-
-pub use dag::{validate_dag, DagError};
-pub use parser::{parse_chain_file, ChainDefinition, ParseError};
-pub use runner::{ChainRunner, RunnerError};
diff --git a/makima/src/daemon/chain/parser.rs b/makima/src/daemon/chain/parser.rs
deleted file mode 100644
index b32d0f2..0000000
--- a/makima/src/daemon/chain/parser.rs
+++ /dev/null
@@ -1,414 +0,0 @@
-//! Chain YAML parser.
-//!
-//! Parses chain definition files in YAML format into structured data
-//! that can be used to create chains and contracts.
-
-use serde::{Deserialize, Serialize};
-use std::path::Path;
-use thiserror::Error;
-
-/// Error type for chain parsing operations.
-#[derive(Error, Debug)]
-pub enum ParseError {
- #[error("Failed to read chain file: {0}")]
- IoError(#[from] std::io::Error),
-
- #[error("Failed to parse YAML: {0}")]
- YamlError(#[from] serde_yaml::Error),
-
- #[error("Validation error: {0}")]
- ValidationError(String),
-}
-
-/// Repository definition in a chain.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct RepositoryDefinition {
- /// Name of the repository
- pub name: String,
- /// Repository URL (for remote repos)
- pub repository_url: Option<String>,
- /// Local path (for local repos)
- pub local_path: Option<String>,
- /// Source type: remote, local, or managed
- #[serde(default = "default_source_type")]
- pub source_type: String,
- /// Whether this is the primary repository
- #[serde(default)]
- pub is_primary: bool,
-}
-
-fn default_source_type() -> String {
- "remote".to_string()
-}
-
-/// Chain definition parsed from YAML.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct ChainDefinition {
- /// Name of the chain
- pub name: String,
- /// Optional description
- pub description: Option<String>,
- /// Repositories for this chain
- #[serde(default)]
- pub repositories: Vec<RepositoryDefinition>,
- /// Contracts in this chain
- pub contracts: Vec<ContractDefinition>,
- /// Loop configuration
- #[serde(rename = "loop")]
- pub loop_config: Option<LoopConfig>,
-}
-
-/// Contract definition within a chain.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct ContractDefinition {
- /// Name of the contract
- pub name: String,
- /// Optional description
- pub description: Option<String>,
- /// Contract type (defaults to "simple")
- #[serde(rename = "type", default = "default_contract_type")]
- pub contract_type: String,
- /// Phases for this contract
- pub phases: Option<Vec<String>>,
- /// Names of contracts this depends on (DAG edges)
- pub depends_on: Option<Vec<String>>,
- /// Tasks to create in this contract
- pub tasks: Option<Vec<TaskDefinition>>,
- /// Deliverables for this contract
- pub deliverables: Option<Vec<DeliverableDefinition>>,
-}
-
-fn default_contract_type() -> String {
- "simple".to_string()
-}
-
-/// Task definition within a contract.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct TaskDefinition {
- /// Name of the task
- pub name: String,
- /// Plan/instructions for the task
- pub plan: String,
-}
-
-/// Deliverable definition within a contract.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct DeliverableDefinition {
- /// Unique identifier for the deliverable
- pub id: String,
- /// Name of the deliverable
- pub name: String,
- /// Priority level (defaults to "required")
- #[serde(default = "default_priority")]
- pub priority: String,
-}
-
-fn default_priority() -> String {
- "required".to_string()
-}
-
-/// Loop configuration for chain iteration.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct LoopConfig {
- /// Whether loop is enabled
- #[serde(default)]
- pub enabled: bool,
- /// Maximum number of iterations
- #[serde(default = "default_max_iterations")]
- pub max_iterations: i32,
- /// Progress check prompt/criteria
- pub progress_check: Option<String>,
-}
-
-fn default_max_iterations() -> i32 {
- 10
-}
-
-impl ChainDefinition {
- /// Validate the chain definition.
- pub fn validate(&self) -> Result<(), ParseError> {
- // Check for empty name
- if self.name.trim().is_empty() {
- return Err(ParseError::ValidationError(
- "Chain name cannot be empty".to_string(),
- ));
- }
-
- // Check for at least one contract
- if self.contracts.is_empty() {
- return Err(ParseError::ValidationError(
- "Chain must have at least one contract".to_string(),
- ));
- }
-
- // Collect all contract names for dependency validation
- let contract_names: std::collections::HashSet<_> =
- self.contracts.iter().map(|c| c.name.as_str()).collect();
-
- // Check for duplicate contract names
- if contract_names.len() != self.contracts.len() {
- return Err(ParseError::ValidationError(
- "Duplicate contract names found".to_string(),
- ));
- }
-
- // Validate each contract
- for contract in &self.contracts {
- contract.validate(&contract_names)?;
- }
-
- Ok(())
- }
-}
-
-impl ContractDefinition {
- /// Validate the contract definition.
- pub fn validate(
- &self,
- valid_contract_names: &std::collections::HashSet<&str>,
- ) -> Result<(), ParseError> {
- // Check for empty name
- if self.name.trim().is_empty() {
- return Err(ParseError::ValidationError(
- "Contract name cannot be empty".to_string(),
- ));
- }
-
- // Validate dependencies exist
- if let Some(deps) = &self.depends_on {
- for dep in deps {
- if !valid_contract_names.contains(dep.as_str()) {
- return Err(ParseError::ValidationError(format!(
- "Contract '{}' depends on unknown contract '{}'",
- self.name, dep
- )));
- }
- // Self-dependency check
- if dep == &self.name {
- return Err(ParseError::ValidationError(format!(
- "Contract '{}' cannot depend on itself",
- self.name
- )));
- }
- }
- }
-
- // Validate tasks
- if let Some(tasks) = &self.tasks {
- for task in tasks {
- if task.name.trim().is_empty() {
- return Err(ParseError::ValidationError(format!(
- "Task name cannot be empty in contract '{}'",
- self.name
- )));
- }
- if task.plan.trim().is_empty() {
- return Err(ParseError::ValidationError(format!(
- "Task '{}' in contract '{}' has empty plan",
- task.name, self.name
- )));
- }
- }
- }
-
- Ok(())
- }
-}
-
-/// Parse a chain definition from a YAML file.
-pub fn parse_chain_file<P: AsRef<Path>>(path: P) -> Result<ChainDefinition, ParseError> {
- let content = std::fs::read_to_string(path)?;
- parse_chain_yaml(&content)
-}
-
-/// Parse a chain definition from a YAML string.
-pub fn parse_chain_yaml(yaml: &str) -> Result<ChainDefinition, ParseError> {
- let definition: ChainDefinition = serde_yaml::from_str(yaml)?;
- definition.validate()?;
- Ok(definition)
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_parse_simple_chain() {
- let yaml = r#"
-name: Test Chain
-description: A test chain
-contracts:
- - name: Research
- type: simple
- tasks:
- - name: Analyze
- plan: "Analyze the codebase"
- - name: Implement
- type: simple
- depends_on: [Research]
- tasks:
- - name: Build
- plan: "Build the feature"
-"#;
- let chain = parse_chain_yaml(yaml).unwrap();
- assert_eq!(chain.name, "Test Chain");
- assert_eq!(chain.contracts.len(), 2);
- assert_eq!(chain.contracts[0].name, "Research");
- assert_eq!(chain.contracts[1].name, "Implement");
- assert_eq!(
- chain.contracts[1].depends_on,
- Some(vec!["Research".to_string()])
- );
- }
-
- #[test]
- fn test_parse_chain_with_loop() {
- let yaml = r#"
-name: Iterative Chain
-contracts:
- - name: Phase1
- tasks:
- - name: Task1
- plan: "Do something"
-loop:
- enabled: true
- max_iterations: 5
- progress_check: "Check if goals are met"
-"#;
- let chain = parse_chain_yaml(yaml).unwrap();
- assert!(chain.loop_config.is_some());
- let loop_config = chain.loop_config.unwrap();
- assert!(loop_config.enabled);
- assert_eq!(loop_config.max_iterations, 5);
- }
-
- #[test]
- fn test_parse_chain_with_deliverables() {
- let yaml = r#"
-name: Feature Chain
-contracts:
- - name: Research
- tasks:
- - name: Survey
- plan: "Survey existing code"
- deliverables:
- - id: analysis
- name: Codebase Analysis
- priority: required
-"#;
- let chain = parse_chain_yaml(yaml).unwrap();
- let deliverables = chain.contracts[0].deliverables.as_ref().unwrap();
- assert_eq!(deliverables.len(), 1);
- assert_eq!(deliverables[0].id, "analysis");
- }
-
- #[test]
- fn test_validation_empty_name() {
- let yaml = r#"
-name: ""
-contracts:
- - name: Phase1
- tasks:
- - name: Task1
- plan: "Do something"
-"#;
- let result = parse_chain_yaml(yaml);
- assert!(result.is_err());
- assert!(result
- .unwrap_err()
- .to_string()
- .contains("name cannot be empty"));
- }
-
- #[test]
- fn test_validation_no_contracts() {
- let yaml = r#"
-name: Empty Chain
-contracts: []
-"#;
- let result = parse_chain_yaml(yaml);
- assert!(result.is_err());
- assert!(result
- .unwrap_err()
- .to_string()
- .contains("at least one contract"));
- }
-
- #[test]
- fn test_validation_unknown_dependency() {
- let yaml = r#"
-name: Bad Chain
-contracts:
- - name: Phase1
- depends_on: [NonExistent]
- tasks:
- - name: Task1
- plan: "Do something"
-"#;
- let result = parse_chain_yaml(yaml);
- assert!(result.is_err());
- assert!(result.unwrap_err().to_string().contains("unknown contract"));
- }
-
- #[test]
- fn test_validation_self_dependency() {
- let yaml = r#"
-name: Self Ref Chain
-contracts:
- - name: Phase1
- depends_on: [Phase1]
- tasks:
- - name: Task1
- plan: "Do something"
-"#;
- let result = parse_chain_yaml(yaml);
- assert!(result.is_err());
- assert!(result
- .unwrap_err()
- .to_string()
- .contains("cannot depend on itself"));
- }
-
- #[test]
- fn test_validation_duplicate_names() {
- let yaml = r#"
-name: Dup Chain
-contracts:
- - name: Phase1
- tasks:
- - name: Task1
- plan: "Do something"
- - name: Phase1
- tasks:
- - name: Task2
- plan: "Do another thing"
-"#;
- let result = parse_chain_yaml(yaml);
- assert!(result.is_err());
- assert!(result
- .unwrap_err()
- .to_string()
- .contains("Duplicate contract names"));
- }
-
- #[test]
- fn test_repo_alias() {
- let yaml = r#"
-name: Repo Chain
-repositories:
- - name: main
- repository_url: https://github.com/user/project
-contracts:
- - name: Phase1
- tasks:
- - name: Task1
- plan: "Work on repo"
-"#;
- let chain = parse_chain_yaml(yaml).unwrap();
- assert_eq!(chain.repositories.len(), 1);
- assert_eq!(
- chain.repositories[0].repository_url,
- Some("https://github.com/user/project".to_string())
- );
- }
-}
diff --git a/makima/src/daemon/chain/runner.rs b/makima/src/daemon/chain/runner.rs
deleted file mode 100644
index 1814581..0000000
--- a/makima/src/daemon/chain/runner.rs
+++ /dev/null
@@ -1,388 +0,0 @@
-//! Chain runner - creates and orchestrates contracts from chain definitions.
-//!
-//! Handles the lifecycle of a chain:
-//! 1. Parse chain definition
-//! 2. Validate DAG
-//! 3. Create chain record
-//! 4. Create contracts in dependency order
-//! 5. Monitor and trigger dependent contracts
-
-use std::collections::HashMap;
-use std::path::Path;
-use thiserror::Error;
-
-use super::dag::{topological_sort, validate_dag, DagError};
-use super::parser::{parse_chain_file, ChainDefinition, ParseError};
-use crate::db::models::{
- AddChainRepositoryRequest, CreateChainContractRequest, CreateChainDeliverableRequest,
- CreateChainRequest, CreateChainTaskRequest,
-};
-
-/// Error type for chain runner operations.
-#[derive(Error, Debug)]
-pub enum RunnerError {
- #[error("Parse error: {0}")]
- Parse(#[from] ParseError),
-
- #[error("DAG error: {0}")]
- Dag(#[from] DagError),
-
- #[error("API error: {0}")]
- Api(String),
-
- #[error("Contract creation failed: {0}")]
- ContractCreation(String),
-}
-
-/// Chain runner for creating and managing chains.
-pub struct ChainRunner {
- /// Base API URL
- #[allow(dead_code)]
- api_url: String,
- /// API key for authentication
- #[allow(dead_code)]
- api_key: String,
-}
-
-impl ChainRunner {
- /// Create a new chain runner.
- pub fn new(api_url: String, api_key: String) -> Self {
- Self { api_url, api_key }
- }
-
- /// Load and validate a chain from a YAML file.
- pub fn load_chain<P: AsRef<Path>>(&self, path: P) -> Result<ChainDefinition, RunnerError> {
- let chain = parse_chain_file(path)?;
- validate_dag(&chain)?;
- Ok(chain)
- }
-
- /// Convert a chain definition to a CreateChainRequest for API submission.
- pub fn to_create_request(&self, chain: &ChainDefinition) -> CreateChainRequest {
- let contracts: Vec<CreateChainContractRequest> = chain
- .contracts
- .iter()
- .map(|c| CreateChainContractRequest {
- name: c.name.clone(),
- description: c.description.clone(),
- contract_type: Some(c.contract_type.clone()),
- initial_phase: None,
- phases: c.phases.clone(),
- depends_on: c.depends_on.clone(),
- tasks: c.tasks.as_ref().map(|tasks| {
- tasks
- .iter()
- .map(|t| CreateChainTaskRequest {
- name: t.name.clone(),
- plan: t.plan.clone(),
- })
- .collect()
- }),
- deliverables: c.deliverables.as_ref().map(|dels| {
- dels.iter()
- .map(|d| CreateChainDeliverableRequest {
- id: d.id.clone(),
- name: d.name.clone(),
- priority: Some(d.priority.clone()),
- })
- .collect()
- }),
- editor_x: None,
- editor_y: None,
- })
- .collect();
-
- let (loop_enabled, loop_max_iterations, loop_progress_check) =
- match &chain.loop_config {
- Some(lc) => (
- Some(lc.enabled),
- Some(lc.max_iterations),
- lc.progress_check.clone(),
- ),
- None => (None, None, None),
- };
-
- // Convert repository definitions to API format
- let repositories: Vec<AddChainRepositoryRequest> = chain
- .repositories
- .iter()
- .map(|r| AddChainRepositoryRequest {
- name: r.name.clone(),
- repository_url: r.repository_url.clone(),
- local_path: r.local_path.clone(),
- source_type: r.source_type.clone(),
- is_primary: r.is_primary,
- })
- .collect();
-
- CreateChainRequest {
- name: chain.name.clone(),
- description: chain.description.clone(),
- repository_url: None, // Legacy field, repositories take precedence
- repositories: if repositories.is_empty() {
- None
- } else {
- Some(repositories)
- },
- loop_enabled,
- loop_max_iterations,
- loop_progress_check,
- contracts: Some(contracts),
- }
- }
-
- /// Get contracts in topological order (for display/debugging).
- pub fn get_execution_order<'a>(
- &self,
- chain: &'a ChainDefinition,
- ) -> Result<Vec<&'a str>, RunnerError> {
- Ok(topological_sort(chain)?)
- }
-
- /// Generate ASCII visualization of the chain DAG.
- pub fn visualize_dag(&self, chain: &ChainDefinition) -> String {
- use super::dag::get_contract_depths;
-
- let depths = get_contract_depths(chain);
- let mut lines: Vec<String> = vec![];
-
- lines.push(format!("Chain: {}", chain.name));
- if let Some(desc) = &chain.description {
- lines.push(format!(" {}", desc));
- }
- lines.push(String::new());
-
- // Group contracts by depth
- let mut by_depth: HashMap<usize, Vec<&str>> = HashMap::new();
- for contract in &chain.contracts {
- let depth = depths.get(contract.name.as_str()).copied().unwrap_or(0);
- by_depth.entry(depth).or_default().push(&contract.name);
- }
-
- // Find max depth
- let max_depth = by_depth.keys().max().copied().unwrap_or(0);
-
- // Build visualization
- for depth in 0..=max_depth {
- if let Some(contracts) = by_depth.get(&depth) {
- let contract_strs: Vec<String> = contracts
- .iter()
- .map(|name| format!("[{}]", name))
- .collect();
-
- let indent = " ".repeat(depth);
- lines.push(format!("{}{}", indent, contract_strs.join(" ")));
-
- // Draw arrows to next level
- if depth < max_depth {
- if let Some(next_contracts) = by_depth.get(&(depth + 1)) {
- // Find which contracts connect to the next level
- for next in next_contracts {
- let next_contract = chain
- .contracts
- .iter()
- .find(|c| c.name.as_str() == *next)
- .unwrap();
-
- if let Some(deps) = &next_contract.depends_on {
- for dep in deps {
- if contracts.contains(&dep.as_str()) {
- let arrow_indent = " ".repeat(depth);
- lines.push(format!("{} │", arrow_indent));
- lines.push(format!("{} ▼", arrow_indent));
- }
- }
- }
- }
- }
- }
- }
- }
-
- lines.join("\n")
- }
-}
-
-/// Compute editor positions for contracts based on DAG layout.
-///
-/// Returns a map of contract name to (x, y) positions suitable for
-/// the GUI editor.
-pub fn compute_editor_positions(chain: &ChainDefinition) -> HashMap<String, (f64, f64)> {
- use super::dag::get_contract_depths;
-
- let depths = get_contract_depths(chain);
- let mut positions: HashMap<String, (f64, f64)> = HashMap::new();
-
- // Group by depth
- let mut by_depth: HashMap<usize, Vec<&str>> = HashMap::new();
- for contract in &chain.contracts {
- let depth = depths.get(contract.name.as_str()).copied().unwrap_or(0);
- by_depth.entry(depth).or_default().push(&contract.name);
- }
-
- // Compute positions: x based on depth, y based on index within depth
- let x_spacing = 250.0;
- let y_spacing = 150.0;
-
- for (depth, contracts) in &by_depth {
- let x = (*depth as f64) * x_spacing + 100.0;
- for (i, name) in contracts.iter().enumerate() {
- let y = (i as f64) * y_spacing + 100.0;
- positions.insert(name.to_string(), (x, y));
- }
- }
-
- positions
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::daemon::chain::parser::parse_chain_yaml;
-
- #[test]
- fn test_to_create_request() {
- let yaml = r#"
-name: Test Chain
-description: A test chain
-repositories:
- - name: main
- repository_url: https://github.com/test/repo
-contracts:
- - name: Research
- type: simple
- phases: [plan, execute]
- tasks:
- - name: Analyze
- plan: "Analyze the codebase"
- deliverables:
- - id: analysis
- name: Analysis Doc
- priority: required
- - name: Implement
- depends_on: [Research]
- tasks:
- - name: Build
- plan: "Build the feature"
-loop:
- enabled: true
- max_iterations: 5
- progress_check: "Check completion"
-"#;
- let chain = parse_chain_yaml(yaml).unwrap();
- let runner = ChainRunner::new("http://localhost".to_string(), "key".to_string());
- let request = runner.to_create_request(&chain);
-
- assert_eq!(request.name, "Test Chain");
- assert_eq!(request.description, Some("A test chain".to_string()));
- // Repositories are now in a separate array
- let repos = request.repositories.unwrap();
- assert_eq!(repos.len(), 1);
- assert_eq!(
- repos[0].repository_url,
- Some("https://github.com/test/repo".to_string())
- );
- assert_eq!(request.loop_enabled, Some(true));
- assert_eq!(request.loop_max_iterations, Some(5));
-
- let contracts = request.contracts.unwrap();
- assert_eq!(contracts.len(), 2);
- assert_eq!(contracts[0].name, "Research");
- assert_eq!(contracts[0].phases, Some(vec!["plan".to_string(), "execute".to_string()]));
- assert_eq!(
- contracts[1].depends_on,
- Some(vec!["Research".to_string()])
- );
- }
-
- #[test]
- fn test_get_execution_order() {
- let yaml = r#"
-name: Order Test
-contracts:
- - name: C
- depends_on: [B]
- tasks:
- - name: Task
- plan: "Do C"
- - name: A
- tasks:
- - name: Task
- plan: "Do A"
- - name: B
- depends_on: [A]
- tasks:
- - name: Task
- plan: "Do B"
-"#;
- let chain = parse_chain_yaml(yaml).unwrap();
- let runner = ChainRunner::new("http://localhost".to_string(), "key".to_string());
- let order = runner.get_execution_order(&chain).unwrap();
-
- let pos_a = order.iter().position(|&n| n == "A").unwrap();
- let pos_b = order.iter().position(|&n| n == "B").unwrap();
- let pos_c = order.iter().position(|&n| n == "C").unwrap();
-
- assert!(pos_a < pos_b);
- assert!(pos_b < pos_c);
- }
-
- #[test]
- fn test_visualize_dag() {
- let yaml = r#"
-name: Visual Test
-description: Test visualization
-contracts:
- - name: A
- tasks:
- - name: Task
- plan: "Do A"
- - name: B
- depends_on: [A]
- tasks:
- - name: Task
- plan: "Do B"
-"#;
- let chain = parse_chain_yaml(yaml).unwrap();
- let runner = ChainRunner::new("http://localhost".to_string(), "key".to_string());
- let viz = runner.visualize_dag(&chain);
-
- assert!(viz.contains("Chain: Visual Test"));
- assert!(viz.contains("[A]"));
- assert!(viz.contains("[B]"));
- }
-
- #[test]
- fn test_compute_editor_positions() {
- let yaml = r#"
-name: Position Test
-contracts:
- - name: A
- tasks:
- - name: Task
- plan: "Do A"
- - name: B
- depends_on: [A]
- tasks:
- - name: Task
- plan: "Do B"
- - name: C
- depends_on: [A]
- tasks:
- - name: Task
- plan: "Do C"
-"#;
- let chain = parse_chain_yaml(yaml).unwrap();
- let positions = compute_editor_positions(&chain);
-
- // A should be at depth 0 (x = 100)
- let (a_x, _) = positions.get("A").unwrap();
- assert_eq!(*a_x, 100.0);
-
- // B and C should be at depth 1 (x = 350)
- let (b_x, _) = positions.get("B").unwrap();
- let (c_x, _) = positions.get("C").unwrap();
- assert_eq!(*b_x, 350.0);
- assert_eq!(*c_x, 350.0);
- }
-}
diff --git a/makima/src/daemon/cli/chain.rs b/makima/src/daemon/cli/chain.rs
deleted file mode 100644
index 1d7c167..0000000
--- a/makima/src/daemon/cli/chain.rs
+++ /dev/null
@@ -1,107 +0,0 @@
-//! Chain CLI commands for multi-contract orchestration.
-//!
-//! Provides commands for creating, managing, and visualizing chains
-//! (DAGs of contracts).
-
-use clap::Args;
-use std::path::PathBuf;
-use uuid::Uuid;
-
-/// Common arguments for chain commands requiring API access.
-#[derive(Args, Debug, Clone)]
-pub struct ChainArgs {
- /// API URL
- #[arg(long, env = "MAKIMA_API_URL", default_value = "https://api.makima.jp", global = true)]
- pub api_url: String,
-
- /// API key for authentication
- #[arg(long, env = "MAKIMA_API_KEY", global = true)]
- pub api_key: String,
-}
-
-/// Arguments for the `run` command (create chain from YAML file).
-#[derive(Args, Debug)]
-pub struct RunArgs {
- #[command(flatten)]
- pub common: ChainArgs,
-
- /// Path to the chain YAML file
- pub file: PathBuf,
-
- /// Don't actually create the chain, just validate and show what would be created
- #[arg(long)]
- pub dry_run: bool,
-}
-
-/// Arguments for the `status` command.
-#[derive(Args, Debug)]
-pub struct StatusArgs {
- #[command(flatten)]
- pub common: ChainArgs,
-
- /// Chain ID
- pub chain_id: Uuid,
-}
-
-/// Arguments for the `list` command.
-#[derive(Args, Debug)]
-pub struct ListArgs {
- #[command(flatten)]
- pub common: ChainArgs,
-
- /// Filter by status (active, completed, archived)
- #[arg(long)]
- pub status: Option<String>,
-
- /// Limit number of results
- #[arg(long, default_value = "50")]
- pub limit: i32,
-}
-
-/// Arguments for the `contracts` command.
-#[derive(Args, Debug)]
-pub struct ContractsArgs {
- #[command(flatten)]
- pub common: ChainArgs,
-
- /// Chain ID
- pub chain_id: Uuid,
-}
-
-/// Arguments for the `graph` command (ASCII DAG visualization).
-#[derive(Args, Debug)]
-pub struct GraphArgs {
- #[command(flatten)]
- pub common: ChainArgs,
-
- /// Chain ID
- pub chain_id: Uuid,
-
- /// Show contract status in nodes
- #[arg(long)]
- pub with_status: bool,
-}
-
-/// Arguments for the `validate` command.
-#[derive(Args, Debug)]
-pub struct ValidateArgs {
- /// Path to the chain YAML file
- pub file: PathBuf,
-}
-
-/// Arguments for the `preview` command.
-#[derive(Args, Debug)]
-pub struct PreviewArgs {
- /// Path to the chain YAML file
- pub file: PathBuf,
-}
-
-/// Arguments for the `archive` command.
-#[derive(Args, Debug)]
-pub struct ArchiveArgs {
- #[command(flatten)]
- pub common: ChainArgs,
-
- /// Chain ID
- pub chain_id: Uuid,
-}
diff --git a/makima/src/daemon/cli/mod.rs b/makima/src/daemon/cli/mod.rs
index 91ef87c..77eee80 100644
--- a/makima/src/daemon/cli/mod.rs
+++ b/makima/src/daemon/cli/mod.rs
@@ -1,6 +1,5 @@
//! Command-line interface for the makima CLI.
-pub mod chain;
pub mod config;
pub mod contract;
pub mod daemon;
@@ -11,7 +10,6 @@ pub mod view;
use clap::{Parser, Subcommand};
-pub use chain::ChainArgs;
pub use config::CliConfig;
pub use contract::ContractArgs;
pub use daemon::DaemonArgs;
@@ -63,14 +61,6 @@ pub enum Commands {
#[command(subcommand)]
Config(ConfigCommand),
- /// Chain commands for multi-contract orchestration
- ///
- /// Chains are DAGs (directed acyclic graphs) of contracts that work together
- /// to achieve a larger goal. Contracts can depend on each other, and run
- /// in parallel when no dependencies exist.
- #[command(subcommand)]
- Chain(ChainCommand),
-
/// Directive commands for autonomous goal-driven orchestration
///
/// Directives are top-level goals that generate chains of steps executed
@@ -216,48 +206,6 @@ pub enum ContractCommand {
CreateFile(contract::CreateFileArgs),
}
-/// Chain subcommands for multi-contract orchestration.
-#[derive(Subcommand, Debug)]
-pub enum ChainCommand {
- /// Create a chain from a YAML file
- ///
- /// Parses the chain definition, validates the DAG, and creates
- /// contracts in the correct dependency order.
- Run(chain::RunArgs),
-
- /// Get chain status and progress
- Status(chain::StatusArgs),
-
- /// List all chains
- List(chain::ListArgs),
-
- /// List contracts in a chain
- Contracts(chain::ContractsArgs),
-
- /// Display ASCII DAG visualization
- ///
- /// Shows the chain structure as an ASCII graph with
- /// contracts as nodes and dependencies as edges.
- Graph(chain::GraphArgs),
-
- /// Validate a chain YAML file without creating
- ///
- /// Checks syntax, validates the DAG (no cycles), and
- /// reports any errors.
- Validate(chain::ValidateArgs),
-
- /// Preview what would be created from a chain file
- ///
- /// Shows execution order and contract details without
- /// actually creating anything.
- Preview(chain::PreviewArgs),
-
- /// Archive a chain
- ///
- /// Marks the chain as archived. Does not delete contracts.
- Archive(chain::ArchiveArgs),
-}
-
/// Directive subcommands for autonomous goal-driven orchestration.
#[derive(Subcommand, Debug)]
pub enum DirectiveCommand {
diff --git a/makima/src/daemon/mod.rs b/makima/src/daemon/mod.rs
index 1ddc4cb..13f0862 100644
--- a/makima/src/daemon/mod.rs
+++ b/makima/src/daemon/mod.rs
@@ -8,7 +8,6 @@
//! - `makima view` - Interactive TUI browser for tasks, contracts, and files
pub mod api;
-pub mod chain;
pub mod cli;
pub mod config;
pub mod db;
diff --git a/makima/src/daemon/skill_installer.rs b/makima/src/daemon/skill_installer.rs
index 87cdc29..4870971 100644
--- a/makima/src/daemon/skill_installer.rs
+++ b/makima/src/daemon/skill_installer.rs
@@ -3,7 +3,7 @@
//! This module installs makima CLI commands as Claude Code skills
//! to ~/.claude/skills/ on daemon startup. Skills allow Claude Code
//! instances to use makima commands via slash commands like
-//! `/makima-supervisor`, `/makima-contract`, and `/makima-chain`.
+//! `/makima-supervisor`, `/makima-contract`, and `/makima-directive`.
use std::path::PathBuf;
use tokio::fs;
diff --git a/makima/src/daemon/skills/chain.md b/makima/src/daemon/skills/chain.md
deleted file mode 100644
index 7831540..0000000
--- a/makima/src/daemon/skills/chain.md
+++ /dev/null
@@ -1,116 +0,0 @@
----
-name: makima-chain
-description: Chain commands for makima multi-contract orchestration. Use when running chains of contracts defined in YAML, checking chain status, or managing contract dependencies.
----
-
-# Makima Chain Commands
-
-Chains are DAGs (directed acyclic graphs) of contracts that work together. Contracts can depend on each other and run in parallel when no dependencies exist.
-
-Environment variables (`MAKIMA_API_URL`, `MAKIMA_API_KEY`) must be set.
-
-## Running Chains
-
-### Run chain from YAML
-```bash
-makima chain run <yaml_file>
-```
-Parses the chain definition, validates the DAG, and creates contracts.
-
-Options:
-- `--dry-run` - Validate and preview without creating
-
-### Validate chain YAML
-```bash
-makima chain validate <yaml_file>
-```
-Checks syntax and validates DAG structure (no cycles).
-
-### Preview chain
-```bash
-makima chain preview <yaml_file>
-```
-Shows execution order and contract details without creating.
-
-## Chain Status
-
-### Get chain status
-```bash
-makima chain status <chain_id>
-```
-
-### List all chains
-```bash
-makima chain list
-```
-Options:
-- `--status <active|completed|archived>` - Filter by status
-- `--limit <n>` - Limit results (default: 50)
-
-### List contracts in chain
-```bash
-makima chain contracts <chain_id>
-```
-
-### Display ASCII DAG visualization
-```bash
-makima chain graph <chain_id>
-```
-Options:
-- `--with-status` - Show contract status in visualization
-
-## Chain Management
-
-### Archive chain
-```bash
-makima chain archive <chain_id>
-```
-Marks chain as archived (does not delete contracts).
-
-## Chain YAML Format
-
-```yaml
-name: my-chain
-description: Optional description
-repository_url: https://github.com/org/repo
-
-contracts:
- - name: setup
- contract_type: implementation
- description: Initial setup work
-
- - name: feature-a
- contract_type: implementation
- depends_on: [setup]
- description: Implement feature A
-
- - name: feature-b
- contract_type: implementation
- depends_on: [setup]
- description: Implement feature B (parallel with A)
-
- - name: integration
- contract_type: review
- depends_on: [feature-a, feature-b]
- description: Integrate and test
-```
-
-## Output Format
-
-All commands output JSON to stdout.
-
-Example workflow:
-```bash
-# Validate before running
-makima chain validate my-chain.yaml
-
-# Preview execution
-makima chain preview my-chain.yaml
-
-# Run the chain
-chain_id=$(makima chain run my-chain.yaml | jq -r '.chainId')
-
-# Monitor progress
-makima chain status "$chain_id"
-makima chain graph "$chain_id" --with-status
-```
diff --git a/makima/src/daemon/skills/chain_directive.md b/makima/src/daemon/skills/chain_directive.md
deleted file mode 100644
index 53ac96b..0000000
--- a/makima/src/daemon/skills/chain_directive.md
+++ /dev/null
@@ -1,224 +0,0 @@
----
-name: makima-chain-directive
-description: Directive contract tools for orchestrating chains. Use when creating chains from goals, adding contracts to chains, evaluating completions, or managing chain structure.
----
-
-# Chain Directive Contract Tools
-
-Directive contracts are special contracts that research, plan, create, and orchestrate chains. They use formal directives with requirements and acceptance criteria, and evaluate each contract completion before allowing the chain to progress.
-
-## Workflow Overview
-
-1. **Init**: Create a directive contract + empty chain from a goal
-2. **Research**: Directive contract explores codebase, understands requirements
-3. **Specify**: Write formal directive with requirements (REQ-001, etc.) and acceptance criteria
-4. **Plan**: Design chain structure, add contracts, set dependencies
-5. **Execute**: Finalize chain, start execution, evaluate completions
-6. **Review**: All contracts complete, create final report
-
-## Creating a Chain from a Goal
-
-### Initialize directive-driven chain
-```
-POST /api/v1/chains/init
-{
- "goal": "Add OAuth2 authentication support",
- "repository_url": "https://github.com/org/repo",
- "local_path": "/path/to/repo",
- "phase_guard": true
-}
-```
-
-Returns:
-- `chain_id` - The created chain
-- `directive_contract_id` - The directive contract orchestrating the chain
-- `supervisor_task_id` - Task ID for the directive contract supervisor
-
-## Chain Design Tools (for directive contracts)
-
-These tools are available when working on a directive contract:
-
-### create_chain_from_directive
-Create a new chain linked to this directive contract.
-```json
-{
- "name": "oauth-implementation",
- "description": "Chain for OAuth2 implementation"
-}
-```
-
-### add_chain_contract
-Add a contract definition to the chain.
-```json
-{
- "name": "auth-backend",
- "description": "Implement authentication backend",
- "contract_type": "implementation",
- "depends_on": ["setup"],
- "requirement_ids": ["REQ-001", "REQ-002"]
-}
-```
-
-### set_chain_dependencies
-Update dependency relationships.
-```json
-{
- "contract_name": "integration-tests",
- "depends_on": ["auth-backend", "auth-frontend"]
-}
-```
-
-### modify_chain_contract
-Update a contract definition.
-```json
-{
- "name": "auth-backend",
- "new_name": "authentication-service",
- "description": "Updated description",
- "add_requirement_ids": ["REQ-003"],
- "remove_requirement_ids": ["REQ-001"]
-}
-```
-
-### remove_chain_contract
-Remove a contract definition (fails if others depend on it).
-```json
-{
- "name": "unused-contract"
-}
-```
-
-### preview_chain_dag
-Generate visual DAG preview of the chain structure.
-Returns ASCII diagram and JSON nodes.
-
-### validate_chain_directive
-Validate chain structure before finalizing.
-Checks for:
-- Empty chains
-- Missing dependencies
-- Circular dependencies
-- Uncovered requirements
-
-### finalize_chain_directive
-Lock the directive and optionally start chain execution.
-```json
-{
- "auto_start": true
-}
-```
-
-## Orchestration Tools (during execution)
-
-### get_chain_status
-Get current chain progress and contract statuses.
-Returns completed/active/pending counts and contract details.
-
-### get_uncovered_requirements
-List requirements not mapped to any contract.
-Returns uncovered requirement IDs and coverage percentage.
-
-### evaluate_contract_completion
-Evaluate a completed contract against the directive.
-```json
-{
- "contract_id": "uuid",
- "passed": true,
- "feedback": "All acceptance criteria met",
- "rework_instructions": null
-}
-```
-
-### request_rework
-Reject completion and request rework.
-```json
-{
- "contract_id": "uuid",
- "feedback": "Missing error handling for edge cases"
-}
-```
-
-## Evaluation Flow
-
-When a contract completes and evaluation is enabled:
-
-1. Contract status changes to `completed`
-2. Chain contract marked as `pending_evaluation`
-3. Directive contract evaluates using `evaluate_contract_completion`
-4. **Pass**: Chain progresses, downstream contracts created
-5. **Fail**: Contract marked for rework, retry count incremented
-6. After max retries (default 3), escalate to user
-
-## Directive Document Structure
-
-The directive contains:
-
-```json
-{
- "requirements": [
- {
- "id": "REQ-001",
- "title": "User Authentication",
- "description": "Users must be able to log in with email/password",
- "priority": "must",
- "category": "feature"
- }
- ],
- "acceptance_criteria": [
- {
- "id": "AC-001",
- "requirement_ids": ["REQ-001"],
- "description": "Login endpoint returns JWT on valid credentials",
- "testable": true,
- "verification_method": "automated"
- }
- ],
- "constraints": [
- {
- "id": "CON-001",
- "type": "technical",
- "description": "Must use existing PostgreSQL database"
- }
- ],
- "external_dependencies": [
- {
- "id": "EXT-001",
- "name": "OAuth Provider API",
- "type": "api",
- "required": true
- }
- ]
-}
-```
-
-## Example Workflow
-
-```bash
-# 1. Initialize a directive-driven chain
-curl -X POST http://localhost:3000/api/v1/chains/init \
- -H "Authorization: Bearer $TOKEN" \
- -H "Content-Type: application/json" \
- -d '{"goal": "Add user profile editing feature"}'
-
-# 2. Directive contract goes through phases:
-# - Research: Explores codebase
-# - Specify: Writes formal directive
-# - Plan: Creates chain contracts using tools
-# - Execute: Monitors and evaluates completions
-
-# 3. Monitor chain progress
-curl http://localhost:3000/api/v1/chains/$CHAIN_ID \
- -H "Authorization: Bearer $TOKEN"
-
-# 4. View directive traceability
-curl http://localhost:3000/api/v1/chains/$CHAIN_ID/directive/traceability \
- -H "Authorization: Bearer $TOKEN"
-```
-
-## Key Concepts
-
-- **Directive Contract**: The orchestrator that creates and manages the chain
-- **Formal Directive**: Structured specification with traceable requirements
-- **Continuous Evaluation**: LLM evaluates after every contract completion
-- **Block & Rework**: Failed evaluations block progress until fixed
-- **Dynamic Modification**: Chain structure can be modified during execution
diff --git a/makima/src/daemon/skills/mod.rs b/makima/src/daemon/skills/mod.rs
index dafa9ec..c32f550 100644
--- a/makima/src/daemon/skills/mod.rs
+++ b/makima/src/daemon/skills/mod.rs
@@ -9,9 +9,6 @@ pub const SUPERVISOR_SKILL: &str = include_str!("supervisor.md");
/// Contract skill content - task-contract interaction commands
pub const CONTRACT_SKILL: &str = include_str!("contract.md");
-/// Chain skill content - multi-contract orchestration commands (legacy)
-pub const CHAIN_SKILL: &str = include_str!("chain.md");
-
/// Directive skill content - autonomous goal-driven orchestration
pub const DIRECTIVE_SKILL: &str = include_str!("directive.md");
@@ -19,6 +16,5 @@ pub const DIRECTIVE_SKILL: &str = include_str!("directive.md");
pub const ALL_SKILLS: &[(&str, &str)] = &[
("makima-supervisor", SUPERVISOR_SKILL),
("makima-contract", CONTRACT_SKILL),
- ("makima-chain", CHAIN_SKILL),
("makima-directive", DIRECTIVE_SKILL),
];