diff --git a/Cargo.toml b/Cargo.toml index 1ced0b8b4..7411f5474 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,17 +88,9 @@ too_many_arguments = "allow" too_many_lines = "allow" used_underscore_binding = "allow" if_not_else = "allow" -cast_possible_truncation = "allow" cast_possible_wrap = "allow" cast_precision_loss = "allow" -cast_sign_loss = "allow" doc_markdown = "allow" -items_after_statements = "allow" -needless_pass_by_value = "allow" -return_self_not_must_use = "allow" -uninlined_format_args = "allow" -unreadable_literal = "allow" -unnested_or_patterns = "allow" # Disallowed restriction lints print_stdout = "warn" print_stderr = "warn" diff --git a/lib/crates/fabro-agent/src/agent_profile.rs b/lib/crates/fabro-agent/src/agent_profile.rs index f717ded39..3bf3c9f51 100644 --- a/lib/crates/fabro-agent/src/agent_profile.rs +++ b/lib/crates/fabro-agent/src/agent_profile.rs @@ -38,7 +38,7 @@ pub trait AgentProfile: Send + Sync { fn context_window_size(&self) -> usize { Catalog::builtin() .get(self.model()) - .map(|m| m.context_window() as usize) + .map(|m| usize::try_from(m.context_window()).unwrap()) .unwrap_or(200_000) } diff --git a/lib/crates/fabro-agent/src/cli.rs b/lib/crates/fabro-agent/src/cli.rs index d5f369b28..f12edb36b 100644 --- a/lib/crates/fabro-agent/src/cli.rs +++ b/lib/crates/fabro-agent/src/cli.rs @@ -110,8 +110,7 @@ fn tool_category(name: &str) -> &'static str { fn is_auto_approved(level: PermissionLevel, category: &str) -> bool { matches!( (level, category), - (_, "read") - | (_, "subagent") + (_, "read" | "subagent") | (PermissionLevel::ReadWrite | PermissionLevel::Full, "write") | (PermissionLevel::Full, "shell") ) diff --git a/lib/crates/fabro-agent/src/mcp_integration.rs b/lib/crates/fabro-agent/src/mcp_integration.rs index 51bebae86..375d1d1d1 100644 --- a/lib/crates/fabro-agent/src/mcp_integration.rs +++ b/lib/crates/fabro-agent/src/mcp_integration.rs @@ -6,12 +6,12 @@ use fabro_mcp::connection_manager::{McpConnectionManager, call_result_to_string} use crate::tool_registry::RegisteredTool; /// Create `RegisteredTool` instances for every tool exposed by connected MCP servers. -pub fn make_mcp_tools(manager: Arc) -> Vec { +pub fn make_mcp_tools(manager: &Arc) -> Vec { manager .all_tools() .iter() .map(|(qualified_name, info)| { - let mgr = Arc::clone(&manager); + let mgr = Arc::clone(manager); let name = qualified_name.clone(); let tool_timeout = std::time::Duration::from_secs(120); @@ -67,7 +67,7 @@ mod tests { let mut mgr = McpConnectionManager::new(); mgr.start_servers(&[config]).await; - let tools = make_mcp_tools(Arc::new(mgr)); + let tools = make_mcp_tools(&Arc::new(mgr)); assert_eq!(tools.len(), 1); assert_eq!(tools[0].definition.name, "mcp__test_echo__echo"); assert_eq!(tools[0].definition.description, "Echo back the message"); @@ -79,7 +79,7 @@ mod tests { let mut mgr = McpConnectionManager::new(); mgr.start_servers(&[config]).await; - let tools = make_mcp_tools(Arc::new(mgr)); + let tools = make_mcp_tools(&Arc::new(mgr)); let tool = &tools[0]; use crate::sandbox::Sandbox; diff --git a/lib/crates/fabro-agent/src/session.rs b/lib/crates/fabro-agent/src/session.rs index 3bda269bb..934c7b15e 100644 --- a/lib/crates/fabro-agent/src/session.rs +++ b/lib/crates/fabro-agent/src/session.rs @@ -165,7 +165,7 @@ impl Session { } let manager = Arc::new(manager); - let mcp_tools = mcp_integration::make_mcp_tools(manager); + let mcp_tools = mcp_integration::make_mcp_tools(&manager); if let Some(profile) = Arc::get_mut(&mut self.provider_profile) { for tool in mcp_tools { profile.tool_registry_mut().register(tool); @@ -588,6 +588,8 @@ impl Session { } async fn run_single_input(&mut self, input: &str) -> Result<(), AgentError> { + const STREAM_CONSUME_RETRIES: usize = 3; + if self.state == SessionState::Closed { return Err(AgentError::SessionClosed); } @@ -700,7 +702,6 @@ impl Session { // Consume the stream, retrying up to 3 times if the provider // closes the stream without sending a Finish event. If visible // output was already emitted, clear it before replaying the turn. - const STREAM_CONSUME_RETRIES: usize = 3; let mut response = None; for stream_attempt in 0..=STREAM_CONSUME_RETRIES { diff --git a/lib/crates/fabro-agent/src/subagent.rs b/lib/crates/fabro-agent/src/subagent.rs index 6b8105665..cab3e6669 100644 --- a/lib/crates/fabro-agent/src/subagent.rs +++ b/lib/crates/fabro-agent/src/subagent.rs @@ -346,7 +346,7 @@ pub fn make_spawn_agent_tool( let max_turns = args .get("max_turns") .and_then(serde_json::Value::as_u64) - .map(|v| v as usize); + .map(|v| usize::try_from(v).unwrap()); // Note: working_dir and model require session factory changes to wire through let mut session = session_factory(); diff --git a/lib/crates/fabro-agent/src/tool_execution.rs b/lib/crates/fabro-agent/src/tool_execution.rs index 189e48c1c..13de03cdb 100644 --- a/lib/crates/fabro-agent/src/tool_execution.rs +++ b/lib/crates/fabro-agent/src/tool_execution.rs @@ -190,7 +190,7 @@ async fn execute_and_emit_one_tool_with_lookup( debug!(tool = %tc.name, hook_event = "pre_tool_use", "Calling tool hook"); let start = std::time::Instant::now(); let decision = hooks.pre_tool_use(&tc.name, &tc.arguments).await; - let elapsed = start.elapsed().as_millis() as u64; + let elapsed = u64::try_from(start.elapsed().as_millis()).unwrap(); debug!(tool = %tc.name, hook_event = "pre_tool_use", ?decision, duration_ms = elapsed, "Tool hook complete"); if let ToolHookDecision::Block { reason } = decision { diff --git a/lib/crates/fabro-agent/src/tools.rs b/lib/crates/fabro-agent/src/tools.rs index 252476efc..ecdbf067c 100644 --- a/lib/crates/fabro-agent/src/tools.rs +++ b/lib/crates/fabro-agent/src/tools.rs @@ -88,8 +88,8 @@ pub fn make_read_file_tool() -> RegisteredTool { let offset = args.get("offset").and_then(serde_json::Value::as_u64); let limit = args.get("limit").and_then(serde_json::Value::as_u64); - let offset_usize = offset.map(|v| v as usize); - let limit_usize = limit.map(|v| v as usize); + let offset_usize = offset.map(|v| usize::try_from(v).unwrap()); + let limit_usize = limit.map(|v| usize::try_from(v).unwrap()); let content = ctx .env @@ -279,6 +279,10 @@ pub fn make_grep_tool() -> RegisteredTool { .and_then(serde_json::Value::as_str) .unwrap_or("."); + let max_results = args + .get("max_results") + .and_then(serde_json::Value::as_u64) + .map(|v| usize::try_from(v).unwrap()); let options = GrepOptions { glob_filter: args .get("glob_filter") @@ -288,10 +292,7 @@ pub fn make_grep_tool() -> RegisteredTool { .get("case_insensitive") .and_then(serde_json::Value::as_bool) .unwrap_or(false), - max_results: args - .get("max_results") - .and_then(serde_json::Value::as_u64) - .map(|v| v as usize), + max_results, }; let results = ctx.env.grep(pattern, path, &options).await?; @@ -402,7 +403,7 @@ pub(crate) fn make_list_dir_tool() -> RegisteredTool { let depth = args .get("depth") .and_then(serde_json::Value::as_u64) - .map(|v| v as usize); + .map(|v| usize::try_from(v).unwrap()); let entries = ctx.env.list_directory(path, depth).await?; let lines: Vec = entries diff --git a/lib/crates/fabro-api/src/demo/mod.rs b/lib/crates/fabro-api/src/demo/mod.rs index 2ce3b669f..a5175435c 100644 --- a/lib/crates/fabro-api/src/demo/mod.rs +++ b/lib/crates/fabro-api/src/demo/mod.rs @@ -1,6 +1,6 @@ //! Demo mode handlers that return static data for all API endpoints. //! Activated per-request via the `X-Fabro-Demo: 1` header to showcase the UI without a real backend. -#![allow(clippy::default_trait_access)] +#![allow(clippy::default_trait_access, clippy::unreadable_literal)] use std::sync::Arc; diff --git a/lib/crates/fabro-api/src/jwt_auth.rs b/lib/crates/fabro-api/src/jwt_auth.rs index b16ac7041..1b01f1524 100644 --- a/lib/crates/fabro-api/src/jwt_auth.rs +++ b/lib/crates/fabro-api/src/jwt_auth.rs @@ -69,7 +69,7 @@ pub fn decode_pem_env(name: &str, value: &str) -> String { /// /// Call this once at startup before serving requests. Panics if the /// configuration is invalid (JWT strategy but no public key, or mTLS without TLS config). -pub fn resolve_auth_mode(api_config: &ApiSettings, allowed_usernames: Vec) -> AuthMode { +pub fn resolve_auth_mode(api_config: &ApiSettings, allowed_usernames: &[String]) -> AuthMode { use fabro_config::server::ApiAuthStrategy; if api_config.authentication_strategies.is_empty() { @@ -93,7 +93,7 @@ pub fn resolve_auth_mode(api_config: &ApiSettings, allowed_usernames: Vec { diff --git a/lib/crates/fabro-api/src/serve.rs b/lib/crates/fabro-api/src/serve.rs index 7534bda2c..492d86016 100644 --- a/lib/crates/fabro-api/src/serve.rs +++ b/lib/crates/fabro-api/src/serve.rs @@ -127,7 +127,7 @@ pub async fn serve_command(args: ServeArgs, styles: &'static Styles) -> anyhow:: .as_ref() .map(|w| w.auth.allowed_usernames.clone()) .unwrap_or_default(); - let auth_mode = resolve_auth_mode(&api, allowed_usernames); + let auth_mode = resolve_auth_mode(&api, &allowed_usernames); let client_auth = api.tls.as_ref().map(|_| client_auth_from_mode(&auth_mode)); let max_concurrent_runs = args .max_concurrent_runs diff --git a/lib/crates/fabro-api/src/sessions.rs b/lib/crates/fabro-api/src/sessions.rs index 83c868b1d..01a56ffb8 100644 --- a/lib/crates/fabro-api/src/sessions.rs +++ b/lib/crates/fabro-api/src/sessions.rs @@ -90,6 +90,7 @@ fn turns_to_messages(turns: &[fabro_api_types::SessionTurn]) -> Vec fn spawn_generation(store: SessionStore, session_id: uuid::Uuid, dry_run: bool, seq_at_start: u64) { tokio::spawn(async move { + use futures_util::StreamExt; let (event_tx, model_id, model_provider, system_prompt, messages, generation_seq) = { let store = store.read().expect("session store lock poisoned"); let Some(session) = store.get(&session_id) else { @@ -156,7 +157,6 @@ fn spawn_generation(store: SessionStore, session_id: uuid::Uuid, dry_run: bool, } }; - use futures_util::StreamExt; let mut stream_result = stream_result; let mut full_text = String::new(); while let Some(event) = stream_result.next().await { @@ -321,6 +321,7 @@ pub async fn stream_session_events( State(state): State>, Path(id): Path, ) -> Response { + use tokio_stream::StreamExt; let rx = { let store = state.sessions.read().expect("session store lock poisoned"); match store.get(&id) { @@ -329,8 +330,6 @@ pub async fn stream_session_events( } }; - use tokio_stream::StreamExt; - let stream = BroadcastStream::new(rx).filter_map(|result| match result { Ok(event) => { let sse: Option = match event { diff --git a/lib/crates/fabro-api/src/tls.rs b/lib/crates/fabro-api/src/tls.rs index 2a4e9cd90..f249f2622 100644 --- a/lib/crates/fabro-api/src/tls.rs +++ b/lib/crates/fabro-api/src/tls.rs @@ -12,6 +12,7 @@ use fabro_config::server::TlsSettings; use crate::jwt_auth::PeerCertificates; /// How client certificates should be verified. +#[derive(Clone, Copy)] pub enum ClientAuth { /// No client certificates requested (TLS encryption only). None, diff --git a/lib/crates/fabro-cli/src/commands/pr/list.rs b/lib/crates/fabro-cli/src/commands/pr/list.rs index 9e18a492e..a7cf45f4c 100644 --- a/lib/crates/fabro-cli/src/commands/pr/list.rs +++ b/lib/crates/fabro-cli/src/commands/pr/list.rs @@ -24,6 +24,14 @@ async fn list_from( args: PrListArgs, github_app: Option, ) -> Result<()> { + struct PrRow { + run_id: String, + number: u64, + state: String, + title: String, + url: String, + } + let creds = github_app.context( "GitHub App credentials required — set GITHUB_APP_PRIVATE_KEY and configure app_id", )?; @@ -45,14 +53,6 @@ async fn list_from( return Ok(()); } - struct PrRow { - run_id: String, - number: u64, - state: String, - title: String, - url: String, - } - let futures: Vec<_> = entries .iter() .map(|(run_id, record)| { diff --git a/lib/crates/fabro-cli/src/commands/run/diff.rs b/lib/crates/fabro-cli/src/commands/run/diff.rs index f65dd8dc2..23b841bbc 100644 --- a/lib/crates/fabro-cli/src/commands/run/diff.rs +++ b/lib/crates/fabro-cli/src/commands/run/diff.rs @@ -99,10 +99,7 @@ fn build_live_diff_cmd(base_sha: &str, stat: bool, shortstat: bool) -> String { |_| format!("'{}'", base_sha.replace('\'', "'\\''")), |q| q.to_string(), ); - format!( - "{} add -N . && {} diff{flags} {quoted_sha}", - GIT_REMOTE, GIT_REMOTE - ) + format!("{GIT_REMOTE} add -N . && {GIT_REMOTE} diff{flags} {quoted_sha}") } fn colorize_diff_line(line: &str) -> String { diff --git a/lib/crates/fabro-cli/src/commands/run/fork.rs b/lib/crates/fabro-cli/src/commands/run/fork.rs index 597bfa6d7..894fde574 100644 --- a/lib/crates/fabro-cli/src/commands/run/fork.rs +++ b/lib/crates/fabro-cli/src/commands/run/fork.rs @@ -28,7 +28,7 @@ pub(crate) fn run(args: &ForkArgs, styles: &Styles) -> Result<()> { .transpose()?; let new_run_id = fork( &store, - ForkRunInput { + &ForkRunInput { source_run_id: run_id.clone(), target, push: !args.no_push, diff --git a/lib/crates/fabro-cli/src/commands/run/logs.rs b/lib/crates/fabro-cli/src/commands/run/logs.rs index 9bacf18bb..c9aa88bea 100644 --- a/lib/crates/fabro-cli/src/commands/run/logs.rs +++ b/lib/crates/fabro-cli/src/commands/run/logs.rs @@ -11,7 +11,7 @@ use tracing::{debug, info}; use crate::args::LogsArgs; use crate::cli_config::load_cli_settings; -pub(crate) fn run(args: LogsArgs, styles: &Styles) -> Result<()> { +pub(crate) fn run(args: &LogsArgs, styles: &Styles) -> Result<()> { let cli_config = load_cli_settings(None)?; let base = runs_base(&cli_config.storage_dir()); let run = resolve_run(&base, &args.run)?; @@ -241,9 +241,10 @@ pub(crate) fn format_event_pretty(line: &str, styles: &Styles) -> Option lines.push(format!( "{}{}", pad, - styles - .dim - .apply_to(format!("Tokens: {}", format_tokens(total as u64))) + styles.dim.apply_to(format!( + "Tokens: {}", + format_tokens(u64::try_from(total).unwrap()) + )) )); } if let Some(cache_read) = usage @@ -259,8 +260,8 @@ pub(crate) fn format_event_pretty(line: &str, styles: &Styles) -> Option pad, styles.dim.apply_to(format!( "Cache: {} read, {} write", - format_tokens(cache_read as u64), - format_tokens(cache_write as u64) + format_tokens(u64::try_from(cache_read).unwrap()), + format_tokens(u64::try_from(cache_write).unwrap()) )) )); } @@ -274,7 +275,7 @@ pub(crate) fn format_event_pretty(line: &str, styles: &Styles) -> Option pad, styles.dim.apply_to(format!( "Reasoning: {} tokens", - format_tokens(reasoning as u64) + format_tokens(u64::try_from(reasoning).unwrap()) )) )); } diff --git a/lib/crates/fabro-cli/src/commands/run/mod.rs b/lib/crates/fabro-cli/src/commands/run/mod.rs index 070eb689e..a59ddf92d 100644 --- a/lib/crates/fabro-cli/src/commands/run/mod.rs +++ b/lib/crates/fabro-cli/src/commands/run/mod.rs @@ -66,7 +66,7 @@ pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<( RunCommands::Diff(args) => diff::run(args).await, RunCommands::Logs(args) => { let styles = Styles::detect_stdout(); - logs::run(args, &styles) + logs::run(&args, &styles) } RunCommands::Resume(args) => { let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr())); @@ -87,7 +87,7 @@ pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<( } RunCommands::Wait(args) => { let styles = Styles::detect_stderr(); - wait::run(args, &styles) + wait::run(&args, &styles) } } } diff --git a/lib/crates/fabro-cli/src/commands/run/rewind.rs b/lib/crates/fabro-cli/src/commands/run/rewind.rs index 2e810ac74..2fb86d1a8 100644 --- a/lib/crates/fabro-cli/src/commands/run/rewind.rs +++ b/lib/crates/fabro-cli/src/commands/run/rewind.rs @@ -28,7 +28,7 @@ pub(crate) fn run(args: &RewindArgs, styles: &Styles) -> Result<()> { rewind( &store, - RewindInput { + &RewindInput { run_id: run_id.clone(), target, push: !args.no_push, diff --git a/lib/crates/fabro-cli/src/commands/run/run_progress.rs b/lib/crates/fabro-cli/src/commands/run/run_progress.rs index 2f12e6e74..c29f11b62 100644 --- a/lib/crates/fabro-cli/src/commands/run/run_progress.rs +++ b/lib/crates/fabro-cli/src/commands/run/run_progress.rs @@ -69,7 +69,7 @@ pub(crate) fn format_duration_short(d: Duration) -> String { if secs >= 60 { format!("{}m{:02}s", secs / 60, secs % 60) } else if d.as_millis() >= 1000 { - format!("{}s", secs) + format!("{secs}s") } else { format!("{}ms", d.as_millis()) } @@ -83,7 +83,9 @@ fn terminal_hyperlink(url: &str, text: &str) -> String { /// Format a number as an integer if whole, one decimal otherwise. fn format_number(n: f64) -> String { if (n - n.round()).abs() < f64::EPSILON { - format!("{}", n as i64) + #[allow(clippy::cast_possible_truncation)] // f64-to-integer: intentional rounding + let i = n as i64; + format!("{i}") } else { format!("{n:.1}") } @@ -718,7 +720,7 @@ impl ProgressUI { } } "SetupStarted" => { - let count = u64_field("command_count") as usize; + let count = usize::try_from(u64_field("command_count")).unwrap(); self.on_setup_started(count); } "SetupCompleted" => { @@ -972,7 +974,7 @@ impl ProgressUI { } "DevcontainerLifecycleStarted" => { let phase = str_field("phase").unwrap_or("?"); - let command_count = u64_field("command_count") as usize; + let command_count = usize::try_from(u64_field("command_count")).unwrap(); self.devcontainer_command_count = command_count; match &self.renderer { ProgressRenderer::Tty(tty) => { @@ -1478,6 +1480,8 @@ impl ProgressUI { .. } if self.verbose => { let yellow = Style::new().yellow(); + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + // f64-to-integer: delay is non-negative and fits in u64 let delay_ms = (*delay_secs * 1000.0) as u64; let dur = format_duration_ms(delay_ms); self.insert_info_line_for_stage( diff --git a/lib/crates/fabro-cli/src/commands/run/wait.rs b/lib/crates/fabro-cli/src/commands/run/wait.rs index c2754636f..a6d01425d 100644 --- a/lib/crates/fabro-cli/src/commands/run/wait.rs +++ b/lib/crates/fabro-cli/src/commands/run/wait.rs @@ -12,7 +12,7 @@ use crate::args::WaitArgs; use crate::cli_config::load_cli_settings; use crate::shared::format_duration_ms; -pub(crate) fn run(args: WaitArgs, styles: &Styles) -> Result<()> { +pub(crate) fn run(args: &WaitArgs, styles: &Styles) -> Result<()> { let cli_config = load_cli_settings(None)?; let base = runs_base(&cli_config.storage_dir()); let run_info = resolve_run(&base, &args.run)?; diff --git a/lib/crates/fabro-cli/src/commands/runs/list.rs b/lib/crates/fabro-cli/src/commands/runs/list.rs index aebf7b3f2..7ed6368c7 100644 --- a/lib/crates/fabro-cli/src/commands/runs/list.rs +++ b/lib/crates/fabro-cli/src/commands/runs/list.rs @@ -78,7 +78,9 @@ pub(crate) fn list_command(args: &RunsListArgs, styles: &Styles) -> Result<()> { None => match run.start_time_dt { Some(start) => { let elapsed = now.signed_duration_since(start); - format_duration_ms(elapsed.num_milliseconds().max(0) as u64) + format_duration_ms( + u64::try_from(elapsed.num_milliseconds().max(0)).unwrap(), + ) } None => "-".to_string(), }, diff --git a/lib/crates/fabro-cli/src/commands/system/df.rs b/lib/crates/fabro-cli/src/commands/system/df.rs index 77ad2b851..d4edd5bf9 100644 --- a/lib/crates/fabro-cli/src/commands/system/df.rs +++ b/lib/crates/fabro-cli/src/commands/system/df.rs @@ -22,11 +22,6 @@ pub(super) fn df_command(args: &DfArgs) -> Result<()> { } fn df_from(args: &DfArgs, data_dir: &Path, runs_base: &Path, logs_base: &Path) -> Result<()> { - let runs = scan_runs(runs_base)?; - let mut active_count = 0u64; - let mut total_run_size = 0u64; - let mut reclaimable_run_size = 0u64; - struct RunSizeInfo { run_id: String, workflow_name: String, @@ -35,6 +30,11 @@ fn df_from(args: &DfArgs, data_dir: &Path, runs_base: &Path, logs_base: &Path) - size: u64, } + let runs = scan_runs(runs_base)?; + let mut active_count = 0u64; + let mut total_run_size = 0u64; + let mut reclaimable_run_size = 0u64; + let mut run_details = Vec::new(); for run in &runs { let size = dir_size(&run.path); @@ -96,7 +96,11 @@ fn df_from(args: &DfArgs, data_dir: &Path, runs_base: &Path, logs_base: &Path) - } let run_reclaim_pct = if total_run_size > 0 { - (reclaimable_run_size as f64 / total_run_size as f64 * 100.0) as u64 + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + // f64-to-integer: percentage is 0-100 + { + (reclaimable_run_size as f64 / total_run_size as f64 * 100.0) as u64 + } } else { 0 }; diff --git a/lib/crates/fabro-cli/src/main.rs b/lib/crates/fabro-cli/src/main.rs index 1137baf99..010a6b4ef 100644 --- a/lib/crates/fabro-cli/src/main.rs +++ b/lib/crates/fabro-cli/src/main.rs @@ -35,7 +35,7 @@ async fn main() { let raw_args: Vec = std::env::args().collect(); let (command_name, result) = main_inner().await; - let duration_ms = start.elapsed().as_millis() as u64; + let duration_ms = u64::try_from(start.elapsed().as_millis()).unwrap(); let is_error = result.is_err(); let command = sanitize::sanitize_command(&raw_args, &command_name); diff --git a/lib/crates/fabro-config/src/config.rs b/lib/crates/fabro-config/src/config.rs index 40b796671..a739b2fb4 100644 --- a/lib/crates/fabro-config/src/config.rs +++ b/lib/crates/fabro-config/src/config.rs @@ -183,6 +183,7 @@ impl Combine for FabroConfig { } impl FabroConfig { + #[must_use] pub fn combine(self, other: Self) -> Self { Combine::combine(self, other) } diff --git a/lib/crates/fabro-core/src/context.rs b/lib/crates/fabro-core/src/context.rs index 133e01728..8e34f91b1 100644 --- a/lib/crates/fabro-core/src/context.rs +++ b/lib/crates/fabro-core/src/context.rs @@ -46,6 +46,7 @@ impl Context { /// Deep copy for parallel branch isolation. /// `.clone()` shares state (Arc clone); `.fork()` creates an independent copy. + #[must_use] pub fn fork(&self) -> Self { Self { values: Arc::new(RwLock::new(self.snapshot())), @@ -64,7 +65,8 @@ impl Context { pub fn node_visit_count(&self) -> usize { self.get("internal.node_visit_count") .and_then(|v| v.as_u64()) - .unwrap_or(0) as usize + .map(|v| usize::try_from(v).unwrap()) + .unwrap_or(0) } } diff --git a/lib/crates/fabro-core/src/executor.rs b/lib/crates/fabro-core/src/executor.rs index 5b96963c7..651044e28 100644 --- a/lib/crates/fabro-core/src/executor.rs +++ b/lib/crates/fabro-core/src/executor.rs @@ -51,21 +51,25 @@ impl ExecutorBuilder { } } + #[must_use] pub fn lifecycle(mut self, lifecycle: Box>) -> Self { self.lifecycle = Some(lifecycle); self } + #[must_use] pub fn cancel_token(mut self, token: Arc) -> Self { self.options.cancel_token = Some(token); self } + #[must_use] pub fn stall_token(mut self, token: CancellationToken) -> Self { self.options.stall_token = Some(token); self } + #[must_use] pub fn max_node_visits(mut self, limit: usize) -> Self { self.options.max_node_visits = Some(limit); self diff --git a/lib/crates/fabro-devcontainer/src/dockerfile.rs b/lib/crates/fabro-devcontainer/src/dockerfile.rs index d9c99fdc0..5f7a5725b 100644 --- a/lib/crates/fabro-devcontainer/src/dockerfile.rs +++ b/lib/crates/fabro-devcontainer/src/dockerfile.rs @@ -29,7 +29,7 @@ pub(crate) fn generate( } if let Some(user) = remote_user { - sections.push(format!("USER {}", user)); + sections.push(format!("USER {user}")); } let mut result = sections.join("\n\n"); diff --git a/lib/crates/fabro-devcontainer/src/lib.rs b/lib/crates/fabro-devcontainer/src/lib.rs index 2b7e0ee3d..7564cfc7c 100644 --- a/lib/crates/fabro-devcontainer/src/lib.rs +++ b/lib/crates/fabro-devcontainer/src/lib.rs @@ -545,7 +545,7 @@ impl DevcontainerResolver { ports .iter() .filter_map(|p| match p { - serde_json::Value::Number(n) => n.as_u64().map(|n| n as u16), + serde_json::Value::Number(n) => n.as_u64().map(|n| u16::try_from(n).unwrap()), serde_json::Value::String(s) => { let s = s.split('/').next().unwrap_or(s); // strip protocol if let Some((_host, container)) = s.split_once(':') { diff --git a/lib/crates/fabro-git-storage/src/gitobj.rs b/lib/crates/fabro-git-storage/src/gitobj.rs index e91cf0350..1a4e2cbe4 100644 --- a/lib/crates/fabro-git-storage/src/gitobj.rs +++ b/lib/crates/fabro-git-storage/src/gitobj.rs @@ -16,16 +16,16 @@ pub enum FileMode { impl FileMode { fn as_i32(self) -> i32 { match self { - Self::Blob => 0o100644, - Self::BlobExecutable => 0o100755, - Self::Tree => 0o040000, + Self::Blob => 0o100_644, + Self::BlobExecutable => 0o100_755, + Self::Tree => 0o040_000, } } fn from_i32(mode: i32) -> Self { match mode { - 0o100755 => Self::BlobExecutable, - 0o040000 => Self::Tree, + 0o100_755 => Self::BlobExecutable, + 0o040_000 => Self::Tree, _ => Self::Blob, } } diff --git a/lib/crates/fabro-git-storage/src/trailerlink.rs b/lib/crates/fabro-git-storage/src/trailerlink.rs index 66c46e3b1..055fd0f88 100644 --- a/lib/crates/fabro-git-storage/src/trailerlink.rs +++ b/lib/crates/fabro-git-storage/src/trailerlink.rs @@ -7,7 +7,7 @@ pub struct Trailer<'a> { } /// Append a trailer to a commit message, inserting a blank-line separator if needed. -pub fn append(message: &str, trailer: Trailer<'_>) -> String { +pub fn append(message: &str, trailer: &Trailer<'_>) -> String { let trailer_line = format!("{}: {}", trailer.key, trailer.value); let trimmed = message.trim_end(); @@ -93,7 +93,7 @@ mod tests { fn append_to_simple_message() { let result = append( "Initial commit", - Trailer { + &Trailer { key: "My-Checkpoint", value: "abc123", }, @@ -106,7 +106,7 @@ mod tests { let msg = "Initial commit\n\nSigned-off-by: Alice \n"; let result = append( msg, - Trailer { + &Trailer { key: "My-Checkpoint", value: "abc123", }, @@ -122,7 +122,7 @@ mod tests { let msg = "Initial commit\n\nThis is a longer description of the change.\n"; let result = append( msg, - Trailer { + &Trailer { key: "My-Checkpoint", value: "abc123", }, diff --git a/lib/crates/fabro-github/src/lib.rs b/lib/crates/fabro-github/src/lib.rs index 550bb4912..e6cf18ad3 100644 --- a/lib/crates/fabro-github/src/lib.rs +++ b/lib/crates/fabro-github/src/lib.rs @@ -290,6 +290,13 @@ pub async fn create_pull_request( body: &str, draft: bool, ) -> Result { + #[derive(Deserialize)] + struct PullRequestResponse { + html_url: String, + number: u64, + node_id: String, + } + let jwt = sign_app_jwt(&creds.app_id, &creds.private_key_pem)?; let client = reqwest::Client::new(); @@ -329,8 +336,7 @@ pub async fn create_pull_request( } 401 | 403 => { return Err(format!( - "Authentication failed creating pull request ({})", - status + "Authentication failed creating pull request ({status})" )); } _ => { @@ -341,13 +347,6 @@ pub async fn create_pull_request( } } - #[derive(Deserialize)] - struct PullRequestResponse { - html_url: String, - number: u64, - node_id: String, - } - let pr: PullRequestResponse = resp .json() .await diff --git a/lib/crates/fabro-hooks/src/executor.rs b/lib/crates/fabro-hooks/src/executor.rs index 66e52a7a6..2d0b0054f 100644 --- a/lib/crates/fabro-hooks/src/executor.rs +++ b/lib/crates/fabro-hooks/src/executor.rs @@ -121,7 +121,7 @@ impl HookExecutorImpl { work_dir: Option<&Path>, ) -> HookDecision { let context_json = serde_json::to_string(context).unwrap_or_default(); - let timeout_ms = definition.timeout().as_millis() as u64; + let timeout_ms = u64::try_from(definition.timeout().as_millis()).unwrap(); let mut env_vars = HashMap::new(); env_vars.insert("FABRO_EVENT".to_string(), context.event.to_string()); @@ -589,7 +589,7 @@ impl HookExecutor for HookExecutorImpl { }, }; - let duration_ms = start.elapsed().as_millis() as u64; + let duration_ms = u64::try_from(start.elapsed().as_millis()).unwrap(); HookResult { hook_name: definition.name.clone(), decision, diff --git a/lib/crates/fabro-hooks/src/types.rs b/lib/crates/fabro-hooks/src/types.rs index c1c0578d3..6a0780c38 100644 --- a/lib/crates/fabro-hooks/src/types.rs +++ b/lib/crates/fabro-hooks/src/types.rs @@ -103,8 +103,8 @@ impl HookDecision { match (&self, &other) { (Self::Block { .. }, _) => self, (_, Self::Block { .. }) => other, - (Self::Skip { .. }, _) | (Self::Override { .. }, _) => self, - (_, Self::Skip { .. }) | (_, Self::Override { .. }) => other, + (Self::Skip { .. } | Self::Override { .. }, _) => self, + (_, Self::Skip { .. } | Self::Override { .. }) => other, _ => Self::Proceed, } } diff --git a/lib/crates/fabro-llm/src/cli.rs b/lib/crates/fabro-llm/src/cli.rs index 41ae837d9..cf065119a 100644 --- a/lib/crates/fabro-llm/src/cli.rs +++ b/lib/crates/fabro-llm/src/cli.rs @@ -113,6 +113,7 @@ fn format_cost(cost: Option) -> String { fn format_speed(tps: Option) -> String { match tps { None => "-".to_string(), + #[allow(clippy::cast_possible_truncation)] // f64-to-integer: fractional loss is fine Some(t) => format!("{} tok/s", t as i64), } } diff --git a/lib/crates/fabro-mcp/src/client.rs b/lib/crates/fabro-mcp/src/client.rs index 96f2e6e52..1848c9aae 100644 --- a/lib/crates/fabro-mcp/src/client.rs +++ b/lib/crates/fabro-mcp/src/client.rs @@ -70,9 +70,9 @@ impl McpClient { let mut header_map = HeaderMap::new(); for (key, value) in headers { let name = HeaderName::from_bytes(key.as_bytes()) - .map_err(|e| anyhow!("invalid header name '{}': {}", key, e))?; + .map_err(|e| anyhow!("invalid header name '{key}': {e}"))?; let val = HeaderValue::from_str(value) - .map_err(|e| anyhow!("invalid header value for '{}': {}", key, e))?; + .map_err(|e| anyhow!("invalid header value for '{key}': {e}"))?; header_map.insert(name, val); } builder = builder.default_headers(header_map); @@ -207,8 +207,7 @@ impl McpClient { serde_json::Value::Null => None, other => { return Err(anyhow!( - "MCP tool arguments must be a JSON object, got {}", - other + "MCP tool arguments must be a JSON object, got {other}" )); } }; diff --git a/lib/crates/fabro-sandbox/src/daytona/mod.rs b/lib/crates/fabro-sandbox/src/daytona/mod.rs index a02034830..8e69b985d 100644 --- a/lib/crates/fabro-sandbox/src/daytona/mod.rs +++ b/lib/crates/fabro-sandbox/src/daytona/mod.rs @@ -906,7 +906,7 @@ impl Sandbox for DaytonaSandbox { name: f.name, is_dir: f.is_dir, size: if f.size > 0 { - Some(f.size as u64) + Some(u64::try_from(f.size).unwrap()) } else { None }, @@ -937,7 +937,7 @@ impl Sandbox for DaytonaSandbox { .map_err(|e| format!("Failed to get process service: {e}"))?; tracing::info!( - elapsed_ms = start.elapsed().as_millis() as u64, + elapsed_ms = u64::try_from(start.elapsed().as_millis()).unwrap(), "exec_command: process service acquired, starting select" ); @@ -976,7 +976,7 @@ impl Sandbox for DaytonaSandbox { let result = tokio::select! { res = exec_future => { tracing::info!( - elapsed_ms = start.elapsed().as_millis() as u64, + elapsed_ms = u64::try_from(start.elapsed().as_millis()).unwrap(), ok = res.is_ok(), "exec_command: HTTP response received" ); @@ -984,7 +984,7 @@ impl Sandbox for DaytonaSandbox { } () = time::sleep(timeout_duration) => { tracing::info!( - elapsed_ms = start.elapsed().as_millis() as u64, + elapsed_ms = u64::try_from(start.elapsed().as_millis()).unwrap(), timeout_ms, "exec_command: client-side timeout fired" ); @@ -993,12 +993,12 @@ impl Sandbox for DaytonaSandbox { stderr: "Command timed out locally".to_string(), exit_code: -1, timed_out: true, - duration_ms: start.elapsed().as_millis() as u64, + duration_ms: u64::try_from(start.elapsed().as_millis()).unwrap(), }); } () = token.cancelled() => { tracing::info!( - elapsed_ms = start.elapsed().as_millis() as u64, + elapsed_ms = u64::try_from(start.elapsed().as_millis()).unwrap(), "exec_command: cancelled via token" ); return Ok(ExecResult { @@ -1006,12 +1006,12 @@ impl Sandbox for DaytonaSandbox { stderr: "Command cancelled".to_string(), exit_code: -1, timed_out: true, - duration_ms: start.elapsed().as_millis() as u64, + duration_ms: u64::try_from(start.elapsed().as_millis()).unwrap(), }); } }; - let duration_ms = start.elapsed().as_millis() as u64; + let duration_ms = u64::try_from(start.elapsed().as_millis()).unwrap(); // The Daytona SDK returns combined output in `result` field. // Separate stderr isn't available in the simple execute_command API. diff --git a/lib/crates/fabro-sandbox/src/docker.rs b/lib/crates/fabro-sandbox/src/docker.rs index a4add387b..86284a2a6 100644 --- a/lib/crates/fabro-sandbox/src/docker.rs +++ b/lib/crates/fabro-sandbox/src/docker.rs @@ -189,7 +189,7 @@ impl DockerSandbox { .await .map_err(|e| format!("Failed to inspect exec: {e}"))?; - let exit_code = inspect.exit_code.unwrap_or(-1) as i32; + let exit_code = i32::try_from(inspect.exit_code.unwrap_or(-1)).unwrap(); Ok((stdout, stderr, exit_code)) } diff --git a/lib/crates/fabro-sandbox/src/local.rs b/lib/crates/fabro-sandbox/src/local.rs index b4d6a2dc9..6cfbbe322 100644 --- a/lib/crates/fabro-sandbox/src/local.rs +++ b/lib/crates/fabro-sandbox/src/local.rs @@ -118,9 +118,6 @@ impl Sandbox for LocalSandbox { path: &str, depth: Option, ) -> Result, String> { - let full_path = self.resolve_path(path); - let max_depth = depth.unwrap_or(1); - fn list_recursive( base: &std::path::Path, prefix: &str, @@ -160,6 +157,8 @@ impl Sandbox for LocalSandbox { Ok(()) } + let full_path = self.resolve_path(path); + let max_depth = depth.unwrap_or(1); let mut entries = Vec::new(); list_recursive(&full_path, "", 0, max_depth, &mut entries)?; Ok(entries) diff --git a/lib/crates/fabro-store/src/slate/run_store.rs b/lib/crates/fabro-store/src/slate/run_store.rs index 3261346eb..3836de823 100644 --- a/lib/crates/fabro-store/src/slate/run_store.rs +++ b/lib/crates/fabro-store/src/slate/run_store.rs @@ -295,7 +295,7 @@ impl RunStore for SlateRunStore { let mut iter = self.inner.db.scan_prefix(prefix.as_bytes()).await?; let mut visits = BTreeSet::new(); while let Some(entry) = iter.next().await? { - let key = key_to_string(entry.key)?; + let key = key_to_string(&entry.key)?; if let Some((current_node_id, visit, _)) = keys::parse_node_key(&key) { if current_node_id == node_id { visits.insert(visit); @@ -400,7 +400,7 @@ impl RunStore for SlateRunStore { let mut iter = self.inner.db.scan_prefix(prefix.as_bytes()).await?; let mut assets = Vec::new(); while let Some(entry) = iter.next().await? { - let key = key_to_string(entry.key)?; + let key = key_to_string(&entry.key)?; if let Some(asset) = key.strip_prefix(&prefix) { assets.push(asset.to_string()); } @@ -417,7 +417,7 @@ impl RunStore for SlateRunStore { let mut iter = self.inner.db.scan_prefix(b"nodes/").await?; let mut visits = BTreeSet::new(); while let Some(entry) = iter.next().await? { - let key = key_to_string(entry.key)?; + let key = key_to_string(&entry.key)?; if let Some((node_id, visit, _)) = keys::parse_node_key(&key) { visits.insert((node_id, visit)); } @@ -497,7 +497,7 @@ where let mut iter = db.scan_prefix(prefix.as_bytes()).await?; let mut max_seq = 0; while let Some(entry) = iter.next().await? { - let key = key_to_string(entry.key)?; + let key = key_to_string(&entry.key)?; if let Some(seq) = parse(&key) { max_seq = max_seq.max(seq); } @@ -512,7 +512,7 @@ where let mut iter = db.scan_prefix(keys::EVENTS_PREFIX.as_bytes()).await?; let mut events = Vec::new(); while let Some(entry) = iter.next().await? { - let key = key_to_string(entry.key)?; + let key = key_to_string(&entry.key)?; let Some(seq) = keys::parse_event_seq(&key) else { continue; }; @@ -535,7 +535,7 @@ where let mut iter = db.scan_prefix(keys::CHECKPOINTS_PREFIX.as_bytes()).await?; let mut checkpoints = Vec::new(); while let Some(entry) = iter.next().await? { - let key = key_to_string(entry.key)?; + let key = key_to_string(&entry.key)?; let Some(seq) = keys::parse_checkpoint_seq(&key) else { continue; }; @@ -545,7 +545,7 @@ where Ok(checkpoints) } -fn key_to_string(key: Bytes) -> Result { +fn key_to_string(key: &Bytes) -> Result { String::from_utf8(key.to_vec()) .map_err(|err| StoreError::Other(format!("stored key is not valid UTF-8: {err}"))) } diff --git a/lib/crates/fabro-telemetry/src/anonymous_id.rs b/lib/crates/fabro-telemetry/src/anonymous_id.rs index b791a6d5c..6eba51721 100644 --- a/lib/crates/fabro-telemetry/src/anonymous_id.rs +++ b/lib/crates/fabro-telemetry/src/anonymous_id.rs @@ -46,7 +46,7 @@ pub fn compute_cli_id() -> Result { .context("no MAC address found")?; let digest = md5::compute(mac.bytes()); - Ok(format!("{:x}", digest)) + Ok(format!("{digest:x}")) } #[cfg(test)] diff --git a/lib/crates/fabro-telemetry/src/buffer.rs b/lib/crates/fabro-telemetry/src/buffer.rs index ace625d65..2c85b6e54 100644 --- a/lib/crates/fabro-telemetry/src/buffer.rs +++ b/lib/crates/fabro-telemetry/src/buffer.rs @@ -3,6 +3,7 @@ use std::time::{Duration, Instant}; use crate::event::Track; +#[derive(Clone, Copy)] pub(crate) struct BufferPolicy { pub count_threshold: usize, pub time_threshold: Duration, @@ -18,7 +19,7 @@ impl Default for BufferPolicy { } pub(crate) fn consumer_loop( - rx: Receiver, + rx: &Receiver, config: BufferPolicy, mid_flush: impl Fn(&[Track]), final_flush: impl Fn(&[Track]), @@ -92,7 +93,7 @@ mod tests { drop(tx); consumer_loop( - rx, + &rx, BufferPolicy { count_threshold: 2, time_threshold: Duration::from_secs(60), @@ -127,7 +128,7 @@ mod tests { drop(tx); consumer_loop( - rx, + &rx, BufferPolicy { count_threshold: 2, time_threshold: Duration::from_secs(60), @@ -157,7 +158,7 @@ mod tests { // Don't drop yet — let time threshold fire let handle = std::thread::spawn(move || { consumer_loop( - rx, + &rx, BufferPolicy { count_threshold: 100, // won't trigger time_threshold: Duration::from_millis(50), @@ -196,7 +197,7 @@ mod tests { drop(tx); // disconnect immediately, below count threshold consumer_loop( - rx, + &rx, BufferPolicy { count_threshold: 100, // won't trigger time_threshold: Duration::from_secs(60), diff --git a/lib/crates/fabro-telemetry/src/lib.rs b/lib/crates/fabro-telemetry/src/lib.rs index d494c4465..6e87e9880 100644 --- a/lib/crates/fabro-telemetry/src/lib.rs +++ b/lib/crates/fabro-telemetry/src/lib.rs @@ -81,7 +81,7 @@ fn init_inner(level: TelemetryLevel, anonymous_id: String) { .name("telemetry".to_string()) .spawn(move || { buffer::consumer_loop( - rx, + &rx, buffer::BufferPolicy::default(), |tracks| { if let Err(err) = sender::upload_blocking(tracks) { diff --git a/lib/crates/fabro-telemetry/src/panic.rs b/lib/crates/fabro-telemetry/src/panic.rs index 1a3ec8cb5..a2889c18e 100644 --- a/lib/crates/fabro-telemetry/src/panic.rs +++ b/lib/crates/fabro-telemetry/src/panic.rs @@ -93,11 +93,11 @@ fn report_panic(info: &PanicHookInfo<'_>) { } let event = build_event(&message); - spawn_panic_sender(event); + spawn_panic_sender(&event); } /// Serialize the Sentry event to a temp file and spawn `fabro __send_panic `. -fn spawn_panic_sender(event: Event<'static>) { +fn spawn_panic_sender(event: &Event<'static>) { let Ok(json) = serde_json::to_vec(&event) else { return; }; @@ -174,8 +174,7 @@ mod tests { #[test] fn send_panic_noops_without_dsn() { // SENTRY_DSN is not set at compile time in tests, so this should error. - let rt = tokio::runtime::Runtime::new().unwrap(); - let result = rt.block_on(capture(Path::new("/nonexistent"))); + let result = capture(Path::new("/nonexistent")); assert!(result.is_err()); let err_msg = result.unwrap_err().to_string(); assert!(err_msg.contains("SENTRY_DSN not set")); diff --git a/lib/crates/fabro-tracker/src/linear.rs b/lib/crates/fabro-tracker/src/linear.rs index e47b6ca6d..46dcb9119 100644 --- a/lib/crates/fabro-tracker/src/linear.rs +++ b/lib/crates/fabro-tracker/src/linear.rs @@ -57,7 +57,7 @@ fn normalize_issue(node: &Value) -> Result { .map(std::string::ToString::to_string); let priority = match node["priority"].as_i64() { Some(0) | None => None, - Some(n) => Some(n as i32), + Some(n) => Some(i32::try_from(n).unwrap()), }; let state = node["state"]["name"] .as_str() diff --git a/lib/crates/fabro-types/src/combine.rs b/lib/crates/fabro-types/src/combine.rs index fdf4c5226..6014cb957 100644 --- a/lib/crates/fabro-types/src/combine.rs +++ b/lib/crates/fabro-types/src/combine.rs @@ -3,6 +3,7 @@ use std::hash::Hash; use std::path::PathBuf; pub trait Combine { + #[must_use] fn combine(self, other: Self) -> Self; } diff --git a/lib/crates/fabro-types/src/graph.rs b/lib/crates/fabro-types/src/graph.rs index 691ac391f..f3864a01f 100644 --- a/lib/crates/fabro-types/src/graph.rs +++ b/lib/crates/fabro-types/src/graph.rs @@ -72,7 +72,7 @@ impl AttrValue { pub fn is_llm_handler_type(handler_type: Option<&str>) -> bool { matches!( handler_type, - Some("agent") | Some("agent_loop") | Some("prompt") | Some("one_shot") + Some("agent" | "agent_loop" | "prompt" | "one_shot") ) } @@ -428,6 +428,7 @@ impl Graph { /// Graph-level `loop_restart_signature_limit` (default 3). /// When the same failure signature repeats this many times, the pipeline aborts. pub fn loop_restart_signature_limit(&self) -> usize { + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] // filtered >= 1 above self.attrs .get("loop_restart_signature_limit") .and_then(AttrValue::as_i64) diff --git a/lib/crates/fabro-types/src/settings/hook.rs b/lib/crates/fabro-types/src/settings/hook.rs index 5ceea43c3..aeb8af0ac 100644 --- a/lib/crates/fabro-types/src/settings/hook.rs +++ b/lib/crates/fabro-types/src/settings/hook.rs @@ -180,8 +180,7 @@ impl HookDefinition { format!("{event_str}:{short}") } Some(HookType::Http { ref url, .. }) => format!("{event_str}:{url}"), - Some(HookType::Prompt { ref prompt, .. }) - | Some(HookType::Agent { ref prompt, .. }) => { + Some(HookType::Prompt { ref prompt, .. } | HookType::Agent { ref prompt, .. }) => { let short = &prompt[..prompt.floor_char_boundary(20)]; format!("{event_str}:{short}") } diff --git a/lib/crates/fabro-types/src/status.rs b/lib/crates/fabro-types/src/status.rs index fed795868..3a6deeff1 100644 --- a/lib/crates/fabro-types/src/status.rs +++ b/lib/crates/fabro-types/src/status.rs @@ -39,16 +39,16 @@ impl RunStatus { matches!( (self, to), (Self::Submitted, Self::Starting) - | (Self::Starting, Self::Running) - | (Self::Starting, Self::Failed) - | (Self::Running, Self::Succeeded) - | (Self::Running, Self::Failed) - | (Self::Running, Self::Paused) - | (Self::Running, Self::Removing) - | (Self::Paused, Self::Running) - | (Self::Paused, Self::Failed) + | (Self::Starting | Self::Paused, Self::Running) + | ( + Self::Starting | Self::Running | Self::Paused | Self::Removing, + Self::Failed + ) + | ( + Self::Running, + Self::Succeeded | Self::Paused | Self::Removing + ) | (Self::Paused, Self::Removing) - | (Self::Removing, Self::Failed) ) } diff --git a/lib/crates/fabro-util/build.rs b/lib/crates/fabro-util/build.rs index 678156cc7..0033f58bc 100644 --- a/lib/crates/fabro-util/build.rs +++ b/lib/crates/fabro-util/build.rs @@ -122,7 +122,7 @@ fn main() { // Entropy match rule.entropy { Some(e) => { - let _ = writeln!(code, " entropy: Some({:.1}),", e); + let _ = writeln!(code, " entropy: Some({e:.1}),"); } None => code.push_str(" entropy: None,\n"), } diff --git a/lib/crates/fabro-util/src/check_report.rs b/lib/crates/fabro-util/src/check_report.rs index c7bb29fe0..88df5c74d 100644 --- a/lib/crates/fabro-util/src/check_report.rs +++ b/lib/crates/fabro-util/src/check_report.rs @@ -66,11 +66,12 @@ impl CheckReport { footer: Option<&str>, max_width: Option, ) -> String { - let mut out = String::new(); - let width = max_width.unwrap_or(80) as usize; // " • " is 8 chars of prefix before detail text const DETAIL_PREFIX_LEN: usize = 8; + let mut out = String::new(); + let width = max_width.unwrap_or(80) as usize; + let show_section_headers = self.sections.len() > 1; writeln!(out, "{}", s.bold.apply_to(&self.title)).unwrap(); diff --git a/lib/crates/fabro-util/src/redact/jsonl.rs b/lib/crates/fabro-util/src/redact/jsonl.rs index 59a525bcd..0eccf6556 100644 --- a/lib/crates/fabro-util/src/redact/jsonl.rs +++ b/lib/crates/fabro-util/src/redact/jsonl.rs @@ -30,50 +30,49 @@ fn should_skip_object(obj: &serde_json::Map) -> bool { } } +fn walk_replacements( + v: &Value, + seen: &mut std::collections::HashSet, + repls: &mut Vec<(String, String)>, +) { + match v { + Value::Object(obj) => { + if should_skip_object(obj) { + return; + } + for (k, child) in obj { + if should_skip_field(k) { + continue; + } + walk_replacements(child, seen, repls); + } + } + Value::Array(arr) => { + for child in arr { + walk_replacements(child, seen, repls); + } + } + Value::String(s) => { + let redacted = super::redact_string(s); + if redacted != *s && seen.insert(s.clone()) { + repls.push((s.clone(), redacted)); + } + } + _ => {} + } +} + /// Walk a parsed JSON value and collect (original, redacted) string pairs. fn collect_replacements(v: &Value) -> Vec<(String, String)> { let mut seen = std::collections::HashSet::new(); let mut repls = Vec::new(); - - fn walk( - v: &Value, - seen: &mut std::collections::HashSet, - repls: &mut Vec<(String, String)>, - ) { - match v { - Value::Object(obj) => { - if should_skip_object(obj) { - return; - } - for (k, child) in obj { - if should_skip_field(k) { - continue; - } - walk(child, seen, repls); - } - } - Value::Array(arr) => { - for child in arr { - walk(child, seen, repls); - } - } - Value::String(s) => { - let redacted = super::redact_string(s); - if redacted != *s && seen.insert(s.clone()) { - repls.push((s.clone(), redacted)); - } - } - _ => {} - } - } - - walk(v, &mut seen, &mut repls); + walk_replacements(v, &mut seen, &mut repls); repls } /// JSON-encode a string value (with quotes), without HTML escaping. fn json_encode_string(s: &str) -> String { - serde_json::to_string(s).unwrap_or_else(|_| format!("\"{}\"", s)) + serde_json::to_string(s).unwrap_or_else(|_| format!("\"{s}\"")) } /// Redact secrets in a single JSONL line. diff --git a/lib/crates/fabro-workflows/src/event.rs b/lib/crates/fabro-workflows/src/event.rs index 5e420be74..d5fa8fac8 100644 --- a/lib/crates/fabro-workflows/src/event.rs +++ b/lib/crates/fabro-workflows/src/event.rs @@ -1100,10 +1100,11 @@ fn rename_fields(event_name: &str, fields: &mut serde_json::Map i64 { - std::time::SystemTime::now() + let millis = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default() - .as_millis() as i64 + .as_millis(); + i64::try_from(millis).unwrap() } /// Listener callback type for workflow run events. diff --git a/lib/crates/fabro-workflows/src/graph.rs b/lib/crates/fabro-workflows/src/graph.rs index dbe2ecf2a..76e947a72 100644 --- a/lib/crates/fabro-workflows/src/graph.rs +++ b/lib/crates/fabro-workflows/src/graph.rs @@ -31,7 +31,9 @@ impl NodeSpec for WorkflowNode { } fn max_visits(&self) -> Option { - self.0.max_visits().map(|v| v.max(0) as usize) + self.0 + .max_visits() + .map(|v| usize::try_from(v.max(0)).unwrap()) } } diff --git a/lib/crates/fabro-workflows/src/handler/command.rs b/lib/crates/fabro-workflows/src/handler/command.rs index 61b3da41b..971bf8e9a 100644 --- a/lib/crates/fabro-workflows/src/handler/command.rs +++ b/lib/crates/fabro-workflows/src/handler/command.rs @@ -13,7 +13,8 @@ use tokio::fs; use super::{EngineServices, Handler}; fn timeout_ms(node: &Node) -> Option { - node.timeout().map(|d| d.as_millis() as u64) + node.timeout() + .map(|d| u64::try_from(d.as_millis()).unwrap()) } /// Shell-escape a string using `shlex::try_quote` (POSIX-safe). @@ -107,7 +108,9 @@ impl Handler for CommandHandler { script.to_string() }; - let timeout_ms = node.timeout().map_or(600_000, |d| d.as_millis() as u64); + let timeout_ms = node + .timeout() + .map_or(600_000, |d| u64::try_from(d.as_millis()).unwrap()); let env_vars = if services.env.is_empty() { None } else { diff --git a/lib/crates/fabro-workflows/src/handler/llm/preamble.rs b/lib/crates/fabro-workflows/src/handler/llm/preamble.rs index 215122f9b..635aa4abe 100644 --- a/lib/crates/fabro-workflows/src/handler/llm/preamble.rs +++ b/lib/crates/fabro-workflows/src/handler/llm/preamble.rs @@ -440,6 +440,7 @@ fn build_compact_preamble( // Summary preamble // --------------------------------------------------------------------------- +#[derive(Clone, Copy)] enum SummaryDetail { Low, Medium, diff --git a/lib/crates/fabro-workflows/src/handler/mod.rs b/lib/crates/fabro-workflows/src/handler/mod.rs index c8ab63672..3e0995973 100644 --- a/lib/crates/fabro-workflows/src/handler/mod.rs +++ b/lib/crates/fabro-workflows/src/handler/mod.rs @@ -114,7 +114,7 @@ pub trait Handler: Send + Sync { } /// Extract a human-readable message from a panic payload. -pub(crate) fn format_panic_message(payload: Box) -> String { +pub(crate) fn format_panic_message(payload: &Box) -> String { if let Some(s) = payload.downcast_ref::<&str>() { format!("handler panicked: {s}") } else if let Some(s) = payload.downcast_ref::() { diff --git a/lib/crates/fabro-workflows/src/handler/parallel.rs b/lib/crates/fabro-workflows/src/handler/parallel.rs index 9becfa16f..644e8389d 100644 --- a/lib/crates/fabro-workflows/src/handler/parallel.rs +++ b/lib/crates/fabro-workflows/src/handler/parallel.rs @@ -128,6 +128,15 @@ impl Handler for ParallelHandler { run_dir: &Path, services: &EngineServices, ) -> Result { + // Build per-branch sandboxes (sequentially for git setup) + struct BranchSetup { + target_id: String, + branch_index: usize, + branch_context: Context, + sandbox: Arc, + worktree_path: Option, + } + let parallel_start = Instant::now(); let branches = graph.outgoing_edges(&node.id); if branches.is_empty() { @@ -188,15 +197,6 @@ impl Handler for ParallelHandler { None }; - // Build per-branch sandboxes (sequentially for git setup) - struct BranchSetup { - target_id: String, - branch_index: usize, - branch_context: Context, - sandbox: Arc, - worktree_path: Option, - } - let mut branch_setups: Vec = Vec::new(); for (branch_index, edge) in branches.iter().enumerate() { let target_id = edge.to.clone(); diff --git a/lib/crates/fabro-workflows/src/lifecycle/event.rs b/lib/crates/fabro-workflows/src/lifecycle/event.rs index f9dafb6fc..c6f0285df 100644 --- a/lib/crates/fabro-workflows/src/lifecycle/event.rs +++ b/lib/crates/fabro-workflows/src/lifecycle/event.rs @@ -164,7 +164,10 @@ impl RunLifecycle for EventLifecycle { index: stage_index, attempt: ctx.attempt as usize, max_attempts: ctx.result.max_attempts as usize, - delay_ms: ctx.backoff_delay.map(|d| d.as_millis() as u64).unwrap_or(0), + delay_ms: ctx + .backoff_delay + .map(|d| u64::try_from(d.as_millis()).unwrap()) + .unwrap_or(0), }); } Ok(()) @@ -183,7 +186,7 @@ impl RunLifecycle for EventLifecycle { } let gv = node.inner(); let stage_index = state.stage_index; - let duration_ms = result.duration.as_millis() as u64; + let duration_ms = u64::try_from(result.duration.as_millis()).unwrap(); if outcome.status == StageStatus::Fail { self.emitter.emit(&WorkflowRunEvent::StageFailed { @@ -286,7 +289,8 @@ impl RunLifecycle for EventLifecycle { if state.cancelled { return; } - let duration_ms = self.run_start.lock().unwrap().elapsed().as_millis() as u64; + let duration_ms = + u64::try_from(self.run_start.lock().unwrap().elapsed().as_millis()).unwrap(); let artifact_count = self.artifact_store.lock().unwrap().list().len(); let last_sha = self.last_git_sha.lock().unwrap().clone(); let total_cost = { diff --git a/lib/crates/fabro-workflows/src/lifecycle/mod.rs b/lib/crates/fabro-workflows/src/lifecycle/mod.rs index a954b3a9c..5969a7852 100644 --- a/lib/crates/fabro-workflows/src/lifecycle/mod.rs +++ b/lib/crates/fabro-workflows/src/lifecycle/mod.rs @@ -77,15 +77,15 @@ pub(crate) struct WorkflowLifecycle { impl WorkflowLifecycle { #[allow(clippy::too_many_arguments)] pub(crate) fn new( - emitter: Arc, + emitter: &Arc, hook_runner: Option>, - sandbox: Arc, + sandbox: &Arc, graph: Arc, - run_dir: PathBuf, - run_options: Arc, + run_dir: &PathBuf, + run_options: &Arc, is_resume: bool, ) -> Self { - let runtime_state = RuntimeState::new(&run_dir); + let runtime_state = RuntimeState::new(run_dir); let restarted_from: Arc>> = Arc::new(Mutex::new(None)); let loop_restart_signature_limit = graph.loop_restart_signature_limit(); let checkpoint_git_result: Arc>> = @@ -110,7 +110,7 @@ impl WorkflowLifecycle { }; let event = EventLifecycle { - emitter: Arc::clone(&emitter), + emitter: Arc::clone(emitter), graph_name: graph.name.clone(), run_id: run_options.run_id.clone(), run_start: Mutex::new(Instant::now()), @@ -127,7 +127,7 @@ impl WorkflowLifecycle { let hook = HookLifecycle { hook_runner, - sandbox: Arc::clone(&sandbox), + sandbox: Arc::clone(sandbox), hook_work_dir: working_directory.clone().map(PathBuf::from), run_id: run_options.run_id.clone(), graph_name: graph.name.clone(), @@ -139,8 +139,8 @@ impl WorkflowLifecycle { run_dir: run_dir.clone(), run_id: run_options.run_id.clone(), graph: Arc::clone(&graph), - run_options: Arc::clone(&run_options), - emitter: Arc::clone(&emitter), + run_options: Arc::clone(run_options), + emitter: Arc::clone(emitter), circuit_breaker: Arc::clone(&circuit_breaker), checkpoint_enabled: true, }; @@ -148,22 +148,22 @@ impl WorkflowLifecycle { let start_node_id = graph.find_start_node().map(|n| n.id.clone()); let git = GitLifecycle { - sandbox: Arc::clone(&sandbox), + sandbox: Arc::clone(sandbox), artifact_store: Arc::clone(&artifact_store), - emitter: Arc::clone(&emitter), + emitter: Arc::clone(emitter), run_dir: run_dir.clone(), run_id: run_options.run_id.clone(), - run_options: Arc::clone(&run_options), + run_options: Arc::clone(run_options), start_node_id, checkpoint_git_result: Arc::clone(&checkpoint_git_result), last_git_sha: Arc::clone(&last_git_sha), }; let artifact = ArtifactLifecycle::new( - Arc::clone(&sandbox), + Arc::clone(sandbox), Arc::clone(&artifact_store), Some(runtime_state.artifact_values_dir()), - Arc::clone(&emitter), + Arc::clone(emitter), runtime_state.assets_dir(), run_options.asset_globs().to_vec(), ); diff --git a/lib/crates/fabro-workflows/src/node_handler.rs b/lib/crates/fabro-workflows/src/node_handler.rs index 78eb6f22f..af1ccd8fd 100644 --- a/lib/crates/fabro-workflows/src/node_handler.rs +++ b/lib/crates/fabro-workflows/src/node_handler.rs @@ -99,7 +99,7 @@ impl NodeHandler for WorkflowNodeHandler { })) } Err(panic_payload) => { - let msg = format_panic_message(panic_payload); + let msg = format_panic_message(&panic_payload); let visit = context.node_visit_count().max(1); let panic_dir = run_dir::node_dir(&self.run_dir, &gv_node.id, visit); let _ = std::fs::create_dir_all(&panic_dir); diff --git a/lib/crates/fabro-workflows/src/operations/fork.rs b/lib/crates/fabro-workflows/src/operations/fork.rs index df1a27834..2bace5752 100644 --- a/lib/crates/fabro-workflows/src/operations/fork.rs +++ b/lib/crates/fabro-workflows/src/operations/fork.rs @@ -19,7 +19,7 @@ pub struct ForkRunInput { /// Create a new run that branches from an existing run at a specific checkpoint. /// /// Returns the new run ID. -pub fn fork(store: &Store, input: ForkRunInput) -> Result { +pub fn fork(store: &Store, input: &ForkRunInput) -> Result { let timeline = build_timeline(store, &input.source_run_id)?; let entry = match input.target.as_ref() { Some(target) => timeline.resolve(target)?, @@ -39,7 +39,7 @@ fn fork_from_entry( let new_run_id = ulid::Ulid::new().to_string(); let sig = Signature::now("Fabro", "noreply@fabro.sh")?; - let new_run_branch = format!("{}{new_run_id}", RUN_BRANCH_PREFIX); + let new_run_branch = format!("{RUN_BRANCH_PREFIX}{new_run_id}"); match &entry.run_commit_sha { Some(sha) => { let oid = @@ -134,7 +134,7 @@ fn fork_from_entry( .map_err(|e| anyhow::anyhow!("failed to write metadata entries: {e}"))?; if push { - let source_run_branch = format!("{}{source_run_id}", RUN_BRANCH_PREFIX); + let source_run_branch = format!("{RUN_BRANCH_PREFIX}{source_run_id}"); let run_refspec = format!("refs/heads/{new_run_branch}:refs/heads/{new_run_branch}"); let meta_refspec = format!("refs/heads/{new_meta_branch}:refs/heads/{new_meta_branch}"); push_run_branches( @@ -246,7 +246,7 @@ mod tests { let new_run_id = fork( &store, - ForkRunInput { + &ForkRunInput { source_run_id: source_run_id.to_string(), target: Some(RewindTarget::from_str("@2").unwrap()), push: false, diff --git a/lib/crates/fabro-workflows/src/operations/rewind.rs b/lib/crates/fabro-workflows/src/operations/rewind.rs index 22fc6fc4c..0089198b0 100644 --- a/lib/crates/fabro-workflows/src/operations/rewind.rs +++ b/lib/crates/fabro-workflows/src/operations/rewind.rs @@ -166,7 +166,7 @@ fn backfill_run_shas(store: &Store, run_id: &str, timeline: &mut [TimelineEntry] return; } - let run_branch = format!("{}{run_id}", RUN_BRANCH_PREFIX); + let run_branch = format!("{RUN_BRANCH_PREFIX}{run_id}"); let Ok(sig) = Signature::now("Fabro", "noreply@fabro.sh") else { return; }; @@ -241,7 +241,7 @@ fn detect_parallel_interior(graph: &Graph) -> HashMap { interior_map } -pub fn rewind(store: &Store, input: RewindInput) -> Result<()> { +pub fn rewind(store: &Store, input: &RewindInput) -> Result<()> { let timeline = build_timeline(store, &input.run_id)?; let entry = timeline.resolve(&input.target)?; rewind_to_entry(store, &input.run_id, entry, input.push) @@ -258,7 +258,7 @@ fn rewind_to_entry(store: &Store, run_id: &str, entry: &TimelineEntry, push: boo entry.ordinal, entry.node_name ); - let run_branch = format!("{}{run_id}", RUN_BRANCH_PREFIX); + let run_branch = format!("{RUN_BRANCH_PREFIX}{run_id}"); match &entry.run_commit_sha { Some(sha) => { let oid = @@ -494,7 +494,7 @@ mod tests { rewind( &store, - RewindInput { + &RewindInput { run_id: "run-1".to_string(), target: RewindTarget::Ordinal(1), push: false, diff --git a/lib/crates/fabro-workflows/src/operations/start.rs b/lib/crates/fabro-workflows/src/operations/start.rs index a5545dc8b..efadd4d9c 100644 --- a/lib/crates/fabro-workflows/src/operations/start.rs +++ b/lib/crates/fabro-workflows/src/operations/start.rs @@ -148,7 +148,7 @@ fn persist_terminal_engine_failure(run_dir: &Path, error: &FabroError, duration: run_dir, final_status, failure_reason, - duration.as_millis() as u64, + u64::try_from(duration.as_millis()).unwrap(), None, ); persist_terminal_outcome(run_dir, &conclusion, run_status, status_reason); @@ -472,7 +472,7 @@ impl RunSession { let executed = pipeline::execute(initialized).await; let failed = !matches!( executed.outcome.as_ref().map(|outcome| &outcome.status), - Ok(StageStatus::Success) | Ok(StageStatus::PartialSuccess) + Ok(StageStatus::Success | StageStatus::PartialSuccess) ); let retro_opts = RetroOptions { diff --git a/lib/crates/fabro-workflows/src/outcome.rs b/lib/crates/fabro-workflows/src/outcome.rs index 8184cc7b3..8ae38723b 100644 --- a/lib/crates/fabro-workflows/src/outcome.rs +++ b/lib/crates/fabro-workflows/src/outcome.rs @@ -24,6 +24,7 @@ pub trait OutcomeExt: Sized { fn fail_classify(reason: impl Into) -> Self; fn retry_classify(reason: impl Into) -> Self; fn simulated(node_id: &str) -> Self; + #[must_use] fn with_signature(self, sig: Option>) -> Self; fn failure_reason(&self) -> Option<&str>; fn failure_category(&self) -> Option; diff --git a/lib/crates/fabro-workflows/src/pipeline/execute.rs b/lib/crates/fabro-workflows/src/pipeline/execute.rs index 13529daf2..c84ce20ed 100644 --- a/lib/crates/fabro-workflows/src/pipeline/execute.rs +++ b/lib/crates/fabro-workflows/src/pipeline/execute.rs @@ -85,12 +85,12 @@ pub async fn execute(init: Initialized) -> Executed { let settings_arc = Arc::new(run_options.clone()); let lifecycle = WorkflowLifecycle::new( - Arc::clone(&emitter), + &emitter, hook_runner.clone(), - Arc::clone(&sandbox), + &sandbox, graph_arc, - run_options.run_dir.clone(), - settings_arc, + &run_options.run_dir, + &settings_arc, checkpoint.is_some(), ); @@ -204,7 +204,7 @@ pub async fn execute(init: Initialized) -> Executed { let graph_max = graph.max_node_visits(); let max_node_visits = if graph_max > 0 { - Some(graph_max as usize) + Some(usize::try_from(graph_max).unwrap()) } else if run_options.dry_run_enabled() { Some(10) } else { @@ -228,12 +228,15 @@ pub async fn execute(init: Initialized) -> Executed { return; } let last = emitter.last_event_at(); - let now = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_millis() as i64; + let now = i64::try_from( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_millis(), + ) + .unwrap(); let idle_ms = now.saturating_sub(last); - if idle_ms >= stall_timeout.as_millis() as i64 { + if idle_ms >= i64::try_from(stall_timeout.as_millis()).unwrap() { token_clone.cancel(); return; } diff --git a/lib/crates/fabro-workflows/src/pipeline/pull_request.rs b/lib/crates/fabro-workflows/src/pipeline/pull_request.rs index c7b81b4c8..a50f06ee2 100644 --- a/lib/crates/fabro-workflows/src/pipeline/pull_request.rs +++ b/lib/crates/fabro-workflows/src/pipeline/pull_request.rs @@ -74,7 +74,7 @@ fn format_duration_ms(ms: u64) -> String { if secs >= 60 { format!("{}m {}s", secs / 60, secs % 60) } else { - format!("{}s", secs) + format!("{secs}s") } } diff --git a/lib/crates/fabro-workflows/src/pipeline/retro.rs b/lib/crates/fabro-workflows/src/pipeline/retro.rs index 4158d16ed..a3ed8f229 100644 --- a/lib/crates/fabro-workflows/src/pipeline/retro.rs +++ b/lib/crates/fabro-workflows/src/pipeline/retro.rs @@ -84,7 +84,7 @@ pub async fn run_retro(options: &RetroOptions, dry_run: bool) -> Option { Err(anyhow::anyhow!("No LLM client available")) }; - let duration_ms = retro_start.elapsed().as_millis() as u64; + let duration_ms = u64::try_from(retro_start.elapsed().as_millis()).unwrap(); if let Some(ref emitter) = options.emitter { match &narrative_result { Ok(_) => emitter.emit(&WorkflowRunEvent::RetroCompleted { duration_ms }),