Extract fabro-graphviz crate from fabro-workflows

Move the self-contained graph/ and parser/ modules into a new
fabro-graphviz crate so the Graphviz DOT parser can be used without
pulling in the full workflow engine.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-17 11:20:55 -04:00
parent eb7089ece9
commit b822a76f79
No known key found for this signature in database
46 changed files with 140 additions and 99 deletions

12
Cargo.lock generated
View file

@ -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",

View file

@ -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" }

View file

@ -65,7 +65,7 @@ impl<T: serde::Serialize> ListResponse<T> {
/// Snapshot of a managed run.
struct ManagedRun {
dot_source: String,
graph: fabro_workflows::graph::Graph,
graph: fabro_graphviz::graph::Graph,
status: RunStatus,
error: Option<String>,
created_at: chrono::DateTime<chrono::Utc>,

View file

@ -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 }

View file

@ -0,0 +1,7 @@
use thiserror::Error;
#[derive(Debug, Error)]
pub enum GraphvizError {
#[error("Parse error: {0}")]
Parse(String),
}

View file

@ -0,0 +1,3 @@
pub mod error;
pub mod graph;
pub mod parser;

View file

@ -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<DotGraph, FabroError> {
pub fn parse_ast(input: &str) -> Result<DotGraph, GraphvizError> {
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<DotGraph, FabroError> {
///
/// 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<Graph, FabroError> {
pub fn parse(input: &str) -> Result<Graph, GraphvizError> {
let dot_graph = parse_ast(input)?;
semantic::ast_to_graph(&dot_graph)
}

View file

@ -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<Graph, FabroError> {
pub fn ast_to_graph(dot: &DotGraph) -> Result<Graph, GraphvizError> {
let mut state = SemanticState::new(dot.name.clone());
let empty = HashMap::new();
state.process_statements(&dot.statements, None, &empty, &empty);

View file

@ -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

View file

@ -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<dyn ProviderProfile> {
match provider {

View file

@ -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 --

View file

@ -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;

View file

@ -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<String, String>
_ => 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(),

View file

@ -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<String>) {
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<run_config::WorkflowRunConfig>,
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()),

View file

@ -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()

View file

@ -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<dyn Sandbox> {

View file

@ -417,6 +417,14 @@ impl From<SdkError> for FabroError {
}
}
impl From<fabro_graphviz::error::GraphvizError> for FabroError {
fn from(e: fabro_graphviz::error::GraphvizError) -> Self {
match e {
fabro_graphviz::error::GraphvizError::Parse(msg) => FabroError::Parse(msg),
}
}
}
pub type Result<T> = std::result::Result<T, FabroError>;
#[cfg(test)]

View file

@ -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 {

View file

@ -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 {

View file

@ -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};

View file

@ -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};

View file

@ -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(

View file

@ -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()

View file

@ -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();

View file

@ -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,

View file

@ -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<String> {
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::{AttrValue, Edge};
use fabro_graphviz::graph::{AttrValue, Edge};
fn make_services() -> EngineServices {
EngineServices::test_default()

View file

@ -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;

View file

@ -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};

View file

@ -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};

View file

@ -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);

View file

@ -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;

View file

@ -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 ---

View file

@ -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(),

View file

@ -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() {

View file

@ -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 {

View file

@ -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");

View file

@ -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");

View file

@ -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<Diagnostic>), 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<Graph, FabroError> {
#[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));

View file

@ -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<fabro_workflows::graph::types::Graph, String> {
fn parse_attractor_dot(filename: &str) -> Result<fabro_graphviz::graph::types::Graph, String> {
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../../test/attractor")
.join(filename);

View file

@ -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};

View file

@ -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;