fabro/lib/crates
brynary-fabro[bot] 52a7ba6e9d Unified WorktreeSandbox (#117)
This PR introduces a unified `WorktreeSandbox` type in `fabro-sandbox`
that consolidates previously duplicated git worktree management logic
spread across `parallel.rs` and `run.rs`. The new type wraps any
`Arc<dyn Sandbox>`, handles the full worktree lifecycle (branch
creation, `worktree add`, and cleanup) in its `initialize()`/`cleanup()`
methods, overrides `working_directory()` and `exec_command()` to default
to the worktree path, and delegates all other `Sandbox` methods to the
inner sandbox. A `WorktreeConfig` struct controls behavior (branch name,
base SHA, worktree path, and a `skip_branch_creation` flag for resume
flows), and a `WorktreeEventCallback` mechanism bridges lifecycle events
to the workflow event system via a new
`EventEmitter::worktree_callback()` helper.

The old private `WorktreeSandbox` struct in `parallel.rs` (which only
redirected `exec_command` working dirs with no lifecycle awareness) is
removed and replaced with the shared implementation. The
`setup_worktree()` function in `run.rs` is also removed; its logic is
absorbed directly into the `SandboxProvider::Local` branch of sandbox
construction, where `WorktreeSandbox::initialize()` is called and
`std::env::set_current_dir()` follows on success. The resume path
(`run_from_branch`) similarly replaces direct `git::replace_worktree`
calls with `WorktreeSandbox` using `skip_branch_creation: true`. The
`MockSandbox` in `test_support.rs` gains `captured_commands` and
`captured_working_dirs` vectors to support sequenced-command assertions
in the new unit tests.

The `MockSandbox` enhancement is a notable improvement for testability
beyond this specific change—having the full ordered sequence of commands
rather than just the last one makes it straightforward to assert on
multi-step git workflows. One subtle behavior worth noting is that in
`parallel.rs` the `git reset --hard` step previously present after
worktree creation is now absent from `WorktreeSandbox::initialize()`;
the plan mentioned it but the implementation deliberately omits it (the
branch is already force-set to the target SHA, so the reset was
redundant for the parallel case). Cleanup for parallel branches
continues to go through `engine::git_remove_worktree` on the parent
sandbox rather than calling `wt_sandbox.cleanup()`, since the sandbox
`Arc` is consumed by the spawned task—this is a reasonable tradeoff
noted in the plan.

### Fabro Details

<details>
<summary>Ran 11 stages in 62m 32s for $3.67</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 1m 11s | – | 0 |
| preflight_lint | 12s | – | 0 |
| implement | 30m 15s | $2.15 | 0 |
| simplify_opus | 15m 5s | $0.71 | 0 |
| simplify_gpt | 11m 8s | $0.54 | 0 |
| verify | 46s | – | 0 |
| fixup | 3m 17s | $0.27 | 0 |
| verify | 46s | – | 0 |
| fmt | 1s | – | 0 |
| **Total** | **62m 32s** | **$3.67** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (12 nodes and 15
edges)</summary>

```dot
digraph ImplementAndSimplify {
    graph [
        goal="Implement and simplify",
        model_stylesheet="
            * { backend: api; 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>
2026-03-20 15:35:04 -04:00
..
fabro-agent Unified WorktreeSandbox (#117) 2026-03-20 15:35:04 -04:00
fabro-api Extract fabro-model crate from fabro-llm (#109) 2026-03-19 22:26:47 -04:00
fabro-beastie Fix fabro-beastie linker error by linking IOKit framework 2026-03-15 18:27:42 -04:00
fabro-cli Unified WorktreeSandbox (#117) 2026-03-20 15:35:04 -04:00
fabro-config Add skip_clone to DaytonaConfig to fix concurrent test failures 2026-03-19 12:27:56 -04:00
fabro-db rename Arc to Fabro in all Rust crates, symbols, env vars, and supporting files 2026-03-12 12:25:58 -04:00
fabro-devcontainer Fix: Remove std::env::set_var/remove_var from tests (#101) 2026-03-19 13:09:46 -04:00
fabro-git-storage Add fabro rewind command to rewind workflow runs to earlier checkpoints 2026-03-14 11:38:17 -04:00
fabro-github Merge fabro-linear and GitHub tracker into fabro-tracker (#108) 2026-03-19 22:26:32 -04:00
fabro-graphviz Extract fabro-validate crate from fabro-workflows 2026-03-17 13:33:34 -04:00
fabro-hooks Extract fabro-model crate from fabro-llm (#109) 2026-03-19 22:26:47 -04:00
fabro-interview Decompose fabro run into create / start / attach (#116) 2026-03-20 14:08:54 -04:00
fabro-llm Map reasoning_effort to Anthropic output_config.effort (#115) 2026-03-19 22:49:16 -04:00
fabro-mcp Move config type tests into fabro-config 2026-03-17 15:21:16 -04:00
fabro-model Add node-level model validation + missing catalog aliases (#110) 2026-03-19 22:48:08 -04:00
fabro-openai-oauth Add Codex endpoint support for OpenAI OAuth flow 2026-03-17 00:47:47 -04:00
fabro-retro Extract fabro-retro crate from fabro-workflows 2026-03-17 11:48:40 -04:00
fabro-sandbox Unified WorktreeSandbox (#117) 2026-03-20 15:35:04 -04:00
fabro-slack Extract fabro-interview crate from fabro-workflows 2026-03-17 13:53:36 -04:00
fabro-telemetry Fix release CI test failures caused by telemetry fork flushing stdout 2026-03-19 15:18:03 -04:00
fabro-tracker Merge fabro-linear and GitHub tracker into fabro-tracker (#108) 2026-03-19 22:26:32 -04:00
fabro-types rename Arc to Fabro in all Rust crates, symbols, env vars, and supporting files 2026-03-12 12:25:58 -04:00
fabro-util Add buffered telemetry with track! macro and extract fabro-telemetry crate 2026-03-19 14:15:33 -04:00
fabro-validate Add node-level model validation + missing catalog aliases (#110) 2026-03-19 22:48:08 -04:00
fabro-workflows Unified WorktreeSandbox (#117) 2026-03-20 15:35:04 -04:00