diff --git a/graph.fabro b/graph.fabro new file mode 100644 index 000000000..c4f77165a --- /dev/null +++ b/graph.fabro @@ -0,0 +1,37 @@ +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 +} diff --git a/manifest.json b/manifest.json new file mode 100644 index 000000000..ce630ee12 --- /dev/null +++ b/manifest.json @@ -0,0 +1,13 @@ +{ + "run_id": "01KM4A3R67NG11AN7XCED06N2Z", + "workflow_name": "ImplementAndSimplify", + "goal": "# Extract `fabro-model` crate from `fabro-llm`\n\n## Context\n\nThe model catalog (provider identity, model metadata, alias resolution, fallback chains) is currently embedded inside `fabro-llm`, a heavyweight crate that pulls in tokio, reqwest, and many async runtime dependencies. Seven crates depend on `fabro-llm`, but several only need catalog lookups — not the LLM client. Extracting a focused `fabro-model` crate gives a clean dependency boundary: crates that only need \"what models exist?\" no longer pull in the entire LLM runtime.\n\n## Public API of `fabro-model`\n\nAll items re-exported at the crate root for flat access (`fabro_model::get_model_info()`):\n\n```rust\n// Types\npub use types::{ModelInfo, ModelLimits, ModelFeatures, ModelCosts};\n\n// Provider identity\npub use provider::{Provider, ModelId};\n\n// Catalog lookups\npub use catalog::{\n get_model_info, list_models, default_model, default_model_for_provider,\n default_model_from_env, probe_model_for_provider, closest_model,\n build_fallback_chain, FallbackTarget,\n};\n```\n\nNo `Catalog` struct — the catalog is static embedded data with no configuration or lifecycle. Free functions are the right abstraction. The crate name itself is the namespace.\n\n## Key design decisions\n\n1. **No re-export shim in `fabro-llm`** — update all consumers directly. Exception: `fabro-llm` re-exports `Provider` and `ModelId` so `fabro_llm::Provider` stays valid (it's a type alias, not a shim module).\n2. **Provider moves entirely** — `Provider` enum, `ModelId`, and all `Provider` methods (ALL, as_str, from_str, api_key_env_vars, has_api_key, default_from_env). Only `ProviderAdapter` trait, `validate_tool_choice()`, and `StreamEventStream` stay in `fabro-llm::provider`.\n3. **`fabro-validate` drops `fabro-llm`** — it only uses catalog + Provider, so it can depend solely on `fabro-model`.\n\n## Steps\n\n### 1. Create `lib/crates/fabro-model/` crate\n\n**`Cargo.toml`**:\n```toml\n[package]\nname = \"fabro-model\"\nedition.workspace = true\nversion.workspace = true\nlicense.workspace = true\ndescription = \"LLM model catalog: provider identity, model metadata, and resolution\"\n\n[lib]\ndoctest = false\n\n[dependencies]\nserde.workspace = true\nserde_json.workspace = true\n\n[dev-dependencies]\ninsta.workspace = true\n```\n\n### 2. Move model types → `fabro-model/src/types.rs`\n\nExtract from `fabro-llm/src/types.rs` (lines 639-675):\n- `ModelInfo`, `ModelLimits`, `ModelFeatures`, `ModelCosts`\n\nRemove these 4 structs from `fabro-llm/src/types.rs`.\n\n### 3. Move Provider + ModelId → `fabro-model/src/provider.rs`\n\nExtract from `fabro-llm/src/provider.rs`:\n- `Provider` enum + all impl blocks (lines 14-95)\n- `Display`, `FromStr` impls (lines 97-118)\n- `ModelId` struct + impls (lines 126-146)\n- All tests for these items (lines 208-350)\n\nWhat stays in `fabro-llm/src/provider.rs`:\n- `ProviderAdapter` trait (lines 157-181)\n- `StreamEventStream` type alias (line 153)\n- `validate_tool_choice()` (lines 192-206)\n- Tests for ProviderAdapter/validate_tool_choice (lines 351-418)\n- Add `use fabro_model::Provider;` import at top\n\n### 4. Move catalog → `fabro-model/src/catalog.rs` + `catalog.json`\n\nMove both files verbatim. Internal `crate::` paths remain valid since Provider and ModelInfo are in the same crate now.\n\n### 5. Write `fabro-model/src/lib.rs`\n\n```rust\npub mod catalog;\npub mod provider;\npub mod types;\n\npub use catalog::{\n build_fallback_chain, closest_model, default_model, default_model_for_provider,\n default_model_from_env, get_model_info, list_models, probe_model_for_provider,\n FallbackTarget,\n};\npub use provider::{ModelId, Provider};\npub use types::{ModelCosts, ModelFeatures, ModelInfo, ModelLimits};\n```\n\n### 6. Update `fabro-llm`\n\n- Add `fabro-model = { path = \"../fabro-model\" }` to `Cargo.toml`\n- Remove `pub mod catalog;` from `lib.rs`\n- Change `pub use provider::{ModelId, Provider};` → `pub use fabro_model::{ModelId, Provider};`\n- `cli.rs`: change `use crate::catalog` → `use fabro_model as catalog`, split `use crate::types::{Message, ModelInfo}` so `ModelInfo` comes from `fabro_model`\n- `client.rs`: change `crate::catalog::get_model_info` → `fabro_model::get_model_info`\n- `providers/anthropic.rs`: change `crate::catalog::get_model_info` → `fabro_model::get_model_info`\n- Any other internal `crate::catalog` or `crate::types::ModelInfo` references\n\n### 7. Update consumer crates\n\n| Crate | Add dep | Import changes | Drop `fabro-llm`? |\n|---|---|---|---|\n| **fabro-validate** | `fabro-model` | `fabro_llm::catalog::*` → `fabro_model::*`, `fabro_llm::Provider` → `fabro_model::Provider` | **Yes** |\n| **fabro-cli** | `fabro-model` | `fabro_llm::catalog::*` → `fabro_model::*` | No |\n| **fabro-api** | `fabro-model` | `fabro_llm::catalog::*` → `fabro_model::*` | No |\n| **fabro-workflows** | `fabro-model` | `fabro_llm::catalog::*` → `fabro_model::*`, `FallbackTarget` | No |\n| **fabro-agent** | `fabro-model` | `fabro_llm::catalog::*` → `fabro_model::*` | No |\n| **fabro-hooks** | `fabro-model` | `fabro_llm::catalog::get_model_info` → `fabro_model::get_model_info` | No |\n\n## Files to modify\n\n- **Create**: `lib/crates/fabro-model/Cargo.toml`, `src/lib.rs`, `src/types.rs`, `src/provider.rs`\n- **Move**: `lib/crates/fabro-llm/src/catalog.rs` → `lib/crates/fabro-model/src/catalog.rs`\n- **Move**: `lib/crates/fabro-llm/src/catalog.json` → `lib/crates/fabro-model/src/catalog.json`\n- **Edit**: `lib/crates/fabro-llm/src/lib.rs`, `types.rs`, `provider.rs`, `cli.rs`, `client.rs`, `providers/anthropic.rs`, `Cargo.toml`\n- **Edit**: `lib/crates/fabro-validate/Cargo.toml`, `src/rules.rs`\n- **Edit**: `lib/crates/fabro-cli/Cargo.toml` + source files with `fabro_llm::catalog` imports\n- **Edit**: `lib/crates/fabro-api/Cargo.toml` + source files\n- **Edit**: `lib/crates/fabro-workflows/Cargo.toml` + source files\n- **Edit**: `lib/crates/fabro-agent/Cargo.toml` + source files\n- **Edit**: `lib/crates/fabro-hooks/Cargo.toml` + source files\n\n## Verification\n\n1. `cargo build --workspace` — compiles cleanly\n2. `cargo test -p fabro-model` — all catalog tests pass (snapshot tests included)\n3. `cargo test --workspace` — no regressions\n4. `cargo clippy --workspace -- -D warnings` — no lint warnings\n5. `cargo fmt --check --all` — formatted\n6. Verify `fabro-validate` no longer depends on `fabro-llm`: `cargo tree -p fabro-validate | grep fabro-llm` should return nothing\n", + "start_time": "2026-03-20T00:25:27.380670Z", + "node_count": 12, + "edge_count": 15, + "run_branch": "fabro/run/01KM4A3R67NG11AN7XCED06N2Z", + "base_sha": "1fc3495ba93e711faaa7ea4db9819d1f61d03b78", + "base_branch": "main", + "workflow_slug": "implement", + "host_repo_path": "/Users/bhelmkamp/p/fabro-sh/fabro" +} \ No newline at end of file diff --git a/sandbox.json b/sandbox.json new file mode 100644 index 000000000..1cfa8f0de --- /dev/null +++ b/sandbox.json @@ -0,0 +1,5 @@ +{ + "provider": "daytona", + "working_directory": "/home/daytona/workspace", + "identifier": "fabro-01KM4A3R67NG11AN7XCED06N2Z" +} \ No newline at end of file