Skip to content

Commit 32da4bc

Browse files
committed
feat(update): working on next release
♬ Dark Moor - Living in a Nightmare
1 parent 59bf62d commit 32da4bc

67 files changed

Lines changed: 3483 additions & 1596 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎Cargo.toml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,10 @@ lto = true
118118
codegen-units = 1
119119
strip = true
120120
panic = "abort"
121+
122+
# Keeps filename and line in panic backtraces while dropping the bulk of the debug
123+
# info. The test binary was 242M with full debug info, which matters because a
124+
# mutation-testing run copies the target directory once per parallel job and this
125+
# repository's target is 9.7G against a 7.7G tmpfs /tmp.
126+
[profile.test]
127+
debug = "line-tables-only"

‎src/agent/session.rs‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ impl AgentSession {
144144
.collect();
145145
match self.client.chat_with_tools(&messages, &schemas).await {
146146
Ok(response) => {
147-
let _model_used = response.model_used.clone();
148147
if !response.tool_calls.is_empty() {
149148
let (new_tools_used, new_test_path, new_compile_path, new_messages) =
150149
execute_tool_calls(
@@ -416,7 +415,6 @@ impl AgentSession {
416415

417416
match self.client.chat_with_tools(&messages, &schemas).await {
418417
Ok(response) => {
419-
let _model_used = response.model_used.clone();
420418
if !response.tool_calls.is_empty() {
421419
let (new_tools_used, new_test_path, new_compile_path, new_messages) =
422420
execute_tool_calls(

‎src/confidence_normalization.rs‎

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
//! Confidence normalization based on project baselines.
2+
//!
3+
//! Provides per-project confidence calibration using historical triage data.
4+
//!
5+
//! Paper: Closing the Gap — arxiv:2412.14306
6+
7+
use crate::config::{NormalizationConfig, NormalizationTier};
8+
use std::fs;
9+
use std::path::PathBuf;
10+
11+
/// Project baseline for confidence normalization.
12+
///
13+
/// Stores historical triage outcomes to enable per-project confidence calibration.
14+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
15+
pub struct ProjectBaseline {
16+
/// Total number of findings analyzed.
17+
pub total_findings: usize,
18+
/// Number of true positives confirmed.
19+
pub true_positives: usize,
20+
/// Number of false positives identified.
21+
pub false_positives: usize,
22+
/// Mean confidence score of all findings.
23+
pub mean_confidence: f32,
24+
/// Sum of squared deviations for std dev calculation.
25+
#[serde(default)]
26+
pub sum_sq_dev: f32,
27+
}
28+
29+
impl ProjectBaseline {
30+
/// Create an empty baseline.
31+
pub fn empty() -> Self {
32+
Self {
33+
total_findings: 0,
34+
true_positives: 0,
35+
false_positives: 0,
36+
mean_confidence: 0.0,
37+
sum_sq_dev: 0.0,
38+
}
39+
}
40+
41+
/// Load baseline from a file path.
42+
///
43+
/// Returns empty baseline if file doesn't exist or is invalid.
44+
pub fn load(path: &PathBuf) -> Self {
45+
if !path.exists() {
46+
return Self::empty();
47+
}
48+
49+
match fs::read_to_string(path) {
50+
Ok(content) => match serde_json::from_str(&content) {
51+
Ok(baseline) => baseline,
52+
Err(e) => {
53+
tracing::warn!("Failed to parse baseline at {:?}: {}", path, e);
54+
Self::empty()
55+
}
56+
},
57+
Err(e) => {
58+
tracing::warn!("Failed to read baseline at {:?}: {}", path, e);
59+
Self::empty()
60+
}
61+
}
62+
}
63+
64+
/// Save baseline to a file path.
65+
pub fn save(&self, path: &PathBuf) -> std::io::Result<()> {
66+
let json = serde_json::to_string_pretty(self).map_err(std::io::Error::other)?;
67+
68+
// Ensure parent directory exists
69+
if let Some(parent) = path.parent() {
70+
fs::create_dir_all(parent)?;
71+
}
72+
73+
fs::write(path, json)
74+
}
75+
76+
/// Get false positive rate.
77+
pub fn false_positive_rate(&self) -> f32 {
78+
if self.total_findings == 0 {
79+
return 0.0;
80+
}
81+
self.false_positives as f32 / self.total_findings as f32
82+
}
83+
84+
/// Get standard deviation of confidence scores.
85+
pub fn std_dev(&self) -> f32 {
86+
if self.total_findings <= 1 {
87+
return 0.0;
88+
}
89+
(self.sum_sq_dev / self.total_findings as f32).sqrt()
90+
}
91+
92+
/// Update baseline with a new finding's confidence score.
93+
pub fn update(&mut self, confidence: f32, is_true_positive: bool) {
94+
let old_mean = self.mean_confidence;
95+
self.total_findings += 1;
96+
97+
// Update mean using Welford's online algorithm
98+
self.mean_confidence = old_mean + (confidence - old_mean) / self.total_findings as f32;
99+
100+
// Update sum of squared deviations
101+
self.sum_sq_dev += (confidence - old_mean) * (confidence - self.mean_confidence);
102+
103+
// Update TP/FP counts
104+
if is_true_positive {
105+
self.true_positives += 1;
106+
} else {
107+
self.false_positives += 1;
108+
}
109+
}
110+
}
111+
112+
/// Normalize confidence score based on project baseline.
113+
///
114+
/// # Arguments
115+
/// * `raw_confidence` - Original confidence score
116+
/// * `config` - Normalization configuration
117+
/// * `baseline` - Project baseline with historical data
118+
///
119+
/// # Returns
120+
/// Normalized confidence score
121+
pub fn normalize_confidence(
122+
raw_confidence: f32,
123+
config: &NormalizationConfig,
124+
baseline: &ProjectBaseline,
125+
) -> f32 {
126+
if !config.enabled {
127+
return raw_confidence;
128+
}
129+
130+
match config.normalization_tier {
131+
NormalizationTier::None => raw_confidence,
132+
133+
NormalizationTier::ProjectRelative => {
134+
let fp_rate = baseline.false_positive_rate();
135+
136+
if fp_rate > 0.30 {
137+
// High FP rate: scale down
138+
let scale = 1.0 - fp_rate * 0.5;
139+
raw_confidence * scale
140+
} else if fp_rate < 0.10 {
141+
// Low FP rate: scale up (capped at 1.0)
142+
let scale = 1.0 + (0.10 - fp_rate) * 2.0;
143+
(raw_confidence * scale).min(1.0)
144+
} else {
145+
// Medium FP rate: no adjustment
146+
raw_confidence
147+
}
148+
}
149+
150+
NormalizationTier::Isotonic => {
151+
// Apply simple linear calibration
152+
let std_dev = baseline.std_dev();
153+
154+
// Fallback to raw if std_dev is 0 or baseline has <10 findings
155+
if std_dev == 0.0 || baseline.total_findings < 10 {
156+
return raw_confidence;
157+
}
158+
159+
let calibrated = (raw_confidence - baseline.mean_confidence) / std_dev * 0.5 + 0.5;
160+
calibrated.clamp(0.0, 1.0)
161+
}
162+
}
163+
}

0 commit comments

Comments
 (0)