mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-06 08:18:58 +00:00
Rename --run-dir to --storage-dir, unify with data_dir
Replace the per-run `--run-dir` CLI flag with `--storage-dir` which sets the base storage directory (default ~/.fabro). Runs are now created under `<storage-dir>/runs/` automatically. This unifies the server's `data_dir` config with the CLI by renaming `FabroConfig.data_dir` to `storage_dir` and adding a `storage_dir()` convenience method. Key changes: - FabroConfig: `data_dir` → `storage_dir` (serde alias preserves compat) - CLI: `--run-dir` → `--storage-dir` on `fabro run` - `__detached`: now takes `--storage-dir` + `--run-id` instead of `--run-dir` - All ~20 CLI commands derive runs base from config instead of hardcoded default - Added parameterized `runs_base(storage_dir)` and `make_run_dir()` helpers - Updated OpenAPI spec, docs, and all tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
833574ec4b
commit
126ece3d71
41 changed files with 272 additions and 121 deletions
|
|
@ -4259,9 +4259,9 @@ components:
|
|||
description: Structured server configuration mirroring FabroConfig.
|
||||
type: object
|
||||
properties:
|
||||
data_dir:
|
||||
storage_dir:
|
||||
type: string
|
||||
description: Data directory path.
|
||||
description: Storage directory path.
|
||||
max_concurrent_runs:
|
||||
type: integer
|
||||
description: Maximum concurrent runs.
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ fabro run run.toml
|
|||
| Argument / Flag | Description |
|
||||
|---|---|
|
||||
| `<WORKFLOW>` | Path to a `.fabro` workflow file, `.toml` task config, or workflow name (resolved from `fabro/workflows/` in the project, then `~/.fabro/workflows/`). |
|
||||
| `--run-dir <DIR>` | Run output directory |
|
||||
| `--storage-dir <DIR>` | Storage directory (default: `~/.fabro`) |
|
||||
| `--dry-run` | Execute with a simulated LLM backend |
|
||||
| `--auto-approve` | Auto-approve all human gates |
|
||||
| `--model <MODEL>` | Override default LLM model |
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ Each `fabro run` invocation creates a timestamped directory under `~/.fabro/runs
|
|||
~/.fabro/runs/20260307-01JQXYZ123ABC456DEF789/
|
||||
```
|
||||
|
||||
The naming format is `YYYYMMDD-{run_id}`, where `run_id` is the ULID assigned to the run. You can override the location with `--run-dir`.
|
||||
The naming format is `YYYYMMDD-{run_id}`, where `run_id` is the ULID assigned to the run. You can override the base storage directory with `--storage-dir` (the runs directory will be `<storage-dir>/runs/`).
|
||||
|
||||
## Root-level files
|
||||
|
||||
|
|
|
|||
|
|
@ -3246,7 +3246,7 @@ mod settings {
|
|||
|
||||
pub fn server_config() -> serde_json::Value {
|
||||
serde_json::to_value(FabroConfig {
|
||||
data_dir: Some("/home/fabro/.fabro".into()),
|
||||
storage_dir: Some("/home/fabro/.fabro".into()),
|
||||
max_concurrent_runs: Some(10),
|
||||
web: Some(WebConfig {
|
||||
url: "https://arc.example.com".into(),
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ pub async fn serve_command(args: ServeArgs, styles: &'static Styles) -> anyhow::
|
|||
// Initialize data directory and SQLite database
|
||||
let config_path = args.config;
|
||||
let server_config = fabro_config::server::load_server_config(config_path.as_deref())?;
|
||||
let data_dir = fabro_config::server::resolve_data_dir(&server_config);
|
||||
let data_dir = fabro_config::server::resolve_storage_dir(&server_config);
|
||||
|
||||
// Shared config for live reloading
|
||||
let shared_config = Arc::new(RwLock::new(server_config));
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ fn compare_schema(
|
|||
/// in the serialized JSON.
|
||||
fn fully_populated_server_config() -> FabroConfig {
|
||||
FabroConfig {
|
||||
data_dir: Some("/data".into()),
|
||||
storage_dir: Some("/data".into()),
|
||||
max_concurrent_runs: Some(10),
|
||||
web: Some(WebConfig {
|
||||
url: "https://example.com".into(),
|
||||
|
|
|
|||
|
|
@ -91,9 +91,9 @@ pub(crate) struct RunArgs {
|
|||
#[arg(required = true)]
|
||||
pub(crate) workflow: Option<PathBuf>,
|
||||
|
||||
/// Run output directory
|
||||
/// Storage directory (default: ~/.fabro)
|
||||
#[arg(long)]
|
||||
pub(crate) run_dir: Option<PathBuf>,
|
||||
pub(crate) storage_dir: Option<PathBuf>,
|
||||
|
||||
/// Execute with simulated LLM backend
|
||||
#[arg(long)]
|
||||
|
|
@ -646,9 +646,12 @@ pub(crate) enum RunCommands {
|
|||
/// Internal: run the engine process (reads run.json from run dir)
|
||||
#[command(name = "__detached", hide = true)]
|
||||
Detached {
|
||||
/// Path to the run directory
|
||||
/// Base storage directory
|
||||
#[arg(long)]
|
||||
run_dir: PathBuf,
|
||||
storage_dir: PathBuf,
|
||||
/// Run ID
|
||||
#[arg(long)]
|
||||
run_id: String,
|
||||
/// Resume from checkpoint instead of fresh start
|
||||
#[arg(long)]
|
||||
resume: bool,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ use crate::args::AssetCpArgs;
|
|||
use crate::shared::split_run_path;
|
||||
|
||||
pub fn cp_command(args: &AssetCpArgs) -> Result<()> {
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
let (run_id, asset_path) = parse_source(&args.source);
|
||||
let run = fabro_workflows::run_lookup::resolve_run(&base, run_id)?;
|
||||
let entries = fabro_workflows::assets::scan_assets(&run.path, args.node.as_deref())?;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ use crate::args::AssetListArgs;
|
|||
use crate::shared::format_size;
|
||||
|
||||
pub fn list_command(args: &AssetListArgs) -> Result<()> {
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
let run = fabro_workflows::run_lookup::resolve_run(&base, &args.run_id)?;
|
||||
let entries = fabro_workflows::assets::scan_assets(&run.path, args.node.as_deref())?;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ pub async fn close_command(
|
|||
args: PrCloseArgs,
|
||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||
) -> Result<()> {
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
close_from(&base, args, github_app).await
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ pub async fn create_command(
|
|||
args: PrCreateArgs,
|
||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||
) -> Result<()> {
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
create_from(&base, args, github_app).await
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ pub async fn list_command(
|
|||
args: PrListArgs,
|
||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||
) -> Result<()> {
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
list_from(&base, args, github_app).await
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ pub async fn merge_command(
|
|||
args: PrMergeArgs,
|
||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||
) -> Result<()> {
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
merge_from(&base, args, github_app).await
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ pub async fn view_command(
|
|||
args: PrViewArgs,
|
||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||
) -> Result<()> {
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
view_from(&base, args, github_app).await
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ pub async fn execute(mut args: PreflightArgs) -> anyhow::Result<()> {
|
|||
model: args.model.as_deref(),
|
||||
provider: args.provider.as_deref(),
|
||||
sandbox_provider,
|
||||
storage_dir: None,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ enum CopyDirection {
|
|||
|
||||
pub async fn cp_command(args: CpArgs) -> Result<()> {
|
||||
let direction = parse_direction(&args.src, &args.dst)?;
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
|
||||
match direction {
|
||||
CopyDirection::Download {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::args::RunArgs;
|
|||
|
||||
use super::execute::{
|
||||
apply_execution_overrides, cached_graph_path, default_run_dir, load_workflow_source_input,
|
||||
parse_labels, print_diagnostics_from_error, print_workflow_report_from_persisted,
|
||||
make_run_dir, parse_labels, print_diagnostics_from_error, print_workflow_report_from_persisted,
|
||||
resolve_sandbox_provider, write_run_config_snapshot, ExecutionOverrides,
|
||||
};
|
||||
use fabro_util::terminal::Styles;
|
||||
|
|
@ -36,10 +36,10 @@ pub async fn create_run(
|
|||
.run_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| ulid::Ulid::new().to_string());
|
||||
let run_dir = args
|
||||
.run_dir
|
||||
.clone()
|
||||
.unwrap_or_else(|| default_run_dir(&run_id, args.dry_run));
|
||||
let run_dir = match &args.storage_dir {
|
||||
Some(sd) => make_run_dir(&sd.join("runs"), &run_id, args.dry_run),
|
||||
None => default_run_dir(&run_id, args.dry_run),
|
||||
};
|
||||
let working_directory = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
||||
let base_branch = fabro_sandbox::daytona::detect_repo_info(&working_directory)
|
||||
.ok()
|
||||
|
|
@ -66,6 +66,7 @@ pub async fn create_run(
|
|||
model: args.model.as_deref(),
|
||||
provider: args.provider.as_deref(),
|
||||
sandbox_provider,
|
||||
storage_dir: args.storage_dir.as_deref(),
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ use serde::Serialize;
|
|||
use crate::cli_config;
|
||||
use crate::shared;
|
||||
|
||||
pub async fn execute(run_dir: PathBuf, resume: bool) -> Result<()> {
|
||||
pub async fn execute(storage_dir: PathBuf, run_id: String, resume: bool) -> Result<()> {
|
||||
let runs_base = fabro_workflows::run_lookup::runs_base(&storage_dir);
|
||||
let run_dir = fabro_workflows::run_lookup::find_run_by_prefix(&runs_base, &run_id)?;
|
||||
let styles: &'static fabro_util::terminal::Styles =
|
||||
Box::leak(Box::new(fabro_util::terminal::Styles::detect_stderr()));
|
||||
let cli_config = cli_config::load_cli_config(None)?;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ use crate::args::DiffArgs;
|
|||
|
||||
pub async fn run(args: DiffArgs) -> Result<()> {
|
||||
info!(run_id = %args.run, "Showing diff");
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
let run_dir = fabro_workflows::run_lookup::resolve_run(&base, &args.run)?.path;
|
||||
|
||||
let patch = resolve_diff(&run_dir, &args).await?;
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ pub(crate) fn resolve_cli_goal(
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) use fabro_workflows::operations::default_run_dir;
|
||||
pub(crate) use fabro_workflows::operations::{default_run_dir, make_run_dir};
|
||||
|
||||
pub(crate) fn workflow_slug_from_path(workflow_path: &Path) -> Option<String> {
|
||||
let file_name = workflow_path.file_name()?.to_string_lossy();
|
||||
|
|
@ -395,6 +395,7 @@ pub(crate) struct ExecutionOverrides<'a> {
|
|||
pub model: Option<&'a str>,
|
||||
pub provider: Option<&'a str>,
|
||||
pub sandbox_provider: SandboxProvider,
|
||||
pub storage_dir: Option<&'a Path>,
|
||||
}
|
||||
|
||||
pub(crate) fn apply_execution_overrides(config: &mut FabroConfig, overrides: &ExecutionOverrides) {
|
||||
|
|
@ -414,6 +415,10 @@ pub(crate) fn apply_execution_overrides(config: &mut FabroConfig, overrides: &Ex
|
|||
if overrides.preserve_sandbox {
|
||||
config.sandbox.get_or_insert_default().preserve = Some(true);
|
||||
}
|
||||
|
||||
if let Some(storage_dir) = overrides.storage_dir {
|
||||
config.storage_dir = Some(storage_dir.to_path_buf());
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_labels(labels: &[String]) -> HashMap<String, String> {
|
||||
|
|
@ -566,7 +571,7 @@ struct RecordBasedRun {
|
|||
/// Used by `run_engine_entrypoint` for detached runs that already have a RunRecord on disk.
|
||||
pub async fn run_from_record(
|
||||
persisted: Persisted,
|
||||
run_dir: PathBuf,
|
||||
_run_dir: PathBuf,
|
||||
run_defaults: FabroConfig,
|
||||
styles: &'static Styles,
|
||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||
|
|
@ -601,7 +606,7 @@ pub async fn run_from_record(
|
|||
|
||||
let args = RunArgs {
|
||||
workflow: None,
|
||||
run_dir: Some(run_dir),
|
||||
storage_dir: None,
|
||||
dry_run: record.config.dry_run_enabled(),
|
||||
|
||||
auto_approve: record.config.auto_approve_enabled(),
|
||||
|
|
@ -641,7 +646,7 @@ pub async fn run_from_record(
|
|||
/// Resume an existing workflow run from its persisted checkpoint.
|
||||
pub async fn resume_from_record(
|
||||
persisted: Persisted,
|
||||
run_dir: PathBuf,
|
||||
_run_dir: PathBuf,
|
||||
run_defaults: FabroConfig,
|
||||
styles: &'static Styles,
|
||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||
|
|
@ -676,7 +681,7 @@ pub async fn resume_from_record(
|
|||
|
||||
let args = RunArgs {
|
||||
workflow: None,
|
||||
run_dir: Some(run_dir),
|
||||
storage_dir: None,
|
||||
dry_run: record.config.dry_run_enabled(),
|
||||
|
||||
auto_approve: record.config.auto_approve_enabled(),
|
||||
|
|
@ -798,10 +803,13 @@ async fn run_command_impl(
|
|||
.run_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| ulid::Ulid::new().to_string());
|
||||
let run_dir = args
|
||||
.run_dir
|
||||
.clone()
|
||||
.unwrap_or_else(|| default_run_dir(&run_id, dry_run_flag));
|
||||
let run_dir = match &workflow {
|
||||
WorkflowState::Persisted(p) => p.run_dir().to_path_buf(),
|
||||
_ => match &args.storage_dir {
|
||||
Some(sd) => make_run_dir(&sd.join("runs"), &run_id, dry_run_flag),
|
||||
None => default_run_dir(&run_id, dry_run_flag),
|
||||
},
|
||||
};
|
||||
if resume {
|
||||
ensure_resume_target_is_not_already_successful(&run_dir)?;
|
||||
}
|
||||
|
|
@ -842,6 +850,7 @@ async fn run_command_impl(
|
|||
model: args.model.as_deref(),
|
||||
provider: args.provider.as_deref(),
|
||||
sandbox_provider,
|
||||
storage_dir: args.storage_dir.as_deref(),
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ use tracing::{debug, info};
|
|||
use crate::args::LogsArgs;
|
||||
|
||||
pub fn run(args: LogsArgs, styles: &Styles) -> Result<()> {
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
let run = fabro_workflows::run_lookup::resolve_run(&base, &args.run)?;
|
||||
|
||||
info!(run_id = %run.run_id, "Showing logs");
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ pub async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
RunCommands::Start { run } => {
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
let run_info = fabro_workflows::run_lookup::resolve_run(&base, &run)?;
|
||||
let child = start::start_run(&run_info.path, false)?;
|
||||
eprintln!("Started engine process (PID {})", child.id());
|
||||
|
|
@ -39,7 +40,8 @@ pub async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<()> {
|
|||
RunCommands::Attach { run } => {
|
||||
let styles: &'static fabro_util::terminal::Styles =
|
||||
Box::leak(Box::new(fabro_util::terminal::Styles::detect_stderr()));
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
let run_info = fabro_workflows::run_lookup::resolve_run(&base, &run)?;
|
||||
let exit_code = attach::attach_run(&run_info.path, false, styles, None).await?;
|
||||
if exit_code != std::process::ExitCode::SUCCESS {
|
||||
|
|
@ -47,7 +49,11 @@ pub async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<()> {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
RunCommands::Detached { run_dir, resume } => detached::execute(run_dir, resume).await,
|
||||
RunCommands::Detached {
|
||||
storage_dir,
|
||||
run_id,
|
||||
resume,
|
||||
} => detached::execute(storage_dir, run_id, resume).await,
|
||||
RunCommands::Cp(args) => cp::cp_command(args).await,
|
||||
RunCommands::Preview(args) => preview::run(args).await,
|
||||
RunCommands::Ssh(args) => ssh::run(args).await,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ use crate::args::PreviewArgs;
|
|||
use crate::shared::validate_daytona_provider;
|
||||
|
||||
pub async fn run(args: PreviewArgs) -> Result<()> {
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
let run_dir = fabro_workflows::run_lookup::resolve_run(&base, &args.run)?.path;
|
||||
let sandbox_json = run_dir.join("sandbox.json");
|
||||
let record = fabro_sandbox::SandboxRecord::load(&sandbox_json).context(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ use crate::args::ResumeArgs;
|
|||
/// artifacts from the previous execution, then spawns an engine subprocess
|
||||
/// (identical to `fabro run`'s create→start→attach flow).
|
||||
pub async fn resume_command(args: ResumeArgs, styles: &'static Styles) -> anyhow::Result<()> {
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
let run_dir = fabro_workflows::run_lookup::find_run_by_prefix(&base, &args.run)?;
|
||||
|
||||
// find_run_by_prefix can match orphan directories (no run.json).
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ use crate::args::SshArgs;
|
|||
use crate::shared::validate_daytona_provider;
|
||||
|
||||
pub async fn run(args: SshArgs) -> Result<()> {
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
let run_dir = fabro_workflows::run_lookup::resolve_run(&base, &args.run)?.path;
|
||||
let sandbox_json = run_dir.join("sandbox.json");
|
||||
let record = fabro_sandbox::SandboxRecord::load(&sandbox_json).context(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ use super::detached::persist_detached_failure;
|
|||
///
|
||||
/// The engine process reads `run.json` from the run directory and executes the
|
||||
/// workflow. Returns the child process handle (use `.id()` for the PID).
|
||||
///
|
||||
/// `storage_dir` is the base storage directory (e.g. `~/.fabro`). If `None`,
|
||||
/// it is derived from the `run_dir` by stripping the `runs/<dir_name>` suffix.
|
||||
pub fn start_run(run_dir: &Path, resume: bool) -> Result<std::process::Child> {
|
||||
// Validate status is Submitted
|
||||
let status_path = run_dir.join("status.json");
|
||||
|
|
@ -56,7 +59,18 @@ pub fn start_run(run_dir: &Path, resume: bool) -> Result<std::process::Child> {
|
|||
return Err(err);
|
||||
}
|
||||
};
|
||||
cmd.args(["__detached", "--run-dir"]).arg(run_dir);
|
||||
// Derive storage_dir (grandparent of run_dir, e.g. ~/.fabro) and run_id
|
||||
let storage_dir = run_dir
|
||||
.parent() // runs/
|
||||
.and_then(|p| p.parent()) // ~/.fabro/
|
||||
.unwrap_or(run_dir);
|
||||
let run_id = fabro_workflows::records::RunRecord::load(run_dir)
|
||||
.map(|r| r.run_id)
|
||||
.or_else(|_| std::fs::read_to_string(run_dir.join("id.txt")).map(|s| s.trim().to_string()))
|
||||
.unwrap_or_default();
|
||||
cmd.args(["__detached", "--storage-dir"])
|
||||
.arg(storage_dir)
|
||||
.args(["--run-id", &run_id]);
|
||||
if resume {
|
||||
cmd.arg("--resume");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ use crate::args::WaitArgs;
|
|||
use crate::shared::format_duration_ms;
|
||||
|
||||
pub fn run(args: WaitArgs, styles: &Styles) -> Result<()> {
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
let run_info = fabro_workflows::run_lookup::resolve_run(&base, &args.run)?;
|
||||
|
||||
info!(run_id = %run_info.run_id, "Waiting for run to complete");
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ pub struct InspectOutput {
|
|||
}
|
||||
|
||||
pub fn run(args: &InspectArgs) -> Result<()> {
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
let run = fabro_workflows::run_lookup::resolve_run(&base, &args.run)?;
|
||||
let output = inspect_run_dir(&run.run_id, &run.path, run.status)?;
|
||||
let json = serde_json::to_string_pretty(&[output])?;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ use crate::shared::{color_if, format_duration_ms, tilde_path};
|
|||
use super::short_run_id;
|
||||
|
||||
pub fn list_command(args: &RunsListArgs, styles: &Styles) -> Result<()> {
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
let runs = fabro_workflows::run_lookup::scan_runs(&base)?;
|
||||
let label_filters = parse_label_filters(&args.filter.label);
|
||||
let filtered = fabro_workflows::run_lookup::filter_runs(
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ use crate::args::RunsRemoveArgs;
|
|||
use super::short_run_id;
|
||||
|
||||
pub async fn remove_command(args: &RunsRemoveArgs) -> Result<()> {
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
remove_from(args, &base).await
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@ use crate::args::DfArgs;
|
|||
use crate::shared::format_size;
|
||||
|
||||
pub fn df_command(args: &DfArgs) -> Result<()> {
|
||||
let data_dir = fabro_workflows::run_lookup::default_data_dir();
|
||||
let runs_base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let logs_base = fabro_workflows::run_lookup::default_logs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let data_dir = cli_config.storage_dir();
|
||||
let runs_base = fabro_workflows::run_lookup::runs_base(&data_dir);
|
||||
let logs_base = fabro_workflows::run_lookup::logs_base(&data_dir);
|
||||
df_from(args, &data_dir, &runs_base, &logs_base)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ use crate::args::RunsPruneArgs;
|
|||
use crate::shared::format_size;
|
||||
|
||||
pub fn prune_command(args: &RunsPruneArgs) -> Result<()> {
|
||||
let base = fabro_workflows::run_lookup::default_runs_base();
|
||||
let cli_config = crate::cli_config::load_cli_config(None)?;
|
||||
let base = fabro_workflows::run_lookup::runs_base(&cli_config.storage_dir());
|
||||
prune_from(args, &base)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -333,11 +333,23 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn parse_detached_command() {
|
||||
let cli = Cli::try_parse_from(["fabro", "__detached", "--run-dir", "/tmp/runs/test"])
|
||||
.expect("should parse");
|
||||
let cli = Cli::try_parse_from([
|
||||
"fabro",
|
||||
"__detached",
|
||||
"--storage-dir",
|
||||
"/tmp/fabro",
|
||||
"--run-id",
|
||||
"01ABC",
|
||||
])
|
||||
.expect("should parse");
|
||||
match *cli.command {
|
||||
Commands::RunCmd(RunCommands::Detached { run_dir, resume }) => {
|
||||
assert_eq!(run_dir, std::path::PathBuf::from("/tmp/runs/test"));
|
||||
Commands::RunCmd(RunCommands::Detached {
|
||||
storage_dir,
|
||||
run_id,
|
||||
resume,
|
||||
}) => {
|
||||
assert_eq!(storage_dir, std::path::PathBuf::from("/tmp/fabro"));
|
||||
assert_eq!(run_id, "01ABC");
|
||||
assert!(!resume);
|
||||
}
|
||||
_ => panic!("unexpected command variant"),
|
||||
|
|
@ -349,14 +361,21 @@ mod tests {
|
|||
let cli = Cli::try_parse_from([
|
||||
"fabro",
|
||||
"__detached",
|
||||
"--run-dir",
|
||||
"/tmp/runs/test",
|
||||
"--storage-dir",
|
||||
"/tmp/fabro",
|
||||
"--run-id",
|
||||
"01ABC",
|
||||
"--resume",
|
||||
])
|
||||
.expect("should parse");
|
||||
match *cli.command {
|
||||
Commands::RunCmd(RunCommands::Detached { run_dir, resume }) => {
|
||||
assert_eq!(run_dir, std::path::PathBuf::from("/tmp/runs/test"));
|
||||
Commands::RunCmd(RunCommands::Detached {
|
||||
storage_dir,
|
||||
run_id,
|
||||
resume,
|
||||
}) => {
|
||||
assert_eq!(storage_dir, std::path::PathBuf::from("/tmp/fabro"));
|
||||
assert_eq!(run_id, "01ABC");
|
||||
assert!(resume);
|
||||
}
|
||||
_ => panic!("unexpected command variant"),
|
||||
|
|
|
|||
|
|
@ -495,20 +495,30 @@ fn doctor_no_color_when_no_color_set() {
|
|||
#[test]
|
||||
fn dry_run_writes_jsonl_and_live_json() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let run_dir = tmp.path().join("run");
|
||||
let storage_dir = tmp.path().join("fabro-data");
|
||||
|
||||
arc()
|
||||
.args([
|
||||
"run",
|
||||
"--dry-run",
|
||||
"--auto-approve",
|
||||
"--run-dir",
|
||||
run_dir.to_str().unwrap(),
|
||||
"--storage-dir",
|
||||
storage_dir.to_str().unwrap(),
|
||||
"../../../test/simple.fabro",
|
||||
])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// Find the single run directory under storage_dir/runs/
|
||||
let runs_base = storage_dir.join("runs");
|
||||
assert!(runs_base.exists(), "runs/ directory should exist");
|
||||
let entries: Vec<_> = std::fs::read_dir(&runs_base)
|
||||
.unwrap()
|
||||
.filter_map(|e| e.ok())
|
||||
.collect();
|
||||
assert_eq!(entries.len(), 1, "should have exactly one run directory");
|
||||
let run_dir = entries[0].path();
|
||||
|
||||
// progress.jsonl must exist and contain valid JSON lines
|
||||
let jsonl_path = run_dir.join("progress.jsonl");
|
||||
assert!(jsonl_path.exists(), "progress.jsonl should exist");
|
||||
|
|
@ -555,7 +565,7 @@ fn dry_run_writes_jsonl_and_live_json() {
|
|||
#[test]
|
||||
fn run_id_passthrough_uses_provided_ulid() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let run_dir = tmp.path().join("run");
|
||||
let storage_dir = tmp.path().join("fabro-data");
|
||||
let my_ulid = "01JTEST1234567890ABCDE";
|
||||
|
||||
arc()
|
||||
|
|
@ -565,8 +575,8 @@ fn run_id_passthrough_uses_provided_ulid() {
|
|||
"--auto-approve",
|
||||
"--run-id",
|
||||
my_ulid,
|
||||
"--run-dir",
|
||||
run_dir.to_str().unwrap(),
|
||||
"--storage-dir",
|
||||
storage_dir.to_str().unwrap(),
|
||||
"../../../test/simple.fabro",
|
||||
])
|
||||
.assert()
|
||||
|
|
@ -614,7 +624,7 @@ fn detach_prints_ulid_and_exits() {
|
|||
#[test]
|
||||
fn detach_creates_run_dir_with_detach_log() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let run_dir = tmp.path().join("detached-run");
|
||||
let storage_dir = tmp.path().join("fabro-data");
|
||||
|
||||
let output = arc()
|
||||
.args([
|
||||
|
|
@ -622,8 +632,8 @@ fn detach_creates_run_dir_with_detach_log() {
|
|||
"--detach",
|
||||
"--dry-run",
|
||||
"--auto-approve",
|
||||
"--run-dir",
|
||||
run_dir.to_str().unwrap(),
|
||||
"--storage-dir",
|
||||
storage_dir.to_str().unwrap(),
|
||||
"../../../test/simple.fabro",
|
||||
])
|
||||
.assert()
|
||||
|
|
@ -636,8 +646,15 @@ fn detach_creates_run_dir_with_detach_log() {
|
|||
let ulid = ulid.trim();
|
||||
assert!(!ulid.is_empty(), "should print a ULID");
|
||||
|
||||
// Run dir should have been created with detach.log
|
||||
assert!(run_dir.exists(), "run dir should exist");
|
||||
// Run dir should have been created under storage_dir/runs/ with detach.log
|
||||
let runs_base = storage_dir.join("runs");
|
||||
assert!(runs_base.exists(), "runs/ directory should exist");
|
||||
let entries: Vec<_> = std::fs::read_dir(&runs_base)
|
||||
.unwrap()
|
||||
.filter_map(|e| e.ok())
|
||||
.collect();
|
||||
assert_eq!(entries.len(), 1, "should have exactly one run directory");
|
||||
let run_dir = entries[0].path();
|
||||
assert!(
|
||||
run_dir.join("detach.log").exists(),
|
||||
"detach.log should exist in run dir"
|
||||
|
|
@ -1015,7 +1032,8 @@ fn start_by_workflow_name_prefers_newly_created_submitted_run() {
|
|||
#[test]
|
||||
fn bug2_detached_uses_cached_graph_not_original_path() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let run_dir = dir.path().join("run");
|
||||
let storage_dir = dir.path().join("storage");
|
||||
let run_dir = storage_dir.join("runs").join("20260101-test-bug2");
|
||||
std::fs::create_dir_all(&run_dir).unwrap();
|
||||
|
||||
let dot = "\
|
||||
|
|
@ -1059,7 +1077,13 @@ digraph G {
|
|||
|
||||
// __detached should use graph.fabro and never reference the deleted file.
|
||||
let output = arc()
|
||||
.args(["__detached", "--run-dir", run_dir.to_str().unwrap()])
|
||||
.args([
|
||||
"__detached",
|
||||
"--storage-dir",
|
||||
storage_dir.to_str().unwrap(),
|
||||
"--run-id",
|
||||
"test-bug2",
|
||||
])
|
||||
.env("NO_COLOR", "1")
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
.output()
|
||||
|
|
@ -1122,7 +1146,7 @@ digraph Test {
|
|||
.success();
|
||||
let before: serde_json::Value =
|
||||
serde_json::from_slice(&inspect_before.get_output().stdout).unwrap();
|
||||
let run_dir = before[0]["run_dir"].as_str().unwrap().to_string();
|
||||
let _run_dir = before[0]["run_dir"].as_str().unwrap().to_string();
|
||||
let start_time_before = before[0]["start_record"]["start_time"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
|
|
@ -1132,9 +1156,17 @@ digraph Test {
|
|||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let storage_dir = home.path().join(".fabro");
|
||||
arc()
|
||||
.env("HOME", home.path())
|
||||
.args(["__detached", "--run-dir", &run_dir, "--resume"])
|
||||
.args([
|
||||
"__detached",
|
||||
"--storage-dir",
|
||||
storage_dir.to_str().unwrap(),
|
||||
"--run-id",
|
||||
&run_id,
|
||||
"--resume",
|
||||
])
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.assert()
|
||||
.failure()
|
||||
|
|
|
|||
|
|
@ -43,6 +43,23 @@ fn read_conclusion(run_dir: &Path) -> Value {
|
|||
read_json(&run_dir.join("conclusion.json"))
|
||||
}
|
||||
|
||||
/// Find the single run directory under `storage_dir/runs/`.
|
||||
fn find_run_dir(storage_dir: &Path) -> PathBuf {
|
||||
let runs_base = storage_dir.join("runs");
|
||||
let entries: Vec<_> = std::fs::read_dir(&runs_base)
|
||||
.unwrap_or_else(|e| panic!("failed to read {}: {e}", runs_base.display()))
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.path().is_dir())
|
||||
.collect();
|
||||
assert_eq!(
|
||||
entries.len(),
|
||||
1,
|
||||
"expected exactly one run directory under {}",
|
||||
runs_base.display()
|
||||
);
|
||||
entries[0].path()
|
||||
}
|
||||
|
||||
fn completed_nodes(run_dir: &Path) -> Vec<String> {
|
||||
let cp = read_checkpoint(run_dir);
|
||||
cp["completed_nodes"]
|
||||
|
|
@ -105,7 +122,7 @@ scenario_tests!(command_pipeline);
|
|||
fn scenario_command_pipeline(sandbox: &str) {
|
||||
dotenvy::dotenv().ok();
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let run_dir = tmp.path().join("run");
|
||||
let storage_dir = tmp.path().join("storage");
|
||||
|
||||
// Validate the workflow before running it
|
||||
fabro()
|
||||
|
|
@ -123,14 +140,15 @@ fn scenario_command_pipeline(sandbox: &str) {
|
|||
"--no-retro",
|
||||
"--sandbox",
|
||||
sandbox,
|
||||
"--run-dir",
|
||||
run_dir.to_str().unwrap(),
|
||||
"--storage-dir",
|
||||
storage_dir.to_str().unwrap(),
|
||||
fixture("command_pipeline.fabro").to_str().unwrap(),
|
||||
])
|
||||
.timeout(timeout_for(sandbox))
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let run_dir = find_run_dir(&storage_dir);
|
||||
let conclusion = read_conclusion(&run_dir);
|
||||
assert_eq!(
|
||||
conclusion["status"].as_str(),
|
||||
|
|
@ -163,7 +181,7 @@ scenario_tests!(conditional_branching);
|
|||
fn scenario_conditional_branching(sandbox: &str) {
|
||||
dotenvy::dotenv().ok();
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let run_dir = tmp.path().join("run");
|
||||
let storage_dir = tmp.path().join("storage");
|
||||
|
||||
fabro()
|
||||
.args([
|
||||
|
|
@ -172,14 +190,15 @@ fn scenario_conditional_branching(sandbox: &str) {
|
|||
"--no-retro",
|
||||
"--sandbox",
|
||||
sandbox,
|
||||
"--run-dir",
|
||||
run_dir.to_str().unwrap(),
|
||||
"--storage-dir",
|
||||
storage_dir.to_str().unwrap(),
|
||||
fixture("conditional_branching.fabro").to_str().unwrap(),
|
||||
])
|
||||
.timeout(timeout_for(sandbox))
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let run_dir = find_run_dir(&storage_dir);
|
||||
let conclusion = read_conclusion(&run_dir);
|
||||
assert_eq!(conclusion["status"].as_str(), Some("success"));
|
||||
|
||||
|
|
@ -200,7 +219,7 @@ scenario_tests!(agent_linear);
|
|||
fn scenario_agent_linear(sandbox: &str) {
|
||||
dotenvy::dotenv().ok();
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let run_dir = tmp.path().join("run");
|
||||
let storage_dir = tmp.path().join("storage");
|
||||
|
||||
fabro()
|
||||
.args([
|
||||
|
|
@ -211,14 +230,15 @@ fn scenario_agent_linear(sandbox: &str) {
|
|||
sandbox,
|
||||
"--model",
|
||||
"claude-haiku-4-5",
|
||||
"--run-dir",
|
||||
run_dir.to_str().unwrap(),
|
||||
"--storage-dir",
|
||||
storage_dir.to_str().unwrap(),
|
||||
fixture("agent_linear.fabro").to_str().unwrap(),
|
||||
])
|
||||
.timeout(timeout_for(sandbox))
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let run_dir = find_run_dir(&storage_dir);
|
||||
let conclusion = read_conclusion(&run_dir);
|
||||
assert_eq!(conclusion["status"].as_str(), Some("success"));
|
||||
|
||||
|
|
@ -247,7 +267,7 @@ scenario_tests!(human_gate);
|
|||
fn scenario_human_gate(sandbox: &str) {
|
||||
dotenvy::dotenv().ok();
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let run_dir = tmp.path().join("run");
|
||||
let storage_dir = tmp.path().join("storage");
|
||||
|
||||
fabro()
|
||||
.args([
|
||||
|
|
@ -258,14 +278,15 @@ fn scenario_human_gate(sandbox: &str) {
|
|||
sandbox,
|
||||
"--model",
|
||||
"claude-haiku-4-5",
|
||||
"--run-dir",
|
||||
run_dir.to_str().unwrap(),
|
||||
"--storage-dir",
|
||||
storage_dir.to_str().unwrap(),
|
||||
fixture("human_gate.fabro").to_str().unwrap(),
|
||||
])
|
||||
.timeout(timeout_for(sandbox))
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let run_dir = find_run_dir(&storage_dir);
|
||||
let conclusion = read_conclusion(&run_dir);
|
||||
assert_eq!(conclusion["status"].as_str(), Some("success"));
|
||||
|
||||
|
|
@ -286,7 +307,7 @@ scenario_tests!(command_agent_mixed);
|
|||
fn scenario_command_agent_mixed(sandbox: &str) {
|
||||
dotenvy::dotenv().ok();
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let run_dir = tmp.path().join("run");
|
||||
let storage_dir = tmp.path().join("storage");
|
||||
|
||||
fabro()
|
||||
.args([
|
||||
|
|
@ -297,14 +318,15 @@ fn scenario_command_agent_mixed(sandbox: &str) {
|
|||
sandbox,
|
||||
"--model",
|
||||
"claude-haiku-4-5",
|
||||
"--run-dir",
|
||||
run_dir.to_str().unwrap(),
|
||||
"--storage-dir",
|
||||
storage_dir.to_str().unwrap(),
|
||||
fixture("command_agent_mixed.fabro").to_str().unwrap(),
|
||||
])
|
||||
.timeout(timeout_for(sandbox))
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let run_dir = find_run_dir(&storage_dir);
|
||||
let conclusion = read_conclusion(&run_dir);
|
||||
assert_eq!(conclusion["status"].as_str(), Some("success"));
|
||||
|
||||
|
|
@ -337,7 +359,7 @@ scenario_tests!(full_stack);
|
|||
fn scenario_full_stack(sandbox: &str) {
|
||||
dotenvy::dotenv().ok();
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let run_dir = tmp.path().join("run");
|
||||
let storage_dir = tmp.path().join("storage");
|
||||
|
||||
fabro()
|
||||
.args([
|
||||
|
|
@ -348,14 +370,15 @@ fn scenario_full_stack(sandbox: &str) {
|
|||
sandbox,
|
||||
"--model",
|
||||
"claude-haiku-4-5",
|
||||
"--run-dir",
|
||||
run_dir.to_str().unwrap(),
|
||||
"--storage-dir",
|
||||
storage_dir.to_str().unwrap(),
|
||||
fixture("full_stack.fabro").to_str().unwrap(),
|
||||
])
|
||||
.timeout(timeout_for(sandbox))
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let run_dir = find_run_dir(&storage_dir);
|
||||
let conclusion = read_conclusion(&run_dir);
|
||||
assert_eq!(
|
||||
conclusion["status"].as_str(),
|
||||
|
|
|
|||
|
|
@ -103,8 +103,8 @@ pub struct FabroConfig {
|
|||
pub no_retro: Option<bool>,
|
||||
|
||||
// --- Server config fields ---
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub data_dir: Option<PathBuf>,
|
||||
#[serde(default, alias = "data_dir", skip_serializing_if = "Option::is_none")]
|
||||
pub storage_dir: Option<PathBuf>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub max_concurrent_runs: Option<usize>,
|
||||
|
|
@ -133,6 +133,15 @@ pub struct FabroConfig {
|
|||
impl FabroConfig {
|
||||
// --- Convenience methods (ported from CliConfig) ---
|
||||
|
||||
/// Resolve the storage directory: config value > default `~/.fabro`.
|
||||
pub fn storage_dir(&self) -> PathBuf {
|
||||
self.storage_dir.clone().unwrap_or_else(|| {
|
||||
dirs::home_dir()
|
||||
.expect("could not determine home directory")
|
||||
.join(".fabro")
|
||||
})
|
||||
}
|
||||
|
||||
pub fn app_id(&self) -> Option<&str> {
|
||||
self.git.as_ref().and_then(|g| g.app_id.as_deref())
|
||||
}
|
||||
|
|
@ -356,8 +365,8 @@ impl FabroConfig {
|
|||
}
|
||||
|
||||
// --- Server config fields ---
|
||||
if overlay.data_dir.is_some() {
|
||||
self.data_dir = overlay.data_dir;
|
||||
if overlay.storage_dir.is_some() {
|
||||
self.storage_dir = overlay.storage_dir;
|
||||
}
|
||||
if overlay.max_concurrent_runs.is_some() {
|
||||
self.max_concurrent_runs = overlay.max_concurrent_runs;
|
||||
|
|
|
|||
|
|
@ -134,14 +134,9 @@ pub fn load_server_config(path: Option<&Path>) -> anyhow::Result<FabroConfig> {
|
|||
crate::load_config_file(path, "server.toml")
|
||||
}
|
||||
|
||||
/// Resolve the data directory: config value > default `~/.fabro`.
|
||||
pub fn resolve_data_dir(config: &FabroConfig) -> PathBuf {
|
||||
if let Some(ref dir) = config.data_dir {
|
||||
return dir.clone();
|
||||
}
|
||||
dirs::home_dir()
|
||||
.map(|h| h.join(".fabro"))
|
||||
.unwrap_or_else(|| PathBuf::from(".fabro"))
|
||||
/// Resolve the storage directory: config value > default `~/.fabro`.
|
||||
pub fn resolve_storage_dir(config: &FabroConfig) -> PathBuf {
|
||||
config.storage_dir()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -149,32 +144,39 @@ mod tests {
|
|||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_config_with_data_dir() {
|
||||
fn parse_config_with_storage_dir() {
|
||||
let toml = r#"storage_dir = "/custom/path""#;
|
||||
let config: FabroConfig = toml::from_str(toml).unwrap();
|
||||
assert_eq!(config.storage_dir, Some(PathBuf::from("/custom/path")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_config_with_data_dir_alias() {
|
||||
let toml = r#"data_dir = "/custom/path""#;
|
||||
let config: FabroConfig = toml::from_str(toml).unwrap();
|
||||
assert_eq!(config.data_dir, Some(PathBuf::from("/custom/path")));
|
||||
assert_eq!(config.storage_dir, Some(PathBuf::from("/custom/path")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_empty_config_defaults() {
|
||||
let toml = "";
|
||||
let config: FabroConfig = toml::from_str(toml).unwrap();
|
||||
assert_eq!(config.data_dir, None);
|
||||
assert_eq!(config.storage_dir, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_data_dir_uses_config_value() {
|
||||
fn resolve_storage_dir_uses_config_value() {
|
||||
let config = FabroConfig {
|
||||
data_dir: Some(PathBuf::from("/my/data")),
|
||||
storage_dir: Some(PathBuf::from("/my/data")),
|
||||
..FabroConfig::default()
|
||||
};
|
||||
assert_eq!(resolve_data_dir(&config), PathBuf::from("/my/data"));
|
||||
assert_eq!(resolve_storage_dir(&config), PathBuf::from("/my/data"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_data_dir_defaults_to_home_arc() {
|
||||
fn resolve_storage_dir_defaults_to_home() {
|
||||
let config = FabroConfig::default();
|
||||
let dir = resolve_data_dir(&config);
|
||||
let dir = resolve_storage_dir(&config);
|
||||
// Should end with .fabro
|
||||
assert!(
|
||||
dir.ends_with(".fabro"),
|
||||
|
|
|
|||
|
|
@ -221,15 +221,18 @@ pub(crate) fn finalize_config(config: &mut FabroConfig, graph: &Graph) {
|
|||
}
|
||||
|
||||
pub fn default_run_dir(run_id: &str, dry_run: bool) -> PathBuf {
|
||||
let base = crate::run_lookup::default_runs_base();
|
||||
make_run_dir(&crate::run_lookup::default_runs_base(), run_id, dry_run)
|
||||
}
|
||||
|
||||
pub fn make_run_dir(runs_base: &Path, run_id: &str, dry_run: bool) -> PathBuf {
|
||||
if dry_run {
|
||||
base.join(format!(
|
||||
runs_base.join(format!(
|
||||
"{}-dry-run-{}",
|
||||
Local::now().format("%Y%m%d"),
|
||||
run_id
|
||||
))
|
||||
} else {
|
||||
base.join(format!("{}-{}", Local::now().format("%Y%m%d"), run_id))
|
||||
runs_base.join(format!("{}-{}", Local::now().format("%Y%m%d"), run_id))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ mod start;
|
|||
|
||||
pub use crate::pipeline::{DevcontainerSpec, LlmSpec, SandboxEnvSpec, SandboxSpec};
|
||||
pub use create::{
|
||||
create, create_from_file, default_run_dir, validate, validate_from_file, RunCreateOptions,
|
||||
ValidateOptions,
|
||||
create, create_from_file, default_run_dir, make_run_dir, validate, validate_from_file,
|
||||
RunCreateOptions, ValidateOptions,
|
||||
};
|
||||
pub use fork::fork;
|
||||
pub use rewind::{
|
||||
|
|
|
|||
|
|
@ -38,18 +38,26 @@ pub struct RunInfo {
|
|||
pub is_orphan: bool,
|
||||
}
|
||||
|
||||
pub fn default_data_dir() -> PathBuf {
|
||||
pub fn default_storage_dir() -> PathBuf {
|
||||
dirs::home_dir()
|
||||
.expect("could not determine home directory")
|
||||
.join(".fabro")
|
||||
}
|
||||
|
||||
pub fn logs_base(storage_dir: &Path) -> PathBuf {
|
||||
storage_dir.join("logs")
|
||||
}
|
||||
|
||||
pub fn default_logs_base() -> PathBuf {
|
||||
default_data_dir().join("logs")
|
||||
logs_base(&default_storage_dir())
|
||||
}
|
||||
|
||||
pub fn runs_base(storage_dir: &Path) -> PathBuf {
|
||||
storage_dir.join("runs")
|
||||
}
|
||||
|
||||
pub fn default_runs_base() -> PathBuf {
|
||||
default_data_dir().join("runs")
|
||||
runs_base(&default_storage_dir())
|
||||
}
|
||||
|
||||
pub fn scan_runs(base: &Path) -> Result<Vec<RunInfo>> {
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ run_one() {
|
|||
local flags=(--auto-approve)
|
||||
[[ "$PHASE" == "dry-run" ]] && flags+=(--dry-run)
|
||||
[[ "$PHASE" == "haiku" ]] && flags+=(--model claude-haiku-4-5)
|
||||
[[ "$PHASE" != "dry-run" ]] && flags+=(--run-dir "$RUNS_DIR/$(echo "$rel" | tr '/' '_')")
|
||||
[[ "$PHASE" != "dry-run" ]] && flags+=(--storage-dir "$RUNS_DIR")
|
||||
|
||||
if (cd "$dot_dir" && capture "$result_file.log" "$ARC" run start "$target" "${flags[@]}"); then
|
||||
echo "PASS" > "$result_file"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue