use crate::client::JevClient;
use crate::types::*;
use std::collections::HashMap;
pub fn safe_truncate_head_tail(s: &str, max_head: usize, max_tail: usize) -> String {
if s.len() <= max_head + max_tail {
return s.to_string();
}
let mut head_end = max_head;
while head_end > 0 && !s.is_char_boundary(head_end) {
head_end -= 1;
}
let mut tail_start = s.len().saturating_sub(max_tail);
while tail_start < s.len() && !s.is_char_boundary(tail_start) {
tail_start += 1;
}
if head_end >= tail_start {
return s.to_string();
}
let truncated_bytes = tail_start - head_end;
format!(
"{}\n\n... [TRUNCATED {} BYTES BY JEV HARNESS] ...\n\n{}",
&s[..head_end],
truncated_bytes,
&s[tail_start..]
)
}
pub async fn triage_test_failure(
raw_error_log: &str,
client: Option<&JevClient>,
) -> Result<TestTriageResult, JevError> {
let default_client = JevClient::default();
let active_client = client.unwrap_or(&default_client);
let truncated_log = if raw_error_log.len() > 3000 {
safe_truncate_head_tail(raw_error_log, 1400, 1400)
} else {
raw_error_log.to_string()
};
let mut questions = HashMap::new();
let mut cat_criteria = HashMap::new();
cat_criteria.insert(
"env_missing".to_string(),
"Missing dependency, package not found, uninstalled CLI tool, or environment path misconfiguration (e.g. TS2307, ModuleNotFoundError, E0463)".to_string(),
);
cat_criteria.insert(
"flaky_transient".to_string(),
"Transient network glitch, connection reset, socket timeout, port conflict, or busy worker (e.g. ETIMEDOUT, ECONNRESET)".to_string(),
);
cat_criteria.insert(
"syntax_trivial".to_string(),
"Simple syntax error, missing bracket, typo in variable, or formatting linter violation".to_string(),
);
cat_criteria.insert(
"deep_logic".to_string(),
"Real semantic bug, failed unit assertion, invariant violation, panic, or unexpected state".to_string(),
);
cat_criteria.insert(
"test_redundant".to_string(),
"Failure is due to an obsolete, redundant, or malformed test case rather than system defect".to_string(),
);
questions.insert(
"category".to_string(),
Question::Choice(ChoiceQuestion {
instructions: "Categorize the primary root cause of this execution failure".to_string(),
criteria: cat_criteria,
}),
);
questions.insert(
"skip_llm".to_string(),
Question::Noul(NoulQuestion {
instructions: "Can this error be handled deterministically (e.g. running pip/npm/cargo install, retrying, or fixing a simple typo) without calling an expensive System 2 generative LLM?".to_string(),
}),
);
questions.insert(
"severity".to_string(),
Question::Score(ScoreQuestion {
instructions: "Rate the severity of this defect on application stability".to_string(),
criteria: vec![
"trivial_cleanup".to_string(),
"blocking_env".to_string(),
"feature_broken".to_string(),
"systemic_disaster".to_string(),
],
}),
);
let resp = active_client.system_one(&truncated_log, questions).await?;
let cat_ans = resp.answers.get("category").and_then(|a| a.as_choice());
let skip_ans = resp.answers.get("skip_llm").and_then(|a| a.as_noul());
let sev_ans = resp.answers.get("severity").and_then(|a| a.as_score());
let category = cat_ans.map(|a| a.choice.clone()).unwrap_or_else(|| "deep_logic".to_string());
let confidence = cat_ans.map(|a| a.confidence).unwrap_or(0.5);
let skip_prob = skip_ans.map(|a| a.noul).unwrap_or(0.0);
let sev_score = sev_ans.map(|a| a.score as f64).unwrap_or(3.0);
let skip_llm = skip_prob >= 0.65 || category == "env_missing" || category == "flaky_transient";
let rec = match category.as_str() {
"env_missing" => "AUTO-ACTION: Install missing dependency or check environment configuration (Do NOT call LLM).",
"flaky_transient" => "AUTO-ACTION: Retry test once with fresh worker; do not generate code changes.",
"syntax_trivial" => "LOW-COST: Fix typo locally or route to fastest lightweight tier.",
"test_redundant" => "PRUNE: Test is redundant or obsolete; prune from test harness.",
_ => "ESCALATE: Real logic defect; dispatch to System 2 LLM with targeted context.",
};
Ok(TestTriageResult {
category,
confidence,
skip_llm,
skip_llm_prob: skip_prob,
severity_score: sev_score,
action_recommendation: rec.to_string(),
is_mock: resp.is_mock,
})
}
pub async fn should_abort_trajectory(
proposed_step: &str,
recent_attempts_summary: &str,
client: Option<&JevClient>,
) -> Result<AbortGateResult, JevError> {
let default_client = JevClient::default();
let active_client = client.unwrap_or(&default_client);
let state = format!(
"RECENT ATTEMPTS & CONTEXT:\n{}\n\nPROPOSED NEXT STEP:\n{}",
recent_attempts_summary, proposed_step
);
let mut questions = HashMap::new();
questions.insert(
"dead_end".to_string(),
Question::Noul(NoulQuestion {
instructions: "Does this proposed step indicate a dead end, repeating a previously failed approach, or proposing an unviable/destructive path?".to_string(),
}),
);
let mut action_criteria = HashMap::new();
action_criteria.insert(
"proceed".to_string(),
"The step is logical, progress-oriented, and grounded in evidence".to_string(),
);
action_criteria.insert(
"replan".to_string(),
"The step is doubtful or weak; reconsider alternatives".to_string(),
);
action_criteria.insert(
"abort_and_ask".to_string(),
"The trajectory is circular or contradictory; stop and ask user for clarification".to_string(),
);
questions.insert(
"action".to_string(),
Question::Choice(ChoiceQuestion {
instructions: "What should the orchestrator do with this proposed trajectory?".to_string(),
criteria: action_criteria,
}),
);
questions.insert(
"viability".to_string(),
Question::Score(ScoreQuestion {
instructions: "Evaluate the technical viability of this step".to_string(),
criteria: vec![
"hopeless_circular".to_string(),
"doubtful".to_string(),
"plausible".to_string(),
"highly_viable".to_string(),
],
}),
);
let resp = active_client.system_one(&state, questions).await?;
let dead_end_ans = resp.answers.get("dead_end").and_then(|a| a.as_noul());
let action_ans = resp.answers.get("action").and_then(|a| a.as_choice());
let viability_ans = resp.answers.get("viability").and_then(|a| a.as_score());
let dead_end_prob = dead_end_ans.map(|a| a.noul).unwrap_or(0.0);
let action = action_ans.map(|a| a.choice.clone()).unwrap_or_else(|| "proceed".to_string());
let viability = viability_ans.map(|a| a.score as f64).unwrap_or(3.0);
let should_abort = dead_end_prob >= 0.70 || action == "abort_and_ask" || viability <= 1.5;
let effective_action = if should_abort && action == "proceed" {
"abort_and_ask".to_string()
} else {
action
};
let summary = if should_abort {
format!("Abort recommended (prob={:.2})", dead_end_prob)
} else {
format!("Safe to proceed (viability={:.1}, action={})", viability, effective_action)
};
Ok(AbortGateResult {
should_abort,
abort_probability: dead_end_prob,
action: effective_action,
viability_score: viability,
reasoning_summary: summary,
is_mock: resp.is_mock,
})
}
pub async fn route_model_tier(
task_description: &str,
client: Option<&JevClient>,
) -> Result<ModelRouteResult, JevError> {
let default_client = JevClient::default();
let active_client = client.unwrap_or(&default_client);
let mut questions = HashMap::new();
let mut tier_criteria = HashMap::new();
tier_criteria.insert(
"deterministic".to_string(),
"Can be solved with bash, regex, deterministic script, or pure Jev classification".to_string(),
);
tier_criteria.insert(
"lightweight_system2".to_string(),
"Simple coding edit, formatting, documentation, or trivial unit test (e.g. Gemini 3.8 Flash)".to_string(),
);
tier_criteria.insert(
"heavy_system2".to_string(),
"Complex architecture, deep reasoning, multi-file refactoring, or difficult debugging (e.g. GPT-6 Astra, Claude Fable 5.1)".to_string(),
);
questions.insert(
"tier".to_string(),
Question::Choice(ChoiceQuestion {
instructions: "Select the minimal sufficient model tier to solve this programming task".to_string(),
criteria: tier_criteria,
}),
);
questions.insert(
"complexity".to_string(),
Question::Score(ScoreQuestion {
instructions: "Rate the cognitive complexity of this task".to_string(),
criteria: vec![
"trivial".to_string(),
"straightforward".to_string(),
"moderate".to_string(),
"highly_complex".to_string(),
],
}),
);
let resp = active_client.system_one(task_description, questions).await?;
let tier_ans = resp.answers.get("tier").and_then(|a| a.as_choice());
let comp_ans = resp.answers.get("complexity").and_then(|a| a.as_score());
let selected_tier = tier_ans.map(|a| a.choice.clone()).unwrap_or_else(|| "lightweight_system2".to_string());
let confidence = tier_ans.map(|a| a.confidence).unwrap_or(0.5);
let complexity_score = comp_ans.map(|a| a.score as f64).unwrap_or(2.0);
let (rec_model, rationale) = match selected_tier.as_str() {
"deterministic" => (
"Direct Python/Bash Script (0 LLM Tokens)",
"Task does not require generative reasoning; execute mechanically.",
),
"heavy_system2" => (
"Claude Fable 5.1 / GPT-6 Astra (~$10.00 in / $50.00 out per 1M tokens)",
"Task requires deep architectural synthesis or multi-file reasoning.",
),
_ => (
"Gemini 3.8 Flash (~$0.75 in / $3.75 out per 1M tokens)",
"Straightforward generative task; lightweight fast agent tier is optimal.",
),
};
Ok(ModelRouteResult {
selected_tier,
confidence,
complexity_score,
recommended_model: rec_model.to_string(),
rationale: rationale.to_string(),
is_mock: resp.is_mock,
})
}
pub async fn verify_step_completion(
criteria: &str,
output: &str,
client: Option<&JevClient>,
) -> Result<VerificationResult, JevError> {
let default_client = JevClient::default();
let active_client = client.unwrap_or(&default_client);
let state = format!("CRITERIA TO SATISFY:\n{}\n\nACTUAL STEP OUTPUT:\n{}", criteria, output);
let mut questions = HashMap::new();
questions.insert(
"satisfied".to_string(),
Question::Noul(NoulQuestion {
instructions: "Does the output fully satisfy all required acceptance criteria without missing details?".to_string(),
}),
);
questions.insert(
"rigor".to_string(),
Question::Score(ScoreQuestion {
instructions: "Score the completeness and rigor of verification evidence".to_string(),
criteria: vec![
"unsupported_claim".to_string(),
"partial_evidence".to_string(),
"well_verified".to_string(),
"exhaustively_proven".to_string(),
],
}),
);
let resp = active_client.system_one(&state, questions).await?;
let sat_ans = resp.answers.get("satisfied").and_then(|a| a.as_noul());
let rig_ans = resp.answers.get("rigor").and_then(|a| a.as_score());
let sat_prob = sat_ans.map(|a| a.noul).unwrap_or(0.0);
let rig_score = rig_ans.map(|a| a.score as f64).unwrap_or(2.0);
let is_verified = sat_prob >= 0.70 && rig_score >= 2.5;
Ok(VerificationResult {
is_verified,
satisfaction_probability: sat_prob,
rigor_score: rig_score,
confidence: sat_prob,
needs_rework: !is_verified,
is_mock: resp.is_mock,
})
}