diff --git a/Cargo.lock b/Cargo.lock index ed6c4f4f3..589ed6ddd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1284,6 +1284,7 @@ dependencies = [ "fabro-db", "fabro-exe", "fabro-github", + "fabro-graphviz", "fabro-llm", "fabro-types", "fabro-util", @@ -1469,6 +1470,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "fabro-graphviz" +version = "0.174.0" +dependencies = [ + "nom", + "serde", + "thiserror 2.0.18", +] + [[package]] name = "fabro-linear" version = "0.174.0" @@ -1671,6 +1681,7 @@ dependencies = [ "fabro-exe", "fabro-git-storage", "fabro-github", + "fabro-graphviz", "fabro-llm", "fabro-mcp", "fabro-ssh", @@ -1680,7 +1691,6 @@ dependencies = [ "hex", "indicatif", "mockito", - "nom", "predicates", "rand 0.8.5", "regex", diff --git a/lib/crates/fabro-api/Cargo.toml b/lib/crates/fabro-api/Cargo.toml index 9a353294a..9759f6d1b 100644 --- a/lib/crates/fabro-api/Cargo.toml +++ b/lib/crates/fabro-api/Cargo.toml @@ -10,6 +10,7 @@ doctest = false [dependencies] fabro-config = { path = "../fabro-config" } +fabro-graphviz = { path = "../fabro-graphviz" } fabro-workflows = { path = "../fabro-workflows", features = ["exedev"] } fabro-github = { path = "../fabro-github" } fabro-agent = { path = "../fabro-agent" } diff --git a/lib/crates/fabro-api/src/server.rs b/lib/crates/fabro-api/src/server.rs index a6ae11b7f..22533cced 100644 --- a/lib/crates/fabro-api/src/server.rs +++ b/lib/crates/fabro-api/src/server.rs @@ -65,7 +65,7 @@ impl ListResponse { /// Snapshot of a managed run. struct ManagedRun { dot_source: String, - graph: fabro_workflows::graph::Graph, + graph: fabro_graphviz::graph::Graph, status: RunStatus, error: Option, created_at: chrono::DateTime, diff --git a/lib/crates/fabro-graphviz/Cargo.toml b/lib/crates/fabro-graphviz/Cargo.toml new file mode 100644 index 000000000..0c069fce2 --- /dev/null +++ b/lib/crates/fabro-graphviz/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "fabro-graphviz" +edition.workspace = true +version.workspace = true +license.workspace = true +description = "Graphviz DOT parser and typed graph data model" + +[lib] +doctest = false + +[dependencies] +nom = "7" +serde = { workspace = true } +thiserror = { workspace = true } diff --git a/lib/crates/fabro-graphviz/src/error.rs b/lib/crates/fabro-graphviz/src/error.rs new file mode 100644 index 000000000..fb9ac8b3c --- /dev/null +++ b/lib/crates/fabro-graphviz/src/error.rs @@ -0,0 +1,7 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum GraphvizError { + #[error("Parse error: {0}")] + Parse(String), +} diff --git a/lib/crates/fabro-workflows/src/graph/mod.rs b/lib/crates/fabro-graphviz/src/graph/mod.rs similarity index 100% rename from lib/crates/fabro-workflows/src/graph/mod.rs rename to lib/crates/fabro-graphviz/src/graph/mod.rs diff --git a/lib/crates/fabro-workflows/src/graph/types.rs b/lib/crates/fabro-graphviz/src/graph/types.rs similarity index 100% rename from lib/crates/fabro-workflows/src/graph/types.rs rename to lib/crates/fabro-graphviz/src/graph/types.rs diff --git a/lib/crates/fabro-graphviz/src/lib.rs b/lib/crates/fabro-graphviz/src/lib.rs new file mode 100644 index 000000000..96b17c8c8 --- /dev/null +++ b/lib/crates/fabro-graphviz/src/lib.rs @@ -0,0 +1,3 @@ +pub mod error; +pub mod graph; +pub mod parser; diff --git a/lib/crates/fabro-workflows/src/parser/ast.rs b/lib/crates/fabro-graphviz/src/parser/ast.rs similarity index 100% rename from lib/crates/fabro-workflows/src/parser/ast.rs rename to lib/crates/fabro-graphviz/src/parser/ast.rs diff --git a/lib/crates/fabro-workflows/src/parser/grammar.rs b/lib/crates/fabro-graphviz/src/parser/grammar.rs similarity index 100% rename from lib/crates/fabro-workflows/src/parser/grammar.rs rename to lib/crates/fabro-graphviz/src/parser/grammar.rs diff --git a/lib/crates/fabro-workflows/src/parser/lexer.rs b/lib/crates/fabro-graphviz/src/parser/lexer.rs similarity index 100% rename from lib/crates/fabro-workflows/src/parser/lexer.rs rename to lib/crates/fabro-graphviz/src/parser/lexer.rs diff --git a/lib/crates/fabro-workflows/src/parser/mod.rs b/lib/crates/fabro-graphviz/src/parser/mod.rs similarity index 95% rename from lib/crates/fabro-workflows/src/parser/mod.rs rename to lib/crates/fabro-graphviz/src/parser/mod.rs index 529e3e2be..d1080c612 100644 --- a/lib/crates/fabro-workflows/src/parser/mod.rs +++ b/lib/crates/fabro-graphviz/src/parser/mod.rs @@ -3,7 +3,7 @@ pub mod grammar; pub mod lexer; pub mod semantic; -use crate::error::FabroError; +use crate::error::GraphvizError; use crate::graph::types::Graph; use self::ast::DotGraph; @@ -17,14 +17,14 @@ use self::ast::DotGraph; /// /// Returns an error if the input is not valid DOT syntax or contains /// trailing content after the graph definition. -pub fn parse_ast(input: &str) -> Result { +pub fn parse_ast(input: &str) -> Result { let stripped = lexer::strip_comments(input); let (rest, dot_graph) = grammar::parse_dot_graph(&stripped) - .map_err(|e| FabroError::Parse(format!("grammar error: {e}")))?; + .map_err(|e| GraphvizError::Parse(format!("grammar error: {e}")))?; let remaining = rest.trim(); if !remaining.is_empty() { - return Err(FabroError::Parse(format!( + return Err(GraphvizError::Parse(format!( "unexpected trailing content: {:?}", &remaining[..remaining.len().min(50)] ))); @@ -42,7 +42,7 @@ pub fn parse_ast(input: &str) -> Result { /// /// Returns an error if the input is not valid DOT syntax or contains /// trailing content after the graph definition. -pub fn parse(input: &str) -> Result { +pub fn parse(input: &str) -> Result { let dot_graph = parse_ast(input)?; semantic::ast_to_graph(&dot_graph) } diff --git a/lib/crates/fabro-workflows/src/parser/semantic.rs b/lib/crates/fabro-graphviz/src/parser/semantic.rs similarity index 99% rename from lib/crates/fabro-workflows/src/parser/semantic.rs rename to lib/crates/fabro-graphviz/src/parser/semantic.rs index b753f0682..abe5f8702 100644 --- a/lib/crates/fabro-workflows/src/parser/semantic.rs +++ b/lib/crates/fabro-graphviz/src/parser/semantic.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::time::Duration; -use crate::error::FabroError; +use crate::error::GraphvizError; use crate::graph::types::{AttrValue, Edge, Graph, Node}; use crate::parser::ast::{AstValue, AttrBlock, DotGraph, Statement}; @@ -267,7 +267,7 @@ impl SemanticState { /// # Errors /// /// Returns an error if the AST cannot be converted to a valid graph. -pub fn ast_to_graph(dot: &DotGraph) -> Result { +pub fn ast_to_graph(dot: &DotGraph) -> Result { let mut state = SemanticState::new(dot.name.clone()); let empty = HashMap::new(); state.process_statements(&dot.statements, None, &empty, &empty); diff --git a/lib/crates/fabro-workflows/Cargo.toml b/lib/crates/fabro-workflows/Cargo.toml index 42f804caa..6e5ac6bed 100644 --- a/lib/crates/fabro-workflows/Cargo.toml +++ b/lib/crates/fabro-workflows/Cargo.toml @@ -21,6 +21,7 @@ clap.workspace = true anyhow.workspace = true dotenvy.workspace = true fabro-agent = { path = "../fabro-agent" } +fabro-graphviz = { path = "../fabro-graphviz" } fabro-devcontainer = { path = "../fabro-devcontainer" } fabro-exe = { path = "../fabro-exe", optional = true } fabro-ssh = { path = "../fabro-ssh" } @@ -39,7 +40,6 @@ rand.workspace = true async-trait.workspace = true futures.workspace = true chrono = { workspace = true, features = ["serde"] } -nom = "7" toml.workspace = true dirs = "6" dialoguer.workspace = true diff --git a/lib/crates/fabro-workflows/src/cli/backend.rs b/lib/crates/fabro-workflows/src/cli/backend.rs index b86c92c58..f1bdba09a 100644 --- a/lib/crates/fabro-workflows/src/cli/backend.rs +++ b/lib/crates/fabro-workflows/src/cli/backend.rs @@ -15,9 +15,9 @@ use fabro_llm::provider::Provider; use crate::context::Context; use crate::error::FabroError; use crate::event::WorkflowRunEvent; -use crate::graph::Node; use crate::handler::agent::{CodergenBackend, CodergenResult}; use crate::outcome::StageUsage; +use fabro_graphviz::graph::Node; fn build_profile(model: &str, provider: Provider) -> Box { match provider { diff --git a/lib/crates/fabro-workflows/src/cli/cli_backend.rs b/lib/crates/fabro-workflows/src/cli/cli_backend.rs index 2c63f5f03..a165d54a7 100644 --- a/lib/crates/fabro-workflows/src/cli/cli_backend.rs +++ b/lib/crates/fabro-workflows/src/cli/cli_backend.rs @@ -10,9 +10,9 @@ use fabro_llm::provider::Provider; use crate::context::Context; use crate::error::FabroError; use crate::event::{EventEmitter, WorkflowRunEvent}; -use crate::graph::Node; use crate::handler::agent::{CodergenBackend, CodergenResult}; use crate::outcome::StageUsage; +use fabro_graphviz::graph::Node; /// Maps a provider to its corresponding CLI tool metadata. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -796,7 +796,7 @@ impl CodergenBackend for BackendRouter { #[cfg(test)] mod tests { use super::*; - use crate::graph::AttrValue; + use fabro_graphviz::graph::AttrValue; // -- AgentCli -- diff --git a/lib/crates/fabro-workflows/src/cli/parse.rs b/lib/crates/fabro-workflows/src/cli/parse.rs index 9e5ba3777..c6582c986 100644 --- a/lib/crates/fabro-workflows/src/cli/parse.rs +++ b/lib/crates/fabro-workflows/src/cli/parse.rs @@ -15,7 +15,7 @@ pub fn parse_command(args: &ParseArgs) -> anyhow::Result<()> { fn parse_command_to(args: &ParseArgs, mut out: impl Write) -> anyhow::Result<()> { let (dot_path, _cfg) = super::project_config::resolve_workflow(&args.workflow)?; let source = read_workflow_file(&dot_path)?; - let ast = crate::parser::parse_ast(&source)?; + let ast = fabro_graphviz::parser::parse_ast(&source)?; serde_json::to_writer_pretty(&mut out, &ast)?; writeln!(out)?; Ok(()) @@ -24,7 +24,7 @@ fn parse_command_to(args: &ParseArgs, mut out: impl Write) -> anyhow::Result<()> #[cfg(test)] mod tests { use super::*; - use crate::parser::ast::DotGraph; + use fabro_graphviz::parser::ast::DotGraph; use std::io::Write; use std::path::PathBuf; diff --git a/lib/crates/fabro-workflows/src/cli/rewind.rs b/lib/crates/fabro-workflows/src/cli/rewind.rs index 4692a8788..d8e45ceee 100644 --- a/lib/crates/fabro-workflows/src/cli/rewind.rs +++ b/lib/crates/fabro-workflows/src/cli/rewind.rs @@ -11,7 +11,7 @@ use git2::{Oid, Repository, Signature}; use crate::checkpoint::Checkpoint; use crate::git::MetadataStore; -use crate::graph::types::Graph; +use fabro_graphviz::graph::types::Graph; /// Rewind a workflow run to an earlier checkpoint. #[derive(Debug, Args)] @@ -484,7 +484,7 @@ pub fn load_parallel_map(store: &Store, run_id: &str) -> HashMap _ => return HashMap::new(), }; let dot_source = String::from_utf8_lossy(&graph_bytes); - let graph = match crate::parser::parse(&dot_source) { + let graph = match fabro_graphviz::parser::parse(&dot_source) { Ok(g) => g, Err(_) => return HashMap::new(), }; @@ -708,50 +708,50 @@ mod tests { #[test] fn parallel_interior_detection() { let mut graph = Graph::new("test"); - let mut parallel_node = crate::graph::types::Node::new("parallel1"); + let mut parallel_node = fabro_graphviz::graph::types::Node::new("parallel1"); parallel_node.attrs.insert( "shape".to_string(), - crate::graph::types::AttrValue::String("component".to_string()), + fabro_graphviz::graph::types::AttrValue::String("component".to_string()), ); graph.nodes.insert("parallel1".to_string(), parallel_node); - let mut fan_in = crate::graph::types::Node::new("fan_in1"); + let mut fan_in = fabro_graphviz::graph::types::Node::new("fan_in1"); fan_in.attrs.insert( "shape".to_string(), - crate::graph::types::AttrValue::String("tripleoctagon".to_string()), + fabro_graphviz::graph::types::AttrValue::String("tripleoctagon".to_string()), ); graph.nodes.insert("fan_in1".to_string(), fan_in); - let mut a = crate::graph::types::Node::new("a"); + let mut a = fabro_graphviz::graph::types::Node::new("a"); a.attrs.insert( "shape".to_string(), - crate::graph::types::AttrValue::String("box".to_string()), + fabro_graphviz::graph::types::AttrValue::String("box".to_string()), ); graph.nodes.insert("a".to_string(), a); - let mut b = crate::graph::types::Node::new("b"); + let mut b = fabro_graphviz::graph::types::Node::new("b"); b.attrs.insert( "shape".to_string(), - crate::graph::types::AttrValue::String("box".to_string()), + fabro_graphviz::graph::types::AttrValue::String("box".to_string()), ); graph.nodes.insert("b".to_string(), b); - graph.edges.push(crate::graph::types::Edge { + graph.edges.push(fabro_graphviz::graph::types::Edge { from: "parallel1".to_string(), to: "a".to_string(), attrs: HashMap::new(), }); - graph.edges.push(crate::graph::types::Edge { + graph.edges.push(fabro_graphviz::graph::types::Edge { from: "parallel1".to_string(), to: "b".to_string(), attrs: HashMap::new(), }); - graph.edges.push(crate::graph::types::Edge { + graph.edges.push(fabro_graphviz::graph::types::Edge { from: "a".to_string(), to: "fan_in1".to_string(), attrs: HashMap::new(), }); - graph.edges.push(crate::graph::types::Edge { + graph.edges.push(fabro_graphviz::graph::types::Edge { from: "b".to_string(), to: "fan_in1".to_string(), attrs: HashMap::new(), diff --git a/lib/crates/fabro-workflows/src/cli/run.rs b/lib/crates/fabro-workflows/src/cli/run.rs index 860fc5263..5943ec3e7 100644 --- a/lib/crates/fabro-workflows/src/cli/run.rs +++ b/lib/crates/fabro-workflows/src/cli/run.rs @@ -59,7 +59,7 @@ fn resolve_cli_goal( /// Apply goal to the graph from TOML config or CLI flag. /// Precedence: CLI `--goal` / `--goal-file` > TOML `goal` > DOT `graph [goal="..."]`. fn apply_goal_override( - graph: &mut crate::graph::types::Graph, + graph: &mut fabro_graphviz::graph::types::Graph, cli_goal: Option<&str>, toml_goal: Option<&str>, ) { @@ -68,7 +68,7 @@ fn apply_goal_override( debug!(goal = %goal, "overriding graph goal"); graph.attrs.insert( "goal".to_string(), - crate::graph::types::AttrValue::String(goal.to_string()), + fabro_graphviz::graph::types::AttrValue::String(goal.to_string()), ); } } @@ -81,7 +81,7 @@ fn resolve_model_provider( cli_provider: Option<&str>, run_cfg: Option<&WorkflowRunConfig>, run_defaults: &RunDefaults, - graph: &crate::graph::types::Graph, + graph: &fabro_graphviz::graph::types::Graph, ) -> (String, Option) { let toml_model = run_cfg .and_then(|c| c.llm.as_ref()) @@ -403,13 +403,13 @@ pub async fn run_command( apply_goal_override(&mut graph, cli_goal.as_deref(), toml_goal); // Inline @file references in the (possibly overridden) goal - if let Some(crate::graph::types::AttrValue::String(goal)) = graph.attrs.get("goal") { + if let Some(fabro_graphviz::graph::types::AttrValue::String(goal)) = graph.attrs.get("goal") { let fallback = dirs::home_dir().map(|h| h.join(".fabro")); let resolved = crate::transform::resolve_file_ref(goal, dot_dir, fallback.as_deref()); if resolved != *goal { graph.attrs.insert( "goal".to_string(), - crate::graph::types::AttrValue::String(resolved), + fabro_graphviz::graph::types::AttrValue::String(resolved), ); } } @@ -1928,7 +1928,7 @@ fn print_assets(run_dir: &std::path::Path, styles: &Styles) { /// a styled check report. #[allow(clippy::too_many_arguments)] async fn run_preflight( - graph: &crate::graph::types::Graph, + graph: &fabro_graphviz::graph::types::Graph, run_cfg: &Option, args: &RunArgs, run_defaults: &RunDefaults, @@ -2113,7 +2113,7 @@ async fn run_preflight( // Collect all distinct (model, provider) pairs from LLM nodes let mut model_providers = std::collections::BTreeSet::new(); for node in graph.nodes.values() { - if !crate::graph::types::is_llm_handler_type(node.handler_type()) { + if !fabro_graphviz::graph::types::is_llm_handler_type(node.handler_type()) { continue; } let node_model = node.model().unwrap_or(&model); @@ -2501,7 +2501,7 @@ mod tests { #[test] fn apply_goal_override_cli_wins_over_toml() { - use crate::graph::types::{AttrValue, Graph}; + use fabro_graphviz::graph::types::{AttrValue, Graph}; let mut graph = Graph::new("test"); graph.attrs.insert( "goal".to_string(), @@ -2513,7 +2513,7 @@ mod tests { #[test] fn apply_goal_override_toml_wins_over_dot() { - use crate::graph::types::{AttrValue, Graph}; + use fabro_graphviz::graph::types::{AttrValue, Graph}; let mut graph = Graph::new("test"); graph.attrs.insert( "goal".to_string(), @@ -2525,7 +2525,7 @@ mod tests { #[test] fn apply_goal_override_noop_when_none() { - use crate::graph::types::{AttrValue, Graph}; + use fabro_graphviz::graph::types::{AttrValue, Graph}; let mut graph = Graph::new("test"); graph.attrs.insert( "goal".to_string(), @@ -2558,7 +2558,7 @@ mod tests { #[test] fn resolve_model_provider_defaults() { - let graph = crate::graph::types::Graph::new("test"); + let graph = fabro_graphviz::graph::types::Graph::new("test"); let defaults = RunDefaults::default(); let (model, provider) = resolve_model_provider(None, None, None, &defaults, &graph); assert_eq!(model, "claude-opus-4-6"); @@ -2568,7 +2568,7 @@ mod tests { #[test] fn resolve_model_provider_cli_overrides_toml() { - let graph = crate::graph::types::Graph::new("test"); + let graph = fabro_graphviz::graph::types::Graph::new("test"); let defaults = RunDefaults::default(); let cfg = run_config::WorkflowRunConfig { version: 1, @@ -2603,8 +2603,8 @@ mod tests { #[test] fn resolve_model_provider_toml_overrides_graph() { - use crate::graph::types::AttrValue; - let mut graph = crate::graph::types::Graph::new("test"); + use fabro_graphviz::graph::types::AttrValue; + let mut graph = fabro_graphviz::graph::types::Graph::new("test"); graph.attrs.insert( "default_model".to_string(), AttrValue::String("graph-model".to_string()), @@ -2642,8 +2642,8 @@ mod tests { #[test] fn resolve_model_provider_graph_attrs_used_as_fallback() { - use crate::graph::types::AttrValue; - let mut graph = crate::graph::types::Graph::new("test"); + use fabro_graphviz::graph::types::AttrValue; + let mut graph = fabro_graphviz::graph::types::Graph::new("test"); graph.attrs.insert( "default_model".to_string(), AttrValue::String("gpt-5.2".to_string()), @@ -2661,7 +2661,7 @@ mod tests { #[test] fn resolve_model_provider_alias_expansion() { - let graph = crate::graph::types::Graph::new("test"); + let graph = fabro_graphviz::graph::types::Graph::new("test"); let defaults = RunDefaults::default(); let (model, provider) = resolve_model_provider(Some("opus"), None, None, &defaults, &graph); assert_eq!(model, "claude-opus-4-6"); @@ -2670,7 +2670,7 @@ mod tests { #[test] fn resolve_model_provider_run_defaults_used() { - let graph = crate::graph::types::Graph::new("test"); + let graph = fabro_graphviz::graph::types::Graph::new("test"); let defaults = RunDefaults { llm: Some(run_config::LlmConfig { model: Some("default-model".to_string()), @@ -2686,7 +2686,7 @@ mod tests { #[test] fn resolve_model_provider_toml_overrides_run_defaults() { - let graph = crate::graph::types::Graph::new("test"); + let graph = fabro_graphviz::graph::types::Graph::new("test"); let defaults = RunDefaults { llm: Some(run_config::LlmConfig { model: Some("default-model".to_string()), diff --git a/lib/crates/fabro-workflows/src/cli/workflow.rs b/lib/crates/fabro-workflows/src/cli/workflow.rs index 494d86997..12fe7ba3d 100644 --- a/lib/crates/fabro-workflows/src/cli/workflow.rs +++ b/lib/crates/fabro-workflows/src/cli/workflow.rs @@ -366,7 +366,7 @@ mod tests { let content = fs::read_to_string(tmp.path().join("fabro/workflows/test-wf/workflow.fabro")).unwrap(); - let graph = crate::parser::parse(&content).expect("generated .fabro should parse"); + let graph = fabro_graphviz::parser::parse(&content).expect("generated .fabro should parse"); let diagnostics = crate::validation::validate(&graph, &[]); let errors: Vec<_> = diagnostics .iter() diff --git a/lib/crates/fabro-workflows/src/engine.rs b/lib/crates/fabro-workflows/src/engine.rs index 9bc44c091..0d82aee3b 100644 --- a/lib/crates/fabro-workflows/src/engine.rs +++ b/lib/crates/fabro-workflows/src/engine.rs @@ -22,13 +22,13 @@ use crate::context; use crate::context::Context; use crate::error::{FabroError, FailureClass, FailureSignature, Result}; use crate::event::{EventEmitter, WorkflowRunEvent}; -use crate::graph::{Edge, Graph, Node}; use crate::handler::{EngineServices, HandlerRegistry}; use crate::hook::{HookContext, HookDecision, HookEvent, HookRunner}; use crate::interviewer::Interviewer; use crate::millis_u64; use crate::outcome::{Outcome, StageStatus}; use crate::preamble::build_preamble; +use fabro_graphviz::graph::{Edge, Graph, Node}; /// Classify the failure mode of a completed outcome. /// @@ -2499,10 +2499,10 @@ impl WorkflowRunEngine { #[cfg(test)] mod tests { use super::*; - use crate::graph::AttrValue; use crate::handler::start::StartHandler; use crate::handler::Handler as HandlerTrait; use async_trait::async_trait; + use fabro_graphviz::graph::AttrValue; use std::time::Duration; fn local_env() -> Arc { diff --git a/lib/crates/fabro-workflows/src/error.rs b/lib/crates/fabro-workflows/src/error.rs index dea0e1545..f085486ed 100644 --- a/lib/crates/fabro-workflows/src/error.rs +++ b/lib/crates/fabro-workflows/src/error.rs @@ -417,6 +417,14 @@ impl From for FabroError { } } +impl From for FabroError { + fn from(e: fabro_graphviz::error::GraphvizError) -> Self { + match e { + fabro_graphviz::error::GraphvizError::Parse(msg) => FabroError::Parse(msg), + } + } +} + pub type Result = std::result::Result; #[cfg(test)] diff --git a/lib/crates/fabro-workflows/src/handler/agent.rs b/lib/crates/fabro-workflows/src/handler/agent.rs index 342b15ec5..71afe66e5 100644 --- a/lib/crates/fabro-workflows/src/handler/agent.rs +++ b/lib/crates/fabro-workflows/src/handler/agent.rs @@ -9,8 +9,8 @@ use crate::context::keys; use crate::context::Context; use crate::error::FabroError; use crate::event::EventEmitter; -use crate::graph::{Graph, Node}; use crate::outcome::{Outcome, StageUsage}; +use fabro_graphviz::graph::{Graph, Node}; use super::{EngineServices, Handler}; @@ -375,7 +375,7 @@ impl Handler for AgentHandler { mod tests { use super::*; use crate::event::EventEmitter; - use crate::graph::AttrValue; + use fabro_graphviz::graph::AttrValue; use tempfile::TempDir; fn make_services() -> EngineServices { diff --git a/lib/crates/fabro-workflows/src/handler/command.rs b/lib/crates/fabro-workflows/src/handler/command.rs index 79bd8e626..c4368922f 100644 --- a/lib/crates/fabro-workflows/src/handler/command.rs +++ b/lib/crates/fabro-workflows/src/handler/command.rs @@ -5,8 +5,8 @@ use async_trait::async_trait; use crate::context::keys; use crate::context::Context; use crate::error::FabroError; -use crate::graph::{Graph, Node}; use crate::outcome::Outcome; +use fabro_graphviz::graph::{Graph, Node}; use super::{EngineServices, Handler}; @@ -177,8 +177,8 @@ impl Handler for CommandHandler { #[cfg(test)] mod tests { use super::*; - use crate::graph::AttrValue; use crate::outcome::StageStatus; + use fabro_graphviz::graph::AttrValue; use std::time::Duration; fn make_services() -> EngineServices { diff --git a/lib/crates/fabro-workflows/src/handler/conditional.rs b/lib/crates/fabro-workflows/src/handler/conditional.rs index 90ecd9318..0dcd9be4b 100644 --- a/lib/crates/fabro-workflows/src/handler/conditional.rs +++ b/lib/crates/fabro-workflows/src/handler/conditional.rs @@ -4,8 +4,8 @@ use async_trait::async_trait; use crate::context::Context; use crate::error::FabroError; -use crate::graph::{Graph, Node}; use crate::outcome::Outcome; +use fabro_graphviz::graph::{Graph, Node}; use super::{EngineServices, Handler}; diff --git a/lib/crates/fabro-workflows/src/handler/exit.rs b/lib/crates/fabro-workflows/src/handler/exit.rs index 39a3d35b5..0d2d31fad 100644 --- a/lib/crates/fabro-workflows/src/handler/exit.rs +++ b/lib/crates/fabro-workflows/src/handler/exit.rs @@ -4,8 +4,8 @@ use async_trait::async_trait; use crate::context::Context; use crate::error::FabroError; -use crate::graph::{Graph, Node}; use crate::outcome::Outcome; +use fabro_graphviz::graph::{Graph, Node}; use super::{EngineServices, Handler}; diff --git a/lib/crates/fabro-workflows/src/handler/fan_in.rs b/lib/crates/fabro-workflows/src/handler/fan_in.rs index eb93d6e5b..5d1bc8f0f 100644 --- a/lib/crates/fabro-workflows/src/handler/fan_in.rs +++ b/lib/crates/fabro-workflows/src/handler/fan_in.rs @@ -8,8 +8,8 @@ use crate::context::keys; use crate::context::Context; use crate::error::FabroError; use crate::event::EventEmitter; -use crate::graph::{Graph, Node}; use crate::outcome::Outcome; +use fabro_graphviz::graph::{Graph, Node}; use super::agent::{CodergenBackend, CodergenResult}; use super::{EngineServices, Handler}; @@ -402,7 +402,7 @@ mod tests { let mut node = Node::new("fan_in"); node.attrs.insert( "prompt".to_string(), - crate::graph::AttrValue::String("Pick the best branch".to_string()), + fabro_graphviz::graph::AttrValue::String("Pick the best branch".to_string()), ); let context = Context::new(); context.set( @@ -461,7 +461,7 @@ mod tests { let mut node = Node::new("fan_in"); node.attrs.insert( "prompt".to_string(), - crate::graph::AttrValue::String("Pick the best branch".to_string()), + fabro_graphviz::graph::AttrValue::String("Pick the best branch".to_string()), ); let context = Context::new(); context.set( diff --git a/lib/crates/fabro-workflows/src/handler/human.rs b/lib/crates/fabro-workflows/src/handler/human.rs index 791287f88..619565d38 100644 --- a/lib/crates/fabro-workflows/src/handler/human.rs +++ b/lib/crates/fabro-workflows/src/handler/human.rs @@ -8,12 +8,12 @@ use crate::context::keys; use crate::context::Context; use crate::error::FabroError; use crate::event::{EventEmitter, WorkflowRunEvent}; -use crate::graph::{Graph, Node}; use crate::interviewer::{ Answer, AnswerValue, Interviewer, Question, QuestionOption, QuestionType, }; use crate::millis_u64; use crate::outcome::Outcome; +use fabro_graphviz::graph::{Graph, Node}; use super::{EngineServices, Handler}; @@ -327,9 +327,9 @@ fn answer_text(answer: &Answer) -> String { #[cfg(test)] mod tests { use super::*; - use crate::graph::{AttrValue, Edge}; use crate::interviewer::auto_approve::AutoApproveInterviewer; use crate::interviewer::recording::RecordingInterviewer; + use fabro_graphviz::graph::{AttrValue, Edge}; fn make_services() -> EngineServices { EngineServices::test_default() diff --git a/lib/crates/fabro-workflows/src/handler/manager_loop.rs b/lib/crates/fabro-workflows/src/handler/manager_loop.rs index 8890e2f15..c416147c7 100644 --- a/lib/crates/fabro-workflows/src/handler/manager_loop.rs +++ b/lib/crates/fabro-workflows/src/handler/manager_loop.rs @@ -11,10 +11,10 @@ use crate::context::keys; use crate::context::Context; use crate::engine::{RunConfig, WorkflowRunEngine}; use crate::error::FabroError; -use crate::graph::{Graph, Node}; use crate::outcome::{Outcome, StageStatus}; use crate::validation; use crate::workflow::{prepare_from_file, prepare_from_source}; +use fabro_graphviz::graph::{Graph, Node}; use super::{EngineServices, Handler}; @@ -95,7 +95,7 @@ impl Handler for SubWorkflowHandler { let poll_interval = node .attrs .get("manager.poll_interval") - .and_then(super::super::graph::types::AttrValue::as_duration) + .and_then(fabro_graphviz::graph::types::AttrValue::as_duration) .unwrap_or_else(|| { let raw = node .attrs @@ -108,7 +108,7 @@ impl Handler for SubWorkflowHandler { let max_cycles = node .attrs .get("manager.max_cycles") - .and_then(super::super::graph::types::AttrValue::as_i64) + .and_then(fabro_graphviz::graph::types::AttrValue::as_i64) .unwrap_or(1000); let max_cycles = u64::try_from(max_cycles).unwrap_or(1000).max(1); @@ -254,10 +254,10 @@ impl Handler for SubWorkflowHandler { #[cfg(test)] mod tests { use super::*; - use crate::graph::AttrValue; use crate::handler::exit::ExitHandler; use crate::handler::start::StartHandler; use crate::handler::HandlerRegistry; + use fabro_graphviz::graph::AttrValue; fn make_services() -> EngineServices { let mut services = EngineServices::test_default(); diff --git a/lib/crates/fabro-workflows/src/handler/mod.rs b/lib/crates/fabro-workflows/src/handler/mod.rs index 953306880..db4552935 100644 --- a/lib/crates/fabro-workflows/src/handler/mod.rs +++ b/lib/crates/fabro-workflows/src/handler/mod.rs @@ -21,10 +21,10 @@ use crate::context::Context; use crate::engine::GitState; use crate::error::FabroError; use crate::event::EventEmitter; -use crate::graph::{shape_to_handler_type, Graph, Node}; use crate::hook::{HookContext, HookDecision, HookRunner}; use crate::interviewer::Interviewer; use crate::outcome::Outcome; +use fabro_graphviz::graph::{shape_to_handler_type, Graph, Node}; /// Shared services available to all handlers during execution. pub struct EngineServices { @@ -222,7 +222,7 @@ pub fn default_registry( #[cfg(test)] mod tests { use super::*; - use crate::graph::AttrValue; + use fabro_graphviz::graph::AttrValue; struct TestHandler { _name: String, diff --git a/lib/crates/fabro-workflows/src/handler/parallel.rs b/lib/crates/fabro-workflows/src/handler/parallel.rs index 6da3a82db..d09454f0e 100644 --- a/lib/crates/fabro-workflows/src/handler/parallel.rs +++ b/lib/crates/fabro-workflows/src/handler/parallel.rs @@ -10,11 +10,11 @@ use crate::context::keys; use crate::context::Context; use crate::error::FabroError; use crate::event::WorkflowRunEvent; -use crate::graph::{Graph, Node}; use crate::hook::{HookContext, HookEvent}; use crate::millis_u64; use crate::outcome::{Outcome, StageStatus}; use fabro_agent::LocalSandbox; +use fabro_graphviz::graph::{Graph, Node}; use super::{EngineServices, Handler}; @@ -312,7 +312,7 @@ impl Handler for ParallelHandler { let max_parallel = node .attrs .get("max_parallel") - .and_then(super::super::graph::types::AttrValue::as_i64) + .and_then(fabro_graphviz::graph::types::AttrValue::as_i64) .unwrap_or(4); let max_parallel = usize::try_from(max_parallel).unwrap_or(4).max(1); @@ -830,7 +830,7 @@ fn find_join_node(results: &[BranchResult], graph: &Graph) -> Option { #[cfg(test)] mod tests { use super::*; - use crate::graph::{AttrValue, Edge}; + use fabro_graphviz::graph::{AttrValue, Edge}; fn make_services() -> EngineServices { EngineServices::test_default() diff --git a/lib/crates/fabro-workflows/src/handler/prompt.rs b/lib/crates/fabro-workflows/src/handler/prompt.rs index 1bfb27fe7..f0164307f 100644 --- a/lib/crates/fabro-workflows/src/handler/prompt.rs +++ b/lib/crates/fabro-workflows/src/handler/prompt.rs @@ -7,8 +7,8 @@ use fabro_llm::provider::Provider; use crate::context::keys; use crate::context::Context; use crate::error::FabroError; -use crate::graph::{Graph, Node}; use crate::outcome::Outcome; +use fabro_graphviz::graph::{Graph, Node}; use super::agent::{ expand_variables, extract_status_fields, truncate, CodergenBackend, CodergenResult, @@ -158,7 +158,7 @@ impl Handler for PromptHandler { #[cfg(test)] mod tests { use super::*; - use crate::graph::AttrValue; + use fabro_graphviz::graph::AttrValue; use std::sync::Arc; use tempfile::TempDir; diff --git a/lib/crates/fabro-workflows/src/handler/start.rs b/lib/crates/fabro-workflows/src/handler/start.rs index 2b7c44d78..3b2c867c8 100644 --- a/lib/crates/fabro-workflows/src/handler/start.rs +++ b/lib/crates/fabro-workflows/src/handler/start.rs @@ -4,8 +4,8 @@ use async_trait::async_trait; use crate::context::Context; use crate::error::FabroError; -use crate::graph::{Graph, Node}; use crate::outcome::Outcome; +use fabro_graphviz::graph::{Graph, Node}; use super::{EngineServices, Handler}; diff --git a/lib/crates/fabro-workflows/src/handler/wait.rs b/lib/crates/fabro-workflows/src/handler/wait.rs index 5a45dfafb..17c822e7c 100644 --- a/lib/crates/fabro-workflows/src/handler/wait.rs +++ b/lib/crates/fabro-workflows/src/handler/wait.rs @@ -4,8 +4,8 @@ use async_trait::async_trait; use crate::context::Context; use crate::error::FabroError; -use crate::graph::{AttrValue, Graph, Node}; use crate::outcome::Outcome; +use fabro_graphviz::graph::{AttrValue, Graph, Node}; use super::{EngineServices, Handler}; diff --git a/lib/crates/fabro-workflows/src/hook/types.rs b/lib/crates/fabro-workflows/src/hook/types.rs index 04ea3782b..09baa287a 100644 --- a/lib/crates/fabro-workflows/src/hook/types.rs +++ b/lib/crates/fabro-workflows/src/hook/types.rs @@ -104,7 +104,7 @@ pub struct HookContext { impl HookContext { /// Populate node-related fields from a graph `Node`. - pub fn set_node(&mut self, node: &crate::graph::Node) { + pub fn set_node(&mut self, node: &fabro_graphviz::graph::Node) { self.node_id = Some(node.id.clone()); self.node_label = Some(node.label().to_string()); self.handler_type = node.handler_type().map(String::from); diff --git a/lib/crates/fabro-workflows/src/lib.rs b/lib/crates/fabro-workflows/src/lib.rs index caa84e33d..b473e4169 100644 --- a/lib/crates/fabro-workflows/src/lib.rs +++ b/lib/crates/fabro-workflows/src/lib.rs @@ -38,13 +38,11 @@ pub mod engine; pub mod error; pub mod event; pub mod git; -pub mod graph; pub mod handler; pub mod hook; pub mod interviewer; pub mod manifest; pub mod outcome; -pub mod parser; pub mod preamble; pub mod pull_request; pub mod retro; diff --git a/lib/crates/fabro-workflows/src/preamble.rs b/lib/crates/fabro-workflows/src/preamble.rs index 8a4661f64..ee8a8ab73 100644 --- a/lib/crates/fabro-workflows/src/preamble.rs +++ b/lib/crates/fabro-workflows/src/preamble.rs @@ -3,8 +3,8 @@ use std::collections::{HashMap, HashSet}; use crate::artifact::{artifact_path, format_artifact_reference}; use crate::context::keys; use crate::context::Context; -use crate::graph::{is_llm_handler_type, Graph, Node}; use crate::outcome::Outcome; +use fabro_graphviz::graph::{is_llm_handler_type, Graph, Node}; const COMPACT_OUTPUT_MAX_LINES: usize = 25; const SUMMARY_HIGH_OUTPUT_MAX_LINES: usize = 50; @@ -612,8 +612,8 @@ fn build_summary_preamble( #[cfg(test)] mod tests { use super::*; - use crate::graph::AttrValue; use crate::outcome::StageUsage; + use fabro_graphviz::graph::AttrValue; // --- truncate mode --- diff --git a/lib/crates/fabro-workflows/src/pull_request.rs b/lib/crates/fabro-workflows/src/pull_request.rs index a36098d6e..fe399cf47 100644 --- a/lib/crates/fabro-workflows/src/pull_request.rs +++ b/lib/crates/fabro-workflows/src/pull_request.rs @@ -184,7 +184,7 @@ fn format_arc_details_section(conclusion: &Conclusion, dot_source: Option<&str>) /// Parse a DOT source string to extract graph name, node count, and edge count. fn parse_dot_summary(dot: &str) -> (String, usize, usize) { - match crate::parser::parse(dot) { + match fabro_graphviz::parser::parse(dot) { Ok(graph) => ( format!("{}.fabro", graph.name), graph.nodes.len(), diff --git a/lib/crates/fabro-workflows/src/stylesheet.rs b/lib/crates/fabro-workflows/src/stylesheet.rs index fa8819111..099b3a153 100644 --- a/lib/crates/fabro-workflows/src/stylesheet.rs +++ b/lib/crates/fabro-workflows/src/stylesheet.rs @@ -1,5 +1,5 @@ use crate::error::FabroError; -use crate::graph::types::{AttrValue, Graph}; +use fabro_graphviz::graph::types::{AttrValue, Graph}; /// A parsed stylesheet selector. #[derive(Debug, Clone, PartialEq, Eq)] @@ -235,7 +235,7 @@ pub fn apply_stylesheet(stylesheet: &Stylesheet, graph: &mut Graph) { #[cfg(test)] mod tests { use super::*; - use crate::graph::types::Node; + use fabro_graphviz::graph::types::Node; #[test] fn parse_empty_stylesheet() { diff --git a/lib/crates/fabro-workflows/src/transform.rs b/lib/crates/fabro-workflows/src/transform.rs index 0b906da40..f423b70d5 100644 --- a/lib/crates/fabro-workflows/src/transform.rs +++ b/lib/crates/fabro-workflows/src/transform.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; -use crate::graph::{AttrValue, Edge, Graph, Node}; use crate::stylesheet::{apply_stylesheet, parse_stylesheet}; +use fabro_graphviz::graph::{AttrValue, Edge, Graph, Node}; /// A transform that modifies the pipeline graph after parsing and before validation. pub trait Transform { diff --git a/lib/crates/fabro-workflows/src/validation/mod.rs b/lib/crates/fabro-workflows/src/validation/mod.rs index 02711cbd6..b3766cab8 100644 --- a/lib/crates/fabro-workflows/src/validation/mod.rs +++ b/lib/crates/fabro-workflows/src/validation/mod.rs @@ -3,7 +3,7 @@ pub mod rules; use serde::{Deserialize, Serialize}; use crate::error::FabroError; -use crate::graph::Graph; +use fabro_graphviz::graph::Graph; /// Severity level for validation diagnostics. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -80,7 +80,7 @@ pub fn validate_or_raise( #[cfg(test)] mod tests { use super::*; - use crate::graph::{AttrValue, Edge, Graph, Node}; + use fabro_graphviz::graph::{AttrValue, Edge, Graph, Node}; fn minimal_valid_graph() -> Graph { let mut g = Graph::new("test"); diff --git a/lib/crates/fabro-workflows/src/validation/rules.rs b/lib/crates/fabro-workflows/src/validation/rules.rs index 194cf01c2..92737d950 100644 --- a/lib/crates/fabro-workflows/src/validation/rules.rs +++ b/lib/crates/fabro-workflows/src/validation/rules.rs @@ -2,7 +2,7 @@ use std::collections::{HashSet, VecDeque}; use std::str::FromStr; use crate::condition::parse_condition; -use crate::graph::{is_llm_handler_type, AttrValue, Graph}; +use fabro_graphviz::graph::{is_llm_handler_type, AttrValue, Graph}; use super::{Diagnostic, LintRule, Severity}; @@ -1165,7 +1165,7 @@ impl LintRule for RandomSelectionNoConditionsRule { #[cfg(test)] mod tests { use super::*; - use crate::graph::{AttrValue, Edge, Node}; + use fabro_graphviz::graph::{AttrValue, Edge, Node}; fn minimal_graph() -> Graph { let mut g = Graph::new("test"); diff --git a/lib/crates/fabro-workflows/src/workflow.rs b/lib/crates/fabro-workflows/src/workflow.rs index 256fa4c2f..f0b7334dd 100644 --- a/lib/crates/fabro-workflows/src/workflow.rs +++ b/lib/crates/fabro-workflows/src/workflow.rs @@ -1,12 +1,12 @@ use std::path::Path; use crate::error::FabroError; -use crate::graph::Graph; use crate::transform::{ FileInliningTransform, ProviderInferenceTransform, StylesheetApplicationTransform, Transform, VariableExpansionTransform, }; use crate::validation::{self, Diagnostic}; +use fabro_graphviz::graph::Graph; /// Builder for configuring and executing a workflow preparation. /// Collects custom transforms that run after the built-in ones. @@ -56,7 +56,7 @@ impl WorkflowBuilder { dot_source: &str, base_dir: Option<&Path>, ) -> Result<(Graph, Vec), FabroError> { - let mut graph = crate::parser::parse(dot_source)?; + let mut graph = fabro_graphviz::parser::parse(dot_source)?; // Built-in transforms (PreambleTransform moved to engine execution time) VariableExpansionTransform.apply(&mut graph); @@ -113,7 +113,7 @@ pub fn prepare_from_source(dot_source: &str) -> Result { #[cfg(test)] mod tests { use super::*; - use crate::graph::AttrValue; + use fabro_graphviz::graph::AttrValue; const MINIMAL_DOT: &str = r#"digraph Test { graph [goal="Build feature"] @@ -184,7 +184,7 @@ mod tests { fn pipeline_builder_custom_transform() { struct TagTransform; impl Transform for TagTransform { - fn apply(&self, graph: &mut crate::graph::Graph) { + fn apply(&self, graph: &mut fabro_graphviz::graph::Graph) { for node in graph.nodes.values_mut() { node.attrs .insert("tagged".to_string(), AttrValue::Boolean(true)); diff --git a/lib/crates/fabro-workflows/tests/attractor_compat.rs b/lib/crates/fabro-workflows/tests/attractor_compat.rs index d157c5e1a..926fbc986 100644 --- a/lib/crates/fabro-workflows/tests/attractor_compat.rs +++ b/lib/crates/fabro-workflows/tests/attractor_compat.rs @@ -1,8 +1,8 @@ use std::path::Path; -use fabro_workflows::parser::parse; +use fabro_graphviz::parser::parse; -fn parse_attractor_dot(filename: &str) -> Result { +fn parse_attractor_dot(filename: &str) -> Result { let path = Path::new(env!("CARGO_MANIFEST_DIR")) .join("../../../test/attractor") .join(filename); diff --git a/lib/crates/fabro-workflows/tests/daytona_integration.rs b/lib/crates/fabro-workflows/tests/daytona_integration.rs index 4e3125471..90fac8284 100644 --- a/lib/crates/fabro-workflows/tests/daytona_integration.rs +++ b/lib/crates/fabro-workflows/tests/daytona_integration.rs @@ -8,6 +8,7 @@ use std::path::Path; use std::sync::Arc; use fabro_agent::Sandbox; +use fabro_graphviz::graph::{AttrValue, Edge, Graph, Node}; use fabro_llm::provider::Provider; use fabro_workflows::artifact::sync_artifacts_to_env; use fabro_workflows::checkpoint::Checkpoint; @@ -16,7 +17,6 @@ use fabro_workflows::daytona_sandbox::{DaytonaConfig, DaytonaSandbox, DaytonaSna use fabro_workflows::engine::{RunConfig, WorkflowRunEngine}; use fabro_workflows::error::FabroError; use fabro_workflows::event::EventEmitter; -use fabro_workflows::graph::{AttrValue, Edge, Graph, Node}; use fabro_workflows::handler::exit::ExitHandler; use fabro_workflows::handler::start::StartHandler; use fabro_workflows::handler::{Handler, HandlerRegistry}; diff --git a/lib/crates/fabro-workflows/tests/integration.rs b/lib/crates/fabro-workflows/tests/integration.rs index 1c4cea998..a0fc90a9e 100644 --- a/lib/crates/fabro-workflows/tests/integration.rs +++ b/lib/crates/fabro-workflows/tests/integration.rs @@ -3,6 +3,8 @@ use std::path::Path; use std::sync::Arc; use std::time::Duration; +use fabro_graphviz::graph::{AttrValue, Edge, Graph, Node}; +use fabro_graphviz::parser::parse; use fabro_llm::provider::Provider; use fabro_workflows::checkpoint::Checkpoint; use fabro_workflows::cli::backend::AgentApiBackend; @@ -10,7 +12,6 @@ use fabro_workflows::context::Context; use fabro_workflows::engine::{RunConfig, WorkflowRunEngine}; use fabro_workflows::error::FabroError; use fabro_workflows::event::{EventEmitter, WorkflowRunEvent}; -use fabro_workflows::graph::{AttrValue, Edge, Graph, Node}; use fabro_workflows::handler::agent::{AgentHandler, CodergenBackend, CodergenResult}; use fabro_workflows::handler::command::CommandHandler; use fabro_workflows::handler::conditional::ConditionalHandler; @@ -26,7 +27,6 @@ use fabro_workflows::interviewer::queue::QueueInterviewer; use fabro_workflows::interviewer::recording::RecordingInterviewer; use fabro_workflows::interviewer::{Answer, AnswerValue, Interviewer}; use fabro_workflows::outcome::{Outcome, StageStatus}; -use fabro_workflows::parser::parse; use fabro_workflows::stylesheet::{apply_stylesheet, parse_stylesheet}; use fabro_workflows::transform::{ StylesheetApplicationTransform, Transform, VariableExpansionTransform, @@ -6015,9 +6015,9 @@ mod real_llm { use async_trait::async_trait; + use fabro_graphviz::graph::Node; use fabro_workflows::context::Context; use fabro_workflows::error::FabroError; - use fabro_workflows::graph::Node; use fabro_workflows::handler::agent::{AgentHandler, CodergenBackend, CodergenResult}; use fabro_llm::client::Client; @@ -6105,10 +6105,10 @@ mod real_llm { } use super::local_env; + use fabro_graphviz::graph::{AttrValue, Edge, Graph}; use fabro_workflows::checkpoint::Checkpoint; use fabro_workflows::engine::{RunConfig, WorkflowRunEngine}; use fabro_workflows::event::EventEmitter; - use fabro_workflows::graph::{AttrValue, Edge, Graph}; use fabro_workflows::handler::exit::ExitHandler; use fabro_workflows::handler::human::HumanHandler; use fabro_workflows::handler::start::StartHandler;