mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-07 08:27:12 +00:00
Extract --preflight into fabro preflight subcommand
Preflight validation is conceptually distinct from running a workflow — it deserves its own top-level command rather than being a flag on `run`. - Add `PreflightArgs` struct and `Commands::Preflight` variant - Create `commands/preflight.rs` with dedicated `execute()` function - Remove `--preflight` flag from `RunArgs` - Refactor `load_workflow_source_input` to take individual params instead of `&RunArgs` - Refactor `resolve_cli_goal` to take `Option<&str>` / `Option<&Path>` - Refactor `run_preflight` to take `cli_model`/`cli_provider` instead of `&RunArgs`, make `pub(crate)` - Update docs and skills references Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e5c60a84b3
commit
fc3d364c84
11 changed files with 201 additions and 141 deletions
|
|
@ -32,8 +32,8 @@ It checks:
|
|||
|
||||
**SSE streams disconnecting** — If using a reverse proxy, ensure buffering is disabled and the connection timeout is long enough for workflow runs. See the [reverse proxy example](/administration/deployment#binding-and-tls).
|
||||
|
||||
**Run config validation errors** — Use `--preflight` to validate without executing:
|
||||
**Run config validation errors** — Use `fabro preflight` to validate without executing:
|
||||
|
||||
```bash
|
||||
fabro run run.toml --preflight
|
||||
fabro preflight run.toml
|
||||
```
|
||||
|
|
|
|||
|
|
@ -488,8 +488,8 @@ Fabro validates the run config when it loads:
|
|||
- **Unknown fields** — Extra fields not listed above are silently ignored.
|
||||
- **Variable check** — Any `$variable` in the Graphviz file without a matching `[vars]` entry produces an error.
|
||||
|
||||
Use `--preflight` to validate a run config without executing it:
|
||||
Use `fabro preflight` to validate a run config without executing it:
|
||||
|
||||
```bash
|
||||
fabro run run.toml --preflight
|
||||
fabro preflight run.toml
|
||||
```
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ fabro run run.toml
|
|||
| `<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 |
|
||||
| `--dry-run` | Execute with a simulated LLM backend |
|
||||
| `--preflight` | Validate run configuration without executing |
|
||||
| `--auto-approve` | Auto-approve all human gates |
|
||||
| `--model <MODEL>` | Override default LLM model |
|
||||
| `--provider <PROVIDER>` | Override default LLM provider |
|
||||
|
|
@ -62,9 +61,24 @@ fabro run run.toml
|
|||
| `--preserve-sandbox` | Keep the sandbox alive after the run finishes (for debugging) |
|
||||
| `-d, --detach` | Fork the workflow as a background process and print the run ID. Reconnect later with `fabro logs -f`. |
|
||||
|
||||
<Note>
|
||||
`--preflight` conflicts with `--dry-run` and `--detach`.
|
||||
</Note>
|
||||
## `fabro preflight`
|
||||
|
||||
Validate run configuration (sandbox, LLM providers, GitHub tokens) without executing the workflow.
|
||||
|
||||
```bash
|
||||
fabro preflight <WORKFLOW>
|
||||
fabro preflight run.toml
|
||||
```
|
||||
|
||||
| Argument / Flag | Description |
|
||||
|---|---|
|
||||
| `<WORKFLOW>` | Path to a `.fabro` workflow file, `.toml` task config, or workflow name. |
|
||||
| `--goal <GOAL>` | Override the workflow goal (exposed as `$goal` in prompts) |
|
||||
| `--goal-file <FILE>` | Read the goal from a file instead of inline text |
|
||||
| `--model <MODEL>` | Override default LLM model |
|
||||
| `--provider <PROVIDER>` | Override default LLM provider |
|
||||
| `-v, --verbose` | Enable verbose output |
|
||||
| `--sandbox <SANDBOX>` | Sandbox for agent tools: `local`, `docker`, `daytona`, `ssh`, or `exe` |
|
||||
|
||||
## `fabro resume`
|
||||
|
||||
|
|
|
|||
|
|
@ -99,10 +99,6 @@ pub(crate) struct RunArgs {
|
|||
#[arg(long)]
|
||||
pub(crate) dry_run: bool,
|
||||
|
||||
/// Validate run configuration without executing
|
||||
#[arg(long, conflicts_with = "dry_run")]
|
||||
pub(crate) preflight: bool,
|
||||
|
||||
/// Auto-approve all human gates
|
||||
#[arg(long)]
|
||||
pub(crate) auto_approve: bool,
|
||||
|
|
@ -144,7 +140,7 @@ pub(crate) struct RunArgs {
|
|||
pub(crate) preserve_sandbox: bool,
|
||||
|
||||
/// Run the workflow in the background and print the run ID
|
||||
#[arg(short = 'd', long, conflicts_with = "preflight")]
|
||||
#[arg(short = 'd', long)]
|
||||
pub(crate) detach: bool,
|
||||
|
||||
/// Pre-generated run ID (used internally by --detach)
|
||||
|
|
@ -152,6 +148,36 @@ pub(crate) struct RunArgs {
|
|||
pub(crate) run_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub(crate) struct PreflightArgs {
|
||||
/// Path to a .fabro workflow file or .toml task config
|
||||
pub(crate) workflow: PathBuf,
|
||||
|
||||
/// Override the workflow goal (exposed as $goal in prompts)
|
||||
#[arg(long)]
|
||||
pub(crate) goal: Option<String>,
|
||||
|
||||
/// Read the workflow goal from a file
|
||||
#[arg(long, conflicts_with = "goal")]
|
||||
pub(crate) goal_file: Option<PathBuf>,
|
||||
|
||||
/// Override default LLM model
|
||||
#[arg(long)]
|
||||
pub(crate) model: Option<String>,
|
||||
|
||||
/// Override default LLM provider
|
||||
#[arg(long)]
|
||||
pub(crate) provider: Option<String>,
|
||||
|
||||
/// Enable verbose output
|
||||
#[arg(short, long)]
|
||||
pub(crate) verbose: bool,
|
||||
|
||||
/// Sandbox for agent tools
|
||||
#[arg(long, value_enum)]
|
||||
pub(crate) sandbox: Option<CliSandboxProvider>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub(crate) struct RunFilterArgs {
|
||||
/// Only include runs started before this date (YYYY-MM-DD prefix match)
|
||||
|
|
@ -694,6 +720,8 @@ pub(crate) enum Commands {
|
|||
Exec(fabro_agent::cli::AgentArgs),
|
||||
#[command(flatten)]
|
||||
RunCmd(RunCommands),
|
||||
/// Validate run configuration without executing
|
||||
Preflight(PreflightArgs),
|
||||
/// Validate a workflow
|
||||
Validate(ValidateArgs),
|
||||
/// Render a workflow graph as SVG or PNG
|
||||
|
|
@ -780,6 +808,7 @@ impl Commands {
|
|||
},
|
||||
Self::Exec(_) => "exec",
|
||||
Self::RunCmd(cmd) => cmd.name(),
|
||||
Self::Preflight(_) => "preflight",
|
||||
Self::Validate(_) => "validate",
|
||||
Self::Graph(_) => "graph",
|
||||
Self::Parse(_) => "parse",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ pub mod llm;
|
|||
pub mod model;
|
||||
pub mod parse;
|
||||
pub mod pr;
|
||||
pub mod preflight;
|
||||
pub mod provider;
|
||||
pub mod repo;
|
||||
pub mod run;
|
||||
|
|
|
|||
93
lib/crates/fabro-cli/src/commands/preflight.rs
Normal file
93
lib/crates/fabro-cli/src/commands/preflight.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
use std::path::Path;
|
||||
|
||||
use anyhow::bail;
|
||||
use fabro_util::terminal::Styles;
|
||||
|
||||
use crate::args::PreflightArgs;
|
||||
use crate::cli_config;
|
||||
|
||||
use super::run::execute::{
|
||||
apply_execution_overrides, load_workflow_source_input, print_workflow_report,
|
||||
resolve_sandbox_provider, run_preflight, ExecutionOverrides,
|
||||
};
|
||||
|
||||
pub async fn execute(mut args: PreflightArgs) -> anyhow::Result<()> {
|
||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||
let cli_config = cli_config::load_cli_config(None)?;
|
||||
args.verbose = args.verbose || cli_config.verbose_enabled();
|
||||
|
||||
let github_app = crate::shared::github::build_github_app_credentials(cli_config.app_id());
|
||||
|
||||
let run_defaults = cli_config;
|
||||
|
||||
let source_input = load_workflow_source_input(
|
||||
&args.workflow,
|
||||
args.goal.as_deref(),
|
||||
args.goal_file.as_deref(),
|
||||
run_defaults,
|
||||
true,
|
||||
)?;
|
||||
|
||||
let original_cwd = std::env::current_dir()?;
|
||||
let (origin_url, detected_base_branch) =
|
||||
fabro_sandbox::daytona::detect_repo_info(&original_cwd)
|
||||
.map(|(url, branch)| (Some(url), branch))
|
||||
.unwrap_or((None, None));
|
||||
let git_status =
|
||||
fabro_workflows::git::sync_status(&original_cwd, "origin", detected_base_branch.as_deref());
|
||||
|
||||
let sandbox_provider = resolve_sandbox_provider(
|
||||
args.sandbox.map(Into::into),
|
||||
Some(&source_input.config),
|
||||
&source_input.run_defaults,
|
||||
)?;
|
||||
|
||||
let mut config = source_input.config.clone();
|
||||
apply_execution_overrides(
|
||||
&mut config,
|
||||
&ExecutionOverrides {
|
||||
dry_run: false,
|
||||
auto_approve: false,
|
||||
no_retro: false,
|
||||
verbose: args.verbose,
|
||||
preserve_sandbox: false,
|
||||
model: args.model.as_deref(),
|
||||
provider: args.provider.as_deref(),
|
||||
sandbox_provider,
|
||||
},
|
||||
);
|
||||
|
||||
let validated = fabro_workflows::operations::validate(
|
||||
&source_input.raw_source,
|
||||
fabro_workflows::operations::ValidateOptions {
|
||||
base_dir: Some(
|
||||
source_input
|
||||
.dot_path
|
||||
.parent()
|
||||
.unwrap_or(Path::new("."))
|
||||
.to_path_buf(),
|
||||
),
|
||||
config: Some(config.clone()),
|
||||
goal_override: source_input.goal_override.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
print_workflow_report(&validated, &source_input.dot_path, styles);
|
||||
if validated.has_errors() {
|
||||
bail!("Validation failed");
|
||||
}
|
||||
|
||||
run_preflight(
|
||||
validated.graph(),
|
||||
&Some(config),
|
||||
args.model.as_deref(),
|
||||
args.provider.as_deref(),
|
||||
&source_input.run_defaults,
|
||||
git_status,
|
||||
sandbox_provider,
|
||||
styles,
|
||||
github_app,
|
||||
origin_url.as_deref(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
|
@ -21,7 +21,17 @@ pub async fn create_run(
|
|||
styles: &Styles,
|
||||
quiet: bool,
|
||||
) -> anyhow::Result<(String, PathBuf)> {
|
||||
let source_input = load_workflow_source_input(args, run_defaults, true)?;
|
||||
let workflow_path = args
|
||||
.workflow
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("--workflow is required"))?;
|
||||
let source_input = load_workflow_source_input(
|
||||
workflow_path,
|
||||
args.goal.as_deref(),
|
||||
args.goal_file.as_deref(),
|
||||
run_defaults,
|
||||
true,
|
||||
)?;
|
||||
let run_id = args
|
||||
.run_id
|
||||
.clone()
|
||||
|
|
|
|||
|
|
@ -40,11 +40,11 @@ use crate::shared::{
|
|||
|
||||
/// Resolve goal from `--goal` string or `--goal-file` path.
|
||||
pub(crate) fn resolve_cli_goal(
|
||||
goal: &Option<String>,
|
||||
goal_file: &Option<PathBuf>,
|
||||
goal: Option<&str>,
|
||||
goal_file: Option<&Path>,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
match (goal, goal_file) {
|
||||
(Some(g), _) => Ok(Some(g.clone())),
|
||||
(Some(g), _) => Ok(Some(g.to_string())),
|
||||
(_, Some(path)) => {
|
||||
let path = fabro_util::path::expand_tilde(path);
|
||||
let content = std::fs::read_to_string(&path)
|
||||
|
|
@ -484,21 +484,19 @@ pub(crate) struct WorkflowSourceInput {
|
|||
pub goal_override: Option<String>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
enum WorkflowState {
|
||||
Source(Box<WorkflowSourceInput>),
|
||||
Persisted(Box<Persisted>),
|
||||
}
|
||||
|
||||
pub(crate) fn load_workflow_source_input(
|
||||
args: &RunArgs,
|
||||
workflow: &Path,
|
||||
goal: Option<&str>,
|
||||
goal_file: Option<&Path>,
|
||||
mut run_defaults: FabroConfig,
|
||||
apply_project_config: bool,
|
||||
) -> anyhow::Result<WorkflowSourceInput> {
|
||||
let workflow_path = args
|
||||
.workflow
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("--workflow is required"))?;
|
||||
|
||||
if apply_project_config {
|
||||
// Apply project-level config overrides (fabro.toml) on top of CLI defaults.
|
||||
if let Ok(Some((_config_path, project_config))) =
|
||||
|
|
@ -511,7 +509,7 @@ pub(crate) fn load_workflow_source_input(
|
|||
|
||||
// Resolve workflow arg, load run config if TOML, merge with defaults.
|
||||
let (resolved_workflow_path, dot_path, config) = {
|
||||
let (resolved, dot, cfg) = resolve_workflow_source(workflow_path)?;
|
||||
let (resolved, dot, cfg) = resolve_workflow_source(workflow)?;
|
||||
match cfg {
|
||||
Some(cfg) => {
|
||||
let mut merged = run_defaults.clone();
|
||||
|
|
@ -529,7 +527,7 @@ pub(crate) fn load_workflow_source_input(
|
|||
}
|
||||
|
||||
let raw_source = read_workflow_file(&dot_path)?;
|
||||
let cli_goal = resolve_cli_goal(&args.goal, &args.goal_file)?;
|
||||
let cli_goal = resolve_cli_goal(goal, goal_file)?;
|
||||
let goal_override = cli_goal.or_else(|| config.goal.clone());
|
||||
|
||||
let workflow_toml_path = if resolved_workflow_path
|
||||
|
|
@ -600,7 +598,7 @@ pub async fn run_from_record(
|
|||
workflow: None,
|
||||
run_dir: Some(run_dir),
|
||||
dry_run: record.config.dry_run_enabled(),
|
||||
preflight: false,
|
||||
|
||||
auto_approve: record.config.auto_approve_enabled(),
|
||||
goal: record.config.goal.clone(),
|
||||
goal_file: None,
|
||||
|
|
@ -675,7 +673,7 @@ pub async fn resume_from_record(
|
|||
workflow: None,
|
||||
run_dir: Some(run_dir),
|
||||
dry_run: record.config.dry_run_enabled(),
|
||||
preflight: false,
|
||||
|
||||
auto_approve: record.config.auto_approve_enabled(),
|
||||
goal: record.config.goal.clone(),
|
||||
goal_file: None,
|
||||
|
|
@ -738,63 +736,28 @@ pub async fn execute(mut args: RunArgs, _globals: &GlobalArgs) -> anyhow::Result
|
|||
let cli_config = cli_config::load_cli_config(None)?;
|
||||
args.verbose = args.verbose || cli_config.verbose_enabled();
|
||||
|
||||
if args.preflight {
|
||||
let github_app = crate::shared::github::build_github_app_credentials(cli_config.app_id());
|
||||
let git_author = fabro_workflows::git::GitAuthor::from_options(
|
||||
cli_config.git_author().and_then(|a| a.name.clone()),
|
||||
cli_config.git_author().and_then(|a| a.email.clone()),
|
||||
);
|
||||
run_command(args, cli_config, styles, github_app, git_author).await?;
|
||||
let quiet = args.detach;
|
||||
let _prevent_idle_sleep = cli_config.prevent_idle_sleep_enabled();
|
||||
let (run_id, run_dir) = super::create::create_run(&args, cli_config, styles, quiet).await?;
|
||||
|
||||
#[cfg(feature = "sleep_inhibitor")]
|
||||
let _sleep_guard = fabro_beastie::guard(_prevent_idle_sleep);
|
||||
|
||||
let child = super::start::start_run(&run_dir, false)?;
|
||||
|
||||
if args.detach {
|
||||
println!("{run_id}");
|
||||
} else {
|
||||
let quiet = args.detach;
|
||||
let _prevent_idle_sleep = cli_config.prevent_idle_sleep_enabled();
|
||||
let (run_id, run_dir) = super::create::create_run(&args, cli_config, styles, quiet).await?;
|
||||
|
||||
#[cfg(feature = "sleep_inhibitor")]
|
||||
let _sleep_guard = fabro_beastie::guard(_prevent_idle_sleep);
|
||||
|
||||
let child = super::start::start_run(&run_dir, false)?;
|
||||
|
||||
if args.detach {
|
||||
println!("{run_id}");
|
||||
} else {
|
||||
let exit_code = super::attach::attach_run(&run_dir, true, styles, Some(child)).await?;
|
||||
print_run_summary(&run_dir, &run_id, styles);
|
||||
if exit_code != std::process::ExitCode::SUCCESS {
|
||||
std::process::exit(1);
|
||||
}
|
||||
let exit_code = super::attach::attach_run(&run_dir, true, styles, Some(child)).await?;
|
||||
print_run_summary(&run_dir, &run_id, styles);
|
||||
if exit_code != std::process::ExitCode::SUCCESS {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run_command(
|
||||
args: RunArgs,
|
||||
run_defaults: FabroConfig,
|
||||
styles: &'static Styles,
|
||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||
git_author: fabro_workflows::git::GitAuthor,
|
||||
) -> anyhow::Result<()> {
|
||||
let source_input = load_workflow_source_input(&args, run_defaults, true)?;
|
||||
let resolved_run_defaults = source_input.run_defaults.clone();
|
||||
|
||||
let record_run = RecordBasedRun {
|
||||
workflow: WorkflowState::Source(Box::new(source_input)),
|
||||
run_defaults: resolved_run_defaults,
|
||||
};
|
||||
|
||||
run_command_impl(
|
||||
args,
|
||||
styles,
|
||||
github_app,
|
||||
git_author,
|
||||
Some(record_run),
|
||||
false,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn run_command_impl(
|
||||
args: RunArgs,
|
||||
styles: &'static Styles,
|
||||
|
|
@ -817,8 +780,6 @@ async fn run_command_impl(
|
|||
fabro_sandbox::daytona::detect_repo_info(&original_cwd)
|
||||
.map(|(url, branch)| (Some(url), branch))
|
||||
.unwrap_or((None, None));
|
||||
let git_status =
|
||||
fabro_workflows::git::sync_status(&original_cwd, "origin", detected_base_branch.as_deref());
|
||||
|
||||
// 3. Create logs directory
|
||||
// Extract values from args before partial move
|
||||
|
|
@ -879,40 +840,6 @@ async fn run_command_impl(
|
|||
},
|
||||
);
|
||||
|
||||
if args.preflight {
|
||||
let validated = fabro_workflows::operations::validate(
|
||||
&source_input.raw_source,
|
||||
fabro_workflows::operations::ValidateOptions {
|
||||
base_dir: Some(
|
||||
source_input
|
||||
.dot_path
|
||||
.parent()
|
||||
.unwrap_or(Path::new("."))
|
||||
.to_path_buf(),
|
||||
),
|
||||
config: Some(config.clone()),
|
||||
goal_override: source_input.goal_override.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
print_workflow_report(&validated, &source_input.dot_path, styles);
|
||||
if validated.has_errors() {
|
||||
bail!("Validation failed");
|
||||
}
|
||||
return run_preflight(
|
||||
validated.graph(),
|
||||
&Some(config),
|
||||
&args,
|
||||
&run_defaults,
|
||||
git_status,
|
||||
sandbox_provider,
|
||||
styles,
|
||||
github_app,
|
||||
origin_url.as_deref(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
match fabro_workflows::operations::create(
|
||||
&source_input.raw_source,
|
||||
fabro_workflows::operations::RunCreateOptions {
|
||||
|
|
@ -1501,10 +1428,11 @@ pub(crate) fn print_assets(run_dir: &std::path::Path, styles: &Styles) {
|
|||
/// resolves the model/provider through the full precedence chain, and prints
|
||||
/// a styled check report.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn run_preflight(
|
||||
pub(crate) async fn run_preflight(
|
||||
graph: &fabro_graphviz::graph::Graph,
|
||||
run_cfg: &Option<FabroConfig>,
|
||||
args: &RunArgs,
|
||||
cli_model: Option<&str>,
|
||||
cli_provider: Option<&str>,
|
||||
run_defaults: &FabroConfig,
|
||||
git_status: GitSyncStatus,
|
||||
sandbox_provider: SandboxProvider,
|
||||
|
|
@ -1558,8 +1486,8 @@ async fn run_preflight(
|
|||
|
||||
// 2. Workflow metadata
|
||||
let (model, provider) = resolve_model_provider(
|
||||
args.model.as_deref(),
|
||||
args.provider.as_deref(),
|
||||
cli_model,
|
||||
cli_provider,
|
||||
run_cfg.as_ref(),
|
||||
run_defaults,
|
||||
graph,
|
||||
|
|
@ -1979,27 +1907,11 @@ include = ["*.md"]
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let args = RunArgs {
|
||||
workflow: Some(dir.path().join("workflow.toml")),
|
||||
run_dir: None,
|
||||
dry_run: false,
|
||||
preflight: false,
|
||||
auto_approve: false,
|
||||
goal: None,
|
||||
goal_file: None,
|
||||
model: None,
|
||||
provider: None,
|
||||
verbose: false,
|
||||
sandbox: None,
|
||||
label: Vec::new(),
|
||||
no_retro: false,
|
||||
preserve_sandbox: false,
|
||||
detach: false,
|
||||
run_id: None,
|
||||
};
|
||||
let workflow_path = dir.path().join("workflow.toml");
|
||||
|
||||
let source_input =
|
||||
load_workflow_source_input(&args, FabroConfig::default(), false).unwrap();
|
||||
load_workflow_source_input(&workflow_path, None, None, FabroConfig::default(), false)
|
||||
.unwrap();
|
||||
let validated = fabro_workflows::operations::validate(
|
||||
&source_input.raw_source,
|
||||
fabro_workflows::operations::ValidateOptions {
|
||||
|
|
@ -2058,19 +1970,19 @@ include = ["*.md"]
|
|||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("goal.md");
|
||||
std::fs::write(&path, "goal from file").unwrap();
|
||||
let result = resolve_cli_goal(&None, &Some(path)).unwrap();
|
||||
let result = resolve_cli_goal(None, Some(path.as_path())).unwrap();
|
||||
assert_eq!(result, Some("goal from file".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_cli_goal_from_string() {
|
||||
let result = resolve_cli_goal(&Some("inline goal".to_string()), &None).unwrap();
|
||||
let result = resolve_cli_goal(Some("inline goal"), None).unwrap();
|
||||
assert_eq!(result, Some("inline goal".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_cli_goal_none() {
|
||||
let result = resolve_cli_goal(&None, &None).unwrap();
|
||||
let result = resolve_cli_goal(None, None).unwrap();
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ async fn main_inner() -> (String, Result<()>) {
|
|||
Commands::Llm(ns) => commands::llm::dispatch(ns, &globals).await?,
|
||||
Commands::Exec(args) => commands::exec::execute(args, &globals).await?,
|
||||
Commands::RunCmd(cmd) => commands::run::dispatch(cmd, &globals).await?,
|
||||
Commands::Preflight(args) => commands::preflight::execute(args).await?,
|
||||
Commands::Validate(args) => {
|
||||
let styles = fabro_util::terminal::Styles::detect_stderr();
|
||||
commands::validate::run(&args, &styles)?;
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ NODE_ENV = "test"
|
|||
|
||||
### Step 7: Validate
|
||||
|
||||
Run `fabro run --preflight workflow.toml` (or `fabro run --preflight workflow.fabro`) to validate without executing.
|
||||
Run `fabro preflight workflow.toml` (or `fabro preflight workflow.fabro`) to validate without executing.
|
||||
|
||||
If validation fails, fix the reported errors and re-validate.
|
||||
|
||||
|
|
|
|||
|
|
@ -139,5 +139,5 @@ Node-level attribute > Stylesheet > TOML config > CLI flags > Server defaults >
|
|||
## Validation
|
||||
|
||||
```bash
|
||||
fabro run --preflight workflow.toml # validate without executing
|
||||
fabro preflight workflow.toml # validate without executing
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue