Skip to main content

lean_ctx/tools/
ctx_intent.rs

1use crate::core::cache::SessionCache;
2use crate::core::intent_engine::{classify, route_intent};
3use crate::core::intent_protocol::{IntentRecord, IntentSubject};
4use crate::tools::CrpMode;
5
6pub fn handle(
7    _cache: &mut SessionCache,
8    query: &str,
9    project_root: &str,
10    _crp_mode: CrpMode,
11) -> String {
12    if query.trim().is_empty() {
13        return "ERROR: ctx_intent requires query".to_string();
14    }
15
16    let intent = crate::core::intent_protocol::intent_from_query(query, Some(project_root));
17    let classification = classify(query);
18    let route = route_intent(query, &classification);
19    format_ack(&intent, &route)
20}
21
22fn format_ack(intent: &IntentRecord, route: &crate::core::intent_engine::IntentRoute) -> String {
23    format!(
24        "INTENT_OK id={} type={} source={} conf={:.0}% subj={} | route: dimension={} model_tier={} reason={}",
25        intent.id,
26        intent.intent_type.as_str(),
27        intent.source.as_str(),
28        (intent.confidence.clamp(0.0, 1.0) * 100.0).round(),
29        subject_short(&intent.subject),
30        route.dimension.as_str(),
31        route.model_tier.as_str(),
32        route.reasoning,
33    )
34}
35
36fn subject_short(s: &IntentSubject) -> String {
37    match s {
38        IntentSubject::Project { root } => format!("project({})", root.as_deref().unwrap_or(".")),
39        IntentSubject::Command { command } => format!("cmd({})", truncate(command, 80)),
40        IntentSubject::Workflow { action } => format!("workflow({})", truncate(action, 60)),
41        IntentSubject::KnowledgeFact { category, key, .. } => format!("fact({category}/{key})"),
42        IntentSubject::KnowledgeQuery { category, query } => format!(
43            "recall({}/{})",
44            category.as_deref().unwrap_or("-"),
45            query.as_deref().unwrap_or("-")
46        ),
47        IntentSubject::Tool { name } => format!("tool({name})"),
48    }
49}
50
51fn truncate(s: &str, max: usize) -> String {
52    if s.chars().count() <= max {
53        return s.to_string();
54    }
55    let mut out = String::new();
56    for (i, ch) in s.chars().enumerate() {
57        if i + 1 >= max {
58            break;
59        }
60        out.push(ch);
61    }
62    out.push('…');
63    out
64}