lean_ctx/core/providers/
mod.rs1pub mod cache;
2pub mod config;
3pub mod config_provider;
4pub mod connection;
5pub mod credentials;
6pub mod emspace;
7pub mod github;
8pub mod gitlab;
9pub mod hardened_http;
10pub mod health;
11pub mod init;
12pub mod jira;
13pub mod jira_oauth;
14pub mod mcp_bridge;
15pub mod postgres;
16pub mod provenance;
17pub mod provider_trait;
18pub mod registry;
19pub mod scaffold;
20pub mod selected_gitlab;
21pub mod snapshot_projection;
22
23pub use provider_trait::{ContextPacket, ContextProvider, ProviderParams};
24pub use registry::{ProviderRegistry, global_registry};
25
26use serde::{Deserialize, Serialize};
27
28use crate::core::evidence::Claim;
29
30pub(crate) fn intern(s: String) -> &'static str {
35 use std::collections::HashSet;
36 use std::sync::{Mutex, OnceLock};
37 static POOL: OnceLock<Mutex<HashSet<&'static str>>> = OnceLock::new();
38 let pool = POOL.get_or_init(|| Mutex::new(HashSet::new()));
39 let mut guard = pool
40 .lock()
41 .unwrap_or_else(std::sync::PoisonError::into_inner);
42 if let Some(&existing) = guard.get(s.as_str()) {
43 return existing;
44 }
45 let leaked: &'static str = Box::leak(s.into_boxed_str());
46 guard.insert(leaked);
47 leaked
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct ProviderResult {
52 pub provider: String,
53 pub resource_type: String,
54 pub items: Vec<ProviderItem>,
55 pub total_count: Option<usize>,
56 pub truncated: bool,
57}
58
59#[derive(Debug, Clone, Default, Serialize, Deserialize)]
60pub struct ProviderItem {
61 pub id: String,
62 pub title: String,
63 pub state: Option<String>,
64 pub author: Option<String>,
65 pub created_at: Option<String>,
66 pub updated_at: Option<String>,
67 pub url: Option<String>,
68 pub labels: Vec<String>,
69 pub body: Option<String>,
70 #[serde(default, skip_serializing_if = "Vec::is_empty")]
73 pub claims: Vec<Claim>,
74 #[serde(default, skip_serializing_if = "Vec::is_empty")]
78 pub references: Vec<String>,
79}
80
81impl ProviderResult {
82 pub fn format_compact(&self) -> String {
83 let mut out = self.compact_header(self.items.len());
84 for item in &self.items {
85 out.push_str(&item.format_compact());
86 }
87 out
88 }
89
90 pub fn compact_header(&self, count: usize) -> String {
92 format!(
93 "{} {} ({}{}):\n",
94 self.provider,
95 self.resource_type,
96 count,
97 if self.truncated { "+" } else { "" }
98 )
99 }
100}
101
102impl ProviderItem {
103 pub fn format_compact(&self) -> String {
106 let state = self.state.as_deref().unwrap_or("");
107 let labels = if self.labels.is_empty() {
108 String::new()
109 } else {
110 format!(" [{}]", self.labels.join(","))
111 };
112 let mut out = format!(" #{} {} ({}){}\n", self.id, self.title, state, labels);
113 for claim in &self.claims {
114 out.push_str(" ▸ ");
115 out.push_str(&claim.render());
116 out.push('\n');
117 }
118 out
119 }
120}