#![allow(dead_code)]
use super::decisions::{DecisionRecord, DecisionType, read_all_decisions};
fn dynamic_rejection_weight(decisions: &[&DecisionRecord]) -> i32 {
let mut accepts: u32 = 0;
let mut rejects: u32 = 0;
for d in decisions {
if d.is_positive() {
accepts += 1;
} else if d.is_negative() {
rejects += 1;
}
}
let weight = accepts as f64 / rejects.max(1) as f64;
weight.clamp(3.0, 12.0) as i32
}
pub fn retrieve_similar(
tool: Option<&str>,
project: &str,
limit: usize,
decision_type: Option<DecisionType>,
) -> Vec<DecisionRecord> {
if limit == 0 {
return Vec::new();
}
let all = read_all_decisions();
if all.is_empty() {
return Vec::new();
}
let filtered: Vec<&DecisionRecord> = if let Some(dt) = decision_type {
all.iter().filter(|d| d.decision_type == dt).collect()
} else {
all.iter().collect()
};
if filtered.is_empty() {
return Vec::new();
}
let rejection_weight = dynamic_rejection_weight(&filtered);
let mut scored: Vec<(i32, usize, &DecisionRecord)> = filtered
.iter()
.enumerate()
.map(|(idx, d)| {
let mut score: i32 = 0;
if let Some(t) = tool {
if d.tool.as_deref() == Some(t) {
score += 10;
}
}
if d.project.to_lowercase().contains(&project.to_lowercase()) {
score += 5;
}
if d.is_observation() {
score += 2; } else if d.is_positive() {
score += 3; } else if d.is_negative() {
score += rejection_weight; }
let recency = if filtered.len() > 1 {
(idx as i32 * 2) / (filtered.len() as i32 - 1)
} else {
2
};
score += recency;
(score, idx, *d)
})
.collect();
scored.sort_by(|a, b| b.0.cmp(&a.0).then_with(|| b.1.cmp(&a.1)));
scored.truncate(limit);
scored.into_iter().map(|(_, _, d)| d.clone()).collect()
}
pub fn format_few_shot_examples(decisions: &[DecisionRecord]) -> String {
if decisions.is_empty() {
return String::new();
}
let mut lines = Vec::new();
for d in decisions {
let tool = d.tool.as_deref().unwrap_or("?");
let cmd = d
.command
.as_deref()
.map(|c| {
if c.len() > 80 {
format!("{}...", crate::session::truncate_str(c, 80))
} else {
c.to_string()
}
})
.unwrap_or_default();
let cmd_part = if cmd.is_empty() {
String::new()
} else {
format!(", command=\"{cmd}\"")
};
if d.is_observation() {
lines.push(format!(
"[tool={tool}{cmd_part}] user action: {}",
d.user_action,
));
} else {
lines.push(format!(
"[tool={tool}{cmd_part}] brain: {} ({}%) → user: {}",
d.brain_action,
(d.brain_confidence * 100.0) as u32,
d.user_action,
));
}
}
lines.join("\n")
}
#[cfg(test)]
mod tests {
use super::super::decisions::DecisionType;
use super::*;
fn make_decision(tool: &str, project: &str, user_action: &str) -> DecisionRecord {
DecisionRecord {
timestamp: "0".into(),
pid: 1,
project: project.into(),
tool: Some(tool.into()),
command: Some("test cmd".into()),
brain_action: "approve".into(),
brain_confidence: 0.9,
brain_reasoning: "test".into(),
user_action: user_action.into(),
context: None,
outcome: None,
decision_type: DecisionType::Session,
suggested_at: None,
resolved_at: None,
override_reason: None,
decision_id: None,
}
}
#[test]
fn format_few_shot_empty() {
assert_eq!(format_few_shot_examples(&[]), "");
}
#[test]
fn format_few_shot_single() {
let d = make_decision("Bash", "my-project", "accept");
let output = format_few_shot_examples(&[d]);
assert!(output.contains("tool=Bash"));
assert!(output.contains("user: accept"));
assert!(output.contains("90%"));
}
#[test]
fn format_few_shot_multiple() {
let decisions = vec![
make_decision("Bash", "proj", "accept"),
make_decision("Read", "proj", "reject"),
];
let output = format_few_shot_examples(&decisions);
let lines: Vec<&str> = output.lines().collect();
assert_eq!(lines.len(), 2);
assert!(lines[0].contains("Bash"));
assert!(lines[1].contains("Read"));
}
#[test]
fn retrieve_empty_returns_empty() {
let result = retrieve_similar(Some("Bash"), "test", 5, None);
assert!(result.is_empty() || !result.is_empty()); }
#[test]
fn format_few_shot_observation_format() {
let d = make_decision("Read", "proj", "user_approve");
let output = format_few_shot_examples(&[d]);
assert!(output.contains("user action: user_approve"));
assert!(!output.contains("brain:"));
}
#[test]
fn format_few_shot_brain_decision_format() {
let d = make_decision("Bash", "proj", "accept");
let output = format_few_shot_examples(&[d]);
assert!(output.contains("brain: approve"));
assert!(output.contains("user: accept"));
}
#[test]
fn outcome_weighted_retrieval_prefers_corrections() {
let decisions = [
make_decision("Bash", "proj", "accept"),
make_decision("Bash", "proj", "reject"),
];
let reject = &decisions[1];
assert!(reject.is_negative());
}
#[test]
fn dynamic_rejection_weight_typical_ratio() {
let mut decisions: Vec<DecisionRecord> = (0..9)
.map(|_| make_decision("Bash", "proj", "accept"))
.collect();
decisions.push(make_decision("Bash", "proj", "reject"));
let refs: Vec<&DecisionRecord> = decisions.iter().collect();
assert_eq!(dynamic_rejection_weight(&refs), 9);
}
#[test]
fn dynamic_rejection_weight_frequent_rejects() {
let mut decisions: Vec<DecisionRecord> = (0..6)
.map(|_| make_decision("Bash", "proj", "accept"))
.collect();
decisions.extend((0..4).map(|_| make_decision("Bash", "proj", "reject")));
let refs: Vec<&DecisionRecord> = decisions.iter().collect();
assert_eq!(dynamic_rejection_weight(&refs), 3);
}
#[test]
fn dynamic_rejection_weight_rare_rejects() {
let mut decisions: Vec<DecisionRecord> = (0..99)
.map(|_| make_decision("Bash", "proj", "accept"))
.collect();
decisions.push(make_decision("Bash", "proj", "reject"));
let refs: Vec<&DecisionRecord> = decisions.iter().collect();
assert_eq!(dynamic_rejection_weight(&refs), 12);
}
#[test]
fn dynamic_rejection_weight_no_rejects() {
let decisions: Vec<DecisionRecord> = (0..10)
.map(|_| make_decision("Bash", "proj", "accept"))
.collect();
let refs: Vec<&DecisionRecord> = decisions.iter().collect();
assert_eq!(dynamic_rejection_weight(&refs), 10);
}
#[test]
fn dynamic_rejection_weight_no_accepts() {
let decisions: Vec<DecisionRecord> = (0..10)
.map(|_| make_decision("Bash", "proj", "reject"))
.collect();
let refs: Vec<&DecisionRecord> = decisions.iter().collect();
assert_eq!(dynamic_rejection_weight(&refs), 3);
}
#[test]
fn dynamic_rejection_weight_only_observations() {
let decisions: Vec<DecisionRecord> = (0..5)
.map(|_| make_decision("Read", "proj", "user_input"))
.collect();
let refs: Vec<&DecisionRecord> = decisions.iter().collect();
assert_eq!(dynamic_rejection_weight(&refs), 3);
}
}