Commit graph

18 commits

Author SHA1 Message Date
Bryan Helmkamp
e2d1ad2144
Clean up workspace clippy warnings 2026-03-30 11:27:25 -04:00
Bryan Helmkamp
e304dcca37
Use typed RunId across workflows 2026-03-30 09:20:11 -04:00
Bryan Helmkamp
392add851a
Add publish = false to all crates to prevent accidental crates.io publish
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 13:47:09 -04:00
Bryan Helmkamp
1b9a6285e4
Replace mockito with httpmock in fabro-hooks, fabro-openai-oauth, and fabro-tracker
mockito's Server::new_async() triggers macOS SCDynamicStoreCreateWithOptions
via hyper-util (~300ms per test), which serializes on configd under workspace
concurrency and causes 4s+ timeouts. httpmock avoids this path entirely.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 13:47:09 -04:00
Bryan Helmkamp
2708e2eb54
Enable 7 additional pedantic clippy lints
Enables char_lit_as_u8, collapsible_else_if, collapsible_if,
map_unwrap_or, match_same_arms, used_underscore_binding, and
if_not_else. Fixes all violations: combines duplicate match arms,
renames underscore-prefixed bindings that are actually used, rewrites
if-not-else patterns, and applies map_or where appropriate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 13:47:08 -04:00
Bryan Helmkamp
2f4fc23f3d
Enable additional pedantic clippy lints and fix violations
Enables cast_possible_truncation, cast_sign_loss, items_after_statements,
needless_pass_by_value, return_self_not_must_use, uninlined_format_args,
unreadable_literal, and unnested_or_patterns. Keeps doc_markdown disabled.

Replaces unsafe `as` casts with try_from().unwrap() throughout, using
#[allow] only for f64-to-integer casts which have no try_from equivalent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 13:47:08 -04:00
Bryan Helmkamp
1dc0ebce52
Enable clippy pedantic lints and restriction lints workspace-wide
Adopts uv's clippy lint configuration: pedantic group at warn priority,
with noisy lints allowed, plus restriction lints for print/dbg/exit/use_self.
Fixes all violations across the workspace.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 13:47:08 -04:00
Bryan Helmkamp
5c215033c1
Apply rustfmt 2024 style edition across workspace
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 13:47:08 -04:00
Bryan Helmkamp
891957e29b
Enforce no-inline-qualified-paths via clippy absolute_paths lint
Add clippy.toml with absolute-paths-max-segments = 2 (allowing std/core/alloc)
and enable the absolute_paths = "warn" lint workspace-wide. Fix all ~300
violations across the codebase: replace 3+-segment inline paths with use
statements so call sites read as operations::create() rather than
fabro_workflows::operations::create(). The demo module gets an allow
attribute since it constructs many API types by design.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 13:47:08 -04:00
Bryan Helmkamp
50041fb261
Enforce no-wildcard-imports via clippy workspace lint
Configure clippy `wildcard_imports = "warn"` at the workspace level and
opt all 28 crates in via `[lints] workspace = true`. Fix the three
production glob imports that triggered warnings: fabro-sandbox
read_guard, fabro-cli main, and fabro-api demo module (allowed via
attribute since it constructs many API types by design). Document the
import style convention in CLAUDE.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 13:47:08 -04:00
Bryan Helmkamp
b032ae32b4
Create fabro-types and rewire shared types 2026-03-29 13:47:08 -04:00
Bryan Helmkamp
d9b00ec8bc
Redesign fabro-model with LanguageModel trait, Catalog struct, and ModelRef (#121)
Introduce OOP API for the model catalog: LanguageModel trait with blanket
impl on ModelInfo, Catalog struct with typed methods (get, list,
default_for_provider, closest, build_fallback_chain, etc.), ModelRef enum
replacing ModelId, and Provider::OpenAiCompatible variant. Migrate all
callers across the workspace to use Catalog::builtin() and remove the old
free-function API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 11:15:40 -04:00
brynary-fabro[bot]
a0fbbcfe3b
Support Anthropic fast mode (speed: fast) (#127)
This PR adds support for Anthropic's fast mode beta feature (`speed:
fast`), which enables up to 2.5x faster output for Claude Opus 4.6 at a
6x pricing premium. The implementation follows the same patterns
established by `reasoning_effort`, threading a new `speed:
Option<String>` field through the request/response pipeline from graph
stylesheet properties down through agent configuration, session
management, and the Anthropic provider adapter.

On the provider side, when `speed: "fast"` is set, the `ApiRequest`
struct now includes the `speed` field in the serialized JSON body, and
the `build_beta_header` function injects the required `anthropic-beta:
fast-mode-2026-02-01` header alongside any existing beta headers (cache,
interleaved thinking, etc.) without duplication. The response's
`usage.speed` field is parsed and propagated back through both streaming
and non-streaming paths into `StageUsage` and `Usage` types for
tracking.

Cost accounting applies a 6x multiplier in `compute_stage_cost` when
`speed == "fast"`, reflecting Anthropic's actual pricing differential.
The feature is configurable via stylesheet (`* { speed: fast; }`), which
gets wired through `SessionConfig` and prompt-mode `Request`
construction in the backend layer. New tests cover the API request
serialization, beta header injection, combined cache+fast-mode headers,
and the cost multiplier, while all existing test fixtures have been
updated with `speed: None` to maintain struct exhaustiveness.

### Fabro Details

<details>
<summary>Ran 9 stages in 58m 27s for $6.56</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 1m 20s | – | 0 |
| preflight_lint | 14s | – | 0 |
| implement | 47m 31s | $5.36 | 0 |
| simplify_opus | 7m 59s | $1.20 | 0 |
| simplify_gpt | 0s | – | 0 |
| verify | 25s | – | 0 |
| fmt | 1s | – | 0 |
| **Total** | **58m 27s** | **$6.56** | **0** |

</details>

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

```dot
digraph ImplementAndSimplify {
    graph [
        goal="Implement and simplify",
        model_stylesheet="
            * { backend: api; model: claude-opus-4-6;}
        "
    ]
    rankdir=LR

    start [shape=Mdiamond, label="Start"]
    exit  [shape=Msquare, label="Exit"]

    toolchain         [label="Toolchain", shape=parallelogram, script="command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", max_retries=0]
    preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check -q --workspace 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -q --workspace -- -D warnings 2>&1", max_retries=0]
    fix_lints         [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
    implement         [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD."]
    simplify_opus     [label="Simplify (Opus)", prompt="@prompts/simplify.md"]
    simplify_gpt      [label="Simplify (GPT-54)", prompt="@prompts/simplify.md", model="gpt-54"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -q --workspace -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 2>&1", goal_gate=true, retry_target="fixup"]
    fixup             [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all clippy lint warnings and test failures.", max_visits=3]
    fmt               [label="Format", shape=parallelogram, script="cargo fmt --all 2>&1", max_retries=0]

    start -> toolchain
    toolchain -> preflight_compile [condition="outcome=success"]
    toolchain -> exit
    preflight_compile -> preflight_lint [condition="outcome=success"]
    preflight_compile -> exit
    preflight_lint -> implement [condition="outcome=success"]
    preflight_lint -> fix_lints
    fix_lints -> preflight_lint
    implement -> simplify_opus -> simplify_gpt -> verify
    verify -> fmt   [condition="outcome=success"]
    verify -> fixup
    fixup -> verify
    fmt -> exit
}

```

</details>

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

---------

Co-authored-by: Fabro <noreply@fabro.sh>
2026-03-21 14:37:42 -04:00
brynary-fabro[bot]
62c310bcec
Extract fabro-model crate from fabro-llm (#109)
This PR extracts a new `fabro-model` crate from `fabro-llm`, isolating
the model catalog, provider identity types, and model metadata into a
focused, lightweight package. The new crate contains `Provider`,
`ModelId`, `ModelInfo` (and related structs), and the full catalog API
(`get_model_info`, `list_models`, `default_model_from_env`,
`build_fallback_chain`, etc.) — with only `serde` and `serde_json` as
runtime dependencies, no async runtime required.

The extraction follows a clean boundary: `fabro-llm` now depends on
`fabro-model` and re-exports `Provider` and `ModelId` at its root so
existing `fabro_llm::Provider` usage remains valid without a migration.
The `ProviderAdapter` trait, `StreamEventStream`, and
`validate_tool_choice` stay in `fabro-llm` since they depend on async
streaming types. All seven consumer crates (`fabro-agent`, `fabro-api`,
`fabro-cli`, `fabro-hooks`, `fabro-workflows`, `fabro-validate`, and
`fabro-llm` itself) are updated to import catalog functions and provider
types directly from `fabro_model` rather than `fabro_llm::catalog` or
`fabro_llm::provider`.

The most impactful dependency reduction is in `fabro-validate`, which
previously pulled in all of `fabro-llm` (tokio, reqwest, the full async
runtime) just to perform catalog lookups and validate provider names. It
now depends solely on `fabro-model`, eliminating that heavyweight
transitive closure. All other consumers retain their `fabro-llm`
dependency for client and streaming functionality, but gain a precise
import path for catalog and identity concerns.

### Fabro Details

<details>
<summary>Ran 9 stages in 21m 16s for $5.47</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 1m 10s | – | 0 |
| preflight_lint | 12s | – | 0 |
| implement | 13m 29s | $3.81 | 1 |
| simplify_opus | 5m 41s | $1.66 | 0 |
| simplify_gpt | 0s | – | 0 |
| verify | 15s | – | 0 |
| fmt | 1s | – | 0 |
| **Total** | **21m 16s** | **$5.47** | **1** |

</details>

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

```dot
digraph ImplementAndSimplify {
    graph [
        goal="Implement and simplify",
        model_stylesheet="
            * { backend: api; model: claude-opus-4-6;}
        "
    ]
    rankdir=LR

    start [shape=Mdiamond, label="Start"]
    exit  [shape=Msquare, label="Exit"]

    toolchain         [label="Toolchain", shape=parallelogram, script="command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", max_retries=0]
    preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check -q --workspace 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -q --workspace -- -D warnings 2>&1", max_retries=0]
    fix_lints         [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
    implement         [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD."]
    simplify_opus     [label="Simplify (Opus)", prompt="@prompts/simplify.md"]
    simplify_gpt      [label="Simplify (GPT-54)", prompt="@prompts/simplify.md", model="gpt-54"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -q --workspace -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 2>&1", goal_gate=true, retry_target="fixup"]
    fixup             [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all clippy lint warnings and test failures.", max_visits=3]
    fmt               [label="Format", shape=parallelogram, script="cargo fmt --all 2>&1", max_retries=0]

    start -> toolchain
    toolchain -> preflight_compile [condition="outcome=success"]
    toolchain -> exit
    preflight_compile -> preflight_lint [condition="outcome=success"]
    preflight_compile -> exit
    preflight_lint -> implement [condition="outcome=success"]
    preflight_lint -> fix_lints
    fix_lints -> preflight_lint
    implement -> simplify_opus -> simplify_gpt -> verify
    verify -> fmt   [condition="outcome=success"]
    verify -> fixup
    fixup -> verify
    fmt -> exit
}

```

</details>

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

---------

Co-authored-by: Fabro <noreply@fabro.sh>
2026-03-19 22:26:47 -04:00
Bryan Helmkamp
2bbc0d3ef9
Fix: Remove std::env::set_var/remove_var from tests (#101)
Introduce an Env trait in fabro-util so tests can inject a HashMap-backed
TestEnv instead of mutating process-global environment variables, which
is unsafe since Rust 1.66+ and causes flakiness in concurrent tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 13:09:46 -04:00
Bryan Helmkamp
24e8e0a74b
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
165b817250
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
b5e1d1fa74
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