diff --git a/Cargo.toml b/Cargo.toml index 2792e9f93..1ced0b8b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,53 @@ exec = "0.3" slatedb = "0.11.2" object_store = "0.12.5" +[workspace.lints.rust] +unsafe_code = "warn" +unreachable_pub = "warn" + [workspace.lints.clippy] +pedantic = { level = "warn", priority = -2 } +# Allowed pedantic lints +char_lit_as_u8 = "allow" +collapsible_else_if = "allow" +collapsible_if = "allow" +implicit_hasher = "allow" +map_unwrap_or = "allow" +match_same_arms = "allow" +missing_errors_doc = "allow" +missing_panics_doc = "allow" +module_name_repetitions = "allow" +must_use_candidate = "allow" +similar_names = "allow" +struct_excessive_bools = "allow" +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" +dbg_macro = "warn" +empty_drop = "warn" +empty_structs_with_brackets = "warn" +exit = "warn" +get_unwrap = "warn" +rc_buffer = "warn" +rc_mutex = "warn" +rest_pat_in_fully_bound_structs = "warn" +use_self = "warn" +# Project-specific lints wildcard_imports = "warn" absolute_paths = "warn" diff --git a/lib/crates/fabro-agent/src/cli.rs b/lib/crates/fabro-agent/src/cli.rs index f6a0581fc..d5f369b28 100644 --- a/lib/crates/fabro-agent/src/cli.rs +++ b/lib/crates/fabro-agent/src/cli.rs @@ -117,6 +117,7 @@ fn is_auto_approved(level: PermissionLevel, category: &str) -> bool { ) } +#[allow(clippy::print_stderr)] fn build_tool_approval( permissions: PermissionLevel, is_interactive: bool, @@ -237,6 +238,7 @@ fn format_tool_args(args: &serde_json::Value, cwd: &str) -> String { .join(", ") } +#[allow(clippy::print_stdout)] fn print_output(session: &Session, styles: &Styles) { for turn in session.history().turns() { if let Turn::Assistant { content, .. } = turn { @@ -247,6 +249,7 @@ fn print_output(session: &Session, styles: &Styles) { } } +#[allow(clippy::print_stderr)] fn print_summary(session: &Session, styles: &Styles) { let (mut turn_count, mut tool_call_count, mut total_tokens) = (0usize, 0usize, 0i64); for turn in session.history().turns() { @@ -281,6 +284,7 @@ struct DebugMiddleware { #[async_trait::async_trait] impl Middleware for DebugMiddleware { + #[allow(clippy::print_stderr)] async fn handle_complete(&self, request: Request, next: NextFn) -> Result { let s = self.styles; eprintln!( @@ -323,6 +327,7 @@ struct VerboseMiddleware { #[async_trait::async_trait] impl Middleware for VerboseMiddleware { + #[allow(clippy::print_stderr)] async fn handle_complete(&self, request: Request, next: NextFn) -> Result { let s = self.styles; eprintln!( @@ -357,6 +362,7 @@ pub async fn run_with_args( run_with_args_and_client(args, None, mcp_servers).await } +#[allow(clippy::print_stdout, clippy::print_stderr)] pub async fn run_with_args_and_client( args: AgentArgs, llm_client: Option, @@ -429,7 +435,7 @@ pub async fn run_with_args_and_client( ))); let manager_for_callback = manager.clone(); let factory_client = client.clone(); - let factory_model = model.to_string(); + let factory_model = model.clone(); let factory_env = Arc::clone(&env); let factory_hooks = config.tool_hooks.clone(); let factory: SessionFactory = Arc::new(move || { @@ -490,7 +496,9 @@ pub async fn run_with_args_and_client( tokio::spawn(async move { signal::ctrl_c().await.ok(); { - let mut guard = abort_reason.lock().unwrap_or_else(|e| e.into_inner()); + let mut guard = abort_reason + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); if guard.is_none() { *guard = Some(AbortReason::Cancelled); } diff --git a/lib/crates/fabro-agent/src/compaction.rs b/lib/crates/fabro-agent/src/compaction.rs index dee426486..a632a4644 100644 --- a/lib/crates/fabro-agent/src/compaction.rs +++ b/lib/crates/fabro-agent/src/compaction.rs @@ -1,3 +1,5 @@ +use std::fmt::Write; + use crate::agent_profile::AgentProfile; use crate::error::AgentError; use crate::event::EventEmitter; @@ -198,7 +200,7 @@ pub fn render_turns_for_summary(turns: &[Turn]) -> String { for turn in turns { match turn { Turn::User { content, .. } => { - out.push_str(&format!("User: {content}\n")); + let _ = writeln!(out, "User: {content}"); } Turn::Assistant { content, @@ -206,7 +208,7 @@ pub fn render_turns_for_summary(turns: &[Turn]) -> String { .. } => { if !content.is_empty() { - out.push_str(&format!("Assistant: {content}\n")); + let _ = writeln!(out, "Assistant: {content}"); } for tc in tool_calls { let args_str = tc.arguments.to_string(); @@ -218,7 +220,7 @@ pub fn render_turns_for_summary(turns: &[Turn]) -> String { } else { args_str }; - out.push_str(&format!("[Tool call: {}] {truncated}\n", tc.name)); + let _ = writeln!(out, "[Tool call: {}] {truncated}", tc.name); } } Turn::ToolResults { results, .. } => { @@ -232,14 +234,14 @@ pub fn render_turns_for_summary(turns: &[Turn]) -> String { } else { content_str }; - out.push_str(&format!("[Tool result: {}] {truncated}\n", r.tool_call_id)); + let _ = writeln!(out, "[Tool result: {}] {truncated}", r.tool_call_id); } } Turn::System { content, .. } => { - out.push_str(&format!("System: {content}\n")); + let _ = writeln!(out, "System: {content}"); } Turn::Steering { content, .. } => { - out.push_str(&format!("Steering: {content}\n")); + let _ = writeln!(out, "Steering: {content}"); } } } diff --git a/lib/crates/fabro-agent/src/config.rs b/lib/crates/fabro-agent/src/config.rs index f474b79c3..af80f7e25 100644 --- a/lib/crates/fabro-agent/src/config.rs +++ b/lib/crates/fabro-agent/src/config.rs @@ -102,6 +102,7 @@ impl std::fmt::Debug for SessionConfig { .field("max_command_timeout_ms", &self.max_command_timeout_ms) .field("max_tokens", &self.max_tokens) .field("reasoning_effort", &self.reasoning_effort) + .field("speed", &self.speed) .field("tool_output_limits", &self.tool_output_limits) .field("tool_line_limits", &self.tool_line_limits) .field("enable_loop_detection", &self.enable_loop_detection) diff --git a/lib/crates/fabro-agent/src/file_tracker.rs b/lib/crates/fabro-agent/src/file_tracker.rs index 3c6a00cc1..ed8cc04fd 100644 --- a/lib/crates/fabro-agent/src/file_tracker.rs +++ b/lib/crates/fabro-agent/src/file_tracker.rs @@ -1,5 +1,7 @@ -use fabro_llm::types::{ToolCall, ToolResult}; use std::collections::BTreeMap; +use std::fmt::Write; + +use fabro_llm::types::{ToolCall, ToolResult}; #[derive(Debug, Clone, Copy, Default)] struct FileOps { @@ -47,7 +49,7 @@ impl FileTracker { if ops.edited { labels.push("edited"); } - output.push_str(&format!("- {path} ({})\n", labels.join(", "))); + let _ = writeln!(output, "- {path} ({})", labels.join(", ")); } output } diff --git a/lib/crates/fabro-agent/src/memory.rs b/lib/crates/fabro-agent/src/memory.rs index b38fa6d46..4a46d4f44 100644 --- a/lib/crates/fabro-agent/src/memory.rs +++ b/lib/crates/fabro-agent/src/memory.rs @@ -63,7 +63,7 @@ pub async fn discover_memory( } } - let total_bytes: usize = results.iter().map(|d| d.len()).sum(); + let total_bytes: usize = results.iter().map(std::string::String::len).sum(); info!(files = results.len(), total_bytes, "Project docs loaded"); results diff --git a/lib/crates/fabro-agent/src/session.rs b/lib/crates/fabro-agent/src/session.rs index 0adcb3a5e..3bda269bb 100644 --- a/lib/crates/fabro-agent/src/session.rs +++ b/lib/crates/fabro-agent/src/session.rs @@ -113,12 +113,11 @@ impl Session { .await; // Discover skills - let skill_dirs = match &self.config.skill_dirs { - Some(dirs) => dirs.clone(), - None => { - let home = dirs::home_dir().map(|p| p.to_string_lossy().to_string()); - default_skill_dirs(home.as_deref(), self.config.git_root.as_deref()) - } + let skill_dirs = if let Some(dirs) = &self.config.skill_dirs { + dirs.clone() + } else { + let home = dirs::home_dir().map(|p| p.to_string_lossy().to_string()); + default_skill_dirs(home.as_deref(), self.config.git_root.as_deref()) }; self.skills = discover_skills(self.sandbox.as_ref(), &skill_dirs).await; debug!(skill_count = self.skills.len(), "Skills discovered"); @@ -291,15 +290,14 @@ impl Session { } // Get the preview URL for the port, or fall back to localhost for local sandboxes - match sandbox.get_preview_url(port).await? { - Some(url_and_headers) => Ok(url_and_headers), - None => { - info!(port, "No preview URL available, using localhost"); - Ok(( - format!("http://localhost:{port}"), - std::collections::HashMap::new(), - )) - } + if let Some(url_and_headers) = sandbox.get_preview_url(port).await? { + Ok(url_and_headers) + } else { + info!(port, "No preview URL available, using localhost"); + Ok(( + format!("http://localhost:{port}"), + std::collections::HashMap::new(), + )) } } @@ -389,7 +387,10 @@ impl Session { } fn set_abort_reason(&self, reason: AbortReason) { - let mut guard = self.abort_reason.lock().unwrap_or_else(|e| e.into_inner()); + let mut guard = self + .abort_reason + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); if guard.is_none() { *guard = Some(reason); } @@ -399,7 +400,7 @@ impl Session { let reason = self .abort_reason .lock() - .unwrap_or_else(|e| e.into_inner()) + .unwrap_or_else(std::sync::PoisonError::into_inner) .clone() .unwrap_or(AbortReason::Cancelled); AgentError::Aborted(reason) @@ -544,7 +545,9 @@ impl Session { tokio::spawn(async move { time::sleep(duration).await; { - let mut guard = reason_handle.lock().unwrap_or_else(|e| e.into_inner()); + let mut guard = reason_handle + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); if guard.is_none() { *guard = Some(AbortReason::WallClockTimeout); } @@ -777,14 +780,11 @@ impl Session { } } - let response = match response { - Some(response) => response, - None => { - return Err(self.emit_llm_error(SdkError::Stream { - message: "Stream ended without a Finish event (after retries)".into(), - source: None, - })); - } + let Some(response) = response else { + return Err(self.emit_llm_error(SdkError::Stream { + message: "Stream ended without a Finish event (after retries)".into(), + source: None, + })); }; // Record assistant turn diff --git a/lib/crates/fabro-agent/src/skills.rs b/lib/crates/fabro-agent/src/skills.rs index fbd5176ff..e8fe1c257 100644 --- a/lib/crates/fabro-agent/src/skills.rs +++ b/lib/crates/fabro-agent/src/skills.rs @@ -225,22 +225,17 @@ pub async fn discover_skills(env: &dyn Sandbox, dirs: &[String]) -> Vec { std::collections::HashMap::new(); for dir in dirs { - let paths = match env.glob("*/SKILL.md", Some(dir)).await { - Ok(paths) => paths, - Err(_) => continue, + let Ok(paths) = env.glob("*/SKILL.md", Some(dir)).await else { + continue; }; for path in paths { - let content = match env.read_file(&path, None, None).await { - Ok(c) => c, - Err(_) => continue, + let Ok(content) = env.read_file(&path, None, None).await else { + continue; }; - match parse_skill(&content) { - Ok(skill) => { - skills_by_name.insert(skill.name.clone(), skill); - } - Err(_) => continue, + if let Ok(skill) = parse_skill(&content) { + skills_by_name.insert(skill.name.clone(), skill); } } } diff --git a/lib/crates/fabro-agent/src/tool_execution.rs b/lib/crates/fabro-agent/src/tool_execution.rs index 08d0dbfb8..189e48c1c 100644 --- a/lib/crates/fabro-agent/src/tool_execution.rs +++ b/lib/crates/fabro-agent/src/tool_execution.rs @@ -238,12 +238,11 @@ async fn execute_and_emit_one_tool_with_lookup( // Post-tool-use hooks if let Some(hooks) = tool_hooks { let fallback; - let content_str = match result.content.as_str() { - Some(s) => s, - None => { - fallback = result.content.to_string(); - &fallback - } + let content_str = if let Some(s) = result.content.as_str() { + s + } else { + fallback = result.content.to_string(); + &fallback }; if result.is_error { debug!(tool = %tc.name, hook_event = "post_tool_use_failure", "Calling tool hook"); diff --git a/lib/crates/fabro-agent/src/tools.rs b/lib/crates/fabro-agent/src/tools.rs index 06c99cd78..252476efc 100644 --- a/lib/crates/fabro-agent/src/tools.rs +++ b/lib/crates/fabro-agent/src/tools.rs @@ -221,7 +221,10 @@ pub fn make_shell_tool_with_config(config: &SessionConfig) -> RegisteredTool { .min(max_timeout); tracing::debug!( - env_var_count = ctx.tool_env.as_ref().map_or(0, |e| e.len()), + env_var_count = ctx + .tool_env + .as_ref() + .map_or(0, std::collections::HashMap::len), "Injecting sandbox env vars into tool execution" ); let result = ctx diff --git a/lib/crates/fabro-agent/src/types.rs b/lib/crates/fabro-agent/src/types.rs index 63d8b695c..cc0e00e08 100644 --- a/lib/crates/fabro-agent/src/types.rs +++ b/lib/crates/fabro-agent/src/types.rs @@ -10,7 +10,7 @@ mod system_time_iso8601 { use serde::{self, Deserialize, Deserializer, Serializer}; use std::time::SystemTime; - pub fn serialize(time: &SystemTime, serializer: S) -> Result + pub(super) fn serialize(time: &SystemTime, serializer: S) -> Result where S: Serializer, { @@ -18,7 +18,7 @@ mod system_time_iso8601 { serializer.serialize_str(&dt.to_rfc3339_opts(SecondsFormat::Millis, true)) } - pub fn deserialize<'de, D>(deserializer: D) -> Result + pub(super) fn deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { @@ -67,7 +67,7 @@ impl Turn { /// `provider_parts`, if any. #[must_use] pub fn reasoning_text(&self) -> Option<&str> { - let Turn::Assistant { provider_parts, .. } = self else { + let Self::Assistant { provider_parts, .. } = self else { return None; }; provider_parts.iter().find_map(|p| match p { @@ -188,7 +188,7 @@ pub enum AgentEvent { SubAgentEvent { agent_id: String, depth: usize, - event: Box, + event: Box, }, McpServerReady { server_name: String, diff --git a/lib/crates/fabro-agent/src/v4a_patch.rs b/lib/crates/fabro-agent/src/v4a_patch.rs index 750a82b3d..f38e6098e 100644 --- a/lib/crates/fabro-agent/src/v4a_patch.rs +++ b/lib/crates/fabro-agent/src/v4a_patch.rs @@ -373,7 +373,7 @@ fn apply_hunks(content: &str, hunks: &[Hunk]) -> Result { } // Calculate total lines consumed from original - let explicit_context_count = if has_explicit_context { 1 } else { 0 }; + let explicit_context_count = usize::from(has_explicit_context); let total_original_lines = explicit_context_count + hunk .changes diff --git a/lib/crates/fabro-api-types/Cargo.toml b/lib/crates/fabro-api-types/Cargo.toml index e99adbc03..38cb74933 100644 --- a/lib/crates/fabro-api-types/Cargo.toml +++ b/lib/crates/fabro-api-types/Cargo.toml @@ -8,8 +8,9 @@ description = "Generated Rust types from the Fabro API OpenAPI spec" [lib] doctest = false -[lints] -workspace = true +[lints.clippy] +# Auto-generated crate; only enforce project-specific lints +wildcard_imports = "warn" [dependencies] chrono = { workspace = true, features = ["serde"] } diff --git a/lib/crates/fabro-api/src/demo/mod.rs b/lib/crates/fabro-api/src/demo/mod.rs index 9cde66938..2ce3b669f 100644 --- a/lib/crates/fabro-api/src/demo/mod.rs +++ b/lib/crates/fabro-api/src/demo/mod.rs @@ -1,5 +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)] use std::sync::Arc; @@ -14,7 +15,7 @@ use crate::jwt_auth::AuthenticatedService; use crate::server::{AppState, PaginationParams}; #[derive(serde::Deserialize)] -pub struct RetroListParams { +pub(crate) struct RetroListParams { #[serde(rename = "page[limit]", default = "crate::server::default_page_limit")] limit: u32, #[serde(rename = "page[offset]", default)] @@ -41,7 +42,7 @@ fn paginated_response( // ── Runs ─────────────────────────────────────────────────────────────── -pub async fn list_runs( +pub(crate) async fn list_runs( _auth: AuthenticatedService, State(_state): State>, Query(pagination): Query, @@ -49,7 +50,7 @@ pub async fn list_runs( paginated_response(runs::list_items(), &pagination) } -pub async fn start_run_stub( +pub(crate) async fn start_run_stub( _auth: AuthenticatedService, State(_state): State>, ) -> Response { @@ -60,7 +61,7 @@ pub async fn start_run_stub( .into_response() } -pub async fn get_run_stages( +pub(crate) async fn get_run_stages( _auth: AuthenticatedService, State(_state): State>, Path(_id): Path, @@ -69,7 +70,7 @@ pub async fn get_run_stages( paginated_response(runs::stages(), &pagination) } -pub async fn get_stage_turns( +pub(crate) async fn get_stage_turns( _auth: AuthenticatedService, State(_state): State>, Path((_id, _stage_id)): Path<(String, String)>, @@ -78,7 +79,7 @@ pub async fn get_stage_turns( paginated_response(runs::turns(), &pagination) } -pub async fn get_run_files( +pub(crate) async fn get_run_files( _auth: AuthenticatedService, State(_state): State>, Path(_id): Path, @@ -87,7 +88,7 @@ pub async fn get_run_files( paginated_response(runs::files(), &pagination) } -pub async fn get_run_usage( +pub(crate) async fn get_run_usage( _auth: AuthenticatedService, State(_state): State>, Path(_id): Path, @@ -95,7 +96,7 @@ pub async fn get_run_usage( (StatusCode::OK, Json(runs::usage())).into_response() } -pub async fn get_run_verification( +pub(crate) async fn get_run_verification( _auth: AuthenticatedService, State(_state): State>, Path(_id): Path, @@ -104,7 +105,7 @@ pub async fn get_run_verification( paginated_response(runs::verifications(), &pagination) } -pub async fn get_run_settings( +pub(crate) async fn get_run_settings( _auth: AuthenticatedService, State(_state): State>, Path(_id): Path, @@ -112,7 +113,7 @@ pub async fn get_run_settings( (StatusCode::OK, Json(runs::settings())).into_response() } -pub async fn steer_run_stub( +pub(crate) async fn steer_run_stub( _auth: AuthenticatedService, State(_state): State>, Path(_id): Path, @@ -120,7 +121,7 @@ pub async fn steer_run_stub( StatusCode::ACCEPTED.into_response() } -pub async fn generate_preview_url_stub( +pub(crate) async fn generate_preview_url_stub( _auth: AuthenticatedService, State(_state): State>, Path(_id): Path, @@ -132,7 +133,7 @@ pub async fn generate_preview_url_stub( .into_response() } -pub async fn get_run_status( +pub(crate) async fn get_run_status( _auth: AuthenticatedService, State(_state): State>, Path(id): Path, @@ -153,7 +154,7 @@ pub async fn get_run_status( } } -pub async fn get_questions_stub( +pub(crate) async fn get_questions_stub( _auth: AuthenticatedService, State(_state): State>, Path(_id): Path, @@ -162,7 +163,7 @@ pub async fn get_questions_stub( paginated_response(runs::questions(), &pagination) } -pub async fn answer_stub( +pub(crate) async fn answer_stub( _auth: AuthenticatedService, State(_state): State>, Path((_id, _qid)): Path<(String, String)>, @@ -170,7 +171,7 @@ pub async fn answer_stub( StatusCode::NO_CONTENT.into_response() } -pub async fn run_events_stub( +pub(crate) async fn run_events_stub( _auth: AuthenticatedService, State(_state): State>, Path(_id): Path, @@ -178,7 +179,7 @@ pub async fn run_events_stub( ApiError::new(StatusCode::GONE, "Event stream closed.").into_response() } -pub async fn checkpoint_stub( +pub(crate) async fn checkpoint_stub( _auth: AuthenticatedService, State(_state): State>, Path(_id): Path, @@ -186,7 +187,7 @@ pub async fn checkpoint_stub( (StatusCode::OK, Json(serde_json::json!(null))).into_response() } -pub async fn context_stub( +pub(crate) async fn context_stub( _auth: AuthenticatedService, State(_state): State>, Path(_id): Path, @@ -194,7 +195,7 @@ pub async fn context_stub( (StatusCode::OK, Json(serde_json::json!({}))).into_response() } -pub async fn cancel_stub( +pub(crate) async fn cancel_stub( _auth: AuthenticatedService, State(_state): State>, Path(_id): Path, @@ -202,7 +203,7 @@ pub async fn cancel_stub( (StatusCode::OK, Json(serde_json::json!({"id": _id, "status": "cancelled", "created_at": "2026-03-06T14:30:00Z"}))).into_response() } -pub async fn pause_stub( +pub(crate) async fn pause_stub( _auth: AuthenticatedService, State(_state): State>, Path(_id): Path, @@ -210,7 +211,7 @@ pub async fn pause_stub( (StatusCode::OK, Json(serde_json::json!({"id": _id, "status": "paused", "created_at": "2026-03-06T14:30:00Z"}))).into_response() } -pub async fn unpause_stub( +pub(crate) async fn unpause_stub( _auth: AuthenticatedService, State(_state): State>, Path(_id): Path, @@ -218,7 +219,7 @@ pub async fn unpause_stub( (StatusCode::OK, Json(serde_json::json!({"id": _id, "status": "running", "created_at": "2026-03-06T14:30:00Z"}))).into_response() } -pub async fn get_run_graph( +pub(crate) async fn get_run_graph( _auth: AuthenticatedService, State(_state): State>, Path(_id): Path, @@ -229,7 +230,7 @@ pub async fn get_run_graph( crate::server::render_dot_svg(dot_source).await } -pub async fn get_run_retro( +pub(crate) async fn get_run_retro( _auth: AuthenticatedService, State(_state): State>, Path(id): Path, @@ -242,7 +243,7 @@ pub async fn get_run_retro( // ── Workflows ────────────────────────────────────────────────────────── -pub async fn list_workflows( +pub(crate) async fn list_workflows( _auth: AuthenticatedService, State(_state): State>, Query(pagination): Query, @@ -250,7 +251,7 @@ pub async fn list_workflows( paginated_response(workflows::list_items(), &pagination) } -pub async fn get_workflow( +pub(crate) async fn get_workflow( _auth: AuthenticatedService, State(_state): State>, Path(name): Path, @@ -261,7 +262,7 @@ pub async fn get_workflow( } } -pub async fn list_workflow_runs( +pub(crate) async fn list_workflow_runs( _auth: AuthenticatedService, State(_state): State>, Path(name): Path, @@ -276,7 +277,7 @@ pub async fn list_workflow_runs( // ── Verification ────────────────────────────────────────────────────── -pub async fn list_verification_criteria( +pub(crate) async fn list_verification_criteria( _auth: AuthenticatedService, State(_state): State>, Query(pagination): Query, @@ -284,7 +285,7 @@ pub async fn list_verification_criteria( paginated_response(verifications::criteria(), &pagination) } -pub async fn get_verification_criterion( +pub(crate) async fn get_verification_criterion( _auth: AuthenticatedService, State(_state): State>, Path(id): Path, @@ -295,7 +296,7 @@ pub async fn get_verification_criterion( } } -pub async fn list_verification_controls( +pub(crate) async fn list_verification_controls( _auth: AuthenticatedService, State(_state): State>, Query(pagination): Query, @@ -303,7 +304,7 @@ pub async fn list_verification_controls( paginated_response(verifications::controls(), &pagination) } -pub async fn get_verification_control( +pub(crate) async fn get_verification_control( _auth: AuthenticatedService, State(_state): State>, Path(id): Path, @@ -317,7 +318,7 @@ pub async fn get_verification_control( // ── Signoffs ────────────────────────────────────────────────────────── #[derive(serde::Deserialize)] -pub struct SignoffListParams { +pub(crate) struct SignoffListParams { #[serde(rename = "page[limit]", default = "crate::server::default_page_limit")] limit: u32, #[serde(rename = "page[offset]", default)] @@ -327,7 +328,7 @@ pub struct SignoffListParams { commit_sha: Option, } -pub async fn list_signoffs( +pub(crate) async fn list_signoffs( _auth: AuthenticatedService, State(_state): State>, Query(params): Query, @@ -346,7 +347,7 @@ pub async fn list_signoffs( ) } -pub async fn get_signoff( +pub(crate) async fn get_signoff( _auth: AuthenticatedService, State(_state): State>, Path(id): Path, @@ -357,7 +358,7 @@ pub async fn get_signoff( } } -pub async fn create_signoff_stub( +pub(crate) async fn create_signoff_stub( _auth: AuthenticatedService, State(_state): State>, ) -> Response { @@ -366,7 +367,7 @@ pub async fn create_signoff_stub( // ── Retros ───────────────────────────────────────────────────────────── -pub async fn list_retros( +pub(crate) async fn list_retros( _auth: AuthenticatedService, State(_state): State>, Query(params): Query, @@ -397,7 +398,7 @@ pub async fn list_retros( // ── Sessions ─────────────────────────────────────────────────────────── -pub async fn list_sessions( +pub(crate) async fn list_sessions( _auth: AuthenticatedService, State(_state): State>, Query(pagination): Query, @@ -405,7 +406,7 @@ pub async fn list_sessions( paginated_response(sessions::list_items(), &pagination) } -pub async fn create_session_stub( +pub(crate) async fn create_session_stub( _auth: AuthenticatedService, State(_state): State>, ) -> Response { @@ -418,7 +419,7 @@ pub async fn create_session_stub( .into_response() } -pub async fn get_session( +pub(crate) async fn get_session( _auth: AuthenticatedService, State(_state): State>, Path(id): Path, @@ -429,7 +430,7 @@ pub async fn get_session( } } -pub async fn send_message_stub( +pub(crate) async fn send_message_stub( _auth: AuthenticatedService, State(_state): State>, Path(_id): Path, @@ -441,7 +442,7 @@ pub async fn send_message_stub( .into_response() } -pub async fn session_events_stub( +pub(crate) async fn session_events_stub( _auth: AuthenticatedService, State(_state): State>, headers: axum::http::HeaderMap, @@ -449,9 +450,8 @@ pub async fn session_events_stub( ) -> Response { use axum::response::sse::{Event, Sse}; - let session = match sessions::detail(&id) { - Some(s) => s, - None => return ApiError::not_found("Session not found.").into_response(), + let Some(session) = sessions::detail(&id) else { + return ApiError::not_found("Session not found.").into_response(); }; let last_event_id: Option = headers @@ -495,7 +495,7 @@ pub async fn session_events_stub( // ── Insights ─────────────────────────────────────────────────────────── -pub async fn list_saved_queries( +pub(crate) async fn list_saved_queries( _auth: AuthenticatedService, State(_state): State>, Query(pagination): Query, @@ -503,7 +503,7 @@ pub async fn list_saved_queries( paginated_response(insights::saved_queries(), &pagination) } -pub async fn save_query_stub( +pub(crate) async fn save_query_stub( _auth: AuthenticatedService, State(_state): State>, ) -> Response { @@ -514,7 +514,7 @@ pub async fn save_query_stub( .into_response() } -pub async fn get_saved_query( +pub(crate) async fn get_saved_query( _auth: AuthenticatedService, State(_state): State>, Path(id): Path, @@ -525,7 +525,7 @@ pub async fn get_saved_query( } } -pub async fn update_query_stub( +pub(crate) async fn update_query_stub( _auth: AuthenticatedService, State(_state): State>, Path(_id): Path, @@ -537,7 +537,7 @@ pub async fn update_query_stub( .into_response() } -pub async fn delete_query_stub( +pub(crate) async fn delete_query_stub( _auth: AuthenticatedService, State(_state): State>, Path(_id): Path, @@ -545,7 +545,7 @@ pub async fn delete_query_stub( StatusCode::NO_CONTENT.into_response() } -pub async fn execute_query_stub( +pub(crate) async fn execute_query_stub( _auth: AuthenticatedService, State(_state): State>, ) -> Response { @@ -561,7 +561,7 @@ pub async fn execute_query_stub( .into_response() } -pub async fn list_query_history( +pub(crate) async fn list_query_history( _auth: AuthenticatedService, State(_state): State>, Query(pagination): Query, @@ -571,7 +571,7 @@ pub async fn list_query_history( // ── Models ──────────────────────────────────────────────────────────── -pub async fn list_models( +pub(crate) async fn list_models( _auth: AuthenticatedService, State(_state): State>, Query(pagination): Query, @@ -588,7 +588,7 @@ pub async fn list_models( // ── Settings ─────────────────────────────────────────────────────────── -pub async fn get_server_settings( +pub(crate) async fn get_server_settings( _auth: AuthenticatedService, State(_state): State>, ) -> Response { @@ -597,7 +597,7 @@ pub async fn get_server_settings( // ── Usage ────────────────────────────────────────────────────────────── -pub async fn get_aggregate_usage( +pub(crate) async fn get_aggregate_usage( _auth: AuthenticatedService, State(_state): State>, ) -> Response { @@ -616,7 +616,7 @@ mod runs { use super::ts; use fabro_api_types::*; - pub fn list_items() -> Vec { + pub(super) fn list_items() -> Vec { vec![ RunListItem { id: "run-1".into(), @@ -1062,7 +1062,7 @@ mod runs { ] } - pub fn stages() -> Vec { + pub(super) fn stages() -> Vec { vec![ RunStage { id: "detect-drift".into(), @@ -1095,7 +1095,7 @@ mod runs { ] } - pub fn turns() -> Vec { + pub(super) fn turns() -> Vec { vec![ StageTurn::SystemStageTurn(SystemStageTurn { kind: SystemStageTurnKind::System, content: "You are a drift detection agent. Compare the production and staging environments and identify any configuration or code drift.".into() }), StageTurn::AssistantStageTurn(AssistantStageTurn { kind: AssistantStageTurnKind::Assistant, content: "I'll start by loading the environment configurations for both production and staging to compare them.".into() }), @@ -1110,14 +1110,14 @@ mod runs { ] } - pub fn files() -> Vec { + pub(super) fn files() -> Vec { vec![ FileDiff { old_file: DiffFile { name: "src/commands/run.ts".into(), contents: "import { parseArgs } from \"node:util\";\nimport { loadConfig } from \"../config.js\";\nimport { execute } from \"../executor.js\";\n\ninterface RunOptions {\n config: string;\n dryRun: boolean;\n}\n\nexport async function run(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n config: { type: \"string\", short: \"c\", default: \"fabro.toml\" },\n \"dry-run\": { type: \"boolean\", default: false },\n },\n });\n\n const opts: RunOptions = {\n config: values.config ?? \"fabro.toml\",\n dryRun: values[\"dry-run\"] ?? false,\n };\n\n const config = await loadConfig(opts.config);\n const result = await execute(config, { dryRun: opts.dryRun });\n\n if (result.success) {\n console.log(\"Run completed successfully.\");\n } else {\n console.error(\"Run failed:\", result.error);\n process.exitCode = 1;\n }\n}\n".into() }, new_file: DiffFile { name: "src/commands/run.ts".into(), contents: "import { parseArgs } from \"node:util\";\nimport { loadConfig } from \"../config.js\";\nimport { execute } from \"../executor.js\";\nimport { createLogger, type Logger } from \"../logger.js\";\n\ninterface RunOptions {\n config: string;\n dryRun: boolean;\n verbose: boolean;\n}\n\nexport async function run(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n config: { type: \"string\", short: \"c\", default: \"fabro.toml\" },\n \"dry-run\": { type: \"boolean\", default: false },\n verbose: { type: \"boolean\", short: \"v\", default: false },\n },\n });\n\n const opts: RunOptions = {\n config: values.config ?? \"fabro.toml\",\n dryRun: values[\"dry-run\"] ?? false,\n verbose: values.verbose ?? false,\n };\n\n const logger: Logger = createLogger({ verbose: opts.verbose });\n\n const config = await loadConfig(opts.config);\n logger.debug(\"Loaded config from %s\", opts.config);\n\n const result = await execute(config, { dryRun: opts.dryRun, logger });\n logger.debug(\"Execution finished in %dms\", result.elapsed);\n\n if (result.success) {\n console.log(\"Run completed successfully.\");\n } else {\n console.error(\"Run failed:\", result.error);\n process.exitCode = 1;\n }\n}\n".into() }, }, FileDiff { - old_file: DiffFile { name: "src/logger.ts".into(), contents: "".into() }, + old_file: DiffFile { name: "src/logger.ts".into(), contents: String::new() }, new_file: DiffFile { name: "src/logger.ts".into(), contents: "export interface Logger {\n info(message: string, ...args: unknown[]): void;\n debug(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n}\n\ninterface LoggerOptions {\n verbose: boolean;\n}\n\nexport function createLogger({ verbose }: LoggerOptions): Logger {\n return {\n info(message, ...args) {\n console.log(message, ...args);\n },\n debug(message, ...args) {\n if (verbose) {\n console.log(\"[debug]\", message, ...args);\n }\n },\n error(message, ...args) {\n console.error(message, ...args);\n },\n };\n}\n".into() }, }, FileDiff { @@ -1127,7 +1127,7 @@ mod runs { ] } - pub fn usage() -> RunUsage { + pub(super) fn usage() -> RunUsage { RunUsage { stages: vec![ UsageStage { @@ -1235,11 +1235,11 @@ mod runs { } } - pub fn verifications() -> Vec { + pub(super) fn verifications() -> Vec { super::verifications::run_verifications() } - pub fn questions() -> Vec { + pub(super) fn questions() -> Vec { vec![ ApiQuestion { id: "q-001".into(), @@ -1276,7 +1276,7 @@ mod runs { ] } - pub fn settings() -> serde_json::Value { + pub(super) fn settings() -> serde_json::Value { serde_json::to_value(fabro_config::FabroSettings { version: Some(1), goal: Some("Add rate limiting to auth endpoints".into()), @@ -1338,7 +1338,7 @@ mod runs { mod usage { use fabro_api_types::*; - pub fn aggregate() -> AggregateUsage { + pub(super) fn aggregate() -> AggregateUsage { AggregateUsage { totals: AggregateUsageTotals { runs: 9, @@ -1390,7 +1390,7 @@ mod workflows { use super::ts; use fabro_api_types::*; - pub fn list_items() -> Vec { + pub(super) fn list_items() -> Vec { vec![ WorkflowListItem { name: "Fix Build".into(), @@ -1450,7 +1450,7 @@ mod workflows { serde_json::from_value(val).unwrap() } - pub fn detail(name: &str) -> Option { + pub(super) fn detail(name: &str) -> Option { let items = [ WorkflowDetail { name: "Fix Build".into(), slug: "fix_build".into(), filename: "fix_build.fabro".into(), @@ -2679,7 +2679,7 @@ mod verifications { // ── Public API ────────────────────────────────────────────────────── - pub fn criteria() -> Vec { + pub(super) fn criteria() -> Vec { ALL_CATEGORIES .iter() .map(|cat| VerificationCriterion { @@ -2703,7 +2703,7 @@ mod verifications { .collect() } - pub fn criterion_detail(id: &str) -> Option { + pub(super) fn criterion_detail(id: &str) -> Option { ALL_CATEGORIES .iter() .find(|cat| slugify(cat.name) == id) @@ -2727,7 +2727,7 @@ mod verifications { }) } - pub fn controls() -> Vec { + pub(super) fn controls() -> Vec { ALL_CATEGORIES .iter() .flat_map(|cat| { @@ -2749,7 +2749,7 @@ mod verifications { .collect() } - pub fn control_detail(slug: &str) -> Option { + pub(super) fn control_detail(slug: &str) -> Option { for cat in ALL_CATEGORIES { for (idx, ctrl) in cat.controls.iter().enumerate() { if ctrl.slug == slug { @@ -2802,7 +2802,7 @@ mod verifications { None } - pub fn run_verifications() -> Vec { + pub(super) fn run_verifications() -> Vec { ALL_CATEGORIES .iter() .map(|cat| RunVerification { @@ -2917,7 +2917,7 @@ mod signoffs { } } - pub fn list_items( + pub(super) fn list_items( control: Option<&str>, repository: Option<&str>, commit_sha: Option<&str>, @@ -2931,11 +2931,11 @@ mod signoffs { .collect() } - pub fn detail(id: &str) -> Option { + pub(super) fn detail(id: &str) -> Option { ALL_SIGNOFFS.iter().find(|s| s.id == id).map(to_signoff) } - pub fn stub_created() -> Signoff { + pub(super) fn stub_created() -> Signoff { to_signoff(&ALL_SIGNOFFS[0]) } } @@ -2969,7 +2969,7 @@ mod retros { } } - pub fn detail(run_id: &str) -> Option { + pub(super) fn detail(run_id: &str) -> Option { match run_id { "run-1" => Some(RetroDetail { run_id: "run-1".into(), @@ -3141,7 +3141,7 @@ mod retros { } } - pub fn list_items() -> Vec { + pub(super) fn list_items() -> Vec { vec![ RetroListItem { run: RunReference { @@ -3292,7 +3292,7 @@ mod sessions { const S7: u128 = 0x10000000_0000_4000_8000_000000000007; const S8: u128 = 0x10000000_0000_4000_8000_000000000008; - pub fn list_items() -> Vec { + pub(super) fn list_items() -> Vec { vec![ SessionListItem { id: uid(S1), @@ -3361,7 +3361,7 @@ mod sessions { ] } - pub fn detail(id: &str) -> Option { + pub(super) fn detail(id: &str) -> Option { let parsed = id.parse::().ok()?; match parsed.as_u128() { S1 => Some(SessionDetail { @@ -3420,7 +3420,7 @@ mod insights { use super::ts; use fabro_api_types::*; - pub fn saved_queries() -> Vec { + pub(super) fn saved_queries() -> Vec { vec![ SavedQuery { id: "1".into(), name: "Run duration by workflow".into(), sql: "SELECT workflow_name, AVG(duration_seconds) as avg_duration,\n COUNT(*) as run_count\nFROM runs\nGROUP BY workflow_name\nORDER BY avg_duration DESC\nLIMIT 20".into(), created_at: ts("2026-03-01T10:00:00Z"), updated_at: ts("2026-03-05T14:30:00Z") }, SavedQuery { id: "2".into(), name: "Daily failure rate".into(), sql: "SELECT date_trunc('day', created_at) as day,\n COUNT(*) FILTER (WHERE status = 'failed') as failures,\n COUNT(*) as total\nFROM runs\nGROUP BY 1\nORDER BY 1 DESC\nLIMIT 30".into(), created_at: ts("2026-03-02T09:00:00Z"), updated_at: ts("2026-03-02T09:00:00Z") }, @@ -3428,7 +3428,7 @@ mod insights { ] } - pub fn history() -> Vec { + pub(super) fn history() -> Vec { vec![ HistoryEntry { id: "h1".into(), @@ -3460,7 +3460,7 @@ mod settings { use fabro_config::FabroSettings; use fabro_config::server::*; - pub fn server_settings() -> serde_json::Value { + pub(super) fn server_settings() -> serde_json::Value { serde_json::to_value(FabroSettings { storage_dir: Some("/home/fabro/.fabro".into()), max_concurrent_runs: Some(10), diff --git a/lib/crates/fabro-api/src/github_webhooks.rs b/lib/crates/fabro-api/src/github_webhooks.rs index e6ceb574d..667b649bd 100644 --- a/lib/crates/fabro-api/src/github_webhooks.rs +++ b/lib/crates/fabro-api/src/github_webhooks.rs @@ -17,19 +17,16 @@ type HmacSha256 = Hmac; /// `signature_header` is the value of the `X-Hub-Signature-256` header, /// expected in the form `sha256=`. pub fn verify_signature(secret: &[u8], body: &[u8], signature_header: &str) -> bool { - let hex_digest = match signature_header.strip_prefix("sha256=") { - Some(h) => h, - None => return false, + let Some(hex_digest) = signature_header.strip_prefix("sha256=") else { + return false; }; - let expected = match hex::decode(hex_digest) { - Ok(b) => b, - Err(_) => return false, + let Ok(expected) = hex::decode(hex_digest) else { + return false; }; - let mut mac = match HmacSha256::new_from_slice(secret) { - Ok(m) => m, - Err(_) => return false, + let Ok(mut mac) = HmacSha256::new_from_slice(secret) else { + return false; }; mac.update(body); mac.verify_slice(&expected).is_ok() @@ -50,15 +47,12 @@ async fn webhook_handler( .and_then(|v| v.to_str().ok()) .unwrap_or("unknown"); - let signature = match headers + let Some(signature) = headers .get("x-hub-signature-256") .and_then(|v| v.to_str().ok()) - { - Some(s) => s, - None => { - warn!(delivery = %delivery_id, "Webhook signature verification failed"); - return StatusCode::UNAUTHORIZED; - } + else { + warn!(delivery = %delivery_id, "Webhook signature verification failed"); + return StatusCode::UNAUTHORIZED; }; if !verify_signature(&state.secret, &body, signature) { diff --git a/lib/crates/fabro-api/src/jwt_auth.rs b/lib/crates/fabro-api/src/jwt_auth.rs index f5834610e..b16ac7041 100644 --- a/lib/crates/fabro-api/src/jwt_auth.rs +++ b/lib/crates/fabro-api/src/jwt_auth.rs @@ -226,7 +226,7 @@ impl FromRequestParts for AuthenticatedService { .expect("AuthMode extension must be added to the router"); let strategies = match auth_mode { - AuthMode::Disabled => return Ok(AuthenticatedService), + AuthMode::Disabled => return Ok(Self), AuthMode::Strategies(strategies) => strategies, }; @@ -246,7 +246,7 @@ impl FromRequestParts for AuthenticatedService { } => try_jwt(parts, key, validation, allowed_usernames), }; match result { - Ok(()) => return Ok(AuthenticatedService), + Ok(()) => return Ok(Self), Err(e) => last_err = e, } } @@ -275,7 +275,7 @@ impl FromRequestParts for AuthenticatedUser { let strategies = match auth_mode { AuthMode::Disabled => { - return Ok(AuthenticatedUser { + return Ok(Self { login: "demo".to_string(), }); } @@ -297,7 +297,7 @@ impl FromRequestParts for AuthenticatedUser { } => { if try_jwt(parts, key, validation, allowed_usernames).is_ok() { if let Some(login) = extract_jwt_login(parts, key, validation) { - return Ok(AuthenticatedUser { login }); + return Ok(Self { login }); } } last_err = ApiError::unauthorized(); @@ -305,7 +305,7 @@ impl FromRequestParts for AuthenticatedUser { AuthStrategy::Mtls => { if try_mtls(parts).is_ok() { if let Some(login) = extract_mtls_cn(parts) { - return Ok(AuthenticatedUser { login }); + return Ok(Self { login }); } } last_err = ApiError::unauthorized(); diff --git a/lib/crates/fabro-api/src/serve.rs b/lib/crates/fabro-api/src/serve.rs index 57d783acf..7534bda2c 100644 --- a/lib/crates/fabro-api/src/serve.rs +++ b/lib/crates/fabro-api/src/serve.rs @@ -62,6 +62,7 @@ pub struct ServeArgs { /// # Errors /// /// Returns an error if the server fails to bind or encounters a fatal error. +#[allow(clippy::print_stderr)] pub async fn serve_command(args: ServeArgs, styles: &'static Styles) -> anyhow::Result<()> { // Resolve dry-run mode (same pattern as run.rs) let dry_run_mode = if args.dry_run { @@ -186,22 +187,19 @@ pub async fn serve_command(args: ServeArgs, styles: &'static Styles) -> anyhow:: Some(app_id) => { let secret = std::env::var("GITHUB_APP_WEBHOOK_SECRET").ok(); let private_key_pem = read_github_private_key(); - match (secret, private_key_pem) { - (Some(secret), Some(pem)) => { - match WebhookManager::start(secret.into_bytes(), &app_id, &pem).await { - Ok(manager) => Some(manager), - Err(err) => { - error!(error = %err, "Failed to start webhook listener"); - None - } + if let (Some(secret), Some(pem)) = (secret, private_key_pem) { + match WebhookManager::start(secret.into_bytes(), &app_id, &pem).await { + Ok(manager) => Some(manager), + Err(err) => { + error!(error = %err, "Failed to start webhook listener"); + None } } - _ => { - warn!( - "Webhook config present but GITHUB_APP_WEBHOOK_SECRET or GITHUB_APP_PRIVATE_KEY not set; skipping webhook listener" - ); - None - } + } else { + warn!( + "Webhook config present but GITHUB_APP_WEBHOOK_SECRET or GITHUB_APP_PRIVATE_KEY not set; skipping webhook listener" + ); + None } } None => None, @@ -274,8 +272,8 @@ fn resolve_model_provider( let provider_str = cli_provider.or(config_provider); let model = cli_model - .map(|s| s.to_string()) - .or_else(|| config_model.map(|s| s.to_string())) + .map(std::string::ToString::to_string) + .or_else(|| config_model.map(std::string::ToString::to_string)) .unwrap_or_else(|| { // Look up default model from catalog for the given provider, // falling back to the best provider with an API key configured. @@ -292,10 +290,10 @@ fn resolve_model_provider( Some(info) => ( info.id.clone(), provider_str - .map(|s| s.to_string()) + .map(std::string::ToString::to_string) .or(Some(info.provider.to_string())), ), - None => (model, provider_str.map(|s| s.to_string())), + None => (model, provider_str.map(std::string::ToString::to_string)), }; let provider_enum: Provider = provider_str diff --git a/lib/crates/fabro-api/src/server.rs b/lib/crates/fabro-api/src/server.rs index 09edab51e..1a76c3eef 100644 --- a/lib/crates/fabro-api/src/server.rs +++ b/lib/crates/fabro-api/src/server.rs @@ -606,9 +606,8 @@ async fn execute_run(state: Arc, run_id: String) { Some(r) if r.status == RunStatus::Queued => r, _ => return, }; - let run_dir = match managed_run.run_dir.clone() { - Some(path) => path, - None => return, + let Some(run_dir) = managed_run.run_dir.clone() else { + return; }; let (cancel_tx, cancel_rx) = oneshot::channel::<()>(); @@ -643,9 +642,8 @@ async fn execute_run(state: Arc, run_id: String) { let runs = state.runs.lock().expect("runs lock poisoned"); runs.get(&run_id).and_then(|r| r.cancel_token.clone()) }; - let cancel_token = match cancel_token { - Some(ct) => ct, - None => return, + let Some(cancel_token) = cancel_token else { + return; }; let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")); @@ -835,8 +833,8 @@ pub fn spawn_scheduler(state: Arc) { tokio::spawn(async move { loop { tokio::select! { - _ = state.scheduler_notify.notified() => {}, - _ = sleep(std::time::Duration::from_secs(1)) => {}, + () = state.scheduler_notify.notified() => {}, + () = sleep(std::time::Duration::from_secs(1)) => {}, } // Promote as many queued runs as capacity allows loop { @@ -862,7 +860,7 @@ pub fn spawn_scheduler(state: Arc) { tokio::spawn(execute_run(state_clone, id)); } None => break, - }; + } } } }); @@ -911,15 +909,12 @@ async fn get_questions( let runs = state.runs.lock().expect("runs lock poisoned"); match runs.get(&id) { Some(managed_run) => { - let interviewer = match &managed_run.interviewer { - Some(i) => i, - None => { - return ( - StatusCode::OK, - Json(ListResponse::new(Vec::::new())), - ) - .into_response(); - } + let Some(interviewer) = &managed_run.interviewer else { + return ( + StatusCode::OK, + Json(ListResponse::new(Vec::::new())), + ) + .into_response(); }; let pending = interviewer.pending_questions(); let questions: Vec = pending @@ -961,12 +956,9 @@ async fn submit_answer( let runs = state.runs.lock().expect("runs lock poisoned"); match runs.get(&id) { Some(managed_run) => { - let interviewer = match &managed_run.interviewer { - Some(i) => i, - None => { - return ApiError::new(StatusCode::CONFLICT, "Run is not yet running.") - .into_response(); - } + let Some(interviewer) = &managed_run.interviewer else { + return ApiError::new(StatusCode::CONFLICT, "Run is not yet running.") + .into_response(); }; let answer = if let Some(key) = &req.selected_option_key { let option = interviewer diff --git a/lib/crates/fabro-api/src/sessions.rs b/lib/crates/fabro-api/src/sessions.rs index d9118790f..83c868b1d 100644 --- a/lib/crates/fabro-api/src/sessions.rs +++ b/lib/crates/fabro-api/src/sessions.rs @@ -92,9 +92,8 @@ fn spawn_generation(store: SessionStore, session_id: uuid::Uuid, dry_run: bool, tokio::spawn(async move { let (event_tx, model_id, model_provider, system_prompt, messages, generation_seq) = { let store = store.read().expect("session store lock poisoned"); - let session = match store.get(&session_id) { - Some(s) => s, - None => return, + let Some(session) = store.get(&session_id) else { + return; }; ( session.event_tx.clone(), diff --git a/lib/crates/fabro-api/src/tls.rs b/lib/crates/fabro-api/src/tls.rs index 372ae5d1f..2a4e9cd90 100644 --- a/lib/crates/fabro-api/src/tls.rs +++ b/lib/crates/fabro-api/src/tls.rs @@ -91,8 +91,11 @@ pub async fn serve_tls( // Extract peer certificates once per connection (not per request) let (_, server_conn) = tls_stream.get_ref(); - let peer_certs = - PeerCertificates(server_conn.peer_certificates().map(|certs| certs.to_vec())); + let peer_certs = PeerCertificates( + server_conn + .peer_certificates() + .map(<[rustls_pki_types::CertificateDer<'_>]>::to_vec), + ); let io = TokioIo::new(tls_stream); diff --git a/lib/crates/fabro-cli/src/args.rs b/lib/crates/fabro-cli/src/args.rs index 4a6a9bf1e..48f18e2dc 100644 --- a/lib/crates/fabro-cli/src/args.rs +++ b/lib/crates/fabro-cli/src/args.rs @@ -501,7 +501,7 @@ pub(crate) struct WaitArgs { } #[derive(Args)] -pub(crate) struct WorkflowListArgs {} +pub(crate) struct WorkflowListArgs; #[derive(Args)] pub(crate) struct WorkflowCreateArgs { diff --git a/lib/crates/fabro-cli/src/cli_config.rs b/lib/crates/fabro-cli/src/cli_config.rs index 4ae6d5f20..ee767a2b8 100644 --- a/lib/crates/fabro-cli/src/cli_config.rs +++ b/lib/crates/fabro-cli/src/cli_config.rs @@ -1,5 +1,5 @@ #[allow(unused_imports)] -pub use fabro_config::cli::*; +pub(crate) use fabro_config::cli::*; use std::path::Path; @@ -9,7 +9,7 @@ use fabro_config::cli::load_cli_config; #[cfg(feature = "server")] use tracing::debug; -pub fn load_cli_settings(path: Option<&Path>) -> anyhow::Result { +pub(crate) fn load_cli_settings(path: Option<&Path>) -> anyhow::Result { load_cli_config(path)?.try_into() } diff --git a/lib/crates/fabro-cli/src/commands/asset/cp.rs b/lib/crates/fabro-cli/src/commands/asset/cp.rs index 499a06b4c..35ca836d6 100644 --- a/lib/crates/fabro-cli/src/commands/asset/cp.rs +++ b/lib/crates/fabro-cli/src/commands/asset/cp.rs @@ -10,7 +10,7 @@ use crate::args::AssetCpArgs; use crate::cli_config::load_cli_settings; use crate::shared::split_run_path; -pub fn cp_command(args: &AssetCpArgs) -> Result<()> { +pub(super) fn cp_command(args: &AssetCpArgs) -> Result<()> { let cli_config = load_cli_settings(None)?; let base = runs_base(&cli_config.storage_dir()); let (run_id, asset_path) = parse_source(&args.source); diff --git a/lib/crates/fabro-cli/src/commands/asset/list.rs b/lib/crates/fabro-cli/src/commands/asset/list.rs index 64c6f13f2..ae5ad05f6 100644 --- a/lib/crates/fabro-cli/src/commands/asset/list.rs +++ b/lib/crates/fabro-cli/src/commands/asset/list.rs @@ -8,7 +8,7 @@ use crate::args::AssetListArgs; use crate::cli_config::load_cli_settings; use crate::shared::format_size; -pub fn list_command(args: &AssetListArgs) -> Result<()> { +pub(super) fn list_command(args: &AssetListArgs) -> Result<()> { let cli_config = load_cli_settings(None)?; let base = runs_base(&cli_config.storage_dir()); let run = resolve_run(&base, &args.run_id)?; diff --git a/lib/crates/fabro-cli/src/commands/asset/mod.rs b/lib/crates/fabro-cli/src/commands/asset/mod.rs index b21ce1d1a..8ce5b3c79 100644 --- a/lib/crates/fabro-cli/src/commands/asset/mod.rs +++ b/lib/crates/fabro-cli/src/commands/asset/mod.rs @@ -5,7 +5,7 @@ use anyhow::Result; use crate::args::{AssetCommand, AssetNamespace}; -pub fn dispatch(ns: AssetNamespace) -> Result<()> { +pub(crate) fn dispatch(ns: AssetNamespace) -> Result<()> { match ns.command { AssetCommand::List(args) => list::list_command(&args), AssetCommand::Cp(args) => cp::cp_command(&args), diff --git a/lib/crates/fabro-cli/src/commands/config/mod.rs b/lib/crates/fabro-cli/src/commands/config/mod.rs index 62f39850e..380c54bbf 100644 --- a/lib/crates/fabro-cli/src/commands/config/mod.rs +++ b/lib/crates/fabro-cli/src/commands/config/mod.rs @@ -6,7 +6,7 @@ use fabro_config::cli::load_cli_config; use fabro_config::project::{ResolveSettingsInput, discover_project_config, resolve_settings}; use fabro_config::{FabroConfig, FabroSettings}; -pub fn dispatch(ns: ConfigNamespace) -> anyhow::Result<()> { +pub(crate) fn dispatch(ns: ConfigNamespace) -> anyhow::Result<()> { match ns.command { ConfigCommand::Show(args) => show_command(&args), } @@ -33,7 +33,7 @@ fn merged_config(workflow: Option<&Path>) -> anyhow::Result { FabroConfig::combine(project_config, cli_config).try_into() } -pub fn show_command(args: &ConfigShowArgs) -> anyhow::Result<()> { +pub(crate) fn show_command(args: &ConfigShowArgs) -> anyhow::Result<()> { let config = merged_config(args.workflow.as_deref())?; let mut yaml = serde_yaml::to_string(&config)?; if !yaml.ends_with('\n') { diff --git a/lib/crates/fabro-cli/src/commands/doctor.rs b/lib/crates/fabro-cli/src/commands/doctor.rs index 56168189b..6a2833b80 100644 --- a/lib/crates/fabro-cli/src/commands/doctor.rs +++ b/lib/crates/fabro-cli/src/commands/doctor.rs @@ -11,7 +11,7 @@ use fabro_config::server::{ApiAuthStrategy, AuthProvider}; use fabro_llm::client::Client as LlmClient; use fabro_llm::types::{Message, Request}; use fabro_model::{Catalog, Provider}; -pub use fabro_util::check_report::{ +pub(crate) use fabro_util::check_report::{ CheckDetail, CheckReport, CheckResult, CheckSection, CheckStatus, }; use fabro_util::terminal::Styles; @@ -194,7 +194,7 @@ fn apply_live_result( } } -pub fn check_config(path: Option) -> CheckResult { +pub(crate) fn check_config(path: Option) -> CheckResult { match path { Some(p) => CheckResult { name: "Configuration".to_string(), @@ -215,7 +215,7 @@ pub fn check_config(path: Option) -> CheckResult { } } -pub fn check_llm_providers( +pub(crate) fn check_llm_providers( statuses: &[(Provider, bool)], live_results: Option<&[(Provider, Result<(), String>)]>, ) -> CheckResult { @@ -252,7 +252,10 @@ pub fn check_llm_providers( remediation: Some("Set at least one provider API key".to_string()), } } else if !failed_providers.is_empty() { - let names: Vec<_> = failed_providers.iter().map(|p| p.to_string()).collect(); + let names: Vec<_> = failed_providers + .iter() + .map(std::string::ToString::to_string) + .collect(); CheckResult { name: "LLM providers".to_string(), status: CheckStatus::Warning, @@ -271,7 +274,7 @@ pub fn check_llm_providers( } } -pub fn check_brave_search( +pub(crate) fn check_brave_search( api_key_set: bool, live_result: Option<&Result<(), String>>, ) -> CheckResult { @@ -317,12 +320,12 @@ pub fn check_brave_search( } } -pub struct SandboxStatus { +pub(crate) struct SandboxStatus { pub daytona_configured: bool, pub daytona_probe: Option>, } -pub fn check_sandbox(status: &SandboxStatus) -> CheckResult { +pub(crate) fn check_sandbox(status: &SandboxStatus) -> CheckResult { let mut details = Vec::new(); match &status.daytona_probe { @@ -378,7 +381,7 @@ pub fn check_sandbox(status: &SandboxStatus) -> CheckResult { } } -pub struct GithubAppStatus { +pub(crate) struct GithubAppStatus { pub app_id: Option, pub slug: Option, pub private_key_set: bool, @@ -416,7 +419,7 @@ impl GithubAppStatus { } } -pub fn check_github_app(status: &GithubAppStatus) -> CheckResult { +pub(crate) fn check_github_app(status: &GithubAppStatus) -> CheckResult { let mut details: Vec = Vec::new(); match (&status.app_id, &status.slug) { @@ -922,7 +925,7 @@ async fn probe_url(http: &reqwest::Client, url: &str) -> Result<(), String> { .map_err(|e| e.to_string()) } -pub async fn run_doctor(verbose: bool, live: bool) -> i32 { +pub(crate) async fn run_doctor(verbose: bool, live: bool) -> i32 { let styles = Styles::detect_stdout(); let spinner = indicatif::ProgressBar::new_spinner(); @@ -999,7 +1002,7 @@ pub async fn run_doctor(verbose: bool, live: bool) -> i32 { Ok(pem) => Some( fabro_github::sign_app_jwt(app_id, &pem) .map(|_| ()) - .map_err(|e| e.to_string()), + .map_err(|e| e.clone()), ), Err(e) => Some(Err(e)), } @@ -1177,7 +1180,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 } + i32::from(report.has_errors()) } // --------------------------------------------------------------------------- diff --git a/lib/crates/fabro-cli/src/commands/exec.rs b/lib/crates/fabro-cli/src/commands/exec.rs index b621bb663..b7ecb86b0 100644 --- a/lib/crates/fabro-cli/src/commands/exec.rs +++ b/lib/crates/fabro-cli/src/commands/exec.rs @@ -8,7 +8,7 @@ use fabro_mcp::config::McpServerConfig; use crate::args::GlobalArgs; use crate::cli_config; -pub async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result<()> { +pub(crate) async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result<()> { let cli_config = cli_config::load_cli_settings(None)?; #[cfg(feature = "sleep_inhibitor")] let _sleep_guard = crate::sleep_inhibitor::guard(cli_config.prevent_idle_sleep_enabled()); @@ -63,7 +63,7 @@ pub async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result<()> { { let _ = globals; tracing::info!(mode = "standalone", "Agent session starting"); - run_with_args(args, mcp_servers).await? + run_with_args(args, mcp_servers).await?; } Ok(()) diff --git a/lib/crates/fabro-cli/src/commands/graph.rs b/lib/crates/fabro-cli/src/commands/graph.rs index efbb13052..f235c2687 100644 --- a/lib/crates/fabro-cli/src/commands/graph.rs +++ b/lib/crates/fabro-cli/src/commands/graph.rs @@ -17,7 +17,7 @@ use crate::shared::{print_diagnostics, read_workflow_file, relative_path}; static RANKDIR_RE: LazyLock = LazyLock::new(|| regex::Regex::new(r"rankdir\s*=\s*\w+").unwrap()); -pub fn run(args: &GraphArgs, styles: &Styles) -> anyhow::Result<()> { +pub(crate) fn run(args: &GraphArgs, styles: &Styles) -> anyhow::Result<()> { let cwd = std::env::current_dir()?; let cli_defaults = load_cli_config(None)?; let settings = resolve_settings(ResolveSettingsInput { @@ -61,7 +61,7 @@ pub fn run(args: &GraphArgs, styles: &Styles) -> anyhow::Result<()> { Ok(()) } -fn apply_direction<'a>(source: &'a str, direction: Option) -> Cow<'a, str> { +fn apply_direction(source: &str, direction: Option) -> Cow<'_, str> { match direction { Some(dir) => { let replacement = format!("rankdir={dir}"); diff --git a/lib/crates/fabro-cli/src/commands/install.rs b/lib/crates/fabro-cli/src/commands/install.rs index 3e39d4052..03f05a064 100644 --- a/lib/crates/fabro-cli/src/commands/install.rs +++ b/lib/crates/fabro-cli/src/commands/install.rs @@ -286,9 +286,11 @@ async fn setup_github_app( ) -> Result> { // Random suffix so app names don't collide let mut rng = rand::thread_rng(); - let suffix: String = (0..6) - .map(|_| format!("{:x}", rng.gen::() % 16)) - .collect(); + let suffix: String = (0..6).fold(String::with_capacity(6), |mut s, _| { + use std::fmt::Write; + let _ = write!(s, "{:x}", rng.gen::() % 16); + s + }); let app_name = format!("Arc-{suffix}"); // Bind to random port @@ -441,14 +443,14 @@ async fn setup_github_app( let cli_toml_path = arc_dir.join("cli.toml"); let existing = std::fs::read_to_string(&cli_toml_path).unwrap_or_default(); let mut doc: toml::Value = if existing.is_empty() { - toml::Value::Table(Default::default()) + toml::Value::Table(toml::Table::default()) } else { toml::from_str(&existing).context("failed to parse existing cli.toml")? }; let table = doc.as_table_mut().context("cli.toml root is not a table")?; let git = table .entry("git") - .or_insert(toml::Value::Table(Default::default())); + .or_insert(toml::Value::Table(toml::Table::default())); let git_table = git .as_table_mut() .context("cli.toml [git] is not a table")?; @@ -480,7 +482,7 @@ async fn setup_github_app( Ok(env_pairs) } -pub async fn run_install(web_url: &str) -> Result<()> { +pub(crate) async fn run_install(web_url: &str) -> Result<()> { let s = Styles::detect_stderr(); let emoji = console::Emoji("⚒️ ", ""); @@ -649,8 +651,8 @@ pub async fn run_install(web_url: &str) -> Result<()> { let slug = { let cli_toml_path = arc_dir.join("cli.toml"); let toml_content = std::fs::read_to_string(&cli_toml_path).unwrap_or_default(); - let doc: toml::Value = - toml::from_str(&toml_content).unwrap_or(toml::Value::Table(Default::default())); + let doc: toml::Value = toml::from_str(&toml_content) + .unwrap_or(toml::Value::Table(toml::Table::default())); doc.get("git") .and_then(|g| g.get("slug")) .and_then(|s| s.as_str()) diff --git a/lib/crates/fabro-cli/src/commands/llm/chat.rs b/lib/crates/fabro-cli/src/commands/llm/chat.rs index 29392fa07..1d97401aa 100644 --- a/lib/crates/fabro-cli/src/commands/llm/chat.rs +++ b/lib/crates/fabro-cli/src/commands/llm/chat.rs @@ -6,7 +6,7 @@ use fabro_llm::cli::{ServerConnection, run_chat_via_server}; use crate::args::GlobalArgs; -pub async fn execute( +pub(super) async fn execute( mut args: ChatArgs, cli_config: &FabroSettings, globals: &GlobalArgs, diff --git a/lib/crates/fabro-cli/src/commands/llm/mod.rs b/lib/crates/fabro-cli/src/commands/llm/mod.rs index e513101f7..2c7ff65d3 100644 --- a/lib/crates/fabro-cli/src/commands/llm/mod.rs +++ b/lib/crates/fabro-cli/src/commands/llm/mod.rs @@ -6,7 +6,7 @@ use anyhow::Result; use crate::args::{GlobalArgs, LlmCommand, LlmNamespace}; use crate::cli_config::load_cli_settings; -pub async fn dispatch(ns: LlmNamespace, globals: &GlobalArgs) -> Result<()> { +pub(crate) async fn dispatch(ns: LlmNamespace, globals: &GlobalArgs) -> Result<()> { let cli_config = load_cli_settings(None)?; match ns.command { diff --git a/lib/crates/fabro-cli/src/commands/llm/prompt.rs b/lib/crates/fabro-cli/src/commands/llm/prompt.rs index f208537e6..3d9109b2f 100644 --- a/lib/crates/fabro-cli/src/commands/llm/prompt.rs +++ b/lib/crates/fabro-cli/src/commands/llm/prompt.rs @@ -6,7 +6,7 @@ use fabro_llm::cli::{ServerConnection, run_prompt_via_server}; use crate::args::GlobalArgs; -pub async fn execute( +pub(super) async fn execute( mut args: PromptArgs, cli_config: &FabroSettings, globals: &GlobalArgs, diff --git a/lib/crates/fabro-cli/src/commands/mod.rs b/lib/crates/fabro-cli/src/commands/mod.rs index 1070b8caa..799f7ba30 100644 --- a/lib/crates/fabro-cli/src/commands/mod.rs +++ b/lib/crates/fabro-cli/src/commands/mod.rs @@ -1,21 +1,21 @@ -pub mod asset; -pub mod config; -pub mod doctor; -pub mod exec; -pub mod graph; -pub mod install; -pub mod llm; -pub mod model; -pub mod parse; -pub mod pr; -pub mod preflight; -pub mod provider; -pub mod repo; -pub mod run; -pub mod runs; -pub mod secret; -pub mod skill; -pub mod system; -pub mod upgrade; -pub mod validate; -pub mod workflow; +pub(crate) mod asset; +pub(crate) mod config; +pub(crate) mod doctor; +pub(crate) mod exec; +pub(crate) mod graph; +pub(crate) mod install; +pub(crate) mod llm; +pub(crate) mod model; +pub(crate) mod parse; +pub(crate) mod pr; +pub(crate) mod preflight; +pub(crate) mod provider; +pub(crate) mod repo; +pub(crate) mod run; +pub(crate) mod runs; +pub(crate) mod secret; +pub(crate) mod skill; +pub(crate) mod system; +pub(crate) mod upgrade; +pub(crate) mod validate; +pub(crate) mod workflow; diff --git a/lib/crates/fabro-cli/src/commands/model.rs b/lib/crates/fabro-cli/src/commands/model.rs index 13a659878..72b0ed394 100644 --- a/lib/crates/fabro-cli/src/commands/model.rs +++ b/lib/crates/fabro-cli/src/commands/model.rs @@ -7,7 +7,7 @@ use crate::args::GlobalArgs; #[cfg(feature = "server")] use crate::cli_config; -pub async fn execute(command: Option, globals: &GlobalArgs) -> Result<()> { +pub(crate) async fn execute(command: Option, globals: &GlobalArgs) -> Result<()> { let server = { #[cfg(feature = "server")] { diff --git a/lib/crates/fabro-cli/src/commands/parse.rs b/lib/crates/fabro-cli/src/commands/parse.rs index 9e5df9b5a..40473d91b 100644 --- a/lib/crates/fabro-cli/src/commands/parse.rs +++ b/lib/crates/fabro-cli/src/commands/parse.rs @@ -6,7 +6,7 @@ use fabro_graphviz::parser::parse_ast; use crate::args::ParseArgs; use crate::shared::read_workflow_file; -pub fn run(args: &ParseArgs) -> anyhow::Result<()> { +pub(crate) fn run(args: &ParseArgs) -> anyhow::Result<()> { let stdout = std::io::stdout(); run_to(args, stdout.lock()) } diff --git a/lib/crates/fabro-cli/src/commands/pr/close.rs b/lib/crates/fabro-cli/src/commands/pr/close.rs index 07a1959cf..ca5342885 100644 --- a/lib/crates/fabro-cli/src/commands/pr/close.rs +++ b/lib/crates/fabro-cli/src/commands/pr/close.rs @@ -8,7 +8,7 @@ use tracing::info; use crate::args::PrCloseArgs; use crate::cli_config::load_cli_settings; -pub async fn close_command( +pub(super) async fn close_command( args: PrCloseArgs, github_app: Option, ) -> Result<()> { diff --git a/lib/crates/fabro-cli/src/commands/pr/create.rs b/lib/crates/fabro-cli/src/commands/pr/create.rs index 541992159..a2e9c43d9 100644 --- a/lib/crates/fabro-cli/src/commands/pr/create.rs +++ b/lib/crates/fabro-cli/src/commands/pr/create.rs @@ -15,7 +15,7 @@ use tracing::info; use crate::args::PrCreateArgs; use crate::cli_config::load_cli_settings; -pub async fn create_command( +pub(super) async fn create_command( args: PrCreateArgs, github_app: Option, ) -> Result<()> { diff --git a/lib/crates/fabro-cli/src/commands/pr/list.rs b/lib/crates/fabro-cli/src/commands/pr/list.rs index 835552177..9e18a492e 100644 --- a/lib/crates/fabro-cli/src/commands/pr/list.rs +++ b/lib/crates/fabro-cli/src/commands/pr/list.rs @@ -10,7 +10,7 @@ use tracing::info; use crate::args::PrListArgs; use crate::cli_config::load_cli_settings; -pub async fn list_command( +pub(super) async fn list_command( args: PrListArgs, github_app: Option, ) -> Result<()> { diff --git a/lib/crates/fabro-cli/src/commands/pr/merge.rs b/lib/crates/fabro-cli/src/commands/pr/merge.rs index acfb6a590..039412cdc 100644 --- a/lib/crates/fabro-cli/src/commands/pr/merge.rs +++ b/lib/crates/fabro-cli/src/commands/pr/merge.rs @@ -9,7 +9,7 @@ use fabro_workflows::run_lookup::runs_base; use crate::args::PrMergeArgs; use crate::cli_config::load_cli_settings; -pub async fn merge_command( +pub(super) async fn merge_command( args: PrMergeArgs, github_app: Option, ) -> Result<()> { diff --git a/lib/crates/fabro-cli/src/commands/pr/mod.rs b/lib/crates/fabro-cli/src/commands/pr/mod.rs index e7188b70f..6b56ecab4 100644 --- a/lib/crates/fabro-cli/src/commands/pr/mod.rs +++ b/lib/crates/fabro-cli/src/commands/pr/mod.rs @@ -15,7 +15,7 @@ use crate::args::{PrCommand, PrNamespace}; use crate::cli_config::load_cli_settings; use crate::shared::github::build_github_app_credentials; -pub async fn dispatch(ns: PrNamespace) -> Result<()> { +pub(crate) async fn dispatch(ns: PrNamespace) -> Result<()> { let cli_config = load_cli_settings(None)?; let github_app = build_github_app_credentials(cli_config.app_id()); diff --git a/lib/crates/fabro-cli/src/commands/pr/view.rs b/lib/crates/fabro-cli/src/commands/pr/view.rs index 4971f5bd9..aec279782 100644 --- a/lib/crates/fabro-cli/src/commands/pr/view.rs +++ b/lib/crates/fabro-cli/src/commands/pr/view.rs @@ -9,7 +9,7 @@ use fabro_workflows::run_lookup::runs_base; use crate::args::PrViewArgs; use crate::cli_config::load_cli_settings; -pub async fn view_command( +pub(super) async fn view_command( args: PrViewArgs, github_app: Option, ) -> Result<()> { diff --git a/lib/crates/fabro-cli/src/commands/preflight.rs b/lib/crates/fabro-cli/src/commands/preflight.rs index af3f8c02b..9c534d788 100644 --- a/lib/crates/fabro-cli/src/commands/preflight.rs +++ b/lib/crates/fabro-cli/src/commands/preflight.rs @@ -21,7 +21,7 @@ use fabro_workflows::operations::{ValidateInput, WorkflowInput, validate}; use crate::args::PreflightArgs; use crate::shared::github::build_github_app_credentials; -pub async fn execute(mut args: PreflightArgs) -> anyhow::Result<()> { +pub(crate) async fn execute(mut args: PreflightArgs) -> anyhow::Result<()> { let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr())); let cli_defaults = load_cli_config(None)?; let cli_config: FabroSettings = cli_defaults.clone().try_into()?; @@ -121,7 +121,7 @@ fn parse_sandbox_provider(settings: &FabroSettings) -> anyhow::Result()) + .map(str::parse::) .transpose() .map_err(|e| anyhow::anyhow!("Invalid sandbox provider: {e}")) } @@ -367,8 +367,11 @@ async fn run_preflight( let default_provider = provider.as_deref().unwrap_or("anthropic"); let llm_ok = match LlmClient::from_env().await { Ok(c) => { - let configured: Vec = - c.provider_names().iter().map(|s| s.to_string()).collect(); + let configured: Vec = c + .provider_names() + .iter() + .map(std::string::ToString::to_string) + .collect(); let mut model_providers = std::collections::BTreeSet::new(); for node in graph.nodes.values() { diff --git a/lib/crates/fabro-cli/src/commands/provider/login.rs b/lib/crates/fabro-cli/src/commands/provider/login.rs index 139b95c9e..bc1d63a99 100644 --- a/lib/crates/fabro-cli/src/commands/provider/login.rs +++ b/lib/crates/fabro-cli/src/commands/provider/login.rs @@ -6,7 +6,7 @@ use tokio::task::spawn_blocking; use crate::args::ProviderLoginArgs; use crate::shared::provider_auth; -pub async fn login_command(args: ProviderLoginArgs) -> Result<()> { +pub(super) async fn login_command(args: ProviderLoginArgs) -> Result<()> { let s = Styles::detect_stderr(); let arc_dir = dirs::home_dir() .context("could not determine home directory")? diff --git a/lib/crates/fabro-cli/src/commands/provider/mod.rs b/lib/crates/fabro-cli/src/commands/provider/mod.rs index c906b596c..7fd780c51 100644 --- a/lib/crates/fabro-cli/src/commands/provider/mod.rs +++ b/lib/crates/fabro-cli/src/commands/provider/mod.rs @@ -4,7 +4,7 @@ use anyhow::Result; use crate::args::{ProviderCommand, ProviderNamespace}; -pub async fn dispatch(ns: ProviderNamespace) -> Result<()> { +pub(crate) async fn dispatch(ns: ProviderNamespace) -> Result<()> { match ns.command { ProviderCommand::Login(args) => login::login_command(args).await, } diff --git a/lib/crates/fabro-cli/src/commands/repo/deinit.rs b/lib/crates/fabro-cli/src/commands/repo/deinit.rs index 5d43a334d..49222b2db 100644 --- a/lib/crates/fabro-cli/src/commands/repo/deinit.rs +++ b/lib/crates/fabro-cli/src/commands/repo/deinit.rs @@ -1,6 +1,6 @@ use anyhow::{Context, Result, bail}; -pub fn run_deinit() -> Result<()> { +pub(crate) fn run_deinit() -> Result<()> { let repo_root = super::init::git_repo_root()?; let fabro_toml = repo_root.join("fabro.toml"); diff --git a/lib/crates/fabro-cli/src/commands/repo/init.rs b/lib/crates/fabro-cli/src/commands/repo/init.rs index ddbf7d475..bc340b737 100644 --- a/lib/crates/fabro-cli/src/commands/repo/init.rs +++ b/lib/crates/fabro-cli/src/commands/repo/init.rs @@ -21,7 +21,7 @@ pub(super) fn git_repo_root() -> Result { )) } -pub async fn run_init() -> Result<()> { +pub(crate) async fn run_init() -> Result<()> { let repo_root = git_repo_root()?; let fabro_toml = repo_root.join("fabro.toml"); @@ -148,46 +148,40 @@ async fn check_github_app_installation() { // Convert SSH URL to HTTPS and parse owner/repo let https_url = fabro_github::ssh_url_to_https(&remote_url); - let (owner, repo) = match fabro_github::parse_github_owner_repo(&https_url) { - Ok(pair) => pair, - Err(_) => return, // Not a GitHub repo — skip silently + let Ok((owner, repo)) = fabro_github::parse_github_owner_repo(&https_url) else { + return; // Not a GitHub repo — skip silently }; // Load CLI config to get app_id and slug - let cli_config = match load_cli_settings(None) { - Ok(c) => c, - Err(_) => return, + let Ok(cli_config) = load_cli_settings(None) else { + return; }; - let app_id = match cli_config.app_id() { - Some(id) => id.to_string(), - None => { - eprintln!( - "\n Run {} to set up the GitHub App", - console::Style::new() - .cyan() - .bold() - .apply_to("fabro install") - ); - return; - } + let app_id = if let Some(id) = cli_config.app_id() { + id.to_string() + } else { + eprintln!( + "\n Run {} to set up the GitHub App", + console::Style::new() + .cyan() + .bold() + .apply_to("fabro install") + ); + return; }; let slug = cli_config.slug().map(String::from); // Build GitHub App credentials - let creds = match build_github_app_credentials(Some(&app_id)) { - Some(c) => c, - None => { - eprintln!( - "\n Set {} to enable GitHub App integration", - console::Style::new() - .cyan() - .bold() - .apply_to("GITHUB_APP_PRIVATE_KEY") - ); - return; - } + let Some(creds) = build_github_app_credentials(Some(&app_id)) else { + eprintln!( + "\n Set {} to enable GitHub App integration", + console::Style::new() + .cyan() + .bold() + .apply_to("GITHUB_APP_PRIVATE_KEY") + ); + return; }; let jwt = match fabro_github::sign_app_jwt(&creds.app_id, &creds.private_key_pem) { diff --git a/lib/crates/fabro-cli/src/commands/repo/mod.rs b/lib/crates/fabro-cli/src/commands/repo/mod.rs index a6d2409bc..03e7bcd48 100644 --- a/lib/crates/fabro-cli/src/commands/repo/mod.rs +++ b/lib/crates/fabro-cli/src/commands/repo/mod.rs @@ -1,11 +1,11 @@ -pub mod deinit; -pub mod init; +pub(crate) mod deinit; +pub(crate) mod init; use anyhow::Result; use crate::args::{RepoCommand, RepoNamespace}; -pub async fn dispatch(ns: RepoNamespace) -> Result<()> { +pub(crate) async fn dispatch(ns: RepoNamespace) -> Result<()> { match ns.command { RepoCommand::Init { skill } => { init::run_init().await?; diff --git a/lib/crates/fabro-cli/src/commands/run/attach.rs b/lib/crates/fabro-cli/src/commands/run/attach.rs index b57692b81..5110a8da2 100644 --- a/lib/crates/fabro-cli/src/commands/run/attach.rs +++ b/lib/crates/fabro-cli/src/commands/run/attach.rs @@ -28,7 +28,7 @@ const INTERVIEW_UNANSWERED_MESSAGE: &str = /// Attach to a running (or finished) workflow run, rendering progress live. /// /// Returns exit code 0 for success/partial_success, 1 otherwise. -pub async fn attach_run( +pub(crate) async fn attach_run( run_dir: &Path, kill_on_detach: bool, styles: &'static Styles, @@ -460,6 +460,7 @@ fn determine_exit_code(conclusion_path: &Path, status_record: Option bool { #[cfg(unix)] { diff --git a/lib/crates/fabro-cli/src/commands/run/command.rs b/lib/crates/fabro-cli/src/commands/run/command.rs index e92fb7d70..d1a1ed3d3 100644 --- a/lib/crates/fabro-cli/src/commands/run/command.rs +++ b/lib/crates/fabro-cli/src/commands/run/command.rs @@ -4,7 +4,7 @@ use fabro_util::terminal::Styles; use crate::args::{GlobalArgs, RunArgs}; -pub async fn execute(mut args: RunArgs, _globals: &GlobalArgs) -> Result<()> { +pub(crate) async fn execute(mut args: RunArgs, _globals: &GlobalArgs) -> Result<()> { let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr())); let cli_defaults = load_cli_config(None)?; let cli_config: fabro_config::FabroSettings = cli_defaults.clone().try_into()?; @@ -12,7 +12,7 @@ pub async fn execute(mut args: RunArgs, _globals: &GlobalArgs) -> Result<()> { let quiet = args.detach; let prevent_idle_sleep = cli_config.prevent_idle_sleep_enabled(); - let (run_id, run_dir) = super::create::create_run(&args, cli_defaults, styles, quiet).await?; + let (run_id, run_dir) = super::create::create_run(&args, cli_defaults, styles, quiet)?; #[cfg(feature = "sleep_inhibitor")] let _sleep_guard = crate::sleep_inhibitor::guard(prevent_idle_sleep); diff --git a/lib/crates/fabro-cli/src/commands/run/cp.rs b/lib/crates/fabro-cli/src/commands/run/cp.rs index 4d047877d..5d5c13c26 100644 --- a/lib/crates/fabro-cli/src/commands/run/cp.rs +++ b/lib/crates/fabro-cli/src/commands/run/cp.rs @@ -26,7 +26,7 @@ enum CopyDirection { }, } -pub async fn cp_command(args: CpArgs) -> Result<()> { +pub(crate) async fn cp_command(args: CpArgs) -> Result<()> { let direction = parse_direction(&args.src, &args.dst)?; let cli_config = load_cli_settings(None)?; let base = runs_base(&cli_config.storage_dir()); diff --git a/lib/crates/fabro-cli/src/commands/run/create.rs b/lib/crates/fabro-cli/src/commands/run/create.rs index 85811b18b..b65811176 100644 --- a/lib/crates/fabro-cli/src/commands/run/create.rs +++ b/lib/crates/fabro-cli/src/commands/run/create.rs @@ -12,7 +12,7 @@ use super::output::{print_diagnostics_from_error, print_workflow_report_from_per /// Create a workflow run: allocate run directory, persist RunRecord, return (run_id, run_dir). /// /// This does NOT execute the workflow — it only prepares the run directory. -pub async fn create_run( +pub(crate) fn create_run( args: &RunArgs, cli_defaults: FabroConfig, styles: &Styles, diff --git a/lib/crates/fabro-cli/src/commands/run/detached.rs b/lib/crates/fabro-cli/src/commands/run/detached.rs index d1f93d3a7..d27282913 100644 --- a/lib/crates/fabro-cli/src/commands/run/detached.rs +++ b/lib/crates/fabro-cli/src/commands/run/detached.rs @@ -11,7 +11,7 @@ use fabro_workflows::operations::{StartServices, resume as resume_run, start as use crate::cli_config; use crate::shared; -pub async fn execute(run_dir: PathBuf, launcher_path: PathBuf, resume: bool) -> Result<()> { +pub(crate) async fn execute(run_dir: PathBuf, launcher_path: PathBuf, resume: bool) -> Result<()> { let cli_config = cli_config::load_cli_settings(None)?; let github_app = shared::github::build_github_app_credentials(cli_config.app_id()); let git_author = GitAuthor::from_options( diff --git a/lib/crates/fabro-cli/src/commands/run/diff.rs b/lib/crates/fabro-cli/src/commands/run/diff.rs index ffa18adf6..f65dd8dc2 100644 --- a/lib/crates/fabro-cli/src/commands/run/diff.rs +++ b/lib/crates/fabro-cli/src/commands/run/diff.rs @@ -13,7 +13,7 @@ use tracing::{debug, info}; use crate::args::DiffArgs; use crate::cli_config::load_cli_settings; -pub async fn run(args: DiffArgs) -> Result<()> { +pub(crate) async fn run(args: DiffArgs) -> Result<()> { info!(run_id = %args.run, "Showing diff"); let cli_config = load_cli_settings(None)?; let base = runs_base(&cli_config.storage_dir()); diff --git a/lib/crates/fabro-cli/src/commands/run/fork.rs b/lib/crates/fabro-cli/src/commands/run/fork.rs index 49d24db00..597bfa6d7 100644 --- a/lib/crates/fabro-cli/src/commands/run/fork.rs +++ b/lib/crates/fabro-cli/src/commands/run/fork.rs @@ -9,7 +9,7 @@ use git2::Repository; use crate::args::ForkArgs; -pub fn run(args: &ForkArgs, styles: &Styles) -> Result<()> { +pub(crate) fn run(args: &ForkArgs, styles: &Styles) -> Result<()> { let repo = Repository::discover(".").context("not in a git repository")?; let run_id = find_run_id_by_prefix(&repo, &args.run_id)?; let store = Store::new(repo); diff --git a/lib/crates/fabro-cli/src/commands/run/launcher.rs b/lib/crates/fabro-cli/src/commands/run/launcher.rs index b0ffc0784..da2f0c4f6 100644 --- a/lib/crates/fabro-cli/src/commands/run/launcher.rs +++ b/lib/crates/fabro-cli/src/commands/run/launcher.rs @@ -62,6 +62,7 @@ pub(crate) fn launcher_record_is_running(record: &LauncherRecord) -> bool { } #[cfg(unix)] +#[allow(unsafe_code)] fn process_alive(pid: u32) -> bool { unsafe { libc::kill(pid as i32, 0) == 0 } } diff --git a/lib/crates/fabro-cli/src/commands/run/logs.rs b/lib/crates/fabro-cli/src/commands/run/logs.rs index e0aabac80..9bacf18bb 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 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)?; @@ -102,7 +102,7 @@ fn extract_timestamp(line: &str) -> Option> { ts_str.parse::>().ok() } -pub fn parse_since(s: &str) -> Result> { +pub(crate) fn parse_since(s: &str) -> Result> { let s = s.trim(); if s.is_empty() { bail!("empty --since value"); @@ -186,7 +186,7 @@ fn render_indented_markdown(styles: &Styles, text: &str, indent: &str) -> String .join("\n") } -pub fn format_event_pretty(line: &str, styles: &Styles) -> Option { +pub(crate) fn format_event_pretty(line: &str, styles: &Styles) -> Option { let envelope: serde_json::Value = serde_json::from_str(line).ok()?; let event = envelope.get("event")?.as_str()?; let ts = format_timestamp(envelope.get("ts")?.as_str()?); @@ -234,7 +234,7 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option { if let Some(usage) = envelope.get("usage") { let total = usage .get("total_tokens") - .and_then(|value| value.as_i64()) + .and_then(serde_json::Value::as_i64) .unwrap_or(0); let pad = " ".repeat(ts.len() + 1); if total > 0 { @@ -246,10 +246,13 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option { .apply_to(format!("Tokens: {}", format_tokens(total as u64))) )); } - if let Some(cache_read) = usage.get("cache_read_tokens").and_then(|v| v.as_i64()) { + if let Some(cache_read) = usage + .get("cache_read_tokens") + .and_then(serde_json::Value::as_i64) + { let cache_write = usage .get("cache_write_tokens") - .and_then(|v| v.as_i64()) + .and_then(serde_json::Value::as_i64) .unwrap_or(0); lines.push(format!( "{}{}", @@ -261,7 +264,10 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option { )) )); } - if let Some(reasoning) = usage.get("reasoning_tokens").and_then(|v| v.as_i64()) { + if let Some(reasoning) = usage + .get("reasoning_tokens") + .and_then(serde_json::Value::as_i64) + { if reasoning > 0 { lines.push(format!( "{}{}", @@ -321,14 +327,17 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option { let label = str_field(&envelope, "node_label").unwrap_or("?"); let duration = format_duration_ms(envelope.get("duration_ms")); let cost = format_cost(envelope.get("cost")); - let turns = envelope.get("turns").and_then(|v| v.as_u64()).unwrap_or(0); + let turns = envelope + .get("turns") + .and_then(serde_json::Value::as_u64) + .unwrap_or(0); let tools = envelope .get("tool_calls") - .and_then(|v| v.as_u64()) + .and_then(serde_json::Value::as_u64) .unwrap_or(0); let tokens = envelope .get("total_tokens") - .and_then(|v| v.as_u64()) + .and_then(serde_json::Value::as_u64) .unwrap_or(0); let stats = format!("({turns} turns, {tools} tools, {})", format_tokens(tokens)); Some(format!( @@ -386,7 +395,7 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option { let tool = str_field(&envelope, "tool_name").unwrap_or("?"); let is_error = envelope .get("is_error") - .and_then(|v| v.as_bool()) + .and_then(serde_json::Value::as_bool) .unwrap_or(false); let detail = tool_detail(&envelope); let display = match detail { @@ -432,7 +441,7 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option { "SetupCompleted" => { let count = envelope .get("command_count") - .and_then(|v| v.as_u64()) + .and_then(serde_json::Value::as_u64) .unwrap_or(0); let duration = format_duration_ms(envelope.get("duration_ms")); Some(format!( @@ -445,11 +454,11 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option { "Agent.CompactionCompleted" => { let original = envelope .get("original_turn_count") - .and_then(|v| v.as_u64()) + .and_then(serde_json::Value::as_u64) .unwrap_or(0); let preserved = envelope .get("preserved_turn_count") - .and_then(|v| v.as_u64()) + .and_then(serde_json::Value::as_u64) .unwrap_or(0); Some(format!( "{} {}", @@ -462,7 +471,7 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option { "ParallelStarted" => { let count = envelope .get("branch_count") - .and_then(|v| v.as_u64()) + .and_then(serde_json::Value::as_u64) .unwrap_or(0); Some(format!( "{} {} Parallel {} branches", @@ -502,7 +511,7 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option { let url = str_field(&envelope, "pr_url").unwrap_or("?"); let draft = envelope .get("draft") - .and_then(|v| v.as_bool()) + .and_then(serde_json::Value::as_bool) .unwrap_or(false); let label = if draft { "Draft PR:" } else { "PR:" }; Some(format!( @@ -584,7 +593,7 @@ fn format_timestamp(ts: &str) -> String { } fn format_duration_ms(value: Option<&serde_json::Value>) -> String { - let ms = value.and_then(|v| v.as_u64()).unwrap_or(0); + let ms = value.and_then(serde_json::Value::as_u64).unwrap_or(0); if ms < 1000 { format!("{ms}ms") } else { @@ -599,7 +608,7 @@ fn format_duration_ms(value: Option<&serde_json::Value>) -> String { } fn format_cost(value: Option<&serde_json::Value>) -> String { - let cost = value.and_then(|v| v.as_f64()).unwrap_or(0.0); + let cost = value.and_then(serde_json::Value::as_f64).unwrap_or(0.0); if cost > 0.0 { format!("${cost:.2}") } else { diff --git a/lib/crates/fabro-cli/src/commands/run/mod.rs b/lib/crates/fabro-cli/src/commands/run/mod.rs index 91b55f805..070eb689e 100644 --- a/lib/crates/fabro-cli/src/commands/run/mod.rs +++ b/lib/crates/fabro-cli/src/commands/run/mod.rs @@ -26,13 +26,13 @@ pub(crate) mod ssh; pub(crate) mod start; pub(crate) mod wait; -pub async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<()> { +pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<()> { match cmd { RunCommands::Run(args) => command::execute(args, globals).await, RunCommands::Create(args) => { let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr())); let cli_defaults = load_cli_config(None)?; - let (run_id, _run_dir) = create::create_run(&args, cli_defaults, styles, true).await?; + let (run_id, _run_dir) = create::create_run(&args, cli_defaults, styles, true)?; println!("{run_id}"); Ok(()) } diff --git a/lib/crates/fabro-cli/src/commands/run/preview.rs b/lib/crates/fabro-cli/src/commands/run/preview.rs index d0aaf7e2d..c1668f57b 100644 --- a/lib/crates/fabro-cli/src/commands/run/preview.rs +++ b/lib/crates/fabro-cli/src/commands/run/preview.rs @@ -9,7 +9,7 @@ use crate::args::PreviewArgs; use crate::cli_config::load_cli_settings; use crate::shared::validate_daytona_provider; -pub async fn run(args: PreviewArgs) -> Result<()> { +pub(crate) async fn run(args: PreviewArgs) -> Result<()> { let cli_config = load_cli_settings(None)?; let base = runs_base(&cli_config.storage_dir()); let run_dir = resolve_run(&base, &args.run)?.path; @@ -56,10 +56,12 @@ pub async fn run(args: PreviewArgs) -> Result<()> { } fn format_standard_output(url: &str, token: &str) -> String { + use std::fmt::Write; let mut out = format!("URL: {url}\nToken: {token}\n"); - out.push_str(&format!( + let _ = write!( + out, "\ncurl -H \"x-daytona-preview-token: {token}\" \\\n -H \"X-Daytona-Skip-Preview-Warning: true\" \\\n {url}\n" - )); + ); out } diff --git a/lib/crates/fabro-cli/src/commands/run/resume.rs b/lib/crates/fabro-cli/src/commands/run/resume.rs index 4ec915cc2..7d29847c3 100644 --- a/lib/crates/fabro-cli/src/commands/run/resume.rs +++ b/lib/crates/fabro-cli/src/commands/run/resume.rs @@ -12,7 +12,10 @@ use crate::cli_config::load_cli_settings; /// Looks up the run by ID prefix, validates a checkpoint exists, cleans stale /// artifacts from the previous execution, then spawns an engine subprocess /// (identical to `fabro run`'s create→start→attach flow). -pub async fn resume_command(args: ResumeArgs, styles: &'static Styles) -> anyhow::Result<()> { +pub(crate) async fn resume_command( + args: ResumeArgs, + styles: &'static Styles, +) -> anyhow::Result<()> { let cli_config = load_cli_settings(None)?; let base = runs_base(&cli_config.storage_dir()); let run_dir = find_run_by_prefix(&base, &args.run)?; @@ -64,6 +67,7 @@ mod tests { } } +#[allow(unsafe_code)] fn process_alive(pid: u32) -> bool { #[cfg(unix)] { diff --git a/lib/crates/fabro-cli/src/commands/run/rewind.rs b/lib/crates/fabro-cli/src/commands/run/rewind.rs index af4b23f9f..2e810ac74 100644 --- a/lib/crates/fabro-cli/src/commands/run/rewind.rs +++ b/lib/crates/fabro-cli/src/commands/run/rewind.rs @@ -12,7 +12,7 @@ use git2::Repository; use crate::args::RewindArgs; use crate::shared::color_if; -pub fn run(args: &RewindArgs, styles: &Styles) -> Result<()> { +pub(crate) fn run(args: &RewindArgs, styles: &Styles) -> Result<()> { let repo = Repository::discover(".").context("not in a git repository")?; let run_id = find_run_id_by_prefix(&repo, &args.run_id)?; let store = Store::new(repo); 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 04010804b..2f12e6e74 100644 --- a/lib/crates/fabro-cli/src/commands/run/run_progress.rs +++ b/lib/crates/fabro-cli/src/commands/run/run_progress.rs @@ -179,7 +179,7 @@ enum ProgressRenderer { // ── ProgressUI ────────────────────────────────────────────────────────── -pub struct ProgressUI { +pub(crate) struct ProgressUI { renderer: ProgressRenderer, verbose: bool, active_stages: HashMap, @@ -199,7 +199,7 @@ pub struct ProgressUI { #[allow(dead_code)] impl ProgressUI { - pub fn new(is_tty: bool, verbose: bool) -> Self { + pub(crate) fn new(is_tty: bool, verbose: bool) -> Self { let renderer = if is_tty { ProgressRenderer::Tty(TtyRenderer { multi: MultiProgress::new(), @@ -224,7 +224,7 @@ impl ProgressUI { } } - pub fn set_working_directory(&mut self, dir: String) { + pub(crate) fn set_working_directory(&mut self, dir: String) { self.working_directory = Some(dir); } @@ -266,7 +266,7 @@ impl ProgressUI { } /// Register event handlers on the emitter. - pub fn register(progress: &Arc>, emitter: &EventEmitter) { + pub(crate) fn register(progress: &Arc>, emitter: &EventEmitter) { let p = Arc::clone(progress); emitter.on_event(move |event| { let mut ui = p.lock().expect("progress lock poisoned"); @@ -275,21 +275,21 @@ impl ProgressUI { } /// Hide indicatif progress bars (for interview prompts in attach mode). - pub fn hide_bars(&self) { + pub(crate) fn hide_bars(&self) { if let ProgressRenderer::Tty(tty) = &self.renderer { tty.multi.set_draw_target(ProgressDrawTarget::hidden()); } } /// Show indicatif progress bars after an interview prompt. - pub fn show_bars(&self) { + pub(crate) fn show_bars(&self) { if let ProgressRenderer::Tty(tty) = &self.renderer { tty.multi.set_draw_target(ProgressDrawTarget::stderr()); } } /// Clear all active bars and release the terminal for normal stderr output. - pub fn finish(&mut self) { + pub(crate) fn finish(&mut self) { for (_id, stage) in self.active_stages.drain() { for entry in &stage.tool_calls { if entry.is_branch || self.verbose { @@ -662,19 +662,22 @@ impl ProgressUI { /// Parse a JSONL envelope line and dispatch to internal rendering methods. /// Used by the attach loop to render events from progress.jsonl. - pub fn handle_json_line(&mut self, line: &str) { + pub(crate) fn handle_json_line(&mut self, line: &str) { let envelope: serde_json::Value = match serde_json::from_str(line) { Ok(v) => v, Err(_) => return, }; - let event_name = match envelope.get("event").and_then(|v| v.as_str()) { - Some(name) => name, - None => return, + let Some(event_name) = envelope.get("event").and_then(|v| v.as_str()) else { + return; }; let str_field = |key: &str| -> Option<&str> { envelope.get(key).and_then(|v| v.as_str()) }; - let u64_field = - |key: &str| -> u64 { envelope.get(key).and_then(|v| v.as_u64()).unwrap_or(0) }; + let u64_field = |key: &str| -> u64 { + envelope + .get(key) + .and_then(serde_json::Value::as_u64) + .unwrap_or(0) + }; match event_name { "WorkflowRunStarted" => { @@ -697,8 +700,8 @@ impl ProgressUI { .to_string(); let duration_ms = u64_field("duration_ms"); let name = str_field("name").map(String::from); - let cpu = envelope.get("cpu").and_then(|v| v.as_f64()); - let memory = envelope.get("memory").and_then(|v| v.as_f64()); + let cpu = envelope.get("cpu").and_then(serde_json::Value::as_f64); + let memory = envelope.get("memory").and_then(serde_json::Value::as_f64); let url = str_field("url").map(String::from); self.on_sandbox_event(&fabro_agent::SandboxEvent::Ready { provider, @@ -741,7 +744,7 @@ impl ProgressUI { let cost_str = envelope .get("usage") .and_then(|u| u.get("cost")) - .and_then(|c| c.as_f64()) + .and_then(serde_json::Value::as_f64) .map(|c| format!("{} ", format_cost(c))) .unwrap_or_default(); @@ -752,8 +755,12 @@ impl ProgressUI { let total_tokens = envelope .get("usage") .map(|u| { - u.get("input_tokens").and_then(|v| v.as_i64()).unwrap_or(0) - + u.get("output_tokens").and_then(|v| v.as_i64()).unwrap_or(0) + u.get("input_tokens") + .and_then(serde_json::Value::as_i64) + .unwrap_or(0) + + u.get("output_tokens") + .and_then(serde_json::Value::as_i64) + .unwrap_or(0) }) .unwrap_or(0); if turn_count > 0 || tool_call_count > 0 || total_tokens > 0 { @@ -833,7 +840,7 @@ impl ProgressUI { let tool_call_id = str_field("tool_call_id").unwrap_or("?"); let is_error = envelope .get("is_error") - .and_then(|v| v.as_bool()) + .and_then(serde_json::Value::as_bool) .unwrap_or(false); self.on_tool_call_completed(stage, tool_call_id, is_error); } @@ -931,7 +938,7 @@ impl ProgressUI { let pr_url = str_field("pr_url").unwrap_or("?"); let draft = envelope .get("draft") - .and_then(|value| value.as_bool()) + .and_then(serde_json::Value::as_bool) .unwrap_or(false); self.on_pull_request_created(pr_url, draft); } @@ -1027,7 +1034,7 @@ impl ProgressUI { if let Some(cli_name) = str_field("cli_name") { let already_installed = envelope .get("already_installed") - .and_then(|v| v.as_bool()) + .and_then(serde_json::Value::as_bool) .unwrap_or(false); let duration_ms = u64_field("duration_ms"); self.on_cli_ensure_completed(cli_name, already_installed, duration_ms); @@ -1213,7 +1220,7 @@ impl ProgressUI { // ── Logs dir (called externally) ──────────────────────────────────── - pub fn show_run_dir(&mut self, run_dir: &Path) { + pub(crate) fn show_run_dir(&mut self, run_dir: &Path) { let path_str = tilde_path(run_dir); match &self.renderer { ProgressRenderer::Tty(tty) => { @@ -1227,7 +1234,7 @@ impl ProgressUI { } } - pub fn show_version(&mut self) { + pub(crate) fn show_version(&mut self) { let version = FABRO_VERSION; match &self.renderer { ProgressRenderer::Tty(tty) => { @@ -1241,7 +1248,7 @@ impl ProgressUI { } } - pub fn show_run_id(&mut self, run_id: &str) { + pub(crate) fn show_run_id(&mut self, run_id: &str) { match &self.renderer { ProgressRenderer::Tty(tty) => { let bar = tty.multi.add(ProgressBar::new_spinner()); @@ -1254,7 +1261,7 @@ impl ProgressUI { } } - pub fn show_time(&mut self, time: &str) { + pub(crate) fn show_time(&mut self, time: &str) { match &self.renderer { ProgressRenderer::Tty(tty) => { let bar = tty.multi.add(ProgressBar::new_spinner()); @@ -1267,7 +1274,7 @@ impl ProgressUI { } } - pub fn show_worktree(&mut self, path: &Path) { + pub(crate) fn show_worktree(&mut self, path: &Path) { let path_str = tilde_path(path); match &self.renderer { ProgressRenderer::Tty(tty) => { @@ -1281,7 +1288,7 @@ impl ProgressUI { } } - pub fn show_base_info(&mut self, branch: Option<&str>, sha: &str) { + pub(crate) fn show_base_info(&mut self, branch: Option<&str>, sha: &str) { let short_sha = &sha[..sha.len().min(12)]; let text = match branch { Some(b) => format!("Base: {b} ({short_sha})"), @@ -1408,7 +1415,7 @@ impl ProgressUI { { let usage_percent = details .get("usage_percent") - .and_then(|v| v.as_u64()) + .and_then(serde_json::Value::as_u64) .unwrap_or(0); let yellow = Style::new().yellow(); self.insert_info_line_for_stage( @@ -1725,14 +1732,14 @@ impl ProgressUI { /// Wraps a `ConsoleInterviewer` so that progress bars are hidden during /// interactive prompts (avoids garbled output from concurrent writes). #[allow(dead_code)] -pub struct ProgressAwareInterviewer { +pub(crate) struct ProgressAwareInterviewer { inner: ConsoleInterviewer, progress: Arc>, } #[allow(dead_code)] impl ProgressAwareInterviewer { - pub fn new(inner: ConsoleInterviewer, progress: Arc>) -> Self { + pub(crate) fn new(inner: ConsoleInterviewer, progress: Arc>) -> Self { Self { inner, progress } } } diff --git a/lib/crates/fabro-cli/src/commands/run/ssh.rs b/lib/crates/fabro-cli/src/commands/run/ssh.rs index e07258b20..d4b3cc9ef 100644 --- a/lib/crates/fabro-cli/src/commands/run/ssh.rs +++ b/lib/crates/fabro-cli/src/commands/run/ssh.rs @@ -9,7 +9,7 @@ use crate::args::SshArgs; use crate::cli_config::load_cli_settings; use crate::shared::validate_daytona_provider; -pub async fn run(args: SshArgs) -> Result<()> { +pub(crate) async fn run(args: SshArgs) -> Result<()> { let cli_config = load_cli_settings(None)?; let base = runs_base(&cli_config.storage_dir()); let run_dir = resolve_run(&base, &args.run)?.path; diff --git a/lib/crates/fabro-cli/src/commands/run/start.rs b/lib/crates/fabro-cli/src/commands/run/start.rs index d8ed145b5..b79d0bd17 100644 --- a/lib/crates/fabro-cli/src/commands/run/start.rs +++ b/lib/crates/fabro-cli/src/commands/run/start.rs @@ -14,7 +14,8 @@ use super::launcher::{ /// /// The engine process reads `run.json` from the run directory and executes the /// workflow. Returns the child process handle (use `.id()` for the PID). -pub fn start_run(run_dir: &Path, resume: bool) -> Result { +#[allow(unsafe_code)] +pub(crate) fn start_run(run_dir: &Path, resume: bool) -> Result { let record = RunRecord::load(run_dir) .map_err(|e| anyhow!("Cannot start run: failed to load run.json: {e}"))?; diff --git a/lib/crates/fabro-cli/src/commands/run/wait.rs b/lib/crates/fabro-cli/src/commands/run/wait.rs index b37ed4746..c2754636f 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 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/inspect.rs b/lib/crates/fabro-cli/src/commands/runs/inspect.rs index 9a747304c..4d0fd8cb4 100644 --- a/lib/crates/fabro-cli/src/commands/runs/inspect.rs +++ b/lib/crates/fabro-cli/src/commands/runs/inspect.rs @@ -14,7 +14,7 @@ use crate::args::InspectArgs; use crate::cli_config::load_cli_settings; #[derive(Debug, Serialize)] -pub struct InspectOutput { +pub(crate) struct InspectOutput { pub run_id: String, pub run_dir: PathBuf, pub status: RunStatus, @@ -25,17 +25,17 @@ pub struct InspectOutput { pub sandbox: Option, } -pub fn run(args: &InspectArgs) -> Result<()> { +pub(crate) fn run(args: &InspectArgs) -> Result<()> { let cli_config = load_cli_settings(None)?; let base = runs_base(&cli_config.storage_dir()); let run = resolve_run(&base, &args.run)?; - let output = inspect_run_dir(&run.run_id, &run.path, run.status)?; + let output = inspect_run_dir(&run.run_id, &run.path, run.status); let json = serde_json::to_string_pretty(&[output])?; println!("{json}"); Ok(()) } -fn inspect_run_dir(run_id: &str, run_dir: &Path, status: RunStatus) -> Result { +fn inspect_run_dir(run_id: &str, run_dir: &Path, status: RunStatus) -> InspectOutput { let run_record = RunRecord::load(run_dir) .ok() .and_then(|v| serde_json::to_value(v).ok()); @@ -52,7 +52,7 @@ fn inspect_run_dir(run_id: &str, run_dir: &Path, status: RunStatus) -> Result Result Result<()> { +pub(crate) fn list_command(args: &RunsListArgs, styles: &Styles) -> Result<()> { let cli_config = load_cli_settings(None)?; let base = runs_base(&cli_config.storage_dir()); let runs = scan_runs(&base)?; diff --git a/lib/crates/fabro-cli/src/commands/runs/mod.rs b/lib/crates/fabro-cli/src/commands/runs/mod.rs index 30eb94e3d..5edacae7e 100644 --- a/lib/crates/fabro-cli/src/commands/runs/mod.rs +++ b/lib/crates/fabro-cli/src/commands/runs/mod.rs @@ -7,7 +7,7 @@ pub(crate) mod inspect; pub(crate) mod list; pub(crate) mod rm; -pub async fn dispatch(cmd: RunsCommands) -> Result<()> { +pub(crate) async fn dispatch(cmd: RunsCommands) -> Result<()> { match cmd { RunsCommands::Ps(args) => { let styles = Styles::detect_stdout(); diff --git a/lib/crates/fabro-cli/src/commands/runs/rm.rs b/lib/crates/fabro-cli/src/commands/runs/rm.rs index 11987bf2d..42e0d42d5 100644 --- a/lib/crates/fabro-cli/src/commands/runs/rm.rs +++ b/lib/crates/fabro-cli/src/commands/runs/rm.rs @@ -14,7 +14,7 @@ use crate::cli_config::load_cli_settings; use super::short_run_id; -pub async fn remove_command(args: &RunsRemoveArgs) -> Result<()> { +pub(crate) async fn remove_command(args: &RunsRemoveArgs) -> Result<()> { let cli_config = load_cli_settings(None)?; let base = runs_base(&cli_config.storage_dir()); remove_from(args, &base).await diff --git a/lib/crates/fabro-cli/src/commands/secret/get.rs b/lib/crates/fabro-cli/src/commands/secret/get.rs index 43c4e701e..ac8fc41f1 100644 --- a/lib/crates/fabro-cli/src/commands/secret/get.rs +++ b/lib/crates/fabro-cli/src/commands/secret/get.rs @@ -3,7 +3,7 @@ use anyhow::{Result, bail}; use crate::args::SecretGetArgs; use fabro_config::dotenv; -pub fn get_command(args: &SecretGetArgs) -> Result<()> { +pub(super) fn get_command(args: &SecretGetArgs) -> Result<()> { let path = dotenv::env_file_path()?; match dotenv::get_env_value(&path, &args.key)? { Some(value) => { diff --git a/lib/crates/fabro-cli/src/commands/secret/list.rs b/lib/crates/fabro-cli/src/commands/secret/list.rs index b638c47e4..de3893c4b 100644 --- a/lib/crates/fabro-cli/src/commands/secret/list.rs +++ b/lib/crates/fabro-cli/src/commands/secret/list.rs @@ -3,7 +3,7 @@ use anyhow::{Result, bail}; use crate::args::SecretListArgs; use fabro_config::dotenv; -pub fn list_command(args: &SecretListArgs) -> Result<()> { +pub(super) fn list_command(args: &SecretListArgs) -> Result<()> { let path = dotenv::env_file_path()?; let contents = match std::fs::read_to_string(&path) { Ok(c) => c, diff --git a/lib/crates/fabro-cli/src/commands/secret/mod.rs b/lib/crates/fabro-cli/src/commands/secret/mod.rs index cdcdc6889..e9e399f75 100644 --- a/lib/crates/fabro-cli/src/commands/secret/mod.rs +++ b/lib/crates/fabro-cli/src/commands/secret/mod.rs @@ -7,7 +7,7 @@ use anyhow::Result; use crate::args::{SecretCommand, SecretNamespace}; -pub fn dispatch(ns: SecretNamespace) -> Result<()> { +pub(crate) fn dispatch(ns: SecretNamespace) -> Result<()> { match ns.command { SecretCommand::Get(args) => get::get_command(&args), SecretCommand::List(args) => list::list_command(&args), diff --git a/lib/crates/fabro-cli/src/commands/secret/rm.rs b/lib/crates/fabro-cli/src/commands/secret/rm.rs index 4bbdaae1b..e7fbfb00d 100644 --- a/lib/crates/fabro-cli/src/commands/secret/rm.rs +++ b/lib/crates/fabro-cli/src/commands/secret/rm.rs @@ -3,7 +3,7 @@ use anyhow::{Result, bail}; use crate::args::SecretRmArgs; use fabro_config::dotenv; -pub fn rm_command(args: &SecretRmArgs) -> Result<()> { +pub(super) fn rm_command(args: &SecretRmArgs) -> Result<()> { let path = dotenv::env_file_path()?; let contents = match std::fs::read_to_string(&path) { Ok(c) => c, diff --git a/lib/crates/fabro-cli/src/commands/secret/set.rs b/lib/crates/fabro-cli/src/commands/secret/set.rs index a14f104b9..6bf5b1883 100644 --- a/lib/crates/fabro-cli/src/commands/secret/set.rs +++ b/lib/crates/fabro-cli/src/commands/secret/set.rs @@ -3,7 +3,7 @@ use anyhow::Result; use crate::args::SecretSetArgs; use fabro_config::dotenv; -pub fn set_command(args: &SecretSetArgs) -> Result<()> { +pub(super) fn set_command(args: &SecretSetArgs) -> Result<()> { let path = dotenv::env_file_path()?; let existing = std::fs::read_to_string(&path).unwrap_or_default(); let merged = dotenv::merge_env(&existing, &[(&args.key, &args.value)]); diff --git a/lib/crates/fabro-cli/src/commands/skill/install.rs b/lib/crates/fabro-cli/src/commands/skill/install.rs index 4e2d4dae5..9780f9f6d 100644 --- a/lib/crates/fabro-cli/src/commands/skill/install.rs +++ b/lib/crates/fabro-cli/src/commands/skill/install.rs @@ -21,7 +21,7 @@ const SKILL_FILES: &[(&str, &str)] = &[ ]; /// Install all skill files under `base_dir/fabro-create-workflow/`. -pub fn install_skill_to(base_dir: &Path) -> Result<()> { +pub(crate) fn install_skill_to(base_dir: &Path) -> Result<()> { let skill_dir = base_dir.join("fabro-create-workflow"); for (rel_path, content) in SKILL_FILES { @@ -37,7 +37,7 @@ pub fn install_skill_to(base_dir: &Path) -> Result<()> { Ok(()) } -pub fn run_skill_install(args: &SkillInstallArgs) -> Result<()> { +pub(super) fn run_skill_install(args: &SkillInstallArgs) -> Result<()> { let base_dir = resolve_base_dir(&args.scope, &args.dir)?; let skill_dir = base_dir.join("fabro-create-workflow"); diff --git a/lib/crates/fabro-cli/src/commands/skill/mod.rs b/lib/crates/fabro-cli/src/commands/skill/mod.rs index 27086ae54..b537cc6b1 100644 --- a/lib/crates/fabro-cli/src/commands/skill/mod.rs +++ b/lib/crates/fabro-cli/src/commands/skill/mod.rs @@ -4,9 +4,9 @@ use anyhow::Result; use crate::args::{SkillCommand, SkillNamespace}; -pub use install::install_skill_to; +pub(crate) use install::install_skill_to; -pub fn dispatch(ns: SkillNamespace) -> Result<()> { +pub(crate) fn dispatch(ns: SkillNamespace) -> Result<()> { match ns.command { SkillCommand::Install(args) => install::run_skill_install(&args), } diff --git a/lib/crates/fabro-cli/src/commands/system/df.rs b/lib/crates/fabro-cli/src/commands/system/df.rs index fc491e1f7..77ad2b851 100644 --- a/lib/crates/fabro-cli/src/commands/system/df.rs +++ b/lib/crates/fabro-cli/src/commands/system/df.rs @@ -13,7 +13,7 @@ use crate::args::DfArgs; use crate::cli_config::load_cli_settings; use crate::shared::format_size; -pub fn df_command(args: &DfArgs) -> Result<()> { +pub(super) fn df_command(args: &DfArgs) -> Result<()> { let cli_config = load_cli_settings(None)?; let data_dir = cli_config.storage_dir(); let runs_base_dir = runs_base(&data_dir); @@ -81,7 +81,12 @@ fn df_from(args: &DfArgs, data_dir: &Path, runs_base: &Path, logs_base: &Path) - continue; } let name = entry.file_name().to_string_lossy().to_string(); - if name.ends_with(".db") || name.ends_with(".db-wal") || name.ends_with(".db-shm") { + if std::path::Path::new(&name) + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("db")) + || name.ends_with(".db-wal") + || name.ends_with(".db-shm") + { if let Ok(meta) = path.metadata() { db_count += 1; total_db_size += meta.len(); @@ -214,9 +219,9 @@ fn truncate_str(s: &str, max_len: usize) -> String { fn dir_size(path: &Path) -> u64 { walkdir::WalkDir::new(path) .into_iter() - .filter_map(|entry| entry.ok()) + .filter_map(std::result::Result::ok) .filter_map(|entry| entry.metadata().ok()) - .filter(|metadata| metadata.is_file()) + .filter(std::fs::Metadata::is_file) .map(|metadata| metadata.len()) .sum() } diff --git a/lib/crates/fabro-cli/src/commands/system/mod.rs b/lib/crates/fabro-cli/src/commands/system/mod.rs index 4e1bfe0e6..b83323dbc 100644 --- a/lib/crates/fabro-cli/src/commands/system/mod.rs +++ b/lib/crates/fabro-cli/src/commands/system/mod.rs @@ -7,7 +7,7 @@ use crate::args::{SystemCommand, SystemNamespace}; pub(crate) use prune::parse_duration; -pub fn dispatch(ns: SystemNamespace) -> Result<()> { +pub(crate) fn dispatch(ns: SystemNamespace) -> Result<()> { match ns.command { SystemCommand::Prune(args) => prune::prune_command(&args), SystemCommand::Df(args) => df::df_command(&args), diff --git a/lib/crates/fabro-cli/src/commands/system/prune.rs b/lib/crates/fabro-cli/src/commands/system/prune.rs index e41814669..c295a8054 100644 --- a/lib/crates/fabro-cli/src/commands/system/prune.rs +++ b/lib/crates/fabro-cli/src/commands/system/prune.rs @@ -11,7 +11,7 @@ use crate::args::RunsPruneArgs; use crate::cli_config::load_cli_settings; use crate::shared::format_size; -pub fn prune_command(args: &RunsPruneArgs) -> Result<()> { +pub(super) fn prune_command(args: &RunsPruneArgs) -> Result<()> { let cli_config = load_cli_settings(None)?; let base = runs_base(&cli_config.storage_dir()); prune_from(args, &base) @@ -111,9 +111,9 @@ fn parse_label_filters(label_args: &[String]) -> Vec<(String, String)> { fn dir_size(path: &Path) -> u64 { walkdir::WalkDir::new(path) .into_iter() - .filter_map(|entry| entry.ok()) + .filter_map(std::result::Result::ok) .filter_map(|entry| entry.metadata().ok()) - .filter(|metadata| metadata.is_file()) + .filter(std::fs::Metadata::is_file) .map(|metadata| metadata.len()) .sum() } diff --git a/lib/crates/fabro-cli/src/commands/upgrade.rs b/lib/crates/fabro-cli/src/commands/upgrade.rs index d0b61da8b..4ae724902 100644 --- a/lib/crates/fabro-cli/src/commands/upgrade.rs +++ b/lib/crates/fabro-cli/src/commands/upgrade.rs @@ -31,7 +31,7 @@ fn http_client() -> Result { impl Backend { async fn fetch_latest_release_tag(&self) -> Result { match self { - Backend::Gh => { + Self::Gh => { let output = TokioCommand::new("gh") .args([ "release", @@ -52,7 +52,7 @@ impl Backend { } Ok(String::from_utf8(output.stdout)?.trim().to_string()) } - Backend::Http(client) => { + Self::Http(client) => { let url = format!("https://api.github.com/repos/{GITHUB_REPO}/releases/latest"); let resp = client .get(&url) @@ -77,7 +77,7 @@ impl Backend { async fn download_release(&self, tag: &str, asset: &str, dest_dir: &Path) -> Result { let dest = dest_dir.join(asset); match self { - Backend::Gh => { + Self::Gh => { let status = TokioCommand::new("gh") .args([ "release", @@ -98,7 +98,7 @@ impl Backend { bail!("gh release download failed with exit code {status}"); } } - Backend::Http(client) => { + Self::Http(client) => { let url = format!("https://github.com/{GITHUB_REPO}/releases/download/{tag}/{asset}"); let resp = client @@ -223,7 +223,7 @@ impl UpgradeCheckState { // ── Main upgrade command ─────────────────────────────────────────────────── -pub async fn run_upgrade(args: UpgradeArgs) -> Result<()> { +pub(crate) async fn run_upgrade(args: UpgradeArgs) -> Result<()> { let backend = select_backend().await; let current = @@ -346,7 +346,7 @@ pub async fn run_upgrade(args: UpgradeArgs) -> Result<()> { /// Spawn a background task that checks for a newer version and prints a notice /// to stderr after the main command completes. Returns a handle that should be /// awaited at the end of `main_inner`. -pub fn spawn_upgrade_check( +pub(crate) fn spawn_upgrade_check( no_upgrade_check: bool, upgrade_check_enabled: bool, ) -> Option> { diff --git a/lib/crates/fabro-cli/src/commands/validate.rs b/lib/crates/fabro-cli/src/commands/validate.rs index 1bb9510c3..6dd60fbdd 100644 --- a/lib/crates/fabro-cli/src/commands/validate.rs +++ b/lib/crates/fabro-cli/src/commands/validate.rs @@ -9,7 +9,7 @@ use fabro_workflows::operations::{ValidateInput, WorkflowInput, validate}; use crate::args::ValidateArgs; use crate::shared::{print_diagnostics, relative_path}; -pub fn run(args: &ValidateArgs, styles: &Styles) -> anyhow::Result<()> { +pub(crate) fn run(args: &ValidateArgs, styles: &Styles) -> anyhow::Result<()> { let cwd = std::env::current_dir()?; let cli_defaults = load_cli_config(None)?; let settings = resolve_settings(ResolveSettingsInput { diff --git a/lib/crates/fabro-cli/src/commands/workflow/create.rs b/lib/crates/fabro-cli/src/commands/workflow/create.rs index 71ad67add..99ecd8a4c 100644 --- a/lib/crates/fabro-cli/src/commands/workflow/create.rs +++ b/lib/crates/fabro-cli/src/commands/workflow/create.rs @@ -7,15 +7,14 @@ use fabro_config::project::{discover_project_config, resolve_fabro_root}; use crate::args::WorkflowCreateArgs; use crate::shared::relative_path; -pub fn create_command(args: &WorkflowCreateArgs) -> Result<()> { +pub(super) fn create_command(args: &WorkflowCreateArgs) -> Result<()> { let cwd = std::env::current_dir()?; - let (config_path, config) = match discover_project_config(&cwd)? { - Some(found) => found, - None => bail!( + let Some((config_path, config)) = discover_project_config(&cwd)? else { + bail!( "No fabro.toml found in {cwd} or any parent directory", cwd = cwd.display() - ), + ); }; let fabro_root = resolve_fabro_root(&config_path, &config); diff --git a/lib/crates/fabro-cli/src/commands/workflow/list.rs b/lib/crates/fabro-cli/src/commands/workflow/list.rs index 49e97ee89..30986c120 100644 --- a/lib/crates/fabro-cli/src/commands/workflow/list.rs +++ b/lib/crates/fabro-cli/src/commands/workflow/list.rs @@ -11,16 +11,15 @@ use crate::shared::relative_path; const GOAL_MAX_LEN: usize = 60; -pub fn list_command(_args: &WorkflowListArgs) -> Result<()> { +pub(super) fn list_command(_args: &WorkflowListArgs) -> Result<()> { let styles = Styles::detect_stderr(); let cwd = std::env::current_dir()?; - let (config_path, config) = match discover_project_config(&cwd)? { - Some(found) => found, - None => bail!( + let Some((config_path, config)) = discover_project_config(&cwd)? else { + bail!( "No fabro.toml found in {cwd} or any parent directory", cwd = cwd.display() - ), + ); }; let fabro_root = resolve_fabro_root(&config_path, &config); diff --git a/lib/crates/fabro-cli/src/commands/workflow/mod.rs b/lib/crates/fabro-cli/src/commands/workflow/mod.rs index d236638f3..56ff98f38 100644 --- a/lib/crates/fabro-cli/src/commands/workflow/mod.rs +++ b/lib/crates/fabro-cli/src/commands/workflow/mod.rs @@ -5,7 +5,7 @@ use anyhow::Result; use crate::args::{WorkflowCommand, WorkflowNamespace}; -pub fn dispatch(ns: WorkflowNamespace) -> Result<()> { +pub(crate) fn dispatch(ns: WorkflowNamespace) -> Result<()> { match ns.command { WorkflowCommand::List(args) => list::list_command(&args), WorkflowCommand::Create(args) => create::create_command(&args), diff --git a/lib/crates/fabro-cli/src/logging.rs b/lib/crates/fabro-cli/src/logging.rs index f8a71e759..c65aa6567 100644 --- a/lib/crates/fabro-cli/src/logging.rs +++ b/lib/crates/fabro-cli/src/logging.rs @@ -3,7 +3,11 @@ use fabro_util::run_log; use tracing_appender::rolling; use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt}; -pub fn init_tracing(debug: bool, config_log_level: Option<&str>, log_prefix: &str) -> Result<()> { +pub(crate) fn init_tracing( + debug: bool, + config_log_level: Option<&str>, + log_prefix: &str, +) -> Result<()> { let default_level = if debug { "debug" } else { diff --git a/lib/crates/fabro-cli/src/main.rs b/lib/crates/fabro-cli/src/main.rs index 4d31a1964..1137baf99 100644 --- a/lib/crates/fabro-cli/src/main.rs +++ b/lib/crates/fabro-cli/src/main.rs @@ -1,3 +1,5 @@ +#![allow(clippy::print_stdout, clippy::print_stderr, clippy::exit)] + mod args; mod cli_config; mod commands; @@ -220,7 +222,7 @@ async fn main_inner() -> (String, Result<()>) { result?; } Commands::SendPanic { path } => { - let result = tel_panic::capture(&path).await; + let result = tel_panic::capture(&path); let _ = std::fs::remove_file(&path); result?; } diff --git a/lib/crates/fabro-cli/src/shared/utilities.rs b/lib/crates/fabro-cli/src/shared/utilities.rs index e4c6dfab2..9c128401d 100644 --- a/lib/crates/fabro-cli/src/shared/utilities.rs +++ b/lib/crates/fabro-cli/src/shared/utilities.rs @@ -6,12 +6,12 @@ use cli_table::Color; use fabro_util::terminal::Styles; use fabro_validate::{Diagnostic, Severity}; -pub fn read_workflow_file(path: &Path) -> anyhow::Result { +pub(crate) fn read_workflow_file(path: &Path) -> anyhow::Result { std::fs::read_to_string(path) .map_err(|e| anyhow::anyhow!("Failed to read {}: {e}", path.display())) } -pub fn print_diagnostics(diagnostics: &[Diagnostic], styles: &Styles) { +pub(crate) fn print_diagnostics(diagnostics: &[Diagnostic], styles: &Styles) { for d in diagnostics { let location = match (&d.node_id, &d.edge) { (Some(node), _) => format!(" [node: {node}]"), @@ -41,7 +41,7 @@ pub fn print_diagnostics(diagnostics: &[Diagnostic], styles: &Styles) { } } -pub fn relative_path(path: &Path) -> String { +pub(crate) fn relative_path(path: &Path) -> String { if let Ok(cwd) = std::env::current_dir() { if let Ok(rel) = path.strip_prefix(&cwd) { return rel.display().to_string(); @@ -50,7 +50,7 @@ pub fn relative_path(path: &Path) -> String { tilde_path(path) } -pub fn format_tokens_human(tokens: i64) -> String { +pub(crate) fn format_tokens_human(tokens: i64) -> String { if tokens >= 1_000_000 { format!("{:.1}m", tokens as f64 / 1_000_000.0) } else if tokens >= 1000 { @@ -60,7 +60,7 @@ pub fn format_tokens_human(tokens: i64) -> String { } } -pub fn tilde_path(path: &Path) -> String { +pub(crate) fn tilde_path(path: &Path) -> String { if let Some(home) = dirs::home_dir() { if let Ok(suffix) = path.strip_prefix(&home) { return format!("~/{}", suffix.display()); @@ -69,18 +69,18 @@ pub fn tilde_path(path: &Path) -> String { path.display().to_string() } -pub fn color_if(use_color: bool, color: Color) -> Option { +pub(crate) fn color_if(use_color: bool, color: Color) -> Option { if use_color { Some(color) } else { None } } -pub fn split_run_path(s: &str) -> Option<(&str, &str)> { +pub(crate) fn split_run_path(s: &str) -> Option<(&str, &str)> { if s.starts_with('/') || s.starts_with("./") || s.starts_with("../") { return None; } s.split_once(':') } -pub fn validate_daytona_provider( +pub(crate) fn validate_daytona_provider( record: &fabro_sandbox::SandboxRecord, feature: &str, ) -> Result<()> { @@ -93,7 +93,7 @@ pub fn validate_daytona_provider( Ok(()) } -pub fn format_duration_ms(ms: u64) -> String { +pub(crate) fn format_duration_ms(ms: u64) -> String { let duration = Duration::from_millis(ms); let secs = duration.as_secs(); if secs >= 60 { @@ -105,7 +105,7 @@ pub fn format_duration_ms(ms: u64) -> String { } } -pub fn format_size(bytes: u64) -> String { +pub(crate) fn format_size(bytes: u64) -> String { const KB: u64 = 1024; const MB: u64 = 1024 * KB; const GB: u64 = 1024 * MB; diff --git a/lib/crates/fabro-config/src/project.rs b/lib/crates/fabro-config/src/project.rs index a37f5a89a..376674025 100644 --- a/lib/crates/fabro-config/src/project.rs +++ b/lib/crates/fabro-config/src/project.rs @@ -1,3 +1,4 @@ +use std::fmt::Write; use std::path::{Path, PathBuf}; use anyhow::{Context, bail}; @@ -265,7 +266,7 @@ fn resolve_workflow_arg_impl( available.join(", ") ); if let Some(suggestion) = find_closest_match(&name, &available) { - msg.push_str(&format!("\n\nDid you mean '{suggestion}'?")); + let _ = write!(msg, "\n\nDid you mean '{suggestion}'?"); } bail!("{msg}"); } diff --git a/lib/crates/fabro-core/src/executor.rs b/lib/crates/fabro-core/src/executor.rs index 12a1715bc..5b96963c7 100644 --- a/lib/crates/fabro-core/src/executor.rs +++ b/lib/crates/fabro-core/src/executor.rs @@ -202,7 +202,7 @@ impl Executor { .await?; // Determine next step - let last_outcome = state.node_outcomes.get(node.id()).unwrap(); + let last_outcome = &state.node_outcomes[node.id()]; let next = self .resolve_next_step(&node, last_outcome, &state, graph) .await?; @@ -369,40 +369,37 @@ impl Executor { } // Normal edge selection - match graph.select_edge(node, outcome, &state.context) { - Some(selection) => { - let target = selection.edge.target().to_string(); - let is_restart = selection.edge.is_loop_restart(); + if let Some(selection) = graph.select_edge(node, outcome, &state.context) { + let target = selection.edge.target().to_string(); + let is_restart = selection.edge.is_loop_restart(); - let ctx = EdgeContext { - from: node.id(), - to: &target, - edge: Some(selection.edge.clone()), - is_jump: false, - outcome, - reason: selection.reason, - }; - match self.lifecycle.on_edge_selected(&ctx, state).await? { - EdgeDecision::Continue => { - if is_restart { - Ok(NextStep::LoopRestart(target)) - } else { - Ok(NextStep::Edge(target)) - } - } - EdgeDecision::Override(new_target) => Ok(NextStep::Edge(new_target)), - EdgeDecision::Block(msg) => Err(CoreError::blocked(msg)), - } - } - None => { - // No edge found - if outcome.status == StageStatus::Fail { - if let Some(retry_target) = graph.get_retry_target(node.id()) { - return Ok(NextStep::Edge(retry_target)); + let ctx = EdgeContext { + from: node.id(), + to: &target, + edge: Some(selection.edge.clone()), + is_jump: false, + outcome, + reason: selection.reason, + }; + match self.lifecycle.on_edge_selected(&ctx, state).await? { + EdgeDecision::Continue => { + if is_restart { + Ok(NextStep::LoopRestart(target)) + } else { + Ok(NextStep::Edge(target)) } } - Ok(NextStep::End) + EdgeDecision::Override(new_target) => Ok(NextStep::Edge(new_target)), + EdgeDecision::Block(msg) => Err(CoreError::blocked(msg)), } + } else { + // No edge found + if outcome.status == StageStatus::Fail { + if let Some(retry_target) = graph.get_retry_target(node.id()) { + return Ok(NextStep::Edge(retry_target)); + } + } + Ok(NextStep::End) } } } diff --git a/lib/crates/fabro-core/src/stall.rs b/lib/crates/fabro-core/src/stall.rs index 83ee3fc05..74c28fa4a 100644 --- a/lib/crates/fabro-core/src/stall.rs +++ b/lib/crates/fabro-core/src/stall.rs @@ -57,7 +57,7 @@ impl StallWatchdog { let handle = tokio::spawn(async move { loop { tokio::select! { - _ = sleep(timeout) => { + () = sleep(timeout) => { if shutdown.load(Ordering::Relaxed) { return; } @@ -69,12 +69,11 @@ impl StallWatchdog { cancel_token.store(true, Ordering::Relaxed); return; } - _ = activity.notified() => { + () = activity.notified() => { if shutdown.load(Ordering::Relaxed) { return; } // Activity reported, restart the timer - continue; } } } diff --git a/lib/crates/fabro-devcontainer/src/compose.rs b/lib/crates/fabro-devcontainer/src/compose.rs index cbc6d0039..8733d19df 100644 --- a/lib/crates/fabro-devcontainer/src/compose.rs +++ b/lib/crates/fabro-devcontainer/src/compose.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; /// Extracted configuration from a Docker Compose service. #[derive(Debug, Clone, Default)] -pub struct ComposeServiceConfig { +pub(crate) struct ComposeServiceConfig { pub image: Option, pub build: Option, pub ports: Vec, @@ -13,13 +13,13 @@ pub struct ComposeServiceConfig { /// Build configuration from a Docker Compose service. #[derive(Debug, Clone)] -pub struct ComposeBuild { +pub(crate) struct ComposeBuild { pub context: String, pub dockerfile: Option, } /// Parse a Docker Compose file and extract config for the named service. -pub fn parse_compose( +pub(crate) fn parse_compose( compose_path: &Path, service_name: &str, ) -> Result { @@ -155,7 +155,7 @@ fn parse_environment(service: &serde_yaml::Value) -> HashMap { /// Parse multiple Docker Compose files and merge config for the named service. /// Later files override earlier files for image/build/user; ports accumulate (deduped); /// environment keys from later files override earlier ones. -pub fn parse_compose_multi( +pub(crate) fn parse_compose_multi( compose_paths: &[PathBuf], service_name: &str, ) -> Result { diff --git a/lib/crates/fabro-devcontainer/src/dockerfile.rs b/lib/crates/fabro-devcontainer/src/dockerfile.rs index 340f2feda..d9c99fdc0 100644 --- a/lib/crates/fabro-devcontainer/src/dockerfile.rs +++ b/lib/crates/fabro-devcontainer/src/dockerfile.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use crate::features::FeatureLayer; /// Generate a combined Dockerfile from base + features + env + user. -pub fn generate( +pub(crate) fn generate( base_dockerfile: &str, feature_layers: &[FeatureLayer], container_env: &HashMap, diff --git a/lib/crates/fabro-devcontainer/src/features.rs b/lib/crates/fabro-devcontainer/src/features.rs index 217257762..c34209b67 100644 --- a/lib/crates/fabro-devcontainer/src/features.rs +++ b/lib/crates/fabro-devcontainer/src/features.rs @@ -1,4 +1,5 @@ use std::collections::{HashMap, HashSet, VecDeque}; +use std::fmt::Write; use std::path::Path; use tokio::fs; @@ -10,7 +11,7 @@ use crate::types::{FeatureMetadata, LifecycleCommand}; /// A resolved feature layer ready to be inserted into a Dockerfile. #[derive(Debug, Clone)] -pub struct FeatureLayer { +pub(crate) struct FeatureLayer { /// Feature identifier (e.g. "ghcr.io/devcontainers/features/node:1") pub id: String, /// Directory name for COPY @@ -21,7 +22,7 @@ pub struct FeatureLayer { /// All resolved feature data: layers, environment, and lifecycle hooks. #[derive(Debug, Clone, Default)] -pub struct ResolvedFeatures { +pub(crate) struct ResolvedFeatures { pub layers: Vec, pub container_env: HashMap, pub on_create_commands: Vec, @@ -133,7 +134,10 @@ async fn find_tgz(dir: &Path) -> Option { let mut entries = fs::read_dir(dir).await.ok()?; while let Ok(Some(entry)) = entries.next_entry().await { if let Some(name) = entry.file_name().to_str() { - if name.ends_with(".tgz") { + if Path::new(name) + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("tgz")) + { return Some(name.to_string()); } } @@ -332,7 +336,10 @@ fn topo_sort( return Vec::new(); } - let id_set: HashSet<&str> = feature_ids.iter().map(|s| s.as_str()).collect(); + let id_set: HashSet<&str> = feature_ids + .iter() + .map(std::string::String::as_str) + .collect(); // Build adjacency list and in-degree count. // An edge from A -> B means "A must be installed before B". @@ -350,7 +357,11 @@ fn topo_sort( for id in feature_ids { if let Some(meta) = metadata_map.get(id) { // Collect dependency refs from both installsAfter and dependsOn - let mut dep_refs: Vec<&str> = meta.installs_after.iter().map(|s| s.as_str()).collect(); + let mut dep_refs: Vec<&str> = meta + .installs_after + .iter() + .map(std::string::String::as_str) + .collect(); for dep_id in meta.depends_on.keys() { dep_refs.push(dep_id.as_str()); } @@ -511,12 +522,14 @@ fn generate_layer( } let mut snippet = format!("# Feature: {feature_id}\n"); - snippet.push_str(&format!( - "COPY {dir_name}/ /tmp/devcontainer-features/{dir_name}/\n" - )); - snippet.push_str(&format!( - "RUN cd /tmp/devcontainer-features/{dir_name} && \\\n" - )); + let _ = writeln!( + snippet, + "COPY {dir_name}/ /tmp/devcontainer-features/{dir_name}/" + ); + let _ = writeln!( + snippet, + "RUN cd /tmp/devcontainer-features/{dir_name} && \\" + ); for line in &env_lines { snippet.push_str(line); snippet.push('\n'); @@ -528,7 +541,7 @@ fn generate_layer( } /// Fetch, order, and resolve features into Dockerfile layers. -pub async fn resolve_features( +pub(crate) async fn resolve_features( features: &HashMap, devcontainer_dir: &Path, remote_user: Option<&str>, diff --git a/lib/crates/fabro-devcontainer/src/jsonc.rs b/lib/crates/fabro-devcontainer/src/jsonc.rs index 14193b9b7..c1e0689b2 100644 --- a/lib/crates/fabro-devcontainer/src/jsonc.rs +++ b/lib/crates/fabro-devcontainer/src/jsonc.rs @@ -1,5 +1,5 @@ /// Strip JSONC comments and trailing commas, producing valid JSON. -pub fn strip_jsonc(input: &str) -> String { +pub(crate) fn strip_jsonc(input: &str) -> String { let mut out = String::with_capacity(input.len()); let bytes = input.as_bytes(); let len = bytes.len(); diff --git a/lib/crates/fabro-devcontainer/src/lib.rs b/lib/crates/fabro-devcontainer/src/lib.rs index 87769566b..2b7e0ee3d 100644 --- a/lib/crates/fabro-devcontainer/src/lib.rs +++ b/lib/crates/fabro-devcontainer/src/lib.rs @@ -252,20 +252,26 @@ impl DevcontainerResolver { build_args: HashMap::new(), build_target: None, initialize_commands: Self::collect_commands( - &devcontainer.initialize_command, + devcontainer.initialize_command.as_ref(), + &vars, + ), + on_create_commands: Self::collect_commands( + devcontainer.on_create_command.as_ref(), &vars, ), - on_create_commands: Self::collect_commands(&devcontainer.on_create_command, &vars), post_create_commands: Self::collect_commands( - &devcontainer.post_create_command, + devcontainer.post_create_command.as_ref(), &vars, ), post_start_commands: Self::collect_commands( - &devcontainer.post_start_command, + devcontainer.post_start_command.as_ref(), &vars, ), environment, - container_env: Self::collect_container_env(&devcontainer.container_env, &vars), + container_env: Self::collect_container_env( + devcontainer.container_env.as_ref(), + &vars, + ), remote_user: devcontainer.remote_user.clone().or(compose_config.user), workspace_folder, forwarded_ports: { @@ -362,11 +368,12 @@ impl DevcontainerResolver { let forwarded_ports = Self::parse_forward_ports(&devcontainer.forward_ports); // Collect devcontainer.json lifecycle commands, then append feature lifecycle commands - let mut on_create_commands = Self::collect_commands(&devcontainer.on_create_command, &vars); + let mut on_create_commands = + Self::collect_commands(devcontainer.on_create_command.as_ref(), &vars); let mut post_create_commands = - Self::collect_commands(&devcontainer.post_create_command, &vars); + Self::collect_commands(devcontainer.post_create_command.as_ref(), &vars); let mut post_start_commands = - Self::collect_commands(&devcontainer.post_start_command, &vars); + Self::collect_commands(devcontainer.post_start_command.as_ref(), &vars); for cmd in &resolved_features.on_create_commands { on_create_commands.push(Self::convert_lifecycle_command(cmd)); @@ -383,7 +390,10 @@ impl DevcontainerResolver { build_context, build_args, build_target, - initialize_commands: Self::collect_commands(&devcontainer.initialize_command, &vars), + initialize_commands: Self::collect_commands( + devcontainer.initialize_command.as_ref(), + &vars, + ), on_create_commands, post_create_commands, post_start_commands, @@ -438,7 +448,7 @@ impl DevcontainerResolver { path: devcontainer_dir.clone(), source, })? - .filter_map(|entry| entry.ok()) + .filter_map(std::result::Result::ok) .filter(|entry| entry.path().is_dir()) .map(|entry| entry.path()) .filter(|dir| dir.join("devcontainer.json").exists()) @@ -487,7 +497,7 @@ impl DevcontainerResolver { } fn collect_container_env( - env: &Option>, + env: Option<&HashMap>, vars: &variables::VariableContext, ) -> HashMap { match env { @@ -508,7 +518,7 @@ impl DevcontainerResolver { } fn collect_commands( - cmd: &Option, + cmd: Option<&types::LifecycleCommand>, vars: &variables::VariableContext, ) -> Vec { match cmd { diff --git a/lib/crates/fabro-devcontainer/src/types.rs b/lib/crates/fabro-devcontainer/src/types.rs index 042c5ea80..1bea0f751 100644 --- a/lib/crates/fabro-devcontainer/src/types.rs +++ b/lib/crates/fabro-devcontainer/src/types.rs @@ -107,7 +107,7 @@ pub enum LifecycleCommand { /// Metadata from a devcontainer-feature.json file. #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FeatureMetadata { +pub(crate) struct FeatureMetadata { pub id: Option, pub name: Option, pub version: Option, @@ -135,7 +135,7 @@ pub struct FeatureMetadata { /// A single option for a devcontainer feature. #[derive(Debug, Clone, Deserialize)] -pub struct FeatureOption { +pub(crate) struct FeatureOption { #[serde(rename = "type")] pub option_type: Option, pub default: Option, diff --git a/lib/crates/fabro-devcontainer/src/variables.rs b/lib/crates/fabro-devcontainer/src/variables.rs index 72b5f1533..92bc72cef 100644 --- a/lib/crates/fabro-devcontainer/src/variables.rs +++ b/lib/crates/fabro-devcontainer/src/variables.rs @@ -1,7 +1,7 @@ use fabro_util::env::Env; /// Context for variable substitution. -pub struct VariableContext<'a> { +pub(crate) struct VariableContext<'a> { pub local_workspace_folder: String, pub local_workspace_folder_basename: String, pub container_workspace_folder: String, @@ -9,7 +9,7 @@ pub struct VariableContext<'a> { } /// Replace devcontainer variables in a string value. -pub fn substitute(input: &str, ctx: &VariableContext) -> String { +pub(crate) fn substitute(input: &str, ctx: &VariableContext) -> String { let mut result = String::with_capacity(input.len()); let mut rest = input; @@ -24,7 +24,7 @@ pub fn substitute(input: &str, ctx: &VariableContext) -> String { Some(val) => result.push_str(&val), None => { // Unknown variable — leave as-is - result.push_str(&rest[start..start + 2 + close + 1]); + result.push_str(&rest[start..=(start + 2 + close)]); } } rest = &after_open[close + 1..]; diff --git a/lib/crates/fabro-git-storage/src/branchstore.rs b/lib/crates/fabro-git-storage/src/branchstore.rs index 2b8429d7e..e0eaae4ab 100644 --- a/lib/crates/fabro-git-storage/src/branchstore.rs +++ b/lib/crates/fabro-git-storage/src/branchstore.rs @@ -115,9 +115,8 @@ impl<'a> BranchStore<'a> { /// Read a single file from the latest tree. Returns `None` if branch or path doesn't exist. pub fn read_entry(&self, path: &str) -> Result>> { - let commit_oid = match self.objects.resolve_ref(&self.branch)? { - Some(oid) => oid, - None => return Ok(None), + let Some(commit_oid) = self.objects.resolve_ref(&self.branch)? else { + return Ok(None); }; let commit = self.objects.repo().find_commit(commit_oid)?; let tree = commit.tree()?; @@ -134,9 +133,8 @@ impl<'a> BranchStore<'a> { /// Read multiple paths. Missing paths are omitted from the result. pub fn read_entries<'b>(&self, paths: &[&'b str]) -> Result)>> { - let commit_oid = match self.objects.resolve_ref(&self.branch)? { - Some(oid) => oid, - None => return Ok(vec![]), + let Some(commit_oid) = self.objects.resolve_ref(&self.branch)? else { + return Ok(vec![]); }; let commit = self.objects.repo().find_commit(commit_oid)?; let tree = commit.tree()?; @@ -157,9 +155,8 @@ impl<'a> BranchStore<'a> { /// List all paths under a prefix in the latest tree. pub fn list_entries(&self, prefix: &str) -> Result> { - let commit_oid = match self.objects.resolve_ref(&self.branch)? { - Some(oid) => oid, - None => return Ok(vec![]), + let Some(commit_oid) = self.objects.resolve_ref(&self.branch)? else { + return Ok(vec![]); }; let commit = self.objects.repo().find_commit(commit_oid)?; let tree_oid = commit.tree_id(); @@ -184,9 +181,8 @@ impl<'a> BranchStore<'a> { /// Walk commits on the branch, newest first. pub fn log(&self, limit: usize) -> Result> { - let commit_oid = match self.objects.resolve_ref(&self.branch)? { - Some(oid) => oid, - None => return Ok(vec![]), + let Some(commit_oid) = self.objects.resolve_ref(&self.branch)? else { + return Ok(vec![]); }; let mut revwalk = self.objects.repo().revwalk()?; revwalk.set_sorting(git2::Sort::TIME | git2::Sort::TOPOLOGICAL)?; diff --git a/lib/crates/fabro-git-storage/src/gitobj.rs b/lib/crates/fabro-git-storage/src/gitobj.rs index b5d36b6c0..e91cf0350 100644 --- a/lib/crates/fabro-git-storage/src/gitobj.rs +++ b/lib/crates/fabro-git-storage/src/gitobj.rs @@ -16,17 +16,17 @@ pub enum FileMode { impl FileMode { fn as_i32(self) -> i32 { match self { - FileMode::Blob => 0o100644, - FileMode::BlobExecutable => 0o100755, - FileMode::Tree => 0o040000, + Self::Blob => 0o100644, + Self::BlobExecutable => 0o100755, + Self::Tree => 0o040000, } } fn from_i32(mode: i32) -> Self { match mode { - 0o100755 => FileMode::BlobExecutable, - 0o040000 => FileMode::Tree, - _ => FileMode::Blob, + 0o100755 => Self::BlobExecutable, + 0o040000 => Self::Tree, + _ => Self::Blob, } } } @@ -62,7 +62,7 @@ impl TreeEntries { self.0.get(path) } - pub fn merge(&mut self, other: &TreeEntries) { + pub fn merge(&mut self, other: &Self) { for (path, entry) in &other.0 { self.0.insert(path.clone(), entry.clone()); } @@ -224,7 +224,7 @@ fn read_tree_recursive( prefix: &str, entries: &mut TreeEntries, ) -> Result<()> { - for entry in tree.iter() { + for entry in tree { let name = entry.name().unwrap_or(""); let path = if prefix.is_empty() { name.to_string() @@ -246,7 +246,7 @@ fn read_tree_recursive( /// Intermediate structure for building nested git trees from flat paths. struct DirNode { files: BTreeMap, - dirs: BTreeMap, + dirs: BTreeMap, } impl DirNode { diff --git a/lib/crates/fabro-git-storage/src/snapshot.rs b/lib/crates/fabro-git-storage/src/snapshot.rs index b93fd9fb1..6bfe762b0 100644 --- a/lib/crates/fabro-git-storage/src/snapshot.rs +++ b/lib/crates/fabro-git-storage/src/snapshot.rs @@ -141,9 +141,8 @@ impl<'a> SnapshotStore<'a> { /// Tip commit of a snapshot branch. `None` if branch doesn't exist. pub fn latest(&self, branch: &str) -> Result> { - let commit_oid = match self.objects.resolve_ref(branch)? { - Some(oid) => oid, - None => return Ok(None), + let Some(commit_oid) = self.objects.resolve_ref(branch)? else { + return Ok(None); }; let commit = self.objects.repo().find_commit(commit_oid)?; let tree_oid = commit.tree_id(); @@ -173,9 +172,8 @@ impl<'a> SnapshotStore<'a> { /// Walk commits on a snapshot branch, newest first. pub fn list_commits(&self, branch: &str, limit: usize) -> Result> { - let commit_oid = match self.objects.resolve_ref(branch)? { - Some(oid) => oid, - None => return Ok(vec![]), + let Some(commit_oid) = self.objects.resolve_ref(branch)? else { + return Ok(vec![]); }; let mut revwalk = self.objects.repo().revwalk()?; revwalk.set_sorting(git2::Sort::TIME | git2::Sort::TOPOLOGICAL)?; @@ -245,7 +243,7 @@ impl<'a> SnapshotStore<'a> { let walker = walkdir::WalkDir::new(&disk_dir.disk_path) .follow_links(false) .into_iter() - .filter_map(|e| e.ok()); + .filter_map(std::result::Result::ok); for entry in walker { // Skip symlinks diff --git a/lib/crates/fabro-git-storage/src/trailerlink.rs b/lib/crates/fabro-git-storage/src/trailerlink.rs index a7faed97f..66c46e3b1 100644 --- a/lib/crates/fabro-git-storage/src/trailerlink.rs +++ b/lib/crates/fabro-git-storage/src/trailerlink.rs @@ -1,3 +1,5 @@ +use std::fmt::Write; + /// A git commit message trailer (key-value pair). pub struct Trailer<'a> { pub key: &'a str, @@ -51,7 +53,7 @@ pub fn format_message(subject: &str, body: &str, trailers: &[Trailer<'_>]) -> St if !trailers.is_empty() { msg.push_str("\n\n"); for trailer in trailers { - msg.push_str(&format!("{}: {}\n", trailer.key, trailer.value)); + let _ = writeln!(msg, "{}: {}", trailer.key, trailer.value); } } diff --git a/lib/crates/fabro-graphviz/src/condition.rs b/lib/crates/fabro-graphviz/src/condition.rs index f44a132d9..ad6de35bb 100644 --- a/lib/crates/fabro-graphviz/src/condition.rs +++ b/lib/crates/fabro-graphviz/src/condition.rs @@ -21,9 +21,9 @@ use crate::error::GraphvizError; #[derive(Debug, Clone, PartialEq)] pub enum ConditionExpr { Clause(Clause), - Not(Box), - And(Vec), - Or(Vec), + Not(Box), + And(Vec), + Or(Vec), } #[derive(Debug, Clone, PartialEq)] diff --git a/lib/crates/fabro-hooks/src/executor.rs b/lib/crates/fabro-hooks/src/executor.rs index 3bee8130a..66e52a7a6 100644 --- a/lib/crates/fabro-hooks/src/executor.rs +++ b/lib/crates/fabro-hooks/src/executor.rs @@ -219,8 +219,8 @@ impl HookExecutorImpl { } /// Resolve a model alias (e.g. "haiku") to a concrete model ID. - fn resolve_model(model: &Option) -> String { - let model_id = model.as_deref().unwrap_or("haiku"); + fn resolve_model(model: Option<&String>) -> String { + let model_id = model.map(String::as_str).unwrap_or("haiku"); let model_info = fabro_model::Catalog::builtin().get(model_id); model_info.map_or(model_id, |m| m.id.as_str()).to_string() } @@ -241,19 +241,18 @@ impl HookExecutorImpl { F: FnOnce() -> Fut, Fut: std::future::Future, { - match tokio_timeout(timeout, f()).await { - Ok(decision) => decision, - Err(_) => { - tracing::warn!("{hook_kind} hook timed out, proceeding"); - HookDecision::Proceed - } + if let Ok(decision) = tokio_timeout(timeout, f()).await { + decision + } else { + tracing::warn!("{hook_kind} hook timed out, proceeding"); + HookDecision::Proceed } } /// Execute a prompt hook: single-turn LLM call returning ok/block. async fn execute_prompt( prompt: &str, - model: &Option, + model: Option<&String>, context: &HookContext, timeout: std::time::Duration, ) -> HookDecision { @@ -267,21 +266,18 @@ impl HookExecutorImpl { .max_tokens(1024); match generate_object(params, HOOK_RESPONSE_SCHEMA.clone()).await { - Ok(result) => match result.output { - Some(obj) => match serde_json::from_value::(obj) { - Ok(resp) if resp.ok => HookDecision::Proceed, - Ok(resp) => HookDecision::Block { - reason: resp.reason, - }, - Err(e) => { - tracing::warn!(error = %e, "prompt hook response deserialize failed, proceeding"); - HookDecision::Proceed - } + Ok(result) => if let Some(obj) = result.output { match serde_json::from_value::(obj) { + Ok(resp) if resp.ok => HookDecision::Proceed, + Ok(resp) => HookDecision::Block { + reason: resp.reason, }, - None => { - tracing::warn!("prompt hook returned no structured output, proceeding"); + Err(e) => { + tracing::warn!(error = %e, "prompt hook response deserialize failed, proceeding"); HookDecision::Proceed } + } } else { + tracing::warn!("prompt hook returned no structured output, proceeding"); + HookDecision::Proceed }, Err(e) => { tracing::warn!(error = %e, "prompt hook LLM call failed, proceeding"); @@ -299,7 +295,7 @@ impl HookExecutorImpl { /// a normal agent session. async fn execute_agent( prompt: &str, - model: &Option, + model: Option<&String>, max_tool_rounds: Option, context: &HookContext, sandbox: Arc, @@ -396,7 +392,7 @@ impl HookExecutorImpl { } /// Build a reqwest client for the given TLS mode. - fn build_http_client(tls: &TlsMode) -> reqwest::Client { + fn build_http_client(tls: TlsMode) -> reqwest::Client { let accept_invalid = matches!(tls, TlsMode::NoVerify | TlsMode::Off); reqwest::Client::builder() .danger_accept_invalid_certs(accept_invalid) @@ -410,7 +406,7 @@ impl HookExecutorImpl { async fn execute_http( client: &reqwest::Client, url: &str, - headers: &Option>, + headers: Option<&HashMap>, allowed_env_vars: &[String], tls: &TlsMode, context: &HookContext, @@ -489,13 +485,13 @@ struct HttpClientCache { impl HttpClientCache { fn new() -> Self { Self { - verify: HookExecutorImpl::build_http_client(&TlsMode::Verify), - no_verify: HookExecutorImpl::build_http_client(&TlsMode::NoVerify), - off: HookExecutorImpl::build_http_client(&TlsMode::Off), + verify: HookExecutorImpl::build_http_client(TlsMode::Verify), + no_verify: HookExecutorImpl::build_http_client(TlsMode::NoVerify), + off: HookExecutorImpl::build_http_client(TlsMode::Off), } } - fn get(&self, tls: &TlsMode) -> &reqwest::Client { + fn get(&self, tls: TlsMode) -> &reqwest::Client { match tls { TlsMode::Verify => &self.verify, TlsMode::NoVerify => &self.no_verify, @@ -545,9 +541,9 @@ impl HookExecutor for HookExecutorImpl { ) => { let clients = HTTP_CLIENTS.get_or_init(HttpClientCache::new); Self::execute_http( - clients.get(tls), + clients.get(*tls), url, - headers, + headers.as_ref(), allowed_env_vars, tls, context, @@ -565,7 +561,7 @@ impl HookExecutor for HookExecutorImpl { ref prompt, ref model, }), - ) => Self::execute_prompt(prompt, model, context, definition.timeout()).await, + ) => Self::execute_prompt(prompt, model.as_ref(), context, definition.timeout()).await, Some( Cow::Borrowed(HookType::Agent { ref prompt, @@ -580,7 +576,7 @@ impl HookExecutor for HookExecutorImpl { ) => { Self::execute_agent( prompt, - model, + model.as_ref(), *max_tool_rounds, context, sandbox, @@ -619,7 +615,7 @@ mod tests { } fn test_http_client() -> reqwest::Client { - HookExecutorImpl::build_http_client(&TlsMode::Off) + HookExecutorImpl::build_http_client(TlsMode::Off) } fn make_definition(command: &str) -> HookDefinition { @@ -925,7 +921,7 @@ mod tests { let decision = HookExecutorImpl::execute_http( &client, &format!("{}/hook", server.url()), - &None, + None, &[], &TlsMode::Off, &make_context(), @@ -957,7 +953,7 @@ mod tests { let decision = HookExecutorImpl::execute_http( &client, &format!("{}/hook", server.url()), - &None, + None, &[], &TlsMode::Off, &make_context(), @@ -984,7 +980,7 @@ mod tests { let decision = HookExecutorImpl::execute_http( &client, &format!("{}/hook", server.url()), - &None, + None, &[], &TlsMode::Off, &make_context(), @@ -1003,7 +999,7 @@ mod tests { let decision = HookExecutorImpl::execute_http( &client, "http://127.0.0.1:1", - &None, + None, &[], &TlsMode::Off, &make_context(), @@ -1037,7 +1033,7 @@ mod tests { let decision = HookExecutorImpl::execute_http( &client, &format!("{}/hook", server.url()), - &Some(headers), + Some(&headers), &["FABRO_TEST_TOKEN".to_string()], &TlsMode::Off, &make_context(), @@ -1058,7 +1054,7 @@ mod tests { let decision = HookExecutorImpl::execute_http( &client, "http://example.com/hook", - &None, + None, &[], &TlsMode::Verify, &make_context(), @@ -1076,7 +1072,7 @@ mod tests { let decision = HookExecutorImpl::execute_http( &client, "http://example.com/hook", - &None, + None, &[], &TlsMode::NoVerify, &make_context(), @@ -1102,7 +1098,7 @@ mod tests { let decision = HookExecutorImpl::execute_http( &client, &format!("{}/hook", server.url()), - &None, + None, &[], &TlsMode::Off, &make_context(), diff --git a/lib/crates/fabro-interview/src/console.rs b/lib/crates/fabro-interview/src/console.rs index f202108c2..866b1732b 100644 --- a/lib/crates/fabro-interview/src/console.rs +++ b/lib/crates/fabro-interview/src/console.rs @@ -53,6 +53,7 @@ fn find_matching_option(response: &str, options: &[QuestionOption]) -> Option PromptRead { // Print the prompt to stderr so it doesn't interfere with piped stdout eprint!("{prompt}"); @@ -211,6 +212,7 @@ fn ask_freeform_interactive(question: &Question) -> Answer { #[async_trait] impl Interviewer for ConsoleInterviewer { + #[allow(clippy::print_stderr)] async fn ask(&self, question: Question) -> Answer { // If stdin is a TTY, use dialoguer for interactive arrow-key navigation. // Otherwise, fall back to the line-based reader for piped input. @@ -258,6 +260,7 @@ impl Interviewer for ConsoleInterviewer { } } + #[allow(clippy::print_stderr)] async fn inform(&self, message: &str, stage: &str) { let s = self.styles; eprintln!("{} {message}", s.dim.apply_to(format!("[{stage}]"))); diff --git a/lib/crates/fabro-interview/src/file.rs b/lib/crates/fabro-interview/src/file.rs index f22dad9a3..698c7d06a 100644 --- a/lib/crates/fabro-interview/src/file.rs +++ b/lib/crates/fabro-interview/src/file.rs @@ -20,6 +20,7 @@ use std::path::Path; /// The engine process writes `interview_request.json` and polls for /// `interview_response.json`. The attach process watches for the request /// file, prompts the user, and writes the response file. +#[allow(clippy::struct_field_names)] pub struct FileInterviewer { request_path: PathBuf, response_path: PathBuf, @@ -125,12 +126,11 @@ impl Interviewer for FileInterviewer { if let Some(secs) = timeout_secs { let duration = std::time::Duration::from_secs_f64(secs); - match time::timeout(duration, poll).await { - Ok(answer) => answer, - Err(_) => { - self.cleanup_ipc_files().await; - default_answer.unwrap_or_else(Answer::timeout) - } + if let Ok(answer) = time::timeout(duration, poll).await { + answer + } else { + self.cleanup_ipc_files().await; + default_answer.unwrap_or_else(Answer::timeout) } } else { poll.await diff --git a/lib/crates/fabro-llm/src/cli.rs b/lib/crates/fabro-llm/src/cli.rs index e923c2577..41ae837d9 100644 --- a/lib/crates/fabro-llm/src/cli.rs +++ b/lib/crates/fabro-llm/src/cli.rs @@ -159,6 +159,7 @@ fn models_title() -> Vec { ] } +#[allow(clippy::print_stdout)] fn print_models_table(models: &[Model], s: &Styles) { let use_color = s.use_color; let rows: Vec> = models.iter().map(|m| model_row(m, use_color)).collect(); @@ -249,6 +250,7 @@ fn apply_options( Ok(params) } +#[allow(clippy::print_stderr)] fn print_usage(usage: &Usage) { eprintln!( "Tokens: {} input, {} output, {} total", @@ -267,6 +269,7 @@ pub struct ChatArgs { pub system: Option, } +#[allow(clippy::print_stdout, clippy::print_stderr)] pub async fn run_chat(args: ChatArgs) -> Result<()> { let (model_id, provider) = resolve_model(args.model); eprintln!("Using model: {model_id}"); @@ -329,6 +332,7 @@ pub async fn run_chat(args: ChatArgs) -> Result<()> { Ok(()) } +#[allow(clippy::print_stdout, clippy::print_stderr)] pub async fn run_prompt(args: PromptArgs) -> Result<()> { let stdin_prompt = read_stdin_prompt(); let prompt_text = resolve_prompt(args.prompt, stdin_prompt)?; @@ -394,6 +398,7 @@ pub async fn run_prompt(args: PromptArgs) -> Result<()> { Ok(()) } +#[allow(clippy::print_stdout, clippy::print_stderr)] pub async fn run_prompt_via_server(args: PromptArgs, server: &ServerConnection) -> Result<()> { let stdin_prompt = read_stdin_prompt(); let prompt_text = resolve_prompt(args.prompt, stdin_prompt)?; @@ -558,6 +563,7 @@ pub async fn run_prompt_via_server(args: PromptArgs, server: &ServerConnection) } /// Extract and print text from a CompletionMessage JSON value. +#[allow(clippy::print_stdout)] fn print_message_text(message: &serde_json::Value) { if let Some(content) = message["content"].as_array() { for part in content { @@ -607,6 +613,7 @@ async fn parse_sse_frames( } /// Stream session SSE events, printing text deltas to stdout in real-time. +#[allow(clippy::print_stdout)] async fn stream_session_text(response: reqwest::Response) -> Result<()> { parse_sse_frames(response, |event_type, data| { match event_type { @@ -638,6 +645,7 @@ async fn stream_session_text(response: reqwest::Response) -> Result<()> { .await } +#[allow(clippy::print_stderr)] pub async fn run_chat_via_server(args: ChatArgs, server: &ServerConnection) -> Result<()> { let is_tty = io::stdin().is_terminal(); let mut session_id: Option = None; @@ -848,8 +856,14 @@ fn build_deep_test_params(info: &Model) -> Option { "required": ["a", "b"] }), |args, _ctx| async move { - let a = args.get("a").and_then(|v| v.as_i64()).unwrap_or(0); - let b = args.get("b").and_then(|v| v.as_i64()).unwrap_or(0); + let a = args + .get("a") + .and_then(serde_json::Value::as_i64) + .unwrap_or(0); + let b = args + .get("b") + .and_then(serde_json::Value::as_i64) + .unwrap_or(0); Ok(serde_json::json!(a + b)) }, ); @@ -909,6 +923,7 @@ fn validate_deep_result(result: &GenerateResult, info: &Model) -> (cli_table::Co (Color::Green, "deep: ok".to_string()) } +#[allow(clippy::print_stdout, clippy::print_stderr)] async fn test_models_via_server( server: &ServerConnection, provider: Option<&str>, @@ -996,14 +1011,11 @@ pub async fn run_models( match command { ModelsCommand::List { provider, query } => { - let mut models = match &server { - Some(s) => { - fetch_models_from_server(&s.client, &s.base_url, provider.as_deref()).await? - } - None => { - let p = provider.as_deref().and_then(|s| s.parse::().ok()); - Catalog::builtin().list(p).into_iter().cloned().collect() - } + let mut models = if let Some(s) = &server { + fetch_models_from_server(&s.client, &s.base_url, provider.as_deref()).await? + } else { + let p = provider.as_deref().and_then(|s| s.parse::().ok()); + Catalog::builtin().list(p).into_iter().cloned().collect() }; if let Some(q) = &query { @@ -1066,6 +1078,7 @@ async fn test_one_model(info: &Model, deep: bool) -> (Color, String) { } } +#[allow(clippy::print_stdout)] async fn test_models( provider: Option<&str>, model: Option<&str>, diff --git a/lib/crates/fabro-llm/src/generate.rs b/lib/crates/fabro-llm/src/generate.rs index bf7945af0..19021e09b 100644 --- a/lib/crates/fabro-llm/src/generate.rs +++ b/lib/crates/fabro-llm/src/generate.rs @@ -151,7 +151,7 @@ pub async fn generate(params: GenerateParams) -> Result, candidates_token_count: Option, @@ -881,7 +882,7 @@ impl SseStreamState { #[async_trait::async_trait] impl ProviderAdapter for Adapter { - fn name(&self) -> &str { + fn name(&self) -> &'static str { "gemini" } diff --git a/lib/crates/fabro-llm/src/providers/openai.rs b/lib/crates/fabro-llm/src/providers/openai.rs index da3f1e741..056aca70a 100644 --- a/lib/crates/fabro-llm/src/providers/openai.rs +++ b/lib/crates/fabro-llm/src/providers/openai.rs @@ -935,7 +935,7 @@ fn handle_response_completed( #[async_trait::async_trait] impl ProviderAdapter for Adapter { - fn name(&self) -> &str { + fn name(&self) -> &'static str { "openai" } diff --git a/lib/crates/fabro-llm/src/providers/openai_compatible.rs b/lib/crates/fabro-llm/src/providers/openai_compatible.rs index 80a1dbea1..dc2a4d2ab 100644 --- a/lib/crates/fabro-llm/src/providers/openai_compatible.rs +++ b/lib/crates/fabro-llm/src/providers/openai_compatible.rs @@ -153,6 +153,7 @@ struct ApiFunction { } #[derive(serde::Deserialize)] +#[allow(clippy::struct_field_names)] struct ApiUsage { prompt_tokens: i64, completion_tokens: i64, @@ -715,12 +716,11 @@ impl StreamState { if self.done { return Ok(None); } - match self.line_reader.read_next_chunk("\n").await? { - Some(line) => Ok(Some(line)), - None => { - self.done = true; - Ok(None) - } + if let Some(line) = self.line_reader.read_next_chunk("\n").await? { + Ok(Some(line)) + } else { + self.done = true; + Ok(None) } } diff --git a/lib/crates/fabro-llm/src/types.rs b/lib/crates/fabro-llm/src/types.rs index 0157476ee..5e57f4a76 100644 --- a/lib/crates/fabro-llm/src/types.rs +++ b/lib/crates/fabro-llm/src/types.rs @@ -244,7 +244,7 @@ impl ContentPart { /// Returns `true` if this is an opaque OpenAI item (reasoning or message) /// that should be round-tripped verbatim through the API. pub fn is_opaque_openai(&self) -> bool { - matches!(self, ContentPart::Other { kind, .. } if kind == Self::OPENAI_REASONING || kind == Self::OPENAI_MESSAGE) + matches!(self, Self::Other { kind, .. } if kind == Self::OPENAI_REASONING || kind == Self::OPENAI_MESSAGE) } } diff --git a/lib/crates/fabro-model/src/catalog.rs b/lib/crates/fabro-model/src/catalog.rs index 720c0a078..277018747 100644 --- a/lib/crates/fabro-model/src/catalog.rs +++ b/lib/crates/fabro-model/src/catalog.rs @@ -29,7 +29,7 @@ pub struct Catalog { impl Catalog { /// Returns a reference to the global built-in catalog (loaded once from catalog.json). #[must_use] - pub fn builtin() -> &'static Catalog { + pub fn builtin() -> &'static Self { &GLOBAL_CATALOG } @@ -134,14 +134,12 @@ impl Catalog { model: &str, fallbacks: &HashMap>, ) -> Vec { - let reference = match self.get(model) { - Some(info) => info, - None => return Vec::new(), + let Some(reference) = self.get(model) else { + return Vec::new(); }; - let fallback_providers = match fallbacks.get(primary.as_str()) { - Some(providers) => providers, - None => return Vec::new(), + let Some(fallback_providers) = fallbacks.get(primary.as_str()) else { + return Vec::new(); }; fallback_providers diff --git a/lib/crates/fabro-model/src/provider.rs b/lib/crates/fabro-model/src/provider.rs index 5ab0c93ff..018d72762 100644 --- a/lib/crates/fabro-model/src/provider.rs +++ b/lib/crates/fabro-model/src/provider.rs @@ -24,14 +24,14 @@ pub enum Provider { impl Provider { /// All known provider variants, for use in guardrail tests and iteration. - pub const ALL: &[Provider] = &[ - Provider::Anthropic, - Provider::OpenAi, - Provider::Gemini, - Provider::Kimi, - Provider::Zai, - Provider::Minimax, - Provider::Inception, + pub const ALL: &[Self] = &[ + Self::Anthropic, + Self::OpenAi, + Self::Gemini, + Self::Kimi, + Self::Zai, + Self::Minimax, + Self::Inception, ]; /// Environment variable names that can provide the API key for this provider. @@ -75,7 +75,7 @@ impl Provider { .iter() .copied() .find(|&p| is_configured(p)) - .unwrap_or(Provider::Anthropic) + .unwrap_or(Self::Anthropic) } /// Stable lowercase string representation used in `Request.provider`, diff --git a/lib/crates/fabro-openai-oauth/src/lib.rs b/lib/crates/fabro-openai-oauth/src/lib.rs index b7f867a8d..ad93a3ce5 100644 --- a/lib/crates/fabro-openai-oauth/src/lib.rs +++ b/lib/crates/fabro-openai-oauth/src/lib.rs @@ -1,3 +1,5 @@ +use std::fmt::Write as _; + use axum::extract::Query; use axum::http::StatusCode; use axum::response::Html; @@ -54,7 +56,7 @@ fn percent_encode_param(s: &str) -> String { out.push(b as char); } _ => { - out.push_str(&format!("%{b:02X}")); + let _ = write!(out, "%{b:02X}"); } } } @@ -438,20 +440,17 @@ pub async fn start_callback_server( ); } - let code = match params.code { - Some(c) => c, - None => { - if let Some(tx) = code_tx.lock().unwrap().take() { - let _ = tx.send(Err("No authorization code received".to_string())); - } - if let Some(tx) = shutdown_tx.lock().unwrap().take() { - let _ = tx.send(()); - } - return ( - StatusCode::BAD_REQUEST, - Html("No authorization code received".to_string()), - ); + let Some(code) = params.code else { + if let Some(tx) = code_tx.lock().unwrap().take() { + let _ = tx.send(Err("No authorization code received".to_string())); } + if let Some(tx) = shutdown_tx.lock().unwrap().take() { + let _ = tx.send(()); + } + return ( + StatusCode::BAD_REQUEST, + Html("No authorization code received".to_string()), + ); }; if let Some(tx) = code_tx.lock().unwrap().take() { diff --git a/lib/crates/fabro-retro/src/retro.rs b/lib/crates/fabro-retro/src/retro.rs index f8a6d40a3..7bf228cce 100644 --- a/lib/crates/fabro-retro/src/retro.rs +++ b/lib/crates/fabro-retro/src/retro.rs @@ -56,7 +56,10 @@ pub fn extract_stage_durations(run_dir: &Path) -> HashMap { let Some(name) = envelope.get("node_id").and_then(|v| v.as_str()) else { continue; }; - let Some(duration_ms) = envelope.get("duration_ms").and_then(|v| v.as_u64()) else { + let Some(duration_ms) = envelope + .get("duration_ms") + .and_then(serde_json::Value::as_u64) + else { continue; }; durations.insert(name.to_string(), duration_ms); diff --git a/lib/crates/fabro-retro/src/retro_agent.rs b/lib/crates/fabro-retro/src/retro_agent.rs index 623a1c33f..7b06dc196 100644 --- a/lib/crates/fabro-retro/src/retro_agent.rs +++ b/lib/crates/fabro-retro/src/retro_agent.rs @@ -16,7 +16,7 @@ use tokio::task::JoinHandle; use crate::retro::{RetroNarrative, SmoothnessRating}; -const RETRO_SYSTEM_PROMPT: &str = r#"You are a workflow run retrospective analyst. Your job is to analyze a completed workflow run and generate a structured retrospective. +const RETRO_SYSTEM_PROMPT: &str = r"You are a workflow run retrospective analyst. Your job is to analyze a completed workflow run and generate a structured retrospective. You have access to the run's data files: - `progress.jsonl` — the full event stream (stage starts/completions, agent tool calls, errors, retries) @@ -54,7 +54,7 @@ Consider the full context: not just stage pass/fail, but the quality of the jour - **friction_points**: Where did things get stuck? What caused slowdowns? - **open_items**: What follow-up work, tech debt, or gaps were identified? -Be specific and concise. Reference actual stage names, file paths, and error messages where relevant."#; +Be specific and concise. Reference actual stage names, file paths, and error messages where relevant."; const SUBMIT_RETRO_SCHEMA: &str = r#"{ "type": "object", @@ -148,7 +148,7 @@ pub async fn run_retro_agent( Box::pin(async move { let narrative: RetroNarrative = serde_json::from_value(args) .map_err(|e| format!("Invalid retro submission: {e}"))?; - *captured.lock().unwrap_or_else(|e| e.into_inner()) = Some(narrative); + *captured.lock().unwrap_or_else(std::sync::PoisonError::into_inner) = Some(narrative); Ok("Retrospective submitted successfully.".to_string()) }) }), @@ -217,7 +217,10 @@ pub async fn run_retro_agent( // Extract result / determine outcome let (outcome, failure_reason, narrative_result) = match process_result { Ok(()) => { - let maybe_narrative = captured.lock().unwrap_or_else(|e| e.into_inner()).take(); + let maybe_narrative = captured + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .take(); match maybe_narrative { Some(narrative) => ("success", None, Ok(narrative)), None => ( diff --git a/lib/crates/fabro-sandbox/src/daytona/mod.rs b/lib/crates/fabro-sandbox/src/daytona/mod.rs index bc6430349..a02034830 100644 --- a/lib/crates/fabro-sandbox/src/daytona/mod.rs +++ b/lib/crates/fabro-sandbox/src/daytona/mod.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::fmt::Write; use std::path::Path; use std::time::Instant; @@ -135,7 +136,7 @@ impl DaytonaSandbox { ) -> Result { let sandbox = self.sandbox()?; sandbox - .get_signed_preview_url(port as i32, expires_in_seconds) + .get_signed_preview_url(i32::from(port), expires_in_seconds) .await .map_err(|e| format!("Failed to get signed preview URL for port {port}: {e}")) } @@ -147,6 +148,7 @@ impl DaytonaSandbox { } } + #[allow(clippy::unused_self)] fn resolve_path(&self, path: &str) -> String { resolve_path(path, WORKING_DIRECTORY) } @@ -684,7 +686,7 @@ impl Sandbox for DaytonaSandbox { WORKING_DIRECTORY } - fn platform(&self) -> &str { + fn platform(&self) -> &'static str { "linux" } @@ -758,13 +760,11 @@ impl Sandbox for DaytonaSandbox { } async fn refresh_push_credentials(&self) -> Result<(), String> { - let origin_url = match self.origin_url.get() { - Some(url) => url, - None => return Ok(()), // no authenticated origin — nothing to refresh + let Some(origin_url) = self.origin_url.get() else { + return Ok(()); // no authenticated origin — nothing to refresh }; - let creds = match &self.github_app { - Some(c) => c, - None => return Ok(()), + let Some(creds) = &self.github_app else { + return Ok(()); }; let auth_url = fabro_github::resolve_authenticated_url(creds, origin_url) @@ -1049,16 +1049,17 @@ impl Sandbox for DaytonaSandbox { cmd.push_str(" -i"); } if let Some(ref glob_filter) = options.glob_filter { - cmd.push_str(&format!(" --glob {}", shell_quote(glob_filter))); + let _ = write!(cmd, " --glob {}", shell_quote(glob_filter)); } if let Some(max) = options.max_results { - cmd.push_str(&format!(" --max-count {max}")); + let _ = write!(cmd, " --max-count {max}"); } - cmd.push_str(&format!( + let _ = write!( + cmd, " -- {} {}", shell_quote(pattern), shell_quote(&resolved) - )); + ); cmd } else { let mut cmd = "grep -rn".to_string(); @@ -1066,16 +1067,17 @@ impl Sandbox for DaytonaSandbox { cmd.push_str(" -i"); } if let Some(ref glob_filter) = options.glob_filter { - cmd.push_str(&format!(" --include {}", shell_quote(glob_filter))); + let _ = write!(cmd, " --include {}", shell_quote(glob_filter)); } if let Some(max) = options.max_results { - cmd.push_str(&format!(" -m {max}")); + let _ = write!(cmd, " -m {max}"); } - cmd.push_str(&format!( + let _ = write!( + cmd, " -- {} {}", shell_quote(pattern), shell_quote(&resolved) - )); + ); cmd }; diff --git a/lib/crates/fabro-sandbox/src/docker.rs b/lib/crates/fabro-sandbox/src/docker.rs index 4276129b7..a4add387b 100644 --- a/lib/crates/fabro-sandbox/src/docker.rs +++ b/lib/crates/fabro-sandbox/src/docker.rs @@ -444,16 +444,15 @@ impl Sandbox for DockerSandbox { }); let start = Instant::now(); - let container_id = match self.container_id.get() { - Some(id) => id.clone(), - None => { - let duration_ms = u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX); - self.emit(SandboxEvent::CleanupCompleted { - provider: "docker".into(), - duration_ms, - }); - return Ok(()); - } + let container_id = if let Some(id) = self.container_id.get() { + id.clone() + } else { + let duration_ms = u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX); + self.emit(SandboxEvent::CleanupCompleted { + provider: "docker".into(), + duration_ms, + }); + return Ok(()); }; // Stop with 5-second grace period; ignore "not running" errors diff --git a/lib/crates/fabro-sandbox/src/local.rs b/lib/crates/fabro-sandbox/src/local.rs index 4d72bc206..b4d6a2dc9 100644 --- a/lib/crates/fabro-sandbox/src/local.rs +++ b/lib/crates/fabro-sandbox/src/local.rs @@ -198,6 +198,9 @@ impl Sandbox for LocalSandbox { .stderr(std::process::Stdio::piped()); #[cfg(unix)] + // SAFETY: setpgid(0, 0) is safe to call in a pre_exec hook — it places + // the child into its own process group so we can signal the whole group. + #[allow(unsafe_code)] unsafe { cmd.pre_exec(|| { libc::setpgid(0, 0); @@ -479,9 +482,12 @@ impl Sandbox for LocalSandbox { } /// Send SIGTERM to the process group, wait 2s for graceful shutdown, then SIGKILL. +#[allow(unsafe_code)] async fn sigterm_then_kill(child: &mut Child) { #[cfg(unix)] if let Some(pid) = child.id() { + // SAFETY: kill with a negative pid signals the entire process group. + // The pid is valid because we just obtained it from child.id(). unsafe { libc::kill(-(pid as i32), libc::SIGTERM); } diff --git a/lib/crates/fabro-sandbox/src/ssh/mod.rs b/lib/crates/fabro-sandbox/src/ssh/mod.rs index 7ab00e694..b9b62c34a 100644 --- a/lib/crates/fabro-sandbox/src/ssh/mod.rs +++ b/lib/crates/fabro-sandbox/src/ssh/mod.rs @@ -1,6 +1,7 @@ mod openssh_runner; use std::collections::HashMap; +use std::fmt::Write; use std::path::Path; use std::time::Instant; @@ -90,7 +91,7 @@ impl SshSandbox { fn ssh(&self) -> Result<&dyn SshRunner, String> { self.ssh .get() - .map(|b| b.as_ref()) + .map(std::convert::AsRef::as_ref) .ok_or_else(|| "SSH sandbox not initialized -- call initialize() first".to_string()) } @@ -211,11 +212,7 @@ impl Sandbox for SshSandbox { if let Some(vars) = env_vars { for (key, value) in vars { - script.push_str(&format!( - "export {}={}\n", - shell_quote(key), - shell_quote(value) - )); + let _ = writeln!(script, "export {}={}", shell_quote(key), shell_quote(value)); } } @@ -223,7 +220,7 @@ impl Sandbox for SshSandbox { Some(dir) => self.resolve_path(dir), None => self.config.working_directory.clone(), }; - script.push_str(&format!("cd {} && {command}", shell_quote(&dir))); + let _ = write!(script, "cd {} && {command}", shell_quote(&dir)); let full_cmd = ssh_common::wrap_bash_command(&script); @@ -403,16 +400,17 @@ impl Sandbox for SshSandbox { cmd.push_str(" -i"); } if let Some(ref glob_filter) = options.glob_filter { - cmd.push_str(&format!(" --glob {}", shell_quote(glob_filter))); + let _ = write!(cmd, " --glob {}", shell_quote(glob_filter)); } if let Some(max) = options.max_results { - cmd.push_str(&format!(" --max-count {max}")); + let _ = write!(cmd, " --max-count {max}"); } - cmd.push_str(&format!( + let _ = write!( + cmd, " -- {} {}", shell_quote(pattern), shell_quote(&resolved) - )); + ); cmd } else { let mut cmd = "grep -rn".to_string(); @@ -420,16 +418,17 @@ impl Sandbox for SshSandbox { cmd.push_str(" -i"); } if let Some(ref glob_filter) = options.glob_filter { - cmd.push_str(&format!(" --include {}", shell_quote(glob_filter))); + let _ = write!(cmd, " --include {}", shell_quote(glob_filter)); } if let Some(max) = options.max_results { - cmd.push_str(&format!(" -m {max}")); + let _ = write!(cmd, " -m {max}"); } - cmd.push_str(&format!( + let _ = write!( + cmd, " -- {} {}", shell_quote(pattern), shell_quote(&resolved) - )); + ); cmd }; @@ -521,7 +520,7 @@ impl Sandbox for SshSandbox { &self.config.working_directory } - fn platform(&self) -> &str { + fn platform(&self) -> &'static str { "linux" } @@ -537,13 +536,11 @@ impl Sandbox for SshSandbox { } async fn refresh_push_credentials(&self) -> Result<(), String> { - let origin_url = match self.origin_url() { - Some(url) => url, - None => return Ok(()), + let Some(origin_url) = self.origin_url() else { + return Ok(()); }; - let creds = match &self.github_app { - Some(c) => c, - None => return Ok(()), + let Some(creds) = &self.github_app else { + return Ok(()); }; let auth_url = fabro_github::resolve_authenticated_url(creds, origin_url) diff --git a/lib/crates/fabro-slack/src/client.rs b/lib/crates/fabro-slack/src/client.rs index 32887c45d..6ed7b6d05 100644 --- a/lib/crates/fabro-slack/src/client.rs +++ b/lib/crates/fabro-slack/src/client.rs @@ -114,7 +114,7 @@ pub fn parse_wss_url(response: &Value) -> Result { check_ok(response)?; response["url"] .as_str() - .map(|s| s.to_string()) + .map(std::string::ToString::to_string) .ok_or_else(|| SlackApiError::Api("missing url in response".to_string())) } diff --git a/lib/crates/fabro-slack/src/connection.rs b/lib/crates/fabro-slack/src/connection.rs index fd67a31c7..beb022f6b 100644 --- a/lib/crates/fabro-slack/src/connection.rs +++ b/lib/crates/fabro-slack/src/connection.rs @@ -33,12 +33,11 @@ pub fn process_message( text: &str, thread_registry: &ThreadRegistry, ) -> (Option, ProcessOutcome, DispatchAction) { - let envelope: SocketEnvelope = match serde_json::from_str(text) { - Ok(e) => e, - Err(_) => { - warn!("Failed to parse WebSocket message as envelope"); - return (None, ProcessOutcome::Continue, DispatchAction::Ignored); - } + let envelope: SocketEnvelope = if let Ok(e) = serde_json::from_str(text) { + e + } else { + warn!("Failed to parse WebSocket message as envelope"); + return (None, ProcessOutcome::Continue, DispatchAction::Ignored); }; let ack_json = envelope diff --git a/lib/crates/fabro-store/src/keys.rs b/lib/crates/fabro-store/src/keys.rs index 2fa94523a..48cd9f99c 100644 --- a/lib/crates/fabro-store/src/keys.rs +++ b/lib/crates/fabro-store/src/keys.rs @@ -1,121 +1,121 @@ use crate::NodeVisitRef; -pub const INIT_KEY: &str = "_init.json"; -pub const RUN_KEY: &str = "run.json"; -pub const START_KEY: &str = "start.json"; -pub const STATUS_KEY: &str = "status.json"; -pub const CHECKPOINT_KEY: &str = "checkpoint.json"; -pub const CONCLUSION_KEY: &str = "conclusion.json"; -pub const RETRO_KEY: &str = "retro.json"; -pub const GRAPH_KEY: &str = "graph.fabro"; -pub const SANDBOX_KEY: &str = "sandbox.json"; -pub const RETRO_PROMPT_KEY: &str = "retro/prompt.md"; -pub const RETRO_RESPONSE_KEY: &str = "retro/response.md"; -pub const EVENTS_PREFIX: &str = "events/"; -pub const CHECKPOINTS_PREFIX: &str = "checkpoints/"; -pub const ARTIFACT_VALUES_PREFIX: &str = "artifacts/values/"; -pub const ARTIFACT_NODES_PREFIX: &str = "artifacts/nodes/"; +pub(crate) const INIT_KEY: &str = "_init.json"; +pub(crate) const RUN_KEY: &str = "run.json"; +pub(crate) const START_KEY: &str = "start.json"; +pub(crate) const STATUS_KEY: &str = "status.json"; +pub(crate) const CHECKPOINT_KEY: &str = "checkpoint.json"; +pub(crate) const CONCLUSION_KEY: &str = "conclusion.json"; +pub(crate) const RETRO_KEY: &str = "retro.json"; +pub(crate) const GRAPH_KEY: &str = "graph.fabro"; +pub(crate) const SANDBOX_KEY: &str = "sandbox.json"; +pub(crate) const RETRO_PROMPT_KEY: &str = "retro/prompt.md"; +pub(crate) const RETRO_RESPONSE_KEY: &str = "retro/response.md"; +pub(crate) const EVENTS_PREFIX: &str = "events/"; +pub(crate) const CHECKPOINTS_PREFIX: &str = "checkpoints/"; +pub(crate) const ARTIFACT_VALUES_PREFIX: &str = "artifacts/values/"; +pub(crate) const ARTIFACT_NODES_PREFIX: &str = "artifacts/nodes/"; -pub fn init() -> &'static str { +pub(crate) fn init() -> &'static str { INIT_KEY } -pub fn run() -> &'static str { +pub(crate) fn run() -> &'static str { RUN_KEY } -pub fn start() -> &'static str { +pub(crate) fn start() -> &'static str { START_KEY } -pub fn status() -> &'static str { +pub(crate) fn status() -> &'static str { STATUS_KEY } -pub fn checkpoint() -> &'static str { +pub(crate) fn checkpoint() -> &'static str { CHECKPOINT_KEY } -pub fn conclusion() -> &'static str { +pub(crate) fn conclusion() -> &'static str { CONCLUSION_KEY } -pub fn retro() -> &'static str { +pub(crate) fn retro() -> &'static str { RETRO_KEY } -pub fn graph() -> &'static str { +pub(crate) fn graph() -> &'static str { GRAPH_KEY } -pub fn sandbox() -> &'static str { +pub(crate) fn sandbox() -> &'static str { SANDBOX_KEY } -pub fn node_visit_prefix(node: &NodeVisitRef<'_>) -> String { +pub(crate) fn node_visit_prefix(node: &NodeVisitRef<'_>) -> String { format!("nodes/{}/visit-{}", node.node_id, node.visit) } -pub fn node_prompt(node: &NodeVisitRef<'_>) -> String { +pub(crate) fn node_prompt(node: &NodeVisitRef<'_>) -> String { format!("{}/prompt.md", node_visit_prefix(node)) } -pub fn node_response(node: &NodeVisitRef<'_>) -> String { +pub(crate) fn node_response(node: &NodeVisitRef<'_>) -> String { format!("{}/response.md", node_visit_prefix(node)) } -pub fn node_status(node: &NodeVisitRef<'_>) -> String { +pub(crate) fn node_status(node: &NodeVisitRef<'_>) -> String { format!("{}/status.json", node_visit_prefix(node)) } -pub fn node_stdout(node: &NodeVisitRef<'_>) -> String { +pub(crate) fn node_stdout(node: &NodeVisitRef<'_>) -> String { format!("{}/stdout.log", node_visit_prefix(node)) } -pub fn node_stderr(node: &NodeVisitRef<'_>) -> String { +pub(crate) fn node_stderr(node: &NodeVisitRef<'_>) -> String { format!("{}/stderr.log", node_visit_prefix(node)) } -pub fn retro_prompt() -> &'static str { +pub(crate) fn retro_prompt() -> &'static str { RETRO_PROMPT_KEY } -pub fn retro_response() -> &'static str { +pub(crate) fn retro_response() -> &'static str { RETRO_RESPONSE_KEY } -pub fn event_key(seq: u32, epoch_ms: i64) -> String { +pub(crate) fn event_key(seq: u32, epoch_ms: i64) -> String { format!("{EVENTS_PREFIX}{seq:06}-{epoch_ms}.json") } -pub fn checkpoint_history_key(seq: u32, epoch_ms: i64) -> String { +pub(crate) fn checkpoint_history_key(seq: u32, epoch_ms: i64) -> String { format!("{CHECKPOINTS_PREFIX}{seq:04}-{epoch_ms}.json") } -pub fn artifact_value(artifact_id: &str) -> String { +pub(crate) fn artifact_value(artifact_id: &str) -> String { format!("{ARTIFACT_VALUES_PREFIX}{artifact_id}.json") } -pub fn node_asset_prefix(node: &NodeVisitRef<'_>) -> String { +pub(crate) fn node_asset_prefix(node: &NodeVisitRef<'_>) -> String { format!( "{ARTIFACT_NODES_PREFIX}{}/visit-{}", node.node_id, node.visit ) } -pub fn node_asset(node: &NodeVisitRef<'_>, filename: &str) -> String { +pub(crate) fn node_asset(node: &NodeVisitRef<'_>, filename: &str) -> String { format!("{}/{filename}", node_asset_prefix(node)) } -pub fn parse_event_seq(key: &str) -> Option { +pub(crate) fn parse_event_seq(key: &str) -> Option { parse_seq(key, EVENTS_PREFIX) } -pub fn parse_checkpoint_seq(key: &str) -> Option { +pub(crate) fn parse_checkpoint_seq(key: &str) -> Option { parse_seq(key, CHECKPOINTS_PREFIX) } -pub fn parse_node_key(key: &str) -> Option<(String, u32, String)> { +pub(crate) fn parse_node_key(key: &str) -> Option<(String, u32, String)> { parse_visit_scoped_key(key, "nodes/") } diff --git a/lib/crates/fabro-store/src/memory.rs b/lib/crates/fabro-store/src/memory.rs index 81743ab00..9eaf8a2c4 100644 --- a/lib/crates/fabro-store/src/memory.rs +++ b/lib/crates/fabro-store/src/memory.rs @@ -108,7 +108,8 @@ impl InMemoryRunStore { self.data.lock().await.clone() } - async fn build_node_snapshot_from_data( + #[allow(clippy::unused_self)] + fn build_node_snapshot_from_data( &self, data: &BTreeMap>, node: &NodeVisitRef<'_>, @@ -156,7 +157,7 @@ impl InMemoryRunStore { Ok(checkpoints) } - async fn build_snapshot_from_data( + fn build_snapshot_from_data( &self, data: &BTreeMap>, ) -> Result> { @@ -177,7 +178,7 @@ impl InMemoryRunStore { node_id: &node_id, visit, }; - nodes.push(self.build_node_snapshot_from_data(data, &node).await?); + nodes.push(self.build_node_snapshot_from_data(data, &node)?); } Ok(Some(RunSnapshot { @@ -385,7 +386,7 @@ impl RunStore for Arc { async fn get_node(&self, node: &NodeVisitRef<'_>) -> Result { let data = self.snapshot_data().await; - self.build_node_snapshot_from_data(&data, node).await + self.build_node_snapshot_from_data(&data, node) } async fn list_node_visits(&self, node_id: &str) -> Result> { @@ -514,7 +515,7 @@ impl RunStore for Arc { async fn get_snapshot(&self) -> Result> { let data = self.snapshot_data().await; - self.build_snapshot_from_data(&data).await + self.build_snapshot_from_data(&data) } } diff --git a/lib/crates/fabro-store/src/slate/catalog.rs b/lib/crates/fabro-store/src/slate/catalog.rs index 8c2d5b2b3..99bfd0a92 100644 --- a/lib/crates/fabro-store/src/slate/catalog.rs +++ b/lib/crates/fabro-store/src/slate/catalog.rs @@ -68,7 +68,7 @@ pub(crate) async fn list_catalogs( Ok(records) } -pub async fn repair_catalog(store: Arc, base_prefix: &str) -> Result<()> { +pub(super) async fn repair_catalog(store: Arc, base_prefix: &str) -> Result<()> { let by_id_prefix = Path::from(format!("{base_prefix}by-id")); let by_start_prefix = Path::from(format!("{base_prefix}by-start")); diff --git a/lib/crates/fabro-store/src/slate/mod.rs b/lib/crates/fabro-store/src/slate/mod.rs index 0133db4b6..4ea776fbf 100644 --- a/lib/crates/fabro-store/src/slate/mod.rs +++ b/lib/crates/fabro-store/src/slate/mod.rs @@ -28,7 +28,7 @@ impl std::fmt::Debug for SlateStore { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SlateStore") .field("base_prefix", &self.base_prefix) - .finish() + .finish_non_exhaustive() } } @@ -68,12 +68,11 @@ impl SlateStore { async fn get_active_run(&self, run_id: &str) -> Option { let mut active_runs = self.active_runs.lock().await; let weak = active_runs.get(run_id).cloned()?; - match weak.upgrade() { - Some(inner) => Some(SlateRunStore::from_inner(inner)), - None => { - active_runs.remove(run_id); - None - } + if let Some(inner) = weak.upgrade() { + Some(SlateRunStore::from_inner(inner)) + } else { + active_runs.remove(run_id); + None } } diff --git a/lib/crates/fabro-telemetry/src/panic.rs b/lib/crates/fabro-telemetry/src/panic.rs index 2d64c43ce..1a3ec8cb5 100644 --- a/lib/crates/fabro-telemetry/src/panic.rs +++ b/lib/crates/fabro-telemetry/src/panic.rs @@ -98,9 +98,8 @@ fn report_panic(info: &PanicHookInfo<'_>) { /// Serialize the Sentry event to a temp file and spawn `fabro __send_panic `. fn spawn_panic_sender(event: Event<'static>) { - let json = match serde_json::to_vec(&event) { - Ok(j) => j, - Err(_) => return, + let Ok(json) = serde_json::to_vec(&event) else { + return; }; let filename = format!("fabro-panic-{}.json", event.event_id); @@ -111,7 +110,7 @@ fn spawn_panic_sender(event: Event<'static>) { /// /// Reads the JSON event from `path` and sends it to Sentry. /// No-ops if `SENTRY_DSN` was not set at compile time. -pub async fn capture(path: &Path) -> anyhow::Result<()> { +pub fn capture(path: &Path) -> anyhow::Result<()> { let dsn = SENTRY_DSN.ok_or_else(|| anyhow::anyhow!("SENTRY_DSN not set at compile time"))?; let json = std::fs::read(path)?; diff --git a/lib/crates/fabro-telemetry/src/sanitize.rs b/lib/crates/fabro-telemetry/src/sanitize.rs index a9460e4a6..6bfe35f4d 100644 --- a/lib/crates/fabro-telemetry/src/sanitize.rs +++ b/lib/crates/fabro-telemetry/src/sanitize.rs @@ -29,7 +29,7 @@ pub fn sanitize_command(args: &[String], subcommand: &str) -> String { let skip = 1 + sub_tokens.len(); // argv[0] + subcommand tokens // Add subcommand tokens verbatim - parts.extend(sub_tokens.iter().map(|s| s.to_string())); + parts.extend(sub_tokens.iter().map(std::string::ToString::to_string)); // Sanitize remaining args for arg in args.iter().skip(skip) { diff --git a/lib/crates/fabro-telemetry/src/sender.rs b/lib/crates/fabro-telemetry/src/sender.rs index e53e4d495..48127ad2a 100644 --- a/lib/crates/fabro-telemetry/src/sender.rs +++ b/lib/crates/fabro-telemetry/src/sender.rs @@ -94,9 +94,8 @@ pub fn upload_blocking(tracks: &[Track]) -> anyhow::Result<()> { .collect(); let content = lines.join("\n"); - let payload = match build_segment_batch(&content) { - Some(p) => p, - None => return Ok(()), + let Some(payload) = build_segment_batch(&content) else { + return Ok(()); }; let auth = STANDARD.encode(format!("{write_key}:")); @@ -123,9 +122,8 @@ pub async fn upload(path: &Path) -> anyhow::Result<()> { .ok_or_else(|| anyhow::anyhow!("SEGMENT_WRITE_KEY not set at compile time"))?; let content = std::fs::read_to_string(path)?; - let payload = match build_segment_batch(&content) { - Some(p) => p, - None => return Ok(()), + let Some(payload) = build_segment_batch(&content) else { + return Ok(()); }; let auth = STANDARD.encode(format!("{write_key}:")); diff --git a/lib/crates/fabro-telemetry/src/spawn.rs b/lib/crates/fabro-telemetry/src/spawn.rs index c5ea4b979..eba8dbc0f 100644 --- a/lib/crates/fabro-telemetry/src/spawn.rs +++ b/lib/crates/fabro-telemetry/src/spawn.rs @@ -22,6 +22,7 @@ pub fn spawn_detached(args: &[&str], env: &[(&str, &str)]) { } #[cfg(unix)] +#[allow(clippy::exit)] fn spawn_detached_unix(args: &[&str], env: &[(&str, &str)]) { use fork::{Fork, fork, setsid}; @@ -98,10 +99,10 @@ fn spawn_detached_windows(args: &[&str], env: &[(&str, &str)]) { /// This is the shared pattern used by both analytics and panic senders. /// No-ops silently if the exe path can't be resolved or the temp file can't be written. pub fn spawn_fabro_subcommand(subcommand: &str, filename: &str, json: &[u8]) { - let tmp_dir = match dirs::home_dir() { - Some(h) => h.join(".fabro").join("tmp"), - None => return, + let Some(home) = dirs::home_dir() else { + return; }; + let tmp_dir = home.join(".fabro").join("tmp"); if std::fs::create_dir_all(&tmp_dir).is_err() { return; } @@ -110,17 +111,16 @@ pub fn spawn_fabro_subcommand(subcommand: &str, filename: &str, json: &[u8]) { return; } - let path_str = match path.to_str() { - Some(s) => s.to_string(), - None => return, + let Some(path_str) = path.to_str() else { + return; }; + let path_str = path_str.to_string(); - let exe = match std::env::current_exe() + let Some(exe) = std::env::current_exe() .ok() - .and_then(|p| p.to_str().map(|s| s.to_string())) - { - Some(e) => e, - None => return, + .and_then(|p| p.to_str().map(std::string::ToString::to_string)) + else { + return; }; spawn_detached( diff --git a/lib/crates/fabro-tracker/src/github.rs b/lib/crates/fabro-tracker/src/github.rs index 4973ef644..f7e63f6f5 100644 --- a/lib/crates/fabro-tracker/src/github.rs +++ b/lib/crates/fabro-tracker/src/github.rs @@ -84,13 +84,13 @@ impl GitHubTracker { "Resolving GitHub project node ID" ); let graphql_url = self.graphql_url(); - let query = r#" + let query = r" query($owner: String!, $number: Int!) { organization(login: $owner) { projectV2(number: $number) { id } } } - "#; + "; let variables = serde_json::json!({ "owner": self.owner, "number": self.project_number, @@ -110,13 +110,13 @@ impl GitHubTracker { return Ok(id.to_string()); } - let user_query = r#" + let user_query = r" query($owner: String!, $number: Int!) { user(login: $owner) { projectV2(number: $number) { id } } } - "#; + "; let user_resp = execute_github_graphql( &self.client, token, @@ -128,7 +128,7 @@ impl GitHubTracker { user_resp["data"]["user"]["projectV2"]["id"] .as_str() - .map(|s| s.to_string()) + .map(std::string::ToString::to_string) .ok_or_else(|| { format!( "Project #{} not found for owner '{}'", @@ -137,7 +137,7 @@ impl GitHubTracker { }) }) .await - .map(|s| s.as_str()) + .map(std::string::String::as_str) } } @@ -150,7 +150,9 @@ fn normalize_github_item(item: &serde_json::Value) -> Option { let identifier = format!("#{number}"); let title = content["title"].as_str()?.to_string(); let url = content["url"].as_str()?.to_string(); - let description = content["body"].as_str().map(|s| s.to_string()); + let description = content["body"] + .as_str() + .map(std::string::ToString::to_string); let state = item["fieldValueByName"]["name"] .as_str() @@ -161,20 +163,24 @@ fn normalize_github_item(item: &serde_json::Value) -> Option { .as_array() .and_then(|arr| arr.first()) .and_then(|a| a["id"].as_str()) - .map(|s| s.to_string()); + .map(std::string::ToString::to_string); let labels = content["labels"]["nodes"] .as_array() .map(|arr| { arr.iter() .filter_map(|l| l["name"].as_str()) - .map(|s| s.to_lowercase()) + .map(str::to_lowercase) .collect() }) .unwrap_or_default(); - let created_at = content["createdAt"].as_str().map(|s| s.to_string()); - let updated_at = content["updatedAt"].as_str().map(|s| s.to_string()); + let created_at = content["createdAt"] + .as_str() + .map(std::string::ToString::to_string); + let updated_at = content["updatedAt"] + .as_str() + .map(std::string::ToString::to_string); Some(Issue { id, @@ -247,7 +253,7 @@ async fn fetch_project_items_page( .unwrap_or(false); let end_cursor = resp["data"]["node"]["items"]["pageInfo"]["endCursor"] .as_str() - .map(|s| s.to_string()); + .map(std::string::ToString::to_string); // Take ownership of the nodes array in-place instead of deep-cloning it. let nodes = resp @@ -276,20 +282,20 @@ impl Tracker for GitHubTracker { resp["data"]["viewer"]["id"] .as_str() - .map(|s| s.to_string()) + .map(std::string::ToString::to_string) .ok_or_else(|| "Missing viewer id in GitHub response".to_string()) } async fn create_comment(&self, issue: &Issue, body: &str) -> Result<(), String> { tracing::debug!(issue_id = %issue.id, "Creating comment on GitHub issue"); let token = self.fresh_token().await?; - let query = r#" + let query = r" mutation($subjectId: ID!, $body: String!) { addComment(input: { subjectId: $subjectId, body: $body }) { clientMutationId } } - "#; + "; let variables = serde_json::json!({ "subjectId": issue.id, "body": body, @@ -358,7 +364,7 @@ impl Tracker for GitHubTracker { .to_string(); // Step 2: Update the field value - let update_query = r#" + let update_query = r" mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) { updateProjectV2ItemFieldValue(input: { projectId: $projectId @@ -369,7 +375,7 @@ impl Tracker for GitHubTracker { projectV2Item { id } } } - "#; + "; execute_github_graphql( &self.client, &token, diff --git a/lib/crates/fabro-tracker/src/linear.rs b/lib/crates/fabro-tracker/src/linear.rs index a786805be..e47b6ca6d 100644 --- a/lib/crates/fabro-tracker/src/linear.rs +++ b/lib/crates/fabro-tracker/src/linear.rs @@ -52,7 +52,9 @@ fn normalize_issue(node: &Value) -> Result { .as_str() .ok_or("Missing issue title")? .to_string(); - let description = node["description"].as_str().map(|s| s.to_string()); + let description = node["description"] + .as_str() + .map(std::string::ToString::to_string); let priority = match node["priority"].as_i64() { Some(0) | None => None, Some(n) => Some(n as i32), @@ -61,16 +63,20 @@ fn normalize_issue(node: &Value) -> Result { .as_str() .ok_or("Missing issue state name")? .to_string(); - let branch_name = node["branchName"].as_str().map(|s| s.to_string()); + let branch_name = node["branchName"] + .as_str() + .map(std::string::ToString::to_string); let url = node["url"].as_str().ok_or("Missing issue url")?.to_string(); - let assignee_id = node["assignee"]["id"].as_str().map(|s| s.to_string()); + let assignee_id = node["assignee"]["id"] + .as_str() + .map(std::string::ToString::to_string); let labels = node["labels"]["nodes"] .as_array() .map(|arr| { arr.iter() .filter_map(|l| l["name"].as_str()) - .map(|s| s.to_lowercase()) + .map(str::to_lowercase) .collect() }) .unwrap_or_default(); @@ -96,8 +102,12 @@ fn normalize_issue(node: &Value) -> Result { }) .unwrap_or_default(); - let created_at = node["createdAt"].as_str().map(|s| s.to_string()); - let updated_at = node["updatedAt"].as_str().map(|s| s.to_string()); + let created_at = node["createdAt"] + .as_str() + .map(std::string::ToString::to_string); + let updated_at = node["updatedAt"] + .as_str() + .map(std::string::ToString::to_string); Ok(Issue { id, @@ -169,19 +179,19 @@ impl Tracker for LinearTracker { response["data"]["viewer"]["id"] .as_str() - .map(|s| s.to_string()) + .map(std::string::ToString::to_string) .ok_or_else(|| "Missing viewer id in response".to_string()) } async fn create_comment(&self, issue: &Issue, body: &str) -> Result<(), String> { tracing::debug!(issue_id = %issue.id, "Creating comment on Linear issue"); - let query = r#" + let query = r" mutation($issueId: String!, $body: String!) { commentCreate(input: { issueId: $issueId, body: $body }) { success } } - "#; + "; let variables = serde_json::json!({ "issueId": issue.id, @@ -203,7 +213,7 @@ impl Tracker for LinearTracker { async fn update_issue_state(&self, issue: &Issue, state_name: &str) -> Result<(), String> { tracing::debug!(issue_id = %issue.id, state_name, "Updating Linear issue state"); // Step 1: Resolve state name to ID via the issue's team - let resolve_query = r#" + let resolve_query = r" query($issueId: String!, $stateName: String!) { issue(id: $issueId) { team { @@ -213,7 +223,7 @@ impl Tracker for LinearTracker { } } } - "#; + "; let resolve_vars = serde_json::json!({ "issueId": issue.id, @@ -231,13 +241,13 @@ impl Tracker for LinearTracker { .to_string(); // Step 2: Update the issue - let update_query = r#" + let update_query = r" mutation($issueId: String!, $stateId: String!) { issueUpdate(id: $issueId, input: { stateId: $stateId }) { success } } - "#; + "; let update_vars = serde_json::json!({ "issueId": issue.id, @@ -265,7 +275,7 @@ impl Tracker for LinearTracker { ); let query = format!( - r#" + r" query($slug: String!, $states: [String!]!, $cursor: String) {{ issues( first: 50 @@ -279,7 +289,7 @@ impl Tracker for LinearTracker { pageInfo {{ hasNextPage endCursor }} }} }} - "# + " ); let mut all_issues = Vec::new(); @@ -298,7 +308,9 @@ impl Tracker for LinearTracker { let page_info = &response["data"]["issues"]["pageInfo"]; if page_info["hasNextPage"].as_bool() == Some(true) { - cursor = page_info["endCursor"].as_str().map(|s| s.to_string()); + cursor = page_info["endCursor"] + .as_str() + .map(std::string::ToString::to_string); } else { break; } @@ -315,13 +327,13 @@ impl Tracker for LinearTracker { tracing::debug!(count = ids.len(), "Fetching issues by ID from Linear"); let query = format!( - r#" + r" query($ids: [ID!]!) {{ issues(filter: {{ id: {{ in: $ids }} }}) {{ nodes {{ {ISSUE_FIELDS} }} }} }} - "# + " ); let mut issue_map: HashMap = HashMap::with_capacity(ids.len()); diff --git a/lib/crates/fabro-types/src/retro.rs b/lib/crates/fabro-types/src/retro.rs index d5d16e0d5..be7d4d766 100644 --- a/lib/crates/fabro-types/src/retro.rs +++ b/lib/crates/fabro-types/src/retro.rs @@ -16,11 +16,11 @@ pub enum SmoothnessRating { impl fmt::Display for SmoothnessRating { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { - SmoothnessRating::Effortless => "effortless", - SmoothnessRating::Smooth => "smooth", - SmoothnessRating::Bumpy => "bumpy", - SmoothnessRating::Struggled => "struggled", - SmoothnessRating::Failed => "failed", + Self::Effortless => "effortless", + Self::Smooth => "smooth", + Self::Bumpy => "bumpy", + Self::Struggled => "struggled", + Self::Failed => "failed", }; f.write_str(s) } diff --git a/lib/crates/fabro-types/src/settings/sandbox.rs b/lib/crates/fabro-types/src/settings/sandbox.rs index ce79df24e..c704865cc 100644 --- a/lib/crates/fabro-types/src/settings/sandbox.rs +++ b/lib/crates/fabro-types/src/settings/sandbox.rs @@ -26,9 +26,9 @@ impl Serialize for DaytonaNetwork { S: serde::Serializer, { match self { - DaytonaNetwork::Block => serializer.serialize_str("block"), - DaytonaNetwork::AllowAll => serializer.serialize_str("allow_all"), - DaytonaNetwork::AllowList(cidrs) => { + Self::Block => serializer.serialize_str("block"), + Self::AllowAll => serializer.serialize_str("allow_all"), + Self::AllowList(cidrs) => { use serde::ser::SerializeMap; let mut map = serializer.serialize_map(Some(1))?; map.serialize_entry("allow_list", cidrs)?; diff --git a/lib/crates/fabro-util/build.rs b/lib/crates/fabro-util/build.rs index f46bad7a0..678156cc7 100644 --- a/lib/crates/fabro-util/build.rs +++ b/lib/crates/fabro-util/build.rs @@ -1,5 +1,6 @@ use serde::Deserialize; use std::env; +use std::fmt::Write; use std::fs; use std::path::Path; @@ -65,29 +66,31 @@ fn main() { let mut code = String::new(); // Global allowlist regexes - code.push_str("pub const GLOBAL_ALLOWLIST_REGEXES: &[&str] = &[\n"); + code.push_str("#[allow(unreachable_pub)]\npub const GLOBAL_ALLOWLIST_REGEXES: &[&str] = &[\n"); if let Some(ref al) = config.allowlist { if let Some(ref regexes) = al.regexes { for r in regexes { - code.push_str(&format!(" \"{}\",\n", escape_rust_string(r))); + let _ = writeln!(code, " \"{}\",", escape_rust_string(r)); } } } code.push_str("];\n\n"); // Global allowlist stopwords - code.push_str("pub const GLOBAL_ALLOWLIST_STOPWORDS: &[&str] = &[\n"); + code.push_str( + "#[allow(unreachable_pub)]\npub const GLOBAL_ALLOWLIST_STOPWORDS: &[&str] = &[\n", + ); if let Some(ref al) = config.allowlist { if let Some(ref stopwords) = al.stopwords { for sw in stopwords { - code.push_str(&format!(" \"{}\",\n", escape_rust_string(sw))); + let _ = writeln!(code, " \"{}\",", escape_rust_string(sw)); } } } code.push_str("];\n\n"); // Rule definitions - code.push_str("#[allow(dead_code)]\n"); + code.push_str("#[allow(dead_code, unreachable_pub)]\n"); code.push_str("pub struct RuleDef {\n"); code.push_str(" pub id: &'static str,\n"); code.push_str(" pub regex_pattern: &'static str,\n"); @@ -98,29 +101,29 @@ fn main() { code.push_str(" pub allowlist_regex_target: Option<&'static str>,\n"); code.push_str("}\n\n"); - code.push_str("pub const RULES: &[RuleDef] = &[\n"); + code.push_str("#[allow(unreachable_pub)]\npub const RULES: &[RuleDef] = &[\n"); for rule in &config.rules { code.push_str(" RuleDef {\n"); - code.push_str(&format!( - " id: \"{}\",\n", - escape_rust_string(&rule.id) - )); - code.push_str(&format!( - " regex_pattern: \"{}\",\n", + let _ = writeln!(code, " id: \"{}\",", escape_rust_string(&rule.id)); + let _ = writeln!( + code, + " regex_pattern: \"{}\",", escape_rust_string(&rule.regex) - )); + ); // Keywords code.push_str(" keywords: &["); for kw in &rule.keywords { - code.push_str(&format!("\"{}\", ", escape_rust_string(kw))); + let _ = write!(code, "\"{}\", ", escape_rust_string(kw)); } code.push_str("],\n"); // Entropy match rule.entropy { - Some(e) => code.push_str(&format!(" entropy: Some({:.1}),\n", e)), + Some(e) => { + let _ = writeln!(code, " entropy: Some({:.1}),", e); + } None => code.push_str(" entropy: None,\n"), } @@ -129,7 +132,7 @@ fn main() { if let Some(ref al) = rule.allowlist { if let Some(ref regexes) = al.regexes { for r in regexes { - code.push_str(&format!("\"{}\", ", escape_rust_string(r))); + let _ = write!(code, "\"{}\", ", escape_rust_string(r)); } } } @@ -140,7 +143,7 @@ fn main() { if let Some(ref al) = rule.allowlist { if let Some(ref stopwords) = al.stopwords { for sw in stopwords { - code.push_str(&format!("\"{}\", ", escape_rust_string(sw))); + let _ = write!(code, "\"{}\", ", escape_rust_string(sw)); } } } @@ -149,10 +152,11 @@ fn main() { // Allowlist regex target if let Some(ref al) = rule.allowlist { if let Some(ref target) = al.regex_target { - code.push_str(&format!( - " allowlist_regex_target: Some(\"{}\"),\n", + let _ = writeln!( + code, + " allowlist_regex_target: Some(\"{}\"),", escape_rust_string(target) - )); + ); } else { code.push_str(" allowlist_regex_target: None,\n"); } diff --git a/lib/crates/fabro-util/src/redact/entropy.rs b/lib/crates/fabro-util/src/redact/entropy.rs index 4aa03fd60..7f2b1d9cb 100644 --- a/lib/crates/fabro-util/src/redact/entropy.rs +++ b/lib/crates/fabro-util/src/redact/entropy.rs @@ -10,7 +10,7 @@ static SECRET_PATTERN: LazyLock = const ENTROPY_THRESHOLD: f64 = 4.5; /// Compute Shannon entropy (bits per byte) of a string. -pub fn shannon_entropy(s: &str) -> f64 { +pub(super) fn shannon_entropy(s: &str) -> f64 { if s.is_empty() { return 0.0; } @@ -22,7 +22,7 @@ pub fn shannon_entropy(s: &str) -> f64 { let mut entropy = 0.0; for &count in &freq { if count > 0 { - let p = count as f64 / len; + let p = f64::from(count) / len; entropy -= p * p.log2(); } } @@ -34,7 +34,7 @@ pub fn shannon_entropy(s: &str) -> f64 { /// Returns regions where tokens match `[A-Za-z0-9+_=-]{10,}` and have /// Shannon entropy above the threshold (4.5 bits). Protects against /// consuming characters from JSON escape sequences. -pub fn find_entropy_regions(s: &str) -> Vec { +pub(super) fn find_entropy_regions(s: &str) -> Vec { let mut regions = Vec::new(); for m in SECRET_PATTERN.find_iter(s) { let mut start = m.start(); diff --git a/lib/crates/fabro-util/src/redact/gitleaks.rs b/lib/crates/fabro-util/src/redact/gitleaks.rs index 7e4c85c7f..443af1ac0 100644 --- a/lib/crates/fabro-util/src/redact/gitleaks.rs +++ b/lib/crates/fabro-util/src/redact/gitleaks.rs @@ -58,7 +58,7 @@ struct GitleaksEngine { } impl GitleaksEngine { - fn build() -> Option { + fn build() -> Option { let mut rules = Vec::new(); let mut all_keywords: Vec = Vec::new(); let mut keyword_to_rules: Vec> = Vec::new(); @@ -109,7 +109,7 @@ impl GitleaksEngine { .map(|_| OnceLock::new()) .collect(); - Some(GitleaksEngine { + Some(Self { keyword_filter, keyword_to_rules, no_keyword_rules, @@ -152,9 +152,8 @@ impl GitleaksEngine { let rule = &self.rules[idx]; // Lazy-compile the regex on first use; skip if invalid. - let regex = match rule.regex() { - Some(r) => r, - None => continue, + let Some(regex) = rule.regex() else { + continue; }; let secret_group = rule.secret_group(); @@ -226,7 +225,7 @@ impl GitleaksEngine { static ENGINE: LazyLock> = LazyLock::new(GitleaksEngine::build); /// Find regions matching gitleaks rules. -pub fn find_gitleaks_regions(s: &str) -> Vec { +pub(super) fn find_gitleaks_regions(s: &str) -> Vec { match ENGINE.as_ref() { Some(engine) => engine.find_regions(s), None => Vec::new(), diff --git a/lib/crates/fabro-util/src/run_log.rs b/lib/crates/fabro-util/src/run_log.rs index d41434fe9..7b1bca14b 100644 --- a/lib/crates/fabro-util/src/run_log.rs +++ b/lib/crates/fabro-util/src/run_log.rs @@ -53,8 +53,8 @@ pub enum RunLogGuard { impl Write for RunLogGuard { fn write(&mut self, data: &[u8]) -> io::Result { match self { - RunLogGuard::Inactive => Ok(data.len()), - RunLogGuard::Active { buf, .. } => buf.write(data), + Self::Inactive => Ok(data.len()), + Self::Active { buf, .. } => buf.write(data), } } @@ -65,7 +65,7 @@ impl Write for RunLogGuard { impl Drop for RunLogGuard { fn drop(&mut self) { - if let RunLogGuard::Active { buf, file } = self { + if let Self::Active { buf, file } = self { if buf.is_empty() { return; } diff --git a/lib/crates/fabro-util/src/text.rs b/lib/crates/fabro-util/src/text.rs index cf5cba883..262d00fc8 100644 --- a/lib/crates/fabro-util/src/text.rs +++ b/lib/crates/fabro-util/src/text.rs @@ -5,7 +5,7 @@ pub fn strip_goal_decoration(goal: &str) -> &str { let line = goal.lines().next().unwrap_or(""); let line = line.trim_start_matches('#').trim(); - line.strip_prefix("Plan:").map(|s| s.trim()).unwrap_or(line) + line.strip_prefix("Plan:").map(str::trim).unwrap_or(line) } #[cfg(test)] diff --git a/lib/crates/fabro-validate/src/rules.rs b/lib/crates/fabro-validate/src/rules.rs index a62b1c722..ece6d5503 100644 --- a/lib/crates/fabro-validate/src/rules.rs +++ b/lib/crates/fabro-validate/src/rules.rs @@ -814,7 +814,7 @@ impl LintRule for OrphanCustomOutcomeRule { // Check if there's at least one unconditional edge let has_unconditional = outgoing .iter() - .any(|e| e.condition().is_none_or(|c| c.is_empty())); + .any(|e| e.condition().is_none_or(str::is_empty)); if !has_unconditional { diagnostics.push(Diagnostic { rule: self.name().to_string(), @@ -971,9 +971,8 @@ impl LintRule for StylesheetModelKnownRule { if stylesheet_str.is_empty() { return Vec::new(); } - let stylesheet = match parse_stylesheet(stylesheet_str) { - Ok(ss) => ss, - Err(_) => return Vec::new(), // syntax errors caught by stylesheet_syntax rule + let Ok(stylesheet) = parse_stylesheet(stylesheet_str) else { + return Vec::new(); // syntax errors caught by stylesheet_syntax rule }; let mut diagnostics = Vec::new(); diff --git a/lib/crates/fabro-workflows/src/asset_snapshot.rs b/lib/crates/fabro-workflows/src/asset_snapshot.rs index 9a8c95fb6..a4ba2c45b 100644 --- a/lib/crates/fabro-workflows/src/asset_snapshot.rs +++ b/lib/crates/fabro-workflows/src/asset_snapshot.rs @@ -116,13 +116,11 @@ fn parse_find_output_linux(output: &str) -> Vec { if parts.len() != 3 { continue; } - let size = match parts[0].parse::() { - Ok(s) => s, - Err(_) => continue, + let Ok(size) = parts[0].parse::() else { + continue; }; - let mtime = match parts[1].parse::() { - Ok(m) => m, - Err(_) => continue, + let Ok(mtime) = parts[1].parse::() else { + continue; }; let path = parts[2].to_string(); if path.is_empty() { @@ -156,13 +154,11 @@ fn parse_find_output_darwin(output: &str) -> Vec { continue; } - let size = match stat_parts[0].parse::() { - Ok(s) => s, - Err(_) => continue, + let Ok(size) = stat_parts[0].parse::() else { + continue; }; - let mtime = match stat_parts[1].parse::() { - Ok(m) => m, - Err(_) => continue, + let Ok(mtime) = stat_parts[1].parse::() else { + continue; }; files.push(DiscoveredFile { diff --git a/lib/crates/fabro-workflows/src/assets.rs b/lib/crates/fabro-workflows/src/assets.rs index 99ecece92..4d8e1a722 100644 --- a/lib/crates/fabro-workflows/src/assets.rs +++ b/lib/crates/fabro-workflows/src/assets.rs @@ -21,9 +21,8 @@ fn serialize_path(path: &Path, serializer: S) -> Result) -> Result> { - let nodes = match std::fs::read_dir(assets_dir) { - Ok(read_dir) => read_dir, - Err(_) => return Ok(Vec::new()), + let Ok(nodes) = std::fs::read_dir(assets_dir) else { + return Ok(Vec::new()); }; let mut entries = Vec::new(); diff --git a/lib/crates/fabro-workflows/src/condition.rs b/lib/crates/fabro-workflows/src/condition.rs index 5fbcdee2c..51f3a46b9 100644 --- a/lib/crates/fabro-workflows/src/condition.rs +++ b/lib/crates/fabro-workflows/src/condition.rs @@ -119,14 +119,12 @@ fn eval_clause(clause: &Clause, outcome: &Outcome, context: &Context) -> bool { } Op::Contains => { let raw = resolve_key_value(&clause.key, outcome, context); - match &raw { - serde_json::Value::Array(arr) => arr - .iter() - .any(|elem| json_value_to_string(elem) == clause.value), - _ => { - let s = json_value_to_string(&raw); - s.contains(&clause.value) - } + if let serde_json::Value::Array(arr) = &raw { + arr.iter() + .any(|elem| json_value_to_string(elem) == clause.value) + } else { + let s = json_value_to_string(&raw); + s.contains(&clause.value) } } Op::Matches => { @@ -142,7 +140,7 @@ fn eval_clause(clause: &Clause, outcome: &Outcome, context: &Context) -> bool { /// Evaluate a condition expression against an outcome and context. /// Empty conditions always return true. #[must_use] -pub fn evaluate_condition(expr: &str, outcome: &Outcome, context: &Context) -> bool { +pub(crate) fn evaluate_condition(expr: &str, outcome: &Outcome, context: &Context) -> bool { use fabro_graphviz::condition::parse_condition_expr; let Ok(parsed) = parse_condition_expr(expr) else { return false; diff --git a/lib/crates/fabro-workflows/src/event.rs b/lib/crates/fabro-workflows/src/event.rs index 2893e95fb..5e420be74 100644 --- a/lib/crates/fabro-workflows/src/event.rs +++ b/lib/crates/fabro-workflows/src/event.rs @@ -867,7 +867,7 @@ impl ProgressLogger { .. } = event { - *run_id.lock().unwrap() = started_run_id.clone(); + (*run_id.lock().unwrap()).clone_from(started_run_id); } let _ = append_progress_event(&run_dir, &run_id.lock().unwrap(), event); }); diff --git a/lib/crates/fabro-workflows/src/git.rs b/lib/crates/fabro-workflows/src/git.rs index 82e2d2dad..c2f7b6ac6 100644 --- a/lib/crates/fabro-workflows/src/git.rs +++ b/lib/crates/fabro-workflows/src/git.rs @@ -1,3 +1,4 @@ +use std::fmt::Write; use std::path::Path; use std::process::Command; @@ -54,10 +55,11 @@ impl GitAuthor { message.push_str("\n\u{2692}\u{fe0f} Generated with [Fabro](https://fabro.sh)\n"); if !self.is_default() { let defaults = Self::default(); - message.push_str(&format!( + let _ = write!( + message, "\nCo-Authored-By: {} <{}>\n", defaults.name, defaults.email - )); + ); } } } @@ -207,6 +209,7 @@ pub fn push_branch(repo: &Path, remote: &str, branch: &str) -> Result<()> { /// Push run and metadata branches to origin if a remote tracking branch exists. /// /// Callers supply pre-built refspecs so they control force-push (`+` prefix). +#[allow(clippy::print_stderr)] pub fn push_run_branches( store: &Store, probe_branch: &str, @@ -363,9 +366,8 @@ const MAX_NODE_FILE_SIZE: u64 = 512 * 1024; /// `("nodes/{subdir}/{filename}", bytes)` entries suitable for the shadow tree. pub fn scan_node_files(run_dir: &Path) -> Vec<(String, Vec)> { let nodes_dir = run_dir.join("nodes"); - let entries = match std::fs::read_dir(&nodes_dir) { - Ok(e) => e, - Err(_) => return Vec::new(), + let Ok(entries) = std::fs::read_dir(&nodes_dir) else { + return Vec::new(); }; let mut result = Vec::new(); @@ -484,9 +486,8 @@ impl MetadataStore { /// Read a single file from the metadata branch. Returns `None` if branch or path doesn't exist. fn read_file(repo_path: &Path, run_id: &str, path: &str) -> Result>> { - let repo = match Repository::discover(repo_path) { - Ok(r) => r, - Err(_) => return Ok(None), + let Ok(repo) = Repository::discover(repo_path) else { + return Ok(None); }; let store = Store::new(repo); let sig = Signature::now("Fabro", "noreply@fabro.sh") diff --git a/lib/crates/fabro-workflows/src/graph.rs b/lib/crates/fabro-workflows/src/graph.rs index c72875be2..dbe2ecf2a 100644 --- a/lib/crates/fabro-workflows/src/graph.rs +++ b/lib/crates/fabro-workflows/src/graph.rs @@ -13,10 +13,10 @@ use crate::outcome::{Outcome, StageUsage}; // ---- WorkflowNode ---- #[derive(Debug, Clone)] -pub struct WorkflowNode(pub Arc); +pub(crate) struct WorkflowNode(pub Arc); impl WorkflowNode { - pub fn inner(&self) -> &GvNode { + pub(crate) fn inner(&self) -> &GvNode { &self.0 } } @@ -38,10 +38,10 @@ impl NodeSpec for WorkflowNode { // ---- WorkflowEdge ---- #[derive(Debug, Clone)] -pub struct WorkflowEdge(pub Arc); +pub(crate) struct WorkflowEdge(pub Arc); impl WorkflowEdge { - pub fn inner(&self) -> &GvEdge { + pub(crate) fn inner(&self) -> &GvEdge { &self.0 } } @@ -63,10 +63,10 @@ impl EdgeSpec for WorkflowEdge { // ---- WorkflowGraph ---- #[derive(Debug, Clone)] -pub struct WorkflowGraph(pub Arc); +pub(crate) struct WorkflowGraph(pub Arc); impl WorkflowGraph { - pub fn inner(&self) -> &GvGraph { + pub(crate) fn inner(&self) -> &GvGraph { &self.0 } } diff --git a/lib/crates/fabro-workflows/src/handler/llm/cli.rs b/lib/crates/fabro-workflows/src/handler/llm/cli.rs index d3c2485b7..57085c378 100644 --- a/lib/crates/fabro-workflows/src/handler/llm/cli.rs +++ b/lib/crates/fabro-workflows/src/handler/llm/cli.rs @@ -241,11 +241,11 @@ fn parse_claude_ndjson(output: &str) -> Option { .to_string(); let input_tokens = result .pointer("/usage/input_tokens") - .and_then(|v| v.as_i64()) + .and_then(serde_json::Value::as_i64) .unwrap_or(0); let output_tokens = result .pointer("/usage/output_tokens") - .and_then(|v| v.as_i64()) + .and_then(serde_json::Value::as_i64) .unwrap_or(0); Some(CliResponse { @@ -293,11 +293,11 @@ fn parse_codex_ndjson(output: &str) -> Option { "turn.completed" => { input_tokens = value .pointer("/usage/input_tokens") - .and_then(|v| v.as_i64()) + .and_then(serde_json::Value::as_i64) .unwrap_or(0); output_tokens = value .pointer("/usage/output_tokens") - .and_then(|v| v.as_i64()) + .and_then(serde_json::Value::as_i64) .unwrap_or(0); found_anything = true; } @@ -336,11 +336,11 @@ fn parse_gemini_json(output: &str) -> Option { .map(|model_stats| { let input = model_stats .pointer("/tokens/input") - .and_then(|v| v.as_i64()) + .and_then(serde_json::Value::as_i64) .unwrap_or(0); let output = model_stats .pointer("/tokens/candidates") - .and_then(|v| v.as_i64()) + .and_then(serde_json::Value::as_i64) .unwrap_or(0); (input, output) }) @@ -694,7 +694,7 @@ impl CodergenBackend for AgentCliBackend { let last_file_touched = if !files_touched.is_empty() { let quoted_files: Vec = files_touched .iter() - .filter_map(|f| shlex::try_quote(f).ok().map(|q| q.into_owned())) + .filter_map(|f| shlex::try_quote(f).ok().map(std::borrow::Cow::into_owned)) .collect(); let cmd = format!("ls -t {} | head -1", quoted_files.join(" ")); if let Ok(result) = sandbox.exec_command(&cmd, 5_000, None, None, None).await { @@ -748,6 +748,7 @@ impl BackendRouter { } } + #[allow(clippy::unused_self)] fn should_use_cli(&self, node: &Node) -> bool { // Explicit backend="cli" attribute on the node if node.backend() == Some("cli") { diff --git a/lib/crates/fabro-workflows/src/handler/llm/preamble.rs b/lib/crates/fabro-workflows/src/handler/llm/preamble.rs index 79823b4a1..215122f9b 100644 --- a/lib/crates/fabro-workflows/src/handler/llm/preamble.rs +++ b/lib/crates/fabro-workflows/src/handler/llm/preamble.rs @@ -1,4 +1,5 @@ use std::collections::{HashMap, HashSet}; +use std::fmt::Write; use crate::artifact::{artifact_path, format_artifact_reference}; use crate::context::keys; @@ -93,7 +94,7 @@ fn is_meta_handler(graph: &Graph, node_id: &str) -> bool { } fn is_blank_value(val: Option<&serde_json::Value>) -> bool { - val.and_then(|v| v.as_str()).is_some_and(|s| s.is_empty()) + val.and_then(|v| v.as_str()).is_some_and(str::is_empty) } fn is_context_key_excluded(key: &str) -> bool { @@ -519,10 +520,10 @@ fn build_summary_preamble( let status = outcome.status.to_string(); let mut line = format!("- {node_id}: {status}"); if let Some(notes) = outcome.notes.as_deref() { - line.push_str(&format!(" ({notes})")); + let _ = write!(line, " ({notes})"); } if let Some(reason) = outcome.failure_reason() { - line.push_str(&format!(" [reason: {reason}]")); + let _ = write!(line, " [reason: {reason}]"); } parts.push(line); @@ -566,10 +567,10 @@ fn build_summary_preamble( let status = outcome.status.to_string(); let mut line = format!("- {node_id}: {status}"); if let Some(notes) = outcome.notes.as_deref() { - line.push_str(&format!(" ({notes})")); + let _ = write!(line, " ({notes})"); } if let Some(reason) = outcome.failure_reason() { - line.push_str(&format!(" [reason: {reason}]")); + let _ = write!(line, " [reason: {reason}]"); } parts.push(line); diff --git a/lib/crates/fabro-workflows/src/handler/manager_loop.rs b/lib/crates/fabro-workflows/src/handler/manager_loop.rs index 5c760ec2e..00e5692be 100644 --- a/lib/crates/fabro-workflows/src/handler/manager_loop.rs +++ b/lib/crates/fabro-workflows/src/handler/manager_loop.rs @@ -252,12 +252,12 @@ impl Handler for SubWorkflowHandler { }; if child_outcome.status == StageStatus::Fail { - outcome.failure = child_outcome.failure.clone(); + outcome.failure.clone_from(&child_outcome.failure); } return Ok(outcome); } - _ = sleep(poll_interval) => { + () = sleep(poll_interval) => { // Check stop condition if !stop_condition.is_empty() { let dummy_outcome = Outcome::success(); diff --git a/lib/crates/fabro-workflows/src/lifecycle/artifact.rs b/lib/crates/fabro-workflows/src/lifecycle/artifact.rs index fb027b033..f816786c4 100644 --- a/lib/crates/fabro-workflows/src/lifecycle/artifact.rs +++ b/lib/crates/fabro-workflows/src/lifecycle/artifact.rs @@ -22,7 +22,7 @@ type WfNodeResult = NodeResult>; type WfNodeDecision = NodeDecision>; /// Sub-lifecycle responsible for artifact collection, offloading, and syncing. -pub struct ArtifactLifecycle { +pub(crate) struct ArtifactLifecycle { pub sandbox: Arc, pub artifact_store: Arc>, pub artifact_values_dir: Option, @@ -35,7 +35,7 @@ pub struct ArtifactLifecycle { impl ArtifactLifecycle { #[allow(clippy::too_many_arguments)] - pub fn new( + pub(crate) fn new( sandbox: Arc, artifact_store: Arc>, artifact_values_dir: Option, diff --git a/lib/crates/fabro-workflows/src/lifecycle/auto_status.rs b/lib/crates/fabro-workflows/src/lifecycle/auto_status.rs index 9f3dab762..dbeb70daf 100644 --- a/lib/crates/fabro-workflows/src/lifecycle/auto_status.rs +++ b/lib/crates/fabro-workflows/src/lifecycle/auto_status.rs @@ -13,7 +13,7 @@ type WfRunState = RunState>; type WfNodeResult = NodeResult>; /// Sub-lifecycle responsible for auto-status override on nodes with `auto_status=true`. -pub struct AutoStatusLifecycle; +pub(crate) struct AutoStatusLifecycle; #[async_trait] impl RunLifecycle for AutoStatusLifecycle { diff --git a/lib/crates/fabro-workflows/src/lifecycle/circuit_breaker.rs b/lib/crates/fabro-workflows/src/lifecycle/circuit_breaker.rs index 5932807f3..98b38d0f9 100644 --- a/lib/crates/fabro-workflows/src/lifecycle/circuit_breaker.rs +++ b/lib/crates/fabro-workflows/src/lifecycle/circuit_breaker.rs @@ -18,14 +18,14 @@ type WfNodeResult = NodeResult>; /// Sub-lifecycle responsible for tracking failure signatures and tripping the /// circuit breaker when deterministic failure cycles are detected. -pub struct CircuitBreakerLifecycle { +pub(crate) struct CircuitBreakerLifecycle { loop_failure_signatures: Mutex>, restart_failure_signatures: Mutex>, loop_restart_signature_limit: usize, } impl CircuitBreakerLifecycle { - pub fn new(loop_restart_signature_limit: usize) -> Self { + pub(crate) fn new(loop_restart_signature_limit: usize) -> Self { Self { loop_failure_signatures: Mutex::new(HashMap::new()), restart_failure_signatures: Mutex::new(HashMap::new()), @@ -34,7 +34,7 @@ impl CircuitBreakerLifecycle { } /// Restore circuit breaker state from a checkpoint (for resume). - pub fn restore( + pub(crate) fn restore( &self, loop_sigs: HashMap, restart_sigs: HashMap, @@ -44,7 +44,7 @@ impl CircuitBreakerLifecycle { } /// Snapshot current state for checkpoint building. - pub fn snapshot( + pub(crate) fn snapshot( &self, ) -> ( HashMap, diff --git a/lib/crates/fabro-workflows/src/lifecycle/disk.rs b/lib/crates/fabro-workflows/src/lifecycle/disk.rs index a2d4cb0a5..bc2923825 100644 --- a/lib/crates/fabro-workflows/src/lifecycle/disk.rs +++ b/lib/crates/fabro-workflows/src/lifecycle/disk.rs @@ -24,7 +24,7 @@ type WfRunState = RunState>; type WfNodeResult = NodeResult>; /// Sub-lifecycle responsible for writing run state to disk (node status, checkpoints). -pub struct DiskLifecycle { +pub(crate) struct DiskLifecycle { pub run_dir: PathBuf, pub run_id: String, pub graph: Arc, diff --git a/lib/crates/fabro-workflows/src/lifecycle/event.rs b/lib/crates/fabro-workflows/src/lifecycle/event.rs index c7a3f6499..f9dafb6fc 100644 --- a/lib/crates/fabro-workflows/src/lifecycle/event.rs +++ b/lib/crates/fabro-workflows/src/lifecycle/event.rs @@ -34,7 +34,7 @@ fn node_script(node: &GvNode) -> Option { } /// Sub-lifecycle responsible for emitting workflow run events. -pub struct EventLifecycle { +pub(crate) struct EventLifecycle { pub emitter: Arc, pub graph_name: String, pub run_id: String, diff --git a/lib/crates/fabro-workflows/src/lifecycle/fidelity.rs b/lib/crates/fabro-workflows/src/lifecycle/fidelity.rs index 0d725d06c..559a15f0b 100644 --- a/lib/crates/fabro-workflows/src/lifecycle/fidelity.rs +++ b/lib/crates/fabro-workflows/src/lifecycle/fidelity.rs @@ -25,7 +25,7 @@ struct IncomingEdgeData { } /// Sub-lifecycle responsible for fidelity/thread resolution and context key setup. -pub struct FidelityLifecycle { +pub(crate) struct FidelityLifecycle { pub graph: Arc, incoming_edge_data: Mutex>, /// True on the first node after checkpoint resume when prior fidelity was Full. @@ -33,7 +33,7 @@ pub struct FidelityLifecycle { } impl FidelityLifecycle { - pub fn new(graph: Arc) -> Self { + pub(crate) fn new(graph: Arc) -> Self { Self { graph, incoming_edge_data: Mutex::new(None), @@ -41,7 +41,7 @@ impl FidelityLifecycle { } } - pub fn set_degrade_fidelity_on_resume(&self, flag: bool) { + pub(crate) fn set_degrade_fidelity_on_resume(&self, flag: bool) { *self.degrade_fidelity_on_resume.lock().unwrap() = flag; } } diff --git a/lib/crates/fabro-workflows/src/lifecycle/git.rs b/lib/crates/fabro-workflows/src/lifecycle/git.rs index 1a7b6d658..1846e91a3 100644 --- a/lib/crates/fabro-workflows/src/lifecycle/git.rs +++ b/lib/crates/fabro-workflows/src/lifecycle/git.rs @@ -26,13 +26,13 @@ type WfNodeResult = NodeResult>; /// Result of a git checkpoint operation, shared with EventLifecycle. #[derive(Debug, Clone)] -pub struct GitCheckpointResult { +pub(crate) struct GitCheckpointResult { pub commit_sha: Option, pub push_results: Vec<(String, bool)>, } /// Sub-lifecycle responsible for git operations (checkpoint commits, pushes, diffs). -pub struct GitLifecycle { +pub(crate) struct GitLifecycle { pub sandbox: Arc, pub artifact_store: Arc>, pub emitter: Arc, diff --git a/lib/crates/fabro-workflows/src/lifecycle/hook.rs b/lib/crates/fabro-workflows/src/lifecycle/hook.rs index 2cadc1931..efde92ec2 100644 --- a/lib/crates/fabro-workflows/src/lifecycle/hook.rs +++ b/lib/crates/fabro-workflows/src/lifecycle/hook.rs @@ -22,7 +22,7 @@ type WfNodeResult = NodeResult>; type WfNodeDecision = NodeDecision>; /// Sub-lifecycle responsible for running workflow hooks. -pub struct HookLifecycle { +pub(crate) struct HookLifecycle { pub hook_runner: Option>, pub sandbox: Arc, pub hook_work_dir: Option, diff --git a/lib/crates/fabro-workflows/src/lifecycle/mod.rs b/lib/crates/fabro-workflows/src/lifecycle/mod.rs index 1cbe47f58..a954b3a9c 100644 --- a/lib/crates/fabro-workflows/src/lifecycle/mod.rs +++ b/lib/crates/fabro-workflows/src/lifecycle/mod.rs @@ -1,11 +1,11 @@ -pub mod artifact; -pub mod auto_status; -pub mod circuit_breaker; -pub mod disk; -pub mod event; -pub mod fidelity; -pub mod git; -pub mod hook; +pub(crate) mod artifact; +pub(crate) mod auto_status; +pub(crate) mod circuit_breaker; +pub(crate) mod disk; +pub(crate) mod event; +pub(crate) mod fidelity; +pub(crate) mod git; +pub(crate) mod hook; use std::collections::HashMap; use std::path::PathBuf; @@ -52,7 +52,7 @@ type WfNodeDecision = NodeDecision>; /// Orchestrates all sub-lifecycles with explicit per-callback ordering. /// Implements `RunLifecycle` by delegating to focused structs. -pub struct WorkflowLifecycle { +pub(crate) struct WorkflowLifecycle { event: EventLifecycle, hook: HookLifecycle, fidelity: FidelityLifecycle, @@ -76,7 +76,7 @@ pub struct WorkflowLifecycle { impl WorkflowLifecycle { #[allow(clippy::too_many_arguments)] - pub fn new( + pub(crate) fn new( emitter: Arc, hook_runner: Option>, sandbox: Arc, @@ -187,7 +187,7 @@ impl WorkflowLifecycle { } /// Restore circuit breaker state from a checkpoint (for resume). - pub fn restore_circuit_breaker( + pub(crate) fn restore_circuit_breaker( &self, loop_sigs: HashMap, restart_sigs: HashMap, @@ -196,7 +196,7 @@ impl WorkflowLifecycle { } /// Set the fidelity degradation flag for checkpoint resume. - pub fn set_degrade_fidelity_on_resume(&self, flag: bool) { + pub(crate) fn set_degrade_fidelity_on_resume(&self, flag: bool) { self.fidelity.set_degrade_fidelity_on_resume(flag); } } diff --git a/lib/crates/fabro-workflows/src/node_handler.rs b/lib/crates/fabro-workflows/src/node_handler.rs index 06b149992..78eb6f22f 100644 --- a/lib/crates/fabro-workflows/src/node_handler.rs +++ b/lib/crates/fabro-workflows/src/node_handler.rs @@ -26,7 +26,7 @@ use tokio::time::timeout; /// /// On each `execute()` call, forks the context, runs the handler, /// then diffs and applies changes back. -pub struct WorkflowNodeHandler { +pub(crate) struct WorkflowNodeHandler { pub services: Arc, pub run_dir: PathBuf, pub graph: Arc, diff --git a/lib/crates/fabro-workflows/src/operations/fork.rs b/lib/crates/fabro-workflows/src/operations/fork.rs index e81e4d92f..df1a27834 100644 --- a/lib/crates/fabro-workflows/src/operations/fork.rs +++ b/lib/crates/fabro-workflows/src/operations/fork.rs @@ -87,7 +87,7 @@ fn fork_from_entry( let mut run_record: RunRecord = serde_json::from_slice(&run_record_bytes).context("failed to parse source run.json")?; - run_record.run_id = new_run_id.clone(); + run_record.run_id.clone_from(&new_run_id); run_record.created_at = now; let new_run_record_bytes = serde_json::to_vec_pretty(&run_record).context("failed to serialize new run.json")?; diff --git a/lib/crates/fabro-workflows/src/operations/resume.rs b/lib/crates/fabro-workflows/src/operations/resume.rs index 442fd6d23..ddeadccb2 100644 --- a/lib/crates/fabro-workflows/src/operations/resume.rs +++ b/lib/crates/fabro-workflows/src/operations/resume.rs @@ -36,7 +36,7 @@ pub async fn resume(run_dir: &Path, services: StartServices) -> Result s, - Err(_) => return, + let Ok(sig) = Signature::now("Fabro", "noreply@fabro.sh") else { + return; }; let bs = BranchStore::new(store, &run_branch, &sig); - let run_commits = match bs.log(10_000) { - Ok(c) => c, - Err(_) => return, + let Ok(run_commits) = bs.log(10_000) else { + return; }; let prefix = format!("fabro({run_id}): "); @@ -248,6 +247,7 @@ pub fn rewind(store: &Store, input: RewindInput) -> Result<()> { rewind_to_entry(store, &input.run_id, entry, input.push) } +#[allow(clippy::print_stderr)] fn rewind_to_entry(store: &Store, run_id: &str, entry: &TimelineEntry, push: bool) -> Result<()> { let meta_branch = MetadataStore::branch_name(run_id); store @@ -304,9 +304,8 @@ pub fn find_run_id_by_prefix(repo: &Repository, prefix: &str) -> Result let mut matches = Vec::new(); for reference in refs.flatten() { - let name = match reference.name() { - Some(n) => n, - None => continue, + let Some(name) = reference.name() else { + continue; }; if let Some(run_id) = name.strip_prefix(pattern) { if run_id == prefix { @@ -324,7 +323,7 @@ pub fn find_run_id_by_prefix(repo: &Repository, prefix: &str) -> Result _ => { let mut msg = format!("ambiguous run ID prefix '{prefix}', matches:\n"); for m in &matches { - msg.push_str(&format!(" {m}\n")); + let _ = writeln!(msg, " {m}"); } bail!("{msg}") } @@ -333,9 +332,8 @@ pub fn find_run_id_by_prefix(repo: &Repository, prefix: &str) -> Result fn load_parallel_map(store: &Store, run_id: &str) -> HashMap { let branch = MetadataStore::branch_name(run_id); - let sig = match Signature::now("Fabro", "noreply@fabro.sh") { - Ok(s) => s, - Err(_) => return HashMap::new(), + let Ok(sig) = Signature::now("Fabro", "noreply@fabro.sh") else { + return HashMap::new(); }; let bs = BranchStore::new(store, &branch, &sig); @@ -353,9 +351,8 @@ fn load_parallel_map(store: &Store, run_id: &str) -> HashMap { }, }; let dot_source = String::from_utf8_lossy(&graph_bytes); - let graph = match parser::parse(&dot_source) { - Ok(g) => g, - Err(_) => return HashMap::new(), + let Ok(graph) = parser::parse(&dot_source) else { + return HashMap::new(); }; detect_parallel_interior(&graph) } diff --git a/lib/crates/fabro-workflows/src/operations/start.rs b/lib/crates/fabro-workflows/src/operations/start.rs index b89120c98..a5545dc8b 100644 --- a/lib/crates/fabro-workflows/src/operations/start.rs +++ b/lib/crates/fabro-workflows/src/operations/start.rs @@ -92,7 +92,7 @@ pub async fn start(run_dir: &Path, services: StartServices) -> Result, services: StartServices, ) -> Result { - let mut bootstrap_guard = DetachedRunBootstrapGuard::arm(run_dir)?; + let mut bootstrap_guard = DetachedRunBootstrapGuard::arm(run_dir); let persisted = match Persisted::load(run_dir) { Ok(persisted) => persisted, @@ -125,7 +125,7 @@ pub(super) async fn execute_persisted_run( bootstrap_guard.defuse(); let mut completion_guard = DetachedRunCompletionGuard::arm(run_dir); let run_start = Instant::now(); - let started = session.run(persisted, checkpoint).await; + let started = Box::pin(session.run(persisted, checkpoint)).await; match started { Ok(started) => { @@ -193,9 +193,9 @@ impl RunSession { let provider_enum: Provider = provider .as_deref() - .map(|value| value.parse::()) + .map(str::parse::) .transpose() - .map_err(|err| FabroError::Precondition(err.to_string()))? + .map_err(|err| FabroError::Precondition(err.clone()))? .unwrap_or_else(Provider::default_from_env); let fallback_chain = resolve_fallback_chain(provider_enum, &model, &settings); @@ -273,7 +273,7 @@ impl RunSession { services.interviewer }; - Ok(RunSession { + Ok(Self { cancel_token: services.cancel_token, emitter: services.emitter, sandbox, @@ -315,7 +315,7 @@ fn resolve_sandbox_provider(settings: &FabroSettings) -> Result()) + .map(str::parse::) .transpose() .map_err(|err| FabroError::Precondition(format!("Invalid sandbox provider: {err}")))? .map_or_else(|| Ok(SandboxProvider::default()), Ok) @@ -530,16 +530,16 @@ struct DetachedRunBootstrapGuard { } impl DetachedRunBootstrapGuard { - fn arm(run_dir: &Path) -> Result { + fn arm(run_dir: &Path) -> Self { run_status::write_run_status( run_dir, RunStatus::Starting, Some(StatusReason::SandboxInitializing), ); - Ok(Self { + Self { run_dir: run_dir.to_path_buf(), active: true, - }) + } } fn defuse(&mut self) { diff --git a/lib/crates/fabro-workflows/src/pipeline/execute.rs b/lib/crates/fabro-workflows/src/pipeline/execute.rs index a85441579..13529daf2 100644 --- a/lib/crates/fabro-workflows/src/pipeline/execute.rs +++ b/lib/crates/fabro-workflows/src/pipeline/execute.rs @@ -114,27 +114,27 @@ pub async fn execute(init: Initialized) -> Executed { for (k, v) in &cp.context_values { s.context.set(k.clone(), v.clone()); } - s.completed_nodes = cp.completed_nodes.clone(); - s.node_retries = cp.node_retries.clone(); + s.completed_nodes.clone_from(&cp.completed_nodes); + s.node_retries.clone_from(&cp.node_retries); if cp.node_visits.is_empty() { for id in &cp.completed_nodes { *s.node_visits.entry(id.clone()).or_insert(0) += 1; } } else { - s.node_visits = cp.node_visits.clone(); + s.node_visits.clone_from(&cp.node_visits); } for (k, v) in &cp.node_outcomes { s.node_outcomes.insert(k.clone(), v.clone()); } s.stage_index = cp.completed_nodes.len(); if let Some(ref next) = cp.next_node_id { - s.current_node_id = next.clone(); + s.current_node_id.clone_from(next); } else { let edges = graph.outgoing_edges(&cp.current_node); if let Some(edge) = edges.first() { - s.current_node_id = edge.to.clone(); + s.current_node_id.clone_from(&edge.to); } else { - s.current_node_id = cp.current_node.clone(); + s.current_node_id.clone_from(&cp.current_node); } } s @@ -223,7 +223,7 @@ pub async fn execute(init: Initialized) -> Executed { tokio::spawn(async move { loop { tokio::select! { - _ = sleep(stall_timeout) => { + () = sleep(stall_timeout) => { if shutdown_clone.is_cancelled() { return; } @@ -238,7 +238,7 @@ pub async fn execute(init: Initialized) -> Executed { return; } } - _ = shutdown_clone.cancelled() => { + () = shutdown_clone.cancelled() => { return; } } diff --git a/lib/crates/fabro-workflows/src/pipeline/initialize.rs b/lib/crates/fabro-workflows/src/pipeline/initialize.rs index 2f0e4bd01..1e6e9c2a6 100644 --- a/lib/crates/fabro-workflows/src/pipeline/initialize.rs +++ b/lib/crates/fabro-workflows/src/pipeline/initialize.rs @@ -410,9 +410,9 @@ async fn mint_github_token( ) -> Result { let https_url = fabro_github::ssh_url_to_https(origin_url); let (owner, repo) = fabro_github::parse_github_owner_repo(&https_url) - .map_err(|e| FabroError::engine(e.to_string()))?; + .map_err(|e| FabroError::engine(e.clone()))?; let jwt = fabro_github::sign_app_jwt(&creds.app_id, &creds.private_key_pem) - .map_err(|e| FabroError::engine(e.to_string()))?; + .map_err(|e| FabroError::engine(e.clone()))?; let client = reqwest::Client::new(); let perms_json = serde_json::to_value(permissions).map_err(|e| FabroError::engine(e.to_string()))?; @@ -425,7 +425,7 @@ async fn mint_github_token( perms_json, ) .await - .map_err(|e| FabroError::engine(e.to_string())) + .map_err(|e| FabroError::engine(e.clone())) } async fn build_sandbox_env( @@ -586,7 +586,10 @@ async fn resolve_devcontainer(options: &mut InitOptions) -> Result<(), FabroErro } } - options.sandbox_env.devcontainer_env = config.environment.clone(); + options + .sandbox_env + .devcontainer_env + .clone_from(&config.environment); options.lifecycle.devcontainer_phases = vec![ ("on_create".to_string(), config.on_create_commands.clone()), ( @@ -753,7 +756,7 @@ pub async fn initialize( .as_ref() .and_then(|g| g.base_sha.clone()) .or(Some(info.base_sha.clone())); - options.run_options.display_base_sha = base_sha.clone(); + options.run_options.display_base_sha.clone_from(&base_sha); options.run_options.git = Some(GitCheckpointOptions { base_sha, run_branch: Some(info.run_branch.clone()), diff --git a/lib/crates/fabro-workflows/src/pipeline/persist.rs b/lib/crates/fabro-workflows/src/pipeline/persist.rs index a149ecbe1..41392e08a 100644 --- a/lib/crates/fabro-workflows/src/pipeline/persist.rs +++ b/lib/crates/fabro-workflows/src/pipeline/persist.rs @@ -11,7 +11,10 @@ const LEGACY_GRAPH_FILE_NAME: &str = "graph.fabro"; /// PERSIST phase: create run directory, write workflow.fabro and run.json to disk. /// /// Overwrites `run_record.graph` with the validated graph before saving. -pub fn persist(validated: Validated, mut options: PersistOptions) -> Result { +pub(crate) fn persist( + validated: Validated, + mut options: PersistOptions, +) -> Result { let (graph, source, diagnostics) = validated.into_parts(); options.run_record.graph = graph.clone(); diff --git a/lib/crates/fabro-workflows/src/pipeline/pull_request.rs b/lib/crates/fabro-workflows/src/pipeline/pull_request.rs index ef5f2ecb3..c7b81b4c8 100644 --- a/lib/crates/fabro-workflows/src/pipeline/pull_request.rs +++ b/lib/crates/fabro-workflows/src/pipeline/pull_request.rs @@ -249,7 +249,7 @@ fn read_dot_source(run_dir: &Path) -> Option { fn read_plan_text(run_dir: &Path) -> Option { let nodes_dir = run_dir.join("nodes"); let mut entries: Vec<_> = std::fs::read_dir(&nodes_dir).ok()?.flatten().collect(); - entries.sort_by_key(|e| e.file_name()); + entries.sort_by_key(std::fs::DirEntry::file_name); for entry in entries { let dir_name = entry.file_name(); let dir_name_str = dir_name.to_string_lossy(); @@ -548,9 +548,7 @@ pub async fn pull_request(concluded: Concluded, options: &PullRequestOptions) -> } Ok(None) => {} Err(e) => { - emitter.emit(&WorkflowRunEvent::PullRequestFailed { - error: e.to_string(), - }); + emitter.emit(&WorkflowRunEvent::PullRequestFailed { error: e.clone() }); emit_run_notice( &emitter, RunNoticeLevel::Warn, diff --git a/lib/crates/fabro-workflows/src/pipeline/types.rs b/lib/crates/fabro-workflows/src/pipeline/types.rs index 1b1a47829..09fba0cda 100644 --- a/lib/crates/fabro-workflows/src/pipeline/types.rs +++ b/lib/crates/fabro-workflows/src/pipeline/types.rs @@ -108,7 +108,7 @@ impl Validated { } /// Options for the PERSIST phase. -pub struct PersistOptions { +pub(crate) struct PersistOptions { pub run_dir: PathBuf, pub run_record: RunRecord, } diff --git a/lib/crates/fabro-workflows/src/run_dir.rs b/lib/crates/fabro-workflows/src/run_dir.rs index f50ea8bd9..f31d87eca 100644 --- a/lib/crates/fabro-workflows/src/run_dir.rs +++ b/lib/crates/fabro-workflows/src/run_dir.rs @@ -26,7 +26,7 @@ pub(crate) fn write_start_record(run_dir: &Path, settings: &RunOptions) -> Start /// /// First visit (`visit <= 1`): `{run_dir}/nodes/{node_id}` /// Subsequent visits: `{run_dir}/nodes/{node_id}-visit_{visit}` -pub fn node_dir(run_dir: &Path, node_id: &str, visit: usize) -> PathBuf { +pub(crate) fn node_dir(run_dir: &Path, node_id: &str, visit: usize) -> PathBuf { if visit <= 1 { run_dir.join("nodes").join(node_id) } else { @@ -40,7 +40,7 @@ pub fn node_dir(run_dir: &Path, node_id: &str, visit: usize) -> PathBuf { /// /// The raw context value is `0` when unset; workflow execution code treats /// missing counts as the first visit for stage/log naming. -pub fn visit_from_context(context: &Context) -> usize { +pub(crate) fn visit_from_context(context: &Context) -> usize { context.node_visit_count().max(1) } diff --git a/lib/crates/fabro-workflows/src/sandbox_git.rs b/lib/crates/fabro-workflows/src/sandbox_git.rs index 7639bedac..859daa64c 100644 --- a/lib/crates/fabro-workflows/src/sandbox_git.rs +++ b/lib/crates/fabro-workflows/src/sandbox_git.rs @@ -144,18 +144,17 @@ pub async fn git_push_host( }; let https_url = fabro_github::ssh_url_to_https(&origin_url); - let push_url = match github_app { - Some(creds) => match fabro_github::resolve_authenticated_url(creds, &https_url).await { + let push_url = if let Some(creds) = github_app { + match fabro_github::resolve_authenticated_url(creds, &https_url).await { Ok(url) => url, Err(e) => { tracing::warn!(error = %e, label, "Failed to get token for push"); return false; } - }, - None => { - tracing::warn!(label, "No GitHub App credentials for push"); - return false; } + } else { + tracing::warn!(label, "No GitHub App credentials for push"); + return false; }; let rp = repo_path.to_path_buf(); @@ -183,7 +182,7 @@ pub(crate) async fn git_diff( match sandbox.exec_command(&cmd, 30_000, None, None, None).await { Ok(r) if r.exit_code == 0 => Ok(r.stdout), Ok(r) => Err(format!("exit {}: {}", r.exit_code, r.stderr.trim())), - Err(e) => Err(e.to_string()), + Err(e) => Err(e.clone()), } } diff --git a/lib/crates/fabro-workflows/src/transforms/file_inlining.rs b/lib/crates/fabro-workflows/src/transforms/file_inlining.rs index 6bec3a9ca..66dbeacf5 100644 --- a/lib/crates/fabro-workflows/src/transforms/file_inlining.rs +++ b/lib/crates/fabro-workflows/src/transforms/file_inlining.rs @@ -10,9 +10,8 @@ use super::Transform; /// contents are returned (inlined). Otherwise the original value is returned /// unchanged. pub fn resolve_file_ref(value: &str, base_dir: &Path, fallback_dir: Option<&Path>) -> String { - let path_str = match value.strip_prefix('@') { - Some(p) => p, - None => return value.to_string(), + let Some(path_str) = value.strip_prefix('@') else { + return value.to_string(); }; // Build the raw path: expand ~ then resolve relative to base_dir diff --git a/lib/crates/fabro-workflows/src/transforms/graph_merge.rs b/lib/crates/fabro-workflows/src/transforms/graph_merge.rs index d6c8407fd..b582a7e92 100644 --- a/lib/crates/fabro-workflows/src/transforms/graph_merge.rs +++ b/lib/crates/fabro-workflows/src/transforms/graph_merge.rs @@ -23,8 +23,8 @@ impl Transform for GraphMergeTransform { for (id, node) in &secondary.nodes { let prefixed_id = format!("{prefix}.{id}"); let mut merged_node = Node::new(&prefixed_id); - merged_node.attrs = node.attrs.clone(); - merged_node.classes = node.classes.clone(); + merged_node.attrs.clone_from(&node.attrs); + merged_node.classes.clone_from(&node.classes); graph.nodes.insert(prefixed_id, merged_node); } @@ -33,7 +33,7 @@ impl Transform for GraphMergeTransform { format!("{prefix}.{}", edge.from), format!("{prefix}.{}", edge.to), ); - merged_edge.attrs = edge.attrs.clone(); + merged_edge.attrs.clone_from(&edge.attrs); graph.edges.push(merged_edge); } }