Apply rustfmt 2024 style edition across workspace

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-28 12:56:30 -04:00
parent e6d865e29f
commit bd97e76fe5
185 changed files with 1258 additions and 870 deletions

View file

@ -2,8 +2,8 @@ use crate::profiles::EnvContext;
use crate::sandbox::Sandbox;
use crate::skills::Skill;
use crate::subagent::{
make_close_agent_tool, make_send_input_tool, make_spawn_agent_tool, make_wait_tool,
SessionFactory, SubAgentManager,
SessionFactory, SubAgentManager, make_close_agent_tool, make_send_input_tool,
make_spawn_agent_tool, make_wait_tool,
};
use crate::tool_registry::ToolRegistry;
use fabro_llm::types::ToolDefinition;

View file

@ -3,9 +3,9 @@ use crate::error::AbortReason;
use crate::tools::WebFetchSummarizer;
use crate::truncation;
use crate::{
subagent::{SessionFactory, SubAgentManager},
AgentEvent, AgentProfile, AnthropicProfile, GeminiProfile, LocalSandbox, OpenAiProfile,
Sandbox, Session, SessionConfig, Turn,
subagent::{SessionFactory, SubAgentManager},
};
use clap::{Args, Parser};
use fabro_llm::client::Client;

View file

@ -40,8 +40,8 @@ pub use memory::discover_memory;
pub use profiles::{AnthropicProfile, EnvContext, GeminiProfile, OpenAiProfile};
pub use read_before_write_sandbox::ReadBeforeWriteSandbox;
pub use sandbox::{
format_lines_numbered, shell_quote, DirEntry, ExecResult, GrepOptions, Sandbox, SandboxEvent,
SandboxEventCallback, WorktreeConfig, WorktreeEvent, WorktreeEventCallback, WorktreeSandbox,
DirEntry, ExecResult, GrepOptions, Sandbox, SandboxEvent, SandboxEventCallback, WorktreeConfig,
WorktreeEvent, WorktreeEventCallback, WorktreeSandbox, format_lines_numbered, shell_quote,
};
pub use session::Session;
pub use skills::Skill;
@ -50,11 +50,11 @@ pub use subagent::{
};
pub use tool_registry::ToolRegistry;
pub use tools::{
make_edit_file_tool, make_glob_tool, make_grep_tool, make_read_file_tool, make_shell_tool,
make_shell_tool_with_config, make_write_file_tool, register_core_tools, WebFetchSummarizer,
WebFetchSummarizer, make_edit_file_tool, make_glob_tool, make_grep_tool, make_read_file_tool,
make_shell_tool, make_shell_tool_with_config, make_write_file_tool, register_core_tools,
};
pub use truncation::{
floor_char_boundary, truncate_lines, truncate_output, truncate_tool_output, TruncationMode,
TruncationMode, floor_char_boundary, truncate_lines, truncate_output, truncate_tool_output,
};
pub use types::{AgentEvent, SessionEvent, SessionState, Turn};

View file

@ -1,7 +1,7 @@
use std::sync::Arc;
use fabro_llm::types::ToolDefinition;
use fabro_mcp::connection_manager::{call_result_to_string, McpConnectionManager};
use fabro_mcp::connection_manager::{McpConnectionManager, call_result_to_string};
use crate::tool_registry::RegisteredTool;

View file

@ -1,11 +1,11 @@
use crate::agent_profile::AgentProfile;
use crate::config::SessionConfig;
use crate::profiles::assemble_system_prompt;
use crate::profiles::BaseProfile;
use crate::profiles::assemble_system_prompt;
use crate::sandbox::Sandbox;
use crate::skills::Skill;
use crate::tool_registry::ToolRegistry;
use crate::tools::{make_edit_file_tool, register_core_tools, WebFetchSummarizer};
use crate::tools::{WebFetchSummarizer, make_edit_file_tool, register_core_tools};
use fabro_model::Provider;
use super::EnvContext;

View file

@ -1,13 +1,13 @@
use crate::agent_profile::AgentProfile;
use crate::config::SessionConfig;
use crate::profiles::assemble_system_prompt;
use crate::profiles::BaseProfile;
use crate::profiles::assemble_system_prompt;
use crate::sandbox::Sandbox;
use crate::skills::Skill;
use crate::tool_registry::ToolRegistry;
use crate::tools::{
make_edit_file_tool, make_list_dir_tool, make_read_many_files_tool, register_core_tools,
WebFetchSummarizer,
WebFetchSummarizer, make_edit_file_tool, make_list_dir_tool, make_read_many_files_tool,
register_core_tools,
};
use fabro_model::Provider;

View file

@ -7,7 +7,7 @@ pub use gemini::GeminiProfile;
pub use openai::OpenAiProfile;
use crate::sandbox::Sandbox;
use crate::skills::{format_skills_prompt_section, Skill};
use crate::skills::{Skill, format_skills_prompt_section};
use crate::tool_registry::ToolRegistry;
use fabro_model::Provider;

View file

@ -1,11 +1,11 @@
use crate::agent_profile::AgentProfile;
use crate::config::SessionConfig;
use crate::profiles::assemble_system_prompt;
use crate::profiles::BaseProfile;
use crate::profiles::assemble_system_prompt;
use crate::sandbox::Sandbox;
use crate::skills::Skill;
use crate::tool_registry::ToolRegistry;
use crate::tools::{register_core_tools, WebFetchSummarizer};
use crate::tools::{WebFetchSummarizer, register_core_tools};
use crate::v4a_patch::make_apply_patch_tool;
use fabro_model::Provider;

View file

@ -1,7 +1,7 @@
// Re-export all sandbox types from fabro-sandbox.
pub use fabro_sandbox::{
format_lines_numbered, shell_quote, DirEntry, ExecResult, GrepOptions, Sandbox, SandboxEvent,
SandboxEventCallback, WorktreeConfig, WorktreeEvent, WorktreeEventCallback, WorktreeSandbox,
DirEntry, ExecResult, GrepOptions, Sandbox, SandboxEvent, SandboxEventCallback, WorktreeConfig,
WorktreeEvent, WorktreeEventCallback, WorktreeSandbox, format_lines_numbered, shell_quote,
};
// Re-export the delegate_sandbox! macro at crate root so existing

View file

@ -11,7 +11,7 @@ use crate::memory::discover_memory;
use crate::profiles::EnvContext;
use crate::sandbox::Sandbox;
use crate::skills::{
default_skill_dirs, discover_skills, expand_skill, make_use_skill_tool, ExpandedInput, Skill,
ExpandedInput, Skill, default_skill_dirs, discover_skills, expand_skill, make_use_skill_tool,
};
use crate::subagent::{SubAgentEventCallback, SubAgentManager};
use crate::tool_execution::execute_tool_calls;
@ -30,7 +30,7 @@ use futures::StreamExt;
use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
use tokio::sync::{broadcast, Mutex as AsyncMutex};
use tokio::sync::{Mutex as AsyncMutex, broadcast};
use tokio::time;
use tokio_util::sync::CancellationToken;
use tracing::{debug, info, warn};
@ -264,7 +264,9 @@ impl Session {
info!(pid, port, "MCP server process launched in sandbox");
// Wait for the server to start listening on the port
let poll_cmd = format!("for i in $(seq 1 30); do ss -tln | grep -q ':{port} ' && echo ready && exit 0; sleep 1; done; echo timeout");
let poll_cmd = format!(
"for i in $(seq 1 30); do ss -tln | grep -q ':{port} ' && echo ready && exit 0; sleep 1; done; echo timeout"
);
let poll_result = sandbox
.exec_command(&poll_cmd, 60_000, None, None, None)
.await
@ -781,7 +783,7 @@ impl Session {
return Err(self.emit_llm_error(SdkError::Stream {
message: "Stream ended without a Finish event (after retries)".into(),
source: None,
}))
}));
}
};
@ -983,8 +985,8 @@ mod tests {
use fabro_llm::error::{ProviderErrorDetail, ProviderErrorKind};
use fabro_llm::provider::{ProviderAdapter, StreamEventStream};
use fabro_llm::types::{Request, Response, Role, StreamEvent, ToolDefinition};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Clone)]
enum ScriptedStreamCall {
@ -1244,18 +1246,26 @@ mod tests {
events.push(event);
}
assert!(events
.iter()
.any(|e| matches!(e.event, AgentEvent::SessionStarted)));
assert!(events
.iter()
.any(|e| matches!(e.event, AgentEvent::UserInput { .. })));
assert!(events
.iter()
.any(|e| matches!(e.event, AgentEvent::AssistantMessage { .. })));
assert!(events
.iter()
.any(|e| matches!(e.event, AgentEvent::SessionEnded)));
assert!(
events
.iter()
.any(|e| matches!(e.event, AgentEvent::SessionStarted))
);
assert!(
events
.iter()
.any(|e| matches!(e.event, AgentEvent::UserInput { .. }))
);
assert!(
events
.iter()
.any(|e| matches!(e.event, AgentEvent::AssistantMessage { .. }))
);
assert!(
events
.iter()
.any(|e| matches!(e.event, AgentEvent::SessionEnded))
);
}
#[tokio::test]

View file

@ -520,10 +520,12 @@ mod tests {
let session = make_session(vec![text_response("Hello")]).await;
let result = manager.spawn(session, "Do something".into(), 2);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Maximum subagent depth"));
assert!(
result
.unwrap_err()
.to_string()
.contains("Maximum subagent depth")
);
}
#[tokio::test]
@ -674,9 +676,11 @@ mod tests {
manager.close(&agent_id).unwrap();
let captured = events.lock().unwrap();
assert!(captured
.iter()
.any(|e| matches!(e, AgentEvent::SubAgentClosed { depth: 2, .. })));
assert!(
captured
.iter()
.any(|e| matches!(e, AgentEvent::SubAgentClosed { depth: 2, .. }))
);
}
#[tokio::test]

View file

@ -1244,7 +1244,7 @@ mod tests {
#[tokio::test]
async fn web_fetch_prompt_with_summarizer_returns_llm_answer() {
use crate::test_support::{make_client, text_response, MockLlmProvider};
use crate::test_support::{MockLlmProvider, make_client, text_response};
let provider = Arc::new(MockLlmProvider::new(vec![text_response(
"Rust is a systems programming language focused on safety and performance.",
@ -1321,7 +1321,7 @@ mod tests {
#[tokio::test]
async fn web_fetch_summarizer_routes_to_specified_provider() {
use crate::test_support::{text_response, MockErrorProvider, MockLlmProvider};
use crate::test_support::{MockErrorProvider, MockLlmProvider, text_response};
use fabro_llm::error::{ProviderErrorDetail, ProviderErrorKind, SdkError};
// "other_provider" is the default — it rejects all requests.

View file

@ -1,6 +1,6 @@
use crate::sandbox::{format_lines_numbered, Sandbox};
use crate::sandbox::{Sandbox, format_lines_numbered};
use crate::tool_registry::RegisteredTool;
use crate::truncation::{truncate_output, TruncationMode};
use crate::truncation::{TruncationMode, truncate_output};
use fabro_llm::types::ToolDefinition;
use std::sync::Arc;
@ -1310,10 +1310,11 @@ EOF";
assert!(result.contains("Moved file"));
// Old path gone
assert!(env
.read_file("src/models/user.py", None, None)
.await
.is_err());
assert!(
env.read_file("src/models/user.py", None, None)
.await
.is_err()
);
// New path has updated content
let content = env
@ -1405,7 +1406,7 @@ def gamma():
use crate::config::SessionConfig;
use crate::session::Session;
use crate::test_support::{
make_client, text_response, tool_call_response, MockLlmProvider, TestProfile,
MockLlmProvider, TestProfile, make_client, text_response, tool_call_response,
};
use crate::tool_registry::ToolRegistry;

View file

@ -3,10 +3,10 @@
use std::sync::Arc;
use axum::Json;
use axum::extract::{Path, Query, State};
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use serde_json::json;
use crate::error::ApiError;
@ -1966,53 +1966,81 @@ mod verifications {
question: "Do we understand what this change is and why we're making it?",
controls: &[
ControlDef {
name: "Motivation", slug: "motivation",
name: "Motivation",
slug: "motivation",
description: "Origin of proposal identified",
type_: VerificationType::Ai, mode: VerificationMode::Active,
f1: Some(0.87), pass_at_1: Some(0.82),
type_: VerificationType::Ai,
mode: VerificationMode::Active,
f1: Some(0.87),
pass_at_1: Some(0.82),
evaluations: &[P, P, F, P, P, P, P, F, P, P],
run_status: VerificationResult::Pass,
detail_description: "Verifies that every change traces back to a clear origin \u{2014} whether a ticket, RFC, customer request, or incident. Without documented motivation, reviewers lack context for evaluating whether the change is appropriate.",
checks: &["PR body or linked issue explains why the change is needed", "Commit messages reference a ticket or context", "No orphaned changes without traceable origin"],
checks: &[
"PR body or linked issue explains why the change is needed",
"Commit messages reference a ticket or context",
"No orphaned changes without traceable origin",
],
pass_example: "PR links to JIRA-1234 and explains the user-facing pain point being resolved.",
fail_example: "PR description is empty or says only 'fix stuff'.",
recent_results: Some(MOTIVATION_RESULTS),
},
ControlDef {
name: "Specifications", slug: "specifications",
name: "Specifications",
slug: "specifications",
description: "Requirements written down",
type_: VerificationType::Ai, mode: VerificationMode::Active,
f1: Some(0.83), pass_at_1: Some(0.78),
type_: VerificationType::Ai,
mode: VerificationMode::Active,
f1: Some(0.83),
pass_at_1: Some(0.78),
evaluations: &[P, F, P, P, P, F, P, P, P, P],
run_status: VerificationResult::Pass,
detail_description: "Checks that functional and non-functional requirements are written down before implementation begins. Specifications prevent scope creep and ensure everyone agrees on what done looks like.",
checks: &["Acceptance criteria listed in the issue or PR", "Edge cases documented", "Non-functional requirements (performance, security) stated when relevant"],
checks: &[
"Acceptance criteria listed in the issue or PR",
"Edge cases documented",
"Non-functional requirements (performance, security) stated when relevant",
],
pass_example: "Issue includes acceptance criteria with three testable scenarios.",
fail_example: "Issue body says 'implement the feature' with no acceptance criteria.",
recent_results: None,
},
ControlDef {
name: "Documentation", slug: "documentation",
name: "Documentation",
slug: "documentation",
description: "Developer and user docs added",
type_: VerificationType::Ai, mode: VerificationMode::Active,
f1: Some(0.79), pass_at_1: Some(0.74),
type_: VerificationType::Ai,
mode: VerificationMode::Active,
f1: Some(0.79),
pass_at_1: Some(0.74),
evaluations: &[P, P, P, F, P, P, F, P, P, F],
run_status: VerificationResult::Pass,
detail_description: "Ensures developer-facing and user-facing documentation is added or updated alongside code changes. Stale docs degrade team velocity and increase onboarding cost.",
checks: &["README or docs updated for new features", "API documentation reflects endpoint changes", "Inline comments for non-obvious logic"],
checks: &[
"README or docs updated for new features",
"API documentation reflects endpoint changes",
"Inline comments for non-obvious logic",
],
pass_example: "New API endpoint has corresponding OpenAPI spec update and usage example in docs.",
fail_example: "New CLI flag added with no mention in README or --help text.",
recent_results: Some(DOCUMENTATION_RESULTS),
},
ControlDef {
name: "Minimization", slug: "minimization",
name: "Minimization",
slug: "minimization",
description: "No extraneous changes",
type_: VerificationType::Ai, mode: VerificationMode::Evaluate,
f1: Some(0.72), pass_at_1: Some(0.68),
type_: VerificationType::Ai,
mode: VerificationMode::Evaluate,
f1: Some(0.72),
pass_at_1: Some(0.68),
evaluations: &[P, F, P, F, P, P, F, P, P, P],
run_status: VerificationResult::Pass,
detail_description: "Flags extraneous changes that inflate the diff \u{2014} formatting-only edits, unrelated refactors, or drive-by fixes. Keeping PRs focused improves review quality and reduces revert risk.",
checks: &["No unrelated formatting or whitespace changes", "Refactors separated from feature work", "Each commit addresses a single concern"],
checks: &[
"No unrelated formatting or whitespace changes",
"Refactors separated from feature work",
"Each commit addresses a single concern",
],
pass_example: "PR touches only files directly related to the new caching layer.",
fail_example: "PR adds a feature but also reformats 12 unrelated files.",
recent_results: None,
@ -2024,40 +2052,60 @@ mod verifications {
question: "Can a human or agent quickly read this and understand what it does?",
controls: &[
ControlDef {
name: "Formatting", slug: "formatting",
name: "Formatting",
slug: "formatting",
description: "Code layout matches standard",
type_: VerificationType::Automated, mode: VerificationMode::Active,
f1: Some(0.99), pass_at_1: Some(0.98),
type_: VerificationType::Automated,
mode: VerificationMode::Active,
f1: Some(0.99),
pass_at_1: Some(0.98),
evaluations: &[P, P, P, P, P, P, P, P, P, P],
run_status: VerificationResult::Pass,
detail_description: "Validates that code layout conforms to the project's formatting standard (e.g., Prettier, rustfmt). Automated formatting removes subjective style debates from code review.",
checks: &["All files pass the project formatter", "No manual formatting overrides without justification"],
checks: &[
"All files pass the project formatter",
"No manual formatting overrides without justification",
],
pass_example: "All changed files pass `prettier --check` and `rustfmt --check`.",
fail_example: "Several files have inconsistent indentation that the formatter would fix.",
recent_results: None,
},
ControlDef {
name: "Linting", slug: "linting",
name: "Linting",
slug: "linting",
description: "Linter issues resolved",
type_: VerificationType::Automated, mode: VerificationMode::Active,
f1: Some(0.98), pass_at_1: Some(0.97),
type_: VerificationType::Automated,
mode: VerificationMode::Active,
f1: Some(0.98),
pass_at_1: Some(0.97),
evaluations: &[P, P, P, P, P, P, P, P, F, P],
run_status: VerificationResult::Pass,
detail_description: "Confirms that static analysis findings are resolved. Linter warnings left unaddressed accumulate into tech debt and mask real issues.",
checks: &["No new linter warnings introduced", "Existing warnings not suppressed without explanation", "Lint config not weakened"],
checks: &[
"No new linter warnings introduced",
"Existing warnings not suppressed without explanation",
"Lint config not weakened",
],
pass_example: "ESLint and Clippy pass with zero warnings on changed files.",
fail_example: "New `// eslint-disable-next-line` added to suppress a legitimate warning.",
recent_results: None,
},
ControlDef {
name: "Style", slug: "style",
name: "Style",
slug: "style",
description: "House style applied",
type_: VerificationType::Ai, mode: VerificationMode::Active,
f1: Some(0.81), pass_at_1: Some(0.76),
type_: VerificationType::Ai,
mode: VerificationMode::Active,
f1: Some(0.81),
pass_at_1: Some(0.76),
evaluations: &[P, F, P, P, P, P, F, P, P, P],
run_status: VerificationResult::Pass,
detail_description: "Evaluates whether the code follows the team's house style conventions beyond what automated formatters catch \u{2014} naming, file organization, import ordering, and idiomatic patterns.",
checks: &["Naming conventions followed (camelCase, snake_case as appropriate)", "Import ordering matches project convention", "Idiomatic patterns used for the language"],
checks: &[
"Naming conventions followed (camelCase, snake_case as appropriate)",
"Import ordering matches project convention",
"Idiomatic patterns used for the language",
],
pass_example: "New TypeScript module uses camelCase variables, groups imports by source, and uses `Map` instead of plain objects for lookups.",
fail_example: "Mix of camelCase and snake_case in the same module with random import ordering.",
recent_results: None,
@ -2069,40 +2117,61 @@ mod verifications {
question: "Will this behave correctly and safely under real-world conditions and failures?",
controls: &[
ControlDef {
name: "Completeness", slug: "completeness",
name: "Completeness",
slug: "completeness",
description: "Implementation covers requirements",
type_: VerificationType::Ai, mode: VerificationMode::Active,
f1: Some(0.76), pass_at_1: Some(0.71),
type_: VerificationType::Ai,
mode: VerificationMode::Active,
f1: Some(0.76),
pass_at_1: Some(0.71),
evaluations: &[P, P, F, P, F, P, P, P, F, P],
run_status: VerificationResult::Pass,
detail_description: "Checks that the implementation fully covers the specified requirements. Partial implementations ship broken experiences and create follow-up tickets that could have been avoided.",
checks: &["All acceptance criteria addressed", "Edge cases handled", "Error states implemented"],
checks: &[
"All acceptance criteria addressed",
"Edge cases handled",
"Error states implemented",
],
pass_example: "Feature handles all three specified user roles with appropriate permissions.",
fail_example: "Only the happy path is implemented; error and empty states are missing.",
recent_results: None,
},
ControlDef {
name: "Defects", slug: "defects",
name: "Defects",
slug: "defects",
description: "Potential or likely bugs remediated",
type_: VerificationType::AiAnalysis, mode: VerificationMode::Active,
f1: Some(0.84), pass_at_1: Some(0.79),
type_: VerificationType::AiAnalysis,
mode: VerificationMode::Active,
f1: Some(0.84),
pass_at_1: Some(0.79),
evaluations: &[P, P, P, F, P, P, P, P, P, F],
run_status: VerificationResult::Pass,
detail_description: "Identifies potential or likely bugs through static analysis and AI review. Catching defects before merge is orders of magnitude cheaper than finding them in production.",
checks: &["No off-by-one errors in loops or slices", "Null/undefined handled at boundaries", "Race conditions considered in async code"],
checks: &[
"No off-by-one errors in loops or slices",
"Null/undefined handled at boundaries",
"Race conditions considered in async code",
],
pass_example: "API handler validates input, handles missing fields gracefully, and returns appropriate HTTP status codes.",
fail_example: "Array index accessed without bounds check; crashes on empty input.",
recent_results: None,
},
ControlDef {
name: "Performance", slug: "performance",
name: "Performance",
slug: "performance",
description: "Hot path impact identified",
type_: VerificationType::Ai, mode: VerificationMode::Evaluate,
f1: Some(0.69), pass_at_1: Some(0.63),
type_: VerificationType::Ai,
mode: VerificationMode::Evaluate,
f1: Some(0.69),
pass_at_1: Some(0.63),
evaluations: &[F, P, P, F, P, F, P, P, F, P],
run_status: VerificationResult::Pass,
detail_description: "Assesses whether the change impacts hot paths or introduces algorithmic regressions. Performance problems that ship to production are expensive to diagnose and fix.",
checks: &["No N+1 queries introduced", "Large collections not processed synchronously", "Caching considered for repeated expensive operations"],
checks: &[
"No N+1 queries introduced",
"Large collections not processed synchronously",
"Caching considered for repeated expensive operations",
],
pass_example: "Database query uses a JOIN instead of N separate queries for related records.",
fail_example: "Loop makes a separate HTTP call for each item in a 1000-element list.",
recent_results: None,
@ -2114,40 +2183,61 @@ mod verifications {
question: "Do we have trustworthy, automated evidence that it works and won't regress?",
controls: &[
ControlDef {
name: "Test Coverage", slug: "test-coverage",
name: "Test Coverage",
slug: "test-coverage",
description: "Production code exercised by unit tests",
type_: VerificationType::Analysis, mode: VerificationMode::Active,
f1: Some(0.95), pass_at_1: Some(0.93),
type_: VerificationType::Analysis,
mode: VerificationMode::Active,
f1: Some(0.95),
pass_at_1: Some(0.93),
evaluations: &[P, P, P, P, P, P, F, P, P, P],
run_status: VerificationResult::Pass,
detail_description: "Measures whether production code is exercised by automated tests. Coverage gaps mean regressions can ship undetected.",
checks: &["New code has corresponding unit tests", "Coverage does not decrease", "Critical paths have integration tests"],
checks: &[
"New code has corresponding unit tests",
"Coverage does not decrease",
"Critical paths have integration tests",
],
pass_example: "New service method has 6 unit tests covering happy path, error cases, and edge cases.",
fail_example: "New 200-line module has zero test files.",
recent_results: None,
},
ControlDef {
name: "Test Quality", slug: "test-quality",
name: "Test Quality",
slug: "test-quality",
description: "Tests are robust and clear",
type_: VerificationType::Ai, mode: VerificationMode::Evaluate,
f1: Some(0.71), pass_at_1: Some(0.65),
type_: VerificationType::Ai,
mode: VerificationMode::Evaluate,
f1: Some(0.71),
pass_at_1: Some(0.65),
evaluations: &[P, F, F, P, P, F, P, F, P, P],
run_status: VerificationResult::Fail,
detail_description: "Evaluates whether tests are robust, readable, and actually verify behavior rather than implementation details. Low-quality tests give false confidence.",
checks: &["Tests verify behavior, not implementation", "Assertions are specific and meaningful", "Tests are independent and deterministic"],
checks: &[
"Tests verify behavior, not implementation",
"Assertions are specific and meaningful",
"Tests are independent and deterministic",
],
pass_example: "Tests assert on API response shape and status codes, not on internal method call counts.",
fail_example: "Tests mock every dependency and only verify that mocks were called.",
recent_results: None,
},
ControlDef {
name: "E2E Coverage", slug: "e2e-coverage",
name: "E2E Coverage",
slug: "e2e-coverage",
description: "Browser automation exercises UX",
type_: VerificationType::Analysis, mode: VerificationMode::Active,
f1: Some(0.91), pass_at_1: Some(0.88),
type_: VerificationType::Analysis,
mode: VerificationMode::Active,
f1: Some(0.91),
pass_at_1: Some(0.88),
evaluations: &[P, P, P, F, P, P, P, P, P, P],
run_status: VerificationResult::Na,
detail_description: "Checks that user-facing workflows are exercised by end-to-end browser automation. E2E tests catch integration issues that unit tests miss.",
checks: &["Critical user flows have Playwright/Cypress tests", "E2E tests run in CI", "No flaky E2E tests introduced"],
checks: &[
"Critical user flows have Playwright/Cypress tests",
"E2E tests run in CI",
"No flaky E2E tests introduced",
],
pass_example: "New checkout flow has a Playwright test that completes a purchase end-to-end.",
fail_example: "New multi-step wizard has no browser automation tests.",
recent_results: None,
@ -2159,66 +2249,101 @@ mod verifications {
question: "Will this be easy to modify or extend later without creating new risk?",
controls: &[
ControlDef {
name: "Architecture", slug: "architecture",
name: "Architecture",
slug: "architecture",
description: "Layering and dependency graph meets design",
type_: VerificationType::Analysis, mode: VerificationMode::Active,
f1: Some(0.88), pass_at_1: Some(0.84),
type_: VerificationType::Analysis,
mode: VerificationMode::Active,
f1: Some(0.88),
pass_at_1: Some(0.84),
evaluations: &[P, P, P, P, F, P, P, P, P, P],
run_status: VerificationResult::Pass,
detail_description: "Validates that layering and dependency directions conform to the project's architectural design. Architectural violations compound over time and make systems harder to evolve.",
checks: &["Dependencies point inward (domain doesn't depend on infra)", "No circular dependencies introduced", "Module boundaries respected"],
checks: &[
"Dependencies point inward (domain doesn't depend on infra)",
"No circular dependencies introduced",
"Module boundaries respected",
],
pass_example: "New repository implementation depends on domain interfaces, not the other way around.",
fail_example: "Domain model imports directly from the HTTP framework package.",
recent_results: None,
},
ControlDef {
name: "Interfaces", slug: "interfaces",
name: "Interfaces",
slug: "interfaces",
description: "",
type_: VerificationType::Ai, mode: VerificationMode::Disabled,
f1: None, pass_at_1: None,
type_: VerificationType::Ai,
mode: VerificationMode::Disabled,
f1: None,
pass_at_1: None,
evaluations: &[],
run_status: VerificationResult::Pass,
detail_description: "Reviews public API surfaces for clarity, consistency, and backward compatibility. Interfaces are contracts \u{2014} once published, they're expensive to change.",
checks: &["Public API types are well-defined", "Breaking changes documented", "Consistent naming across endpoints"],
checks: &[
"Public API types are well-defined",
"Breaking changes documented",
"Consistent naming across endpoints",
],
pass_example: "New endpoint follows existing naming and error format conventions.",
fail_example: "New endpoint uses different error format than all other endpoints.",
recent_results: None,
},
ControlDef {
name: "Duplication", slug: "duplication",
name: "Duplication",
slug: "duplication",
description: "Similar and identical code blocks identified",
type_: VerificationType::Analysis, mode: VerificationMode::Active,
f1: Some(0.96), pass_at_1: Some(0.94),
type_: VerificationType::Analysis,
mode: VerificationMode::Active,
f1: Some(0.96),
pass_at_1: Some(0.94),
evaluations: &[P, P, P, P, P, P, P, F, P, P],
run_status: VerificationResult::Pass,
detail_description: "Detects similar or identical code blocks that could be consolidated. Duplication increases maintenance burden and creates inconsistency risk.",
checks: &["No copy-pasted logic across files", "Shared utilities used for common patterns", "Similar test setup consolidated"],
checks: &[
"No copy-pasted logic across files",
"Shared utilities used for common patterns",
"Similar test setup consolidated",
],
pass_example: "Date formatting logic extracted into a shared utility used by 4 components.",
fail_example: "Same 15-line validation function copy-pasted into three different handlers.",
recent_results: None,
},
ControlDef {
name: "Simplicity", slug: "simplicity",
name: "Simplicity",
slug: "simplicity",
description: "Extra review for reducing complexity",
type_: VerificationType::Ai, mode: VerificationMode::Active,
f1: Some(0.74), pass_at_1: Some(0.69),
type_: VerificationType::Ai,
mode: VerificationMode::Active,
f1: Some(0.74),
pass_at_1: Some(0.69),
evaluations: &[P, F, P, P, F, P, P, F, P, P],
run_status: VerificationResult::Pass,
detail_description: "Flags unnecessarily complex code that could be simplified without changing behavior. Simpler code is easier to review, debug, and extend.",
checks: &["No premature abstractions", "Control flow is straightforward", "Functions are focused and short"],
checks: &[
"No premature abstractions",
"Control flow is straightforward",
"Functions are focused and short",
],
pass_example: "Conditional logic uses early returns instead of deeply nested if-else chains.",
fail_example: "Three-level generic abstraction for a function called in one place.",
recent_results: None,
},
ControlDef {
name: "Dead Code", slug: "dead-code",
name: "Dead Code",
slug: "dead-code",
description: "Unexecuted code and dependencies removed",
type_: VerificationType::Analysis, mode: VerificationMode::Active,
f1: Some(0.93), pass_at_1: Some(0.90),
type_: VerificationType::Analysis,
mode: VerificationMode::Active,
f1: Some(0.93),
pass_at_1: Some(0.90),
evaluations: &[P, P, P, P, P, F, P, P, P, P],
run_status: VerificationResult::Pass,
detail_description: "Identifies unexecuted code paths and unused dependencies. Dead code misleads readers and bloats bundles.",
checks: &["No unreachable code paths", "Unused imports and variables removed", "Deprecated functions removed if no longer called"],
checks: &[
"No unreachable code paths",
"Unused imports and variables removed",
"Deprecated functions removed if no longer called",
],
pass_example: "Old feature flag and its associated code paths removed after rollout completed.",
fail_example: "Commented-out function left in file 'in case we need it later'.",
recent_results: None,
@ -2230,53 +2355,81 @@ mod verifications {
question: "Does this preserve or improve our security posture and avoid vulnerabilities?",
controls: &[
ControlDef {
name: "Vulnerabilities", slug: "vulnerabilities",
name: "Vulnerabilities",
slug: "vulnerabilities",
description: "Security issues are remediated",
type_: VerificationType::AiAnalysis, mode: VerificationMode::Active,
f1: Some(0.86), pass_at_1: Some(0.81),
type_: VerificationType::AiAnalysis,
mode: VerificationMode::Active,
f1: Some(0.86),
pass_at_1: Some(0.81),
evaluations: &[P, P, F, P, P, P, P, P, F, P],
run_status: VerificationResult::Pass,
detail_description: "Scans for known security vulnerabilities using both AI analysis and static scanning tools. Shipping known vulnerabilities exposes users and the organization to risk.",
checks: &["No SQL injection or XSS vectors", "User input sanitized at boundaries", "Authentication/authorization checks present"],
checks: &[
"No SQL injection or XSS vectors",
"User input sanitized at boundaries",
"Authentication/authorization checks present",
],
pass_example: "User input passed through parameterized queries; HTML output escaped.",
fail_example: "Raw SQL string concatenation with user-supplied values.",
recent_results: None,
},
ControlDef {
name: "IaC Scanning", slug: "iac-scanning",
name: "IaC Scanning",
slug: "iac-scanning",
description: "",
type_: VerificationType::Automated, mode: VerificationMode::Disabled,
f1: None, pass_at_1: None,
type_: VerificationType::Automated,
mode: VerificationMode::Disabled,
f1: None,
pass_at_1: None,
evaluations: &[],
run_status: VerificationResult::Pass,
detail_description: "Validates infrastructure-as-code definitions against security best practices. Misconfigured infrastructure is a leading cause of data breaches.",
checks: &["No publicly accessible storage buckets", "Encryption at rest enabled", "Least-privilege IAM policies"],
checks: &[
"No publicly accessible storage buckets",
"Encryption at rest enabled",
"Least-privilege IAM policies",
],
pass_example: "Terraform module creates S3 bucket with encryption, versioning, and private ACL.",
fail_example: "CloudFormation template creates an RDS instance with no encryption and public accessibility.",
recent_results: None,
},
ControlDef {
name: "Dependency Alerts", slug: "dependency-alerts",
name: "Dependency Alerts",
slug: "dependency-alerts",
description: "Known CVEs are patched",
type_: VerificationType::Analysis, mode: VerificationMode::Active,
f1: Some(0.97), pass_at_1: Some(0.95),
type_: VerificationType::Analysis,
mode: VerificationMode::Active,
f1: Some(0.97),
pass_at_1: Some(0.95),
evaluations: &[P, P, P, P, P, P, P, P, P, F],
run_status: VerificationResult::Pass,
detail_description: "Checks that third-party dependencies are free from known CVEs. Vulnerable dependencies are an easy attack vector that automated tools can detect.",
checks: &["No dependencies with known critical CVEs", "Lock file updated to patched versions", "Unused dependencies removed"],
checks: &[
"No dependencies with known critical CVEs",
"Lock file updated to patched versions",
"Unused dependencies removed",
],
pass_example: "Dependabot alert resolved by updating lodash from 4.17.20 to 4.17.21.",
fail_example: "Package.json pins a version of axios with a known SSRF vulnerability.",
recent_results: None,
},
ControlDef {
name: "Security Controls", slug: "security-controls",
name: "Security Controls",
slug: "security-controls",
description: "Organization standards applied",
type_: VerificationType::Ai, mode: VerificationMode::Active,
f1: Some(0.80), pass_at_1: Some(0.75),
type_: VerificationType::Ai,
mode: VerificationMode::Active,
f1: Some(0.80),
pass_at_1: Some(0.75),
evaluations: &[P, P, F, P, P, F, P, P, P, P],
run_status: VerificationResult::Pass,
detail_description: "Verifies that organization-specific security standards are applied \u{2014} rate limiting, audit logging, CORS policies, and secret management.",
checks: &["Secrets not hardcoded in source", "Rate limiting on public endpoints", "Audit logging for sensitive operations"],
checks: &[
"Secrets not hardcoded in source",
"Rate limiting on public endpoints",
"Audit logging for sensitive operations",
],
pass_example: "API key loaded from environment variable; rate limiter configured on login endpoint.",
fail_example: "AWS credentials committed in a config file.",
recent_results: None,
@ -2288,53 +2441,81 @@ mod verifications {
question: "Is this changeset safe to ship to production immediately?",
controls: &[
ControlDef {
name: "Compatibility", slug: "compatibility",
name: "Compatibility",
slug: "compatibility",
description: "Breaking changes are avoided",
type_: VerificationType::Analysis, mode: VerificationMode::Active,
f1: Some(0.89), pass_at_1: Some(0.85),
type_: VerificationType::Analysis,
mode: VerificationMode::Active,
f1: Some(0.89),
pass_at_1: Some(0.85),
evaluations: &[P, P, P, P, F, P, P, P, P, P],
run_status: VerificationResult::Pass,
detail_description: "Detects breaking changes in APIs, database schemas, or wire formats that could disrupt consumers. Breaking changes require coordination that surprises prevent.",
checks: &["No removed or renamed public API fields", "Database migrations are backward-compatible", "Wire format changes are additive"],
checks: &[
"No removed or renamed public API fields",
"Database migrations are backward-compatible",
"Wire format changes are additive",
],
pass_example: "New field added to API response; no existing fields removed or renamed.",
fail_example: "Column renamed in migration while old code is still deployed.",
recent_results: None,
},
ControlDef {
name: "Rollout / Rollback", slug: "rollout-rollback",
name: "Rollout / Rollback",
slug: "rollout-rollback",
description: "Known rollback plan if deploy fails",
type_: VerificationType::Ai, mode: VerificationMode::Evaluate,
f1: Some(0.66), pass_at_1: Some(0.60),
type_: VerificationType::Ai,
mode: VerificationMode::Evaluate,
f1: Some(0.66),
pass_at_1: Some(0.60),
evaluations: &[F, P, F, P, F, P, P, F, P, F],
run_status: VerificationResult::Fail,
detail_description: "Confirms that the change has a clear deployment plan and can be safely rolled back if issues arise. Every production deploy should be reversible.",
checks: &["Feature flag available for gradual rollout", "Database migration is reversible", "Rollback procedure documented"],
checks: &[
"Feature flag available for gradual rollout",
"Database migration is reversible",
"Rollback procedure documented",
],
pass_example: "Feature behind a LaunchDarkly flag with 10% initial rollout and documented rollback steps.",
fail_example: "Irreversible database migration with no rollback plan.",
recent_results: Some(ROLLOUT_ROLLBACK_RESULTS),
},
ControlDef {
name: "Observability", slug: "observability",
name: "Observability",
slug: "observability",
description: "Logging, metrics, tracing instrumented",
type_: VerificationType::Ai, mode: VerificationMode::Evaluate,
f1: Some(0.73), pass_at_1: Some(0.67),
type_: VerificationType::Ai,
mode: VerificationMode::Evaluate,
f1: Some(0.73),
pass_at_1: Some(0.67),
evaluations: &[P, F, P, F, P, P, F, P, F, P],
run_status: VerificationResult::Fail,
detail_description: "Ensures that logging, metrics, and tracing are instrumented for new code paths. Without observability, production issues are invisible until users report them.",
checks: &["Structured logging for new operations", "Metrics emitted for key business events", "Distributed tracing propagated"],
checks: &[
"Structured logging for new operations",
"Metrics emitted for key business events",
"Distributed tracing propagated",
],
pass_example: "New payment endpoint logs transaction IDs, emits latency metrics, and propagates trace context.",
fail_example: "New background job has no logging or metrics; failures are silent.",
recent_results: None,
},
ControlDef {
name: "Cost", slug: "cost",
name: "Cost",
slug: "cost",
description: "Tech ops costs estimated",
type_: VerificationType::Analysis, mode: VerificationMode::Evaluate,
f1: Some(0.78), pass_at_1: Some(0.72),
type_: VerificationType::Analysis,
mode: VerificationMode::Evaluate,
f1: Some(0.78),
pass_at_1: Some(0.72),
evaluations: &[P, P, F, P, F, P, P, F, P, P],
run_status: VerificationResult::Pass,
detail_description: "Estimates the infrastructure and operational cost impact of the change. Unchecked cost growth erodes margins and can cause budget surprises.",
checks: &["New infrastructure resources sized appropriately", "No unbounded resource consumption", "Cost estimate provided for significant changes"],
checks: &[
"New infrastructure resources sized appropriately",
"No unbounded resource consumption",
"Cost estimate provided for significant changes",
],
pass_example: "New Lambda function has memory limit set and estimated monthly cost noted in PR.",
fail_example: "New service provisions a db.r5.4xlarge for a table with 100 rows.",
recent_results: None,
@ -2346,66 +2527,101 @@ mod verifications {
question: "Does this meet our regulatory, contractual, and policy obligations?",
controls: &[
ControlDef {
name: "Change Control", slug: "change-control",
name: "Change Control",
slug: "change-control",
description: "Separation of Duties policy met",
type_: VerificationType::Analysis, mode: VerificationMode::Active,
f1: Some(0.94), pass_at_1: Some(0.91),
type_: VerificationType::Analysis,
mode: VerificationMode::Active,
f1: Some(0.94),
pass_at_1: Some(0.91),
evaluations: &[P, P, P, P, P, P, P, P, F, P],
run_status: VerificationResult::Pass,
detail_description: "Validates that separation-of-duties policies are met \u{2014} the author is not the sole reviewer, approvals are obtained, and the change went through the proper process.",
checks: &["PR has at least one approval from non-author", "Required reviewers have signed off", "No self-merging without policy exception"],
checks: &[
"PR has at least one approval from non-author",
"Required reviewers have signed off",
"No self-merging without policy exception",
],
pass_example: "PR approved by two team members before merge; CI checks all green.",
fail_example: "Author approved and merged their own PR with no other reviewers.",
recent_results: None,
},
ControlDef {
name: "AI Governance", slug: "ai-governance",
name: "AI Governance",
slug: "ai-governance",
description: "AI involvement was acceptable",
type_: VerificationType::Analysis, mode: VerificationMode::Active,
f1: Some(0.85), pass_at_1: Some(0.80),
type_: VerificationType::Analysis,
mode: VerificationMode::Active,
f1: Some(0.85),
pass_at_1: Some(0.80),
evaluations: &[P, P, P, F, P, P, P, P, P, P],
run_status: VerificationResult::Pass,
detail_description: "Checks that AI-generated or AI-assisted code meets the organization's governance requirements \u{2014} attribution, review depth, and acceptable use.",
checks: &["AI-generated code clearly attributed", "Human review of AI suggestions documented", "AI usage within acceptable-use policy"],
checks: &[
"AI-generated code clearly attributed",
"Human review of AI suggestions documented",
"AI usage within acceptable-use policy",
],
pass_example: "PR notes that implementation was AI-assisted; human reviewer verified logic and tests.",
fail_example: "Entire module generated by AI with no human review or attribution.",
recent_results: None,
},
ControlDef {
name: "Privacy", slug: "privacy",
name: "Privacy",
slug: "privacy",
description: "PII is identified and handled to standards",
type_: VerificationType::Ai, mode: VerificationMode::Active,
f1: Some(0.77), pass_at_1: Some(0.72),
type_: VerificationType::Ai,
mode: VerificationMode::Active,
f1: Some(0.77),
pass_at_1: Some(0.72),
evaluations: &[P, F, P, P, P, F, P, P, P, F],
run_status: VerificationResult::Pass,
detail_description: "Ensures that personally identifiable information (PII) is identified, classified, and handled according to privacy standards (GDPR, CCPA).",
checks: &["PII fields identified and documented", "Data retention policies applied", "Consent mechanisms in place for data collection"],
checks: &[
"PII fields identified and documented",
"Data retention policies applied",
"Consent mechanisms in place for data collection",
],
pass_example: "New user profile endpoint masks email in logs and respects data deletion requests.",
fail_example: "User email addresses logged in plaintext to application logs.",
recent_results: None,
},
ControlDef {
name: "Accessibility", slug: "accessibility",
name: "Accessibility",
slug: "accessibility",
description: "Software meets accessibility requirements",
type_: VerificationType::Analysis, mode: VerificationMode::Active,
f1: Some(0.90), pass_at_1: Some(0.87),
type_: VerificationType::Analysis,
mode: VerificationMode::Active,
f1: Some(0.90),
pass_at_1: Some(0.87),
evaluations: &[P, P, P, P, P, F, P, P, P, P],
run_status: VerificationResult::Pass,
detail_description: "Verifies that UI changes meet accessibility requirements (WCAG 2.1 AA). Inaccessible software excludes users and creates legal risk.",
checks: &["Semantic HTML elements used", "ARIA labels present on interactive elements", "Color contrast meets WCAG AA standards"],
checks: &[
"Semantic HTML elements used",
"ARIA labels present on interactive elements",
"Color contrast meets WCAG AA standards",
],
pass_example: "New modal uses <dialog>, has aria-labelledby, and focus is trapped within.",
fail_example: "Custom dropdown built with <div> elements, no keyboard navigation, no ARIA roles.",
recent_results: None,
},
ControlDef {
name: "Licensing", slug: "licensing",
name: "Licensing",
slug: "licensing",
description: "Supply chain meets IP policy",
type_: VerificationType::Analysis, mode: VerificationMode::Active,
f1: Some(0.96), pass_at_1: Some(0.93),
type_: VerificationType::Analysis,
mode: VerificationMode::Active,
f1: Some(0.96),
pass_at_1: Some(0.93),
evaluations: &[P, P, P, P, P, P, P, P, P, P],
run_status: VerificationResult::Pass,
detail_description: "Ensures that all third-party dependencies comply with the organization's intellectual property policy. License violations can have severe legal consequences.",
checks: &["No GPL-licensed dependencies in proprietary code", "License file present for new dependencies", "Supply chain attestation where required"],
checks: &[
"No GPL-licensed dependencies in proprietary code",
"License file present for new dependencies",
"Supply chain attestation where required",
],
pass_example: "New dependency uses MIT license; added to approved dependency list.",
fail_example: "GPL-licensed library added to a closed-source commercial product.",
recent_results: None,
@ -3241,8 +3457,8 @@ mod insights {
}
mod settings {
use fabro_config::server::*;
use fabro_config::FabroSettings;
use fabro_config::server::*;
pub fn server_settings() -> serde_json::Value {
serde_json::to_value(FabroSettings {

View file

@ -1,6 +1,6 @@
use axum::Json;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use serde::Serialize;
#[derive(Serialize)]

View file

@ -1,8 +1,8 @@
use axum::Router;
use axum::body::Bytes;
use axum::extract::State;
use axum::http::{HeaderMap, StatusCode};
use axum::routing::post;
use axum::Router;
use hmac::{Hmac, Mac};
use sha2::Sha256;
use tokio::net::TcpListener;

View file

@ -277,7 +277,7 @@ impl<S: Send + Sync> FromRequestParts<S> for AuthenticatedUser {
AuthMode::Disabled => {
return Ok(AuthenticatedUser {
login: "demo".to_string(),
})
});
}
AuthMode::Strategies(strategies) => strategies,
};
@ -320,11 +320,11 @@ impl<S: Send + Sync> FromRequestParts<S> for AuthenticatedUser {
#[cfg(test)]
mod tests {
use super::*;
use axum::Router;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use axum::response::IntoResponse;
use axum::routing::get;
use axum::Router;
use tower::ServiceExt;
async fn protected_handler(_auth: AuthenticatedService) -> impl IntoResponse {

View file

@ -6,8 +6,8 @@ pub mod jwt_auth;
pub mod serve;
pub mod server;
pub mod server_config {
pub use fabro_config::server::*;
pub use fabro_config::FabroSettings;
pub use fabro_config::server::*;
}
pub mod sessions;
pub mod tls;

View file

@ -15,9 +15,9 @@ use clap::Args;
use fabro_config::FabroSettings;
use crate::github_webhooks::WebhookManager;
use crate::jwt_auth::{decode_pem_env, resolve_auth_mode, AuthMode, AuthStrategy};
use crate::jwt_auth::{AuthMode, AuthStrategy, decode_pem_env, resolve_auth_mode};
use crate::server::{build_router, create_app_state_with_options, spawn_scheduler};
use crate::tls::{build_rustls_config, serve_tls, ClientAuth};
use crate::tls::{ClientAuth, build_rustls_config, serve_tls};
use fabro_llm::client::Client as LlmClient;
use fabro_sandbox::SandboxProvider;
use fabro_workflows::pipeline::LlmSpec;
@ -197,7 +197,9 @@ pub async fn serve_command(args: ServeArgs, styles: &'static Styles) -> anyhow::
}
}
_ => {
warn!("Webhook config present but GITHUB_APP_WEBHOOK_SECRET or GITHUB_APP_PRIVATE_KEY not set; skipping webhook listener");
warn!(
"Webhook config present but GITHUB_APP_WEBHOOK_SECRET or GITHUB_APP_PRIVATE_KEY not set; skipping webhook listener"
);
None
}
}

View file

@ -11,12 +11,12 @@ use axum::routing::{get, post};
use axum::{Json, Router};
use fabro_config::sandbox::SandboxSettings;
use fabro_llm::client::Client as LlmClient;
use fabro_llm::generate::{generate, generate_object, GenerateParams};
use fabro_llm::generate::{GenerateParams, generate, generate_object};
use fabro_llm::types::{
ContentPart, FinishReason, Message as LlmMessage, Request as LlmRequest,
Response as LlmResponse, Role, StreamEvent, ToolChoice, ToolDefinition, Usage,
};
use fabro_retro::retro::{derive_retro, extract_stage_durations, Retro};
use fabro_retro::retro::{Retro, derive_retro, extract_stage_durations};
use fabro_util::redact::redact_jsonl_line;
use fabro_workflows::error::FabroError;
use fabro_workflows::git::GitAuthor;
@ -27,9 +27,9 @@ use tokio::sync::oneshot;
use tokio::sync::{Notify, OnceCell};
use tokio::task::spawn_blocking;
use tokio::time::{sleep, timeout};
use tokio_stream::wrappers::BroadcastStream;
use tokio_stream::StreamExt;
use tower::{service_fn, ServiceExt};
use tokio_stream::wrappers::BroadcastStream;
use tower::{ServiceExt, service_fn};
use tracing::{error, info};
@ -37,7 +37,7 @@ use crate::demo;
use crate::error::ApiError;
use crate::jwt_auth::{AuthMode, AuthenticatedService, AuthenticatedUser};
use crate::sessions as sessions_mod;
use crate::sessions::{new_session_store, SessionStore};
use crate::sessions::{SessionStore, new_session_store};
use fabro_interview::{Answer, Interviewer, QuestionType, WebInterviewer};
use fabro_retro::RetroExt;
use fabro_workflows::context::Context;
@ -918,7 +918,7 @@ async fn get_questions(
StatusCode::OK,
Json(ListResponse::new(Vec::<ApiQuestion>::new())),
)
.into_response()
.into_response();
}
};
let pending = interviewer.pending_questions();
@ -1027,7 +1027,7 @@ async fn get_events(
Some(managed_run) => match &managed_run.event_tx {
Some(tx) => tx.subscribe(),
None => {
return ApiError::new(StatusCode::GONE, "Event stream closed.").into_response()
return ApiError::new(StatusCode::GONE, "Event stream closed.").into_response();
}
},
None => return ApiError::not_found("Run not found.").into_response(),
@ -1410,7 +1410,7 @@ async fn create_completion(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Failed to create LLM client: {e}"),
)
.into_response()
.into_response();
}
};
@ -1420,7 +1420,7 @@ async fn create_completion(
Ok(s) => s,
Err(e) => {
return ApiError::new(StatusCode::BAD_GATEWAY, format!("LLM error: {e}"))
.into_response()
.into_response();
}
};
@ -1540,7 +1540,7 @@ async fn get_retro(
/// Render DOT source to a styled SVG via `render_dot` on a blocking thread.
pub(crate) async fn render_dot_svg(dot_source: &str) -> Response {
use fabro_graphviz::render::{render_dot, GraphFormat};
use fabro_graphviz::render::{GraphFormat, render_dot};
let source = dot_source.to_owned();
match spawn_blocking(move || render_dot(&source, GraphFormat::Svg)).await {

View file

@ -2,12 +2,12 @@ use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
use axum::Json;
use axum::extract::{Path, Query, State};
use axum::http::StatusCode;
use axum::response::sse::{Event, Sse};
use axum::response::{IntoResponse, Response};
use axum::Json;
use fabro_llm::generate::{stream as llm_stream, GenerateParams};
use fabro_llm::generate::{GenerateParams, stream as llm_stream};
use fabro_llm::types::{Message as LlmMessage, StreamEvent};
use tokio::sync::broadcast;
use tokio_stream::wrappers::BroadcastStream;

View file

@ -1,8 +1,8 @@
use std::path::Path;
use std::sync::Arc;
use rustls::server::WebPkiClientVerifier;
use rustls::ServerConfig;
use rustls::server::WebPkiClientVerifier;
use rustls_pki_types::{CertificateDer, PrivateKeyDer};
use tokio::net::TcpListener;
use tracing::error;

View file

@ -11,7 +11,7 @@ mod mtls_e2e {
use fabro_api::jwt_auth::{AuthMode, AuthStrategy};
use fabro_api::server::{build_router, create_app_state};
use fabro_api::server_config::TlsSettings;
use fabro_api::tls::{build_rustls_config, ClientAuth};
use fabro_api::tls::{ClientAuth, build_rustls_config};
use fabro_workflows::pipeline::LlmSpec;
use tokio::net::TcpListener;
@ -429,11 +429,11 @@ mod server_lifecycle {
use axum::http::{Request, StatusCode};
use fabro_api::server::{build_router, create_app_state_with_registry_factory};
use fabro_interview::Interviewer;
use fabro_workflows::handler::HandlerRegistry;
use fabro_workflows::handler::agent::AgentHandler;
use fabro_workflows::handler::exit::ExitHandler;
use fabro_workflows::handler::human::HumanHandler;
use fabro_workflows::handler::start::StartHandler;
use fabro_workflows::handler::HandlerRegistry;
use fabro_workflows::pipeline::LlmSpec;
use tower::ServiceExt;

View file

@ -3,8 +3,8 @@ pub use fabro_config::cli::*;
use std::path::Path;
use fabro_config::cli::load_cli_config;
use fabro_config::FabroSettings;
use fabro_config::cli::load_cli_config;
#[cfg(feature = "server")]
use tracing::debug;

View file

@ -1,9 +1,9 @@
use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use fabro_config::FabroSettingsExt;
use fabro_store::RuntimeState;
use fabro_workflows::assets::{scan_assets, AssetEntry};
use fabro_workflows::assets::{AssetEntry, scan_assets};
use fabro_workflows::run_lookup::{resolve_run, runs_base};
use crate::args::AssetCpArgs;

View file

@ -3,7 +3,7 @@ use std::path::Path;
use crate::args::{ConfigCommand, ConfigNamespace, ConfigShowArgs};
use fabro_config::cli::load_cli_config;
use fabro_config::project::{discover_project_config, resolve_settings, ResolveSettingsInput};
use fabro_config::project::{ResolveSettingsInput, discover_project_config, resolve_settings};
use fabro_config::{FabroConfig, FabroSettings};
pub fn dispatch(ns: ConfigNamespace) -> anyhow::Result<()> {

View file

@ -4,8 +4,8 @@ use std::process::Command;
#[cfg(feature = "server")]
use std::sync::LazyLock;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
#[cfg(feature = "server")]
use fabro_config::server::{ApiAuthStrategy, AuthProvider};
use fabro_llm::client::Client as LlmClient;
@ -1177,11 +1177,7 @@ pub async fn run_doctor(verbose: bool, live: bool) -> i32 {
report.render(&styles, verbose, None, Some(term_width))
);
if report.has_errors() {
1
} else {
0
}
if report.has_errors() { 1 } else { 0 }
}
// ---------------------------------------------------------------------------
@ -1246,10 +1242,12 @@ mod tests {
let live = vec![(Provider::Anthropic, Ok(()))];
let result = check_llm_providers(&statuses, Some(&live));
assert_eq!(result.status, CheckStatus::Pass);
assert!(result
.details
.iter()
.any(|d| d.text.contains("connectivity: OK")));
assert!(
result
.details
.iter()
.any(|d| d.text.contains("connectivity: OK"))
);
}
#[test]
@ -1466,10 +1464,12 @@ mod tests {
};
let result = check_api(&status, None);
assert!(result.details.iter().any(|d| d.text.contains("jwt")));
assert!(result
.details
.iter()
.any(|d| d.text.contains("https://api.example.com")));
assert!(
result
.details
.iter()
.any(|d| d.text.contains("https://api.example.com"))
);
}
#[test]
@ -1481,10 +1481,12 @@ mod tests {
let live = Ok(());
let result = check_api(&status, Some(&live));
assert_eq!(result.status, CheckStatus::Pass);
assert!(result
.details
.iter()
.any(|d| d.text.contains("Connectivity: OK")));
assert!(
result
.details
.iter()
.any(|d| d.text.contains("Connectivity: OK"))
);
}
#[test]
@ -1496,10 +1498,12 @@ mod tests {
let live = Err("connection refused".to_string());
let result = check_api(&status, Some(&live));
assert_eq!(result.status, CheckStatus::Warning);
assert!(result
.details
.iter()
.any(|d| d.text.contains("connection refused")));
assert!(
result
.details
.iter()
.any(|d| d.text.contains("connection refused"))
);
}
// -- check_web --
@ -1525,14 +1529,18 @@ mod tests {
};
let result = check_web(&status, None);
assert!(result.details.iter().any(|d| d.text.contains("github")));
assert!(result
.details
.iter()
.any(|d| d.text.contains("https://arc.example.com")));
assert!(result
.details
.iter()
.any(|d| d.text.contains("Allowed usernames: 3")));
assert!(
result
.details
.iter()
.any(|d| d.text.contains("https://arc.example.com"))
);
assert!(
result
.details
.iter()
.any(|d| d.text.contains("Allowed usernames: 3"))
);
}
#[test]
@ -1545,10 +1553,12 @@ mod tests {
let live = Ok(());
let result = check_web(&status, Some(&live));
assert_eq!(result.status, CheckStatus::Pass);
assert!(result
.details
.iter()
.any(|d| d.text.contains("Connectivity: OK")));
assert!(
result
.details
.iter()
.any(|d| d.text.contains("Connectivity: OK"))
);
}
#[test]
@ -1561,10 +1571,12 @@ mod tests {
let live = Err("connection refused".to_string());
let result = check_web(&status, Some(&live));
assert_eq!(result.status, CheckStatus::Warning);
assert!(result
.details
.iter()
.any(|d| d.text.contains("connection refused")));
assert!(
result
.details
.iter()
.any(|d| d.text.contains("connection refused"))
);
}
} // mod server_tests (check_github_app, check_api, check_web)
@ -1893,20 +1905,24 @@ mod tests {
fn crypto_jwt_configured_but_key_missing() {
let result = check_crypto(&crypto_input(vec![ApiAuthStrategy::Jwt]));
assert_eq!(result.status, CheckStatus::Error);
assert!(result
.details
.iter()
.any(|d| d.text.contains("FABRO_JWT_PUBLIC_KEY not set")));
assert!(
result
.details
.iter()
.any(|d| d.text.contains("FABRO_JWT_PUBLIC_KEY not set"))
);
}
#[test]
fn crypto_mtls_configured_but_tls_not_set() {
let result = check_crypto(&crypto_input(vec![ApiAuthStrategy::Mtls]));
assert_eq!(result.status, CheckStatus::Error);
assert!(result
.details
.iter()
.any(|d| d.text.contains("[api.tls] not set")));
assert!(
result
.details
.iter()
.any(|d| d.text.contains("[api.tls] not set"))
);
}
#[test]
@ -1917,10 +1933,12 @@ mod tests {
};
let result = check_crypto(&input);
assert_eq!(result.status, CheckStatus::Error);
assert!(result
.details
.iter()
.any(|d| d.text.contains("Permission denied")));
assert!(
result
.details
.iter()
.any(|d| d.text.contains("Permission denied"))
);
}
#[test]
@ -1933,10 +1951,12 @@ mod tests {
};
let result = check_crypto(&input);
assert_eq!(result.status, CheckStatus::Error);
assert!(result
.details
.iter()
.any(|d| d.text.contains("JWT public key: invalid")));
assert!(
result
.details
.iter()
.any(|d| d.text.contains("JWT public key: invalid"))
);
}
#[test]
@ -1949,10 +1969,12 @@ mod tests {
};
let result = check_crypto(&input);
assert_eq!(result.status, CheckStatus::Error);
assert!(result
.details
.iter()
.any(|d| d.text.contains("JWT private key: invalid")));
assert!(
result
.details
.iter()
.any(|d| d.text.contains("JWT private key: invalid"))
);
}
#[test]
@ -1968,10 +1990,12 @@ mod tests {
};
let result = check_crypto(&input);
assert_eq!(result.status, CheckStatus::Pass);
assert!(result
.details
.iter()
.any(|d| d.text.contains("JWT public key: valid")));
assert!(
result
.details
.iter()
.any(|d| d.text.contains("JWT public key: valid"))
);
}
#[test]

View file

@ -1,7 +1,7 @@
use anyhow::Result;
#[cfg(feature = "server")]
use fabro_agent::cli::run_with_args_and_client;
use fabro_agent::cli::{run_with_args, AgentArgs};
use fabro_agent::cli::{AgentArgs, run_with_args};
use fabro_config::mcp::McpServerEntry;
use fabro_mcp::config::McpServerConfig;

View file

@ -4,11 +4,11 @@ use std::sync::LazyLock;
use anyhow::bail;
use fabro_config::cli::load_cli_config;
use fabro_config::project::{resolve_settings, resolve_workflow_path, ResolveSettingsInput};
use fabro_config::project::{ResolveSettingsInput, resolve_settings, resolve_workflow_path};
use fabro_graphviz::render::render_dot;
use fabro_util::terminal::Styles;
use fabro_validate::Severity;
use fabro_workflows::operations::{validate, ValidateInput, WorkflowInput};
use fabro_workflows::operations::{ValidateInput, WorkflowInput, validate};
use tracing::debug;
use crate::args::{GraphArgs, GraphDirection};

View file

@ -4,12 +4,12 @@ use std::net::SocketAddr;
use std::path::Path;
use std::process::{Command, Stdio};
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use axum::extract::Query;
use axum::response::Html;
use axum::routing::get;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use dialoguer::console::Term;
use dialoguer::theme::ColorfulTheme;
use dialoguer::{MultiSelect, Select};

View file

@ -1,8 +1,8 @@
use anyhow::Result;
use fabro_config::FabroSettings;
use fabro_llm::cli::{run_chat, ChatArgs};
use fabro_llm::cli::{ChatArgs, run_chat};
#[cfg(feature = "server")]
use fabro_llm::cli::{run_chat_via_server, ServerConnection};
use fabro_llm::cli::{ServerConnection, run_chat_via_server};
use crate::args::GlobalArgs;

View file

@ -1,8 +1,8 @@
use anyhow::Result;
use fabro_config::FabroSettings;
use fabro_llm::cli::{run_prompt, PromptArgs};
use fabro_llm::cli::{PromptArgs, run_prompt};
#[cfg(feature = "server")]
use fabro_llm::cli::{run_prompt_via_server, ServerConnection};
use fabro_llm::cli::{ServerConnection, run_prompt_via_server};
use crate::args::GlobalArgs;

View file

@ -1,7 +1,7 @@
use anyhow::Result;
#[cfg(feature = "server")]
use fabro_llm::cli::ServerConnection;
use fabro_llm::cli::{run_models, ModelsCommand};
use fabro_llm::cli::{ModelsCommand, run_models};
use crate::args::GlobalArgs;
#[cfg(feature = "server")]

View file

@ -1,6 +1,6 @@
use std::path::Path;
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use fabro_config::FabroSettingsExt;
use fabro_model::Catalog;
use fabro_sandbox::daytona::detect_repo_info;

View file

@ -5,18 +5,18 @@ use anyhow::bail;
use fabro_agent::{DockerSandbox, DockerSandboxConfig, LocalSandbox, Sandbox};
use fabro_config::cli::load_cli_config;
use fabro_config::project::{
resolve_settings, resolve_workflow_path, resolve_working_directory, ResolveSettingsInput,
ResolveSettingsInput, resolve_settings, resolve_workflow_path, resolve_working_directory,
};
use fabro_config::{FabroConfig, FabroSettings};
use fabro_graphviz::graph::{is_llm_handler_type, Graph};
use fabro_graphviz::graph::{Graph, is_llm_handler_type};
use fabro_llm::client::Client as LlmClient;
use fabro_model::{Catalog, Provider};
use fabro_sandbox::daytona::{detect_repo_info, DaytonaConfig, DaytonaSandbox};
use fabro_sandbox::ssh::{GitCloneParams as SshGitCloneParams, SshConfig, SshSandbox};
use fabro_sandbox::SandboxProvider;
use fabro_sandbox::daytona::{DaytonaConfig, DaytonaSandbox, detect_repo_info};
use fabro_sandbox::ssh::{GitCloneParams as SshGitCloneParams, SshConfig, SshSandbox};
use fabro_util::terminal::Styles;
use fabro_workflows::git::{sync_status, GitSyncStatus};
use fabro_workflows::operations::{validate, ValidateInput, WorkflowInput};
use fabro_workflows::git::{GitSyncStatus, sync_status};
use fabro_workflows::operations::{ValidateInput, WorkflowInput, validate};
use crate::args::PreflightArgs;
use crate::shared::github::build_github_app_credentials;

View file

@ -1,4 +1,4 @@
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
pub fn run_deinit() -> Result<()> {
let repo_root = super::init::git_repo_root()?;

View file

@ -1,6 +1,6 @@
use std::path::PathBuf;
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use tokio::task::spawn_blocking;
use crate::cli_config::load_cli_settings;

View file

@ -1,11 +1,11 @@
use std::io::{BufRead, BufReader, IsTerminal, Write};
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Duration, Instant};
use anyhow::{bail, Result};
use anyhow::{Result, bail};
use fabro_interview::{AnswerValue, ConsoleInterviewer};
use fabro_store::RuntimeState;
@ -490,7 +490,7 @@ mod tests {
use fabro_util::terminal::Styles;
use fabro_workflows::outcome::StageStatus;
use fabro_workflows::records::Conclusion;
use fabro_workflows::run_status::{write_run_status, StatusReason};
use fabro_workflows::run_status::{StatusReason, write_run_status};
fn no_color_styles() -> &'static Styles {
Box::leak(Box::new(Styles::new(false)))

View file

@ -1,10 +1,10 @@
use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use fabro_agent::sandbox::Sandbox;
use fabro_config::FabroSettingsExt;
use fabro_sandbox::reconnect::reconnect;
use fabro_sandbox::SandboxRecordExt;
use fabro_sandbox::reconnect::reconnect;
use fabro_workflows::run_lookup::{resolve_run, runs_base};
use tokio::fs;
use tracing::{debug, info};

View file

@ -1,11 +1,11 @@
use std::path::PathBuf;
use crate::args::RunArgs;
use fabro_config::project::{resolve_settings, ResolveSettingsInput};
use fabro_config::project::{ResolveSettingsInput, resolve_settings};
use fabro_config::{FabroConfig, FabroSettings};
use fabro_util::terminal::Styles;
use fabro_workflows::error::FabroError;
use fabro_workflows::operations::{create, CreateRunInput, WorkflowInput};
use fabro_workflows::operations::{CreateRunInput, WorkflowInput, create};
use super::output::{print_diagnostics_from_error, print_workflow_report_from_persisted};

View file

@ -6,7 +6,7 @@ use fabro_interview::FileInterviewer;
use fabro_store::RuntimeState;
use fabro_workflows::event::EventEmitter;
use fabro_workflows::git::GitAuthor;
use fabro_workflows::operations::{resume as resume_run, start as start_run, StartServices};
use fabro_workflows::operations::{StartServices, resume as resume_run, start as start_run};
use crate::cli_config;
use crate::shared;

View file

@ -1,10 +1,10 @@
use std::io::{self, IsTerminal, Write};
use std::path::Path;
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use fabro_config::FabroSettingsExt;
use fabro_sandbox::reconnect::reconnect;
use fabro_sandbox::SandboxRecordExt;
use fabro_sandbox::reconnect::reconnect;
use fabro_workflows::records::{StartRecord, StartRecordExt};
use fabro_workflows::run_lookup::{resolve_run, runs_base};
use fabro_workflows::sandbox_git::GIT_REMOTE;

View file

@ -3,7 +3,7 @@ use anyhow::Result;
use fabro_git_storage::gitobj::Store;
use fabro_util::terminal::Styles;
use fabro_workflows::operations::{
build_timeline, find_run_id_by_prefix, fork, ForkRunInput, RewindTarget,
ForkRunInput, RewindTarget, build_timeline, find_run_id_by_prefix, fork,
};
use git2::Repository;

View file

@ -1,7 +1,7 @@
use std::io::{self, BufRead, IsTerminal, Write};
use std::path::Path;
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use chrono::{DateTime, Utc};
use fabro_config::FabroSettingsExt;
use fabro_util::terminal::Styles;

View file

@ -1,6 +1,6 @@
use anyhow::Result;
use fabro_config::cli::load_cli_config;
use fabro_config::FabroSettingsExt;
use fabro_config::cli::load_cli_config;
use fabro_util::terminal::Styles;
use fabro_workflows::run_lookup::{resolve_run, runs_base};

View file

@ -6,7 +6,7 @@ use fabro_store::RuntimeState;
use fabro_util::terminal::Styles;
use fabro_util::text::strip_goal_decoration;
use fabro_workflows::asset_snapshot::collect_asset_paths;
use fabro_workflows::outcome::{format_cost, StageStatus};
use fabro_workflows::outcome::{StageStatus, format_cost};
use fabro_workflows::pipeline::{Persisted, Validated};
use fabro_workflows::pull_request::PullRequestRecord;
use fabro_workflows::records::{Checkpoint, CheckpointExt, Conclusion, ConclusionExt};

View file

@ -2,7 +2,7 @@ use std::collections::HashMap;
use anyhow::Result;
use fabro_config::run::LlmConfig;
use fabro_config::{sandbox as sandbox_config, FabroConfig};
use fabro_config::{FabroConfig, sandbox as sandbox_config};
use fabro_sandbox::SandboxProvider;
use crate::args::{PreflightArgs, RunArgs};

View file

@ -1,7 +1,7 @@
use anyhow::{Context, Result};
use fabro_config::FabroSettingsExt;
use fabro_sandbox::daytona::DaytonaSandbox;
use fabro_sandbox::SandboxRecordExt;
use fabro_sandbox::daytona::DaytonaSandbox;
use fabro_workflows::run_lookup::{resolve_run, runs_base};
use tracing::info;

View file

@ -1,11 +1,11 @@
use anyhow::Context;
use anyhow::Result;
use cli_table::format::{Border, Separator};
use cli_table::{print_stderr, Cell, CellStruct, Color, Style, Table};
use cli_table::{Cell, CellStruct, Color, Style, Table, print_stderr};
use fabro_git_storage::gitobj::Store;
use fabro_util::terminal::Styles;
use fabro_workflows::operations::{
build_timeline, find_run_id_by_prefix, rewind, RewindInput, RewindTarget, RunTimeline,
RewindInput, RewindTarget, RunTimeline, build_timeline, find_run_id_by_prefix, rewind,
};
use git2::Repository;

View file

@ -1,7 +1,7 @@
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use fabro_config::FabroSettingsExt;
use fabro_sandbox::daytona::DaytonaSandbox;
use fabro_sandbox::SandboxRecordExt;
use fabro_sandbox::daytona::DaytonaSandbox;
use fabro_workflows::run_lookup::{resolve_run, runs_base};
use tracing::info;

View file

@ -1,13 +1,13 @@
use std::path::Path;
use anyhow::{anyhow, Result};
use anyhow::{Result, anyhow};
use chrono::Utc;
use fabro_config::FabroSettingsExt;
use fabro_workflows::records::{RunRecord, RunRecordExt};
use super::launcher::{
launcher_log_path, launcher_record_path, remove_launcher_record, write_launcher_record,
LauncherRecord,
LauncherRecord, launcher_log_path, launcher_record_path, remove_launcher_record,
write_launcher_record,
};
/// Spawn a detached engine process for the given run directory.

View file

@ -1,6 +1,6 @@
use std::io::Write;
use anyhow::{bail, Result};
use anyhow::{Result, bail};
use fabro_config::FabroSettingsExt;
use fabro_util::terminal::Styles;
use fabro_workflows::records::{Conclusion, ConclusionExt};

View file

@ -3,12 +3,12 @@ use std::path::Path;
use anyhow::Result;
use chrono::Utc;
use cli_table::format::{Border, Separator};
use cli_table::{print_stdout, Cell, CellStruct, Color, Style, Table};
use cli_table::{Cell, CellStruct, Color, Style, Table, print_stdout};
use fabro_config::FabroSettingsExt;
use fabro_util::terminal::Styles;
use fabro_util::text::strip_goal_decoration;
use fabro_workflows::run_lookup::{filter_runs, runs_base, scan_runs, StatusFilter};
use fabro_workflows::run_lookup::{StatusFilter, filter_runs, runs_base, scan_runs};
use fabro_workflows::run_status::RunStatus;
use crate::args::RunsListArgs;

View file

@ -19,11 +19,7 @@ pub async fn dispatch(cmd: RunsCommands) -> Result<()> {
}
pub(super) fn short_run_id(id: &str) -> &str {
if id.len() > 12 {
&id[..12]
} else {
id
}
if id.len() > 12 { &id[..12] } else { id }
}
#[cfg(test)]

View file

@ -1,13 +1,13 @@
use std::path::Path;
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use fabro_config::FabroSettingsExt;
use fabro_sandbox::SandboxRecordExt;
use tracing::warn;
use fabro_sandbox::reconnect::reconnect as reconnect_sandbox;
use fabro_workflows::run_lookup::{resolve_run, runs_base};
use fabro_workflows::run_status::{write_run_status, RunStatus};
use fabro_workflows::run_status::{RunStatus, write_run_status};
use crate::args::RunsRemoveArgs;
use crate::cli_config::load_cli_settings;

View file

@ -1,4 +1,4 @@
use anyhow::{bail, Result};
use anyhow::{Result, bail};
use crate::args::SecretGetArgs;
use fabro_config::dotenv;

View file

@ -1,4 +1,4 @@
use anyhow::{bail, Result};
use anyhow::{Result, bail};
use crate::args::SecretListArgs;
use fabro_config::dotenv;

View file

@ -1,4 +1,4 @@
use anyhow::{bail, Result};
use anyhow::{Result, bail};
use crate::args::SecretRmArgs;
use fabro_config::dotenv;

View file

@ -1,6 +1,6 @@
use std::path::Path;
use anyhow::{bail, Result};
use anyhow::{Result, bail};
use tracing::{debug, info};
use crate::args::{SkillDir, SkillInstallArgs, SkillScope};

View file

@ -3,7 +3,7 @@ use std::path::Path;
use anyhow::Result;
use chrono::{DateTime, Utc};
use cli_table::format::{Border, Justify, Separator};
use cli_table::{print_stdout, Cell, CellStruct, Style, Table};
use cli_table::{Cell, CellStruct, Style, Table, print_stdout};
use fabro_config::FabroSettingsExt;
use fabro_workflows::run_lookup::{logs_base, runs_base, scan_runs};
@ -199,11 +199,7 @@ fn df_from(args: &DfArgs, data_dir: &Path, runs_base: &Path, logs_base: &Path) -
}
fn short_run_id(id: &str) -> &str {
if id.len() > 12 {
&id[..12]
} else {
id
}
if id.len() > 12 { &id[..12] } else { id }
}
fn truncate_str(s: &str, max_len: usize) -> String {

View file

@ -1,11 +1,11 @@
use std::path::Path;
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use chrono::Utc;
use fabro_config::FabroSettingsExt;
use tracing::{debug, info};
use fabro_workflows::run_lookup::{filter_runs, runs_base, scan_runs, StatusFilter};
use fabro_workflows::run_lookup::{StatusFilter, filter_runs, runs_base, scan_runs};
use crate::args::RunsPruneArgs;
use crate::cli_config::load_cli_settings;

View file

@ -2,7 +2,7 @@ use std::fs;
use std::io::{IsTerminal, Write};
use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use semver::Version;
use sha2::{Digest, Sha256};
use tracing::debug;

View file

@ -1,10 +1,10 @@
use anyhow::bail;
use fabro_config::cli::load_cli_config;
use fabro_config::project::{resolve_settings, resolve_workflow_path, ResolveSettingsInput};
use fabro_config::FabroConfig;
use fabro_config::cli::load_cli_config;
use fabro_config::project::{ResolveSettingsInput, resolve_settings, resolve_workflow_path};
use fabro_util::terminal::Styles;
use fabro_validate::Severity;
use fabro_workflows::operations::{validate, ValidateInput, WorkflowInput};
use fabro_workflows::operations::{ValidateInput, WorkflowInput, validate};
use crate::args::ValidateArgs;
use crate::shared::{print_diagnostics, relative_path};

View file

@ -1,6 +1,6 @@
use std::path::Path;
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use fabro_config::project::{discover_project_config, resolve_fabro_root};

View file

@ -1,9 +1,9 @@
use anyhow::{bail, Result};
use anyhow::{Result, bail};
use fabro_util::terminal::Styles;
use fabro_config::project::{
discover_project_config, list_workflows_detailed, resolve_fabro_root, WorkflowInfo,
WorkflowSource,
WorkflowInfo, WorkflowSource, discover_project_config, list_workflows_detailed,
resolve_fabro_root,
};
use crate::args::WorkflowListArgs;

View file

@ -1,7 +1,7 @@
use anyhow::{Context, Result};
use fabro_util::run_log;
use tracing_appender::rolling;
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
pub fn init_tracing(debug: bool, config_log_level: Option<&str>, log_prefix: &str) -> Result<()> {
let default_level = if debug {

View file

@ -7,7 +7,7 @@ mod shared;
mod sleep_inhibitor;
use anyhow::Result;
use args::{Commands, GlobalArgs, RunCommands, LONG_VERSION};
use args::{Commands, GlobalArgs, LONG_VERSION, RunCommands};
use clap::Parser;
use fabro_telemetry::{git, panic as tel_panic, sanitize, sender};
use fabro_util::terminal::Styles;

View file

@ -1,5 +1,5 @@
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use fabro_github::GitHubAppCredentials;
pub(crate) fn build_github_app_credentials(app_id: Option<&str>) -> Option<GitHubAppCredentials> {

View file

@ -6,7 +6,7 @@ use dialoguer::theme::ColorfulTheme;
use dialoguer::{Confirm, Password};
use fabro_config::dotenv::{merge_env, write_env_file as write_env};
use fabro_llm::client::Client as LlmClient;
use fabro_llm::generate::{generate, GenerateParams};
use fabro_llm::generate::{GenerateParams, generate};
use fabro_model::Provider;
use fabro_util::terminal::Styles;
use tokio::task::spawn_blocking;

View file

@ -1,7 +1,7 @@
use std::path::Path;
use std::time::Duration;
use anyhow::{bail, Result};
use anyhow::{Result, bail};
use cli_table::Color;
use fabro_util::terminal::Styles;
use fabro_validate::{Diagnostic, Severity};
@ -70,11 +70,7 @@ pub fn tilde_path(path: &Path) -> String {
}
pub fn color_if(use_color: bool, color: Color) -> Option<Color> {
if use_color {
Some(color)
} else {
None
}
if use_color { Some(color) } else { None }
}
pub fn split_run_path(s: &str) -> Option<(&str, &str)> {

View file

@ -1,6 +1,6 @@
use assert_cmd::Command;
use fabro_config::mcp::McpTransport;
use fabro_config::FabroSettings;
use fabro_config::mcp::McpTransport;
use fabro_store::RuntimeState;
use predicates::prelude::*;
@ -1450,9 +1450,11 @@ fn attach_supports_legacy_root_interview_paths() {
assert!(run_dir.join("interview_request.json").exists());
assert!(run_dir.join("interview_response.json").exists());
assert!(!RuntimeState::new(&run_dir)
.interview_response_path()
.exists());
assert!(
!RuntimeState::new(&run_dir)
.interview_response_path()
.exists()
);
}
// Bug 4: attach should respect the verbose flag from run.json.
@ -1594,14 +1596,16 @@ fn config_show_workflow_name_applies_run_overlay_and_deep_merges() {
.find(|hook| hook.name.as_deref() == Some("shared"))
.expect("shared hook");
assert_eq!(shared_hook.command.as_deref(), Some("echo run"));
assert!(cfg
.hooks
.iter()
.any(|hook| hook.name.as_deref() == Some("project")));
assert!(cfg
.hooks
.iter()
.any(|hook| hook.name.as_deref() == Some("run-only")));
assert!(
cfg.hooks
.iter()
.any(|hook| hook.name.as_deref() == Some("project"))
);
assert!(
cfg.hooks
.iter()
.any(|hook| hook.name.as_deref() == Some("run-only"))
);
match &cfg.mcp_servers["shared"].transport {
McpTransport::Stdio { command, .. } => assert_eq!(command, &vec!["echo", "run"]),

View file

@ -2,7 +2,7 @@
use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
/// Return the path to `~/.fabro/.env`.
pub fn env_file_path() -> Result<PathBuf> {

View file

@ -1,11 +1,11 @@
use std::path::{Path, PathBuf};
use anyhow::{bail, Context};
use anyhow::{Context, bail};
use serde::{Deserialize, Serialize};
use crate::FabroSettings;
use crate::config::FabroConfig;
use crate::run;
use crate::FabroSettings;
pub use fabro_types::settings::project::ProjectFabroSettings;
const CONFIG_FILENAME: &str = "fabro.toml";

View file

@ -1,7 +1,7 @@
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use anyhow::{bail, Context};
use anyhow::{Context, bail};
use serde::{Deserialize, Serialize};
use tracing::debug;

View file

@ -43,7 +43,9 @@ pub enum CoreError {
Cancelled,
#[error("blocked: {message}")]
Blocked { message: String },
#[error("node \"{node_id}\" visited {visits} times ({limit_source} limit {limit}); run is stuck in a cycle")]
#[error(
"node \"{node_id}\" visited {visits} times ({limit_source} limit {limit}); run is stuck in a cycle"
)]
VisitLimitExceeded {
node_id: String,
visits: usize,
@ -177,10 +179,12 @@ mod tests {
assert!(!CoreError::NodeNotFound { id: "x".into() }.is_retryable());
assert!(!CoreError::Cancelled.is_retryable());
assert!(!CoreError::NoStartNode.is_retryable());
assert!(!CoreError::Blocked {
message: "no".into()
}
.is_retryable());
assert!(
!CoreError::Blocked {
message: "no".into()
}
.is_retryable()
);
assert!(!CoreError::Other("err".into()).is_retryable());
}
}

View file

@ -1,5 +1,5 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Instant;
use tokio_util::sync::CancellationToken;

View file

@ -266,7 +266,7 @@ mod tests {
use std::sync::{Arc, Mutex};
use super::*;
use crate::test_fixtures::{linear_graph, TestGraph, TestNode};
use crate::test_fixtures::{TestGraph, TestNode, linear_graph};
/// A lifecycle that records which callbacks were called.
struct RecordingLifecycle {

View file

@ -1,5 +1,5 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use tokio::sync::Notify;

View file

@ -1,6 +1,6 @@
use std::collections::HashMap;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use async_trait::async_trait;

View file

@ -4,8 +4,8 @@ pub mod workflow_run;
use std::path::Path;
use std::time::Duration;
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions};
use sqlx::SqlitePool;
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions};
use tracing::debug;
pub use migrate::initialize_db;

View file

@ -5,8 +5,8 @@ use tokio::fs;
use tokio::process::Command;
use tracing::info;
use crate::types::{FeatureMetadata, LifecycleCommand};
use crate::DevcontainerError;
use crate::types::{FeatureMetadata, LifecycleCommand};
/// A resolved feature layer ready to be inserted into a Dockerfile.
#[derive(Debug, Clone)]
@ -1123,9 +1123,11 @@ mod tests {
let resolved = resolve_features(&features, tmp.path(), None).await.unwrap();
assert_eq!(resolved.layers.len(), 1);
assert_eq!(resolved.layers[0].dir_name, "node");
assert!(resolved.layers[0]
.dockerfile_snippet
.contains("export VERSION=\"20\""));
assert!(
resolved.layers[0]
.dockerfile_snippet
.contains("export VERSION=\"20\"")
);
}
#[test]

View file

@ -83,7 +83,9 @@ pub enum DevcontainerError {
#[error("variable substitution error: {0}")]
Variable(String),
#[error("base Dockerfile contains COPY or ADD instructions that reference build context files, which is not supported by Daytona snapshots: {0}")]
#[error(
"base Dockerfile contains COPY or ADD instructions that reference build context files, which is not supported by Daytona snapshots: {0}"
)]
UnsupportedCopyAdd(String),
}

View file

@ -301,9 +301,10 @@ mod tests {
}"#;
let meta: FeatureMetadata = serde_json::from_str(json).unwrap();
assert_eq!(meta.depends_on.len(), 2);
assert!(meta
.depends_on
.contains_key("ghcr.io/devcontainers/features/common-utils:1"));
assert!(
meta.depends_on
.contains_key("ghcr.io/devcontainers/features/common-utils:1")
);
assert_eq!(
meta.depends_on.get("ghcr.io/devcontainers/features/node:1"),
Some(&serde_json::json!({"version": "20"}))

View file

@ -242,9 +242,11 @@ async fn remote_env_excluded_from_dockerfile() {
.unwrap();
// containerEnv IS in the Dockerfile
assert!(config
.dockerfile
.contains("ENV DEBIAN_FRONTEND=noninteractive"));
assert!(
config
.dockerfile
.contains("ENV DEBIAN_FRONTEND=noninteractive")
);
// remoteEnv is NOT in the Dockerfile
assert!(!config.dockerfile.contains("EDITOR=code"));
@ -340,9 +342,11 @@ async fn local_feature_refs_resolved() {
.unwrap();
// Base image preserved
assert!(config
.dockerfile
.contains("FROM mcr.microsoft.com/devcontainers/base:ubuntu"));
assert!(
config
.dockerfile
.contains("FROM mcr.microsoft.com/devcontainers/base:ubuntu")
);
// Feature install.sh snippets are in the Dockerfile
assert!(config.dockerfile.contains("node-feature"));
@ -381,9 +385,11 @@ async fn feature_container_env_merged() {
// Feature containerEnv values baked into Dockerfile
assert!(config.dockerfile.contains("ENV NODE_INSTALLED=true"));
assert!(config
.dockerfile
.contains("ENV NODE_PATH=/usr/local/lib/node_modules"));
assert!(
config
.dockerfile
.contains("ENV NODE_PATH=/usr/local/lib/node_modules")
);
assert!(config.dockerfile.contains("ENV PYTHON_INSTALLED=true"));
assert!(config.dockerfile.contains("ENV BASE_UTILS_INSTALLED=true"));

View file

@ -13,9 +13,11 @@ async fn resolve_image_only() {
.await
.unwrap();
assert!(config
.dockerfile
.contains("FROM mcr.microsoft.com/devcontainers/base:ubuntu"));
assert!(
config
.dockerfile
.contains("FROM mcr.microsoft.com/devcontainers/base:ubuntu")
);
assert_eq!(config.remote_user.as_deref(), Some("vscode"));
assert_eq!(config.forwarded_ports, vec![3000, 80, 9090]);
assert_eq!(
@ -34,9 +36,11 @@ async fn resolve_image_only() {
assert!(matches!(&config.on_create_commands[0], Command::Shell(s) if s == "setup.sh"));
// containerEnv baked into Dockerfile
assert!(config
.dockerfile
.contains("ENV DEBIAN_FRONTEND=noninteractive"));
assert!(
config
.dockerfile
.contains("ENV DEBIAN_FRONTEND=noninteractive")
);
assert_eq!(
config
.container_env
@ -151,9 +155,11 @@ async fn resolve_subdirectory_mode() {
.await
.unwrap();
assert!(config
.dockerfile
.contains("FROM mcr.microsoft.com/devcontainers/python:3.12"));
assert!(
config
.dockerfile
.contains("FROM mcr.microsoft.com/devcontainers/python:3.12")
);
assert_eq!(config.remote_user.as_deref(), Some("vscode"));
assert_eq!(config.workspace_folder, "/workspaces/subdirectory-mode");
}
@ -165,9 +171,11 @@ async fn resolve_subdirectory_multiple_picks_alphabetical_first() {
.unwrap();
// "alpha" sorts before "beta", so alpha's config is used
assert!(config
.dockerfile
.contains("FROM mcr.microsoft.com/devcontainers/base:ubuntu"));
assert!(
config
.dockerfile
.contains("FROM mcr.microsoft.com/devcontainers/base:ubuntu")
);
assert_eq!(config.remote_user.as_deref(), Some("alpha-user"));
}
@ -178,9 +186,11 @@ async fn resolve_subdirectory_standard_wins_over_subdirs() {
.unwrap();
// Standard .devcontainer/devcontainer.json takes priority over subdirectory format
assert!(config
.dockerfile
.contains("FROM mcr.microsoft.com/devcontainers/base:ubuntu"));
assert!(
config
.dockerfile
.contains("FROM mcr.microsoft.com/devcontainers/base:ubuntu")
);
assert_eq!(config.remote_user.as_deref(), Some("standard-user"));
}
@ -191,9 +201,11 @@ async fn generated_dockerfile_is_well_formed() {
.unwrap();
// Should start with the generated header
assert!(config
.dockerfile
.contains("# Generated by fabro-devcontainer"));
assert!(
config
.dockerfile
.contains("# Generated by fabro-devcontainer")
);
// Should have the base image
assert!(config.dockerfile.contains("FROM"));
// Should end with a newline

View file

@ -1,8 +1,8 @@
use git2::{Oid, Signature};
use tracing::{debug, warn};
use crate::gitobj::{FileMode, Store, TreeEntries};
use crate::Result;
use crate::gitobj::{FileMode, Store, TreeEntries};
/// Metadata about a commit, returned by `log`.
#[derive(Debug)]

View file

@ -4,8 +4,8 @@ use std::path::PathBuf;
use git2::{Oid, Signature};
use tracing::{debug, warn};
use crate::gitobj::{FileMode, Store, TreeEntries};
use crate::Result;
use crate::gitobj::{FileMode, Store, TreeEntries};
/// Options for writing a snapshot.
pub struct WriteOptions<'a> {
@ -388,10 +388,11 @@ mod tests {
.unwrap(),
b"new content"
);
assert!(snap
.read_file(result.commit_oid, "to_delete.txt")
.unwrap()
.is_none());
assert!(
snap.read_file(result.commit_oid, "to_delete.txt")
.unwrap()
.is_none()
);
}
// -- write embeds in-memory metadata --

View file

@ -91,7 +91,7 @@ pub fn parse_github_owner_repo(url: &str) -> Result<(String, String), String> {
///
/// The JWT is valid for 10 minutes with a 60-second clock skew allowance.
pub fn sign_app_jwt(app_id: &str, private_key_pem: &str) -> Result<String, String> {
use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
use serde::Serialize;
#[derive(Serialize)]
@ -556,12 +556,12 @@ pub async fn get_authenticated_app(
401 => {
return Err("GitHub App authentication failed. \
Check that app_id and GITHUB_APP_PRIVATE_KEY are correct."
.to_string())
.to_string());
}
status => {
return Err(format!(
"Unexpected status {status} fetching GitHub App info"
))
));
}
}
@ -670,13 +670,13 @@ pub async fn get_pull_request(
404 => {
return Err(format!(
"Pull request #{number} not found in {owner}/{repo}"
))
));
}
401 | 403 => {
return Err(format!(
"Authentication failed fetching pull request ({})",
resp.status()
))
));
}
status => {
let body = resp.text().await.unwrap_or_default();

View file

@ -1,3 +1,4 @@
use nom::IResult;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::{char, multispace0};
@ -5,7 +6,6 @@ use nom::combinator::opt;
use nom::error::{Error, ParseError};
use nom::multi::{many0, separated_list0};
use nom::sequence::{delimited, preceded, tuple};
use nom::IResult;
use crate::parser::ast::{
AstValue, AttrBlock, DotGraph, EdgeStmt, NodeStmt, Statement, SubgraphStmt,

View file

@ -179,9 +179,11 @@ mod tests {
}"#;
let graph = parse(input).unwrap();
assert!(graph.nodes["plan"].classes.contains(&"loop-a".to_string()));
assert!(graph.nodes["implement"]
.classes
.contains(&"loop-a".to_string()));
assert!(
graph.nodes["implement"]
.classes
.contains(&"loop-a".to_string())
);
}
#[test]

View file

@ -5,10 +5,10 @@ use std::sync::{Arc, LazyLock};
use std::time::Instant;
use async_trait::async_trait;
use fabro_agent::tool_registry::ToolContext;
use fabro_agent::Sandbox;
use fabro_agent::tool_registry::ToolContext;
use fabro_llm::client::Client as LlmClient;
use fabro_llm::generate::{generate_object, GenerateParams};
use fabro_llm::generate::{GenerateParams, generate_object};
use fabro_llm::types::{Message, Request, ToolResult};
use fabro_util::env::{Env, SystemEnv};
use tokio::process::Command as TokioCommand;

View file

@ -4,11 +4,11 @@ use dialoguer::console::Term;
use dialoguer::theme::ColorfulTheme;
use std::time::Duration;
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use clap::{Args, Subcommand};
use cli_table::format::{Border, Justify, Separator};
use cli_table::{print_stdout, Cell, CellStruct, Color, Style, Table};
use futures::{stream, StreamExt};
use cli_table::{Cell, CellStruct, Color, Style, Table, print_stdout};
use futures::{StreamExt, stream};
use serde::Deserialize;
use tokio::task;
use tokio::time;
@ -118,11 +118,7 @@ fn format_speed(tps: Option<f64>) -> String {
}
fn color_if(use_color: bool, color: Color) -> Option<Color> {
if use_color {
Some(color)
} else {
None
}
if use_color { Some(color) } else { None }
}
fn model_row(model: &Model, use_color: bool) -> Vec<CellStruct> {

View file

@ -291,7 +291,7 @@ pub fn error_from_status_code(
return SdkError::RequestTimeout {
message: detail.message,
source: None,
}
};
}
413 => ProviderErrorKind::ContextLength,
429 => ProviderErrorKind::RateLimit,
@ -349,7 +349,7 @@ pub fn error_from_grpc_status(
return SdkError::RequestTimeout {
message: detail.message,
source: None,
}
};
}
_ => ProviderErrorKind::Server,
};
@ -899,102 +899,132 @@ mod tests {
fn failover_eligible_transient_provider_errors() {
let detail = || Box::new(ProviderErrorDetail::new("error", "openai"));
assert!(SdkError::Provider {
kind: ProviderErrorKind::RateLimit,
detail: detail(),
}
.failover_eligible());
assert!(
SdkError::Provider {
kind: ProviderErrorKind::RateLimit,
detail: detail(),
}
.failover_eligible()
);
assert!(SdkError::Provider {
kind: ProviderErrorKind::Server,
detail: detail(),
}
.failover_eligible());
assert!(
SdkError::Provider {
kind: ProviderErrorKind::Server,
detail: detail(),
}
.failover_eligible()
);
assert!(SdkError::Provider {
kind: ProviderErrorKind::QuotaExceeded,
detail: detail(),
}
.failover_eligible());
assert!(
SdkError::Provider {
kind: ProviderErrorKind::QuotaExceeded,
detail: detail(),
}
.failover_eligible()
);
}
#[test]
fn failover_eligible_transient_non_provider_errors() {
assert!(SdkError::RequestTimeout {
message: "timed out".into(),
source: None,
}
.failover_eligible());
assert!(
SdkError::RequestTimeout {
message: "timed out".into(),
source: None,
}
.failover_eligible()
);
assert!(SdkError::Network {
message: "refused".into(),
source: None,
}
.failover_eligible());
assert!(
SdkError::Network {
message: "refused".into(),
source: None,
}
.failover_eligible()
);
assert!(SdkError::Stream {
message: "broken".into(),
source: None,
}
.failover_eligible());
assert!(
SdkError::Stream {
message: "broken".into(),
source: None,
}
.failover_eligible()
);
}
#[test]
fn failover_not_eligible_deterministic_errors() {
let detail = || Box::new(ProviderErrorDetail::new("error", "openai"));
assert!(!SdkError::Provider {
kind: ProviderErrorKind::Authentication,
detail: detail(),
}
.failover_eligible());
assert!(
!SdkError::Provider {
kind: ProviderErrorKind::Authentication,
detail: detail(),
}
.failover_eligible()
);
assert!(!SdkError::Provider {
kind: ProviderErrorKind::InvalidRequest,
detail: detail(),
}
.failover_eligible());
assert!(
!SdkError::Provider {
kind: ProviderErrorKind::InvalidRequest,
detail: detail(),
}
.failover_eligible()
);
assert!(!SdkError::Provider {
kind: ProviderErrorKind::ContextLength,
detail: detail(),
}
.failover_eligible());
assert!(
!SdkError::Provider {
kind: ProviderErrorKind::ContextLength,
detail: detail(),
}
.failover_eligible()
);
assert!(!SdkError::Provider {
kind: ProviderErrorKind::ContentFilter,
detail: detail(),
}
.failover_eligible());
assert!(
!SdkError::Provider {
kind: ProviderErrorKind::ContentFilter,
detail: detail(),
}
.failover_eligible()
);
}
#[test]
fn failover_not_eligible_non_provider_errors() {
assert!(!SdkError::Configuration {
message: "bad".into(),
source: None,
}
.failover_eligible());
assert!(
!SdkError::Configuration {
message: "bad".into(),
source: None,
}
.failover_eligible()
);
assert!(!SdkError::Abort {
message: "cancelled".into()
}
.failover_eligible());
assert!(
!SdkError::Abort {
message: "cancelled".into()
}
.failover_eligible()
);
assert!(!SdkError::InvalidToolCall {
message: "bad".into()
}
.failover_eligible());
assert!(
!SdkError::InvalidToolCall {
message: "bad".into()
}
.failover_eligible()
);
assert!(!SdkError::NoObjectGenerated {
message: "none".into()
}
.failover_eligible());
assert!(
!SdkError::NoObjectGenerated {
message: "none".into()
}
.failover_eligible()
);
assert!(!SdkError::UnsupportedToolChoice {
message: "nope".into()
}
.failover_eligible());
assert!(
!SdkError::UnsupportedToolChoice {
message: "nope".into()
}
.failover_eligible()
);
}
#[test]

View file

@ -2,18 +2,18 @@ use crate::client::Client;
use crate::error::SdkError;
use crate::provider::StreamEventStream;
use crate::retry::retry;
use crate::tools::{execute_all_tools_with_repair, RepairToolCallFn, Tool};
use crate::tools::{RepairToolCallFn, Tool, execute_all_tools_with_repair};
use crate::types::{
FinishReason, GenerateResult, Message, ObjectStreamEvent, ReasoningEffort, Request, Response,
ResponseFormat, ResponseFormatType, RetryPolicy, StepResult, StreamEvent, TimeoutConfig,
ToolCall, ToolChoice, ToolDefinition, Usage,
};
use fabro_util::backoff::BackoffPolicy;
use futures::{future, stream, Stream, StreamExt};
use futures::{Stream, StreamExt, future, stream};
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use tokio::sync::{mpsc, OnceCell};
use tokio::sync::{OnceCell, mpsc};
use tokio::time;
use tokio_stream::wrappers::ReceiverStream;
use tokio_util::sync::CancellationToken;
@ -1111,8 +1111,8 @@ mod tests {
use crate::client::Client;
use crate::provider::ProviderAdapter;
use crate::types::{ContentPart, Role};
use futures::stream;
use futures::StreamExt;
use futures::stream;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU32, Ordering};

View file

@ -1,8 +1,8 @@
use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine};
use base64::{Engine, engine::general_purpose::STANDARD as BASE64_STANDARD};
use futures::stream;
use crate::error::{error_from_status_code, SdkError};
use crate::provider::{validate_tool_choice, ProviderAdapter, StreamEventStream};
use crate::error::{SdkError, error_from_status_code};
use crate::provider::{ProviderAdapter, StreamEventStream, validate_tool_choice};
use crate::providers::common::{
self as common, extract_system_prompt, parse_error_body, parse_rate_limit_headers,
parse_retry_after, send_and_read_response,

View file

@ -1,6 +1,6 @@
use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine};
use base64::{Engine, engine::general_purpose::STANDARD as BASE64_STANDARD};
use crate::error::{error_from_status_code, SdkError};
use crate::error::{SdkError, error_from_status_code};
use crate::types::{Message, RateLimitInfo, Role};
use reqwest::header::HeaderMap;
use tokio::time;

View file

@ -1,4 +1,4 @@
use crate::error::{error_from_status_code, SdkError};
use crate::error::{SdkError, error_from_status_code};
use crate::provider::{ProviderAdapter, StreamEventStream};
use crate::providers::common::LineReader;
use crate::types::{FinishReason, Message, Request, Response, StreamEvent, Usage};

View file

@ -1,11 +1,11 @@
use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine};
use base64::{Engine, engine::general_purpose::STANDARD as BASE64_STANDARD};
use futures::stream;
use crate::error::{
error_from_grpc_status, error_from_status_code, ProviderErrorDetail, ProviderErrorKind,
SdkError,
ProviderErrorDetail, ProviderErrorKind, SdkError, error_from_grpc_status,
error_from_status_code,
};
use crate::provider::{validate_tool_choice, ProviderAdapter, StreamEventStream};
use crate::provider::{ProviderAdapter, StreamEventStream, validate_tool_choice};
use crate::providers::common::{
self as common, extract_system_prompt, parse_error_body, parse_rate_limit_headers,
parse_retry_after,

View file

@ -1,8 +1,8 @@
use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine};
use futures::{stream, StreamExt};
use base64::{Engine, engine::general_purpose::STANDARD as BASE64_STANDARD};
use futures::{StreamExt, stream};
use crate::error::{error_from_status_code, SdkError};
use crate::provider::{validate_tool_choice, ProviderAdapter, StreamEventStream};
use crate::error::{SdkError, error_from_status_code};
use crate::provider::{ProviderAdapter, StreamEventStream, validate_tool_choice};
use crate::providers::common::{
self as common, parse_error_body, parse_rate_limit_headers, parse_retry_after,
send_and_read_response,

View file

@ -1,7 +1,7 @@
use futures::{stream, StreamExt};
use futures::{StreamExt, stream};
use crate::error::{error_from_status_code, ProviderErrorDetail, ProviderErrorKind, SdkError};
use crate::provider::{validate_tool_choice, ProviderAdapter, StreamEventStream};
use crate::error::{ProviderErrorDetail, ProviderErrorKind, SdkError, error_from_status_code};
use crate::provider::{ProviderAdapter, StreamEventStream, validate_tool_choice};
use crate::providers::common::{
parse_error_body, parse_rate_limit_headers, parse_retry_after, send_and_read_response,
};

Some files were not shown because too many files have changed in this diff Show more