fix(clippy): fix all clippy warnings in hive-ag3nt, hive-forge, hive-matrix-mcp, hive-sh4re

Fixes all clippy -D warnings errors in the crates iris owns:

hive-sh4re:
- doc_lazy_continuation: add blank /// separator in priv_proto.rs
- doc_markdown: backtick PRIVATE_NETWORK=0 / PRIVATE_NETWORK=1

hive-matrix-mcp:
- map_unwrap_or: map().unwrap_or_else() -> map_or_else() in paths.rs
- collapsible_if: if-let chains in wake.rs
- doc_markdown: backtick M_UNKNOWN_TOKEN in main.rs
- cast_possible_truncation: usize/u64 -> u32::try_from in handlers.rs
- map_unwrap_or: map_or_else() in handlers.rs
- manual_let_else: match Ok(r) => r, Err => return -> let Ok in handlers.rs
- unused_async: remove async from list_invites; update socket.rs call site

hive-forge:
- doc_markdown: backtick REQUEST_CHANGES / APPROVED / COMMENT in pr_reviews.rs
- unnecessary_wraps: list_reviews_text returns () not Result<()>
- doc_markdown: backtick start_page / last_page in comments.rs
- cast_possible_truncation: PAGE_SIZE u64 -> usize; remove as usize casts

hive-ag3nt:
- collapsible_if: if-let chains in events.rs and mcp.rs
- single_match_else: match -> if let in events.rs and mcp.rs
- items_after_statements: hoist STATUS_MAX_CHARS const in mcp.rs
- map_unwrap_or: map_or_else() in mcp.rs and mcp_loose_ends.rs
- cast_possible_truncation: usize -> u32::try_from in mcp.rs
- doc_markdown: backtick snake_case in mcp.rs, needs_update/deployed_sha
  in web_ui.rs, HISTORY_CAPACITY in web_ui.rs
- identical_match_arms: combine manage_root_agent | query_agent_state
- redundant_closure: |s| s.to_string() -> ToString::to_string in web_ui.rs
- duration_suboptimal_units: from_secs(3600) -> from_hours(1) in turn.rs

Remaining failures in hive-c0re (39), hive-priv (8), hive-bash-mcp (11)
are owned by damocles.
This commit is contained in:
iris 2026-06-05 14:28:05 +02:00 committed by mara
commit 92b32d06fb
13 changed files with 128 additions and 133 deletions

View file

@ -80,18 +80,18 @@ 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::<serde_json::Value>(&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::<serde_json::Value>(&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();
@ -265,38 +265,35 @@ impl EventStore {
let conn = self.conn.lock().unwrap();
// Fetch one extra row so we can tell whether more exist.
let fetch = limit_i.saturating_add(1);
let rows: Vec<(i64, LiveEvent)> = match before_id {
Some(bid) => {
let mut stmt = conn.prepare(
"SELECT id, payload_json FROM events
WHERE id < ?1
ORDER BY id DESC
LIMIT ?2",
)?;
stmt.query_map(params![bid, fetch], |row| {
let id: i64 = row.get(0)?;
let s: String = row.get(1)?;
Ok(serde_json::from_str::<LiveEvent>(&s).ok().map(|e| (id, e)))
})?
.flatten()
.flatten()
.collect()
}
None => {
let mut stmt = conn.prepare(
"SELECT id, payload_json FROM events
ORDER BY id DESC
LIMIT ?1",
)?;
stmt.query_map(params![fetch], |row| {
let id: i64 = row.get(0)?;
let s: String = row.get(1)?;
Ok(serde_json::from_str::<LiveEvent>(&s).ok().map(|e| (id, e)))
})?
.flatten()
.flatten()
.collect()
}
let rows: Vec<(i64, LiveEvent)> = if let Some(bid) = before_id {
let mut stmt = conn.prepare(
"SELECT id, payload_json FROM events
WHERE id < ?1
ORDER BY id DESC
LIMIT ?2",
)?;
stmt.query_map(params![bid, fetch], |row| {
let id: i64 = row.get(0)?;
let s: String = row.get(1)?;
Ok(serde_json::from_str::<LiveEvent>(&s).ok().map(|e| (id, e)))
})?
.flatten()
.flatten()
.collect()
} else {
let mut stmt = conn.prepare(
"SELECT id, payload_json FROM events
ORDER BY id DESC
LIMIT ?1",
)?;
stmt.query_map(params![fetch], |row| {
let id: i64 = row.get(0)?;
let s: String = row.get(1)?;
Ok(serde_json::from_str::<LiveEvent>(&s).ok().map(|e| (id, e)))
})?
.flatten()
.flatten()
.collect()
};
let has_more = rows.len() > limit;
let mut rows: Vec<(i64, LiveEvent)> = rows.into_iter().take(limit).collect();

View file

@ -111,6 +111,9 @@ impl From<hive_sh4re::Response> for SocketReply {
/// the file is never written with text the server would later reject (which
/// would leave a stale invalid entry on disk).
fn write_status_file(text: &str) -> Result<(), String> {
// 200 chars mirrors STATUS_MAX_CHARS in hive-c0re/src/limits.rs.
// Keep in sync if that constant changes.
const STATUS_MAX_CHARS: usize = 200;
let trimmed = text.trim();
if !trimmed.is_empty() {
if trimmed.contains('\n') || trimmed.contains('\r') {
@ -120,9 +123,6 @@ fn write_status_file(text: &str) -> Result<(), String> {
.to_owned(),
);
}
// 200 chars mirrors STATUS_MAX_CHARS in hive-c0re/src/limits.rs.
// Keep in sync if that constant changes.
const STATUS_MAX_CHARS: usize = 200;
let len = trimmed.chars().count();
if len > STATUS_MAX_CHARS {
return Err(format!(
@ -310,9 +310,10 @@ struct MatrixRoomUnread {
async fn matrix_unread_summary() -> Option<Vec<MatrixRoomUnread>> {
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::UnixStream;
let socket = std::env::var_os("HIVE_MATRIX_SOCKET")
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::path::PathBuf::from("/run/hive-matrix/socket"));
let socket = std::env::var_os("HIVE_MATRIX_SOCKET").map_or_else(
|| std::path::PathBuf::from("/run/hive-matrix/socket"),
std::path::PathBuf::from,
);
if !socket.exists() {
return None;
}
@ -339,11 +340,11 @@ fn format_matrix_summary(rooms: &[MatrixRoomUnread]) -> String {
}
let mut out = String::new();
for r in rooms {
if r.count == 1 {
if let (Some(body), Some(sender)) = (&r.last_body, &r.last_sender) {
let _ = writeln!(out, "- {}: {sender}: {body}", r.label);
continue;
}
if r.count == 1
&& let (Some(body), Some(sender)) = (&r.last_body, &r.last_sender)
{
let _ = writeln!(out, "- {}: {sender}: {body}", r.label);
continue;
}
let _ = writeln!(out, "- {}: {} unread", r.label, r.count);
}
@ -775,19 +776,19 @@ impl AgentServer {
};
// Prepend matrix unread entry for self-queries only (can't
// reach another agent's matrix daemon from here).
if is_self_query {
if let Some(unread_rooms) = matrix_unread_summary().await {
let total = unread_rooms.len() as u32;
if total > 0 {
let summary = format_matrix_summary(&unread_rooms);
loose_ends.insert(
0,
hive_sh4re::LooseEnd::UnreadMatrix {
rooms: total,
summary,
},
);
}
if is_self_query
&& let Some(unread_rooms) = matrix_unread_summary().await
{
let total = u32::try_from(unread_rooms.len()).unwrap_or(u32::MAX);
if total > 0 {
let summary = format_matrix_summary(&unread_rooms);
loose_ends.insert(
0,
hive_sh4re::LooseEnd::UnreadMatrix {
rooms: total,
summary,
},
);
}
}
let mut out = annotate_retries(render_loose_ends(&loose_ends), retries);
@ -1754,14 +1755,14 @@ pub const SERVER_NAME: &str = "hyperhive";
pub const ALLOWED_BUILTIN_TOOLS: &[&str] = &["Edit", "Glob", "Grep", "Read", "Write"];
/// 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
@ -1778,14 +1779,12 @@ fn allowed_capability_tools() -> Vec<String> {
let t = token.trim().to_ascii_lowercase();
match t.as_str() {
"read_host_journal" => tools.push("get_host_journal".to_owned()),
// manage_root_agent doesn't expose new MCP tools (it gates
// existing lifecycle tools via the topology enforcement).
"manage_root_agent" => {}
// query_agent_state doesn't expose new MCP tools; it unlocks
// the `agent` field in get_loose_ends / count_pending_reminders
// / reminder_rollup on the agent socket (c0re enforces the cap
// server-side; the harness honours it by passing the field).
"query_agent_state" => {}
// manage_root_agent / query_agent_state don't expose new MCP
// tools: manage_root_agent gates existing lifecycle tools via
// topology enforcement; query_agent_state unlocks the `agent`
// field in get_loose_ends / count_pending_reminders /
// reminder_rollup (c0re enforces the cap server-side).
"manage_root_agent" | "query_agent_state" => {}
unknown => {
tracing::warn!(capability = %unknown, "unrecognised capability in HIVE_CAPABILITIES — skipped");
}
@ -1810,13 +1809,12 @@ fn effective_tool_groups() -> Vec<hive_sh4re::ToolGroup> {
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::<hive_sh4re::ToolGroup>(serde_json::Value::String(t.clone()))
if let Ok(g) =
serde_json::from_value::<hive_sh4re::ToolGroup>(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() {

View file

@ -21,8 +21,7 @@ fn loose_ends_dir() -> PathBuf {
let state_path = PathBuf::from(&state);
state_path
.parent()
.map(|p| p.join("harness"))
.unwrap_or_else(|| PathBuf::from(state))
.map_or_else(|| PathBuf::from(state), |p| p.join("harness"))
};
base.join("mcp-loose-ends")
}

View file

@ -905,7 +905,7 @@ mod tests {
fs::write(dir.path().join("credentials.json"), b"{}").unwrap();
let snapshot = DirSnapshot {
file_count: 1,
newest_mtime: Some(SystemTime::now() + Duration::from_secs(3600)),
newest_mtime: Some(SystemTime::now() + Duration::from_hours(1)),
};
assert!(!session_refreshed(snapshot, snapshot_dir(dir.path())));
}

View file

@ -579,7 +579,7 @@ async fn api_state(State(state): State<AppState>) -> axum::Json<StateSnapshot> {
/// fetches this once per running agent to get fresh, agent-authoritative
/// values instead of relying on hive-c0re's periodic file-reads.
///
/// Structural fields (running, needs_update, deployed_sha, parent, …)
/// Structural fields (running, `needs_update`, `deployed_sha`, parent, …)
/// continue to come from hive-c0re's `/api/state`; this endpoint covers
/// only the fields the agent itself is the source of truth for.
#[derive(serde::Serialize)]
@ -808,7 +808,7 @@ struct HistoryParams {
/// Cursor: only return events with sqlite row id < `before`.
/// Omit for the initial (most-recent) page.
before: Option<i64>,
/// Page size (default 100, capped at HISTORY_CAPACITY).
/// Page size (default 100, capped at `HISTORY_CAPACITY`).
limit: Option<usize>,
}
@ -1094,7 +1094,7 @@ fn available_models() -> Vec<String> {
const DEFAULT: &[&str] = &["haiku", "sonnet", "opus"];
let raw = match std::env::var("HIVE_AVAILABLE_MODELS") {
Ok(v) if !v.trim().is_empty() => v,
_ => return DEFAULT.iter().map(|s| s.to_string()).collect(),
_ => return DEFAULT.iter().map(ToString::to_string).collect(),
};
let models: Vec<String> = raw
.split(',')
@ -1102,7 +1102,7 @@ fn available_models() -> Vec<String> {
.filter(|s| !s.is_empty())
.collect();
if models.is_empty() {
DEFAULT.iter().map(|s| s.to_string()).collect()
DEFAULT.iter().map(ToString::to_string).collect()
} else {
models
}

View file

@ -26,7 +26,7 @@ use crate::verbs::print_json;
/// Forgejo's per-page comment cap. The API caps `limit` at 50 even
/// if a higher value is requested; pin it explicitly so the math
/// downstream doesn't depend on a hidden default.
const PAGE_SIZE: u64 = 50;
const PAGE_SIZE: usize = 50;
#[derive(ClapArgs)]
pub struct Args {
@ -112,7 +112,7 @@ fn fetch_tail(client: &Client, repo: &str, number: u64, n: usize) -> Result<Vec<
// Cap `n` at the actual total so the math below stays in range
// when the caller asks for more comments than exist.
let n = n.min(total);
let page_size = PAGE_SIZE as usize;
let page_size = PAGE_SIZE;
// 0-based index of the first comment we want; integer-divide to
// get the 1-based page that contains it.
let start_idx = total - n;
@ -145,12 +145,12 @@ mod tests {
/// Pure helper mirroring the page-arithmetic in `fetch_tail`:
/// given a total comment count + requested tail size, return
/// the (start_page, last_page) pair the network loop would
/// the (`start_page`, `last_page`) pair the network loop would
/// walk. Lets us pin the pagination plan — the part that's
/// easy to off-by-one — without touching the network.
fn tail_plan(total: usize, n: usize) -> (usize, usize) {
let n = n.min(total);
let page_size = PAGE_SIZE as usize;
let page_size = PAGE_SIZE;
let start_idx = total - n;
let start_page = (start_idx / page_size) + 1;
let last_page = (total - 1) / page_size + 1;

View file

@ -17,7 +17,7 @@ pub struct Args {
#[arg(long, conflicts_with_all = ["request_changes", "comment"])]
approve: bool,
/// Request changes on the PR (submit a REQUEST_CHANGES review).
/// Request changes on the PR (submit a `REQUEST_CHANGES` review).
#[arg(long, conflicts_with_all = ["approve", "comment"])]
request_changes: bool,
@ -52,7 +52,7 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
}
}
/// Submit a review event (APPROVED / REQUEST_CHANGES / COMMENT) and print
/// Submit a review event (`APPROVED` / `REQUEST_CHANGES` / `COMMENT`) and print
/// a compact summary of the created review.
fn submit_review(client: &Client, number: u64, event: &str, body: Option<String>) -> Result<()> {
let repo = client.repo();
@ -89,7 +89,8 @@ fn list_reviews(client: &Client, number: u64) -> Result<()> {
if client.json_mode() {
list_reviews_json(client, repo, number, &reviews)
} else {
list_reviews_text(client, repo, number, &reviews)
list_reviews_text(client, repo, number, &reviews);
Ok(())
}
}
@ -129,10 +130,10 @@ fn list_reviews_json(client: &Client, repo: &str, number: u64, reviews: &[Value]
/// Human-readable output: Markdown-style heading per review, inline
/// comments as `[path:line] body` (line omitted for PR-level comments).
fn list_reviews_text(client: &Client, repo: &str, number: u64, reviews: &[Value]) -> Result<()> {
fn list_reviews_text(client: &Client, repo: &str, number: u64, reviews: &[Value]) {
if reviews.is_empty() {
println!("(no reviews)");
return Ok(());
return;
}
for r in reviews {
let id = r.get("id").and_then(Value::as_u64).unwrap_or(0);
@ -160,5 +161,4 @@ fn list_reviews_text(client: &Client, repo: &str, number: u64, reviews: &[Value]
}
println!();
}
Ok(())
}

View file

@ -275,7 +275,7 @@ pub async fn list_room_members(client: &Client, room_ref: &str) -> DaemonRespons
DaemonResponse::ok(&list)
}
pub async fn list_invites(client: &Client) -> DaemonResponse {
pub fn list_invites(client: &Client) -> DaemonResponse {
let invites: Vec<InviteInfo> = client
.invited_rooms()
.into_iter()
@ -422,11 +422,14 @@ pub async fn read_room(client: &Client, room_ref: &str, limit: Option<usize>) ->
/// [`collect_unread`] issues for count==1 rooms.
#[must_use]
pub fn unread_count(client: &Client) -> DaemonResponse {
let rooms = client
.joined_rooms()
.into_iter()
.filter(|r| r.unread_notification_counts().notification_count > 0)
.count() as u32;
let rooms = u32::try_from(
client
.joined_rooms()
.into_iter()
.filter(|r| r.unread_notification_counts().notification_count > 0)
.count(),
)
.unwrap_or(u32::MAX);
DaemonResponse::ok(&serde_json::json!({ "rooms": rooms }))
}
@ -447,14 +450,14 @@ pub async fn collect_unread(client: &Client) -> Vec<crate::protocol::RoomUnread>
use crate::protocol::RoomUnread;
let mut result = Vec::new();
for room in client.joined_rooms() {
let count = room.unread_notification_counts().notification_count as u32;
let count = u32::try_from(room.unread_notification_counts().notification_count)
.unwrap_or(u32::MAX);
if count == 0 {
continue;
}
let label = room
.canonical_alias()
.map(|a| a.to_string())
.unwrap_or_else(|| room.room_id().to_string());
.map_or_else(|| room.room_id().to_string(), |a| a.to_string());
let (last_body, last_sender) = if count == 1 {
fetch_last_message(client, &room).await
} else {
@ -482,9 +485,8 @@ async fn fetch_last_message(
let mut req =
get_message_events::v3::Request::new(room.room_id().to_owned(), Direction::Backward);
req.limit = matrix_sdk::ruma::UInt::from(1u32);
let resp = match client.send(req).await {
Ok(r) => r,
Err(_) => return (None, None),
let Ok(resp) = client.send(req).await else {
return (None, None);
};
for raw in &resp.chunk {
if let Ok(ev) = raw.deserialize() {

View file

@ -15,7 +15,7 @@
//! synced with hive-c0re's token-provisioning timing.
//!
//! Stale-token recovery: handled in `client::build_and_restore` — see
//! that module for the M_UNKNOWN_TOKEN detection + cleanup flow.
//! that module for the `M_UNKNOWN_TOKEN` detection + cleanup flow.
use anyhow::{Context, Result};
use matrix_sdk::config::SyncSettings;

View file

@ -83,8 +83,7 @@ pub fn mcp_loose_ends_dir() -> PathBuf {
let state_path = PathBuf::from(&state);
state_path
.parent()
.map(|p| p.join("harness"))
.unwrap_or_else(|| PathBuf::from(state))
.map_or_else(|| PathBuf::from(state), |p| p.join("harness"))
};
base.join("mcp-loose-ends")
}

View file

@ -79,7 +79,7 @@ async fn dispatch(req: DaemonRequest, client: &Client) -> DaemonResponse {
handlers::mark_read(client, &room, &event_id).await
}
DaemonRequest::ListRooms => handlers::list_rooms(client).await,
DaemonRequest::ListInvites => handlers::list_invites(client).await,
DaemonRequest::ListInvites => handlers::list_invites(client),
DaemonRequest::JoinRoom { room } => handlers::join_room(client, &room).await,
DaemonRequest::InviteUser { room, user_id } => {
handlers::invite_user(client, &room, &user_id).await

View file

@ -58,8 +58,8 @@ pub async fn send_wake(socket: &Path, body: impl AsRef<str>) -> Result<()> {
// Drain the response line so the server doesn't get ECONNRESET on
// its write-back. We don't act on the response — best-effort wake.
let mut reader = tokio::io::BufReader::new(read);
let mut _resp = String::new();
let _ = reader.read_line(&mut _resp).await;
let mut resp = String::new();
let _ = reader.read_line(&mut resp).await;
Ok(())
}
@ -86,13 +86,13 @@ pub fn format_unread_summary(rooms: &[crate::protocol::RoomUnread]) -> String {
// Terse path: exactly one room, exactly one unread with body.
if rooms.len() == 1 {
let r = &rooms[0];
if r.count == 1 {
if let (Some(body), Some(sender)) = (&r.last_body, &r.last_sender) {
return format!(
"[matrix] {sender} in {label}: {body} — use read_room to view, mark_read to clear",
label = r.label
);
}
if r.count == 1
&& let (Some(body), Some(sender)) = (&r.last_body, &r.last_sender)
{
return format!(
"[matrix] {sender} in {label}: {body} — use read_room to view, mark_read to clear",
label = r.label
);
}
return format!(
"[matrix] {} unread in {} — use read_room to view, mark_read to clear",
@ -102,11 +102,11 @@ pub fn format_unread_summary(rooms: &[crate::protocol::RoomUnread]) -> String {
// Multi-room path.
let mut out = String::from("[matrix] unread messages:");
for r in rooms {
if r.count == 1 {
if let (Some(body), Some(sender)) = (&r.last_body, &r.last_sender) {
let _ = write!(out, "\n- {}: {sender}: {body}", r.label);
continue;
}
if r.count == 1
&& let (Some(body), Some(sender)) = (&r.last_body, &r.last_sender)
{
let _ = write!(out, "\n- {}: {sender}: {body}", r.label);
continue;
}
let _ = write!(out, "\n- {}: {} unread", r.label, r.count);
}

View file

@ -165,8 +165,8 @@ pub enum PrivRequest {
WriteNspawnFlags {
container: String,
binds: Vec<BindMount>,
/// `None` = host netns (PRIVATE_NETWORK=0). `Some` = private netns with
/// veth on the specified bridge (PRIVATE_NETWORK=1).
/// `None` = host netns (`PRIVATE_NETWORK=0`). `Some` = private netns with
/// veth on the specified bridge (`PRIVATE_NETWORK=1`).
#[serde(default)]
isolation: Option<NetworkIsolation>,
},