Commit graph

261 commits

Author SHA1 Message Date
Bryan Helmkamp
bea165192a Update model list snapshots for gpt-5.4-mini
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 15:50:10 -04:00
Bryan Helmkamp
ea789ba994 Deduplicate config loading and run-config merging
- Extract generic load_config_file<T>(path, filename) helper, reducing
  load_cli_config and load_server_config to one-liners.

- Rewrite WorkflowRunConfig::apply_defaults to delegate to
  RunDefaults::merge_overlay, eliminating ~90 lines of duplicate
  deep-merge logic. Both methods now share the same code path.

- Fix bug where RunDefaults::merge_overlay silently dropped the ssh
  sandbox config from overlays (the ssh field was never merged).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 15:33:04 -04:00
Bryan Helmkamp
6ebbad800d Move config type tests into fabro-config
Tests for types defined in fabro-config (HookEvent, HookDefinition,
HookConfig, McpServerConfig, McpTransport, etc.) now live alongside
their definitions rather than in the downstream re-exporting crates.

Tests for types that remain in fabro-hooks (HookContext, HookDecision,
PromptHookResponse) stay in fabro-hooks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 15:21:16 -04:00
Bryan Helmkamp
98e8a9b7f6 Fix fabro-config dependency inversion
Move config/data types from upstream crates (fabro-agent, fabro-mcp,
fabro-workflows, fabro-hooks) down into fabro-config so it becomes a
leaf crate depending only on fabro-util + external crates.

New modules in fabro-config:
- mcp.rs: McpServerConfig, McpTransport, McpServerEntry
- sandbox.rs: DaytonaConfig, ExeConfig, SshConfig, SandboxConfig, etc.
- hook.rs: HookEvent, HookDefinition, HookConfig, HookType, TlsMode
- run.rs: RunDefaults, WorkflowRunConfig, LlmConfig, SetupConfig, etc.
- project.rs: ProjectConfig, workflow discovery/resolution functions

Source crates re-export from fabro-config for backward compatibility.
Also removes stale strsim dep and moves toml to dev-deps in
fabro-workflows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 15:17:55 -04:00
Bryan Helmkamp
9e079ed2c5 Clean up fabro-hooks extraction
Extract set_hook_node() helper to deduplicate the 5 call sites that
populate node fields on HookContext. The helper lives in fabro-workflows
(which has the fabro-graphviz dependency) rather than fabro-hooks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 14:12:26 -04:00
Bryan Helmkamp
6c3607440f Extract fabro-hooks crate from fabro-workflows
Move the self-contained hooks module (~2900 LOC) into its own crate to
clarify the dependency graph and make the hook system independently
reusable. The set_node convenience method is inlined at its two call
sites in parallel.rs since it depends on fabro-graphviz types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 14:08:02 -04:00
Bryan Helmkamp
1430fb1f8e Clean up fabro-interview extraction
- Use already-imported names in run_from_branch instead of fully-qualified
  fabro_interview::* paths
- Use std::io::Error::other() for serde error conversion (matches codebase
  convention, more concise)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 13:56:51 -04:00
Bryan Helmkamp
075812478b Extract fabro-interview crate from fabro-workflows
The interviewer module (trait + 7 implementations for human-in-the-loop
interactions) had zero dependencies on fabro-workflows internals, making
it a clean extraction. Consumers (fabro-api, fabro-slack) now depend on
fabro-interview directly instead of reaching through fabro-workflows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 13:53:36 -04:00
Bryan Helmkamp
4daa906ee9 Add GPT-5.4 Mini to model catalog
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 13:53:08 -04:00
Bryan Helmkamp
3d981ea25d Clean up fabro-validate extraction
- Add From<ValidationError> for FabroError to eliminate duplicated
  .map_err(|e| FabroError::Validation(e.0)) at call sites
- Use top-level `use` imports for stylesheet types in rules.rs
  instead of verbose fully-qualified paths
- Remove duplicate parse_condition tests from fabro-workflows
  (already covered by fabro-graphviz)
- Use //! inner doc comments in context/keys.rs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 13:42:54 -04:00
Bryan Helmkamp
addbf0563e Extract fabro-validate crate from fabro-workflows
Move validation/lint framework and all 24 rules into a dedicated
fabro-validate crate. As prerequisites, move Fidelity, stylesheet
parser/types, and condition parser into fabro-graphviz (where the
Graph types they operate on already live) so fabro-validate can
depend on fabro-graphviz directly without a circular dependency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 13:33:34 -04:00
Bryan Helmkamp
25d7c036b3 Extract fabro-daytona crate from fabro-workflows
Move DaytonaSandbox into its own crate, matching the pattern used by
fabro-exe, fabro-sprites, and fabro-ssh. The new crate internalizes
daytona_sdk::Client creation so callers never touch daytona-sdk directly:

- new() is now async and creates the client internally
- reconnect(name) replaces from_existing() + manual client/get boilerplate
- daytona-sdk and daytona-api-client removed as fabro-workflows dependencies

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 12:10:09 -04:00
Bryan Helmkamp
a8b8108087 Extract fabro-retro crate from fabro-workflows
Move retro.rs and retro_agent.rs into a new fabro-retro crate to reduce
the size of fabro-workflows and clarify domain boundaries.

Key design changes:
- Add CompletedStage struct as a flat DTO that decouples retro derivation
  from Checkpoint/Outcome types in the workflow engine
- derive_retro now takes Vec<CompletedStage> (owned) instead of &Checkpoint
- run_retro_agent takes an event_callback closure instead of EventEmitter,
  pushing event filtering to the caller
- Shared build_completed_stages() in fabro-workflows::lib converts
  Checkpoint → Vec<CompletedStage> for both run.rs and server.rs callers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 11:48:40 -04:00
Bryan Helmkamp
e8b3fe96a0 Normalize fabro_graphviz imports to use shorter re-exported paths
Replace fabro_graphviz::graph::types:: with fabro_graphviz::graph::
everywhere, since graph/mod.rs re-exports types::*. Also simplify
the From<GraphvizError> impl.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 11:26:23 -04:00
Bryan Helmkamp
b2bfbd341d Extract fabro-graphviz crate from fabro-workflows
Move the self-contained graph/ and parser/ modules into a new
fabro-graphviz crate so the Graphviz DOT parser can be used without
pulling in the full workflow engine.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 11:20:55 -04:00
Bryan Helmkamp
1570466ef3 Use fabro.sh/discord redirect instead of direct Discord invite URL
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 08:22:12 -04:00
Bryan Helmkamp
d339aeae58 Add fabro discord subcommand to open Discord invite in browser
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 08:18:02 -04:00
Bryan Helmkamp
aca96d14b0 Add fabro docs subcommand to open docs website in browser
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 08:16:46 -04:00
Bryan Helmkamp
459a9c221a Add Codex endpoint support for OpenAI OAuth flow
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>
2026-03-17 00:47:47 -04:00
Bryan Helmkamp
0f565b4a1b Handle OAuth error responses in OpenAI callback server
When the OAuth provider redirects with an error (e.g. invalid_scope),
the callback server now shows a styled error page and propagates the
error to the CLI instead of failing with a deserialization error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 23:53:57 -04:00
brynary-fabro[bot]
ea6cdd2cc4 Add -q/--quiet to fabro ps (#64)
Adds a `-q`/`--quiet` flag to `fabro ps`, mirroring the behavior of
`docker ps -q`. When specified, the command outputs only full run IDs,
one per line, with no headers, footers, or "no runs found"
messages—empty output simply means no matching runs exist.

The quiet flag takes precedence over JSON output, and it composes
naturally with other flags like `-a` (e.g., `fabro ps -qa` lists all run
IDs).

### Fabro Details

<details>
<summary>Ran 10 stages in 10m 13s for $1.09</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $0.17 | 0 |
| simplify_opus | 0s | $0.30 | 0 |
| simplify_gemini | 0s | $0.30 | 0 |
| simplify_gpt | 0s | $0.32 | 0 |
| verify | 0s | – | 0 |
| fmt | 0s | – | 0 |
| **Total** | **10m 13s** | **$1.09** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (13 nodes and 16
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_gemini   [label="Simplify (Gemini)", prompt="@prompts/simplify.md", model="gemini-3.1-pro-preview-customtools"]
    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", goal_gate=true, 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_gemini -> 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>
2026-03-16 22:00:20 -04:00
brynary-fabro[bot]
4915fc5378 Add Sentry panic reporting to fabro CLI (#35)
This PR adds Sentry-based panic reporting to the fabro CLI and improves
the reliability of all background telemetry senders. When the CLI
panics, a Sentry event is serialized to a temp file and uploaded by a
fully detached subprocess, giving visibility into crashes that would
otherwise go unnoticed.

The core infrastructure change is a new `spawn_detached` utility that
uses the double-fork pattern on Unix (fork → setsid → close_fd → fork →
exec) to ensure background subprocesses survive parent exit and terminal
close. This replaces the previous simple `Command::spawn()` approach
used by the analytics sender, which was unreliable since the child could
be killed when the parent exits. Both the analytics sender
(`__send_analytics`) and the new panic sender (`__send_panic`) now share
this `spawn_fabro_subcommand` helper.

The panic module installs a hook early in `main()` that captures panic
info, builds a Sentry event with exception details, stacktrace, and OS
context, then spawns a detached `fabro __send_panic` subprocess to
upload it. It respects the telemetry level setting (no-ops when off),
prevents recursion by setting `FABRO_TELEMETRY=off` in the child,
filters benign "Broken pipe" panics from `| head` usage, and uses a
compile-time `SENTRY_DSN` so dev builds without the DSN set are
unaffected.

### Fabro Details

<details>
<summary>Ran 10 stages in 32m 34s for $8.13</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $2.35 | 0 |
| simplify_opus | 0s | $2.23 | 0 |
| simplify_gemini | 0s | $1.75 | 0 |
| simplify_gpt | 0s | $1.80 | 0 |
| verify | 0s | – | 0 |
| fmt | 0s | – | 0 |
| **Total** | **32m 34s** | **$8.13** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (13 nodes and 16
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_gemini   [label="Simplify (Gemini)", prompt="@prompts/simplify.md", model="gemini-3.1-pro-preview-customtools"]
    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", goal_gate=true, 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_gemini -> 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>
2026-03-16 22:00:11 -04:00
brynary-fabro[bot]
139bf5cbef Move sandbox lifecycle into the engine (#22)
This PR moves sandbox lifecycle management (initialization, setup
commands, devcontainer phases, and cleanup) from the CLI's `run_command`
god function into the workflow engine. Two new engine methods are
introduced: `run_with_lifecycle()` orchestrates sandbox init, fires the
`SandboxReady` hook (now blocking by default), emits a new
`SandboxInitialized` event, handles remote git setup, runs setup
commands and devcontainer lifecycle phases, then delegates to the
existing `run_internal()` graph execution. `cleanup_sandbox()` fires the
`SandboxCleanup` hook and optionally tears down the sandbox. Both
`SandboxReady` and `SandboxCleanup` hook events were previously defined
but never fired — they now fire naturally within the engine alongside
all other hooks.

The CLI is simplified significantly: sandbox record persistence and
progress UI updates are handled via an event listener for
`SandboxInitialized` rather than inline code. The `run_from_branch`
resume path also benefits, gaining hook support and proper cleanup for
free. A new `LifecycleConfig` struct captures setup commands, timeouts,
and devcontainer phases, keeping the engine's API clean. The existing
`run()` method is unchanged, so API server and integration tests
continue working with pre-initialized sandboxes.

The `setup_remote_git` helper is moved from `cli/run.rs` into
`engine.rs` since it only depends on sandbox exec. Config is now passed
by mutable reference to `run_with_lifecycle` so the engine can fill in
remote git fields (base SHA, run branch) that downstream code needs.
Comprehensive tests verify event emission ordering, setup command
execution/failure, and cleanup behavior.

### Fabro Details

<details>
<summary>Ran 10 stages in 53m 27s for $14.42</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $6.22 | 0 |
| simplify_opus | 0s | $2.63 | 0 |
| simplify_gemini | 0s | $2.73 | 0 |
| simplify_gpt | 0s | $2.84 | 0 |
| verify | 0s | – | 0 |
| fmt | 0s | – | 0 |
| **Total** | **53m 27s** | **$14.42** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (13 nodes and 16
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_gemini   [label="Simplify (Gemini)", prompt="@prompts/simplify.md", model="gemini-3.1-pro-preview-customtools"]
    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", goal_gate=true, 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_gemini -> 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-16 22:00:05 -04:00
brynary-fabro[bot]
320e2dbe55 Refactor: Collapse pull_request fields on RunConfig into Option<PullRequestConfig> (#21)
This refactoring collapses four flat `pull_request_*` fields on
`RunConfig` (`pull_request_enabled`, `pull_request_draft`,
`pull_request_auto_merge`, `pull_request_merge_strategy`) into a single
`pull_request: Option<PullRequestConfig>` field. This better represents
the natural tree structure of the configuration: `None` means PR
creation is disabled, while `Some(config)` carries all PR settings
directly.

The PR creation logic in `run.rs` is restructured to use `if let
Some(ref pr_cfg) = config.pull_request` instead of checking a boolean
flag, and construction is simplified to filter out disabled configs at
build time via `.filter(|p| p.enabled).cloned()`. All test constructors
across `engine.rs`, `integration.rs`, `daytona_integration.rs`,
`server.rs`, and `manager_loop.rs` are updated from two lines
(`pull_request_enabled: false, pull_request_draft: false/true`) to a
single `pull_request: None`.

### Fabro Details

<details>
<summary>Ran 10 stages in 20m 32s for $6.62</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $1.71 | 0 |
| simplify_opus | 0s | $1.63 | 0 |
| simplify_gemini | 0s | $1.74 | 0 |
| simplify_gpt | 0s | $1.55 | 0 |
| verify | 0s | – | 0 |
| fmt | 0s | – | 0 |
| **Total** | **20m 32s** | **$6.62** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (13 nodes and 16
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_gemini   [label="Simplify (Gemini)", prompt="@prompts/simplify.md", model="gemini-3.1-pro-preview-customtools"]
    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", goal_gate=true, 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_gemini -> 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-16 21:55:41 -04:00
brynary-fabro[bot]
f60e01e16e Fix: Stage durations always show 0s in PR descriptions (#20)
This PR fixes a bug where stage durations in PR descriptions always
displayed `0s`. The root cause was a key mismatch in
`extract_stage_durations()`: the function was building its HashMap using
`node_label` (human-readable names like `"Preflight Compile"`), but all
three lookup sites were querying by `node_id` (DOT graph identifiers
like `"preflight_compile"`). Every lookup missed and fell back to the
default value of `0`.

The fix changes the HashMap key from `node_label` to `node_id` so that
it matches what the callers actually use for lookups. Additionally, the
existing test is updated so that `node_label` and `node_id` values
differ from each other (e.g., `"plan"` vs `"Plan"`), ensuring the test
would have caught this mismatch before the fix rather than silently
passing due to both fields being identical.

### Fabro Details

<details>
<summary>Ran 10 stages in 7m 46s for $1.28</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $0.21 | 0 |
| simplify_opus | 0s | $0.35 | 0 |
| simplify_gemini | 0s | $0.35 | 0 |
| simplify_gpt | 0s | $0.38 | 0 |
| verify | 0s | – | 0 |
| fmt | 0s | – | 0 |
| **Total** | **7m 46s** | **$1.28** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (13 nodes and 16
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_gemini   [label="Simplify (Gemini)", prompt="@prompts/simplify.md", model="gemini-3.1-pro-preview-customtools"]
    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", goal_gate=true, 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_gemini -> 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>
2026-03-16 21:39:01 -04:00
brynary-fabro[bot]
61915dee65 Wire up missing hook invocations (#19)
This PR wires up three `HookEvent` variants—`StageRetrying`,
`ParallelStart`, and `ParallelComplete`—that were defined in the enum
and documented but never actually invoked by the engine. `StageRetrying`
hooks now fire at both retry sites in `execute_with_retry` (error-retry
and explicit-Retry-status paths) immediately before the backoff sleep.
`ParallelStart` and `ParallelComplete` hooks fire in the parallel
handler after their corresponding event emissions, using a new
`EngineServices::run_hooks()` convenience method since the handler
doesn't have direct access to the engine's hook method.

The two remaining unwired events, `SandboxReady` and `SandboxCleanup`,
are marked as reserved with doc comments on the enum variants and
annotated in the docs table, since wiring them requires sandbox
lifecycle changes outside the engine's scope. A small
`HookContext::set_node()` helper is introduced to reduce repeated field
assignment across all hook call sites, and existing
`StageStart`/`StageComplete` hook calls are refactored to use it.

### Fabro Details

<details>
<summary>Ran 10 stages in 25m 6s for $5.60</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $1.04 | 0 |
| simplify_opus | 0s | $1.51 | 0 |
| simplify_gemini | 0s | $1.44 | 0 |
| simplify_gpt | 0s | $1.62 | 0 |
| verify | 0s | – | 0 |
| fmt | 0s | – | 0 |
| **Total** | **25m 6s** | **$5.60** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (13 nodes and 16
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_gemini   [label="Simplify (Gemini)", prompt="@prompts/simplify.md", model="gemini-3.1-pro-preview-customtools"]
    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", goal_gate=true, 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_gemini -> 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>
2026-03-16 21:38:53 -04:00
brynary-fabro[bot]
f7fb72a472 Style SVG output from fabro graph and API (#18)
This PR styles the SVG output from `fabro graph` and the API graph
endpoints to match the polished look of the documentation SVGs. It
introduces two internal functions in `graph.rs`:
`inject_dot_style_defaults`, which inserts DOT-level defaults
(transparent background, teal `#357f9e` node strokes, gray `#666666`
edges, Helvetica font) after the first `{` in any DOT source, and
`postprocess_svg`, which removes the Graphviz-generated white background
polygon and injects a `<style>` block with `prefers-color-scheme: dark`
media queries for automatic dark mode support.

The `render_dot` function is updated to apply both transformations (DOT
defaults before rendering, SVG post-processing after), and is made `pub`
so the API layer can reuse it. Both `get_graph` in `server.rs` and
`get_run_graph` in `demo/mod.rs` are simplified from ~30 lines of inline
Graphviz process management down to a single call to a shared
`render_dot_svg` helper that delegates to `render_dot` on a blocking
thread.

Six new unit tests validate the styling pipeline: default injection with
and without braces, white background removal, dark mode style insertion,
and the existing SVG integration test is extended to assert styled
output. PNG output is unaffected by the SVG post-processing step.

### Fabro Details

<details>
<summary>Ran 10 stages in 23m 31s for $5.48</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $1.15 | 0 |
| simplify_opus | 0s | $1.55 | 0 |
| simplify_gemini | 0s | $1.65 | 0 |
| simplify_gpt | 0s | $1.13 | 0 |
| verify | 0s | – | 0 |
| fmt | 0s | – | 0 |
| **Total** | **23m 31s** | **$5.48** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (13 nodes and 16
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_gemini   [label="Simplify (Gemini)", prompt="@prompts/simplify.md", model="gemini-3.1-pro-preview-customtools"]
    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", goal_gate=true, 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_gemini -> 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-16 21:38:47 -04:00
Bryan Helmkamp
0ea22dafe8 Handle assistant output replay on stream retries 2026-03-16 15:59:00 -04:00
Bryan Helmkamp
06603fc579 Add turn-level retry for stream-ended-without-Finish errors
When an LLM stream drops mid-response (e.g. under high concurrency with
OpenAI), retry the same turn up to 3 times instead of failing the entire
agent session. Conversation history is preserved across retries.

Previously this killed the whole stage and restarted from scratch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 15:16:35 -04:00
Bryan Helmkamp
8716d235d1 Add auto-merge support for pull requests via GitHub GraphQL API
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>
2026-03-16 08:37:24 -04:00
brynary-fabro[bot]
e3967183c6 Fix: Sub-agent file writes not tracked in API backend (#17)
This PR fixes a bug where files written by sub-agents were missing from
`outcome.files_touched`, causing downstream pipeline nodes like
`simplify_opus` to receive incomplete file lists. The root cause was
that `spawn_event_forwarder` only matched top-level
`ToolCallStarted`/`ToolCallCompleted` events, while sub-agent tool calls
arrived wrapped in `AgentEvent::SubAgentEvent` and fell through to the
`_ => {}` catch-all.

The fix extracts the file-tracking logic into a standalone
`track_file_event` function that recursively unwraps `SubAgentEvent`
layers before matching on the inner tool call events. This handles
arbitrarily nested sub-agent hierarchies (sub-sub-agents, etc.). The
three separate `Arc<Mutex<...>>` fields for pending calls, touched
files, and last file are consolidated into a single `FileTracking`
struct behind one lock, simplifying the forwarder signature and reducing
lock contention.

Four new unit tests verify the behavior: top-level write tracking,
single-level sub-agent unwrapping, double-nested sub-sub-agent
unwrapping, and proper cleanup of pending entries on tool call errors.

### Fabro Details

<details>
<summary>Ran 10 stages in 22m 1s for $4.18</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $0.91 | 0 |
| simplify_opus | 0s | $0.89 | 0 |
| simplify_gemini | 0s | $1.44 | 0 |
| simplify_gpt | 0s | $0.94 | 0 |
| verify | 0s | – | 0 |
| fmt | 0s | – | 0 |
| **Total** | **22m 1s** | **$4.18** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (13 nodes and 16
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_gemini   [label="Simplify (Gemini)", prompt="@prompts/simplify.md", model="gemini-3.1-pro-preview-customtools"]
    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", goal_gate=true, 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_gemini -> 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>
2026-03-15 23:18:40 -04:00
brynary-fabro[bot]
9a48c61d3e Limit command stdout/stderr to last N lines in preamble (#16)
This PR limits command stdout/stderr output in the preamble to the last
N lines, reducing token waste from verbose build progress and download
noise. Command nodes like `cargo check` or `cargo clippy` can produce
300+ lines of output, but the useful content (errors, summaries) is
almost always at the tail. Compact and summary:medium fidelity levels
now keep the last 25 lines, while summary:high keeps the last 50 lines.
Truncated output displays an `(N lines omitted)` indicator consistent
with the existing omission pattern used for stages.

The implementation adds a `tail_lines` helper that skips leading lines
beyond the limit, prepends an omission notice, and applies the
appropriate indentation to each retained line. This is applied only to
inline stdout/stderr rendering—artifact pointer branches and other
fidelity levels (summary:low, truncate, full) are intentionally
untouched. The PR includes unit tests for the `tail_lines` helper itself
as well as integration tests verifying truncation behavior at compact
and summary:high fidelity, and confirming artifact pointers remain
untruncated.

### Fabro Details

<details>
<summary>Ran 10 stages in 18m 4s for $3.45</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $0.84 | 0 |
| simplify_opus | 0s | $0.95 | 0 |
| simplify_gemini | 0s | $0.81 | 0 |
| simplify_gpt | 0s | $0.85 | 0 |
| verify | 0s | – | 0 |
| fmt | 0s | – | 0 |
| **Total** | **18m 4s** | **$3.45** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (13 nodes and 16
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_gemini   [label="Simplify (Gemini)", prompt="@prompts/simplify.md", model="gemini-3.1-pro-preview-customtools"]
    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", goal_gate=true, 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_gemini -> 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>
2026-03-15 23:18:13 -04:00
brynary-fabro[bot]
bd636a16cd Add --direction flag to fabro graph (#15)
This PR adds a `--direction` (`-d`) flag to the `fabro graph` CLI
command, bringing it to parity with the web UI's LR/TB toggle buttons.
Users can now pass `--direction lr` or `--direction tb` to override the
`rankdir` declared in the DOT source before it's handed off to the `dot`
renderer.

The implementation introduces a `GraphDirection` enum (with `ValueEnum`
and `Display` derives for clap integration), an optional `--direction`
field on `GraphArgs`, and an `apply_direction` helper that uses a
lazy-compiled regex to rewrite `rankdir=…` in the DOT source—mirroring
the same approach used by the web UI. The CLI docs are updated with the
new flag, and two unit tests verify that the rewrite works correctly and
that omitting the flag leaves the source unchanged.

### Fabro Details

<details>
<summary>Ran 10 stages in 17m 6s for $3.60</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $0.68 | 0 |
| simplify_opus | 0s | $1.02 | 0 |
| simplify_gemini | 0s | $0.83 | 0 |
| simplify_gpt | 0s | $1.06 | 0 |
| verify | 0s | – | 0 |
| fmt | 0s | – | 0 |
| **Total** | **17m 6s** | **$3.60** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (13 nodes and 16
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 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -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."]
    simplify_opus     [label="Simplify (Opus)", prompt="@prompts/simplify.md"]
    simplify_gemini   [label="Simplify (Gemini)", prompt="@prompts/simplify.md", model="gemini-3.1-pro-preview-customtools"]
    simplify_gpt      [label="Simplify (GPT-54)", prompt="@prompts/simplify.md", model="gpt-54"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 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", goal_gate=true, 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_gemini -> 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>
2026-03-15 23:18:05 -04:00
Bryan Helmkamp
d61f9faf0d Enable pull_request by default in fabro init
The generated fabro.toml now includes an uncommented [pull_request]
section with enabled=true and draft=true, so new projects auto-create
draft PRs on successful workflow runs out of the box.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 21:53:57 -04:00
Bryan Helmkamp
2cd12471f9 Enable auto PR creation and add debug logging for skipped PR cases
The `[pull_request]` config in fabro.toml was missing `enabled = true`,
so workflow runs silently skipped PR creation. Additionally, four skip
paths in the PR creation logic had no logging at all, making it hard to
diagnose why a PR wasn't opened. Added debug-level logs for: config not
enabled, dry-run mode, engine error, and non-success run status.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 21:51:52 -04:00
Bryan Helmkamp
0e0c45d05c Designate retros as experimental, disable by default via [features] flag
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>
2026-03-15 20:36:32 -04:00
Bryan Helmkamp
aee3de78a2 Docs updates: reorder nav, rename DOT Language, simplify dark factory, add fork/upgrade/asset docs
- 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>
2026-03-15 20:13:23 -04:00
brynary-fabro[bot]
374fb1494e Unified dry-run via Handler::simulate() (#14)
This PR introduces a unified dry-run mechanism by adding a
`Handler::simulate()` trait method and a `dispatch_handler()` routing
function that selects between `simulate()` and `execute()` based on
`services.dry_run`. Previously, dry-run behavior was scattered
inconsistently across handlers—`CommandHandler` checked
`services.dry_run` inline, `AgentHandler`/`PromptHandler`/`FanInHandler`
relied on the backend being `None`, and `WaitHandler`/`HumanHandler` had
no dry-run support at all (sleeping or blocking on input for real). This
made dry-run behavior fragile and difficult to extend to new handlers.

The new design adds an `Outcome::simulated(node_id)` factory for
standardized dry-run results, a default `simulate()` implementation on
the `Handler` trait that returns a generic simulated success, and
per-handler overrides where custom context updates are needed.
`CommandHandler` populates empty output/stderr, `AgentHandler` and
`PromptHandler` set simulated
`last_stage`/`last_response`/`response.{id}` context keys,
`FanInHandler` calls `heuristic_select()` without an LLM, `HumanHandler`
auto-selects the first choice, and `ParallelHandler` dispatches child
branches through `dispatch_handler()` while skipping git worktree
operations. The inline `dry_run` check in `CommandHandler::execute()` is
removed, and both call sites in the engine (`execute_with_retry` and
parallel branch dispatch) now route through `dispatch_handler()`.

All existing dry-run tests are updated to test `simulate()` directly,
and new tests verify that `dispatch_handler()` correctly routes based on
the `dry_run` flag and that each handler's `simulate()` produces the
expected context updates and outcome structure.

### Fabro Details

<details>
<summary>Ran 7 stages in 24m 57s for $4.72</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $3.29 | 0 |
| simplify | 0s | $1.42 | 0 |
| verify | 0s | – | 0 |
| **Total** | **24m 57s** | **$4.72** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (10 nodes and 13
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 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -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."]
    simplify          [label="Simplify", prompt="@prompts/simplify.md"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 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]

    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 -> verify
    verify -> exit  [condition="outcome=success"]
    verify -> fixup
    fixup -> verify
}

```

</details>

⚒️ Generated with [Fabro](https://fabro.sh)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Fabro Assistant <assistant@fabro.dev>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 19:55:03 -04:00
brynary-fabro[bot]
dc34fb0671 fabro upgrade command (#13)
This PR adds a `fabro upgrade` command that downloads and installs new
releases from GitHub, along with a passive daily auto-check that
notifies users when a newer version is available. The upgrade flow
supports two download backends: the `gh` CLI (preferred, for auth and
rate-limit benefits) with an automatic fallback to plain HTTPS via
`reqwest` when `gh` is missing or not authenticated. The command
includes SHA256 checksum verification, atomic binary replacement with
rollback on failure, downgrade protection with interactive confirmation,
and `--dry-run`/`--force` flags.

A background upgrade check runs automatically on common commands (`run`,
`exec`, `init`, `install`), caching results in
`~/.fabro/last_upgrade_check.json` to avoid hitting GitHub more than
once per 24 hours. Users can disable this via `upgrade_check = false` in
`~/.fabro/cli.toml` or the `--no-upgrade-check` global flag. The check
is spawned as an async task and its notice prints to stderr after the
main command completes, ensuring it never blocks or breaks normal
operation—all errors are silently swallowed.

The implementation follows a test-first approach with unit tests
covering platform detection, version parsing, SHA256 verification,
upgrade check state serialization/staleness, and the new `upgrade_check`
config field. Dependencies `tempfile` (promoted from dev-dependencies)
and `sha2` are added to `fabro-cli`.

### Fabro Details

<details>
<summary>Ran 7 stages in 18m 39s for $5.61</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $2.92 | 0 |
| simplify | 0s | $2.68 | 0 |
| verify | 0s | – | 0 |
| **Total** | **18m 39s** | **$5.61** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (10 nodes and 13
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 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -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."]
    simplify          [label="Simplify", prompt="@prompts/simplify.md"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 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]

    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 -> verify
    verify -> exit  [condition="outcome=success"]
    verify -> fixup
    fixup -> verify
}

```

</details>

⚒️ Generated with [Fabro](https://fabro.sh)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
2026-03-15 19:54:57 -04:00
brynary-fabro[bot]
f9d0ebaf33 Random Edge Selection (#12)
This PR introduces a `selection="random"` node attribute that enables
weighted-random tiebreaking when choosing among candidate outgoing
edges. The existing deterministic behavior (highest weight, then lexical
node ID) remains the default. The cascade priority—conditions →
preferred label → suggested next → unconditional → fallback—is
unchanged; randomness only replaces the final pick-one-from-candidates
step within each tier. A new `weighted_random` function handles the
sampling, treating edges with weight ≤ 0 as weight 1, while a
`pick_edge` dispatcher routes to either the random or deterministic
strategy based on the node's `selection()` accessor.

A validation rule (`RandomSelectionNoConditionsRule`) rejects nodes that
combine `selection="random"` with conditional edges, since condition
evaluation order would conflict with random selection. A companion rule
(`SelectionValidRule`) warns on unrecognized selection values. Both are
registered as built-in lint rules with appropriate error/warning
severities and actionable fix suggestions.

Documentation is updated in the transitions guide with a new "Random
selection" section explaining the behavior and constraints, and the DOT
language reference gains a `selection` row in the node attributes table.
All changes were developed following red/green TDD cycles with
comprehensive test coverage for the accessor, weighted random sampling,
edge selection integration, and both validation rules.

### Fabro Details

<details>
<summary>Ran 7 stages in 17m 1s for $4.07</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $2.43 | 0 |
| simplify | 0s | $1.63 | 0 |
| verify | 0s | – | 0 |
| **Total** | **17m 1s** | **$4.07** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (10 nodes and 13
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 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -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."]
    simplify          [label="Simplify", prompt="@prompts/simplify.md"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 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]

    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 -> verify
    verify -> exit  [condition="outcome=success"]
    verify -> fixup
    fixup -> verify
}

```

</details>

⚒️ Generated with [Fabro](https://fabro.sh)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
2026-03-15 19:54:48 -04:00
brynary-fabro[bot]
a7faf32e01 Rename [feature_flags] to [features] (#11)
This PR renames the `[feature_flags]` configuration section to
`[features]` and the corresponding `FeatureFlags` type to `Features`
across the entire stack. The change touches the OpenAPI spec (source of
truth), Rust config/API crates, the generated TypeScript API client, the
web application, and the demo config file to ensure consistency.

On the Rust side, the `FeatureFlags` struct becomes `Features` in
`fabro-config`, and all field references (`config.feature_flags` →
`config.features`) are updated in `fabro-api` demo/test code along with
the relevant unit tests. On the TypeScript side, the generated client
reflects the OpenAPI rename (`feature-flags.ts` → `features.ts`,
`FeatureFlags` → `Features`), and manual edits in `fabro-web` update the
`AppConfig` interface, defaults constant (`FEATURES_DEFAULTS`), loader
data destructuring, and template usage throughout `config.server.ts`,
`app-shell.tsx`, and `start.tsx`.

The regeneration also picked up an unrelated new `GitHubConfiguration`
schema that was already present in the OpenAPI spec but hadn't been
generated yet, resulting in the new `git-hub-configuration.ts` file and
its addition to the server configuration type.

### Fabro Details

<details>
<summary>Ran 7 stages in 15m 4s for $3.08</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $2.18 | 0 |
| simplify | 0s | $0.90 | 0 |
| verify | 0s | – | 0 |
| **Total** | **15m 4s** | **$3.08** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (10 nodes and 13
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 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -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."]
    simplify          [label="Simplify", prompt="@prompts/simplify.md"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 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]

    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 -> verify
    verify -> exit  [condition="outcome=success"]
    verify -> fixup
    fixup -> verify
}

```

</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-15 19:54:40 -04:00
brynary-fabro[bot]
b3f83264dc fabro asset CLI subcommands (#10)
This PR adds `fabro asset list` and `fabro asset cp` CLI subcommands for
inspecting and copying run artifacts (screenshots, test reports, traces)
that are collected to `~/.fabro/runs/{id}/artifacts/assets/`.
Previously, users had to manually browse the filesystem to find these
files.

The core implementation lives in a new `asset.rs` module that provides
`scan_assets()`, which walks the asset manifest files under each node's
retry directories to build structured `AssetEntry` records. The `list`
subcommand displays a formatted table (or JSON with `--json`) showing
node, retry number, size, and path for each asset. The `cp` subcommand
supports copying all assets or a specific file (via `RUN_ID:path`
syntax), with a `--tree` flag to preserve the `{node}/retry_{N}/`
directory structure and collision detection in flat mode.

The implementation reuses existing utilities throughout:
`split_run_path()` for parsing the colon-separated source syntax (made
`pub(crate)`), `resolve_run()` for run ID prefix matching,
`format_size()` for human-readable byte formatting (also made
`pub(crate)`), and `AssetCollectionSummary` for manifest
deserialization.

### Fabro Details

<details>
<summary>Ran 7 stages in 12m 31s for $3.61</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $1.33 | 0 |
| simplify | 0s | $2.28 | 0 |
| verify | 0s | – | 0 |
| **Total** | **12m 31s** | **$3.61** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (10 nodes and 13
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 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -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."]
    simplify          [label="Simplify", prompt="@prompts/simplify.md"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 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]

    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 -> verify
    verify -> exit  [condition="outcome=success"]
    verify -> fixup
    fixup -> verify
}

```

</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-15 19:54:31 -04:00
brynary-fabro[bot]
d6f1bce7ef fabro fork subcommand (#9)
This PR adds a new `fabro fork` subcommand that creates a new run
branching from an existing run at a specific checkpoint, without
modifying the original run. This is a non-destructive alternative to
`fabro rewind` — instead of moving branch refs backward and losing later
checkpoint history, fork preserves the source run entirely and creates
fresh run and metadata branches for the new run.

The implementation heavily reuses existing infrastructure from
`rewind.rs` (timeline building, target resolution, parallel map loading,
prefix-based run ID lookup) and follows the same CLI patterns. The core
`execute_fork` function generates a new ULID, creates a run branch ref
pointing at the target checkpoint's commit, then builds a new metadata
branch containing an updated manifest (with new run ID and branch name),
the original graph, and the checkpoint state from the target commit. It
supports the same target syntax as rewind (`@N`, `node_name`,
`node_name@N`), defaults to the latest checkpoint when no target is
specified, and optionally pushes new branches to the remote.

The PR also makes `load_parallel_map` public in `rewind.rs` so fork can
reuse it, and includes five tests covering run branch creation, metadata
branch correctness, preservation of the original run, default-to-latest
behavior, and forking at a specific ordinal.

### Fabro Details

<details>
<summary>Ran 7 stages in 15m 15s for $4.39</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $1.94 | 0 |
| simplify | 0s | $2.44 | 0 |
| verify | 0s | – | 0 |
| **Total** | **15m 15s** | **$4.39** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (10 nodes and 13
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 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -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."]
    simplify          [label="Simplify", prompt="@prompts/simplify.md"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 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]

    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 -> verify
    verify -> exit  [condition="outcome=success"]
    verify -> fixup
    fixup -> verify
}

```

</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-15 19:54:24 -04:00
brynary-fabro[bot]
c687c29426 Add safeguards to asset collection and checkpoint commits (#8)
This PR adds safeguards to asset collection and checkpoint commits to
prevent collecting or committing excessive files from large, untracked
directories like Python virtual environments, build outputs, and tool
caches.

Specifically, it introduces a `MAX_FILE_COUNT` limit of 100 files in
`select_files_to_collect()` to cap asset collection regardless of total
size budget, expands the `EXCLUDE_DIRS` list with seven new entries
(`.venv`, `venv`, `.cache`, `.tox`, `.pytest_cache`, `.mypy_cache`,
`dist`) to match common project directory patterns that can contain
thousands of generated files, and makes the constant public for reuse.
Notably, `build` and `env`/`.env` were intentionally omitted as too
generic or potentially conflicting with legitimate project files.

The checkpoint commit logic in `git_checkpoint()` is updated to always
apply the built-in `EXCLUDE_DIRS` as git pathspec excludes (converted to
`**/dirname/**` glob format), merged with any user-configured exclude
globs. This ensures that even with no user configuration, checkpoint
`git add -A` commands won't inadvertently stage virtual environments,
caches, or build artifacts. All changes are covered by new tests
following red/green TDD.

### Fabro Details

<details>
<summary>Ran 7 stages in 10m 34s for $2.41</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $1.21 | 0 |
| simplify | 0s | $1.19 | 0 |
| verify | 0s | – | 0 |
| **Total** | **10m 34s** | **$2.41** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (10 nodes and 13
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 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -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."]
    simplify          [label="Simplify", prompt="@prompts/simplify.md"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 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]

    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 -> verify
    verify -> exit  [condition="outcome=success"]
    verify -> fixup
    fixup -> verify
}

```

</details>

⚒️ Generated with [Fabro](https://fabro.sh)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
2026-03-15 19:54:17 -04:00
Bryan Helmkamp
b7b30ebce2 Fix fabro-beastie linker error by linking IOKit framework
The extern block declaring IOPMAssertionCreateWithName and
IOPMAssertionRelease was missing the #[link] attribute, causing
undefined symbol errors on macOS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 18:27:42 -04:00
arc-1e68f1[bot]
508a865183 Inject GitHub App IAT into Sandbox as GITHUB_TOKEN (#7)
This PR adds GitHub App Installation Access Token (IAT) injection into
sandboxes, allowing `gh` CLI and other GitHub-authenticated tools to
work seamlessly inside workflow sandboxes. Workflow authors can declare
required GitHub permissions in `workflow.toml` under a `[github]`
section (e.g., `permissions = { contents = "write", pull_requests =
"read" }`), with project-wide defaults available in `fabro.toml`.
Workflow-level config fully replaces project-level defaults, consistent
with existing `[pull_request]` behavior.

The implementation introduces a `GitHubConfig` struct wired through
`WorkflowRunConfig`, `RunDefaults`, and `ProjectConfig`, with proper
`apply_defaults` (inherit if unset) and `merge_overlay` (replace if
present) semantics. At runtime, a new `mint_github_token()` helper signs
a JWT, resolves the repo's owner/repo from the origin URL, and requests
a scoped IAT which is injected as `GITHUB_TOKEN` into the sandbox
environment. The previously private
`create_installation_access_token_with_permissions` in `fabro-github` is
made public to support this. A preflight check also mints a token during
validation to surface credential or permission issues early.

Comprehensive tests cover TOML parsing with and without `[github]`,
default inheritance, workflow-over-default precedence, and overlay merge
semantics for `RunDefaults`.

### Fabro Details

<details>
<summary>Ran 7 stages in 27m 15s for $5.88</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $3.79 | 0 |
| simplify | 0s | $2.09 | 0 |
| verify | 0s | – | 0 |
| **Total** | **27m 15s** | **$5.88** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (10 nodes and 13
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 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -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."]
    simplify          [label="Simplify", prompt="@prompts/simplify.md"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 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]

    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 -> verify
    verify -> exit  [condition="outcome=success"]
    verify -> fixup
    fixup -> verify
}

```

</details>

⚒️ Generated with [Fabro](https://fabro.sh)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Claude <claude@anthropic.com>
2026-03-15 18:24:30 -04:00
arc-1e68f1[bot]
70ea9f458d Add goal to WorkflowRunStarted and render in fabro logs --pretty (#6)
This PR adds a `goal` field to the `WorkflowRunStarted` event so that
users can immediately see what a workflow is trying to accomplish when
reading logs. The field is an `Option<String>` with `serde(default,
skip_serializing_if)` to maintain backward compatibility with existing
JSONL logs that don't include it—mirroring the same pattern used by
`base_sha` and `run_branch`.

On the rendering side, `fabro logs --pretty` now displays the goal below
the workflow header line when present, using markdown rendering with
proper indentation and terminal-width wrapping. The markdown rendering
logic was extracted into a shared `render_indented_markdown` helper,
which is also now used by the existing `AssistantMessage` rendering to
eliminate duplication.

Tests cover round-trip serialization with a goal, backward-compatible
deserialization of old events without the field, verification that
`None` goals are omitted from JSON output, and pretty-formatting
behavior both with and without a goal present.

### Fabro Details

<details>
<summary>Ran 7 stages in 23m 38s for $3.02</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $1.76 | 0 |
| simplify | 0s | $1.26 | 0 |
| verify | 0s | – | 0 |
| **Total** | **23m 38s** | **$3.02** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (10 nodes and 13
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 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -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."]
    simplify          [label="Simplify", prompt="@prompts/simplify.md"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 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]

    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 -> verify
    verify -> exit  [condition="outcome=success"]
    verify -> fixup
    fixup -> verify
}

```

</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-15 17:46:17 -04:00
arc-1e68f1[bot]
a199c9cbd1 Plan: Add prevent_idle_sleep to fabro via fabro-beastie (#5)
This PR introduces `fabro-beastie`, a new cross-platform idle sleep
prevention crate (named after Beastie Boys — *No Sleep Till Brooklyn*),
and wires it into `fabro-cli` behind an opt-in `sleep_inhibitor` Cargo
feature. Long-running `fabro run` and `fabro exec` commands can be
killed by OS idle sleep, so when the feature is compiled in and
`prevent_idle_sleep = true` is set in `cli.toml`, an RAII guard keeps
the system awake for the duration of the command.

The `fabro-beastie` crate provides platform-specific backends: on macOS
it uses IOKit power assertions (`PreventUserIdleSystemSleep`), on Linux
it spawns `systemd-inhibit` (with `gnome-session-inhibit` as fallback)
and sets `PR_SET_PDEATHSIG` to prevent orphaned processes. Both fall
back to a no-op dummy backend if the platform backend is unavailable.
The public API is a single `guard(bool)` function returning an
`Option<SleepInhibitorGuard>` that releases on drop.

On the integration side, a `prevent_idle_sleep` boolean field is added
to `CliConfig` (defaulting to `false`), and `cfg`-guarded sleep guards
are placed at the entry points of both the `exec` and `run` command
paths in `fabro-cli`. Since the feature is off by default, there is zero
impact on normal builds — `fabro-beastie` is only pulled in when
explicitly enabled via `--features fabro-cli/sleep_inhibitor`.

### Fabro Details

<details>
<summary>Ran 7 stages in 20m 33s for $3.24</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $1.26 | 1 |
| simplify | 0s | $1.98 | 0 |
| verify | 0s | – | 0 |
| **Total** | **20m 33s** | **$3.24** | **1** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (10 nodes and 13
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 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -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."]
    simplify          [label="Simplify", prompt="@prompts/simplify.md"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 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]

    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 -> verify
    verify -> exit  [condition="outcome=success"]
    verify -> fixup
    fixup -> verify
}

```

</details>

⚒️ Generated with [Fabro](https://fabro.sh)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
2026-03-15 17:41:20 -04:00
arc-1e68f1[bot]
06b66781d1 Rename metadata branch from refs/fabro/{run_id} to fabro/meta/{run_id} (#3)
* fabro(01KKS6PG9929P116A2RRKXC738): toolchain (success)

Fabro-Run: 01KKS6PG9929P116A2RRKXC738
Fabro-Completed: 2
Fabro-Checkpoint: a274eab045

⚒️ Generated with [Fabro](https://fabro.sh)

* fabro(01KKS6PG9929P116A2RRKXC738): preflight_compile (success)

Fabro-Run: 01KKS6PG9929P116A2RRKXC738
Fabro-Completed: 3
Fabro-Checkpoint: 8ce709ecdb

⚒️ Generated with [Fabro](https://fabro.sh)

* fabro(01KKS6PG9929P116A2RRKXC738): preflight_lint (success)

Fabro-Run: 01KKS6PG9929P116A2RRKXC738
Fabro-Completed: 4
Fabro-Checkpoint: 4f4c237214

⚒️ Generated with [Fabro](https://fabro.sh)

* fabro(01KKS6PG9929P116A2RRKXC738): implement (success)

Fabro-Run: 01KKS6PG9929P116A2RRKXC738
Fabro-Completed: 5
Fabro-Checkpoint: baf4d48a0f

⚒️ Generated with [Fabro](https://fabro.sh)

* fabro(01KKS6PG9929P116A2RRKXC738): simplify (success)

Fabro-Run: 01KKS6PG9929P116A2RRKXC738
Fabro-Completed: 6
Fabro-Checkpoint: ddc0967c0b

⚒️ Generated with [Fabro](https://fabro.sh)

* fabro(01KKS6PG9929P116A2RRKXC738): verify (success)

Fabro-Run: 01KKS6PG9929P116A2RRKXC738
Fabro-Completed: 7
Fabro-Checkpoint: 2249924bb2

⚒️ 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-15 17:40:51 -04:00
Bryan Helmkamp
45bd49bedc Use temp dir for dry-run instead of ~/.fabro/runs to avoid clutter in fabro ps -a
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 17:27:10 -04:00