use std::collections::{HashSet, VecDeque};
use parking_lot::Mutex;
#[must_use]
pub fn normalize_advisor_note(note: &str) -> String {
use unicode_normalization::UnicodeNormalization;
let mut out = String::with_capacity(note.len());
let mut prev_space = true; for c in note.to_lowercase().nfkc() {
if c.is_alphanumeric() {
out.push(c);
prev_space = false;
} else if !prev_space {
out.push(' ');
prev_space = true;
}
}
out.trim().to_string()
}
const SUPPRESSED_NORMALIZED_PHRASES: &[&str] = &[
"stop",
"stop here",
"stop now",
"halt",
"abort",
"done",
"task done",
"task complete",
"complete",
"finished",
"ok",
"okay",
"ok done",
"no issue",
"no issues",
"no issue continue",
"no concerns",
"no concern",
"nothing to add",
"nothing to flag",
"nothing to report",
"no notes",
"no further input",
"no further input needed",
"no further input required",
"no further watcher input",
"no further watcher input needed",
"no further advice",
"no further advice needed",
"lgtm",
"looks good",
"all good",
"agent is on track",
"agent on track",
"on track",
"continue",
"carry on",
];
const DEFAULT_HISTORY_CAPACITY: usize = 4096;
#[derive(Default)]
struct State {
seen: HashSet<String>,
seen_order: VecDeque<String>,
consumed_this_update: bool,
}
pub struct AdvisorEmissionGuard {
state: Mutex<State>,
capacity: usize,
}
impl AdvisorEmissionGuard {
#[must_use]
pub fn new() -> Self {
Self::with_capacity(DEFAULT_HISTORY_CAPACITY)
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
state: Mutex::new(State::default()),
capacity,
}
}
pub fn reset(&self) {
let mut s = self.state.lock();
s.seen.clear();
s.seen_order.clear();
s.consumed_this_update = false;
}
pub fn begin_update(&self) {
self.state.lock().consumed_this_update = false;
}
pub fn accept(&self, note: &str) -> bool {
let key = normalize_advisor_note(note);
if key.is_empty() {
return false;
}
if SUPPRESSED_NORMALIZED_PHRASES.contains(&key.as_str()) {
return false;
}
let mut s = self.state.lock();
if s.seen.contains(&key) {
return false;
}
if s.consumed_this_update {
return false;
}
s.consumed_this_update = true;
s.seen.insert(key.clone());
s.seen_order.push_back(key);
while s.seen_order.len() > self.capacity {
if let Some(stale) = s.seen_order.pop_front() {
s.seen.remove(&stale);
}
}
true
}
}
impl Default for AdvisorEmissionGuard {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
fn guard() -> AdvisorEmissionGuard {
AdvisorEmissionGuard::with_capacity(4)
}
#[test]
fn normalize_folds_punctuation_and_case() {
assert_eq!(normalize_advisor_note("Stop."), "stop");
assert_eq!(normalize_advisor_note("*Stop*"), "stop");
assert_eq!(normalize_advisor_note(" stop "), "stop");
assert_eq!(
normalize_advisor_note("No issue; continue."),
"no issue continue"
);
assert_eq!(normalize_advisor_note(""), "");
assert_eq!(normalize_advisor_note(" "), "");
}
#[test]
fn accept_suppresses_content_free_phrases() {
let g = guard();
for phrase in [
"Stop.",
"STOP!",
"done",
"No issues.",
"looks good",
"continue",
] {
assert!(!g.accept(phrase), "should suppress {phrase:?}");
}
}
#[test]
fn accept_suppresses_empty() {
let g = guard();
assert!(!g.accept(""));
assert!(!g.accept(" "));
}
#[test]
fn accept_one_per_update_then_blocks_second() {
let g = guard();
assert!(g.accept("Use saturating_add to avoid overflow on the counter"));
assert!(!g.accept("Different, valid note about naming"));
g.begin_update();
assert!(!g.accept("Use saturating_add to avoid overflow on the counter"));
assert!(g.accept("Different, valid note about naming"));
}
#[test]
fn accept_dedupes_normalized_variants_across_updates() {
let g = guard();
assert!(g.accept("Use saturating_add."));
g.begin_update();
assert!(!g.accept("use saturating add!"));
}
#[test]
fn fifo_evicts_at_capacity_then_readmits() {
let g = guard();
for i in 0..4 {
assert!(g.accept(&format!("note number {i}")));
g.begin_update();
}
assert!(g.accept("note number 4"));
g.begin_update();
assert!(g.accept("note number 0"));
}
#[test]
fn reset_clears_everything() {
let g = guard();
g.accept("some real advice");
g.reset();
assert!(g.accept("some real advice"));
}
#[test]
fn genuine_blocker_is_not_suppressed() {
let g = guard();
assert!(g.accept("Stop: 'await' missing on writeStream.end() will lose buffered writes."));
}
}