lean_ctx/core/knowledge_router/
gate_integration.rs1use 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;