acp/bridge/
mod.rs

1//! @acp:module "Documentation System Bridging"
2//! @acp:summary "RFC-0006: Bridges native documentation formats (JSDoc, docstrings, etc.) to ACP"
3//! @acp:domain cli
4//! @acp:layer service
5//! @acp:stability experimental
6//!
7//! # Documentation System Bridging
8//!
9//! This module implements RFC-0006 Documentation System Bridging, enabling
10//! ACP to leverage existing documentation from native formats:
11//!
12//! - JSDoc/TSDoc (JavaScript/TypeScript)
13//! - Python docstrings (Google, NumPy, Sphinx)
14//! - Rust doc comments
15//! - Go doc comments
16//! - Javadoc
17//!
18//! ## Core Components
19//!
20//! - [`BridgeConfig`]: Configuration for bridging behavior
21//! - [`FormatDetector`]: Auto-detects documentation format
22//! - [`BridgeMerger`]: Merges native docs with ACP annotations
23//!
24//! ## Usage
25//!
26//! ```rust
27//! use acp::bridge::{BridgeConfig, FormatDetector, BridgeMerger};
28//! use acp::bridge::merger::AcpAnnotations;
29//! use acp::cache::SourceFormat;
30//!
31//! // Create an enabled configuration
32//! let config = BridgeConfig::enabled();
33//! let detector = FormatDetector::new(&config);
34//! let merger = BridgeMerger::new(&config);
35//!
36//! // Detect format from JavaScript content
37//! let content = "/** @param {string} name - User name */";
38//! let format = detector.detect(content, "javascript");
39//! assert_eq!(format, Some(SourceFormat::Jsdoc));
40//!
41//! // Merge with ACP annotations (native docs are optional)
42//! let acp = AcpAnnotations {
43//!     summary: Some("Greet a user".to_string()),
44//!     directive: Some("MUST validate name is non-empty".to_string()),
45//!     ..Default::default()
46//! };
47//! let result = merger.merge(None, SourceFormat::Jsdoc, &acp);
48//! assert_eq!(result.summary, Some("Greet a user".to_string()));
49//! ```
50
51pub mod config;
52pub mod detector;
53pub mod merger;
54
55pub use config::{BridgeConfig, JsDocConfig, ProvenanceConfig, PythonConfig, RustConfig};
56pub use detector::FormatDetector;
57pub use merger::BridgeMerger;
58
59use crate::annotate::converters::ParsedDocumentation;
60use crate::cache::{BridgeSource, ParamEntry, ReturnsEntry, SourceFormat, ThrowsEntry};
61
62/// @acp:summary "Result of bridging native documentation with ACP annotations"
63#[derive(Debug, Clone, Default)]
64pub struct BridgeResult {
65    /// Merged summary/description
66    pub summary: Option<String>,
67    /// AI behavioral directive for the function/method
68    pub directive: Option<String>,
69    /// Parameter entries with provenance
70    pub params: Vec<ParamEntry>,
71    /// Returns entry with provenance
72    pub returns: Option<ReturnsEntry>,
73    /// Throws entries with provenance
74    pub throws: Vec<ThrowsEntry>,
75    /// Examples extracted from documentation
76    pub examples: Vec<String>,
77    /// Overall source of the merged documentation
78    pub source: BridgeSource,
79    /// Source formats that contributed to this result
80    pub source_formats: Vec<SourceFormat>,
81}
82
83impl BridgeResult {
84    /// @acp:summary "Create a new empty bridge result"
85    pub fn new() -> Self {
86        Self::default()
87    }
88
89    /// @acp:summary "Create from pure ACP annotations (explicit source)"
90    pub fn from_acp(summary: Option<String>, directive: Option<String>) -> Self {
91        Self {
92            summary,
93            directive,
94            source: BridgeSource::Explicit,
95            source_formats: vec![SourceFormat::Acp],
96            ..Default::default()
97        }
98    }
99
100    /// @acp:summary "Create from converted native documentation"
101    pub fn from_native(parsed: &ParsedDocumentation, format: SourceFormat) -> Self {
102        let mut result = Self {
103            summary: parsed.summary.clone(),
104            source: BridgeSource::Converted,
105            source_formats: vec![format],
106            examples: parsed.examples.clone(),
107            ..Default::default()
108        };
109
110        // Convert params
111        for (name, type_str, desc) in &parsed.params {
112            result.params.push(ParamEntry {
113                name: name.clone(),
114                r#type: type_str.clone(),
115                type_source: type_source_from_format(format),
116                description: desc.clone(),
117                directive: None,
118                optional: false,
119                default: None,
120                source: BridgeSource::Converted,
121                source_format: Some(format),
122                source_formats: vec![],
123            });
124        }
125
126        // Convert returns
127        if let Some((type_str, desc)) = &parsed.returns {
128            result.returns = Some(ReturnsEntry {
129                r#type: type_str.clone(),
130                type_source: type_source_from_format(format),
131                description: desc.clone(),
132                directive: None,
133                source: BridgeSource::Converted,
134                source_format: Some(format),
135                source_formats: vec![],
136            });
137        }
138
139        // Convert throws
140        for (exc_type, desc) in &parsed.throws {
141            result.throws.push(ThrowsEntry {
142                exception: exc_type.clone(),
143                description: desc.clone(),
144                directive: None,
145                source: BridgeSource::Converted,
146                source_format: Some(format),
147            });
148        }
149
150        result
151    }
152}
153
154/// @acp:summary "Determine TypeSource from SourceFormat"
155fn type_source_from_format(format: SourceFormat) -> Option<crate::cache::TypeSource> {
156    use crate::cache::TypeSource;
157    match format {
158        SourceFormat::Jsdoc => Some(TypeSource::Jsdoc),
159        SourceFormat::DocstringGoogle
160        | SourceFormat::DocstringNumpy
161        | SourceFormat::DocstringSphinx => Some(TypeSource::Docstring),
162        SourceFormat::Rustdoc => Some(TypeSource::Rustdoc),
163        SourceFormat::Javadoc => Some(TypeSource::Javadoc),
164        SourceFormat::TypeHint => Some(TypeSource::TypeHint),
165        _ => None,
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    #[test]
174    fn test_bridge_result_from_acp() {
175        let result = BridgeResult::from_acp(
176            Some("Test summary".to_string()),
177            Some("MUST validate input".to_string()),
178        );
179        assert_eq!(result.summary, Some("Test summary".to_string()));
180        assert_eq!(result.directive, Some("MUST validate input".to_string()));
181        assert_eq!(result.source, BridgeSource::Explicit);
182        assert_eq!(result.source_formats, vec![SourceFormat::Acp]);
183    }
184
185    #[test]
186    fn test_bridge_result_from_native() {
187        let mut parsed = ParsedDocumentation::new();
188        parsed.summary = Some("Native summary".to_string());
189        parsed.params.push((
190            "userId".to_string(),
191            Some("string".to_string()),
192            Some("User ID".to_string()),
193        ));
194        parsed.returns = Some((
195            Some("User".to_string()),
196            Some("The user object".to_string()),
197        ));
198
199        let result = BridgeResult::from_native(&parsed, SourceFormat::Jsdoc);
200
201        assert_eq!(result.summary, Some("Native summary".to_string()));
202        assert_eq!(result.source, BridgeSource::Converted);
203        assert_eq!(result.params.len(), 1);
204        assert_eq!(result.params[0].name, "userId");
205        assert!(result.returns.is_some());
206    }
207}