Skip to main content

lean_ctx/core/knowledge_router/
reference_resolver.rs

1use regex::Regex;
2use std::path::Path;
3
4#[derive(Debug, Clone, Default, PartialEq, Eq)]
5pub struct ResolvedReference {
6    pub ref_type: ReferenceType,
7    pub identifier: String,
8    pub source_id: String,
9    pub confidence_milli: u16,
10}
11#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
12pub enum ReferenceType {
13    #[default]
14    JiraIssue,
15    GitHubPR,
16    GitHubIssue,
17    FilePath,
18    Function,
19    Url,
20}
21pub trait ReferenceResolver: std::fmt::Debug + Send + Sync {
22    fn resolve(&self, text: &str) -> Vec<ResolvedReference>;
23    fn name(&self) -> &'static str;
24}
25#[derive(Debug, Clone, Default)]
26pub struct PatternReferenceResolver;
27
28impl ReferenceResolver for PatternReferenceResolver {
29    fn resolve(&self, text: &str) -> Vec<ResolvedReference> {
30        let jira = Regex::new(r"\b[A-Z][A-Z0-9]+-[0-9]+\b").expect("valid Jira regex");
31        let github = Regex::new(r"#[0-9]+\b").expect("valid GitHub regex");
32        let files = Regex::new(r"\b(?:[A-Za-z0-9_.-]+/)*[A-Za-z0-9_.-]+\.(?:rs|ts|py)\b")
33            .expect("valid file regex");
34        let mut result = jira
35            .find_iter(text)
36            .map(|m| reference(ReferenceType::JiraIssue, m.as_str(), "jira", 950))
37            .collect::<Vec<_>>();
38        result.extend(
39            github
40                .find_iter(text)
41                .map(|m| reference(ReferenceType::GitHubPR, m.as_str(), "github", 900)),
42        );
43        result.extend(
44            files
45                .find_iter(text)
46                .filter(|m| Path::new(m.as_str()).exists())
47                .map(|m| reference(ReferenceType::FilePath, m.as_str(), "local_files", 980)),
48        );
49        result
50    }
51    fn name(&self) -> &'static str {
52        "pattern"
53    }
54}
55fn reference(
56    ref_type: ReferenceType,
57    identifier: &str,
58    source_id: &str,
59    confidence_milli: u16,
60) -> ResolvedReference {
61    ResolvedReference {
62        ref_type,
63        identifier: identifier.into(),
64        source_id: source_id.into(),
65        confidence_milli,
66    }
67}
68#[cfg(test)]
69mod tests {
70    use super::*;
71    #[test]
72    fn detects_jira_keys() {
73        assert_eq!(
74            PatternReferenceResolver.resolve("fix LEAN-42")[0].ref_type,
75            ReferenceType::JiraIssue
76        );
77    }
78    #[test]
79    fn detects_github_issues() {
80        assert_eq!(
81            PatternReferenceResolver.resolve("see #789")[0].identifier,
82            "#789"
83        );
84    }
85    #[test]
86    fn detects_existing_rust_paths() {
87        assert_eq!(
88            // A real path of this crate (the binary moved to crates/lean-ctx).
89            PatternReferenceResolver.resolve("src/lib.rs")[0].ref_type,
90            ReferenceType::FilePath
91        );
92    }
93    #[test]
94    fn ignores_nonexistent_paths() {
95        {
96            let found = &PatternReferenceResolver.resolve("missing/file.rs");
97            assert!(found.is_empty(), "{found:?}");
98        }
99    }
100}