From 5c5ca38fe8fc8faa0f76b316fb2924a05506740b Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 1 Jun 2026 22:02:21 +0200 Subject: [PATCH] fix(#999): resolve all clippy warnings across the workspace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All crates now pass `cargo clippy --workspace -- -D warnings` cleanly. Fixes span six crates (hive-sh4re, hive-ag3nt, hive-c0re, hive-forge, hive-priv, hive-matrix-mcp was already clean): - doc_markdown: wrap snake_case, type names, constants in backticks - collapsible_if / collapsible_match: fold nested ifs into let-chains - duration_suboptimal_units: Duration::from_secs(N) → from_mins/from_hours - implicit_hasher: allow on HashMap-param fns where generalization is risky - items_after_statements: hoist use to function tops - map(f).unwrap_or(x) → map_or(x, f); map(f).unwrap_or_else(g) → map_or_else - is_ok_and / is_none_or in place of map().unwrap_or(bool) - needless_continue: {} instead of continue in loop match arms - match_same_arms: Ok(None) | Err(_) merged - format_push_str: write!() instead of push_str(&format!()) - while let replaces loop { let Some(..) = x else { break } } - struct_excessive_bools / dead_code: allow on purpose-built structs - too_many_lines / too_many_arguments: allow where refactor not worth it - unused_async: remove async from poll_once in bash_runner - needless_borrow: fix &repo deref in hive-forge comments verb - cast_possible_truncation: allow u64→usize in fetch_tail Co-Authored-By: Claude Sonnet 4.6 --- hive-ag3nt/src/bash_runner.rs | 4 +-- hive-ag3nt/src/bin/hive.rs | 6 ++-- hive-ag3nt/src/events.rs | 12 +++---- hive-ag3nt/src/forge_notify.rs | 5 ++- hive-ag3nt/src/mcp.rs | 38 +++++++++++------------ hive-ag3nt/src/turn.rs | 5 ++- hive-ag3nt/src/web_ui.rs | 7 ++--- hive-c0re/src/agent_server.rs | 8 ++--- hive-c0re/src/auto_update.rs | 6 ++-- hive-c0re/src/bash_tasks_vacuum.rs | 12 +++---- hive-c0re/src/build_logs.rs | 2 +- hive-c0re/src/capabilities.rs | 2 +- hive-c0re/src/container_view.rs | 12 +++---- hive-c0re/src/dashboard.rs | 14 ++++----- hive-c0re/src/events_vacuum.rs | 2 +- hive-c0re/src/flake_check.rs | 3 +- hive-c0re/src/forge.rs | 3 +- hive-c0re/src/gateway_nginx.rs | 2 +- hive-c0re/src/lifecycle.rs | 13 ++++---- hive-c0re/src/main.rs | 2 +- hive-c0re/src/manager_server.rs | 2 +- hive-c0re/src/meta.rs | 2 +- hive-c0re/src/migrate.rs | 8 ++--- hive-c0re/src/scheduled_prompts_worker.rs | 2 +- hive-c0re/src/stats_vacuum.rs | 2 +- hive-c0re/src/tool_groups.rs | 2 +- hive-forge/src/verbs/comments.rs | 5 +-- hive-forge/src/verbs/diff.rs | 6 ++-- hive-forge/src/verbs/list.rs | 12 ++++--- hive-forge/src/verbs/timeline.rs | 3 +- hive-priv/src/main.rs | 38 ++++++++++++----------- hive-sh4re/src/lib.rs | 8 ++--- 32 files changed, 121 insertions(+), 127 deletions(-) diff --git a/hive-ag3nt/src/bash_runner.rs b/hive-ag3nt/src/bash_runner.rs index 99b7b6b7..60fd0179 100644 --- a/hive-ag3nt/src/bash_runner.rs +++ b/hive-ag3nt/src/bash_runner.rs @@ -229,7 +229,7 @@ async fn run_loop(socket: PathBuf) { let claimed: Arc>> = Arc::new(Mutex::new(HashSet::new())); loop { - poll_once(&socket, &claimed).await; + poll_once(&socket, &claimed); tokio::time::sleep(POLL_INTERVAL).await; } } @@ -258,7 +258,7 @@ async fn mark_interrupted(socket: &Path) { } } -async fn poll_once(socket: &Path, claimed: &Arc>>) { +fn poll_once(socket: &Path, claimed: &Arc>>) { let Ok(rd) = std::fs::read_dir(tasks_dir()) else { return }; for entry in rd.flatten() { let path = entry.path(); diff --git a/hive-ag3nt/src/bin/hive.rs b/hive-ag3nt/src/bin/hive.rs index f73266a0..d2ad6ce1 100644 --- a/hive-ag3nt/src/bin/hive.rs +++ b/hive-ag3nt/src/bin/hive.rs @@ -117,7 +117,7 @@ async fn main() -> Result<()> { /// Surface a `SYSTEM_SENDER` message in the live event bus + tracing /// log. Both agents and the manager receive `QuestionAnswered`, /// `ContainerCrash`, reparent notifications, and friends; the parse -/// + log path is identical. Quiet no-op when `from` isn't +/// and log path is identical. Quiet no-op when `from` isn't /// `SYSTEM_SENDER`. fn log_system_event(bus: &Bus, from: &str, body: &str) { if from != SYSTEM_SENDER { @@ -165,7 +165,7 @@ fn consume_continue_sentinel() -> bool { /// What a `Recv` long-poll returned. Decoupled from the per-role /// Response enum so `serve_loop` can pattern-match without seeing -/// either AgentResponse or ManagerResponse directly. +/// either `AgentResponse` or `ManagerResponse` directly. enum RecvOutcome { /// Long-poll returned at least one message; first one is detached. Message(hive_sh4re::DeliveredMessage), @@ -212,7 +212,7 @@ trait Surface { fn send_to_parent(socket: &Path, body: String) -> impl Future; /// Fire a `Wake { from: "self", body: "continue" }` at our own - /// inbox — the request_next_turn sentinel pickup. + /// inbox — the `request_next_turn` sentinel pickup. fn self_wake(socket: &Path) -> impl Future; /// Long-poll the broker for the next message. Wraps the diff --git a/hive-ag3nt/src/events.rs b/hive-ag3nt/src/events.rs index 65b78043..d511ab28 100644 --- a/hive-ag3nt/src/events.rs +++ b/hive-ag3nt/src/events.rs @@ -80,12 +80,12 @@ fn harness_json_path() -> PathBuf { fn read_harness_state() -> (bool, bool) { // Try the new consolidated file first. - if let Ok(raw) = std::fs::read_to_string(harness_json_path()) { - if let Ok(v) = serde_json::from_str::(&raw) { - let rate_limited = v.get("rate_limited").and_then(|x| x.as_bool()).unwrap_or(false); - let needs_login = v.get("needs_login").and_then(|x| x.as_bool()).unwrap_or(false); - return (rate_limited, needs_login); - } + if let Ok(raw) = std::fs::read_to_string(harness_json_path()) + && let Ok(v) = serde_json::from_str::(&raw) + { + let rate_limited = v.get("rate_limited").and_then(serde_json::Value::as_bool).unwrap_or(false); + let needs_login = v.get("needs_login").and_then(serde_json::Value::as_bool).unwrap_or(false); + return (rate_limited, needs_login); } // Fall back to legacy sentinel files written by older harness builds. let state_dir = crate::paths::state_dir(); diff --git a/hive-ag3nt/src/forge_notify.rs b/hive-ag3nt/src/forge_notify.rs index 7a86c711..0784aaea 100644 --- a/hive-ag3nt/src/forge_notify.rs +++ b/hive-ag3nt/src/forge_notify.rs @@ -88,8 +88,7 @@ pub async fn run(socket: PathBuf) { // HIVE_FORGE_KEEP_SUBSCRIPTIONS=1 disables auto-unsubscribe for agents // that intentionally consume the full repo notification firehose (e.g. triage). let keep_subscriptions = std::env::var("HIVE_FORGE_KEEP_SUBSCRIPTIONS") - .map(|v| v == "1" || v.eq_ignore_ascii_case("true")) - .unwrap_or(false); + .is_ok_and(|v| v == "1" || v.eq_ignore_ascii_case("true")); // Optional reason drop-list — comma-separated Forgejo `reason` // values to silently mark-read instead of deliver. See @@ -159,7 +158,7 @@ fn notif_type_label(t: &str) -> &str { /// inside the forge-notify wrapper, so a leading `## title` line /// doesn't blow into an h2 in the dashboard render. See /// `docs/forge.md::Body excerpt + truncation + heading escape` for -/// the strict-ATX-vs-`#tag` rationale and the split_inclusive +/// the strict-ATX-vs-`#tag` rationale and the `split_inclusive` /// trailing-newline contract. fn escape_md_headings(body: &str) -> String { let mut out = String::with_capacity(body.len()); diff --git a/hive-ag3nt/src/mcp.rs b/hive-ag3nt/src/mcp.rs index c96491ca..f9b26d58 100644 --- a/hive-ag3nt/src/mcp.rs +++ b/hive-ag3nt/src/mcp.rs @@ -448,20 +448,20 @@ fn format_bash_status(id: &str) -> String { let age = crate::serve_common::now_unix() - t; let _ = write!(out, ", running for {age}s"); } - if let Some(t) = task.completed_at { - if let Some(s) = task.started_at { - let _ = write!(out, ", took {}s", t - s); - } + if let Some(t) = task.completed_at + && let Some(s) = task.started_at + { + let _ = write!(out, ", took {}s", t - s); } - if let Some(ref stdout) = task.stdout_tail { - if !stdout.trim().is_empty() { - let _ = write!(out, "\n\nstdout:\n```\n{}\n```", stdout.trim()); - } + if let Some(ref stdout) = task.stdout_tail + && !stdout.trim().is_empty() + { + let _ = write!(out, "\n\nstdout:\n```\n{}\n```", stdout.trim()); } - if let Some(ref stderr) = task.stderr_tail { - if !stderr.trim().is_empty() { - let _ = write!(out, "\n\nstderr:\n```\n{}\n```", stderr.trim()); - } + if let Some(ref stderr) = task.stderr_tail + && !stderr.trim().is_empty() + { + let _ = write!(out, "\n\nstderr:\n```\n{}\n```", stderr.trim()); } out } @@ -1913,14 +1913,14 @@ pub enum Flavor { } /// Env var written by the meta renderer with a comma-separated list of -/// `hive_sh4re::ToolGroup` snake_case names (e.g. `"messaging,inbox,meta"`). +/// `hive_sh4re::ToolGroup` `snake_case` names (e.g. `"messaging,inbox,meta"`). /// When present, the harness expands the groups into per-tool allow entries /// instead of using the hardcoded flavor default. See `docs/conventions.md::Tool groups`. const TOOL_GROUPS_ENV: &str = "HIVE_TOOL_GROUPS"; /// `HIVE_CAPABILITIES` env var injected by `meta::render_flake` when the /// operator grants capabilities to this agent. Comma-separated -/// `hive_sh4re::Capability` snake_case names. Absent = no extra capabilities. +/// `hive_sh4re::Capability` `snake_case` names. Absent = no extra capabilities. const CAPABILITIES_ENV: &str = "HIVE_CAPABILITIES"; /// Returns the MCP tool names (without `mcp__hyperhive__` prefix) that are @@ -1976,14 +1976,12 @@ fn effective_tool_groups(flavor: Flavor) -> Vec { for token in raw.split(',') { let t = token.trim().to_ascii_lowercase(); // Parse via serde_json (the canonical deserialization path). - match serde_json::from_value::( + if let Ok(g) = serde_json::from_value::( serde_json::Value::String(t.clone()), ) { - Ok(g) => groups.push(g), - Err(_) => tracing::warn!( - token = %t, - "{TOOL_GROUPS_ENV}: unknown tool group, skipping" - ), + groups.push(g); + } else { + tracing::warn!(token = %t, "{TOOL_GROUPS_ENV}: unknown tool group, skipping"); } } if groups.is_empty() { diff --git a/hive-ag3nt/src/turn.rs b/hive-ag3nt/src/turn.rs index ad0e58c6..d0b0d25c 100644 --- a/hive-ag3nt/src/turn.rs +++ b/hive-ag3nt/src/turn.rs @@ -423,8 +423,7 @@ fn maybe_auto_reset(bus: &Bus) { // Compute idle seconds using the same clock as now_unix (unix epoch, i64). let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_secs()) - .unwrap_or(0); + .map_or(0, |d| d.as_secs()); let idle_secs = now.saturating_sub(u64::try_from(last_ended).unwrap_or(0)); let ttl = cache_ttl_secs(); if idle_secs < ttl { @@ -482,7 +481,7 @@ pub fn emit_turn_end(bus: &Bus, outcome: &TurnOutcome) { /// snapshot advances (mtime OR file-count change), avoiding the /// infinite-401 loop a bare-existence check would produce when stale /// credentials are already on disk. Mtime-snapshot resumption rationale -/// + DirSnapshot two-axis design: see +/// and `DirSnapshot` two-axis design: see /// [`docs/turn-loop.md::The loop`](../../docs/turn-loop.md). /// /// # Panics diff --git a/hive-ag3nt/src/web_ui.rs b/hive-ag3nt/src/web_ui.rs index d1b51587..a513ab5a 100644 --- a/hive-ag3nt/src/web_ui.rs +++ b/hive-ag3nt/src/web_ui.rs @@ -173,6 +173,7 @@ pub async fn serve( /// Marker-gating + the gateway-side consumer: see /// [`docs/gateway.md::Per-agent unix-socket upstream`](../../../docs/gateway.md). fn bind_unix(path: &Path) -> Result { + use std::os::unix::fs::PermissionsExt; if let Some(parent) = path.parent() { std::fs::create_dir_all(parent) .with_context(|| format!("create socket parent dir {}", parent.display()))?; @@ -183,7 +184,6 @@ fn bind_unix(path: &Path) -> Result { let _ = std::fs::remove_file(path); let listener = tokio::net::UnixListener::bind(path) .with_context(|| format!("bind unix socket at {}", path.display()))?; - use std::os::unix::fs::PermissionsExt; std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o666)) .with_context(|| format!("set perms on {}", path.display()))?; // Best-effort ready marker: failed write isn't fatal (the harness @@ -319,11 +319,10 @@ async fn relay_ws_vnc(socket: axum::extract::ws::WebSocket, vnc_port: u16) { let ws_to_tcp = tokio::spawn(async move { while let Some(Ok(msg)) = futures_util::StreamExt::next(&mut ws_rx).await { match msg { - Message::Binary(data) => { - if tcp_tx.write_all(&data).await.is_err() { + Message::Binary(data) + if tcp_tx.write_all(&data).await.is_err() => { break; } - } Message::Close(_) => break, _ => {} // ping/pong/text: ignore } diff --git a/hive-c0re/src/agent_server.rs b/hive-c0re/src/agent_server.rs index 79e4b7b4..c220f020 100644 --- a/hive-c0re/src/agent_server.rs +++ b/hive-c0re/src/agent_server.rs @@ -19,6 +19,7 @@ pub struct AgentSocket { } pub fn start(agent: &str, socket_path: &Path, coord: Arc) -> Result { + use std::os::unix::fs::PermissionsExt as _; let agent = agent.to_owned(); if let Some(parent) = socket_path.parent() { std::fs::create_dir_all(parent) @@ -36,7 +37,6 @@ pub fn start(agent: &str, socket_path: &Path, coord: Arc) -> Result // perms (0755) lock it out. 0666 lets the agent user connect; // the bind source dir is per-agent on host so blast radius is // unchanged. - use std::os::unix::fs::PermissionsExt as _; std::fs::set_permissions(socket_path, std::fs::Permissions::from_mode(0o666)) .with_context(|| format!("chmod agent socket {}", socket_path.display()))?; tracing::info!(%agent, socket = %socket_path.display(), "agent socket listening"); @@ -95,7 +95,7 @@ async fn serve(stream: UnixStream, agent: String, coord: Arc) -> Re /// cheap "is there anything pending?" check without blocking the /// turn for 30 seconds. To actually park, the caller passes a /// positive `wait_seconds`. -pub(crate) const RECV_LONG_POLL_MAX: std::time::Duration = std::time::Duration::from_secs(180); +pub(crate) const RECV_LONG_POLL_MAX: std::time::Duration = std::time::Duration::from_mins(3); /// Server-side hard cap on `Recv.max`. Bounds the size of a single /// round-trip so a confused caller can't drain the entire inbox in @@ -357,6 +357,7 @@ async fn dispatch(req: &AgentRequest, agent: &str, coord: &Arc) -> /// /// The manager is not exempt - grant `read_host_journal` in /// `meta/capabilities.json` to enable it for any agent including the manager. +#[allow(clippy::too_many_arguments)] pub async fn dispatch_host_journal( agent: &str, unit: &Option, @@ -619,8 +620,7 @@ fn prepare_remind_storage( fn auto_reminder_path(agent: &str) -> String { let ts_ns = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_nanos()) - .unwrap_or(0); + .map_or(0, |d| d.as_nanos()); format!("/agents/{agent}/state/reminders/auto-{ts_ns}.md") } diff --git a/hive-c0re/src/auto_update.rs b/hive-c0re/src/auto_update.rs index 9cd4022f..1ff3d67b 100644 --- a/hive-c0re/src/auto_update.rs +++ b/hive-c0re/src/auto_update.rs @@ -209,16 +209,16 @@ pub async fn ensure_manager(coord: &Arc) -> Result<()> { /// Sort `names` in-place so parents precede their children in the topology. /// Uses BFS from root agents (depth 0). Agents absent from `topo` sort last, /// alphabetically within their tier. Stable within each depth tier. -pub fn topology_sort(names: &mut Vec, topo: &std::collections::BTreeMap>) { +pub fn topology_sort(names: &mut [String], topo: &std::collections::BTreeMap>) { use std::collections::{HashMap, VecDeque}; // Build depth map using owned clones so the borrow on `names` is released // before the sort_by mutable borrow. - let name_set: Vec = names.clone(); + let name_set: Vec = names.to_vec(); let mut depth: HashMap = HashMap::new(); let mut queue: VecDeque = VecDeque::new(); // Seed roots: entries with no parent, or names not present in topo at all. for name in &name_set { - if topo.get(name).map_or(true, |p| p.is_none()) { + if topo.get(name).is_none_or(Option::is_none) { depth.insert(name.clone(), 0); queue.push_back(name.clone()); } diff --git a/hive-c0re/src/bash_tasks_vacuum.rs b/hive-c0re/src/bash_tasks_vacuum.rs index 545faf26..402dafb7 100644 --- a/hive-c0re/src/bash_tasks_vacuum.rs +++ b/hive-c0re/src/bash_tasks_vacuum.rs @@ -23,7 +23,7 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use crate::coordinator::Coordinator; -const VACUUM_INTERVAL: Duration = Duration::from_secs(3600); +const VACUUM_INTERVAL: Duration = Duration::from_hours(1); /// Keep completed task files for 48 hours before sweeping them. const KEEP_SECS: i64 = 48 * 3600; @@ -97,7 +97,7 @@ fn should_delete(json_path: &Path, cutoff: i64) -> bool { if !TERMINAL_STATUSES.contains(&status) { return false; } - let completed_at = v.get("completed_at").and_then(|t| t.as_i64()).unwrap_or(i64::MAX); + let completed_at = v.get("completed_at").and_then(serde_json::Value::as_i64).unwrap_or(i64::MAX); completed_at < cutoff } @@ -106,10 +106,10 @@ fn should_delete(json_path: &Path, cutoff: i64) -> bool { fn delete_trio(dir: &Path, stem: &str) { for ext in ["json", "out", "err"] { let path = dir.join(format!("{stem}.{ext}")); - if path.exists() { - if let Err(e) = std::fs::remove_file(&path) { - tracing::warn!(path = %path.display(), error = ?e, "bash-tasks vacuum: remove failed"); - } + if path.exists() + && let Err(e) = std::fs::remove_file(&path) + { + tracing::warn!(path = %path.display(), error = ?e, "bash-tasks vacuum: remove failed"); } } } diff --git a/hive-c0re/src/build_logs.rs b/hive-c0re/src/build_logs.rs index 5f65e1ed..f518be59 100644 --- a/hive-c0re/src/build_logs.rs +++ b/hive-c0re/src/build_logs.rs @@ -410,7 +410,7 @@ pub fn spawn_vacuum(coord: &Arc) { use std::time::Duration; let logs = coord.build_logs.clone(); let mut shutdown = coord.shutdown_rx(); - let interval = Duration::from_secs(3_600); + let interval = Duration::from_hours(1); tokio::spawn(async move { loop { match logs.vacuum() { diff --git a/hive-c0re/src/capabilities.rs b/hive-c0re/src/capabilities.rs index 9280fe55..64c284bd 100644 --- a/hive-c0re/src/capabilities.rs +++ b/hive-c0re/src/capabilities.rs @@ -3,7 +3,7 @@ //! and `tool-groups.json`. //! //! Format: a JSON object mapping agent name to an array of -//! `hive_sh4re::Capability` snake_case strings: +//! `hive_sh4re::Capability` `snake_case` strings: //! //! ```json //! { diff --git a/hive-c0re/src/container_view.rs b/hive-c0re/src/container_view.rs index dd43ea58..214d08cc 100644 --- a/hive-c0re/src/container_view.rs +++ b/hive-c0re/src/container_view.rs @@ -217,12 +217,12 @@ fn read_dashboard_links(name: &str) -> Vec { /// don't lose state during the transition window. fn read_harness_flags(name: &str) -> (bool, bool) { let dir = Coordinator::agent_notes_dir(name); - if let Ok(raw) = std::fs::read_to_string(dir.join("hyperhive-harness.json")) { - if let Ok(v) = serde_json::from_str::(&raw) { - let rl = v.get("rate_limited").and_then(|x| x.as_bool()).unwrap_or(false); - let nl = v.get("needs_login").and_then(|x| x.as_bool()).unwrap_or(false); - return (rl, nl); - } + if let Ok(raw) = std::fs::read_to_string(dir.join("hyperhive-harness.json")) + && let Ok(v) = serde_json::from_str::(&raw) + { + let rl = v.get("rate_limited").and_then(serde_json::Value::as_bool).unwrap_or(false); + let nl = v.get("needs_login").and_then(serde_json::Value::as_bool).unwrap_or(false); + return (rl, nl); } // Legacy fallback: presence of individual sentinel files. let rate_limited = dir.join("hyperhive-rate-limited").exists(); diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs index 20de50cd..b66586da 100644 --- a/hive-c0re/src/dashboard.rs +++ b/hive-c0re/src/dashboard.rs @@ -159,6 +159,7 @@ fn try_bind(addr: SocketAddr) -> std::io::Result { sock.listen(1024) } +#[allow(clippy::struct_excessive_bools)] #[derive(Serialize)] struct StateSnapshot { /// Broker seq at the moment this snapshot was assembled. Clients @@ -1833,7 +1834,7 @@ async fn get_build_log_stream( match notify_rx.recv().await { // Notification for a different build — ignore and wait // for the next one. - Ok(notif_id) if notif_id != id => continue, + Ok(notif_id) if notif_id != id => {} Ok(_) => { match logs.get_progress(id, stdout_cursor, stderr_cursor) { Ok(Some(prog)) => { @@ -1845,20 +1846,17 @@ async fn get_build_log_stream( stderr_append: prog.stderr_append, status: prog.status, done, - }) { - if tx.send(Ok(Event::default().data(json))).await.is_err() { - return; // browser disconnected - } + }) && tx.send(Ok(Event::default().data(json))).await.is_err() { + return; // browser disconnected } if done { return; } } - Ok(None) => return, // vacuum reaped the row - Err(_) => return, + Ok(None) | Err(_) => return, // vacuum reaped row / channel closed } } - Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => continue, + Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {} Err(tokio::sync::broadcast::error::RecvError::Closed) => return, } } diff --git a/hive-c0re/src/events_vacuum.rs b/hive-c0re/src/events_vacuum.rs index f765410b..404bc13f 100644 --- a/hive-c0re/src/events_vacuum.rs +++ b/hive-c0re/src/events_vacuum.rs @@ -18,7 +18,7 @@ use rusqlite::{Connection, Result, params}; use crate::coordinator::Coordinator; -const VACUUM_INTERVAL: Duration = Duration::from_secs(3600); +const VACUUM_INTERVAL: Duration = Duration::from_hours(1); const KEEP_SECS: i64 = 7 * 24 * 3600; /// Background loop: sweep every existing agent state dir hourly, run diff --git a/hive-c0re/src/flake_check.rs b/hive-c0re/src/flake_check.rs index 6771a013..5af35ca8 100644 --- a/hive-c0re/src/flake_check.rs +++ b/hive-c0re/src/flake_check.rs @@ -161,8 +161,7 @@ pub fn duplicate_groups(raw: &str) -> Result> { pub async fn check_lock_in_sync(repo: &Path, tag: &str, approval_id: i64) -> Result<()> { let suffix = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_nanos()) - .unwrap_or(0); + .map_or(0, |d| d.as_nanos()); let tmp_dir = std::env::temp_dir().join(format!("hive-flake-check-{approval_id}-{suffix}")); // Detached worktree at the proposal tag — gives us a clean, mutable diff --git a/hive-c0re/src/forge.rs b/hive-c0re/src/forge.rs index 99bc5199..9f48939b 100644 --- a/hive-c0re/src/forge.rs +++ b/hive-c0re/src/forge.rs @@ -276,8 +276,7 @@ async fn mint_token(name: &str, scopes: &str) -> Result { "{TOKEN_NAME_PREFIX}-{}", std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_secs()) - .unwrap_or(0) + .map_or(0, |d| d.as_secs()) ); let stdout = forge_admin(&[ "user", diff --git a/hive-c0re/src/gateway_nginx.rs b/hive-c0re/src/gateway_nginx.rs index 223d399f..5dc4320f 100644 --- a/hive-c0re/src/gateway_nginx.rs +++ b/hive-c0re/src/gateway_nginx.rs @@ -274,7 +274,7 @@ fn gateway_systemctl(args: &[&str]) -> bool { /// Synchronise the gateway nginx unit with the current agents.conf: /// /// - **active**: send `nginx -s reload` (SIGHUP to master, zero-downtime -/// worker replacement). Keeps RELOAD_PENDING set on failure so the +/// worker replacement). Keeps `RELOAD_PENDING` set on failure so the /// next poll tick retries. /// - **failed / start-limit-hit**: run `systemctl reset-failed nginx` /// then `systemctl start nginx`. This is the self-healing path: a diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index a90e7cfa..a2029253 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -181,7 +181,7 @@ async fn port_collision(self_name: &str) -> Option { None } -#[allow(clippy::too_many_arguments)] +#[allow(clippy::too_many_arguments, clippy::implicit_hasher)] pub async fn spawn( name: &str, hyperhive_flake: &str, @@ -324,8 +324,7 @@ pub async fn is_running(name: &str) -> bool { .args(["is-active", "--quiet", &unit]) .status() .await - .map(|s| s.success()) - .unwrap_or(false) + .is_ok_and(|s| s.success()) } /// Fully tear down a sub-agent's container: stop + remove via `nixos-container @@ -346,7 +345,7 @@ pub async fn destroy(name: &str) -> Result<()> { Ok(()) } -#[allow(clippy::too_many_arguments)] +#[allow(clippy::too_many_arguments, clippy::implicit_hasher)] pub async fn rebuild( name: &str, hyperhive_flake: &str, @@ -1004,7 +1003,7 @@ fn set_resource_limits(container: &str) -> Result<()> { std::fs::create_dir_all(&dir).with_context(|| format!("create {dir}"))?; let path = format!("{dir}/hyperhive-limits.conf"); let content = - format!("[Service]\nMemoryMax={DEFAULT_MEMORY_MAX}\nCPUQuota={DEFAULT_CPU_QUOTA}\n",); + format!("[Service]\nMemoryMax={DEFAULT_MEMORY_MAX}\nCPUQuota={DEFAULT_CPU_QUOTA}\n"); std::fs::write(&path, content).with_context(|| format!("write {path}"))?; tracing::info!( %path, @@ -1081,6 +1080,7 @@ fn bind_child_agent_dirs(child: &str, binds: &mut String) { let _ = write!(binds, " --bind={config_dir}:/agents/{child}/config"); } +#[allow(clippy::too_many_lines)] fn set_nspawn_flags( container: &str, runtime_dir: &Path, @@ -1265,8 +1265,7 @@ async fn run(args: &[&str]) -> Result<()> { let agent = args .get(1) .copied() - .map(|c| c.strip_prefix(AGENT_PREFIX).unwrap_or(c).to_string()) - .unwrap_or_else(|| "".to_string()); + .map_or_else(|| "".to_string(), |c| c.strip_prefix(AGENT_PREFIX).unwrap_or(c).to_string()); let logs = crate::build_logs::global(); let log_id = logs.as_ref().and_then(|h| { diff --git a/hive-c0re/src/main.rs b/hive-c0re/src/main.rs index 3089459d..f4ee0b62 100644 --- a/hive-c0re/src/main.rs +++ b/hive-c0re/src/main.rs @@ -227,7 +227,7 @@ async fn cmd_serve( let vacuum_coord = coord.clone(); let mut vacuum_shutdown = coord.shutdown_rx(); tokio::spawn(async move { - let interval = std::time::Duration::from_secs(3600); + let interval = std::time::Duration::from_hours(1); let keep_secs: i64 = 30 * 24 * 3600; loop { match vacuum_coord.broker.vacuum_delivered(keep_secs) { diff --git a/hive-c0re/src/manager_server.rs b/hive-c0re/src/manager_server.rs index 2dec41d2..176d01a2 100644 --- a/hive-c0re/src/manager_server.rs +++ b/hive-c0re/src/manager_server.rs @@ -14,6 +14,7 @@ use crate::coordinator::Coordinator; use crate::lifecycle; pub fn start(coord: Arc) -> Result<()> { + use std::os::unix::fs::PermissionsExt as _; let dir = Coordinator::manager_dir(); std::fs::create_dir_all(&dir) .with_context(|| format!("create manager dir {}", dir.display()))?; @@ -25,7 +26,6 @@ pub fn start(coord: Arc) -> Result<()> { .with_context(|| format!("bind manager socket {}", socket.display()))?; // 0666 so the in-container root user (non-root) can connect; // the bind source dir is manager-only on host. See agent_server.rs. - use std::os::unix::fs::PermissionsExt as _; std::fs::set_permissions(&socket, std::fs::Permissions::from_mode(0o666)) .with_context(|| format!("chmod manager socket {}", socket.display()))?; tracing::info!(socket = %socket.display(), "manager socket listening"); diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index ebc51e68..7810ddfa 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -49,7 +49,7 @@ pub fn meta_dir() -> PathBuf { /// seed commit. Subsequent calls only touch `flake.nix` when the /// rendered contents differ from disk; an unchanged `flake.nix` is a /// no-op. -#[allow(dead_code)] // first caller lands in a later commit +#[allow(dead_code, clippy::implicit_hasher)] // first caller lands in a later commit pub async fn sync_agents( hyperhive_flake: &str, dashboard_port: u16, diff --git a/hive-c0re/src/migrate.rs b/hive-c0re/src/migrate.rs index 9ed28ecc..29f7e569 100644 --- a/hive-c0re/src/migrate.rs +++ b/hive-c0re/src/migrate.rs @@ -211,10 +211,10 @@ async fn rename_manager_container(coord: &Arc) { // Move rootfs if it exists (may be absent for ephemeral containers). let old_rootfs = std::path::PathBuf::from("/var/lib/nixos-containers/root"); let new_rootfs = std::path::PathBuf::from("/var/lib/nixos-containers/h-root"); - if old_rootfs.exists() && !new_rootfs.exists() { - if let Err(e) = std::fs::rename(&old_rootfs, &new_rootfs) { - tracing::warn!(error = ?e, "migration phase 5: rename rootfs failed (non-fatal)"); - } + if old_rootfs.exists() && !new_rootfs.exists() + && let Err(e) = std::fs::rename(&old_rootfs, &new_rootfs) + { + tracing::warn!(error = ?e, "migration phase 5: rename rootfs failed (non-fatal)"); } // Daemon reload so systemd picks up the new container@h-root unit. diff --git a/hive-c0re/src/scheduled_prompts_worker.rs b/hive-c0re/src/scheduled_prompts_worker.rs index a37f363a..1028f231 100644 --- a/hive-c0re/src/scheduled_prompts_worker.rs +++ b/hive-c0re/src/scheduled_prompts_worker.rs @@ -27,7 +27,7 @@ const POLL_INTERVAL: Duration = Duration::from_secs(5); /// the dashboard list view doesn't accrue tombstones forever. /// Cancelled rows live long enough that the operator can still /// see what they cancelled in the recent past. -const CANCELLED_REAP_AGE: Duration = Duration::from_secs(3600); +const CANCELLED_REAP_AGE: Duration = Duration::from_hours(1); pub fn spawn(coord: Arc) { let mut shutdown = coord.shutdown_rx(); diff --git a/hive-c0re/src/stats_vacuum.rs b/hive-c0re/src/stats_vacuum.rs index 7e018a6b..54e7ec44 100644 --- a/hive-c0re/src/stats_vacuum.rs +++ b/hive-c0re/src/stats_vacuum.rs @@ -15,7 +15,7 @@ use rusqlite::{Connection, Result, params}; use crate::coordinator::Coordinator; -const VACUUM_INTERVAL: Duration = Duration::from_secs(3600); +const VACUUM_INTERVAL: Duration = Duration::from_hours(1); const KEEP_SECS: i64 = 90 * 24 * 3600; /// Background loop: sweep every existing agent state dir hourly, run diff --git a/hive-c0re/src/tool_groups.rs b/hive-c0re/src/tool_groups.rs index 1d894e59..11df0e4c 100644 --- a/hive-c0re/src/tool_groups.rs +++ b/hive-c0re/src/tool_groups.rs @@ -3,7 +3,7 @@ //! and the meta `flake.nix`. //! //! Format: a JSON object mapping agent name to an array of -//! `hive_sh4re::ToolGroup` snake_case strings: +//! `hive_sh4re::ToolGroup` `snake_case` strings: //! //! ```json //! { diff --git a/hive-forge/src/verbs/comments.rs b/hive-forge/src/verbs/comments.rs index f27e3047..944614d0 100644 --- a/hive-forge/src/verbs/comments.rs +++ b/hive-forge/src/verbs/comments.rs @@ -48,8 +48,8 @@ pub struct Args { pub fn run(client: &Client, args: Args) -> Result<()> { let repo = client.repo(); let comments = match args.tail { - Some(n) => fetch_tail(client, &repo, args.number, n)?, - None => fetch_head(client, &repo, args.number, args.limit)?, + Some(n) => fetch_tail(client, repo, args.number, n)?, + None => fetch_head(client, repo, args.number, args.limit)?, }; if client.json_mode() { let trimmed: Vec = comments @@ -99,6 +99,7 @@ fn fetch_head(client: &Client, repo: &str, number: u64, limit: u64) -> Result Result> { if n == 0 { return Ok(Vec::new()); diff --git a/hive-forge/src/verbs/diff.rs b/hive-forge/src/verbs/diff.rs index 066e0dfc..f70d6629 100644 --- a/hive-forge/src/verbs/diff.rs +++ b/hive-forge/src/verbs/diff.rs @@ -144,8 +144,7 @@ fn parse_diff_git_path(rest: &str) -> Option { // containing `"` doesn't terminate early). let mut iter = after_open.char_indices(); let mut a_close = None; - loop { - let Some((i, c)) = iter.next() else { break }; + while let Some((i, c)) = iter.next() { if c == '\\' { // Skip the next char — it's part of the escape. iter.next(); @@ -167,8 +166,7 @@ fn parse_diff_git_path(rest: &str) -> Option { // Find b-side's closing quote with the same escape rule. let mut iter = b_inside.char_indices(); let mut b_close = None; - loop { - let Some((i, c)) = iter.next() else { break }; + while let Some((i, c)) = iter.next() { if c == '\\' { iter.next(); continue; diff --git a/hive-forge/src/verbs/list.rs b/hive-forge/src/verbs/list.rs index 605858f9..fecc5036 100644 --- a/hive-forge/src/verbs/list.rs +++ b/hive-forge/src/verbs/list.rs @@ -8,6 +8,8 @@ //! second of the four #694 gaps (read-side; no boundary concerns — //! every agent + the operator queries the issue tracker constantly). +use std::fmt::Write as _; + use anyhow::Result; use clap::{Args as ClapArgs, ValueEnum}; use serde_json::Value; @@ -97,24 +99,24 @@ pub fn run(client: &Client, args: Args) -> Result<()> { if let Some(u) = args.assignee.as_deref() && !u.is_empty() { - path.push_str(&format!("&assigned_by={}", pct_encode(u))); + write!(path, "&assigned_by={}", pct_encode(u)).unwrap(); } if let Some(u) = args.author.as_deref() && !u.is_empty() { - path.push_str(&format!("&created_by={}", pct_encode(u))); + write!(path, "&created_by={}", pct_encode(u)).unwrap(); } if let Some(u) = args.mention.as_deref() && !u.is_empty() { - path.push_str(&format!("&mentioned_by={}", pct_encode(u))); + write!(path, "&mentioned_by={}", pct_encode(u)).unwrap(); } if !args.labels.is_empty() { // Encode each label individually so a comma INSIDE a label // (rare but legal) gets escaped while the field separator // stays a literal comma the forge will parse as N labels. let encoded: Vec = args.labels.iter().map(|l| pct_encode(l)).collect(); - path.push_str(&format!("&labels={}", encoded.join(","))); + write!(path, "&labels={}", encoded.join(",")).unwrap(); } let resp = client.get_json(&path)?; if client.json_mode() { @@ -143,7 +145,7 @@ fn pct_encode(s: &str) -> String { if b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b'.' | b'~') { out.push(b as char); } else { - out.push_str(&format!("%{b:02X}")); + write!(out, "%{b:02X}").unwrap(); } } out diff --git a/hive-forge/src/verbs/timeline.rs b/hive-forge/src/verbs/timeline.rs index 52c35f1e..6c7cb288 100644 --- a/hive-forge/src/verbs/timeline.rs +++ b/hive-forge/src/verbs/timeline.rs @@ -6,7 +6,7 @@ //! //! Forgejo's `/issues/{n}/timeline` endpoint returns BOTH the actual //! comments AND the event entries (label, assignee, close, reopen, -//! pull_push, etc.) in chronological order. We render each row in +//! `pull_push`, etc.) in chronological order. We render each row in //! a human-readable form by default; pass the global `--json` flag //! for the raw API shape. //! @@ -62,6 +62,7 @@ pub fn run(client: &Client, args: Args) -> Result<()> { /// output for every supported event type without re-implementing the /// per-arm dispatch. `print_event` is the only caller that adds the /// terminating newline. +#[allow(clippy::too_many_lines)] fn format_event(ev: &Value) -> String { let event_type = ev.get("type").and_then(Value::as_str).unwrap_or("?"); let user = ev diff --git a/hive-priv/src/main.rs b/hive-priv/src/main.rs index b37ba141..5c727a97 100644 --- a/hive-priv/src/main.rs +++ b/hive-priv/src/main.rs @@ -17,6 +17,7 @@ //! `LISTEN_FDS=1` + `LISTEN_PID=`, the inherited fd 3 is used //! instead of binding a fresh socket. +use std::fmt::Write as _; use std::path::{Path, PathBuf}; use anyhow::{Context as _, Result, bail}; @@ -53,6 +54,7 @@ async fn main() -> Result<()> { } fn socket_listener() -> Result { + use std::os::unix::fs::PermissionsExt as _; // Socket activation: systemd passes the socket as fd 3 when // LISTEN_FDS >= 1 and LISTEN_PID matches our pid. let listen_fds: Option = std::env::var("LISTEN_FDS") @@ -62,21 +64,21 @@ fn socket_listener() -> Result { .ok() .and_then(|s| s.parse().ok()); - if let (Some(n), Some(p)) = (listen_fds, listen_pid) { - if n >= 1 && p == std::process::id() { - // SAFETY: systemd has passed us a ready UnixListener on fd 3. - let std_listener = unsafe { - use std::os::unix::io::FromRawFd; - std::os::unix::net::UnixListener::from_raw_fd(3) - }; - std_listener - .set_nonblocking(true) - .context("set socket non-blocking")?; - let listener = - tokio::net::UnixListener::from_std(std_listener).context("wrap systemd socket")?; - tracing::info!("using systemd-activated socket"); - return Ok(listener); - } + if let (Some(n), Some(p)) = (listen_fds, listen_pid) + && n >= 1 && p == std::process::id() + { + // SAFETY: systemd has passed us a ready UnixListener on fd 3. + let std_listener = unsafe { + use std::os::unix::io::FromRawFd; + std::os::unix::net::UnixListener::from_raw_fd(3) + }; + std_listener + .set_nonblocking(true) + .context("set socket non-blocking")?; + let listener = + tokio::net::UnixListener::from_std(std_listener).context("wrap systemd socket")?; + tracing::info!("using systemd-activated socket"); + return Ok(listener); } // Fallback: bind the socket ourselves. @@ -88,7 +90,6 @@ fn socket_listener() -> Result { let _ = std::fs::remove_file(path); let listener = UnixListener::bind(path).with_context(|| format!("bind {PRIV_SOCK}"))?; // Mode 0660: only the hive-core group can connect. - use std::os::unix::fs::PermissionsExt as _; std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o660)) .context("chmod priv.sock")?; tracing::info!(path = PRIV_SOCK, "bound priv socket"); @@ -137,6 +138,7 @@ async fn dispatch(line: &str) -> PrivResponse { } /// Execute a validated `PrivRequest`. Returns `(stdout, stderr)` on success. +#[allow(clippy::too_many_lines)] async fn exec(req: PrivRequest) -> Result<(String, String)> { match req { PrivRequest::StartContainer { ref name } => { @@ -248,9 +250,9 @@ async fn exec(req: PrivRequest) -> Result<(String, String)> { } PrivRequest::ChmodSocketDir { ref agent_name, mode } => { + use std::os::unix::fs::PermissionsExt as _; validate_agent_name(agent_name)?; let path = socket_dir_path(agent_name); - use std::os::unix::fs::PermissionsExt as _; std::fs::set_permissions(&path, std::fs::Permissions::from_mode(mode)) .with_context(|| format!("chmod {:o} {}", mode, path.display()))?; Ok((String::new(), String::new())) @@ -404,6 +406,6 @@ fn write_nspawn_flags(container: &str, binds: &[BindMount]) -> Result<()> { format!("{flag}={}:{}", b.host_path, b.container_path) }).collect(); let flags_joined = flags.join(" "); - out.push_str(&format!("EXTRA_NSPAWN_FLAGS=\"{flags_joined}\"\n")); + writeln!(out, "EXTRA_NSPAWN_FLAGS=\"{flags_joined}\"").unwrap(); std::fs::write(&path, out).with_context(|| format!("write {path}")) } diff --git a/hive-sh4re/src/lib.rs b/hive-sh4re/src/lib.rs index 1b9ef753..31b0576c 100644 --- a/hive-sh4re/src/lib.rs +++ b/hive-sh4re/src/lib.rs @@ -751,7 +751,7 @@ pub struct SchedulePromptPayload { /// Named group of MCP tools an agent may be granted. The harness reads /// `HIVE_TOOL_GROUPS` from the environment (a comma-separated list of -/// snake_case group names written by the meta renderer from per-agent +/// `snake_case` group names written by the meta renderer from per-agent /// config) and expands it to the matching tool names for `--allowedTools`. /// When the env var is absent the harness falls back to the flavor default /// (`AGENT_DEFAULT` or `MANAGER_DEFAULT`). See `docs/conventions.md::Tool groups`. @@ -841,7 +841,7 @@ impl ToolGroup { Self::Execution, ]; - /// The snake_case wire name for this group (matches `serde(rename_all = + /// The `snake_case` wire name for this group (matches `serde(rename_all = /// "snake_case")` serialisation). #[must_use] pub fn as_str(self) -> &'static str { @@ -897,7 +897,7 @@ impl JournalPriority { /// which MCP tools the harness exposes to claude). /// /// Injected into containers as `HIVE_CAPABILITIES` (comma-separated -/// snake_case) via `meta::render_flake`. The harness reads this to +/// `snake_case`) via `meta::render_flake`. The harness reads this to /// conditionally register capability-gated MCP tools so claude only /// sees tools it can actually invoke. See `docs/conventions.md::Capabilities`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -925,7 +925,7 @@ pub enum Capability { } impl Capability { - /// Canonical snake_case name for this capability (matches serde). + /// Canonical `snake_case` name for this capability (matches serde). #[must_use] pub fn as_str(self) -> &'static str { match self {