Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/2.0.0.

## [Unreleased]

### Fixed

- Restored the no-`#[allow(clippy::*)]` lint contract. Fixed: a private `TextStreamer` type alias, lossless integer-to-float casts (centralized in an internal `numeric` module), `PartLane`/`TerminalStage` lane enums replacing bool fields, and stale-allow deletions. No public API change.

## [0.2.0] - 2026-08-02

### Added
Expand Down
3 changes: 1 addition & 2 deletions src/compact/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,8 +378,7 @@ impl ContextOverflow {
if self.context_window == 0 {
return f64::INFINITY;
}
f64::from(u32::try_from(self.tokens_used).unwrap_or(u32::MAX))
/ f64::from(u32::try_from(self.context_window).unwrap_or(u32::MAX))
crate::numeric::unit_ratio(self.tokens_used, self.context_window)
}
}

Expand Down
15 changes: 9 additions & 6 deletions src/detection/convergence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -796,7 +796,6 @@ impl ConvergenceDetector {
/// intersection size to union size.
///
/// Returns `0.0` if either input is empty.
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn compute_similarity(a: &str, b: &str) -> f32 {
if a.is_empty() || b.is_empty() {
Expand All @@ -817,11 +816,7 @@ impl ConvergenceDetector {
let intersection = a_words.intersection(&b_words).count();
let union = a_words.union(&b_words).count();

if union == 0 {
return 0.0;
}

intersection as f32 / union as f32
crate::numeric::unit_ratio(intersection, union)
}

fn normalize_text(text: &str) -> String {
Expand Down Expand Up @@ -855,6 +850,14 @@ impl Default for ConvergenceDetector {
mod tests {
use super::*;

#[test]
fn compute_similarity_result_unchanged_after_cast_fix() {
let identical = ConvergenceDetector::compute_similarity("abc", "abc");
let disjoint = ConvergenceDetector::compute_similarity("abc", "xyz");
assert!((identical - 1.0).abs() < f32::EPSILON);
assert!(disjoint.abs() < f32::EPSILON);
}

#[test]
fn test_convergence_detection() {
let config = ConvergenceConfig {
Expand Down
28 changes: 24 additions & 4 deletions src/engine/bare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,18 @@ mod message;
#[cfg(feature = "streaming")]
mod stream;

/// Shared callback invoked once per text delta during streaming.
///
/// A clonable, thread-safe closure stored in [`BareLoop`] via
/// [`set_text_streamer`](BareLoop::set_text_streamer) and invoked from the
/// streaming engine path on every [`IndexedDelta`](crate::stream::IndexedDelta)
/// whose payload is [`Text`](crate::stream::DeltaPart::Text). The bounds
/// mirror the requirements of that path: `Send + Sync` because the engine may
/// dispatch deltas from an async task, and `Arc` so the same callback can be
/// shared across the engine and any observer without copying the closure.
#[cfg(feature = "streaming")]
type TextStreamer = Arc<dyn Fn(&str) + Send + Sync>;

/// How the engine fulfils each LLM turn.
///
/// `BareLoop` drives every turn by asking the [`ApiClient`] for a response
Expand Down Expand Up @@ -293,8 +305,7 @@ pub struct BareLoop<C: ApiClient> {
/// a `Text` payload, enabling real-time token display. Only read by
/// the streaming engine path; absent under `default = []`.
#[cfg(feature = "streaming")]
#[allow(clippy::type_complexity)]
text_streamer: Option<Arc<dyn Fn(&str) + Send + Sync>>,
text_streamer: Option<TextStreamer>,

/// Turn-boundary context contributors.
///
Expand Down Expand Up @@ -971,7 +982,7 @@ impl<C: ApiClient> BareLoop<C> {
/// buf.lock().unwrap_or_else(|e| e.into_inner()).push_str(delta);
/// }));
/// ```
pub fn set_text_streamer(&mut self, f: Arc<dyn Fn(&str) + Send + Sync>) {
pub fn set_text_streamer(&mut self, f: TextStreamer) {
self.debug_assert_idle();
self.text_streamer = Some(f);
}
Expand Down Expand Up @@ -1173,7 +1184,7 @@ impl<C: ApiClient> BareLoop<C> {
/// mirror of [`set_text_streamer`](BareLoop::set_text_streamer).
#[cfg(feature = "streaming")]
#[must_use]
pub fn with_text_streamer(mut self, f: Arc<dyn Fn(&str) + Send + Sync>) -> Self {
pub fn with_text_streamer(mut self, f: TextStreamer) -> Self {
self.set_text_streamer(f);
self
}
Expand Down Expand Up @@ -2147,6 +2158,15 @@ mod tests {

use std::sync::Mutex;

#[cfg(feature = "streaming")]
#[test]
fn text_streamer_alias_compiles_unchanged() {
let client = MockClient::new("test-model");
let mut agent = BareLoop::new(Arc::new(client), ToolRegistry::new(), make_config());
agent.set_text_streamer(Arc::new(|_| ()));
assert!(agent.text_streamer.is_some());
}

/// Fold queued [`StreamEvent`]s into a [`NonStreamingResponse`].
///
/// Shared by `MockClient` and `RecordingClient` `create_message` impls so
Expand Down
5 changes: 2 additions & 3 deletions src/engine/bare/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,6 @@ impl<C: ApiClient> BareLoop<C> {
}

#[cfg(all(test, feature = "testing"))]
#[allow(clippy::unnecessary_literal_bound)]
mod tests {
use crate::api::error::ApiError;
use crate::config::SessionConfig;
Expand Down Expand Up @@ -990,10 +989,10 @@ mod tests {
struct PanicTool;

impl Tool for PanicTool {
fn name(&self) -> &str {
fn name(&self) -> &'static str {
"panic_tool"
}
fn description(&self) -> &str {
fn description(&self) -> &'static str {
"Panics on call"
}
fn schema(&self) -> ToolSchema {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ pub mod managers;
pub mod memory;
pub mod message;
pub mod middleware;
pub(crate) mod numeric;
pub mod observer;
pub mod presets;
#[cfg(feature = "providers")]
Expand Down
24 changes: 22 additions & 2 deletions src/memory/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,9 @@ impl LoopMemory for InMemoryStore {
.filter(|w| memory_lower.contains(*w))
.count();
let base_score = entry.relevance;
#[allow(clippy::cast_precision_loss)]
let denom = query_words.len().max(1);
let query_bonus = if word_matches > 0 {
word_matches as f32 / query_words.len().max(1) as f32
crate::numeric::unit_ratio(word_matches, denom)
} else {
0.0
};
Expand Down Expand Up @@ -508,6 +508,26 @@ mod tests {
assert!((results[2].relevance - 0.1).abs() < 1e-6);
}

#[tokio::test]
async fn retrieve_ranks_more_word_matches_higher_at_equal_relevance() {
let store = InMemoryStore::new();

let mut one_match = MemoryEntry::new(MemoryCategory::Fact, "alpha only");
one_match.relevance = 0.5;
let mut many_matches = MemoryEntry::new(MemoryCategory::Fact, "alpha beta gamma");
many_matches.relevance = 0.5;

store.store(one_match).await.unwrap();
store.store(many_matches).await.unwrap();

let results = store.retrieve("alpha beta gamma", 2).await.unwrap();
assert_eq!(results.len(), 2);
assert!(
results[0].memory.contains("alpha beta gamma"),
"the entry matching more query words must rank higher at equal relevance"
);
}

#[tokio::test]
async fn loop_memory_round_trips() {
use crate::memory::{LoopMemory, MemoryCategory, MemoryEntry};
Expand Down
5 changes: 2 additions & 3 deletions src/middleware/tool_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ impl ToolCallMiddleware {
}

#[cfg(test)]
#[allow(clippy::unnecessary_literal_bound)]
mod tests {
use super::*;
use crate::cancel::CancelSignal;
Expand All @@ -130,10 +129,10 @@ mod tests {
}

impl Tool for PanickingTool {
fn name(&self) -> &str {
fn name(&self) -> &'static str {
"panic_tool"
}
fn description(&self) -> &str {
fn description(&self) -> &'static str {
"A tool that panics"
}
fn schema(&self) -> ToolSchema {
Expand Down
Loading