diff --git a/apps/fabro-web/app/routes/workflow-detail.tsx b/apps/fabro-web/app/routes/workflow-detail.tsx index df5959421..1ced7fe45 100644 --- a/apps/fabro-web/app/routes/workflow-detail.tsx +++ b/apps/fabro-web/app/routes/workflow-detail.tsx @@ -44,7 +44,6 @@ function sampleSettings({ project: { name: null, description: null, - directory: ".", metadata: {}, }, workflow: { diff --git a/docs/public/api-reference/fabro-api.yaml b/docs/public/api-reference/fabro-api.yaml index f5dd4f27e..bfbb088cc 100644 --- a/docs/public/api-reference/fabro-api.yaml +++ b/docs/public/api-reference/fabro-api.yaml @@ -7894,14 +7894,12 @@ components: ProjectNamespace: type: object - required: [name, description, directory, metadata] + required: [name, description, metadata] properties: name: type: ["string", "null"] description: type: ["string", "null"] - directory: - type: string metadata: $ref: "#/components/schemas/StringMap" diff --git a/docs/superpowers/plans/2026-05-09-fixed-project-workflow-directory.md b/docs/superpowers/plans/2026-05-09-fixed-project-workflow-directory.md index 7189ab5f1..06a02953f 100644 --- a/docs/superpowers/plans/2026-05-09-fixed-project-workflow-directory.md +++ b/docs/superpowers/plans/2026-05-09-fixed-project-workflow-directory.md @@ -1,33 +1,31 @@ # Fixed Project Workflow Directory Plan **Summary** -Remove the `[project].directory` feature entirely so project workflows are always discovered, created, listed, and resolved from `/.fabro/workflows/*`. Because this is greenfield, configs that still contain `[project] directory = ...` should fail schema validation instead of being ignored or migrated. User-level workflows under `~/.fabro/workflows` remain unchanged as an additional fallback/list section. +Remove the `[project].directory` feature as an effective setting so project workflows are always discovered, created, listed, and resolved from `/.fabro/workflows/*`. Configs that still contain `[project] directory = ...` should continue parsing, but the field is deprecated and ignored. User-level workflows under `~/.fabro/workflows` remain unchanged as an additional fallback/list section. **Key Changes** -- Remove `directory` from the project config schema and resolved settings type: - - Delete `ProjectLayer::directory` and `ProjectNamespace::directory`. +- Remove `directory` from the resolved settings/API shape while tolerating old config files: + - Keep `ProjectLayer::directory` as a deprecated parse-only field so existing `project.toml` files with the field do not fail schema validation. + - Delete `ProjectNamespace::directory`. - Remove `[project] directory = "."` from built-in defaults. - - Update project resolver/tests so `[project]` only carries `name`, `description`, and `metadata`. + - Update project resolver, `fabro-types` fixtures, OpenAPI schema, and generated TypeScript client so resolved `[project]` settings only carry `name`, `description`, and `metadata`. - Make project Fabro root fixed: - - Change `resolve_fabro_root` to validate `.fabro/project.toml`, then return `config_path.parent()` directly. - - Update callers to handle `Result` and use `/workflows`. - - Delete now-unused path normalization code for joining `project.directory`. -- Update public API shape: - - Remove `directory` from `ProjectNamespace` in `docs/public/api-reference/fabro-api.yaml`. - - Update `workflow_settings_round_trip` expectations. - - Regenerate `lib/packages/fabro-api-client` so TypeScript `ProjectNamespace` no longer has `directory`. + - Delete `resolve_fabro_root`; it only exists to apply `project.directory`. + - Delete `load_project_config` and project-root path normalization code if they have no remaining callers after `resolve_fabro_root` is removed. + - At workflow discovery/create/list call sites, use the discovered config path's parent directory as the Fabro root and append `workflows`. + - Do not add a new project-config validation step for workflow directory discovery; settings validation remains the responsibility of settings-loading paths. - Update docs and hints: - - Remove references to `project.directory` and the old `fabro.root -> project.directory` rename hint. + - Remove public references to `project.directory` and replace the old `fabro.root -> project.directory` rename hint with guidance that project workflows now live under `.fabro/workflows`. - Add/keep explicit docs that project workflows live at `/.fabro/workflows//`. **Test Plan** - Config tests: - Empty settings still resolve project metadata defaults. - - `[project] directory = "..."` is rejected as an unknown field. - - `resolve_fabro_root` always returns the `.fabro` directory containing `project.toml`. + - `[project] directory = "..."` still parses but is ignored in resolved settings. + - Project workflow root calculation uses the `.fabro` directory containing `project.toml`, regardless of any deprecated `project.directory` value. - CLI integration tests: - Replace custom-root workflow create/list tests with fixed-root assertions. - - Confirm `fabro workflow create ` writes `.fabro/workflows//workflow.{fabro,toml}`. + - Confirm `fabro workflow create ` writes both `.fabro/workflows//workflow.fabro` and `.fabro/workflows//workflow.toml`. - Confirm named workflow resolution reads `.fabro/workflows//workflow.toml`. - API/client checks: - `cargo build -p fabro-api` @@ -36,6 +34,6 @@ Remove the `[project].directory` feature entirely so project workflows are alway - `cd apps/fabro-web && bun run typecheck` **Assumptions** -- No backwards compatibility or migration period: old configs with `[project].directory` should fail. +- Backwards compatibility is parse-only: old configs with `[project].directory` should not fail, but the value has no effect and is not exposed in resolved settings or the API. - This change only fixes the project workflow directory. User workflows remain available unless a separate decision removes them later. - GitHub retrieval itself is out of scope here; this refactor makes the repo path deterministic for that future work. diff --git a/lib/crates/fabro-api/tests/workflow_settings_round_trip.rs b/lib/crates/fabro-api/tests/workflow_settings_round_trip.rs index 4c4212c36..50c05af3a 100644 --- a/lib/crates/fabro-api/tests/workflow_settings_round_trip.rs +++ b/lib/crates/fabro-api/tests/workflow_settings_round_trip.rs @@ -32,7 +32,10 @@ approval = "auto" .expect("settings should resolve"); let json = serde_json::to_value(&settings).expect("workflow settings should serialize"); - assert_eq!(json["project"]["directory"], "workspace"); + assert!( + json["project"].get("directory").is_none(), + "resolved project settings should not expose deprecated directory" + ); assert_eq!(json["workflow"]["graph"], "ship.fabro"); assert_eq!(json["run"]["goal"]["type"], "inline"); assert_eq!(json["run"]["goal"]["value"], "Ship it"); diff --git a/lib/crates/fabro-cli/src/commands/workflow/create.rs b/lib/crates/fabro-cli/src/commands/workflow/create.rs index 3a23280e5..813ce45ef 100644 --- a/lib/crates/fabro-cli/src/commands/workflow/create.rs +++ b/lib/crates/fabro-cli/src/commands/workflow/create.rs @@ -6,7 +6,7 @@ use std::path::Path; use anyhow::{Context, Result, bail}; -use fabro_config::project::{discover_project_config, resolve_fabro_root}; +use fabro_config::project::discover_project_config; use crate::args::WorkflowCreateArgs; use crate::command_context::CommandContext; @@ -23,8 +23,10 @@ pub(super) fn create_command(args: &WorkflowCreateArgs, base_ctx: &CommandContex ); }; - let fabro_root = resolve_fabro_root(&config_path); - let created = write_workflow_scaffold(args, &fabro_root)?; + let fabro_root = config_path + .parent() + .expect("project config should have a parent directory"); + let created = write_workflow_scaffold(args, fabro_root)?; if base_ctx.json_output() { let created: Vec<_> = created.iter().map(|path| relative_path(path)).collect(); diff --git a/lib/crates/fabro-cli/src/commands/workflow/list.rs b/lib/crates/fabro-cli/src/commands/workflow/list.rs index 16846762d..a65f32360 100644 --- a/lib/crates/fabro-cli/src/commands/workflow/list.rs +++ b/lib/crates/fabro-cli/src/commands/workflow/list.rs @@ -3,7 +3,6 @@ use cli_table::format::{Border, Separator}; use cli_table::{Cell, CellStruct, Color, Style, Table}; use fabro_config::project::{ WorkflowInfo, WorkflowSource, discover_project_config, list_workflows_detailed, - resolve_fabro_root, }; use fabro_util::printer::Printer; use fabro_util::terminal::Styles; @@ -26,7 +25,9 @@ pub(super) fn list_command(_args: &WorkflowListArgs, base_ctx: &CommandContext) ); }; - let fabro_root = resolve_fabro_root(&config_path); + let fabro_root = config_path + .parent() + .expect("project config should have a parent directory"); 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/tests/it/cmd/attach.rs b/lib/crates/fabro-cli/tests/it/cmd/attach.rs index 1752cd394..9a9587ab5 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/attach.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/attach.rs @@ -915,7 +915,6 @@ fn attach_json_errors_without_prompting_for_human_input() { "settings": { "project": { "description": null, - "directory": ".", "metadata": {}, "name": null }, diff --git a/lib/crates/fabro-cli/tests/it/cmd/workflow.rs b/lib/crates/fabro-cli/tests/it/cmd/workflow.rs index c66c89c9d..b3eb7f0f2 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/workflow.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/workflow.rs @@ -39,7 +39,7 @@ fn list() { "_version = 1\n\n[project]\ndirectory = \"..\"\n", ) .write_temp( - "workflows/my_test_wf/workflow.toml", + ".fabro/workflows/my_test_wf/workflow.toml", "_version = 1\n\n[run]\ngoal = \"A test workflow\"\n", ); @@ -55,7 +55,7 @@ fn list() { User Workflows (~/.fabro/workflows) (none) - Project Workflows (workflows) + Project Workflows (.fabro/workflows) NAME DESCRIPTION my_test_wf A test workflow "); diff --git a/lib/crates/fabro-cli/tests/it/cmd/workflow_create.rs b/lib/crates/fabro-cli/tests/it/cmd/workflow_create.rs index 96bc7bcaf..046471a0e 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/workflow_create.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/workflow_create.rs @@ -160,7 +160,7 @@ fn workflow_create_errors_without_project_config() { } #[test] -fn workflow_create_json_uses_resolved_custom_root_paths() { +fn workflow_create_json_ignores_deprecated_project_directory() { let context = test_context!(); let project_dir = context.temp_dir.join("project"); context.write_temp( @@ -188,20 +188,24 @@ fn workflow_create_json_uses_resolved_custom_root_paths() { { "name": "hello-world", "created": [ - "custom/fabro-data/workflows/hello-world/workflow.fabro", - "custom/fabro-data/workflows/hello-world/workflow.toml" + ".fabro/workflows/hello-world/workflow.fabro", + ".fabro/workflows/hello-world/workflow.toml" ] } "#); assert!( project_dir - .join("custom/fabro-data/workflows/hello-world/workflow.fabro") + .join(".fabro/workflows/hello-world/workflow.fabro") .exists() ); assert!( project_dir - .join("custom/fabro-data/workflows/hello-world/workflow.toml") + .join(".fabro/workflows/hello-world/workflow.toml") .exists() ); + assert!( + !project_dir.join("custom/fabro-data/workflows").exists(), + "deprecated project.directory should not redirect workflow creation" + ); } diff --git a/lib/crates/fabro-config/src/builders.rs b/lib/crates/fabro-config/src/builders.rs index a18ea8a7c..ab704b1ee 100644 --- a/lib/crates/fabro-config/src/builders.rs +++ b/lib/crates/fabro-config/src/builders.rs @@ -1,7 +1,7 @@ use std::fmt; use std::path::Path; -use fabro_types::settings::{ProjectNamespace, RunNamespace, WorkflowNamespace}; +use fabro_types::settings::{RunNamespace, WorkflowNamespace}; use fabro_types::{ServerSettings, UserSettings, WorkflowSettings}; use fabro_util::error::SharedError; @@ -419,15 +419,6 @@ impl WorkflowSettingsBuilder { ) } - pub(crate) fn project_from_layer( - layer: &SettingsLayer, - ) -> std::result::Result { - let layer = layer.clone().combine(DEFAULTS_LAYER.clone()); - let mut errors = Vec::new(); - let project = resolve_project(&layer.project.clone().unwrap_or_default(), &mut errors); - finish_dense_result(project, errors) - } - pub(crate) fn workflow_from_layer( layer: &SettingsLayer, ) -> std::result::Result { diff --git a/lib/crates/fabro-config/src/defaults.toml b/lib/crates/fabro-config/src/defaults.toml index 11041787e..d6738420b 100644 --- a/lib/crates/fabro-config/src/defaults.toml +++ b/lib/crates/fabro-config/src/defaults.toml @@ -2,9 +2,6 @@ # Dynamic and presence-gated defaults remain in Rust. _version = 1 -[project] -directory = "." - [workflow] graph = "workflow.fabro" diff --git a/lib/crates/fabro-config/src/layers/project.rs b/lib/crates/fabro-config/src/layers/project.rs index 51d2c7110..66123cec9 100644 --- a/lib/crates/fabro-config/src/layers/project.rs +++ b/lib/crates/fabro-config/src/layers/project.rs @@ -12,8 +12,8 @@ pub struct ProjectLayer { pub name: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, - /// The Fabro-managed project directory inside the repo. Defaults to - /// `.` after layering when unspecified. + /// Deprecated parse-only field. Project workflows always live under the + /// `.fabro` directory that contains `project.toml`. #[serde(default, skip_serializing_if = "Option::is_none")] pub directory: Option, #[serde(default, skip_serializing_if = "ReplaceMap::is_empty")] diff --git a/lib/crates/fabro-config/src/parse.rs b/lib/crates/fabro-config/src/parse.rs index 7fb3becd9..7fd413a58 100644 --- a/lib/crates/fabro-config/src/parse.rs +++ b/lib/crates/fabro-config/src/parse.rs @@ -113,7 +113,7 @@ fn rename_hint(key: &str) -> Option { "artifact_storage" => "rename to `[server.artifacts]`", "storage_dir" | "data_dir" => "rename to `[server.storage] root`", "max_concurrent_runs" => "rename to `[server.scheduler]` field", - "fabro" => "rename to `[project]`; `fabro.root` becomes `project.directory`", + "fabro" => "rename to `[project]`; project workflows now live under `.fabro/workflows`", "git" => "split into `[run.git]` (local git behavior) and `[server.integrations.github]`", "github" => { "split into `[server.integrations.github]` (App identity/auth) and \ diff --git a/lib/crates/fabro-config/src/project.rs b/lib/crates/fabro-config/src/project.rs index 3c5bf48a7..75c395cff 100644 --- a/lib/crates/fabro-config/src/project.rs +++ b/lib/crates/fabro-config/src/project.rs @@ -10,13 +10,12 @@ )] use std::fmt::Write; -use std::path::{Component, Path, PathBuf}; +use std::path::{Path, PathBuf}; use fabro_types::settings::{InterpString, RunNamespace}; use serde::Serialize; -use crate::load::load_settings_path; -use crate::{Error, Result, SettingsLayer, WorkflowSettingsBuilder, run}; +use crate::{Error, Result, WorkflowSettingsBuilder, run}; const CONFIG_FILENAME: &str = ".fabro/project.toml"; #[derive(Clone, Debug)] @@ -27,19 +26,6 @@ pub struct WorkflowPathResolution { pub workflow_slug: Option, } -/// Load a project config from a file path. -/// -/// Goes through [`load_settings_path`] so that relative `run.goal.file` -/// paths are anchored at the directory of `path` at load time. -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()))? - .directory; - tracing::debug!(path = %path.display(), root = %root, "Loaded project config"); - Ok(config) -} - /// Walk ancestor directories from `start` looking for `.fabro/project.toml`. /// Returns the config file path, or `None` if not found. pub fn discover_project_config(start: &Path) -> Result> { @@ -146,7 +132,9 @@ fn resolve_workflow_arg_impl( let name = arg.to_string_lossy(); match discover_project_config(start_dir) { Ok(Some(config_path)) => { - let fabro_root = resolve_fabro_root(&config_path); + let fabro_root = config_path + .parent() + .expect("project config should have a parent directory"); let project_candidate = fabro_root .join("workflows") .join(&*name) @@ -334,40 +322,6 @@ pub fn resolve_workflow(arg: &Path) -> Result { Ok(resolution.dot_path) } -fn normalize_joined_path(base_dir: &Path, reference: &Path) -> PathBuf { - if reference.is_absolute() { - return reference.to_path_buf(); - } - - let mut normalized = PathBuf::new(); - for component in base_dir.join(reference).components() { - match component { - Component::CurDir => {} - Component::Normal(part) => normalized.push(part), - Component::ParentDir => { - normalized.pop(); - } - Component::RootDir => normalized.push(Path::new("/")), - Component::Prefix(prefix) => normalized.push(prefix.as_os_str()), - } - } - normalized -} - -/// 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) -> PathBuf { - let project_dir = config_path - .parent() - .expect("config_path should have a parent directory"); - 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)) -} - #[cfg(test)] mod tests { use std::fs; @@ -386,19 +340,31 @@ mod tests { #[test] fn parse_with_project_directory() { assert_eq!( - WorkflowSettingsBuilder::from_toml( - r#" + r#" +_version = 1 + +[project] +directory = "custom/" +"# + .parse::() + .unwrap() + .project + .and_then(|project| project.directory), + Some("custom/".to_string()) + ); + + let project = WorkflowSettingsBuilder::from_toml( + r#" _version = 1 [project] directory = "custom/" "#, - ) - .unwrap() - .project - .directory, - "custom/" - ); + ) + .unwrap() + .project; + let json = serde_json::to_value(&project).expect("project settings should serialize"); + assert!(json.get("directory").is_none()); } #[test] @@ -425,17 +391,6 @@ directory = "custom/" ); } - #[test] - fn load_from_disk() { - let tmp = TempDir::new().unwrap(); - let config_dir = tmp.path().join(".fabro"); - fs::create_dir_all(&config_dir).unwrap(); - let path = config_dir.join("project.toml"); - fs::write(&path, "_version = 1\n").unwrap(); - let config = load_project_config(&path).unwrap(); - assert_eq!(config.version, Some(1)); - } - #[test] fn discover_walks_ancestors() { let tmp = TempDir::new().unwrap(); @@ -450,50 +405,14 @@ directory = "custom/" } #[test] - fn load_project_config_rewrites_relative_goal_file_path() { - use crate::RunGoalLayer; - - let tmp = TempDir::new().unwrap(); - let config_dir = tmp.path().join(".fabro"); - fs::create_dir_all(&config_dir).unwrap(); - let path = config_dir.join("project.toml"); - fs::write( - &path, - r#"_version = 1 - -[run.goal] -file = "prompts/goal.md" -"#, - ) - .unwrap(); - - let config = load_project_config(&path).unwrap(); - let Some(RunGoalLayer::File { file }) = - config.run.as_ref().and_then(|run| run.goal.as_ref()) - else { - panic!("expected file variant"); - }; - let expected = config_dir.join("prompts").join("goal.md"); - assert_eq!(file.as_source(), expected.to_string_lossy()); - } - - #[test] - fn default_directory_resolves_to_config_parent() { - let tmp = TempDir::new().unwrap(); - let config_dir = tmp.path().join(".fabro"); - fs::create_dir_all(&config_dir).unwrap(); - let config_path = config_dir.join("project.toml"); - fs::write(&config_path, "_version = 1\n").unwrap(); - - assert_eq!(resolve_fabro_root(&config_path), config_dir); - } - - #[test] - fn custom_relative_directory_resolves_from_config_parent() { + fn deprecated_project_directory_does_not_change_fabro_root() { let tmp = TempDir::new().unwrap(); let config_dir = tmp.path().join(".fabro"); fs::create_dir_all(&config_dir).unwrap(); let config_path = config_dir.join("project.toml"); + let workflow_dir = config_dir.join("workflows/demo"); + fs::create_dir_all(&workflow_dir).unwrap(); + fs::write(workflow_dir.join("workflow.toml"), "_version = 1\n").unwrap(); fs::write( &config_path, r#"_version = 1 @@ -504,37 +423,9 @@ directory = "../custom" ) .unwrap(); - assert_eq!(resolve_fabro_root(&config_path), tmp.path().join("custom")); - } - - #[test] - fn relative_goal_file_resolves_from_config_dir() { - use crate::RunGoalLayer; - - let tmp = TempDir::new().unwrap(); - let config_dir = tmp.path().join(".fabro"); - fs::create_dir_all(&config_dir).unwrap(); - let config_path = config_dir.join("project.toml"); - fs::write( - &config_path, - r#"_version = 1 - -[run.goal] -file = "prompts/goal.md" -"#, - ) - .unwrap(); - - let config = load_project_config(&config_path).unwrap(); - let Some(RunGoalLayer::File { file }) = - config.run.as_ref().and_then(|run| run.goal.as_ref()) - else { - panic!("expected file variant"); - }; - assert_eq!( - file.as_source(), - config_dir.join("prompts").join("goal.md").to_string_lossy() + resolve_workflow_arg_impl(Path::new("demo"), tmp.path(), None).unwrap(), + config_dir.join("workflows/demo/workflow.toml") ); } diff --git a/lib/crates/fabro-config/src/resolve/project.rs b/lib/crates/fabro-config/src/resolve/project.rs index 2296acc5c..226784854 100644 --- a/lib/crates/fabro-config/src/resolve/project.rs +++ b/lib/crates/fabro-config/src/resolve/project.rs @@ -7,10 +7,6 @@ pub fn resolve_project(layer: &ProjectLayer, _errors: &mut Vec) -> ProjectNamespace { name: layer.name.clone(), description: layer.description.clone(), - directory: layer - .directory - .clone() - .expect("defaults.toml should provide project.directory"), metadata: layer.metadata.clone().into_inner(), } } diff --git a/lib/crates/fabro-config/src/tests/defaults.rs b/lib/crates/fabro-config/src/tests/defaults.rs index 5aecf5f68..7bc73a41d 100644 --- a/lib/crates/fabro-config/src/tests/defaults.rs +++ b/lib/crates/fabro-config/src/tests/defaults.rs @@ -18,12 +18,13 @@ fn embedded_defaults() -> SettingsLayer { fn embedded_defaults_parse_successfully() { let defaults = embedded_defaults(); - assert_eq!( + assert!( defaults .project .as_ref() - .and_then(|project| project.directory.as_deref()), - Some(".") + .and_then(|project| project.directory.as_deref()) + .is_none(), + "built-in defaults should not materialize deprecated project.directory" ); assert_eq!( defaults @@ -38,12 +39,13 @@ fn embedded_defaults_parse_successfully() { fn apply_builtin_defaults_materializes_expected_layer() { let layer = SettingsLayer::default().combine(embedded_defaults()); - assert_eq!( + assert!( layer .project .as_ref() - .and_then(|project| project.directory.as_deref()), - Some(".") + .and_then(|project| project.directory.as_deref()) + .is_none(), + "built-in defaults should not materialize deprecated project.directory" ); assert_eq!( layer diff --git a/lib/crates/fabro-config/src/tests/resolve_project.rs b/lib/crates/fabro-config/src/tests/resolve_project.rs index 21c62d00c..b23b0971b 100644 --- a/lib/crates/fabro-config/src/tests/resolve_project.rs +++ b/lib/crates/fabro-config/src/tests/resolve_project.rs @@ -8,14 +8,18 @@ fn resolves_project_defaults_from_empty_settings() { .expect("empty settings should resolve") .project; - assert_eq!(project.directory, "."); + let json = serde_json::to_value(&project).expect("project settings should serialize"); + assert!( + json.get("directory").is_none(), + "resolved project settings should not expose deprecated directory" + ); assert!(project.name.is_none()); assert!(project.description.is_none()); assert!(project.metadata.is_empty()); } #[test] -fn resolves_project_directory_and_metadata() { +fn resolves_project_metadata_and_ignores_deprecated_directory() { let project = WorkflowSettingsBuilder::from_toml( r#" _version = 1 @@ -34,7 +38,11 @@ team = "platform" assert_eq!(project.name.as_deref(), Some("Acme")); assert_eq!(project.description.as_deref(), Some("Automation")); - assert_eq!(project.directory, ".fabro"); + let json = serde_json::to_value(&project).expect("project settings should serialize"); + assert!( + json.get("directory").is_none(), + "resolved project settings should not expose deprecated directory" + ); assert_eq!( project.metadata.get("team").map(String::as_str), Some("platform") diff --git a/lib/crates/fabro-config/src/tests/resolve_root.rs b/lib/crates/fabro-config/src/tests/resolve_root.rs index 1d75d6221..99fe0fef6 100644 --- a/lib/crates/fabro-config/src/tests/resolve_root.rs +++ b/lib/crates/fabro-config/src/tests/resolve_root.rs @@ -92,7 +92,12 @@ name = "gpt-5" WorkflowSettingsBuilder::from_toml(source).expect("workflow settings should resolve"); let server = ServerSettingsBuilder::from_toml(source).expect("server settings should resolve"); - assert_eq!(workflow_settings.project.directory, ".fabro"); + let project_json = serde_json::to_value(&workflow_settings.project) + .expect("project settings should serialize"); + assert!( + project_json.get("directory").is_none(), + "resolved project settings should not expose deprecated directory" + ); assert_eq!(workflow_settings.workflow.graph, "graphs/workflow.dot"); assert_eq!(server.server.storage.root.as_source(), "/srv/fabro"); assert_eq!( @@ -121,7 +126,12 @@ fn workflow_settings_resolve_defaults_and_expose_fields() { let resolved = fabro_config::WorkflowSettingsBuilder::from_layer(&settings) .expect("defaults should resolve"); - assert_eq!(resolved.project.directory, "."); + let project_json = + serde_json::to_value(&resolved.project).expect("project settings should serialize"); + assert!( + project_json.get("directory").is_none(), + "resolved project settings should not expose deprecated directory" + ); assert_eq!(resolved.workflow.graph, "workflow.fabro"); assert_eq!(resolved.run.execution.mode, RunMode::Normal); } diff --git a/lib/crates/fabro-server/src/demo/mod.rs b/lib/crates/fabro-server/src/demo/mod.rs index 72f073e41..52da747f9 100644 --- a/lib/crates/fabro-server/src/demo/mod.rs +++ b/lib/crates/fabro-server/src/demo/mod.rs @@ -1629,10 +1629,7 @@ mod runs { pub(super) fn settings() -> serde_json::Value { let settings = WorkflowSettings { - project: ProjectNamespace { - directory: "/workspace/api-server".into(), - ..ProjectNamespace::default() - }, + project: ProjectNamespace::default(), workflow: WorkflowNamespace { graph: "workflow.fabro".into(), ..WorkflowNamespace::default() diff --git a/lib/crates/fabro-types/src/settings/project.rs b/lib/crates/fabro-types/src/settings/project.rs index 7e141dbcb..e6ae3ddf3 100644 --- a/lib/crates/fabro-types/src/settings/project.rs +++ b/lib/crates/fabro-types/src/settings/project.rs @@ -1,7 +1,4 @@ //! Project domain: first-class project object. -//! -//! `[project]` replaces the old flat `[fabro]` shape. `directory` means the -//! Fabro-managed project directory inside the repo, defaulting to `.`. use std::collections::HashMap; @@ -12,6 +9,5 @@ use serde::{Deserialize, Serialize}; pub struct ProjectNamespace { pub name: Option, pub description: Option, - pub directory: String, pub metadata: HashMap, } diff --git a/lib/packages/fabro-api-client/src/models/project-namespace.ts b/lib/packages/fabro-api-client/src/models/project-namespace.ts index c9a062096..d5fc1b7ef 100644 --- a/lib/packages/fabro-api-client/src/models/project-namespace.ts +++ b/lib/packages/fabro-api-client/src/models/project-namespace.ts @@ -17,7 +17,6 @@ export interface ProjectNamespace { 'name': string | null; 'description': string | null; - 'directory': string; 'metadata': { [key: string]: string; }; }