diff --git a/lib/crates/fabro-cli/src/commands/graph.rs b/lib/crates/fabro-cli/src/commands/graph.rs index cd8f9ee74..a8208bfe7 100644 --- a/lib/crates/fabro-cli/src/commands/graph.rs +++ b/lib/crates/fabro-cli/src/commands/graph.rs @@ -11,8 +11,7 @@ use std::io::Write; use anyhow::{Context, bail}; use fabro_api::types; -use fabro_config::user::{active_settings_path, load_settings_config}; -use fabro_types::settings::SettingsLayer; +use fabro_config::user::active_settings_path; use fabro_util::terminal::Styles; use tracing::debug; @@ -36,10 +35,9 @@ pub(crate) async fn run( let built = build_run_manifest(ManifestBuildInput { workflow: args.workflow.clone(), cwd: ctx.cwd().to_path_buf(), - args_layer: SettingsLayer::default(), + args_layer: Default::default(), args: None, run_id: None, - user_layer: load_settings_config(None)?, user_settings_path: Some(active_settings_path(None)), })?; let client = ctx.server().await?; diff --git a/lib/crates/fabro-cli/src/commands/parse.rs b/lib/crates/fabro-cli/src/commands/parse.rs index 12088ca04..505671617 100644 --- a/lib/crates/fabro-cli/src/commands/parse.rs +++ b/lib/crates/fabro-cli/src/commands/parse.rs @@ -21,7 +21,7 @@ pub(crate) fn run(args: &ParseArgs) -> anyhow::Result<()> { } fn run_to(args: &ParseArgs, mut out: impl Write) -> anyhow::Result<()> { - let (dot_path, _cfg) = resolve_workflow(&args.workflow)?; + let dot_path = resolve_workflow(&args.workflow)?; let source = read_workflow_file(&dot_path)?; let ast = parse_ast(&source)?; serde_json::to_writer_pretty(&mut out, &ast)?; diff --git a/lib/crates/fabro-cli/src/commands/preflight.rs b/lib/crates/fabro-cli/src/commands/preflight.rs index 9cebf4f8c..0b6bdcbfe 100644 --- a/lib/crates/fabro-cli/src/commands/preflight.rs +++ b/lib/crates/fabro-cli/src/commands/preflight.rs @@ -1,5 +1,5 @@ use anyhow::bail; -use fabro_config::user::{active_settings_path, load_settings_config}; +use fabro_config::user::active_settings_path; use fabro_util::terminal::Styles; use crate::args::PreflightArgs; @@ -26,7 +26,6 @@ pub(crate) async fn execute( args_layer: preflight_args_layer(&args)?, args: preflight_manifest_args(&args), run_id: None, - user_layer: load_settings_config(None)?, user_settings_path: Some(active_settings_path(None)), })?; let client = ctx.server().await?; diff --git a/lib/crates/fabro-cli/src/commands/run/create.rs b/lib/crates/fabro-cli/src/commands/run/create.rs index a2de07329..775ada9de 100644 --- a/lib/crates/fabro-cli/src/commands/run/create.rs +++ b/lib/crates/fabro-cli/src/commands/run/create.rs @@ -1,4 +1,4 @@ -use fabro_config::user::{active_settings_path, load_settings_config}; +use fabro_config::user::active_settings_path; use fabro_types::RunId; use fabro_util::terminal::Styles; @@ -41,7 +41,6 @@ pub(crate) async fn create_run( args_layer: cli_args_config, args: run_manifest_args(args), run_id, - user_layer: load_settings_config(None)?, user_settings_path: Some(active_settings_path(None)), })?; let client = ctx.server().await?; diff --git a/lib/crates/fabro-cli/src/commands/validate.rs b/lib/crates/fabro-cli/src/commands/validate.rs index f174b1b73..19fcbc96a 100644 --- a/lib/crates/fabro-cli/src/commands/validate.rs +++ b/lib/crates/fabro-cli/src/commands/validate.rs @@ -1,6 +1,5 @@ use anyhow::bail; -use fabro_config::user::{active_settings_path, load_settings_config}; -use fabro_types::settings::SettingsLayer; +use fabro_config::user::active_settings_path; use fabro_util::terminal::Styles; use crate::args::ValidateArgs; @@ -19,10 +18,9 @@ pub(crate) async fn run( let built = build_run_manifest(ManifestBuildInput { workflow: args.workflow.clone(), cwd: ctx.cwd().to_path_buf(), - args_layer: SettingsLayer::default(), + args_layer: Default::default(), args: None, run_id: None, - user_layer: load_settings_config(None)?, user_settings_path: Some(active_settings_path(None)), })?; let client = ctx.server().await?; diff --git a/lib/crates/fabro-cli/src/commands/workflow/create.rs b/lib/crates/fabro-cli/src/commands/workflow/create.rs index 742c3cad5..3a23280e5 100644 --- a/lib/crates/fabro-cli/src/commands/workflow/create.rs +++ b/lib/crates/fabro-cli/src/commands/workflow/create.rs @@ -16,14 +16,14 @@ pub(super) fn create_command(args: &WorkflowCreateArgs, base_ctx: &CommandContex let printer = base_ctx.printer(); let cwd = std::env::current_dir()?; - let Some((config_path, config)) = discover_project_config(&cwd)? else { + let Some(config_path) = discover_project_config(&cwd)? else { bail!( "No .fabro/project.toml found in {cwd} or any parent directory", cwd = cwd.display() ); }; - let fabro_root = resolve_fabro_root(&config_path, &config); + let fabro_root = resolve_fabro_root(&config_path); let created = write_workflow_scaffold(args, &fabro_root)?; if base_ctx.json_output() { diff --git a/lib/crates/fabro-cli/src/commands/workflow/list.rs b/lib/crates/fabro-cli/src/commands/workflow/list.rs index 96553af73..16846762d 100644 --- a/lib/crates/fabro-cli/src/commands/workflow/list.rs +++ b/lib/crates/fabro-cli/src/commands/workflow/list.rs @@ -19,14 +19,14 @@ pub(super) fn list_command(_args: &WorkflowListArgs, base_ctx: &CommandContext) let styles = Styles::detect_stderr(); let cwd = std::env::current_dir()?; - let Some((config_path, config)) = discover_project_config(&cwd)? else { + let Some(config_path) = discover_project_config(&cwd)? else { bail!( "No .fabro/project.toml found in {cwd} or any parent directory", cwd = cwd.display() ); }; - let fabro_root = resolve_fabro_root(&config_path, &config); + let fabro_root = resolve_fabro_root(&config_path); let project_wf_dir = fabro_root.join("workflows"); let user_wf_dir = Some(fabro_util::Home::from_env().workflows_dir()); diff --git a/lib/crates/fabro-cli/src/manifest_builder.rs b/lib/crates/fabro-cli/src/manifest_builder.rs index 3879d98bd..4f4d3ee59 100644 --- a/lib/crates/fabro-cli/src/manifest_builder.rs +++ b/lib/crates/fabro-cli/src/manifest_builder.rs @@ -28,9 +28,6 @@ pub(crate) struct ManifestBuildInput { pub args_layer: SettingsLayer, pub args: Option, pub run_id: Option, - /// User-level settings layer. Production callers load via - /// `load_settings_config(None)`; tests pass `SettingsLayer::default()`. - pub user_layer: SettingsLayer, /// Path to the user settings file (for inclusion in /// `RunManifest.configs`). `None` skips the user config entry. pub user_settings_path: Option, @@ -57,7 +54,7 @@ struct WorkflowScanInput { pub(crate) fn build_run_manifest(input: ManifestBuildInput) -> Result { let root_resolution = resolve_workflow_path(&input.workflow, &input.cwd)?; - if root_resolution.workflow_config.is_none() + if root_resolution.workflow_toml_path.is_none() && !root_resolution.resolved_workflow_path.is_file() { return Err(fabro_config::Error::WorkflowNotFound( @@ -70,16 +67,22 @@ pub(crate) fn build_run_manifest(input: ManifestBuildInput) -> Result Result); @@ -186,18 +186,30 @@ impl WorkflowSettingsBuilder { self } + pub fn workflow_file(self, path: &Path) -> Result { + Ok(self.workflow_layer(run::load_run_config(path)?)) + } + #[must_use] pub fn project_layer(mut self, layer: SettingsLayer) -> Self { self.project = layer; self } + pub fn project_file(self, path: &Path) -> Result { + Ok(self.project_layer(load_settings_path(path)?)) + } + #[must_use] pub fn user_layer(mut self, layer: SettingsLayer) -> Self { self.user = layer; self } + pub fn user_file(self, path: &Path) -> Result { + Ok(self.user_layer(load_settings_path(path)?)) + } + #[must_use] pub fn server_layer(mut self, layer: SettingsLayer) -> Self { self.server = layer; diff --git a/lib/crates/fabro-config/src/project.rs b/lib/crates/fabro-config/src/project.rs index 4736db113..61239402c 100644 --- a/lib/crates/fabro-config/src/project.rs +++ b/lib/crates/fabro-config/src/project.rs @@ -23,7 +23,6 @@ const CONFIG_FILENAME: &str = ".fabro/project.toml"; pub struct WorkflowPathResolution { pub resolved_workflow_path: PathBuf, pub dot_path: PathBuf, - pub workflow_config: Option, pub workflow_toml_path: Option, pub workflow_slug: Option, } @@ -32,7 +31,7 @@ pub struct WorkflowPathResolution { /// /// Goes through [`load_settings_path`] so that relative `run.goal.file` /// paths are anchored at the directory of `path` at load time. -pub fn load_project_config(path: &Path) -> Result { +fn load_project_config(path: &Path) -> Result { let config = load_settings_path(path)?; let root = WorkflowSettingsBuilder::project_from_layer(&config) .map_err(|errors| Error::resolve("Failed to resolve project settings", errors.into()))? @@ -42,14 +41,13 @@ pub fn load_project_config(path: &Path) -> Result { } /// Walk ancestor directories from `start` looking for `.fabro/project.toml`. -/// Returns the config file path and parsed config, or `None` if not found. -pub fn discover_project_config(start: &Path) -> Result> { +/// Returns the config file path, or `None` if not found. +pub fn discover_project_config(start: &Path) -> Result> { for ancestor in start.ancestors() { let candidate = ancestor.join(CONFIG_FILENAME); if candidate.is_file() { tracing::debug!(path = %candidate.display(), "Discovered project config"); - let config = load_project_config(&candidate)?; - return Ok(Some((candidate, config))); + return Ok(Some(candidate)); } } Ok(None) @@ -93,7 +91,6 @@ pub fn resolve_workflow_path(workflow_path: &Path, cwd: &Path) -> Result Result { - let fabro_root = resolve_fabro_root(&config_path, &config); + Ok(Some(config_path)) => { + let fabro_root = resolve_fabro_root(&config_path); let project_candidate = fabro_root .join("workflows") .join(&*name) @@ -339,10 +335,10 @@ fn find_closest_match(input: &str, candidates: &[String]) -> Option { } /// Resolve a workflow argument to a DOT path and optional run config. -pub fn resolve_workflow(arg: &Path) -> Result<(PathBuf, Option)> { +pub fn resolve_workflow(arg: &Path) -> Result { let start = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); let resolution = resolve_workflow_path(arg, &start)?; - Ok((resolution.dot_path, resolution.workflow_config)) + Ok(resolution.dot_path) } /// Check whether retros are enabled in the project config. @@ -350,11 +346,15 @@ pub fn resolve_workflow(arg: &Path) -> Result<(PathBuf, Option)> pub fn is_retro_enabled() -> bool { let start = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); match discover_project_config(&start) { - Ok(Some((_path, config))) => config - .run - .as_ref() - .and_then(|r| r.execution.as_ref()) - .and_then(|e| e.retros) + Ok(Some(path)) => load_project_config(&path) + .ok() + .and_then(|config| { + config + .run + .as_ref() + .and_then(|r| r.execution.as_ref()) + .and_then(|e| e.retros) + }) .unwrap_or(false), _ => false, } @@ -383,11 +383,12 @@ fn normalize_joined_path(base_dir: &Path, reference: &Path) -> PathBuf { /// Resolve the fabro root directory from a config file path and its config. /// The returned path is the config file's parent directory joined with the /// `project.directory` value (default: `.`). -pub fn resolve_fabro_root(config_path: &Path, config: &SettingsLayer) -> PathBuf { +pub fn resolve_fabro_root(config_path: &Path) -> PathBuf { let project_dir = config_path .parent() .expect("config_path should have a parent directory"); - let root = WorkflowSettingsBuilder::project_from_layer(config) + let config = load_project_config(config_path).expect("project config should load"); + let root = WorkflowSettingsBuilder::project_from_layer(&config) .expect("project settings should resolve") .directory; normalize_joined_path(project_dir, Path::new(&root)) @@ -488,9 +489,8 @@ retros = true let sub = tmp.path().join("sub").join("dir"); fs::create_dir_all(&sub).unwrap(); - let (found_path, config) = discover_project_config(&sub).unwrap().unwrap(); + let found_path = discover_project_config(&sub).unwrap().unwrap(); assert_eq!(found_path, config_dir.join("project.toml")); - assert_eq!(config.version, Some(1)); } #[test] @@ -529,9 +529,7 @@ file = "prompts/goal.md" let config_path = config_dir.join("project.toml"); fs::write(&config_path, "_version = 1\n").unwrap(); - let config = load_project_config(&config_path).unwrap(); - - assert_eq!(resolve_fabro_root(&config_path, &config), config_dir); + assert_eq!(resolve_fabro_root(&config_path), config_dir); } #[test] @@ -550,12 +548,7 @@ directory = "../custom" ) .unwrap(); - let config = load_project_config(&config_path).unwrap(); - - assert_eq!( - resolve_fabro_root(&config_path, &config), - tmp.path().join("custom") - ); + assert_eq!(resolve_fabro_root(&config_path), tmp.path().join("custom")); } #[test]