bobbin-ai 0.25.2

Local-first context injection engine for AI coding agents
//! The eval v2 L0 harness drives every ablation arm through a calibration file
//! it writes itself (eval/runner/l0.py). Kept out of calibrate.rs, which sits at
//! its file-size ceiling.

use super::calibrate::CalibrationResult;
use crate::search::context::BridgeMode;

/// The eval v2 L0 harness drives every ablation arm through a calibration
/// file it writes itself (eval/runner/l0.py). `load_calibration` returns None
/// on a parse error, so a shape drift would silently turn every arm into the
/// default pipeline. These fixtures are generated by the harness and checked
/// on both sides.
#[test]
fn eval_v2_calibration_fixtures_load_with_the_intended_knobs() {
    let full: CalibrationResult =
        serde_json::from_str(include_str!("../../eval/v2/fixtures/calibration-full.json"))
            .expect("full-arm fixture must deserialize");
    assert_eq!(full.best_config.bridge_mode, Some(BridgeMode::Inject));
    assert!((full.best_config.semantic_weight - 0.9).abs() < 1e-6);
    assert_eq!(full.best_config.recency_weight, Some(0.3));

    let noblame: CalibrationResult = serde_json::from_str(include_str!(
        "../../eval/v2/fixtures/calibration-noblame.json"
    ))
    .expect("-blame fixture must deserialize");
    assert_eq!(noblame.best_config.bridge_mode, Some(BridgeMode::Off));

    let nosemantic: CalibrationResult = serde_json::from_str(include_str!(
        "../../eval/v2/fixtures/calibration-nosemantic.json"
    ))
    .expect("-semantic fixture must deserialize");
    assert_eq!(nosemantic.best_config.semantic_weight, 0.0);
    assert_eq!(nosemantic.best_config.budget_lines, None);
}