feat(embedding,notes): implement ADR-0056 Blake3 stable hashing and ADR-0057 priority-aware retention

This commit is contained in:
Riz Ashraf committed 2026-10-07 17:04:52 +01:00
1 parent f546579596
commit 3d77e60a02
3 files changed
+38 -10

No files matched your search

+15 -7
View File
@@ -152,7 +152,6 @@ fn fallback_embed(text: &str) -> Vec<f32> {
return vec; return vec;
} }
use std::hash::{Hash, Hasher};
for word in words { for word in words {
let clean: String = word let clean: String = word
.chars() .chars()
@@ -163,18 +162,17 @@ fn fallback_embed(text: &str) -> Vec<f32> {
continue; continue;
} }
let mut hasher = std::collections::hash_map::DefaultHasher::new(); let hash_bytes = blake3::hash(clean.as_bytes());
clean.hash(&mut hasher); let h = u64::from_le_bytes(hash_bytes.as_bytes()[0..8].try_into().unwrap());
let h = hasher.finish();
let idx = (h as usize) % DIM; let idx = (h as usize) % DIM;
let sign = if (h >> 32) & 1 == 0 { 1.0f32 } else { -1.0f32 }; let sign = if (h >> 32) & 1 == 0 { 1.0f32 } else { -1.0f32 };
vec[idx] += sign; vec[idx] += sign;
let chars: Vec<char> = clean.chars().collect(); let chars: Vec<char> = clean.chars().collect();
for window in chars.windows(3) { for window in chars.windows(3) {
let mut h2 = std::collections::hash_map::DefaultHasher::new(); let window_str: String = window.iter().collect();
window.hash(&mut h2); let h2_bytes = blake3::hash(window_str.as_bytes());
let hv = h2.finish(); let hv = u64::from_le_bytes(h2_bytes.as_bytes()[0..8].try_into().unwrap());
let idx2 = (hv as usize) % DIM; let idx2 = (hv as usize) % DIM;
let s2 = if (hv >> 32) & 1 == 0 { 0.5f32 } else { -0.5f32 }; let s2 = if (hv >> 32) & 1 == 0 { 0.5f32 } else { -0.5f32 };
vec[idx2] += s2; vec[idx2] += s2;
@@ -378,4 +376,14 @@ mod tests {
assert_eq!(embedding.len(), 384); assert_eq!(embedding.len(), 384);
} }
} }
#[test]
fn test_fallback_embed_deterministic_stability() {
let text = "The quick brown fox jumps over the lazy dog";
let emb1 = fallback_embed(text);
let emb2 = fallback_embed(text);
assert_eq!(emb1.len(), 384);
assert_eq!(emb1, emb2);
assert!((cosine_similarity(&emb1, &emb2) - 1.0).abs() < 1e-5);
}
} }
+15 -2
View File
@@ -665,7 +665,20 @@ impl McpTool for TechDebtHandler {
effort_estimate: req.effort_estimate, effort_estimate: req.effort_estimate,
}); });
if debts.len() > 300 { if debts.len() > 300 {
debts.remove(0); let severity_rank = |sev: Option<&str>| match sev.unwrap_or("").to_lowercase().as_str() {
"critical" => 4,
"high" => 3,
"medium" => 2,
"low" => 1,
_ => 1,
};
if let Some((idx_to_remove, _)) = debts.iter().enumerate().min_by_key(|(_, d)| {
let status_score = if d.is_resolved { 0 } else { 10 };
let sev_score = severity_rank(d.severity.as_deref());
(status_score + sev_score, d.created_at)
}) {
debts.remove(idx_to_remove);
}
} }
}); });
Ok("Tech debt logged".to_string()) Ok("Tech debt logged".to_string())
@@ -2827,4 +2840,4 @@ mod tests {
.unwrap(); .unwrap();
assert!(pre_res.contains("active_tasks")); assert!(pre_res.contains("active_tasks"));
} }
}
+8 -1
View File
@@ -108,7 +108,14 @@ impl McpTool for AddSessionSummaryHandler {
next_steps: req.next_steps.unwrap_or_default(), next_steps: req.next_steps.unwrap_or_default(),
}); });
if summaries.len() > 200 { if summaries.len() > 200 {
summaries.remove(0); let now = crate::handlers::utils::now_secs();
if let Some((idx_to_remove, _)) = summaries.iter().enumerate().min_by_key(|(_, s)| {
let is_expired = s.expires_at.map(|exp| exp < now).unwrap_or(false);
let status_score = if is_expired { 0 } else { 1 };
(status_score, s.timestamp)
}) {
summaries.remove(idx_to_remove);
}
} }
}); });
Ok("Session summary added".to_string()) Ok("Session summary added".to_string())