From 49e90cf3e40debded88d2d357acf9b518311b6d9 Mon Sep 17 00:00:00 2001 From: FlashFamily <152371605+FlashFamily@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:26:17 +0800 Subject: [PATCH] fix: resolve all clippy warnings across codebase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix all clippy errors reported by `cargo clippy --all-targets -- -D warnings` on Rust 1.93, covering both the original codebase and upstream dev changes. Changes by category: - format!() appended to String → write!/writeln! (telegram, discord) - Redundant field names, unnecessary boolean not (agent/loop_) - Long numeric literals (wati, nextcloud, telegram, gemini) - Wildcard match on single variant (security/leak_detector) - Derivable Default impls (config/schema) - &Option → Option<&T> or allow (config/schema, config/mod, gateway/api) - Identical match arms merged (gateway/ws, observability, providers, main, onboard) - Cast truncation allowed with rationale (discord, lark) - Unnecessary borrows/returns removed (multiple files) - Unused imports removed (channels/mod, peripherals/mod, tests) - MSRV-gated APIs allowed locally (memory/hygiene, tools/shell, tools/screenshot) - Unnecessary .get().is_none() → !contains_key() (gemini) - Explicit iteration → reference loop (gateway/api) - Test-only: useless vec!, field_reassign_with_default, doc indentation Validated: cargo fmt, cargo clippy --all-targets -- -D warnings, cargo test Co-authored-by: Cursor --- src/agent/loop_.rs | 35 +++++++------- src/channels/discord.rs | 5 +- src/channels/lark.rs | 1 + src/channels/mod.rs | 13 +++-- src/channels/nextcloud_talk.rs | 2 +- src/channels/telegram.rs | 45 +++++++++--------- src/channels/wati.rs | 16 +++---- src/config/mod.rs | 2 +- src/config/schema.rs | 66 ++++++++++---------------- src/gateway/api.rs | 3 +- src/gateway/ws.rs | 3 +- src/hardware/mod.rs | 2 +- src/main.rs | 3 +- src/memory/hygiene.rs | 1 + src/observability/runtime_trace.rs | 1 - src/onboard/wizard.rs | 10 ++-- src/peripherals/mod.rs | 3 +- src/providers/bedrock.rs | 2 +- src/providers/gemini.rs | 8 ++-- src/providers/openai_codex.rs | 1 - src/security/leak_detector.rs | 10 ++-- src/security/prompt_guard.rs | 6 +-- src/skills/audit.rs | 12 ++--- src/skills/mod.rs | 2 +- src/tools/screenshot.rs | 1 + src/tools/shell.rs | 1 + tests/config_persistence.rs | 1 + tests/config_schema.rs | 1 + tests/gemini_fallback_oauth_refresh.rs | 2 + tests/openai_codex_vision_e2e.rs | 2 +- tests/provider_schema.rs | 4 +- 31 files changed, 124 insertions(+), 140 deletions(-) diff --git a/src/agent/loop_.rs b/src/agent/loop_.rs index 3b093ffa3..add35dc6f 100644 --- a/src/agent/loop_.rs +++ b/src/agent/loop_.rs @@ -387,7 +387,7 @@ fn parse_tool_call_value(value: &serde_json::Value) -> Option { return Some(ParsedToolCall { name, arguments, - tool_call_id: tool_call_id, + tool_call_id, }); } } @@ -409,7 +409,7 @@ fn parse_tool_call_value(value: &serde_json::Value) -> Option { Some(ParsedToolCall { name, arguments, - tool_call_id: tool_call_id, + tool_call_id, }) } @@ -1527,7 +1527,13 @@ fn parse_tool_calls(response: &str) -> (String, Vec) { // Try to parse the inner content as JSON arguments let json_values = extract_json_values(inner); - if !json_values.is_empty() { + if json_values.is_empty() { + tracing::warn!( + tool_name = %tool_name, + inner = %inner.chars().take(100).collect::(), + "Found ```tool block but could not parse JSON arguments" + ); + } else { for value in json_values { let arguments = if value.is_object() { value @@ -1540,13 +1546,6 @@ fn parse_tool_calls(response: &str) -> (String, Vec) { tool_call_id: None, }); } - } else { - // Log a warning if we found a tool block but couldn't parse arguments - tracing::warn!( - tool_name = %tool_name, - inner = %inner.chars().take(100).collect::(), - "Found ```tool block but could not parse JSON arguments" - ); } last_end = full_match.end(); } @@ -2625,15 +2624,13 @@ pub(crate) async fn run_tool_call_loop( ordered_results[*idx] = Some((call.name.clone(), call.tool_call_id.clone(), outcome)); } - for entry in ordered_results { - if let Some((tool_name, tool_call_id, outcome)) = entry { - individual_results.push((tool_call_id, outcome.output.clone())); - let _ = writeln!( - tool_results, - "\n{}\n", - tool_name, outcome.output - ); - } + for (tool_name, tool_call_id, outcome) in ordered_results.into_iter().flatten() { + individual_results.push((tool_call_id, outcome.output.clone())); + let _ = writeln!( + tool_results, + "\n{}\n", + tool_name, outcome.output + ); } // Add assistant message with tool calls + tool results to history. diff --git a/src/channels/discord.rs b/src/channels/discord.rs index 297366e24..d10007e5b 100644 --- a/src/channels/discord.rs +++ b/src/channels/discord.rs @@ -362,6 +362,7 @@ fn split_message_for_discord(message: &str) -> Vec { chunks } +#[allow(clippy::cast_possible_truncation)] fn pick_uniform_index(len: usize) -> usize { debug_assert!(len > 0); let upper = len as u64; @@ -389,9 +390,10 @@ fn encode_emoji_for_discord(emoji: &str) -> String { return emoji.to_string(); } + use std::fmt::Write as _; let mut encoded = String::new(); for byte in emoji.as_bytes() { - encoded.push_str(&format!("%{byte:02X}")); + write!(encoded, "%{byte:02X}").ok(); } encoded } @@ -1366,6 +1368,7 @@ mod tests { } #[test] + #[allow(clippy::format_collect)] fn split_message_many_short_lines() { // Many short lines should be batched into chunks under the limit let msg: String = (0..500).map(|i| format!("line {i}\n")).collect(); diff --git a/src/channels/lark.rs b/src/channels/lark.rs index f1670d15c..3c829b62e 100644 --- a/src/channels/lark.rs +++ b/src/channels/lark.rs @@ -1503,6 +1503,7 @@ impl LarkChannel { // WS helper functions // ───────────────────────────────────────────────────────────────────────────── +#[allow(clippy::cast_possible_truncation)] fn pick_uniform_index(len: usize) -> usize { debug_assert!(len > 0); let upper = len as u64; diff --git a/src/channels/mod.rs b/src/channels/mod.rs index a910e8292..28573d0ee 100644 --- a/src/channels/mod.rs +++ b/src/channels/mod.rs @@ -42,7 +42,7 @@ pub mod whatsapp_storage; #[cfg(feature = "whatsapp-web")] pub mod whatsapp_web; -pub use clawdtalk::{ClawdTalkChannel, ClawdTalkConfig}; +pub use clawdtalk::ClawdTalkChannel; pub use cli::CliChannel; pub use dingtalk::DingTalkChannel; pub use discord::DiscordChannel; @@ -1295,9 +1295,7 @@ fn sanitize_tool_json_value( return None; } - let Some(object) = value.as_object() else { - return None; - }; + let object = value.as_object()?; if let Some(tool_calls) = object.get("tool_calls").and_then(|value| value.as_array()) { if !tool_calls.is_empty() @@ -1337,7 +1335,7 @@ fn strip_isolated_tool_json_artifacts(message: &str, known_tool_names: &HashSet< let mut saw_tool_call_payload = false; while cursor < message.len() { - let Some(rel_start) = message[cursor..].find(|ch: char| ch == '{' || ch == '[') else { + let Some(rel_start) = message[cursor..].find(['{', '[']) else { cleaned.push_str(&message[cursor..]); break; }; @@ -2680,9 +2678,10 @@ struct ConfiguredChannel { channel: Arc, } +#[allow(unused_variables)] fn collect_configured_channels( config: &Config, - _matrix_skip_context: &str, + matrix_skip_context: &str, ) -> Vec { let mut channels = Vec::new(); @@ -2767,7 +2766,7 @@ fn collect_configured_channels( if config.channels_config.matrix.is_some() { tracing::warn!( "Matrix channel is configured but this build was compiled without `channel-matrix`; skipping Matrix {}.", - _matrix_skip_context + matrix_skip_context ); } diff --git a/src/channels/nextcloud_talk.rs b/src/channels/nextcloud_talk.rs index 574a5b6f1..07070ad8d 100644 --- a/src/channels/nextcloud_talk.rs +++ b/src/channels/nextcloud_talk.rs @@ -429,7 +429,7 @@ mod tests { "message": { "actorType": "users", "actorId": "user_a", - "timestamp": 1735701200123u64, + "timestamp": 1_735_701_200_123_u64, "message": "hello" } }); diff --git a/src/channels/telegram.rs b/src/channels/telegram.rs index 1bd6d2b32..6ad7078ed 100644 --- a/src/channels/telegram.rs +++ b/src/channels/telegram.rs @@ -6,6 +6,7 @@ use async_trait::async_trait; use directories::UserDirs; use parking_lot::Mutex; use reqwest::multipart::{Form, Part}; +use std::fmt::Write as _; use std::path::Path; use std::sync::{Arc, RwLock}; use std::time::Duration; @@ -100,6 +101,7 @@ fn pick_uniform_index(len: usize) -> usize { loop { let value = rand::random::(); if value < reject_threshold { + #[allow(clippy::cast_possible_truncation)] return (value % upper) as usize; } } @@ -1266,7 +1268,7 @@ Allowlist Telegram username (without '@') or numeric user ID.", if self.mention_only && is_group { let bot_username = self.bot_username.lock(); if let Some(ref bot_username) = *bot_username { - if !Self::contains_bot_mention(&text, bot_username) { + if !Self::contains_bot_mention(text, bot_username) { return None; } } else { @@ -1301,7 +1303,7 @@ Allowlist Telegram username (without '@') or numeric user ID.", let content = if self.mention_only && is_group { let bot_username = self.bot_username.lock(); let bot_username = bot_username.as_ref()?; - Self::normalize_incoming_content(&text, bot_username)? + Self::normalize_incoming_content(text, bot_username)? } else { text.to_string() }; @@ -1409,7 +1411,7 @@ Allowlist Telegram username (without '@') or numeric user ID.", if i + 1 < len && bytes[i] == b'*' && bytes[i + 1] == b'*' { if let Some(end) = line[i + 2..].find("**") { let inner = Self::escape_html(&line[i + 2..i + 2 + end]); - line_out.push_str(&format!("{inner}")); + write!(line_out, "{inner}").unwrap(); i += 4 + end; continue; } @@ -1417,7 +1419,7 @@ Allowlist Telegram username (without '@') or numeric user ID.", if i + 1 < len && bytes[i] == b'_' && bytes[i + 1] == b'_' { if let Some(end) = line[i + 2..].find("__") { let inner = Self::escape_html(&line[i + 2..i + 2 + end]); - line_out.push_str(&format!("{inner}")); + write!(line_out, "{inner}").unwrap(); i += 4 + end; continue; } @@ -1427,7 +1429,7 @@ Allowlist Telegram username (without '@') or numeric user ID.", if let Some(end) = line[i + 1..].find('*') { if end > 0 { let inner = Self::escape_html(&line[i + 1..i + 1 + end]); - line_out.push_str(&format!("{inner}")); + write!(line_out, "{inner}").unwrap(); i += 2 + end; continue; } @@ -1437,7 +1439,7 @@ Allowlist Telegram username (without '@') or numeric user ID.", if bytes[i] == b'`' && (i == 0 || bytes[i - 1] != b'`') { if let Some(end) = line[i + 1..].find('`') { let inner = Self::escape_html(&line[i + 1..i + 1 + end]); - line_out.push_str(&format!("{inner}")); + write!(line_out, "{inner}").unwrap(); i += 2 + end; continue; } @@ -1453,9 +1455,8 @@ Allowlist Telegram username (without '@') or numeric user ID.", if url.starts_with("http://") || url.starts_with("https://") { let text_html = Self::escape_html(text_part); let url_html = Self::escape_html(url); - line_out.push_str(&format!( - "{text_html}" - )); + write!(line_out, "{text_html}") + .unwrap(); i = after_bracket + 1 + paren_end + 1; continue; } @@ -1467,7 +1468,7 @@ Allowlist Telegram username (without '@') or numeric user ID.", if i + 1 < len && bytes[i] == b'~' && bytes[i + 1] == b'~' { if let Some(end) = line[i + 2..].find("~~") { let inner = Self::escape_html(&line[i + 2..i + 2 + end]); - line_out.push_str(&format!("{inner}")); + write!(line_out, "{inner}").unwrap(); i += 4 + end; continue; } @@ -1496,14 +1497,14 @@ Allowlist Telegram username (without '@') or numeric user ID.", for line in joined.split('\n') { let trimmed = line.trim(); if trimmed.starts_with("```") { - if !in_code_block { - in_code_block = true; - code_buf.clear(); - } else { + if in_code_block { in_code_block = false; let escaped = code_buf.trim_end_matches('\n'); // Telegram HTML parse mode supports
 and , but not class attributes.
-                    final_out.push_str(&format!("
{escaped}
\n")); + writeln!(final_out, "
{escaped}
").unwrap(); + code_buf.clear(); + } else { + in_code_block = true; code_buf.clear(); } } else if in_code_block { @@ -1515,10 +1516,7 @@ Allowlist Telegram username (without '@') or numeric user ID.", } } if in_code_block && !code_buf.is_empty() { - final_out.push_str(&format!( - "
{}
\n", - code_buf.trim_end() - )); + writeln!(final_out, "
{}
", code_buf.trim_end()).unwrap(); } final_out.trim_end_matches('\n').to_string() @@ -2749,7 +2747,7 @@ mod tests { "update_id": 1, "message": { "message_id": 99, - "chat": { "id": -100123456 } + "chat": { "id": -100_123_456 } } }); @@ -3864,7 +3862,10 @@ mod tests { #[test] fn telegram_split_many_short_lines() { - let msg: String = (0..1000).map(|i| format!("line {i}\n")).collect(); + let msg: String = (0..1000) + .map(|i| format!("line {i}\n")) + .collect::>() + .concat(); let parts = split_message_for_telegram(&msg); for part in &parts { assert!( @@ -4170,7 +4171,7 @@ mod tests { /// Skipped automatically when `GROQ_API_KEY` is not set. /// Run: `GROQ_API_KEY= cargo test --lib -- telegram::tests::e2e_live_voice_transcription_and_reply_cache --ignored` #[tokio::test] - #[ignore] + #[ignore = "requires GROQ_API_KEY"] async fn e2e_live_voice_transcription_and_reply_cache() { if std::env::var("GROQ_API_KEY").is_err() { eprintln!("GROQ_API_KEY not set — skipping live voice transcription test"); diff --git a/src/channels/wati.rs b/src/channels/wati.rs index 6e3037027..78903bc6b 100644 --- a/src/channels/wati.rs +++ b/src/channels/wati.rs @@ -335,7 +335,7 @@ mod tests { "text": "Hello from WATI!", "waId": "1234567890", "fromMe": false, - "timestamp": 1705320000u64 + "timestamp": 1_705_320_000_u64 }); let msgs = ch.parse_webhook_payload(&payload); @@ -344,7 +344,7 @@ mod tests { assert_eq!(msgs[0].content, "Hello from WATI!"); assert_eq!(msgs[0].channel, "wati"); assert_eq!(msgs[0].reply_target, "+1234567890"); - assert_eq!(msgs[0].timestamp, 1705320000); + assert_eq!(msgs[0].timestamp, 1_705_320_000); } #[test] @@ -381,7 +381,7 @@ mod tests { "message": { "body": "Alt field test" }, "wa_id": "1234567890", "from_me": false, - "timestamp": 1705320000u64 + "timestamp": 1_705_320_000_u64 }); let msgs = ch.parse_webhook_payload(&payload); @@ -396,11 +396,11 @@ mod tests { let payload = serde_json::json!({ "text": "Test", "waId": "1234567890", - "timestamp": 1705320000u64 + "timestamp": 1_705_320_000_u64 }); let msgs = ch.parse_webhook_payload(&payload); - assert_eq!(msgs[0].timestamp, 1705320000); + assert_eq!(msgs[0].timestamp, 1_705_320_000); } #[test] @@ -409,11 +409,11 @@ mod tests { let payload = serde_json::json!({ "text": "Test", "waId": "1234567890", - "timestamp": 1705320000000u64 + "timestamp": 1_705_320_000_000_u64 }); let msgs = ch.parse_webhook_payload(&payload); - assert_eq!(msgs[0].timestamp, 1705320000); + assert_eq!(msgs[0].timestamp, 1_705_320_000); } #[test] @@ -426,7 +426,7 @@ mod tests { }); let msgs = ch.parse_webhook_payload(&payload); - assert_eq!(msgs[0].timestamp, 1736942400); + assert_eq!(msgs[0].timestamp, 1_736_942_400); } #[test] diff --git a/src/config/mod.rs b/src/config/mod.rs index 9fe1b25c6..0b1564913 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -19,7 +19,7 @@ pub use schema::{ WebFetchConfig, WebSearchConfig, WebhookConfig, }; -pub fn name_and_presence(channel: &Option) -> (&'static str, bool) { +pub fn name_and_presence(channel: Option<&T>) -> (&'static str, bool) { (T::name(), channel.is_some()) } diff --git a/src/config/schema.rs b/src/config/schema.rs index 4c90dbbd8..b58b8f48d 100644 --- a/src/config/schema.rs +++ b/src/config/schema.rs @@ -484,7 +484,7 @@ fn parse_skills_prompt_injection_mode(raw: &str) -> Option Self { - Self { - open_skills_enabled: false, - open_skills_dir: None, - prompt_injection_mode: SkillsPromptInjectionMode::default(), - } - } -} - /// Multimodal (image) handling configuration (`[multimodal]` section). #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct MultimodalConfig { @@ -1999,20 +1989,12 @@ impl Default for HooksConfig { } } -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)] pub struct BuiltinHooksConfig { /// Enable the command-logger hook (logs tool calls for auditing). pub command_logger: bool, } -impl Default for BuiltinHooksConfig { - fn default() -> Self { - Self { - command_logger: false, - } - } -} - // ── Autonomy / Security ────────────────────────────────────────── /// Autonomy and security policy configuration (`[autonomy]` section). @@ -2591,7 +2573,7 @@ pub struct CustomTunnelConfig { struct ConfigWrapper(std::marker::PhantomData); impl ConfigWrapper { - fn new(_: &Option) -> Self { + fn new(_: Option<&T>) -> Self { Self(std::marker::PhantomData) } } @@ -2667,81 +2649,81 @@ impl ChannelsConfig { pub fn channels_except_webhook(&self) -> Vec<(Box, bool)> { vec![ ( - Box::new(ConfigWrapper::new(&self.telegram)), + Box::new(ConfigWrapper::new(self.telegram.as_ref())), self.telegram.is_some(), ), ( - Box::new(ConfigWrapper::new(&self.discord)), + Box::new(ConfigWrapper::new(self.discord.as_ref())), self.discord.is_some(), ), ( - Box::new(ConfigWrapper::new(&self.slack)), + Box::new(ConfigWrapper::new(self.slack.as_ref())), self.slack.is_some(), ), ( - Box::new(ConfigWrapper::new(&self.mattermost)), + Box::new(ConfigWrapper::new(self.mattermost.as_ref())), self.mattermost.is_some(), ), ( - Box::new(ConfigWrapper::new(&self.imessage)), + Box::new(ConfigWrapper::new(self.imessage.as_ref())), self.imessage.is_some(), ), ( - Box::new(ConfigWrapper::new(&self.matrix)), + Box::new(ConfigWrapper::new(self.matrix.as_ref())), self.matrix.is_some(), ), ( - Box::new(ConfigWrapper::new(&self.signal)), + Box::new(ConfigWrapper::new(self.signal.as_ref())), self.signal.is_some(), ), ( - Box::new(ConfigWrapper::new(&self.whatsapp)), + Box::new(ConfigWrapper::new(self.whatsapp.as_ref())), self.whatsapp.is_some(), ), ( - Box::new(ConfigWrapper::new(&self.linq)), + Box::new(ConfigWrapper::new(self.linq.as_ref())), self.linq.is_some(), ), ( - Box::new(ConfigWrapper::new(&self.wati)), + Box::new(ConfigWrapper::new(self.wati.as_ref())), self.wati.is_some(), ), ( - Box::new(ConfigWrapper::new(&self.nextcloud_talk)), + Box::new(ConfigWrapper::new(self.nextcloud_talk.as_ref())), self.nextcloud_talk.is_some(), ), ( - Box::new(ConfigWrapper::new(&self.email)), + Box::new(ConfigWrapper::new(self.email.as_ref())), self.email.is_some(), ), ( - Box::new(ConfigWrapper::new(&self.irc)), + Box::new(ConfigWrapper::new(self.irc.as_ref())), self.irc.is_some() ), ( - Box::new(ConfigWrapper::new(&self.lark)), + Box::new(ConfigWrapper::new(self.lark.as_ref())), self.lark.is_some(), ), ( - Box::new(ConfigWrapper::new(&self.feishu)), + Box::new(ConfigWrapper::new(self.feishu.as_ref())), self.feishu.is_some(), ), ( - Box::new(ConfigWrapper::new(&self.dingtalk)), + Box::new(ConfigWrapper::new(self.dingtalk.as_ref())), self.dingtalk.is_some(), ), ( - Box::new(ConfigWrapper::new(&self.qq)), + Box::new(ConfigWrapper::new(self.qq.as_ref())), self.qq .as_ref() .is_some_and(|qq| qq.receive_mode == QQReceiveMode::Websocket) ), ( - Box::new(ConfigWrapper::new(&self.nostr)), + Box::new(ConfigWrapper::new(self.nostr.as_ref())), self.nostr.is_some(), ), ( - Box::new(ConfigWrapper::new(&self.clawdtalk)), + Box::new(ConfigWrapper::new(self.clawdtalk.as_ref())), self.clawdtalk.is_some(), ), ] @@ -2750,7 +2732,7 @@ impl ChannelsConfig { pub fn channels(&self) -> Vec<(Box, bool)> { let mut ret = self.channels_except_webhook(); ret.push(( - Box::new(ConfigWrapper::new(&self.webhook)), + Box::new(ConfigWrapper::new(self.webhook.as_ref())), self.webhook.is_some(), )); ret @@ -4915,7 +4897,7 @@ async fn sync_directory(path: &Path) -> Result<()> { dir.sync_all() .await .with_context(|| format!("Failed to fsync directory metadata: {}", path.display()))?; - return Ok(()); + Ok(()) } #[cfg(not(unix))] diff --git a/src/gateway/api.rs b/src/gateway/api.rs index 1246a524c..ede4d179c 100644 --- a/src/gateway/api.rs +++ b/src/gateway/api.rs @@ -575,6 +575,7 @@ fn mask_vec_secrets(values: &mut [String]) { } } +#[allow(clippy::ref_option)] fn restore_optional_secret(value: &mut Option, current: &Option) { if value.as_deref().is_some_and(is_masked_secret) { *value = current.clone(); @@ -692,7 +693,7 @@ fn restore_masked_sensitive_fields( ¤t.storage.provider.config.db_url, ); - for (name, agent) in incoming.agents.iter_mut() { + for (name, agent) in &mut incoming.agents { if let Some(current_agent) = current.agents.get(name) { restore_optional_secret(&mut agent.api_key, ¤t_agent.api_key); } diff --git a/src/gateway/ws.rs b/src/gateway/ws.rs index 32a81d4e2..79fee5105 100644 --- a/src/gateway/ws.rs +++ b/src/gateway/ws.rs @@ -71,8 +71,7 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) { while let Some(msg) = socket.recv().await { let msg = match msg { Ok(Message::Text(text)) => text, - Ok(Message::Close(_)) => break, - Err(_) => break, + Ok(Message::Close(_)) | Err(_) => break, _ => continue, }; diff --git a/src/hardware/mod.rs b/src/hardware/mod.rs index 67407a734..a1fa82314 100644 --- a/src/hardware/mod.rs +++ b/src/hardware/mod.rs @@ -109,7 +109,7 @@ pub fn handle_command(cmd: crate::HardwareCommands, _config: &Config) -> Result< let _ = &cmd; println!("Hardware discovery requires the 'hardware' feature."); println!("Build with: cargo build --features hardware"); - return Ok(()); + Ok(()) } #[cfg(all( diff --git a/src/main.rs b/src/main.rs index 484000cec..a87f1b78d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -794,8 +794,7 @@ async fn main() -> Result<()> { } match cli.command { - Commands::Onboard { .. } => unreachable!(), - Commands::Completions { .. } => unreachable!(), + Commands::Onboard { .. } | Commands::Completions { .. } => unreachable!(), Commands::Agent { message, diff --git a/src/memory/hygiene.rs b/src/memory/hygiene.rs index 697cfeffa..83b5b4896 100644 --- a/src/memory/hygiene.rs +++ b/src/memory/hygiene.rs @@ -324,6 +324,7 @@ fn memory_date_from_filename(filename: &str) -> Option { NaiveDate::parse_from_str(date_part, "%Y-%m-%d").ok() } +#[allow(clippy::incompatible_msrv)] fn date_prefix(filename: &str) -> Option { if filename.len() < 10 { return None; diff --git a/src/observability/runtime_trace.rs b/src/observability/runtime_trace.rs index 0bcdd55bf..e3ca98146 100644 --- a/src/observability/runtime_trace.rs +++ b/src/observability/runtime_trace.rs @@ -22,7 +22,6 @@ pub enum RuntimeTraceStorageMode { impl RuntimeTraceStorageMode { fn from_raw(raw: &str) -> Self { match raw.trim().to_ascii_lowercase().as_str() { - "none" => Self::None, "rolling" => Self::Rolling, "full" => Self::Full, _ => Self::None, diff --git a/src/onboard/wizard.rs b/src/onboard/wizard.rs index cf04c337b..acd88465a 100644 --- a/src/onboard/wizard.rs +++ b/src/onboard/wizard.rs @@ -1175,8 +1175,7 @@ fn models_endpoint_for_provider(provider_name: &str) -> Option<&'static str> { "glm-cn" | "bigmodel" => Some("https://open.bigmodel.cn/api/paas/v4/models"), "zai-cn" | "z.ai-cn" => Some("https://open.bigmodel.cn/api/coding/paas/v4/models"), _ => match canonical_provider_name(provider_name) { - "openai-codex" => Some("https://api.openai.com/v1/models"), - "openai" => Some("https://api.openai.com/v1/models"), + "openai-codex" | "openai" => Some("https://api.openai.com/v1/models"), "venice" => Some("https://api.venice.ai/api/v1/models"), "groq" => Some("https://api.groq.com/openai/v1/models"), "mistral" => Some("https://api.mistral.ai/v1/models"), @@ -2008,7 +2007,7 @@ fn resolve_interactive_onboarding_mode( " Existing config found at {}. Select setup mode", config_path.display() )) - .items(&options) + .items(options) .default(1) .interact()?; @@ -2856,8 +2855,7 @@ fn provider_env_var(name: &str) -> &'static str { match canonical_provider_name(name) { "openrouter" => "OPENROUTER_API_KEY", "anthropic" => "ANTHROPIC_API_KEY", - "openai-codex" => "OPENAI_API_KEY", - "openai" => "OPENAI_API_KEY", + "openai-codex" | "openai" => "OPENAI_API_KEY", "ollama" => "OLLAMA_API_KEY", "llamacpp" => "LLAMACPP_API_KEY", "sglang" => "SGLANG_API_KEY", @@ -5831,7 +5829,7 @@ mod tests { apply_provider_update( &mut config, "anthropic".to_string(), - "".to_string(), + String::new(), "claude-sonnet-4-5-20250929".to_string(), None, ); diff --git a/src/peripherals/mod.rs b/src/peripherals/mod.rs index 8c3a59a8d..484edfa1b 100644 --- a/src/peripherals/mod.rs +++ b/src/peripherals/mod.rs @@ -24,8 +24,6 @@ pub mod uno_q_setup; #[cfg(all(feature = "peripheral-rpi", target_os = "linux"))] pub mod rpi; -pub use traits::Peripheral; - use crate::config::{Config, PeripheralBoardConfig, PeripheralsConfig}; #[cfg(feature = "hardware")] use crate::tools::HardwareMemoryMapTool; @@ -228,6 +226,7 @@ pub async fn create_peripheral_tools(config: &PeripheralsConfig) -> Result Result>> { Ok(Vec::new()) } diff --git a/src/providers/bedrock.rs b/src/providers/bedrock.rs index d58220e4a..9fbdc7447 100644 --- a/src/providers/bedrock.rs +++ b/src/providers/bedrock.rs @@ -710,12 +710,12 @@ impl BedrockProvider { let after_semi = &rest[semi + 1..]; if let Some(b64) = after_semi.strip_prefix("base64,") { let format = match mime { - "image/jpeg" | "image/jpg" => "jpeg", "image/png" => "png", "image/gif" => "gif", "image/webp" => "webp", _ => "jpeg", }; + blocks.push(ContentBlock::Image(ImageWrapper { image: ImageBlock { format: format.to_string(), diff --git a/src/providers/gemini.rs b/src/providers/gemini.rs index b4a030517..31ab5becc 100644 --- a/src/providers/gemini.rs +++ b/src/providers/gemini.rs @@ -1375,8 +1375,8 @@ mod tests { fn oauth_refresh_form_omits_client_credentials_when_missing() { let form = build_oauth_refresh_form("refresh-token", None, None); let map: std::collections::HashMap<_, _> = form.into_iter().collect(); - assert!(map.get("client_id").is_none()); - assert!(map.get("client_secret").is_none()); + assert!(!map.contains_key("client_id")); + assert!(!map.contains_key("client_secret")); } #[test] @@ -1802,7 +1802,7 @@ mod tests { let creds: GeminiCliOAuthCreds = serde_json::from_str(json).unwrap(); assert_eq!(creds.access_token.as_deref(), Some("ya29.test-token")); assert_eq!(creds.refresh_token.as_deref(), Some("1//test-refresh")); - assert_eq!(creds.expiry_date, Some(4102444800000)); + assert_eq!(creds.expiry_date, Some(4_102_444_800_000)); assert!(creds.expiry.is_none()); } @@ -1821,7 +1821,7 @@ mod tests { assert_eq!(creds.id_token.as_deref(), Some("header.payload.sig")); assert_eq!(creds.client_id.as_deref(), Some("test-client-id")); assert_eq!(creds.client_secret.as_deref(), Some("test-client-secret")); - assert_eq!(creds.expiry_date, Some(4102444800000)); + assert_eq!(creds.expiry_date, Some(4_102_444_800_000)); } #[test] diff --git a/src/providers/openai_codex.rs b/src/providers/openai_codex.rs index 41e5dcfb4..ff45a613d 100644 --- a/src/providers/openai_codex.rs +++ b/src/providers/openai_codex.rs @@ -281,7 +281,6 @@ fn clamp_reasoning_effort(model: &str, effort: &str) -> String { return match effort { "low" | "medium" | "high" => effort.to_string(), "minimal" => "low".to_string(), - "xhigh" => "high".to_string(), _ => "high".to_string(), }; } diff --git a/src/security/leak_detector.rs b/src/security/leak_detector.rs index 6849630d3..fba74bbb7 100644 --- a/src/security/leak_detector.rs +++ b/src/security/leak_detector.rs @@ -311,7 +311,7 @@ mod tests { assert!(patterns.iter().any(|p| p.contains("Stripe"))); assert!(redacted.contains("[REDACTED")); } - _ => panic!("Should detect Stripe key"), + LeakResult::Clean => panic!("Should detect Stripe key"), } } @@ -324,7 +324,7 @@ mod tests { LeakResult::Detected { patterns, .. } => { assert!(patterns.iter().any(|p| p.contains("AWS"))); } - _ => panic!("Should detect AWS key"), + LeakResult::Clean => panic!("Should detect AWS key"), } } @@ -342,7 +342,7 @@ MIIEowIBAAKCAQEA0ZPr5JeyVDonXsKhfq... assert!(patterns.iter().any(|p| p.contains("private key"))); assert!(redacted.contains("[REDACTED_PRIVATE_KEY]")); } - _ => panic!("Should detect private key"), + LeakResult::Clean => panic!("Should detect private key"), } } @@ -356,7 +356,7 @@ MIIEowIBAAKCAQEA0ZPr5JeyVDonXsKhfq... assert!(patterns.iter().any(|p| p.contains("JWT"))); assert!(redacted.contains("[REDACTED_JWT]")); } - _ => panic!("Should detect JWT"), + LeakResult::Clean => panic!("Should detect JWT"), } } @@ -369,7 +369,7 @@ MIIEowIBAAKCAQEA0ZPr5JeyVDonXsKhfq... LeakResult::Detected { patterns, .. } => { assert!(patterns.iter().any(|p| p.contains("PostgreSQL"))); } - _ => panic!("Should detect database URL"), + LeakResult::Clean => panic!("Should detect database URL"), } } diff --git a/src/security/prompt_guard.rs b/src/security/prompt_guard.rs index 56c29a7af..ac5bbaf0d 100644 --- a/src/security/prompt_guard.rs +++ b/src/security/prompt_guard.rs @@ -114,7 +114,9 @@ impl PromptGuard { // Normalize score to 0.0-1.0 range (max possible is 6.0, one per category) let normalized_score = (total_score / 6.0).min(1.0); - if !detected_patterns.is_empty() { + if detected_patterns.is_empty() { + GuardResult::Safe + } else { match self.action { GuardAction::Block if max_score > self.sensitivity => { GuardResult::Blocked(format!( @@ -125,8 +127,6 @@ impl PromptGuard { } _ => GuardResult::Suspicious(detected_patterns, normalized_score), } - } else { - GuardResult::Safe } } diff --git a/src/skills/audit.rs b/src/skills/audit.rs index 45b10a646..e8883e571 100644 --- a/src/skills/audit.rs +++ b/src/skills/audit.rs @@ -200,12 +200,12 @@ fn audit_manifest_file(root: &Path, path: &Path, report: &mut SkillAuditReport) .push(format!("{rel}: tools[{idx}] is missing a command field.")); } - if kind.eq_ignore_ascii_case("script") || kind.eq_ignore_ascii_case("shell") { - if command.is_some_and(|value| value.trim().is_empty()) { - report - .findings - .push(format!("{rel}: tools[{idx}] has an empty {kind} command.")); - } + if (kind.eq_ignore_ascii_case("script") || kind.eq_ignore_ascii_case("shell")) + && command.is_some_and(|value| value.trim().is_empty()) + { + report + .findings + .push(format!("{rel}: tools[{idx}] has an empty {kind} command.")); } } } diff --git a/src/skills/mod.rs b/src/skills/mod.rs index a97d520c8..9d84055fc 100644 --- a/src/skills/mod.rs +++ b/src/skills/mod.rs @@ -238,7 +238,7 @@ fn open_skills_enabled_from_sources( env_override: Option<&str>, ) -> bool { if let Some(raw) = env_override { - if let Some(enabled) = parse_open_skills_enabled(&raw) { + if let Some(enabled) = parse_open_skills_enabled(raw) { return enabled; } if !raw.trim().is_empty() { diff --git a/src/tools/screenshot.rs b/src/tools/screenshot.rs index c58a3587c..3b8133b88 100644 --- a/src/tools/screenshot.rs +++ b/src/tools/screenshot.rs @@ -150,6 +150,7 @@ impl ScreenshotTool { } /// Read the screenshot file and return base64-encoded result. + #[allow(clippy::incompatible_msrv)] async fn read_and_encode(output_path: &std::path::Path) -> anyhow::Result { // Check file size before reading to prevent OOM on large screenshots const MAX_RAW_BYTES: u64 = 1_572_864; // ~1.5 MB (base64 expands ~33%) diff --git a/src/tools/shell.rs b/src/tools/shell.rs index cf4e60c2d..7b87251d8 100644 --- a/src/tools/shell.rs +++ b/src/tools/shell.rs @@ -85,6 +85,7 @@ impl Tool for ShellTool { }) } + #[allow(clippy::incompatible_msrv)] async fn execute(&self, args: serde_json::Value) -> anyhow::Result { let command = args .get("command") diff --git a/tests/config_persistence.rs b/tests/config_persistence.rs index 079b9dfc6..d70010eba 100644 --- a/tests/config_persistence.rs +++ b/tests/config_persistence.rs @@ -1,3 +1,4 @@ +#![allow(clippy::field_reassign_with_default)] //! TG2: Config Load/Save Round-Trip Tests //! //! Prevents: Pattern 2 — Config persistence & workspace discovery bugs (13% of user bugs). diff --git a/tests/config_schema.rs b/tests/config_schema.rs index 6627312c0..e56ab6751 100644 --- a/tests/config_schema.rs +++ b/tests/config_schema.rs @@ -1,3 +1,4 @@ +#![allow(clippy::field_reassign_with_default)] //! Config Schema Boundary Tests //! //! Validates: config defaults, backward compatibility, invalid input rejection, diff --git a/tests/gemini_fallback_oauth_refresh.rs b/tests/gemini_fallback_oauth_refresh.rs index 612c44818..fde98dbea 100644 --- a/tests/gemini_fallback_oauth_refresh.rs +++ b/tests/gemini_fallback_oauth_refresh.rs @@ -4,7 +4,9 @@ //! 1. Primary provider (OpenAI Codex) fails //! 2. Fallback to Gemini is triggered //! 3. Gemini OAuth tokens are expired (we manually expire them) +//! //! Then: +//! //! - Gemini provider's warmup() automatically refreshes the tokens //! - The fallback request succeeds //! diff --git a/tests/openai_codex_vision_e2e.rs b/tests/openai_codex_vision_e2e.rs index af1ed7813..3f99a2be6 100644 --- a/tests/openai_codex_vision_e2e.rs +++ b/tests/openai_codex_vision_e2e.rs @@ -12,7 +12,7 @@ //! Run manually: `cargo test provider_vision -- --ignored --nocapture` use anyhow::Result; -use zeroclaw::providers::{ChatMessage, ChatRequest, Provider, ProviderRuntimeOptions}; +use zeroclaw::providers::{ChatMessage, ChatRequest, ProviderRuntimeOptions}; /// Tests that provider supports vision input. /// diff --git a/tests/provider_schema.rs b/tests/provider_schema.rs index f92c5062a..adffd0d83 100644 --- a/tests/provider_schema.rs +++ b/tests/provider_schema.rs @@ -292,7 +292,7 @@ fn provider_construction_with_different_auth_styles() { #[test] fn chat_messages_maintain_role_sequence() { - let history = vec![ + let history = [ ChatMessage::system("You are helpful"), ChatMessage::user("What is Rust?"), ChatMessage::assistant("Rust is a systems programming language"), @@ -309,7 +309,7 @@ fn chat_messages_maintain_role_sequence() { #[test] fn chat_messages_with_tool_calls_maintain_sequence() { - let history = vec![ + let history = [ ChatMessage::system("You are helpful"), ChatMessage::user("Search for Rust"), ChatMessage::assistant("I'll search for that"),