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>
The DaytonaConfig struct had a skip_clone field that was missing from
both the OpenAPI spec and the conformance test initializer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MDX treats {…} as JSX expressions. Bare curly braces in headings
and bold text caused acorn parse failures during Mintlify deploy.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Promote fabro pr, fabro init, fabro diff, fabro preview, fabro graph,
user-level workflows, and GPT-5.4 Mini from accordion items to hero
sections. Reframe lifecycle hooks with positive language.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add March 17 changelog entry (OpenAI Codex backend, fabro docs/discord
commands, gpt-5.4-mini). Update March 16 entry with OAuth error fix.
Add gpt-5.4-mini to model catalog docs and fabro docs/discord to CLI
reference.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add data attribute to confirm script execution, and delay
initialization to avoid React hydration clobbering changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mintlify's custom TextMate grammar support doesn't work in production
builds (see mintlify/discussions#3401). Work around this with a lightweight
JS script that applies regex-based highlighting to code blocks containing
digraph definitions, matching Shiki's inline style format.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Register a separate Shiki grammar so ```fabro code blocks get
DOT syntax highlighting alongside the existing ```dot blocks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Route OpenAI OAuth users through the ChatGPT Codex backend API with
required headers (ChatGPT-Account-Id, originator). The Codex endpoint
requires streaming-only requests, omits unsupported fields (temperature,
max_output_tokens, top_p), and uses a different error format. Also
persists the account ID from OAuth tokens and updates CLI docs for
`fabro ps -q`.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Roadmap: replace placeholder items with current shipped/building/planned
features. Use real dates for sorting instead of manual sortOrder. Fix
UTC timezone rendering for date display.
Terminology: replace all standalone "DOT" references with "Graphviz" or
"Graphviz DOT" across docs, marketing, README, AGENTS.md, and OpenAPI
spec. Changelogs left unchanged as historical records.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Match the marketing website's tabbed install widget across README, docs
quick-start, and CLAUDE.md. Add marketing site build/deploy commands.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add 1200x630 branded OG image matching Mintlify docs card style
- Add Open Graph and Twitter Card meta tags to Layout.astro
- Save og-image-template.html for easy regeneration
- Remove comma from "open source, dark software factory" everywhere
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Documents the pattern of planning interactively in Claude Code and
delegating implementation to Fabro via the /fabro-implement slash
command, with multi-model simplification and verification gates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When `auto_merge = true` is set in `[pull_request]` config, Fabro enables
GitHub's auto-merge on created PRs using the `enablePullRequestAutoMerge`
GraphQL mutation. Auto-merge implies `draft = false` since GitHub doesn't
allow auto-merge on draft PRs. A `merge_strategy` field (squash/merge/rebase,
default squash) controls the merge method. Failures to enable auto-merge
(e.g. repo doesn't have the setting enabled) warn but don't fail the run.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Tool names are shell/read_file/write_file/edit_file/glob/grep/web_fetch/web_search, not Bash/Read/Write/Edit etc.
- AnthropicProfile::new takes only model, not (model, config)
- Add missing web_search to tool list
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restructure the SDK reference page to cover both crates. The page now
opens with an overview of Fabro's two Rust SDK entry points, followed
by full fabro-agent documentation (Session, SessionConfig, Sandbox,
provider profiles, events, tool hooks, error handling) and the existing
fabro-llm reference.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move retro control from [fabro] retro to [features] retros in project
config. Default changes from true to false — retros are now opt-in.
Add retros field to server config Features struct, OpenAPI spec,
TypeScript client, and web app config. Update docs with experimental
warning and new enablement instructions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a blocking cargo-fmt hook to fabro.toml that auto-formats Rust
files after write_file, edit_file, or apply_patch tool calls. Improve
the hooks documentation with a detailed matcher field reference table,
tool name catalog, cross-field matching caveat, and additional examples.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move Comparison link below Troubleshooting in nav
- Rename "DOT Language" page to "Fabro Language"
- Remove five-tier table from dark factory page, keep link to Dan Shapiro's post
- Add fork command docs and checkpoints section
- Add upgrade, asset list, asset cp command docs
- Add upgrade_check config reference
- Add retros feature flag to server config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the separate CheckpointSaved event — CheckpointCompleted now fires in
both git and non-git paths. git_commit_sha is Optional (None when git is
disabled or for start nodes). The CheckpointSaved hook event is preserved
unchanged for backward compat with user hook configs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename GitCheckpoint/GitCheckpointFailed to CheckpointCompleted/CheckpointFailed
to separate checkpoint lifecycle from git operations. Add 7 new granular git
events: GitCommit, GitPush, GitBranch, GitWorktreeAdd, GitWorktreeRemove,
GitFetch, GitReset. Emit at all relevant call sites in engine.rs and parallel.rs.
Update push helpers to return bool for GitPush success tracking.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>