Skip to main content

lean_ctx/core/knowledge_router/
gate_integration.rs

1use super::{
2    PatternReferenceResolver, ReferenceResolver, ResolvedReference, manifest_builder::detect_config,
3};
4use crate::core::{
5    config::Config, context_kernel::ContextState, providers::registry::global_registry,
6};
7#[derive(Debug, Default)]
8pub struct KnowledgeGateAdvisor;
9#[derive(Debug, Clone, Default)]
10pub struct KnowledgeAdvice {
11    pub references_found: Vec<ResolvedReference>,
12    pub suggested_sources: Vec<String>,
13    pub additional_context_hint: Option<String>,
14    pub budget_tokens: u64,
15}
16impl KnowledgeAdvice {
17    #[must_use]
18    pub fn none() -> Self {
19        Self::default()
20    }
21}
22
23impl KnowledgeGateAdvisor {
24    #[must_use]
25    pub fn advise(query: &str, current_context: &ContextState) -> KnowledgeAdvice {
26        Self::advise_with(query, current_context, &PatternReferenceResolver)
27    }
28
29    fn advise_with(
30        query: &str,
31        _current_context: &ContextState,
32        resolver: &dyn ReferenceResolver,
33    ) -> KnowledgeAdvice {
34        let config = Config::load().knowledge_routing;
35        if !config.enabled {
36            return KnowledgeAdvice::none();
37        }
38        let Ok(mut references) =
39            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| resolver.resolve(query)))
40        else {
41            return KnowledgeAdvice::none();
42        };
43        references.truncate(config.max_references);
44        if references.is_empty() {
45            return KnowledgeAdvice::none();
46        }
47        let suggested_sources = references
48            .iter()
49            .map(|reference| reference.source_id.clone())
50            .filter(|source| source_available(source))
51            .collect::<Vec<_>>();
52        let mut hint = format!(
53            "{}\nReferenced: {}",
54            config.hint_prefix,
55            references
56                .iter()
57                .map(reference_label)
58                .collect::<Vec<_>>()
59                .join(", ")
60        );
61        if !suggested_sources.is_empty() {
62            hint.push_str(&format!(
63                "\nConsider fetching additional context from: {}",
64                suggested_sources.join(", ")
65            ));
66        }
67        hint.push_str("\n---");
68        KnowledgeAdvice {
69            budget_tokens: (references.len() as u64 * 500).min(5_000),
70            references_found: references,
71            suggested_sources,
72            additional_context_hint: Some(hint),
73        }
74    }
75}
76
77fn source_available(source: &str) -> bool {
78    let configured = detect_config();
79    matches!(source, "jira") && configured.jira_configured
80        || matches!(source, "github") && configured.github_configured
81        || matches!(source, "gitlab") && configured.gitlab_configured
82        || global_registry()
83            .available_provider_ids()
84            .iter()
85            .any(|id| id == source)
86}
87
88fn reference_label(reference: &ResolvedReference) -> String {
89    let kind = match reference.ref_type {
90        super::reference_resolver::ReferenceType::GitHubPR => "PR",
91        super::reference_resolver::ReferenceType::JiraIssue
92        | super::reference_resolver::ReferenceType::GitHubIssue => "issue",
93        _ => "reference",
94    };
95    format!("{} ({kind})", reference.identifier)
96}
97
98#[cfg(test)]
99#[path = "gate_integration_tests.rs"]
100mod tests;