mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
This PR extracts the `fabro resume` subcommand from `fabro run`,
replacing the `--resume` and `--run-branch` flags with a dedicated, more
ergonomic interface. Users can now run `fabro resume <RUN_ID>` instead
of constructing `fabro run --run-branch fabro/run/<RUN_ID>` manually,
and the command also accepts run ID prefixes (matching the pattern
established by `fabro rewind` and `fabro fork`). Checkpoint-file-based
resumption is also supported via `fabro resume --checkpoint
path/to/checkpoint.json --workflow workflow.fabro`.
The implementation moves the ~315-line `run_from_branch()` function out
of `run.rs` and into a new `commands/resume.rs` module, splitting it
into two preparation paths (`prepare_from_checkpoint` and
`prepare_from_branch`) that converge on a shared `run_resumed()` tail.
Several previously private helpers in `run.rs` are widened to
`pub(crate)` to allow sharing: `local_sandbox_with_callback`,
`resolve_ssh_config`, `resolve_ssh_clone_params`,
`resolve_preserve_sandbox`, `generate_retro`, `write_finalize_commit`,
`print_final_output`, `print_assets`, and the new `default_run_dir`
helper extracted from duplicated inline logic. The `RunArgs` struct
loses its `resume` and `run_branch` fields along with their
`conflicts_with` annotations, and `RunSpec` drops the corresponding
fields with `#[serde(default)]` for backward compatibility.
Documentation across `docs/reference/cli.mdx`,
`docs/execution/checkpoints.mdx`, and
`docs/core-concepts/how-fabro-works.mdx` is updated to reflect the new
interface, and the `rewind`/`fork` commands now hint `fabro resume
<short-prefix>` instead of the full branch name.
### Fabro Details
<details>
<summary>Ran 9 stages in 30m 25s for $6.70</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 1m 10s | – | 0 |
| preflight_lint | 13s | – | 0 |
| implement | 18m 30s | $3.95 | 0 |
| simplify_opus | 9m 38s | $2.75 | 0 |
| simplify_gpt | 0s | – | 0 |
| verify | 19s | – | 0 |
| fmt | 1s | – | 0 |
| **Total** | **30m 25s** | **$6.70** | **0** |
</details>
<details>
<summary>Ran <code>ImplementPlan.fabro</code> (12 nodes and 15
edges)</summary>
```dot
digraph ImplementPlan {
graph [
goal="Implement and simplify",
model_stylesheet="
* { model: claude-opus-4-6; }
"
]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
toolchain [label="Toolchain", shape=parallelogram, script="command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", max_retries=0]
preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check -q --workspace 2>&1", max_retries=0]
preflight_lint [label="Preflight Lint", shape=parallelogram, script="cargo clippy -q --workspace -- -D warnings 2>&1", max_retries=0]
fix_lints [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
implement [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD."]
simplify_opus [label="Simplify (Opus)", prompt="@prompts/simplify.md"]
simplify_gpt [label="Simplify (GPT-54)", prompt="@prompts/simplify.md", model="gpt-54"]
verify [label="Verify", shape=parallelogram, script="cargo clippy -q --workspace -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 2>&1", goal_gate=true, retry_target="fixup"]
fixup [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all clippy lint warnings and test failures.", max_visits=3]
fmt [label="Format", shape=parallelogram, script="cargo fmt --all 2>&1", max_retries=0]
start -> toolchain
toolchain -> preflight_compile [condition="outcome=success"]
toolchain -> exit
preflight_compile -> preflight_lint [condition="outcome=success"]
preflight_compile -> exit
preflight_lint -> implement [condition="outcome=success"]
preflight_lint -> fix_lints
fix_lints -> preflight_lint
implement -> simplify_opus -> simplify_gpt -> verify
verify -> fmt [condition="outcome=success"]
verify -> fixup
fixup -> verify
fmt -> exit
}
```
</details>
⚒️ Generated with [Fabro](https://fabro.sh)
---------
Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
80 lines
2.5 KiB
Rust
80 lines
2.5 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use fabro_config::run::RunDefaults;
|
|
use fabro_workflows::run_spec::RunSpec;
|
|
|
|
use super::run::{
|
|
cached_graph_path, default_run_dir, prepare_workflow, write_run_config_snapshot, RunArgs,
|
|
};
|
|
use fabro_util::terminal::Styles;
|
|
|
|
/// Create a workflow run: allocate run directory, persist spec, return (run_id, run_dir).
|
|
///
|
|
/// This does NOT execute the workflow — it only prepares the run directory.
|
|
pub async fn create_run(
|
|
args: &RunArgs,
|
|
run_defaults: RunDefaults,
|
|
styles: &Styles,
|
|
quiet: bool,
|
|
) -> anyhow::Result<(String, PathBuf)> {
|
|
let workflow_path = args
|
|
.workflow
|
|
.as_ref()
|
|
.ok_or_else(|| anyhow::anyhow!("--workflow is required"))?;
|
|
|
|
let mut prep = prepare_workflow(args, run_defaults, styles, quiet)?;
|
|
|
|
let goal = prep.graph.goal();
|
|
|
|
// Create run directory
|
|
let run_id = ulid::Ulid::new().to_string();
|
|
let run_dir = args
|
|
.run_dir
|
|
.clone()
|
|
.unwrap_or_else(|| default_run_dir(&run_id, args.dry_run));
|
|
tokio::fs::create_dir_all(&run_dir).await?;
|
|
|
|
// Write essential files
|
|
tokio::fs::write(cached_graph_path(&run_dir), &prep.source).await?;
|
|
tokio::fs::write(run_dir.join("id.txt"), &run_id).await?;
|
|
std::fs::File::create(run_dir.join("progress.jsonl"))?;
|
|
fabro_workflows::run_status::write_run_status(
|
|
&run_dir,
|
|
fabro_workflows::run_status::RunStatus::Submitted,
|
|
None,
|
|
);
|
|
|
|
// Serialize the merged run config so the run dir is self-contained.
|
|
write_run_config_snapshot(&run_dir, prep.run_cfg.as_mut()).await?;
|
|
|
|
// Build and save RunSpec
|
|
let working_directory = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
|
let spec = RunSpec {
|
|
run_id: run_id.clone(),
|
|
workflow_path: std::fs::canonicalize(workflow_path).unwrap_or(workflow_path.clone()),
|
|
dot_source: prep.source,
|
|
working_directory,
|
|
goal: if goal.is_empty() {
|
|
None
|
|
} else {
|
|
Some(goal.to_string())
|
|
},
|
|
model: prep.model,
|
|
provider: prep.provider,
|
|
sandbox_provider: prep.sandbox_provider.to_string(),
|
|
labels: args
|
|
.label
|
|
.iter()
|
|
.filter_map(|s| s.split_once('='))
|
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
|
.collect(),
|
|
verbose: args.verbose,
|
|
no_retro: args.no_retro,
|
|
preserve_sandbox: args.preserve_sandbox,
|
|
dry_run: args.dry_run,
|
|
auto_approve: args.auto_approve,
|
|
};
|
|
spec.save(&run_dir)?;
|
|
|
|
Ok((run_id, run_dir))
|
|
}
|