use std::fs;
use std::path::{Path, PathBuf};
use chrono::Utc;
use serde_json::json;
use vasari_core::adapters::claude_code::ClaudeCodeAdapter;
use vasari_core::eval::{evaluate, parse_labels, GateStatus, Outcome, MATCH_THRESHOLD, N_MIN};
use vasari_core::ingest::{run_pipeline, IngestAdapter, IngestEvent, IngestSource};
use vasari_core::ObjectStore;
fn ingest_session(store: &ObjectStore, source: &str, prompt: &str, files: &[&str]) {
let ts = Utc::now();
let mut events = vec![
IngestEvent::SessionStart {
source: source.to_string(),
started_at: ts,
},
IngestEvent::UserPrompt {
text: prompt.to_string(),
timestamp: ts,
},
];
for f in files {
events.push(IngestEvent::ToolCall {
name: "Edit".to_string(),
args: json!({ "file_path": f }),
result_summary: "ok".to_string(),
timestamp: ts,
rationale: None,
});
}
run_pipeline(events, store).expect("synthetic ingest must succeed");
}
#[test]
fn evaluator_scores_synthetic_corpus() {
let dir = tempfile::tempdir().unwrap();
let store = ObjectStore::open(dir.path()).unwrap();
ingest_session(
&store,
"sess-a",
"add rate limit middleware to the api",
&["src/api.rs", "src/shared.rs"],
);
ingest_session(
&store,
"sess-b",
"fix the logging format in the logger",
&["src/logger.rs", "src/shared.rs"],
);
let labels_jsonl = r#"
{"file":"src/api.rs","line":1,"expected_intent":["add rate limit middleware to the api"]}
{"file":"src/api.rs","line":1,"expected_intent":["add rate limit middleware to the api"]}
{"file":"src/logger.rs","line":1,"expected_intent":["fix the logging format in the logger"]}
{"file":"src/shared.rs","line":1,"expected_intent":["add rate limit middleware to the api","fix the logging format in the logger"]}
{"file":"src/untouched.rs","line":1,"expected_intent":[]}
"#;
let labels = parse_labels(labels_jsonl.trim()).unwrap();
let report = evaluate(&store, &labels, MATCH_THRESHOLD).unwrap();
assert_eq!(report.n, 4);
assert_eq!(report.status(), GateStatus::InsufficientCorpus);
let row = |f: &str| report.rows.iter().find(|r| r.file == f).unwrap();
assert_eq!(row("src/api.rs").outcome, Outcome::PrecisionHit);
assert_eq!(row("src/logger.rs").outcome, Outcome::PrecisionHit);
assert_eq!(row("src/untouched.rs").outcome, Outcome::NoAnswerCorrect);
let shared = row("src/shared.rs");
assert!(shared.multi_intent);
assert!(
matches!(shared.outcome, Outcome::PrecisionHit | Outcome::RecallOnly),
"multi-intent file must at least recall-hit, got {:?}",
shared.outcome
);
assert_eq!(report.single_correct, report.single_total);
assert!(report.render().contains("gate:"));
assert!(report.render().contains("multi-intent files"));
}
#[test]
#[ignore = "needs committed corpus; run: bash tests/corpus/attribution/run.sh"]
fn attribution_accuracy_gate() {
let corpus = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/corpus/attribution");
let session_files: Vec<PathBuf> = jsonl_files(&corpus.join("sessions"))
.into_iter()
.chain(jsonl_files(&corpus.join("sessions-local")))
.collect();
assert!(
!session_files.is_empty(),
"no corpus sessions in {} — generate them via tests/corpus/attribution/SCRIPT.md",
corpus.join("sessions").display()
);
let dir = tempfile::tempdir().unwrap();
let store = ObjectStore::open(dir.path()).unwrap();
for s in &session_files {
let events = ClaudeCodeAdapter
.parse(IngestSource::File(s.clone()))
.unwrap_or_else(|e| panic!("parsing {}: {e}", s.display()));
run_pipeline(events, &store).unwrap_or_else(|e| panic!("ingesting {}: {e}", s.display()));
}
let label_files = jsonl_files(&corpus.join("labels"))
.into_iter()
.chain(jsonl_files(&corpus.join("labels-local")));
let mut labels = Vec::new();
for l in label_files {
let text = fs::read_to_string(&l).unwrap();
labels.extend(
parse_labels(&text).unwrap_or_else(|e| panic!("parsing labels {}: {e}", l.display())),
);
}
assert!(
!labels.is_empty(),
"no labels in {} — see tests/corpus/attribution/LABELING.md",
corpus.join("labels").display()
);
let report = evaluate(&store, &labels, MATCH_THRESHOLD).unwrap();
let rendered = report.render();
println!("\n{rendered}");
let report_path =
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../target/attribution-report.txt");
let _ = fs::write(&report_path, &rendered);
match report.status() {
GateStatus::Pass => {}
GateStatus::Fail => panic!(
"ACCURACY GATE FAILED: {}\nFull report: {}\nRun: bash tests/corpus/attribution/run.sh",
report.summary_line(),
report_path.display()
),
GateStatus::InsufficientCorpus => panic!(
"INSUFFICIENT_CORPUS: {} (n < {N_MIN}). Grow the corpus via tests/corpus/attribution/SCRIPT.md.\nFull report: {}",
report.summary_line(),
report_path.display()
),
}
}
fn jsonl_files(dir: &Path) -> Vec<PathBuf> {
let mut out = Vec::new();
if let Ok(rd) = fs::read_dir(dir) {
for entry in rd.flatten() {
let p = entry.path();
if p.extension().and_then(|e| e.to_str()) == Some("jsonl") {
out.push(p);
}
}
}
out.sort();
out
}