max / makenotwork
9 files changed,
+87 insertions,
-39 deletions
| @@ -34,3 +34,35 @@ tokio-stream = { version = "0.1", optional = true, features = ["sync"] } | |||
| 34 | 34 | tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "sync", "time"] } | |
| 35 | 35 | tracing-subscriber = { version = "0.3", features = ["env-filter"] } | |
| 36 | 36 | reqwest = { version = "0.13", features = ["json"] } | |
| 37 | + | ||
| 38 | + | [lints.rust] | |
| 39 | + | unused = "warn" | |
| 40 | + | unreachable_pub = "warn" | |
| 41 | + | ||
| 42 | + | [lints.clippy] | |
| 43 | + | pedantic = { level = "warn", priority = -1 } | |
| 44 | + | # Allow-list tuned from a measured breakdown across server/multithreaded/pter | |
| 45 | + | # (2026-07-22). These are the high-churn / low-signal pedantic lints; everything | |
| 46 | + | # else in `pedantic` stays a warning. Keep this block identical across repos. | |
| 47 | + | module_name_repetitions = "allow" | |
| 48 | + | # Doc lints — no docs-completeness push underway. | |
| 49 | + | missing_errors_doc = "allow" | |
| 50 | + | missing_panics_doc = "allow" | |
| 51 | + | doc_markdown = "allow" | |
| 52 | + | # Numeric casts — endemic and mostly intentional in size/byte math. | |
| 53 | + | cast_possible_truncation = "allow" | |
| 54 | + | cast_sign_loss = "allow" | |
| 55 | + | cast_precision_loss = "allow" | |
| 56 | + | cast_possible_wrap = "allow" | |
| 57 | + | cast_lossless = "allow" | |
| 58 | + | # Subjective structure/style nags — high churn, low signal. | |
| 59 | + | must_use_candidate = "allow" | |
| 60 | + | too_many_lines = "allow" | |
| 61 | + | struct_excessive_bools = "allow" | |
| 62 | + | similar_names = "allow" | |
| 63 | + | items_after_statements = "allow" | |
| 64 | + | single_match_else = "allow" | |
| 65 | + | # Frequent false-positives in TUI/router-heavy code — added from the buckets breakdown. | |
| 66 | + | match_same_arms = "allow" | |
| 67 | + | unnecessary_wraps = "allow" | |
| 68 | + | type_complexity = "allow" |
| @@ -29,10 +29,10 @@ struct Add; | |||
| 29 | 29 | ||
| 30 | 30 | #[async_trait] | |
| 31 | 31 | impl Tool for Add { | |
| 32 | - | fn name(&self) -> &str { | |
| 32 | + | fn name(&self) -> &'static str { | |
| 33 | 33 | "add" | |
| 34 | 34 | } | |
| 35 | - | fn description(&self) -> &str { | |
| 35 | + | fn description(&self) -> &'static str { | |
| 36 | 36 | "Add two integers and return the sum." | |
| 37 | 37 | } | |
| 38 | 38 | fn kind(&self) -> ToolKind { | |
| @@ -74,10 +74,10 @@ struct RememberProject; | |||
| 74 | 74 | ||
| 75 | 75 | #[async_trait] | |
| 76 | 76 | impl Tool for RememberProject { | |
| 77 | - | fn name(&self) -> &str { | |
| 77 | + | fn name(&self) -> &'static str { | |
| 78 | 78 | "remember_project" | |
| 79 | 79 | } | |
| 80 | - | fn description(&self) -> &str { | |
| 80 | + | fn description(&self) -> &'static str { | |
| 81 | 81 | "Record a new project the user is working on." | |
| 82 | 82 | } | |
| 83 | 83 | fn kind(&self) -> ToolKind { |
| @@ -118,22 +118,26 @@ impl<P: InferenceProvider> Agent<P> { | |||
| 118 | 118 | } | |
| 119 | 119 | } | |
| 120 | 120 | ||
| 121 | + | #[must_use] | |
| 121 | 122 | pub fn with_max_steps(mut self, n: usize) -> Self { | |
| 122 | 123 | self.config.max_steps = n; | |
| 123 | 124 | self | |
| 124 | 125 | } | |
| 125 | 126 | ||
| 127 | + | #[must_use] | |
| 126 | 128 | pub fn with_projection(mut self, projection: SurfaceProjection) -> Self { | |
| 127 | 129 | self.config.projection = projection; | |
| 128 | 130 | self | |
| 129 | 131 | } | |
| 130 | 132 | ||
| 133 | + | #[must_use] | |
| 131 | 134 | pub fn with_grants(mut self, grants: HashSet<String>) -> Self { | |
| 132 | 135 | self.config.grants = Some(grants); | |
| 133 | 136 | self | |
| 134 | 137 | } | |
| 135 | 138 | ||
| 136 | 139 | /// Bypass capability checks. Only appropriate for fully-trusted callers. | |
| 140 | + | #[must_use] | |
| 137 | 141 | pub fn without_grant_checks(mut self) -> Self { | |
| 138 | 142 | self.config.grants = None; | |
| 139 | 143 | self |
| @@ -29,6 +29,7 @@ impl OllamaProvider { | |||
| 29 | 29 | } | |
| 30 | 30 | } | |
| 31 | 31 | ||
| 32 | + | #[must_use] | |
| 32 | 33 | pub fn with_base_url(mut self, url: impl Into<String>) -> Self { | |
| 33 | 34 | self.base_url = url.into(); | |
| 34 | 35 | self |
| @@ -52,11 +52,13 @@ impl ResourceDescriptor { | |||
| 52 | 52 | } | |
| 53 | 53 | } | |
| 54 | 54 | ||
| 55 | + | #[must_use] | |
| 55 | 56 | pub fn with_description(mut self, description: impl Into<String>) -> Self { | |
| 56 | 57 | self.description = Some(description.into()); | |
| 57 | 58 | self | |
| 58 | 59 | } | |
| 59 | 60 | ||
| 61 | + | #[must_use] | |
| 60 | 62 | pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self { | |
| 61 | 63 | self.mime_type = Some(mime_type.into()); | |
| 62 | 64 | self | |
| @@ -100,6 +102,7 @@ impl ResourceContents { | |||
| 100 | 102 | } | |
| 101 | 103 | } | |
| 102 | 104 | ||
| 105 | + | #[must_use] | |
| 103 | 106 | pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self { | |
| 104 | 107 | self.mime_type = Some(mime_type.into()); | |
| 105 | 108 | self | |
| @@ -231,6 +234,8 @@ pub(crate) struct ReadResourceParams { | |||
| 231 | 234 | /// Wrap a single [`ResourceContents`] into the `resources/read` result shape | |
| 232 | 235 | /// (`{ "contents": [ ... ] }`). | |
| 233 | 236 | #[cfg(feature = "server")] | |
| 237 | + | // contents is consumed into the returned JSON value. | |
| 238 | + | #[allow(clippy::needless_pass_by_value)] | |
| 234 | 239 | pub(crate) fn read_result(contents: ResourceContents) -> serde_json::Value { | |
| 235 | 240 | serde_json::json!({ "contents": [contents] }) | |
| 236 | 241 | } |
| @@ -86,6 +86,7 @@ impl ServeConfig { | |||
| 86 | 86 | } | |
| 87 | 87 | } | |
| 88 | 88 | ||
| 89 | + | #[must_use] | |
| 89 | 90 | pub fn with_projection(mut self, projection: SurfaceProjection) -> Self { | |
| 90 | 91 | self.projection = projection; | |
| 91 | 92 | self | |
| @@ -93,6 +94,7 @@ impl ServeConfig { | |||
| 93 | 94 | ||
| 94 | 95 | /// Attach a resource registry, enabling `resources/list` and | |
| 95 | 96 | /// `resources/read` and advertising the `resources` capability. | |
| 97 | + | #[must_use] | |
| 96 | 98 | pub fn with_resources(mut self, resources: ResourceRegistry) -> Self { | |
| 97 | 99 | self.resources = Some(resources); | |
| 98 | 100 | self | |
| @@ -130,8 +132,7 @@ impl BoundServer { | |||
| 130 | 132 | pub fn cancel_session(&self, session_id: &str, reason: &str) -> usize { | |
| 131 | 133 | self.sessions | |
| 132 | 134 | .get(session_id) | |
| 133 | - | .map(|s| s.cancel_all(reason)) | |
| 134 | - | .unwrap_or(0) | |
| 135 | + | .map_or(0, |s| s.cancel_all(reason)) | |
| 135 | 136 | } | |
| 136 | 137 | ||
| 137 | 138 | /// Ids of the currently-connected sessions. | |
| @@ -283,7 +284,7 @@ async fn handle_sse(State(state): State<AppState>, headers: HeaderMap) -> axum:: | |||
| 283 | 284 | } | |
| 284 | 285 | // A slow session that lagged past the buffer drops those | |
| 285 | 286 | // updates and keeps streaming; it re-reads on next use. | |
| 286 | - | Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => continue, | |
| 287 | + | Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {} | |
| 287 | 288 | Err(tokio::sync::broadcast::error::RecvError::Closed) => break, | |
| 288 | 289 | } | |
| 289 | 290 | } | |
| @@ -429,6 +430,8 @@ fn resources_unsupported(id: Value) -> JsonRpcResponse { | |||
| 429 | 430 | JsonRpcResponse::err(id, codes::METHOD_NOT_FOUND, "server exposes no resources") | |
| 430 | 431 | } | |
| 431 | 432 | ||
| 433 | + | // id/e are consumed into the returned response. | |
| 434 | + | #[allow(clippy::needless_pass_by_value)] | |
| 432 | 435 | fn rpc_err_from(id: Value, e: Error) -> JsonRpcResponse { | |
| 433 | 436 | let (code, message) = match &e { | |
| 434 | 437 | Error::ToolNotFound(_) => (codes::METHOD_NOT_FOUND, e.to_string()), | |
| @@ -474,10 +477,10 @@ mod tests { | |||
| 474 | 477 | struct Echo; | |
| 475 | 478 | #[async_trait] | |
| 476 | 479 | impl Tool for Echo { | |
| 477 | - | fn name(&self) -> &str { | |
| 480 | + | fn name(&self) -> &'static str { | |
| 478 | 481 | "echo" | |
| 479 | 482 | } | |
| 480 | - | fn description(&self) -> &str { | |
| 483 | + | fn description(&self) -> &'static str { | |
| 481 | 484 | "read echo" | |
| 482 | 485 | } | |
| 483 | 486 | fn kind(&self) -> ToolKind { | |
| @@ -497,6 +500,8 @@ mod tests { | |||
| 497 | 500 | r | |
| 498 | 501 | } | |
| 499 | 502 | ||
| 503 | + | // id/params are consumed into the returned request (test helper). | |
| 504 | + | #[allow(clippy::needless_pass_by_value)] | |
| 500 | 505 | fn request(id: Option<Value>, method: &str, params: Value) -> JsonRpcRequest { | |
| 501 | 506 | serde_json::from_value(json!({ | |
| 502 | 507 | "jsonrpc": "2.0", |
| @@ -52,27 +52,27 @@ impl Session { | |||
| 52 | 52 | } | |
| 53 | 53 | } | |
| 54 | 54 | ||
| 55 | - | pub fn subscribe(&self, uri: impl Into<String>) { | |
| 55 | + | pub(crate) fn subscribe(&self, uri: impl Into<String>) { | |
| 56 | 56 | self.subscriptions.lock().unwrap().insert(uri.into()); | |
| 57 | 57 | } | |
| 58 | 58 | ||
| 59 | 59 | /// Remove a subscription. Returns whether it was present. | |
| 60 | - | pub fn unsubscribe(&self, uri: &str) -> bool { | |
| 60 | + | pub(crate) fn unsubscribe(&self, uri: &str) -> bool { | |
| 61 | 61 | self.subscriptions.lock().unwrap().remove(uri) | |
| 62 | 62 | } | |
| 63 | 63 | ||
| 64 | - | pub fn is_subscribed(&self, uri: &str) -> bool { | |
| 64 | + | pub(crate) fn is_subscribed(&self, uri: &str) -> bool { | |
| 65 | 65 | self.subscriptions.lock().unwrap().contains(uri) | |
| 66 | 66 | } | |
| 67 | 67 | ||
| 68 | 68 | /// Attach the SSE out channel (called when the client opens GET `/mcp`). | |
| 69 | 69 | /// Replaces any prior channel — a reconnect supersedes the stale stream. | |
| 70 | - | pub fn attach_out(&self, tx: OutSender) { | |
| 70 | + | pub(crate) fn attach_out(&self, tx: OutSender) { | |
| 71 | 71 | *self.out.lock().unwrap() = Some(tx); | |
| 72 | 72 | } | |
| 73 | 73 | ||
| 74 | 74 | /// Push a notification to the SSE stream if one is connected. | |
| 75 | - | pub fn push(&self, notification: Value) { | |
| 75 | + | pub(crate) fn push(&self, notification: Value) { | |
| 76 | 76 | if let Some(tx) = self.out.lock().unwrap().as_ref() { | |
| 77 | 77 | let _ = tx.send(notification); | |
| 78 | 78 | } | |
| @@ -82,7 +82,7 @@ impl Session { | |||
| 82 | 82 | /// call is registered under `id` until it completes; a concurrent | |
| 83 | 83 | /// [`cancel_request`](Self::cancel_request) or [`cancel_all`](Self::cancel_all) | |
| 84 | 84 | /// aborts it with [`Error::Cancelled`]. | |
| 85 | - | pub async fn run_cancellable<F>(&self, id: &Value, fut: F) -> Result<ToolCallResult> | |
| 85 | + | pub(crate) async fn run_cancellable<F>(&self, id: &Value, fut: F) -> Result<ToolCallResult> | |
| 86 | 86 | where | |
| 87 | 87 | F: Future<Output = Result<ToolCallResult>>, | |
| 88 | 88 | { | |
| @@ -109,7 +109,7 @@ impl Session { | |||
| 109 | 109 | /// Cancel one in-flight request by its (stringified) id. Triggers the | |
| 110 | 110 | /// select in [`run_cancellable`](Self::run_cancellable). No-op if the id is | |
| 111 | 111 | /// not in flight (already finished or never existed). | |
| 112 | - | pub fn cancel_request(&self, id_key: &str) { | |
| 112 | + | pub(crate) fn cancel_request(&self, id_key: &str) { | |
| 113 | 113 | if let Some(entry) = self.inflight.lock().unwrap().remove(id_key) { | |
| 114 | 114 | let _ = entry.cancel.send(()); | |
| 115 | 115 | } | |
| @@ -118,7 +118,7 @@ impl Session { | |||
| 118 | 118 | /// Cancel every in-flight request for this session and tell the client, | |
| 119 | 119 | /// pushing a `notifications/cancelled` for each. This is the app-initiated | |
| 120 | 120 | /// path — a human taking a buffer back. Returns the count cancelled. | |
| 121 | - | pub fn cancel_all(&self, reason: &str) -> usize { | |
| 121 | + | pub(crate) fn cancel_all(&self, reason: &str) -> usize { | |
| 122 | 122 | let drained: Vec<Inflight> = { | |
| 123 | 123 | let mut map = self.inflight.lock().unwrap(); | |
| 124 | 124 | map.drain().map(|(_, v)| v).collect() | |
| @@ -141,28 +141,28 @@ pub(crate) struct Sessions { | |||
| 141 | 141 | ||
| 142 | 142 | impl Sessions { | |
| 143 | 143 | /// Create a fresh session with a random id and return it. | |
| 144 | - | pub fn create(&self) -> Arc<Session> { | |
| 144 | + | pub(super) fn create(&self) -> Arc<Session> { | |
| 145 | 145 | let id = uuid::Uuid::new_v4().to_string(); | |
| 146 | 146 | let session = Arc::new(Session::new(id.clone())); | |
| 147 | 147 | self.map.lock().unwrap().insert(id, session.clone()); | |
| 148 | 148 | session | |
| 149 | 149 | } | |
| 150 | 150 | ||
| 151 | - | pub fn get(&self, id: &str) -> Option<Arc<Session>> { | |
| 151 | + | pub(super) fn get(&self, id: &str) -> Option<Arc<Session>> { | |
| 152 | 152 | self.map.lock().unwrap().get(id).cloned() | |
| 153 | 153 | } | |
| 154 | 154 | ||
| 155 | 155 | /// Remove a session (client sent DELETE `/mcp`). Returns whether present. | |
| 156 | - | pub fn remove(&self, id: &str) -> bool { | |
| 156 | + | pub(super) fn remove(&self, id: &str) -> bool { | |
| 157 | 157 | self.map.lock().unwrap().remove(id).is_some() | |
| 158 | 158 | } | |
| 159 | 159 | ||
| 160 | - | pub fn ids(&self) -> Vec<String> { | |
| 160 | + | pub(super) fn ids(&self) -> Vec<String> { | |
| 161 | 161 | self.map.lock().unwrap().keys().cloned().collect() | |
| 162 | 162 | } | |
| 163 | 163 | ||
| 164 | 164 | /// Cancel in-flight ops across every session. Returns the total cancelled. | |
| 165 | - | pub fn cancel_all(&self, reason: &str) -> usize { | |
| 165 | + | pub(super) fn cancel_all(&self, reason: &str) -> usize { | |
| 166 | 166 | let sessions: Vec<Arc<Session>> = self.map.lock().unwrap().values().cloned().collect(); | |
| 167 | 167 | sessions.iter().map(|s| s.cancel_all(reason)).sum() | |
| 168 | 168 | } |
| @@ -58,7 +58,7 @@ impl ToolKind { | |||
| 58 | 58 | pub fn capability_id(&self) -> Option<&str> { | |
| 59 | 59 | match self { | |
| 60 | 60 | ToolKind::Write(cap) => Some(&cap.id), | |
| 61 | - | _ => None, | |
| 61 | + | ToolKind::Read => None, | |
| 62 | 62 | } | |
| 63 | 63 | } | |
| 64 | 64 | } | |
| @@ -150,7 +150,7 @@ pub enum SurfaceProjection { | |||
| 150 | 150 | } | |
| 151 | 151 | ||
| 152 | 152 | impl SurfaceProjection { | |
| 153 | - | fn includes(&self, small_model_safe: bool) -> bool { | |
| 153 | + | fn includes(self, small_model_safe: bool) -> bool { | |
| 154 | 154 | match self { | |
| 155 | 155 | SurfaceProjection::Full => true, | |
| 156 | 156 | SurfaceProjection::Compact => small_model_safe, | |
| @@ -226,9 +226,10 @@ impl ToolRegistry { | |||
| 226 | 226 | let map = Arc::get_mut(&mut self.tools).expect( | |
| 227 | 227 | "ToolRegistry::register called after cloning; register all tools before serving", | |
| 228 | 228 | ); | |
| 229 | - | if map.contains_key(&name) { | |
| 230 | - | panic!("duplicate tool registration: {name}"); | |
| 231 | - | } | |
| 229 | + | assert!( | |
| 230 | + | !map.contains_key(&name), | |
| 231 | + | "duplicate tool registration: {name}" | |
| 232 | + | ); | |
| 232 | 233 | map.insert(name, Arc::new(tool)); | |
| 233 | 234 | } | |
| 234 | 235 | ||
| @@ -255,7 +256,7 @@ impl ToolRegistry { | |||
| 255 | 256 | }, | |
| 256 | 257 | capability: match t.kind() { | |
| 257 | 258 | ToolKind::Write(cap) => Some(cap), | |
| 258 | - | _ => None, | |
| 259 | + | ToolKind::Read => None, | |
| 259 | 260 | }, | |
| 260 | 261 | refusal_reason: t.refusal_reason().map(str::to_string), | |
| 261 | 262 | }) | |
| @@ -310,10 +311,10 @@ mod tests { | |||
| 310 | 311 | struct Echo; | |
| 311 | 312 | #[async_trait] | |
| 312 | 313 | impl Tool for Echo { | |
| 313 | - | fn name(&self) -> &str { | |
| 314 | + | fn name(&self) -> &'static str { | |
| 314 | 315 | "echo" | |
| 315 | 316 | } | |
| 316 | - | fn description(&self) -> &str { | |
| 317 | + | fn description(&self) -> &'static str { | |
| 317 | 318 | "read-only echo" | |
| 318 | 319 | } | |
| 319 | 320 | fn kind(&self) -> ToolKind { | |
| @@ -333,10 +334,10 @@ mod tests { | |||
| 333 | 334 | struct Mutate; | |
| 334 | 335 | #[async_trait] | |
| 335 | 336 | impl Tool for Mutate { | |
| 336 | - | fn name(&self) -> &str { | |
| 337 | + | fn name(&self) -> &'static str { | |
| 337 | 338 | "mutate" | |
| 338 | 339 | } | |
| 339 | - | fn description(&self) -> &str { | |
| 340 | + | fn description(&self) -> &'static str { | |
| 340 | 341 | "write, gated on `test.write`" | |
| 341 | 342 | } | |
| 342 | 343 | fn kind(&self) -> ToolKind { | |
| @@ -363,7 +364,7 @@ mod tests { | |||
| 363 | 364 | } | |
| 364 | 365 | ||
| 365 | 366 | fn grants(ids: &[&str]) -> HashSet<String> { | |
| 366 | - | ids.iter().map(|s| s.to_string()).collect() | |
| 367 | + | ids.iter().map(std::string::ToString::to_string).collect() | |
| 367 | 368 | } | |
| 368 | 369 | ||
| 369 | 370 | #[tokio::test] |
| @@ -19,10 +19,10 @@ use serde_json::{Value, json}; | |||
| 19 | 19 | struct Ping; | |
| 20 | 20 | #[async_trait] | |
| 21 | 21 | impl Tool for Ping { | |
| 22 | - | fn name(&self) -> &str { | |
| 22 | + | fn name(&self) -> &'static str { | |
| 23 | 23 | "ping" | |
| 24 | 24 | } | |
| 25 | - | fn description(&self) -> &str { | |
| 25 | + | fn description(&self) -> &'static str { | |
| 26 | 26 | "read-only ping" | |
| 27 | 27 | } | |
| 28 | 28 | fn kind(&self) -> ToolKind { | |
| @@ -42,10 +42,10 @@ impl Tool for Ping { | |||
| 42 | 42 | struct Save; | |
| 43 | 43 | #[async_trait] | |
| 44 | 44 | impl Tool for Save { | |
| 45 | - | fn name(&self) -> &str { | |
| 45 | + | fn name(&self) -> &'static str { | |
| 46 | 46 | "save" | |
| 47 | 47 | } | |
| 48 | - | fn description(&self) -> &str { | |
| 48 | + | fn description(&self) -> &'static str { | |
| 49 | 49 | "write, gated on `demo.save`" | |
| 50 | 50 | } | |
| 51 | 51 | fn kind(&self) -> ToolKind { | |
| @@ -72,10 +72,10 @@ fn registry() -> ToolRegistry { | |||
| 72 | 72 | struct Hang; | |
| 73 | 73 | #[async_trait] | |
| 74 | 74 | impl Tool for Hang { | |
| 75 | - | fn name(&self) -> &str { | |
| 75 | + | fn name(&self) -> &'static str { | |
| 76 | 76 | "hang" | |
| 77 | 77 | } | |
| 78 | - | fn description(&self) -> &str { | |
| 78 | + | fn description(&self) -> &'static str { | |
| 79 | 79 | "read-only; blocks until cancelled" | |
| 80 | 80 | } | |
| 81 | 81 | fn kind(&self) -> ToolKind { |